@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.
- package/CHANGELOG.md +6 -0
- package/dist/components/actions/action-button.js +13 -1
- package/dist/components/actions/action-button.js.map +1 -1
- package/dist/components/dev-tools/dev-tools-inspector.js +2 -2
- package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
- package/dist/components/fields/money-input.d.ts +5 -0
- package/dist/components/fields/money-input.d.ts.map +1 -1
- package/dist/components/fields/money-input.js +2 -5
- package/dist/components/fields/money-input.js.map +1 -1
- package/dist/components/panel/lazy-placeholder.d.ts +1 -0
- package/dist/components/panel/lazy-placeholder.d.ts.map +1 -1
- package/dist/components/panel/lazy-placeholder.js +38 -4
- package/dist/components/panel/lazy-placeholder.js.map +1 -1
- package/dist/components/table/cells.js +1 -1
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/ui/calendar.d.ts.map +1 -1
- package/dist/components/ui/calendar.js +7 -0
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/widgets/dashboard-customizer.js +2 -2
- package/dist/components/widgets/dashboard-customizer.js.map +1 -1
- package/dist/components/widgets/widgets-block.js +2 -2
- package/dist/components/widgets/widgets-block.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +3 -2
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/standalone/alfakit-component.js +2 -2
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone.css +21 -0
- package/dist/starter-kit/index.d.ts +2 -2
- package/dist/starter-kit/index.js +2 -2
- package/dist/starter-kit/starter-kit.d.ts +9 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +21 -2
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/package.json +2 -2
- package/src/components/actions/action-button.tsx +15 -1
- package/src/components/dev-tools/dev-tools-inspector.tsx +6 -1
- package/src/components/fields/money-input.tsx +3 -14
- package/src/components/panel/lazy-placeholder.tsx +41 -1
- package/src/components/table/cells.tsx +3 -1
- package/src/components/ui/calendar.tsx +8 -0
- package/src/components/widgets/dashboard-customizer.tsx +2 -2
- package/src/components/widgets/widgets-block.tsx +2 -2
- package/src/create-alfakit.tsx +9 -1
- package/src/standalone/alfakit-component.tsx +2 -2
- package/src/starter-kit/index.ts +1 -0
- 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.
|
|
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.
|
|
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.
|
|
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={
|
|
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
|
|
50
|
-
//
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
-
<
|
|
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 {
|
|
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
|
-
<
|
|
52
|
+
<LazyWidgetPlaceholder widget={widget} />
|
|
53
53
|
) : (
|
|
54
54
|
<WidgetRenderer widget={widget} />
|
|
55
55
|
)}
|
package/src/create-alfakit.tsx
CHANGED
|
@@ -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(
|
|
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
|
|
209
|
+
className="max-w-full min-w-0"
|
|
210
210
|
/>
|
|
211
211
|
)}
|
|
212
212
|
</header>
|
package/src/starter-kit/index.ts
CHANGED
|
@@ -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()
|