Module:UtilsLayout/Tabs

From Zelda Wiki, the Zelda encyclopedia
Revision as of 18:41, 23 March 2022 by PhantomCaleb (talk | contribs) (Bugfix: Added newline before tabcontent so that wikitables work inside tab contents)
Jump to navigation Jump to search

Lua error in Module:Documentation/Module at line 351: attempt to call field 'Documentation' (a table value).


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

p.Schemas = {
	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 <code>top</code> (default), the tabs are placed above their content. If <code>bottom</code>, 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 <code>stretch</code>."
						},
					},
				},
				{
					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: <code>center</code> (default) and <code>bottom</code>.",
						},
					},
				},
				{
					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 <code>px</code>. Or, if set to <code>true</code>, 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 <code>px</code>. Or, if set to <code>true</code>, 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 <code>fixedHeight</code>."
						},
					},
				},
			},
		},
	}
}

p.Documentation = {
	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 <code>collapse</code> is set to true.",
				args = {
					{{ label = "Single Tab", content = "Content" }}
				}
			},
			{
				args = {
					{{ label = "Single Tab", content = "Content" }},
					{ 
						tabOptions = {
							collapse = true
						}
					},
				}
			},
			{
				desc = "<code>fixedContentWidth</code> and <code>fixedContentHeight</code> 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 = "[[File:ST Engine Icon.png|link=]]", content = "Engines" },
						{ label = "[[File:ST Passenger Car Icon.png|link=]]", content = "Passenger Cars" },
					},
					{
						labelOptions = {
							alignVertical = "bottom",
						},
					},
				},
			},
		},
	},
}

return p