@statistikzh/leu 0.18.0 → 0.19.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 (106) hide show
  1. package/.release-please-manifest.json +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/dist/Accordion.js +1 -1
  4. package/dist/Button.js +1 -1
  5. package/dist/ButtonGroup.js +1 -1
  6. package/dist/ChartWrapper.js +1 -1
  7. package/dist/Checkbox.js +1 -1
  8. package/dist/CheckboxGroup.js +1 -1
  9. package/dist/Chip.js +1 -1
  10. package/dist/ChipGroup.js +1 -1
  11. package/dist/ChipLink.js +1 -1
  12. package/dist/ChipRemovable.js +1 -1
  13. package/dist/ChipSelectable.js +1 -1
  14. package/dist/Dialog.js +1 -1
  15. package/dist/Dropdown.js +1 -1
  16. package/dist/FileInput.d.ts +59 -0
  17. package/dist/FileInput.js +383 -0
  18. package/dist/Icon.js +1 -1
  19. package/dist/Input.d.ts +0 -9
  20. package/dist/Input.js +4 -25
  21. package/dist/{LeuElement-DlQXnrk8.js → LeuElement-CKq5epyY.js} +1 -1
  22. package/dist/Menu.js +1 -1
  23. package/dist/MenuItem.js +1 -1
  24. package/dist/Message.js +1 -1
  25. package/dist/Pagination.js +1 -1
  26. package/dist/Placeholder.js +1 -1
  27. package/dist/Popup.js +1 -1
  28. package/dist/ProgressBar.d.ts +29 -0
  29. package/dist/ProgressBar.js +166 -0
  30. package/dist/Radio.js +1 -1
  31. package/dist/RadioGroup.js +1 -1
  32. package/dist/Range.js +1 -1
  33. package/dist/ScrollTop.js +1 -1
  34. package/dist/Select.js +1 -1
  35. package/dist/Spinner.js +1 -1
  36. package/dist/Table.js +1 -1
  37. package/dist/Tag.js +1 -1
  38. package/dist/VisuallyHidden.js +1 -1
  39. package/dist/components/file-input/FileInput.d.ts +54 -0
  40. package/dist/components/file-input/FileInput.d.ts.map +1 -0
  41. package/dist/components/file-input/leu-file-input.d.ts +3 -0
  42. package/dist/components/file-input/leu-file-input.d.ts.map +1 -0
  43. package/dist/components/file-input/stories/file-input.stories.d.ts +30 -0
  44. package/dist/components/file-input/stories/file-input.stories.d.ts.map +1 -0
  45. package/dist/components/file-input/test/file-input.test.d.ts +2 -0
  46. package/dist/components/file-input/test/file-input.test.d.ts.map +1 -0
  47. package/dist/components/input/Input.d.ts +0 -9
  48. package/dist/components/input/Input.d.ts.map +1 -1
  49. package/dist/components/progress-bar/ProgressBar.d.ts +25 -0
  50. package/dist/components/progress-bar/ProgressBar.d.ts.map +1 -0
  51. package/dist/components/progress-bar/leu-progress-bar.d.ts +3 -0
  52. package/dist/components/progress-bar/leu-progress-bar.d.ts.map +1 -0
  53. package/dist/components/progress-bar/stories/progress-bar.stories.d.ts +48 -0
  54. package/dist/components/progress-bar/stories/progress-bar.stories.d.ts.map +1 -0
  55. package/dist/components/progress-bar/test/progress-bar.test.d.ts +2 -0
  56. package/dist/components/progress-bar/test/progress-bar.test.d.ts.map +1 -0
  57. package/dist/index.js +1 -1
  58. package/dist/leu-accordion.js +1 -1
  59. package/dist/leu-button-group.js +1 -1
  60. package/dist/leu-button.js +1 -1
  61. package/dist/leu-chart-wrapper.js +1 -1
  62. package/dist/leu-checkbox-group.js +1 -1
  63. package/dist/leu-checkbox.js +1 -1
  64. package/dist/leu-chip-group.js +1 -1
  65. package/dist/leu-chip-link.js +1 -1
  66. package/dist/leu-chip-removable.js +1 -1
  67. package/dist/leu-chip-selectable.js +1 -1
  68. package/dist/leu-dialog.js +1 -1
  69. package/dist/leu-dropdown.js +1 -1
  70. package/dist/leu-file-input.d.ts +8 -0
  71. package/dist/leu-file-input.js +42 -0
  72. package/dist/leu-icon.js +1 -1
  73. package/dist/leu-input.js +1 -1
  74. package/dist/leu-menu-item.js +1 -1
  75. package/dist/leu-menu.js +1 -1
  76. package/dist/leu-message.js +1 -1
  77. package/dist/leu-pagination.js +1 -1
  78. package/dist/leu-placeholder.js +1 -1
  79. package/dist/leu-popup.js +1 -1
  80. package/dist/leu-progress-bar.d.ts +4 -0
  81. package/dist/leu-progress-bar.js +10 -0
  82. package/dist/leu-radio-group.js +1 -1
  83. package/dist/leu-radio.js +1 -1
  84. package/dist/leu-range.js +1 -1
  85. package/dist/leu-scroll-top.js +1 -1
  86. package/dist/leu-select.js +1 -1
  87. package/dist/leu-spinner.js +1 -1
  88. package/dist/leu-table.js +1 -1
  89. package/dist/leu-tag.js +1 -1
  90. package/dist/leu-visually-hidden.js +1 -1
  91. package/dist/vscode.html-custom-data.json +65 -0
  92. package/dist/vue/index.d.ts +48 -0
  93. package/dist/web-types.json +119 -1
  94. package/package.json +1 -1
  95. package/scripts/generate-component/templates/test/[name].test.ts +4 -3
  96. package/src/components/file-input/FileInput.ts +266 -0
  97. package/src/components/file-input/file-input.css +118 -0
  98. package/src/components/file-input/leu-file-input.ts +5 -0
  99. package/src/components/file-input/stories/file-input.stories.ts +38 -0
  100. package/src/components/file-input/test/file-input.test.ts +25 -0
  101. package/src/components/input/Input.ts +3 -29
  102. package/src/components/progress-bar/ProgressBar.ts +52 -0
  103. package/src/components/progress-bar/leu-progress-bar.ts +5 -0
  104. package/src/components/progress-bar/progress-bar.css +97 -0
  105. package/src/components/progress-bar/stories/progress-bar.stories.ts +39 -0
  106. package/src/components/progress-bar/test/progress-bar.test.ts +61 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "@statistikzh/leu",
4
- "version": "0.18.0",
4
+ "version": "0.19.0",
5
5
  "description-markup": "markdown",
6
6
  "contributions": {
7
7
  "html": {
@@ -595,6 +595,81 @@
595
595
  "events": []
596
596
  }
597
597
  },
598
+ {
599
+ "name": "leu-file-input",
600
+ "description": "\n---\n",
601
+ "doc-url": "",
602
+ "attributes": [
603
+ {
604
+ "name": "label",
605
+ "value": { "type": "string", "default": "\"\"" }
606
+ },
607
+ {
608
+ "name": "accept",
609
+ "description": "A list of acceptable file types. Must be a comma-separated list of [unique file type specifiers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers).",
610
+ "value": { "type": "string", "default": "\"\"" }
611
+ },
612
+ {
613
+ "name": "disabled",
614
+ "description": "Whether the file input is disabled.",
615
+ "value": { "type": "boolean", "default": "false" }
616
+ },
617
+ {
618
+ "name": "multiple",
619
+ "description": "Whether the file input allows multiple files.",
620
+ "value": { "type": "boolean", "default": "false" }
621
+ },
622
+ {
623
+ "name": "required",
624
+ "description": "Whether the file input is required.",
625
+ "value": { "type": "boolean", "default": "false" }
626
+ },
627
+ {
628
+ "name": "variant",
629
+ "description": "The variant of the file list item. `filled` renders a gray background.",
630
+ "value": {
631
+ "type": "\"filled\" | \"transparent\"",
632
+ "default": "\"filled\""
633
+ }
634
+ }
635
+ ],
636
+ "events": [],
637
+ "js": {
638
+ "properties": [
639
+ { "name": "label", "type": "string" },
640
+ {
641
+ "name": "accept",
642
+ "description": "A list of acceptable file types. Must be a comma-separated list of [unique file type specifiers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers).",
643
+ "type": "string"
644
+ },
645
+ {
646
+ "name": "disabled",
647
+ "description": "Whether the file input is disabled.",
648
+ "type": "boolean"
649
+ },
650
+ {
651
+ "name": "multiple",
652
+ "description": "Whether the file input allows multiple files.",
653
+ "type": "boolean"
654
+ },
655
+ {
656
+ "name": "required",
657
+ "description": "Whether the file input is required.",
658
+ "type": "boolean"
659
+ },
660
+ {
661
+ "name": "variant",
662
+ "description": "The variant of the file list item. `filled` renders a gray background.",
663
+ "type": "\"filled\" | \"transparent\""
664
+ },
665
+ { "name": "files", "type": "File[]" },
666
+ { "name": "input", "type": "HTMLInputElement" },
667
+ { "name": "form" },
668
+ { "name": "name" }
669
+ ],
670
+ "events": []
671
+ }
672
+ },
598
673
  {
599
674
  "name": "leu-icon",
600
675
  "description": "A component to render all defined zhWeb icons.\nThe `fill` of the icon is set to `currentColor` and\ncan be overriden by setting the css `color` property.\nIf the icon name is not found, a placeholder will be displayed.\n---\n\n\n### **CSS Properties:**\n - **--leu-icon-size** - The size of the icon. _(default: undefined)_",
@@ -1199,6 +1274,49 @@
1199
1274
  "events": []
1200
1275
  }
1201
1276
  },
1277
+ {
1278
+ "name": "leu-progress-bar",
1279
+ "description": "An indicator showing the completion progress of a task\n---\n",
1280
+ "doc-url": "",
1281
+ "attributes": [
1282
+ {
1283
+ "name": "value",
1284
+ "description": "Progress as a percentage from 0 to 100",
1285
+ "value": { "type": "number", "default": "0" }
1286
+ },
1287
+ {
1288
+ "name": "label",
1289
+ "description": "Label that is displayed below the progress bar",
1290
+ "value": { "type": "string", "default": "\"\"" }
1291
+ },
1292
+ {
1293
+ "name": "indeterminate",
1294
+ "description": "Whether the progress bar is in indeterminate state.",
1295
+ "value": { "type": "boolean", "default": "false" }
1296
+ }
1297
+ ],
1298
+ "events": [],
1299
+ "js": {
1300
+ "properties": [
1301
+ {
1302
+ "name": "value",
1303
+ "description": "Progress as a percentage from 0 to 100",
1304
+ "type": "number"
1305
+ },
1306
+ {
1307
+ "name": "label",
1308
+ "description": "Label that is displayed below the progress bar",
1309
+ "type": "string"
1310
+ },
1311
+ {
1312
+ "name": "indeterminate",
1313
+ "description": "Whether the progress bar is in indeterminate state.",
1314
+ "type": "boolean"
1315
+ }
1316
+ ],
1317
+ "events": []
1318
+ }
1319
+ },
1202
1320
  {
1203
1321
  "name": "leu-radio",
1204
1322
  "description": "\n---\n",
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "UI component library of the canton of zurich",
4
4
  "license": "MIT",
5
5
  "author": "statistikzh",
6
- "version": "0.18.0",
6
+ "version": "0.19.0",
7
7
  "type": "module",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.js",
@@ -2,16 +2,17 @@ import { html } from "lit"
2
2
  import { fixture, expect } from "@open-wc/testing"
3
3
 
4
4
  import "../[namespace]-[name].js"
5
+ import { type [Namespace][Name] } from "../[Name].js"
5
6
 
6
7
  async function defaultFixture() {
7
- return fixture(html`<[namespace]-[name]></[namespace]-[name]>`)
8
+ return fixture<[Namespace][Name]>(html`<[namespace]-[name]></[namespace]-[name]>`)
8
9
  }
9
10
 
10
11
  describe("[Namespace][Name]", () => {
11
12
  it("is a defined element", async () => {
12
- const el = await customElements.get("[namespace]-[name]")
13
+ const el = customElements.get("[namespace]-[name]")
13
14
 
14
- await expect(el).not.to.be.undefined
15
+ expect(el).not.to.be.undefined
15
16
  })
16
17
 
17
18
  it("passes the a11y audit", async () => {
@@ -0,0 +1,266 @@
1
+ import { html, nothing } from "lit"
2
+ import { property, query, state } from "lit/decorators.js"
3
+ import { ifDefined } from "lit/directives/if-defined.js"
4
+ import { classMap } from "lit/directives/class-map.js"
5
+
6
+ import { LeuElement } from "../../lib/LeuElement.js"
7
+
8
+ import styles from "./file-input.css"
9
+ import { LeuButton } from "../../index.js"
10
+ import { LeuIcon } from "../icon/leu-icon.js"
11
+ import { LeuVisuallyHidden } from "../visually-hidden/VisuallyHidden.js"
12
+
13
+ /**
14
+ * @tagname leu-file-input
15
+ */
16
+ export class LeuFileInput extends LeuElement {
17
+ static dependencies = {
18
+ "leu-icon": LeuIcon,
19
+ "leu-button": LeuButton,
20
+ "leu-visually-hidden": LeuVisuallyHidden,
21
+ }
22
+
23
+ static styles = [LeuElement.styles, styles]
24
+
25
+ static shadowRootOptions = {
26
+ ...LeuElement.shadowRootOptions,
27
+ delegatesFocus: true,
28
+ }
29
+
30
+ static formAssociated = true
31
+
32
+ protected internals: ElementInternals
33
+
34
+ @property({ type: String })
35
+ label: string = ""
36
+
37
+ /** A list of acceptable file types. Must be a comma-separated list of [unique file type specifiers](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#unique_file_type_specifiers). */
38
+ @property({ type: String })
39
+ accept: string = ""
40
+
41
+ /** Whether the file input is disabled. */
42
+ @property({ type: Boolean, reflect: true })
43
+ disabled: boolean = false
44
+
45
+ /** Whether the file input allows multiple files. */
46
+ @property({ type: Boolean, reflect: true })
47
+ multiple: boolean = false
48
+
49
+ /** Whether the file input is required. */
50
+ @property({ type: Boolean, reflect: true })
51
+ required: boolean = false
52
+
53
+ /** The variant of the file list item. `filled` renders a gray background. */
54
+ @property({ type: String, reflect: true })
55
+ variant: "filled" | "transparent" = "filled"
56
+
57
+ @state()
58
+ public files: File[] = []
59
+
60
+ @state()
61
+ private isDragging: boolean = false
62
+
63
+ @query('input[type="file"]')
64
+ input: HTMLInputElement
65
+
66
+ constructor() {
67
+ super()
68
+ // Initialize the ElementInternals for form association
69
+ this.internals = this.attachInternals()
70
+ }
71
+
72
+ get form() {
73
+ return this.internals.form
74
+ }
75
+
76
+ get name() {
77
+ return this.getAttribute("name")
78
+ }
79
+
80
+ updated(changedProperties) {
81
+ if (
82
+ changedProperties.has("files") ||
83
+ changedProperties.has("disabled") ||
84
+ changedProperties.has("multiple")
85
+ ) {
86
+ this.updateFormValue()
87
+ }
88
+ }
89
+
90
+ protected handleInput() {
91
+ // Append selected files
92
+ if (this.input.files) {
93
+ const acceptableFiles = [...this.input.files].filter((file) =>
94
+ this.isAcceptedFile(file),
95
+ )
96
+
97
+ this.files = this.multiple
98
+ ? this.files.concat(acceptableFiles)
99
+ : acceptableFiles.slice(0, 1)
100
+ }
101
+ }
102
+
103
+ protected updateFormValue() {
104
+ const formData = new FormData()
105
+
106
+ const files = this.multiple ? this.files : this.files.slice(0, 1)
107
+
108
+ files.forEach((file) => {
109
+ formData.append(this.name, file)
110
+ })
111
+
112
+ this.internals.setFormValue(formData)
113
+ }
114
+
115
+ protected removeFile(fileToRemove: File) {
116
+ this.files = this.files.filter((file) => file !== fileToRemove)
117
+ }
118
+
119
+ protected static formatFileSize(size: number) {
120
+ if (size < 1e3) {
121
+ return html`${size}&nbsp;bytes`
122
+ }
123
+
124
+ if (size >= 1e3 && size < 1e6) {
125
+ return html`${(size / 1e3).toFixed(1)}&nbsp;KB`
126
+ }
127
+
128
+ return html`${(size / 1e6).toFixed(1)}&nbsp;MB`
129
+ }
130
+
131
+ protected handleDragEnter = (event: DragEvent) => {
132
+ if (this.disabled) return
133
+
134
+ event.preventDefault()
135
+ event.stopPropagation()
136
+ this.isDragging = [...event.dataTransfer.items].some(
137
+ (item) => item.kind === "file",
138
+ )
139
+ }
140
+
141
+ // eslint-disable-next-line class-methods-use-this
142
+ protected handleDragOver = (event: DragEvent) => {
143
+ if (this.disabled) return
144
+
145
+ event.preventDefault()
146
+ event.stopPropagation()
147
+ }
148
+
149
+ protected handleDragLeave = (event: DragEvent) => {
150
+ if (this.disabled) return
151
+
152
+ event.preventDefault()
153
+ event.stopPropagation()
154
+ this.isDragging = false
155
+ }
156
+
157
+ protected handleDrop = (event: DragEvent) => {
158
+ if (this.disabled) return
159
+
160
+ event.preventDefault()
161
+ event.stopPropagation()
162
+
163
+ const dt = event.dataTransfer
164
+ const files = dt.files
165
+ const acceptedFiles = [...files].filter((file) => this.isAcceptedFile(file))
166
+
167
+ this.files = this.multiple
168
+ ? this.files.concat(acceptedFiles)
169
+ : acceptedFiles.slice(0, 1)
170
+ this.isDragging = false
171
+ }
172
+
173
+ isAcceptedFile(file: File): boolean {
174
+ const acceptedTypes = this.accept.split(",").map((type) => type.trim())
175
+ const mimeType = file.type
176
+
177
+ for (const acceptedType of acceptedTypes) {
178
+ // Handle file extensions (e.g. .jpg, .png)
179
+ if (acceptedType.startsWith(".")) {
180
+ const name = file.name.toLowerCase()
181
+ const extension = acceptedType.toLowerCase()
182
+
183
+ if (name.endsWith(extension)) {
184
+ return true
185
+ }
186
+ // Handle wildcard types (e.g. image/*)
187
+ } else if (/^\w+\/\*$/.test(acceptedType)) {
188
+ if (mimeType.split("/")[0] === acceptedType.split("/")[0]) {
189
+ return true
190
+ }
191
+ }
192
+
193
+ if (mimeType === acceptedType) {
194
+ return true
195
+ }
196
+ }
197
+
198
+ return false
199
+ }
200
+
201
+ render() {
202
+ const dropzoneClasses = {
203
+ dropzone: true,
204
+ "dropzone--dragging": this.isDragging,
205
+ }
206
+
207
+ return html`
208
+ <div class="container">
209
+ <label class="label" for="input">${this.label}</label>
210
+ <leu-visually-hidden>
211
+ <input
212
+ id="input"
213
+ type="file"
214
+ ?multiple=${this.multiple}
215
+ accept=${ifDefined(this.accept)}
216
+ ?disabled=${this.disabled}
217
+ @input=${this.handleInput}
218
+ />
219
+ </leu-visually-hidden>
220
+ <div
221
+ class=${classMap(dropzoneClasses)}
222
+ @dragenter=${this.handleDragEnter}
223
+ @dragover=${this.handleDragOver}
224
+ @dragleave=${this.handleDragLeave}
225
+ @drop=${this.handleDrop}
226
+ >
227
+ <slot class="dropzone__text"
228
+ ><p>Zum Hochladen Dateien ziehen und hier ablegen.</p></slot
229
+ >
230
+ <leu-button
231
+ variant="secondary"
232
+ ?disabled=${this.disabled}
233
+ @click=${() => this.input.click()}
234
+ >
235
+ Datei auswählen
236
+ <leu-icon name="upload" slot="after"></leu-icon>
237
+ </leu-button>
238
+ </div>
239
+ ${this.files.length > 0
240
+ ? html`<ul class="file-list">
241
+ ${this.files.map(
242
+ (file) =>
243
+ html`<li class="file">
244
+ <strong class="file__name">${file.name}</strong>
245
+ <p class="file__size">
246
+ <span class="file__size-label">Grösse:</span>
247
+ ${LeuFileInput.formatFileSize(file.size)}
248
+ </p>
249
+ <leu-button
250
+ round
251
+ class="file__button"
252
+ label="Datei entfernen"
253
+ size="small"
254
+ variant="secondary"
255
+ ?disabled=${this.disabled}
256
+ @click=${() => this.removeFile(file)}
257
+ ><leu-icon name="delete"></leu-icon
258
+ ></leu-button>
259
+ </li>`,
260
+ )}
261
+ </ul>`
262
+ : nothing}
263
+ </div>
264
+ `
265
+ }
266
+ }
@@ -0,0 +1,118 @@
1
+ @import url("../../styles/custom-media.css");
2
+
3
+ :host {
4
+ --file-input-font-regular: var(--leu-font-family-regular);
5
+ --file-input-font-black: var(--leu-font-family-black);
6
+
7
+ font-family: var(--file-input-font-regular);
8
+ }
9
+
10
+ .label {
11
+ display: block;
12
+ margin-bottom: 0.5rem;
13
+ font: var(--leu-t-curve-regular-black-font);
14
+ }
15
+
16
+ :host([required]) .label::after {
17
+ content: "*";
18
+ }
19
+
20
+ .dropzone {
21
+ display: flex;
22
+ flex-direction: column;
23
+ align-items: center;
24
+ text-align: center;
25
+
26
+ border: 2px dashed var(--leu-color-black-20);
27
+ border-radius: 0.25rem;
28
+
29
+ padding: 2rem 1.5rem;
30
+
31
+ @media (--viewport-regular) {
32
+ padding: 2.5rem 2rem;
33
+ }
34
+
35
+ @media (--viewport-xlarge) {
36
+ padding: 3.5rem 2.5rem;
37
+ }
38
+ }
39
+
40
+ .dropzone--dragging {
41
+ border-color: var(--leu-color-black-60);
42
+ background-color: var(--leu-color-black-transp-5);
43
+ cursor: copy;
44
+ }
45
+
46
+ .dropzone__text {
47
+ font: var(--leu-t-curve-small-regular-font);
48
+ color: var(--leu-color-black-60);
49
+
50
+ margin-bottom: 0.75rem;
51
+
52
+ @media (--viewport-small) {
53
+ margin-bottom: 2rem;
54
+ }
55
+
56
+ @media (--viewport-regular) {
57
+ margin-bottom: 1.25rem;
58
+ }
59
+
60
+ @media (--viewport-xlarge) {
61
+ margin-bottom: 1.5rem;
62
+ }
63
+ }
64
+
65
+ .file-list {
66
+ list-style: none;
67
+ padding: 0;
68
+
69
+ display: flex;
70
+ flex-direction: column;
71
+ gap: 0.125rem;
72
+ }
73
+
74
+ .file {
75
+ display: grid;
76
+ grid-template-columns: minmax(0, 1fr) min-content;
77
+ gap: 0.25rem 0.5rem;
78
+
79
+ place-items: start;
80
+
81
+ background-color: var(--leu-color-black-10);
82
+ padding: 0.75rem 1.25rem;
83
+
84
+ @media (--viewport-regular) {
85
+ column-gap: 0.75rem;
86
+ }
87
+
88
+ @media (--viewport-large) {
89
+ column-gap: 1rem;
90
+ padding: 1.5rem 1rem;
91
+ }
92
+ }
93
+
94
+ .file__name {
95
+ grid-column: 1 / 2;
96
+ font: var(--leu-t-regular-black-font);
97
+ text-overflow: ellipsis;
98
+ overflow: hidden;
99
+ max-width: 100%;
100
+ }
101
+
102
+ .file__size {
103
+ grid-column: 1 / 2;
104
+ display: flex;
105
+ gap: 0.25rem;
106
+ margin: 0;
107
+ font: var(--leu-t-tiny-regular-font);
108
+ }
109
+
110
+ .file__size-label {
111
+ color: var(--leu-color-black-60);
112
+ }
113
+
114
+ .file__button {
115
+ grid-column: 2 / 3;
116
+ grid-row: 1 / 3;
117
+ align-self: center;
118
+ }
@@ -0,0 +1,5 @@
1
+ import { LeuFileInput } from "./FileInput.js"
2
+
3
+ export { LeuFileInput }
4
+
5
+ LeuFileInput.define("leu-file-input")
@@ -0,0 +1,38 @@
1
+ import { Meta, StoryObj } from "@storybook/web-components"
2
+ import { html } from "lit"
3
+ import { ifDefined } from "lit/directives/if-defined.js"
4
+
5
+ import "../leu-file-input.js"
6
+ import { LeuFileInput } from "../FileInput.js"
7
+
8
+ type StoryArgs = LeuFileInput
9
+ type Story = StoryObj<StoryArgs>
10
+
11
+ export default {
12
+ title: "Components/FileInput",
13
+ component: "leu-file-input",
14
+ } satisfies Meta<StoryArgs>
15
+
16
+ const Template: Story = {
17
+ render: (args: StoryArgs) => html`
18
+ <leu-file-input
19
+ name="file"
20
+ label=${ifDefined(args.label)}
21
+ accept=${ifDefined(args.accept)}
22
+ ?disabled=${args.disabled}
23
+ ?multiple=${args.multiple}
24
+ ?required=${args.required}
25
+ ></leu-file-input>
26
+ `,
27
+ }
28
+
29
+ export const Regular = {
30
+ ...Template,
31
+ args: {
32
+ accept: "image/*",
33
+ disabled: false,
34
+ multiple: false,
35
+ label: "Dokumente hochladen",
36
+ // Add default args here
37
+ },
38
+ }
@@ -0,0 +1,25 @@
1
+ import { html } from "lit"
2
+ import { fixture, expect } from "@open-wc/testing"
3
+
4
+ import "../leu-file-input.js"
5
+ import { type LeuFileInput } from "../FileInput.js"
6
+
7
+ async function defaultFixture() {
8
+ return fixture<LeuFileInput>(
9
+ html`<leu-file-input label="File upload"></leu-file-input>`,
10
+ )
11
+ }
12
+
13
+ describe("LeuFileInput", () => {
14
+ it("is a defined element", async () => {
15
+ const el = customElements.get("leu-file-input")
16
+
17
+ expect(el).not.to.be.undefined
18
+ })
19
+
20
+ it("passes the a11y audit", async () => {
21
+ const el = await defaultFixture()
22
+
23
+ await expect(el).shadowDom.to.be.accessible()
24
+ })
25
+ })
@@ -132,9 +132,6 @@ export class LeuInput extends LeuElement {
132
132
  this.novalidate = false
133
133
  this.value = ""
134
134
 
135
- /** @internal */
136
- this._identifier = ""
137
-
138
135
  /**
139
136
  * @internal
140
137
  * @type {import("lit/directives/ref.js").Ref<HTMLInputElement>}
@@ -250,29 +247,6 @@ export class LeuInput extends LeuElement {
250
247
  )
251
248
  }
252
249
 
253
- /**
254
- * Method for getting the id of the input element.
255
- * If the id attribute is set, the value of the id attribute is returned.
256
- * Otherwise a random id is generated and returned.
257
- *
258
- * @private
259
- * @returns {string} id
260
- */
261
- getId() {
262
- const id = this.getAttribute("id")
263
-
264
- if (id !== null && id !== "") {
265
- return id
266
- }
267
-
268
- if (this._identifier !== "") {
269
- return this._identifier
270
- }
271
-
272
- this._identifier = crypto.randomUUID()
273
- return this._identifier
274
- }
275
-
276
250
  /**
277
251
  * Merge custom and default validation messages.
278
252
  * A validation message can be a function or a string.
@@ -352,7 +326,7 @@ export class LeuInput extends LeuElement {
352
326
  }
353
327
 
354
328
  return html`
355
- <ul class="error" aria-errormessage=${`input-${this.getId()}`}>
329
+ <ul class="error" aria-errormessage="input">
356
330
  ${errorMessages.map(
357
331
  (message) => html`<li class="error-message">${message}</li>`,
358
332
  )}
@@ -412,7 +386,7 @@ export class LeuInput extends LeuElement {
412
386
  >
413
387
  <input
414
388
  class="input"
415
- id="input-${this.getId()}"
389
+ id="input"
416
390
  type=${this.type}
417
391
  name=${this.name}
418
392
  @change=${this.handleChange}
@@ -431,7 +405,7 @@ export class LeuInput extends LeuElement {
431
405
  ref=${ref(this._inputRef)}
432
406
  aria-invalid=${isInvalid}
433
407
  />
434
- <label for="input-${this.getId()}" class="label">${this.label}</label>
408
+ <label for="input" class="label">${this.label}</label>
435
409
  ${this.prefix
436
410
  ? html`<div class="prefix" .aria-hidden=${true}>${this.prefix}</div>`
437
411
  : nothing}