@efa-one/sdk 1.13.1 → 1.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -1
- package/frontend/index.d.ts +1 -0
- package/frontend/index.js +1 -0
- package/frontend/ui/DataTable.js +13 -7
- package/frontend/ui/RecordDialog.js +1 -1
- package/frontend/ui/index.d.ts +1 -0
- package/frontend/ui/index.js +1 -0
- package/frontend/useIsMobile.d.ts +2 -0
- package/frontend/useIsMobile.js +17 -0
- package/package.json +13 -6
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ sub-paths — this keeps `express` out of the frontend and `react` out of the ba
|
|
|
14
14
|
|---|---|---|
|
|
15
15
|
| `@efa-one/sdk/backend` | Node/Express | Auth + token exchange (`requireAuth`, `createExchangeRouter`, `requireInternalOrAuth` …), health router, service discovery + gateway client (`serviceClient`, `resolveService`), clients for audit/reporting/mail/notifications, permission resolution/registration (`getUserPermissions`, `registerPermissions`, `checkPermission`), capability registry (`registerApiMetadata`), backend i18n |
|
|
16
16
|
| `@efa-one/sdk/frontend` | Browser/React | postMessage IPC (`registerAppInfo`, `sendAtStart`, `navigateToApp`, `notifyRouteChange` …), react-i18next factory (`initI18n`), `DevHeader` |
|
|
17
|
-
| `@efa-one/sdk/frontend/ui` | Browser/React | The efa-one design-system kit: `Button`, `Input`, `Badge`, `Alert`, `Dialog`, `DropdownMenu`, `Tooltip`, `EmptyState`, `Skeleton`, `RecordDialog`, and the **`DataTable`** (sortable/filterable/groupable list with per-user persisted view). Ship its companion CSS once: `import '@efa-one/sdk/frontend/ui/styles.css'` |
|
|
17
|
+
| `@efa-one/sdk/frontend/ui` | Browser/React | The efa-one design-system kit: `Button`, `Input`, `Badge`, `Alert`, `Dialog`, `DropdownMenu`, `Tooltip`, `EmptyState`, `Skeleton`, `RecordDialog`, and the **`DataTable`** (sortable/filterable/groupable list with per-user persisted view; reflows to stacked cards below 640px). Also exports `useIsMobile` — the platform-wide 640px breakpoint hook. Ship its companion CSS once: `import '@efa-one/sdk/frontend/ui/styles.css'` |
|
|
18
18
|
| `@efa-one/sdk/frontend/viewPreferences` | Browser/React | Persistence seam for `DataTable` — `createViewPreferencesClient()` (standard `/api/view-preferences/:listId` adapter) + `useViewPreferences()` |
|
|
19
19
|
|
|
20
20
|
Every individual module is also reachable directly, e.g.
|
|
@@ -112,6 +112,33 @@ The flat output layout (`backend/` + `frontend/` at the package root) is a
|
|
|
112
112
|
deliberate choice so that consumers using `moduleResolution: node` (node10) can
|
|
113
113
|
resolve the subpath imports physically — without having to change their tsconfig.
|
|
114
114
|
|
|
115
|
+
## Quality gates (CI)
|
|
116
|
+
|
|
117
|
+
Every push and pull request runs the full gate; the same gate also runs as a
|
|
118
|
+
prerequisite job of the release workflow, so a `v*` tag can never publish a state
|
|
119
|
+
that a PR would have rejected.
|
|
120
|
+
|
|
121
|
+
| Workflow | Runs on | What it enforces |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `build-check.yml` | push (`main`, `development`), PR, called by `publish.yml` | `tsc` typecheck (sources **and** `test/` + `scripts/`), Vitest **with coverage thresholds**, ESLint, full `npm run build` (incl. the ESM specifier gate), plus `npm ci` **and** `npm run build` in both `template/` scaffolds |
|
|
124
|
+
| `security.yml` | push, PR, weekly (Mon 06:00 UTC) | `npm audit --audit-level=high` and a copyleft license gate (GPL/AGPL/LGPL) across all three npm directories, Trivy filesystem scan (HIGH/CRITICAL, **including devDependencies** — without that switch Trivy skips them and the scan would be structurally empty here), Trivy misconfig/secret scan (report-only) |
|
|
125
|
+
| `publish.yml` | `v*` tag | tag ↔ `package.json` version match, then npm publish via OIDC trusted publishing |
|
|
126
|
+
| `dependabot.yml` + `dependabot-automerge.yml` | weekly | grouped minor/patch dependency PRs for the SDK, both scaffolds, the scaffold's Docker base images and the GitHub Actions themselves. Auto-merge is present but dormant — see the header comment in the workflow for the three settings that arm it |
|
|
127
|
+
|
|
128
|
+
Run the blocking gates locally before pushing:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
./scripts/ci-local.sh # mirrors build-check.yml + security.yml (Trivy via docker if not installed)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Coverage uses a two-level ratchet (global floor + per-file gates on the modules that
|
|
135
|
+
are deliberately tested); see `vitest.config.ts`. When new tests raise coverage, raise
|
|
136
|
+
the thresholds — never lower them.
|
|
137
|
+
|
|
138
|
+
**TypeScript is pinned to exactly `6.0.3`.** That is the last JS-based line;
|
|
139
|
+
`typescript-eslint` does not support the native 7.0 compiler and aborts the entire lint
|
|
140
|
+
run under it. Dependabot is configured to never propose a TypeScript major.
|
|
141
|
+
|
|
115
142
|
## Versioning
|
|
116
143
|
|
|
117
144
|
SemVer. Consuming apps pull new versions with `npm update @efa-one/sdk`.
|
package/frontend/index.d.ts
CHANGED
package/frontend/index.js
CHANGED
package/frontend/ui/DataTable.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useMemo, useState } from 'react';
|
|
3
3
|
import * as DropdownMenu from './DropdownMenu.js';
|
|
4
4
|
import { ChevronDown, ChevronUp, ChevronRight, Settings, EyeOff, Filter, RotateCcw, ArrowUp, ArrowDown, Layers, X } from 'lucide-react';
|
|
5
5
|
import { Button } from './Button.js';
|
|
6
6
|
import { useViewPreferences } from '../viewPreferences.js';
|
|
7
|
+
import { useIsMobile } from '../useIsMobile.js';
|
|
7
8
|
const GROUP_KEY_SEP = '\x1f';
|
|
8
9
|
const EMPTY_GROUP_LABEL = '– ohne –';
|
|
9
10
|
export const DEFAULT_VIEW_VERSION = 2;
|
|
@@ -79,6 +80,7 @@ export function buildRenderItems(rows, groupBy, columnsById, expandedGroups) {
|
|
|
79
80
|
export function DataTable({ listId, rows, columns, rowKey, onRowClick, selection, initialPrefs, footer, persistence, }) {
|
|
80
81
|
const defaultPrefs = useMemo(() => defaultPrefsFor(columns, initialPrefs), [columns, initialPrefs]);
|
|
81
82
|
const [prefs, setPrefs, reset] = useViewPreferences(listId, defaultPrefs, persistence);
|
|
83
|
+
const isMobile = useIsMobile();
|
|
82
84
|
const [expandedGroups, setExpandedGroups] = useState(new Set());
|
|
83
85
|
const toggleGroup = (key) => {
|
|
84
86
|
setExpandedGroups((prev) => {
|
|
@@ -236,12 +238,13 @@ export function DataTable({ listId, rows, columns, rowKey, onRowClick, selection
|
|
|
236
238
|
}
|
|
237
239
|
setPrefs({ ...prefs, filters });
|
|
238
240
|
};
|
|
239
|
-
|
|
241
|
+
const gearButton = (_jsx(ColumnInventoryButton, { columns: columns, prefs: prefs, setColumnVisible: setColumnVisible, moveColumn: moveColumn, reset: reset, groupBy: groupBy, addGroupBy: addGroupBy, removeGroupBy: removeGroupBy, moveGroupBy: moveGroupBy, clearGroupBy: clearGroupBy }));
|
|
242
|
+
return (_jsxs("div", { className: "border border-[var(--color-border)] overflow-x-auto", children: [selection && selection.selected.size > 0 && (_jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-sm border-b border-[var(--color-border)]", style: { background: 'var(--color-surface-raised)' }, children: [_jsxs("span", { children: [selection.selected.size, " ausgew\u00E4hlt"] }), _jsx("button", { type: "button", className: "text-xs underline text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]", onClick: () => selection.onChange(new Set()), children: "Auswahl l\u00F6schen" }), _jsx("div", { className: "flex-1" }), selection.bulkActions] })), isMobile ? (_jsxs("div", { className: "flex items-center gap-3 px-3 py-2 text-xs font-medium border-b border-[var(--color-border)]", style: { background: 'var(--color-surface-raised)' }, children: [selection && (_jsxs("label", { className: "flex items-center gap-2 py-2 pr-2 cursor-pointer", children: [_jsx("input", { type: "checkbox", className: "w-5 h-5", checked: allSelected, onChange: toggleAll, "aria-label": "Alle ausw\u00E4hlen" }), _jsx("span", { className: "text-[var(--color-text-muted)]", children: "Alle" })] })), _jsx("div", { className: "flex-1" }), gearButton] })) : (_jsxs("div", { className: "grid items-center min-w-min px-3 py-2 text-xs font-medium border-b border-[var(--color-border)]", style: { gridTemplateColumns: gridTemplate, background: 'var(--color-surface-raised)' }, children: [selection && (_jsx("input", { type: "checkbox", checked: allSelected, onChange: toggleAll, "aria-label": "Alle ausw\u00E4hlen" })), visibleColumns.map((col) => {
|
|
240
243
|
const activeSort = prefs.sort?.columnId === col.id ? prefs.sort.direction : null;
|
|
241
244
|
const filterValue = prefs.filters[col.id];
|
|
242
245
|
const isFiltered = filterValue != null;
|
|
243
246
|
return (_jsxs(DropdownMenu.Root, { children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: "flex items-center gap-1 px-1 py-0.5 hover:bg-[var(--color-surface)] text-left", style: { borderRadius: 'var(--border-radius-md)' }, children: [_jsx("span", { children: col.label }), activeSort === 'asc' && _jsx(ChevronUp, { className: "w-3 h-3" }), activeSort === 'desc' && _jsx(ChevronDown, { className: "w-3 h-3" }), isFiltered && _jsx(Filter, { className: "w-3 h-3 text-[var(--color-primary)]" })] }) }), _jsxs(DropdownMenu.Content, { className: "min-w-[220px] p-1", children: [col.sortable !== false && (_jsxs(_Fragment, { children: [_jsxs(DropdownMenu.Item, { onSelect: () => setSort(col.id, 'asc'), children: [_jsx(ArrowUp, { className: "w-4 h-4" }), "Sortieren A\u2192Z"] }), _jsxs(DropdownMenu.Item, { onSelect: () => setSort(col.id, 'desc'), children: [_jsx(ArrowDown, { className: "w-4 h-4" }), "Sortieren Z\u2192A"] }), activeSort && (_jsx(DropdownMenu.Item, { onSelect: clearSort, children: "Sortierung zur\u00FCcksetzen" })), _jsx(DropdownMenu.Separator, {})] })), groupBy.includes(col.id) ? (_jsxs(DropdownMenu.Item, { onSelect: () => removeGroupBy(col.id), children: [_jsx(Layers, { className: "w-4 h-4" }), "Gruppierung aufheben"] })) : (_jsxs(DropdownMenu.Item, { onSelect: () => addGroupBy(col.id), children: [_jsx(Layers, { className: "w-4 h-4" }), "Nach dieser Spalte gruppieren"] })), _jsx(DropdownMenu.Separator, {}), col.filter && (_jsx(FilterEditor, { column: col, value: filterValue, onChange: (v) => setFilter(col.id, v) })), _jsxs(DropdownMenu.Item, { onSelect: () => setColumnVisible(col.id, false), variant: "danger", children: [_jsx(EyeOff, { className: "w-4 h-4" }), "Spalte ausblenden"] })] })] }, col.id));
|
|
244
|
-
}),
|
|
247
|
+
}), gearButton] })), sortedFilteredRows.length === 0 ? (_jsx("div", { className: "px-3 py-8 text-center text-sm text-[var(--color-text-muted)]", children: "Keine Eintr\u00E4ge" })) : (buildRenderItems(sortedFilteredRows, groupBy, columnsById, expandedGroups).map((item, idx) => {
|
|
245
248
|
if (item.kind === 'group') {
|
|
246
249
|
const level = item.level ?? 0;
|
|
247
250
|
const groupColLabel = columnsById.get(groupBy[level])?.label ?? '';
|
|
@@ -256,7 +259,10 @@ export function DataTable({ listId, rows, columns, rowKey, onRowClick, selection
|
|
|
256
259
|
const key = rowKey(row);
|
|
257
260
|
const isChecked = selection?.selected.has(key) ?? false;
|
|
258
261
|
const rowIndent = groupBy.length > 0 ? 12 + groupBy.length * 18 : undefined;
|
|
259
|
-
|
|
262
|
+
if (isMobile) {
|
|
263
|
+
return (_jsxs("div", { className: `flex flex-col gap-1.5 px-3 py-3 border-b border-[var(--color-border)] ${onRowClick ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]' : ''}`, style: { background: isChecked ? 'rgba(99,102,241,0.07)' : undefined, paddingLeft: rowIndent }, onClick: () => onRowClick?.(row), children: [selection && (_jsxs("label", { className: "flex items-center gap-2 self-start py-1 pr-2 cursor-pointer", onClick: (e) => e.stopPropagation(), children: [_jsx("input", { type: "checkbox", className: "w-5 h-5", checked: isChecked, onChange: () => toggleOne(key), "aria-label": "Zeile ausw\u00E4hlen" }), _jsx("span", { className: "text-xs text-[var(--color-text-muted)]", children: "Ausw\u00E4hlen" })] })), visibleColumns.map((col) => (_jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-[var(--color-text-muted)]", children: col.label }), _jsx("div", { className: "min-w-0 break-words text-sm", children: col.cell ? col.cell(row) : String(col.accessor(row) ?? '') })] }, col.id)))] }, `r:${String(key)}:${idx}`));
|
|
264
|
+
}
|
|
265
|
+
return (_jsxs("div", { className: `grid items-center min-w-min px-3 py-2 text-sm border-b border-[var(--color-border)] ${onRowClick ? 'cursor-pointer hover:bg-[var(--color-surface-raised)]' : ''}`, style: {
|
|
260
266
|
gridTemplateColumns: gridTemplate,
|
|
261
267
|
background: isChecked ? 'rgba(99,102,241,0.07)' : undefined,
|
|
262
268
|
paddingLeft: rowIndent,
|
|
@@ -306,9 +312,9 @@ function ColumnInventoryButton({ columns, prefs, setColumnVisible, moveColumn, r
|
|
|
306
312
|
const byId = new Map(columns.map((c) => [c.id, c]));
|
|
307
313
|
return prefs.columnOrder.map((id) => byId.get(id)).filter(Boolean);
|
|
308
314
|
}, [columns, prefs.columnOrder]);
|
|
309
|
-
return (_jsxs(DropdownMenu.Root, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsx("button", { type: "button", className: "p-1 hover:bg-[var(--color-surface)] text-[var(--color-text-muted)] justify-self-end", title: "Spalten konfigurieren", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(Settings, { className: "w-4 h-4" }) }) }), _jsxs(DropdownMenu.Content, { className: "min-w-[260px] p-2", children: [_jsx("div", { className: "text-xs font-semibold mb-2 text-[var(--color-text-muted)] uppercase", children: "Gruppieren nach" }), groupBy.length === 0 ? (_jsx("div", { className: "text-xs text-[var(--color-text-muted)] italic px-1 mb-2", children: "Keine Gruppierung aktiv." })) : (_jsxs("div", { className: "mb-2 space-y-1", onClick: (e) => e.stopPropagation(), children: [groupBy.map((id, idx) => {
|
|
315
|
+
return (_jsxs(DropdownMenu.Root, { open: open, onOpenChange: setOpen, children: [_jsx(DropdownMenu.Trigger, { asChild: true, children: _jsx("button", { type: "button", className: "p-2.5 sm:p-1 hover:bg-[var(--color-surface)] text-[var(--color-text-muted)] justify-self-end", title: "Spalten konfigurieren", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(Settings, { className: "w-4 h-4" }) }) }), _jsxs(DropdownMenu.Content, { className: "min-w-[260px] p-2", children: [_jsx("div", { className: "text-xs font-semibold mb-2 text-[var(--color-text-muted)] uppercase", children: "Gruppieren nach" }), groupBy.length === 0 ? (_jsx("div", { className: "text-xs text-[var(--color-text-muted)] italic px-1 mb-2", children: "Keine Gruppierung aktiv." })) : (_jsxs("div", { className: "mb-2 space-y-1", onClick: (e) => e.stopPropagation(), children: [groupBy.map((id, idx) => {
|
|
310
316
|
const col = columns.find((c) => c.id === id);
|
|
311
|
-
return (_jsxs("div", { className: "flex items-center gap-2 px-1 py-1 text-xs", children: [_jsxs("span", { className: "text-[var(--color-text-muted)] w-4", children: [idx + 1, "."] }), _jsx("span", { className: "flex-1 truncate", children: col?.label ?? id }), _jsx("button", { type: "button", disabled: idx === 0, onClick: () => moveGroupBy(id, -1), className: "p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Ebene hoch", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowUp, { className: "w-3 h-3" }) }), _jsx("button", { type: "button", disabled: idx === groupBy.length - 1, onClick: () => moveGroupBy(id, 1), className: "p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Ebene runter", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowDown, { className: "w-3 h-3" }) }), _jsx("button", { type: "button", onClick: () => removeGroupBy(id), className: "p-0.5 text-[var(--color-text-muted)] hover:text-[var(--color-danger)]", title: "Diese Ebene entfernen", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(X, { className: "w-3 h-3" }) })] }, id));
|
|
317
|
+
return (_jsxs("div", { className: "flex items-center gap-2 px-1 py-1 text-xs", children: [_jsxs("span", { className: "text-[var(--color-text-muted)] w-4", children: [idx + 1, "."] }), _jsx("span", { className: "flex-1 truncate", children: col?.label ?? id }), _jsx("button", { type: "button", disabled: idx === 0, onClick: () => moveGroupBy(id, -1), className: "p-2 sm:p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Ebene hoch", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowUp, { className: "w-3 h-3" }) }), _jsx("button", { type: "button", disabled: idx === groupBy.length - 1, onClick: () => moveGroupBy(id, 1), className: "p-2 sm:p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Ebene runter", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowDown, { className: "w-3 h-3" }) }), _jsx("button", { type: "button", onClick: () => removeGroupBy(id), className: "p-2 sm:p-0.5 text-[var(--color-text-muted)] hover:text-[var(--color-danger)]", title: "Diese Ebene entfernen", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(X, { className: "w-3 h-3" }) })] }, id));
|
|
312
318
|
}), _jsx("button", { type: "button", onClick: clearGroupBy, className: "text-xs underline text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] px-1", children: "Alle aufheben" })] })), groupableCandidates.length > 0 && (_jsxs("div", { className: "mb-2 flex items-center gap-2 px-1", onClick: (e) => e.stopPropagation(), children: [_jsx("span", { className: "text-xs text-[var(--color-text-muted)]", children: "Ebene hinzuf\u00FCgen:" }), _jsxs("select", { defaultValue: "", onChange: (e) => {
|
|
313
319
|
const v = e.target.value;
|
|
314
320
|
if (v)
|
|
@@ -316,6 +322,6 @@ function ColumnInventoryButton({ columns, prefs, setColumnVisible, moveColumn, r
|
|
|
316
322
|
e.target.value = '';
|
|
317
323
|
}, className: "flex-1 px-1 py-0.5 text-xs border border-[var(--color-border)] bg-[var(--color-surface)]", style: { borderRadius: 'var(--border-radius-md)' }, children: [_jsx("option", { value: "", children: "\u2013 w\u00E4hlen \u2013" }), groupableCandidates.map((c) => (_jsx("option", { value: c.id, children: c.label }, c.id)))] })] })), _jsx("div", { className: "border-t border-[var(--color-border)] mb-2" }), _jsx("div", { className: "text-xs font-semibold mb-2 text-[var(--color-text-muted)] uppercase", children: "Spalten" }), orderedColumns.map((col, idx) => {
|
|
318
324
|
const visible = prefs.columnVisibility[col.id] !== false;
|
|
319
|
-
return (_jsxs("div", { className: "flex items-center gap-2 px-1 py-1 text-sm", onClick: (e) => e.stopPropagation(), children: [_jsx("input", { type: "checkbox", checked: visible, onChange: () => setColumnVisible(col.id, !visible), "aria-label": `Spalte ${col.label} ${visible ? 'ausblenden' : 'einblenden'}` }), _jsx("span", { className: "flex-1 text-xs", children: col.label }), _jsx("button", { type: "button", disabled: idx === 0, onClick: () => moveColumn(col.id, -1), className: "p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Nach oben", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowUp, { className: "w-3 h-3" }) }), _jsx("button", { type: "button", disabled: idx === orderedColumns.length - 1, onClick: () => moveColumn(col.id, 1), className: "p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Nach unten", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowDown, { className: "w-3 h-3" }) })] }, col.id));
|
|
325
|
+
return (_jsxs("div", { className: "flex items-center gap-2 px-1 py-1 text-sm", onClick: (e) => e.stopPropagation(), children: [_jsx("input", { type: "checkbox", checked: visible, onChange: () => setColumnVisible(col.id, !visible), "aria-label": `Spalte ${col.label} ${visible ? 'ausblenden' : 'einblenden'}` }), _jsx("span", { className: "flex-1 text-xs", children: col.label }), _jsx("button", { type: "button", disabled: idx === 0, onClick: () => moveColumn(col.id, -1), className: "p-2 sm:p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Nach oben", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowUp, { className: "w-3 h-3" }) }), _jsx("button", { type: "button", disabled: idx === orderedColumns.length - 1, onClick: () => moveColumn(col.id, 1), className: "p-2 sm:p-0.5 disabled:opacity-30 hover:bg-[var(--color-surface-raised)]", title: "Nach unten", style: { borderRadius: 'var(--border-radius-md)' }, children: _jsx(ArrowDown, { className: "w-3 h-3" }) })] }, col.id));
|
|
320
326
|
}), _jsx("div", { className: "border-t border-[var(--color-border)] mt-2 pt-2", children: _jsxs(Button, { variant: "ghost", onClick: () => { reset(); setOpen(false); }, children: [_jsx(RotateCcw, { className: "w-4 h-4" }), "Zur\u00FCcksetzen"] }) })] })] }));
|
|
321
327
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
3
|
import { Pencil } from 'lucide-react';
|
|
4
4
|
import { Dialog, DialogClose } from './Dialog.js';
|
package/frontend/ui/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ export { Tooltip, TooltipProvider } from './Tooltip.js';
|
|
|
8
8
|
export { EmptyState } from './EmptyState.js';
|
|
9
9
|
export { Skeleton, SkeletonRow } from './Skeleton.js';
|
|
10
10
|
export { RecordDialog, type RecordDialogMode } from './RecordDialog.js';
|
|
11
|
+
export { useIsMobile, MOBILE_QUERY } from '../useIsMobile.js';
|
|
11
12
|
export { DataTable, DEFAULT_VIEW_VERSION, type ColumnDef, type FilterDef, type DataTableSelectionProps, } from './DataTable.js';
|
package/frontend/ui/index.js
CHANGED
|
@@ -8,4 +8,5 @@ export { Tooltip, TooltipProvider } from './Tooltip.js';
|
|
|
8
8
|
export { EmptyState } from './EmptyState.js';
|
|
9
9
|
export { Skeleton, SkeletonRow } from './Skeleton.js';
|
|
10
10
|
export { RecordDialog } from './RecordDialog.js';
|
|
11
|
+
export { useIsMobile, MOBILE_QUERY } from '../useIsMobile.js';
|
|
11
12
|
export { DataTable, DEFAULT_VIEW_VERSION, } from './DataTable.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
export const MOBILE_QUERY = '(max-width: 640px)';
|
|
3
|
+
function subscribe(callback) {
|
|
4
|
+
if (typeof window === 'undefined' || !window.matchMedia)
|
|
5
|
+
return () => { };
|
|
6
|
+
const mql = window.matchMedia(MOBILE_QUERY);
|
|
7
|
+
mql.addEventListener('change', callback);
|
|
8
|
+
return () => mql.removeEventListener('change', callback);
|
|
9
|
+
}
|
|
10
|
+
function getSnapshot() {
|
|
11
|
+
if (typeof window === 'undefined' || !window.matchMedia)
|
|
12
|
+
return false;
|
|
13
|
+
return window.matchMedia(MOBILE_QUERY).matches;
|
|
14
|
+
}
|
|
15
|
+
export function useIsMobile() {
|
|
16
|
+
return useSyncExternalStore(subscribe, getSnapshot, () => false);
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@efa-one/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.14.1",
|
|
4
4
|
"description": "Offizielles SDK für efa-one-Apps: Auth/Session, Service-Discovery, Gateway-Client, Audit/Reporting/Mail/Notifications, Permission- und Capability-Registrierung sowie das Frontend-IPC-/i18n-Toolkit.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -43,8 +43,10 @@
|
|
|
43
43
|
"build:backend": "tsc -p tsconfig.backend.json",
|
|
44
44
|
"build:frontend": "tsc -p tsconfig.frontend.json && node scripts/postbuild-frontend.mjs",
|
|
45
45
|
"build": "npm run clean && npm run build:backend && npm run build:frontend",
|
|
46
|
-
"typecheck": "tsc -p tsconfig.backend.json --noEmit && tsc -p tsconfig.frontend.json --noEmit",
|
|
46
|
+
"typecheck": "tsc -p tsconfig.backend.json --noEmit && tsc -p tsconfig.frontend.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
47
|
+
"lint": "eslint src test scripts",
|
|
47
48
|
"test": "vitest run",
|
|
49
|
+
"test:coverage": "vitest run --coverage",
|
|
48
50
|
"prepublishOnly": "npm run build"
|
|
49
51
|
},
|
|
50
52
|
"publishConfig": {
|
|
@@ -103,28 +105,33 @@
|
|
|
103
105
|
}
|
|
104
106
|
},
|
|
105
107
|
"devDependencies": {
|
|
108
|
+
"@eslint/js": "^9.39.5",
|
|
106
109
|
"@radix-ui/react-dialog": "^1.1.4",
|
|
107
110
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
|
108
111
|
"@radix-ui/react-tooltip": "^1.1.6",
|
|
109
112
|
"@types/express": "^4.17.21",
|
|
110
113
|
"@types/jsonwebtoken": "^9.0.6",
|
|
111
|
-
"cookie-parser": "^1.4.6",
|
|
112
|
-
"lucide-react": "^0.468.0",
|
|
113
114
|
"@types/node": "^20.14.0",
|
|
114
115
|
"@types/pg": "^8.11.0",
|
|
115
116
|
"@types/react": "^18.2.46",
|
|
117
|
+
"@types/react-dom": "^18.2.0",
|
|
116
118
|
"@types/supertest": "^7.2.0",
|
|
119
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
120
|
+
"cookie-parser": "^1.4.6",
|
|
121
|
+
"eslint": "^9.39.5",
|
|
122
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
117
123
|
"express": "^4.19.2",
|
|
118
124
|
"i18next": "^23.11.0",
|
|
119
125
|
"i18next-http-backend": "^2.7.3",
|
|
120
126
|
"jsonwebtoken": "^9.0.2",
|
|
127
|
+
"lucide-react": "^0.577.0",
|
|
121
128
|
"pg": "^8.12.0",
|
|
122
129
|
"react": "^18.2.0",
|
|
123
130
|
"react-dom": "^18.2.0",
|
|
124
|
-
"@types/react-dom": "^18.2.0",
|
|
125
131
|
"react-i18next": "^13.5.0",
|
|
126
132
|
"supertest": "^7.2.2",
|
|
127
|
-
"typescript": "
|
|
133
|
+
"typescript": "6.0.3",
|
|
134
|
+
"typescript-eslint": "^8.67.0",
|
|
128
135
|
"vitest": "^4.1.4"
|
|
129
136
|
},
|
|
130
137
|
"engines": {
|