@statistikzh/leu 0.27.0 → 0.28.0

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.
Files changed (202) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/.storybook/main.ts +1 -1
  3. package/.storybook/preview.ts +6 -4
  4. package/.storybook/static/global.css +5 -0
  5. package/CHANGELOG.md +23 -0
  6. package/dist/{Accordion-DLsqXcK8.js → Accordion-D9kLsiBW.js} +1 -1
  7. package/dist/Accordion.d.ts +1 -1
  8. package/dist/Accordion.js +2 -2
  9. package/dist/{Button-BgNUxmo_.d.ts → Button-DcuvEVkC.d.ts} +14 -12
  10. package/dist/{Button-BSyDL_cV.js → Button-DyNVOHCd.js} +90 -82
  11. package/dist/Button.d.ts +1 -1
  12. package/dist/Button.js +4 -4
  13. package/dist/{ButtonGroup-BmSvl-Oc.js → ButtonGroup-MEh4vb5a.js} +2 -2
  14. package/dist/ButtonGroup.d.ts +1 -1
  15. package/dist/ButtonGroup.js +5 -5
  16. package/dist/{ChartWrapper-CvDvQsd5.js → ChartWrapper-DAl91BIN.js} +2 -2
  17. package/dist/ChartWrapper.d.ts +3 -3
  18. package/dist/ChartWrapper.js +3 -3
  19. package/dist/{Checkbox-Cl_X6gBJ.js → Checkbox-CGGyUW9U.js} +2 -2
  20. package/dist/Checkbox.d.ts +3 -3
  21. package/dist/Checkbox.js +3 -3
  22. package/dist/{CheckboxGroup-BKhOmZYX.js → CheckboxGroup-DXt5iMdj.js} +2 -2
  23. package/dist/CheckboxGroup.d.ts +1 -1
  24. package/dist/CheckboxGroup.js +4 -4
  25. package/dist/{Chip-McVP3N_x.js → Chip-BGs71WGH.js} +1 -1
  26. package/dist/{Chip-DLKM9P7v.d.ts → Chip-DVGjEPJE.d.ts} +1 -1
  27. package/dist/Chip.d.ts +1 -1
  28. package/dist/Chip.js +2 -2
  29. package/dist/{ChipGroup-Ta8Ht4jc.d.ts → ChipGroup-BK5BYM0X.d.ts} +2 -2
  30. package/dist/{ChipGroup-DUGavZeU.js → ChipGroup-BcGyusP-.js} +1 -1
  31. package/dist/ChipGroup.d.ts +1 -1
  32. package/dist/ChipGroup.js +3 -3
  33. package/dist/{ChipLink-BAxyQO2M.d.ts → ChipLink-BdG2o-nk.d.ts} +1 -1
  34. package/dist/ChipLink.d.ts +1 -1
  35. package/dist/ChipLink.js +2 -2
  36. package/dist/{ChipRemovable-DBjwt0CH.d.ts → ChipRemovable-CCwSQTAL.d.ts} +2 -2
  37. package/dist/ChipRemovable.d.ts +1 -1
  38. package/dist/ChipRemovable.js +3 -3
  39. package/dist/{ChipSelectable-CMJNcE4U.d.ts → ChipSelectable-BQ3VLVi5.d.ts} +1 -1
  40. package/dist/ChipSelectable.d.ts +1 -1
  41. package/dist/ChipSelectable.js +2 -2
  42. package/dist/{Dialog-BlDd4T2u.js → Dialog-BzuyL1U3.js} +2 -2
  43. package/dist/Dialog.d.ts +3 -3
  44. package/dist/Dialog.js +3 -3
  45. package/dist/{Dropdown-BLxSIe6p.js → Dropdown-plyBTM15.js} +5 -5
  46. package/dist/Dropdown.d.ts +6 -6
  47. package/dist/Dropdown.js +8 -8
  48. package/dist/{FileInput-DntYrpZ-.js → FileInput-BT3Fe-0J.js} +4 -4
  49. package/dist/FileInput.d.ts +5 -5
  50. package/dist/FileInput.js +6 -6
  51. package/dist/{Icon-Op80LrrO.d.ts → Icon-CUfh3eb3.d.ts} +1 -1
  52. package/dist/{Icon-CbZXpyHU.js → Icon-D83qesg5.js} +1 -1
  53. package/dist/Icon.d.ts +1 -1
  54. package/dist/Icon.js +2 -2
  55. package/dist/{Input-DBXX7ev8.js → Input-D7zS50oz.js} +2 -2
  56. package/dist/{Input-CeaAOB4p.d.ts → Input-fEiQvGDF.d.ts} +3 -3
  57. package/dist/Input.d.ts +1 -1
  58. package/dist/Input.js +3 -3
  59. package/dist/{LeuElement-k4RjIeoG.js → LeuElement-DQI8cqZV.js} +1 -1
  60. package/dist/{Menu-Cu8eIF1T.js → Menu-DRU1LiMM.js} +2 -2
  61. package/dist/{Menu-CQdx1ef3.d.ts → Menu-Z2b7dsB5.d.ts} +2 -2
  62. package/dist/Menu.d.ts +1 -1
  63. package/dist/Menu.js +4 -4
  64. package/dist/{MenuItem-Cs3KFhJm.js → MenuItem-DCttylRO.js} +2 -2
  65. package/dist/{MenuItem-QcgnRk_7.d.ts → MenuItem-LY4TRIho.d.ts} +2 -2
  66. package/dist/MenuItem.d.ts +1 -1
  67. package/dist/MenuItem.js +3 -3
  68. package/dist/{Message-C6Zlk_2p.js → Message-0NxnKEqw.js} +2 -2
  69. package/dist/Message.d.ts +2 -2
  70. package/dist/Message.js +3 -3
  71. package/dist/{Pagination-CqkHh-Vd.d.ts → Pagination-9eZ8WMvR.d.ts} +4 -4
  72. package/dist/{Pagination-CB2eVlXk.js → Pagination-CIy7YvWE.js} +4 -4
  73. package/dist/Pagination.d.ts +1 -1
  74. package/dist/Pagination.js +6 -6
  75. package/dist/{Placeholder-DHMexMhK.js → Placeholder-Dol_X5Hp.js} +1 -1
  76. package/dist/Placeholder.d.ts +1 -1
  77. package/dist/Placeholder.js +2 -2
  78. package/dist/{Popup-Btgm2a3D.d.ts → Popup-B5iDSLIO.d.ts} +1 -1
  79. package/dist/{Popup-8jhVy8gB.js → Popup-nJrJHGSy.js} +1 -1
  80. package/dist/Popup.d.ts +1 -1
  81. package/dist/Popup.js +2 -2
  82. package/dist/{ProgressBar-CG0_lHfS.js → ProgressBar-Dmq9veqU.js} +1 -1
  83. package/dist/ProgressBar.d.ts +1 -1
  84. package/dist/ProgressBar.js +2 -2
  85. package/dist/{Radio-DG3xqP3s.js → Radio-W5ck_IJI.js} +1 -1
  86. package/dist/Radio.d.ts +1 -1
  87. package/dist/Radio.js +2 -2
  88. package/dist/{RadioGroup-BKCp9ICX.js → RadioGroup-De5x2YCO.js} +2 -2
  89. package/dist/RadioGroup.d.ts +1 -1
  90. package/dist/RadioGroup.js +3 -3
  91. package/dist/{Range-7LrESv4K.js → Range-NCdfDkeD.js} +1 -1
  92. package/dist/Range.d.ts +1 -1
  93. package/dist/Range.js +2 -2
  94. package/dist/{ScrollTop-CJJsfniA.js → ScrollTop-DwcNIKmN.js} +3 -3
  95. package/dist/ScrollTop.d.ts +3 -3
  96. package/dist/ScrollTop.js +5 -5
  97. package/dist/{Select-CxEDXIBn.js → Select-Bpicra9q.js} +7 -8
  98. package/dist/Select.d.ts +9 -9
  99. package/dist/Select.js +9 -9
  100. package/dist/{Spinner-VhKfzI3Q.js → Spinner-BBiVZxFH.js} +1 -1
  101. package/dist/{Spinner-CrM1enM0.d.ts → Spinner-DHYaX6-Y.d.ts} +1 -1
  102. package/dist/Spinner.d.ts +1 -1
  103. package/dist/Spinner.js +2 -2
  104. package/dist/Tab-CN97q0nj.d.ts +30 -0
  105. package/dist/Tab-Ce9nrDok.js +117 -0
  106. package/dist/Tab.d.ts +2 -0
  107. package/dist/Tab.js +3 -0
  108. package/dist/TabGroup-C-cd4Wcx.js +248 -0
  109. package/dist/TabGroup.d.ts +64 -0
  110. package/dist/TabGroup.js +5 -0
  111. package/dist/TabPanel-BW1ydVBT.js +65 -0
  112. package/dist/TabPanel-DQgWP7LO.d.ts +26 -0
  113. package/dist/TabPanel.d.ts +2 -0
  114. package/dist/TabPanel.js +3 -0
  115. package/dist/{Table-rg_JCtsA.js → Table-DiYqIzBu.js} +3 -3
  116. package/dist/Table.d.ts +3 -3
  117. package/dist/Table.js +7 -7
  118. package/dist/{Tag-BROUaDAZ.js → Tag-Ct8Hhv7W.js} +1 -1
  119. package/dist/Tag.d.ts +1 -1
  120. package/dist/Tag.js +2 -2
  121. package/dist/{VisuallyHidden-OeQvhxYn.d.ts → VisuallyHidden-CB7aRJzF.d.ts} +1 -1
  122. package/dist/{VisuallyHidden-Co_txzxB.js → VisuallyHidden-CpYXyuC7.js} +1 -1
  123. package/dist/VisuallyHidden.d.ts +1 -1
  124. package/dist/VisuallyHidden.js +2 -2
  125. package/dist/index.d.ts +14 -14
  126. package/dist/index.js +30 -30
  127. package/dist/leu-accordion.js +2 -2
  128. package/dist/leu-button-group.js +5 -5
  129. package/dist/leu-button.d.ts +1 -1
  130. package/dist/leu-button.js +4 -4
  131. package/dist/leu-chart-wrapper.js +3 -3
  132. package/dist/leu-checkbox-group.js +4 -4
  133. package/dist/leu-checkbox.js +3 -3
  134. package/dist/leu-chip-group.d.ts +1 -1
  135. package/dist/leu-chip-group.js +3 -3
  136. package/dist/leu-chip-link.d.ts +1 -1
  137. package/dist/leu-chip-link.js +2 -2
  138. package/dist/leu-chip-removable.d.ts +1 -1
  139. package/dist/leu-chip-removable.js +3 -3
  140. package/dist/leu-chip-selectable.d.ts +1 -1
  141. package/dist/leu-chip-selectable.js +2 -2
  142. package/dist/leu-dialog.js +3 -3
  143. package/dist/leu-dropdown.js +8 -8
  144. package/dist/leu-file-input.js +6 -6
  145. package/dist/leu-icon.d.ts +1 -1
  146. package/dist/leu-icon.js +2 -2
  147. package/dist/leu-input.d.ts +1 -1
  148. package/dist/leu-input.js +3 -3
  149. package/dist/leu-menu-item.d.ts +1 -1
  150. package/dist/leu-menu-item.js +3 -3
  151. package/dist/leu-menu.d.ts +1 -1
  152. package/dist/leu-menu.js +4 -4
  153. package/dist/leu-message.js +3 -3
  154. package/dist/leu-pagination.d.ts +1 -1
  155. package/dist/leu-pagination.js +6 -6
  156. package/dist/leu-placeholder.js +2 -2
  157. package/dist/leu-popup.d.ts +1 -1
  158. package/dist/leu-popup.js +2 -2
  159. package/dist/leu-progress-bar.js +2 -2
  160. package/dist/leu-radio-group.js +3 -3
  161. package/dist/leu-radio.js +2 -2
  162. package/dist/leu-range.js +2 -2
  163. package/dist/leu-scroll-top.js +5 -5
  164. package/dist/leu-select.js +9 -9
  165. package/dist/leu-spinner.d.ts +1 -1
  166. package/dist/leu-spinner.js +2 -2
  167. package/dist/leu-tab-group.d.ts +10 -0
  168. package/dist/leu-tab-group.js +8 -0
  169. package/dist/leu-tab-panel.d.ts +10 -0
  170. package/dist/leu-tab-panel.js +6 -0
  171. package/dist/leu-tab.d.ts +10 -0
  172. package/dist/leu-tab.js +6 -0
  173. package/dist/leu-table.js +7 -7
  174. package/dist/leu-tag.js +2 -2
  175. package/dist/leu-visually-hidden.d.ts +1 -1
  176. package/dist/leu-visually-hidden.js +2 -2
  177. package/dist/vscode.html-custom-data.json +76 -8
  178. package/dist/vue/index.d.ts +73 -5
  179. package/dist/web-types.json +142 -17
  180. package/package.json +1 -2
  181. package/src/components/button/Button.ts +45 -30
  182. package/src/components/button/button.css +55 -54
  183. package/src/components/button/stories/button.stories.ts +17 -20
  184. package/src/components/button/test/button.test.ts +46 -0
  185. package/src/components/select/Select.ts +0 -1
  186. package/src/components/tab/Tab.ts +72 -0
  187. package/src/components/tab/TabGroup.ts +267 -0
  188. package/src/components/tab/TabPanel.ts +59 -0
  189. package/src/components/tab/leu-tab-group.ts +11 -0
  190. package/src/components/tab/leu-tab-panel.ts +11 -0
  191. package/src/components/tab/leu-tab.ts +11 -0
  192. package/src/components/tab/stories/tab.stories.ts +97 -0
  193. package/src/components/tab/tab-group.css +63 -0
  194. package/src/components/tab/tab-panel.css +10 -0
  195. package/src/components/tab/tab.css +54 -0
  196. package/src/components/tab/test/tab-group.test.ts +426 -0
  197. package/src/components/tab/test/tab-panel.test.ts +102 -0
  198. package/src/components/tab/test/tab.test.ts +139 -0
  199. package/tsconfig.json +1 -0
  200. /package/dist/{FormAssociatedMixin-Cc74LjbC.d.ts → FormAssociatedMixin-Cw7LsSUE.d.ts} +0 -0
  201. /package/dist/{LeuElement-pJFU18Xm.d.ts → LeuElement-DK1jntuu.d.ts} +0 -0
  202. /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,11 @@
1
+ import { LeuTabGroup } from "./TabGroup.js"
2
+
3
+ export { LeuTabGroup }
4
+
5
+ LeuTabGroup.define("leu-tab-group")
6
+
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ "leu-tab-group": LeuTabGroup
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ import { LeuTabPanel } from "./TabPanel.js"
2
+
3
+ export { LeuTabPanel }
4
+
5
+ LeuTabPanel.define("leu-tab-panel")
6
+
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ "leu-tab-panel": LeuTabPanel
10
+ }
11
+ }
@@ -0,0 +1,11 @@
1
+ import { LeuTab } from "./Tab.js"
2
+
3
+ export { LeuTab }
4
+
5
+ LeuTab.define("leu-tab")
6
+
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ "leu-tab": LeuTab
10
+ }
11
+ }
@@ -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,10 @@
1
+ :host {
2
+ --tab-panel-font-regular: var(--leu-font-family-regular);
3
+ --tab-panel-font-black: var(--leu-font-family-black);
4
+
5
+ font-family: var(--tab-panel-font-regular);
6
+ }
7
+
8
+ :host([aria-hidden="true"]) {
9
+ display: none;
10
+ }
@@ -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
+ }