Module:UtilsLayout/Tabs

local p = {} local h = {}

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" if #data == 1 and tabOptions.collapse then return data[1].content end local tabContainer = h.tabContainer(data, defaultTab, align, tabOptions, labelOptions) local tabContents = h.tabContents(data, defaultTab, align, contentOptions) local html = mw.html.create("div") 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) if stretch then tabContainer:addClass("tabcontainer--stretch") end if columns then tabContainer:addClass("tabcontainer--columns") end for i, tabData in ipairs(data) do		local tab = mw.html.create("span") :addClass("tab") :addClass("explain") :addClass("tab--label-align-y-"..labelAlignVertical) :attr("title", tabData.tooltip) :wikitext(tabData.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("content") :wikitext("\n", tabData.content) if i == defaultTab then content:addClass("content--active") end tabContents:node(content) end return tabContents end

function p.Schemas return { tabs = { data = { type = "array", required = true, items = { type = "record", properties = { {							name = "label", type = "string", required = true, desc = "Button text for the tab." },						{							name = "tooltip", type = "string", desc = "Tooltip for the tab button", },						{							name = "content", type = "string", required = true, desc = "Content for the tab.", },					}				}			},			options = { type = "record", properties = { {						name = "default", type = "number", default = 1, desc = "Index of default tab.", },					{						name = "align", type = "string", enum = {"left", "center", "right"}, default = mw.dumpObject("left"), desc = "Horizontal alignment for tabs and their content.", },					{						name = "tabOptions", type = "record", desc = "Display options for the tabs themselves.", properties = { {								name = "position", type = "string", enum = {"top", "bottom"}, default = mw.dumpObject("top"), desc = "If  (default), the tabs are placed above their content. If , then the opposite." },							{								name = "collapse", type = "boolean", desc = "If truthy, tabs will not be rendered if there is only one tab. The content of that tab will simply be rendered instead." },							{								name = "stretch", type = "boolean", desc = "If true, then tabs will stretch to fill the available space in their container.", },							{								name = "columns", type = "number", desc = "If specified, the tabs will attempt to arrange themselves in N columns of equal width. This option is not compatible with ." },						},					},					{						name = "labelOptions", type = "record", desc = "Display options for the tab labels.", properties = { {								name = "alignVertical", type = "string", enum = {"center", "bottom"}, default = mw.dumpObject("center"), desc = "Vertical alignment of the label with respect to its tab. Options are:  (default) and  .", },						},					},					{						name = "contentOptions", type = "record", desc = "Display options for the tab contents.", properties = { {								name = "fixedContentWidth", oneOf = { { type = "boolean" }, { type = "number" }, },								desc = "Width for the tab container in . Or, if set to , the tab container will  assume the width of the largest tab. By default, the tab container assumes the width of the current tab." },							{								name = "fixedContentHeight", oneOf = { { type = "boolean" }, { type = "number" }, },								desc = "Height for the tab container in . Or, if set to , the tab container will  assume the height of the largest tab. By default, the tab container assumes the height of the current tab.", },							{								name = "alignVertical", type = "string", enum = {"top", "center", "bottom"}, default = mw.dumpObject("top"), desc = "Vertical alignment of tab contents with respect to the tab container. Useful only alonside ." },						},					},				},			},		}	} end

function p.Documentation return { tabs = { params = {"data", "options"}, returns = "HTML markup rendering tabs.", cases = { resultOnly = true, {					args = { {							{								label = "Tab1", content = "Content1", },							{								label = "Tab2", content = "Content2" },						},					}				},				{					args = { {							{								label = "Tab1", content = "Content1", },							{								label = "Tab2", content = "Content2" },						},						{							tabOptions = { stretch = true, }						},					},				},				{					args = { {							{								label = "Tab1", tooltip = "This is the first tab.", content = "Content1" },							{								label = "Tab2", tooltip = "This is the second tab.", content = "Content2" },							{								label = "Tab3", tooltip = "This is the third tab.", content = "Content3" }						},						{							default = 2, align = "center", tabOptions = { position = "bottom", },						}					}				},				{					desc = "Tabs display even for a single tab unless  is set to true.", args = { }				},				{					args = { ,						{ 							tabOptions = { collapse = true }						},					}				},				{					desc = " and   determine how the overall tab container is sized.", args = { {							{ label = "Small Tab", content = "meep" }, { label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep" }, { label = "Tall tab", content = "m\ne\ne\ne\ne\np" }, },					},				},				{					args = { {							{ label = "Small Tab", content = "meep" }, { label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeep" }, { label = "Tall tab", content = "m\ne\ne\ne\ne\np" }, },						{							contentOptions = { fixedWidth = true, },						},	 				},				},				{					args = { {							{ label = "Small Tab", content = "meep" }, { label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep" }, { label = "Tall tab", content = "m\ne\ne\ne\ne\np" }, },						{							contentOptions = { fixedHeight = true, },						},	 				},				},				{					args = { {							{ label = "Small Tab", content = "meep" }, { label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeep" }, { label = "Tall tab", content = "m\ne\ne\ne\ne\np" }, },						{ 							contentOptions = { fixedWidth = true, fixedHeight = true, alignVertical = "center", },						},	 				},				},				{					args = { {							{ label = "Small Tab", content = "meep" }, { label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeep" }, { label = "Tall tab", content = "m\ne\ne\ne\ne\np" }, },						{ 							contentOptions = { fixedWidth = 80, fixedHeight = 80, alignVertical = "center", },						},	 				},				},				{					desc = "When images are used as tab labels, the label alignment options can be useful.", args = { {							{ label = "", content = "Engines" }, { label = "", content = "Passenger Cars" }, },						{							labelOptions = { alignVertical = "bottom", },						},					},				},			},		},	} end

return p