@alfakit/react 0.1.10 → 0.1.11

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.
Files changed (50) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/components/actions/action-button.js +13 -1
  3. package/dist/components/actions/action-button.js.map +1 -1
  4. package/dist/components/dev-tools/dev-tools-inspector.js +2 -2
  5. package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
  6. package/dist/components/fields/money-input.d.ts +5 -0
  7. package/dist/components/fields/money-input.d.ts.map +1 -1
  8. package/dist/components/fields/money-input.js +2 -5
  9. package/dist/components/fields/money-input.js.map +1 -1
  10. package/dist/components/panel/lazy-placeholder.d.ts +1 -0
  11. package/dist/components/panel/lazy-placeholder.d.ts.map +1 -1
  12. package/dist/components/panel/lazy-placeholder.js +38 -4
  13. package/dist/components/panel/lazy-placeholder.js.map +1 -1
  14. package/dist/components/table/cells.js +1 -1
  15. package/dist/components/table/cells.js.map +1 -1
  16. package/dist/components/ui/calendar.d.ts.map +1 -1
  17. package/dist/components/ui/calendar.js +7 -0
  18. package/dist/components/ui/calendar.js.map +1 -1
  19. package/dist/components/widgets/dashboard-customizer.js +2 -2
  20. package/dist/components/widgets/dashboard-customizer.js.map +1 -1
  21. package/dist/components/widgets/widgets-block.js +2 -2
  22. package/dist/components/widgets/widgets-block.js.map +1 -1
  23. package/dist/create-alfakit.d.ts.map +1 -1
  24. package/dist/create-alfakit.js +3 -2
  25. package/dist/create-alfakit.js.map +1 -1
  26. package/dist/eject-manifest.json +4 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +2 -2
  29. package/dist/standalone/alfakit-component.js +2 -2
  30. package/dist/standalone/alfakit-component.js.map +1 -1
  31. package/dist/standalone.css +21 -0
  32. package/dist/starter-kit/index.d.ts +2 -2
  33. package/dist/starter-kit/index.js +2 -2
  34. package/dist/starter-kit/starter-kit.d.ts +9 -0
  35. package/dist/starter-kit/starter-kit.d.ts.map +1 -1
  36. package/dist/starter-kit/starter-kit.js +21 -2
  37. package/dist/starter-kit/starter-kit.js.map +1 -1
  38. package/package.json +2 -2
  39. package/src/components/actions/action-button.tsx +15 -1
  40. package/src/components/dev-tools/dev-tools-inspector.tsx +6 -1
  41. package/src/components/fields/money-input.tsx +3 -14
  42. package/src/components/panel/lazy-placeholder.tsx +41 -1
  43. package/src/components/table/cells.tsx +3 -1
  44. package/src/components/ui/calendar.tsx +8 -0
  45. package/src/components/widgets/dashboard-customizer.tsx +2 -2
  46. package/src/components/widgets/widgets-block.tsx +2 -2
  47. package/src/create-alfakit.tsx +9 -1
  48. package/src/standalone/alfakit-component.tsx +2 -2
  49. package/src/starter-kit/index.ts +1 -0
  50. package/src/starter-kit/starter-kit.tsx +22 -0
@@ -35,10 +35,29 @@ function StarterKitContentLayout({ children, maxContentWidth }) {
35
35
  ]
36
36
  });
37
37
  }
38
+ /**
39
+ * A `$layout = 'fullscreen'` page inside the host layout: what `layout:fullscreen` renders in
40
+ * starter kit mode. The host layout is the chrome, so there is no AlfaKit header (brand, top
41
+ * bar slots, user menu, Exit); the screen gets the whole content area. Replace it with
42
+ * `{ components: { 'layout:fullscreen': MyScreen } }`.
43
+ */
44
+ function StarterKitFullscreenLayout({ children }) {
45
+ return /* @__PURE__ */ jsxs("div", {
46
+ "data-slot": "content",
47
+ "data-layout": "fullscreen",
48
+ "data-alfakit-embedded": "",
49
+ className: "flex min-h-0 w-full min-w-0 flex-1 flex-col",
50
+ children: [
51
+ /* @__PURE__ */ jsx(Slot, { name: "content.start" }),
52
+ children,
53
+ /* @__PURE__ */ jsx(Slot, { name: "content.end" })
54
+ ]
55
+ });
56
+ }
38
57
  let browserContext = null;
39
58
  function createStarterKitContext(options) {
40
59
  const discovered = discoverComponents(options.discover);
41
- const registry = createDefaultRegistry().registerMany(lazyPanelLayouts).register("layout:auth", AuthLayout).register("layout:app", StarterKitContentLayout).registerMany(discovered.components).registerMany(options.components ?? {});
60
+ const registry = createDefaultRegistry().registerMany(lazyPanelLayouts).register("layout:auth", AuthLayout).register("layout:app", StarterKitContentLayout).register("layout:fullscreen", StarterKitFullscreenLayout).registerMany(discovered.components).registerMany(options.components ?? {});
42
61
  const translator = new Translator();
43
62
  return {
44
63
  registry,
@@ -131,6 +150,6 @@ function createStarterKitPage(options = {}) {
131
150
  return AlfakitStarterKitPage;
132
151
  }
133
152
  //#endregion
134
- export { AlfakitStarterKitRoot, StarterKitContentLayout, createStarterKitPage };
153
+ export { AlfakitStarterKitRoot, StarterKitContentLayout, StarterKitFullscreenLayout, createStarterKitPage };
135
154
 
136
155
  //# sourceMappingURL=starter-kit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"starter-kit.js","names":[],"sources":["../../src/starter-kit/starter-kit.tsx"],"sourcesContent":["import {\n ActionRunner,\n STARTER_KIT_TOASTER_ID,\n Translator,\n configureCharts,\n installStarterKitRuntime,\n registerRichEditorExtensions,\n resolveRequestErrorNotification,\n starterKitBreadcrumbsFromProps,\n syncStarterKitPage,\n usesStarterKitHostLayout,\n type AlfakitOptions,\n type AlfakitPageProps,\n type StarterKitBreadcrumb,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { useEffect, useState, type ComponentType, type ReactNode } from 'react'\nimport { setToasterId, showToast } from '../components/notifications/flash-toasts'\nimport { DebugPanelSlot } from '../components/panel/debug-panel-slot'\nimport { Slot } from '../components/panel/slot'\nimport {\n AlfakitProvider,\n useAlfakitProps,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { AlfakitRoot } from '../create-alfakit'\nimport type { AppLayoutProps } from '../layouts/app-layout'\nimport { AuthLayout } from '../layouts/auth-layout'\nimport { lazyPanelLayouts } from '../layouts/lazy-layouts'\nimport { MAX_WIDTHS } from '../layouts/max-widths'\nimport { discoverComponents } from '../lib/discovery'\nimport { preloadIcons } from '../lib/icons'\nimport { cn } from '../lib/utils'\nimport { AlfakitPage } from '../pages/page'\nimport { createDefaultRegistry } from '../registry'\n\n/** The host's layout, as the starter kits' `AppLayout` takes its props. */\nexport type StarterKitLayout = ComponentType<{\n breadcrumbs?: StarterKitBreadcrumb[]\n children: ReactNode\n}>\n\nexport interface StarterKitPageOptions extends Pick<\n AlfakitOptions<AnyComponent>,\n 'components' | 'pages' | 'chartFormatters' | 'chartPlugins' | 'richEditorExtensions'\n> {\n /**\n * The host layout Alfakit pages render in, usually the starter kit's `AppLayout`\n * (`@/layouts/app-layout`). It receives the page's breadcrumbs as `{ title, href }`.\n * `null` renders the pages without a host layout.\n */\n layout?: StarterKitLayout | null\n /**\n * Auto-registered custom components, as for `createAlfakit({ discover })`: pass\n * `import.meta.glob('../../alfakit/{fields,columns,entries,widgets,pages,components,blocks}/**\\/*.tsx')`\n * from `resources/js/pages/Alfakit/Page.tsx` and every file there registers under its name.\n */\n discover?: AlfakitOptions<AnyComponent>['discover']\n}\n\n/**\n * The content area of an Alfakit page inside the host layout: what `layout:app` renders in\n * starter kit mode instead of Alfakit's sidebar and top bar. Replace it with\n * `createStarterKitPage({ components: { 'layout:app': MyContent } })`.\n */\nexport function StarterKitContentLayout({ children, maxContentWidth }: AppLayoutProps) {\n const props = useAlfakitProps()\n // The host's pages fill the content area, so starter kit pages default to the full width.\n const width = maxContentWidth ?? props.alfakit.maxContentWidth ?? 'full'\n\n return (\n <div\n data-slot=\"content\"\n data-alfakit-embedded=\"\"\n className={cn(\n 'mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6',\n MAX_WIDTHS[width] ?? 'max-w-full',\n )}\n >\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </div>\n )\n}\n\n// One context for the browser session (it survives navigations between host and Alfakit\n// pages); a server render builds one per request so concurrent renders never share a locale.\nlet browserContext: AlfakitContextValue | null = null\n\nfunction createStarterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n // The host's layout replaces the panel's. Auth pages (invitations, tenant registration)\n // keep the panel's card; the fullscreen layout loads on demand.\n // Auto-registered files sit between the built-ins and the explicit `components` / `pages`.\n const discovered = discoverComponents(options.discover)\n const registry = createDefaultRegistry()\n .registerMany(lazyPanelLayouts)\n .register('layout:auth', AuthLayout)\n .register('layout:app', StarterKitContentLayout)\n .registerMany(discovered.components)\n .registerMany(options.components ?? {})\n const translator = new Translator()\n\n return {\n registry,\n pages: { 'Alfakit/Page': AlfakitPage, ...discovered.pages, ...options.pages },\n translator,\n actions: new ActionRunner({\n endpoints: { action: '', modal: '' },\n // A failed action request is a toast, never a silent no-op.\n onError: (error) => {\n const notification = resolveRequestErrorNotification(\n error,\n null,\n (key, replacements, fallback) => translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n },\n }),\n }\n}\n\nfunction starterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n if (typeof window === 'undefined') return createStarterKitContext(options)\n\n browserContext ??= createStarterKitContext(options)\n\n return browserContext\n}\n\n/**\n * The Inertia layout around every Alfakit page in starter kit mode, inside the host layout:\n * Alfakit's context, modals, toasts and the reading direction. It stays mounted while the\n * user moves between Alfakit pages.\n */\nexport function AlfakitStarterKitRoot({\n options,\n children,\n}: {\n options?: StarterKitPageOptions\n children?: ReactNode\n}) {\n const [context] = useState(() => starterKitContext(options ?? {}))\n const props = usePage<AlfakitPageProps>().props\n const panel = props.alfakit\n\n // Endpoints and error notifications follow the open page.\n if (panel) {\n context.actions.setEndpoints({\n action: panel.endpoints.action ?? '',\n modal: panel.endpoints.modal ?? '',\n })\n }\n\n useEffect(() => {\n syncStarterKitPage(props)\n }, [props])\n\n useEffect(() => {\n installStarterKitRuntime(\n (notification) => showToast(notification),\n (key, replacements, fallback) => context.translator.t(key, replacements, fallback),\n )\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => {\n // A host page is open: its errors and toasts are the host's again.\n syncStarterKitPage(null)\n setToasterId(undefined)\n }\n }, [context])\n\n return (\n <AlfakitProvider value={context}>\n {/* The reading direction and language of Alfakit's content only: the host layout around\n it (sidebar, header) and the app's own pages keep the document's. */}\n <div\n data-slot=\"starter-kit-root\"\n dir={panel?.localization?.direction ?? 'ltr'}\n lang={panel?.localization?.tag}\n className=\"contents\"\n >\n <AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>\n {children}\n {/* The debug panel's button lives in AlfaKit's top bar, which a starter kit doesn't\n render: float it in the corner instead (nothing without `alfakitDebug`). The corner is the\n document's right: the kit's sidebar stays on the left in a right-to-left panel language. */}\n <div data-slot=\"starter-kit-debug-panel\" className=\"fixed right-16 bottom-16 z-40\">\n <DebugPanelSlot />\n </div>\n </AlfakitRoot>\n </div>\n </AlfakitProvider>\n )\n}\n\n/**\n * The page component for `Alfakit/Page` in a starter kit app: the host's\n * `resources/js/pages/Alfakit/Page.tsx` (written by `alfakit:install --starter-kit`) exports it,\n * so Inertia's page resolution, the root view's page preload and SSR all find it like any\n * other host page.\n *\n * ```tsx\n * import { createStarterKitPage } from '@alfakit/react'\n * import AppLayout from '@/layouts/app-layout'\n *\n * export default createStarterKitPage({ layout: AppLayout })\n * ```\n */\nexport function createStarterKitPage(options: StarterKitPageOptions = {}) {\n // Panel pages draw named icons: fetch `lucide-react/dynamic` with the page.\n preloadIcons()\n // `'js:name'` chart option callbacks, Chart.js plugins and rich editor extensions.\n configureCharts(options)\n registerRichEditorExtensions(options.richEditorExtensions ?? {})\n\n function AlfakitStarterKitPage() {\n return <AlfakitPage />\n }\n\n // A layout callback: the host layout (with the page's breadcrumbs), then Alfakit's root.\n AlfakitStarterKitPage.layout = (props: Record<string, unknown>) => [\n ...(options.layout && usesStarterKitHostLayout(props)\n ? [[options.layout, { breadcrumbs: starterKitBreadcrumbsFromProps(props) }] as const]\n : []),\n [AlfakitStarterKitRoot, { options }] as const,\n ]\n\n return AlfakitStarterKitPage\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"starter-kit.js","names":[],"sources":["../../src/starter-kit/starter-kit.tsx"],"sourcesContent":["import {\n ActionRunner,\n STARTER_KIT_TOASTER_ID,\n Translator,\n configureCharts,\n installStarterKitRuntime,\n registerRichEditorExtensions,\n resolveRequestErrorNotification,\n starterKitBreadcrumbsFromProps,\n syncStarterKitPage,\n usesStarterKitHostLayout,\n type AlfakitOptions,\n type AlfakitPageProps,\n type StarterKitBreadcrumb,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { useEffect, useState, type ComponentType, type ReactNode } from 'react'\nimport { setToasterId, showToast } from '../components/notifications/flash-toasts'\nimport { DebugPanelSlot } from '../components/panel/debug-panel-slot'\nimport { Slot } from '../components/panel/slot'\nimport {\n AlfakitProvider,\n useAlfakitProps,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { AlfakitRoot } from '../create-alfakit'\nimport type { AppLayoutProps } from '../layouts/app-layout'\nimport { AuthLayout } from '../layouts/auth-layout'\nimport { lazyPanelLayouts } from '../layouts/lazy-layouts'\nimport { MAX_WIDTHS } from '../layouts/max-widths'\nimport { discoverComponents } from '../lib/discovery'\nimport { preloadIcons } from '../lib/icons'\nimport { cn } from '../lib/utils'\nimport { AlfakitPage } from '../pages/page'\nimport { createDefaultRegistry } from '../registry'\n\n/** The host's layout, as the starter kits' `AppLayout` takes its props. */\nexport type StarterKitLayout = ComponentType<{\n breadcrumbs?: StarterKitBreadcrumb[]\n children: ReactNode\n}>\n\nexport interface StarterKitPageOptions extends Pick<\n AlfakitOptions<AnyComponent>,\n 'components' | 'pages' | 'chartFormatters' | 'chartPlugins' | 'richEditorExtensions'\n> {\n /**\n * The host layout Alfakit pages render in, usually the starter kit's `AppLayout`\n * (`@/layouts/app-layout`). It receives the page's breadcrumbs as `{ title, href }`.\n * `null` renders the pages without a host layout.\n */\n layout?: StarterKitLayout | null\n /**\n * Auto-registered custom components, as for `createAlfakit({ discover })`: pass\n * `import.meta.glob('../../alfakit/{fields,columns,entries,widgets,pages,components,blocks}/**\\/*.tsx')`\n * from `resources/js/pages/Alfakit/Page.tsx` and every file there registers under its name.\n */\n discover?: AlfakitOptions<AnyComponent>['discover']\n}\n\n/**\n * The content area of an Alfakit page inside the host layout: what `layout:app` renders in\n * starter kit mode instead of Alfakit's sidebar and top bar. Replace it with\n * `createStarterKitPage({ components: { 'layout:app': MyContent } })`.\n */\nexport function StarterKitContentLayout({ children, maxContentWidth }: AppLayoutProps) {\n const props = useAlfakitProps()\n // The host's pages fill the content area, so starter kit pages default to the full width.\n const width = maxContentWidth ?? props.alfakit.maxContentWidth ?? 'full'\n\n return (\n <div\n data-slot=\"content\"\n data-alfakit-embedded=\"\"\n className={cn(\n 'mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6',\n MAX_WIDTHS[width] ?? 'max-w-full',\n )}\n >\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </div>\n )\n}\n\n/**\n * A `$layout = 'fullscreen'` page inside the host layout: what `layout:fullscreen` renders in\n * starter kit mode. The host layout is the chrome, so there is no AlfaKit header (brand, top\n * bar slots, user menu, Exit); the screen gets the whole content area. Replace it with\n * `{ components: { 'layout:fullscreen': MyScreen } }`.\n */\nexport function StarterKitFullscreenLayout({ children }: { children: ReactNode }) {\n return (\n <div\n data-slot=\"content\"\n data-layout=\"fullscreen\"\n data-alfakit-embedded=\"\"\n className=\"flex min-h-0 w-full min-w-0 flex-1 flex-col\"\n >\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </div>\n )\n}\n\n// One context for the browser session (it survives navigations between host and Alfakit\n// pages); a server render builds one per request so concurrent renders never share a locale.\nlet browserContext: AlfakitContextValue | null = null\n\nfunction createStarterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n // The host's layout replaces the panel's. Auth pages (invitations, tenant registration)\n // keep the panel's card; the fullscreen layout loads on demand.\n // Auto-registered files sit between the built-ins and the explicit `components` / `pages`.\n const discovered = discoverComponents(options.discover)\n const registry = createDefaultRegistry()\n .registerMany(lazyPanelLayouts)\n .register('layout:auth', AuthLayout)\n .register('layout:app', StarterKitContentLayout)\n .register('layout:fullscreen', StarterKitFullscreenLayout)\n .registerMany(discovered.components)\n .registerMany(options.components ?? {})\n const translator = new Translator()\n\n return {\n registry,\n pages: { 'Alfakit/Page': AlfakitPage, ...discovered.pages, ...options.pages },\n translator,\n actions: new ActionRunner({\n endpoints: { action: '', modal: '' },\n // A failed action request is a toast, never a silent no-op.\n onError: (error) => {\n const notification = resolveRequestErrorNotification(\n error,\n null,\n (key, replacements, fallback) => translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n },\n }),\n }\n}\n\nfunction starterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n if (typeof window === 'undefined') return createStarterKitContext(options)\n\n browserContext ??= createStarterKitContext(options)\n\n return browserContext\n}\n\n/**\n * The Inertia layout around every Alfakit page in starter kit mode, inside the host layout:\n * Alfakit's context, modals, toasts and the reading direction. It stays mounted while the\n * user moves between Alfakit pages.\n */\nexport function AlfakitStarterKitRoot({\n options,\n children,\n}: {\n options?: StarterKitPageOptions\n children?: ReactNode\n}) {\n const [context] = useState(() => starterKitContext(options ?? {}))\n const props = usePage<AlfakitPageProps>().props\n const panel = props.alfakit\n\n // Endpoints and error notifications follow the open page.\n if (panel) {\n context.actions.setEndpoints({\n action: panel.endpoints.action ?? '',\n modal: panel.endpoints.modal ?? '',\n })\n }\n\n useEffect(() => {\n syncStarterKitPage(props)\n }, [props])\n\n useEffect(() => {\n installStarterKitRuntime(\n (notification) => showToast(notification),\n (key, replacements, fallback) => context.translator.t(key, replacements, fallback),\n )\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => {\n // A host page is open: its errors and toasts are the host's again.\n syncStarterKitPage(null)\n setToasterId(undefined)\n }\n }, [context])\n\n return (\n <AlfakitProvider value={context}>\n {/* The reading direction and language of Alfakit's content only: the host layout around\n it (sidebar, header) and the app's own pages keep the document's. */}\n <div\n data-slot=\"starter-kit-root\"\n dir={panel?.localization?.direction ?? 'ltr'}\n lang={panel?.localization?.tag}\n className=\"contents\"\n >\n <AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>\n {children}\n {/* The debug panel's button lives in AlfaKit's top bar, which a starter kit doesn't\n render: float it in the corner instead (nothing without `alfakitDebug`). The corner is the\n document's right: the kit's sidebar stays on the left in a right-to-left panel language. */}\n <div data-slot=\"starter-kit-debug-panel\" className=\"fixed right-16 bottom-16 z-40\">\n <DebugPanelSlot />\n </div>\n </AlfakitRoot>\n </div>\n </AlfakitProvider>\n )\n}\n\n/**\n * The page component for `Alfakit/Page` in a starter kit app: the host's\n * `resources/js/pages/Alfakit/Page.tsx` (written by `alfakit:install --starter-kit`) exports it,\n * so Inertia's page resolution, the root view's page preload and SSR all find it like any\n * other host page.\n *\n * ```tsx\n * import { createStarterKitPage } from '@alfakit/react'\n * import AppLayout from '@/layouts/app-layout'\n *\n * export default createStarterKitPage({ layout: AppLayout })\n * ```\n */\nexport function createStarterKitPage(options: StarterKitPageOptions = {}) {\n // Panel pages draw named icons: fetch `lucide-react/dynamic` with the page.\n preloadIcons()\n // `'js:name'` chart option callbacks, Chart.js plugins and rich editor extensions.\n configureCharts(options)\n registerRichEditorExtensions(options.richEditorExtensions ?? {})\n\n function AlfakitStarterKitPage() {\n return <AlfakitPage />\n }\n\n // A layout callback: the host layout (with the page's breadcrumbs), then Alfakit's root.\n AlfakitStarterKitPage.layout = (props: Record<string, unknown>) => [\n ...(options.layout && usesStarterKitHostLayout(props)\n ? [[options.layout, { breadcrumbs: starterKitBreadcrumbsFromProps(props) }] as const]\n : []),\n [AlfakitStarterKitRoot, { options }] as const,\n ]\n\n return AlfakitStarterKitPage\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfakit/react",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "React + shadcn/ui renderer for Alfakit, the Laravel admin panel builder by Alfa Six.",
5
5
  "license": "MIT",
6
6
  "author": "Alfa Six (https://alfasixbh.com)",
@@ -96,7 +96,7 @@
96
96
  "react-day-picker": "^10.0.2",
97
97
  "sonner": "^2.0.8",
98
98
  "tailwind-merge": "^3.7.0",
99
- "@alfakit/core": "0.1.10"
99
+ "@alfakit/core": "0.1.11"
100
100
  },
101
101
  "peerDependencies": {
102
102
  "@inertiajs/react": "^3.0.0",
@@ -180,7 +180,21 @@ export function ActionButton({
180
180
  const ariaLabel = isIconOnly || labeledFrom ? accessibleName : undefined
181
181
 
182
182
  const button = isPlainLink ? (
183
- action.openUrlInNewTab ? (
183
+ action.download ? (
184
+ // `download()`: a plain link that saves the file (an Inertia visit would discard it).
185
+ <Button
186
+ asChild
187
+ variant="plain"
188
+ size={sizeProp}
189
+ disabled={action.disabled}
190
+ aria-label={ariaLabel}
191
+ {...extraProps(action.extraAttributes, classes)}
192
+ >
193
+ <a href={action.url} download="">
194
+ {content}
195
+ </a>
196
+ </Button>
197
+ ) : action.openUrlInNewTab ? (
184
198
  <Button
185
199
  asChild
186
200
  variant="plain"
@@ -6,6 +6,7 @@ import {
6
6
  jsonScalar,
7
7
  jsonSize,
8
8
  latestServerTiming,
9
+ overlayPlacementDirection,
9
10
  prettyJson,
10
11
  searchJson,
11
12
  type JsonEntry,
@@ -86,7 +87,11 @@ export function DevToolsInspector() {
86
87
  </Button>
87
88
  </SheetTrigger>
88
89
  <SheetContent
89
- side={direction === 'rtl' ? 'left' : 'right'}
90
+ side={
91
+ overlayPlacementDirection(direction, props.alfakit?.starterKit) === 'rtl'
92
+ ? 'left'
93
+ : 'right'
94
+ }
90
95
  className="flex w-full flex-col gap-0 p-0 sm:max-w-xl"
91
96
  >
92
97
  <div data-slot="dev-tools" className="flex min-h-0 flex-1 flex-col">
@@ -13,13 +13,6 @@ import { Input } from '../ui/input'
13
13
  * it, accepts the locale's separators and digits ("1.234,50", "١٢٫٥"), and rounds to
14
14
  * the currency's precision on blur. The form value is a plain decimal string ("1234.50").
15
15
  */
16
- export function clampAmount(amount: number, min?: number | null, max?: number | null): number {
17
- const lower = min ?? Number.NEGATIVE_INFINITY
18
- const upper = max ?? Number.POSITIVE_INFINITY
19
-
20
- return Math.min(upper, Math.max(lower, amount))
21
- }
22
-
23
16
  export function MoneyInputField({ component }: { component: MoneyInputSchema }) {
24
17
  const { value, setValue, blur, error, id, disabled, aria } = useField<string | number | null>(
25
18
  component,
@@ -46,13 +39,9 @@ export function MoneyInputField({ component }: { component: MoneyInputSchema })
46
39
 
47
40
  const parsed = format.parseNumber(text)
48
41
 
49
- // Unparseable input is kept as typed so server validation can explain the problem;
50
- // amounts are brought inside `minValue()` / `maxValue()`.
51
- setValue(
52
- parsed === null
53
- ? text
54
- : clampAmount(parsed, component.min, component.max).toFixed(component.decimals),
55
- )
42
+ // Unparseable input is kept as typed and out-of-range amounts are kept as well, so the
43
+ // server's `minValue()` / `maxValue()` rules report them (W-V3-11).
44
+ setValue(parsed === null ? text : parsed.toFixed(component.decimals))
56
45
  }
57
46
 
58
47
  const affix = (text: string, icon: string | undefined, side: 'start' | 'end') =>
@@ -1,5 +1,8 @@
1
- import { useT } from '../../context'
1
+ import { widgetPollTarget, type LazyWidgetSchema } from '@alfakit/core'
2
+ import { useState } from 'react'
3
+ import { useRefreshComponents, useT } from '../../context'
2
4
  import { cn } from '../../lib/utils'
5
+ import { Button } from '../ui/button'
3
6
  import { Skeleton } from '../ui/skeleton'
4
7
 
5
8
  /**
@@ -37,3 +40,40 @@ export function LazyPlaceholder({
37
40
  </div>
38
41
  )
39
42
  }
43
+
44
+ /**
45
+ * A lazy widget's cell: the skeleton while its schema loads, or, when the request failed
46
+ * (outside panels: a refused or failed fetch), a message with a retry.
47
+ */
48
+ export function LazyWidgetPlaceholder({ widget }: { widget: LazyWidgetSchema }) {
49
+ const t = useT()
50
+ const refresh = useRefreshComponents()
51
+ const [retrying, setRetrying] = useState(false)
52
+ const variant = widget.type === 'TableWidget' ? 'table' : 'widget'
53
+
54
+ if (!widget.failed) return <LazyPlaceholder variant={variant} />
55
+
56
+ const retry = () => {
57
+ if (!refresh) return
58
+
59
+ setRetrying(true)
60
+ void Promise.resolve(refresh([widgetPollTarget(widget)])).finally(() => setRetrying(false))
61
+ }
62
+
63
+ return (
64
+ <div
65
+ data-slot="lazy-placeholder"
66
+ data-variant={variant}
67
+ data-failed=""
68
+ role="alert"
69
+ className="bg-card text-muted-foreground flex min-h-28 flex-col items-center justify-center gap-3 rounded-xl border border-dashed p-6 text-center text-sm"
70
+ >
71
+ <p>{t('widget_failed', {}, 'This widget could not be loaded.')}</p>
72
+ {refresh && (
73
+ <Button type="button" variant="outline" size="sm" disabled={retrying} onClick={retry}>
74
+ {t('retry', {}, 'Retry')}
75
+ </Button>
76
+ )}
77
+ </div>
78
+ )
79
+ }
@@ -220,7 +220,9 @@ function ImageCell({ column, cell }: CellProps) {
220
220
  <img
221
221
  key={index}
222
222
  src={url}
223
- alt={cell.alt ?? ''}
223
+ // Without `alt()` the first image is named after the column, so a record link that
224
+ // wraps it has a name (W-V4-20).
225
+ alt={cell.alt ?? (index === 0 ? (column.label ?? '') : '')}
224
226
  style={{
225
227
  height: schema.height ?? size,
226
228
  width: schema.width ?? (schema.square || schema.circular ? size : undefined),
@@ -1,6 +1,7 @@
1
1
  import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'
2
2
  import type { ComponentProps } from 'react'
3
3
  import { DayPicker } from 'react-day-picker'
4
+ import { useT } from '../../context'
4
5
  import { cn } from '../../lib/utils'
5
6
  import { buttonVariants } from './button'
6
7
 
@@ -10,9 +11,16 @@ function Calendar({
10
11
  showOutsideDays = true,
11
12
  ...props
12
13
  }: ComponentProps<typeof DayPicker>) {
14
+ const t = useT()
15
+
13
16
  return (
14
17
  <DayPicker
15
18
  showOutsideDays={showOutsideDays}
19
+ labels={{
20
+ labelPrevious: () => t('form.previous_month', {}, 'Previous month'),
21
+ labelNext: () => t('form.next_month', {}, 'Next month'),
22
+ ...props.labels,
23
+ }}
16
24
  className={cn('p-3', className)}
17
25
  classNames={{
18
26
  months: 'flex flex-col sm:flex-row gap-2',
@@ -24,7 +24,7 @@ import { useEffect, useId, useMemo, type ReactNode } from 'react'
24
24
  import { useDirection, useT } from '../../context'
25
25
  import { useStore } from '../../hooks/use-store'
26
26
  import { cn } from '../../lib/utils'
27
- import { LazyPlaceholder } from '../panel/lazy-placeholder'
27
+ import { LazyWidgetPlaceholder } from '../panel/lazy-placeholder'
28
28
  import { Button } from '../ui/button'
29
29
 
30
30
  /**
@@ -150,7 +150,7 @@ export function CustomizableWidgetGrid({
150
150
 
151
151
  const content = widget ? (
152
152
  isLazyPlaceholder(widget) ? (
153
- <LazyPlaceholder variant={widget.type === 'TableWidget' ? 'table' : 'widget'} />
153
+ <LazyWidgetPlaceholder widget={widget} />
154
154
  ) : (
155
155
  renderWidget(widget)
156
156
  )
@@ -10,7 +10,7 @@ import { createElement, type ComponentProps } from 'react'
10
10
  import { useRegistry, useT } from '../../context'
11
11
  import { lazyComponent } from '../../lib/lazy'
12
12
  import { cn } from '../../lib/utils'
13
- import { LazyPlaceholder } from '../panel/lazy-placeholder'
13
+ import { LazyWidgetPlaceholder } from '../panel/lazy-placeholder'
14
14
  import { Slot } from '../panel/slot'
15
15
  import { DataTable } from '../table/table-block'
16
16
  import { ChartWidget } from './chart-lazy'
@@ -49,7 +49,7 @@ function StaticWidgetGrid({ block }: { block: WidgetsBlockSchema }) {
49
49
  {block.widgets.map((widget) => (
50
50
  <div key={widget.key} className={cn('min-w-0', columnSpanClasses(widget.columnSpan))}>
51
51
  {isLazyPlaceholder(widget) ? (
52
- <LazyPlaceholder variant={widget.type === 'TableWidget' ? 'table' : 'widget'} />
52
+ <LazyWidgetPlaceholder widget={widget} />
53
53
  ) : (
54
54
  <WidgetRenderer widget={widget} />
55
55
  )}
@@ -13,10 +13,12 @@ import {
13
13
  installErrorNotifications,
14
14
  installPrefetchInvalidation,
15
15
  installSpaUrlExceptions,
16
+ overlayPlacementDirection,
16
17
  registerRichEditorExtensions,
17
18
  reloadOnExpiredPage,
18
19
  resolveRequestErrorNotification,
19
20
  resolveToastPosition,
21
+ setIconAliases,
20
22
  setRequestErrorReporter,
21
23
  whenIdle,
22
24
  type AlfakitOptions,
@@ -72,6 +74,9 @@ export function AlfakitRoot({
72
74
  if (panel) {
73
75
  translator.setTranslations(panel.translations ?? {})
74
76
  translator.setLocale(localization?.tag ?? 'en')
77
+ // Chrome icons replaced with `Icons::register()`, also in a server render, so the server
78
+ // and the browser draw the same glyph (W-V3-28).
79
+ setIconAliases(panel.theme?.icons)
75
80
  }
76
81
 
77
82
  // Starter kit mode: the host app owns `<html lang dir>` (its own pages and layout are not
@@ -104,7 +109,10 @@ export function AlfakitRoot({
104
109
  })}
105
110
  <Toaster
106
111
  id={toasterId}
107
- position={resolveToastPosition(panel?.toastPosition, direction)}
112
+ position={resolveToastPosition(
113
+ panel?.toastPosition,
114
+ overlayPlacementDirection(direction, panel?.starterKit),
115
+ )}
108
116
  offset={PANEL_TOAST_OFFSET}
109
117
  mobileOffset={PANEL_TOAST_OFFSET}
110
118
  dir={direction}
@@ -193,7 +193,7 @@ export function AlfakitComponent({
193
193
  data-slot="page-header"
194
194
  className="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"
195
195
  >
196
- <div className="min-w-0">
196
+ <div className="min-w-0 sm:max-w-[50%] sm:shrink-0">
197
197
  {page.heading && (
198
198
  <h2 className="text-xl font-semibold tracking-tight">{page.heading}</h2>
199
199
  )}
@@ -206,7 +206,7 @@ export function AlfakitComponent({
206
206
  actions={page.headerActions}
207
207
  context={actionContext}
208
208
  align="end"
209
- className="max-w-full shrink-0"
209
+ className="max-w-full min-w-0"
210
210
  />
211
211
  )}
212
212
  </header>
@@ -3,6 +3,7 @@ export { useAlfakitNavigation, type AlfakitNavigation } from './navigation'
3
3
  export {
4
4
  AlfakitStarterKitRoot,
5
5
  StarterKitContentLayout,
6
+ StarterKitFullscreenLayout,
6
7
  createStarterKitPage,
7
8
  type StarterKitLayout,
8
9
  type StarterKitPageOptions,
@@ -85,6 +85,27 @@ export function StarterKitContentLayout({ children, maxContentWidth }: AppLayout
85
85
  )
86
86
  }
87
87
 
88
+ /**
89
+ * A `$layout = 'fullscreen'` page inside the host layout: what `layout:fullscreen` renders in
90
+ * starter kit mode. The host layout is the chrome, so there is no AlfaKit header (brand, top
91
+ * bar slots, user menu, Exit); the screen gets the whole content area. Replace it with
92
+ * `{ components: { 'layout:fullscreen': MyScreen } }`.
93
+ */
94
+ export function StarterKitFullscreenLayout({ children }: { children: ReactNode }) {
95
+ return (
96
+ <div
97
+ data-slot="content"
98
+ data-layout="fullscreen"
99
+ data-alfakit-embedded=""
100
+ className="flex min-h-0 w-full min-w-0 flex-1 flex-col"
101
+ >
102
+ <Slot name="content.start" />
103
+ {children}
104
+ <Slot name="content.end" />
105
+ </div>
106
+ )
107
+ }
108
+
88
109
  // One context for the browser session (it survives navigations between host and Alfakit
89
110
  // pages); a server render builds one per request so concurrent renders never share a locale.
90
111
  let browserContext: AlfakitContextValue | null = null
@@ -98,6 +119,7 @@ function createStarterKitContext(options: StarterKitPageOptions): AlfakitContext
98
119
  .registerMany(lazyPanelLayouts)
99
120
  .register('layout:auth', AuthLayout)
100
121
  .register('layout:app', StarterKitContentLayout)
122
+ .register('layout:fullscreen', StarterKitFullscreenLayout)
101
123
  .registerMany(discovered.components)
102
124
  .registerMany(options.components ?? {})
103
125
  const translator = new Translator()