Module:TabberUtils

Documentation for this module may be created at Module:TabberUtils/doc

-- Adapted from ZeldaWiki: https://zeldawiki.wiki/w/index.php?title=Module:UtilsLayout/Tabs
-- Edited by Vish
local p = {}
local h = {}

-- Function to parse arguments
function p.parse(frameArgs)
    local args = {
        tabs = {},
        align = "left",
        default = 1,
        columns = nil,
        position = "top",
        distribution = nil,
        tabAlign = "center",
        contentAlign = "left",
        class = nil,
    }

    -- Parse named parameters
    for k, v in pairs(frameArgs) do
        if k == "align" then
            args.align = v
        elseif k == "default" then
            args.default = tonumber(v) or 1
        elseif k == "columns" then
            args.columns = tonumber(v)
        elseif k == "position" then
            args.position = v
        elseif k == "distribution" then
            args.distribution = v
        elseif k == "tabAlign" then
            args.tabAlign = v
        elseif k == "contentAlign" then
            args.contentAlign = v
        elseif k == "class" then
        	args.class = v
        end
    end

    -- Parse tab names and contents
    local i = 1
    while frameArgs[i] or frameArgs["Tab" .. i] do
        local key = frameArgs[i] or frameArgs["Tab" .. i]
        local value = frameArgs[i + 1] or frameArgs["Content" .. i]
        
        if key and key ~= "" then
            table.insert(args.tabs, {
                tab = key,
                content = value
            })
        end
        i = i + (frameArgs[i] and 2 or 1)
    end

    return args
end

function p.tabs(data, options)
    local options = options or {}
    local tabOptions = options.tabOptions or {}
    local labelOptions = options.labelOptions or {}
    local contentOptions = options.contentOptions or {}

    local defaultTab = options.default or 1
    local align = options.align or "left"
    local tabAlign = options.tabAlign or "center"
    local contentAlign = options.contentAlign or "left"

    if #data == 1 and tabOptions.collapse then
        return data[1].content
    end

    local tabContainer = h.tabContainer(data, defaultTab, tabAlign, tabOptions, labelOptions)
    local tabContents = h.tabContents(data, defaultTab, contentAlign, contentOptions)

    local html = mw.html.create("div")
        :addClass("jw-tabs")
        :addClass(tabOptions.stretch and "jw-tabs--stretch" or nil)
        :addClass(tabOptions.columns and "jw-tabs--columns" or nil)
        :addClass(options.class)
    if tabOptions.position == "bottom" then
        html:node(tabContents)
            :node(tabContainer)
    else
        html:node(tabContainer)
            :node(tabContents)
    end
    return tostring(html)
end

function h.tabContainer(data, defaultTab, align, tabOptions, labelOptions)
    local position = tabOptions.position or "top"
    local stretch = tabOptions.stretch
    local columns = tabOptions.columns
    local labelAlignVertical = labelOptions.alignVertical or "center"

    local tabContainer = mw.html.create("div")
        :addClass("tabcontainer tabcontainer-"..position)
        :addClass("tabcontainer--align-x-"..align)
        :attr("role", "tablist")

    for i, tabData in ipairs(data) do
        local label = tabData.label
        local sanitizedLabel = mw.uri.anchorEncode(label)

        local tab = mw.html.create("span")
            :addClass("tab")
            :addClass("tab--label-align-y-"..labelAlignVertical)
            :attr("role", "tab")
            :attr("tabindex", "0")
            :attr("data-tab-hash", sanitizedLabel)
            :wikitext(label)
        if i == defaultTab then
            tab:addClass("active")
        end
        if columns then
            tab:css({
                ["max-width"] = "calc(100%/"..columns.." - 2*"..(columns-1).."px)" -- the subtraction is to account for tab margins
            })
        end
        tabContainer:node(tab)
    end
    return tabContainer
end

function h.tabContents(data, defaultTab, align, contentOptions)
    local alignVertical = contentOptions.alignVertical or "top"
    local fixedWidth = contentOptions.fixedWidth
    local fixedHeight = contentOptions.fixedHeight

    local tabContents = mw.html.create("div")
        :addClass("tabcontents")
        :addClass("tabcontents--align-x-"..align)
        :addClass("tabcontents--align-y-"..alignVertical)

    if fixedWidth then
        tabContents:addClass("tabcontents--fixed-width")
        if type(fixedWidth) == "number" then
            tabContents:css("width", fixedWidth .. "px")
        end
    end
    if fixedHeight then
        tabContents:addClass("tabcontents--fixed-height")
        if type(fixedHeight) == "number" then
            tabContents:css("height", fixedHeight .. "px")
        end
    end

    for i, tabData in ipairs(data) do
        local content = mw.html.create("div")
            :addClass("jw-tabs-content")
            :attr("role", "tabpanel")
            :wikitext("\n", tabData.content or "")
        if i == defaultTab then
            content:addClass("jw-tabs-content--active")
        end
        tabContents:node(content)
    end
    return tabContents
end

return p