@statistikzh/leu 0.5.0 → 0.6.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 (240) hide show
  1. package/.husky/commit-msg +0 -3
  2. package/.husky/pre-commit +0 -3
  3. package/CHANGELOG.md +48 -0
  4. package/dist/Accordion.d.ts +10 -9
  5. package/dist/Accordion.d.ts.map +1 -1
  6. package/dist/Accordion.js +12 -11
  7. package/dist/Breadcrumb.d.ts +4 -4
  8. package/dist/Breadcrumb.d.ts.map +1 -1
  9. package/dist/Breadcrumb.js +28 -26
  10. package/dist/{Button-da11d064.d.ts → Button-9692e403.d.ts} +10 -11
  11. package/dist/Button-9692e403.d.ts.map +1 -0
  12. package/dist/{Button-da11d064.js → Button-9692e403.js} +65 -62
  13. package/dist/Button.d.ts +1 -1
  14. package/dist/Button.js +3 -3
  15. package/dist/ButtonGroup.d.ts +2 -2
  16. package/dist/ButtonGroup.d.ts.map +1 -1
  17. package/dist/ButtonGroup.js +3 -3
  18. package/dist/Checkbox.d.ts +4 -3
  19. package/dist/Checkbox.d.ts.map +1 -1
  20. package/dist/Checkbox.js +14 -19
  21. package/dist/CheckboxGroup.d.ts +2 -2
  22. package/dist/CheckboxGroup.d.ts.map +1 -1
  23. package/dist/CheckboxGroup.js +4 -6
  24. package/dist/Chip.d.ts +2 -2
  25. package/dist/Chip.d.ts.map +1 -1
  26. package/dist/Chip.js +6 -13
  27. package/dist/ChipGroup.d.ts +9 -7
  28. package/dist/ChipGroup.d.ts.map +1 -1
  29. package/dist/ChipGroup.js +8 -5
  30. package/dist/ChipLink.d.ts +2 -1
  31. package/dist/ChipLink.d.ts.map +1 -1
  32. package/dist/ChipLink.js +4 -7
  33. package/dist/ChipRemovable.d.ts +0 -2
  34. package/dist/ChipRemovable.d.ts.map +1 -1
  35. package/dist/ChipRemovable.js +8 -11
  36. package/dist/ChipSelectable.d.ts +9 -1
  37. package/dist/ChipSelectable.d.ts.map +1 -1
  38. package/dist/ChipSelectable.js +12 -16
  39. package/dist/Dropdown.d.ts +9 -5
  40. package/dist/Dropdown.d.ts.map +1 -1
  41. package/dist/Dropdown.js +73 -32
  42. package/dist/Icon.d.ts +20 -0
  43. package/dist/Icon.d.ts.map +1 -0
  44. package/dist/{icon-03e86700.js → Icon.js} +61 -32
  45. package/dist/Input.d.ts +7 -16
  46. package/dist/Input.d.ts.map +1 -1
  47. package/dist/Input.js +24 -28
  48. package/dist/LeuElement-6de6f209.d.ts +7 -0
  49. package/dist/LeuElement-6de6f209.d.ts.map +1 -0
  50. package/dist/LeuElement-6de6f209.js +43 -0
  51. package/dist/Menu.d.ts +24 -2
  52. package/dist/Menu.d.ts.map +1 -1
  53. package/dist/Menu.js +120 -3
  54. package/dist/MenuItem.d.ts +28 -11
  55. package/dist/MenuItem.d.ts.map +1 -1
  56. package/dist/MenuItem.js +110 -63
  57. package/dist/Pagination.d.ts +10 -3
  58. package/dist/Pagination.d.ts.map +1 -1
  59. package/dist/Pagination.js +24 -21
  60. package/dist/Popup.d.ts +21 -3
  61. package/dist/Popup.d.ts.map +1 -1
  62. package/dist/Popup.js +44 -19
  63. package/dist/Radio.d.ts +4 -2
  64. package/dist/Radio.d.ts.map +1 -1
  65. package/dist/Radio.js +9 -16
  66. package/dist/RadioGroup.d.ts +2 -2
  67. package/dist/RadioGroup.d.ts.map +1 -1
  68. package/dist/RadioGroup.js +4 -6
  69. package/dist/ScrollTop.d.ts +2 -2
  70. package/dist/ScrollTop.d.ts.map +1 -1
  71. package/dist/ScrollTop.js +10 -8
  72. package/dist/Select.d.ts +75 -37
  73. package/dist/Select.d.ts.map +1 -1
  74. package/dist/Select.js +279 -183
  75. package/dist/Table.d.ts +2 -6
  76. package/dist/Table.d.ts.map +1 -1
  77. package/dist/Table.js +17 -18
  78. package/dist/VisuallyHidden.d.ts +2 -2
  79. package/dist/VisuallyHidden.d.ts.map +1 -1
  80. package/dist/VisuallyHidden.js +5 -7
  81. package/dist/{index.js.d.ts → index.d.ts} +3 -3
  82. package/dist/index.d.ts.map +1 -0
  83. package/dist/{index.js.js → index.js} +5 -14
  84. package/dist/leu-accordion.d.ts.map +1 -1
  85. package/dist/leu-accordion.js +2 -3
  86. package/dist/leu-breadcrumb.d.ts.map +1 -1
  87. package/dist/leu-breadcrumb.js +4 -10
  88. package/dist/leu-button-group.d.ts.map +1 -1
  89. package/dist/leu-button-group.js +2 -3
  90. package/dist/leu-button.d.ts +1 -1
  91. package/dist/leu-button.d.ts.map +1 -1
  92. package/dist/leu-button.js +4 -5
  93. package/dist/leu-checkbox-group.d.ts.map +1 -1
  94. package/dist/leu-checkbox-group.js +2 -3
  95. package/dist/leu-checkbox.d.ts.map +1 -1
  96. package/dist/leu-checkbox.js +3 -4
  97. package/dist/leu-chip-group.d.ts.map +1 -1
  98. package/dist/leu-chip-group.js +2 -3
  99. package/dist/leu-chip-link.d.ts.map +1 -1
  100. package/dist/leu-chip-link.js +2 -3
  101. package/dist/leu-chip-removable.d.ts.map +1 -1
  102. package/dist/leu-chip-removable.js +3 -4
  103. package/dist/leu-chip-selectable.d.ts.map +1 -1
  104. package/dist/leu-chip-selectable.js +2 -3
  105. package/dist/leu-dropdown.d.ts.map +1 -1
  106. package/dist/leu-dropdown.js +5 -10
  107. package/dist/leu-icon.d.ts +3 -0
  108. package/dist/leu-icon.d.ts.map +1 -0
  109. package/dist/leu-icon.js +7 -0
  110. package/dist/leu-input.d.ts.map +1 -1
  111. package/dist/leu-input.js +3 -4
  112. package/dist/leu-menu-item.d.ts.map +1 -1
  113. package/dist/leu-menu-item.js +3 -5
  114. package/dist/leu-menu.d.ts.map +1 -1
  115. package/dist/leu-menu.js +5 -3
  116. package/dist/leu-pagination.d.ts.map +1 -1
  117. package/dist/leu-pagination.js +4 -7
  118. package/dist/leu-popup.d.ts.map +1 -1
  119. package/dist/leu-popup.js +2 -3
  120. package/dist/leu-radio-group.d.ts.map +1 -1
  121. package/dist/leu-radio-group.js +2 -3
  122. package/dist/leu-radio.d.ts.map +1 -1
  123. package/dist/leu-radio.js +2 -3
  124. package/dist/leu-scroll-top.d.ts.map +1 -1
  125. package/dist/leu-scroll-top.js +4 -6
  126. package/dist/leu-select.d.ts.map +1 -1
  127. package/dist/leu-select.js +5 -13
  128. package/dist/leu-table.d.ts.map +1 -1
  129. package/dist/leu-table.js +4 -8
  130. package/dist/leu-visually-hidden.d.ts.map +1 -1
  131. package/dist/leu-visually-hidden.js +2 -3
  132. package/dist/theme.css +2 -0
  133. package/dist/vscode.html-custom-data.json +616 -0
  134. package/dist/vue/index.d.ts +682 -0
  135. package/dist/web-types.json +1211 -0
  136. package/package.json +10 -13
  137. package/rollup.config.js +1 -1
  138. package/scripts/generate-component/templates/[Name].js +6 -3
  139. package/scripts/generate-component/templates/test/[name].test.js +1 -1
  140. package/src/components/accordion/Accordion.js +13 -10
  141. package/src/components/accordion/leu-accordion.js +1 -2
  142. package/src/components/breadcrumb/Breadcrumb.js +31 -18
  143. package/src/components/breadcrumb/leu-breadcrumb.js +1 -2
  144. package/src/components/button/Button.js +45 -71
  145. package/src/components/button/button.css +19 -4
  146. package/src/components/button/leu-button.js +1 -2
  147. package/src/components/button/stories/button.stories.js +60 -19
  148. package/src/components/button/test/button.test.js +26 -63
  149. package/src/components/button-group/ButtonGroup.js +4 -2
  150. package/src/components/button-group/leu-button-group.js +1 -2
  151. package/src/components/checkbox/Checkbox.js +17 -11
  152. package/src/components/checkbox/CheckboxGroup.js +6 -3
  153. package/src/components/checkbox/leu-checkbox-group.js +1 -2
  154. package/src/components/checkbox/leu-checkbox.js +1 -2
  155. package/src/components/checkbox/stories/checkbox-group.stories.js +10 -26
  156. package/src/components/checkbox/stories/checkbox.stories.js +2 -7
  157. package/src/components/checkbox/test/checkbox-group.test.js +6 -21
  158. package/src/components/checkbox/test/checkbox.test.js +1 -12
  159. package/src/components/chip/Chip.js +5 -4
  160. package/src/components/chip/ChipGroup.js +10 -4
  161. package/src/components/chip/ChipLink.js +3 -7
  162. package/src/components/chip/ChipRemovable.js +8 -11
  163. package/src/components/chip/ChipSelectable.js +11 -17
  164. package/src/components/chip/chip.css +3 -4
  165. package/src/components/chip/leu-chip-group.js +1 -2
  166. package/src/components/chip/leu-chip-link.js +1 -2
  167. package/src/components/chip/leu-chip-removable.js +1 -2
  168. package/src/components/chip/leu-chip-selectable.js +1 -2
  169. package/src/components/chip/stories/chip-link.stories.js +3 -5
  170. package/src/components/chip/stories/chip-removable.stories.js +3 -4
  171. package/src/components/chip/stories/chip-selectable.stories.js +2 -2
  172. package/src/components/chip/test/chip-group.test.js +15 -30
  173. package/src/components/chip/test/chip-link.test.js +2 -6
  174. package/src/components/chip/test/chip-removable.test.js +4 -10
  175. package/src/components/chip/test/chip-selectable.test.js +3 -5
  176. package/src/components/dropdown/Dropdown.js +80 -26
  177. package/src/components/dropdown/dropdown.css +4 -0
  178. package/src/components/dropdown/leu-dropdown.js +1 -2
  179. package/src/components/dropdown/stories/dropdown.stories.js +30 -7
  180. package/src/components/dropdown/test/dropdown.test.js +5 -5
  181. package/src/components/icon/Icon.js +55 -0
  182. package/src/components/icon/icon.css +6 -0
  183. package/src/components/icon/leu-icon.js +5 -0
  184. package/src/components/icon/{icon.js → paths.js} +4 -37
  185. package/src/components/icon/stories/icon.stories.js +47 -0
  186. package/src/components/icon/test/icon.test.js +23 -40
  187. package/src/components/input/Input.js +21 -23
  188. package/src/components/input/input.css +4 -2
  189. package/src/components/input/leu-input.js +1 -2
  190. package/src/components/input/stories/input.stories.js +2 -2
  191. package/src/components/input/test/input.test.js +2 -0
  192. package/src/components/menu/Menu.js +143 -2
  193. package/src/components/menu/MenuItem.js +104 -52
  194. package/src/components/menu/leu-menu-item.js +1 -2
  195. package/src/components/menu/leu-menu.js +1 -2
  196. package/src/components/menu/menu-item.css +11 -4
  197. package/src/components/menu/stories/menu-item.stories.js +15 -4
  198. package/src/components/menu/stories/menu.stories.js +34 -7
  199. package/src/components/menu/test/menu-item.test.js +88 -82
  200. package/src/components/menu/test/menu.test.js +101 -8
  201. package/src/components/pagination/Pagination.js +27 -18
  202. package/src/components/pagination/leu-pagination.js +1 -2
  203. package/src/components/popup/Popup.js +39 -16
  204. package/src/components/popup/leu-popup.js +1 -2
  205. package/src/components/popup/popup.css +1 -0
  206. package/src/components/radio/Radio.js +12 -7
  207. package/src/components/radio/RadioGroup.js +6 -3
  208. package/src/components/radio/leu-radio-group.js +1 -2
  209. package/src/components/radio/leu-radio.js +1 -2
  210. package/src/components/radio/stories/radio-group.stories.js +5 -19
  211. package/src/components/radio/stories/radio.stories.js +2 -7
  212. package/src/components/radio/test/radio-group.test.js +6 -9
  213. package/src/components/radio/test/radio.test.js +3 -13
  214. package/src/components/scroll-top/ScrollTop.js +15 -5
  215. package/src/components/scroll-top/leu-scroll-top.js +1 -2
  216. package/src/components/select/Select.js +279 -175
  217. package/src/components/select/leu-select.js +1 -2
  218. package/src/components/select/select.css +20 -12
  219. package/src/components/select/stories/select.stories.js +16 -2
  220. package/src/components/select/test/select.test.js +191 -37
  221. package/src/components/table/Table.js +15 -9
  222. package/src/components/table/leu-table.js +1 -2
  223. package/src/components/table/table.css +3 -1
  224. package/src/components/visually-hidden/VisuallyHidden.js +6 -2
  225. package/src/components/visually-hidden/leu-visually-hidden.js +1 -2
  226. package/src/lib/LeuElement.js +23 -0
  227. package/src/lib/a11y.js +26 -0
  228. package/src/styles/custom-properties.css +2 -0
  229. package/web-test-runner.config.mjs +2 -0
  230. package/dist/Button-da11d064.d.ts.map +0 -1
  231. package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts +0 -3
  232. package/dist/_rollupPluginBabelHelpers-20f659f4.d.ts.map +0 -1
  233. package/dist/_rollupPluginBabelHelpers-20f659f4.js +0 -30
  234. package/dist/defineElement-40372b4b.d.ts +0 -9
  235. package/dist/defineElement-40372b4b.d.ts.map +0 -1
  236. package/dist/defineElement-40372b4b.js +0 -15
  237. package/dist/icon-03e86700.d.ts +0 -11
  238. package/dist/icon-03e86700.d.ts.map +0 -1
  239. package/dist/index.js.d.ts.map +0 -1
  240. package/src/lib/defineElement.js +0 -13
@@ -1,44 +1,48 @@
1
- import { LitElement, nothing } from "lit"
2
- import { html, unsafeStatic } from "lit/static-html.js"
1
+ import { html } from "lit"
3
2
  import { ifDefined } from "lit/directives/if-defined.js"
4
3
 
4
+ import { LeuElement } from "../../lib/LeuElement.js"
5
+ import { LeuIcon } from "../icon/Icon.js"
6
+
7
+ // @ts-ignore
5
8
  import styles from "./menu-item.css"
6
9
 
7
- import { Icon, ICON_NAMES } from "../icon/icon.js"
10
+ /**
11
+ * @typedef {'menuitem' | 'menuitemcheckbox' | 'menuitemradio' | 'option' | 'none'} MenuItemRole
12
+ */
8
13
 
9
14
  /**
10
15
  * @tagname leu-menu-item
11
16
  * @slot - The label of the menu item
17
+ * @property {boolean} active - Defines if the item is selected or checked
18
+ * @property {boolean} disabled - Disables the underlying button or link
19
+ * @property {string} value - The value of the item. It must not contain commas. See `getValue()`
20
+ * @property {string} href - The href of the underlying link
21
+ * @property {boolean} tabbable - If the item should be focusable. Will be reflected as `tabindex` to the underlying button or link
22
+ * @property {MenuItemRole} componentRole - The role of the item. This will be reflected as `role` to the underlying button or link. Default is `'menuitem'.`
12
23
  */
13
- export class LeuMenuItem extends LitElement {
24
+ export class LeuMenuItem extends LeuElement {
25
+ static dependencies = {
26
+ "leu-icon": LeuIcon,
27
+ }
28
+
14
29
  static styles = styles
15
30
 
16
31
  /**
17
32
  * @internal
18
33
  */
19
34
  static shadowRootOptions = {
20
- ...LitElement.shadowRootOptions,
35
+ ...LeuElement.shadowRootOptions,
21
36
  delegatesFocus: true,
22
37
  }
23
38
 
24
39
  static properties = {
25
- /**
26
- * Can be either an icon name or a text
27
- * If no icon with this value is found, it will be displayed as text.
28
- * If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
29
- */
30
- before: { type: String, reflect: true },
31
- /**
32
- * Can be either an icon name or a text
33
- * If no icon with this value is found, it will be displayed as text
34
- * If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
35
- */
36
- after: { type: String, reflect: true },
37
40
  active: { type: Boolean, reflect: true },
38
- highlighted: { type: Boolean, reflect: true },
39
41
  disabled: { type: Boolean, reflect: true },
40
- label: { type: String, reflect: true },
42
+ tabbable: { type: Boolean, reflect: true },
41
43
  href: { type: String, reflect: true },
44
+ value: { type: String, reflect: true },
45
+ componentRole: { type: String, reflect: true },
42
46
  }
43
47
 
44
48
  constructor() {
@@ -46,57 +50,105 @@ export class LeuMenuItem extends LitElement {
46
50
 
47
51
  this.active = false
48
52
  this.disabled = false
53
+ this.value = undefined
54
+ this.href = undefined
55
+ this.tabbable = undefined
49
56
 
50
- /**
51
- * A programmatic way to highlight the menu item like it is hovered.
52
- * This is just a visual effect and does not change the active state.
53
- */
54
- this.highlighted = false
57
+ /** @type {MenuItemRole} */
58
+ this.componentRole = "menuitem"
55
59
  }
56
60
 
57
- static getIconOrText(name) {
58
- if (ICON_NAMES.includes(name)) {
59
- return Icon(name)
60
- }
61
+ connectedCallback() {
62
+ super.connectedCallback()
63
+ this.addEventListener("click", this._handleClick, true)
64
+ }
65
+
66
+ disconnectedCallback() {
67
+ super.disconnectedCallback()
68
+ this.removeEventListener("click", this._handleClick, true)
69
+ }
61
70
 
62
- if (name === "EMPTY") {
63
- return html`<div class="icon-placeholder"></div>`
71
+ _handleClick(event) {
72
+ if (this.disabled) {
73
+ event.stopPropagation()
74
+ event.preventDefault()
64
75
  }
76
+ }
65
77
 
66
- return name
78
+ /**
79
+ * Returns the value of the item. If `value` is not set, it will return the inner text
80
+ * @returns {string}
81
+ */
82
+ getValue() {
83
+ return this.value || this.innerText
67
84
  }
68
85
 
69
- renderBefore() {
70
- if (this.before) {
71
- const content = LeuMenuItem.getIconOrText(this.before)
72
- return html`<span class="before">${content}</span>`
86
+ _getAria() {
87
+ const commonAttributes = {
88
+ disabled: this.disabled,
89
+ }
90
+
91
+ if (this.href) {
92
+ return commonAttributes
73
93
  }
74
94
 
75
- return nothing
95
+ return {
96
+ ...commonAttributes,
97
+ checked:
98
+ this.componentRole === "menuitemcheckbox" ||
99
+ this.componentRole === "menuitemradio"
100
+ ? this.active
101
+ : undefined,
102
+ selected: this.componentRole === "option" ? this.active : undefined,
103
+ role: this.componentRole === "none" ? undefined : this.componentRole,
104
+ }
76
105
  }
77
106
 
78
- renderAfter() {
79
- if (this.after) {
80
- const content = LeuMenuItem.getIconOrText(this.after)
81
- return html`<span class="after">${content}</span>`
107
+ _getTabIndex() {
108
+ if (typeof this.tabbable === "boolean") {
109
+ return this.tabbable ? 0 : -1
82
110
  }
83
111
 
84
- return nothing
112
+ return undefined
113
+ }
114
+
115
+ _renderLink(content) {
116
+ const aria = this._getAria()
117
+
118
+ return html`<a
119
+ class="button"
120
+ href=${this.href}
121
+ aria-disabled=${ifDefined(aria.disabled)}
122
+ aria-checked=${ifDefined(aria.checked)}
123
+ aria-selected=${ifDefined(aria.selected)}
124
+ role=${ifDefined(aria.role)}
125
+ tabindex=${ifDefined(this._getTabIndex())}
126
+ >${content}</a
127
+ >`
85
128
  }
86
129
 
87
- getTagName() {
88
- return this.href ? "a" : "button"
130
+ _renderButton(content) {
131
+ const aria = this._getAria()
132
+
133
+ return html`<button
134
+ class="button"
135
+ aria-disabled=${ifDefined(aria.disabled)}
136
+ aria-checked=${ifDefined(aria.checked)}
137
+ aria-selected=${ifDefined(aria.selected)}
138
+ role=${ifDefined(aria.role)}
139
+ tabindex=${ifDefined(this._getTabIndex())}
140
+ >
141
+ ${content}
142
+ </button>`
89
143
  }
90
144
 
91
145
  render() {
92
- /* The eslint rules don't recognize html import from lit/static-html.js */
93
- /* eslint-disable lit/binding-positions, lit/no-invalid-html */
94
- return html`<${unsafeStatic(
95
- this.getTagName()
96
- )} class="button" href=${ifDefined(this.href)} ?disabled=${this.disabled}>
97
- ${this.renderBefore()}<span class="label">${this.label}</span
98
- >${this.renderAfter()}
99
- </${unsafeStatic(this.getTagName())}>`
100
- /* eslint-enable lit/binding-positions, lit/no-invalid-html */
146
+ const content = html`
147
+ <slot class="before" name="before"></slot>
148
+ <span class="label"><slot></slot></span>
149
+ <slot class="after" name="after"></slot>
150
+ `
151
+
152
+ return this.href ? this._renderLink(content) : this._renderButton(content)
101
153
  }
102
154
  }
@@ -1,6 +1,5 @@
1
- import { defineElement } from "../../lib/defineElement.js"
2
1
  import { LeuMenuItem } from "./MenuItem.js"
3
2
 
4
3
  export { LeuMenuItem }
5
4
 
6
- defineElement("menu-item", LeuMenuItem)
5
+ LeuMenuItem.define("leu-menu-item")
@@ -1,6 +1,5 @@
1
- import { defineElement } from "../../lib/defineElement.js"
2
1
  import { LeuMenu } from "./Menu.js"
3
2
 
4
3
  export { LeuMenu }
5
4
 
6
- defineElement("menu", LeuMenu)
5
+ LeuMenu.define("leu-menu")
@@ -6,9 +6,10 @@
6
6
  :host {
7
7
  --background: var(--leu-color-black-0);
8
8
  --background-hover: var(--leu-color-black-10);
9
- --background-active: var(--leu-color-func-cyan);
9
+ --background-active: var(--leu-color-accent-blue);
10
10
  --background-disabled: var(--leu-color-black-black-0);
11
11
  --color: var(--leu-color-black-transp-60);
12
+ --color-active: var(--leu-color-black-0);
12
13
  --color-disabled: var(--leu-color-black-transp-20);
13
14
  --font-regular: var(--leu-font-family-regular);
14
15
  --font-black: var(--leu-font-family-black);
@@ -29,6 +30,7 @@
29
30
 
30
31
  padding: 0.75rem;
31
32
 
33
+ font-family: inherit;
32
34
  font-size: 1rem;
33
35
  line-height: 1.5;
34
36
  text-align: left;
@@ -43,21 +45,26 @@
43
45
  }
44
46
 
45
47
  .button:hover,
46
- :host([highlighted]) .button {
48
+ .button:focus-visible {
47
49
  --background: var(--background-hover);
48
50
  }
49
51
 
52
+ /*
53
+ * These colors do not match with the design system (yet).
54
+ * But at least they are compliant with the WCAG AA contrast ratio.
55
+ */
50
56
  :host([active]) .button {
51
57
  --background: var(--background-active);
58
+ --color: var(--color-active);
52
59
  }
53
60
 
54
61
  :host([disabled]) .button {
55
62
  --background: var(--background-disabled);
56
63
  --color: var(--color-disabled);
57
- cursor: default;
64
+ cursor: not-allowed;
58
65
  }
59
66
 
60
- :is(.before, .after) svg {
67
+ :is(.before, .after) leu-icon {
61
68
  display: block;
62
69
  }
63
70
 
@@ -2,6 +2,12 @@ import { html } from "lit"
2
2
  import { ifDefined } from "lit/directives/if-defined.js"
3
3
 
4
4
  import "../leu-menu-item.js"
5
+ import "../../icon/leu-icon.js"
6
+ import { paths as iconPaths } from "../../icon/paths.js"
7
+
8
+ function isIcon(name) {
9
+ return name === "EMPTY" || Object.keys(iconPaths).includes(name)
10
+ }
5
11
 
6
12
  export default {
7
13
  title: "Menu/Item",
@@ -20,13 +26,18 @@ export default {
20
26
  function Template(args) {
21
27
  return html`
22
28
  <leu-menu-item
23
- label=${args.label}
24
- before=${ifDefined(args.before)}
25
- after=${ifDefined(args.after)}
26
29
  href=${ifDefined(args.href)}
27
30
  ?active=${args.active}
28
31
  ?disabled=${args.disabled}
29
- ></leu-menu-item>
32
+ >
33
+ ${isIcon(args.before)
34
+ ? html`<leu-icon slot="before" name=${args.before}></leu-icon>`
35
+ : html`<span slot="before">${args.before}</span>`}
36
+ ${args.label}
37
+ ${isIcon(args.after)
38
+ ? html`<leu-icon slot="after" name=${args.after}></leu-icon>`
39
+ : html`<span slot="after">${args.after}</span>`}
40
+ </leu-menu-item>
30
41
  `
31
42
  }
32
43
 
@@ -1,6 +1,8 @@
1
1
  import { html } from "lit"
2
2
  import "../leu-menu.js"
3
3
  import "../leu-menu-item.js"
4
+ import "../../icon/leu-icon.js"
5
+ import { ifDefined } from "lit/directives/if-defined.js"
4
6
 
5
7
  export default {
6
8
  title: "Menu",
@@ -11,16 +13,41 @@ export default {
11
13
  url: "https://www.figma.com/file/d6Pv21UVUbnBs3AdcZijHmbN/KTZH-Design-System?type=design&node-id=17340-82208&mode=design&t=lzVrtq8lxYVJU5TB-11",
12
14
  },
13
15
  },
16
+ argTypes: {
17
+ selects: {
18
+ control: "select",
19
+ options: ["single", "multiple"],
20
+ },
21
+ role: {
22
+ control: "select",
23
+ options: ["menu", "listbox"],
24
+ },
25
+ },
14
26
  }
15
27
 
16
- function Template() {
17
- return html` <leu-menu>
18
- <leu-menu-item label="Menu Item 1" before="EMPTY"></leu-menu-item>
19
- <leu-menu-item label="Menu Item 2" before="check" active></leu-menu-item>
20
- <leu-menu-item label="Menu Item 3" before="EMPTY"></leu-menu-item>
28
+ function Template(args) {
29
+ return html` <leu-menu
30
+ role=${ifDefined(args.role)}
31
+ selects=${ifDefined(args.selects)}
32
+ >
33
+ <leu-menu-item
34
+ ><leu-icon slot="before"></leu-icon>Menu Item 1</leu-menu-item
35
+ >
36
+ <leu-menu-item active
37
+ ><leu-icon slot="before" name="check"></leu-icon>Menu Item
38
+ 2</leu-menu-item
39
+ >
40
+ <leu-menu-item
41
+ ><leu-icon slot="before"></leu-icon>Menu Item 3</leu-menu-item
42
+ >
21
43
  <hr />
22
- <leu-menu-item label="Menu Item 3" before="pin" after="CH"></leu-menu-item>
23
- <leu-menu-item label="Menu Item 4"></leu-menu-item>
44
+ <leu-menu-item
45
+ ><leu-icon slot="before" name="pin"></leu-icon>Menu Item 3<span
46
+ slot="after"
47
+ >CH</span
48
+ ></leu-menu-item
49
+ >
50
+ <leu-menu-item>Menu Item 4</leu-menu-item>
24
51
  </leu-menu>`
25
52
  }
26
53
 
@@ -1,20 +1,31 @@
1
1
  import { html } from "lit"
2
- import { fixture, expect, oneEvent } from "@open-wc/testing"
2
+ import { fixture, expect, oneEvent, elementUpdated } from "@open-wc/testing"
3
3
  import { ifDefined } from "lit/directives/if-defined.js"
4
4
  import { spy } from "sinon"
5
5
 
6
+ import "../leu-menu.js"
6
7
  import "../leu-menu-item.js"
7
8
 
8
9
  async function defaultFixture(args = {}) {
9
10
  return fixture(html`
10
11
  <leu-menu-item
11
- label=${args.label}
12
- before=${ifDefined(args.before)}
13
- after=${ifDefined(args.after)}
14
12
  href=${ifDefined(args.href)}
13
+ componentRole=${ifDefined(args.componentRole)}
14
+ value=${ifDefined(args.value)}
15
15
  ?active=${args.active}
16
16
  ?disabled=${args.disabled}
17
- ></leu-menu-item>
17
+ ?tabbable=${args.tabbable}
18
+ >
19
+ ${args.label}
20
+ </leu-menu-item>
21
+ `)
22
+ }
23
+
24
+ async function wrappedFixture(args = {}) {
25
+ return fixture(html`
26
+ <leu-menu role=${ifDefined(args.menuRole)}>
27
+ ${await defaultFixture(args)}
28
+ </leu-menu>
18
29
  `)
19
30
  }
20
31
 
@@ -26,26 +37,25 @@ describe("LeuMenuItem", () => {
26
37
  })
27
38
 
28
39
  it("passes the a11y audit", async () => {
29
- const el = await defaultFixture({ label: "Download" })
40
+ const el = await wrappedFixture({ label: "Download" })
30
41
 
31
- await expect(el).shadowDom.to.be.accessible()
42
+ await expect(el).dom.to.be.accessible()
32
43
  })
33
44
 
34
45
  it("passes the a11y audit with a link", async () => {
35
- const el = await defaultFixture({
46
+ const el = await wrappedFixture({
36
47
  label: "Download",
37
48
  href: "https://zh.ch",
49
+ menuRole: "none",
38
50
  })
39
51
 
40
- await expect(el).shadowDom.to.be.accessible()
52
+ await expect(el).dom.to.be.accessible()
41
53
  })
42
54
 
43
55
  it("renders a label", async () => {
44
56
  const el = await defaultFixture({ label: "Download" })
45
57
 
46
- const button = el.shadowRoot.querySelector("button")
47
-
48
- expect(button).to.have.trimmed.text("Download")
58
+ expect(el).to.have.trimmed.text("Download")
49
59
  })
50
60
 
51
61
  it("renders a button", async () => {
@@ -66,90 +76,61 @@ describe("LeuMenuItem", () => {
66
76
 
67
77
  expect(link).to.exist
68
78
  expect(link).to.have.attribute("href", "https://zh.ch")
69
- expect(link).to.have.trimmed.text("Kanton Zürich")
70
- })
71
-
72
- it("renders a before icon", async () => {
73
- const el = await defaultFixture({ label: "Download", before: "download" })
74
-
75
- const before = el.shadowRoot.querySelector(".before")
76
- expect(before).to.exist
77
-
78
- expect(el).shadowDom.to.equal(
79
- "<button class='button'><span class='before'></span><span class='label'>Download</span></button>"
80
- )
79
+ expect(el).to.have.trimmed.text("Kanton Zürich")
81
80
  })
82
81
 
83
- it("renders a before label", async () => {
84
- const el = await defaultFixture({ label: "Download", before: "DE" })
85
-
86
- const before = el.shadowRoot.querySelector(".before")
87
- expect(before).to.exist
88
- expect(before).to.have.trimmed.text("DE")
89
-
90
- expect(el).shadowDom.to.equal(
91
- "<button class='button'><span class='before'>DE</span><span class='label'>Download</span></button>"
92
- )
93
- })
94
-
95
- it("renders a before placeholder", async () => {
96
- const el = await defaultFixture({ label: "Download", before: "EMPTY" })
97
-
98
- const before = el.shadowRoot.querySelector(".before")
99
- expect(before).to.exist
100
- expect(before).to.not.have.trimmed.text()
101
-
102
- const iconPlaceholder = before.querySelector(".icon-placeholder")
103
- expect(iconPlaceholder).to.exist
104
-
105
- expect(el).shadowDom.to.equal(
106
- "<button class='button'><span class='before'><div class='icon-placeholder'></div></span><span class='label'>Download</span></button>"
107
- )
108
- })
109
-
110
- it("renders a after icon", async () => {
111
- const el = await defaultFixture({ label: "Download", after: "download" })
112
-
113
- const after = el.shadowRoot.querySelector(".after")
114
- expect(after).to.exist
82
+ it("sets the aria-disabled attribute to the button", async () => {
83
+ const el = await defaultFixture({ label: "Download", disabled: true })
115
84
 
116
- expect(el).shadowDom.to.equal(
117
- "<button class='button'><span class='label'>Download</span><span class='after'></span></button>"
118
- )
85
+ const button = el.shadowRoot.querySelector("button")
86
+ expect(button).to.have.attribute("aria-disabled", "true")
119
87
  })
120
88
 
121
- it("renders a after label", async () => {
122
- const el = await defaultFixture({ label: "Download", after: "DE" })
123
-
124
- const after = el.shadowRoot.querySelector(".after")
125
- expect(after).to.exist
126
- expect(after).to.have.trimmed.text("DE")
89
+ it("sets the defined role on the button", async () => {
90
+ const el = await defaultFixture({
91
+ label: "Download",
92
+ })
127
93
 
128
- expect(el).shadowDom.to.equal(
129
- "<button class='button'><span class='label'>Download</span><span class='after'>DE</span></button>"
130
- )
131
- })
94
+ const button = el.shadowRoot.querySelector("button")
95
+ expect(button).to.have.attribute("role", "menuitem")
132
96
 
133
- it("renders a after placeholder", async () => {
134
- const el = await defaultFixture({ label: "Download", after: "EMPTY" })
97
+ el.componentRole = "menuitemcheckbox"
98
+ await elementUpdated(el)
99
+ expect(button).to.have.attribute("role", "menuitemcheckbox")
135
100
 
136
- const after = el.shadowRoot.querySelector(".after")
137
- expect(after).to.exist
138
- expect(after).to.not.have.trimmed.text()
101
+ el.componentRole = "menuitemradio"
102
+ await elementUpdated(el)
103
+ expect(button).to.have.attribute("role", "menuitemradio")
139
104
 
140
- const iconPlaceholder = after.querySelector(".icon-placeholder")
141
- expect(iconPlaceholder).to.exist
105
+ el.componentRole = "option"
106
+ await elementUpdated(el)
107
+ expect(button).to.have.attribute("role", "option")
142
108
 
143
- expect(el).shadowDom.to.equal(
144
- "<button class='button'><span class='label'>Download</span><span class='after'><div class='icon-placeholder'></div></span></button>"
145
- )
109
+ el.componentRole = "none"
110
+ await elementUpdated(el)
111
+ expect(button).to.not.have.attribute("role")
146
112
  })
147
113
 
148
- it("passes the disabled attribute to the button", async () => {
149
- const el = await defaultFixture({ label: "Download", disabled: true })
114
+ it("adds either the aria-checked or aria-selected attribute to the button when the item is active", async () => {
115
+ const el = await defaultFixture({
116
+ label: "Download",
117
+ componentRole: "option",
118
+ active: true,
119
+ })
150
120
 
151
121
  const button = el.shadowRoot.querySelector("button")
152
- expect(button).to.have.attribute("disabled")
122
+ expect(button).to.have.attribute("aria-selected", "true")
123
+ expect(button).not.to.have.attribute("aria-checked")
124
+
125
+ el.componentRole = "menuitemcheckbox"
126
+ await elementUpdated(el)
127
+ expect(button).to.have.attribute("aria-checked", "true")
128
+ expect(button).not.to.have.attribute("aria-selected")
129
+
130
+ el.componentRole = "menuitemradio"
131
+ await elementUpdated(el)
132
+ expect(button).to.have.attribute("aria-checked", "true")
133
+ expect(button).not.to.have.attribute("aria-selected")
153
134
  })
154
135
 
155
136
  it("lets the click event bubble up", async () => {
@@ -177,4 +158,29 @@ describe("LeuMenuItem", () => {
177
158
 
178
159
  expect(clickSpy).to.have.not.been.called
179
160
  })
161
+
162
+ it("reflects the tabbable property as tabindex to the button", async () => {
163
+ const el = await defaultFixture({ label: "Download", tabbable: true })
164
+
165
+ const button = el.shadowRoot.querySelector("button")
166
+ expect(button).to.have.attribute("tabindex", "0")
167
+
168
+ el.tabbable = false
169
+ await elementUpdated(el)
170
+ expect(button).to.have.attribute("tabindex", "-1")
171
+
172
+ el.tabbable = undefined
173
+ await elementUpdated(el)
174
+ expect(button).to.not.have.attribute("tabindex")
175
+ })
176
+
177
+ it("returns the value or label when getValue is called", async () => {
178
+ const el = await defaultFixture({ label: "Download " })
179
+
180
+ expect(el.getValue()).to.equal("Download")
181
+
182
+ el.value = "download-01"
183
+
184
+ expect(el.getValue()).to.equal("download-01")
185
+ })
180
186
  })