@statistikzh/leu 0.3.0 → 0.5.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 (295) hide show
  1. package/.github/workflows/deploy-github-pages.yaml +33 -0
  2. package/.storybook/main.js +27 -1
  3. package/.storybook/manager-head.html +1 -0
  4. package/.storybook/manager.js +9 -0
  5. package/.storybook/preview-head.html +1 -1
  6. package/.storybook/preview.js +59 -6
  7. package/.storybook/static/logo.svg +19 -0
  8. package/.storybook/theme.js +7 -0
  9. package/CHANGELOG.md +54 -0
  10. package/README.md +1 -1
  11. package/custom-elements-manifest.config.js +46 -0
  12. package/dist/Accordion.d.ts +31 -0
  13. package/dist/Accordion.d.ts.map +1 -0
  14. package/dist/Accordion.js +257 -0
  15. package/dist/Breadcrumb.d.ts +69 -0
  16. package/dist/Breadcrumb.d.ts.map +1 -0
  17. package/dist/Breadcrumb.js +392 -0
  18. package/dist/Button-da11d064.d.ts +84 -0
  19. package/dist/Button-da11d064.d.ts.map +1 -0
  20. package/dist/Button-da11d064.js +542 -0
  21. package/dist/Button.d.ts +2 -0
  22. package/dist/Button.d.ts.map +1 -0
  23. package/dist/Button.js +6 -423
  24. package/dist/ButtonGroup.d.ts +24 -0
  25. package/dist/ButtonGroup.d.ts.map +1 -0
  26. package/dist/ButtonGroup.js +72 -43
  27. package/dist/Checkbox.d.ts +13 -0
  28. package/dist/Checkbox.d.ts.map +1 -0
  29. package/dist/Checkbox.js +101 -84
  30. package/dist/CheckboxGroup.d.ts +13 -0
  31. package/dist/CheckboxGroup.d.ts.map +1 -0
  32. package/dist/CheckboxGroup.js +41 -37
  33. package/dist/Chip.d.ts +5 -0
  34. package/dist/Chip.d.ts.map +1 -0
  35. package/dist/{Chip-5f70d04f.js → Chip.js} +21 -6
  36. package/dist/ChipGroup.d.ts +28 -0
  37. package/dist/ChipGroup.d.ts.map +1 -0
  38. package/dist/ChipGroup.js +64 -10
  39. package/dist/ChipLink.d.ts +15 -0
  40. package/dist/ChipLink.d.ts.map +1 -0
  41. package/dist/ChipLink.js +4 -7
  42. package/dist/ChipRemovable.d.ts +13 -0
  43. package/dist/ChipRemovable.d.ts.map +1 -0
  44. package/dist/ChipRemovable.js +5 -8
  45. package/dist/ChipSelectable.d.ts +22 -0
  46. package/dist/ChipSelectable.d.ts.map +1 -0
  47. package/dist/ChipSelectable.js +8 -11
  48. package/dist/Dropdown.d.ts +15 -0
  49. package/dist/Dropdown.d.ts.map +1 -0
  50. package/dist/Dropdown.js +73 -26
  51. package/dist/Input.d.ts +154 -0
  52. package/dist/Input.d.ts.map +1 -0
  53. package/dist/Input.js +42 -35
  54. package/dist/Menu.d.ts +8 -0
  55. package/dist/Menu.d.ts.map +1 -0
  56. package/dist/Menu.js +2 -5
  57. package/dist/MenuItem.d.ts +21 -0
  58. package/dist/MenuItem.d.ts.map +1 -0
  59. package/dist/MenuItem.js +32 -17
  60. package/dist/Pagination.d.ts +27 -0
  61. package/dist/Pagination.d.ts.map +1 -0
  62. package/dist/Pagination.js +121 -89
  63. package/dist/Popup.d.ts +18 -0
  64. package/dist/Popup.d.ts.map +1 -0
  65. package/dist/Popup.js +215 -0
  66. package/dist/Radio.d.ts +12 -0
  67. package/dist/Radio.d.ts.map +1 -0
  68. package/dist/Radio.js +9 -8
  69. package/dist/RadioGroup.d.ts +20 -0
  70. package/dist/RadioGroup.d.ts.map +1 -0
  71. package/dist/RadioGroup.js +41 -39
  72. package/dist/ScrollTop.d.ts +19 -0
  73. package/dist/ScrollTop.d.ts.map +1 -0
  74. package/dist/ScrollTop.js +122 -0
  75. package/dist/Select.d.ts +98 -0
  76. package/dist/Select.d.ts.map +1 -0
  77. package/dist/Select.js +79 -122
  78. package/dist/Table.d.ts +48 -0
  79. package/dist/Table.d.ts.map +1 -0
  80. package/dist/Table.js +141 -121
  81. package/dist/VisuallyHidden.d.ts +8 -0
  82. package/dist/VisuallyHidden.d.ts.map +1 -0
  83. package/dist/VisuallyHidden.js +28 -0
  84. package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts +3 -0
  85. package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts.map +1 -0
  86. package/dist/{defineElement-ba770aed.js → _rollupPluginBabelHelpers-20f659f4.js} +1 -15
  87. package/dist/defineElement-40372b4b.d.ts +9 -0
  88. package/dist/defineElement-40372b4b.d.ts.map +1 -0
  89. package/dist/defineElement-40372b4b.js +15 -0
  90. package/dist/icon-03e86700.d.ts +11 -0
  91. package/dist/icon-03e86700.d.ts.map +1 -0
  92. package/dist/index.js.d.ts +21 -0
  93. package/dist/index.js.d.ts.map +1 -0
  94. package/dist/index.js.js +42 -0
  95. package/dist/leu-accordion.d.ts +3 -0
  96. package/dist/leu-accordion.d.ts.map +1 -0
  97. package/dist/leu-accordion.js +9 -0
  98. package/dist/leu-breadcrumb.d.ts +3 -0
  99. package/dist/leu-breadcrumb.d.ts.map +1 -0
  100. package/dist/leu-breadcrumb.js +23 -0
  101. package/dist/leu-button-group.d.ts +3 -0
  102. package/dist/leu-button-group.d.ts.map +1 -0
  103. package/dist/leu-button-group.js +6 -6
  104. package/dist/leu-button.d.ts +3 -0
  105. package/dist/leu-button.d.ts.map +1 -0
  106. package/dist/leu-button.js +7 -3
  107. package/dist/leu-checkbox-group.d.ts +3 -0
  108. package/dist/leu-checkbox-group.d.ts.map +1 -0
  109. package/dist/leu-checkbox-group.js +6 -3
  110. package/dist/leu-checkbox.d.ts +3 -0
  111. package/dist/leu-checkbox.d.ts.map +1 -0
  112. package/dist/leu-checkbox.js +6 -3
  113. package/dist/leu-chip-group.d.ts +3 -0
  114. package/dist/leu-chip-group.d.ts.map +1 -0
  115. package/dist/leu-chip-group.js +7 -3
  116. package/dist/leu-chip-link.d.ts +3 -0
  117. package/dist/leu-chip-link.d.ts.map +1 -0
  118. package/dist/leu-chip-link.js +7 -4
  119. package/dist/leu-chip-removable.d.ts +3 -0
  120. package/dist/leu-chip-removable.d.ts.map +1 -0
  121. package/dist/leu-chip-removable.js +7 -4
  122. package/dist/leu-chip-selectable.d.ts +3 -0
  123. package/dist/leu-chip-selectable.d.ts.map +1 -0
  124. package/dist/leu-chip-selectable.js +7 -4
  125. package/dist/leu-dropdown.d.ts +3 -0
  126. package/dist/leu-dropdown.d.ts.map +1 -0
  127. package/dist/leu-dropdown.js +15 -4
  128. package/dist/leu-input.d.ts +3 -0
  129. package/dist/leu-input.d.ts.map +1 -0
  130. package/dist/leu-input.js +7 -3
  131. package/dist/leu-menu-item.d.ts +3 -0
  132. package/dist/leu-menu-item.d.ts.map +1 -0
  133. package/dist/leu-menu-item.js +8 -3
  134. package/dist/leu-menu.d.ts +3 -0
  135. package/dist/leu-menu.d.ts.map +1 -0
  136. package/dist/leu-menu.js +6 -3
  137. package/dist/leu-pagination.d.ts +3 -0
  138. package/dist/leu-pagination.d.ts.map +1 -0
  139. package/dist/leu-pagination.js +12 -4
  140. package/dist/leu-popup.d.ts +3 -0
  141. package/dist/leu-popup.d.ts.map +1 -0
  142. package/dist/leu-popup.js +9 -0
  143. package/dist/leu-radio-group.d.ts +3 -0
  144. package/dist/leu-radio-group.d.ts.map +1 -0
  145. package/dist/leu-radio-group.js +6 -3
  146. package/dist/leu-radio.d.ts +3 -0
  147. package/dist/leu-radio.d.ts.map +1 -0
  148. package/dist/leu-radio.js +6 -3
  149. package/dist/leu-scroll-top.d.ts +3 -0
  150. package/dist/leu-scroll-top.d.ts.map +1 -0
  151. package/dist/leu-scroll-top.js +14 -0
  152. package/dist/leu-select.d.ts +3 -0
  153. package/dist/leu-select.d.ts.map +1 -0
  154. package/dist/leu-select.js +16 -4
  155. package/dist/leu-table.d.ts +3 -0
  156. package/dist/leu-table.d.ts.map +1 -0
  157. package/dist/leu-table.js +13 -4
  158. package/dist/leu-visually-hidden.d.ts +3 -0
  159. package/dist/leu-visually-hidden.d.ts.map +1 -0
  160. package/dist/leu-visually-hidden.js +8 -0
  161. package/dist/theme.css +386 -2
  162. package/dist/utils-65469421.d.ts +16 -0
  163. package/dist/utils-65469421.d.ts.map +1 -0
  164. package/dist/utils-65469421.js +35 -0
  165. package/index.js +3 -0
  166. package/package.json +47 -17
  167. package/postcss.config.cjs +2 -0
  168. package/rollup.config.js +21 -40
  169. package/scripts/generate-component/templates/[Name].js +0 -5
  170. package/scripts/generate-component/templates/[name].css +3 -3
  171. package/scripts/generate-component/templates/[namespace]-[name].js +5 -2
  172. package/scripts/postcss-leu-font-styles.cjs +160 -0
  173. package/src/components/accordion/Accordion.js +0 -6
  174. package/src/components/accordion/accordion.css +2 -2
  175. package/src/components/accordion/leu-accordion.js +5 -2
  176. package/src/components/accordion/stories/accordion.stories.js +8 -4
  177. package/src/components/accordion/test/accordion.test.js +95 -3
  178. package/src/components/breadcrumb/Breadcrumb.js +311 -0
  179. package/src/components/breadcrumb/breadcrumb.css +103 -0
  180. package/src/components/breadcrumb/leu-breadcrumb.js +6 -0
  181. package/src/components/breadcrumb/stories/breadcrumb.stories.js +73 -0
  182. package/src/components/breadcrumb/test/breadcrumb.test.js +141 -0
  183. package/src/components/button/Button.js +76 -20
  184. package/src/components/button/button.css +13 -5
  185. package/src/components/button/leu-button.js +5 -2
  186. package/src/components/button/stories/button.stories.js +79 -105
  187. package/src/components/button/test/button.test.js +184 -3
  188. package/src/components/button-group/ButtonGroup.js +76 -40
  189. package/src/components/button-group/leu-button-group.js +5 -2
  190. package/src/components/button-group/stories/button-group.stories.js +19 -6
  191. package/src/components/button-group/test/button-group.test.js +87 -4
  192. package/src/components/checkbox/Checkbox.js +6 -85
  193. package/src/components/checkbox/CheckboxGroup.js +8 -38
  194. package/src/components/checkbox/checkbox-group.css +29 -0
  195. package/src/components/checkbox/checkbox.css +76 -0
  196. package/src/components/checkbox/leu-checkbox-group.js +5 -2
  197. package/src/components/checkbox/leu-checkbox.js +5 -2
  198. package/src/components/checkbox/stories/checkbox-group.stories.js +44 -21
  199. package/src/components/checkbox/stories/checkbox.stories.js +7 -1
  200. package/src/components/checkbox/test/checkbox-group.test.js +124 -0
  201. package/src/components/checkbox/test/checkbox.test.js +72 -59
  202. package/src/components/chip/Chip.js +1 -0
  203. package/src/components/chip/ChipGroup.js +42 -7
  204. package/src/components/chip/ChipLink.js +1 -6
  205. package/src/components/chip/ChipRemovable.js +2 -7
  206. package/src/components/chip/ChipSelectable.js +5 -10
  207. package/src/components/chip/chip-group.css +12 -2
  208. package/src/components/chip/chip.css +14 -3
  209. package/src/components/chip/exports.js +4 -10
  210. package/src/components/chip/leu-chip-group.js +5 -2
  211. package/src/components/chip/leu-chip-link.js +5 -2
  212. package/src/components/chip/leu-chip-removable.js +5 -2
  213. package/src/components/chip/leu-chip-selectable.js +5 -2
  214. package/src/components/chip/stories/chip-group.stories.js +110 -44
  215. package/src/components/chip/stories/chip-link.stories.js +16 -4
  216. package/src/components/chip/stories/chip-removable.stories.js +15 -4
  217. package/src/components/chip/stories/chip-selectable.stories.js +13 -3
  218. package/src/components/chip/test/chip-group.test.js +124 -0
  219. package/src/components/chip/test/chip-link.test.js +58 -0
  220. package/src/components/chip/test/chip-removable.test.js +79 -0
  221. package/src/components/chip/test/chip-selectable.test.js +95 -0
  222. package/src/components/chip/test/chip.test.js +1 -1
  223. package/src/components/dropdown/Dropdown.js +72 -24
  224. package/src/components/dropdown/dropdown.css +1 -2
  225. package/src/components/dropdown/leu-dropdown.js +5 -2
  226. package/src/components/dropdown/stories/dropdown.stories.js +11 -5
  227. package/src/components/dropdown/test/dropdown.test.js +6 -6
  228. package/src/components/icon/icon.js +1 -1
  229. package/src/components/icon/test/icon.test.js +66 -0
  230. package/src/components/input/Input.js +25 -28
  231. package/src/components/input/input.css +11 -8
  232. package/src/components/input/leu-input.js +5 -2
  233. package/src/components/input/stories/input.stories.js +21 -2
  234. package/src/components/input/test/input.test.js +432 -4
  235. package/src/components/menu/Menu.js +0 -5
  236. package/src/components/menu/MenuItem.js +20 -13
  237. package/src/components/menu/leu-menu-item.js +5 -2
  238. package/src/components/menu/leu-menu.js +5 -2
  239. package/src/components/menu/menu-item.css +7 -4
  240. package/src/components/menu/stories/menu-item.stories.js +13 -4
  241. package/src/components/menu/stories/menu.stories.js +11 -5
  242. package/src/components/menu/test/menu-item.test.js +180 -0
  243. package/src/components/menu/test/menu.test.js +10 -2
  244. package/src/components/pagination/Pagination.js +118 -99
  245. package/src/components/pagination/leu-pagination.js +5 -2
  246. package/src/components/pagination/pagination.css +6 -1
  247. package/src/components/pagination/stories/pagination.stories.js +30 -9
  248. package/src/components/pagination/test/pagination.test.js +191 -5
  249. package/src/components/popup/Popup.js +200 -0
  250. package/src/components/popup/leu-popup.js +6 -0
  251. package/src/components/popup/popup.css +27 -0
  252. package/src/components/popup/stories/popup.stories.js +58 -0
  253. package/src/components/popup/test/popup.test.js +29 -0
  254. package/src/components/radio/Radio.js +2 -6
  255. package/src/components/radio/RadioGroup.js +6 -38
  256. package/src/components/radio/leu-radio-group.js +5 -2
  257. package/src/components/radio/leu-radio.js +5 -2
  258. package/src/components/radio/radio-group.css +29 -0
  259. package/src/components/radio/radio.css +1 -1
  260. package/src/components/radio/stories/radio-group.stories.js +38 -19
  261. package/src/components/radio/stories/radio.stories.js +7 -1
  262. package/src/components/radio/test/radio-group.test.js +86 -0
  263. package/src/components/radio/test/radio.test.js +108 -17
  264. package/src/components/scroll-top/ScrollTop.js +87 -0
  265. package/src/components/scroll-top/leu-scroll-top.js +6 -0
  266. package/src/components/scroll-top/scroll-top.css +34 -0
  267. package/src/components/scroll-top/stories/scroll-top.stories.js +217 -0
  268. package/src/components/scroll-top/test/scroll-top.test.js +22 -0
  269. package/src/components/select/Select.js +58 -37
  270. package/src/components/select/leu-select.js +5 -2
  271. package/src/components/select/select.css +15 -15
  272. package/src/components/select/stories/select.stories.js +15 -168
  273. package/src/components/select/test/fixtures.js +162 -0
  274. package/src/components/select/test/select.test.js +236 -12
  275. package/src/components/table/Table.js +43 -118
  276. package/src/components/table/leu-table.js +5 -2
  277. package/src/components/table/stories/table.stories.js +20 -10
  278. package/src/components/table/table.css +99 -0
  279. package/src/components/table/test/table.test.js +1 -1
  280. package/src/components/visually-hidden/VisuallyHidden.js +13 -0
  281. package/src/components/visually-hidden/leu-visually-hidden.js +6 -0
  282. package/src/components/visually-hidden/stories/visually-hidden.stories.js +22 -0
  283. package/src/components/visually-hidden/test/visually-hidden.test.js +36 -0
  284. package/src/components/visually-hidden/visually-hidden.css +10 -0
  285. package/src/lib/defineElement.js +1 -1
  286. package/src/lib/hasSlotController.js +5 -3
  287. package/src/lib/utils.js +35 -0
  288. package/src/styles/custom-properties.css +6 -2
  289. package/src/styles/font-definitions.json +202 -0
  290. package/stylelint.config.mjs +2 -0
  291. package/tsconfig.build.json +21 -0
  292. package/tsconfig.json +16 -0
  293. package/{web-dev-server-storybook.config.mjs → web-dev-server.config.mjs} +1 -2
  294. package/web-test-runner.config.mjs +15 -2
  295. package/dist/index.js +0 -26
@@ -0,0 +1,311 @@
1
+ import { html, LitElement, nothing } from "lit"
2
+ import { createRef, ref } from "lit/directives/ref.js"
3
+ import { classMap } from "lit/directives/class-map.js"
4
+
5
+ import styles from "./breadcrumb.css"
6
+ import { Icon } from "../icon/icon.js"
7
+ import "../menu/leu-menu.js"
8
+ import "../menu/leu-menu-item.js"
9
+ import "../popup/leu-popup.js"
10
+ import "../visually-hidden/leu-visually-hidden.js"
11
+ import { debounce } from "../../lib/utils.js"
12
+
13
+ /**
14
+ * A Breadcrumb Navigation.
15
+ *
16
+ * The breadcrumbs can be displayed in two different layouts.
17
+ * Only the back link (the last item / parent of the current page)
18
+ * is displayed when…
19
+ * - … the width of the container is smaller
20
+ * than the BACK_ONLY_BREAKPOINT.
21
+ * - … less then two breadcrumb items could be displayed
22
+ * without overflowing the container.
23
+ *
24
+ * Otherwise as many items as possible are displayed in an inline list
25
+ * without overflowing the container. The remaining items are displayed
26
+ * in a dropdown menu.
27
+ *
28
+ * In order to determine the exact numbers of items that have to be
29
+ * hidden inside the dropdown, all of them have to be rendered first.
30
+ * 1. Render all items
31
+ * 2. Calculate (measure) the number of items that can be displayed
32
+ * without overflowing the container.
33
+ * 3. Updating the state (_hiddeItems) which will trigger a rerender
34
+ * 4. Render the items again with the new state.
35
+ *
36
+ * This results in multiple updates scheduled one after another. Lit
37
+ * will also print a waring in the console beacause of that.
38
+ * It's no a nice behaviour but the only one that works without
39
+ * having duplicate and hidden markup to derive the sizes from that.
40
+ *
41
+ *
42
+ * @prop {Array} items - Object array with { label, href }
43
+ * @prop {Boolean} inverted - invert color on dark background
44
+ *
45
+ * @tagname leu-breadcrumb
46
+ */
47
+ export class LeuBreadcrumb extends LitElement {
48
+ static styles = styles
49
+
50
+ static properties = {
51
+ items: { type: Array },
52
+ inverted: { type: Boolean, reflect: true },
53
+
54
+ _hiddenItems: { state: true },
55
+ _showBackOnly: { state: true },
56
+ _isRecalculating: { state: true },
57
+ _isDropdownOpen: { state: true },
58
+ }
59
+
60
+ static BACK_ONLY_BREAKPOINT = 320
61
+
62
+ constructor() {
63
+ super()
64
+ /** @type {Array} */
65
+ this.items = []
66
+ /** @type {Boolean} - will be used on dark Background */
67
+ this.inverted = false
68
+
69
+ /** @internal */
70
+ this._containerRef = createRef()
71
+ /** @internal */
72
+ this._hiddenItems = 0
73
+ /** @internal */
74
+ this._showBackOnly = null
75
+ /** @internal */
76
+ this._lastContainerWidth = null
77
+ /**
78
+ * @internal
79
+ * Forces the toggle button to be rendered
80
+ * so that all possible inline items will be measured.
81
+ * */
82
+ this._isRecalculating = true
83
+ /** @internal */
84
+ this._isDropdownOpen = false
85
+
86
+ this.resizeObserver = new ResizeObserver(
87
+ debounce(() => {
88
+ this._handleResize()
89
+ }, 500)
90
+ )
91
+ }
92
+
93
+ firstUpdated() {
94
+ this.resizeObserver.observe(this._containerRef.value)
95
+ }
96
+
97
+ async updated(changedProperties) {
98
+ if (changedProperties.has("items")) {
99
+ this._hiddenItems = 0
100
+ this._isRecalculating = true
101
+ await this.updateComplete
102
+ this._checkWidth()
103
+ }
104
+ }
105
+
106
+ disconnectedCallback() {
107
+ super.disconnectedCallback()
108
+
109
+ window.removeEventListener("click", this._closeDropdown)
110
+ this.resizeObserver.disconnect()
111
+ }
112
+
113
+ /** @internal */
114
+ get _listItems() {
115
+ return this.items.toSpliced(1, this._hiddenItems)
116
+ }
117
+
118
+ /** @internal */
119
+ get _dropdownItems() {
120
+ return this.items.slice(1, 1 + this._hiddenItems)
121
+ }
122
+
123
+ _handleResize = async () => {
124
+ const containerOffsetWidth = this._containerRef.value.offsetWidth
125
+ const sizeIsGrowing = containerOffsetWidth > this._lastContainerWidth
126
+ this._lastContainerWidth = containerOffsetWidth
127
+
128
+ /**
129
+ * Show only the back link (parent of the current page)
130
+ * when the width of the container is smaller than the BACK_ONLY_BREAKPOINT
131
+ */
132
+ if (containerOffsetWidth <= LeuBreadcrumb.BACK_ONLY_BREAKPOINT) {
133
+ this._showBackOnly = true
134
+ this._isRecalculating = false
135
+ return
136
+ }
137
+
138
+ this._showBackOnly = false
139
+
140
+ /**
141
+ * In order to calculate how many items can be displayed
142
+ * when the container is growing, all items have to
143
+ * be marked as displayed (_hiddenItems = 0) and
144
+ * rendered.
145
+ */
146
+ if (sizeIsGrowing && this._hiddenItems > 0) {
147
+ this._hiddenItems = 0
148
+ this._isRecalculating = true
149
+ await this.updateComplete
150
+ }
151
+
152
+ this._checkWidth()
153
+ }
154
+
155
+ /**
156
+ * Calculate the number of items that can be displayed
157
+ * without overflowing the container.
158
+ * @internal
159
+ * @returns {void}
160
+ */
161
+ _checkWidth() {
162
+ const containerOffsetWidth = this._containerRef.value.offsetWidth
163
+ const containerScrollWidth = this._containerRef.value.scrollWidth
164
+ this._lastContainerWidth = containerOffsetWidth
165
+
166
+ /** When the container is not overflowing, nothing has to be done */
167
+ if (containerOffsetWidth === containerScrollWidth) {
168
+ this._isRecalculating = false
169
+ return
170
+ }
171
+
172
+ const listItems = this._containerRef.value.querySelectorAll(
173
+ "li:not([data-dropdown-toggle])"
174
+ )
175
+ const listItemWidths = [...listItems].map((o) => o.offsetWidth)
176
+
177
+ let hiddenItems = 0
178
+ let hiddenItemsWidth = 0
179
+
180
+ /**
181
+ * Remove item by item until the sum of the remaining items
182
+ * is smaller than the width of the container.
183
+ * The first item will not be removed.
184
+ */
185
+ while (
186
+ hiddenItems < listItemWidths.length &&
187
+ containerOffsetWidth < containerScrollWidth - hiddenItemsWidth
188
+ ) {
189
+ hiddenItems += 1
190
+
191
+ hiddenItemsWidth = listItemWidths
192
+ .slice(1, 1 + hiddenItems)
193
+ .reduce((sum, itemWidth) => sum + itemWidth, 0)
194
+ }
195
+
196
+ this._hiddenItems += hiddenItems
197
+ this._isRecalculating = false
198
+ }
199
+
200
+ /** @internal */
201
+ _handleDropdownToggle = (e) => {
202
+ e.stopPropagation()
203
+
204
+ this._isDropdownOpen = !this._isDropdownOpen
205
+
206
+ if (this._isDropdownOpen) {
207
+ window.addEventListener("click", this._closeDropdown)
208
+ } else {
209
+ window.removeEventListener("click", this._closeDropdown)
210
+ }
211
+ }
212
+
213
+ _closeDropdown = () => {
214
+ this._isDropdownOpen = false
215
+ window.removeEventListener("click", this._closeDropdown)
216
+ }
217
+
218
+ /**
219
+ * Render the dropdown menu
220
+ * @returns
221
+ */
222
+ renderDropdown() {
223
+ if (this._dropdownItems.length === 0 && !this._isRecalculating)
224
+ return nothing
225
+
226
+ return html`
227
+ <li class="breadcrumbs__item" data-dropdown-toggle>
228
+ <span class="breadcrumbs__icon">${Icon("angleRight")}</span>
229
+ <leu-popup
230
+ ?active=${this._isDropdownOpen}
231
+ placement="bottom-start"
232
+ shift
233
+ shiftPadding="8"
234
+ autoSize="width"
235
+ autoSizePadding="8"
236
+ >
237
+ <button
238
+ slot="anchor"
239
+ class="menu"
240
+ @click=${this._handleDropdownToggle}
241
+ tabindex="0"
242
+ >
243
+ &hellip;
244
+ </button>
245
+ <div class="dropdown">
246
+ ${html`
247
+ <leu-menu>
248
+ ${this._dropdownItems.map(
249
+ (item) =>
250
+ html`
251
+ <leu-menu-item
252
+ label=${item.label}
253
+ href=${item.href}
254
+ ></leu-menu-item>
255
+ `
256
+ )}
257
+ </leu-menu>
258
+ `}
259
+ </div>
260
+ </leu-popup>
261
+ </li>
262
+ `
263
+ }
264
+
265
+ render() {
266
+ if (this.items.length < 2) return nothing
267
+
268
+ const parentItem = this.items[this.items.length - 2]
269
+
270
+ const showBackOnly =
271
+ this._showBackOnly || this.items.length - this._hiddenItems < 2
272
+
273
+ const wrapperClasses = {
274
+ breadcrumbs: true,
275
+ "breadcrumbs--back-only": showBackOnly,
276
+ }
277
+
278
+ return html`
279
+ <nav class=${classMap(wrapperClasses)}>
280
+ <leu-visually-hidden><h2>Sie sind hier:</h2></leu-visually-hidden>
281
+ <ol class="breadcrumbs__list" ref=${ref(this._containerRef)}>
282
+ ${showBackOnly
283
+ ? html` <li class="breadcrumbs__item breadcrumbs__item--back">
284
+ <span class="breadcrumbs__icon">${Icon("arrowLeft")}</span>
285
+ <a class="breadcrumbs__link" href=${parentItem.href}
286
+ >${parentItem.label}</a
287
+ >
288
+ </li>`
289
+ : this._listItems.map(
290
+ (item, index, list) =>
291
+ html`
292
+ <li class="breadcrumbs__item">
293
+ ${index > 0
294
+ ? html`<span class="breadcrumbs__icon"
295
+ >${Icon("angleRight")}</span
296
+ >` // First list item doesn't have an arrow
297
+ : nothing}
298
+ ${index === list.length - 1
299
+ ? item.label // Last list item doesn't contain a link
300
+ : html`<a class="breadcrumbs__link" href=${item.href}
301
+ >${item.label}</a
302
+ >`}
303
+ </li>
304
+ ${index === 0 ? this.renderDropdown() : nothing}
305
+ `
306
+ )}
307
+ </ol>
308
+ </nav>
309
+ `
310
+ }
311
+ }
@@ -0,0 +1,103 @@
1
+ :host,
2
+ :host * {
3
+ box-sizing: border-box;
4
+ }
5
+
6
+ :host {
7
+ --breadcrumb-font-regular: var(--leu-font-family-regular);
8
+ --breadcrumb-font-black: var(--leu-font-family-black);
9
+
10
+ font-family: var(--breadcrumb-font-regular);
11
+ line-height: 1.5;
12
+ color: var(--leu-color-black-100);
13
+ }
14
+
15
+ :host([inverted]) {
16
+ color: var(--leu-color-black-0);
17
+ }
18
+
19
+ .breadcrumbs__list {
20
+ display: flex;
21
+ align-items: center;
22
+ list-style-type: none;
23
+ margin: 0;
24
+ padding: 0;
25
+ }
26
+
27
+ .breadcrumbs--back-only .breadcrumbs__list {
28
+ overflow: hidden;
29
+ }
30
+
31
+ .breadcrumbs__item,
32
+ .breadcrumbs__icon {
33
+ align-items: center;
34
+ display: flex;
35
+ min-height: 1.875rem;
36
+ white-space: nowrap;
37
+ }
38
+
39
+ .breadcrumbs__item:first-child:not(.breadcrumbs__item--back) {
40
+ font-family: var(--breadcrumb-font-black);
41
+ }
42
+
43
+ .breadcrumbs__item--back {
44
+ max-width: 100%;
45
+ }
46
+
47
+ .breadcrumbs__link {
48
+ color: inherit;
49
+ text-decoration: none;
50
+ transition: color 0.1s ease;
51
+ white-space: nowrap;
52
+ }
53
+
54
+ .breadcrumbs__item--back .breadcrumbs__link {
55
+ overflow: hidden;
56
+ text-overflow: ellipsis;
57
+ margin-right: 0.25rem;
58
+ }
59
+
60
+ .menu {
61
+ background: none;
62
+ color: inherit;
63
+ cursor: pointer;
64
+ border: 2px solid transparent;
65
+ }
66
+
67
+ .menu:focus-visible {
68
+ outline: 2px solid var(--leu-color-func-cyan);
69
+ outline-offset: 2px;
70
+ }
71
+
72
+ .dropdown {
73
+ background-color: var(--leu-color-black-0);
74
+ box-shadow: var(--leu-box-shadow-short);
75
+ }
76
+
77
+ .breadcrumbs {
78
+ font-size: 1rem;
79
+ }
80
+
81
+ @media (width >= 320px) {
82
+ .breadcrumbs {
83
+ font-size: calc(2.5vw + 8px);
84
+ }
85
+ }
86
+
87
+ @media (width >= 400px) {
88
+ .breadcrumbs {
89
+ font-size: 18px;
90
+ }
91
+ }
92
+
93
+ @media (width >= 1024px) {
94
+ .breadcrumbs {
95
+ font-size: calc(0.7813vw + 10px);
96
+ }
97
+ }
98
+
99
+ @media (width >= 1280px) {
100
+ .breadcrumbs {
101
+ font-size: 20px;
102
+ }
103
+ }
@@ -0,0 +1,6 @@
1
+ import { defineElement } from "../../lib/defineElement.js"
2
+ import { LeuBreadcrumb } from "./Breadcrumb.js"
3
+
4
+ export { LeuBreadcrumb }
5
+
6
+ defineElement("breadcrumb", LeuBreadcrumb)
@@ -0,0 +1,73 @@
1
+ import { html } from "lit"
2
+ import "../leu-breadcrumb.js"
3
+
4
+ export default {
5
+ title: "Breadcrumb",
6
+ component: "leu-breadcrumb",
7
+ parameters: {
8
+ design: {
9
+ type: "figma",
10
+ url: "https://www.figma.com/file/d6Pv21UVUbnBs3AdcZijHmbN/KTZH-Design-System?type=design&node-id=18100-258351&mode=design&t=lzVrtq8lxYVJU5TB-11",
11
+ },
12
+ html: {
13
+ root: "[data-root]",
14
+ },
15
+ },
16
+ }
17
+
18
+ function Template({ items, inverted }) {
19
+ return html`
20
+ <div
21
+ style=${inverted ? "background: var(--leu-color-accent-blue);" : ""}
22
+ data-root
23
+ >
24
+ <leu-breadcrumb .items=${items} ?inverted=${inverted}></leu-breadcrumb>
25
+ </div>
26
+ <button
27
+ @click=${() => {
28
+ document.getElementsByTagName("leu-breadcrumb")[0].items = [
29
+ { label: "Kanton Zürich", href: "https://zh.ch" },
30
+ { label: "Bildung", href: "https://www.zh.ch/de/bildung.html" },
31
+ {
32
+ label: "Schulen",
33
+ href: "https://www.zh.ch/de/bildung/schulen.html",
34
+ },
35
+ {
36
+ label: "Volksschule",
37
+ href: "https://www.zh.ch/de/bildung/schulen/volksschule.html",
38
+ },
39
+ ]
40
+ }}
41
+ style="margin-top:50px;"
42
+ >
43
+ update items
44
+ </button>
45
+ `
46
+ }
47
+
48
+ export const Regular = Template.bind({})
49
+ Regular.argTypes = {
50
+ _allListElementWidths: { table: { disable: true } },
51
+ _visible: { table: { disable: true } },
52
+ _small: { table: { disable: true } },
53
+ _resizeListenerFunction: { table: { disable: true } },
54
+ }
55
+ Regular.args = {
56
+ items: [
57
+ { label: "Kanton Zürich", href: "https://zh.ch" },
58
+ { label: "Gesundheit", href: "https://zh.ch/de/gesundheit.html" },
59
+ {
60
+ label: "Lebensmittel & Gebrauchsgegenstände",
61
+ href: "https://zh.ch/de/gesundheit/lebensmittel-gebrauchsgegenstaende.html",
62
+ },
63
+ {
64
+ label: "Lebensmittel",
65
+ href: "https://zh.ch/de/gesundheit/lebensmittel-gebrauchsgegenstaende/lebensmittel.html",
66
+ },
67
+ {
68
+ label: "Trinkwasser",
69
+ href: "https://zh.ch/de/gesundheit/lebensmittel-gebrauchsgegenstaende/lebensmittel.html",
70
+ },
71
+ ],
72
+ inverted: true,
73
+ }
@@ -0,0 +1,141 @@
1
+ import { html } from "lit"
2
+ import { fixture, expect, aTimeout } from "@open-wc/testing"
3
+ import { setViewport } from "@web/test-runner-commands"
4
+
5
+ import "../leu-breadcrumb.js"
6
+
7
+ const items = [
8
+ { label: "Kanton Zürich", href: "https://zh.ch" },
9
+ { label: "Gesundheit", href: "https://zh.ch/de/gesundheit.html" },
10
+ {
11
+ label: "Lebensmittel & Gebrauchsgegenstände",
12
+ href: "https://zh.ch/de/gesundheit/lebensmittel-gebrauchsgegenstaende.html",
13
+ },
14
+ {
15
+ label: "Lebensmittel",
16
+ href: "https://zh.ch/de/gesundheit/lebensmittel-gebrauchsgegenstaende/lebensmittel.html",
17
+ },
18
+ {
19
+ label: "Trinkwasser",
20
+ href: "https://zh.ch/de/gesundheit/lebensmittel-gebrauchsgegenstaende/lebensmittel.html",
21
+ },
22
+ ]
23
+
24
+ async function defaultFixture(args = {}) {
25
+ return fixture(
26
+ html` <leu-breadcrumb .items="${args.items ?? items}"></leu-breadcrumb> `
27
+ )
28
+ }
29
+
30
+ describe("LeuBreadcrumb", () => {
31
+ it("is a defined element", async () => {
32
+ const el = customElements.get("leu-breadcrumb")
33
+
34
+ expect(el).not.to.be.undefined
35
+ })
36
+
37
+ it("passes the a11y audit", async () => {
38
+ const el = await defaultFixture()
39
+ await expect(el).to.be.accessible()
40
+ })
41
+
42
+ it("renders a list of items", async () => {
43
+ await setViewport({ width: 1024, height: 1024 })
44
+ const el = await defaultFixture()
45
+
46
+ const itemEls = el.shadowRoot.querySelectorAll("li")
47
+
48
+ expect(itemEls[0]).to.have.trimmed.text(items[0].label)
49
+ expect(itemEls[0].querySelector("a")).to.have.attribute(
50
+ "href",
51
+ items[0].href
52
+ )
53
+ expect(itemEls[1]).to.have.trimmed.text(items[1].label)
54
+ expect(itemEls[1].querySelector("a")).to.have.attribute(
55
+ "href",
56
+ items[1].href
57
+ )
58
+ expect(itemEls[2]).to.have.trimmed.text(items[2].label)
59
+ expect(itemEls[2].querySelector("a")).to.have.attribute(
60
+ "href",
61
+ items[2].href
62
+ )
63
+ expect(itemEls[3]).to.have.trimmed.text(items[3].label)
64
+ expect(itemEls[3].querySelector("a")).to.have.attribute(
65
+ "href",
66
+ items[3].href
67
+ )
68
+
69
+ expect(itemEls[4]).to.have.trimmed.text(items[4].label)
70
+ expect(itemEls[4].querySelector("a")).to.not.exist
71
+ })
72
+
73
+ it("hides the overflowing items when shrinking the viewport", async () => {
74
+ await setViewport({ width: 1024, height: 1024 })
75
+ const el = await defaultFixture()
76
+
77
+ let itemEls = el.shadowRoot.querySelectorAll("li")
78
+ expect(itemEls.length).to.equal(5)
79
+
80
+ await setViewport({ width: 768, height: 1024 })
81
+ await aTimeout(600)
82
+ await el.updateComplete
83
+ itemEls = el.shadowRoot.querySelectorAll("li")
84
+
85
+ expect(itemEls.length).to.equal(4)
86
+ })
87
+
88
+ it("shows all the items when viewport is enlarged", async () => {
89
+ await setViewport({ width: 768, height: 1024 })
90
+ const el = await defaultFixture()
91
+
92
+ let itemEls = el.shadowRoot.querySelectorAll("li")
93
+ expect(itemEls.length).to.equal(4)
94
+
95
+ await setViewport({ width: 1024, height: 1024 })
96
+ await aTimeout(600)
97
+ await el.updateComplete
98
+ itemEls = el.shadowRoot.querySelectorAll("li")
99
+
100
+ expect(itemEls.length).to.equal(5)
101
+ })
102
+
103
+ it("only shows the first item when the viewport is too small", async () => {
104
+ await setViewport({ width: 240, height: 1024 })
105
+ const el = await defaultFixture()
106
+
107
+ const itemEls = el.shadowRoot.querySelectorAll("li")
108
+ expect(itemEls.length).to.equal(1)
109
+
110
+ expect(itemEls[0]).to.have.trimmed.text(items[3].label)
111
+ expect(itemEls[0].querySelector("a")).to.have.attribute(
112
+ "href",
113
+ items[3].href
114
+ )
115
+ })
116
+
117
+ it("shows a dropdown toggle when items are hidden", async () => {
118
+ await setViewport({ width: 768, height: 1024 })
119
+ const el = await defaultFixture()
120
+
121
+ const dropdownToggle = el.shadowRoot.querySelector("li:nth-child(2) button")
122
+ expect(dropdownToggle).to.exist
123
+ expect(dropdownToggle).to.have.trimmed.text("…")
124
+ })
125
+
126
+ it("shows a dropdown when the toggle is clicked", async () => {
127
+ await setViewport({ width: 768, height: 1024 })
128
+ const el = await defaultFixture()
129
+
130
+ const dropdownToggle = el.shadowRoot.querySelector("li:nth-child(2) button")
131
+ dropdownToggle.click()
132
+
133
+ await el.updateComplete
134
+
135
+ const dropdown = el.shadowRoot.querySelectorAll("leu-menu")
136
+ expect(dropdown).to.exist
137
+
138
+ const dropdownItems = el.shadowRoot.querySelectorAll("leu-menu-item")
139
+ expect(dropdownItems.length).to.equal(2)
140
+ })
141
+ })