@alfakit/react 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/actions/action-modals.js +1 -1
  3. package/dist/components/actions/action-modals.js.map +1 -1
  4. package/dist/components/dev-tools/dev-tools-inspector.js +1 -1
  5. package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
  6. package/dist/components/fields/checkbox-list.js +2 -2
  7. package/dist/components/fields/checkbox-list.js.map +1 -1
  8. package/dist/components/fields/rich-editor-extensions.js +14 -4
  9. package/dist/components/fields/rich-editor-extensions.js.map +1 -1
  10. package/dist/components/file-manager/file-manager.js +2 -0
  11. package/dist/components/file-manager/file-manager.js.map +1 -1
  12. package/dist/components/form/form-block.d.ts.map +1 -1
  13. package/dist/components/form/form-block.js +6 -3
  14. package/dist/components/form/form-block.js.map +1 -1
  15. package/dist/components/layout/elements.d.ts.map +1 -1
  16. package/dist/components/layout/elements.js +16 -5
  17. package/dist/components/layout/elements.js.map +1 -1
  18. package/dist/components/layout/mixed-content.d.ts +6 -4
  19. package/dist/components/layout/mixed-content.d.ts.map +1 -1
  20. package/dist/components/layout/mixed-content.js +6 -4
  21. package/dist/components/layout/mixed-content.js.map +1 -1
  22. package/dist/components/layout/section.js +4 -2
  23. package/dist/components/layout/section.js.map +1 -1
  24. package/dist/components/notifications/notifications-menu.d.ts.map +1 -1
  25. package/dist/components/notifications/notifications-menu.js +45 -17
  26. package/dist/components/notifications/notifications-menu.js.map +1 -1
  27. package/dist/components/panel/app-sidebar.js +3 -3
  28. package/dist/components/panel/app-sidebar.js.map +1 -1
  29. package/dist/components/panel/bottom-nav.js +2 -2
  30. package/dist/components/panel/bottom-nav.js.map +1 -1
  31. package/dist/components/panel/keyboard-shortcuts.js +1 -1
  32. package/dist/components/panel/keyboard-shortcuts.js.map +1 -1
  33. package/dist/components/panel/shortcuts-overlay.js +13 -10
  34. package/dist/components/panel/shortcuts-overlay.js.map +1 -1
  35. package/dist/components/table/column-manager.js +4 -4
  36. package/dist/components/table/column-manager.js.map +1 -1
  37. package/dist/components/table/spreadsheet.js +8 -1
  38. package/dist/components/table/spreadsheet.js.map +1 -1
  39. package/dist/components/translation-manager/translation-manager.js +1 -1
  40. package/dist/components/translation-manager/translation-manager.js.map +1 -1
  41. package/dist/components/ui/tooltip.d.ts.map +1 -1
  42. package/dist/components/ui/tooltip.js +1 -1
  43. package/dist/components/ui/tooltip.js.map +1 -1
  44. package/dist/components/widgets/stats-overview.js +3 -3
  45. package/dist/components/widgets/stats-overview.js.map +1 -1
  46. package/dist/eject-manifest.json +2 -2
  47. package/dist/layouts/top-nav-layout.js +3 -3
  48. package/dist/layouts/top-nav-layout.js.map +1 -1
  49. package/dist/pages/page.d.ts.map +1 -1
  50. package/dist/pages/page.js +4 -7
  51. package/dist/pages/page.js.map +1 -1
  52. package/dist/standalone/alfakit-component.d.ts +0 -10
  53. package/dist/standalone/alfakit-component.d.ts.map +1 -1
  54. package/dist/standalone/alfakit-component.js +20 -9
  55. package/dist/standalone/alfakit-component.js.map +1 -1
  56. package/dist/standalone/runtime.js +2 -1
  57. package/dist/standalone/runtime.js.map +1 -1
  58. package/dist/standalone.css +12 -7
  59. package/dist/starter-kit/navigation.d.ts +6 -1
  60. package/dist/starter-kit/navigation.d.ts.map +1 -1
  61. package/dist/starter-kit/navigation.js +3 -2
  62. package/dist/starter-kit/navigation.js.map +1 -1
  63. package/dist/starter-kit/starter-kit.d.ts.map +1 -1
  64. package/dist/starter-kit/starter-kit.js +7 -2
  65. package/dist/starter-kit/starter-kit.js.map +1 -1
  66. package/dist/styles.css +2 -1
  67. package/dist/themes.css +4 -0
  68. package/package.json +2 -2
  69. package/src/components/actions/action-modals.tsx +2 -0
  70. package/src/components/dev-tools/dev-tools-inspector.tsx +1 -1
  71. package/src/components/fields/checkbox-list.tsx +2 -2
  72. package/src/components/fields/rich-editor-extensions.tsx +24 -2
  73. package/src/components/file-manager/file-manager.tsx +3 -1
  74. package/src/components/form/form-block.tsx +15 -2
  75. package/src/components/layout/elements.tsx +27 -4
  76. package/src/components/layout/mixed-content.tsx +8 -4
  77. package/src/components/layout/section.tsx +2 -2
  78. package/src/components/notifications/notifications-menu.tsx +42 -1
  79. package/src/components/panel/app-sidebar.tsx +3 -3
  80. package/src/components/panel/bottom-nav.tsx +2 -2
  81. package/src/components/panel/keyboard-shortcuts.tsx +1 -1
  82. package/src/components/panel/shortcuts-overlay.tsx +29 -24
  83. package/src/components/table/column-manager.tsx +6 -4
  84. package/src/components/table/spreadsheet.tsx +18 -7
  85. package/src/components/translation-manager/translation-manager.tsx +1 -1
  86. package/src/components/ui/tooltip.tsx +10 -1
  87. package/src/components/widgets/stats-overview.tsx +6 -3
  88. package/src/layouts/top-nav-layout.tsx +3 -3
  89. package/src/pages/page.tsx +7 -3
  90. package/src/standalone/alfakit-component.tsx +18 -3
  91. package/src/standalone/runtime.ts +3 -0
  92. package/src/starter-kit/navigation.ts +12 -1
  93. package/src/starter-kit/starter-kit.tsx +10 -1
  94. package/src/styles.css +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"form-block.js","names":["FormBlock"],"sources":["../../../src/components/form/form-block.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema, FormBlock as FormBlockSchema } from '@alfakit/core'\nimport {\n FormStore,\n applyFormRefresh,\n formActionsVisible,\n formIdentity,\n formRefreshFor,\n isImplicitSubmit,\n isLeavingVisit,\n pageFlash,\n pageFormName,\n registerConflictForm,\n registerFormSubmitter,\n registerPageForm,\n resolvePartialProps,\n shouldResyncForm,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useActionRunner, useAlfakitProps, usePageSchema, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { ActionList } from '../actions/action-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { FormProvider } from './form-context'\n\n/**\n * A page form: its state lives in a FormStore, and its buttons are actions that post the\n * current values through the action endpoint.\n */\nexport function FormBlock({ block }: { block: FormBlockSchema }) {\n const panel = usePanel()\n const page = usePageSchema()\n const props = useAlfakitProps()\n const runner = useActionRunner()\n const t = useT()\n const token = page?.token ?? ''\n\n // The store survives reloads of the same form (table sorting/paging, polling,\n // relation-manager actions all deliver a new `block` object); only a different form\n // identity starts from server state again. The token is re-issued per response, so it\n // is swapped in place instead of being part of the identity.\n const identity = formIdentity(page, block)\n\n const store = useMemo(\n () =>\n new FormStore(block, {\n token,\n component: block.component,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n // Live dashboard filters are applied by the refresh; the widgets reload to follow.\n onRefreshed: () => {\n if (latestBlock.current.liveFilters)\n router.reload({ only: resolvePartialProps(['page']) })\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [identity],\n )\n\n useEffect(() => () => store.destroy(), [store])\n\n // Named forms of custom pages (`<PageForm name>`) publish their store for page method calls.\n useEffect(() => {\n const name = pageFormName(block.component)\n\n return name ? registerPageForm(name, store) : undefined\n }, [block.component, store])\n\n // Edit pages: the conflict banner diffs and merges the main form (`detectConflicts()`).\n useEffect(\n () => (block.component === 'form' ? registerConflictForm(store) : undefined),\n [block.component, store],\n )\n\n useEffect(() => {\n store.setToken(token)\n }, [store, token])\n\n useEffect(() => {\n store.setErrors((props.errors as Record<string, string>) ?? {})\n }, [props.errors, store])\n\n const state = useStore(store.store)\n const context = useMemo<ActionContext>(() => ({ token, component: 'page' }), [token])\n const submitting = useRef(false)\n // After this form's own successful submit, refill from the server's new state. The new\n // block may arrive before or after Inertia's `success` event, so handle both orders.\n const latestBlock = useRef(block)\n const submittedBlock = useRef<FormBlockSchema | null>(null)\n const refillPending = useRef(false)\n // Another action succeeded and its new block has not arrived yet.\n const resyncPending = useRef(false)\n // The block when the latest non-GET visit started, to tell whether its response arrived.\n const visitBlock = useRef<FormBlockSchema | null>(null)\n\n useEffect(() => {\n latestBlock.current = block\n\n if (refillPending.current) {\n refillPending.current = false\n store.fill(block)\n }\n\n if (resyncPending.current) {\n resyncPending.current = false\n\n if (!store.isDirty) store.fill(block)\n }\n }, [block, store])\n\n const actions: ActionSchema[] = useMemo(\n () => block.actions.map((action) => ({ ...action, hasModal: false })),\n [block.actions],\n )\n\n const trigger = useCallback(\n async (action: ActionSchema, ctx: ActionContext) => {\n if (action.url && !action.hasAction) {\n return runner.trigger(action, ctx)\n }\n\n submitting.current = true\n submittedBlock.current = latestBlock.current\n store.setProcessing(true)\n // A click that blurred a live field started a refresh: send the state it returns.\n store.whenSettled(() => runner.execute(action, ctx, store.state.values))\n },\n [runner, store],\n )\n\n // Buttons elsewhere with `formId('form')` (a header \"Save\") submit through this form.\n useEffect(() => registerFormSubmitter(block.component, trigger), [block.component, trigger])\n\n // `refreshFormData([...])`: an action changed the record; copy those fields in.\n useEffect(\n () =>\n router.on('success', (event) => {\n const refresh = formRefreshFor(pageFlash(event.detail.page), block.component)\n\n if (refresh) applyFormRefresh(store, refresh)\n }),\n [block.component, store],\n )\n\n // Wizard forms (`HasWizard` pages) show their buttons on the last step only.\n const progressStore = useMemo(() => wizardProgressStore(store), [store])\n const progress = useStore(progressStore)\n const showActions = formActionsVisible(block, progress)\n\n // Track this form's own submit visit so unrelated reloads finishing meanwhile (polling,\n // relation-manager paging) neither end its processing state nor count as its success.\n const submitVisit = useRef<string | null>(null)\n\n useEffect(() => {\n const offStart = router.on('start', (event) => {\n const visit = event.detail.visit\n\n if (submitting.current && !submitVisit.current && visit.method !== 'get') {\n submitVisit.current = visit.id\n }\n\n if (visit.method !== 'get') visitBlock.current = latestBlock.current\n })\n\n const offSuccess = router.on('success', (event) => {\n const id = submitVisit.current\n\n // Another action changed the record: show the server's new form unless it holds edits.\n if (!id && shouldResyncForm(pageFlash(event.detail.page), store)) {\n if (latestBlock.current !== visitBlock.current) {\n store.fill(latestBlock.current)\n } else {\n resyncPending.current = true\n }\n\n return\n }\n\n if (!id || (event.detail.visitId ?? id) !== id) return\n\n // A halted action (rate limit, `halt()`) saved nothing: keep what the user typed.\n if (pageFlash(event.detail.page).alfakitAction?.status === 'halted') return\n\n if (latestBlock.current !== submittedBlock.current) {\n store.fill(latestBlock.current)\n } else {\n refillPending.current = true\n }\n })\n\n const offFinish = router.on('finish', (event) => {\n if (submitVisit.current && event.detail.visit.id !== submitVisit.current) return\n\n store.setProcessing(false)\n submitting.current = false\n submitVisit.current = null\n })\n\n return () => {\n offStart()\n offSuccess()\n offFinish()\n }\n }, [store])\n\n useEffect(() => {\n if (!panel.unsavedChangesAlerts) {\n return\n }\n\n return router.on('before', (event) => {\n if (\n store.isDirty &&\n !submitting.current &&\n isLeavingVisit(event.detail.visit) &&\n !window.confirm(t('unsaved_changes', {}, 'You have unsaved changes. Leave anyway?'))\n ) {\n event.preventDefault()\n }\n })\n }, [panel.unsavedChangesAlerts, store, t])\n\n const primary = actions[0]\n\n return (\n <FormProvider store={store}>\n <form\n data-slot=\"form-block\"\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n\n if (primary && showActions) {\n void trigger(primary, context)\n }\n }}\n onKeyDown={(event) => {\n // Enter in a text field runs the primary action (actions are type=\"button\").\n if (primary && showActions && isImplicitSubmit(event.nativeEvent, event.currentTarget)) {\n event.preventDefault()\n void trigger(primary, context)\n }\n }}\n >\n <fieldset disabled={state.processing || block.operation === 'view'} className=\"contents\">\n <SchemaRenderer components={state.components} columns={state.columns} />\n </fieldset>\n {actions.length > 0 && showActions && (\n <div data-slot=\"form-actions\" className=\"border-t pt-6\">\n <ActionList actions={actions} context={context} onTrigger={trigger} />\n </div>\n )}\n </form>\n </FormProvider>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"form-block.js","names":["FormBlock"],"sources":["../../../src/components/form/form-block.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema, FormBlock as FormBlockSchema } from '@alfakit/core'\nimport {\n FormStore,\n applyFormRefresh,\n formActionsVisible,\n formIdPrefix,\n formIdentity,\n formRefreshFor,\n isImplicitSubmit,\n isLeavingVisit,\n pageFlash,\n pageFormName,\n registerConflictForm,\n registerFormSubmitter,\n registerPageForm,\n resolvePartialProps,\n shouldResyncForm,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { useCallback, useEffect, useId, useMemo, useRef } from 'react'\nimport {\n useActionRunner,\n useAlfakitProps,\n usePageSchema,\n usePanel,\n useStandalone,\n useT,\n} from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { ActionList } from '../actions/action-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { FormProvider } from './form-context'\n\n/**\n * A page form: its state lives in a FormStore, and its buttons are actions that post the\n * current values through the action endpoint.\n */\nexport function FormBlock({ block }: { block: FormBlockSchema }) {\n const panel = usePanel()\n const page = usePageSchema()\n const props = useAlfakitProps()\n const runner = useActionRunner()\n const t = useT()\n const token = page?.token ?? ''\n\n // The store survives reloads of the same form (table sorting/paging, polling,\n // relation-manager actions all deliver a new `block` object); only a different form\n // identity starts from server state again. The token is re-issued per response, so it\n // is swapped in place instead of being part of the identity.\n const identity = formIdentity(page, block)\n // Several standalone components can share a page (and field names): each form gets its\n // own field id prefix, so labels and errors point at their own inputs.\n const standalone = useStandalone()\n const instanceId = useId()\n\n const store = useMemo(\n () =>\n new FormStore(block, {\n token,\n component: block.component,\n idPrefix: standalone ? formIdPrefix(`form-${instanceId}`) : undefined,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n // Live dashboard filters are applied by the refresh; the widgets reload to follow.\n onRefreshed: () => {\n if (latestBlock.current.liveFilters)\n router.reload({ only: resolvePartialProps(['page']) })\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [identity],\n )\n\n useEffect(() => () => store.destroy(), [store])\n\n // Named forms of custom pages (`<PageForm name>`) publish their store for page method calls.\n useEffect(() => {\n const name = pageFormName(block.component)\n\n return name ? registerPageForm(name, store) : undefined\n }, [block.component, store])\n\n // Edit pages: the conflict banner diffs and merges the main form (`detectConflicts()`).\n useEffect(\n () => (block.component === 'form' ? registerConflictForm(store) : undefined),\n [block.component, store],\n )\n\n useEffect(() => {\n store.setToken(token)\n }, [store, token])\n\n useEffect(() => {\n store.setErrors((props.errors as Record<string, string>) ?? {})\n }, [props.errors, store])\n\n const state = useStore(store.store)\n const context = useMemo<ActionContext>(() => ({ token, component: 'page' }), [token])\n const submitting = useRef(false)\n // After this form's own successful submit, refill from the server's new state. The new\n // block may arrive before or after Inertia's `success` event, so handle both orders.\n const latestBlock = useRef(block)\n const submittedBlock = useRef<FormBlockSchema | null>(null)\n const refillPending = useRef(false)\n // Another action succeeded and its new block has not arrived yet.\n const resyncPending = useRef(false)\n // The block when the latest non-GET visit started, to tell whether its response arrived.\n const visitBlock = useRef<FormBlockSchema | null>(null)\n\n useEffect(() => {\n latestBlock.current = block\n\n if (refillPending.current) {\n refillPending.current = false\n store.fill(block)\n }\n\n if (resyncPending.current) {\n resyncPending.current = false\n\n if (!store.isDirty) store.fill(block)\n }\n }, [block, store])\n\n const actions: ActionSchema[] = useMemo(\n () => block.actions.map((action) => ({ ...action, hasModal: false })),\n [block.actions],\n )\n\n const trigger = useCallback(\n async (action: ActionSchema, ctx: ActionContext) => {\n if (action.url && !action.hasAction) {\n return runner.trigger(action, ctx)\n }\n\n submitting.current = true\n submittedBlock.current = latestBlock.current\n store.setProcessing(true)\n // A click that blurred a live field started a refresh: send the state it returns.\n store.whenSettled(() => runner.execute(action, ctx, store.state.values))\n },\n [runner, store],\n )\n\n // Buttons elsewhere with `formId('form')` (a header \"Save\") submit through this form.\n useEffect(() => registerFormSubmitter(block.component, trigger), [block.component, trigger])\n\n // `refreshFormData([...])`: an action changed the record; copy those fields in.\n useEffect(\n () =>\n router.on('success', (event) => {\n const refresh = formRefreshFor(pageFlash(event.detail.page), block.component)\n\n if (refresh) applyFormRefresh(store, refresh)\n }),\n [block.component, store],\n )\n\n // Wizard forms (`HasWizard` pages) show their buttons on the last step only.\n const progressStore = useMemo(() => wizardProgressStore(store), [store])\n const progress = useStore(progressStore)\n const showActions = formActionsVisible(block, progress)\n\n // Track this form's own submit visit so unrelated reloads finishing meanwhile (polling,\n // relation-manager paging) neither end its processing state nor count as its success.\n const submitVisit = useRef<string | null>(null)\n\n useEffect(() => {\n const offStart = router.on('start', (event) => {\n const visit = event.detail.visit\n\n if (submitting.current && !submitVisit.current && visit.method !== 'get') {\n submitVisit.current = visit.id\n }\n\n if (visit.method !== 'get') visitBlock.current = latestBlock.current\n })\n\n const offSuccess = router.on('success', (event) => {\n const id = submitVisit.current\n\n // Another action changed the record: show the server's new form unless it holds edits.\n if (!id && shouldResyncForm(pageFlash(event.detail.page), store)) {\n if (latestBlock.current !== visitBlock.current) {\n store.fill(latestBlock.current)\n } else {\n resyncPending.current = true\n }\n\n return\n }\n\n if (!id || (event.detail.visitId ?? id) !== id) return\n\n // A halted action (rate limit, `halt()`) saved nothing: keep what the user typed.\n if (pageFlash(event.detail.page).alfakitAction?.status === 'halted') return\n\n if (latestBlock.current !== submittedBlock.current) {\n store.fill(latestBlock.current)\n } else {\n refillPending.current = true\n }\n })\n\n const offFinish = router.on('finish', (event) => {\n if (submitVisit.current && event.detail.visit.id !== submitVisit.current) return\n\n store.setProcessing(false)\n submitting.current = false\n submitVisit.current = null\n })\n\n return () => {\n offStart()\n offSuccess()\n offFinish()\n }\n }, [store])\n\n useEffect(() => {\n if (!panel.unsavedChangesAlerts) {\n return\n }\n\n return router.on('before', (event) => {\n if (\n store.isDirty &&\n !submitting.current &&\n isLeavingVisit(event.detail.visit) &&\n !window.confirm(t('unsaved_changes', {}, 'You have unsaved changes. Leave anyway?'))\n ) {\n event.preventDefault()\n }\n })\n }, [panel.unsavedChangesAlerts, store, t])\n\n const primary = actions[0]\n\n return (\n <FormProvider store={store}>\n <form\n data-slot=\"form-block\"\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n\n if (primary && showActions) {\n void trigger(primary, context)\n }\n }}\n onKeyDown={(event) => {\n // Enter in a text field runs the primary action (actions are type=\"button\").\n if (primary && showActions && isImplicitSubmit(event.nativeEvent, event.currentTarget)) {\n event.preventDefault()\n void trigger(primary, context)\n }\n }}\n >\n <fieldset disabled={state.processing || block.operation === 'view'} className=\"contents\">\n <SchemaRenderer components={state.components} columns={state.columns} />\n </fieldset>\n {actions.length > 0 && showActions && (\n <div data-slot=\"form-actions\" className=\"border-t pt-6\">\n <ActionList actions={actions} context={context} onTrigger={trigger} />\n </div>\n )}\n </form>\n </FormProvider>\n )\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"elements.d.ts","names":[],"sources":["../../../src/components/layout/elements.tsx"],"mappings":";;;wBA+BgB,gBAAgB;EAAe,WAAW;oBAAY,IAAA;wBAqCtD,mBAAmB;EAAe,WAAW;oBAAe,IAAA;wBA0D5D,sBAAsB;EAAe,WAAW;oBAA2B,IAAA;wBAkD3E,gBAAgB;EAAe,WAAW;IAAY,+CAAA;wBA2BtD,iBAAiB;EAAe,WAAW;oBAAa,IAAA;wBAoCxD,yBAAyB;EAAe,WAAW;oBAAqB,IAAA"}
1
+ {"version":3,"file":"elements.d.ts","names":[],"sources":["../../../src/components/layout/elements.tsx"],"mappings":";;;wBAiCgB,gBAAgB;EAAe,WAAW;oBAAY,IAAA;wBAqCtD,mBAAmB;EAAe,WAAW;oBAAe,IAAA;wBAqE5D,sBAAsB;EAAe,WAAW;oBAA2B,IAAA;wBA4D3E,gBAAgB;EAAe,WAAW;IAAY,+CAAA;wBA2BtD,iBAAiB;EAAe,WAAW;oBAAa,IAAA;wBAoCxD,yBAAyB;EAAe,WAAW;oBAAqB,IAAA"}
@@ -5,6 +5,8 @@ import { Icon } from "../../lib/icons.js";
5
5
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
6
6
  import { extraProps } from "../../lib/attributes.js";
7
7
  import { ActionList } from "../actions/action-list.js";
8
+ import { useOptionalFormStore } from "../form/form-context.js";
9
+ import { useFieldActionContext } from "../schema/field-actions.js";
8
10
  import { RenderComponent } from "../schema/schema-renderer.js";
9
11
  import { MixedContent } from "./mixed-content.js";
10
12
  import { actionsAlignmentClass, flexItemClasses, flexLayoutClasses, schemaGapClass } from "@alfakit/core";
@@ -39,7 +41,10 @@ const CALLOUT_ICON_SIZES = {
39
41
  "2xl": "size-8"
40
42
  };
41
43
  function CalloutComponent({ component }) {
42
- const context = usePageActionContext();
44
+ const form = useOptionalFormStore();
45
+ const pageContext = usePageActionContext();
46
+ const fieldContext = useFieldActionContext(component);
47
+ const context = form ? fieldContext : pageContext;
43
48
  const tone = component.color ?? component.status;
44
49
  const iconColor = component.iconColor ?? (component.color ? void 0 : component.status);
45
50
  return /* @__PURE__ */ jsxs("div", {
@@ -77,20 +82,25 @@ function CalloutComponent({ component }) {
77
82
  /* @__PURE__ */ jsx(MixedContent, {
78
83
  items: component.footer,
79
84
  name: "callout-footer",
80
- className: "mt-2"
85
+ className: "mt-2",
86
+ context
81
87
  })
82
88
  ]
83
89
  }),
84
90
  /* @__PURE__ */ jsx(MixedContent, {
85
91
  items: component.controls,
86
92
  name: "callout-controls",
87
- className: "-me-1 -mt-1 shrink-0 gap-1"
93
+ className: "-me-1 -mt-1 shrink-0 gap-1",
94
+ context
88
95
  })
89
96
  ]
90
97
  });
91
98
  }
92
99
  function EmptyStateComponent({ component }) {
93
- const context = usePageActionContext();
100
+ const form = useOptionalFormStore();
101
+ const pageContext = usePageActionContext();
102
+ const fieldContext = useFieldActionContext(component);
103
+ const context = form ? fieldContext : pageContext;
94
104
  return /* @__PURE__ */ jsxs("div", {
95
105
  "data-slot": "empty-state",
96
106
  ...extraProps(component.extraAttributes, cn("flex flex-col items-center justify-center gap-3 px-6 py-10 text-center", component.contained && "rounded-xl border border-dashed")),
@@ -118,7 +128,8 @@ function EmptyStateComponent({ component }) {
118
128
  /* @__PURE__ */ jsx(MixedContent, {
119
129
  items: component.footer,
120
130
  name: "empty-state-footer",
121
- className: "justify-center"
131
+ className: "justify-center",
132
+ context
122
133
  })
123
134
  ]
124
135
  });
@@ -1 +1 @@
1
- {"version":3,"file":"elements.js","names":[],"sources":["../../../src/components/layout/elements.tsx"],"sourcesContent":["import type {\n CalloutSchema,\n EmptyStateComponentSchema,\n FlexSchema,\n IconSchema,\n ImageSchema,\n SchemaComponent,\n UnorderedListSchema,\n} from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n flexItemClasses,\n flexLayoutClasses,\n schemaGapClass,\n} from '@alfakit/core'\nimport type { ReactElement } from 'react'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n iconSizeClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { RenderComponent } from '../schema/schema-renderer'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\nimport { MixedContent } from './mixed-content'\n\nexport function FlexComponent({ component }: { component: FlexSchema }) {\n return (\n <div\n data-slot=\"flex\"\n {...extraProps(\n component.extraAttributes,\n cn(flexLayoutClasses(component), schemaGapClass(component)),\n )}\n >\n {(component.schema ?? []).map((child: SchemaComponent) => (\n <div key={child.key} data-slot=\"flex-item\" className={flexItemClasses(child)}>\n <RenderComponent component={child} />\n </div>\n ))}\n </div>\n )\n}\n\nconst CALLOUT_COLORS: Record<string, string> = {\n primary: 'border-primary/30 bg-primary/5 [&_[data-slot=callout-icon]]:text-primary',\n secondary: 'border-border bg-secondary [&_[data-slot=callout-icon]]:text-secondary-foreground',\n success: 'border-success/30 bg-success/5 [&_[data-slot=callout-icon]]:text-success',\n warning: 'border-warning/40 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning',\n danger: 'border-destructive/30 bg-destructive/5 [&_[data-slot=callout-icon]]:text-destructive',\n info: 'border-info/30 bg-info/5 [&_[data-slot=callout-icon]]:text-info',\n gray: 'border-border bg-muted/40 [&_[data-slot=callout-icon]]:text-muted-foreground',\n}\n\nconst CALLOUT_ICON_SIZES: Record<string, string> = {\n xs: 'size-4',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-7',\n '2xl': 'size-8',\n}\n\nexport function CalloutComponent({ component }: { component: CalloutSchema }) {\n const context = usePageActionContext()\n const tone = component.color ?? component.status\n // `iconColor()`, or the status colour once `color(null)` removed the background.\n const iconColor = component.iconColor ?? (component.color ? undefined : component.status)\n\n return (\n <div\n data-slot=\"callout\"\n data-color={component.color ?? 'gray'}\n data-status={component.status}\n role={tone === 'danger' || tone === 'warning' ? 'alert' : 'note'}\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex items-start gap-3 rounded-lg border p-4 text-sm',\n colorClass(CALLOUT_COLORS, component.color),\n ),\n )}\n >\n {component.icon && (\n <span data-slot=\"callout-icon\" className=\"mt-0.5 shrink-0\">\n <Icon\n name={component.icon}\n className={cn(\n (component.iconSize && CALLOUT_ICON_SIZES[component.iconSize]) || 'size-5',\n iconColor && colorClass(textColorClasses, iconColor),\n )}\n />\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {component.heading && <p className=\"leading-6 font-semibold\">{component.heading}</p>}\n {component.description && (\n <div\n className=\"text-muted-foreground alfakit-prose\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList\n actions={component.actions}\n context={context}\n size=\"sm\"\n className={cn('mt-2', actionsAlignmentClass(component.footerActionsAlignment))}\n />\n )}\n <MixedContent items={component.footer} name=\"callout-footer\" className=\"mt-2\" />\n </div>\n <MixedContent\n items={component.controls}\n name=\"callout-controls\"\n className=\"-me-1 -mt-1 shrink-0 gap-1\"\n />\n </div>\n )\n}\n\nexport function EmptyStateComponent({ component }: { component: EmptyStateComponentSchema }) {\n const context = usePageActionContext()\n\n return (\n <div\n data-slot=\"empty-state\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex flex-col items-center justify-center gap-3 px-6 py-10 text-center',\n component.contained && 'rounded-xl border border-dashed',\n ),\n )}\n >\n {component.icon && (\n <div\n className={cn(\n 'flex size-12 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, component.color),\n )}\n >\n <Icon name={component.icon} className=\"size-6\" />\n </div>\n )}\n {component.heading && <h3 className=\"text-base font-semibold\">{component.heading}</h3>}\n {component.description && (\n <div\n className=\"text-muted-foreground max-w-md text-sm\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList actions={component.actions} context={context} className=\"mt-2 justify-center\" />\n )}\n <MixedContent items={component.footer} name=\"empty-state-footer\" className=\"justify-center\" />\n </div>\n )\n}\n\nfunction withTooltip(element: ReactElement, tooltip?: string): ReactElement {\n if (!tooltip) return element\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{element}</TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n )\n}\n\nexport function IconComponent({ component }: { component: IconSchema }) {\n return withTooltip(\n <span\n data-slot=\"icon\"\n {...extraProps(component.extraAttributes, 'inline-flex')}\n aria-label={component.tooltip}\n role={component.tooltip ? 'img' : undefined}\n >\n <Icon\n name={component.icon}\n aria-hidden\n className={cn(\n iconSizeClasses[component.size ?? 'md'] ?? 'size-5',\n component.color && textColorClasses[component.color],\n )}\n />\n </span>,\n component.tooltip,\n )\n}\n\nconst IMAGE_ALIGNMENT: Record<string, string> = {\n start: 'justify-start',\n center: 'justify-center',\n end: 'justify-end',\n}\n\nexport function ImageComponent({ component }: { component: ImageSchema }) {\n return (\n <div\n data-slot=\"image\"\n {...extraProps(\n component.extraAttributes,\n cn('flex', IMAGE_ALIGNMENT[component.alignment ?? 'start'] ?? 'justify-start'),\n )}\n >\n {withTooltip(\n <img\n src={component.url}\n alt={component.alt}\n loading=\"lazy\"\n className={cn(\n 'max-w-full object-cover',\n component.circular ? 'rounded-full' : 'rounded-md',\n )}\n style={{\n width: component.imageWidth ?? undefined,\n height: component.imageHeight ?? undefined,\n }}\n />,\n component.tooltip,\n )}\n </div>\n )\n}\n\nconst LIST_SIZES: Record<string, string> = {\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n}\n\nexport function UnorderedListComponent({ component }: { component: UnorderedListSchema }) {\n return (\n <ul\n data-slot=\"unordered-list\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'marker:text-muted-foreground flex list-disc flex-col gap-1 ps-5',\n LIST_SIZES[component.size ?? 'sm'] ?? 'text-sm',\n ),\n )}\n >\n {(component.schema ?? []).map((child) => (\n <li key={child.key}>\n <RenderComponent component={child} />\n </li>\n ))}\n </ul>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"elements.js","names":[],"sources":["../../../src/components/layout/elements.tsx"],"sourcesContent":["import type {\n CalloutSchema,\n EmptyStateComponentSchema,\n FlexSchema,\n IconSchema,\n ImageSchema,\n SchemaComponent,\n UnorderedListSchema,\n} from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n flexItemClasses,\n flexLayoutClasses,\n schemaGapClass,\n} from '@alfakit/core'\nimport type { ReactElement } from 'react'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n iconSizeClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { RenderComponent } from '../schema/schema-renderer'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\nimport { MixedContent } from './mixed-content'\n\nexport function FlexComponent({ component }: { component: FlexSchema }) {\n return (\n <div\n data-slot=\"flex\"\n {...extraProps(\n component.extraAttributes,\n cn(flexLayoutClasses(component), schemaGapClass(component)),\n )}\n >\n {(component.schema ?? []).map((child: SchemaComponent) => (\n <div key={child.key} data-slot=\"flex-item\" className={flexItemClasses(child)}>\n <RenderComponent component={child} />\n </div>\n ))}\n </div>\n )\n}\n\nconst CALLOUT_COLORS: Record<string, string> = {\n primary: 'border-primary/30 bg-primary/5 [&_[data-slot=callout-icon]]:text-primary',\n secondary: 'border-border bg-secondary [&_[data-slot=callout-icon]]:text-secondary-foreground',\n success: 'border-success/30 bg-success/5 [&_[data-slot=callout-icon]]:text-success',\n warning: 'border-warning/40 bg-warning/10 [&_[data-slot=callout-icon]]:text-warning',\n danger: 'border-destructive/30 bg-destructive/5 [&_[data-slot=callout-icon]]:text-destructive',\n info: 'border-info/30 bg-info/5 [&_[data-slot=callout-icon]]:text-info',\n gray: 'border-border bg-muted/40 [&_[data-slot=callout-icon]]:text-muted-foreground',\n}\n\nconst CALLOUT_ICON_SIZES: Record<string, string> = {\n xs: 'size-4',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-7',\n '2xl': 'size-8',\n}\n\nexport function CalloutComponent({ component }: { component: CalloutSchema }) {\n // Inside a form the actions run as field actions of this component, so `$get` / `$set`\n // reach the form (like a section's header actions).\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n const tone = component.color ?? component.status\n // `iconColor()`, or the status colour once `color(null)` removed the background.\n const iconColor = component.iconColor ?? (component.color ? undefined : component.status)\n\n return (\n <div\n data-slot=\"callout\"\n data-color={component.color ?? 'gray'}\n data-status={component.status}\n role={tone === 'danger' || tone === 'warning' ? 'alert' : 'note'}\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex items-start gap-3 rounded-lg border p-4 text-sm',\n colorClass(CALLOUT_COLORS, component.color),\n ),\n )}\n >\n {component.icon && (\n <span data-slot=\"callout-icon\" className=\"mt-0.5 shrink-0\">\n <Icon\n name={component.icon}\n className={cn(\n (component.iconSize && CALLOUT_ICON_SIZES[component.iconSize]) || 'size-5',\n iconColor && colorClass(textColorClasses, iconColor),\n )}\n />\n </span>\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n {component.heading && <p className=\"leading-6 font-semibold\">{component.heading}</p>}\n {component.description && (\n <div\n className=\"text-muted-foreground alfakit-prose\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList\n actions={component.actions}\n context={context}\n size=\"sm\"\n className={cn('mt-2', actionsAlignmentClass(component.footerActionsAlignment))}\n />\n )}\n <MixedContent\n items={component.footer}\n name=\"callout-footer\"\n className=\"mt-2\"\n context={context}\n />\n </div>\n <MixedContent\n items={component.controls}\n name=\"callout-controls\"\n className=\"-me-1 -mt-1 shrink-0 gap-1\"\n context={context}\n />\n </div>\n )\n}\n\nexport function EmptyStateComponent({ component }: { component: EmptyStateComponentSchema }) {\n // Inside a form the actions run as field actions of this component, so `$get` / `$set`\n // reach the form (like a section's header actions).\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n\n return (\n <div\n data-slot=\"empty-state\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'flex flex-col items-center justify-center gap-3 px-6 py-10 text-center',\n component.contained && 'rounded-xl border border-dashed',\n ),\n )}\n >\n {component.icon && (\n <div\n className={cn(\n 'flex size-12 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, component.color),\n )}\n >\n <Icon name={component.icon} className=\"size-6\" />\n </div>\n )}\n {component.heading && <h3 className=\"text-base font-semibold\">{component.heading}</h3>}\n {component.description && (\n <div\n className=\"text-muted-foreground max-w-md text-sm\"\n dangerouslySetInnerHTML={{ __html: component.description }}\n />\n )}\n {component.actions && component.actions.length > 0 && (\n <ActionList actions={component.actions} context={context} className=\"mt-2 justify-center\" />\n )}\n <MixedContent\n items={component.footer}\n name=\"empty-state-footer\"\n className=\"justify-center\"\n context={context}\n />\n </div>\n )\n}\n\nfunction withTooltip(element: ReactElement, tooltip?: string): ReactElement {\n if (!tooltip) return element\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{element}</TooltipTrigger>\n <TooltipContent>{tooltip}</TooltipContent>\n </Tooltip>\n )\n}\n\nexport function IconComponent({ component }: { component: IconSchema }) {\n return withTooltip(\n <span\n data-slot=\"icon\"\n {...extraProps(component.extraAttributes, 'inline-flex')}\n aria-label={component.tooltip}\n role={component.tooltip ? 'img' : undefined}\n >\n <Icon\n name={component.icon}\n aria-hidden\n className={cn(\n iconSizeClasses[component.size ?? 'md'] ?? 'size-5',\n component.color && textColorClasses[component.color],\n )}\n />\n </span>,\n component.tooltip,\n )\n}\n\nconst IMAGE_ALIGNMENT: Record<string, string> = {\n start: 'justify-start',\n center: 'justify-center',\n end: 'justify-end',\n}\n\nexport function ImageComponent({ component }: { component: ImageSchema }) {\n return (\n <div\n data-slot=\"image\"\n {...extraProps(\n component.extraAttributes,\n cn('flex', IMAGE_ALIGNMENT[component.alignment ?? 'start'] ?? 'justify-start'),\n )}\n >\n {withTooltip(\n <img\n src={component.url}\n alt={component.alt}\n loading=\"lazy\"\n className={cn(\n 'max-w-full object-cover',\n component.circular ? 'rounded-full' : 'rounded-md',\n )}\n style={{\n width: component.imageWidth ?? undefined,\n height: component.imageHeight ?? undefined,\n }}\n />,\n component.tooltip,\n )}\n </div>\n )\n}\n\nconst LIST_SIZES: Record<string, string> = {\n xs: 'text-xs',\n sm: 'text-sm',\n md: 'text-base',\n lg: 'text-lg',\n}\n\nexport function UnorderedListComponent({ component }: { component: UnorderedListSchema }) {\n return (\n <ul\n data-slot=\"unordered-list\"\n {...extraProps(\n component.extraAttributes,\n cn(\n 'marker:text-muted-foreground flex list-disc flex-col gap-1 ps-5',\n LIST_SIZES[component.size ?? 'sm'] ?? 'text-sm',\n ),\n )}\n >\n {(component.schema ?? []).map((child) => (\n <li key={child.key}>\n <RenderComponent component={child} />\n </li>\n ))}\n </ul>\n )\n}\n"],"mappings":"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"}
@@ -1,14 +1,16 @@
1
- import { ContentSlotItem } from "@alfakit/core";
1
+ import { ActionContext, ContentSlotItem } from "@alfakit/core";
2
2
  //#region src/components/layout/mixed-content.d.ts
3
3
  /**
4
4
  * Actions and components placed in a layout component's fixed spot (a section's
5
- * `afterHeader()`, a callout's `controls()`). Actions run in the page's action context,
6
- * like section header actions.
5
+ * `afterHeader()`, a callout's `controls()`). Actions run in the owner's action context
6
+ * (`context`: inside a form, the owner's field context, so `$get` / `$set` work), else the
7
+ * page's.
7
8
  */
8
- export declare function MixedContent({ items, name, className }: {
9
+ export declare function MixedContent({ items, name, className, context: ownerContext }: {
9
10
  items: ContentSlotItem[] | undefined;
10
11
  name: string;
11
12
  className?: string;
13
+ context?: ActionContext;
12
14
  }): import("react").JSX.Element | null;
13
15
  //#endregion
14
16
  //# sourceMappingURL=mixed-content.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mixed-content.d.ts","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"mappings":";;;;;;;wBAYgB,eACd,OACA,MACA;EAEA,OAAO;EACP;EACA;oBACD,IAAA"}
1
+ {"version":3,"file":"mixed-content.d.ts","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"mappings":";;;;;;;;wBAagB,eACd,OACA,MACA,WACA,SAAS;EAET,OAAO;EACP;EACA;EACA,UAAU;oBACX,IAAA"}
@@ -6,11 +6,13 @@ import { jsx } from "react/jsx-runtime";
6
6
  //#region src/components/layout/mixed-content.tsx
7
7
  /**
8
8
  * Actions and components placed in a layout component's fixed spot (a section's
9
- * `afterHeader()`, a callout's `controls()`). Actions run in the page's action context,
10
- * like section header actions.
9
+ * `afterHeader()`, a callout's `controls()`). Actions run in the owner's action context
10
+ * (`context`: inside a form, the owner's field context, so `$get` / `$set` work), else the
11
+ * page's.
11
12
  */
12
- function MixedContent({ items, name, className }) {
13
- const context = usePageActionContext();
13
+ function MixedContent({ items, name, className, context: ownerContext }) {
14
+ const pageContext = usePageActionContext();
15
+ const context = ownerContext ?? pageContext;
14
16
  if (!items?.length) return null;
15
17
  const nodes = [];
16
18
  let actions = [];
@@ -1 +1 @@
1
- {"version":3,"file":"mixed-content.js","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"sourcesContent":["import type { AnyActionSchema, ContentSlotItem } from '@alfakit/core'\nimport type { ReactNode } from 'react'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { RenderComponent } from '../schema/schema-renderer'\n\n/**\n * Actions and components placed in a layout component's fixed spot (a section's\n * `afterHeader()`, a callout's `controls()`). Actions run in the page's action context,\n * like section header actions.\n */\nexport function MixedContent({\n items,\n name,\n className,\n}: {\n items: ContentSlotItem[] | undefined\n name: string\n className?: string\n}) {\n const context = usePageActionContext()\n\n if (!items?.length) {\n return null\n }\n\n // Consecutive actions share one action list so they sit together.\n const nodes: ReactNode[] = []\n let actions: AnyActionSchema[] = []\n\n const flush = () => {\n if (actions.length === 0) return\n\n nodes.push(\n <ActionList key={`actions-${nodes.length}`} actions={actions} context={context} size=\"sm\" />,\n )\n actions = []\n }\n\n for (const item of items) {\n if (item.action) {\n actions.push(item.action)\n\n continue\n }\n\n flush()\n nodes.push(<RenderComponent key={item.component.key} component={item.component} />)\n }\n\n flush()\n\n return (\n <div data-slot={name} className={cn('flex flex-wrap items-center gap-2', className)}>\n {nodes}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,aAAa,EAC3B,OACA,MACA,aAKC;CACD,MAAM,UAAU,qBAAqB;CAErC,IAAI,CAAC,OAAO,QACV,OAAO;CAIT,MAAM,QAAqB,CAAC;CAC5B,IAAI,UAA6B,CAAC;CAElC,MAAM,cAAc;EAClB,IAAI,QAAQ,WAAW,GAAG;EAE1B,MAAM,KACJ,oBAAC,YAAD;GAAqD;GAAkB;GAAS,MAAK;EAAM,GAA1E,WAAW,MAAM,QAAyD,CAC7F;EACA,UAAU,CAAC;CACb;CAEA,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,QAAQ;GACf,QAAQ,KAAK,KAAK,MAAM;GAExB;EACF;EAEA,MAAM;EACN,MAAM,KAAK,oBAAC,iBAAD,EAA0C,WAAW,KAAK,UAAY,GAAhD,KAAK,UAAU,GAAiC,CAAC;CACpF;CAEA,MAAM;CAEN,OACE,oBAAC,OAAD;EAAK,aAAW;EAAM,WAAW,GAAG,qCAAqC,SAAS;EAC/E,UAAA;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"mixed-content.js","names":[],"sources":["../../../src/components/layout/mixed-content.tsx"],"sourcesContent":["import type { ActionContext, AnyActionSchema, ContentSlotItem } from '@alfakit/core'\nimport type { ReactNode } from 'react'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { RenderComponent } from '../schema/schema-renderer'\n\n/**\n * Actions and components placed in a layout component's fixed spot (a section's\n * `afterHeader()`, a callout's `controls()`). Actions run in the owner's action context\n * (`context`: inside a form, the owner's field context, so `$get` / `$set` work), else the\n * page's.\n */\nexport function MixedContent({\n items,\n name,\n className,\n context: ownerContext,\n}: {\n items: ContentSlotItem[] | undefined\n name: string\n className?: string\n context?: ActionContext\n}) {\n const pageContext = usePageActionContext()\n const context = ownerContext ?? pageContext\n\n if (!items?.length) {\n return null\n }\n\n // Consecutive actions share one action list so they sit together.\n const nodes: ReactNode[] = []\n let actions: AnyActionSchema[] = []\n\n const flush = () => {\n if (actions.length === 0) return\n\n nodes.push(\n <ActionList key={`actions-${nodes.length}`} actions={actions} context={context} size=\"sm\" />,\n )\n actions = []\n }\n\n for (const item of items) {\n if (item.action) {\n actions.push(item.action)\n\n continue\n }\n\n flush()\n nodes.push(<RenderComponent key={item.component.key} component={item.component} />)\n }\n\n flush()\n\n return (\n <div data-slot={name} className={cn('flex flex-wrap items-center gap-2', className)}>\n {nodes}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAaA,SAAgB,aAAa,EAC3B,OACA,MACA,WACA,SAAS,gBAMR;CACD,MAAM,cAAc,qBAAqB;CACzC,MAAM,UAAU,gBAAgB;CAEhC,IAAI,CAAC,OAAO,QACV,OAAO;CAIT,MAAM,QAAqB,CAAC;CAC5B,IAAI,UAA6B,CAAC;CAElC,MAAM,cAAc;EAClB,IAAI,QAAQ,WAAW,GAAG;EAE1B,MAAM,KACJ,oBAAC,YAAD;GAAqD;GAAkB;GAAS,MAAK;EAAM,GAA1E,WAAW,MAAM,QAAyD,CAC7F;EACA,UAAU,CAAC;CACb;CAEA,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,QAAQ;GACf,QAAQ,KAAK,KAAK,MAAM;GAExB;EACF;EAEA,MAAM;EACN,MAAM,KAAK,oBAAC,iBAAD,EAA0C,WAAW,KAAK,UAAY,GAAhD,KAAK,UAAU,GAAiC,CAAC;CACpF;CAEA,MAAM;CAEN,OACE,oBAAC,OAAD;EAAK,aAAW;EAAM,WAAW,GAAG,qCAAqC,SAAS;EAC/E,UAAA;CACE,CAAA;AAET"}
@@ -61,7 +61,8 @@ function SectionComponent({ component }) {
61
61
  }),
62
62
  /* @__PURE__ */ jsx(MixedContent, {
63
63
  items: component.afterHeader,
64
- name: "section-after-header"
64
+ name: "section-after-header",
65
+ context
65
66
  }),
66
67
  component.collapsible && /* @__PURE__ */ jsx(CollapsibleTrigger, {
67
68
  asChild: true,
@@ -89,7 +90,8 @@ function SectionComponent({ component }) {
89
90
  className: actionsAlignmentClass(component.footerActionsAlignment) || void 0
90
91
  }), /* @__PURE__ */ jsx(MixedContent, {
91
92
  items: component.footer,
92
- name: "section-footer-content"
93
+ name: "section-footer-content",
94
+ context
93
95
  })]
94
96
  }) : null;
95
97
  if (component.aside) return /* @__PURE__ */ jsx(Collapsible, {
@@ -1 +1 @@
1
- {"version":3,"file":"section.js","names":[],"sources":["../../../src/components/layout/section.tsx"],"sourcesContent":["import type { SectionSchema } from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n headingTag,\n readPersisted,\n schemaGapClass,\n writePersisted,\n} from '@alfakit/core'\nimport { ChevronDownIcon } from 'lucide-react'\nimport { createElement, useState } from 'react'\nimport { useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport { DeferredSchema } from './deferred'\nimport { MixedContent } from './mixed-content'\n\nexport function SectionComponent({ component }: { component: SectionSchema }) {\n const t = useT()\n const [choice, setChoice] = useState<boolean | null>(null)\n // Inside a form the section's actions run as field actions, so `$get` / `$set` work.\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n const persistKey = component.persistCollapsed\n const hydrated = useHydrated()\n // Read only after hydration, so the server render and hydration use the schema's default.\n const stored = hydrated && persistKey ? readPersisted(persistKey) : null\n const open = choice ?? (stored !== null ? stored !== 'collapsed' : !component.collapsed)\n const hasHeader =\n component.heading ||\n component.description ||\n component.icon ||\n component.headerActions?.length ||\n component.afterHeader?.length\n\n const setOpen = (next: boolean) => {\n setChoice(next)\n\n if (persistKey) writePersisted(persistKey, next ? 'expanded' : 'collapsed')\n }\n\n const content = (\n <DeferredSchema component={component}>\n {(loaded) => (\n <SchemaRenderer\n components={loaded.schema ?? []}\n columns={loaded.columns}\n className={schemaGapClass(loaded)}\n />\n )}\n </DeferredSchema>\n )\n\n const header = hasHeader ? (\n <div\n className={cn(\n 'flex flex-wrap items-start gap-3',\n component.aside ? 'lg:col-span-1' : 'border-b px-6 py-4',\n component.compact && !component.aside && 'px-4 py-3',\n )}\n >\n {component.icon && (\n <Icon name={component.icon} className=\"text-muted-foreground mt-0.5 size-5\" />\n )}\n <div className=\"min-w-0 flex-1 basis-48\">\n {component.heading &&\n createElement(\n headingTag(component.headingLevel),\n { className: 'text-base leading-6 font-semibold break-words' },\n component.heading,\n )}\n {component.description && (\n <p className=\"text-muted-foreground mt-1 text-sm break-words\">{component.description}</p>\n )}\n </div>\n {component.headerActions && component.headerActions.length > 0 && (\n <ActionList actions={component.headerActions} context={context} size=\"sm\" />\n )}\n <MixedContent items={component.afterHeader} name=\"section-after-header\" />\n {component.collapsible && (\n <CollapsibleTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('toggle_section', {}, 'Toggle section')}\n >\n <Icon\n name=\"alfakit::schemas.section.collapse-button\"\n fallback={ChevronDownIcon}\n className={cn('transition-transform', open && 'rotate-180')}\n />\n </Button>\n </CollapsibleTrigger>\n )}\n </div>\n ) : null\n\n const hasFooterActions = Boolean(component.footerActions?.length)\n const footer =\n hasFooterActions || component.footer?.length ? (\n <div\n data-slot=\"section-footer\"\n className={cn(\n 'flex flex-wrap items-center gap-3 border-t px-6 py-4',\n component.compact && 'px-4 py-3',\n )}\n >\n {hasFooterActions && (\n <ActionList\n actions={component.footerActions ?? []}\n context={context}\n className={actionsAlignmentClass(component.footerActionsAlignment) || undefined}\n />\n )}\n <MixedContent items={component.footer} name=\"section-footer-content\" />\n </div>\n ) : null\n\n if (component.aside) {\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n {...extraProps(component.extraAttributes, 'grid gap-6 lg:grid-cols-3')}\n >\n {header}\n <CollapsibleContent asChild>\n <div\n data-surface=\"section\"\n className={cn(\n 'bg-card rounded-xl border shadow-xs lg:col-span-2',\n component.secondary && 'bg-muted/40',\n )}\n >\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </div>\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n }\n\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n data-surface=\"section\"\n {...extraProps(\n component.extraAttributes,\n cn('bg-card rounded-xl border shadow-xs', component.secondary && 'bg-muted/40'),\n )}\n >\n {header}\n <CollapsibleContent>\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"section.js","names":[],"sources":["../../../src/components/layout/section.tsx"],"sourcesContent":["import type { SectionSchema } from '@alfakit/core'\nimport {\n actionsAlignmentClass,\n headingTag,\n readPersisted,\n schemaGapClass,\n writePersisted,\n} from '@alfakit/core'\nimport { ChevronDownIcon } from 'lucide-react'\nimport { createElement, useState } from 'react'\nimport { useT } from '../../context'\nimport { useHydrated } from '../../hooks/use-hydrated'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport { DeferredSchema } from './deferred'\nimport { MixedContent } from './mixed-content'\n\nexport function SectionComponent({ component }: { component: SectionSchema }) {\n const t = useT()\n const [choice, setChoice] = useState<boolean | null>(null)\n // Inside a form the section's actions run as field actions, so `$get` / `$set` work.\n const form = useOptionalFormStore()\n const pageContext = usePageActionContext()\n const fieldContext = useFieldActionContext(component)\n const context = form ? fieldContext : pageContext\n const persistKey = component.persistCollapsed\n const hydrated = useHydrated()\n // Read only after hydration, so the server render and hydration use the schema's default.\n const stored = hydrated && persistKey ? readPersisted(persistKey) : null\n const open = choice ?? (stored !== null ? stored !== 'collapsed' : !component.collapsed)\n const hasHeader =\n component.heading ||\n component.description ||\n component.icon ||\n component.headerActions?.length ||\n component.afterHeader?.length\n\n const setOpen = (next: boolean) => {\n setChoice(next)\n\n if (persistKey) writePersisted(persistKey, next ? 'expanded' : 'collapsed')\n }\n\n const content = (\n <DeferredSchema component={component}>\n {(loaded) => (\n <SchemaRenderer\n components={loaded.schema ?? []}\n columns={loaded.columns}\n className={schemaGapClass(loaded)}\n />\n )}\n </DeferredSchema>\n )\n\n const header = hasHeader ? (\n <div\n className={cn(\n 'flex flex-wrap items-start gap-3',\n component.aside ? 'lg:col-span-1' : 'border-b px-6 py-4',\n component.compact && !component.aside && 'px-4 py-3',\n )}\n >\n {component.icon && (\n <Icon name={component.icon} className=\"text-muted-foreground mt-0.5 size-5\" />\n )}\n <div className=\"min-w-0 flex-1 basis-48\">\n {component.heading &&\n createElement(\n headingTag(component.headingLevel),\n { className: 'text-base leading-6 font-semibold break-words' },\n component.heading,\n )}\n {component.description && (\n <p className=\"text-muted-foreground mt-1 text-sm break-words\">{component.description}</p>\n )}\n </div>\n {component.headerActions && component.headerActions.length > 0 && (\n <ActionList actions={component.headerActions} context={context} size=\"sm\" />\n )}\n <MixedContent items={component.afterHeader} name=\"section-after-header\" context={context} />\n {component.collapsible && (\n <CollapsibleTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('toggle_section', {}, 'Toggle section')}\n >\n <Icon\n name=\"alfakit::schemas.section.collapse-button\"\n fallback={ChevronDownIcon}\n className={cn('transition-transform', open && 'rotate-180')}\n />\n </Button>\n </CollapsibleTrigger>\n )}\n </div>\n ) : null\n\n const hasFooterActions = Boolean(component.footerActions?.length)\n const footer =\n hasFooterActions || component.footer?.length ? (\n <div\n data-slot=\"section-footer\"\n className={cn(\n 'flex flex-wrap items-center gap-3 border-t px-6 py-4',\n component.compact && 'px-4 py-3',\n )}\n >\n {hasFooterActions && (\n <ActionList\n actions={component.footerActions ?? []}\n context={context}\n className={actionsAlignmentClass(component.footerActionsAlignment) || undefined}\n />\n )}\n <MixedContent items={component.footer} name=\"section-footer-content\" context={context} />\n </div>\n ) : null\n\n if (component.aside) {\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n {...extraProps(component.extraAttributes, 'grid gap-6 lg:grid-cols-3')}\n >\n {header}\n <CollapsibleContent asChild>\n <div\n data-surface=\"section\"\n className={cn(\n 'bg-card rounded-xl border shadow-xs lg:col-span-2',\n component.secondary && 'bg-muted/40',\n )}\n >\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </div>\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n }\n\n return (\n <Collapsible open={!component.collapsible || open} onOpenChange={setOpen} asChild>\n <section\n data-slot=\"section\"\n data-surface=\"section\"\n {...extraProps(\n component.extraAttributes,\n cn('bg-card rounded-xl border shadow-xs', component.secondary && 'bg-muted/40'),\n )}\n >\n {header}\n <CollapsibleContent>\n <div className={cn('p-6', component.compact && 'p-4')}>{content}</div>\n {footer}\n </CollapsibleContent>\n </section>\n </Collapsible>\n )\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"notifications-menu.d.ts","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"mappings":";iBAmBiB;;;;;EAKf;;;;;;wBAOc,oBAAoB,YAAsB,yCAA2B,IAAA"}
1
+ {"version":3,"file":"notifications-menu.d.ts","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"mappings":";iBAuBiB;;;;;EAKf;;;;;;wBAOc,oBAAoB,YAAsB,yCAA2B,IAAA"}
@@ -8,10 +8,11 @@ import { Spinner } from "../ui/spinner.js";
8
8
  import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover.js";
9
9
  import { NotificationActionButton } from "./flash-toasts.js";
10
10
  import { ScrollArea } from "../ui/scroll-area.js";
11
- import { NotificationCenter, notificationTintStyles, onDatabaseNotificationsSent, onOpenDatabaseNotifications } from "@alfakit/core";
11
+ import { NotificationCenter, notificationTintStyles, onDatabaseNotificationsSent, onOpenDatabaseNotifications, pwaStore, startPwa, togglePushNotifications } from "@alfakit/core";
12
12
  import { useEffect, useMemo, useState } from "react";
13
13
  import { jsx, jsxs } from "react/jsx-runtime";
14
- import { BellIcon, CheckCheckIcon, Trash2Icon } from "lucide-react";
14
+ import { BellIcon, BellOffIcon, BellRingIcon, CheckCheckIcon, Trash2Icon } from "lucide-react";
15
+ import { toast } from "sonner";
15
16
  //#region src/components/notifications/notifications-menu.tsx
16
17
  /**
17
18
  * Bell menu for database notifications. It also opens when something calls
@@ -55,6 +56,19 @@ function NotificationsMenu({ variant = "topbar" } = {}) {
55
56
  }, [open, center]);
56
57
  useEffect(() => onOpenDatabaseNotifications(() => setOpen(true)), []);
57
58
  useEffect(() => center ? onDatabaseNotificationsSent(() => void center.load()) : void 0, [center]);
59
+ const pwa = useStore(pwaStore);
60
+ const kitPush = Boolean(panel.starterKit && panel.pwa?.push);
61
+ const pushToggle = kitPush && pwa.push !== "unsupported";
62
+ useEffect(() => {
63
+ if (kitPush) startPwa(panel.pwa);
64
+ }, [kitPush, panel.pwa]);
65
+ const togglePush = async () => {
66
+ const { message } = await togglePushNotifications();
67
+ const text = t(message.key, {}, message.fallback);
68
+ if (message.error) toast.error(text);
69
+ else toast.success(text);
70
+ };
71
+ const pushLabel = pwa.push === "subscribed" ? t("pwa.notifications.disable", {}, "Turn off notifications") : t("pwa.notifications.enable", {}, "Turn on notifications");
58
72
  if (!config || !center) return null;
59
73
  return /* @__PURE__ */ jsxs(Popover, {
60
74
  open,
@@ -110,21 +124,35 @@ function NotificationsMenu({ variant = "topbar" } = {}) {
110
124
  children: t("notifications", {}, "Notifications")
111
125
  }), /* @__PURE__ */ jsxs("div", {
112
126
  className: "flex items-center gap-1",
113
- children: [state.unreadCount > 0 && /* @__PURE__ */ jsx(Button, {
114
- type: "button",
115
- variant: "ghost",
116
- size: "icon-sm",
117
- title: t("mark_all_read", {}, "Mark all as read"),
118
- onClick: () => void center.markAllAsRead(),
119
- children: /* @__PURE__ */ jsx(CheckCheckIcon, {})
120
- }), state.notifications.length > 0 && /* @__PURE__ */ jsx(Button, {
121
- type: "button",
122
- variant: "ghost",
123
- size: "icon-sm",
124
- title: t("clear_notifications", {}, "Clear notifications"),
125
- onClick: () => void center.clear(),
126
- children: /* @__PURE__ */ jsx(Trash2Icon, {})
127
- })]
127
+ children: [
128
+ pushToggle && /* @__PURE__ */ jsx(Button, {
129
+ type: "button",
130
+ variant: "ghost",
131
+ size: "icon-sm",
132
+ "data-slot": "pwa-push",
133
+ disabled: pwa.pushBusy || pwa.push === "denied",
134
+ title: pushLabel,
135
+ "aria-label": pushLabel,
136
+ onClick: () => void togglePush(),
137
+ children: pwa.push === "subscribed" ? /* @__PURE__ */ jsx(BellOffIcon, {}) : /* @__PURE__ */ jsx(BellRingIcon, {})
138
+ }),
139
+ state.unreadCount > 0 && /* @__PURE__ */ jsx(Button, {
140
+ type: "button",
141
+ variant: "ghost",
142
+ size: "icon-sm",
143
+ title: t("mark_all_read", {}, "Mark all as read"),
144
+ onClick: () => void center.markAllAsRead(),
145
+ children: /* @__PURE__ */ jsx(CheckCheckIcon, {})
146
+ }),
147
+ state.notifications.length > 0 && /* @__PURE__ */ jsx(Button, {
148
+ type: "button",
149
+ variant: "ghost",
150
+ size: "icon-sm",
151
+ title: t("clear_notifications", {}, "Clear notifications"),
152
+ onClick: () => void center.clear(),
153
+ children: /* @__PURE__ */ jsx(Trash2Icon, {})
154
+ })
155
+ ]
128
156
  })]
129
157
  }), /* @__PURE__ */ jsxs(ScrollArea, {
130
158
  className: "max-h-96",
@@ -1 +1 @@
1
- {"version":3,"file":"notifications-menu.js","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"sourcesContent":["import {\n NotificationCenter,\n notificationTintStyles,\n onDatabaseNotificationsSent,\n onOpenDatabaseNotifications,\n} from '@alfakit/core'\nimport { BellIcon, CheckCheckIcon, Trash2Icon } from 'lucide-react'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useDirection, useFormatter, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cssColor, textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../ui/button'\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'\nimport { ScrollArea } from '../ui/scroll-area'\nimport { Spinner } from '../ui/spinner'\nimport { NotificationActionButton } from './flash-toasts'\n\nexport interface NotificationsMenuProps {\n /**\n * Where the trigger sits: `topbar` (an icon button, default) or `sidebar` (a full-width\n * row with a label, for layouts that move the bell into the sidebar).\n */\n variant?: 'topbar' | 'sidebar'\n}\n\n/**\n * Bell menu for database notifications. It also opens when something calls\n * `openDatabaseNotifications()` (or an action with `->openDatabaseNotifications()`).\n */\nexport function NotificationsMenu({ variant = 'topbar' }: NotificationsMenuProps = {}) {\n const t = useT()\n const direction = useDirection()\n const format = useFormatter()\n const panel = usePanel()\n const config = panel.databaseNotifications\n const endpoints = panel.endpoints\n\n const center = useMemo(\n () =>\n endpoints.notifications && endpoints.notificationsReadAll && endpoints.notificationsDestroyAll\n ? new NotificationCenter(\n {\n notifications: endpoints.notifications,\n notificationsReadAll: endpoints.notificationsReadAll,\n notificationsDestroyAll: endpoints.notificationsDestroyAll,\n },\n config?.unreadCount ?? 0,\n )\n : null,\n [\n endpoints.notifications,\n endpoints.notificationsReadAll,\n endpoints.notificationsDestroyAll,\n config?.unreadCount,\n ],\n )\n\n const [open, setOpen] = useState(false)\n const state = useStore(\n center?.store ?? {\n get: () => ({ notifications: [], unreadCount: 0, loading: false, hasMore: false, page: 0 }),\n subscribe: () => () => {},\n },\n )\n\n useEffect(() => {\n if (!center) return\n\n center.startPolling(config?.pollingInterval)\n\n return () => center.stopPolling()\n }, [center, config?.pollingInterval])\n\n useEffect(() => {\n if (open) void center?.load()\n }, [open, center])\n\n // `openDatabaseNotifications()`: opened from code or an action.\n useEffect(() => onOpenDatabaseNotifications(() => setOpen(true)), [])\n\n // Realtime notifications announce stored ones, so the unread count updates right away.\n useEffect(\n () => (center ? onDatabaseNotificationsSent(() => void center.load()) : undefined),\n [center],\n )\n\n if (!config || !center) {\n return null\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n {variant === 'sidebar' ? (\n // In a collapsed (icon) sidebar only the bell and its badge stay visible.\n <Button\n type=\"button\"\n variant=\"ghost\"\n className=\"relative w-full justify-start gap-2 group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0\"\n data-slot=\"notifications-trigger\"\n data-variant=\"sidebar\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n <span className=\"flex-1 text-start group-data-[collapsible=icon]:sr-only\">\n {t('notifications', {}, 'Notifications')}\n </span>\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground flex h-5 min-w-5 items-center justify-center rounded-full px-1 text-xs font-medium group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:-end-1 group-data-[collapsible=icon]:-top-1 group-data-[collapsible=icon]:h-4 group-data-[collapsible=icon]:min-w-4 group-data-[collapsible=icon]:text-[10px]\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n <span className=\"sr-only\"> {t('unread_notifications', {}, 'unread')}</span>\n </span>\n )}\n </Button>\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative\"\n aria-label={t('notifications', {}, 'Notifications')}\n data-slot=\"notifications-trigger\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground absolute -end-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n </span>\n )}\n </Button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align={variant === 'sidebar' ? 'start' : 'end'}\n side={variant === 'sidebar' ? (direction === 'rtl' ? 'left' : 'right') : 'bottom'}\n className=\"w-80 p-0 sm:w-96\"\n >\n <div className=\"flex items-center justify-between border-b px-3 py-2\">\n <span className=\"text-sm font-semibold\">{t('notifications', {}, 'Notifications')}</span>\n <div className=\"flex items-center gap-1\">\n {state.unreadCount > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('mark_all_read', {}, 'Mark all as read')}\n onClick={() => void center.markAllAsRead()}\n >\n <CheckCheckIcon />\n </Button>\n )}\n {state.notifications.length > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('clear_notifications', {}, 'Clear notifications')}\n onClick={() => void center.clear()}\n >\n <Trash2Icon />\n </Button>\n )}\n </div>\n </div>\n <ScrollArea className=\"max-h-96\">\n {state.loading && state.notifications.length === 0 ? (\n <div className=\"flex justify-center py-8\">\n <Spinner />\n </div>\n ) : state.notifications.length === 0 ? (\n <p className=\"text-muted-foreground px-3 py-8 text-center text-sm\">\n {t('no_notifications', {}, 'You have no notifications.')}\n </p>\n ) : (\n <ul className=\"divide-y\">\n {state.notifications.map((notification) => (\n <li\n key={notification.id}\n className={cn('flex gap-3 px-3 py-3', !notification.readAt && 'bg-primary/5')}\n style={\n notification.color\n ? notificationTintStyles(cssColor(notification.color)).item\n : undefined\n }\n >\n {notification.icon && (\n <Icon\n name={notification.icon}\n className={cn(\n 'mt-0.5 size-5 shrink-0',\n notification.iconColor && textColorClasses[notification.iconColor],\n )}\n />\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-medium\">{notification.title}</p>\n {!notification.readAt && (\n <button\n type=\"button\"\n className=\"focus-visible:ring-ring/50 -me-2 -mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full outline-none focus-visible:ring-[3px]\"\n aria-label={t('mark_as_read', {}, 'Mark as read')}\n onClick={() => void center.markAsRead(notification.id)}\n >\n <span className=\"bg-primary size-2 rounded-full\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n {notification.body && (\n <p\n className=\"text-muted-foreground text-sm\"\n dangerouslySetInnerHTML={{ __html: notification.body }}\n />\n )}\n {notification.date && (\n <time className=\"text-muted-foreground text-xs\">\n {format.relative(notification.date)}\n </time>\n )}\n {notification.actions && (\n <div className=\"flex flex-wrap gap-1\">\n {notification.actions.map((action) => (\n <NotificationActionButton\n key={action.name}\n action={action}\n onMarkAsRead={() => void center.markAsRead(notification.id)}\n onMarkAsUnread={() => void center.markAsUnread(notification.id)}\n />\n ))}\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n {state.hasMore && state.notifications.length > 0 && (\n <div className=\"flex justify-center border-t p-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={state.loading}\n onClick={() => void center.loadMore()}\n >\n {state.loading && <Spinner />}\n {t('load_more', {}, 'Load more')}\n </Button>\n </div>\n )}\n </ScrollArea>\n </PopoverContent>\n </Popover>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"notifications-menu.js","names":[],"sources":["../../../src/components/notifications/notifications-menu.tsx"],"sourcesContent":["import {\n NotificationCenter,\n notificationTintStyles,\n onDatabaseNotificationsSent,\n onOpenDatabaseNotifications,\n pwaStore,\n startPwa,\n togglePushNotifications,\n} from '@alfakit/core'\nimport { BellIcon, BellOffIcon, BellRingIcon, CheckCheckIcon, Trash2Icon } from 'lucide-react'\nimport { useEffect, useMemo, useState } from 'react'\nimport { toast } from 'sonner'\nimport { useDirection, useFormatter, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cssColor, textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../ui/button'\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'\nimport { ScrollArea } from '../ui/scroll-area'\nimport { Spinner } from '../ui/spinner'\nimport { NotificationActionButton } from './flash-toasts'\n\nexport interface NotificationsMenuProps {\n /**\n * Where the trigger sits: `topbar` (an icon button, default) or `sidebar` (a full-width\n * row with a label, for layouts that move the bell into the sidebar).\n */\n variant?: 'topbar' | 'sidebar'\n}\n\n/**\n * Bell menu for database notifications. It also opens when something calls\n * `openDatabaseNotifications()` (or an action with `->openDatabaseNotifications()`).\n */\nexport function NotificationsMenu({ variant = 'topbar' }: NotificationsMenuProps = {}) {\n const t = useT()\n const direction = useDirection()\n const format = useFormatter()\n const panel = usePanel()\n const config = panel.databaseNotifications\n const endpoints = panel.endpoints\n\n const center = useMemo(\n () =>\n endpoints.notifications && endpoints.notificationsReadAll && endpoints.notificationsDestroyAll\n ? new NotificationCenter(\n {\n notifications: endpoints.notifications,\n notificationsReadAll: endpoints.notificationsReadAll,\n notificationsDestroyAll: endpoints.notificationsDestroyAll,\n },\n config?.unreadCount ?? 0,\n )\n : null,\n [\n endpoints.notifications,\n endpoints.notificationsReadAll,\n endpoints.notificationsDestroyAll,\n config?.unreadCount,\n ],\n )\n\n const [open, setOpen] = useState(false)\n const state = useStore(\n center?.store ?? {\n get: () => ({ notifications: [], unreadCount: 0, loading: false, hasMore: false, page: 0 }),\n subscribe: () => () => {},\n },\n )\n\n useEffect(() => {\n if (!center) return\n\n center.startPolling(config?.pollingInterval)\n\n return () => center.stopPolling()\n }, [center, config?.pollingInterval])\n\n useEffect(() => {\n if (open) void center?.load()\n }, [open, center])\n\n // `openDatabaseNotifications()`: opened from code or an action.\n useEffect(() => onOpenDatabaseNotifications(() => setOpen(true)), [])\n\n // Realtime notifications announce stored ones, so the unread count updates right away.\n useEffect(\n () => (center ? onDatabaseNotificationsSent(() => void center.load()) : undefined),\n [center],\n )\n\n // `pwa(push: true)` in a starter kit: the app's user menu has no AlfaKit entries, so Web\n // Push is turned on or off from the bell's menu.\n const pwa = useStore(pwaStore)\n const kitPush = Boolean(panel.starterKit && panel.pwa?.push)\n const pushToggle = kitPush && pwa.push !== 'unsupported'\n\n useEffect(() => {\n if (kitPush) startPwa(panel.pwa)\n }, [kitPush, panel.pwa])\n\n const togglePush = async () => {\n const { message } = await togglePushNotifications()\n const text = t(message.key, {}, message.fallback)\n\n if (message.error) toast.error(text)\n else toast.success(text)\n }\n\n const pushLabel =\n pwa.push === 'subscribed'\n ? t('pwa.notifications.disable', {}, 'Turn off notifications')\n : t('pwa.notifications.enable', {}, 'Turn on notifications')\n\n if (!config || !center) {\n return null\n }\n\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n {variant === 'sidebar' ? (\n // In a collapsed (icon) sidebar only the bell and its badge stay visible.\n <Button\n type=\"button\"\n variant=\"ghost\"\n className=\"relative w-full justify-start gap-2 group-data-[collapsible=icon]:size-8 group-data-[collapsible=icon]:justify-center group-data-[collapsible=icon]:p-0\"\n data-slot=\"notifications-trigger\"\n data-variant=\"sidebar\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n <span className=\"flex-1 text-start group-data-[collapsible=icon]:sr-only\">\n {t('notifications', {}, 'Notifications')}\n </span>\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground flex h-5 min-w-5 items-center justify-center rounded-full px-1 text-xs font-medium group-data-[collapsible=icon]:absolute group-data-[collapsible=icon]:-end-1 group-data-[collapsible=icon]:-top-1 group-data-[collapsible=icon]:h-4 group-data-[collapsible=icon]:min-w-4 group-data-[collapsible=icon]:text-[10px]\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n <span className=\"sr-only\"> {t('unread_notifications', {}, 'unread')}</span>\n </span>\n )}\n </Button>\n ) : (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"relative\"\n aria-label={t('notifications', {}, 'Notifications')}\n data-slot=\"notifications-trigger\"\n >\n <Icon\n name=\"alfakit::panels.topbar.open-database-notifications-button\"\n fallback={BellIcon}\n />\n {state.unreadCount > 0 && (\n <span className=\"bg-primary text-primary-foreground absolute -end-0.5 -top-0.5 flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-medium\">\n {state.unreadCount > 99 ? '99+' : state.unreadCount}\n </span>\n )}\n </Button>\n )}\n </PopoverTrigger>\n <PopoverContent\n align={variant === 'sidebar' ? 'start' : 'end'}\n side={variant === 'sidebar' ? (direction === 'rtl' ? 'left' : 'right') : 'bottom'}\n className=\"w-80 p-0 sm:w-96\"\n >\n <div className=\"flex items-center justify-between border-b px-3 py-2\">\n <span className=\"text-sm font-semibold\">{t('notifications', {}, 'Notifications')}</span>\n <div className=\"flex items-center gap-1\">\n {pushToggle && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n data-slot=\"pwa-push\"\n disabled={pwa.pushBusy || pwa.push === 'denied'}\n title={pushLabel}\n aria-label={pushLabel}\n onClick={() => void togglePush()}\n >\n {pwa.push === 'subscribed' ? <BellOffIcon /> : <BellRingIcon />}\n </Button>\n )}\n {state.unreadCount > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('mark_all_read', {}, 'Mark all as read')}\n onClick={() => void center.markAllAsRead()}\n >\n <CheckCheckIcon />\n </Button>\n )}\n {state.notifications.length > 0 && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n title={t('clear_notifications', {}, 'Clear notifications')}\n onClick={() => void center.clear()}\n >\n <Trash2Icon />\n </Button>\n )}\n </div>\n </div>\n <ScrollArea className=\"max-h-96\">\n {state.loading && state.notifications.length === 0 ? (\n <div className=\"flex justify-center py-8\">\n <Spinner />\n </div>\n ) : state.notifications.length === 0 ? (\n <p className=\"text-muted-foreground px-3 py-8 text-center text-sm\">\n {t('no_notifications', {}, 'You have no notifications.')}\n </p>\n ) : (\n <ul className=\"divide-y\">\n {state.notifications.map((notification) => (\n <li\n key={notification.id}\n className={cn('flex gap-3 px-3 py-3', !notification.readAt && 'bg-primary/5')}\n style={\n notification.color\n ? notificationTintStyles(cssColor(notification.color)).item\n : undefined\n }\n >\n {notification.icon && (\n <Icon\n name={notification.icon}\n className={cn(\n 'mt-0.5 size-5 shrink-0',\n notification.iconColor && textColorClasses[notification.iconColor],\n )}\n />\n )}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <div className=\"flex items-start justify-between gap-2\">\n <p className=\"text-sm font-medium\">{notification.title}</p>\n {!notification.readAt && (\n <button\n type=\"button\"\n className=\"focus-visible:ring-ring/50 -me-2 -mt-0.5 flex size-6 shrink-0 items-center justify-center rounded-full outline-none focus-visible:ring-[3px]\"\n aria-label={t('mark_as_read', {}, 'Mark as read')}\n onClick={() => void center.markAsRead(notification.id)}\n >\n <span className=\"bg-primary size-2 rounded-full\" aria-hidden=\"true\" />\n </button>\n )}\n </div>\n {notification.body && (\n <p\n className=\"text-muted-foreground text-sm\"\n dangerouslySetInnerHTML={{ __html: notification.body }}\n />\n )}\n {notification.date && (\n <time className=\"text-muted-foreground text-xs\">\n {format.relative(notification.date)}\n </time>\n )}\n {notification.actions && (\n <div className=\"flex flex-wrap gap-1\">\n {notification.actions.map((action) => (\n <NotificationActionButton\n key={action.name}\n action={action}\n onMarkAsRead={() => void center.markAsRead(notification.id)}\n onMarkAsUnread={() => void center.markAsUnread(notification.id)}\n />\n ))}\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n {state.hasMore && state.notifications.length > 0 && (\n <div className=\"flex justify-center border-t p-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={state.loading}\n onClick={() => void center.loadMore()}\n >\n {state.loading && <Spinner />}\n {t('load_more', {}, 'Load more')}\n </Button>\n </div>\n )}\n </ScrollArea>\n </PopoverContent>\n </Popover>\n )\n}\n"],"mappings":"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"}
@@ -80,7 +80,7 @@ function NavSubItem({ item }) {
80
80
  ]
81
81
  })
82
82
  })
83
- }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: item.children.map((child) => /* @__PURE__ */ jsx(NavSubItem, { item: child }, child.label)) }) })]
83
+ }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: item.children.map((child) => /* @__PURE__ */ jsx(NavSubItem, { item: child }, child.url ?? child.label)) }) })]
84
84
  }) });
85
85
  return /* @__PURE__ */ jsx(SidebarMenuSubItem, { children: /* @__PURE__ */ jsx(SidebarMenuSubButton, {
86
86
  asChild: true,
@@ -113,7 +113,7 @@ function NavItem({ item }) {
113
113
  /* @__PURE__ */ jsx(ChevronRightIcon, { className: "ms-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/collapsible:rotate-90" })
114
114
  ]
115
115
  })
116
- }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: item.children.map((child) => /* @__PURE__ */ jsx(NavSubItem, { item: child }, child.label)) }) })] })
116
+ }), /* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: item.children.map((child) => /* @__PURE__ */ jsx(NavSubItem, { item: child }, child.url ?? child.label)) }) })] })
117
117
  });
118
118
  return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, {
119
119
  asChild: true,
@@ -181,7 +181,7 @@ function NavGroup({ group }) {
181
181
  const flyout = Boolean(group.label && group.icon);
182
182
  const content = /* @__PURE__ */ jsx(SidebarGroupContent, {
183
183
  className: cn(flyout && "group-data-[collapsible=icon]:hidden"),
184
- children: /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((item) => /* @__PURE__ */ jsx(NavItem, { item }, item.label)) })
184
+ children: /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((item) => /* @__PURE__ */ jsx(NavItem, { item }, item.url ?? item.label)) })
185
185
  });
186
186
  const flyoutMenu = flyout ? /* @__PURE__ */ jsx(NavGroupFlyout, { group }) : null;
187
187
  if (!group.label) return /* @__PURE__ */ jsx(SidebarGroup, {