@statistikzh/leu 0.27.0 → 0.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +1 -3
- package/.release-please-manifest.json +1 -1
- package/.storybook/main.ts +1 -1
- package/.storybook/preview.ts +6 -4
- package/.storybook/static/global.css +5 -0
- package/AGENTS.md +162 -0
- package/CHANGELOG.md +30 -0
- package/dist/{Accordion-DLsqXcK8.js → Accordion-CwkI7sfx.js} +1 -1
- package/dist/Accordion.d.ts +1 -1
- package/dist/Accordion.js +2 -2
- package/dist/{Button-BSyDL_cV.js → Button-D1aYnunQ.js} +90 -82
- package/dist/{Button-BgNUxmo_.d.ts → Button-DcuvEVkC.d.ts} +14 -12
- package/dist/Button.d.ts +1 -1
- package/dist/Button.js +4 -4
- package/dist/{ButtonGroup-BmSvl-Oc.js → ButtonGroup-CqThYjzX.js} +2 -2
- package/dist/ButtonGroup.d.ts +1 -1
- package/dist/ButtonGroup.js +5 -5
- package/dist/{ChartWrapper-CvDvQsd5.js → ChartWrapper-BjVT4x-H.js} +2 -2
- package/dist/ChartWrapper.d.ts +3 -3
- package/dist/ChartWrapper.js +3 -3
- package/dist/{Checkbox-Cl_X6gBJ.js → Checkbox-HxYqp2w4.js} +2 -2
- package/dist/Checkbox.d.ts +3 -3
- package/dist/Checkbox.js +3 -3
- package/dist/{CheckboxGroup-BKhOmZYX.js → CheckboxGroup-DYws2rwt.js} +2 -2
- package/dist/CheckboxGroup.d.ts +1 -1
- package/dist/CheckboxGroup.js +4 -4
- package/dist/{Chip-DLKM9P7v.d.ts → Chip-DVGjEPJE.d.ts} +1 -1
- package/dist/{Chip-McVP3N_x.js → Chip-gKxD6IaZ.js} +1 -1
- package/dist/Chip.d.ts +1 -1
- package/dist/Chip.js +2 -2
- package/dist/{ChipGroup-Ta8Ht4jc.d.ts → ChipGroup-BK5BYM0X.d.ts} +2 -2
- package/dist/{ChipGroup-DUGavZeU.js → ChipGroup-ZvBzX_wd.js} +1 -1
- package/dist/ChipGroup.d.ts +1 -1
- package/dist/ChipGroup.js +3 -3
- package/dist/{ChipLink-BAxyQO2M.d.ts → ChipLink-BdG2o-nk.d.ts} +1 -1
- package/dist/ChipLink.d.ts +1 -1
- package/dist/ChipLink.js +2 -2
- package/dist/{ChipRemovable-DBjwt0CH.d.ts → ChipRemovable-CCwSQTAL.d.ts} +2 -2
- package/dist/ChipRemovable.d.ts +1 -1
- package/dist/ChipRemovable.js +3 -3
- package/dist/{ChipSelectable-CMJNcE4U.d.ts → ChipSelectable-BQ3VLVi5.d.ts} +1 -1
- package/dist/ChipSelectable.d.ts +1 -1
- package/dist/ChipSelectable.js +2 -2
- package/dist/{Dialog-BlDd4T2u.js → Dialog-BYpzTprV.js} +2 -2
- package/dist/Dialog.d.ts +3 -3
- package/dist/Dialog.js +3 -3
- package/dist/{Dropdown-BLxSIe6p.js → Dropdown-C4CgE4E-.js} +5 -5
- package/dist/Dropdown.d.ts +6 -6
- package/dist/Dropdown.js +8 -8
- package/dist/{FileInput-DntYrpZ-.js → FileInput-nsnSQCaU.js} +8 -6
- package/dist/FileInput.d.ts +6 -6
- package/dist/FileInput.js +6 -6
- package/dist/{Icon-Op80LrrO.d.ts → Icon-CUfh3eb3.d.ts} +1 -1
- package/dist/{Icon-CbZXpyHU.js → Icon-D8HTPEFH.js} +1 -1
- package/dist/Icon.d.ts +1 -1
- package/dist/Icon.js +2 -2
- package/dist/{Input-DBXX7ev8.js → Input-OrILqlax.js} +2 -2
- package/dist/{Input-CeaAOB4p.d.ts → Input-fEiQvGDF.d.ts} +3 -3
- package/dist/Input.d.ts +1 -1
- package/dist/Input.js +3 -3
- package/dist/{LeuElement-k4RjIeoG.js → LeuElement-C_lcHzQI.js} +1 -1
- package/dist/{Menu-Cu8eIF1T.js → Menu-CFLCnI34.js} +2 -2
- package/dist/{Menu-CQdx1ef3.d.ts → Menu-Z2b7dsB5.d.ts} +2 -2
- package/dist/Menu.d.ts +1 -1
- package/dist/Menu.js +4 -4
- package/dist/{MenuItem-Cs3KFhJm.js → MenuItem-ICjLCGim.js} +2 -2
- package/dist/{MenuItem-QcgnRk_7.d.ts → MenuItem-LY4TRIho.d.ts} +2 -2
- package/dist/MenuItem.d.ts +1 -1
- package/dist/MenuItem.js +3 -3
- package/dist/{Message-C6Zlk_2p.js → Message-Dw5D_0i1.js} +2 -2
- package/dist/Message.d.ts +2 -2
- package/dist/Message.js +3 -3
- package/dist/{Pagination-CqkHh-Vd.d.ts → Pagination-9eZ8WMvR.d.ts} +4 -4
- package/dist/{Pagination-CB2eVlXk.js → Pagination-D1tP5FrM.js} +4 -4
- package/dist/Pagination.d.ts +1 -1
- package/dist/Pagination.js +6 -6
- package/dist/{Placeholder-DHMexMhK.js → Placeholder-CnGzCZ5-.js} +1 -1
- package/dist/Placeholder.d.ts +1 -1
- package/dist/Placeholder.js +2 -2
- package/dist/{Popup-Btgm2a3D.d.ts → Popup-B5iDSLIO.d.ts} +1 -1
- package/dist/{Popup-8jhVy8gB.js → Popup-BiN_tZDN.js} +1 -1
- package/dist/Popup.d.ts +1 -1
- package/dist/Popup.js +2 -2
- package/dist/{ProgressBar-CG0_lHfS.js → ProgressBar-BfJo_KyU.js} +1 -1
- package/dist/ProgressBar.d.ts +1 -1
- package/dist/ProgressBar.js +2 -2
- package/dist/{Radio-DG3xqP3s.js → Radio-CV7vuQUj.js} +1 -1
- package/dist/Radio.d.ts +1 -1
- package/dist/Radio.js +2 -2
- package/dist/{RadioGroup-BKCp9ICX.js → RadioGroup-C3XWSScc.js} +2 -2
- package/dist/RadioGroup.d.ts +1 -1
- package/dist/RadioGroup.js +3 -3
- package/dist/{Range-7LrESv4K.js → Range-C8RVrIM9.js} +1 -1
- package/dist/Range.d.ts +1 -1
- package/dist/Range.js +2 -2
- package/dist/{ScrollTop-CJJsfniA.js → ScrollTop-B_TJ_k4m.js} +3 -3
- package/dist/ScrollTop.d.ts +3 -3
- package/dist/ScrollTop.js +5 -5
- package/dist/{Select-CxEDXIBn.js → Select-CbPTrL3G.js} +7 -8
- package/dist/Select.d.ts +9 -9
- package/dist/Select.js +9 -9
- package/dist/{Spinner-VhKfzI3Q.js → Spinner-ChKJQJTN.js} +1 -1
- package/dist/{Spinner-CrM1enM0.d.ts → Spinner-DHYaX6-Y.d.ts} +1 -1
- package/dist/Spinner.d.ts +1 -1
- package/dist/Spinner.js +2 -2
- package/dist/Tab-BJbzY1xd.js +117 -0
- package/dist/Tab-CN97q0nj.d.ts +30 -0
- package/dist/Tab.d.ts +2 -0
- package/dist/Tab.js +3 -0
- package/dist/TabGroup-BIaCHrKR.js +248 -0
- package/dist/TabGroup.d.ts +64 -0
- package/dist/TabGroup.js +5 -0
- package/dist/TabPanel-CTyw410b.js +65 -0
- package/dist/TabPanel-DQgWP7LO.d.ts +26 -0
- package/dist/TabPanel.d.ts +2 -0
- package/dist/TabPanel.js +3 -0
- package/dist/{Table-rg_JCtsA.js → Table-D3QmePJd.js} +3 -3
- package/dist/Table.d.ts +3 -3
- package/dist/Table.js +7 -7
- package/dist/{Tag-BROUaDAZ.js → Tag-nUnWtHYy.js} +1 -1
- package/dist/Tag.d.ts +1 -1
- package/dist/Tag.js +2 -2
- package/dist/{VisuallyHidden-OeQvhxYn.d.ts → VisuallyHidden-CB7aRJzF.d.ts} +1 -1
- package/dist/{VisuallyHidden-Co_txzxB.js → VisuallyHidden-DF2q9pTa.js} +1 -1
- package/dist/VisuallyHidden.d.ts +1 -1
- package/dist/VisuallyHidden.js +2 -2
- package/dist/index.d.ts +14 -14
- package/dist/index.js +30 -30
- package/dist/leu-accordion.js +2 -2
- package/dist/leu-button-group.js +5 -5
- package/dist/leu-button.d.ts +1 -1
- package/dist/leu-button.js +4 -4
- package/dist/leu-chart-wrapper.js +3 -3
- package/dist/leu-checkbox-group.js +4 -4
- package/dist/leu-checkbox.js +3 -3
- package/dist/leu-chip-group.d.ts +1 -1
- package/dist/leu-chip-group.js +3 -3
- package/dist/leu-chip-link.d.ts +1 -1
- package/dist/leu-chip-link.js +2 -2
- package/dist/leu-chip-removable.d.ts +1 -1
- package/dist/leu-chip-removable.js +3 -3
- package/dist/leu-chip-selectable.d.ts +1 -1
- package/dist/leu-chip-selectable.js +2 -2
- package/dist/leu-dialog.js +3 -3
- package/dist/leu-dropdown.js +8 -8
- package/dist/leu-file-input.js +6 -6
- package/dist/leu-icon.d.ts +1 -1
- package/dist/leu-icon.js +2 -2
- package/dist/leu-input.d.ts +1 -1
- package/dist/leu-input.js +3 -3
- package/dist/leu-menu-item.d.ts +1 -1
- package/dist/leu-menu-item.js +3 -3
- package/dist/leu-menu.d.ts +1 -1
- package/dist/leu-menu.js +4 -4
- package/dist/leu-message.js +3 -3
- package/dist/leu-pagination.d.ts +1 -1
- package/dist/leu-pagination.js +6 -6
- package/dist/leu-placeholder.js +2 -2
- package/dist/leu-popup.d.ts +1 -1
- package/dist/leu-popup.js +2 -2
- package/dist/leu-progress-bar.js +2 -2
- package/dist/leu-radio-group.js +3 -3
- package/dist/leu-radio.js +2 -2
- package/dist/leu-range.js +2 -2
- package/dist/leu-scroll-top.js +5 -5
- package/dist/leu-select.js +9 -9
- package/dist/leu-spinner.d.ts +1 -1
- package/dist/leu-spinner.js +2 -2
- package/dist/leu-tab-group.d.ts +10 -0
- package/dist/leu-tab-group.js +8 -0
- package/dist/leu-tab-panel.d.ts +10 -0
- package/dist/leu-tab-panel.js +6 -0
- package/dist/leu-tab.d.ts +10 -0
- package/dist/leu-tab.js +6 -0
- package/dist/leu-table.js +7 -7
- package/dist/leu-tag.js +2 -2
- package/dist/leu-visually-hidden.d.ts +1 -1
- package/dist/leu-visually-hidden.js +2 -2
- package/dist/vscode.html-custom-data.json +87 -19
- package/dist/vue/index.d.ts +93 -25
- package/dist/web-types.json +169 -44
- package/package.json +6 -5
- package/src/components/button/Button.ts +45 -30
- package/src/components/button/button.css +55 -54
- package/src/components/button/stories/button.stories.ts +17 -20
- package/src/components/button/test/button.test.ts +46 -0
- package/src/components/file-input/FileInput.ts +4 -2
- package/src/components/file-input/test/file-input.test.ts +24 -0
- package/src/components/select/Select.ts +0 -1
- package/src/components/tab/Tab.ts +72 -0
- package/src/components/tab/TabGroup.ts +267 -0
- package/src/components/tab/TabPanel.ts +59 -0
- package/src/components/tab/leu-tab-group.ts +11 -0
- package/src/components/tab/leu-tab-panel.ts +11 -0
- package/src/components/tab/leu-tab.ts +11 -0
- package/src/components/tab/stories/tab.stories.ts +97 -0
- package/src/components/tab/tab-group.css +63 -0
- package/src/components/tab/tab-panel.css +10 -0
- package/src/components/tab/tab.css +54 -0
- package/src/components/tab/test/tab-group.test.ts +426 -0
- package/src/components/tab/test/tab-panel.test.ts +102 -0
- package/src/components/tab/test/tab.test.ts +139 -0
- package/tsconfig.json +1 -0
- /package/dist/{FormAssociatedMixin-Cc74LjbC.d.ts → FormAssociatedMixin-Cw7LsSUE.d.ts} +0 -0
- /package/dist/{LeuElement-pJFU18Xm.d.ts → LeuElement-DK1jntuu.d.ts} +0 -0
- /package/dist/{hasSlotController-DWPyZ52b.d.ts → hasSlotController-BjKyhJm-.d.ts} +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { html, PropertyValues } from "lit"
|
|
2
|
+
import { property, state } from "lit/decorators.js"
|
|
3
|
+
import { classMap } from "lit/directives/class-map.js"
|
|
4
|
+
import { createRef, ref } from "lit/directives/ref.js"
|
|
5
|
+
|
|
6
|
+
import { LeuElement } from "../../lib/LeuElement.js"
|
|
7
|
+
import { LeuTab } from "./Tab.js"
|
|
8
|
+
import { LeuTabPanel } from "./TabPanel.js"
|
|
9
|
+
|
|
10
|
+
import styles from "./tab-group.css?inline"
|
|
11
|
+
|
|
12
|
+
type ScrollableState = {
|
|
13
|
+
left: boolean
|
|
14
|
+
right: boolean
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tab Group
|
|
19
|
+
*
|
|
20
|
+
* @slot tabs - Slot for the leu-tab elements
|
|
21
|
+
* @slot panels - Slot for the leu-tab-panel elements
|
|
22
|
+
* @todo: add disabled state to tabs and panels
|
|
23
|
+
* @todo: add backdrop / shadow
|
|
24
|
+
* @todo: add styling option for full-bleed layout (tabslist is full-bleed, panels use the full width of the container)
|
|
25
|
+
*
|
|
26
|
+
* @tagname leu-tab-group
|
|
27
|
+
*/
|
|
28
|
+
export class LeuTabGroup extends LeuElement {
|
|
29
|
+
static styles = [LeuElement.styles, styles]
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Label for the tab list, used for accessibility.
|
|
33
|
+
* Content will not be visible on the page, but should be provided for screen readers.
|
|
34
|
+
*/
|
|
35
|
+
@property({ type: String })
|
|
36
|
+
label = ""
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Name of the active/selected tab and panel. Has to match the name property of a leu-tab and leu-tab-panel.
|
|
40
|
+
*/
|
|
41
|
+
@property({ type: String, reflect: true })
|
|
42
|
+
active = ""
|
|
43
|
+
|
|
44
|
+
@state()
|
|
45
|
+
protected isScrollable: ScrollableState = { left: false, right: false }
|
|
46
|
+
|
|
47
|
+
@state()
|
|
48
|
+
protected tabs: LeuTab[] = []
|
|
49
|
+
|
|
50
|
+
@state()
|
|
51
|
+
protected panels: LeuTabPanel[] = []
|
|
52
|
+
|
|
53
|
+
protected initialShowEventDispatched = false
|
|
54
|
+
|
|
55
|
+
protected tabMenuRef = createRef<HTMLDivElement>()
|
|
56
|
+
|
|
57
|
+
protected resizeObserver = new ResizeObserver(() => {
|
|
58
|
+
this.checkScrollable()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// Observe changes to the id attribute of tabs and panels
|
|
62
|
+
// to update the aria attributes accordingly
|
|
63
|
+
protected mutationObserver = new MutationObserver((records) => {
|
|
64
|
+
for (const record of records) {
|
|
65
|
+
if (!(record.target instanceof HTMLElement)) {
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
record.type === "attributes" &&
|
|
71
|
+
record.attributeName === "id" &&
|
|
72
|
+
record.target.matches("leu-tab, leu-tab-panel")
|
|
73
|
+
) {
|
|
74
|
+
this.linkTabsAndPanels()
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
connectedCallback() {
|
|
81
|
+
super.connectedCallback()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
disconnectedCallback() {
|
|
85
|
+
super.disconnectedCallback()
|
|
86
|
+
this.resizeObserver.disconnect()
|
|
87
|
+
this.mutationObserver.disconnect()
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
firstUpdated() {
|
|
91
|
+
if (this.tabMenuRef.value) {
|
|
92
|
+
this.resizeObserver.observe(this.tabMenuRef.value)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.mutationObserver.observe(this, {
|
|
96
|
+
subtree: true,
|
|
97
|
+
attributes: true,
|
|
98
|
+
attributeFilter: ["id"],
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
updated(changedProperties: PropertyValues) {
|
|
103
|
+
if (
|
|
104
|
+
changedProperties.has("active") ||
|
|
105
|
+
changedProperties.has("tabs") ||
|
|
106
|
+
changedProperties.has("panels")
|
|
107
|
+
) {
|
|
108
|
+
this.updatePanels()
|
|
109
|
+
this.updateTabs()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
protected async handleTabsSlotChange() {
|
|
114
|
+
const slot =
|
|
115
|
+
this.shadowRoot?.querySelector<HTMLSlotElement>('slot[name="tabs"]')
|
|
116
|
+
|
|
117
|
+
this.tabs =
|
|
118
|
+
slot
|
|
119
|
+
?.assignedElements({ flatten: true })
|
|
120
|
+
.filter((el) => el instanceof LeuTab) ?? []
|
|
121
|
+
|
|
122
|
+
// if the active tab is not in the new set of tabs, activate the first tab
|
|
123
|
+
if (
|
|
124
|
+
this.tabs.length > 0 &&
|
|
125
|
+
!this.tabs.some((tab) => tab.name === this.active)
|
|
126
|
+
) {
|
|
127
|
+
this.active = this.tabs[0].name
|
|
128
|
+
}
|
|
129
|
+
this.linkTabsAndPanels()
|
|
130
|
+
|
|
131
|
+
await this.updateComplete
|
|
132
|
+
this.checkScrollable()
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected handlePanelsSlotChange() {
|
|
136
|
+
const slot = this.shadowRoot?.querySelector<HTMLSlotElement>(
|
|
137
|
+
'slot[name="panels"]',
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
this.panels =
|
|
141
|
+
slot
|
|
142
|
+
?.assignedElements({ flatten: true })
|
|
143
|
+
.filter((el) => el instanceof LeuTabPanel) ?? []
|
|
144
|
+
|
|
145
|
+
this.linkTabsAndPanels()
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Link tabs and panels by setting the appropriate aria attributes.
|
|
150
|
+
* Generates global ids for tabs and panels if they don't have one.
|
|
151
|
+
*/
|
|
152
|
+
protected linkTabsAndPanels() {
|
|
153
|
+
for (const tab of this.tabs) {
|
|
154
|
+
const panel = this.panels.find((o) => o.name === tab.name)
|
|
155
|
+
|
|
156
|
+
if (!panel) continue
|
|
157
|
+
|
|
158
|
+
tab.setAttribute("aria-controls", panel.id)
|
|
159
|
+
panel.setAttribute("aria-labelledby", tab.id)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected get activeTab() {
|
|
164
|
+
return this.tabs.find((o) => o.active)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
protected get activePanel() {
|
|
168
|
+
return this.panels.find((o) => o.name === this.activeTab?.name)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
protected updatePanels() {
|
|
172
|
+
for (const panel of this.panels) {
|
|
173
|
+
panel.active = panel.name === this.active
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
protected updateTabs() {
|
|
178
|
+
for (const tab of this.tabs) {
|
|
179
|
+
tab.active = tab.name === this.active
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected async keydownHandler(event: KeyboardEvent) {
|
|
184
|
+
const activeTab = this.activeTab
|
|
185
|
+
let nextTab: LeuTab | null = null
|
|
186
|
+
|
|
187
|
+
if (
|
|
188
|
+
!["ArrowRight", "ArrowLeft", "Home", "End"].includes(event.key) ||
|
|
189
|
+
!activeTab
|
|
190
|
+
) {
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
switch (event.key) {
|
|
195
|
+
case "ArrowRight":
|
|
196
|
+
case "ArrowLeft": {
|
|
197
|
+
const direction = event.key === "ArrowRight" ? 1 : -1
|
|
198
|
+
const currentIndex = this.tabs.indexOf(activeTab)
|
|
199
|
+
const numOfTabs = this.tabs.length
|
|
200
|
+
// cycle through the tabs
|
|
201
|
+
const nextIndex = (currentIndex + direction + numOfTabs) % numOfTabs
|
|
202
|
+
nextTab = this.tabs[nextIndex]
|
|
203
|
+
break
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case "Home":
|
|
207
|
+
nextTab = this.tabs[0]
|
|
208
|
+
break
|
|
209
|
+
case "End":
|
|
210
|
+
nextTab = this.tabs[this.tabs.length - 1]
|
|
211
|
+
break
|
|
212
|
+
default:
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
this.active = nextTab.name
|
|
217
|
+
await this.updateComplete
|
|
218
|
+
nextTab.focus()
|
|
219
|
+
nextTab.scrollIntoView({
|
|
220
|
+
block: "nearest",
|
|
221
|
+
inline: "nearest",
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
protected checkScrollable() {
|
|
226
|
+
const tabMenu = this.tabMenuRef.value
|
|
227
|
+
if (!tabMenu) return
|
|
228
|
+
|
|
229
|
+
this.isScrollable = {
|
|
230
|
+
left: tabMenu.scrollLeft > 0,
|
|
231
|
+
right: tabMenu.scrollLeft < tabMenu.scrollWidth - tabMenu.clientWidth,
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
protected handleScrollEvent() {
|
|
236
|
+
this.checkScrollable()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
protected handleTabSelect(event: CustomEvent) {
|
|
240
|
+
this.active = event.detail.name
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
render() {
|
|
244
|
+
const containerClasses = {
|
|
245
|
+
container: true,
|
|
246
|
+
"container--scrollable-left": this.isScrollable.left,
|
|
247
|
+
"container--scrollable-right": this.isScrollable.right,
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return html`
|
|
251
|
+
<div class=${classMap(containerClasses)}>
|
|
252
|
+
<div
|
|
253
|
+
class="tab-menu"
|
|
254
|
+
role="tablist"
|
|
255
|
+
@keydown=${this.keydownHandler}
|
|
256
|
+
@leu:tab-select=${this.handleTabSelect}
|
|
257
|
+
@scroll="${this.handleScrollEvent}"
|
|
258
|
+
aria-label=${this.label}
|
|
259
|
+
${ref(this.tabMenuRef)}
|
|
260
|
+
>
|
|
261
|
+
<slot name="tabs" @slotchange=${this.handleTabsSlotChange}></slot>
|
|
262
|
+
</div>
|
|
263
|
+
</div>
|
|
264
|
+
<slot name="panels" @slotchange=${this.handlePanelsSlotChange}></slot>
|
|
265
|
+
`
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { html, PropertyValues } from "lit"
|
|
2
|
+
import { property } from "lit/decorators.js"
|
|
3
|
+
|
|
4
|
+
import { LeuElement } from "../../lib/LeuElement.js"
|
|
5
|
+
|
|
6
|
+
import styles from "./tab-panel.css?inline"
|
|
7
|
+
|
|
8
|
+
let nextId = 1
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tab Panel
|
|
12
|
+
*
|
|
13
|
+
* @tagname leu-tab-panel
|
|
14
|
+
* @fires leu:show-tab-panel - Fired when a tab panel is shown
|
|
15
|
+
*/
|
|
16
|
+
export class LeuTabPanel extends LeuElement {
|
|
17
|
+
static styles = [LeuElement.styles, styles]
|
|
18
|
+
|
|
19
|
+
protected readonly componentId = `leu-tab-panel-${nextId++}`
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Name of the tab. Apply the same name to the corresponding tab button to link them together.
|
|
23
|
+
* Has to be unique within the tab component.
|
|
24
|
+
*/
|
|
25
|
+
@property({ type: String, reflect: true })
|
|
26
|
+
name = ""
|
|
27
|
+
|
|
28
|
+
@property({ type: Boolean, reflect: true })
|
|
29
|
+
active = false
|
|
30
|
+
|
|
31
|
+
connectedCallback() {
|
|
32
|
+
super.connectedCallback()
|
|
33
|
+
this.setAttribute("role", "tabpanel")
|
|
34
|
+
this.tabIndex = 0
|
|
35
|
+
// Set an id if not already provided. The id is used by the tab-group
|
|
36
|
+
// to set the aria-controls attribute on the corresponding tab button.
|
|
37
|
+
this.id = this.id.length > 0 ? this.id : this.componentId
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
protected updated(changedProperties: PropertyValues): void {
|
|
41
|
+
if (changedProperties.has("active")) {
|
|
42
|
+
this.ariaHidden = this.active ? "false" : "true"
|
|
43
|
+
|
|
44
|
+
if (this.active) {
|
|
45
|
+
this.dispatchEvent(
|
|
46
|
+
new CustomEvent("leu:show-tab-panel", {
|
|
47
|
+
detail: { name: this.name },
|
|
48
|
+
bubbles: true,
|
|
49
|
+
composed: true,
|
|
50
|
+
}),
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
render() {
|
|
57
|
+
return html`<slot></slot>`
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Meta, StoryObj } from "@storybook/web-components-vite"
|
|
2
|
+
import { action } from "storybook/actions"
|
|
3
|
+
import { html } from "lit"
|
|
4
|
+
|
|
5
|
+
import { LeuTabGroup } from "../leu-tab-group.js"
|
|
6
|
+
import "../leu-tab-group.js"
|
|
7
|
+
import "../leu-tab.js"
|
|
8
|
+
import "../leu-tab-panel.js"
|
|
9
|
+
import { ifDefined } from "lit/directives/if-defined.js"
|
|
10
|
+
|
|
11
|
+
type StoryArgs = LeuTabGroup
|
|
12
|
+
type Story = StoryObj<StoryArgs>
|
|
13
|
+
|
|
14
|
+
export default {
|
|
15
|
+
title: "Components/Tab",
|
|
16
|
+
component: "leu-tab-group",
|
|
17
|
+
parameters: {
|
|
18
|
+
design: {
|
|
19
|
+
type: "figma",
|
|
20
|
+
url: "https://www.figma.com/design/d6Pv21UVUbnBs3AdcZijHmbN/KTZH-Design-System?node-id=21161-184423",
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
args: {
|
|
24
|
+
onLeuTabPanelShow: action("leu:show-tab-panel"),
|
|
25
|
+
},
|
|
26
|
+
} satisfies Meta<StoryArgs>
|
|
27
|
+
|
|
28
|
+
interface Args {
|
|
29
|
+
active?: string
|
|
30
|
+
label?: string
|
|
31
|
+
onLeuTabPanelShow?: (event: CustomEvent) => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const Template: Story = {
|
|
35
|
+
render: (args: Args = {}) => {
|
|
36
|
+
return html`
|
|
37
|
+
<leu-tab-group
|
|
38
|
+
active=${ifDefined(args.active)}
|
|
39
|
+
label=${ifDefined(args.label)}
|
|
40
|
+
@leu:show-tab-panel=${args.onLeuTabPanelShow}
|
|
41
|
+
>
|
|
42
|
+
<leu-tab slot="tabs" name="online">Online</leu-tab>
|
|
43
|
+
<leu-tab-panel slot="panels" name="online">
|
|
44
|
+
<p>
|
|
45
|
+
Online – Wenn Ihr Ausweis vor dem 01.01.2013 ausgestellt wurde,
|
|
46
|
+
müssen Sie ein neues Passfoto machen. Ihr Reisebüro oder das
|
|
47
|
+
Konsulat Ihres Reiseziels können Ihnen dabei helfen.
|
|
48
|
+
</p>
|
|
49
|
+
</leu-tab-panel>
|
|
50
|
+
<leu-tab slot="tabs" name="vor-ort">Vor Ort</leu-tab>
|
|
51
|
+
|
|
52
|
+
<leu-tab-panel slot="panels" name="vor-ort">
|
|
53
|
+
<p>
|
|
54
|
+
Vor Ort – Wenn Ihr Ausweis vor dem 01.01.2013 ausgestellt wurde,
|
|
55
|
+
müssen Sie ein neues Passfoto machen. Ihr Reisebüro oder das
|
|
56
|
+
Konsulat Ihres Reiseziels können Ihnen dabei helfen.
|
|
57
|
+
</p>
|
|
58
|
+
</leu-tab-panel>
|
|
59
|
+
|
|
60
|
+
<leu-tab slot="tabs" name="per-post">Per Post</leu-tab>
|
|
61
|
+
<leu-tab-panel slot="panels" name="per-post">
|
|
62
|
+
<p>
|
|
63
|
+
Per Post – Wenn Ihr Ausweis vor dem 01.01.2013 ausgestellt wurde,
|
|
64
|
+
müssen Sie ein neues Passfoto machen. Ihr Reisebüro oder das
|
|
65
|
+
Konsulat Ihres Reiseziels können Ihnen dabei helfen.
|
|
66
|
+
</p>
|
|
67
|
+
</leu-tab-panel>
|
|
68
|
+
|
|
69
|
+
<leu-tab slot="tabs" name="telefonisch">Telefonisch</leu-tab>
|
|
70
|
+
<leu-tab-panel slot="panels" name="telefonisch">
|
|
71
|
+
<p>
|
|
72
|
+
Telefonisch – Wenn Ihr Ausweis vor dem 01.01.2013 ausgestellt wurde,
|
|
73
|
+
müssen Sie ein neues Passfoto machen. Ihr Reisebüro oder das
|
|
74
|
+
Konsulat Ihres Reiseziels können Ihnen dabei helfen.
|
|
75
|
+
</p>
|
|
76
|
+
</leu-tab-panel>
|
|
77
|
+
|
|
78
|
+
<leu-tab slot="tabs" name="service">Service</leu-tab>
|
|
79
|
+
<leu-tab-panel slot="panels" name="service">
|
|
80
|
+
<p>
|
|
81
|
+
Service – Wenn Ihr Ausweis vor dem 01.01.2013 ausgestellt wurde,
|
|
82
|
+
müssen Sie ein neues Passfoto machen. Ihr Reisebüro oder das
|
|
83
|
+
Konsulat Ihres Reiseziels können Ihnen dabei helfen.
|
|
84
|
+
</p>
|
|
85
|
+
</leu-tab-panel>
|
|
86
|
+
</leu-tab-group>
|
|
87
|
+
`
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const Regular = {
|
|
92
|
+
...Template,
|
|
93
|
+
args: {
|
|
94
|
+
active: "online",
|
|
95
|
+
label: "Reiseinformationen",
|
|
96
|
+
},
|
|
97
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
@import url("../../styles/custom-media.css");
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
--tab-font-regular: var(--leu-font-family-regular);
|
|
5
|
+
--tab-font-black: var(--leu-font-family-black);
|
|
6
|
+
|
|
7
|
+
font-family: var(--tab-font-regular);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.container {
|
|
11
|
+
position: relative;
|
|
12
|
+
width: 100%;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.container::before,
|
|
16
|
+
.container::after {
|
|
17
|
+
content: "";
|
|
18
|
+
position: absolute;
|
|
19
|
+
top: 0;
|
|
20
|
+
height: 100%;
|
|
21
|
+
width: 0.375rem;
|
|
22
|
+
pointer-events: none;
|
|
23
|
+
visibility: hidden;
|
|
24
|
+
opacity: 0;
|
|
25
|
+
transition: 0.2s ease;
|
|
26
|
+
transition-property: visibility, opacity;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.container::before {
|
|
30
|
+
left: 0;
|
|
31
|
+
background: linear-gradient(to right, rgb(0 0 0 / 20%), transparent);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.container::after {
|
|
35
|
+
right: 0;
|
|
36
|
+
background: linear-gradient(to left, rgb(0 0 0 / 20%), transparent);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.container--scrollable-left::before,
|
|
40
|
+
.container--scrollable-right::after {
|
|
41
|
+
visibility: visible;
|
|
42
|
+
opacity: 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.tab-menu {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 0.5rem;
|
|
48
|
+
padding: 0.5rem 0;
|
|
49
|
+
margin-bottom: 1.5rem;
|
|
50
|
+
border-bottom: 1px solid var(--leu-color-black-transp-10);
|
|
51
|
+
|
|
52
|
+
overflow: auto;
|
|
53
|
+
scrollbar-width: thin;
|
|
54
|
+
|
|
55
|
+
@media (--viewport-large) {
|
|
56
|
+
margin-bottom: 2rem;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@media (--viewport-xlarge) {
|
|
60
|
+
padding: 1rem 0;
|
|
61
|
+
margin-bottom: 2.5rem;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
@import url("../../styles/custom-media.css");
|
|
2
|
+
|
|
3
|
+
:host {
|
|
4
|
+
--tab-button-font-regular: var(--leu-font-family-regular);
|
|
5
|
+
|
|
6
|
+
--tab-button-active-background: var(--leu-color-black-100);
|
|
7
|
+
--tab-button-active-color: var(--leu-color-black-0);
|
|
8
|
+
|
|
9
|
+
--tab-button-inactive-background: var(--leu-color-black-transp-10);
|
|
10
|
+
--tab-button-inactive-color: var(--leu-color-black-transp-60);
|
|
11
|
+
|
|
12
|
+
--tab-button-hover-background: var(--leu-color-black-transp-20);
|
|
13
|
+
|
|
14
|
+
display: flex;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
align-items: center;
|
|
17
|
+
font-family: var(--tab-button-font-regular);
|
|
18
|
+
background: var(--tab-button-inactive-background);
|
|
19
|
+
color: var(--tab-button-inactive-color);
|
|
20
|
+
border: 0;
|
|
21
|
+
border-radius: 0.25rem;
|
|
22
|
+
padding: 0.5rem 0.75rem;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
|
|
27
|
+
@media (--viewport-large) {
|
|
28
|
+
padding-inline: 1rem;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
:host(:hover) {
|
|
33
|
+
background: var(--tab-button-hover-background);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
:host(:focus-visible) {
|
|
37
|
+
outline: 2px solid var(--leu-color-func-cyan);
|
|
38
|
+
outline-offset: 2px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
:host([active]) {
|
|
42
|
+
background: var(--tab-button-active-background);
|
|
43
|
+
color: var(--tab-button-active-color);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.label {
|
|
47
|
+
font-size: 1rem;
|
|
48
|
+
line-height: 1.5rem;
|
|
49
|
+
|
|
50
|
+
@media (--viewport-large) {
|
|
51
|
+
font-size: 1.125rem;
|
|
52
|
+
line-height: 1.75rem;
|
|
53
|
+
}
|
|
54
|
+
}
|