@vss-software/ui
v0.2.2
Published
Produktuebergreifendes Design-System (Vue-Komponenten)
Readme
@vss-software/ui
Produktuebergreifendes Design-System mit Vue 3 Komponenten fuer die lumen.hr Plattform.
Stil: Professional Calm — dunkle Sidebar (Navy #0B1929), helle Arbeitsflaechen (Gray-50 #F8FAFC), Teal-Akzentfarbe (#00BFA6), Inter Schrift, WCAG 2.1 AA konform.
Installation
npm install @vss-software/ui
# or
pnpm add @vss-software/uiPeer Dependencies
{
"vue": "^3.5.0",
"vue-router": "^4",
"tailwindcss": "^4.0.0"
}Setup
Option 1: Vue Plugin (alle Komponenten global registrieren)
import { createApp } from 'vue'
import LumenUI from '@vss-software/ui'
import '@vss-software/ui/styles'
const app = createApp(App)
app.use(LumenUI)
app.mount('#app')Option 2: Einzelimport (Tree-Shaking)
import { LuButton, LuCard, LuInput } from '@vss-software/ui'
import '@vss-software/ui/styles'Tailwind Integration
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.
Design Tokens
Farben
| Token | Hex | Verwendung |
| ------------ | --------- | ------------------------------------- |
| navy-900 | #0B1929 | Sidebar-Hintergrund, Login-BG |
| navy-800 | #0F2438 | Sidebar Hover-States |
| teal-500 | #00BFA6 | Primaere CTAs, Links, aktive Elemente |
| teal-400 | #26EDCA | Hover auf dunklem Hintergrund |
| teal-700 | #00897B | Pressed-States, dunkle Akzente |
| lu-bg | #F8FAFC | Seiten-Hintergrund (gray-50) |
| lu-surface | #FFFFFF | Karten, Modals, Tabellen |
| lu-border | #E2E8F0 | Trennlinien, Karten-Raender |
Typografie
| Token | Groesse | Verwendung |
| -------------------- | ------- | ------------------------- |
| --lu-text-display | 30px | Display-Headlines |
| --lu-text-h1 | 24px | Seiten-Ueberschriften |
| --lu-text-h2 | 20px | Abschnitts-Ueberschriften |
| --lu-text-h3 | 16px | Karten-Ueberschriften |
| --lu-text-body | 14px | Fliesstext |
| --lu-text-body-sm | 13px | Kompakter Text |
| --lu-text-caption | 12px | Beschriftungen |
| --lu-text-overline | 11px | Labels, Overlines |
Schriftarten: Inter (sans-serif), JetBrains Mono (monospace).
Spacing
4px-Basisraster: 1=4px, 2=8px, 3=12px, 4=16px, 5=20px, 6=24px, 8=32px, 10=40px, 12=48px.
Z-Index Skala
| Token | Wert | Verwendung |
| ------------------ | ---- | -------------------- |
| z-header | 30 | LuHeader |
| z-sidebar | 40 | LuSidebar |
| z-dropdown | 50 | LuDropdown, LuSelect |
| z-modal-backdrop | 60 | Modal-Backdrop |
| z-modal | 70 | LuModal |
| z-toast | 80 | LuToast |
Komponenten
Layout
LuAppLayout
Haupt-Application-Shell mit Sidebar, Header und scrollbarem Content-Bereich.
<LuAppLayout
:nav-items="navGroups"
:user="{ name: 'Max Mustermann', role: 'HR' }"
:notification-count="3"
:current-user-roles="['HR']"
v-model:sidebar-collapsed="collapsed"
:user-menu-items="[
{ label: 'Profil', icon: 'User', action: 'profile' },
{ label: 'Abmelden', icon: 'LogOut', action: 'logout' },
]"
@notifications-click="openNotifications"
@user-menu-action="handleMenuAction"
>
<template #sidebar-logo>
<img src="/logo.svg" alt="Logo" />
</template>
<template #breadcrumb>
<LuBreadcrumb :items="[{ label: 'Dashboard', to: '/' }, { label: 'Mitarbeiter' }]" />
</template>
<RouterView />
</LuAppLayout>Props:
| Prop | Typ | Default | Beschreibung |
| ------------------- | ----------------------------------------- | ---------------------- | ------------------------------------- |
| navItems | NavGroup[] | [] | Navigationsgruppen fuer die Sidebar |
| user | { name, role?, roleLabel?, avatarSrc? } | { name: 'Benutzer' } | Aktuelle Benutzerinfo |
| notificationCount | Number | 0 | Anzahl ungelesener Benachrichtigungen |
| currentUserRoles | String[] | [] | Benutzerrollen fuer RBAC-Filterung |
| sidebarCollapsed | Boolean | false | Sidebar eingeklappt (v-model) |
| userMenuItems | { label, icon?, action }[] | [] | Eintraege im Benutzer-Dropdown |
Events: notifications-click, user-menu-action(action), update:sidebarCollapsed(boolean)
Slots: default (Content), sidebar-logo, sidebar-bottom, breadcrumb
LuSidebar
Navigations-Sidebar mit RBAC-gefilterter Navigation, Collapse-Modus und responsivem Overlay.
<LuSidebar
:items="[
{
label: 'Hauptmenue',
items: [
{ icon: 'LayoutDashboard', label: 'Dashboard', to: '/' },
{ icon: 'Users', label: 'Mitarbeiter', to: '/employees', roles: ['HR', 'GF'] },
{ icon: 'Clock', label: 'Zeiterfassung', to: '/time-tracking' },
],
},
]"
v-model:open="sidebarOpen"
v-model:collapsed="sidebarCollapsed"
:current-user-roles="['HR']"
>
<template #logo>
<img src="/logo.svg" alt="Logo" />
</template>
</LuSidebar>Props:
| Prop | Typ | Default | Beschreibung |
| ------------------ | ------------ | ------- | ---------------------------------------- |
| items | NavGroup[] | [] | Navigationsgruppen mit Items |
| open | Boolean | false | Sichtbarkeit auf Mobile/Tablet (v-model) |
| collapsed | Boolean | false | Icons-Only-Modus auf Desktop (v-model) |
| currentUserRoles | String[] | [] | Rollen fuer RBAC-Filterung |
Events: update:open(boolean), update:collapsed(boolean)
Slots: logo, bottom
NavGroup-Format:
{
label: 'Gruppenname',
items: [
{ icon: 'LucideIconName', label: 'Link-Text', to: '/route', roles: ['HR'] }
]
}Items mit roles-Array werden nur angezeigt, wenn der Benutzer mindestens eine passende Rolle hat.
LuHeader
Applikations-Header (64px) mit Hamburger-Toggle, Benachrichtigungen und Benutzer-Dropdown.
<LuHeader
:user="{ name: 'Max Mustermann', roleLabel: 'HR-Manager', avatarSrc: '/avatar.jpg' }"
:notification-count="5"
:pending-corrections-count="2"
:user-menu-items="menuItems"
@toggle-sidebar="toggleSidebar"
@notifications-click="openNotifications"
@corrections-click="openCorrections"
@user-menu-action="handleAction"
>
<template #breadcrumb>
<LuBreadcrumb />
</template>
</LuHeader>Props:
| Prop | Typ | Default | Beschreibung |
| ------------------------- | ----------------------------------------- | ---------------------- | ------------------------------------- |
| user | { name, role?, roleLabel?, avatarSrc? } | { name: 'Benutzer' } | Benutzerinfo fuer Avatar und Dropdown |
| notificationCount | Number | 0 | Badge-Zaehler (max "99+") |
| pendingCorrectionsCount | Number | 0 | Amber Badge fuer offene Korrekturen |
| userMenuItems | { label, icon?, action }[] | [] | Dropdown-Eintraege |
Events: toggle-sidebar, notifications-click, corrections-click, user-menu-action(action)
Slots: breadcrumb
LuBreadcrumb
Breadcrumb-Navigation — liest Items aus Props oder automatisch aus route.meta.breadcrumb.
<LuBreadcrumb
:items="[
{ label: 'Dashboard', to: '/' },
{ label: 'Mitarbeiter', to: '/employees' },
{ label: 'Max Mustermann' },
]"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ------- | ------------------ | ----------- | ----------------------------------------------------------------- |
| items | { label, to? }[] | undefined | Breadcrumb-Pfad. Ohne Prop wird route.meta.breadcrumb verwendet |
Das letzte Item wird als aktuelle Seite dargestellt (aria-current="page").
Formulare
LuInput
Vielseitiges Textfeld mit Label, Fehler/Hinweis, Prefix/Suffix und Passwort-Toggle.
<LuInput
v-model="email"
type="email"
label="E-Mail-Adresse"
placeholder="[email protected]"
prefix="@"
required
:error="errors.email"
hint="Ihre geschaeftliche E-Mail-Adresse"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ------------- | --------------------------------------------------------- | -------- | ------------------------------------ |
| modelValue | String \| Number | '' | Eingabewert (v-model) |
| type | 'text' \| 'password' \| 'number' \| 'email' \| 'search' | 'text' | Input-Typ |
| label | String | '' | Label-Text |
| placeholder | String | '' | Platzhaltertext |
| error | String | '' | Fehlermeldung (aktiviert roten Rand) |
| hint | String | '' | Hinweistext (versteckt bei Fehler) |
| prefix | String | '' | Prefix-Text links im Input |
| suffix | String | '' | Suffix-Text rechts im Input |
| disabled | Boolean | false | Deaktiviert |
| readonly | Boolean | false | Schreibgeschuetzt |
| required | Boolean | false | Pflichtfeld (roter Stern) |
Expose: { focus } — Fokus programmatisch setzen.
Zusaetzliche HTML-Attribute (min, max, step, pattern, maxlength) werden an das native <input> weitergeleitet.
LuTextarea
Mehrzeiliges Textfeld mit Label, Fehler/Hinweis und konfigurierbarer Groessenanpassung.
<LuTextarea
v-model="notes"
label="Notizen"
placeholder="Optionale Anmerkungen..."
:rows="5"
resize="vertical"
:error="errors.notes"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ------------- | -------------------------------- | ------------ | -------------------- |
| modelValue | String | '' | Textwert (v-model) |
| label | String | '' | Label-Text |
| placeholder | String | '' | Platzhaltertext |
| error | String | '' | Fehlermeldung |
| hint | String | '' | Hinweistext |
| disabled | Boolean | false | Deaktiviert |
| readonly | Boolean | false | Schreibgeschuetzt |
| required | Boolean | false | Pflichtfeld |
| rows | Number \| String | 3 | Sichtbare Textzeilen |
| resize | 'none' \| 'vertical' \| 'both' | 'vertical' | CSS-Resize-Verhalten |
LuSelect
Dropdown-Select mit Suche, Mehrfachauswahl und gruppierten Optionen.
<!-- Einfache Auswahl -->
<LuSelect
v-model="department"
label="Abteilung"
:options="[
{ value: 'hr', label: 'Personal' },
{ value: 'it', label: 'IT' },
{ value: 'sales', label: 'Vertrieb' },
]"
searchable
/>
<!-- Mehrfachauswahl mit Gruppen -->
<LuSelect
v-model="selectedRoles"
label="Rollen"
multiple
searchable
:options="[
{
group: 'Management',
items: [
{ value: 'GF', label: 'Geschaeftsfuehrung' },
{ value: 'HR', label: 'Personal' },
],
},
{
group: 'Operativ',
items: [
{ value: 'TEAMLEITUNG', label: 'Teamleitung' },
{ value: 'MITARBEITER', label: 'Mitarbeiter' },
],
},
]"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ------------- | --------- | -------------------- | ---------------------------------------------------------------- |
| modelValue | any | null | Ausgewaehlter Wert (v-model) |
| options | Array | [] | Flach: [{ value, label }] oder gruppiert: [{ group, items }] |
| label | String | '' | Label-Text |
| error | String | '' | Fehlermeldung |
| placeholder | String | 'Bitte waehlen...' | Platzhaltertext |
| searchable | Boolean | false | Suchfeld aktivieren |
| multiple | Boolean | false | Mehrfachauswahl mit Chips |
| disabled | Boolean | false | Deaktiviert |
Multi-Select zeigt ausgewaehlte Werte als Teal-Chips mit Entfernen-Button. Suche ist akzent-unabhaengig (NFD-Normalisierung).
LuDatePicker
Datumseingabe mit Kalender-Overlay, Monatsnavigation und optionalem Range-Highlighting.
<LuDatePicker
v-model="startDate"
label="Startdatum"
placeholder="TT.MM.JJJJ"
required
:range-start="rangeStart"
:range-end="rangeEnd"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ------------- | --------- | -------------- | --------------------------------------- |
| modelValue | String | '' | ISO-Datumsstring YYYY-MM-DD (v-model) |
| label | String | '' | Label-Text |
| placeholder | String | 'TT.MM.JJJJ' | Platzhaltertext |
| error | String | '' | Fehlermeldung |
| disabled | Boolean | false | Deaktiviert |
| required | Boolean | false | Pflichtfeld |
| rangeStart | String | '' | ISO-Datum fuer Range-Start-Hervorhebung |
| rangeEnd | String | '' | ISO-Datum fuer Range-Ende-Hervorhebung |
Deutsche Lokalisierung (date-fns de). Woche beginnt am Montag. Anzeige als dd.MM.yyyy.
LuCheckbox
Checkbox mit Boolean- und Array-Modus sowie Indeterminate-State.
<!-- Boolean-Modus -->
<LuCheckbox v-model="accepted" label="AGB akzeptieren" />
<!-- Array-Modus (Mehrfachauswahl) -->
<LuCheckbox v-model="selectedDays" value="MO" label="Montag" />
<LuCheckbox v-model="selectedDays" value="DI" label="Dienstag" />
<!-- Indeterminate ("Alle auswaehlen") -->
<LuCheckbox v-model="allSelected" :indeterminate="partiallySelected" label="Alle" />Props:
| Prop | Typ | Default | Beschreibung |
| --------------- | ------------------ | ----------- | --------------------------- |
| modelValue | Boolean \| Array | false | Checked-Status (v-model) |
| value | String \| Number | undefined | Wert im Array-Modus |
| label | String | '' | Label-Text |
| disabled | Boolean | false | Deaktiviert |
| indeterminate | Boolean | false | Zeigt Strich statt Haekchen |
LuRadio
Radio-Button fuer Einzelauswahl in Gruppen.
<LuRadio v-model="status" value="active" label="Aktiv" name="status" />
<LuRadio v-model="status" value="inactive" label="Inaktiv" name="status" />Props:
| Prop | Typ | Default | Beschreibung |
| ------------ | ----------------------------- | ------- | --------------------------------------- |
| modelValue | String \| Number \| Boolean | '' | Ausgewaehlter Wert der Gruppe (v-model) |
| value | String \| Number \| Boolean | — | Wert dieser Radio-Option |
| label | String | '' | Label-Text |
| name | String | '' | Name-Attribut fuer Gruppierung |
| disabled | Boolean | false | Deaktiviert |
LuToggle
Toggle-Switch mit Slide-Animation.
<LuToggle v-model="darkMode" label="Dunkelmodus" />Props:
| Prop | Typ | Default | Beschreibung |
| ------------ | --------- | ------- | ----------------------- |
| modelValue | Boolean | false | Toggle-Status (v-model) |
| label | String | '' | Label-Text |
| disabled | Boolean | false | Deaktiviert |
LuFormGroup
Container fuer konsistentes Label-, Fehler- und Hinweis-Layout um Formularfelder.
<LuFormGroup label="Abteilung" error="Bitte Abteilung waehlen" required>
<LuSelect v-model="department" :options="departments" />
</LuFormGroup>Props:
| Prop | Typ | Default | Beschreibung |
| ---------- | --------- | ------- | ------------------------------------- |
| label | String | '' | Label-Text (uppercase, tracking-wide) |
| error | String | '' | Fehlermeldung mit AlertCircle-Icon |
| hint | String | '' | Hinweistext (versteckt bei Fehler) |
| for | String | '' | HTML for-Attribut |
| required | Boolean | false | Roter Stern am Label |
Slots: default — Formularfeld(er)
Datendarstellung
LuTable
Datentabelle mit Sortierung, Zebra-Striping, Loading-Skeleton und Scoped Slots.
<LuTable
:columns="[
{ key: 'name', label: 'Name', sortable: true },
{ key: 'department', label: 'Abteilung', sortable: true, hideOnMobile: true },
{ key: 'status', label: 'Status' },
{ key: 'actions', label: '', width: '80px' },
]"
:data="employees"
:loading="isLoading"
:sort-field="sortField"
:sort-dir="sortDir"
@sort="handleSort"
@row-click="openEmployee"
>
<template #cell-status="{ value }">
<LuBadge :variant="value === 'active' ? 'success' : 'neutral'">
{{ value === 'active' ? 'Aktiv' : 'Inaktiv' }}
</LuBadge>
</template>
<template #cell-actions="{ row }">
<LuButton variant="ghost" size="sm" @click.stop="editEmployee(row)">
<template #icon-left><LuIcon name="Pencil" :size="16" /></template>
</LuButton>
</template>
<template #empty>Keine Mitarbeiter gefunden.</template>
</LuTable>Props:
| Prop | Typ | Default | Beschreibung |
| ----------- | ------------------ | ------- | -------------------------------------------------------- |
| columns | Column[] | — | Spaltendefinitionen |
| data | Object[] | [] | Zeilendaten |
| loading | Boolean | false | Zeigt 3 Skeleton-Zeilen |
| sortField | String | '' | Aktives Sortierfeld |
| sortDir | 'asc' \| 'desc' | 'asc' | Sortierrichtung |
| rowKey | String | 'id' | Property fuer Zeilen-Key (Fallback: _id, dann Index) |
| rowClass | Function \| null | null | (row, index) => string fuer individuelle Zeilenklassen |
Column-Definition:
{ key: 'name', label: 'Name', sortable: true, hideOnMobile: true, width: '200px' }Events: sort(field), row-click(row, index)
Slots: cell-{key}({ row, value }), header-{key}({ column }), empty, row({ row, index })
LuPagination
Seitennavigation mit Seiten-Links, Vor/Zurueck und Items-pro-Seite-Auswahl.
<LuPagination
:current-page="page"
:total-pages="totalPages"
:total-items="487"
:items-per-page="25"
@change="page = $event"
@change-per-page="perPage = $event"
/>Props:
| Prop | Typ | Default | Beschreibung |
| -------------- | -------- | ------- | -------------------------- |
| currentPage | Number | — | Aktuelle Seite (1-basiert) |
| totalPages | Number | — | Gesamtseitenanzahl |
| totalItems | Number | 0 | Gesamtanzahl Eintraege |
| itemsPerPage | Number | 10 | Eintraege pro Seite |
Events: change(page), change-per-page(perPage)
Zeigt Items-pro-Seite-Selektor (10/25/50), intelligente Seitenwindows mit Ellipsis, und Eintrags-Bereich (z.B. "1-25 von 487 Eintraegen").
Feedback & Overlay
LuModal
Modaler Dialog mit Backdrop, ESC-Schliessen und Bottom-Sheet auf Mobile.
<LuModal v-model="showModal" title="Mitarbeiter bearbeiten" size="lg" persistent>
<form @submit.prevent="save">
<LuInput v-model="name" label="Name" />
</form>
<template #footer>
<LuButton variant="secondary" @click="showModal = false">Abbrechen</LuButton>
<LuButton @click="save" :loading="saving">Speichern</LuButton>
</template>
</LuModal>Props:
| Prop | Typ | Default | Beschreibung |
| ------------ | ------------------------------ | ------- | ------------------------------------------ |
| modelValue | Boolean | — | Sichtbarkeit (v-model) |
| title | String | '' | Titel in der Kopfzeile |
| size | 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | sm=400px, md=560px, lg=768px, xl=1024px |
| persistent | Boolean | false | Verhindert Schliessen durch Backdrop-Klick |
Slots: default (Body, scrollbar), footer (Aktionen)
Features: Focus-Trap (Tab/Shift+Tab), Body-Scroll-Lock, Bottom-Sheet unter 768px, prefers-reduced-motion Support.
Empfehlung: useModal() Composable fuer einfache Zustandsverwaltung.
LuAlert
Inline-Alert fuer kontextbezogene Rueckmeldungen.
<LuAlert
variant="warning"
title="Achtung"
message="Ihre Sitzung laeuft in 5 Minuten ab."
dismissible
@dismiss="hideWarning"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ------------- | --------------------------------------------- | -------- | -------------------------- |
| variant | 'info' \| 'warning' \| 'error' \| 'success' | 'info' | Semantische Variante |
| title | String | '' | Optionale fette Titelzeile |
| message | String | '' | Nachrichtentext |
| dismissible | Boolean | false | Zeigt Schliessen-Button |
Events: dismiss
Icons pro Variante: success=CircleCheck, warning=TriangleAlert, error=XCircle, info=Info.
LuToast
Toast-Benachrichtigungs-Container. Einmal im App-Root einbinden, Steuerung via useToast().
<!-- In App.vue (einmalig einbinden) -->
<template>
<LuAppLayout>
<RouterView />
</LuAppLayout>
<LuToast />
</template>// In beliebiger Komponente
import { useToast } from '@vss-software/ui'
const { addToast } = useToast()
addToast({
variant: 'success',
title: 'Gespeichert',
message: 'Mitarbeiterdaten wurden aktualisiert.',
duration: 5000,
})Teleportiert zu <body>. Fixiert unten rechts. Auto-Dismiss nach duration (Standard: 5000ms, 0 = kein Auto-Dismiss).
LuDropdown
Dropdown-Menue mit selektierbaren Optionen, Trennlinien und optionalen Beschreibungen.
<LuDropdown
label="Exportieren"
:options="[
{ value: 'csv', label: 'CSV-Export', description: 'Komma-getrennte Werte' },
{ value: 'xlsx', label: 'Excel-Export' },
{ divider: true },
{ value: 'datev', label: 'DATEV-Export', icon: 'FileSpreadsheet' },
]"
@select="handleExport"
/>Props:
| Prop | Typ | Default | Beschreibung |
| --------- | ------------------------------------- | ------------- | ------------------ |
| label | String | — | Button-Label |
| options | DropdownOption[] | [] | Menue-Optionen |
| icon | String \| Object | null | Icon vor dem Label |
| variant | 'primary' \| 'secondary' \| 'ghost' | 'secondary' | Button-Variante |
| size | 'sm' \| 'md' \| 'lg' | 'sm' | Button-Groesse |
DropdownOption:
{ value: 'id', label: 'Text', description?: 'Zusatzinfo', disabled?: false, icon?: 'LucideName', divider?: false }Events: select(value)
Allgemeine UI
LuButton
Vielseitiger Button fuer primaere, sekundaere, Danger-, Ghost- und Icon-Aktionen.
<LuButton @click="save" :loading="saving">
<template #icon-left><LuIcon name="Save" :size="16" /></template>
Speichern
</LuButton>
<LuButton variant="danger" size="sm">Loeschen</LuButton>
<LuButton variant="ghost">Abbrechen</LuButton>
<LuButton variant="icon" aria-label="Bearbeiten">
<LuIcon name="Pencil" :size="18" />
</LuButton>Props:
| Prop | Typ | Default | Beschreibung |
| ------------- | ----------------------------------------------------------- | ------------------- | ------------------------------------------- |
| variant | 'primary' \| 'secondary' \| 'danger' \| 'ghost' \| 'icon' | 'primary' | Visuelle Variante |
| size | 'sm' \| 'md' \| 'lg' | 'md' | sm=32px, md=40px, lg=48px Hoehe |
| disabled | Boolean | false | Deaktiviert |
| loading | Boolean | false | Zeigt Lade-Spinner, deaktiviert Interaktion |
| type | 'button' \| 'submit' \| 'reset' | 'button' | HTML button type |
| loadingText | String | 'Wird geladen...' | Screenreader-Ansage bei Loading |
Slots: default (Label), icon-left, icon-right
LuCard
Flexible Karte mit Standard- und KPI-Variante.
<!-- Standard -->
<LuCard>
<template #header>
<h2>Mitarbeiterliste</h2>
</template>
<LuTable :columns="cols" :data="rows" />
<template #footer>
<LuPagination :current-page="1" :total-pages="5" />
</template>
</LuCard>
<!-- KPI -->
<LuCard variant="kpi" value="42" label="Offene Antraege" trend="+12%" trend-direction="up" />Props:
| Prop | Typ | Default | Beschreibung |
| ---------------- | ----------------------------- | ------------ | ---------------------------------------- |
| variant | 'standard' \| 'kpi' | 'standard' | Darstellungsvariante |
| value | String \| Number | undefined | KPI-Wert (nur kpi-Variante) |
| label | String | '' | KPI-Label (nur kpi-Variante) |
| trend | String | '' | Trend-Text z.B. "+5%" (nur kpi-Variante) |
| trendDirection | 'up' \| 'down' \| 'neutral' | 'neutral' | Trend-Pfeil und Farbe |
Slots (Standard): default (Body), header, footer
LuKpiCard
Kompakte KPI-Karte fuer Dashboards mit Status-Farbgebung und Trend-Indikator.
<LuKpiCard
label="Krankenquote"
value="4.2%"
status="warning"
:trend="-0.3"
subtext="vs. Vormonat"
/>Props:
| Prop | Typ | Default | Beschreibung |
| --------- | ------------------------------------------------ | ----------- | -------------------------------------------------------- |
| label | String | — | Label (uppercase, xs) |
| value | String \| Number | — | KPI-Wert (gross dargestellt) |
| status | 'success' \| 'warning' \| 'error' \| 'default' | 'default' | Wert-Textfarbe |
| trend | Number | undefined | Trend-Prozent (positiv=TrendingUp, negativ=TrendingDown) |
| subtext | String | undefined | Zusatztext unter dem Wert |
LuBadge
Pill-foermiges Badge fuer Status- und Kategorie-Anzeigen.
<LuBadge variant="success">Aktiv</LuBadge>
<LuBadge variant="warning" size="sm">Ausstehend</LuBadge>
<LuBadge variant="error">Abgelehnt</LuBadge>Props:
| Prop | Typ | Default | Beschreibung |
| --------- | ---------------------------------------------------------- | ----------- | ------------ |
| variant | 'success' \| 'warning' \| 'error' \| 'info' \| 'neutral' | 'neutral' | Farbvariante |
| size | 'sm' \| 'md' | 'md' | Groesse |
Slots: default (Label-Text)
LuAvatar
Avatar mit Initialen-Fallback und optionalem Bild. Farbe wird deterministisch aus dem Namen berechnet.
<LuAvatar name="Max Mustermann" size="lg" />
<LuAvatar name="Anna Schmidt" src="/avatars/anna.jpg" size="md" />Props:
| Prop | Typ | Default | Beschreibung |
| ------ | ------------------------------ | ------- | ------------------------------------------------------- |
| name | String | — | Vollstaendiger Name (fuer Initialen und Farbberechnung) |
| src | String | '' | Optionale Bild-URL |
| size | 'sm' \| 'md' \| 'lg' \| 'xl' | 'md' | sm=32px, md=40px, lg=48px, xl=64px |
Bild wird vorab geladen. Bei Fehler werden 2-Buchstaben-Initialen auf farbigem Hintergrund angezeigt (6 deterministische Farben).
LuIcon
Dynamischer Lucide-Icon-Wrapper mit Lazy-Loading und Caching.
<LuIcon name="Clock" :size="24" color="#00BFA6" />
<LuIcon name="Users" />
<LuIcon name="AlertTriangle" :stroke-width="2" />Props:
| Prop | Typ | Default | Beschreibung |
| ------------- | ------------------ | ---------------- | ------------------------------ |
| name | String | — | Lucide-Icon-Name in PascalCase |
| size | Number \| String | 20 | Groesse in Pixeln |
| color | String | 'currentColor' | CSS-Farbwert |
| strokeWidth | Number \| String | 1.5 | SVG Strichstaerke |
Icons werden on-demand geladen und im Cache gehalten. Vollstaendige Icon-Liste: lucide.dev/icons
LuSkeleton
Lade-Platzhalter mit Shimmer-Animation.
<LuSkeleton width="200px" height="20px" />
<LuSkeleton width="48px" height="48px" rounded />
<LuSkeleton height="120px" />Props:
| Prop | Typ | Default | Beschreibung |
| --------- | --------- | -------- | -------------------------- |
| width | String | '100%' | CSS-Breite |
| height | String | '16px' | CSS-Hoehe |
| rounded | Boolean | false | Komplett rund (Kreis/Pill) |
Respektiert prefers-reduced-motion.
LuEmptyState
Zentrierter Platzhalter fuer leere Ansichten.
<LuEmptyState
icon="Users"
title="Keine Mitarbeiter gefunden"
description="Passen Sie Ihre Filterkriterien an oder legen Sie einen neuen Mitarbeiter an."
action-label="Mitarbeiter anlegen"
:action-handler="createEmployee"
/>Props:
| Prop | Typ | Default | Beschreibung |
| --------------- | ---------- | ------- | -------------------- |
| icon | String | '' | Lucide-Icon-Name |
| title | String | — | Ueberschrift |
| description | String | '' | Beschreibungstext |
| actionLabel | String | '' | Button-Text |
| actionHandler | Function | null | Button-Click-Handler |
Button nur sichtbar wenn actionLabel und actionHandler gesetzt sind.
LuActionBar
Fixierte Aktionsleiste am unteren Bildschirmrand fuer Bulk-Operationen.
<LuActionBar
:selected-count="selectedIds.length"
:loading="processing"
:show-delete="canDelete"
:show-export="true"
:show-status="true"
:show-department="false"
@cancel="clearSelection"
@action-delete="deleteSelected"
@action-export="exportSelected"
@action-status="changeStatus"
>
<template #actions>
<LuButton variant="secondary" size="sm" @click="customAction">
Eigene Aktion
</LuButton>
</template>
</LuActionBar>Props:
| Prop | Typ | Default | Beschreibung |
| ---------------- | --------- | ------- | ----------------------------- |
| selectedCount | Number | — | Anzahl ausgewaehlter Elemente |
| loading | Boolean | false | Ladezustand |
| showDelete | Boolean | true | Loeschen-Button anzeigen |
| showExport | Boolean | true | Export-Button anzeigen |
| showStatus | Boolean | true | Status-Button anzeigen |
| showDepartment | Boolean | true | Abteilungs-Button anzeigen |
Events: cancel, action-status, action-department, action-export, action-delete
Slots: actions — zusaetzliche eigene Aktionsbuttons
Wird nur angezeigt wenn selectedCount > 0. Slide-Up-Animation. Dunkler Hintergrund (gray-900).
Charts
Alle Chart-Komponenten nutzen Chart.js via vue-chartjs und wenden automatisch die lumen.hr Design-Defaults an.
LuLineChart
<LuLineChart
:data="{
labels: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun'],
datasets: [{ label: 'Ueberstunden', data: [12, 19, 3, 5, 2, 3] }],
}"
height="300px"
aria-label="Ueberstunden pro Monat"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ----------- | ---------------------- | ------------------ | ----------------------------------- |
| data | ChartData<'line'> | — | Chart.js Daten |
| options | ChartOptions<'line'> | {} | Optionen (deep-merged mit Defaults) |
| height | String | '100%' | CSS-Hoehe |
| ariaLabel | String | 'Liniendiagramm' | Barrierefreie Beschriftung |
Defaults: Teal-500 Linie, 10% Teal-Fuellung, 3px Punkte, 0.3 Kurvenspannung.
LuBarChart
<LuBarChart
:data="{
labels: ['Mo', 'Di', 'Mi', 'Do', 'Fr'],
datasets: [{ label: 'Anwesenheit', data: [45, 42, 48, 43, 40] }],
}"
height="250px"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ----------- | --------------------- | ------------------ | ----------------------------------- |
| data | ChartData<'bar'> | — | Chart.js Daten |
| options | ChartOptions<'bar'> | {} | Optionen (deep-merged mit Defaults) |
| height | String | '100%' | CSS-Hoehe |
| ariaLabel | String | 'Balkendiagramm' | Barrierefreie Beschriftung |
Defaults: Teal-500 Hintergrund, 4px abgerundete Ecken.
LuDonutChart
<LuDonutChart
:data="{
labels: ['Anwesend', 'Urlaub', 'Krank', 'Abwesend'],
datasets: [{ data: [35, 5, 3, 2] }],
}"
height="300px"
/>Props:
| Prop | Typ | Default | Beschreibung |
| ----------- | -------------------------- | ----------------- | ----------------------------------- |
| data | ChartData<'doughnut'> | — | Chart.js Daten |
| options | ChartOptions<'doughnut'> | {} | Optionen (deep-merged mit Defaults) |
| height | String | '300px' | CSS-Hoehe |
| ariaLabel | String | 'Kreisdiagramm' | Barrierefreie Beschriftung |
Defaults: 65% Cutout (Donut), 10-Farben-Palette im "Professional Calm"-Stil, Legende unten. Tooltip zeigt Label, Wert und berechneten Prozentsatz.
Tabs (Compound Pattern)
Barrierefreies Tab-System nach WAI-ARIA Tabs Pattern mit Tastaturnavigation (Pfeiltasten, Home, End).
<LuTabs v-model="activeTab">
<LuTabList>
<LuTab value="overview">Uebersicht</LuTab>
<LuTab value="details">Details</LuTab>
<LuTab value="history">Verlauf</LuTab>
</LuTabList>
<LuTabPanel value="overview">
<p>Uebersichts-Inhalt...</p>
</LuTabPanel>
<LuTabPanel value="details">
<p>Detail-Inhalt...</p>
</LuTabPanel>
<LuTabPanel value="history">
<p>Verlaufs-Inhalt...</p>
</LuTabPanel>
</LuTabs>LuTabs
Root-Container. Stellt aktiven Tab-Status via provide/inject bereit.
| Prop | Typ | Default | Beschreibung |
| ------------ | -------- | ------- | -------------------------- |
| modelValue | String | '' | Aktiver Tab-Wert (v-model) |
LuTabList
Horizontaler Container fuer Tab-Buttons mit WAI-ARIA Tastaturnavigation.
LuTab
Einzelner Tab-Button. role="tab", aria-selected, aria-controls.
| Prop | Typ | Default | Beschreibung |
| ------- | -------- | ------- | --------------------------------------------------- |
| value | String | — | Eindeutige ID (muss mit LuTabPanel uebereinstimmen) |
Slots: default (Tab-Label)
LuTabPanel
Content-Panel. Wird nur gerendert wenn der zugehoerige Tab aktiv ist (v-if).
| Prop | Typ | Default | Beschreibung |
| ------- | -------- | ------- | ----------------------------------------------------------- |
| value | String | — | Muss mit dem value des zugehoerigen LuTab uebereinstimmen |
Slots: default (Panel-Inhalt)
Composables
useToast()
Globaler Toast-Zustand. Wird von <LuToast> fuer die Darstellung genutzt.
import { useToast } from '@vss-software/ui'
const { toasts, addToast, removeToast } = useToast()
// Toast hinzufuegen
const id = addToast({
variant: 'success', // 'success' | 'warning' | 'error' | 'info'
title: 'Gespeichert', // Optional
message: 'Aenderungen uebernommen.',
duration: 5000, // ms, 0 = kein Auto-Dismiss
})
// Toast manuell entfernen
removeToast(id)| Return | Typ | Beschreibung |
| ------------- | --------------------- | ---------------------------------- |
| toasts | Toast[] (reactive) | Alle aktiven Toasts |
| addToast | (options) => number | Toast hinzufuegen, gibt ID zurueck |
| removeToast | (id) => void | Toast entfernen |
useModal()
Einfache reaktive Zustandsverwaltung fuer Modals.
import { useModal } from '@vss-software/ui'
const { isOpen, open, close, toggle } = useModal()<LuButton @click="open">Dialog oeffnen</LuButton>
<LuModal v-model="isOpen" title="Mein Dialog">
Inhalt...
</LuModal>| Return | Typ | Beschreibung |
| -------- | -------------- | ----------------------------- |
| isOpen | Ref<boolean> | Reaktiver Sichtbarkeitsstatus |
| open | () => void | Oeffnen |
| close | () => void | Schliessen |
| toggle | () => void | Umschalten |
useClickOutside()
Erkennt Klicks ausserhalb eines Elements.
import { useClickOutside } from '@vss-software/ui'
import { ref } from 'vue'
const dropdownRef = ref(null)
useClickOutside(dropdownRef, () => {
isOpen.value = false
})| Parameter | Typ | Beschreibung |
| ----------- | -------------------------- | ------------------------------------ |
| targetRef | Ref<HTMLElement \| null> | Ref zum Begrenzungselement |
| callback | () => void | Wird bei Klick ausserhalb aufgerufen |
Registriert mousedown und touchstart Listener. Bereinigt automatisch bei Unmount.
Utilities
Chart Defaults
import {
baseChartOptions,
lineDatasetDefaults,
barDatasetDefaults,
deepMerge,
} from '@vss-software/ui'| Export | Beschreibung |
| --------------------------- | ---------------------------------------------------------------------------------- |
| baseChartOptions | Gemeinsame Chart.js Optionen (responsive, Tooltip-Stil, Achsen-Formatierung) |
| lineDatasetDefaults | Linien-Defaults: Teal-500, 10% Fuellung, 3px Punkte, 0.3 Spannung |
| barDatasetDefaults | Balken-Defaults: Teal-500, 4px Border-Radius |
| deepMerge(target, source) | Deep-Merge zweier Objekte. Arrays werden ersetzt. Schutz gegen Prototype-Pollution |
Barrierefreiheit (a11y)
Alle Komponenten folgen WCAG 2.1 AA Richtlinien:
- ARIA-Rollen und -Attribute auf allen interaktiven Elementen
- Tastaturnavigation (Tab, Enter, Space, Escape, Pfeiltasten)
- Focus-Management mit sichtbarem Fokusring (Teal-500, 2px)
- Focus-Trap in Modals
- Screen-Reader-Unterstuetzung via
aria-label,aria-live,role - Reduzierte Bewegung (
prefers-reduced-motion) wird respektiert - Farbkontraste eingehalten (mindestens 4.5:1)
Lizenz
Proprietaer. Alle Rechte vorbehalten.
