Module:UtilsLayout/Tabs: Difference between revisions
Jump to navigation
Jump to search
Add ARIA roles so that tabs can at least be manipulated via keyboard when using screen readers. MediaWiki limitations prevent me from adding proper accessibility features, unfortunately.
PhantomCaleb (talk | contribs) (refining API) |
PhantomCaleb (talk | contribs) (Add ARIA roles so that tabs can at least be manipulated via keyboard when using screen readers. MediaWiki limitations prevent me from adding proper accessibility features, unfortunately.) |
||
(9 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local h = {} | local h = {} | ||
local CLASS_TOOLTIP = require("Module:Constants/class/tooltip") | |||
function p.tabs(data, options) | function p.tabs(data, options) | ||
Line 19: | Line 21: | ||
local html = mw.html.create("div") | local html = mw.html.create("div") | ||
if tabOptions.position == " | :addClass("zw-tabs") | ||
:addClass(tabOptions.stretch and "zw-tabs--stretch" or nil) | |||
:addClass(tabOptions.columns and "zw-tabs--columns" or nil) | |||
if tabOptions.position == "bottom" then | |||
html:node(tabContents) | |||
:node(tabContainer) | |||
else | |||
html:node(tabContainer) | html:node(tabContainer) | ||
:node(tabContents) | :node(tabContents) | ||
end | end | ||
return tostring(html) | return tostring(html) | ||
Line 38: | Line 43: | ||
:addClass("tabcontainer tabcontainer-"..position) | :addClass("tabcontainer tabcontainer-"..position) | ||
:addClass("tabcontainer--align-x-"..align) | :addClass("tabcontainer--align-x-"..align) | ||
:attr("role", "tablist") | |||
for i, tabData in ipairs(data) do | for i, tabData in ipairs(data) do | ||
local tab = mw.html.create("span") | local tab = mw.html.create("span") | ||
:addClass("tab") | :addClass("tab") | ||
:addClass( | :addClass(CLASS_TOOLTIP) | ||
:addClass("tab--label-align-y-"..labelAlignVertical) | :addClass("tab--label-align-y-"..labelAlignVertical) | ||
:attr("title", tabData.tooltip) | :attr("title", tabData.tooltip) | ||
:attr("role", "tab") | |||
:attr("tabindex", "0") | |||
:wikitext(tabData.label) | :wikitext(tabData.label) | ||
if i == defaultTab then | if i == defaultTab then | ||
Line 92: | Line 93: | ||
local content = mw.html.create("div") | local content = mw.html.create("div") | ||
:addClass("content") | :addClass("content") | ||
:wikitext(tabData.content) | :attr("role", "tabpanel") | ||
:wikitext("\n", tabData.content) | |||
if i == defaultTab then | if i == defaultTab then | ||
content:addClass("content--active") | content:addClass("content--active") | ||
Line 101: | Line 103: | ||
end | end | ||
p.Schemas | 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", | type = "record", | ||
properties = { | properties = { | ||
{ | { | ||
name = " | name = "default", | ||
type = "number", | |||
default = 1, | |||
desc = "Index of default tab.", | |||
}, | |||
{ | |||
name = "align", | |||
type = "string", | type = "string", | ||
enum = {"left", "center", "right"}, | |||
desc = " | default = mw.dumpObject("left"), | ||
desc = "Horizontal alignment for tabs and their content.", | |||
}, | }, | ||
{ | { | ||
name = " | name = "tabOptions", | ||
type = "string", | type = "record", | ||
desc = " | 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 = " | name = "contentOptions", | ||
type = " | type = "record", | ||
desc = "Display options for the tab contents.", | |||
properties = { | |||
{ | |||
name = "fixedWidth", | |||
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 = "fixedHeight", | |||
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>." | |||
}, | |||
}, | |||
}, | }, | ||
}, | }, | ||
}, | |||
} | |||
} | |||
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", | |||
desc = " | 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>fixedtWidth</code> and <code>fixedHeight</code> determine how the overall tab container is sized.", | |||
args = { | |||
desc = " | |||
{ | { | ||
{ 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 = " | { label = "Small Tab", content = "meep" }, | ||
content = " | { label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeep" }, | ||
{ label = "Tall tab", content = "m\ne\ne\ne\ne\np" }, | |||
}, | }, | ||
{ | { | ||
contentOptions = { | |||
fixedHeight = true, | |||
}, | |||
}, | }, | ||
}, | |||
}, | |||
{ | |||
args = { | |||
{ | { | ||
label = " | { label = "Small Tab", content = "meep" }, | ||
content = " | { label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeep" }, | ||
{ label = "Tall tab", content = "m\ne\ne\ne\ne\np" }, | |||
}, | }, | ||
{ | { | ||
contentOptions = { | |||
fixedWidth = true, | |||
fixedHeight = true, | |||
alignVertical = "center", | |||
}, | |||
}, | }, | ||
}, | |||
}, | }, | ||
{ | |||
args = { | |||
{ | { | ||
label = " | { label = "Small Tab", content = "meep" }, | ||
{ label = "Wide Tab", content = "meeeeeeeeeeeeeeeeeeeeeeeeeeep" }, | |||
content = " | { 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 = " | { label = "[[File:ST Engine Icon.png|link=]]", content = "Engines" }, | ||
{ label = "[[File:ST Passenger Car Icon.png|link=]]", content = "Passenger Cars" }, | |||
}, | }, | ||
{ | { | ||
labelOptions = { | |||
alignVertical = "bottom", | |||
}, | |||
}, | }, | ||
}, | }, | ||
Line 391: | Line 397: | ||
}, | }, | ||
}, | }, | ||
} | } | ||
end | |||
return p | return p |