@cosmicdrift/kumiko-renderer-web 0.237.1 → 0.237.2

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.237.1",
3
+ "version": "0.237.2",
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.237.1",
20
- "@cosmicdrift/kumiko-headless": "0.237.1",
21
- "@cosmicdrift/kumiko-renderer": "0.237.1",
19
+ "@cosmicdrift/kumiko-dispatcher-live": "0.237.2",
20
+ "@cosmicdrift/kumiko-headless": "0.237.2",
21
+ "@cosmicdrift/kumiko-renderer": "0.237.2",
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",
@@ -64,7 +64,7 @@
64
64
  "@types/react-dom": "^19.2.3",
65
65
  "jsdom": "^29.1.1",
66
66
  "tailwindcss": "^4.3.0",
67
- "@cosmicdrift/kumiko-locale-de": "0.237.1"
67
+ "@cosmicdrift/kumiko-locale-de": "0.237.2"
68
68
  },
69
69
  "repository": {
70
70
  "type": "git",
@@ -17,6 +17,7 @@ import {
17
17
  import { type ReactNode, useCallback, useState } from "react";
18
18
  import { z } from "zod";
19
19
  import { PATCH_DRAFT_SAVE_DEBOUNCE_MS } from "../../../renderer/src/components/render-edit";
20
+ import { defaultPrimitives, ScreenWidthProvider } from "../primitives";
20
21
  import {
21
22
  act,
22
23
  createFakeDraftStorage,
@@ -37,6 +38,8 @@ const orderEntity = {
37
38
  },
38
39
  } as unknown as EntityDefinition;
39
40
 
41
+ const { Button } = defaultPrimitives;
42
+
40
43
  function makeScreen(): EntityEditScreenDefinition {
41
44
  return {
42
45
  id: "orders:screen:order-edit",
@@ -320,6 +323,78 @@ describe("RenderEdit", () => {
320
323
  expect(shell?.className).not.toContain("max-w-full");
321
324
  });
322
325
 
326
+ test("ScreenWidthProvider width='full' widens the form shell when layout.width is unset (fw#2656)", () => {
327
+ render(
328
+ <ScreenWidthProvider width="full">
329
+ <DispatcherProvider dispatcher={makeDispatcher()}>
330
+ <RenderEdit<TestValues>
331
+ screen={makeScreen()}
332
+ entity={orderEntity}
333
+ featureName="orders"
334
+ initial={{ title: "", count: 0, isUrgent: false }}
335
+ writeCommand="order:create"
336
+ />
337
+ </DispatcherProvider>
338
+ </ScreenWidthProvider>,
339
+ );
340
+
341
+ const shell = screen.getByTestId("render-edit-form").firstElementChild;
342
+ expect(shell?.className).toContain("max-w-full");
343
+ expect(shell?.className).not.toContain("max-w-4xl");
344
+ });
345
+
346
+ test("layout.width: '3xl' overrides ScreenWidthProvider width='full' (fw#2656)", () => {
347
+ const screenDef = makeScreen();
348
+ render(
349
+ <ScreenWidthProvider width="full">
350
+ <DispatcherProvider dispatcher={makeDispatcher()}>
351
+ <RenderEdit<TestValues>
352
+ screen={{ ...screenDef, layout: { ...screenDef.layout, width: "3xl" } }}
353
+ entity={orderEntity}
354
+ featureName="orders"
355
+ initial={{ title: "", count: 0, isUrgent: false }}
356
+ writeCommand="order:create"
357
+ />
358
+ </DispatcherProvider>
359
+ </ScreenWidthProvider>,
360
+ );
361
+
362
+ const shell = screen.getByTestId("render-edit-form").firstElementChild;
363
+ expect(shell?.className).toContain("max-w-3xl");
364
+ expect(shell?.className).not.toContain("max-w-full");
365
+ });
366
+
367
+ test("Cancel button renders with the same hover-class family as a secondary button, not the link look (fw#2656)", () => {
368
+ render(
369
+ <>
370
+ <Button testId="secondary-reference" variant="secondary">
371
+ Reference
372
+ </Button>
373
+ <DispatcherProvider dispatcher={makeDispatcher()}>
374
+ <RenderEdit<TestValues>
375
+ screen={makeScreen()}
376
+ entity={orderEntity}
377
+ featureName="orders"
378
+ initial={{ title: "", count: 0, isUrgent: false }}
379
+ writeCommand="order:create"
380
+ onCancel={() => {}}
381
+ />
382
+ </DispatcherProvider>
383
+ </>,
384
+ );
385
+
386
+ const reference = screen.getByTestId("secondary-reference");
387
+ const cancel = screen.getByTestId("render-edit-cancel");
388
+ const referenceHoverClass = reference.className
389
+ .split(" ")
390
+ .find((token) => token.startsWith("hover:"));
391
+
392
+ expect(referenceHoverClass).toBeDefined();
393
+ expect(cancel.className).toContain(referenceHoverClass as string);
394
+ expect(cancel.className).not.toContain("h-auto");
395
+ expect(cancel.className).not.toContain("px-0");
396
+ });
397
+
323
398
  test("title resolved aus i18n-Key `screen:<id>.title` mit screenId als Fallback", () => {
324
399
  const dispatcher = makeDispatcher();
325
400
  render(
@@ -19,6 +19,7 @@ import {
19
19
  DraftStorageProvider,
20
20
  ExtensionSectionsProvider,
21
21
  type FeatureSchema,
22
+ type FormWidth,
22
23
  hasDetailScreen,
23
24
  KumikoScreen,
24
25
  kumikoDefaultTranslations,
@@ -40,7 +41,7 @@ import {
40
41
  import { type ComponentType, type ReactNode, useMemo } from "react";
41
42
  import { createRoot, type Root } from "react-dom/client";
42
43
  import { lastSegment } from "../layout/nav-tree";
43
- import { defaultPrimitives } from "../primitives";
44
+ import { defaultPrimitives, ScreenWidthProvider } from "../primitives";
44
45
  import { ToastProvider } from "../primitives/toast";
45
46
  import { createEventSourceLiveEvents } from "../sse/live-events";
46
47
  import { useBrowserTokensApi } from "../tokens";
@@ -189,6 +190,13 @@ export type CreateKumikoAppOptions = {
189
190
  readonly hasWorkspaces?: boolean;
190
191
  readonly features?: readonly FeatureSchema[];
191
192
  }) => NavApi;
193
+ /** App-wide default width for form/detail screens that don't set their
194
+ * own `layout.width` (fw#2656). Default "4xl" (unchanged behavior) — a
195
+ * consumer app that wants e.g. bundled-feature screens to match its own
196
+ * full-width lists passes `screenWidth: "full"` once here instead of
197
+ * forking every screen it doesn't own. Per-screen `layout.width` always
198
+ * wins over this default. */
199
+ readonly screenWidth?: FormWidth;
192
200
  };
193
201
 
194
202
  // Reads the dev-server-injected schema from the global. Guarded for
@@ -429,32 +437,34 @@ export function createKumikoApp(options: CreateKumikoAppOptions = {}): { readonl
429
437
  <LocaleProvider resolver={localeResolver} fallbackBundles={fallbackBundles}>
430
438
  <DocumentLangSync resolver={localeResolver} />
431
439
  <PrimitivesProvider value={primitives}>
432
- <AppFeaturesProvider features={app.features}>
433
- <DispatcherProvider dispatcher={dispatcher}>
434
- <DraftStorageProvider value={draftStorage}>
435
- <LiveEventsProvider value={liveEvents}>
436
- <DashboardBodyProvider value={WebDashboardBody}>
437
- <CustomScreensProvider value={customScreens}>
438
- <ColumnRenderersProvider value={columnRenderers}>
439
- <ContentEditorsProvider value={contentEditors}>
440
- <ExtensionSectionsProvider value={extensionSectionComponents}>
441
- <NavProvidersProvider value={navProviders} entities={navEntities}>
442
- <ResolversProvider resolvers={resolvers}>
443
- <ToastProvider>
444
- <UpdateChecker />
445
- {stackWrappers(providers, stackWrappers(gates, screenNode))}
446
- </ToastProvider>
447
- </ResolversProvider>
448
- </NavProvidersProvider>
449
- </ExtensionSectionsProvider>
450
- </ContentEditorsProvider>
451
- </ColumnRenderersProvider>
452
- </CustomScreensProvider>
453
- </DashboardBodyProvider>
454
- </LiveEventsProvider>
455
- </DraftStorageProvider>
456
- </DispatcherProvider>
457
- </AppFeaturesProvider>
440
+ <ScreenWidthProvider width={options.screenWidth ?? "4xl"}>
441
+ <AppFeaturesProvider features={app.features}>
442
+ <DispatcherProvider dispatcher={dispatcher}>
443
+ <DraftStorageProvider value={draftStorage}>
444
+ <LiveEventsProvider value={liveEvents}>
445
+ <DashboardBodyProvider value={WebDashboardBody}>
446
+ <CustomScreensProvider value={customScreens}>
447
+ <ColumnRenderersProvider value={columnRenderers}>
448
+ <ContentEditorsProvider value={contentEditors}>
449
+ <ExtensionSectionsProvider value={extensionSectionComponents}>
450
+ <NavProvidersProvider value={navProviders} entities={navEntities}>
451
+ <ResolversProvider resolvers={resolvers}>
452
+ <ToastProvider>
453
+ <UpdateChecker />
454
+ {stackWrappers(providers, stackWrappers(gates, screenNode))}
455
+ </ToastProvider>
456
+ </ResolversProvider>
457
+ </NavProvidersProvider>
458
+ </ExtensionSectionsProvider>
459
+ </ContentEditorsProvider>
460
+ </ColumnRenderersProvider>
461
+ </CustomScreensProvider>
462
+ </DashboardBodyProvider>
463
+ </LiveEventsProvider>
464
+ </DraftStorageProvider>
465
+ </DispatcherProvider>
466
+ </AppFeaturesProvider>
467
+ </ScreenWidthProvider>
458
468
  </PrimitivesProvider>
459
469
  </LocaleProvider>
460
470
  </TokensBoot>
package/src/index.ts CHANGED
@@ -141,6 +141,7 @@ export {
141
141
  DefaultCard as Card,
142
142
  defaultPrimitives,
143
143
  FormScreenShell,
144
+ ScreenWidthProvider,
144
145
  } from "./primitives";
145
146
  export type { ActionMenuProps, MenuItemDef } from "./primitives/action-menu";
146
147
  export { ActionMenu } from "./primitives/action-menu";
@@ -2028,6 +2028,21 @@ export function BareFormProvider({ children }: { children: ReactNode }): ReactNo
2028
2028
  return <BareFormContext.Provider value={true}>{children}</BareFormContext.Provider>;
2029
2029
  }
2030
2030
 
2031
+ // App-wide default for FormScreenShell's width when a screen doesn't set its
2032
+ // own `layout.width`. Default "4xl" keeps today's behavior for apps that
2033
+ // don't opt into `createKumikoApp({ screenWidth })` (fw#2656).
2034
+ const ScreenWidthContext = createContext<FormWidth>("4xl");
2035
+
2036
+ export function ScreenWidthProvider({
2037
+ width,
2038
+ children,
2039
+ }: {
2040
+ readonly width: FormWidth;
2041
+ readonly children: ReactNode;
2042
+ }): ReactNode {
2043
+ return <ScreenWidthContext.Provider value={width}>{children}</ScreenWidthContext.Provider>;
2044
+ }
2045
+
2031
2046
  function DefaultForm({
2032
2047
  onSubmit,
2033
2048
  children,
@@ -2161,27 +2176,29 @@ function DefaultForm({
2161
2176
  }
2162
2177
 
2163
2178
  // Canonical form/settings shell shared by DefaultForm (configEdit/entityEdit)
2164
- // and custom settings screens (profile, privacy-center). Default width is
2165
- // "4xl" a centered column, narrower than list chrome (which runs full-
2166
- // width) override via maxWidth / layout.width, e.g. "full" to match list
2167
- // chrome or "3xl" for a narrower auth-adjacent form.
2179
+ // and custom settings screens (profile, privacy-center). Width resolution:
2180
+ // explicit `maxWidth` prop (screen's `layout.width`) wins, else the app-wide
2181
+ // `createKumikoApp({ screenWidth })` default from ScreenWidthContext, else
2182
+ // "4xl" (a centered column, narrower than list chrome, which runs full-width).
2168
2183
  export type FormScreenShellWidth = FormWidth;
2169
2184
 
2170
2185
  export function FormScreenShell({
2171
2186
  children,
2172
2187
  className,
2173
2188
  testId,
2174
- maxWidth = "4xl",
2189
+ maxWidth,
2175
2190
  }: {
2176
2191
  readonly children: ReactNode;
2177
2192
  readonly className?: string;
2178
2193
  readonly testId?: string;
2179
2194
  readonly maxWidth?: FormScreenShellWidth;
2180
2195
  }): ReactNode {
2196
+ const contextWidth = useContext(ScreenWidthContext);
2197
+ const width = maxWidth ?? contextWidth;
2181
2198
  return (
2182
2199
  <div
2183
2200
  data-testid={testId}
2184
- className={cn("px-6 pt-6 pb-12 w-full", screenWidthClassName[maxWidth], className)}
2201
+ className={cn("px-6 pt-6 pb-12 w-full", screenWidthClassName[width], className)}
2185
2202
  >
2186
2203
  {children}
2187
2204
  </div>