@cosmicdrift/kumiko-renderer-web 0.115.0 → 0.116.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.
|
|
3
|
+
"version": "0.116.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.
|
|
20
|
-
"@cosmicdrift/kumiko-headless": "0.
|
|
21
|
-
"@cosmicdrift/kumiko-renderer": "0.
|
|
19
|
+
"@cosmicdrift/kumiko-dispatcher-live": "0.116.0",
|
|
20
|
+
"@cosmicdrift/kumiko-headless": "0.116.0",
|
|
21
|
+
"@cosmicdrift/kumiko-renderer": "0.116.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",
|
|
@@ -329,10 +329,11 @@ describe("WorkspaceShell", () => {
|
|
|
329
329
|
<div>content</div>
|
|
330
330
|
</WorkspaceShell>,
|
|
331
331
|
);
|
|
332
|
-
// admin → system + orders, NOT tours
|
|
333
|
-
|
|
334
|
-
expect(screen.
|
|
335
|
-
expect(screen.
|
|
332
|
+
// admin → system + orders, NOT tours. getAllByText: die shadcn-Sidebar
|
|
333
|
+
// rendert Desktop + Mobile-Sheet, jeder Nav-Eintrag kommt also 2× vor.
|
|
334
|
+
expect(screen.getAllByText("System").length).toBeGreaterThan(0);
|
|
335
|
+
expect(screen.getAllByText("Orders").length).toBeGreaterThan(0);
|
|
336
|
+
expect(screen.queryAllByText("Tours").length).toBe(0);
|
|
336
337
|
});
|
|
337
338
|
|
|
338
339
|
test("clicking the dispatch tab swaps the visible nav set", () => {
|
|
@@ -346,10 +347,10 @@ describe("WorkspaceShell", () => {
|
|
|
346
347
|
</WorkspaceShell>,
|
|
347
348
|
);
|
|
348
349
|
fireEvent.click(screen.getByTestId("workspace-tab-dispatch"));
|
|
349
|
-
// dispatch → orders + tours, NOT system
|
|
350
|
-
expect(screen.
|
|
351
|
-
expect(screen.
|
|
352
|
-
expect(screen.
|
|
350
|
+
// dispatch → orders + tours, NOT system (getAllByText: Desktop + Mobile-Sheet)
|
|
351
|
+
expect(screen.getAllByText("Orders").length).toBeGreaterThan(0);
|
|
352
|
+
expect(screen.getAllByText("Tours").length).toBeGreaterThan(0);
|
|
353
|
+
expect(screen.queryAllByText("System").length).toBe(0);
|
|
353
354
|
});
|
|
354
355
|
|
|
355
356
|
test("a driver lands on the driver workspace (their only one)", () => {
|
|
@@ -365,9 +366,9 @@ describe("WorkspaceShell", () => {
|
|
|
365
366
|
// Only one workspace → switcher renders nothing, but the nav still
|
|
366
367
|
// shows that workspace's members.
|
|
367
368
|
expect(screen.queryByTestId("workspace-tab-driver")).toBeNull();
|
|
368
|
-
expect(screen.
|
|
369
|
-
expect(screen.
|
|
370
|
-
expect(screen.
|
|
369
|
+
expect(screen.getAllByText("Tours").length).toBeGreaterThan(0);
|
|
370
|
+
expect(screen.queryAllByText("System").length).toBe(0);
|
|
371
|
+
expect(screen.queryAllByText("Orders").length).toBe(0);
|
|
371
372
|
});
|
|
372
373
|
|
|
373
374
|
test("schema without workspaces falls back to plain rendering (all navs visible)", () => {
|
|
@@ -710,8 +711,9 @@ describe("WorkspaceShell — AppSchema (multi-feature)", () => {
|
|
|
710
711
|
<div>content</div>
|
|
711
712
|
</WorkspaceShell>,
|
|
712
713
|
);
|
|
713
|
-
|
|
714
|
-
expect(
|
|
714
|
+
const footers = screen.getAllByTestId("sidebar-footer");
|
|
715
|
+
expect(footers.length).toBeGreaterThan(0);
|
|
716
|
+
expect(footers[0]?.textContent).toBe("v1.2.3");
|
|
715
717
|
});
|
|
716
718
|
|
|
717
719
|
test("ohne sidebarFooter-Prop rendert die Sidebar ohne Footer-Slot (default)", () => {
|
|
@@ -14,13 +14,8 @@
|
|
|
14
14
|
// Apps die feingranulare Kontrolle wollen, gehen direkt auf die ui/-Teile.
|
|
15
15
|
// Wer ein Topbar mit Workspace-Switcher braucht, nutzt <WorkspaceShell>.
|
|
16
16
|
|
|
17
|
-
import type { NavNode } from "@cosmicdrift/kumiko-headless";
|
|
18
|
-
import { resolveNavigation } from "@cosmicdrift/kumiko-headless";
|
|
19
17
|
import type { AppSchema, FeatureSchema } from "@cosmicdrift/kumiko-renderer";
|
|
20
|
-
import {
|
|
21
|
-
import { type ReactNode, useMemo } from "react";
|
|
22
|
-
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "../ui/breadcrumb";
|
|
23
|
-
import { Separator } from "../ui/separator";
|
|
18
|
+
import type { ReactNode } from "react";
|
|
24
19
|
import {
|
|
25
20
|
Sidebar,
|
|
26
21
|
SidebarContent,
|
|
@@ -30,9 +25,9 @@ import {
|
|
|
30
25
|
SidebarInset,
|
|
31
26
|
SidebarProvider,
|
|
32
27
|
SidebarRail,
|
|
33
|
-
SidebarTrigger,
|
|
34
28
|
} from "../ui/sidebar";
|
|
35
|
-
import {
|
|
29
|
+
import { NavTree } from "./nav-tree";
|
|
30
|
+
import { ShellHeader } from "./shell-header";
|
|
36
31
|
|
|
37
32
|
export type DefaultAppShellUser = {
|
|
38
33
|
readonly id: string;
|
|
@@ -123,64 +118,3 @@ export function DefaultAppShell({
|
|
|
123
118
|
</SidebarProvider>
|
|
124
119
|
);
|
|
125
120
|
}
|
|
126
|
-
|
|
127
|
-
// Inset-Header: hostet den SidebarTrigger (öffnet das Mobile-Sheet / toggled
|
|
128
|
-
// die Rail) und einen Breadcrumb mit dem aktiven Screen. Höhe == Sidebar-
|
|
129
|
-
// Header (h-12), damit Rail und Panel oben bündig sind.
|
|
130
|
-
function ShellHeader({
|
|
131
|
-
schema,
|
|
132
|
-
user,
|
|
133
|
-
headerActions,
|
|
134
|
-
}: {
|
|
135
|
-
readonly schema: AppSchema | FeatureSchema;
|
|
136
|
-
readonly user?: DefaultAppShellUser;
|
|
137
|
-
readonly headerActions?: ReactNode;
|
|
138
|
-
}): ReactNode {
|
|
139
|
-
const nav = useNav();
|
|
140
|
-
const t = useTranslation();
|
|
141
|
-
const tree = useMemo(() => {
|
|
142
|
-
const source = buildNavRegistrySliceForApp(toAppSchema(schema));
|
|
143
|
-
return resolveNavigation({ source, ...(user !== undefined && { user }) });
|
|
144
|
-
}, [schema, user]);
|
|
145
|
-
|
|
146
|
-
const screenId = nav.route?.screenId;
|
|
147
|
-
const label = screenId !== undefined ? activeNavLabel(tree, screenId, (k) => t(k)) : undefined;
|
|
148
|
-
|
|
149
|
-
return (
|
|
150
|
-
<header className="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
|
151
|
-
<div className="flex items-center gap-2 px-4">
|
|
152
|
-
<SidebarTrigger className="-ml-1" />
|
|
153
|
-
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
|
|
154
|
-
{label !== undefined && (
|
|
155
|
-
<Breadcrumb>
|
|
156
|
-
<BreadcrumbList>
|
|
157
|
-
<BreadcrumbItem>
|
|
158
|
-
<BreadcrumbPage>{label}</BreadcrumbPage>
|
|
159
|
-
</BreadcrumbItem>
|
|
160
|
-
</BreadcrumbList>
|
|
161
|
-
</Breadcrumb>
|
|
162
|
-
)}
|
|
163
|
-
</div>
|
|
164
|
-
{headerActions !== undefined && (
|
|
165
|
-
<div data-kumiko-layout="header-actions" className="ml-auto flex items-center gap-2 px-4">
|
|
166
|
-
{headerActions}
|
|
167
|
-
</div>
|
|
168
|
-
)}
|
|
169
|
-
</header>
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function activeNavLabel(
|
|
174
|
-
nodes: readonly NavNode[],
|
|
175
|
-
screenId: string,
|
|
176
|
-
t: (key: string) => string,
|
|
177
|
-
): string | undefined {
|
|
178
|
-
for (const node of nodes) {
|
|
179
|
-
if (node.screen !== undefined && lastSegment(node.screen) === screenId) {
|
|
180
|
-
return node.label.includes(".") ? t(node.label) : node.label;
|
|
181
|
-
}
|
|
182
|
-
const child = activeNavLabel(node.children, screenId, t);
|
|
183
|
-
if (child !== undefined) return child;
|
|
184
|
-
}
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// ShellHeader — die Inset-Kopfzeile für Sidebar-basierte Shells: SidebarTrigger
|
|
2
|
+
// (Rail-/Mobile-Sheet-Toggle) + Breadcrumb mit dem aktiven Screen + optionale
|
|
3
|
+
// rechtsbündige headerActions. Geteilt von DefaultAppShell und WorkspaceShell,
|
|
4
|
+
// damit beide dieselbe Kopfzeile tragen (Höhe h-16, kollabiert auf h-12 mit
|
|
5
|
+
// der Icon-Rail).
|
|
6
|
+
|
|
7
|
+
import type { NavNode } from "@cosmicdrift/kumiko-headless";
|
|
8
|
+
import { resolveNavigation } from "@cosmicdrift/kumiko-headless";
|
|
9
|
+
import type { AppSchema, FeatureSchema } from "@cosmicdrift/kumiko-renderer";
|
|
10
|
+
import { toAppSchema, useNav, useTranslation } from "@cosmicdrift/kumiko-renderer";
|
|
11
|
+
import { type ReactNode, useMemo } from "react";
|
|
12
|
+
import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "../ui/breadcrumb";
|
|
13
|
+
import { Separator } from "../ui/separator";
|
|
14
|
+
import { SidebarTrigger } from "../ui/sidebar";
|
|
15
|
+
import { buildNavRegistrySliceForApp, lastSegment } from "./nav-tree";
|
|
16
|
+
|
|
17
|
+
type ShellHeaderUser = {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly roles: readonly string[];
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export function ShellHeader({
|
|
23
|
+
schema,
|
|
24
|
+
user,
|
|
25
|
+
headerActions,
|
|
26
|
+
}: {
|
|
27
|
+
readonly schema: AppSchema | FeatureSchema;
|
|
28
|
+
readonly user?: ShellHeaderUser;
|
|
29
|
+
readonly headerActions?: ReactNode;
|
|
30
|
+
}): ReactNode {
|
|
31
|
+
const nav = useNav();
|
|
32
|
+
const t = useTranslation();
|
|
33
|
+
const tree = useMemo(() => {
|
|
34
|
+
const source = buildNavRegistrySliceForApp(toAppSchema(schema));
|
|
35
|
+
return resolveNavigation({ source, ...(user !== undefined && { user }) });
|
|
36
|
+
}, [schema, user]);
|
|
37
|
+
|
|
38
|
+
const screenId = nav.route?.screenId;
|
|
39
|
+
const label = screenId !== undefined ? activeNavLabel(tree, screenId, (k) => t(k)) : undefined;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<header className="flex h-16 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12">
|
|
43
|
+
<div className="flex items-center gap-2 px-4">
|
|
44
|
+
<SidebarTrigger className="-ml-1" />
|
|
45
|
+
<Separator orientation="vertical" className="mr-2 data-[orientation=vertical]:h-4" />
|
|
46
|
+
{label !== undefined && (
|
|
47
|
+
<Breadcrumb>
|
|
48
|
+
<BreadcrumbList>
|
|
49
|
+
<BreadcrumbItem>
|
|
50
|
+
<BreadcrumbPage>{label}</BreadcrumbPage>
|
|
51
|
+
</BreadcrumbItem>
|
|
52
|
+
</BreadcrumbList>
|
|
53
|
+
</Breadcrumb>
|
|
54
|
+
)}
|
|
55
|
+
</div>
|
|
56
|
+
{headerActions !== undefined && (
|
|
57
|
+
<div data-kumiko-layout="header-actions" className="ml-auto flex items-center gap-2 px-4">
|
|
58
|
+
{headerActions}
|
|
59
|
+
</div>
|
|
60
|
+
)}
|
|
61
|
+
</header>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function activeNavLabel(
|
|
66
|
+
nodes: readonly NavNode[],
|
|
67
|
+
screenId: string,
|
|
68
|
+
t: (key: string) => string,
|
|
69
|
+
): string | undefined {
|
|
70
|
+
for (const node of nodes) {
|
|
71
|
+
if (node.screen !== undefined && lastSegment(node.screen) === screenId) {
|
|
72
|
+
return node.label.includes(".") ? t(node.label) : node.label;
|
|
73
|
+
}
|
|
74
|
+
const child = activeNavLabel(node.children, screenId, t);
|
|
75
|
+
if (child !== undefined) return child;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
@@ -34,13 +34,19 @@ import type { AppSchema, FeatureSchema, WorkspaceSchema } from "@cosmicdrift/kum
|
|
|
34
34
|
import { qualifyNavId, toAppSchema, useNav } from "@cosmicdrift/kumiko-renderer";
|
|
35
35
|
import { type ReactNode, useCallback, useLayoutEffect, useMemo } from "react";
|
|
36
36
|
import { useResolvers } from "../app/resolvers-context";
|
|
37
|
-
import {
|
|
38
|
-
|
|
37
|
+
import {
|
|
38
|
+
Sidebar,
|
|
39
|
+
SidebarContent,
|
|
40
|
+
SidebarFooter,
|
|
41
|
+
SidebarHeader,
|
|
42
|
+
SidebarInset,
|
|
43
|
+
SidebarProvider,
|
|
44
|
+
SidebarRail,
|
|
45
|
+
} from "../ui/sidebar";
|
|
39
46
|
import { EditorPanel } from "./editor-panel";
|
|
40
47
|
import { lastSegment, NavTree } from "./nav-tree";
|
|
41
|
-
import {
|
|
48
|
+
import { ShellHeader } from "./shell-header";
|
|
42
49
|
import { parseTargetFromSearchParams } from "./target-url";
|
|
43
|
-
import { Topbar } from "./topbar";
|
|
44
50
|
import { WorkspaceSwitcher } from "./workspace-switcher";
|
|
45
51
|
|
|
46
52
|
export type WorkspaceShellUser = {
|
|
@@ -204,21 +210,34 @@ export function WorkspaceShell({
|
|
|
204
210
|
/>
|
|
205
211
|
);
|
|
206
212
|
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
// (
|
|
213
|
+
// Modern shell (wie DefaultAppShell): collapsible Icon-Rail-Sidebar trägt
|
|
214
|
+
// Brand + Workspace-Switcher + Nav + Footer; der SidebarInset rendert die
|
|
215
|
+
// geteilte ShellHeader (Panel-Toggle + Breadcrumb + Actions) über dem
|
|
216
|
+
// Content. topbarActions landen rechts in der Header-Zeile statt in einer
|
|
217
|
+
// separaten Topbar — eine Kopfzeile statt zwei.
|
|
210
218
|
return (
|
|
211
219
|
<SidebarProvider>
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
<Sidebar collapsible="icon">
|
|
221
|
+
<SidebarHeader data-kumiko-layout="sidebar-header">
|
|
222
|
+
{brand}
|
|
223
|
+
{switcher}
|
|
224
|
+
</SidebarHeader>
|
|
225
|
+
<SidebarContent data-kumiko-layout="sidebar-nav">{sidebarContent}</SidebarContent>
|
|
226
|
+
{sidebarFooter !== undefined && (
|
|
227
|
+
<SidebarFooter data-kumiko-layout="sidebar-footer">{sidebarFooter}</SidebarFooter>
|
|
228
|
+
)}
|
|
229
|
+
<SidebarRail />
|
|
230
|
+
</Sidebar>
|
|
231
|
+
<SidebarInset>
|
|
232
|
+
<ShellHeader
|
|
233
|
+
schema={app}
|
|
234
|
+
{...(user !== undefined && { user })}
|
|
235
|
+
{...(topbarActions !== undefined && { headerActions: topbarActions })}
|
|
236
|
+
/>
|
|
237
|
+
<main className="flex-1 overflow-auto">
|
|
238
|
+
{activeTarget !== undefined ? <EditorPanel resolvers={resolvers} /> : children}
|
|
239
|
+
</main>
|
|
240
|
+
</SidebarInset>
|
|
222
241
|
</SidebarProvider>
|
|
223
242
|
);
|
|
224
243
|
}
|