@statistikzh/leu 0.18.1 → 0.19.1
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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/dist/Accordion.js +1 -1
- package/dist/Button.js +1 -1
- package/dist/ButtonGroup.js +1 -1
- package/dist/ChartWrapper.js +1 -1
- package/dist/Checkbox.js +1 -1
- package/dist/CheckboxGroup.js +1 -1
- package/dist/Chip.js +1 -1
- package/dist/ChipGroup.js +1 -1
- package/dist/ChipLink.js +1 -1
- package/dist/ChipRemovable.js +1 -1
- package/dist/ChipSelectable.js +1 -1
- package/dist/Dialog.js +1 -1
- package/dist/Dropdown.js +1 -1
- package/dist/FileInput.d.ts +60 -0
- package/dist/FileInput.js +387 -0
- package/dist/Icon.js +1 -1
- package/dist/Input.js +1 -3
- package/dist/{LeuElement-DgiuzsX9.js → LeuElement-CRdVLttR.js} +1 -1
- package/dist/Menu.js +1 -1
- package/dist/MenuItem.js +1 -1
- package/dist/Message.js +1 -1
- package/dist/Pagination.js +1 -1
- package/dist/Placeholder.js +1 -1
- package/dist/Popup.js +1 -1
- package/dist/ProgressBar.d.ts +29 -0
- package/dist/ProgressBar.js +166 -0
- package/dist/Radio.js +1 -1
- package/dist/RadioGroup.js +1 -1
- package/dist/Range.js +1 -1
- package/dist/ScrollTop.js +1 -1
- package/dist/Select.js +1 -1
- package/dist/Spinner.js +1 -1
- package/dist/Table.js +1 -1
- package/dist/Tag.js +1 -1
- package/dist/VisuallyHidden.js +1 -1
- package/dist/components/file-input/FileInput.d.ts +55 -0
- package/dist/components/file-input/FileInput.d.ts.map +1 -0
- package/dist/components/file-input/leu-file-input.d.ts +3 -0
- package/dist/components/file-input/leu-file-input.d.ts.map +1 -0
- package/dist/components/file-input/stories/file-input.stories.d.ts +30 -0
- package/dist/components/file-input/stories/file-input.stories.d.ts.map +1 -0
- package/dist/components/file-input/test/file-input.test.d.ts +2 -0
- package/dist/components/file-input/test/file-input.test.d.ts.map +1 -0
- package/dist/components/input/Input.d.ts.map +1 -1
- package/dist/components/progress-bar/ProgressBar.d.ts +25 -0
- package/dist/components/progress-bar/ProgressBar.d.ts.map +1 -0
- package/dist/components/progress-bar/leu-progress-bar.d.ts +3 -0
- package/dist/components/progress-bar/leu-progress-bar.d.ts.map +1 -0
- package/dist/components/progress-bar/stories/progress-bar.stories.d.ts +48 -0
- package/dist/components/progress-bar/stories/progress-bar.stories.d.ts.map +1 -0
- package/dist/components/progress-bar/test/progress-bar.test.d.ts +2 -0
- package/dist/components/progress-bar/test/progress-bar.test.d.ts.map +1 -0
- package/dist/index.js +1 -1
- package/dist/leu-accordion.js +1 -1
- package/dist/leu-button-group.js +1 -1
- package/dist/leu-button.js +1 -1
- package/dist/leu-chart-wrapper.js +1 -1
- package/dist/leu-checkbox-group.js +1 -1
- package/dist/leu-checkbox.js +1 -1
- package/dist/leu-chip-group.js +1 -1
- package/dist/leu-chip-link.js +1 -1
- package/dist/leu-chip-removable.js +1 -1
- package/dist/leu-chip-selectable.js +1 -1
- package/dist/leu-dialog.js +1 -1
- package/dist/leu-dropdown.js +1 -1
- package/dist/leu-file-input.d.ts +8 -0
- package/dist/leu-file-input.js +42 -0
- package/dist/leu-icon.js +1 -1
- package/dist/leu-input.js +1 -1
- package/dist/leu-menu-item.js +1 -1
- package/dist/leu-menu.js +1 -1
- package/dist/leu-message.js +1 -1
- package/dist/leu-pagination.js +1 -1
- package/dist/leu-placeholder.js +1 -1
- package/dist/leu-popup.js +1 -1
- package/dist/leu-progress-bar.d.ts +4 -0
- package/dist/leu-progress-bar.js +10 -0
- package/dist/leu-radio-group.js +1 -1
- package/dist/leu-radio.js +1 -1
- package/dist/leu-range.js +1 -1
- package/dist/leu-scroll-top.js +1 -1
- package/dist/leu-select.js +1 -1
- package/dist/leu-spinner.js +1 -1
- package/dist/leu-table.js +1 -1
- package/dist/leu-tag.js +1 -1
- package/dist/leu-visually-hidden.js +1 -1
- package/dist/vscode.html-custom-data.json +65 -0
- package/dist/vue/index.d.ts +48 -0
- package/dist/web-types.json +119 -1
- package/package.json +1 -1
- package/scripts/generate-component/templates/test/[name].test.ts +4 -3
- package/src/components/file-input/FileInput.ts +271 -0
- package/src/components/file-input/file-input.css +118 -0
- package/src/components/file-input/leu-file-input.ts +5 -0
- package/src/components/file-input/stories/file-input.stories.ts +38 -0
- package/src/components/file-input/test/file-input.test.ts +25 -0
- package/src/components/input/Input.ts +0 -3
- package/src/components/progress-bar/ProgressBar.ts +52 -0
- package/src/components/progress-bar/leu-progress-bar.ts +5 -0
- package/src/components/progress-bar/progress-bar.css +97 -0
- package/src/components/progress-bar/stories/progress-bar.stories.ts +39 -0
- package/src/components/progress-bar/test/progress-bar.test.ts +61 -0
|
@@ -0,0 +1,271 @@
|
|
|
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
|
+
public formResetCallback() {
|
|
104
|
+
this.files = []
|
|
105
|
+
this.input.value = ""
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
protected updateFormValue() {
|
|
109
|
+
const formData = new FormData()
|
|
110
|
+
|
|
111
|
+
const files = this.multiple ? this.files : this.files.slice(0, 1)
|
|
112
|
+
|
|
113
|
+
files.forEach((file) => {
|
|
114
|
+
formData.append(this.name, file)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
this.internals.setFormValue(formData)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
protected removeFile(fileToRemove: File) {
|
|
121
|
+
this.files = this.files.filter((file) => file !== fileToRemove)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected static formatFileSize(size: number) {
|
|
125
|
+
if (size < 1e3) {
|
|
126
|
+
return html`${size} bytes`
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (size >= 1e3 && size < 1e6) {
|
|
130
|
+
return html`${(size / 1e3).toFixed(1)} KB`
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return html`${(size / 1e6).toFixed(1)} MB`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected handleDragEnter = (event: DragEvent) => {
|
|
137
|
+
if (this.disabled) return
|
|
138
|
+
|
|
139
|
+
event.preventDefault()
|
|
140
|
+
event.stopPropagation()
|
|
141
|
+
this.isDragging = [...event.dataTransfer.items].some(
|
|
142
|
+
(item) => item.kind === "file",
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// eslint-disable-next-line class-methods-use-this
|
|
147
|
+
protected handleDragOver = (event: DragEvent) => {
|
|
148
|
+
if (this.disabled) return
|
|
149
|
+
|
|
150
|
+
event.preventDefault()
|
|
151
|
+
event.stopPropagation()
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
protected handleDragLeave = (event: DragEvent) => {
|
|
155
|
+
if (this.disabled) return
|
|
156
|
+
|
|
157
|
+
event.preventDefault()
|
|
158
|
+
event.stopPropagation()
|
|
159
|
+
this.isDragging = false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
protected handleDrop = (event: DragEvent) => {
|
|
163
|
+
if (this.disabled) return
|
|
164
|
+
|
|
165
|
+
event.preventDefault()
|
|
166
|
+
event.stopPropagation()
|
|
167
|
+
|
|
168
|
+
const dt = event.dataTransfer
|
|
169
|
+
const files = dt.files
|
|
170
|
+
const acceptedFiles = [...files].filter((file) => this.isAcceptedFile(file))
|
|
171
|
+
|
|
172
|
+
this.files = this.multiple
|
|
173
|
+
? this.files.concat(acceptedFiles)
|
|
174
|
+
: acceptedFiles.slice(0, 1)
|
|
175
|
+
this.isDragging = false
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
isAcceptedFile(file: File): boolean {
|
|
179
|
+
const acceptedTypes = this.accept.split(",").map((type) => type.trim())
|
|
180
|
+
const mimeType = file.type
|
|
181
|
+
|
|
182
|
+
for (const acceptedType of acceptedTypes) {
|
|
183
|
+
// Handle file extensions (e.g. .jpg, .png)
|
|
184
|
+
if (acceptedType.startsWith(".")) {
|
|
185
|
+
const name = file.name.toLowerCase()
|
|
186
|
+
const extension = acceptedType.toLowerCase()
|
|
187
|
+
|
|
188
|
+
if (name.endsWith(extension)) {
|
|
189
|
+
return true
|
|
190
|
+
}
|
|
191
|
+
// Handle wildcard types (e.g. image/*)
|
|
192
|
+
} else if (/^\w+\/\*$/.test(acceptedType)) {
|
|
193
|
+
if (mimeType.split("/")[0] === acceptedType.split("/")[0]) {
|
|
194
|
+
return true
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (mimeType === acceptedType) {
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return false
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
render() {
|
|
207
|
+
const dropzoneClasses = {
|
|
208
|
+
dropzone: true,
|
|
209
|
+
"dropzone--dragging": this.isDragging,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return html`
|
|
213
|
+
<div class="container">
|
|
214
|
+
<label class="label" for="input">${this.label}</label>
|
|
215
|
+
<leu-visually-hidden>
|
|
216
|
+
<input
|
|
217
|
+
id="input"
|
|
218
|
+
type="file"
|
|
219
|
+
?multiple=${this.multiple}
|
|
220
|
+
accept=${ifDefined(this.accept)}
|
|
221
|
+
?disabled=${this.disabled}
|
|
222
|
+
@input=${this.handleInput}
|
|
223
|
+
/>
|
|
224
|
+
</leu-visually-hidden>
|
|
225
|
+
<div
|
|
226
|
+
class=${classMap(dropzoneClasses)}
|
|
227
|
+
@dragenter=${this.handleDragEnter}
|
|
228
|
+
@dragover=${this.handleDragOver}
|
|
229
|
+
@dragleave=${this.handleDragLeave}
|
|
230
|
+
@drop=${this.handleDrop}
|
|
231
|
+
>
|
|
232
|
+
<slot class="dropzone__text"
|
|
233
|
+
><p>Zum Hochladen Dateien ziehen und hier ablegen.</p></slot
|
|
234
|
+
>
|
|
235
|
+
<leu-button
|
|
236
|
+
variant="secondary"
|
|
237
|
+
?disabled=${this.disabled}
|
|
238
|
+
@click=${() => this.input.click()}
|
|
239
|
+
>
|
|
240
|
+
Datei auswählen
|
|
241
|
+
<leu-icon name="upload" slot="after"></leu-icon>
|
|
242
|
+
</leu-button>
|
|
243
|
+
</div>
|
|
244
|
+
${this.files.length > 0
|
|
245
|
+
? html`<ul class="file-list">
|
|
246
|
+
${this.files.map(
|
|
247
|
+
(file) =>
|
|
248
|
+
html`<li class="file">
|
|
249
|
+
<strong class="file__name">${file.name}</strong>
|
|
250
|
+
<p class="file__size">
|
|
251
|
+
<span class="file__size-label">Grösse:</span>
|
|
252
|
+
${LeuFileInput.formatFileSize(file.size)}
|
|
253
|
+
</p>
|
|
254
|
+
<leu-button
|
|
255
|
+
round
|
|
256
|
+
class="file__button"
|
|
257
|
+
label="Datei entfernen"
|
|
258
|
+
size="small"
|
|
259
|
+
variant="secondary"
|
|
260
|
+
?disabled=${this.disabled}
|
|
261
|
+
@click=${() => this.removeFile(file)}
|
|
262
|
+
><leu-icon name="delete"></leu-icon
|
|
263
|
+
></leu-button>
|
|
264
|
+
</li>`,
|
|
265
|
+
)}
|
|
266
|
+
</ul>`
|
|
267
|
+
: nothing}
|
|
268
|
+
</div>
|
|
269
|
+
`
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -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,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
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { html, nothing } from "lit"
|
|
2
|
+
import { ifDefined } from "lit/directives/if-defined.js"
|
|
3
|
+
import { property } from "lit/decorators.js"
|
|
4
|
+
|
|
5
|
+
import { LeuElement } from "../../lib/LeuElement.js"
|
|
6
|
+
|
|
7
|
+
import styles from "./progress-bar.css"
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* An indicator showing the completion progress of a task
|
|
11
|
+
*
|
|
12
|
+
* @tagname leu-progress-bar
|
|
13
|
+
*/
|
|
14
|
+
export class LeuProgressBar extends LeuElement {
|
|
15
|
+
static styles = [LeuElement.styles, styles]
|
|
16
|
+
|
|
17
|
+
static shadowRootOptions = {
|
|
18
|
+
...LeuElement.shadowRootOptions,
|
|
19
|
+
delegatesFocus: true,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Progress as a percentage from 0 to 100 */
|
|
23
|
+
@property({ type: Number, reflect: true })
|
|
24
|
+
value: number = 0
|
|
25
|
+
|
|
26
|
+
/** Label that is displayed below the progress bar */
|
|
27
|
+
@property({ type: String, reflect: true })
|
|
28
|
+
label: string = ""
|
|
29
|
+
|
|
30
|
+
/** Whether the progress bar is in indeterminate state. */
|
|
31
|
+
@property({ type: Boolean, reflect: true })
|
|
32
|
+
indeterminate: boolean = false
|
|
33
|
+
|
|
34
|
+
render() {
|
|
35
|
+
return html`
|
|
36
|
+
<progress
|
|
37
|
+
class="progress"
|
|
38
|
+
max=${ifDefined(!this.indeterminate ? 100 : undefined)}
|
|
39
|
+
value=${ifDefined(!this.indeterminate ? this.value : undefined)}
|
|
40
|
+
id="progress"
|
|
41
|
+
></progress>
|
|
42
|
+
<div class="info">
|
|
43
|
+
${this.label
|
|
44
|
+
? html`<label class="label" for="progress">${this.label}</label>`
|
|
45
|
+
: html`<div class="label label--placeholder"></div>`}
|
|
46
|
+
${this.indeterminate
|
|
47
|
+
? nothing
|
|
48
|
+
: html`<span class="value">${Math.round(this.value)} %</span>`}
|
|
49
|
+
</div>
|
|
50
|
+
`
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/* :host {
|
|
2
|
+
--progress-bar-font-regular: var(--leu-font-family-regular);
|
|
3
|
+
--progress-bar-font-black: var(--leu-font-family-black);
|
|
4
|
+
|
|
5
|
+
font-family: var(--progress-bar-font-regular);
|
|
6
|
+
} */
|
|
7
|
+
|
|
8
|
+
.progress {
|
|
9
|
+
--_height: 0.5rem;
|
|
10
|
+
--_border-radius: 0.25rem;
|
|
11
|
+
--_progress-color: var(--leu-color-func-cyan);
|
|
12
|
+
--_indeterminate-animation: indeterminate 2s ease infinite;
|
|
13
|
+
--_indeterminate-width: 25%;
|
|
14
|
+
|
|
15
|
+
appearance: none;
|
|
16
|
+
display: block;
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: var(--_height);
|
|
19
|
+
|
|
20
|
+
background-color: var(--leu-color-black-transp-10);
|
|
21
|
+
border: none;
|
|
22
|
+
border-radius: var(--_border-radius);
|
|
23
|
+
margin-bottom: 0.5rem;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.progress::-webkit-progress-bar {
|
|
27
|
+
background: transparent;
|
|
28
|
+
border-radius: var(--_border-radius);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Progress bar */
|
|
32
|
+
.progress::-moz-progress-bar {
|
|
33
|
+
background-color: var(--_progress-color);
|
|
34
|
+
border-radius: var(--_border-radius);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.progress::-webkit-progress-value {
|
|
38
|
+
background-color: var(--_progress-color);
|
|
39
|
+
border-radius: var(--_border-radius);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Indeterminate state */
|
|
43
|
+
.progress:indeterminate::-moz-progress-bar {
|
|
44
|
+
width: var(--_indeterminate-width);
|
|
45
|
+
animation: var(--_indeterminate-animation);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Safari */
|
|
49
|
+
.progress:indeterminate::-webkit-progress-value {
|
|
50
|
+
display: block;
|
|
51
|
+
width: var(--_indeterminate-width);
|
|
52
|
+
animation: var(--_indeterminate-animation);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Chrome */
|
|
56
|
+
.progress:indeterminate::before {
|
|
57
|
+
content: "";
|
|
58
|
+
display: block;
|
|
59
|
+
width: var(--_indeterminate-width);
|
|
60
|
+
height: var(--_height);
|
|
61
|
+
background-color: var(--_progress-color);
|
|
62
|
+
border-radius: var(--_border-radius);
|
|
63
|
+
animation: var(--_indeterminate-animation);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@keyframes indeterminate {
|
|
67
|
+
0% {
|
|
68
|
+
margin-left: 0%;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
50% {
|
|
72
|
+
margin-left: 75%;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
100% {
|
|
76
|
+
margin-left: 0%;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Info section (text label and value) */
|
|
81
|
+
.info {
|
|
82
|
+
display: flex;
|
|
83
|
+
justify-content: space-between;
|
|
84
|
+
align-items: center;
|
|
85
|
+
flex-wrap: wrap;
|
|
86
|
+
gap: 0.25rem;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.label,
|
|
90
|
+
.value {
|
|
91
|
+
font: var(--leu-t-tiny-regular-font);
|
|
92
|
+
color: var(--leu-color-black-60);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.value {
|
|
96
|
+
/* margin-left: auto; */
|
|
97
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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-progress-bar.js"
|
|
6
|
+
import { LeuProgressBar } from "../ProgressBar.js"
|
|
7
|
+
|
|
8
|
+
type StoryArgs = LeuProgressBar
|
|
9
|
+
type Story = StoryObj<StoryArgs>
|
|
10
|
+
|
|
11
|
+
export default {
|
|
12
|
+
title: "Components/ProgressBar",
|
|
13
|
+
component: "leu-progress-bar",
|
|
14
|
+
} satisfies Meta<StoryArgs>
|
|
15
|
+
|
|
16
|
+
const Template: Story = {
|
|
17
|
+
render: ({ label, value, indeterminate }) =>
|
|
18
|
+
html` <leu-progress-bar
|
|
19
|
+
.label="${ifDefined(label)}"
|
|
20
|
+
.value="${ifDefined(value)}"
|
|
21
|
+
?indeterminate=${indeterminate}
|
|
22
|
+
></leu-progress-bar>`,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const Regular = {
|
|
26
|
+
...Template,
|
|
27
|
+
args: {
|
|
28
|
+
value: 50,
|
|
29
|
+
label: "Datei hochladen",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Indeterminate = {
|
|
34
|
+
...Template,
|
|
35
|
+
args: {
|
|
36
|
+
indeterminate: true,
|
|
37
|
+
label: "Datei hochladen",
|
|
38
|
+
},
|
|
39
|
+
}
|