Jump to content

Module:Sandbox/TheUnit 72/UCB

From Meta, a Wikimedia project coordination wiki
Module documentation

The Universal Clickable Button (UCB) module renders a styled, clickable button inline in wikitext. It supports internal wiki links, external URLs, named colors, raw hex color codes, icons, tooltips, custom CSS, and hover effects. It is implemented via Module:Sandbox/TheUnit_72/UCB with styles loaded from Module:Sandbox/TheUnit_72/UCB/styles.css.

Usage

[edit]

To use this template, transclude it with the following syntax:

{{Universal Clickable Button|<link or URL>|<label>|color=<color>}}

Or with fully named parameters:

{{Universal Clickable Button|link=Target page|text=Label|color=blue}}

Parameters

[edit]
Parameter Aliases Description Default
1 (first positional) link, url The link target. Accepts an internal wiki page title or an external URL beginning with https://, http://, or //. Required. If empty, the template renders nothing.
2 (second positional) text The visible label displayed on the button. (empty)
3 (third positional) color, colour The background color of the button. Accepts a named color (see color table below), a 6-digit hex code (#3366cc), or a 3-digit hex code (#36c). The hash symbol is optional. #f8f9fa (light grey)
color / colour Named alternative to the third positional parameter for the background color. #f8f9fa
icon Filename of a wiki file (with or without the File: prefix) to display as a 16px icon to the left of the label text. (none)
tooltip Text for the HTML title attribute, shown on mouse hover as a browser tooltip. (none)
class One or more additional CSS classes to apply to the button span. The class mw-ui-button is silently ignored. (none)
style Inline CSS applied directly to the button span, overriding default styles. (none)
hover Set to yes to render internal links using wikitext link syntax ([[...]]) instead of full URL syntax, enabling the wiki's standard hover and visited-link styling. Has no effect on external URLs. (off)

Colors

[edit]

The following named colors are recognized. Color names are case-insensitive and extra whitespace is ignored. The alias dark is treated as equivalent to deep (e.g., dark blue resolves to deep blue).

Name Hex value Name Hex value
red #d33 light red #ff7f7f
deep red #8b0000 orange #ff8c00
light orange #ffd1a3 yellow #ffeb3b
gold / deep yellow #d4af37 light yellow #fff59d
green #4caf50 deep green #1b5e20
turquoise #40e0d0 blue #3366cc
deep blue #0d47a1 light blue #90caf9
purple #7953a9 deep purple #4a148c
light purple #ce93d8 pink #e91e63
magenta #c2185b white #ffffff
black #000000 grey / gray #a2a9b1
silver #c8ccd1 light grey #f8f9fa
deep grey #54595d beige #f8eaba
deep beige #c0c090 progressive #36c
destructive #d32f2f constructive #008000
neutral #f8f9fa

Unrecognized color names are ignored and the default background is used. Raw hex values not in the table are accepted directly. As well, using the standard terms "Progressive," "Destructive," Constructive," or "Neutral" will redirect the color to the appropriate hue.

Text color

[edit]

Text color (black or white) is automatically selected for legibility based on the relative luminance of the background color using the formula 0.299R + 0.587G + 0.114B. Values above 186 produce black text; values at or below 186 produce white text.

Border color

[edit]

The border color matches the background color, with two exceptions: light grey (#f8f9fa) uses #a2a9b1, and beige (#f8eaba) uses #c0c090.

Behavior notes

[edit]
Self-links
If the link target resolves to the current page, the button is rendered without a hyperlink (as a plain styled span). This mirrors standard MediaWiki self-link behavior.
External URL validation
If url is specified but the value does not begin with https://, http://, or //, an inline error message is shown instead of the button.
Empty link
If the first positional parameter (or url/link) is absent or blank, the template outputs nothing.
Hover mode
When hover=yes is set and the target is an internal page, the link is rendered using standard wiki bracket syntax, enabling the default MediaWiki hover and visited-link CSS. This option is ignored for external URLs.
Icon rendering
Icons are rendered at 16px with no link on the image itself (link=), followed by a non-breaking space before the label text.

Examples

[edit]
[edit]
{{Universal Clickable Button|Main Page|Go to Main Page}}

Colored button

[edit]
{{Universal Clickable Button|Wikipedia:About|About Wikipedia|color=blue}}

External URL

[edit]
{{Universal Clickable Button|url=https://www.mediawiki.org|text=MediaWiki|color=green}}

Hex color

[edit]
{{Universal Clickable Button|Wikipedia:Sandbox|Try the sandbox|color=#e65100}}

With icon

[edit]
{{Universal Clickable Button|Wikipedia:Featured articles|Featured articles|color=gold|icon=Star_(feature).svg}}

With tooltip

[edit]
{{Universal Clickable Button|Help:Contents|Help|color=progressive|tooltip=Go to the help centre}}

With hover mode

[edit]
{{Universal Clickable Button|Wikipedia:Community portal|Community|color=purple|hover=yes}}

Destructive action style

[edit]
{{Universal Clickable Button|Wikipedia:Articles for deletion|AfD|color=destructive}}

TemplateData

[edit]

Renders a styled, clickable button linking to a wiki page or external URL.

Template parameters[Edit template data]

This template prefers inline formatting of parameters.

ParameterDescriptionTypeStatus
Link target1 link url

The internal wiki page title or external URL to link to. Required.

Stringrequired
Label2 text

The visible text displayed on the button.

Stringoptional
Color3 color colour

Background color. Accepts a named color, a 6-digit hex code, or a 3-digit hex code. Hash symbol is optional.

Stringoptional
Iconicon

Filename of a wiki file to show as a 16px icon before the label. The File: prefix is optional.

Fileoptional
Tooltiptooltip

Text for the HTML title attribute shown on hover.

Stringoptional
CSS classclass

Additional CSS class(es) to apply to the button element.

Stringoptional
Inline stylestyle

Inline CSS to apply directly to the button, overriding defaults.

Stringoptional
Hover modehover

Set to "yes" to use wikitext link syntax for internal links, enabling standard MediaWiki hover styling.

Suggested values
yes
Stringoptional

local p = {}

local colorMap = {
    red = "#d33",
    ["deep red"] = "#8b0000",
    ["light red"] = "#ff7f7f",

    orange = "#ff8c00",
    ["light orange"] = "#ffd1a3",

    yellow = "#ffeb3b",
    gold = "#d4af37",
    ["deep yellow"] = "#d4af37",
    ["light yellow"] = "#fff59d",

    green = "#4caf50",
    ["deep green"] = "#1b5e20",

    turquoise = "#40e0d0",
    turqoise = "#40e0d0",

    blue = "#3366cc",
    ["deep blue"] = "#0d47a1",
    ["light blue"] = "#90caf9",

    purple = "#7953a9",
    ["deep purple"] = "#4a148c",
    ["light purple"] = "#ce93d8",

    pink = "#e91e63",
    magenta = "#c2185b",

    white = "#ffffff",
    black = "#000000",

    grey = "#a2a9b1",
    gray = "#a2a9b1",
    silver = "#c8ccd1",
    ["light grey"] = "#f8f9fa",
    ["deep grey"] = "#54595d",

    beige = "#f8eaba",
    ["deep beige"] = "#c0c090",

	["progressive"] = "#36c",
    ["destructive"] = "#bf3c2c",
    ["constructive"] = "#008000",
    ["neutral"] = "#f8f9fa"
}

local DEFAULT_BG = "#f8f9fa"
local DEFAULT_FG = "#000"
local DEFAULT_BORDER = "#a2a9b1"

local function normalizeKey(str)
    if not str then return nil end
    str = tostring(str):lower()
        :gsub("^%s+", "")
        :gsub("%s+$", "")
        :gsub("%s+", " ")
    str = str:gsub("^dark%s+", "deep ")
    return str
end

local function resolveColor(input)
    if not input then return nil end
    local key = normalizeKey(input)
    if colorMap[key] then return colorMap[key] end
    if key:match("^#%x%x%x$") or key:match("^#%x%x%x%x%x%x$") then return key end
    if key:match("^%x%x%x$") or key:match("^%x%x%x%x%x%x$") then return "#" .. key end
    return nil
end

local function expandHex(hex)
    hex = hex:gsub("#", "")
    if #hex == 3 then
        return hex:sub(1,1) .. hex:sub(1,1)
            .. hex:sub(2,2) .. hex:sub(2,2)
            .. hex:sub(3,3) .. hex:sub(3,3)
    elseif #hex == 6 then
        return hex
    end
    return "808080"
end

local function getTextColor(bg)
    local hex = expandHex(bg)
    local r = tonumber(hex:sub(1,2), 16)
    local g = tonumber(hex:sub(3,4), 16)
    local b = tonumber(hex:sub(5,6), 16)
    local lum = (0.299 * r + 0.587 * g + 0.114 * b)
    return (lum > 186) and "#000" or "#fff"
end

local function pickBaseColor(args)
    local raw = args.color or args.colour or args[3]
    local resolved = resolveColor(raw)
    if resolved then
        local border
        if resolved == "#f8f9fa" then
            border = "#a2a9b1"
        elseif resolved == "#f8eaba" then
            border = "#c0c090"
        else
            border = resolved
        end
        return resolved, getTextColor(resolved), border
    end
    return DEFAULT_BG, DEFAULT_FG, DEFAULT_BORDER
end

local function isValidUrl(url)
    if not url or url == "" then return false end
    return url:match("^https?://") ~= nil
        or url:match("^//") ~= nil
end

local function adjustColor(hex, factor)
    local h = expandHex(hex)
    local r = tonumber(h:sub(1,2), 16)
    local g = tonumber(h:sub(3,4), 16)
    local b = tonumber(h:sub(5,6), 16)
    r = math.min(255, math.max(0, math.floor(r * factor)))
    g = math.min(255, math.max(0, math.floor(g * factor)))
    b = math.min(255, math.max(0, math.floor(b * factor)))
    return string.format("#%02x%02x%02x", r, g, b)
end

local function getHoverColor(bg)
    local hex = expandHex(bg)
    local r = tonumber(hex:sub(1,2), 16)
    local g = tonumber(hex:sub(3,4), 16)
    local b = tonumber(hex:sub(5,6), 16)
    local lum = (0.299 * r + 0.587 * g + 0.114 * b)

    if bg == "#000000" or bg == "#000" then
        return adjustColor(bg, 1.15)
    elseif lum >= 220 and lum < 255 then
        -- Near-white: lighten slightly
        return adjustColor(bg, 1.08)
    else
        return adjustColor(bg, 0.82)
    end
end

local function buildDisplay(args, bg, fg, border)
    local span = mw.html.create("span")
    span
        :addClass("ucb-button")
        :css({
            ["background-color"] = bg,
            ["color"]            = fg,
            ["border-color"]     = border,
            ["vertical-align"]   = "middle"
        })
   
    local userClass = args.class and tostring(args.class):lower():gsub("^%s+", ""):gsub("%s+$", "") or nil
    if userClass and userClass ~= "" and userClass ~= "mw-ui-button" then
        span:addClass(userClass)
    end

    if args.tooltip and tostring(args.tooltip) ~= "" then
        span:attr("title", tostring(args.tooltip))
    end

    if args.style then
        span:cssText(args.style)
    end

    local text = args.text or args[2] or args[1] or ""

    if args.icon and tostring(args.icon) ~= "" then
        local filename = tostring(args.icon):gsub("^[Ff]ile:", "")
        span:wikitext(string.format('[[File:%s|16px|link=]]&nbsp; ', filename))
    end

    span:wikitext(text)
    return tostring(span)
end

function p.main(frame)
    local args = frame:getParent().args

    local styleLink = frame:extensionTag("templatestyles", "", {
        src = "Module:Sandbox/TheUnit_72/UCB/styles.css"
    })

    local link = args.url or args[1] or args.link
    if not link or tostring(link) == "" then
        return ""
    end

    local isUrl = args.url ~= nil and tostring(args.url) ~= ""

    if isUrl and not isValidUrl(tostring(args.url)) then
        return '<span class="error">Universal Clickable Button: invalid or unsafe URL.</span>'
    end

    local bg, fg, border = pickBaseColor(args)

    local useHover = not isUrl
        and args.hover and tostring(args.hover):lower():gsub("^%s+", ""):gsub("%s+$", "") == "yes"

    if not isUrl then
        local currentTitle = mw.title.getCurrentTitle()
        local targetTitle = mw.title.new(tostring(link))
        if targetTitle and currentTitle
            and targetTitle.prefixedText == currentTitle.prefixedText then
            return styleLink .. buildDisplay(args, bg, fg, border)
        end
    end

    local display = buildDisplay(args, bg, fg, border)
    local linkHtml
    if useHover then
        linkHtml = string.format('[[%s|%s]]', tostring(link), display)
        return styleLink .. string.format('<span class="plainlinks">%s</span>', linkHtml)
    end

    local href
    if isUrl then
        href = tostring(args.url)
    else
        local titleObj = mw.title.new(tostring(link))
        href = titleObj and titleObj:fullUrl() or tostring(link)
    end

    linkHtml = string.format('[%s %s]', href, display)
    return styleLink .. string.format('<span class="plainlinks">%s</span>', linkHtml)
end

return p