@cosmicdrift/kumiko-renderer-web 0.67.0 → 0.68.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-renderer-web",
3
- "version": "0.67.0",
3
+ "version": "0.68.0",
4
4
  "description": "Web-platform bindings for @cosmicdrift/kumiko-renderer. HTML default-primitives, browser history-based navigation, EventSource-backed live events, and a one-call createKumikoApp that mounts the whole stack via react-dom.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -16,9 +16,9 @@
16
16
  "./styles.css": "./src/styles.css"
17
17
  },
18
18
  "dependencies": {
19
- "@cosmicdrift/kumiko-dispatcher-live": "0.67.0",
20
- "@cosmicdrift/kumiko-headless": "0.67.0",
21
- "@cosmicdrift/kumiko-renderer": "0.67.0",
19
+ "@cosmicdrift/kumiko-dispatcher-live": "0.68.0",
20
+ "@cosmicdrift/kumiko-headless": "0.68.0",
21
+ "@cosmicdrift/kumiko-renderer": "0.68.0",
22
22
  "@radix-ui/react-dialog": "^1.1.15",
23
23
  "@radix-ui/react-dropdown-menu": "^2.1.16",
24
24
  "@radix-ui/react-label": "^2.1.8",
@@ -9,7 +9,7 @@ import type {
9
9
  EntityEditScreenDefinition,
10
10
  } from "@cosmicdrift/kumiko-framework/ui-types";
11
11
  import { DispatcherProvider, RenderEdit } from "@cosmicdrift/kumiko-renderer";
12
- import { defaultPrimitives } from "../primitives";
12
+ import { BareFormProvider, defaultPrimitives } from "../primitives";
13
13
  import { createMockDispatcher, render, screen } from "./test-utils";
14
14
 
15
15
  const { Form, Section, Button } = defaultPrimitives;
@@ -55,11 +55,12 @@ describe("Form = eine Card, Sections als innere Abschnitte", () => {
55
55
  </Section>
56
56
  </Form>,
57
57
  );
58
- // Inner-Region: keine eigene bg-card; die Divider zwischen Sections
59
- // macht der divide-y-Wrapper, nicht die Section selbst.
58
+ // Inner-Region: keine eigene bg-card; beide Sections sind Geschwister im
59
+ // selben Body-Wrapper (der die Trennlinie ZWISCHEN ihnen per CSS macht).
60
60
  const s1 = screen.getByTestId("s1");
61
+ const s2 = screen.getByTestId("s2");
61
62
  expect(s1.className).not.toContain("bg-card");
62
- expect(s1.closest(".divide-y")).toBeTruthy();
63
+ expect(s1.parentElement).toBe(s2.parentElement);
63
64
  // Das Form wrappt alles in GENAU eine Card-Fläche.
64
65
  const card = screen.getByTestId("f").querySelector(".bg-card");
65
66
  expect(card).toBeTruthy();
@@ -69,6 +70,21 @@ describe("Form = eine Card, Sections als innere Abschnitte", () => {
69
70
  expect(card?.querySelector("[data-testid='f-actions']")).toBeTruthy();
70
71
  });
71
72
 
73
+ test("BareFormProvider: Form rendert nackt (keine eigene Card) — gegen Card-in-Card im AuthCard", () => {
74
+ render(
75
+ <BareFormProvider>
76
+ <Form onSubmit={() => {}} testId="bare">
77
+ <div>a</div>
78
+ </Form>
79
+ </BareFormProvider>,
80
+ );
81
+ const form = screen.getByTestId("bare");
82
+ expect(form.tagName).toBe("FORM");
83
+ expect(form.className).not.toContain("max-w-3xl");
84
+ expect(form.querySelector(".bg-card")).toBeNull();
85
+ expect(form.className).toContain("gap-4");
86
+ });
87
+
72
88
  test("Section standalone (außerhalb Form) bleibt eine eigene Card", () => {
73
89
  render(
74
90
  <Section testId="solo" title="Solo">
@@ -207,6 +207,23 @@ describe("NavTree", () => {
207
207
  expect(container.querySelectorAll("svg").length).toBe(1);
208
208
  });
209
209
 
210
+ test("layers + building (money-horse Kredit-Gruppen/Mandanten) lösen auf ein Icon auf", () => {
211
+ const schema = {
212
+ featureName: "showcase",
213
+ entities: {},
214
+ screens: [
215
+ { id: "groups", type: "entityList", entity: "x", columns: [] },
216
+ { id: "tenants", type: "entityList", entity: "x", columns: [] },
217
+ ],
218
+ navs: [
219
+ { id: "groups", label: "Gruppen", screen: "groups", order: 10, icon: "layers" },
220
+ { id: "tenants", label: "Mandanten", screen: "tenants", order: 20, icon: "building" },
221
+ ],
222
+ } as FeatureSchema;
223
+ const { container } = render(<NavTree schema={schema} />);
224
+ expect(container.querySelectorAll("svg").length).toBe(2);
225
+ });
226
+
210
227
  test("unbekannter icon-Key fällt sauber auf den Dot zurück (kein svg)", () => {
211
228
  const schema = {
212
229
  featureName: "showcase",
package/src/index.ts CHANGED
@@ -122,7 +122,7 @@ export { filterByAccess, resolveDefaultId, WorkspaceShell } from "./layout/works
122
122
  export type { WorkspaceSwitcherProps } from "./layout/workspace-switcher";
123
123
  export { WorkspaceSwitcher } from "./layout/workspace-switcher";
124
124
  export { cn } from "./lib/cn";
125
- export { defaultPrimitives } from "./primitives";
125
+ export { BareFormProvider, defaultPrimitives } from "./primitives";
126
126
  export type { ActionMenuProps, MenuItemDef } from "./primitives/action-menu";
127
127
  export { ActionMenu } from "./primitives/action-menu";
128
128
  export {
@@ -145,3 +145,4 @@ export {
145
145
  lightTokens,
146
146
  useBrowserTokensApi,
147
147
  } from "./tokens";
148
+ export { SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider } from "./ui/sidebar";
@@ -26,6 +26,7 @@ import {
26
26
  import {
27
27
  BarChart3,
28
28
  Bell,
29
+ Building,
29
30
  Calculator,
30
31
  CalendarDays,
31
32
  ChevronDown,
@@ -36,6 +37,7 @@ import {
36
37
  Folder,
37
38
  Gauge,
38
39
  Home,
40
+ Layers,
39
41
  LayoutDashboard,
40
42
  LineChart,
41
43
  List,
@@ -82,6 +84,8 @@ const NAV_ICONS: Readonly<Record<string, typeof Folder>> = {
82
84
  gauge: Gauge,
83
85
  list: List,
84
86
  table: Table,
87
+ layers: Layers,
88
+ building: Building,
85
89
  calculator: Calculator,
86
90
  wallet: Wallet,
87
91
  coins: Coins,
@@ -1292,6 +1292,16 @@ function DataTableCell({
1292
1292
  // eigene Card.
1293
1293
  const InsideFormContext = createContext(false);
1294
1294
 
1295
+ // Eingebettete Forms (z.B. im AuthCard) tragen ihre Card-Fläche schon vom
1296
+ // Container — der self-cardende DefaultForm würde sonst eine Card-in-Card
1297
+ // erzeugen. BareFormProvider schaltet DefaultForm auf ein nacktes <form>
1298
+ // (gestapelte Felder, kein eigener Rahmen/max-width).
1299
+ const BareFormContext = createContext(false);
1300
+
1301
+ export function BareFormProvider({ children }: { children: ReactNode }): ReactNode {
1302
+ return <BareFormContext.Provider value={true}>{children}</BareFormContext.Provider>;
1303
+ }
1304
+
1295
1305
  function DefaultForm({
1296
1306
  onSubmit,
1297
1307
  children,
@@ -1300,6 +1310,26 @@ function DefaultForm({
1300
1310
  actions,
1301
1311
  testId,
1302
1312
  }: FormProps): ReactNode {
1313
+ // Eingebettet (AuthCard etc.): nacktes <form>, gestapelte Felder mit gap —
1314
+ // der Container trägt Card/Titel selbst, sonst Card-in-Card.
1315
+ if (useContext(BareFormContext)) {
1316
+ return (
1317
+ <form
1318
+ onSubmit={(e) => {
1319
+ e.preventDefault();
1320
+ onSubmit(e);
1321
+ }}
1322
+ data-testid={testId}
1323
+ className="flex flex-col gap-4"
1324
+ >
1325
+ <InsideFormContext.Provider value={true}>{children}</InsideFormContext.Provider>
1326
+ {actions !== undefined && (
1327
+ <div className="flex items-center justify-end gap-2">{actions}</div>
1328
+ )}
1329
+ </form>
1330
+ );
1331
+ }
1332
+
1303
1333
  // shadcn-Form-Muster: das Formular ist EINE Card — Titel als Card-Header
1304
1334
  // OHNE Trennlinie darunter (Titel fließt in die erste Section). Sections
1305
1335
  // sind divide-y-getrennt (Linien nur ZWISCHEN ihnen). Action-Footer mit
@@ -1336,7 +1366,17 @@ function DefaultForm({
1336
1366
  )}
1337
1367
  </div>
1338
1368
  )}
1339
- <div className="divide-y">
1369
+ <div
1370
+ className={cn(
1371
+ "flex flex-col",
1372
+ // Section-Children (Auto-UI-Edit) trennt eine Linie ZWISCHEN
1373
+ // ihnen — sie padden sich selbst. Flache Felder (Custom-Screens)
1374
+ // kriegen Padding + Rhythmus, keine Linie zwischen jedem Feld.
1375
+ "[&>section:not(:first-child)]:border-t",
1376
+ "[&>:not(section)]:px-6 [&>:not(section)]:py-3",
1377
+ "[&>:not(section):first-child]:pt-6 [&>:not(section):last-child]:pb-6",
1378
+ )}
1379
+ >
1340
1380
  <InsideFormContext.Provider value={true}>{children}</InsideFormContext.Provider>
1341
1381
  </div>
1342
1382
  {actions !== undefined && (