@alfakit/react 0.1.11 → 0.1.13
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 +12 -0
- package/dist/components/entries/other-entries.d.ts.map +1 -1
- package/dist/components/entries/other-entries.js +1 -1
- package/dist/components/entries/other-entries.js.map +1 -1
- package/dist/components/fields/builder.d.ts.map +1 -1
- package/dist/components/fields/builder.js +3 -3
- package/dist/components/fields/builder.js.map +1 -1
- package/dist/components/fields/file-upload.d.ts.map +1 -1
- package/dist/components/fields/file-upload.js +2 -5
- package/dist/components/fields/file-upload.js.map +1 -1
- package/dist/components/fields/repeater.d.ts.map +1 -1
- package/dist/components/fields/repeater.js +3 -3
- package/dist/components/fields/repeater.js.map +1 -1
- package/dist/components/fields/rich-editor-extensions.js +5 -2
- package/dist/components/fields/rich-editor-extensions.js.map +1 -1
- package/dist/components/layout/wizard.d.ts.map +1 -1
- package/dist/components/layout/wizard.js +4 -1
- package/dist/components/layout/wizard.js.map +1 -1
- package/dist/components/notifications/flash-toasts.d.ts.map +1 -1
- package/dist/components/notifications/flash-toasts.js +11 -1
- package/dist/components/notifications/flash-toasts.js.map +1 -1
- package/dist/components/notifications/notifications-menu.js +3 -3
- package/dist/components/notifications/notifications-menu.js.map +1 -1
- package/dist/components/panel/pwa.d.ts.map +1 -1
- package/dist/components/panel/pwa.js +8 -7
- package/dist/components/panel/pwa.js.map +1 -1
- package/dist/components/panel/shortcuts-overlay.js +1 -1
- package/dist/components/panel/shortcuts-overlay.js.map +1 -1
- package/dist/components/table/cells.js +1 -1
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components.css +19 -0
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +3 -1
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/pages/page.js +1 -1
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +2 -1
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/alfakit-toaster.js +2 -1
- package/dist/standalone/alfakit-toaster.js.map +1 -1
- package/dist/standalone/runtime.js +1 -11
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone/toaster-claims.js +16 -0
- package/dist/standalone/toaster-claims.js.map +1 -0
- package/dist/starter-kit/widgets-loaded.js +63 -6
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/package.json +2 -2
- package/src/components/entries/other-entries.tsx +2 -0
- package/src/components/fields/builder.tsx +7 -2
- package/src/components/fields/file-upload.tsx +2 -7
- package/src/components/fields/repeater.tsx +6 -3
- package/src/components/fields/rich-editor-extensions.tsx +9 -5
- package/src/components/layout/wizard.tsx +5 -1
- package/src/components/notifications/flash-toasts.tsx +8 -0
- package/src/components/notifications/notifications-menu.tsx +3 -3
- package/src/components/panel/pwa.tsx +23 -9
- package/src/components/panel/shortcuts-overlay.tsx +1 -1
- package/src/components/table/cells.tsx +3 -1
- package/src/create-alfakit.tsx +6 -0
- package/src/pages/page.tsx +1 -1
- package/src/standalone/runtime.ts +2 -14
- package/src/standalone/toaster-claims.ts +16 -0
- package/src/starter-kit/widgets-loaded.tsx +84 -1
|
@@ -3,7 +3,8 @@ import { PageActionProvider } from "../components/actions/action-context.js";
|
|
|
3
3
|
import { cn } from "../lib/utils.js";
|
|
4
4
|
import { TooltipProvider } from "../components/ui/tooltip.js";
|
|
5
5
|
import { ActionList } from "../components/actions/action-list.js";
|
|
6
|
-
import { claimToaster,
|
|
6
|
+
import { claimToaster, hasToaster } from "./toaster-claims.js";
|
|
7
|
+
import { createStandaloneContext, createStandaloneRegistry, ensureStandaloneRuntime } from "./runtime.js";
|
|
7
8
|
import { ContentBlockRenderer } from "../pages/page.js";
|
|
8
9
|
import { ActionModals } from "../components/actions/action-modals.js";
|
|
9
10
|
import { AlfakitToaster } from "./alfakit-toaster.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alfakit-component.js","names":[],"sources":["../../src/standalone/alfakit-component.tsx"],"sourcesContent":["import {\n StandaloneComponentState,\n findStandalonePropName,\n registerStandaloneProp,\n standalonePanelSchema,\n type ContentBlock,\n type StandaloneComponentSchema,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { Direction } from 'radix-ui'\nimport { useEffect, useLayoutEffect, useMemo, useState, type ReactNode } from 'react'\nimport { PageActionProvider } from '../components/actions/action-context'\nimport { ActionList } from '../components/actions/action-list'\nimport { ActionModals } from '../components/actions/action-modals'\nimport { TooltipProvider } from '../components/ui/tooltip'\nimport {\n AlfakitProvider,\n StandaloneProvider,\n useOptionalAlfakit,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { cn } from '../lib/utils'\nimport { ContentBlockRenderer } from '../pages/page'\nimport { AlfakitToaster } from './alfakit-toaster'\nimport {\n claimToaster,\n createStandaloneContext,\n createStandaloneRegistry,\n ensureStandaloneRuntime,\n hasToaster,\n} from './runtime'\n\nexport interface AlfakitComponentProps {\n /** The payload: `ContactForm::make()` passed as an Inertia prop. */\n component: StandaloneComponentSchema\n /** Renderer overrides for this instance (`{ TextInput: MyInput }`), on top of `configureAlfakit()`. */\n components?: Record<string, AnyComponent>\n className?: string\n /** Rendered above the content blocks (after the heading and actions). */\n header?: ReactNode\n /** Rendered below the content blocks. */\n footer?: ReactNode\n /** Render each content block yourself (e.g. to lay a form and a table side by side). */\n children?: (blocks: ContentBlock[], render: (block: ContentBlock) => ReactNode) => ReactNode\n}\n\n/**\n * Renders an Alfakit component outside a panel: a form, table, infolist, widgets or any mix\n * (`AlfaSix\\Alfakit\\Standalone\\*`) in any Inertia page of the application. Needs no\n * `createAlfakit()` and no panel layout: it provides its own translator, registry, action\n * runner and modals, and inherits the application's shadcn tokens.\n *\n * ```tsx\n * <AlfakitComponent component={orders} className=\"max-w-4xl\" />\n * ```\n */\n// `useLayoutEffect` warns during a server render, where nothing registers anyway.\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect\n\nexport function AlfakitComponent({\n component,\n components,\n className,\n header,\n footer,\n children,\n}: AlfakitComponentProps) {\n const parent = useOptionalAlfakit()\n const props = useOptionalPageProps()\n const [, setVersion] = useState(0)\n // The registry overrides are an object literal in JSX: compare them by their keys.\n const overrideKeys = Object.keys(components ?? {}).join(',')\n\n // Lazy and polled schemas of this component, merged into the page it renders.\n const state = useMemo(\n () => new StandaloneComponentState(component, () => setVersion((value) => value + 1)),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n )\n state.setComponent(component)\n\n const context = useMemo<AlfakitContextValue>(() => {\n const poll = (target: string, interval: string | number | null | undefined) =>\n state.poll(target, interval)\n const refresh = (targets: string[]) => state.refresh(targets)\n\n if (parent) {\n // Inside a panel page: the panel's registry and translator, but this component's own\n // action runner, so its actions post to its own endpoints with its own token (sharing\n // the panel's runner sent them to the panel's endpoint: 403).\n const own = createStandaloneContext(\n { action: component.endpoints.action, modal: component.endpoints.modal },\n components,\n poll,\n )\n\n return {\n ...parent,\n actions: own.actions,\n registry: createStandaloneRegistry(components, parent.registry),\n poll,\n refresh,\n }\n }\n\n return {\n ...createStandaloneContext(\n { action: component.endpoints.action, modal: component.endpoints.modal },\n components,\n poll,\n ),\n refresh,\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parent, state, overrideKeys])\n\n // Endpoints follow the latest payload (they change per locale or path).\n context.actions.setEndpoints({\n action: component.endpoints.action,\n modal: component.endpoints.modal,\n })\n\n // Inside `createAlfakit()` the panel feeds the shared translator; on a page without the\n // `alfakit` prop (the rest of the app) the component's own translations apply.\n const ownsTranslator = !parent || !props?.alfakit\n\n if (ownsTranslator) {\n context.translator.setTranslations(component.translations ?? {})\n context.translator.setLocale(component.localization?.tag ?? 'en')\n }\n\n const panel = useMemo(() => standalonePanelSchema(component), [component])\n const page = state.page\n const direction = component.localization?.direction ?? 'ltr'\n\n useEffect(() => {\n ensureStandaloneRuntime()\n }, [])\n\n // Partial reloads (sorting, paging, inline edits) request this component's own prop.\n const propName = findStandalonePropName(props, component)\n\n // A layout effect runs before the children's effects: a `deferLoading()` table's first\n // reload must already ask for this component's prop, not the panel `page` prop.\n useIsomorphicLayoutEffect(() => registerStandaloneProp(propName ?? null), [propName])\n\n useEffect(() => {\n void state.loadLazy(component)\n // The token identifies a new payload.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [state, component.token])\n\n // Toasts: use the layout's <AlfakitToaster /> when there is one, else the first component\n // on the page renders one (checked after every component's effects ran).\n const [ownsToaster, setOwnsToaster] = useState(false)\n\n useEffect(() => {\n if (parent) return\n\n let release: (() => void) | null = null\n let cancelled = false\n\n queueMicrotask(() => {\n if (cancelled || hasToaster()) return\n\n release = claimToaster()\n setOwnsToaster(true)\n })\n\n return () => {\n cancelled = true\n release?.()\n }\n }, [parent])\n\n const render = (block: ContentBlock) => <ContentBlockRenderer key={block.key} block={block} />\n const actionContext = useMemo(\n () => ({ token: component.token, component: 'page' }),\n [component.token],\n )\n\n const body = (\n <PageActionProvider value={actionContext}>\n <div\n data-slot=\"standalone\"\n data-alfakit-component={component.class}\n dir={direction}\n className={cn('flex flex-col gap-6', className)}\n >\n {(page.heading || page.headerActions?.length) && (\n <header\n data-slot=\"page-header\"\n className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\"\n >\n <div className=\"min-w-0 sm:max-w-[50%] sm:shrink-0\">\n {page.heading && (\n <h2 className=\"text-xl font-semibold tracking-tight\">{page.heading}</h2>\n )}\n {page.subheading && (\n <p className=\"text-muted-foreground mt-1 text-sm\">{page.subheading}</p>\n )}\n </div>\n {page.headerActions && page.headerActions.length > 0 && (\n <ActionList\n actions={page.headerActions}\n context={actionContext}\n align=\"end\"\n className=\"max-w-full min-w-0\"\n />\n )}\n </header>\n )}\n {header}\n {children ? children(page.content, render) : page.content.map(render)}\n {footer}\n </div>\n <ActionModals />\n {ownsToaster && <AlfakitToaster claimed dir={direction} />}\n </PageActionProvider>\n )\n\n return (\n <AlfakitProvider value={context}>\n <StandaloneProvider value={{ panel, page }}>\n {parent ? (\n body\n ) : (\n <Direction.Provider dir={direction}>\n <TooltipProvider delayDuration={0}>{body}</TooltipProvider>\n </Direction.Provider>\n )}\n </StandaloneProvider>\n </AlfakitProvider>\n )\n}\n\n/**\n * The host page's Inertia props, or undefined when rendered outside an Inertia app (a\n * component test, Storybook, a plain server render): partial reloads then reload every prop.\n */\nfunction useOptionalPageProps(): Record<string, unknown> | undefined {\n try {\n return usePage().props as Record<string, unknown>\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"alfakit-component.js","names":[],"sources":["../../src/standalone/alfakit-component.tsx"],"sourcesContent":["import {\n StandaloneComponentState,\n findStandalonePropName,\n registerStandaloneProp,\n standalonePanelSchema,\n type ContentBlock,\n type StandaloneComponentSchema,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { Direction } from 'radix-ui'\nimport { useEffect, useLayoutEffect, useMemo, useState, type ReactNode } from 'react'\nimport { PageActionProvider } from '../components/actions/action-context'\nimport { ActionList } from '../components/actions/action-list'\nimport { ActionModals } from '../components/actions/action-modals'\nimport { TooltipProvider } from '../components/ui/tooltip'\nimport {\n AlfakitProvider,\n StandaloneProvider,\n useOptionalAlfakit,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { cn } from '../lib/utils'\nimport { ContentBlockRenderer } from '../pages/page'\nimport { AlfakitToaster } from './alfakit-toaster'\nimport {\n claimToaster,\n createStandaloneContext,\n createStandaloneRegistry,\n ensureStandaloneRuntime,\n hasToaster,\n} from './runtime'\n\nexport interface AlfakitComponentProps {\n /** The payload: `ContactForm::make()` passed as an Inertia prop. */\n component: StandaloneComponentSchema\n /** Renderer overrides for this instance (`{ TextInput: MyInput }`), on top of `configureAlfakit()`. */\n components?: Record<string, AnyComponent>\n className?: string\n /** Rendered above the content blocks (after the heading and actions). */\n header?: ReactNode\n /** Rendered below the content blocks. */\n footer?: ReactNode\n /** Render each content block yourself (e.g. to lay a form and a table side by side). */\n children?: (blocks: ContentBlock[], render: (block: ContentBlock) => ReactNode) => ReactNode\n}\n\n/**\n * Renders an Alfakit component outside a panel: a form, table, infolist, widgets or any mix\n * (`AlfaSix\\Alfakit\\Standalone\\*`) in any Inertia page of the application. Needs no\n * `createAlfakit()` and no panel layout: it provides its own translator, registry, action\n * runner and modals, and inherits the application's shadcn tokens.\n *\n * ```tsx\n * <AlfakitComponent component={orders} className=\"max-w-4xl\" />\n * ```\n */\n// `useLayoutEffect` warns during a server render, where nothing registers anyway.\nconst useIsomorphicLayoutEffect = typeof window === 'undefined' ? useEffect : useLayoutEffect\n\nexport function AlfakitComponent({\n component,\n components,\n className,\n header,\n footer,\n children,\n}: AlfakitComponentProps) {\n const parent = useOptionalAlfakit()\n const props = useOptionalPageProps()\n const [, setVersion] = useState(0)\n // The registry overrides are an object literal in JSX: compare them by their keys.\n const overrideKeys = Object.keys(components ?? {}).join(',')\n\n // Lazy and polled schemas of this component, merged into the page it renders.\n const state = useMemo(\n () => new StandaloneComponentState(component, () => setVersion((value) => value + 1)),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [],\n )\n state.setComponent(component)\n\n const context = useMemo<AlfakitContextValue>(() => {\n const poll = (target: string, interval: string | number | null | undefined) =>\n state.poll(target, interval)\n const refresh = (targets: string[]) => state.refresh(targets)\n\n if (parent) {\n // Inside a panel page: the panel's registry and translator, but this component's own\n // action runner, so its actions post to its own endpoints with its own token (sharing\n // the panel's runner sent them to the panel's endpoint: 403).\n const own = createStandaloneContext(\n { action: component.endpoints.action, modal: component.endpoints.modal },\n components,\n poll,\n )\n\n return {\n ...parent,\n actions: own.actions,\n registry: createStandaloneRegistry(components, parent.registry),\n poll,\n refresh,\n }\n }\n\n return {\n ...createStandaloneContext(\n { action: component.endpoints.action, modal: component.endpoints.modal },\n components,\n poll,\n ),\n refresh,\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [parent, state, overrideKeys])\n\n // Endpoints follow the latest payload (they change per locale or path).\n context.actions.setEndpoints({\n action: component.endpoints.action,\n modal: component.endpoints.modal,\n })\n\n // Inside `createAlfakit()` the panel feeds the shared translator; on a page without the\n // `alfakit` prop (the rest of the app) the component's own translations apply.\n const ownsTranslator = !parent || !props?.alfakit\n\n if (ownsTranslator) {\n context.translator.setTranslations(component.translations ?? {})\n context.translator.setLocale(component.localization?.tag ?? 'en')\n }\n\n const panel = useMemo(() => standalonePanelSchema(component), [component])\n const page = state.page\n const direction = component.localization?.direction ?? 'ltr'\n\n useEffect(() => {\n ensureStandaloneRuntime()\n }, [])\n\n // Partial reloads (sorting, paging, inline edits) request this component's own prop.\n const propName = findStandalonePropName(props, component)\n\n // A layout effect runs before the children's effects: a `deferLoading()` table's first\n // reload must already ask for this component's prop, not the panel `page` prop.\n useIsomorphicLayoutEffect(() => registerStandaloneProp(propName ?? null), [propName])\n\n useEffect(() => {\n void state.loadLazy(component)\n // The token identifies a new payload.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [state, component.token])\n\n // Toasts: use the layout's <AlfakitToaster /> when there is one, else the first component\n // on the page renders one (checked after every component's effects ran).\n const [ownsToaster, setOwnsToaster] = useState(false)\n\n useEffect(() => {\n if (parent) return\n\n let release: (() => void) | null = null\n let cancelled = false\n\n queueMicrotask(() => {\n if (cancelled || hasToaster()) return\n\n release = claimToaster()\n setOwnsToaster(true)\n })\n\n return () => {\n cancelled = true\n release?.()\n }\n }, [parent])\n\n const render = (block: ContentBlock) => <ContentBlockRenderer key={block.key} block={block} />\n const actionContext = useMemo(\n () => ({ token: component.token, component: 'page' }),\n [component.token],\n )\n\n const body = (\n <PageActionProvider value={actionContext}>\n <div\n data-slot=\"standalone\"\n data-alfakit-component={component.class}\n dir={direction}\n className={cn('flex flex-col gap-6', className)}\n >\n {(page.heading || page.headerActions?.length) && (\n <header\n data-slot=\"page-header\"\n className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\"\n >\n <div className=\"min-w-0 sm:max-w-[50%] sm:shrink-0\">\n {page.heading && (\n <h2 className=\"text-xl font-semibold tracking-tight\">{page.heading}</h2>\n )}\n {page.subheading && (\n <p className=\"text-muted-foreground mt-1 text-sm\">{page.subheading}</p>\n )}\n </div>\n {page.headerActions && page.headerActions.length > 0 && (\n <ActionList\n actions={page.headerActions}\n context={actionContext}\n align=\"end\"\n className=\"max-w-full min-w-0\"\n />\n )}\n </header>\n )}\n {header}\n {children ? children(page.content, render) : page.content.map(render)}\n {footer}\n </div>\n <ActionModals />\n {ownsToaster && <AlfakitToaster claimed dir={direction} />}\n </PageActionProvider>\n )\n\n return (\n <AlfakitProvider value={context}>\n <StandaloneProvider value={{ panel, page }}>\n {parent ? (\n body\n ) : (\n <Direction.Provider dir={direction}>\n <TooltipProvider delayDuration={0}>{body}</TooltipProvider>\n </Direction.Provider>\n )}\n </StandaloneProvider>\n </AlfakitProvider>\n )\n}\n\n/**\n * The host page's Inertia props, or undefined when rendered outside an Inertia app (a\n * component test, Storybook, a plain server render): partial reloads then reload every prop.\n */\nfunction useOptionalPageProps(): Record<string, unknown> | undefined {\n try {\n return usePage().props as Record<string, unknown>\n } catch {\n return undefined\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useOptionalAlfakit } from "../context.js";
|
|
2
|
-
import { claimToaster
|
|
2
|
+
import { claimToaster } from "./toaster-claims.js";
|
|
3
|
+
import { createStandaloneContext, ensureFlashToasts } from "./runtime.js";
|
|
3
4
|
import { setToasterId } from "../components/notifications/flash-toasts.js";
|
|
4
5
|
import { Toaster } from "../components/ui/sonner.js";
|
|
5
6
|
import { STARTER_KIT_TOASTER_ID, resolveToastPosition } from "@alfakit/core";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alfakit-toaster.js","names":[],"sources":["../../src/standalone/alfakit-toaster.tsx"],"sourcesContent":["import { resolveToastPosition, STARTER_KIT_TOASTER_ID, type ToastPosition } from '@alfakit/core'\nimport { useEffect, useMemo } from 'react'\nimport { setToasterId } from '../components/notifications/flash-toasts'\nimport { Toaster } from '../components/ui/sonner'\nimport { useOptionalAlfakit, type AlfakitContextValue } from '../context'\nimport { claimToaster, createStandaloneContext, ensureFlashToasts } from './runtime'\n\nexport interface AlfakitToasterProps {\n /** Where toasts appear; `start`/`end` follow the reading direction. */\n position?: ToastPosition\n dir?: 'ltr' | 'rtl'\n /**\n * Render sonner's `<Toaster>` too (default). Pass `false` when your layout already mounts\n * one: flashed Alfakit notifications are then shown through it.\n */\n toaster?: boolean\n /** @internal Set by `<AlfakitComponent>` when it takes the toaster role. */\n claimed?: boolean\n}\n\n/**\n * Shows notifications flashed by standalone components (`Notification::make()->send()`\n * from `submit()` or an action) as toasts. Place it once in your app layout; a page whose\n * layout has none gets one from its first `<AlfakitComponent>`.\n *\n * ```tsx\n * <AlfakitToaster />\n * ```\n */\nexport function AlfakitToaster({\n position,\n dir = 'ltr',\n toaster = true,\n claimed = false,\n}: AlfakitToasterProps) {\n const parent = useOptionalAlfakit()\n\n // Outside <AlfakitComponent> the flash reader needs a registry for custom toast views.\n const context = useMemo<AlfakitContextValue>(\n () => parent ?? createStandaloneContext({ action: '', modal: '' }, undefined, undefined),\n [parent],\n )\n\n useEffect(() => (claimed ? undefined : claimToaster()), [claimed])\n\n useEffect(() => {\n ensureFlashToasts(context.registry)\n }, [context])\n\n // Our own toaster gets an id, so a `<Toaster />` the app's layout already mounts (the\n // starter kits have one) doesn't show every Alfakit toast a second time.\n useEffect(() => {\n if (!toaster) return undefined\n\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => setToasterId(undefined)\n }, [toaster])\n\n if (!toaster) return null\n\n return (\n <Toaster\n id={STARTER_KIT_TOASTER_ID}\n position={resolveToastPosition(position, dir)}\n dir={dir}\n richColors\n closeButton\n />\n )\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"alfakit-toaster.js","names":[],"sources":["../../src/standalone/alfakit-toaster.tsx"],"sourcesContent":["import { resolveToastPosition, STARTER_KIT_TOASTER_ID, type ToastPosition } from '@alfakit/core'\nimport { useEffect, useMemo } from 'react'\nimport { setToasterId } from '../components/notifications/flash-toasts'\nimport { Toaster } from '../components/ui/sonner'\nimport { useOptionalAlfakit, type AlfakitContextValue } from '../context'\nimport { claimToaster, createStandaloneContext, ensureFlashToasts } from './runtime'\n\nexport interface AlfakitToasterProps {\n /** Where toasts appear; `start`/`end` follow the reading direction. */\n position?: ToastPosition\n dir?: 'ltr' | 'rtl'\n /**\n * Render sonner's `<Toaster>` too (default). Pass `false` when your layout already mounts\n * one: flashed Alfakit notifications are then shown through it.\n */\n toaster?: boolean\n /** @internal Set by `<AlfakitComponent>` when it takes the toaster role. */\n claimed?: boolean\n}\n\n/**\n * Shows notifications flashed by standalone components (`Notification::make()->send()`\n * from `submit()` or an action) as toasts. Place it once in your app layout; a page whose\n * layout has none gets one from its first `<AlfakitComponent>`.\n *\n * ```tsx\n * <AlfakitToaster />\n * ```\n */\nexport function AlfakitToaster({\n position,\n dir = 'ltr',\n toaster = true,\n claimed = false,\n}: AlfakitToasterProps) {\n const parent = useOptionalAlfakit()\n\n // Outside <AlfakitComponent> the flash reader needs a registry for custom toast views.\n const context = useMemo<AlfakitContextValue>(\n () => parent ?? createStandaloneContext({ action: '', modal: '' }, undefined, undefined),\n [parent],\n )\n\n useEffect(() => (claimed ? undefined : claimToaster()), [claimed])\n\n useEffect(() => {\n ensureFlashToasts(context.registry)\n }, [context])\n\n // Our own toaster gets an id, so a `<Toaster />` the app's layout already mounts (the\n // starter kits have one) doesn't show every Alfakit toast a second time.\n useEffect(() => {\n if (!toaster) return undefined\n\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => setToasterId(undefined)\n }, [toaster])\n\n if (!toaster) return null\n\n return (\n <Toaster\n id={STARTER_KIT_TOASTER_ID}\n position={resolveToastPosition(position, dir)}\n dir={dir}\n richColors\n closeButton\n />\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,SAAgB,eAAe,EAC7B,UACA,MAAM,OACN,UAAU,MACV,UAAU,SACY;CACtB,MAAM,SAAS,mBAAmB;CAGlC,MAAM,UAAU,cACR,UAAU,wBAAwB;EAAE,QAAQ;EAAI,OAAO;CAAG,GAAG,KAAA,GAAW,KAAA,CAAS,GACvF,CAAC,MAAM,CACT;CAEA,gBAAiB,UAAU,KAAA,IAAY,aAAa,GAAI,CAAC,OAAO,CAAC;CAEjE,gBAAgB;EACd,kBAAkB,QAAQ,QAAQ;CACpC,GAAG,CAAC,OAAO,CAAC;CAIZ,gBAAgB;EACd,IAAI,CAAC,SAAS,OAAO,KAAA;EAErB,aAAa,sBAAsB;EAEnC,aAAa,aAAa,KAAA,CAAS;CACrC,GAAG,CAAC,OAAO,CAAC;CAEZ,IAAI,CAAC,SAAS,OAAO;CAErB,OACE,oBAAC,SAAD;EACE,IAAI;EACJ,UAAU,qBAAqB,UAAU,GAAG;EACvC;EACL,YAAA;EACA,aAAA;CACD,CAAA;AAEL"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createDefaultRegistry } from "../registry.js";
|
|
2
2
|
import { getStandaloneDefaults } from "./configure.js";
|
|
3
|
+
import { claimToaster, hasToaster } from "./toaster-claims.js";
|
|
3
4
|
import { showToast } from "../components/notifications/flash-toasts.js";
|
|
4
5
|
import { ActionRunner, ComponentRegistry, FlashNotificationTracker, Translator, installPrefetchInvalidation, reloadOnExpiredPage, resolveRequestErrorNotification } from "@alfakit/core";
|
|
5
6
|
import { router } from "@inertiajs/react";
|
|
@@ -48,17 +49,6 @@ function createStandaloneContext(endpoints, overrides, poll) {
|
|
|
48
49
|
poll
|
|
49
50
|
};
|
|
50
51
|
}
|
|
51
|
-
let toasters = 0;
|
|
52
|
-
/** An `<AlfakitToaster>` (or the component that took the role) is mounted; returns a release. */
|
|
53
|
-
function claimToaster() {
|
|
54
|
-
toasters++;
|
|
55
|
-
return () => {
|
|
56
|
-
toasters--;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
function hasToaster() {
|
|
60
|
-
return toasters > 0;
|
|
61
|
-
}
|
|
62
52
|
const flashTracker = new FlashNotificationTracker();
|
|
63
53
|
let flashRegistry = null;
|
|
64
54
|
let flashListening = false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":[],"sources":["../../src/standalone/runtime.ts"],"sourcesContent":["import {\n ActionRunner,\n ComponentRegistry,\n FlashNotificationTracker,\n Translator,\n installPrefetchInvalidation,\n reloadOnExpiredPage,\n resolveRequestErrorNotification,\n type NotificationSchema,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { showToast } from '../components/notifications/flash-toasts'\nimport type { AlfakitContextValue, AnyComponent } from '../context'\nimport { createDefaultRegistry } from '../registry'\nimport { getStandaloneDefaults } from './configure'\n\nconst isServer = typeof window === 'undefined'\n\n// Translations are the same for every component of a page, so the browser shares one\n// translator; a server render gets its own so concurrent requests never share a locale.\nconst browserTranslator = isServer ? null : new Translator()\n\nlet browserReady = false\n\n/** Browser-wide listeners every panel app installs; once per page load. */\nexport function ensureStandaloneRuntime(): void {\n if (isServer || browserReady) return\n\n browserReady = true\n reloadOnExpiredPage()\n // Pages restored by Back after a change are reloaded; shared with a surrounding panel.\n installPrefetchInvalidation()\n}\n\n/**\n * The registry of a standalone component: the built-in renderers, the `configureAlfakit()`\n * defaults (or the surrounding panel's registry), then the component's own overrides.\n */\nexport function createStandaloneRegistry(\n overrides?: Record<string, AnyComponent>,\n base?: ComponentRegistry<AnyComponent>,\n): ComponentRegistry<AnyComponent> {\n if (!base) {\n return createDefaultRegistry()\n .registerMany(getStandaloneDefaults().components ?? {})\n .registerMany(overrides ?? {})\n }\n\n if (!overrides || Object.keys(overrides).length === 0) {\n return base\n }\n\n const registry = new ComponentRegistry<AnyComponent>()\n\n for (const type of base.types()) {\n const component = base.resolve(type)\n\n if (component) registry.register(type, component)\n }\n\n return registry.registerMany(overrides)\n}\n\n/**\n * The context value of one `<AlfakitComponent>` rendered outside `createAlfakit()`: its\n * own registry and action runner (so its modals are its own), the shared translator.\n */\nexport function createStandaloneContext(\n endpoints: { action: string; modal: string },\n overrides: Record<string, AnyComponent> | undefined,\n poll: AlfakitContextValue['poll'],\n): AlfakitContextValue {\n const translator = browserTranslator ?? new Translator()\n\n return {\n registry: createStandaloneRegistry(overrides),\n pages: {},\n translator,\n actions: new ActionRunner({\n endpoints: { ...endpoints },\n // Outside a panel there is no `errorNotifications` config: a generic toast.\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 poll,\n }\n}\n\n// ---------------------------------------------------------------- Toaster ownership\n\
|
|
1
|
+
{"version":3,"file":"runtime.js","names":[],"sources":["../../src/standalone/runtime.ts"],"sourcesContent":["import {\n ActionRunner,\n ComponentRegistry,\n FlashNotificationTracker,\n Translator,\n installPrefetchInvalidation,\n reloadOnExpiredPage,\n resolveRequestErrorNotification,\n type NotificationSchema,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { showToast } from '../components/notifications/flash-toasts'\nimport type { AlfakitContextValue, AnyComponent } from '../context'\nimport { createDefaultRegistry } from '../registry'\nimport { getStandaloneDefaults } from './configure'\n\nconst isServer = typeof window === 'undefined'\n\n// Translations are the same for every component of a page, so the browser shares one\n// translator; a server render gets its own so concurrent requests never share a locale.\nconst browserTranslator = isServer ? null : new Translator()\n\nlet browserReady = false\n\n/** Browser-wide listeners every panel app installs; once per page load. */\nexport function ensureStandaloneRuntime(): void {\n if (isServer || browserReady) return\n\n browserReady = true\n reloadOnExpiredPage()\n // Pages restored by Back after a change are reloaded; shared with a surrounding panel.\n installPrefetchInvalidation()\n}\n\n/**\n * The registry of a standalone component: the built-in renderers, the `configureAlfakit()`\n * defaults (or the surrounding panel's registry), then the component's own overrides.\n */\nexport function createStandaloneRegistry(\n overrides?: Record<string, AnyComponent>,\n base?: ComponentRegistry<AnyComponent>,\n): ComponentRegistry<AnyComponent> {\n if (!base) {\n return createDefaultRegistry()\n .registerMany(getStandaloneDefaults().components ?? {})\n .registerMany(overrides ?? {})\n }\n\n if (!overrides || Object.keys(overrides).length === 0) {\n return base\n }\n\n const registry = new ComponentRegistry<AnyComponent>()\n\n for (const type of base.types()) {\n const component = base.resolve(type)\n\n if (component) registry.register(type, component)\n }\n\n return registry.registerMany(overrides)\n}\n\n/**\n * The context value of one `<AlfakitComponent>` rendered outside `createAlfakit()`: its\n * own registry and action runner (so its modals are its own), the shared translator.\n */\nexport function createStandaloneContext(\n endpoints: { action: string; modal: string },\n overrides: Record<string, AnyComponent> | undefined,\n poll: AlfakitContextValue['poll'],\n): AlfakitContextValue {\n const translator = browserTranslator ?? new Translator()\n\n return {\n registry: createStandaloneRegistry(overrides),\n pages: {},\n translator,\n actions: new ActionRunner({\n endpoints: { ...endpoints },\n // Outside a panel there is no `errorNotifications` config: a generic toast.\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 poll,\n }\n}\n\n// ---------------------------------------------------------------- Toaster ownership\n\n// In a module of its own: the starter kit header components check it without loading this one.\nexport { claimToaster, hasToaster } from './toaster-claims'\n\n// ---------------------------------------------------------------- Flashed notifications\n\nconst flashTracker = new FlashNotificationTracker()\nlet flashRegistry: ComponentRegistry<AnyComponent> | null = null\nlet flashListening = false\n\n/**\n * Show the notifications the server flashed (`Notification::make()->send()`), from the\n * `flash` Inertia event after each visit and from the initial page (whose event fired\n * before the app mounted). One listener per page load, so it survives layouts that remount\n * between visits; the registry of the last mounted toaster renders custom toast views.\n */\nexport function ensureFlashToasts(registry: ComponentRegistry<AnyComponent>): void {\n flashRegistry = registry\n\n if (isServer || flashListening) return\n\n flashListening = true\n\n const show = (flash: unknown) => {\n const flashed = (flash as { alfakitNotifications?: NotificationSchema[] } | undefined)\n ?.alfakitNotifications\n\n for (const notification of flashTracker.fresh(flashed)) {\n showToast(notification, flashRegistry?.resolve(notification.view ?? ''))\n }\n }\n\n show(initialFlash())\n router.on('flash', (event) => show(event.detail.flash))\n}\n\n/** The initial page's flash data (fired as an event before the app mounted). */\nexport function initialFlash(): unknown {\n if (isServer) return undefined\n\n try {\n const page = document.querySelector<HTMLElement>('[data-page]')?.dataset.page\n\n return page ? (JSON.parse(page) as { flash?: unknown }).flash : undefined\n } catch {\n return undefined\n }\n}\n"],"mappings":";;;;;;;AAgBA,MAAM,WAAW,OAAO,WAAW;AAInC,MAAM,oBAAoB,WAAW,OAAO,IAAI,WAAW;AAE3D,IAAI,eAAe;;AAGnB,SAAgB,0BAAgC;CAC9C,IAAI,YAAY,cAAc;CAE9B,eAAe;CACf,oBAAoB;CAEpB,4BAA4B;AAC9B;;;;;AAMA,SAAgB,yBACd,WACA,MACiC;CACjC,IAAI,CAAC,MACH,OAAO,sBAAsB,CAAC,CAC3B,aAAa,sBAAsB,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,CACtD,aAAa,aAAa,CAAC,CAAC;CAGjC,IAAI,CAAC,aAAa,OAAO,KAAK,SAAS,CAAC,CAAC,WAAW,GAClD,OAAO;CAGT,MAAM,WAAW,IAAI,kBAAgC;CAErD,KAAK,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC/B,MAAM,YAAY,KAAK,QAAQ,IAAI;EAEnC,IAAI,WAAW,SAAS,SAAS,MAAM,SAAS;CAClD;CAEA,OAAO,SAAS,aAAa,SAAS;AACxC;;;;;AAMA,SAAgB,wBACd,WACA,WACA,MACqB;CACrB,MAAM,aAAa,qBAAqB,IAAI,WAAW;CAEvD,OAAO;EACL,UAAU,yBAAyB,SAAS;EAC5C,OAAO,CAAC;EACR;EACA,SAAS,IAAI,aAAa;GACxB,WAAW,EAAE,GAAG,UAAU;GAE1B,UAAU,UAAU;IAClB,MAAM,eAAe,gCACnB,OACA,OACC,KAAK,cAAc,aAAa,WAAW,EAAE,KAAK,cAAc,QAAQ,CAC3E;IAEA,IAAI,cAAc,UAAU,YAAY;GAC1C;EACF,CAAC;EACD;CACF;AACF;AASA,MAAM,eAAe,IAAI,yBAAyB;AAClD,IAAI,gBAAwD;AAC5D,IAAI,iBAAiB;;;;;;;AAQrB,SAAgB,kBAAkB,UAAiD;CACjF,gBAAgB;CAEhB,IAAI,YAAY,gBAAgB;CAEhC,iBAAiB;CAEjB,MAAM,QAAQ,UAAmB;EAC/B,MAAM,UAAW,OACb;EAEJ,KAAK,MAAM,gBAAgB,aAAa,MAAM,OAAO,GACnD,UAAU,cAAc,eAAe,QAAQ,aAAa,QAAQ,EAAE,CAAC;CAE3E;CAEA,KAAK,aAAa,CAAC;CACnB,OAAO,GAAG,UAAU,UAAU,KAAK,MAAM,OAAO,KAAK,CAAC;AACxD;;AAGA,SAAgB,eAAwB;CACtC,IAAI,UAAU,OAAO,KAAA;CAErB,IAAI;EACF,MAAM,OAAO,SAAS,cAA2B,aAAa,CAAC,EAAE,QAAQ;EAEzE,OAAO,OAAQ,KAAK,MAAM,IAAI,CAAC,CAAyB,QAAQ,KAAA;CAClE,QAAQ;EACN;CACF;AACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/standalone/toaster-claims.ts
|
|
2
|
+
let toasters = 0;
|
|
3
|
+
/** An `<AlfakitToaster>` (or the component that took the role) is mounted; returns a release. */
|
|
4
|
+
function claimToaster() {
|
|
5
|
+
toasters++;
|
|
6
|
+
return () => {
|
|
7
|
+
toasters--;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function hasToaster() {
|
|
11
|
+
return toasters > 0;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { claimToaster, hasToaster };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=toaster-claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toaster-claims.js","names":[],"sources":["../../src/standalone/toaster-claims.ts"],"sourcesContent":["// Who renders AlfaKit's toaster on the page: `<AlfakitToaster>`, `AlfakitRoot`, the first\n// standalone component or the starter kit header components on the host's own pages.\nlet toasters = 0\n\n/** An `<AlfakitToaster>` (or the component that took the role) is mounted; returns a release. */\nexport function claimToaster(): () => void {\n toasters++\n\n return () => {\n toasters--\n }\n}\n\nexport function hasToaster(): boolean {\n return toasters > 0\n}\n"],"mappings":";AAEA,IAAI,WAAW;;AAGf,SAAgB,eAA2B;CACzC;CAEA,aAAa;EACX;CACF;AACF;AAEA,SAAgB,aAAsB;CACpC,OAAO,WAAW;AACpB"}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import { AlfakitProvider, StandaloneProvider, useLocalization, useT } from "../context.js";
|
|
1
|
+
import { AlfakitProvider, StandaloneProvider, useLocalization, useRegistry, useT } from "../context.js";
|
|
2
2
|
import { Icon } from "../lib/icons.js";
|
|
3
3
|
import { DropdownMenu as DropdownMenu$1, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "../components/ui/dropdown-menu.js";
|
|
4
4
|
import { TooltipProvider } from "../components/ui/tooltip.js";
|
|
5
5
|
import { AiCopilot } from "../components/ai/copilot.js";
|
|
6
|
+
import { claimToaster, hasToaster } from "../standalone/toaster-claims.js";
|
|
7
|
+
import { ensureFlashToasts } from "../standalone/runtime.js";
|
|
8
|
+
import { setToasterId } from "../components/notifications/flash-toasts.js";
|
|
6
9
|
import { TenantName } from "../components/panel/nav-label.js";
|
|
7
10
|
import { Avatar as Avatar$1, AvatarFallback, AvatarImage } from "../components/ui/avatar.js";
|
|
8
11
|
import { NotificationsMenu } from "../components/notifications/notifications-menu.js";
|
|
@@ -10,13 +13,15 @@ import { RealtimeNotifications } from "../components/notifications/realtime-noti
|
|
|
10
13
|
import { GlobalSearch } from "../components/panel/global-search.js";
|
|
11
14
|
import { TenantMenu } from "../components/panel/tenant-menu.js";
|
|
12
15
|
import { LocaleOptions } from "../components/panel/locale-switcher.js";
|
|
16
|
+
import { Toaster } from "../components/ui/sonner.js";
|
|
13
17
|
import { CopilotTrigger, LanguageSwitcherTrigger, NotificationsTrigger, SearchTrigger, TenantSwitcherTrigger, languageTriggerClasses } from "./widgets-triggers.js";
|
|
14
|
-
import { ActionRunner, ComponentRegistry, Translator, initials, isAlfakitPage, loadStarterKitPanel, loadedStarterKitPanel, openRequestedStarterKitSearch, showsLanguageSwitcher, starterKitCopilot, starterKitPanelLocale, starterKitWidgetsPage, starterKitWidgetsPanel, takeRequestedStarterKitCopilot, takeRequestedStarterKitLanguages, tenantNameText } from "@alfakit/core";
|
|
18
|
+
import { ActionRunner, ComponentRegistry, STARTER_KIT_TOASTER_ID, Translator, initials, isAlfakitPage, loadStarterKitPanel, loadedStarterKitPanel, openRequestedStarterKitSearch, overlayPlacementDirection, resolveToastPosition, showsLanguageSwitcher, starterKitCopilot, starterKitPanelLocale, starterKitWidgetsPage, starterKitWidgetsPanel, takeRequestedStarterKitCopilot, takeRequestedStarterKitLanguages, tenantNameText } from "@alfakit/core";
|
|
15
19
|
import { router, usePage } from "@inertiajs/react";
|
|
16
20
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
17
21
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
18
22
|
import { Direction } from "radix-ui";
|
|
19
23
|
import { ChevronsUpDownIcon, LanguagesIcon, PlusIcon } from "lucide-react";
|
|
24
|
+
import { createPortal } from "react-dom";
|
|
20
25
|
//#region src/starter-kit/widgets-loaded.tsx
|
|
21
26
|
/**
|
|
22
27
|
* The loaded header components of starter kit mode: the panel's own search palette,
|
|
@@ -66,23 +71,75 @@ function useWidgetsPanel(source) {
|
|
|
66
71
|
widgets
|
|
67
72
|
]);
|
|
68
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* AlfaKit's toaster on a host page (as `<AlfakitToaster>`, without its standalone context):
|
|
76
|
+
* Alfakit's toasts go to it, and so do flashed notifications.
|
|
77
|
+
*/
|
|
78
|
+
function HostToaster({ position, dir }) {
|
|
79
|
+
const registry = useRegistry();
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
ensureFlashToasts(registry);
|
|
82
|
+
}, [registry]);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
setToasterId(STARTER_KIT_TOASTER_ID);
|
|
85
|
+
return () => setToasterId(void 0);
|
|
86
|
+
}, []);
|
|
87
|
+
return /* @__PURE__ */ jsx(Toaster, {
|
|
88
|
+
id: STARTER_KIT_TOASTER_ID,
|
|
89
|
+
position: resolveToastPosition(position, dir),
|
|
90
|
+
dir,
|
|
91
|
+
richColors: true,
|
|
92
|
+
closeButton: true
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* On the host's own pages no AlfaKit toaster is mounted, so the first loaded header
|
|
97
|
+
* component renders one: the push toggle's and the PWA's messages show there too (W-S1-03).
|
|
98
|
+
* AlfaKit pages (`AlfakitRoot`) and standalone components claim it themselves; the check
|
|
99
|
+
* runs after every component's effects, and again after each visit.
|
|
100
|
+
*/
|
|
101
|
+
function useOwnsHostToaster() {
|
|
102
|
+
const alfakitPage = isAlfakitPage(usePage().props);
|
|
103
|
+
const [owns, setOwns] = useState(false);
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (alfakitPage) return void 0;
|
|
106
|
+
let release = null;
|
|
107
|
+
let cancelled = false;
|
|
108
|
+
queueMicrotask(() => {
|
|
109
|
+
if (cancelled || hasToaster()) return;
|
|
110
|
+
release = claimToaster();
|
|
111
|
+
setOwns(true);
|
|
112
|
+
});
|
|
113
|
+
return () => {
|
|
114
|
+
cancelled = true;
|
|
115
|
+
release?.();
|
|
116
|
+
setOwns(false);
|
|
117
|
+
};
|
|
118
|
+
}, [alfakitPage]);
|
|
119
|
+
return owns && !alfakitPage;
|
|
120
|
+
}
|
|
69
121
|
function WidgetShell({ panel, children }) {
|
|
70
122
|
const context = useWidgetsContext(panel);
|
|
71
123
|
const page = useMemo(() => starterKitWidgetsPage(), []);
|
|
124
|
+
const ownsToaster = useOwnsHostToaster();
|
|
125
|
+
const direction = panel.localization?.direction ?? "ltr";
|
|
72
126
|
return /* @__PURE__ */ jsx(AlfakitProvider, {
|
|
73
127
|
value: context,
|
|
74
|
-
children: /* @__PURE__ */
|
|
128
|
+
children: /* @__PURE__ */ jsxs(StandaloneProvider, {
|
|
75
129
|
value: {
|
|
76
130
|
panel,
|
|
77
131
|
page
|
|
78
132
|
},
|
|
79
|
-
children: /* @__PURE__ */ jsx(Direction.Provider, {
|
|
80
|
-
dir:
|
|
133
|
+
children: [/* @__PURE__ */ jsx(Direction.Provider, {
|
|
134
|
+
dir: direction,
|
|
81
135
|
children: /* @__PURE__ */ jsx(TooltipProvider, {
|
|
82
136
|
delayDuration: 0,
|
|
83
137
|
children
|
|
84
138
|
})
|
|
85
|
-
})
|
|
139
|
+
}), ownsToaster && createPortal(/* @__PURE__ */ jsx(HostToaster, {
|
|
140
|
+
position: panel.toastPosition,
|
|
141
|
+
dir: overlayPlacementDirection(direction, true)
|
|
142
|
+
}), document.body)]
|
|
86
143
|
})
|
|
87
144
|
});
|
|
88
145
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"widgets-loaded.js","names":["DropdownMenu","Avatar"],"sources":["../../src/starter-kit/widgets-loaded.tsx"],"sourcesContent":["import {\n ActionRunner,\n ComponentRegistry,\n Translator,\n initials,\n isAlfakitPage,\n loadStarterKitPanel,\n loadedStarterKitPanel,\n openRequestedStarterKitSearch,\n showsLanguageSwitcher,\n starterKitCopilot,\n starterKitPanelLocale,\n starterKitWidgetsPage,\n starterKitWidgetsPanel,\n takeRequestedStarterKitCopilot,\n takeRequestedStarterKitLanguages,\n tenantNameText,\n type PanelSchema,\n type StarterKitWidgets,\n} from '@alfakit/core'\nimport { router, usePage } from '@inertiajs/react'\nimport { ChevronsUpDownIcon, LanguagesIcon, PlusIcon } from 'lucide-react'\nimport { Direction } from 'radix-ui'\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { AiCopilot } from '../components/ai/copilot'\nimport { NotificationsMenu } from '../components/notifications/notifications-menu'\nimport { RealtimeNotifications } from '../components/notifications/realtime-notifications'\nimport { GlobalSearch } from '../components/panel/global-search'\nimport { LocaleOptions } from '../components/panel/locale-switcher'\nimport { TenantName } from '../components/panel/nav-label'\nimport { TenantMenu } from '../components/panel/tenant-menu'\nimport { Avatar, AvatarFallback, AvatarImage } from '../components/ui/avatar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../components/ui/dropdown-menu'\nimport { TooltipProvider } from '../components/ui/tooltip'\nimport {\n AlfakitProvider,\n StandaloneProvider,\n useLocalization,\n useT,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { Icon } from '../lib/icons'\nimport {\n CopilotTrigger,\n LanguageSwitcherTrigger,\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n languageTriggerClasses,\n type WidgetProps,\n} from './widgets-triggers'\n\n/**\n * The loaded header components of starter kit mode: the panel's own search palette,\n * notifications menu, tenant menu and language menu, rendered outside Alfakit pages with\n * the panel data from `widgets.endpoint`. This module only loads in the browser.\n */\n\n// One context for the browser session: its own translator (the panel's strings), an empty\n// registry (only `panel:icon` overrides are looked up here) and an idle action runner.\nlet widgetsContext: AlfakitContextValue | null = null\n\nfunction useWidgetsContext(panel: PanelSchema): AlfakitContextValue {\n widgetsContext ??= {\n registry: new ComponentRegistry<AnyComponent>(),\n pages: {},\n translator: new Translator(),\n actions: new ActionRunner({ endpoints: { action: '', modal: '' } }),\n }\n\n widgetsContext.translator.setTranslations(panel.translations ?? {})\n widgetsContext.translator.setLocale(panel.localization?.tag ?? 'en')\n\n return widgetsContext\n}\n\n/**\n * The panel the components render with, once its data has loaded (null until then). It is\n * loaded per endpoint (a tenant switch) and language (a language switch), so the components\n * follow a switch instead of keeping the old language's strings.\n */\nfunction useWidgetsPanel(source: StarterKitWidgets): PanelSchema | null {\n const endpoint = source.widgets.endpoint\n const locale = starterKitPanelLocale(source)\n const [loaded, setLoaded] = useState(() => loadedStarterKitPanel(endpoint, locale))\n // Until the new language's data arrives, not the old language's.\n const current =\n loaded && (locale === undefined || loaded.localization?.code === locale) ? loaded : null\n const base = loadedStarterKitPanel(endpoint, locale) ?? current\n\n useEffect(() => {\n let active = true\n\n void loadStarterKitPanel(endpoint, locale).then(\n (panel) => active && setLoaded(panel),\n () => {},\n )\n\n return () => {\n active = false\n }\n }, [endpoint, locale])\n\n const { navigation, widgets } = source\n\n return useMemo(\n () => (base ? starterKitWidgetsPanel(base, { navigation, widgets }) : null),\n [base, navigation, widgets],\n )\n}\n\nfunction WidgetShell({ panel, children }: { panel: PanelSchema; children: ReactNode }) {\n const context = useWidgetsContext(panel)\n const page = useMemo(() => starterKitWidgetsPage(), [])\n\n return (\n <AlfakitProvider value={context}>\n <StandaloneProvider value={{ panel, page }}>\n <Direction.Provider dir={panel.localization?.direction ?? 'ltr'}>\n <TooltipProvider delayDuration={0}>{children}</TooltipProvider>\n </Direction.Provider>\n </StandaloneProvider>\n </AlfakitProvider>\n )\n}\n\n/** The panel's command palette and global search. */\nexport function LoadedSearch({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n // A click or the key bindings before the palette loaded open it now (after its own\n // effects registered the shortcut).\n useEffect(() => {\n if (panel) openRequestedStarterKitSearch()\n }, [panel])\n\n if (!panel) return <SearchTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <GlobalSearch />\n </WidgetShell>\n )\n}\n\n/** The notifications bell, with realtime notifications on the host's own pages. */\nexport function LoadedNotifications({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // Alfakit pages subscribe to realtime notifications themselves.\n const alfakitPage = isAlfakitPage(usePage().props)\n\n if (!panel) return <NotificationsTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <NotificationsMenu />\n {!alfakitPage && <RealtimeNotifications />}\n </WidgetShell>\n )\n}\n\n/** The panel's tenant menu inside a tenant, a menu to choose one elsewhere. */\nexport function LoadedTenantSwitcher({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n if (!panel) return <TenantSwitcherTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n {panel.tenancy?.current ? <TenantMenu variant=\"topbar\" /> : <TenantChooser panel={panel} />}\n </WidgetShell>\n )\n}\n\n/** The AI copilot (the panel pages' `topbar.end` slot component). */\nexport function LoadedCopilot({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // A click or the key bindings before the copilot loaded open it at once.\n const [autoOpen] = useState(() => takeRequestedStarterKitCopilot())\n const component = useMemo(() => (panel ? starterKitCopilot(panel) : null), [panel])\n\n if (!panel) return <CopilotTrigger source={source} />\n\n if (!component) return null\n\n return (\n <WidgetShell panel={panel}>\n <AiCopilot component={component} autoOpen={autoOpen || takeRequestedStarterKitCopilot()} />\n </WidgetShell>\n )\n}\n\n/**\n * The panel's language menu (its languages in their own names, `LocaleOptions`): picking\n * one posts to the panel's locale endpoint and reloads the page in it. Nothing without a\n * language switcher with two or more languages.\n */\nexport function LoadedLanguageSwitcher({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n if (!panel) return <LanguageSwitcherTrigger source={source} />\n\n if (!showsLanguageSwitcher(panel.localization, 'auth')) return null\n\n return (\n <WidgetShell panel={panel}>\n <LanguageMenu />\n </WidgetShell>\n )\n}\n\nfunction LanguageMenu() {\n const t = useT()\n const localization = useLocalization()\n const [open, setOpen] = useState(false)\n // Taken once per mount (effects run twice in StrictMode).\n const requested = useRef<boolean | null>(null)\n\n // A click on the trigger before the menu loaded opens it once mounted: a frame later, as\n // the menu closes again when it opens while focus is still on the replaced button.\n useEffect(() => {\n requested.current ??= takeRequestedStarterKitLanguages()\n\n if (!requested.current) return\n\n const frame = requestAnimationFrame(() => setOpen(true))\n\n return () => cancelAnimationFrame(frame)\n }, [])\n const label = t('change_language', {}, 'Change language')\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"language-switcher-trigger\"\n aria-label={label}\n title={label}\n className={languageTriggerClasses}\n >\n <LanguagesIcon className=\"size-4\" aria-hidden=\"true\" />\n <span lang={localization.tag} dir={localization.direction} className=\"hidden sm:inline\">\n {localization.label}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-40\">\n <LocaleOptions />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/** The user's tenants on a page outside the panel (no current tenant). */\nfunction TenantChooser({ panel }: { panel: PanelSchema }) {\n const t = useT()\n const tenancy = panel.tenancy\n\n if (!tenancy || (tenancy.tenants.length === 0 && !tenancy.registrationUrl)) return null\n\n const label = t('starter_kit.choose_tenant', {}, 'Choose a tenant')\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"tenant-menu\"\n className=\"hover:bg-accent data-[state=open]:bg-accent inline-flex h-9 max-w-60 shrink-0 items-center gap-2 rounded-md px-2 text-sm font-medium\"\n >\n <span className=\"sr-only lg:not-sr-only lg:truncate\">{label}</span>\n <Icon\n name=\"alfakit::panels.tenant-menu.toggle-button\"\n fallback={ChevronsUpDownIcon}\n className=\"text-muted-foreground size-4 shrink-0\"\n />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"min-w-56 rounded-lg\" side=\"bottom\" align=\"end\" sideOffset={4}>\n {tenancy.tenants.length > 0 && (\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">{label}</DropdownMenuLabel>\n )}\n {tenancy.tenants.map((tenant) => (\n <DropdownMenuItem key={String(tenant.id)} onSelect={() => router.visit(tenant.url)}>\n <Avatar className=\"size-6 rounded-sm\">\n {tenant.avatar && <AvatarImage src={tenant.avatar} alt=\"\" />}\n <AvatarFallback className=\"rounded-sm text-[10px]\">\n {initials(tenantNameText(tenant))}\n </AvatarFallback>\n </Avatar>\n <TenantName tenant={tenant} className=\"flex-1 truncate\" />\n </DropdownMenuItem>\n ))}\n {tenancy.registrationUrl && (\n <>\n {tenancy.tenants.length > 0 && <DropdownMenuSeparator />}\n <DropdownMenuItem onSelect={() => router.visit(tenancy.registrationUrl!)}>\n <Icon name=\"alfakit::panels.tenant-menu.registration-button\" fallback={PlusIcon} />\n {tenancy.registrationLabel ?? t('tenancy.register', {}, 'New')}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"widgets-loaded.js","names":["DropdownMenu","Avatar"],"sources":["../../src/starter-kit/widgets-loaded.tsx"],"sourcesContent":["import {\n ActionRunner,\n ComponentRegistry,\n STARTER_KIT_TOASTER_ID,\n Translator,\n initials,\n isAlfakitPage,\n loadStarterKitPanel,\n loadedStarterKitPanel,\n openRequestedStarterKitSearch,\n overlayPlacementDirection,\n resolveToastPosition,\n showsLanguageSwitcher,\n starterKitCopilot,\n starterKitPanelLocale,\n starterKitWidgetsPage,\n starterKitWidgetsPanel,\n takeRequestedStarterKitCopilot,\n takeRequestedStarterKitLanguages,\n tenantNameText,\n type PanelSchema,\n type StarterKitWidgets,\n type ToastPosition,\n} from '@alfakit/core'\nimport { router, usePage } from '@inertiajs/react'\nimport { ChevronsUpDownIcon, LanguagesIcon, PlusIcon } from 'lucide-react'\nimport { Direction } from 'radix-ui'\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { createPortal } from 'react-dom'\nimport { AiCopilot } from '../components/ai/copilot'\nimport { setToasterId } from '../components/notifications/flash-toasts'\nimport { NotificationsMenu } from '../components/notifications/notifications-menu'\nimport { RealtimeNotifications } from '../components/notifications/realtime-notifications'\nimport { GlobalSearch } from '../components/panel/global-search'\nimport { LocaleOptions } from '../components/panel/locale-switcher'\nimport { TenantName } from '../components/panel/nav-label'\nimport { TenantMenu } from '../components/panel/tenant-menu'\nimport { Avatar, AvatarFallback, AvatarImage } from '../components/ui/avatar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../components/ui/dropdown-menu'\nimport { Toaster } from '../components/ui/sonner'\nimport { TooltipProvider } from '../components/ui/tooltip'\nimport {\n AlfakitProvider,\n StandaloneProvider,\n useLocalization,\n useRegistry,\n useT,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { Icon } from '../lib/icons'\nimport { ensureFlashToasts } from '../standalone/runtime'\nimport { claimToaster, hasToaster } from '../standalone/toaster-claims'\nimport {\n CopilotTrigger,\n LanguageSwitcherTrigger,\n NotificationsTrigger,\n SearchTrigger,\n TenantSwitcherTrigger,\n languageTriggerClasses,\n type WidgetProps,\n} from './widgets-triggers'\n\n/**\n * The loaded header components of starter kit mode: the panel's own search palette,\n * notifications menu, tenant menu and language menu, rendered outside Alfakit pages with\n * the panel data from `widgets.endpoint`. This module only loads in the browser.\n */\n\n// One context for the browser session: its own translator (the panel's strings), an empty\n// registry (only `panel:icon` overrides are looked up here) and an idle action runner.\nlet widgetsContext: AlfakitContextValue | null = null\n\nfunction useWidgetsContext(panel: PanelSchema): AlfakitContextValue {\n widgetsContext ??= {\n registry: new ComponentRegistry<AnyComponent>(),\n pages: {},\n translator: new Translator(),\n actions: new ActionRunner({ endpoints: { action: '', modal: '' } }),\n }\n\n widgetsContext.translator.setTranslations(panel.translations ?? {})\n widgetsContext.translator.setLocale(panel.localization?.tag ?? 'en')\n\n return widgetsContext\n}\n\n/**\n * The panel the components render with, once its data has loaded (null until then). It is\n * loaded per endpoint (a tenant switch) and language (a language switch), so the components\n * follow a switch instead of keeping the old language's strings.\n */\nfunction useWidgetsPanel(source: StarterKitWidgets): PanelSchema | null {\n const endpoint = source.widgets.endpoint\n const locale = starterKitPanelLocale(source)\n const [loaded, setLoaded] = useState(() => loadedStarterKitPanel(endpoint, locale))\n // Until the new language's data arrives, not the old language's.\n const current =\n loaded && (locale === undefined || loaded.localization?.code === locale) ? loaded : null\n const base = loadedStarterKitPanel(endpoint, locale) ?? current\n\n useEffect(() => {\n let active = true\n\n void loadStarterKitPanel(endpoint, locale).then(\n (panel) => active && setLoaded(panel),\n () => {},\n )\n\n return () => {\n active = false\n }\n }, [endpoint, locale])\n\n const { navigation, widgets } = source\n\n return useMemo(\n () => (base ? starterKitWidgetsPanel(base, { navigation, widgets }) : null),\n [base, navigation, widgets],\n )\n}\n\n/**\n * AlfaKit's toaster on a host page (as `<AlfakitToaster>`, without its standalone context):\n * Alfakit's toasts go to it, and so do flashed notifications.\n */\nfunction HostToaster({ position, dir }: { position?: ToastPosition; dir: 'ltr' | 'rtl' }) {\n const registry = useRegistry()\n\n useEffect(() => {\n ensureFlashToasts(registry)\n }, [registry])\n\n useEffect(() => {\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => setToasterId(undefined)\n }, [])\n\n return (\n <Toaster\n id={STARTER_KIT_TOASTER_ID}\n position={resolveToastPosition(position, dir)}\n dir={dir}\n richColors\n closeButton\n />\n )\n}\n\n/**\n * On the host's own pages no AlfaKit toaster is mounted, so the first loaded header\n * component renders one: the push toggle's and the PWA's messages show there too (W-S1-03).\n * AlfaKit pages (`AlfakitRoot`) and standalone components claim it themselves; the check\n * runs after every component's effects, and again after each visit.\n */\nfunction useOwnsHostToaster(): boolean {\n const alfakitPage = isAlfakitPage(usePage().props)\n const [owns, setOwns] = useState(false)\n\n useEffect(() => {\n if (alfakitPage) return undefined\n\n let release: (() => void) | null = null\n let cancelled = false\n\n queueMicrotask(() => {\n if (cancelled || hasToaster()) return\n\n release = claimToaster()\n setOwns(true)\n })\n\n return () => {\n cancelled = true\n release?.()\n setOwns(false)\n }\n }, [alfakitPage])\n\n return owns && !alfakitPage\n}\n\nfunction WidgetShell({ panel, children }: { panel: PanelSchema; children: ReactNode }) {\n const context = useWidgetsContext(panel)\n const page = useMemo(() => starterKitWidgetsPage(), [])\n const ownsToaster = useOwnsHostToaster()\n const direction = panel.localization?.direction ?? 'ltr'\n\n return (\n <AlfakitProvider value={context}>\n <StandaloneProvider value={{ panel, page }}>\n <Direction.Provider dir={direction}>\n <TooltipProvider delayDuration={0}>{children}</TooltipProvider>\n </Direction.Provider>\n {/* In the body, not in the header (a header with a backdrop filter would hold its\n fixed position), placed in the document's direction as on AlfaKit pages. */}\n {ownsToaster &&\n createPortal(\n <HostToaster\n position={panel.toastPosition}\n dir={overlayPlacementDirection(direction, true)}\n />,\n document.body,\n )}\n </StandaloneProvider>\n </AlfakitProvider>\n )\n}\n\n/** The panel's command palette and global search. */\nexport function LoadedSearch({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n // A click or the key bindings before the palette loaded open it now (after its own\n // effects registered the shortcut).\n useEffect(() => {\n if (panel) openRequestedStarterKitSearch()\n }, [panel])\n\n if (!panel) return <SearchTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <GlobalSearch />\n </WidgetShell>\n )\n}\n\n/** The notifications bell, with realtime notifications on the host's own pages. */\nexport function LoadedNotifications({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // Alfakit pages subscribe to realtime notifications themselves.\n const alfakitPage = isAlfakitPage(usePage().props)\n\n if (!panel) return <NotificationsTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n <NotificationsMenu />\n {!alfakitPage && <RealtimeNotifications />}\n </WidgetShell>\n )\n}\n\n/** The panel's tenant menu inside a tenant, a menu to choose one elsewhere. */\nexport function LoadedTenantSwitcher({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n if (!panel) return <TenantSwitcherTrigger source={source} />\n\n return (\n <WidgetShell panel={panel}>\n {panel.tenancy?.current ? <TenantMenu variant=\"topbar\" /> : <TenantChooser panel={panel} />}\n </WidgetShell>\n )\n}\n\n/** The AI copilot (the panel pages' `topbar.end` slot component). */\nexport function LoadedCopilot({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n // A click or the key bindings before the copilot loaded open it at once.\n const [autoOpen] = useState(() => takeRequestedStarterKitCopilot())\n const component = useMemo(() => (panel ? starterKitCopilot(panel) : null), [panel])\n\n if (!panel) return <CopilotTrigger source={source} />\n\n if (!component) return null\n\n return (\n <WidgetShell panel={panel}>\n <AiCopilot component={component} autoOpen={autoOpen || takeRequestedStarterKitCopilot()} />\n </WidgetShell>\n )\n}\n\n/**\n * The panel's language menu (its languages in their own names, `LocaleOptions`): picking\n * one posts to the panel's locale endpoint and reloads the page in it. Nothing without a\n * language switcher with two or more languages.\n */\nexport function LoadedLanguageSwitcher({ source }: WidgetProps) {\n const panel = useWidgetsPanel(source)\n\n if (!panel) return <LanguageSwitcherTrigger source={source} />\n\n if (!showsLanguageSwitcher(panel.localization, 'auth')) return null\n\n return (\n <WidgetShell panel={panel}>\n <LanguageMenu />\n </WidgetShell>\n )\n}\n\nfunction LanguageMenu() {\n const t = useT()\n const localization = useLocalization()\n const [open, setOpen] = useState(false)\n // Taken once per mount (effects run twice in StrictMode).\n const requested = useRef<boolean | null>(null)\n\n // A click on the trigger before the menu loaded opens it once mounted: a frame later, as\n // the menu closes again when it opens while focus is still on the replaced button.\n useEffect(() => {\n requested.current ??= takeRequestedStarterKitLanguages()\n\n if (!requested.current) return\n\n const frame = requestAnimationFrame(() => setOpen(true))\n\n return () => cancelAnimationFrame(frame)\n }, [])\n const label = t('change_language', {}, 'Change language')\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"language-switcher-trigger\"\n aria-label={label}\n title={label}\n className={languageTriggerClasses}\n >\n <LanguagesIcon className=\"size-4\" aria-hidden=\"true\" />\n <span lang={localization.tag} dir={localization.direction} className=\"hidden sm:inline\">\n {localization.label}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-40\">\n <LocaleOptions />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/** The user's tenants on a page outside the panel (no current tenant). */\nfunction TenantChooser({ panel }: { panel: PanelSchema }) {\n const t = useT()\n const tenancy = panel.tenancy\n\n if (!tenancy || (tenancy.tenants.length === 0 && !tenancy.registrationUrl)) return null\n\n const label = t('starter_kit.choose_tenant', {}, 'Choose a tenant')\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"tenant-menu\"\n className=\"hover:bg-accent data-[state=open]:bg-accent inline-flex h-9 max-w-60 shrink-0 items-center gap-2 rounded-md px-2 text-sm font-medium\"\n >\n <span className=\"sr-only lg:not-sr-only lg:truncate\">{label}</span>\n <Icon\n name=\"alfakit::panels.tenant-menu.toggle-button\"\n fallback={ChevronsUpDownIcon}\n className=\"text-muted-foreground size-4 shrink-0\"\n />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent className=\"min-w-56 rounded-lg\" side=\"bottom\" align=\"end\" sideOffset={4}>\n {tenancy.tenants.length > 0 && (\n <DropdownMenuLabel className=\"text-muted-foreground text-xs\">{label}</DropdownMenuLabel>\n )}\n {tenancy.tenants.map((tenant) => (\n <DropdownMenuItem key={String(tenant.id)} onSelect={() => router.visit(tenant.url)}>\n <Avatar className=\"size-6 rounded-sm\">\n {tenant.avatar && <AvatarImage src={tenant.avatar} alt=\"\" />}\n <AvatarFallback className=\"rounded-sm text-[10px]\">\n {initials(tenantNameText(tenant))}\n </AvatarFallback>\n </Avatar>\n <TenantName tenant={tenant} className=\"flex-1 truncate\" />\n </DropdownMenuItem>\n ))}\n {tenancy.registrationUrl && (\n <>\n {tenancy.tenants.length > 0 && <DropdownMenuSeparator />}\n <DropdownMenuItem onSelect={() => router.visit(tenancy.registrationUrl!)}>\n <Icon name=\"alfakit::panels.tenant-menu.registration-button\" fallback={PlusIcon} />\n {tenancy.registrationLabel ?? t('tenancy.register', {}, 'New')}\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )\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.13",
|
|
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.13"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"@inertiajs/react": "^3.0.0",
|
|
@@ -115,6 +115,8 @@ export function ImageEntryComponent({ component }: { component: ImageEntrySchema
|
|
|
115
115
|
data-slot="image-remaining"
|
|
116
116
|
className={cn(
|
|
117
117
|
'text-muted-foreground',
|
|
118
|
+
// The last image keeps the stack's negative end margin (W-S3-03).
|
|
119
|
+
component.stacked && usesDefaultImageStack(component) && 'ms-3',
|
|
118
120
|
remainingTextSizeClass(component.limitedRemainingTextSize),
|
|
119
121
|
)}
|
|
120
122
|
>
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
insertBuilderItem,
|
|
18
18
|
isDeferredPlaceholder,
|
|
19
19
|
itemActionContext,
|
|
20
|
+
itemExtraActions,
|
|
20
21
|
moveIndexedState,
|
|
21
22
|
moveRepeaterItem,
|
|
22
23
|
removeIndexedState,
|
|
@@ -316,7 +317,9 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
316
317
|
index={index}
|
|
317
318
|
count={items.length}
|
|
318
319
|
resolve={(name) => resolve(name, serverItem)}
|
|
319
|
-
extraActions={
|
|
320
|
+
extraActions={
|
|
321
|
+
menu || locked ? undefined : itemExtraActions(component, serverItem)
|
|
322
|
+
}
|
|
320
323
|
buttons={buttons && !menu}
|
|
321
324
|
canClone={canClone && !menu}
|
|
322
325
|
canDelete={canDelete && !menu}
|
|
@@ -353,7 +356,9 @@ function BuilderFieldContent({ component }: { component: BuilderSchema }) {
|
|
|
353
356
|
canMoveDown={buttons && index < items.length - 1}
|
|
354
357
|
canClone={canClone}
|
|
355
358
|
canDelete={canDelete}
|
|
356
|
-
extraActions={
|
|
359
|
+
extraActions={
|
|
360
|
+
locked ? undefined : itemExtraActions(component, serverItem)
|
|
361
|
+
}
|
|
357
362
|
onAction={(name, onClient) =>
|
|
358
363
|
runItemAction(
|
|
359
364
|
resolve(name, serverItem),
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
fileManagerSize,
|
|
10
10
|
filesFromClipboard,
|
|
11
11
|
fileUploadAccept,
|
|
12
|
+
fileUploadEntries,
|
|
12
13
|
fileUploadPaths,
|
|
13
14
|
insertUploadedFile,
|
|
14
15
|
moveRepeaterItem,
|
|
@@ -103,13 +104,7 @@ export function FileUploadField({ component }: { component: FileUploadSchema })
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
const paths = fileUploadPaths(value)
|
|
106
|
-
const files: LocalFile[] = paths.
|
|
107
|
-
(path) =>
|
|
108
|
-
[...component.files, ...uploaded].find((file) => file.path === path) ?? {
|
|
109
|
-
path,
|
|
110
|
-
name: path.replace(/^tmp:/, ''),
|
|
111
|
-
},
|
|
112
|
-
)
|
|
107
|
+
const files: LocalFile[] = fileUploadEntries(paths, component.files, uploaded)
|
|
113
108
|
const maxFiles = component.multiple ? (component.maxFiles ?? Infinity) : 1
|
|
114
109
|
const canAddMore = files.length + uploading.length < maxFiles
|
|
115
110
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { RepeaterSchema, SchemaComponent } from '@alfakit/core'
|
|
2
2
|
import {
|
|
3
|
-
VIRTUAL_ESTIMATES,
|
|
4
3
|
appendRepeaterItem,
|
|
5
4
|
cloneIndexedState,
|
|
6
5
|
cloneRepeaterItem,
|
|
@@ -8,12 +7,14 @@ import {
|
|
|
8
7
|
fieldIds,
|
|
9
8
|
gridColumnsClasses,
|
|
10
9
|
isDeferredPlaceholder,
|
|
10
|
+
itemExtraActions,
|
|
11
11
|
moveIndexedState,
|
|
12
12
|
moveRepeaterItem,
|
|
13
13
|
removeIndexedState,
|
|
14
14
|
removeRepeaterItem,
|
|
15
15
|
reorderedItemKeys,
|
|
16
16
|
templateForIndex,
|
|
17
|
+
VIRTUAL_ESTIMATES,
|
|
17
18
|
virtualizeOptions,
|
|
18
19
|
} from '@alfakit/core'
|
|
19
20
|
import { useState } from 'react'
|
|
@@ -165,7 +166,7 @@ function RepeaterFieldContent({ component }: { component: RepeaterSchema }) {
|
|
|
165
166
|
virtual={virtual}
|
|
166
167
|
context={context}
|
|
167
168
|
resolve={(name, index) => resolve(name, component.items[index])}
|
|
168
|
-
extraActionsFor={(index) => component.items[index]
|
|
169
|
+
extraActionsFor={(index) => itemExtraActions(component, component.items[index])}
|
|
169
170
|
/>
|
|
170
171
|
) : (
|
|
171
172
|
<>
|
|
@@ -245,7 +246,9 @@ function RepeaterFieldContent({ component }: { component: RepeaterSchema }) {
|
|
|
245
246
|
index={index}
|
|
246
247
|
count={items.length}
|
|
247
248
|
resolve={(name) => resolve(name, serverItem)}
|
|
248
|
-
extraActions={
|
|
249
|
+
extraActions={
|
|
250
|
+
locked ? undefined : itemExtraActions(component, serverItem)
|
|
251
|
+
}
|
|
249
252
|
buttons={buttons}
|
|
250
253
|
canClone={Boolean(component.cloneable) && canAdd}
|
|
251
254
|
canDelete={canDelete}
|
|
@@ -152,11 +152,15 @@ export function useRichEditorExtensions(
|
|
|
152
152
|
|
|
153
153
|
if (!result || !editor || editor.isDestroyed) return
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
155
|
+
const block = { type: 'customBlock', attrs: { id: result.id, config: result.config } }
|
|
156
|
+
// A block inserted just before is still selected (a node selection): insert after it
|
|
157
|
+
// instead of replacing it (W-S2-05).
|
|
158
|
+
const { selection } = editor.state
|
|
159
|
+
const chain = editor.chain().focus()
|
|
160
|
+
;('node' in selection && selection.node
|
|
161
|
+
? chain.insertContentAt(selection.to, block)
|
|
162
|
+
: chain.insertContent(block)
|
|
163
|
+
).run()
|
|
160
164
|
}
|
|
161
165
|
|
|
162
166
|
return { extensions, suggestions, insertBlock, context }
|
|
@@ -21,6 +21,7 @@ import { cn } from '../../lib/utils'
|
|
|
21
21
|
import { ActionButton } from '../actions/action-button'
|
|
22
22
|
import { usePageActionContext } from '../actions/action-context'
|
|
23
23
|
import { useOptionalFormStore } from '../form/form-context'
|
|
24
|
+
import { useFieldActionContext } from '../schema/field-actions'
|
|
24
25
|
import { SchemaRenderer } from '../schema/schema-renderer'
|
|
25
26
|
import { Button } from '../ui/button'
|
|
26
27
|
import { DeferredSchema } from './deferred'
|
|
@@ -52,6 +53,9 @@ export function WizardComponent({ component }: { component: WizardSchema }) {
|
|
|
52
53
|
if (stepQueryKey) persistWizardStep(current, stepQueryKey)
|
|
53
54
|
}, [current, stepQueryKey])
|
|
54
55
|
const store = useOptionalFormStore()
|
|
56
|
+
// Inside a form the submit action runs as a field action, so `$get` / `$set` work (W-S2-09).
|
|
57
|
+
const fieldContext = useFieldActionContext(component)
|
|
58
|
+
const submitContext = store ? fieldContext : actionContext
|
|
55
59
|
|
|
56
60
|
// An action modal around this wizard shows its submit button on the last step only.
|
|
57
61
|
useEffect(() => {
|
|
@@ -229,7 +233,7 @@ export function WizardComponent({ component }: { component: WizardSchema }) {
|
|
|
229
233
|
</Button>
|
|
230
234
|
)
|
|
231
235
|
) : component.submitAction ? (
|
|
232
|
-
<ActionButton action={component.submitAction} context={
|
|
236
|
+
<ActionButton action={component.submitAction} context={submitContext} />
|
|
233
237
|
) : component.submitActionHtml ? (
|
|
234
238
|
<div
|
|
235
239
|
data-slot="wizard-submit"
|
|
@@ -43,6 +43,14 @@ export function setToasterId(id: string | undefined): void {
|
|
|
43
43
|
toasterId = id
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Options for a direct `toast.*()` call: the starter kit toaster's id merged in, so PWA and
|
|
48
|
+
* notification messages reach AlfaKit's toaster in a starter kit too (W-S1-03).
|
|
49
|
+
*/
|
|
50
|
+
export function toastOptions<T extends object>(options?: T): T & { toasterId?: string } {
|
|
51
|
+
return { ...(options ?? ({} as T)), toasterId }
|
|
52
|
+
}
|
|
53
|
+
|
|
46
54
|
/** `closeNotification(id)` (or a `close-notification` event) dismisses that toast. */
|
|
47
55
|
export function listenForClosedNotifications(): void {
|
|
48
56
|
if (closeListening || typeof window === 'undefined') return
|
|
@@ -19,7 +19,7 @@ import { Button } from '../ui/button'
|
|
|
19
19
|
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
|
|
20
20
|
import { ScrollArea } from '../ui/scroll-area'
|
|
21
21
|
import { Spinner } from '../ui/spinner'
|
|
22
|
-
import { NotificationActionButton } from './flash-toasts'
|
|
22
|
+
import { NotificationActionButton, toastOptions } from './flash-toasts'
|
|
23
23
|
|
|
24
24
|
export interface NotificationsMenuProps {
|
|
25
25
|
/**
|
|
@@ -104,8 +104,8 @@ export function NotificationsMenu({ variant = 'topbar' }: NotificationsMenuProps
|
|
|
104
104
|
const { message } = await togglePushNotifications()
|
|
105
105
|
const text = t(message.key, {}, message.fallback)
|
|
106
106
|
|
|
107
|
-
if (message.error) toast.error(text)
|
|
108
|
-
else toast.success(text)
|
|
107
|
+
if (message.error) toast.error(text, toastOptions())
|
|
108
|
+
else toast.success(text, toastOptions())
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
const pushLabel =
|