@statistikzh/leu 0.1.0 → 0.3.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 (47) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +27 -2
  3. package/dist/Button.js +24 -12
  4. package/dist/Checkbox.js +6 -4
  5. package/dist/CheckboxGroup.js +2 -1
  6. package/dist/{Chip-389013ff.js → Chip-5f70d04f.js} +2 -1
  7. package/dist/ChipGroup.js +2 -1
  8. package/dist/ChipLink.js +3 -2
  9. package/dist/ChipRemovable.js +1 -1
  10. package/dist/ChipSelectable.js +7 -4
  11. package/dist/Dropdown.js +2 -1
  12. package/dist/Input.js +38 -15
  13. package/dist/Menu.js +10 -3
  14. package/dist/MenuItem.js +4 -2
  15. package/dist/Pagination.js +4 -2
  16. package/dist/Radio.js +6 -4
  17. package/dist/RadioGroup.js +2 -1
  18. package/dist/Select.js +25 -8
  19. package/dist/Table.js +10 -5
  20. package/dist/index.js +1 -1
  21. package/dist/leu-chip-link.js +1 -1
  22. package/dist/leu-chip-removable.js +1 -1
  23. package/dist/leu-chip-selectable.js +1 -1
  24. package/dist/theme.css +7 -7
  25. package/package.json +6 -1
  26. package/src/components/accordion/Accordion.js +102 -0
  27. package/src/components/accordion/accordion.css +160 -0
  28. package/src/components/accordion/leu-accordion.js +3 -0
  29. package/src/components/accordion/stories/accordion.stories.js +55 -0
  30. package/src/components/accordion/test/accordion.test.js +22 -0
  31. package/src/components/button/Button.js +13 -13
  32. package/src/components/checkbox/Checkbox.js +3 -4
  33. package/src/components/checkbox/CheckboxGroup.js +1 -1
  34. package/src/components/chip/Chip.js +1 -1
  35. package/src/components/chip/ChipGroup.js +1 -1
  36. package/src/components/chip/ChipLink.js +1 -2
  37. package/src/components/chip/ChipSelectable.js +3 -3
  38. package/src/components/dropdown/Dropdown.js +1 -1
  39. package/src/components/input/Input.js +25 -15
  40. package/src/components/menu/MenuItem.js +2 -2
  41. package/src/components/menu/menu.css +9 -3
  42. package/src/components/pagination/Pagination.js +2 -2
  43. package/src/components/radio/Radio.js +3 -4
  44. package/src/components/radio/RadioGroup.js +1 -1
  45. package/src/components/select/Select.js +29 -9
  46. package/src/components/table/Table.js +5 -5
  47. package/src/styles/custom-properties.css +7 -7
@@ -0,0 +1,102 @@
1
+ import { LitElement, nothing } from "lit"
2
+ import { html, unsafeStatic } from "lit/static-html.js"
3
+
4
+ import { defineElement } from "../../lib/defineElement.js"
5
+ import styles from "./accordion.css"
6
+
7
+ /**
8
+ * @tagname leu-accordion
9
+ * @slot content - The content of the accordion. No styles will be applied to the content.
10
+ * @prop {Number} headingLevel - The heading level of the accordion title. Must be between 1 and 6.
11
+ * @prop {Boolean} open - The expanded state of the accordion.
12
+ * @prop {String} label - The label (title) of the accordion.
13
+ * @prop {String} labelPrefix - The prefix of the accordion label. e.g. "01"
14
+ * @attr {Number} heading-level - The heading level of the accordion title. Must be between 1 and 6.
15
+ * @attr {String} label-prefix - The prefix of the accordion label. e.g. "01"
16
+ */
17
+ export class LeuAccordion extends LitElement {
18
+ static styles = styles
19
+
20
+ /** @internal */
21
+ static shadowRootOptions = {
22
+ ...LitElement.shadowRootOptions,
23
+ delegatesFocus: true,
24
+ }
25
+
26
+ static properties = {
27
+ headingLevel: { type: Number, attribute: "heading-level", reflect: true },
28
+ open: { type: Boolean, reflect: true },
29
+ label: { type: String, reflect: true },
30
+ labelPrefix: { type: String, attribute: "label-prefix", reflect: true },
31
+ }
32
+
33
+ constructor() {
34
+ super()
35
+ this.headingLevel = 2
36
+ this.open = false
37
+ this.label = ""
38
+ this.labelPrefix = ""
39
+ }
40
+
41
+ /**
42
+ * Determines the heading tag of the accordion toggle.
43
+ * The headingLevel shouldn't be used directly to render the heading tag
44
+ * in order to avoid XSS issues.
45
+ * @returns {String} The heading tag of the accordion toggle.
46
+ * @internal
47
+ */
48
+ _getHeadingTag() {
49
+ let level = 2
50
+ if (this.headingLevel > 0 && this.headingLevel < 7) {
51
+ level = this.headingLevel
52
+ }
53
+
54
+ return `h${level}`
55
+ }
56
+
57
+ /**
58
+ * Toggles the accordion open state.
59
+ * @internal
60
+ */
61
+ _handleToggleClick() {
62
+ this.open = !this.open
63
+ }
64
+
65
+ render() {
66
+ const hTag = this._getHeadingTag()
67
+
68
+ /* The eslint rules don't recognize html import from lit/static-html.js */
69
+ /* eslint-disable lit/binding-positions, lit/no-invalid-html */
70
+ return html`<${unsafeStatic(hTag)} class="heading"><button
71
+ id="toggle"
72
+ type="button"
73
+ class="button"
74
+ aria-controls="content"
75
+ aria-expanded="${this.open}"
76
+ @click=${this._handleToggleClick}
77
+ >
78
+ ${
79
+ this.labelPrefix
80
+ ? html`<span class="label-prefix">${this.labelPrefix}</span>`
81
+ : nothing
82
+ }
83
+ <span class="label">${this.label}</span>
84
+ <div class="plus"></div>
85
+ </button></${unsafeStatic(hTag)}>
86
+ <div
87
+ id="content"
88
+ class="content"
89
+ aria-labelledby="toggle"
90
+ role="region"
91
+ ?hidden=${!this.open}
92
+ >
93
+ <slot name="content"></slot>
94
+ </div>
95
+ <hr class="divider" />`
96
+ }
97
+ /* eslint-enable lit/binding-positions, lit/no-invalid-html */
98
+ }
99
+
100
+ export function defineAccordionElements() {
101
+ defineElement("accordion", LeuAccordion)
102
+ }
@@ -0,0 +1,160 @@
1
+ @import url("../../styles/custom-media.css");
2
+
3
+ :host,
4
+ :host * {
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ :host {
9
+ --accordion-font-regular: var(--leu-font-regular);
10
+ --accordion-font-black: var(--leu-font-black);
11
+
12
+ --accordion-toggle-font: var(--accordion-font-black);
13
+
14
+ --label-color: var(--leu-color-black-60);
15
+ --label-color-active: var(--leu-color-black-100);
16
+
17
+ --divider-color: var(--leu-color-black-20);
18
+ --divider-color-active: var(--leu-color-black-100);
19
+
20
+ --transition: 0.1s ease;
21
+
22
+ font-family: var(--chip-font-regular);
23
+
24
+ position: relative;
25
+ display: grid;
26
+ grid-template-rows: auto 0fr;
27
+
28
+ transition: grid-template-rows var(--transition);
29
+ }
30
+
31
+ :host([open]) {
32
+ grid-template-rows: auto 1fr;
33
+ }
34
+
35
+ .heading {
36
+ margin: 0;
37
+ }
38
+
39
+ .button {
40
+ width: 100%;
41
+ background: none;
42
+ padding: 1rem 0;
43
+ margin: none;
44
+ cursor: pointer;
45
+
46
+ border: none;
47
+
48
+ color: var(--label-color);
49
+ font-family: var(--accordion-toggle-font);
50
+ font-size: 1rem;
51
+ line-height: 1.5rem;
52
+ text-align: left;
53
+
54
+ display: flex;
55
+ gap: 0.25rem;
56
+
57
+ transition: color var(--transition);
58
+
59
+ @media (--viewport-regular) {
60
+ font-size: 1.125rem;
61
+ gap: 0.8rem;
62
+ }
63
+
64
+ @media (--viewport-xlarge) {
65
+ font-size: 1.25rem;
66
+ line-height: 1.625rem;
67
+ }
68
+ }
69
+
70
+ .button:focus-visible {
71
+ outline: 2px solid var(--leu-color-func-cyan);
72
+ outline-offset: 4px;
73
+ }
74
+
75
+ .button:hover,
76
+ .button:focus-visible,
77
+ :host([open]) .button {
78
+ color: var(--label-color-active);
79
+ }
80
+
81
+ .plus {
82
+ position: relative;
83
+ flex: 0 0 1rem;
84
+ aspect-ratio: 1;
85
+ align-self: center;
86
+ margin-inline: 0.5rem;
87
+ }
88
+
89
+ .plus::before,
90
+ .plus::after {
91
+ content: "";
92
+
93
+ position: absolute;
94
+ top: calc(50% - 0.0625rem);
95
+ left: 0;
96
+
97
+ display: block;
98
+ width: 1rem;
99
+ height: 0.125rem;
100
+ aspect-ratio: 1 / 0.125;
101
+ background-color: currentcolor;
102
+ transition: transform var(--transition);
103
+ }
104
+
105
+ .plus::before {
106
+ transform: rotate(90deg);
107
+ }
108
+
109
+ :host([open]) .plus::before {
110
+ transform: rotate(180deg);
111
+ }
112
+
113
+ .label {
114
+ display: block;
115
+ flex-grow: 1;
116
+ }
117
+
118
+ .content {
119
+ overflow: hidden;
120
+ transition: visibility var(--transition), opacity var(--transition);
121
+ }
122
+
123
+ .content[hidden] {
124
+ display: block;
125
+ visibility: hidden;
126
+ opacity: 0;
127
+ }
128
+
129
+ slot[name="content"] {
130
+ display: block;
131
+ padding: 0.5rem 0 1.5rem;
132
+
133
+ @media (--viewport-medium) {
134
+ padding: 0.75rem 0 1.5rem;
135
+ }
136
+
137
+ @media (--viewport-xlarge) {
138
+ padding: 1rem 0 2.5rem;
139
+ }
140
+ }
141
+
142
+ .divider {
143
+ position: absolute;
144
+ top: 100%;
145
+ left: 0;
146
+
147
+ width: 100%;
148
+ height: 1px;
149
+ margin: 0;
150
+
151
+ border: none;
152
+ background-color: var(--divider-color);
153
+
154
+ transition: transform var(--transition), background-color var(--transition);
155
+ }
156
+
157
+ :host(:not([open])) .heading:is(:hover, :focus-visible) ~ .divider {
158
+ background-color: var(--divider-color-active);
159
+ transform: scaleY(3);
160
+ }
@@ -0,0 +1,3 @@
1
+ import { defineAccordionElements } from "./Accordion.js"
2
+
3
+ defineAccordionElements()
@@ -0,0 +1,55 @@
1
+ import { html } from "lit"
2
+ import { ifDefined } from "lit/directives/if-defined.js"
3
+
4
+ import "../leu-accordion.js"
5
+
6
+ export default {
7
+ title: "Accordion",
8
+ component: "leu-accordion",
9
+ argTypes: {
10
+ headingLevel: {
11
+ control: {
12
+ type: "select",
13
+ options: [0, 1, 2, 3, 4, 5, 6],
14
+ },
15
+ },
16
+ content: {
17
+ control: {
18
+ type: "text",
19
+ },
20
+ },
21
+ },
22
+ }
23
+
24
+ function Template(args) {
25
+ return html` <leu-accordion
26
+ heading-level=${ifDefined(args.headingLevel)}
27
+ label=${ifDefined(args.label)}
28
+ label-prefix=${ifDefined(args.labelPrefix)}
29
+ >
30
+ <div slot="content">${args.content}</div>
31
+ </leu-accordion>`
32
+ }
33
+
34
+ export const Regular = Template.bind({})
35
+ Regular.args = {
36
+ headingLevel: 2,
37
+ label:
38
+ "Akkordeontitel der lang und noch länger werden kann und dann umbricht",
39
+ content: `Regular Interessierte können ab sofort die Genauigkeit ihrer Smartphones
40
+ und Navigationsgeräte überprüfen. Die Baudirektion hat beim Landesmuseum
41
+ in Zürich einen Kontrollpunkt beim Landesmuseum in Zürich einen
42
+ Kontrollpunktfür mobile Geräte eingerichtet – den ersten in der
43
+ Schweiz.P, Helvetic Roman Interessierte können ab sofort die Genauigkeit
44
+ ihrer Smartphones und Navigationsgeräte überprüfen.
45
+
46
+ Die Zürich einen Kontrollpunktfür mobile einen Kontrollpunkt beim
47
+ Landesmuseum in Zürich einen Kontrollpunktfür mobile Geräte eingerichtet
48
+ – den ersten in der Schweiz.`,
49
+ }
50
+
51
+ export const Prefix = Template.bind({})
52
+ Prefix.args = {
53
+ ...Regular.args,
54
+ labelPrefix: "01",
55
+ }
@@ -0,0 +1,22 @@
1
+ import { html } from "lit"
2
+ import { fixture, expect } from "@open-wc/testing"
3
+
4
+ import "../leu-accordion.js"
5
+
6
+ async function defaultFixture() {
7
+ return fixture(html` <leu-accordion /> `)
8
+ }
9
+
10
+ describe("LeuAccordion", () => {
11
+ it("is a defined element", async () => {
12
+ const el = await customElements.get("leu-accordion")
13
+
14
+ await expect(el).not.to.be.undefined
15
+ })
16
+
17
+ it("passes the a11y audit", async () => {
18
+ const el = await defaultFixture()
19
+
20
+ await expect(el).shadowDom.to.be.accessible()
21
+ })
22
+ })
@@ -40,19 +40,19 @@ export class LeuButton extends LitElement {
40
40
  }
41
41
 
42
42
  static properties = {
43
- label: { type: String },
44
- icon: { type: String },
45
- iconAfter: { type: String },
46
- size: { type: String },
47
- variant: { type: String },
48
- type: { type: String },
49
-
50
- disabled: { type: Boolean },
51
- round: { type: Boolean },
52
- active: { type: Boolean },
53
- inverted: { type: Boolean },
54
- expanded: { type: String },
55
- fluid: { type: Boolean },
43
+ label: { type: String, reflect: true },
44
+ icon: { type: String, reflect: true },
45
+ iconAfter: { type: String, reflect: true },
46
+ size: { type: String, reflect: true },
47
+ variant: { type: String, reflect: true },
48
+ type: { type: String, reflect: true },
49
+
50
+ disabled: { type: Boolean, reflect: true },
51
+ round: { type: Boolean, reflect: true },
52
+ active: { type: Boolean, reflect: true },
53
+ inverted: { type: Boolean, reflect: true },
54
+ expanded: { type: String, reflect: true },
55
+ fluid: { type: Boolean, reflect: true },
56
56
  }
57
57
 
58
58
  constructor() {
@@ -93,16 +93,15 @@ export class LeuCheckbox extends LitElement {
93
93
  static properties = {
94
94
  checked: { type: Boolean, reflect: true },
95
95
  disabled: { type: Boolean, reflect: true },
96
- identifier: { type: String },
97
- value: { type: String },
98
- name: { type: String },
96
+ identifier: { type: String, reflect: true },
97
+ value: { type: String, reflect: true },
98
+ name: { type: String, reflect: true },
99
99
  }
100
100
 
101
101
  constructor() {
102
102
  super()
103
103
  this.checked = false
104
104
  this.disabled = false
105
- this.tabIndex = 0
106
105
 
107
106
  this.checkIcon = Icon("check")
108
107
  }
@@ -39,7 +39,7 @@ export class LeuCheckboxGroup extends LitElement {
39
39
  `
40
40
 
41
41
  static properties = {
42
- orientation: { type: String },
42
+ orientation: { type: String, reflect: true },
43
43
  }
44
44
 
45
45
  constructor() {
@@ -13,7 +13,7 @@ export class LeuChipBase extends LitElement {
13
13
  }
14
14
 
15
15
  static properties = {
16
- inverted: { type: Boolean },
16
+ inverted: { type: Boolean, reflect: true },
17
17
  }
18
18
 
19
19
  constructor() {
@@ -18,7 +18,7 @@ export class LeuChipGroup extends LitElement {
18
18
  static styles = styles
19
19
 
20
20
  static properties = {
21
- selectionMode: { type: String, attribute: "selection-mode" },
21
+ selectionMode: { type: String, attribute: "selection-mode", reflect: true },
22
22
  }
23
23
 
24
24
  constructor() {
@@ -20,8 +20,7 @@ export class LeuChipLink extends LeuChipBase {
20
20
  * The size of the chip
21
21
  * @type {keyof typeof SIZES}
22
22
  */
23
- size: { type: String },
24
-
23
+ size: { type: String, reflect: true },
25
24
  href: { type: String, reflect: true },
26
25
  }
27
26
 
@@ -27,7 +27,7 @@ export class LeuChipSelectable extends LeuChipBase {
27
27
  * @type {keyof typeof SIZES}
28
28
  * @default "regular"
29
29
  */
30
- size: { type: String },
30
+ size: { type: String, reflect: true },
31
31
 
32
32
  /**
33
33
  * The variant of the chip. Has an effect not only on the visual appearance but also on the behavior.
@@ -37,10 +37,10 @@ export class LeuChipSelectable extends LeuChipBase {
37
37
  * @type {keyof typeof VARIANTS}
38
38
  * @default "default"
39
39
  */
40
- variant: { type: String },
40
+ variant: { type: String, reflect: true },
41
41
 
42
42
  selected: { type: Boolean, reflect: true },
43
- value: { type: String },
43
+ value: { type: String, reflect: true },
44
44
  }
45
45
 
46
46
  constructor() {
@@ -13,7 +13,7 @@ export class LeuDropdown extends LitElement {
13
13
  static styles = styles
14
14
 
15
15
  static properties = {
16
- label: { type: String },
16
+ label: { type: String, reflect: true },
17
17
  expanded: { type: Boolean, reflect: true },
18
18
  }
19
19
 
@@ -65,30 +65,38 @@ const VALIDATION_MESSAGES = {
65
65
  export class LeuInput extends LitElement {
66
66
  static styles = styles
67
67
 
68
+ /**
69
+ * @internal
70
+ */
71
+ static shadowRootOptions = {
72
+ ...LitElement.shadowRootOptions,
73
+ delegatesFocus: true,
74
+ }
75
+
68
76
  static properties = {
69
77
  disabled: { type: Boolean, reflect: true },
70
78
  required: { type: Boolean, reflect: true },
71
79
  clearable: { type: Boolean, reflect: true },
72
80
 
73
- value: { type: String },
74
- name: { type: String },
75
- error: { type: String },
81
+ value: { type: String, reflect: true },
82
+ name: { type: String, reflect: true },
83
+ error: { type: String, reflect: true },
76
84
 
77
- label: { type: String },
78
- prefix: { type: String },
79
- suffix: { type: String },
80
- size: { type: String },
81
- icon: { type: String },
85
+ label: { type: String, reflect: true },
86
+ prefix: { type: String, reflect: true },
87
+ suffix: { type: String, reflect: true },
88
+ size: { type: String, reflect: true },
89
+ icon: { type: String, reflect: true },
82
90
 
83
91
  /* Validation attributes */
84
- pattern: { type: String },
85
- type: { type: String },
86
- min: { type: Number },
87
- max: { type: Number },
88
- maxlength: { type: Number },
89
- minlength: { type: Number },
92
+ pattern: { type: String, reflect: true },
93
+ type: { type: String, reflect: true },
94
+ min: { type: Number, reflect: true },
95
+ max: { type: Number, reflect: true },
96
+ maxlength: { type: Number, reflect: true },
97
+ minlength: { type: Number, reflect: true },
90
98
  validationMessages: { type: Object },
91
- novalidate: { type: Boolean },
99
+ novalidate: { type: Boolean, reflect: true },
92
100
 
93
101
  /** @type {ValidityState} */
94
102
  _validity: { state: true },
@@ -223,6 +231,8 @@ export class LeuInput extends LitElement {
223
231
  clear() {
224
232
  this.value = ""
225
233
 
234
+ this._inputRef.value.focus()
235
+
226
236
  this.dispatchEvent(
227
237
  new CustomEvent("input", { bubbles: true, composed: true })
228
238
  )
@@ -25,13 +25,13 @@ export class LeuMenuItem extends LitElement {
25
25
  * If no icon with this value is found, it will be displayed as text.
26
26
  * If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
27
27
  */
28
- before: { type: String },
28
+ before: { type: String, reflect: true },
29
29
  /**
30
30
  * Can be either an icon name or a text
31
31
  * If no icon with this value is found, it will be displayed as text
32
32
  * If the value is "EMPTY", an empty placeholder with the size of an icon will be displayed.
33
33
  */
34
- after: { type: String },
34
+ after: { type: String, reflect: true },
35
35
  active: { type: Boolean, reflect: true },
36
36
  highlighted: { type: Boolean, reflect: true },
37
37
  disabled: { type: Boolean, reflect: true },
@@ -7,8 +7,14 @@
7
7
  --menu-divider-color: var(--leu-color-black-transp-20);
8
8
  }
9
9
 
10
+ /*
11
+ * Set styles with important as the hr element is part of the
12
+ * light dom and therefore is affected by the global styles.
13
+ */
10
14
  :host ::slotted(hr) {
11
- border: 0;
12
- border-top: 1px solid var(--menu-divider-color);
13
- margin: 0.5rem 0;
15
+ border: 0 !important;
16
+ border-top: 1px solid var(--menu-divider-color) !important;
17
+ margin: 0.5rem 0 !important;
18
+ padding: 0 !important;
19
+ background: none !important;
14
20
  }
@@ -18,8 +18,8 @@ export class LeuPagination extends LitElement {
18
18
 
19
19
  static properties = {
20
20
  page: { type: Number, reflect: true },
21
- itemsOnAPage: { type: Number },
22
- dataLength: { type: Number },
21
+ itemsOnAPage: { type: Number, reflect: true },
22
+ dataLength: { type: Number, reflect: true },
23
23
 
24
24
  _minPage: { type: Number, state: true },
25
25
  }
@@ -16,16 +16,15 @@ export class LeuRadio extends LitElement {
16
16
  static properties = {
17
17
  checked: { type: Boolean, reflect: true },
18
18
  disabled: { type: Boolean, reflect: true },
19
- identifier: { type: String },
20
- value: { type: String },
21
- name: { type: String },
19
+ identifier: { type: String, reflect: true },
20
+ value: { type: String, reflect: true },
21
+ name: { type: String, reflect: true },
22
22
  }
23
23
 
24
24
  constructor() {
25
25
  super()
26
26
  this.checked = false
27
27
  this.disabled = false
28
- this.tabIndex = 0
29
28
  }
30
29
 
31
30
  handleChange(event) {
@@ -39,7 +39,7 @@ export class LeuRadioGroup extends LitElement {
39
39
  `
40
40
 
41
41
  static properties = {
42
- orientation: { type: String },
42
+ orientation: { type: String, reflect: true },
43
43
  }
44
44
 
45
45
  constructor() {