@vss-software/ui 0.1.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.
package/README.md ADDED
@@ -0,0 +1,1255 @@
1
+ # @vss-software/ui
2
+
3
+ Produktuebergreifendes Design-System mit Vue 3 Komponenten fuer die lumen.hr Plattform.
4
+
5
+ Stil: **Professional Calm** — dunkle Sidebar (Navy `#0B1929`), helle Arbeitsflaechen (Gray-50 `#F8FAFC`), Teal-Akzentfarbe (`#00BFA6`), Inter Schrift, WCAG 2.1 AA konform.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @vss-software/ui
11
+ # or
12
+ pnpm add @vss-software/ui
13
+ ```
14
+
15
+ ### Peer Dependencies
16
+
17
+ ```json
18
+ {
19
+ "vue": "^3.5.0",
20
+ "vue-router": "^4",
21
+ "tailwindcss": "^4.0.0"
22
+ }
23
+ ```
24
+
25
+ ## Setup
26
+
27
+ ### Option 1: Vue Plugin (alle Komponenten global registrieren)
28
+
29
+ ```js
30
+ import { createApp } from 'vue'
31
+ import LumenUI from '@vss-software/ui'
32
+ import '@vss-software/ui/styles'
33
+
34
+ const app = createApp(App)
35
+ app.use(LumenUI)
36
+ app.mount('#app')
37
+ ```
38
+
39
+ ### Option 2: Einzelimport (Tree-Shaking)
40
+
41
+ ```js
42
+ import { LuButton, LuCard, LuInput } from '@vss-software/ui'
43
+ import '@vss-software/ui/styles'
44
+ ```
45
+
46
+ ### Tailwind Integration
47
+
48
+ Da `@vss-software/ui` auf Tailwind CSS v4 basiert, muss das konsumierende Projekt ebenfalls Tailwind v4 verwenden. Die Styles werden ueber `@vss-software/ui/styles` eingebunden.
49
+
50
+ ---
51
+
52
+ ## Design Tokens
53
+
54
+ ### Farben
55
+
56
+ | Token | Hex | Verwendung |
57
+ | ------------ | --------- | ------------------------------------- |
58
+ | `navy-900` | `#0B1929` | Sidebar-Hintergrund, Login-BG |
59
+ | `navy-800` | `#0F2438` | Sidebar Hover-States |
60
+ | `teal-500` | `#00BFA6` | Primaere CTAs, Links, aktive Elemente |
61
+ | `teal-400` | `#26EDCA` | Hover auf dunklem Hintergrund |
62
+ | `teal-700` | `#00897B` | Pressed-States, dunkle Akzente |
63
+ | `lu-bg` | `#F8FAFC` | Seiten-Hintergrund (gray-50) |
64
+ | `lu-surface` | `#FFFFFF` | Karten, Modals, Tabellen |
65
+ | `lu-border` | `#E2E8F0` | Trennlinien, Karten-Raender |
66
+
67
+ ### Typografie
68
+
69
+ | Token | Groesse | Verwendung |
70
+ | -------------------- | ------- | ------------------------- |
71
+ | `--lu-text-display` | 30px | Display-Headlines |
72
+ | `--lu-text-h1` | 24px | Seiten-Ueberschriften |
73
+ | `--lu-text-h2` | 20px | Abschnitts-Ueberschriften |
74
+ | `--lu-text-h3` | 16px | Karten-Ueberschriften |
75
+ | `--lu-text-body` | 14px | Fliesstext |
76
+ | `--lu-text-body-sm` | 13px | Kompakter Text |
77
+ | `--lu-text-caption` | 12px | Beschriftungen |
78
+ | `--lu-text-overline` | 11px | Labels, Overlines |
79
+
80
+ Schriftarten: `Inter` (sans-serif), `JetBrains Mono` (monospace).
81
+
82
+ ### Spacing
83
+
84
+ 4px-Basisraster: `1=4px`, `2=8px`, `3=12px`, `4=16px`, `5=20px`, `6=24px`, `8=32px`, `10=40px`, `12=48px`.
85
+
86
+ ### Z-Index Skala
87
+
88
+ | Token | Wert | Verwendung |
89
+ | ------------------ | ---- | -------------------- |
90
+ | `z-header` | 30 | LuHeader |
91
+ | `z-sidebar` | 40 | LuSidebar |
92
+ | `z-dropdown` | 50 | LuDropdown, LuSelect |
93
+ | `z-modal-backdrop` | 60 | Modal-Backdrop |
94
+ | `z-modal` | 70 | LuModal |
95
+ | `z-toast` | 80 | LuToast |
96
+
97
+ ---
98
+
99
+ ## Komponenten
100
+
101
+ ### Layout
102
+
103
+ #### LuAppLayout
104
+
105
+ Haupt-Application-Shell mit Sidebar, Header und scrollbarem Content-Bereich.
106
+
107
+ ```vue
108
+ <LuAppLayout
109
+ :nav-items="navGroups"
110
+ :user="{ name: 'Max Mustermann', role: 'HR' }"
111
+ :notification-count="3"
112
+ :current-user-roles="['HR']"
113
+ v-model:sidebar-collapsed="collapsed"
114
+ :user-menu-items="[
115
+ { label: 'Profil', icon: 'User', action: 'profile' },
116
+ { label: 'Abmelden', icon: 'LogOut', action: 'logout' },
117
+ ]"
118
+ @notifications-click="openNotifications"
119
+ @user-menu-action="handleMenuAction"
120
+ >
121
+ <template #sidebar-logo>
122
+ <img src="/logo.svg" alt="Logo" />
123
+ </template>
124
+
125
+ <template #breadcrumb>
126
+ <LuBreadcrumb :items="[{ label: 'Dashboard', to: '/' }, { label: 'Mitarbeiter' }]" />
127
+ </template>
128
+
129
+ <RouterView />
130
+ </LuAppLayout>
131
+ ```
132
+
133
+ **Props:**
134
+
135
+ | Prop | Typ | Default | Beschreibung |
136
+ | ------------------- | ----------------------------------------- | ---------------------- | ------------------------------------- |
137
+ | `navItems` | `NavGroup[]` | `[]` | Navigationsgruppen fuer die Sidebar |
138
+ | `user` | `{ name, role?, roleLabel?, avatarSrc? }` | `{ name: 'Benutzer' }` | Aktuelle Benutzerinfo |
139
+ | `notificationCount` | `Number` | `0` | Anzahl ungelesener Benachrichtigungen |
140
+ | `currentUserRoles` | `String[]` | `[]` | Benutzerrollen fuer RBAC-Filterung |
141
+ | `sidebarCollapsed` | `Boolean` | `false` | Sidebar eingeklappt (v-model) |
142
+ | `userMenuItems` | `{ label, icon?, action }[]` | `[]` | Eintraege im Benutzer-Dropdown |
143
+
144
+ **Events:** `notifications-click`, `user-menu-action(action)`, `update:sidebarCollapsed(boolean)`
145
+
146
+ **Slots:** `default` (Content), `sidebar-logo`, `sidebar-bottom`, `breadcrumb`
147
+
148
+ ---
149
+
150
+ #### LuSidebar
151
+
152
+ Navigations-Sidebar mit RBAC-gefilterter Navigation, Collapse-Modus und responsivem Overlay.
153
+
154
+ ```vue
155
+ <LuSidebar
156
+ :items="[
157
+ {
158
+ label: 'Hauptmenue',
159
+ items: [
160
+ { icon: 'LayoutDashboard', label: 'Dashboard', to: '/' },
161
+ { icon: 'Users', label: 'Mitarbeiter', to: '/employees', roles: ['HR', 'GF'] },
162
+ { icon: 'Clock', label: 'Zeiterfassung', to: '/time-tracking' },
163
+ ],
164
+ },
165
+ ]"
166
+ v-model:open="sidebarOpen"
167
+ v-model:collapsed="sidebarCollapsed"
168
+ :current-user-roles="['HR']"
169
+ >
170
+ <template #logo>
171
+ <img src="/logo.svg" alt="Logo" />
172
+ </template>
173
+ </LuSidebar>
174
+ ```
175
+
176
+ **Props:**
177
+
178
+ | Prop | Typ | Default | Beschreibung |
179
+ | ------------------ | ------------ | ------- | ---------------------------------------- |
180
+ | `items` | `NavGroup[]` | `[]` | Navigationsgruppen mit Items |
181
+ | `open` | `Boolean` | `false` | Sichtbarkeit auf Mobile/Tablet (v-model) |
182
+ | `collapsed` | `Boolean` | `false` | Icons-Only-Modus auf Desktop (v-model) |
183
+ | `currentUserRoles` | `String[]` | `[]` | Rollen fuer RBAC-Filterung |
184
+
185
+ **Events:** `update:open(boolean)`, `update:collapsed(boolean)`
186
+
187
+ **Slots:** `logo`, `bottom`
188
+
189
+ **NavGroup-Format:**
190
+
191
+ ```js
192
+ {
193
+ label: 'Gruppenname',
194
+ items: [
195
+ { icon: 'LucideIconName', label: 'Link-Text', to: '/route', roles: ['HR'] }
196
+ ]
197
+ }
198
+ ```
199
+
200
+ Items mit `roles`-Array werden nur angezeigt, wenn der Benutzer mindestens eine passende Rolle hat.
201
+
202
+ ---
203
+
204
+ #### LuHeader
205
+
206
+ Applikations-Header (64px) mit Hamburger-Toggle, Benachrichtigungen und Benutzer-Dropdown.
207
+
208
+ ```vue
209
+ <LuHeader
210
+ :user="{ name: 'Max Mustermann', roleLabel: 'HR-Manager', avatarSrc: '/avatar.jpg' }"
211
+ :notification-count="5"
212
+ :pending-corrections-count="2"
213
+ :user-menu-items="menuItems"
214
+ @toggle-sidebar="toggleSidebar"
215
+ @notifications-click="openNotifications"
216
+ @corrections-click="openCorrections"
217
+ @user-menu-action="handleAction"
218
+ >
219
+ <template #breadcrumb>
220
+ <LuBreadcrumb />
221
+ </template>
222
+ </LuHeader>
223
+ ```
224
+
225
+ **Props:**
226
+
227
+ | Prop | Typ | Default | Beschreibung |
228
+ | ------------------------- | ----------------------------------------- | ---------------------- | ------------------------------------- |
229
+ | `user` | `{ name, role?, roleLabel?, avatarSrc? }` | `{ name: 'Benutzer' }` | Benutzerinfo fuer Avatar und Dropdown |
230
+ | `notificationCount` | `Number` | `0` | Badge-Zaehler (max "99+") |
231
+ | `pendingCorrectionsCount` | `Number` | `0` | Amber Badge fuer offene Korrekturen |
232
+ | `userMenuItems` | `{ label, icon?, action }[]` | `[]` | Dropdown-Eintraege |
233
+
234
+ **Events:** `toggle-sidebar`, `notifications-click`, `corrections-click`, `user-menu-action(action)`
235
+
236
+ **Slots:** `breadcrumb`
237
+
238
+ ---
239
+
240
+ #### LuBreadcrumb
241
+
242
+ Breadcrumb-Navigation — liest Items aus Props oder automatisch aus `route.meta.breadcrumb`.
243
+
244
+ ```vue
245
+ <LuBreadcrumb
246
+ :items="[
247
+ { label: 'Dashboard', to: '/' },
248
+ { label: 'Mitarbeiter', to: '/employees' },
249
+ { label: 'Max Mustermann' },
250
+ ]"
251
+ />
252
+ ```
253
+
254
+ **Props:**
255
+
256
+ | Prop | Typ | Default | Beschreibung |
257
+ | ------- | ------------------ | ----------- | ----------------------------------------------------------------- |
258
+ | `items` | `{ label, to? }[]` | `undefined` | Breadcrumb-Pfad. Ohne Prop wird `route.meta.breadcrumb` verwendet |
259
+
260
+ Das letzte Item wird als aktuelle Seite dargestellt (`aria-current="page"`).
261
+
262
+ ---
263
+
264
+ ### Formulare
265
+
266
+ #### LuInput
267
+
268
+ Vielseitiges Textfeld mit Label, Fehler/Hinweis, Prefix/Suffix und Passwort-Toggle.
269
+
270
+ ```vue
271
+ <LuInput
272
+ v-model="email"
273
+ type="email"
274
+ label="E-Mail-Adresse"
275
+ placeholder="name@firma.de"
276
+ prefix="@"
277
+ required
278
+ :error="errors.email"
279
+ hint="Ihre geschaeftliche E-Mail-Adresse"
280
+ />
281
+ ```
282
+
283
+ **Props:**
284
+
285
+ | Prop | Typ | Default | Beschreibung |
286
+ | ------------- | --------------------------------------------------------- | -------- | ------------------------------------ |
287
+ | `modelValue` | `String \| Number` | `''` | Eingabewert (v-model) |
288
+ | `type` | `'text' \| 'password' \| 'number' \| 'email' \| 'search'` | `'text'` | Input-Typ |
289
+ | `label` | `String` | `''` | Label-Text |
290
+ | `placeholder` | `String` | `''` | Platzhaltertext |
291
+ | `error` | `String` | `''` | Fehlermeldung (aktiviert roten Rand) |
292
+ | `hint` | `String` | `''` | Hinweistext (versteckt bei Fehler) |
293
+ | `prefix` | `String` | `''` | Prefix-Text links im Input |
294
+ | `suffix` | `String` | `''` | Suffix-Text rechts im Input |
295
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
296
+ | `readonly` | `Boolean` | `false` | Schreibgeschuetzt |
297
+ | `required` | `Boolean` | `false` | Pflichtfeld (roter Stern) |
298
+
299
+ **Expose:** `{ focus }` — Fokus programmatisch setzen.
300
+
301
+ Zusaetzliche HTML-Attribute (`min`, `max`, `step`, `pattern`, `maxlength`) werden an das native `<input>` weitergeleitet.
302
+
303
+ ---
304
+
305
+ #### LuTextarea
306
+
307
+ Mehrzeiliges Textfeld mit Label, Fehler/Hinweis und konfigurierbarer Groessenanpassung.
308
+
309
+ ```vue
310
+ <LuTextarea
311
+ v-model="notes"
312
+ label="Notizen"
313
+ placeholder="Optionale Anmerkungen..."
314
+ :rows="5"
315
+ resize="vertical"
316
+ :error="errors.notes"
317
+ />
318
+ ```
319
+
320
+ **Props:**
321
+
322
+ | Prop | Typ | Default | Beschreibung |
323
+ | ------------- | -------------------------------- | ------------ | -------------------- |
324
+ | `modelValue` | `String` | `''` | Textwert (v-model) |
325
+ | `label` | `String` | `''` | Label-Text |
326
+ | `placeholder` | `String` | `''` | Platzhaltertext |
327
+ | `error` | `String` | `''` | Fehlermeldung |
328
+ | `hint` | `String` | `''` | Hinweistext |
329
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
330
+ | `readonly` | `Boolean` | `false` | Schreibgeschuetzt |
331
+ | `required` | `Boolean` | `false` | Pflichtfeld |
332
+ | `rows` | `Number \| String` | `3` | Sichtbare Textzeilen |
333
+ | `resize` | `'none' \| 'vertical' \| 'both'` | `'vertical'` | CSS-Resize-Verhalten |
334
+
335
+ ---
336
+
337
+ #### LuSelect
338
+
339
+ Dropdown-Select mit Suche, Mehrfachauswahl und gruppierten Optionen.
340
+
341
+ ```vue
342
+ <!-- Einfache Auswahl -->
343
+ <LuSelect
344
+ v-model="department"
345
+ label="Abteilung"
346
+ :options="[
347
+ { value: 'hr', label: 'Personal' },
348
+ { value: 'it', label: 'IT' },
349
+ { value: 'sales', label: 'Vertrieb' },
350
+ ]"
351
+ searchable
352
+ />
353
+
354
+ <!-- Mehrfachauswahl mit Gruppen -->
355
+ <LuSelect
356
+ v-model="selectedRoles"
357
+ label="Rollen"
358
+ multiple
359
+ searchable
360
+ :options="[
361
+ {
362
+ group: 'Management',
363
+ items: [
364
+ { value: 'GF', label: 'Geschaeftsfuehrung' },
365
+ { value: 'HR', label: 'Personal' },
366
+ ],
367
+ },
368
+ {
369
+ group: 'Operativ',
370
+ items: [
371
+ { value: 'TEAMLEITUNG', label: 'Teamleitung' },
372
+ { value: 'MITARBEITER', label: 'Mitarbeiter' },
373
+ ],
374
+ },
375
+ ]"
376
+ />
377
+ ```
378
+
379
+ **Props:**
380
+
381
+ | Prop | Typ | Default | Beschreibung |
382
+ | ------------- | --------- | -------------------- | ---------------------------------------------------------------- |
383
+ | `modelValue` | `any` | `null` | Ausgewaehlter Wert (v-model) |
384
+ | `options` | `Array` | `[]` | Flach: `[{ value, label }]` oder gruppiert: `[{ group, items }]` |
385
+ | `label` | `String` | `''` | Label-Text |
386
+ | `error` | `String` | `''` | Fehlermeldung |
387
+ | `placeholder` | `String` | `'Bitte waehlen...'` | Platzhaltertext |
388
+ | `searchable` | `Boolean` | `false` | Suchfeld aktivieren |
389
+ | `multiple` | `Boolean` | `false` | Mehrfachauswahl mit Chips |
390
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
391
+
392
+ Multi-Select zeigt ausgewaehlte Werte als Teal-Chips mit Entfernen-Button. Suche ist akzent-unabhaengig (NFD-Normalisierung).
393
+
394
+ ---
395
+
396
+ #### LuDatePicker
397
+
398
+ Datumseingabe mit Kalender-Overlay, Monatsnavigation und optionalem Range-Highlighting.
399
+
400
+ ```vue
401
+ <LuDatePicker
402
+ v-model="startDate"
403
+ label="Startdatum"
404
+ placeholder="TT.MM.JJJJ"
405
+ required
406
+ :range-start="rangeStart"
407
+ :range-end="rangeEnd"
408
+ />
409
+ ```
410
+
411
+ **Props:**
412
+
413
+ | Prop | Typ | Default | Beschreibung |
414
+ | ------------- | --------- | -------------- | --------------------------------------- |
415
+ | `modelValue` | `String` | `''` | ISO-Datumsstring YYYY-MM-DD (v-model) |
416
+ | `label` | `String` | `''` | Label-Text |
417
+ | `placeholder` | `String` | `'TT.MM.JJJJ'` | Platzhaltertext |
418
+ | `error` | `String` | `''` | Fehlermeldung |
419
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
420
+ | `required` | `Boolean` | `false` | Pflichtfeld |
421
+ | `rangeStart` | `String` | `''` | ISO-Datum fuer Range-Start-Hervorhebung |
422
+ | `rangeEnd` | `String` | `''` | ISO-Datum fuer Range-Ende-Hervorhebung |
423
+
424
+ Deutsche Lokalisierung (date-fns `de`). Woche beginnt am Montag. Anzeige als `dd.MM.yyyy`.
425
+
426
+ ---
427
+
428
+ #### LuCheckbox
429
+
430
+ Checkbox mit Boolean- und Array-Modus sowie Indeterminate-State.
431
+
432
+ ```vue
433
+ <!-- Boolean-Modus -->
434
+ <LuCheckbox v-model="accepted" label="AGB akzeptieren" />
435
+
436
+ <!-- Array-Modus (Mehrfachauswahl) -->
437
+ <LuCheckbox v-model="selectedDays" value="MO" label="Montag" />
438
+ <LuCheckbox v-model="selectedDays" value="DI" label="Dienstag" />
439
+
440
+ <!-- Indeterminate ("Alle auswaehlen") -->
441
+ <LuCheckbox v-model="allSelected" :indeterminate="partiallySelected" label="Alle" />
442
+ ```
443
+
444
+ **Props:**
445
+
446
+ | Prop | Typ | Default | Beschreibung |
447
+ | --------------- | ------------------ | ----------- | --------------------------- |
448
+ | `modelValue` | `Boolean \| Array` | `false` | Checked-Status (v-model) |
449
+ | `value` | `String \| Number` | `undefined` | Wert im Array-Modus |
450
+ | `label` | `String` | `''` | Label-Text |
451
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
452
+ | `indeterminate` | `Boolean` | `false` | Zeigt Strich statt Haekchen |
453
+
454
+ ---
455
+
456
+ #### LuRadio
457
+
458
+ Radio-Button fuer Einzelauswahl in Gruppen.
459
+
460
+ ```vue
461
+ <LuRadio v-model="status" value="active" label="Aktiv" name="status" />
462
+ <LuRadio v-model="status" value="inactive" label="Inaktiv" name="status" />
463
+ ```
464
+
465
+ **Props:**
466
+
467
+ | Prop | Typ | Default | Beschreibung |
468
+ | ------------ | ----------------------------- | ------- | --------------------------------------- |
469
+ | `modelValue` | `String \| Number \| Boolean` | `''` | Ausgewaehlter Wert der Gruppe (v-model) |
470
+ | `value` | `String \| Number \| Boolean` | — | Wert dieser Radio-Option |
471
+ | `label` | `String` | `''` | Label-Text |
472
+ | `name` | `String` | `''` | Name-Attribut fuer Gruppierung |
473
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
474
+
475
+ ---
476
+
477
+ #### LuToggle
478
+
479
+ Toggle-Switch mit Slide-Animation.
480
+
481
+ ```vue
482
+ <LuToggle v-model="darkMode" label="Dunkelmodus" />
483
+ ```
484
+
485
+ **Props:**
486
+
487
+ | Prop | Typ | Default | Beschreibung |
488
+ | ------------ | --------- | ------- | ----------------------- |
489
+ | `modelValue` | `Boolean` | `false` | Toggle-Status (v-model) |
490
+ | `label` | `String` | `''` | Label-Text |
491
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
492
+
493
+ ---
494
+
495
+ #### LuFormGroup
496
+
497
+ Container fuer konsistentes Label-, Fehler- und Hinweis-Layout um Formularfelder.
498
+
499
+ ```vue
500
+ <LuFormGroup label="Abteilung" error="Bitte Abteilung waehlen" required>
501
+ <LuSelect v-model="department" :options="departments" />
502
+ </LuFormGroup>
503
+ ```
504
+
505
+ **Props:**
506
+
507
+ | Prop | Typ | Default | Beschreibung |
508
+ | ---------- | --------- | ------- | ------------------------------------- |
509
+ | `label` | `String` | `''` | Label-Text (uppercase, tracking-wide) |
510
+ | `error` | `String` | `''` | Fehlermeldung mit AlertCircle-Icon |
511
+ | `hint` | `String` | `''` | Hinweistext (versteckt bei Fehler) |
512
+ | `for` | `String` | `''` | HTML `for`-Attribut |
513
+ | `required` | `Boolean` | `false` | Roter Stern am Label |
514
+
515
+ **Slots:** `default` — Formularfeld(er)
516
+
517
+ ---
518
+
519
+ ### Datendarstellung
520
+
521
+ #### LuTable
522
+
523
+ Datentabelle mit Sortierung, Zebra-Striping, Loading-Skeleton und Scoped Slots.
524
+
525
+ ```vue
526
+ <LuTable
527
+ :columns="[
528
+ { key: 'name', label: 'Name', sortable: true },
529
+ { key: 'department', label: 'Abteilung', sortable: true, hideOnMobile: true },
530
+ { key: 'status', label: 'Status' },
531
+ { key: 'actions', label: '', width: '80px' },
532
+ ]"
533
+ :data="employees"
534
+ :loading="isLoading"
535
+ :sort-field="sortField"
536
+ :sort-dir="sortDir"
537
+ @sort="handleSort"
538
+ @row-click="openEmployee"
539
+ >
540
+ <template #cell-status="{ value }">
541
+ <LuBadge :variant="value === 'active' ? 'success' : 'neutral'">
542
+ {{ value === 'active' ? 'Aktiv' : 'Inaktiv' }}
543
+ </LuBadge>
544
+ </template>
545
+
546
+ <template #cell-actions="{ row }">
547
+ <LuButton variant="ghost" size="sm" @click.stop="editEmployee(row)">
548
+ <template #icon-left><LuIcon name="Pencil" :size="16" /></template>
549
+ </LuButton>
550
+ </template>
551
+
552
+ <template #empty>Keine Mitarbeiter gefunden.</template>
553
+ </LuTable>
554
+ ```
555
+
556
+ **Props:**
557
+
558
+ | Prop | Typ | Default | Beschreibung |
559
+ | ----------- | ------------------ | ------- | -------------------------------------------------------- |
560
+ | `columns` | `Column[]` | — | Spaltendefinitionen |
561
+ | `data` | `Object[]` | `[]` | Zeilendaten |
562
+ | `loading` | `Boolean` | `false` | Zeigt 3 Skeleton-Zeilen |
563
+ | `sortField` | `String` | `''` | Aktives Sortierfeld |
564
+ | `sortDir` | `'asc' \| 'desc'` | `'asc'` | Sortierrichtung |
565
+ | `rowKey` | `String` | `'id'` | Property fuer Zeilen-Key (Fallback: `_id`, dann Index) |
566
+ | `rowClass` | `Function \| null` | `null` | `(row, index) => string` fuer individuelle Zeilenklassen |
567
+
568
+ **Column-Definition:**
569
+
570
+ ```js
571
+ { key: 'name', label: 'Name', sortable: true, hideOnMobile: true, width: '200px' }
572
+ ```
573
+
574
+ **Events:** `sort(field)`, `row-click(row, index)`
575
+
576
+ **Slots:** `cell-{key}({ row, value })`, `header-{key}({ column })`, `empty`, `row({ row, index })`
577
+
578
+ ---
579
+
580
+ #### LuPagination
581
+
582
+ Seitennavigation mit Seiten-Links, Vor/Zurueck und Items-pro-Seite-Auswahl.
583
+
584
+ ```vue
585
+ <LuPagination
586
+ :current-page="page"
587
+ :total-pages="totalPages"
588
+ :total-items="487"
589
+ :items-per-page="25"
590
+ @change="page = $event"
591
+ @change-per-page="perPage = $event"
592
+ />
593
+ ```
594
+
595
+ **Props:**
596
+
597
+ | Prop | Typ | Default | Beschreibung |
598
+ | -------------- | -------- | ------- | -------------------------- |
599
+ | `currentPage` | `Number` | — | Aktuelle Seite (1-basiert) |
600
+ | `totalPages` | `Number` | — | Gesamtseitenanzahl |
601
+ | `totalItems` | `Number` | `0` | Gesamtanzahl Eintraege |
602
+ | `itemsPerPage` | `Number` | `10` | Eintraege pro Seite |
603
+
604
+ **Events:** `change(page)`, `change-per-page(perPage)`
605
+
606
+ Zeigt Items-pro-Seite-Selektor (10/25/50), intelligente Seitenwindows mit Ellipsis, und Eintrags-Bereich (z.B. "1-25 von 487 Eintraegen").
607
+
608
+ ---
609
+
610
+ ### Feedback & Overlay
611
+
612
+ #### LuModal
613
+
614
+ Modaler Dialog mit Backdrop, ESC-Schliessen und Bottom-Sheet auf Mobile.
615
+
616
+ ```vue
617
+ <LuModal v-model="showModal" title="Mitarbeiter bearbeiten" size="lg" persistent>
618
+ <form @submit.prevent="save">
619
+ <LuInput v-model="name" label="Name" />
620
+ </form>
621
+
622
+ <template #footer>
623
+ <LuButton variant="secondary" @click="showModal = false">Abbrechen</LuButton>
624
+ <LuButton @click="save" :loading="saving">Speichern</LuButton>
625
+ </template>
626
+ </LuModal>
627
+ ```
628
+
629
+ **Props:**
630
+
631
+ | Prop | Typ | Default | Beschreibung |
632
+ | ------------ | ------------------------------ | ------- | ------------------------------------------ |
633
+ | `modelValue` | `Boolean` | — | Sichtbarkeit (v-model) |
634
+ | `title` | `String` | `''` | Titel in der Kopfzeile |
635
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | sm=400px, md=560px, lg=768px, xl=1024px |
636
+ | `persistent` | `Boolean` | `false` | Verhindert Schliessen durch Backdrop-Klick |
637
+
638
+ **Slots:** `default` (Body, scrollbar), `footer` (Aktionen)
639
+
640
+ Features: Focus-Trap (Tab/Shift+Tab), Body-Scroll-Lock, Bottom-Sheet unter 768px, `prefers-reduced-motion` Support.
641
+
642
+ Empfehlung: `useModal()` Composable fuer einfache Zustandsverwaltung.
643
+
644
+ ---
645
+
646
+ #### LuAlert
647
+
648
+ Inline-Alert fuer kontextbezogene Rueckmeldungen.
649
+
650
+ ```vue
651
+ <LuAlert
652
+ variant="warning"
653
+ title="Achtung"
654
+ message="Ihre Sitzung laeuft in 5 Minuten ab."
655
+ dismissible
656
+ @dismiss="hideWarning"
657
+ />
658
+ ```
659
+
660
+ **Props:**
661
+
662
+ | Prop | Typ | Default | Beschreibung |
663
+ | ------------- | --------------------------------------------- | -------- | -------------------------- |
664
+ | `variant` | `'info' \| 'warning' \| 'error' \| 'success'` | `'info'` | Semantische Variante |
665
+ | `title` | `String` | `''` | Optionale fette Titelzeile |
666
+ | `message` | `String` | `''` | Nachrichtentext |
667
+ | `dismissible` | `Boolean` | `false` | Zeigt Schliessen-Button |
668
+
669
+ **Events:** `dismiss`
670
+
671
+ Icons pro Variante: success=CircleCheck, warning=TriangleAlert, error=XCircle, info=Info.
672
+
673
+ ---
674
+
675
+ #### LuToast
676
+
677
+ Toast-Benachrichtigungs-Container. Einmal im App-Root einbinden, Steuerung via `useToast()`.
678
+
679
+ ```vue
680
+ <!-- In App.vue (einmalig einbinden) -->
681
+ <template>
682
+ <LuAppLayout>
683
+ <RouterView />
684
+ </LuAppLayout>
685
+ <LuToast />
686
+ </template>
687
+ ```
688
+
689
+ ```js
690
+ // In beliebiger Komponente
691
+ import { useToast } from '@vss-software/ui'
692
+
693
+ const { addToast } = useToast()
694
+
695
+ addToast({
696
+ variant: 'success',
697
+ title: 'Gespeichert',
698
+ message: 'Mitarbeiterdaten wurden aktualisiert.',
699
+ duration: 5000,
700
+ })
701
+ ```
702
+
703
+ Teleportiert zu `<body>`. Fixiert unten rechts. Auto-Dismiss nach `duration` (Standard: 5000ms, `0` = kein Auto-Dismiss).
704
+
705
+ ---
706
+
707
+ #### LuDropdown
708
+
709
+ Dropdown-Menue mit selektierbaren Optionen, Trennlinien und optionalen Beschreibungen.
710
+
711
+ ```vue
712
+ <LuDropdown
713
+ label="Exportieren"
714
+ :options="[
715
+ { value: 'csv', label: 'CSV-Export', description: 'Komma-getrennte Werte' },
716
+ { value: 'xlsx', label: 'Excel-Export' },
717
+ { divider: true },
718
+ { value: 'datev', label: 'DATEV-Export', icon: 'FileSpreadsheet' },
719
+ ]"
720
+ @select="handleExport"
721
+ />
722
+ ```
723
+
724
+ **Props:**
725
+
726
+ | Prop | Typ | Default | Beschreibung |
727
+ | --------- | ------------------------------------- | ------------- | ------------------ |
728
+ | `label` | `String` | — | Button-Label |
729
+ | `options` | `DropdownOption[]` | `[]` | Menue-Optionen |
730
+ | `icon` | `String \| Object` | `null` | Icon vor dem Label |
731
+ | `variant` | `'primary' \| 'secondary' \| 'ghost'` | `'secondary'` | Button-Variante |
732
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'sm'` | Button-Groesse |
733
+
734
+ **DropdownOption:**
735
+
736
+ ```js
737
+ { value: 'id', label: 'Text', description?: 'Zusatzinfo', disabled?: false, icon?: 'LucideName', divider?: false }
738
+ ```
739
+
740
+ **Events:** `select(value)`
741
+
742
+ ---
743
+
744
+ ### Allgemeine UI
745
+
746
+ #### LuButton
747
+
748
+ Vielseitiger Button fuer primaere, sekundaere, Danger-, Ghost- und Icon-Aktionen.
749
+
750
+ ```vue
751
+ <LuButton @click="save" :loading="saving">
752
+ <template #icon-left><LuIcon name="Save" :size="16" /></template>
753
+ Speichern
754
+ </LuButton>
755
+
756
+ <LuButton variant="danger" size="sm">Loeschen</LuButton>
757
+
758
+ <LuButton variant="ghost">Abbrechen</LuButton>
759
+
760
+ <LuButton variant="icon" aria-label="Bearbeiten">
761
+ <LuIcon name="Pencil" :size="18" />
762
+ </LuButton>
763
+ ```
764
+
765
+ **Props:**
766
+
767
+ | Prop | Typ | Default | Beschreibung |
768
+ | ------------- | ----------------------------------------------------------- | ------------------- | ------------------------------------------- |
769
+ | `variant` | `'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'icon'` | `'primary'` | Visuelle Variante |
770
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | sm=32px, md=40px, lg=48px Hoehe |
771
+ | `disabled` | `Boolean` | `false` | Deaktiviert |
772
+ | `loading` | `Boolean` | `false` | Zeigt Lade-Spinner, deaktiviert Interaktion |
773
+ | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | HTML button type |
774
+ | `loadingText` | `String` | `'Wird geladen...'` | Screenreader-Ansage bei Loading |
775
+
776
+ **Slots:** `default` (Label), `icon-left`, `icon-right`
777
+
778
+ ---
779
+
780
+ #### LuCard
781
+
782
+ Flexible Karte mit Standard- und KPI-Variante.
783
+
784
+ ```vue
785
+ <!-- Standard -->
786
+ <LuCard>
787
+ <template #header>
788
+ <h2>Mitarbeiterliste</h2>
789
+ </template>
790
+ <LuTable :columns="cols" :data="rows" />
791
+ <template #footer>
792
+ <LuPagination :current-page="1" :total-pages="5" />
793
+ </template>
794
+ </LuCard>
795
+
796
+ <!-- KPI -->
797
+ <LuCard variant="kpi" value="42" label="Offene Antraege" trend="+12%" trend-direction="up" />
798
+ ```
799
+
800
+ **Props:**
801
+
802
+ | Prop | Typ | Default | Beschreibung |
803
+ | ---------------- | ----------------------------- | ------------ | ---------------------------------------- |
804
+ | `variant` | `'standard' \| 'kpi'` | `'standard'` | Darstellungsvariante |
805
+ | `value` | `String \| Number` | `undefined` | KPI-Wert (nur kpi-Variante) |
806
+ | `label` | `String` | `''` | KPI-Label (nur kpi-Variante) |
807
+ | `trend` | `String` | `''` | Trend-Text z.B. "+5%" (nur kpi-Variante) |
808
+ | `trendDirection` | `'up' \| 'down' \| 'neutral'` | `'neutral'` | Trend-Pfeil und Farbe |
809
+
810
+ **Slots (Standard):** `default` (Body), `header`, `footer`
811
+
812
+ ---
813
+
814
+ #### LuKpiCard
815
+
816
+ Kompakte KPI-Karte fuer Dashboards mit Status-Farbgebung und Trend-Indikator.
817
+
818
+ ```vue
819
+ <LuKpiCard
820
+ label="Krankenquote"
821
+ value="4.2%"
822
+ status="warning"
823
+ :trend="-0.3"
824
+ subtext="vs. Vormonat"
825
+ />
826
+ ```
827
+
828
+ **Props:**
829
+
830
+ | Prop | Typ | Default | Beschreibung |
831
+ | --------- | ------------------------------------------------ | ----------- | -------------------------------------------------------- |
832
+ | `label` | `String` | — | Label (uppercase, xs) |
833
+ | `value` | `String \| Number` | — | KPI-Wert (gross dargestellt) |
834
+ | `status` | `'success' \| 'warning' \| 'error' \| 'default'` | `'default'` | Wert-Textfarbe |
835
+ | `trend` | `Number` | `undefined` | Trend-Prozent (positiv=TrendingUp, negativ=TrendingDown) |
836
+ | `subtext` | `String` | `undefined` | Zusatztext unter dem Wert |
837
+
838
+ ---
839
+
840
+ #### LuBadge
841
+
842
+ Pill-foermiges Badge fuer Status- und Kategorie-Anzeigen.
843
+
844
+ ```vue
845
+ <LuBadge variant="success">Aktiv</LuBadge>
846
+ <LuBadge variant="warning" size="sm">Ausstehend</LuBadge>
847
+ <LuBadge variant="error">Abgelehnt</LuBadge>
848
+ ```
849
+
850
+ **Props:**
851
+
852
+ | Prop | Typ | Default | Beschreibung |
853
+ | --------- | ---------------------------------------------------------- | ----------- | ------------ |
854
+ | `variant` | `'success' \| 'warning' \| 'error' \| 'info' \| 'neutral'` | `'neutral'` | Farbvariante |
855
+ | `size` | `'sm' \| 'md'` | `'md'` | Groesse |
856
+
857
+ **Slots:** `default` (Label-Text)
858
+
859
+ ---
860
+
861
+ #### LuAvatar
862
+
863
+ Avatar mit Initialen-Fallback und optionalem Bild. Farbe wird deterministisch aus dem Namen berechnet.
864
+
865
+ ```vue
866
+ <LuAvatar name="Max Mustermann" size="lg" />
867
+ <LuAvatar name="Anna Schmidt" src="/avatars/anna.jpg" size="md" />
868
+ ```
869
+
870
+ **Props:**
871
+
872
+ | Prop | Typ | Default | Beschreibung |
873
+ | ------ | ------------------------------ | ------- | ------------------------------------------------------- |
874
+ | `name` | `String` | — | Vollstaendiger Name (fuer Initialen und Farbberechnung) |
875
+ | `src` | `String` | `''` | Optionale Bild-URL |
876
+ | `size` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | sm=32px, md=40px, lg=48px, xl=64px |
877
+
878
+ Bild wird vorab geladen. Bei Fehler werden 2-Buchstaben-Initialen auf farbigem Hintergrund angezeigt (6 deterministische Farben).
879
+
880
+ ---
881
+
882
+ #### LuIcon
883
+
884
+ Dynamischer Lucide-Icon-Wrapper mit Lazy-Loading und Caching.
885
+
886
+ ```vue
887
+ <LuIcon name="Clock" :size="24" color="#00BFA6" />
888
+ <LuIcon name="Users" />
889
+ <LuIcon name="AlertTriangle" :stroke-width="2" />
890
+ ```
891
+
892
+ **Props:**
893
+
894
+ | Prop | Typ | Default | Beschreibung |
895
+ | ------------- | ------------------ | ---------------- | ------------------------------ |
896
+ | `name` | `String` | — | Lucide-Icon-Name in PascalCase |
897
+ | `size` | `Number \| String` | `20` | Groesse in Pixeln |
898
+ | `color` | `String` | `'currentColor'` | CSS-Farbwert |
899
+ | `strokeWidth` | `Number \| String` | `1.5` | SVG Strichstaerke |
900
+
901
+ Icons werden on-demand geladen und im Cache gehalten. Vollstaendige Icon-Liste: [lucide.dev/icons](https://lucide.dev/icons)
902
+
903
+ ---
904
+
905
+ #### LuSkeleton
906
+
907
+ Lade-Platzhalter mit Shimmer-Animation.
908
+
909
+ ```vue
910
+ <LuSkeleton width="200px" height="20px" />
911
+ <LuSkeleton width="48px" height="48px" rounded />
912
+ <LuSkeleton height="120px" />
913
+ ```
914
+
915
+ **Props:**
916
+
917
+ | Prop | Typ | Default | Beschreibung |
918
+ | --------- | --------- | -------- | -------------------------- |
919
+ | `width` | `String` | `'100%'` | CSS-Breite |
920
+ | `height` | `String` | `'16px'` | CSS-Hoehe |
921
+ | `rounded` | `Boolean` | `false` | Komplett rund (Kreis/Pill) |
922
+
923
+ Respektiert `prefers-reduced-motion`.
924
+
925
+ ---
926
+
927
+ #### LuEmptyState
928
+
929
+ Zentrierter Platzhalter fuer leere Ansichten.
930
+
931
+ ```vue
932
+ <LuEmptyState
933
+ icon="Users"
934
+ title="Keine Mitarbeiter gefunden"
935
+ description="Passen Sie Ihre Filterkriterien an oder legen Sie einen neuen Mitarbeiter an."
936
+ action-label="Mitarbeiter anlegen"
937
+ :action-handler="createEmployee"
938
+ />
939
+ ```
940
+
941
+ **Props:**
942
+
943
+ | Prop | Typ | Default | Beschreibung |
944
+ | --------------- | ---------- | ------- | -------------------- |
945
+ | `icon` | `String` | `''` | Lucide-Icon-Name |
946
+ | `title` | `String` | — | Ueberschrift |
947
+ | `description` | `String` | `''` | Beschreibungstext |
948
+ | `actionLabel` | `String` | `''` | Button-Text |
949
+ | `actionHandler` | `Function` | `null` | Button-Click-Handler |
950
+
951
+ Button nur sichtbar wenn `actionLabel` und `actionHandler` gesetzt sind.
952
+
953
+ ---
954
+
955
+ #### LuActionBar
956
+
957
+ Fixierte Aktionsleiste am unteren Bildschirmrand fuer Bulk-Operationen.
958
+
959
+ ```vue
960
+ <LuActionBar
961
+ :selected-count="selectedIds.length"
962
+ :loading="processing"
963
+ :show-delete="canDelete"
964
+ :show-export="true"
965
+ :show-status="true"
966
+ :show-department="false"
967
+ @cancel="clearSelection"
968
+ @action-delete="deleteSelected"
969
+ @action-export="exportSelected"
970
+ @action-status="changeStatus"
971
+ >
972
+ <template #actions>
973
+ <LuButton variant="secondary" size="sm" @click="customAction">
974
+ Eigene Aktion
975
+ </LuButton>
976
+ </template>
977
+ </LuActionBar>
978
+ ```
979
+
980
+ **Props:**
981
+
982
+ | Prop | Typ | Default | Beschreibung |
983
+ | ---------------- | --------- | ------- | ----------------------------- |
984
+ | `selectedCount` | `Number` | — | Anzahl ausgewaehlter Elemente |
985
+ | `loading` | `Boolean` | `false` | Ladezustand |
986
+ | `showDelete` | `Boolean` | `true` | Loeschen-Button anzeigen |
987
+ | `showExport` | `Boolean` | `true` | Export-Button anzeigen |
988
+ | `showStatus` | `Boolean` | `true` | Status-Button anzeigen |
989
+ | `showDepartment` | `Boolean` | `true` | Abteilungs-Button anzeigen |
990
+
991
+ **Events:** `cancel`, `action-status`, `action-department`, `action-export`, `action-delete`
992
+
993
+ **Slots:** `actions` — zusaetzliche eigene Aktionsbuttons
994
+
995
+ Wird nur angezeigt wenn `selectedCount > 0`. Slide-Up-Animation. Dunkler Hintergrund (gray-900).
996
+
997
+ ---
998
+
999
+ ### Charts
1000
+
1001
+ Alle Chart-Komponenten nutzen Chart.js via vue-chartjs und wenden automatisch die lumen.hr Design-Defaults an.
1002
+
1003
+ #### LuLineChart
1004
+
1005
+ ```vue
1006
+ <LuLineChart
1007
+ :data="{
1008
+ labels: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun'],
1009
+ datasets: [{ label: 'Ueberstunden', data: [12, 19, 3, 5, 2, 3] }],
1010
+ }"
1011
+ height="300px"
1012
+ aria-label="Ueberstunden pro Monat"
1013
+ />
1014
+ ```
1015
+
1016
+ **Props:**
1017
+
1018
+ | Prop | Typ | Default | Beschreibung |
1019
+ | ----------- | ---------------------- | ------------------ | ----------------------------------- |
1020
+ | `data` | `ChartData<'line'>` | — | Chart.js Daten |
1021
+ | `options` | `ChartOptions<'line'>` | `{}` | Optionen (deep-merged mit Defaults) |
1022
+ | `height` | `String` | `'100%'` | CSS-Hoehe |
1023
+ | `ariaLabel` | `String` | `'Liniendiagramm'` | Barrierefreie Beschriftung |
1024
+
1025
+ Defaults: Teal-500 Linie, 10% Teal-Fuellung, 3px Punkte, 0.3 Kurvenspannung.
1026
+
1027
+ ---
1028
+
1029
+ #### LuBarChart
1030
+
1031
+ ```vue
1032
+ <LuBarChart
1033
+ :data="{
1034
+ labels: ['Mo', 'Di', 'Mi', 'Do', 'Fr'],
1035
+ datasets: [{ label: 'Anwesenheit', data: [45, 42, 48, 43, 40] }],
1036
+ }"
1037
+ height="250px"
1038
+ />
1039
+ ```
1040
+
1041
+ **Props:**
1042
+
1043
+ | Prop | Typ | Default | Beschreibung |
1044
+ | ----------- | --------------------- | ------------------ | ----------------------------------- |
1045
+ | `data` | `ChartData<'bar'>` | — | Chart.js Daten |
1046
+ | `options` | `ChartOptions<'bar'>` | `{}` | Optionen (deep-merged mit Defaults) |
1047
+ | `height` | `String` | `'100%'` | CSS-Hoehe |
1048
+ | `ariaLabel` | `String` | `'Balkendiagramm'` | Barrierefreie Beschriftung |
1049
+
1050
+ Defaults: Teal-500 Hintergrund, 4px abgerundete Ecken.
1051
+
1052
+ ---
1053
+
1054
+ #### LuDonutChart
1055
+
1056
+ ```vue
1057
+ <LuDonutChart
1058
+ :data="{
1059
+ labels: ['Anwesend', 'Urlaub', 'Krank', 'Abwesend'],
1060
+ datasets: [{ data: [35, 5, 3, 2] }],
1061
+ }"
1062
+ height="300px"
1063
+ />
1064
+ ```
1065
+
1066
+ **Props:**
1067
+
1068
+ | Prop | Typ | Default | Beschreibung |
1069
+ | ----------- | -------------------------- | ----------------- | ----------------------------------- |
1070
+ | `data` | `ChartData<'doughnut'>` | — | Chart.js Daten |
1071
+ | `options` | `ChartOptions<'doughnut'>` | `{}` | Optionen (deep-merged mit Defaults) |
1072
+ | `height` | `String` | `'300px'` | CSS-Hoehe |
1073
+ | `ariaLabel` | `String` | `'Kreisdiagramm'` | Barrierefreie Beschriftung |
1074
+
1075
+ Defaults: 65% Cutout (Donut), 10-Farben-Palette im "Professional Calm"-Stil, Legende unten. Tooltip zeigt Label, Wert und berechneten Prozentsatz.
1076
+
1077
+ ---
1078
+
1079
+ ### Tabs (Compound Pattern)
1080
+
1081
+ Barrierefreies Tab-System nach WAI-ARIA Tabs Pattern mit Tastaturnavigation (Pfeiltasten, Home, End).
1082
+
1083
+ ```vue
1084
+ <LuTabs v-model="activeTab">
1085
+ <LuTabList>
1086
+ <LuTab value="overview">Uebersicht</LuTab>
1087
+ <LuTab value="details">Details</LuTab>
1088
+ <LuTab value="history">Verlauf</LuTab>
1089
+ </LuTabList>
1090
+
1091
+ <LuTabPanel value="overview">
1092
+ <p>Uebersichts-Inhalt...</p>
1093
+ </LuTabPanel>
1094
+ <LuTabPanel value="details">
1095
+ <p>Detail-Inhalt...</p>
1096
+ </LuTabPanel>
1097
+ <LuTabPanel value="history">
1098
+ <p>Verlaufs-Inhalt...</p>
1099
+ </LuTabPanel>
1100
+ </LuTabs>
1101
+ ```
1102
+
1103
+ #### LuTabs
1104
+
1105
+ Root-Container. Stellt aktiven Tab-Status via provide/inject bereit.
1106
+
1107
+ | Prop | Typ | Default | Beschreibung |
1108
+ | ------------ | -------- | ------- | -------------------------- |
1109
+ | `modelValue` | `String` | `''` | Aktiver Tab-Wert (v-model) |
1110
+
1111
+ #### LuTabList
1112
+
1113
+ Horizontaler Container fuer Tab-Buttons mit WAI-ARIA Tastaturnavigation.
1114
+
1115
+ #### LuTab
1116
+
1117
+ Einzelner Tab-Button. `role="tab"`, `aria-selected`, `aria-controls`.
1118
+
1119
+ | Prop | Typ | Default | Beschreibung |
1120
+ | ------- | -------- | ------- | --------------------------------------------------- |
1121
+ | `value` | `String` | — | Eindeutige ID (muss mit LuTabPanel uebereinstimmen) |
1122
+
1123
+ **Slots:** `default` (Tab-Label)
1124
+
1125
+ #### LuTabPanel
1126
+
1127
+ Content-Panel. Wird nur gerendert wenn der zugehoerige Tab aktiv ist (`v-if`).
1128
+
1129
+ | Prop | Typ | Default | Beschreibung |
1130
+ | ------- | -------- | ------- | ----------------------------------------------------------- |
1131
+ | `value` | `String` | — | Muss mit dem `value` des zugehoerigen LuTab uebereinstimmen |
1132
+
1133
+ **Slots:** `default` (Panel-Inhalt)
1134
+
1135
+ ---
1136
+
1137
+ ## Composables
1138
+
1139
+ ### useToast()
1140
+
1141
+ Globaler Toast-Zustand. Wird von `<LuToast>` fuer die Darstellung genutzt.
1142
+
1143
+ ```js
1144
+ import { useToast } from '@vss-software/ui'
1145
+
1146
+ const { toasts, addToast, removeToast } = useToast()
1147
+
1148
+ // Toast hinzufuegen
1149
+ const id = addToast({
1150
+ variant: 'success', // 'success' | 'warning' | 'error' | 'info'
1151
+ title: 'Gespeichert', // Optional
1152
+ message: 'Aenderungen uebernommen.',
1153
+ duration: 5000, // ms, 0 = kein Auto-Dismiss
1154
+ })
1155
+
1156
+ // Toast manuell entfernen
1157
+ removeToast(id)
1158
+ ```
1159
+
1160
+ | Return | Typ | Beschreibung |
1161
+ | ------------- | --------------------- | ---------------------------------- |
1162
+ | `toasts` | `Toast[]` (reactive) | Alle aktiven Toasts |
1163
+ | `addToast` | `(options) => number` | Toast hinzufuegen, gibt ID zurueck |
1164
+ | `removeToast` | `(id) => void` | Toast entfernen |
1165
+
1166
+ ---
1167
+
1168
+ ### useModal()
1169
+
1170
+ Einfache reaktive Zustandsverwaltung fuer Modals.
1171
+
1172
+ ```js
1173
+ import { useModal } from '@vss-software/ui'
1174
+
1175
+ const { isOpen, open, close, toggle } = useModal()
1176
+ ```
1177
+
1178
+ ```vue
1179
+ <LuButton @click="open">Dialog oeffnen</LuButton>
1180
+ <LuModal v-model="isOpen" title="Mein Dialog">
1181
+ Inhalt...
1182
+ </LuModal>
1183
+ ```
1184
+
1185
+ | Return | Typ | Beschreibung |
1186
+ | -------- | -------------- | ----------------------------- |
1187
+ | `isOpen` | `Ref<boolean>` | Reaktiver Sichtbarkeitsstatus |
1188
+ | `open` | `() => void` | Oeffnen |
1189
+ | `close` | `() => void` | Schliessen |
1190
+ | `toggle` | `() => void` | Umschalten |
1191
+
1192
+ ---
1193
+
1194
+ ### useClickOutside()
1195
+
1196
+ Erkennt Klicks ausserhalb eines Elements.
1197
+
1198
+ ```js
1199
+ import { useClickOutside } from '@vss-software/ui'
1200
+ import { ref } from 'vue'
1201
+
1202
+ const dropdownRef = ref(null)
1203
+ useClickOutside(dropdownRef, () => {
1204
+ isOpen.value = false
1205
+ })
1206
+ ```
1207
+
1208
+ | Parameter | Typ | Beschreibung |
1209
+ | ----------- | -------------------------- | ------------------------------------ |
1210
+ | `targetRef` | `Ref<HTMLElement \| null>` | Ref zum Begrenzungselement |
1211
+ | `callback` | `() => void` | Wird bei Klick ausserhalb aufgerufen |
1212
+
1213
+ Registriert `mousedown` und `touchstart` Listener. Bereinigt automatisch bei Unmount.
1214
+
1215
+ ---
1216
+
1217
+ ## Utilities
1218
+
1219
+ ### Chart Defaults
1220
+
1221
+ ```js
1222
+ import {
1223
+ baseChartOptions,
1224
+ lineDatasetDefaults,
1225
+ barDatasetDefaults,
1226
+ deepMerge,
1227
+ } from '@vss-software/ui'
1228
+ ```
1229
+
1230
+ | Export | Beschreibung |
1231
+ | --------------------------- | ---------------------------------------------------------------------------------- |
1232
+ | `baseChartOptions` | Gemeinsame Chart.js Optionen (responsive, Tooltip-Stil, Achsen-Formatierung) |
1233
+ | `lineDatasetDefaults` | Linien-Defaults: Teal-500, 10% Fuellung, 3px Punkte, 0.3 Spannung |
1234
+ | `barDatasetDefaults` | Balken-Defaults: Teal-500, 4px Border-Radius |
1235
+ | `deepMerge(target, source)` | Deep-Merge zweier Objekte. Arrays werden ersetzt. Schutz gegen Prototype-Pollution |
1236
+
1237
+ ---
1238
+
1239
+ ## Barrierefreiheit (a11y)
1240
+
1241
+ Alle Komponenten folgen WCAG 2.1 AA Richtlinien:
1242
+
1243
+ - **ARIA-Rollen und -Attribute** auf allen interaktiven Elementen
1244
+ - **Tastaturnavigation** (Tab, Enter, Space, Escape, Pfeiltasten)
1245
+ - **Focus-Management** mit sichtbarem Fokusring (Teal-500, 2px)
1246
+ - **Focus-Trap** in Modals
1247
+ - **Screen-Reader-Unterstuetzung** via `aria-label`, `aria-live`, `role`
1248
+ - **Reduzierte Bewegung** (`prefers-reduced-motion`) wird respektiert
1249
+ - **Farbkontraste** eingehalten (mindestens 4.5:1)
1250
+
1251
+ ---
1252
+
1253
+ ## Lizenz
1254
+
1255
+ Proprietaer. Alle Rechte vorbehalten.