@openeos/ui
v0.7.0
Published
OpenEOS Designsystem — Tokens, Klassenbibliothek und React-Komponenten für Admin, POS, Shop und Displays
Downloads
490
Readme
@openeos/ui
Das OpenEOS-Designsystem: Tokens, Klassenbibliothek und React-Komponenten für Admin, POS, Shop, Landing und Displays.
Ein Paket, damit die Optik an genau einer Stelle gepflegt wird und alle Repos sie per Versionssprung übernehmen.
Installation
pnpm add @openeos/uiEinbinden
1. Styles
Einmal pro Anwendung, im Root-Layout:
import '@openeos/ui/styles.css';Das bringt Tokens (hell und dunkel), die .oe-*-Klassenbibliothek und
die (veralteten) --pos-*-Aliase mit.
2. Schriften (Next.js)
import { openEosFonts } from '@openeos/ui/fonts';
export default function RootLayout({ children }) {
return <html className={openEosFonts.className}>{/* … */}</html>;
}Setzt --font-oe-sans (Geist) und --font-oe-mono (JetBrains Mono) —
genau die Variablen, die tokens.css erwartet.
Display-Schrift ist seit 0.3.3 Geist Extra Bold (800), vorher
Bricolage Grotesque. Es gibt nur noch zwei Familien: Geist und
JetBrains Mono. .oe-display setzt Gewicht 800, Laufweite -0.04em
und Zeilenhöhe 0.98; für sehr große Headlines (ab ca. 80px) darf es
-0.045em sein. font-variation-settings: "opsz" … hat bei Geist
keine Wirkung und kann in eigenem CSS entfallen. .oe-italic wird
synthetisch geneigt (Geist hat keinen Kursivschnitt).
--font-oe-display setzt tokens.css als Alias auf --font-oe-sans —
dieselbe Datei, kein zweiter Download. Wer tokens.css nicht einbindet,
verwendet für Überschriften direkt var(--font-oe-sans). Der Export
bricolageDisplay bleibt als veralteter Alias auf geistSans bestehen.
Die Schriften liegen seit 0.3.2 als WOFF2 im Paket und werden über
next/font/local eingebunden — weder next build noch der Browser
greifen auf Google Fonts zu (vorher next/font/google). Variable Fonts
aus Fontsource, Teilmengen latin und latin-ext (Umlaute, ß, ẞ, €);
Lizenz jeweils SIL OFL 1.1, siehe src/fonts/files/OFL-*.txt.
Verbraucher müssen nichts ändern.
3. Untitled-UI-Bridge (nur Tailwind-Projekte)
Projekte mit Untitled UI binden statt styles.css die Bridge in ihre
Tailwind-Einstiegsdatei ein. Sie färbt die beiden Primitiv-Ramps
(--color-neutral-*, --color-brand-*) auf die OpenEOS-Palette um —
damit übernehmen alle bestehenden Untitled-Komponenten die neue Optik,
ohne dass eine einzige angefasst werden muss.
@import "tailwindcss";
@import "./theme.css";
@import "@openeos/ui/css/bridge-untitled.css"; /* muss NACH theme.css stehen */
@import "@openeos/ui/css/index.css";Die Reihenfolge ist nicht optional: Tailwind führt alle @theme-Blöcke
in Quellreihenfolge zusammen, der spätere Wert gewinnt.
Dark-Mode
Der Entwurf war hell. Die dunkle Palette ist daraus abgeleitet und
greift bei .dark-mode (Untitled UI), .dark (Tailwind-Konvention)
oder [data-theme="dark"].
Zwei Tokengruppen existieren eigens dafür — wer eigene Komponenten schreibt, sollte sie kennen:
| Token | Wofür |
|---|---|
| --oe-primary / --oe-on-primary | Primäraktion. Nicht --oe-ink benutzen: das ist im Dunkeln die Textfarbe und würde weiße Flächen erzeugen. |
| --oe-contrast* / --oe-on-contrast* | Invertierte Flächen (Ink-Karten, KPI, POS-Kacheln, Toast, Tooltip). Im Dunkeln werden sie angehoben statt invertiert. |
| --oe-on-accent | Kontrast auf --oe-green-ink-Füllungen (Häkchen, Meter). |
| --oe-on-signal | Kontrast auf --oe-green — immer dunkel, weil Signalgrün in beiden Modi hell ist. |
React
import { Button, Card, CardHead, CardBody, Kpi, Kpis } from '@openeos/ui';
<Kpis>
<Kpi label="Umsatz heute" value="1.284,50 €" variant="accent" />
</Kpis>
<Card variant="raised">
<CardHead title="Bestellungen" actions={<Button variant="primary">Neu</Button>} />
<CardBody>…</CardBody>
</Card>Die Komponenten sind bewusst dünn: sie setzen Klassen und Struktur und
halten keinen fachlichen Zustand. Ausnahmen mit eigenem Verhalten sind
Sheet (Portal, Fokusfalle, Escape, Scroll-Sperre, Wischen) und
FloorPlan im Bearbeitungsmodus (Ziehen, Eckgriff, Werkzeuge, Tastatur). Alle Texte
sind Props mit deutschem Default (du-Form); Anwendungen übergeben
übersetzte Texte.
Kasse (seit 0.4.0)
| Komponente | Zweck | Klassen |
|---|---|---|
| Icon | Linien-Icon aus dem OpenEOS-Set (Lucide) (name, size, label, filled) | .oe-icon |
| IconBox | Icon oder Bild auf getönter Fläche, optional Mengenkreis; accent (seit 0.5.1) färbt Fläche und Icon in einer eigenen Farbe, z. B. der Kategoriefarbe, mit mindestens 3:1 Kontrast in Hell und Dunkel | .oe-icobox*, .oe-icobox--tint + --oe-icobox-accent |
| Tile variant="product" | Produktkachel: Name, Zeile 2, Icon-Box, Menge, Preis, Hinweise | .oe-tile--product, .oe-tile__* |
| TableChip, TableGrid | Tisch-Kachel (state, current, size="lg") und Raster (min) | .oe-tablechip*, .oe-tables |
| Legend | Legende der Tischfarben | .oe-legend* |
| Keypad | Ziffernblock mit Icon-Tasten, size="lg", captureKeyboard | .oe-keypad* |
| Segment | jetzt mit icon, disabled, size="lg" | .oe-segment* |
| Stepper | Menge −/+, removeAtMin zeigt Papierkorb | .oe-stepper* |
| ChoiceGroup | Zahlarten-Auswahl (radiogroup, Pfeiltasten) | .oe-choices, .oe-choice |
| Sheet | Dialog-Blatt: md 480 · wide 720 · pay 820 · done 400 px, ≤ 820 px Bottom-Sheet | .oe-sheet*, .oe-sheet-layer |
| CartLine, CartBar | Warenkorbzeile (<li> in .oe-cartlines) und Warenkorb-Leiste | .oe-cartline*, .oe-cartbar*, .oe-bump |
| CategoryNav, CategoryButton | Kategorienleiste, responsive folgt den Kassen-Breakpoints | .oe-catnav*, .oe-cat* |
| Prompt | gestrichelte Hinweisfläche mit großem Icon (Kartenterminal) | .oe-prompt* |
| UserChip | Benutzer mit Schloss-Knopf | .oe-userchip* |
| StatusPill | Kopf-Status (Online, TSE, Drucker, Uhr) | .oe-statuspill* |
| FloorPlan | Tischplan, mode="view" (Kasse) bzw. "edit" (Verwaltung); seit 0.6.0 mit Raumform, Wänden und Zonen | .oe-floor* |
| Receipt | jetzt mit Einzelpreis-Spalte, info- und note-Zeile | .oe-receipt* |
Ohne React-Komponente, als Klassen: .oe-due / .oe-due__amount
(zu zahlender Betrag), .oe-given (Gegeben; leer zeigt einen CSS-Strich),
.oe-change / .oe-change--neg (Rückgeld bzw. fehlender Betrag).
FloorPlan setzt Lage und Größe ausschließlich als CSS-Variablen in
Prozent (--x, --y, --w, --h, --r); die Fläche skaliert über
aspect-ratio. Die Rechenfunktionen (snapToGrid, clampToArea,
pxToUnits, rotateBy, moveRect, resizeRect, toPercent) sind
einzeln exportiert.
Seit 0.6.0: Raumform, Wände, Zonen. Neben rechteckiger Deko (decor)
nimmt FloorPlan auf:
| Prop | Typ | Darstellung |
|---|---|---|
| outline | FloorPoint[] \| null | Umriss des Raums (Polygon). Außerhalb grau schraffiert. Ohne (oder < 3 Punkte) gilt die ganze Fläche. |
| walls | FloorWall[] (id, points ≥ 2, thickness? Default 10) | Linienzug (SVG-Polyline), Stärke in Einheiten |
| zones | FloorZone[] (id, zoneType: 'kitchen' \| 'blocked' \| 'bar' \| 'other', points ≥ 3, label?) | Polygon, Farbe aus dem Typ, blocked schraffiert; Beschriftung mit Icon im Schwerpunkt. Reine Darstellung, nie anklickbar in view. |
Koordinaten in Einheiten des Bereichs; das SVG liegt mit
viewBox="0 0 width height" unter Deko und Tischen. Im Bearbeitungsmodus
wählt tool das Werkzeug:
select(Default): Tische/Deko wie bisher; Wände und Zonen anklicken, ziehen (verschiebt alle Punkte), Punktgriffe ziehen, an Kantenmitten Punkte einfügen, Entf löscht den gewählten Punkt bzw. ohne Punktauswahl das Element (onDelete(id, 'wall' | 'zone')), Pfeiltasten verschieben. Änderungen kommen alsonShapeCommit({ id, kind, points }).wall/zone: Punkte klicken/tippen, Einrasten ans Raster und an 0/45/90°; Doppelklick, Enter, „Fertig“ oder (Zone) Tipp auf den ersten Punkt beendet →onShapeCreate({ kind, points }). Rücktaste nimmt den letzten Punkt, Esc bricht ab (ohne Punkte:onToolCancel()).outline: Umriss (Default: Rechteck der Fläche) mit Punktgriffen bearbeiten →onOutlineCommit(points); „Fertig“ ruftonToolCancel().
Tische außerhalb des Umrisses bzw. in gesperrten Zonen markiert der
Bearbeitungsmodus (.has-issue, Warn-Icon, Zusatz im aria-label;
abschaltbar mit showIssues={false}). Alle Texte über labels
(FLOOR_LABELS_DE). Rechenfunktionen ohne DOM: snapPoint,
pointInPolygon, polygonCentroid, polygonArea, movePoints,
insertPoint, removePoint, edgeMidpoints, rectOutline,
isRectOutline, tableIssue; Grenzen FLOOR_MAX_POINTS (100),
FLOOR_MIN_LINE_POINTS (2), FLOOR_MIN_POLYGON_POINTS (3).
Eine statische Übersicht aller Bausteine in hell und
dunkel liegt in examples/pos.html (nach pnpm build über einen lokalen
Server öffnen).
Admin-Tabellen (seit 0.7.0)
Dichte Listen für die Verwaltung: auf dem Desktop alles sichtbar ohne
waagerechtes Scrollen, Aktionen als Icon-Knöpfe mit Tooltip, auf dem
Telefon (≤ 767 px) jede Zeile als kompakter zweizeiliger Eintrag. Eine
Spielwiese liegt in examples/admin.html (pnpm build && pnpm examples,
dann über einen lokalen Server öffnen).
| Komponente | Zweck | Klassen |
|---|---|---|
| DataTable<T> | Tabelle aus rows + columns, Spalten-Prioritäten, Telefon-Rollen, Zeilenaktionen, Zeilenklick, dense, stickyHeader, loading, empty, footer | .oe-dt* |
| DataPager | Seitenwahl (page, pageSize, total, onChange, pageSizes, compact) | .oe-pager* |
| FilterBar | Filterzeile; ≤ 767 px nur leading + Filter-Knopf mit Zähler, der ein Sheet mit den Kindern öffnet | .oe-filterbar* |
| IconButton | Button iconOnly mit Pflicht-label (aria-label) und Tooltip; size xs/sm/md, variant wie Button, badge, href | .oe-btn--icon, .oe-btn__badge |
| Tooltip | Hover (300 ms), Tastaturfokus, langes Drücken; Portal, weicht Rändern aus, Escape schließt, aria-describedby | .oe-tip__bubble--float |
import { DataTable, DataPager, FilterBar, IconButton, Badge, Select, SearchInput } from '@openeos/ui';
import type { DataColumn, RowAction } from '@openeos/ui';
const columns: DataColumn<Order>[] = [
{ id: 'number', header: 'Nr.', cell: (o) => o.number, mobile: 'meta', width: 90 },
{ id: 'guest', header: 'Gast', cell: (o) => <span title={o.guest}>{o.guest}</span>, mobile: 'title' },
{ id: 'status', header: 'Status', cell: (o) => <Badge tone="success">Bezahlt</Badge>, mobile: 'subtitle' },
{ id: 'table', header: 'Tisch', cell: (o) => o.table, priority: 2, mobile: 'meta' },
{ id: 'items', header: 'Pos.', cell: (o) => o.items, num: true, priority: 3 },
{ id: 'total', header: 'Summe', cell: (o) => eur(o.total), num: true, mobile: 'trailing' },
];
const actions = (o: Order): RowAction[] => [
{ id: 'open', label: 'Öffnen', icon: 'eye', primary: true, href: `/orders/${o.id}` },
{ id: 'print', label: 'Bon drucken', icon: 'print', onClick: () => print(o) },
{ id: 'cancel', label: 'Stornieren', icon: 'x-circle', danger: true, hidden: o.cancelled, onClick: () => cancel(o) },
];
<FilterBar leading={<SearchInput … />} activeCount={active} onReset={reset} actions={<Button variant="primary">Neu</Button>}>
<Select aria-label="Status" …>…</Select>
</FilterBar>
<DataTable
rows={orders} rowKey="id" columns={columns} actions={actions} inlineActions={2}
dense stickyHeader caption="Bestellungen" onRowClick={(o) => router.push(`/orders/${o.id}`)}
footer={<DataPager page={page} pageSize={25} total={total} onChange={setPage} />}
/>Spalten. priority 1 (Default) ist immer da, 2 verschwindet unter
1024 px, 3 unter 1180 px — als Klassen .oe-dt__p2 / .oe-dt__p3 auf th
und td, ohne JavaScript. num setzt Mono, Tabellenziffern, rechtsbündig
und nowrap; align, width, minWidth, className wie erwartet. Die
Spalte mit mobile: 'title' (ohne Angabe: die erste) nimmt auf dem Desktop
die Restbreite und kürzt mit Auslassung.
Telefon (≤ 767 px). mobile ordnet jede Spalte einer Rolle zu: Zeile 1
= title (fett, gekürzt) + trailing (rechts, z. B. Summe oder Status),
Zeile 2 = subtitle und meta in Spaltenreihenfolge, durch einen Punkt
getrennt (leere Zellen fallen weg), hidden (Default für alle anderen) ist
unsichtbar. Der Kopf bleibt für Screenreader im Markup. <table>, <tr>,
<td> bleiben erhalten und tragen explizite Rollen (table, row,
cell, columnheader), damit getByRole('row') / getByRole('cell')
auch dann greifen, wenn CSS die Zeile als Flex-Grid zeichnet. Zelleninhalt
wird unverändert gerendert — title-Attribute bleiben.
Aktionen. actions(row) liefert RowAction[] (id, label, icon,
onClick oder href, danger, primary, disabled, hidden). Desktop:
die ersten inlineActions (Default 2) als IconButton (xs, Tooltip =
label, data-action={id}), der Rest im ⋯-Dropdown (aria-label
moreLabel, Default „Weitere Aktionen“). Telefon: nur das ⋯, das Menü
enthält alle Aktionen mit Text. Die Aktionszelle stoppt die Weitergabe —
onRowClick feuert nur bei Klicks auf Zellen (und Enter/Leertaste auf der
fokussierbaren Zeile), nicht bei Links, Knöpfen oder Feldern darin und
nicht bei markiertem Text.
Weitere Props. caption (Pflicht, nur für Screenreader), empty
(Default „Keine Einträge“), loading (aria-busy, Spinner, Zeilen
abgedunkelt), footer (unter der Tabelle, z. B. DataPager), dense,
stickyHeader (Kopf klebt am Scroll-Container; Versatz über
--oe-dt-sticky-top), rowClassName, actionsLabel, loadingLabel.
DataPager. page 1-basiert, onChange(page, pageSize); pageSizes
zeigt ein Select; ≤ 767 px oder compact nur Zurück/Weiter und „Seite 3
von 10“. Texte über labels (PAGER_LABELS_DE), pageItems(page, pages)
liefert die Zahlenreihe mit null als Lücke.
FilterBar. Kinder sind die Filterfelder (ihr Zustand gehört der
Anwendung); leading (Suche) und actions (z. B. „Neu“) bleiben immer
sichtbar. ≤ 767 px (useMediaQuery(FILTER_BAR_QUERY)) werden die Kinder
nur im Sheet gerendert, das der Filter-Knopf (Badge = activeCount)
öffnet; onReset zeigt „Zurücksetzen“ im Fuß.
Tooltip. <Tooltip label> klont sein Kind (ein Element, das Props an
sein DOM-Element durchreicht — Button, IconButton, Dropdown,
<span>) und hängt Hover-, Fokus- und Pointer-Handler an. placement
top (Default) oder bottom, delay 300 ms, disabled. Die Blase liegt
als position: fixed in einem Portal und nutzt die Kollisionslogik des
Dropdowns. always zeigt die Blase statisch wie bis 0.6 (ohne Portal).
Kassen-Breakpoints: kompakt ≤ 820 px, mittel ≤ 1180 px, darüber breit —
als breakpoints.posCompact / breakpoints.posMedium in
@openeos/ui/tokens. .oe-catnav--responsive, .oe-sheet,
.oe-tablechip--lg und .oe-keypad schalten an diesen Grenzen um.
Icons
import { Icon } from '@openeos/ui'; // nur die Komponente
import { iconNames, iconGroups, iconKeywords, iconSources, iconSvg, isIconName, legacyIcon } from '@openeos/ui/icons';
<Icon name="beer" /> // dekorativ: aria-hidden
<Icon name="lock" label="Sperren" /> // mit Bedeutung: role="img" + aria-label
<Icon name="star" filled size={20} /> // gefüllt, 20 px
iconSvg('beer', 20) // SVG-String ohne React (Doku, Landing, statische Seiten)97 Linien-Icons in fünf Gruppen (food, actions, payment,
operations, status); Größe über --oe-ico (Default 18 px), Farbe über
currentColor. Alle Icons stammen aus Lucide
(ISC) und werden zur Buildzeit ins Paket übernommen — keine
Laufzeit-Abhängigkeit. Die OpenEOS-Namen bleiben stabil; welches
Lucide-Icon dahintersteht, liefert iconSources (z. B. cart →
shopping-cart). iconKeywords liefert deutsche und englische Suchwörter
für einen Icon-Picker. Die SVG-Dateien liegen im Paket unter
src/icons/svg/ — Zuordnung, Stil und Lizenz in
src/icons/README.md.
Produkt-Icons sind keine Linien-Icons: Produktbilder (Pils,
Apfelschorle, Grillwurst …) kommen als Bild aus @openeos/pos-icons
(pos-icon:<id>); dieses Paket bildet sie seit 0.5.0 nicht mehr ab.
legacyIcon(value) bildet Emojis aus alten Kategoriedaten auf ein
OpenEOS-Icon ab; die Tabelle steht als legacyIconMap zur Verfügung, ohne
Treffer gilt LEGACY_FALLBACK_ICON (utensils).
Icons statt Zeichen
Im Markup stehen keine Emojis und keine Unicode-Symbole (⌫ ✕ ✓ ▾ → ⋯ ×)
— weder in den Komponenten noch in Anwendungen, die sie verwenden.
Pfeile, Kreuze, Haken und Rücktaste kommen aus dem Icon-Set, leere Werte
zeigt CSS (z. B. .oe-given b:empty). Seit 0.4.0 halten sich auch
Keypad, Chip und Toast daran.
Changelog
0.7.0
- DataTable (
src/react/data-table.tsx,.oe-dt*): dichte Admin-Tabelle mit Spalten-Prioritäten (3 fällt unter 1180 px, 2 unter 1024 px — reine CSS-Klassen), Telefon-Ansicht ≤ 767 px als zweizeiliger Eintrag (mobile: title | subtitle | meta | trailing | hidden), Zeilenaktionen inline alsIconButton+ Rest im ⋯-Dropdown (Telefon: alles im Dropdown mit Text),onRowClick(Maus und Tastatur, Aktionen stoppen die Weitergabe),dense,stickyHeader,caption,empty,loading,footer.<table>/<tr>/<td>mit expliziten ARIA-Rollen. - DataPager zur
.oe-pager-Klasse (Seitenzahlen mit Lücken, Select für die Seitengröße, kompakt auf dem Telefon) und FilterBar (inline; ≤ 767 px Filter-Knopf mit Zähler →Sheet). - IconButton:
Button iconOnlymit Pflicht-label(aria-label) und Tooltip,sizexs/sm/md,badge,href. - Tooltip verhält sich jetzt: öffnet bei Hover (300 ms), Tastaturfokus
und langem Drücken, Portal mit Kollisionslogik, Escape schließt,
aria-describedby. Die statische Blase von vorher gibt es mitalways. - Dropdown:
triggerVariant="icon"(quadratisch, ohne Chevron),triggerSize="xs",chevron={false}; Button-Attribute (aria-label, Handler) gehen an den Auslöser.resolvePanelPoskenntalign: 'center'. - Spielwiese
examples/admin.html(pnpm examples), Tests mit jsdom für Tooltip und Zeilenklick.
0.6.2
- Lizenz:
AGPL-3.0-or-laterstattAGPL-3.0-only(einheitlich mit allen OpenEOS-Repos), Copyright-Vermerk in README undNOTICE(liegt jetzt im Paket). - Icon
code(Lucidecode-xml) für Quellcode-Links.
0.6.1
- FloorPlan: Die Werkzeugleiste beim Zeichnen bzw. Bearbeiten der Raumform steht jetzt unter der Karte statt darüber. Vorher rutschte die Karte beim Wechsel des Werkzeugs um die Höhe der Leiste nach unten — wer gerade klickte, traf daneben.
0.6.0
- FloorPlan: Raumform, Wände, Zonen. Neue Props
outline,walls,zones,tool,onShapeCreate,onShapeCommit,onOutlineCommit,onToolCancel,showIssues,zoneLabel,labels. Darstellung als SVG inviewundedit, hell und dunkel; Werkzeuge Wand zeichnen, Zone zeichnen, Raumform bearbeiten (Punktgriffe, Einrasten an Raster und 0/45/90°, Entf/Esc/Enter). Warnung für Tische außerhalb des Umrisses bzw. in gesperrten Zonen. - Rechenfunktionen für Punkte und Polygone (siehe oben), mit
node --test. - Tokens
--oe-floor-outside,--oe-floor-hatch,--oe-floor-outline,--oe-floor-zone-kitchen|blocked|bar|other(hell und dunkel). - Icons
pointer(Lucidemouse-pointer-2),zone(square-dashed),outline(pentagon),ban(ban) — 97 Icons.
0.5.3
- Icon
menu(Lucidemenu, drei waagerechte Striche) für Haupt-Menüs, etwa den Menü-Knopf im Kassen-Kopf.more(···) bleibt für Zeilen- und Kontextaktionen.
0.5.2
- Sheet: Herunterziehen schließt am Griff, am Kopf und im Inhalt,
wenn er oben steht (Schwelle oder schneller Wisch, sonst federt das
Blatt zurück). Die Geste steht als
useSwipeToCloseauch eigenen Bottom-Sheets zur Verfügung;data-oe-nodragnimmt Elemente aus. - Icons
sun,moon,monitor(Darstellung hell/dunkel/System).
0.5.0
- Icons aus Lucide: Alle 89 Icons sind jetzt Lucide-Icons (1.52.0, ISC)
unter den bisherigen Namen; die eigenen Zeichnungen sind entfallen.
scripts/build-icons.mjsübernimmt die Geometrie auslucide-static(devDependency) nachsrc/icons/lucide.json. Strich bleibt 1,8,currentColor, runde Enden. Wo Lucide kein eigenes Motiv hat (z. B.table,sausage,fries), steht das nächstliegende — Liste insrc/icons/README.md. - Neu:
iconSources(Name → Lucide-Icon),LUCIDE_VERSION,ICON_STROKE_WIDTH. - Breaking:
legacyIconMapenthält keinepos-icon:<id>-Einträge mehr,legacyIcon('pos-icon:…')liefertundefined.IconNodeTagumfasst jetzt auch'line'. Details unter „Migration 0.4 → 0.5“. - Lizenz:
src/icons/LICENSE-lucide.txt(ISC/MIT) liegt bei,LICENSE-tabler.txtist entfallen. pos-alias.cssund der AliasTableMapbleiben vorerst (veraltet) im Paket; sie entfallen in einer späteren Version.
0.4.1
- Silbentrennung in Produktkachel und Warenkorbzeile:
.oe-tile--product(Name) und.oe-cartline__nametrennen lange Wörter jetzt nach Silben (hyphens: auto; overflow-wrap: break-word) statt mitten im Wort (overflow-wrap: anywhere): „Apfel-|schorle“ statt „Apfelsch|orle“. Voraussetzung ist einlang-Attribut am Dokument (<html lang="de">); ohnelangbricht der Browser nur an Wortgrenzen bzw. als letzte Rettung im Wort. pos-alias.cssentschärft: Die Grundregeln für.pos-root(Schrift, Farbe,box-sizing, Knopf-Reset, Fokusring) stehen jetzt in:where()und haben Spezifität 0. Vorher setzte.pos-root button { color: inherit }die Textfarbe von.oe-btn--primary& Co. außer Kraft — Primärknöpfe in.pos-roothatten unsichtbaren Text. Kein Breaking Change; eigene.pos-root-Regeln der App gewinnen jetzt immer. Die Datei bleibt veraltet und entfällt in 0.5.0 (statt.pos-root→.oe-root).
Migration 0.6 → 0.7
| Änderung | Was tun |
|---|---|
| Tooltip zeigt die Blase nicht mehr dauerhaft, sondern bei Hover/Fokus/langem Drücken in einem Portal. Das Kind muss ein einzelnes Element sein, das Props durchreicht. | Für feste Hinweise always setzen; sonst nichts. |
| Tooltip kommt aus src/react/tooltip.tsx (Export aus dem Paket unverändert). | Nichts. |
| DropdownProps erbt jetzt Button-Attribute; triggerVariant erlaubt 'icon', triggerSize 'xs'. | Nichts. |
Migration 0.5 → 0.6
| Änderung | Was tun |
|---|---|
| FloorItemKind (Breaking für erschöpfende switch): jetzt 'table' \| 'decor' \| 'wall' \| 'zone'. onSelect, onDelete, onDuplicate können 'wall'/'zone' liefern. | Zweige für Wände/Zonen ergänzen oder diese Fälle ignorieren. |
| FloorChange.kind ist jetzt FloorRectKind ('table' \| 'decor') — unverändert in der Sache, nur ein eigener Typ. | Nichts; wer den Typ ausschreibt, FloorRectKind nutzen. |
| Wand als Linienzug kommt über walls, nicht über decor. decor bleibt rechteckig (bar, wall, stage, label). | Daten mit points (z. B. aus table_areas.decor) vor der Übergabe aufteilen: Rechtecke → decor, { type: 'wall', points } → walls, { type: 'zone', … } → zones. |
| Hülle im Bearbeitungsmodus hat .oe-floor-wrap--edit mit 8 px Innenabstand (Platz für Punktgriffe am Rand); beim Zeichnen steht eine Werkzeugleiste (.oe-floor__toolbar) unter der Karte (seit 0.6.1). | Feste Höhen/Abstände um den Editor prüfen. |
| Neue Icons pointer, zone, outline, ban (97 statt 93) | Tests, die die Anzahl prüfen, anpassen. |
Migration 0.4 → 0.5
| Änderung | Was tun |
|---|---|
| Icon-Geometrie (sichtbar): Jedes Icon sieht jetzt aus wie sein Lucide-Gegenstück; Namen, Größe, Strich und Farbe bleiben. | Nichts — außer Screenshots/visuelle Tests neu aufnehmen. Wer ein bestimmtes Motiv erwartet, in iconSources bzw. src/icons/README.md nachsehen. |
| Produkt-Icons (Breaking): legacyIconMap enthält keine pos-icon:<id>-Einträge mehr; legacyIcon('pos-icon:pils') liefert undefined statt 'beer'. | Produkte mit pos-icon:<id> als Bild aus @openeos/pos-icons zeigen (Paket als eigene Abhängigkeit), nicht als Linien-Icon. Ohne Bild auf das Icon der Kategorie bzw. LEGACY_FALLBACK_ICON zurückfallen. |
| Emojis aus Kategoriedaten | Unverändert: legacyIcon('\u{1F37A}') → 'beer'. |
| IconNodeTag umfasst jetzt 'line' (Lucide zeichnet einige Icons mit <line>). | Nur relevant, wer ICON_NODES selbst rendert und nach Tag verzweigt: line (Attribute x1, y1, x2, y2) unterstützen. Icon und iconSvg tun das bereits. |
| src/icons/svg/*.svg haben einen Kopfkommentar mit dem Lucide-Namen | Wer die Dateien direkt einliest, Kommentare überspringen. |
| src/icons/LICENSE-tabler.txt entfällt | Verweise darauf entfernen; maßgeblich ist LICENSE-lucide.txt. |
Migration 0.3 → 0.4
| Änderung | Was tun |
|---|---|
| Keypad-Tasten (Breaking): Default jetzt 1–9, backspace, 0, enter statt 1–9, '.', 0, '⌫'. 'backspace' und 'enter' sind Spezialtasten mit Icon; onKey liefert diese Namen. | Wer die Defaults nutzte und '⌫'/'.' auswertete, auf 'backspace' umstellen bzw. keys explizit übergeben ('.' geht weiter als Text-Taste). Zugängliche Namen über labels. |
| Keypad-Größe: Tasten haben jetzt eine Mindesthöhe von 52 px (Touch). | Bei Bedarf über eigene Klasse überschreiben. |
| TableMap heißt jetzt TableGrid | TableMap bleibt als veralteter Alias bis 0.5.0. |
| pos-alias.css (--pos-*) ist veraltet | Auf --oe-*-Tokens umstellen; die Aliase entfallen in 0.5.0. |
| react-dom ist Peer-Abhängigkeit (Portal von Sheet) | In React-Projekten ohnehin vorhanden. |
| .oe-root button setzt Schrift/Farbe nur noch mit Spezifität 0,0,1 | Vorher überschrieb der Reset die Textfarbe von .oe-btn--primary & Co. innerhalb von .oe-root; keine Aktion nötig. |
| Text in Warn-/Fehlerfarbe nutzt --oe-warn-ink / --oe-danger-ink | .oe-badge--warn/--danger, .oe-banner--warn/--danger und die Tisch-Zustände erreichen damit 4,5:1. Eigene Klassen mit color: var(--oe-warn) auf hellem Grund ebenso umstellen. |
| .oe-tile b/span-Regeln greifen nur noch auf direkte Kinder der einfachen Kachel | Nur relevant für eigenes, tiefer verschachteltes Markup in .oe-tile. |
Tokens in JavaScript
Für alles, was keine CSS-Variable auflösen kann — Charts, Canvas, PDF, E-Mail:
import { theme, chartSeries } from '@openeos/ui/tokens';Im normalen Markup immer die CSS-Variablen benutzen: nur die kennen den Dark-Mode.
Entwicklung
pnpm install
pnpm build # dist/ + gebündeltes styles.css
pnpm dev # Watch-Modus
pnpm typecheck
pnpm icons # src/icons/svg/, generated.ts, LICENSE-lucide.txt aus Lucide (src/icons/lucide.json)
pnpm test # node:test gegen dist/ (vorher pnpm build)Die Styles liegen als einzelne Module unter src/styles/ und werden beim
Build zu einer flachen dist/styles.css zusammengezogen. Konsumenten
können auch einzelne Module ziehen: @openeos/ui/css/buttons.css.
Release
Veröffentlicht wird automatisch aus GitHub Actions
(.github/workflows/publish.yml) per npm Trusted Publishing — es gibt
kein npm-Token im Repo.
Version in einem PR anheben (
versioninpackage.json, z. B. mitnpm version 0.3.2 --no-git-tag-version) und mergen.Release anlegen — der Tag muss
v+ die Version auspackage.jsonsein:gh release create v0.4.0 --target main --generate-notesDer Workflow „Publish to npm“ prüft Tag gegen Version, baut und veröffentlicht mit Provenance. Passen Tag und Version nicht zusammen, bricht er ab. Prerelease-Versionen (
1.0.0-rc.1) landen unter dem dist-tagnext, nichtlatest.
Einmalige Einrichtung auf npmjs.com
npmjs.com → Paket @openeos/ui → Settings → Trusted Publisher →
GitHub Actions:
| Feld | Wert |
|---|---|
| Organization or user | OpenEOS-Project |
| Repository | openeos-ui |
| Workflow filename | publish.yml |
| Environment name | (leer lassen) |
Danach kann unter Settings → Publishing access „Require two-factor authentication and disallow tokens“ gesetzt werden: Trusted Publishing funktioniert weiter, klassische Tokens nicht mehr.
Lizenz
Copyright (C) 2026 LUXCODE by Lukas Lukic-Spitznagel und Mitwirkende.
Lizenziert unter der GNU Affero General Public License v3.0 oder (nach
deiner Wahl) jeder späteren Version (AGPL-3.0-or-later), siehe
LICENSE und NOTICE. Schriften: SIL OFL 1.1 (src/fonts/files/OFL-*.txt).
Icons: ausschließlich Lucide, ISC (Teile MIT von
Feather) — src/icons/LICENSE-lucide.txt, Details in
src/icons/README.md.
