@alfakit/react 0.1.8 → 0.1.9
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 +8 -0
- package/dist/components/actions/action-modals.js +1 -1
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/form/form-block.d.ts.map +1 -1
- package/dist/components/form/form-block.js +6 -3
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/panel/app-sidebar.js +3 -3
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/bottom-nav.js +2 -2
- package/dist/components/panel/bottom-nav.js.map +1 -1
- package/dist/components/panel/keyboard-shortcuts.js +1 -1
- package/dist/components/panel/keyboard-shortcuts.js.map +1 -1
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/layouts/top-nav-layout.js +3 -3
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/standalone/alfakit-component.js +17 -7
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone.css +10 -6
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +7 -2
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/package.json +2 -2
- package/src/components/actions/action-modals.tsx +2 -0
- package/src/components/form/form-block.tsx +15 -2
- package/src/components/panel/app-sidebar.tsx +3 -3
- package/src/components/panel/bottom-nav.tsx +2 -2
- package/src/components/panel/keyboard-shortcuts.tsx +1 -1
- package/src/components/ui/tooltip.tsx +10 -1
- package/src/layouts/top-nav-layout.tsx +3 -3
- package/src/standalone/alfakit-component.tsx +11 -1
- package/src/starter-kit/starter-kit.tsx +9 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @alfakit/react
|
|
2
2
|
|
|
3
|
+
## 0.1.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Row action modals work on every list tab; starter kits: the debug panel, "G then D" to the app dashboard and badge contrast; two standalone forms on a page get their own field ids; an `<AlfakitComponent>` in a panel page posts to its own endpoint; tooltips keep the button's `data-slot`; navigation items keyed by URL; modal headers leave room for the close button.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @alfakit/core@0.1.9
|
|
10
|
+
|
|
3
11
|
## 0.1.8
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
|
@@ -345,7 +345,7 @@ function ActionModal({ modal, hidden = false }) {
|
|
|
345
345
|
onEscapeKeyDown: (event) => !schema.closeByEscaping && event.preventDefault(),
|
|
346
346
|
children: [
|
|
347
347
|
/* @__PURE__ */ jsx(DialogHeader, {
|
|
348
|
-
className: cn(schema.stickyHeader && "bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4"),
|
|
348
|
+
className: cn(schema.stickyHeader && "bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4", schema.closeButton !== false && (schema.stickyHeader ? "pe-12" : "pe-6")),
|
|
349
349
|
children: header
|
|
350
350
|
}),
|
|
351
351
|
(schema.content || form || schema.contentFooter || schema.passwordConfirmation) && body,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-modals.js","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"sourcesContent":["import type { FormState, OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n actionFormParams,\n actionPath,\n confirmModalClose,\n formIdPrefix,\n FormStore,\n guardModalUnload,\n isModalHiddenByChild,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider } from '../form/form-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport { ActionList } from './action-list'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\n// Module-level so the snapshot stays stable (a new object per read re-renders forever).\nconst EMPTY_PROGRESS = { step: 0, count: 0 }\nconst NO_PROGRESS = { get: () => EMPTY_PROGRESS, subscribe: () => () => {} }\nconst NO_FORM = { get: () => null, subscribe: () => () => {} }\n\n/** Renders every open action modal from the runner store. Mount once per app. */\nexport function ActionModals() {\n const runner = useActionRunner()\n const { modals } = useStore(runner.store)\n\n // Notifications from JSON responses (a modal `mountUsing()` cancelled, field actions).\n useEffect(() => {\n runner.setNotificationHandler((notifications) => notifications.forEach((n) => showToast(n)))\n\n return () => runner.setNotificationHandler(undefined)\n }, [runner])\n\n return (\n <>\n {modals.map((modal, index) => (\n <ActionModal\n key={`${actionPath(modal.action, modal.context)}-${modal.context.record ?? ''}`}\n modal={modal}\n hidden={isModalHiddenByChild(modals, index)}\n />\n ))}\n </>\n )\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\n/**\n * Put PHP attributes (`extraModalOverlayAttributes()`) on an element the renderer doesn't\n * create itself (the dialog's backdrop). `class` is added to the element's classes.\n */\nfunction applyAttributes(\n element: Element | null | undefined,\n attributes: Record<string, unknown> | undefined,\n) {\n if (!element || !attributes) return\n\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class' || name === 'className') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nfunction ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boolean }) {\n const runner = useActionRunner()\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n const { action, context, modal: schema } = modal\n const submittedRef = useRef(false)\n\n const form = useMemo(() => {\n if (!schema.form) {\n return null\n }\n\n return new FormStore(schema.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n // Own field ids: the modal opens over a form whose fields may share names.\n idPrefix: formIdPrefix(actionFormComponent(action, context)),\n record: context.record ?? null,\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 params: actionFormParams(context),\n })\n }, [schema.form, context, action, panel.endpoints])\n\n useEffect(() => {\n form?.setErrors(modal.errors)\n }, [form, modal.errors])\n\n useEffect(() => () => form?.destroy(), [form])\n\n // \"Create & create another\": back to the `autofocus()` field of the fresh form.\n const refocus = modal.refocus\n\n useEffect(() => {\n if (!refocus || !form) return\n\n return scheduleAutofocus(\n () => document.getElementById(`action-form-${action.name}`),\n form.state.components,\n )\n }, [refocus, form, action.name])\n\n // Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n // (`fillData()`).\n useEffect(\n () => (form ? runner.registerModalForm(action, context, form) : undefined),\n [runner, form, action, context],\n )\n\n // `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\n // A callback ref: the portal renders the content after the first commit.\n const [content, setContent] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (hidden) return\n\n const overlay = content?.previousElementSibling\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay')) {\n applyAttributes(overlay, schema.overlayAttributes)\n }\n }, [schema.overlayAttributes, hidden, content])\n\n // Live updates replace the form's components (fields shown, options changed).\n const formState = useStore<FormState | null>(form?.store ?? NO_FORM)\n\n // A wizard form reports its step, so the submit button waits for the last one.\n const progressStore = useMemo(() => (form ? wizardProgressStore(form) : null), [form])\n const progress = useStore(progressStore ?? NO_PROGRESS)\n const submitVisible = isModalSubmitVisible(schema, progressStore ? progress : null)\n\n const [password, setPassword] = useState('')\n const passwordId = useId()\n const passwordError = modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0]\n\n const submit = (createAnother = false) => {\n submittedRef.current = true\n\n // A click that blurred a live field started a refresh: send the state it returns.\n if (form) {\n form.whenSettled(() => run(createAnother))\n } else {\n run(createAnother)\n }\n }\n\n const run = (createAnother: boolean) => {\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.passwordConfirmation ? { password } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n }\n\n const footerContext = useMemo(() => nestedActionContext(context, action), [context, action])\n\n // Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\n const close = () => runner.cancelModal(action.name)\n const unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n // Cancel, Escape, clicking away and the close button ask first when the form changed.\n const requestClose = () => {\n if (confirmModalClose(schema, form, unsavedMessage)) close()\n }\n\n useEffect(() => guardModalUnload(schema, form, unsavedMessage), [schema, form, unsavedMessage])\n const title = schema.heading ?? action.label ?? t('actions', {}, 'Actions')\n\n const header = (\n <div\n className={cn(\n 'flex gap-4',\n schema.alignment === 'center' && 'flex-col items-center text-center',\n )}\n >\n {schema.icon && (\n <div\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )}\n >\n <Icon name={schema.icon} className=\"size-5\" />\n </div>\n )}\n <div className=\"flex flex-col gap-1.5\">\n {/* Every dialog needs an accessible name: without a heading it stays visually hidden. */}\n {schema.slideOver ? (\n <SheetTitle className={cn(!schema.heading && 'sr-only')}>{title}</SheetTitle>\n ) : (\n <DialogTitle className={cn(!schema.heading && 'sr-only')}>{title}</DialogTitle>\n )}\n {schema.description ? (\n schema.slideOver ? (\n <SheetDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n ) : (\n <DialogDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n )\n ) : schema.slideOver ? (\n <SheetDescription className=\"sr-only\">{title}</SheetDescription>\n ) : (\n <DialogDescription className=\"sr-only\">{title}</DialogDescription>\n )}\n </div>\n </div>\n )\n\n const body = (\n <div className=\"flex flex-col gap-6\">\n {schema.content && <SchemaRenderer components={schema.content} />}\n {form && <Slot name=\"modal.schema.before\" data={{ action: action.name }} />}\n {form && (\n <FormProvider store={form}>\n <form\n id={`action-form-${action.name}`}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n >\n <SchemaRenderer\n components={formState?.components ?? form.state.components}\n columns={formState?.columns ?? form.state.columns}\n />\n </form>\n </FormProvider>\n )}\n {form && <Slot name=\"modal.schema.after\" data={{ action: action.name }} />}\n {schema.contentFooter && <SchemaRenderer components={schema.contentFooter} />}\n {schema.passwordConfirmation && (\n <div data-slot=\"password-confirmation\" className=\"flex flex-col gap-2\">\n <Label htmlFor={passwordId}>{t('current_password', {}, 'Current password')}</Label>\n <Input\n id={passwordId}\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n aria-invalid={passwordError ? true : undefined}\n aria-describedby={passwordError ? `${passwordId}-error` : `${passwordId}-hint`}\n onChange={(event) => setPassword(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n submit()\n }\n }}\n />\n {passwordError ? (\n <p id={`${passwordId}-error`} className=\"text-destructive text-sm\">\n {passwordError}\n </p>\n ) : (\n <p id={`${passwordId}-hint`} className=\"text-muted-foreground text-sm\">\n {t('confirm_password', {}, 'Confirm your password to continue')}\n </p>\n )}\n </div>\n )}\n </div>\n )\n\n const footerAlignment = modalFooterAlignment(schema)\n const footer = (schema.submitLabel ||\n schema.cancelLabel ||\n schema.extraFooterActions?.length) && (\n <div\n className={cn(\n 'flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center',\n FOOTER_JUSTIFY[footerAlignment],\n )}\n >\n {schema.extraFooterActions && (\n <ActionList\n actions={schema.extraFooterActions}\n context={footerContext}\n className={footerAlignment === 'end' ? 'sm:me-auto' : undefined}\n />\n )}\n {schema.cancelLabel && (\n <Button\n type=\"button\"\n variant=\"outline\"\n className={\n schema.cancelColor\n ? colorClass(outlineButtonColorClasses, schema.cancelColor)\n : undefined\n }\n onClick={requestClose}\n disabled={modal.processing}\n >\n {schema.cancelLabel}\n </Button>\n )}\n {schema.createAnotherLabel && schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n className={\n schema.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.createAnotherColor)\n : undefined\n }\n onClick={() => submit(true)}\n disabled={modal.processing}\n >\n {schema.createAnotherIcon && <Icon name={schema.createAnotherIcon} />}\n {schema.createAnotherLabel}\n </Button>\n )}\n {schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n className={colorClass(solidButtonColorClasses, schema.submitColor, 'primary')}\n onClick={() => submit()}\n disabled={modal.processing}\n autoFocus={schema.autofocus && !form}\n >\n {modal.processing ? <Spinner /> : schema.submitIcon && <Icon name={schema.submitIcon} />}\n {schema.submitLabel}\n </Button>\n )}\n </div>\n )\n\n const onOpenChange = (open: boolean) => {\n if (!open && !modal.processing) {\n requestClose()\n }\n }\n\n // `modalClickThrough()`: a non-modal dialog has no backdrop and leaves the page usable.\n const isModal = !schema.clickThrough\n\n if (schema.slideOver) {\n return (\n <Sheet open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <SheetContent\n ref={setContent}\n side={slideOverSide(schema.slideOverPosition, direction)}\n {...extraProps(\n schema.windowAttributes,\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <SheetHeader className=\"border-b\">{header}</SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n {footer && (\n <SheetFooter\n className={cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')}\n >\n {footer}\n </SheetFooter>\n )}\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <DialogContent\n ref={setContent}\n {...extraProps(\n schema.windowAttributes,\n cn('max-h-[calc(100vh-2rem)] overflow-y-auto', WIDTHS[schema.width] ?? 'sm:max-w-lg'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <DialogHeader\n className={cn(\n // Pinned to the top of the scrolling dialog, edge to edge.\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n )}\n >\n {header}\n </DialogHeader>\n {(schema.content || form || schema.contentFooter || schema.passwordConfirmation) && body}\n {footer && (\n <DialogFooter\n className={cn(\n // Pinned to the bottom of the scrolling dialog, edge to edge.\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )}\n >\n {footer}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"action-modals.js","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"sourcesContent":["import type { FormState, OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n actionFormParams,\n actionPath,\n confirmModalClose,\n formIdPrefix,\n FormStore,\n guardModalUnload,\n isModalHiddenByChild,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider } from '../form/form-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport { ActionList } from './action-list'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\n// Module-level so the snapshot stays stable (a new object per read re-renders forever).\nconst EMPTY_PROGRESS = { step: 0, count: 0 }\nconst NO_PROGRESS = { get: () => EMPTY_PROGRESS, subscribe: () => () => {} }\nconst NO_FORM = { get: () => null, subscribe: () => () => {} }\n\n/** Renders every open action modal from the runner store. Mount once per app. */\nexport function ActionModals() {\n const runner = useActionRunner()\n const { modals } = useStore(runner.store)\n\n // Notifications from JSON responses (a modal `mountUsing()` cancelled, field actions).\n useEffect(() => {\n runner.setNotificationHandler((notifications) => notifications.forEach((n) => showToast(n)))\n\n return () => runner.setNotificationHandler(undefined)\n }, [runner])\n\n return (\n <>\n {modals.map((modal, index) => (\n <ActionModal\n key={`${actionPath(modal.action, modal.context)}-${modal.context.record ?? ''}`}\n modal={modal}\n hidden={isModalHiddenByChild(modals, index)}\n />\n ))}\n </>\n )\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\n/**\n * Put PHP attributes (`extraModalOverlayAttributes()`) on an element the renderer doesn't\n * create itself (the dialog's backdrop). `class` is added to the element's classes.\n */\nfunction applyAttributes(\n element: Element | null | undefined,\n attributes: Record<string, unknown> | undefined,\n) {\n if (!element || !attributes) return\n\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class' || name === 'className') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nfunction ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boolean }) {\n const runner = useActionRunner()\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n const { action, context, modal: schema } = modal\n const submittedRef = useRef(false)\n\n const form = useMemo(() => {\n if (!schema.form) {\n return null\n }\n\n return new FormStore(schema.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n // Own field ids: the modal opens over a form whose fields may share names.\n idPrefix: formIdPrefix(actionFormComponent(action, context)),\n record: context.record ?? null,\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 params: actionFormParams(context),\n })\n }, [schema.form, context, action, panel.endpoints])\n\n useEffect(() => {\n form?.setErrors(modal.errors)\n }, [form, modal.errors])\n\n useEffect(() => () => form?.destroy(), [form])\n\n // \"Create & create another\": back to the `autofocus()` field of the fresh form.\n const refocus = modal.refocus\n\n useEffect(() => {\n if (!refocus || !form) return\n\n return scheduleAutofocus(\n () => document.getElementById(`action-form-${action.name}`),\n form.state.components,\n )\n }, [refocus, form, action.name])\n\n // Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n // (`fillData()`).\n useEffect(\n () => (form ? runner.registerModalForm(action, context, form) : undefined),\n [runner, form, action, context],\n )\n\n // `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\n // A callback ref: the portal renders the content after the first commit.\n const [content, setContent] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (hidden) return\n\n const overlay = content?.previousElementSibling\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay')) {\n applyAttributes(overlay, schema.overlayAttributes)\n }\n }, [schema.overlayAttributes, hidden, content])\n\n // Live updates replace the form's components (fields shown, options changed).\n const formState = useStore<FormState | null>(form?.store ?? NO_FORM)\n\n // A wizard form reports its step, so the submit button waits for the last one.\n const progressStore = useMemo(() => (form ? wizardProgressStore(form) : null), [form])\n const progress = useStore(progressStore ?? NO_PROGRESS)\n const submitVisible = isModalSubmitVisible(schema, progressStore ? progress : null)\n\n const [password, setPassword] = useState('')\n const passwordId = useId()\n const passwordError = modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0]\n\n const submit = (createAnother = false) => {\n submittedRef.current = true\n\n // A click that blurred a live field started a refresh: send the state it returns.\n if (form) {\n form.whenSettled(() => run(createAnother))\n } else {\n run(createAnother)\n }\n }\n\n const run = (createAnother: boolean) => {\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.passwordConfirmation ? { password } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n }\n\n const footerContext = useMemo(() => nestedActionContext(context, action), [context, action])\n\n // Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\n const close = () => runner.cancelModal(action.name)\n const unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n // Cancel, Escape, clicking away and the close button ask first when the form changed.\n const requestClose = () => {\n if (confirmModalClose(schema, form, unsavedMessage)) close()\n }\n\n useEffect(() => guardModalUnload(schema, form, unsavedMessage), [schema, form, unsavedMessage])\n const title = schema.heading ?? action.label ?? t('actions', {}, 'Actions')\n\n const header = (\n <div\n className={cn(\n 'flex gap-4',\n schema.alignment === 'center' && 'flex-col items-center text-center',\n )}\n >\n {schema.icon && (\n <div\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )}\n >\n <Icon name={schema.icon} className=\"size-5\" />\n </div>\n )}\n <div className=\"flex flex-col gap-1.5\">\n {/* Every dialog needs an accessible name: without a heading it stays visually hidden. */}\n {schema.slideOver ? (\n <SheetTitle className={cn(!schema.heading && 'sr-only')}>{title}</SheetTitle>\n ) : (\n <DialogTitle className={cn(!schema.heading && 'sr-only')}>{title}</DialogTitle>\n )}\n {schema.description ? (\n schema.slideOver ? (\n <SheetDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n ) : (\n <DialogDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n )\n ) : schema.slideOver ? (\n <SheetDescription className=\"sr-only\">{title}</SheetDescription>\n ) : (\n <DialogDescription className=\"sr-only\">{title}</DialogDescription>\n )}\n </div>\n </div>\n )\n\n const body = (\n <div className=\"flex flex-col gap-6\">\n {schema.content && <SchemaRenderer components={schema.content} />}\n {form && <Slot name=\"modal.schema.before\" data={{ action: action.name }} />}\n {form && (\n <FormProvider store={form}>\n <form\n id={`action-form-${action.name}`}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n >\n <SchemaRenderer\n components={formState?.components ?? form.state.components}\n columns={formState?.columns ?? form.state.columns}\n />\n </form>\n </FormProvider>\n )}\n {form && <Slot name=\"modal.schema.after\" data={{ action: action.name }} />}\n {schema.contentFooter && <SchemaRenderer components={schema.contentFooter} />}\n {schema.passwordConfirmation && (\n <div data-slot=\"password-confirmation\" className=\"flex flex-col gap-2\">\n <Label htmlFor={passwordId}>{t('current_password', {}, 'Current password')}</Label>\n <Input\n id={passwordId}\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n aria-invalid={passwordError ? true : undefined}\n aria-describedby={passwordError ? `${passwordId}-error` : `${passwordId}-hint`}\n onChange={(event) => setPassword(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n submit()\n }\n }}\n />\n {passwordError ? (\n <p id={`${passwordId}-error`} className=\"text-destructive text-sm\">\n {passwordError}\n </p>\n ) : (\n <p id={`${passwordId}-hint`} className=\"text-muted-foreground text-sm\">\n {t('confirm_password', {}, 'Confirm your password to continue')}\n </p>\n )}\n </div>\n )}\n </div>\n )\n\n const footerAlignment = modalFooterAlignment(schema)\n const footer = (schema.submitLabel ||\n schema.cancelLabel ||\n schema.extraFooterActions?.length) && (\n <div\n className={cn(\n 'flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center',\n FOOTER_JUSTIFY[footerAlignment],\n )}\n >\n {schema.extraFooterActions && (\n <ActionList\n actions={schema.extraFooterActions}\n context={footerContext}\n className={footerAlignment === 'end' ? 'sm:me-auto' : undefined}\n />\n )}\n {schema.cancelLabel && (\n <Button\n type=\"button\"\n variant=\"outline\"\n className={\n schema.cancelColor\n ? colorClass(outlineButtonColorClasses, schema.cancelColor)\n : undefined\n }\n onClick={requestClose}\n disabled={modal.processing}\n >\n {schema.cancelLabel}\n </Button>\n )}\n {schema.createAnotherLabel && schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n className={\n schema.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.createAnotherColor)\n : undefined\n }\n onClick={() => submit(true)}\n disabled={modal.processing}\n >\n {schema.createAnotherIcon && <Icon name={schema.createAnotherIcon} />}\n {schema.createAnotherLabel}\n </Button>\n )}\n {schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n className={colorClass(solidButtonColorClasses, schema.submitColor, 'primary')}\n onClick={() => submit()}\n disabled={modal.processing}\n autoFocus={schema.autofocus && !form}\n >\n {modal.processing ? <Spinner /> : schema.submitIcon && <Icon name={schema.submitIcon} />}\n {schema.submitLabel}\n </Button>\n )}\n </div>\n )\n\n const onOpenChange = (open: boolean) => {\n if (!open && !modal.processing) {\n requestClose()\n }\n }\n\n // `modalClickThrough()`: a non-modal dialog has no backdrop and leaves the page usable.\n const isModal = !schema.clickThrough\n\n if (schema.slideOver) {\n return (\n <Sheet open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <SheetContent\n ref={setContent}\n side={slideOverSide(schema.slideOverPosition, direction)}\n {...extraProps(\n schema.windowAttributes,\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <SheetHeader className=\"border-b\">{header}</SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n {footer && (\n <SheetFooter\n className={cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')}\n >\n {footer}\n </SheetFooter>\n )}\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <DialogContent\n ref={setContent}\n {...extraProps(\n schema.windowAttributes,\n cn('max-h-[calc(100vh-2rem)] overflow-y-auto', WIDTHS[schema.width] ?? 'sm:max-w-lg'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <DialogHeader\n className={cn(\n // Pinned to the top of the scrolling dialog, edge to edge.\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n // Room for the close button (X): a long heading would run under it.\n schema.closeButton !== false && (schema.stickyHeader ? 'pe-12' : 'pe-6'),\n )}\n >\n {header}\n </DialogHeader>\n {(schema.content || form || schema.contentFooter || schema.passwordConfirmation) && body}\n {footer && (\n <DialogFooter\n className={cn(\n // Pinned to the bottom of the scrolling dialog, edge to edge.\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )}\n >\n {footer}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-block.d.ts","names":["FormBlock","FormBlockSchema"],"sources":["../../../src/components/form/form-block.tsx"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"form-block.d.ts","names":["FormBlock","FormBlockSchema"],"sources":["../../../src/components/form/form-block.tsx"],"mappings":";;;;;;iBAuCgBA,cAAY;EAAW,OAAOC;oBAAiB,IAAA"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { useActionRunner, useAlfakitProps, usePageSchema, usePanel, useT } from "../../context.js";
|
|
1
|
+
import { useActionRunner, useAlfakitProps, usePageSchema, usePanel, useStandalone, useT } from "../../context.js";
|
|
2
2
|
import { useStore } from "../../hooks/use-store.js";
|
|
3
3
|
import { ActionList } from "../actions/action-list.js";
|
|
4
4
|
import { FormProvider } from "./form-context.js";
|
|
5
5
|
import { SchemaRenderer } from "../schema/schema-renderer.js";
|
|
6
6
|
import { showToast } from "../notifications/flash-toasts.js";
|
|
7
|
-
import { FormStore, applyFormRefresh, formActionsVisible, formIdentity, formRefreshFor, isImplicitSubmit, isLeavingVisit, pageFlash, pageFormName, registerConflictForm, registerFormSubmitter, registerPageForm, resolvePartialProps, shouldResyncForm, wizardProgressStore } from "@alfakit/core";
|
|
7
|
+
import { FormStore, applyFormRefresh, formActionsVisible, formIdPrefix, formIdentity, formRefreshFor, isImplicitSubmit, isLeavingVisit, pageFlash, pageFormName, registerConflictForm, registerFormSubmitter, registerPageForm, resolvePartialProps, shouldResyncForm, wizardProgressStore } from "@alfakit/core";
|
|
8
8
|
import { router } from "@inertiajs/react";
|
|
9
|
-
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
9
|
+
import { useCallback, useEffect, useId, useMemo, useRef } from "react";
|
|
10
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
11
|
//#region src/components/form/form-block.tsx
|
|
12
12
|
/**
|
|
@@ -21,9 +21,12 @@ function FormBlock$1({ block }) {
|
|
|
21
21
|
const t = useT();
|
|
22
22
|
const token = page?.token ?? "";
|
|
23
23
|
const identity = formIdentity(page, block);
|
|
24
|
+
const standalone = useStandalone();
|
|
25
|
+
const instanceId = useId();
|
|
24
26
|
const store = useMemo(() => new FormStore(block, {
|
|
25
27
|
token,
|
|
26
28
|
component: block.component,
|
|
29
|
+
idPrefix: standalone ? formIdPrefix(`form-${instanceId}`) : void 0,
|
|
27
30
|
refreshUrl: panel.endpoints.schemaRefresh,
|
|
28
31
|
optionsUrl: panel.endpoints.schemaOptions,
|
|
29
32
|
validateUrl: panel.endpoints.schemaValidate,
|
|
@@ -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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"}
|
|
@@ -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, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-sidebar.js","names":[],"sources":["../../../src/components/panel/app-sidebar.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n flattenNavigationItems,\n hasActiveNavigationItem,\n navigationItemText,\n type NavigationGroupSchema,\n type NavigationItemSchema,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { ChevronRightIcon } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { badgeColorClasses, colorClass } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { NotificationsMenu } from '../notifications/notifications-menu'\nimport { Badge } from '../ui/badge'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarRail,\n} from '../ui/sidebar'\nimport { Brand } from './brand'\nimport { GlobalSearch } from './global-search'\nimport { NavLabel } from './nav-label'\nimport { Slot } from './slot'\nimport { TenantMenu } from './tenant-menu'\nimport { UserMenu } from './user-menu'\n\nexport { Brand } from './brand'\n\nfunction NavBadge({ item }: { item: NavigationItemSchema }) {\n const t = useT()\n\n // `deferNavigationBadges()`: a placeholder until the deferred badges arrive.\n if (item.badgePending) {\n return (\n <span\n data-slot=\"badge\"\n aria-busy=\"true\"\n aria-label={t('loading', {}, 'Loading')}\n className=\"bg-muted-foreground/20 ms-auto inline-block h-4 w-5 animate-pulse rounded-md\"\n />\n )\n }\n\n if (item.badge === undefined || item.badge === null) return null\n\n return (\n <Badge\n variant=\"outline\"\n className={cn('ms-auto px-1.5 py-0', colorClass(badgeColorClasses, item.badgeColor, 'gray'))}\n title={item.badgeTooltip}\n >\n {item.badge}\n </Badge>\n )\n}\n\n/**\n * A navigation link: an Inertia link, or a plain anchor for `openUrlInNewTab()`. Used as an\n * `asChild` target, so it must pass on the classes, data attributes and handlers it receives.\n */\nfunction NavLink({\n item,\n children,\n ...props\n}: { item: NavigationItemSchema; children: ReactNode } & Omit<\n ComponentProps<'a'>,\n 'href' | 'children'\n>) {\n // `NavigationItem::extraAttributes()`; `class` is merged with the link's own.\n const attributes = {\n ...props,\n ...extraProps(item.extraAttributes, props.className),\n } as ComponentProps<'a'>\n\n return item.openInNewTab ? (\n <a {...attributes} href={item.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n <NewTabHint />\n </a>\n ) : (\n // Slot passes DOM props; Inertia's Link redefines a few event names, so pass them through untyped.\n <Link\n {...(attributes as Record<string, unknown>)}\n href={item.url ?? '#'}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n >\n {children}\n </Link>\n )\n}\n\n/** Child items (any depth) with their own badges, new-tab links and nested children. */\nfunction NavSubItem({ item }: { item: NavigationItemSchema }) {\n if (item.children && item.children.length > 0) {\n return (\n <SidebarMenuSubItem>\n <Collapsible defaultOpen={item.active} className=\"group/nav-sub\">\n <CollapsibleTrigger asChild>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <button type=\"button\" {...extraProps(item.extraAttributes, 'w-full')}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <ChevronRightIcon className=\"ms-auto transition-transform duration-200 group-data-[state=open]/nav-sub:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-sub:rotate-90\" />\n </button>\n </SidebarMenuSubButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuSubItem>\n )\n }\n\n return (\n <SidebarMenuSubItem>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n )\n}\n\nfunction NavItem({ item }: { item: NavigationItemSchema }) {\n if (item.children && item.children.length > 0) {\n return (\n <Collapsible asChild defaultOpen={item.active} className=\"group/collapsible\">\n <SidebarMenuItem>\n <CollapsibleTrigger asChild>\n <SidebarMenuButton\n tooltip={navigationItemText(item)}\n isActive={item.active}\n {...extraProps(item.extraAttributes)}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <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\" />\n </SidebarMenuButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </SidebarMenuItem>\n </Collapsible>\n )\n }\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton asChild isActive={item.active} tooltip={navigationItemText(item)}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuButton>\n </SidebarMenuItem>\n )\n}\n\n/** A group's items in its flyout menu, children indented under their parent. */\nfunction FlyoutItems({ items }: { items: NavigationItemSchema[] }) {\n return flattenNavigationItems(items).map(({ item, depth }, index) => (\n <DropdownMenuItem\n key={`${depth}-${item.label}-${index}`}\n asChild\n data-active={item.active ? 'true' : undefined}\n >\n <NavLink\n item={item}\n aria-current={item.active ? 'page' : undefined}\n className={cn(\n 'data-[active=true]:bg-accent data-[active=true]:font-medium',\n depth === 1 && 'ps-6',\n depth > 1 && 'ps-9',\n )}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"truncate\" />\n <NavBadge item={item} />\n </NavLink>\n </DropdownMenuItem>\n ))\n}\n\n/**\n * In the icon-only (collapsed) sidebar, a group with an icon is one button that opens its\n * items in a flyout menu, like Filament. Hidden while the sidebar is expanded.\n */\nfunction NavGroupFlyout({ group }: { group: NavigationGroupSchema }) {\n const direction = useDirection()\n const active = hasActiveNavigationItem(group.items)\n\n return (\n <SidebarMenu className=\"hidden group-data-[collapsible=icon]:flex\" data-nav-flyout=\"\">\n <SidebarMenuItem>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton isActive={active}>\n <Icon name={group.icon!} />\n <span>{group.label}</span>\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={direction === 'rtl' ? 'left' : 'right'}\n align=\"start\"\n className=\"min-w-56\"\n >\n <DropdownMenuLabel>{group.label}</DropdownMenuLabel>\n <FlyoutItems items={group.items} />\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n\nfunction NavGroup({ group }: { group: NavigationGroupSchema }) {\n // `NavigationGroup::extraSidebarAttributes()`; `class` is merged with the group's own.\n const attributes = extraProps(group.extraSidebarAttributes)\n // A labelled group with an icon collapses into a flyout in the icon-only sidebar.\n const flyout = Boolean(group.label && group.icon)\n const content = (\n <SidebarGroupContent className={cn(flyout && 'group-data-[collapsible=icon]:hidden')}>\n <SidebarMenu>\n {group.items.map((item) => (\n <NavItem key={item.label} item={item} />\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n )\n const flyoutMenu = flyout ? <NavGroupFlyout group={group} /> : null\n\n if (!group.label) {\n return <SidebarGroup {...attributes}>{content}</SidebarGroup>\n }\n\n if (!group.collapsible) {\n return (\n <SidebarGroup {...attributes}>\n <SidebarGroupLabel>\n {group.icon && <Icon name={group.icon} className=\"me-1.5\" />}\n {group.label}\n </SidebarGroupLabel>\n {content}\n {flyoutMenu}\n </SidebarGroup>\n )\n }\n\n return (\n <Collapsible\n defaultOpen={!group.collapsed || group.items.some((item) => item.active)}\n className=\"group/nav-group\"\n >\n <SidebarGroup {...attributes}>\n <CollapsibleTrigger asChild>\n <SidebarGroupLabel\n asChild\n className=\"hover:bg-sidebar-accent w-full cursor-pointer justify-between\"\n >\n <button type=\"button\">\n <span className=\"flex items-center gap-1.5\">\n {group.icon && <Icon name={group.icon} />}\n {group.label}\n </span>\n <Icon\n name=\"alfakit::panels.sidebar.group.collapse-button\"\n fallback={ChevronRightIcon}\n className=\"size-3.5 transition-transform group-data-[state=open]/nav-group:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-group:rotate-90\"\n />\n </button>\n </SidebarGroupLabel>\n </CollapsibleTrigger>\n <CollapsibleContent>{content}</CollapsibleContent>\n {flyoutMenu}\n </SidebarGroup>\n </Collapsible>\n )\n}\n\nexport function AppSidebar() {\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n\n return (\n <Sidebar\n side={direction === 'rtl' ? 'right' : 'left'}\n collapsible={\n panel.theme?.design?.layout === 'rail'\n ? 'icon'\n : panel.sidebarFullyCollapsibleOnDesktop\n ? 'offcanvas'\n : panel.sidebarCollapsibleOnDesktop\n ? 'icon'\n : 'none'\n }\n variant={panel.theme?.sidebar === 'flush' ? 'sidebar' : (panel.theme?.sidebar ?? 'inset')}\n >\n <SidebarHeader>\n <Slot name=\"sidebar.start\" />\n <Slot name=\"sidebar.logo.before\" />\n <SidebarMenu>\n <SidebarMenuItem>\n {panel.tenancy?.current ? (\n <>\n <Slot name=\"tenant-menu.before\" />\n <TenantMenu />\n <Slot name=\"tenant-menu.after\" />\n </>\n ) : (\n <SidebarMenuButton size=\"lg\" asChild>\n <div>\n <Brand />\n </div>\n </SidebarMenuButton>\n )}\n </SidebarMenuItem>\n </SidebarMenu>\n <Slot name=\"sidebar.logo.after\" />\n {/* `databaseNotifications(position: 'sidebar')` or `topbar(false)`. */}\n {panel.databaseNotifications?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <NotificationsMenu variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n {/* `globalSearch(position: GlobalSearchPosition::Sidebar)` or `topbar(false)`. */}\n {panel.globalSearch?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <GlobalSearch variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.header\" />\n </SidebarHeader>\n <SidebarContent>\n <Slot name=\"sidebar.nav.start\" />\n <nav aria-label={t('sidebar', {}, 'Sidebar')} className=\"contents\">\n {panel.navigation.map((group, index) => (\n <NavGroup key={group.label ?? `group-${index}`} group={group} />\n ))}\n </nav>\n <Slot name=\"sidebar.nav.end\" />\n </SidebarContent>\n <SidebarFooter>\n <Slot name=\"sidebar.footer\" />\n {/* `userMenu(position: UserMenuPosition::Topbar)` shows it in the top bar instead. */}\n {panel.userMenu?.position !== 'topbar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <UserMenu />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.end\" />\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAoDA,SAAS,SAAS,EAAE,QAAwC;CAC1D,MAAM,IAAI,KAAK;CAGf,IAAI,KAAK,cACP,OACE,oBAAC,QAAD;EACE,aAAU;EACV,aAAU;EACV,cAAY,EAAE,WAAW,CAAC,GAAG,SAAS;EACtC,WAAU;CACX,CAAA;CAIL,IAAI,KAAK,UAAU,KAAA,KAAa,KAAK,UAAU,MAAM,OAAO;CAE5D,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,WAAW,GAAG,uBAAuB,WAAW,mBAAmB,KAAK,YAAY,MAAM,CAAC;EAC3F,OAAO,KAAK;EAEX,UAAA,KAAK;CACD,CAAA;AAEX;;;;;AAMA,SAAS,QAAQ,EACf,MACA,UACA,GAAG,SAIF;CAED,MAAM,aAAa;EACjB,GAAG;EACH,GAAG,WAAW,KAAK,iBAAiB,MAAM,SAAS;CACrD;CAEA,OAAO,KAAK,eACV,qBAAC,KAAD;EAAG,GAAI;EAAY,MAAM,KAAK;EAAK,QAAO;EAAS,KAAI;EAAvD,UAAA,CACG,UACD,oBAAC,YAAD,CAAa,CAAA,CACZ;CAGH,CAAA,IAAA,oBAAC,MAAD;EACE,GAAK;EACL,MAAM,KAAK,OAAO;EAClB,UAAA;EACA,UAAU;EAET;CACG,CAAA;AAEV;;AAGA,SAAS,WAAW,EAAE,QAAwC;CAC5D,IAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAC1C,OACE,oBAAC,oBAAD,EAAA,UACE,qBAAC,aAAD;EAAa,aAAa,KAAK;EAAQ,WAAU;EAAjD,UAAA,CACE,oBAAC,oBAAD;GAAoB,SAAA;GAClB,UAAA,oBAAC,sBAAD;IAAsB,SAAA;IAAQ,UAAU,KAAK;IAC3C,UAAA,qBAAC,UAAD;KAAQ,MAAK;KAAS,GAAI,WAAW,KAAK,iBAAiB,QAAQ;KAAnE,UAAA;MACG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA;MACtC,oBAAC,UAAD,EAAgB,KAAO,CAAA;MACvB,oBAAC,UAAD,EAAgB,KAAO,CAAA;MACvB,oBAAC,kBAAD,EAAkB,WAAU,mJAAoJ,CAAA;KAC1K;;GACY,CAAA;EACJ,CAAA,GACpB,oBAAC,oBAAD,EAAA,UACE,oBAAC,gBAAD,EAAA,UACG,KAAK,SAAS,KAAK,UAClB,oBAAC,YAAD,EAA8B,MAAM,MAAQ,GAA3B,MAAM,KAAqB,CAC7C,EACa,CAAA,EACE,CAAA,CACT;CACK,CAAA,EAAA,CAAA;CAIxB,OACE,oBAAC,oBAAD,EAAA,UACE,oBAAC,sBAAD;EAAsB,SAAA;EAAQ,UAAU,KAAK;EAC3C,UAAA,qBAAC,SAAD;GAAe;GAAf,UAAA;IACG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA;IACtC,oBAAC,UAAD,EAAgB,KAAO,CAAA;IACvB,oBAAC,UAAD,EAAgB,KAAO,CAAA;GAChB;;CACW,CAAA,EACJ,CAAA;AAExB;AAEA,SAAS,QAAQ,EAAE,QAAwC;CACzD,IAAI,KAAK,YAAY,KAAK,SAAS,SAAS,GAC1C,OACE,oBAAC,aAAD;EAAa,SAAA;EAAQ,aAAa,KAAK;EAAQ,WAAU;EACvD,UAAA,qBAAC,iBAAD,EAAA,UAAA,CACE,oBAAC,oBAAD;GAAoB,SAAA;GAClB,UAAA,qBAAC,mBAAD;IACE,SAAS,mBAAmB,IAAI;IAChC,UAAU,KAAK;IACf,GAAI,WAAW,KAAK,eAAe;IAHrC,UAAA;KAKG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA;KACtC,oBAAC,UAAD,EAAgB,KAAO,CAAA;KACvB,oBAAC,UAAD,EAAgB,KAAO,CAAA;KACvB,oBAAC,kBAAD,EAAkB,WAAU,2JAA4J,CAAA;IACvK;;EACD,CAAA,GACpB,oBAAC,oBAAD,EAAA,UACE,oBAAC,gBAAD,EAAA,UACG,KAAK,SAAS,KAAK,UAClB,oBAAC,YAAD,EAA8B,MAAM,MAAQ,GAA3B,MAAM,KAAqB,CAC7C,EACa,CAAA,EACE,CAAA,CACL,EAAA,CAAA;CACN,CAAA;CAIjB,OACE,oBAAC,iBAAD,EAAA,UACE,oBAAC,mBAAD;EAAmB,SAAA;EAAQ,UAAU,KAAK;EAAQ,SAAS,mBAAmB,IAAI;EAChF,UAAA,qBAAC,SAAD;GAAe;GAAf,UAAA;IACG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA;IACtC,oBAAC,UAAD,EAAgB,KAAO,CAAA;IACvB,oBAAC,UAAD,EAAgB,KAAO,CAAA;GAChB;;CACQ,CAAA,EACJ,CAAA;AAErB;;AAGA,SAAS,YAAY,EAAE,SAA4C;CACjE,OAAO,uBAAuB,KAAK,CAAC,CAAC,KAAK,EAAE,MAAM,SAAS,UACzD,oBAAC,kBAAD;EAEE,SAAA;EACA,eAAa,KAAK,SAAS,SAAS,KAAA;EAEpC,UAAA,qBAAC,SAAD;GACQ;GACN,gBAAc,KAAK,SAAS,SAAS,KAAA;GACrC,WAAW,GACT,+DACA,UAAU,KAAK,QACf,QAAQ,KAAK,MACf;GAPF,UAAA;IASG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA;IACtC,oBAAC,UAAD;KAAgB;KAAM,WAAU;IAAY,CAAA;IAC5C,oBAAC,UAAD,EAAgB,KAAO,CAAA;GAChB;;CACO,GAjBX,GAAG,MAAM,GAAG,KAAK,MAAM,GAAG,OAiBf,CACnB;AACH;;;;;AAMA,SAAS,eAAe,EAAE,SAA2C;CACnE,MAAM,YAAY,aAAa;CAC/B,MAAM,SAAS,wBAAwB,MAAM,KAAK;CAElD,OACE,oBAAC,aAAD;EAAa,WAAU;EAA4C,mBAAgB;EACjF,UAAA,oBAAC,iBAAD,EAAA,UACE,qBAAC,cAAD;GAAc,KAAK;GAAnB,UAAA,CACE,oBAAC,qBAAD;IAAqB,SAAA;IACnB,UAAA,qBAAC,mBAAD;KAAmB,UAAU;KAA7B,UAAA,CACE,oBAAC,MAAD,EAAM,MAAM,MAAM,KAAQ,CAAA,GAC1B,oBAAC,QAAD,EAAA,UAAO,MAAM,MAAY,CAAA,CACR;;GACA,CAAA,GACrB,qBAAC,qBAAD;IACE,MAAM,cAAc,QAAQ,SAAS;IACrC,OAAM;IACN,WAAU;IAHZ,UAAA,CAKE,oBAAC,mBAAD,EAAA,UAAoB,MAAM,MAAyB,CAAA,GACnD,oBAAC,aAAD,EAAa,OAAO,MAAM,MAAQ,CAAA,CACf;GACT,CAAA,CAAA;EACC,CAAA,EAAA,CAAA;CACN,CAAA;AAEjB;AAEA,SAAS,SAAS,EAAE,SAA2C;CAE7D,MAAM,aAAa,WAAW,MAAM,sBAAsB;CAE1D,MAAM,SAAS,QAAQ,MAAM,SAAS,MAAM,IAAI;CAChD,MAAM,UACJ,oBAAC,qBAAD;EAAqB,WAAW,GAAG,UAAU,sCAAsC;EACjF,UAAA,oBAAC,aAAD,EAAA,UACG,MAAM,MAAM,KAAK,SAChB,oBAAC,SAAD,EAAgC,KAAO,GAAzB,KAAK,KAAoB,CACxC,EACU,CAAA;CACM,CAAA;CAEvB,MAAM,aAAa,SAAS,oBAAC,gBAAD,EAAuB,MAAQ,CAAA,IAAI;CAE/D,IAAI,CAAC,MAAM,OACT,OAAO,oBAAC,cAAD;EAAc,GAAI;EAAa,UAAA;CAAsB,CAAA;CAG9D,IAAI,CAAC,MAAM,aACT,OACE,qBAAC,cAAD;EAAc,GAAI;EAAlB,UAAA;GACE,qBAAC,mBAAD,EAAA,UAAA,CACG,MAAM,QAAQ,oBAAC,MAAD;IAAM,MAAM,MAAM;IAAM,WAAU;GAAU,CAAA,GAC1D,MAAM,KACU,EAAA,CAAA;GAClB;GACA;EACW;;CAIlB,OACE,oBAAC,aAAD;EACE,aAAa,CAAC,MAAM,aAAa,MAAM,MAAM,MAAM,SAAS,KAAK,MAAM;EACvE,WAAU;EAEV,UAAA,qBAAC,cAAD;GAAc,GAAI;GAAlB,UAAA;IACE,oBAAC,oBAAD;KAAoB,SAAA;KAClB,UAAA,oBAAC,mBAAD;MACE,SAAA;MACA,WAAU;MAEV,UAAA,qBAAC,UAAD;OAAQ,MAAK;OAAb,UAAA,CACE,qBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA,CACG,MAAM,QAAQ,oBAAC,MAAD,EAAM,MAAM,MAAM,KAAO,CAAA,GACvC,MAAM,KACH;OACN,CAAA,GAAA,oBAAC,MAAD;QACE,MAAK;QACL,UAAU;QACV,WAAU;OACX,CAAA,CACK;;KACS,CAAA;IACD,CAAA;IACpB,oBAAC,oBAAD,EAAA,UAAqB,QAA4B,CAAA;IAChD;GACW;;CACH,CAAA;AAEjB;AAEA,SAAgB,aAAa;CAC3B,MAAM,QAAQ,SAAS;CACvB,MAAM,YAAY,aAAa;CAC/B,MAAM,IAAI,KAAK;CAEf,OACE,qBAAC,SAAD;EACE,MAAM,cAAc,QAAQ,UAAU;EACtC,aACE,MAAM,OAAO,QAAQ,WAAW,SAC5B,SACA,MAAM,mCACJ,cACA,MAAM,8BACJ,SACA;EAEV,SAAS,MAAM,OAAO,YAAY,UAAU,YAAa,MAAM,OAAO,WAAW;EAXnF,UAAA;GAaE,qBAAC,eAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;IAC5B,oBAAC,MAAD,EAAM,MAAK,sBAAuB,CAAA;IAClC,oBAAC,aAAD,EAAA,UACE,oBAAC,iBAAD,EAAA,UACG,MAAM,SAAS,UACd,qBAAA,UAAA,EAAA,UAAA;KACE,oBAAC,MAAD,EAAM,MAAK,qBAAsB,CAAA;KACjC,oBAAC,YAAD,CAAa,CAAA;KACb,oBAAC,MAAD,EAAM,MAAK,oBAAqB,CAAA;IAChC,EAAA,CAAA,IAEF,oBAAC,mBAAD;KAAmB,MAAK;KAAK,SAAA;KAC3B,UAAA,oBAAC,OAAD,EAAA,UACE,oBAAC,OAAD,CAAQ,CAAA,EACL,CAAA;IACY,CAAA,EAEN,CAAA,EACN,CAAA;IACb,oBAAC,MAAD,EAAM,MAAK,qBAAsB,CAAA;IAEhC,MAAM,uBAAuB,aAAa,aACzC,oBAAC,aAAD,EAAA,UACE,oBAAC,iBAAD,EAAA,UACE,oBAAC,mBAAD,EAAmB,SAAQ,UAAW,CAAA,EACvB,CAAA,EACN,CAAA;IAGd,MAAM,cAAc,aAAa,aAChC,oBAAC,aAAD,EAAA,UACE,oBAAC,iBAAD,EAAA,UACE,oBAAC,cAAD,EAAc,SAAQ,UAAW,CAAA,EAClB,CAAA,EACN,CAAA;IAEf,oBAAC,MAAD,EAAM,MAAK,iBAAkB,CAAA;GAChB,EAAA,CAAA;GACf,qBAAC,gBAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAM,MAAK,oBAAqB,CAAA;IAChC,oBAAC,OAAD;KAAK,cAAY,EAAE,WAAW,CAAC,GAAG,SAAS;KAAG,WAAU;KACrD,UAAA,MAAM,WAAW,KAAK,OAAO,UAC5B,oBAAC,UAAD,EAAuD,MAAQ,GAAhD,MAAM,SAAS,SAAS,OAAwB,CAChE;IACE,CAAA;IACL,oBAAC,MAAD,EAAM,MAAK,kBAAmB,CAAA;GAChB,EAAA,CAAA;GAChB,qBAAC,eAAD,EAAA,UAAA;IACE,oBAAC,MAAD,EAAM,MAAK,iBAAkB,CAAA;IAE5B,MAAM,UAAU,aAAa,YAC5B,oBAAC,aAAD,EAAA,UACE,oBAAC,iBAAD,EAAA,UACE,oBAAC,UAAD,CAAW,CAAA,EACI,CAAA,EACN,CAAA;IAEf,oBAAC,MAAD,EAAM,MAAK,cAAe,CAAA;GACb,EAAA,CAAA;GACf,oBAAC,aAAD,CAAc,CAAA;EACP;;AAEb"}
|
|
1
|
+
{"version":3,"file":"app-sidebar.js","names":[],"sources":["../../../src/components/panel/app-sidebar.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n flattenNavigationItems,\n hasActiveNavigationItem,\n navigationItemText,\n type NavigationGroupSchema,\n type NavigationItemSchema,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { ChevronRightIcon } from 'lucide-react'\nimport type { ComponentProps, ReactNode } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { badgeColorClasses, colorClass } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { NotificationsMenu } from '../notifications/notifications-menu'\nimport { Badge } from '../ui/badge'\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../ui/collapsible'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n SidebarMenuSubButton,\n SidebarMenuSubItem,\n SidebarRail,\n} from '../ui/sidebar'\nimport { Brand } from './brand'\nimport { GlobalSearch } from './global-search'\nimport { NavLabel } from './nav-label'\nimport { Slot } from './slot'\nimport { TenantMenu } from './tenant-menu'\nimport { UserMenu } from './user-menu'\n\nexport { Brand } from './brand'\n\nfunction NavBadge({ item }: { item: NavigationItemSchema }) {\n const t = useT()\n\n // `deferNavigationBadges()`: a placeholder until the deferred badges arrive.\n if (item.badgePending) {\n return (\n <span\n data-slot=\"badge\"\n aria-busy=\"true\"\n aria-label={t('loading', {}, 'Loading')}\n className=\"bg-muted-foreground/20 ms-auto inline-block h-4 w-5 animate-pulse rounded-md\"\n />\n )\n }\n\n if (item.badge === undefined || item.badge === null) return null\n\n return (\n <Badge\n variant=\"outline\"\n className={cn('ms-auto px-1.5 py-0', colorClass(badgeColorClasses, item.badgeColor, 'gray'))}\n title={item.badgeTooltip}\n >\n {item.badge}\n </Badge>\n )\n}\n\n/**\n * A navigation link: an Inertia link, or a plain anchor for `openUrlInNewTab()`. Used as an\n * `asChild` target, so it must pass on the classes, data attributes and handlers it receives.\n */\nfunction NavLink({\n item,\n children,\n ...props\n}: { item: NavigationItemSchema; children: ReactNode } & Omit<\n ComponentProps<'a'>,\n 'href' | 'children'\n>) {\n // `NavigationItem::extraAttributes()`; `class` is merged with the link's own.\n const attributes = {\n ...props,\n ...extraProps(item.extraAttributes, props.className),\n } as ComponentProps<'a'>\n\n return item.openInNewTab ? (\n <a {...attributes} href={item.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n <NewTabHint />\n </a>\n ) : (\n // Slot passes DOM props; Inertia's Link redefines a few event names, so pass them through untyped.\n <Link\n {...(attributes as Record<string, unknown>)}\n href={item.url ?? '#'}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n >\n {children}\n </Link>\n )\n}\n\n/** Child items (any depth) with their own badges, new-tab links and nested children. */\nfunction NavSubItem({ item }: { item: NavigationItemSchema }) {\n if (item.children && item.children.length > 0) {\n return (\n <SidebarMenuSubItem>\n <Collapsible defaultOpen={item.active} className=\"group/nav-sub\">\n <CollapsibleTrigger asChild>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <button type=\"button\" {...extraProps(item.extraAttributes, 'w-full')}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <ChevronRightIcon className=\"ms-auto transition-transform duration-200 group-data-[state=open]/nav-sub:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-sub:rotate-90\" />\n </button>\n </SidebarMenuSubButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.url ?? child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuSubItem>\n )\n }\n\n return (\n <SidebarMenuSubItem>\n <SidebarMenuSubButton asChild isActive={item.active}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuSubButton>\n </SidebarMenuSubItem>\n )\n}\n\nfunction NavItem({ item }: { item: NavigationItemSchema }) {\n if (item.children && item.children.length > 0) {\n return (\n <Collapsible asChild defaultOpen={item.active} className=\"group/collapsible\">\n <SidebarMenuItem>\n <CollapsibleTrigger asChild>\n <SidebarMenuButton\n tooltip={navigationItemText(item)}\n isActive={item.active}\n {...extraProps(item.extraAttributes)}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n <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\" />\n </SidebarMenuButton>\n </CollapsibleTrigger>\n <CollapsibleContent>\n <SidebarMenuSub>\n {item.children.map((child) => (\n <NavSubItem key={child.url ?? child.label} item={child} />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </SidebarMenuItem>\n </Collapsible>\n )\n }\n\n return (\n <SidebarMenuItem>\n <SidebarMenuButton asChild isActive={item.active} tooltip={navigationItemText(item)}>\n <NavLink item={item}>\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NavBadge item={item} />\n </NavLink>\n </SidebarMenuButton>\n </SidebarMenuItem>\n )\n}\n\n/** A group's items in its flyout menu, children indented under their parent. */\nfunction FlyoutItems({ items }: { items: NavigationItemSchema[] }) {\n return flattenNavigationItems(items).map(({ item, depth }, index) => (\n <DropdownMenuItem\n key={`${depth}-${item.label}-${index}`}\n asChild\n data-active={item.active ? 'true' : undefined}\n >\n <NavLink\n item={item}\n aria-current={item.active ? 'page' : undefined}\n className={cn(\n 'data-[active=true]:bg-accent data-[active=true]:font-medium',\n depth === 1 && 'ps-6',\n depth > 1 && 'ps-9',\n )}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"truncate\" />\n <NavBadge item={item} />\n </NavLink>\n </DropdownMenuItem>\n ))\n}\n\n/**\n * In the icon-only (collapsed) sidebar, a group with an icon is one button that opens its\n * items in a flyout menu, like Filament. Hidden while the sidebar is expanded.\n */\nfunction NavGroupFlyout({ group }: { group: NavigationGroupSchema }) {\n const direction = useDirection()\n const active = hasActiveNavigationItem(group.items)\n\n return (\n <SidebarMenu className=\"hidden group-data-[collapsible=icon]:flex\" data-nav-flyout=\"\">\n <SidebarMenuItem>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton isActive={active}>\n <Icon name={group.icon!} />\n <span>{group.label}</span>\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={direction === 'rtl' ? 'left' : 'right'}\n align=\"start\"\n className=\"min-w-56\"\n >\n <DropdownMenuLabel>{group.label}</DropdownMenuLabel>\n <FlyoutItems items={group.items} />\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n )\n}\n\nfunction NavGroup({ group }: { group: NavigationGroupSchema }) {\n // `NavigationGroup::extraSidebarAttributes()`; `class` is merged with the group's own.\n const attributes = extraProps(group.extraSidebarAttributes)\n // A labelled group with an icon collapses into a flyout in the icon-only sidebar.\n const flyout = Boolean(group.label && group.icon)\n const content = (\n <SidebarGroupContent className={cn(flyout && 'group-data-[collapsible=icon]:hidden')}>\n <SidebarMenu>\n {group.items.map((item) => (\n <NavItem key={item.url ?? item.label} item={item} />\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n )\n const flyoutMenu = flyout ? <NavGroupFlyout group={group} /> : null\n\n if (!group.label) {\n return <SidebarGroup {...attributes}>{content}</SidebarGroup>\n }\n\n if (!group.collapsible) {\n return (\n <SidebarGroup {...attributes}>\n <SidebarGroupLabel>\n {group.icon && <Icon name={group.icon} className=\"me-1.5\" />}\n {group.label}\n </SidebarGroupLabel>\n {content}\n {flyoutMenu}\n </SidebarGroup>\n )\n }\n\n return (\n <Collapsible\n defaultOpen={!group.collapsed || group.items.some((item) => item.active)}\n className=\"group/nav-group\"\n >\n <SidebarGroup {...attributes}>\n <CollapsibleTrigger asChild>\n <SidebarGroupLabel\n asChild\n className=\"hover:bg-sidebar-accent w-full cursor-pointer justify-between\"\n >\n <button type=\"button\">\n <span className=\"flex items-center gap-1.5\">\n {group.icon && <Icon name={group.icon} />}\n {group.label}\n </span>\n <Icon\n name=\"alfakit::panels.sidebar.group.collapse-button\"\n fallback={ChevronRightIcon}\n className=\"size-3.5 transition-transform group-data-[state=open]/nav-group:rotate-90 rtl:rotate-180 rtl:group-data-[state=open]/nav-group:rotate-90\"\n />\n </button>\n </SidebarGroupLabel>\n </CollapsibleTrigger>\n <CollapsibleContent>{content}</CollapsibleContent>\n {flyoutMenu}\n </SidebarGroup>\n </Collapsible>\n )\n}\n\nexport function AppSidebar() {\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n\n return (\n <Sidebar\n side={direction === 'rtl' ? 'right' : 'left'}\n collapsible={\n panel.theme?.design?.layout === 'rail'\n ? 'icon'\n : panel.sidebarFullyCollapsibleOnDesktop\n ? 'offcanvas'\n : panel.sidebarCollapsibleOnDesktop\n ? 'icon'\n : 'none'\n }\n variant={panel.theme?.sidebar === 'flush' ? 'sidebar' : (panel.theme?.sidebar ?? 'inset')}\n >\n <SidebarHeader>\n <Slot name=\"sidebar.start\" />\n <Slot name=\"sidebar.logo.before\" />\n <SidebarMenu>\n <SidebarMenuItem>\n {panel.tenancy?.current ? (\n <>\n <Slot name=\"tenant-menu.before\" />\n <TenantMenu />\n <Slot name=\"tenant-menu.after\" />\n </>\n ) : (\n <SidebarMenuButton size=\"lg\" asChild>\n <div>\n <Brand />\n </div>\n </SidebarMenuButton>\n )}\n </SidebarMenuItem>\n </SidebarMenu>\n <Slot name=\"sidebar.logo.after\" />\n {/* `databaseNotifications(position: 'sidebar')` or `topbar(false)`. */}\n {panel.databaseNotifications?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <NotificationsMenu variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n {/* `globalSearch(position: GlobalSearchPosition::Sidebar)` or `topbar(false)`. */}\n {panel.globalSearch?.position === 'sidebar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <GlobalSearch variant=\"sidebar\" />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.header\" />\n </SidebarHeader>\n <SidebarContent>\n <Slot name=\"sidebar.nav.start\" />\n <nav aria-label={t('sidebar', {}, 'Sidebar')} className=\"contents\">\n {panel.navigation.map((group, index) => (\n <NavGroup key={group.label ?? `group-${index}`} group={group} />\n ))}\n </nav>\n <Slot name=\"sidebar.nav.end\" />\n </SidebarContent>\n <SidebarFooter>\n <Slot name=\"sidebar.footer\" />\n {/* `userMenu(position: UserMenuPosition::Topbar)` shows it in the top bar instead. */}\n {panel.userMenu?.position !== 'topbar' && (\n <SidebarMenu>\n <SidebarMenuItem>\n <UserMenu />\n </SidebarMenuItem>\n </SidebarMenu>\n )}\n <Slot name=\"sidebar.end\" />\n </SidebarFooter>\n <SidebarRail />\n </Sidebar>\n )\n}\n"],"mappings":"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"}
|
|
@@ -55,7 +55,7 @@ function BottomNav() {
|
|
|
55
55
|
"data-slot": "bottom-nav",
|
|
56
56
|
"aria-label": t("navigation", {}, "Navigation"),
|
|
57
57
|
className: "md:hidden",
|
|
58
|
-
children: [primary.map((item) => /* @__PURE__ */ jsx(BottomNavLink, { item }, item.label)), overflow.length > 0 && /* @__PURE__ */ jsxs(Sheet, {
|
|
58
|
+
children: [primary.map((item) => /* @__PURE__ */ jsx(BottomNavLink, { item }, item.url ?? item.label)), overflow.length > 0 && /* @__PURE__ */ jsxs(Sheet, {
|
|
59
59
|
open,
|
|
60
60
|
onOpenChange: setOpen,
|
|
61
61
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
@@ -88,7 +88,7 @@ function BottomNav() {
|
|
|
88
88
|
"data-active": isNavigationItemActive(item) ? "true" : void 0,
|
|
89
89
|
className: cn("hover:bg-accent data-[active=true]:bg-accent flex min-h-11 items-center gap-3 rounded-md px-3 font-medium [&_svg]:size-4"),
|
|
90
90
|
children: [item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon }), /* @__PURE__ */ jsx(NavLabel, { item })]
|
|
91
|
-
}, item.label))
|
|
91
|
+
}, item.url ?? item.label))
|
|
92
92
|
})]
|
|
93
93
|
})]
|
|
94
94
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bottom-nav.js","names":[],"sources":["../../../src/components/panel/bottom-nav.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n bottomNavigationItems,\n isNavigationItemActive,\n navigationItemUrl,\n type NavigationItemSchema,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { MenuIcon } from 'lucide-react'\nimport { useState } from 'react'\nimport { usePanel, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from '../ui/sheet'\nimport { NavLabel } from './nav-label'\n\nfunction BottomNavLink({\n item,\n onNavigate,\n}: {\n item: NavigationItemSchema\n onNavigate?: () => void\n}) {\n const active = isNavigationItemActive(item)\n const url = navigationItemUrl(item) ?? '#'\n\n if (item.openInNewTab) {\n return (\n <a\n {...extraProps(item.extraAttributes, 'min-w-0')}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-slot=\"bottom-nav-item\"\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"max-w-full truncate\" />\n </a>\n )\n }\n\n return (\n <Link\n {...extraProps(item.extraAttributes, 'min-w-0')}\n href={url}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n data-slot=\"bottom-nav-item\"\n data-active={active ? 'true' : undefined}\n aria-current={active ? 'page' : undefined}\n onClick={onNavigate}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"max-w-full truncate\" />\n </Link>\n )\n}\n\n/**\n * The mobile preset's bottom navigation bar (`Panel::mobile()`): the top-level items, with\n * the rest behind a \"More\" button that opens a sheet. Hidden from `md` up; replace it\n * everywhere with `createAlfakit({ components: { 'panel:bottom-nav': … } })`.\n */\nexport function BottomNav() {\n const panel = usePanel()\n const t = useT()\n const [open, setOpen] = useState(false)\n const { primary, overflow } = bottomNavigationItems(panel.navigation)\n\n if (primary.length === 0 && overflow.length === 0) {\n return null\n }\n\n const overflowActive = overflow.some(isNavigationItemActive)\n\n return (\n <nav\n data-slot=\"bottom-nav\"\n aria-label={t('navigation', {}, 'Navigation')}\n className=\"md:hidden\"\n >\n {primary.map((item) => (\n <BottomNavLink key={item.label} item={item} />\n ))}\n {overflow.length > 0 && (\n <Sheet open={open} onOpenChange={setOpen}>\n <button\n type=\"button\"\n data-slot=\"bottom-nav-item\"\n data-active={overflowActive ? 'true' : undefined}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n className=\"min-w-0 bg-transparent\"\n onClick={() => setOpen(true)}\n >\n <Icon name=\"alfakit::panels.topbar.open-sidebar-button\" fallback={MenuIcon} />\n <span className=\"max-w-full truncate\">{t('more', {}, 'More')}</span>\n </button>\n <SheetContent side=\"bottom\" className=\"max-h-[80dvh] overflow-y-auto rounded-t-xl p-0\">\n <SheetHeader className=\"border-b\">\n <SheetTitle>{t('more', {}, 'More')}</SheetTitle>\n </SheetHeader>\n <nav\n aria-label={t('navigation', {}, 'Navigation')}\n className=\"flex flex-col gap-1 p-3 text-sm\"\n >\n {overflow\n .flatMap((item) => [item, ...(item.children ?? [])])\n .map((item) => (\n <Link\n key={item.label}\n href={navigationItemUrl(item) ?? '#'}\n onClick={() => setOpen(false)}\n data-active={isNavigationItemActive(item) ? 'true' : undefined}\n className={cn(\n 'hover:bg-accent data-[active=true]:bg-accent flex min-h-11 items-center gap-3 rounded-md px-3 font-medium [&_svg]:size-4',\n )}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n ))}\n </nav>\n </SheetContent>\n </Sheet>\n )}\n </nav>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,SAAS,cAAc,EACrB,MACA,cAIC;CACD,MAAM,SAAS,uBAAuB,IAAI;CAC1C,MAAM,MAAM,kBAAkB,IAAI,KAAK;CAEvC,IAAI,KAAK,cACP,OACE,qBAAC,KAAD;EACE,GAAI,WAAW,KAAK,iBAAiB,SAAS;EAC9C,MAAM;EACN,QAAO;EACP,KAAI;EACJ,aAAU;EALZ,UAAA,CAOG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA,GACtC,oBAAC,UAAD;GAAgB;GAAM,WAAU;EAAuB,CAAA,CACtD;;CAIP,OACE,qBAAC,MAAD;EACE,GAAI,WAAW,KAAK,iBAAiB,SAAS;EAC9C,MAAM;EACN,UAAA;EACA,UAAU;EACV,aAAU;EACV,eAAa,SAAS,SAAS,KAAA;EAC/B,gBAAc,SAAS,SAAS,KAAA;EAChC,SAAS;EARX,UAAA,CAUG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA,GACtC,oBAAC,UAAD;GAAgB;GAAM,WAAU;EAAuB,CAAA,CACnD;;AAEV;;;;;;AAOA,SAAgB,YAAY;CAC1B,MAAM,QAAQ,SAAS;CACvB,MAAM,IAAI,KAAK;CACf,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,EAAE,SAAS,aAAa,sBAAsB,MAAM,UAAU;CAEpE,IAAI,QAAQ,WAAW,KAAK,SAAS,WAAW,GAC9C,OAAO;CAGT,MAAM,iBAAiB,SAAS,KAAK,sBAAsB;CAE3D,OACE,qBAAC,OAAD;EACE,aAAU;EACV,cAAY,EAAE,cAAc,CAAC,GAAG,YAAY;EAC5C,WAAU;EAHZ,UAAA,CAKG,QAAQ,KAAK,SACZ,oBAAC,eAAD,
|
|
1
|
+
{"version":3,"file":"bottom-nav.js","names":[],"sources":["../../../src/components/panel/bottom-nav.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n bottomNavigationItems,\n isNavigationItemActive,\n navigationItemUrl,\n type NavigationItemSchema,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { MenuIcon } from 'lucide-react'\nimport { useState } from 'react'\nimport { usePanel, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from '../ui/sheet'\nimport { NavLabel } from './nav-label'\n\nfunction BottomNavLink({\n item,\n onNavigate,\n}: {\n item: NavigationItemSchema\n onNavigate?: () => void\n}) {\n const active = isNavigationItemActive(item)\n const url = navigationItemUrl(item) ?? '#'\n\n if (item.openInNewTab) {\n return (\n <a\n {...extraProps(item.extraAttributes, 'min-w-0')}\n href={url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-slot=\"bottom-nav-item\"\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"max-w-full truncate\" />\n </a>\n )\n }\n\n return (\n <Link\n {...extraProps(item.extraAttributes, 'min-w-0')}\n href={url}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n data-slot=\"bottom-nav-item\"\n data-active={active ? 'true' : undefined}\n aria-current={active ? 'page' : undefined}\n onClick={onNavigate}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} className=\"max-w-full truncate\" />\n </Link>\n )\n}\n\n/**\n * The mobile preset's bottom navigation bar (`Panel::mobile()`): the top-level items, with\n * the rest behind a \"More\" button that opens a sheet. Hidden from `md` up; replace it\n * everywhere with `createAlfakit({ components: { 'panel:bottom-nav': … } })`.\n */\nexport function BottomNav() {\n const panel = usePanel()\n const t = useT()\n const [open, setOpen] = useState(false)\n const { primary, overflow } = bottomNavigationItems(panel.navigation)\n\n if (primary.length === 0 && overflow.length === 0) {\n return null\n }\n\n const overflowActive = overflow.some(isNavigationItemActive)\n\n return (\n <nav\n data-slot=\"bottom-nav\"\n aria-label={t('navigation', {}, 'Navigation')}\n className=\"md:hidden\"\n >\n {primary.map((item) => (\n <BottomNavLink key={item.url ?? item.label} item={item} />\n ))}\n {overflow.length > 0 && (\n <Sheet open={open} onOpenChange={setOpen}>\n <button\n type=\"button\"\n data-slot=\"bottom-nav-item\"\n data-active={overflowActive ? 'true' : undefined}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n className=\"min-w-0 bg-transparent\"\n onClick={() => setOpen(true)}\n >\n <Icon name=\"alfakit::panels.topbar.open-sidebar-button\" fallback={MenuIcon} />\n <span className=\"max-w-full truncate\">{t('more', {}, 'More')}</span>\n </button>\n <SheetContent side=\"bottom\" className=\"max-h-[80dvh] overflow-y-auto rounded-t-xl p-0\">\n <SheetHeader className=\"border-b\">\n <SheetTitle>{t('more', {}, 'More')}</SheetTitle>\n </SheetHeader>\n <nav\n aria-label={t('navigation', {}, 'Navigation')}\n className=\"flex flex-col gap-1 p-3 text-sm\"\n >\n {overflow\n .flatMap((item) => [item, ...(item.children ?? [])])\n .map((item) => (\n <Link\n key={item.url ?? item.label}\n href={navigationItemUrl(item) ?? '#'}\n onClick={() => setOpen(false)}\n data-active={isNavigationItemActive(item) ? 'true' : undefined}\n className={cn(\n 'hover:bg-accent data-[active=true]:bg-accent flex min-h-11 items-center gap-3 rounded-md px-3 font-medium [&_svg]:size-4',\n )}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n ))}\n </nav>\n </SheetContent>\n </Sheet>\n )}\n </nav>\n )\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,SAAS,cAAc,EACrB,MACA,cAIC;CACD,MAAM,SAAS,uBAAuB,IAAI;CAC1C,MAAM,MAAM,kBAAkB,IAAI,KAAK;CAEvC,IAAI,KAAK,cACP,OACE,qBAAC,KAAD;EACE,GAAI,WAAW,KAAK,iBAAiB,SAAS;EAC9C,MAAM;EACN,QAAO;EACP,KAAI;EACJ,aAAU;EALZ,UAAA,CAOG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA,GACtC,oBAAC,UAAD;GAAgB;GAAM,WAAU;EAAuB,CAAA,CACtD;;CAIP,OACE,qBAAC,MAAD;EACE,GAAI,WAAW,KAAK,iBAAiB,SAAS;EAC9C,MAAM;EACN,UAAA;EACA,UAAU;EACV,aAAU;EACV,eAAa,SAAS,SAAS,KAAA;EAC/B,gBAAc,SAAS,SAAS,KAAA;EAChC,SAAS;EARX,UAAA,CAUG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA,GACtC,oBAAC,UAAD;GAAgB;GAAM,WAAU;EAAuB,CAAA,CACnD;;AAEV;;;;;;AAOA,SAAgB,YAAY;CAC1B,MAAM,QAAQ,SAAS;CACvB,MAAM,IAAI,KAAK;CACf,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,EAAE,SAAS,aAAa,sBAAsB,MAAM,UAAU;CAEpE,IAAI,QAAQ,WAAW,KAAK,SAAS,WAAW,GAC9C,OAAO;CAGT,MAAM,iBAAiB,SAAS,KAAK,sBAAsB;CAE3D,OACE,qBAAC,OAAD;EACE,aAAU;EACV,cAAY,EAAE,cAAc,CAAC,GAAG,YAAY;EAC5C,WAAU;EAHZ,UAAA,CAKG,QAAQ,KAAK,SACZ,oBAAC,eAAD,EAAkD,KAAO,GAArC,KAAK,OAAO,KAAK,KAAoB,CAC1D,GACA,SAAS,SAAS,KACjB,qBAAC,OAAD;GAAa;GAAM,cAAc;GAAjC,UAAA,CACE,qBAAC,UAAD;IACE,MAAK;IACL,aAAU;IACV,eAAa,iBAAiB,SAAS,KAAA;IACvC,iBAAc;IACd,iBAAe;IACf,WAAU;IACV,eAAe,QAAQ,IAAI;IAP7B,UAAA,CASE,oBAAC,MAAD;KAAM,MAAK;KAA6C,UAAU;IAAW,CAAA,GAC7E,oBAAC,QAAD;KAAM,WAAU;KAAuB,UAAA,EAAE,QAAQ,CAAC,GAAG,MAAM;IAAQ,CAAA,CAC7D;GACR,CAAA,GAAA,qBAAC,cAAD;IAAc,MAAK;IAAS,WAAU;IAAtC,UAAA,CACE,oBAAC,aAAD;KAAa,WAAU;KACrB,UAAA,oBAAC,YAAD,EAAA,UAAa,EAAE,QAAQ,CAAC,GAAG,MAAM,EAAc,CAAA;IACpC,CAAA,GACb,oBAAC,OAAD;KACE,cAAY,EAAE,cAAc,CAAC,GAAG,YAAY;KAC5C,WAAU;KAET,UAAA,SACE,SAAS,SAAS,CAAC,MAAM,GAAI,KAAK,YAAY,CAAC,CAAE,CAAC,CAAC,CACnD,KAAK,SACJ,qBAAC,MAAD;MAEE,MAAM,kBAAkB,IAAI,KAAK;MACjC,eAAe,QAAQ,KAAK;MAC5B,eAAa,uBAAuB,IAAI,IAAI,SAAS,KAAA;MACrD,WAAW,GACT,0HACF;MAPF,UAAA,CASG,KAAK,QAAQ,oBAAC,MAAD,EAAM,MAAM,KAAK,KAAO,CAAA,GACtC,oBAAC,UAAD,EAAgB,KAAO,CAAA,CACnB;KAVC,GAAA,KAAK,OAAO,KAAK,KAUlB,CACP;IACA,CAAA,CACO;GACT,CAAA,CAAA;EAEN,CAAA,CAAA;;AAET"}
|
|
@@ -31,7 +31,7 @@ function KeyboardShortcuts() {
|
|
|
31
31
|
const overrides = JSON.stringify(config ? config.overrides ?? {} : {});
|
|
32
32
|
const token = page?.token ?? "";
|
|
33
33
|
const commands = panel.commandPalette?.commands;
|
|
34
|
-
const home = panel.url;
|
|
34
|
+
const home = panel.dashboardUrl ?? panel.url;
|
|
35
35
|
const overlayLabel = t("shortcuts.show_overlay", {}, "Show keyboard shortcuts");
|
|
36
36
|
const searchLabel = t("shortcuts.focus_search", {}, "Search the table");
|
|
37
37
|
const dashboardLabel = t("shortcuts.go_dashboard", {}, "Go to the dashboard");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard-shortcuts.js","names":[],"sources":["../../../src/components/panel/keyboard-shortcuts.tsx"],"sourcesContent":["import type { CommandSchema } from '@alfakit/core'\nimport {\n COMMANDS_COMPONENT,\n commandShortcutId,\n focusTableSearch,\n SHORTCUT_IDS,\n shortcuts,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { useEffect, useState } from 'react'\nimport { useActionRunner, usePageSchema, usePanel, useT } from '../../context'\nimport type { ShortcutsOverlay as Overlay } from './shortcuts-overlay'\n\ntype OverlayComponent = typeof Overlay\n\nlet loadedOverlay: OverlayComponent | undefined\nlet loadingOverlay: Promise<OverlayComponent> | undefined\n\n/** The `?` overlay's code: loaded the first time it opens; the shortcuts register eagerly. */\nfunction loadOverlay(): Promise<OverlayComponent> {\n loadingOverlay ??= import('./shortcuts-overlay').then(\n (module) => (loadedOverlay = module.ShortcutsOverlay),\n (error: unknown) => {\n loadingOverlay = undefined\n throw error\n },\n )\n\n return loadingOverlay\n}\n\n/**\n * Keyboard shortcuts of the panel: `/` (focus the table search), `g d` (dashboard), `?`\n * (the overlay) and the panel commands' `shortcut()`, with the user's customised keys\n * (`alfakit.keyboardShortcuts`). The overlay lists everything available on the page,\n * grouped, and lets users change keys (stored on the server).\n */\nexport function KeyboardShortcuts() {\n const t = useT()\n const panel = usePanel()\n const page = usePageSchema()\n const runner = useActionRunner()\n const config = panel.keyboardShortcuts\n const [open, setOpen] = useState(false)\n // Undefined on the server and during hydration (nothing loads there), so the markup matches.\n const [ShortcutsOverlay, setOverlay] = useState<OverlayComponent | undefined>(() => loadedOverlay)\n const overrides = JSON.stringify(config ? (config.overrides ?? {}) : {})\n const token = page?.token ?? ''\n const commands = panel.commandPalette?.commands\n const home = panel.url\n // Strings, so the effect below doesn't re-run on every render (`t` is a new function each time).\n const overlayLabel = t('shortcuts.show_overlay', {}, 'Show keyboard shortcuts')\n const searchLabel = t('shortcuts.focus_search', {}, 'Search the table')\n const dashboardLabel = t('shortcuts.go_dashboard', {}, 'Go to the dashboard')\n\n useEffect(() => {\n shortcuts.setEnabled(config !== false)\n shortcuts.setOverrides(JSON.parse(overrides) as Record<string, string[]>)\n }, [config, overrides])\n\n useEffect(() => {\n if (!config) return\n\n const disposers = [\n shortcuts.register({\n id: SHORTCUT_IDS.overlay,\n keys: ['?'],\n label: overlayLabel,\n group: 'general',\n handler: () => setOpen(true),\n }),\n // Declined (`false`) on pages without a table search, so `/` keeps its default.\n shortcuts.register({\n id: SHORTCUT_IDS.search,\n keys: ['/'],\n label: searchLabel,\n group: 'general',\n handler: () => focusTableSearch(),\n }),\n ...(home\n ? [\n shortcuts.register({\n id: SHORTCUT_IDS.dashboard,\n keys: ['g d'],\n label: dashboardLabel,\n group: 'navigation',\n handler: () => router.visit(home),\n }),\n ]\n : []),\n ...(commands ?? [])\n .filter((command: CommandSchema) => command.keyBindings?.length && !command.disabled)\n .map((command: CommandSchema) =>\n shortcuts.register({\n id: commandShortcutId(command.name),\n keys: command.keyBindings ?? [],\n label: command.label ?? command.name,\n group: command.url ? 'navigation' : 'commands',\n handler: () => void runner.trigger(command, { token, component: COMMANDS_COMPONENT }),\n }),\n ),\n ]\n\n return () => disposers.forEach((dispose) => dispose())\n }, [config, commands, home, runner, token, overlayLabel, searchLabel, dashboardLabel])\n\n // `?` (or the palette's \"Keyboard shortcuts\") before the code has arrived keeps `open`\n // set; the overlay mounts open once it has loaded.\n useEffect(() => {\n if (!open || ShortcutsOverlay) return\n\n let active = true\n\n loadOverlay().then(\n (component) => active && setOverlay(() => component),\n () => active && setOpen(false),\n )\n\n return () => {\n active = false\n }\n }, [open, ShortcutsOverlay])\n\n if (!config || !ShortcutsOverlay) return null\n\n return (\n <ShortcutsOverlay\n open={open}\n onOpenChange={setOpen}\n customizable={Boolean(config.customizable && panel.endpoints.shortcuts)}\n endpoint={panel.endpoints.shortcuts}\n />\n )\n}\n"],"mappings":";;;;;;AAeA,IAAI;AACJ,IAAI;;AAGJ,SAAS,cAAyC;CAChD,mBAAmB,OAAO,yBAAsB,CAAC,MAC9C,WAAY,gBAAgB,OAAO,mBACnC,UAAmB;EAClB,iBAAiB,KAAA;EACjB,MAAM;CACR,CACF;CAEA,OAAO;AACT;;;;;;;AAQA,SAAgB,oBAAoB;CAClC,MAAM,IAAI,KAAK;CACf,MAAM,QAAQ,SAAS;CACvB,MAAM,OAAO,cAAc;CAC3B,MAAM,SAAS,gBAAgB;CAC/B,MAAM,SAAS,MAAM;CACrB,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,MAAM,CAAC,kBAAkB,cAAc,eAA6C,aAAa;CACjG,MAAM,YAAY,KAAK,UAAU,SAAU,OAAO,aAAa,CAAC,IAAK,CAAC,CAAC;CACvE,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,WAAW,MAAM,gBAAgB;CACvC,MAAM,OAAO,MAAM;
|
|
1
|
+
{"version":3,"file":"keyboard-shortcuts.js","names":[],"sources":["../../../src/components/panel/keyboard-shortcuts.tsx"],"sourcesContent":["import type { CommandSchema } from '@alfakit/core'\nimport {\n COMMANDS_COMPONENT,\n commandShortcutId,\n focusTableSearch,\n SHORTCUT_IDS,\n shortcuts,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { useEffect, useState } from 'react'\nimport { useActionRunner, usePageSchema, usePanel, useT } from '../../context'\nimport type { ShortcutsOverlay as Overlay } from './shortcuts-overlay'\n\ntype OverlayComponent = typeof Overlay\n\nlet loadedOverlay: OverlayComponent | undefined\nlet loadingOverlay: Promise<OverlayComponent> | undefined\n\n/** The `?` overlay's code: loaded the first time it opens; the shortcuts register eagerly. */\nfunction loadOverlay(): Promise<OverlayComponent> {\n loadingOverlay ??= import('./shortcuts-overlay').then(\n (module) => (loadedOverlay = module.ShortcutsOverlay),\n (error: unknown) => {\n loadingOverlay = undefined\n throw error\n },\n )\n\n return loadingOverlay\n}\n\n/**\n * Keyboard shortcuts of the panel: `/` (focus the table search), `g d` (dashboard), `?`\n * (the overlay) and the panel commands' `shortcut()`, with the user's customised keys\n * (`alfakit.keyboardShortcuts`). The overlay lists everything available on the page,\n * grouped, and lets users change keys (stored on the server).\n */\nexport function KeyboardShortcuts() {\n const t = useT()\n const panel = usePanel()\n const page = usePageSchema()\n const runner = useActionRunner()\n const config = panel.keyboardShortcuts\n const [open, setOpen] = useState(false)\n // Undefined on the server and during hydration (nothing loads there), so the markup matches.\n const [ShortcutsOverlay, setOverlay] = useState<OverlayComponent | undefined>(() => loadedOverlay)\n const overrides = JSON.stringify(config ? (config.overrides ?? {}) : {})\n const token = page?.token ?? ''\n const commands = panel.commandPalette?.commands\n const home = panel.dashboardUrl ?? panel.url\n // Strings, so the effect below doesn't re-run on every render (`t` is a new function each time).\n const overlayLabel = t('shortcuts.show_overlay', {}, 'Show keyboard shortcuts')\n const searchLabel = t('shortcuts.focus_search', {}, 'Search the table')\n const dashboardLabel = t('shortcuts.go_dashboard', {}, 'Go to the dashboard')\n\n useEffect(() => {\n shortcuts.setEnabled(config !== false)\n shortcuts.setOverrides(JSON.parse(overrides) as Record<string, string[]>)\n }, [config, overrides])\n\n useEffect(() => {\n if (!config) return\n\n const disposers = [\n shortcuts.register({\n id: SHORTCUT_IDS.overlay,\n keys: ['?'],\n label: overlayLabel,\n group: 'general',\n handler: () => setOpen(true),\n }),\n // Declined (`false`) on pages without a table search, so `/` keeps its default.\n shortcuts.register({\n id: SHORTCUT_IDS.search,\n keys: ['/'],\n label: searchLabel,\n group: 'general',\n handler: () => focusTableSearch(),\n }),\n ...(home\n ? [\n shortcuts.register({\n id: SHORTCUT_IDS.dashboard,\n keys: ['g d'],\n label: dashboardLabel,\n group: 'navigation',\n handler: () => router.visit(home),\n }),\n ]\n : []),\n ...(commands ?? [])\n .filter((command: CommandSchema) => command.keyBindings?.length && !command.disabled)\n .map((command: CommandSchema) =>\n shortcuts.register({\n id: commandShortcutId(command.name),\n keys: command.keyBindings ?? [],\n label: command.label ?? command.name,\n group: command.url ? 'navigation' : 'commands',\n handler: () => void runner.trigger(command, { token, component: COMMANDS_COMPONENT }),\n }),\n ),\n ]\n\n return () => disposers.forEach((dispose) => dispose())\n }, [config, commands, home, runner, token, overlayLabel, searchLabel, dashboardLabel])\n\n // `?` (or the palette's \"Keyboard shortcuts\") before the code has arrived keeps `open`\n // set; the overlay mounts open once it has loaded.\n useEffect(() => {\n if (!open || ShortcutsOverlay) return\n\n let active = true\n\n loadOverlay().then(\n (component) => active && setOverlay(() => component),\n () => active && setOpen(false),\n )\n\n return () => {\n active = false\n }\n }, [open, ShortcutsOverlay])\n\n if (!config || !ShortcutsOverlay) return null\n\n return (\n <ShortcutsOverlay\n open={open}\n onOpenChange={setOpen}\n customizable={Boolean(config.customizable && panel.endpoints.shortcuts)}\n endpoint={panel.endpoints.shortcuts}\n />\n )\n}\n"],"mappings":";;;;;;AAeA,IAAI;AACJ,IAAI;;AAGJ,SAAS,cAAyC;CAChD,mBAAmB,OAAO,yBAAsB,CAAC,MAC9C,WAAY,gBAAgB,OAAO,mBACnC,UAAmB;EAClB,iBAAiB,KAAA;EACjB,MAAM;CACR,CACF;CAEA,OAAO;AACT;;;;;;;AAQA,SAAgB,oBAAoB;CAClC,MAAM,IAAI,KAAK;CACf,MAAM,QAAQ,SAAS;CACvB,MAAM,OAAO,cAAc;CAC3B,MAAM,SAAS,gBAAgB;CAC/B,MAAM,SAAS,MAAM;CACrB,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CAEtC,MAAM,CAAC,kBAAkB,cAAc,eAA6C,aAAa;CACjG,MAAM,YAAY,KAAK,UAAU,SAAU,OAAO,aAAa,CAAC,IAAK,CAAC,CAAC;CACvE,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,WAAW,MAAM,gBAAgB;CACvC,MAAM,OAAO,MAAM,gBAAgB,MAAM;CAEzC,MAAM,eAAe,EAAE,0BAA0B,CAAC,GAAG,yBAAyB;CAC9E,MAAM,cAAc,EAAE,0BAA0B,CAAC,GAAG,kBAAkB;CACtE,MAAM,iBAAiB,EAAE,0BAA0B,CAAC,GAAG,qBAAqB;CAE5E,gBAAgB;EACd,UAAU,WAAW,WAAW,KAAK;EACrC,UAAU,aAAa,KAAK,MAAM,SAAS,CAA6B;CAC1E,GAAG,CAAC,QAAQ,SAAS,CAAC;CAEtB,gBAAgB;EACd,IAAI,CAAC,QAAQ;EAEb,MAAM,YAAY;GAChB,UAAU,SAAS;IACjB,IAAI,aAAa;IACjB,MAAM,CAAC,GAAG;IACV,OAAO;IACP,OAAO;IACP,eAAe,QAAQ,IAAI;GAC7B,CAAC;GAED,UAAU,SAAS;IACjB,IAAI,aAAa;IACjB,MAAM,CAAC,GAAG;IACV,OAAO;IACP,OAAO;IACP,eAAe,iBAAiB;GAClC,CAAC;GACD,GAAI,OACA,CACE,UAAU,SAAS;IACjB,IAAI,aAAa;IACjB,MAAM,CAAC,KAAK;IACZ,OAAO;IACP,OAAO;IACP,eAAe,OAAO,MAAM,IAAI;GAClC,CAAC,CACH,IACA,CAAC;GACL,IAAI,YAAY,CAAC,EAAA,CACd,QAAQ,YAA2B,QAAQ,aAAa,UAAU,CAAC,QAAQ,QAAQ,CAAC,CACpF,KAAK,YACJ,UAAU,SAAS;IACjB,IAAI,kBAAkB,QAAQ,IAAI;IAClC,MAAM,QAAQ,eAAe,CAAC;IAC9B,OAAO,QAAQ,SAAS,QAAQ;IAChC,OAAO,QAAQ,MAAM,eAAe;IACpC,eAAe,KAAK,OAAO,QAAQ,SAAS;KAAE;KAAO,WAAW;IAAmB,CAAC;GACtF,CAAC,CACH;EACJ;EAEA,aAAa,UAAU,SAAS,YAAY,QAAQ,CAAC;CACvD,GAAG;EAAC;EAAQ;EAAU;EAAM;EAAQ;EAAO;EAAc;EAAa;CAAc,CAAC;CAIrF,gBAAgB;EACd,IAAI,CAAC,QAAQ,kBAAkB;EAE/B,IAAI,SAAS;EAEb,YAAY,CAAC,CAAC,MACX,cAAc,UAAU,iBAAiB,SAAS,SAC7C,UAAU,QAAQ,KAAK,CAC/B;EAEA,aAAa;GACX,SAAS;EACX;CACF,GAAG,CAAC,MAAM,gBAAgB,CAAC;CAE3B,IAAI,CAAC,UAAU,CAAC,kBAAkB,OAAO;CAEzC,OACE,oBAAC,kBAAD;EACQ;EACN,cAAc;EACd,cAAc,QAAQ,OAAO,gBAAgB,MAAM,UAAU,SAAS;EACtE,UAAU,MAAM,UAAU;CAC3B,CAAA;AAEL"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.d.ts","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"mappings":";;;iBAIS,kBACP,kBACG,SACF,sBAAsBA,QAAiB,4BAAS,IAAA;iBAU1CC,eAAa,SAAS,sBAAsBD,QAAiB,wBAAK,IAAA;iBAIlE,oBAAoB,SAAS,sBAAsBA,QAAiB,2BAAQ,IAAA;
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"mappings":";;;iBAIS,kBACP,kBACG,SACF,sBAAsBA,QAAiB,4BAAS,IAAA;iBAU1CC,eAAa,SAAS,sBAAsBD,QAAiB,wBAAK,IAAA;iBAIlE,oBAAoB,SAAS,sBAAsBA,QAAiB,2BAAQ,IAAA;iBAa5E,iBACP,WACA,YACA,aACG,SACF,sBAAsBA,QAAiB,2BAAQ,IAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"sourcesContent":["import { Direction, Tooltip as TooltipPrimitive } from 'radix-ui'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/utils'\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: ComponentProps<typeof TooltipPrimitive.Provider>) {\n return (\n <TooltipPrimitive.Provider\n data-slot=\"tooltip-provider\"\n delayDuration={delayDuration}\n {...props}\n />\n )\n}\n\nfunction Tooltip({ ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {\n return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n}\n\nfunction TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {\n return <TooltipPrimitive.Trigger data-slot
|
|
1
|
+
{"version":3,"file":"tooltip.js","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"sourcesContent":["import { Direction, Tooltip as TooltipPrimitive } from 'radix-ui'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/utils'\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: ComponentProps<typeof TooltipPrimitive.Provider>) {\n return (\n <TooltipPrimitive.Provider\n data-slot=\"tooltip-provider\"\n delayDuration={delayDuration}\n {...props}\n />\n )\n}\n\nfunction Tooltip({ ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {\n return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n}\n\nfunction TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {\n // With asChild the child keeps its own data-slot (an action's `button`, which the\n // button-shape and touch-target CSS select).\n // (The key must be left out, not set to undefined: Slot passes it on and the child's own\n // `{...props}` would erase its data-slot.)\n return (\n <TooltipPrimitive.Trigger\n {...(props.asChild ? {} : { 'data-slot': 'tooltip-trigger' })}\n {...props}\n />\n )\n}\n\nfunction TooltipContent({\n className,\n sideOffset = 4,\n children,\n ...props\n}: ComponentProps<typeof TooltipPrimitive.Content>) {\n // Portaled outside Alfakit's root: carry the reading direction (starter kit pages and\n // standalone components leave <html dir> to the host).\n const dir = Direction.useDirection()\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n dir={dir}\n data-slot=\"tooltip-content\"\n sideOffset={sideOffset}\n className={cn(\n 'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',\n className,\n )}\n {...props}\n >\n {children}\n <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n )\n}\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }\n"],"mappings":";;;;AAIA,SAAS,gBAAgB,EACvB,gBAAgB,GAChB,GAAG,SACgD;CACnD,OACE,oBAACA,QAAiB,UAAlB;EACE,aAAU;EACK;EACf,GAAI;CACL,CAAA;AAEL;AAEA,SAASC,UAAQ,EAAE,GAAG,SAAuD;CAC3E,OAAO,oBAACD,QAAiB,MAAlB;EAAuB,aAAU;EAAU,GAAI;CAAQ,CAAA;AAChE;AAEA,SAAS,eAAe,EAAE,GAAG,SAA0D;CAKrF,OACE,oBAACA,QAAiB,SAAlB;EACE,GAAK,MAAM,UAAU,CAAC,IAAI,EAAE,aAAa,kBAAkB;EAC3D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,EACtB,WACA,aAAa,GACb,UACA,GAAG,SAC+C;CAGlD,MAAM,MAAM,UAAU,aAAa;CACnC,OACE,oBAACA,QAAiB,QAAlB,EAAA,UACE,qBAACA,QAAiB,SAAlB;EACO;EACL,aAAU;EACE;EACZ,WAAW,GACT,qaACA,SACF;EACA,GAAI;EARN,UAAA,CAUG,UACD,oBAACA,QAAiB,OAAlB,EAAwB,WAAU,qGAAsG,CAAA,CAChH;CACH,CAAA,EAAA,CAAA;AAE7B"}
|
package/dist/eject-manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@alfakit/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"adapter": "react",
|
|
5
|
-
"generatedAt": "2026-10-
|
|
5
|
+
"generatedAt": "2026-10-25T05:35:46.937Z",
|
|
6
6
|
"components": {
|
|
7
7
|
"TextInput": {
|
|
8
8
|
"file": "src/components/fields/text-input.tsx",
|
|
@@ -76,7 +76,7 @@ function TopNavMenu({ label, icon, items, active, attributes }) {
|
|
|
76
76
|
href: item.url ?? "#",
|
|
77
77
|
children: [item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon }), /* @__PURE__ */ jsx(NavLabel, { item })]
|
|
78
78
|
})
|
|
79
|
-
}, item.label))
|
|
79
|
+
}, item.url ?? item.label))
|
|
80
80
|
})] });
|
|
81
81
|
}
|
|
82
82
|
function TopNavGroup({ group }) {
|
|
@@ -88,7 +88,7 @@ function TopNavGroup({ group }) {
|
|
|
88
88
|
extraTopbarAttributes: void 0
|
|
89
89
|
} })
|
|
90
90
|
});
|
|
91
|
-
if (!group.label) return /* @__PURE__ */ jsx(Fragment$1, { children: group.items.map((item) => /* @__PURE__ */ jsx(TopNavLink, { item }, item.label)) });
|
|
91
|
+
if (!group.label) return /* @__PURE__ */ jsx(Fragment$1, { children: group.items.map((item) => /* @__PURE__ */ jsx(TopNavLink, { item }, item.url ?? item.label)) });
|
|
92
92
|
return /* @__PURE__ */ jsx(TopNavMenu, {
|
|
93
93
|
label: group.label,
|
|
94
94
|
icon: group.icon,
|
|
@@ -112,7 +112,7 @@ function MobileNav({ onNavigate }) {
|
|
|
112
112
|
"data-active": item.active ? "true" : void 0,
|
|
113
113
|
className: "hover:bg-accent data-[active=true]:bg-accent flex items-center gap-2 rounded-md px-2 py-2 font-medium [&_svg]:size-4",
|
|
114
114
|
children: [item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon }), /* @__PURE__ */ jsx(NavLabel, { item })]
|
|
115
|
-
}, item.label))]
|
|
115
|
+
}, item.url ?? item.label))]
|
|
116
116
|
}, group.label ?? index))
|
|
117
117
|
});
|
|
118
118
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"top-nav-layout.js","names":[],"sources":["../../src/layouts/top-nav-layout.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n hasMobilePreset,\n type BreadcrumbSchema,\n type NavigationGroupSchema,\n type NavigationItemSchema,\n type Width,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { ChevronDownIcon, MenuIcon } from 'lucide-react'\nimport { createElement, useState, type ReactNode } from 'react'\nimport { NotificationsMenu } from '../components/notifications/notifications-menu'\nimport { BottomNav } from '../components/panel/bottom-nav'\nimport { Brand } from '../components/panel/brand'\nimport { DebugPanelSlot } from '../components/panel/debug-panel-slot'\nimport { GlobalSearch } from '../components/panel/global-search'\nimport { TopbarLocaleSwitcher } from '../components/panel/locale-switcher'\nimport { NavLabel } from '../components/panel/nav-label'\nimport { Slot } from '../components/panel/slot'\nimport { TenantMenu } from '../components/panel/tenant-menu'\nimport { ThemeToggle } from '../components/panel/theme-toggle'\nimport { Breadcrumbs } from '../components/panel/topbar'\nimport { UserMenu } from '../components/panel/user-menu'\nimport { Button } from '../components/ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '../components/ui/dropdown-menu'\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../components/ui/sheet'\nimport { useDirection, usePanel, useRegistry, useT } from '../context'\nimport { extraProps } from '../lib/attributes'\nimport { Icon } from '../lib/icons'\nimport { NewTabHint } from '../lib/new-tab-hint'\nimport { cn } from '../lib/utils'\nimport { MAX_WIDTHS } from './max-widths'\n\nexport interface TopNavLayoutProps {\n children: ReactNode\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n}\n\nconst isActive = (item: NavigationItemSchema): boolean =>\n Boolean(item.active || item.children?.some(isActive))\n\nfunction TopNavLink({ item }: { item: NavigationItemSchema }) {\n const className =\n 'text-muted-foreground hover:text-foreground hover:bg-accent data-[active=true]:bg-accent data-[active=true]:text-foreground inline-flex h-9 shrink-0 items-center gap-2 rounded-md px-3 text-sm font-medium transition-colors [&_svg]:size-4'\n\n if (item.children && item.children.length > 0) {\n return (\n <TopNavMenu\n label={<NavLabel item={item} />}\n icon={item.icon}\n items={item.children}\n active={isActive(item)}\n attributes={item.extraAttributes}\n />\n )\n }\n\n return item.openInNewTab ? (\n <a\n // `NavigationItem::extraAttributes()`; `class` is merged with the link's own.\n {...extraProps(item.extraAttributes, className)}\n href={item.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-slot=\"topnav-link\"\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NewTabHint />\n </a>\n ) : (\n <Link\n {...extraProps(item.extraAttributes, className)}\n href={item.url ?? '#'}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n data-slot=\"topnav-link\"\n data-active={item.active ? 'true' : undefined}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n )\n}\n\nfunction TopNavMenu({\n label,\n icon,\n items,\n active,\n attributes,\n}: {\n label: ReactNode\n icon?: string\n items: NavigationItemSchema[]\n active: boolean\n attributes?: NavigationItemSchema['extraAttributes']\n}) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n {...extraProps(\n attributes,\n 'text-muted-foreground hover:text-foreground hover:bg-accent data-[active=true]:bg-accent data-[active=true]:text-foreground data-[state=open]:text-foreground inline-flex h-9 shrink-0 items-center gap-1.5 rounded-md px-3 text-sm font-medium transition-colors outline-none [&_svg]:size-4',\n )}\n data-slot=\"topnav-link\"\n data-active={active ? 'true' : undefined}\n >\n {icon && <Icon name={icon} />}\n {label}\n <ChevronDownIcon className=\"opacity-60\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-48\">\n {items.map((item) => (\n <DropdownMenuItem key={item.label} asChild>\n <Link\n {...extraProps(item.extraAttributes, cn('w-full', item.active && 'font-medium'))}\n href={item.url ?? '#'}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction TopNavGroup({ group }: { group: NavigationGroupSchema }) {\n // `NavigationGroup::extraTopbarAttributes()` wrap the group; `class` is merged.\n if (group.extraTopbarAttributes) {\n return (\n <div\n data-slot=\"topnav-group\"\n {...extraProps(group.extraTopbarAttributes, 'flex shrink-0 items-center gap-1')}\n >\n <TopNavGroup group={{ ...group, extraTopbarAttributes: undefined }} />\n </div>\n )\n }\n\n if (!group.label) {\n return (\n <>\n {group.items.map((item) => (\n <TopNavLink key={item.label} item={item} />\n ))}\n </>\n )\n }\n\n return (\n <TopNavMenu\n label={group.label}\n icon={group.icon}\n items={group.items}\n active={group.items.some(isActive)}\n />\n )\n}\n\n/** Vertical navigation for the mobile drawer. */\nfunction MobileNav({ onNavigate }: { onNavigate: () => void }) {\n const panel = usePanel()\n\n return (\n <nav className=\"flex flex-col gap-4 overflow-y-auto p-4 text-sm\">\n {panel.navigation.map((group, index) => (\n <div key={group.label ?? index} className=\"flex flex-col gap-1\">\n {group.label && (\n <p className=\"text-muted-foreground px-2 text-xs font-medium\">{group.label}</p>\n )}\n {group.items\n .flatMap((item) => [item, ...(item.children ?? [])])\n .map((item) => (\n <Link\n key={item.label}\n href={item.url ?? '#'}\n onClick={onNavigate}\n data-active={item.active ? 'true' : undefined}\n className=\"hover:bg-accent data-[active=true]:bg-accent flex items-center gap-2 rounded-md px-2 py-2 font-medium [&_svg]:size-4\"\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n ))}\n </div>\n ))}\n </nav>\n )\n}\n\n/**\n * Navigation across the top instead of a sidebar (`layout: 'topbar'`). Ungrouped items are\n * links; labelled groups and items with children open as dropdowns.\n */\nexport function TopNavLayout({ children, breadcrumbs, maxContentWidth }: TopNavLayoutProps) {\n const panel = usePanel()\n const t = useT()\n const direction = useDirection()\n const registry = useRegistry()\n const [open, setOpen] = useState(false)\n const width = MAX_WIDTHS[maxContentWidth ?? panel.maxContentWidth ?? '7xl'] ?? 'max-w-7xl'\n const Bottom = registry.resolve('panel:bottom-nav') ?? BottomNav\n\n return (\n <div data-slot=\"topnav-layout\" className=\"bg-background flex min-h-svh flex-col\">\n <Slot name=\"body.start\" />\n <Slot name=\"layout.start\" />\n <Slot name=\"topbar.before\" />\n <header\n data-slot=\"topbar\"\n className=\"bg-background/90 supports-[backdrop-filter]:bg-background/70 sticky top-0 z-30 border-b backdrop-blur\"\n >\n <div className={cn('mx-auto flex h-14 w-full items-center gap-3 px-4 md:px-6', width)}>\n <Sheet open={open} onOpenChange={setOpen}>\n <SheetTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"md:hidden\"\n aria-label={t('toggle_sidebar', {}, 'Toggle sidebar')}\n >\n <Icon name=\"alfakit::panels.topbar.open-sidebar-button\" fallback={MenuIcon} />\n </Button>\n </SheetTrigger>\n <SheetContent side={direction === 'rtl' ? 'right' : 'left'} className=\"w-72 p-0\">\n <SheetHeader className=\"border-b\">\n <SheetTitle asChild>\n <div>\n <Brand />\n </div>\n </SheetTitle>\n </SheetHeader>\n <MobileNav onNavigate={() => setOpen(false)} />\n </SheetContent>\n </Sheet>\n <Slot name=\"topbar.logo.before\" />\n <Brand className=\"me-2 shrink-0\" />\n <Slot name=\"topbar.logo.after\" />\n {panel.tenancy?.current && <Slot name=\"tenant-menu.before\" />}\n <TenantMenu variant=\"topbar\" />\n {panel.tenancy?.current && <Slot name=\"tenant-menu.after\" />}\n <nav\n data-slot=\"topnav\"\n aria-label={t('navigation', {}, 'Navigation')}\n className=\"hidden min-w-0 flex-1 items-center gap-1 self-stretch overflow-x-auto md:flex\"\n >\n {panel.navigation.map((group, index) => (\n <TopNavGroup key={group.label ?? `group-${index}`} group={group} />\n ))}\n </nav>\n <Slot name=\"topbar.start\" />\n <div className=\"ms-auto flex shrink-0 items-center gap-1\">\n <Slot name=\"topbar.end\" />\n <GlobalSearch />\n <NotificationsMenu />\n <TopbarLocaleSwitcher />\n <ThemeToggle />\n <DebugPanelSlot />\n <UserMenu variant=\"topbar\" />\n </div>\n </div>\n </header>\n <Slot name=\"topbar.after\" />\n <Slot name=\"content.before\" />\n <main\n data-slot=\"content\"\n className={cn('mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6', width)}\n >\n {panel.breadcrumbs && breadcrumbs && breadcrumbs.length > 0 && (\n <Breadcrumbs items={breadcrumbs} />\n )}\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </main>\n <Slot name=\"content.after\" />\n <Slot name=\"footer\" />\n {hasMobilePreset(panel) && createElement(Bottom)}\n <Slot name=\"layout.end\" />\n <Slot name=\"body.end\" />\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"top-nav-layout.js","names":[],"sources":["../../src/layouts/top-nav-layout.tsx"],"sourcesContent":["import {\n PREFETCH_CACHE_FOR,\n hasMobilePreset,\n type BreadcrumbSchema,\n type NavigationGroupSchema,\n type NavigationItemSchema,\n type Width,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { ChevronDownIcon, MenuIcon } from 'lucide-react'\nimport { createElement, useState, type ReactNode } from 'react'\nimport { NotificationsMenu } from '../components/notifications/notifications-menu'\nimport { BottomNav } from '../components/panel/bottom-nav'\nimport { Brand } from '../components/panel/brand'\nimport { DebugPanelSlot } from '../components/panel/debug-panel-slot'\nimport { GlobalSearch } from '../components/panel/global-search'\nimport { TopbarLocaleSwitcher } from '../components/panel/locale-switcher'\nimport { NavLabel } from '../components/panel/nav-label'\nimport { Slot } from '../components/panel/slot'\nimport { TenantMenu } from '../components/panel/tenant-menu'\nimport { ThemeToggle } from '../components/panel/theme-toggle'\nimport { Breadcrumbs } from '../components/panel/topbar'\nimport { UserMenu } from '../components/panel/user-menu'\nimport { Button } from '../components/ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '../components/ui/dropdown-menu'\nimport { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '../components/ui/sheet'\nimport { useDirection, usePanel, useRegistry, useT } from '../context'\nimport { extraProps } from '../lib/attributes'\nimport { Icon } from '../lib/icons'\nimport { NewTabHint } from '../lib/new-tab-hint'\nimport { cn } from '../lib/utils'\nimport { MAX_WIDTHS } from './max-widths'\n\nexport interface TopNavLayoutProps {\n children: ReactNode\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n}\n\nconst isActive = (item: NavigationItemSchema): boolean =>\n Boolean(item.active || item.children?.some(isActive))\n\nfunction TopNavLink({ item }: { item: NavigationItemSchema }) {\n const className =\n 'text-muted-foreground hover:text-foreground hover:bg-accent data-[active=true]:bg-accent data-[active=true]:text-foreground inline-flex h-9 shrink-0 items-center gap-2 rounded-md px-3 text-sm font-medium transition-colors [&_svg]:size-4'\n\n if (item.children && item.children.length > 0) {\n return (\n <TopNavMenu\n label={<NavLabel item={item} />}\n icon={item.icon}\n items={item.children}\n active={isActive(item)}\n attributes={item.extraAttributes}\n />\n )\n }\n\n return item.openInNewTab ? (\n <a\n // `NavigationItem::extraAttributes()`; `class` is merged with the link's own.\n {...extraProps(item.extraAttributes, className)}\n href={item.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-slot=\"topnav-link\"\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n <NewTabHint />\n </a>\n ) : (\n <Link\n {...extraProps(item.extraAttributes, className)}\n href={item.url ?? '#'}\n prefetch\n cacheFor={PREFETCH_CACHE_FOR}\n data-slot=\"topnav-link\"\n data-active={item.active ? 'true' : undefined}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n )\n}\n\nfunction TopNavMenu({\n label,\n icon,\n items,\n active,\n attributes,\n}: {\n label: ReactNode\n icon?: string\n items: NavigationItemSchema[]\n active: boolean\n attributes?: NavigationItemSchema['extraAttributes']\n}) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger\n {...extraProps(\n attributes,\n 'text-muted-foreground hover:text-foreground hover:bg-accent data-[active=true]:bg-accent data-[active=true]:text-foreground data-[state=open]:text-foreground inline-flex h-9 shrink-0 items-center gap-1.5 rounded-md px-3 text-sm font-medium transition-colors outline-none [&_svg]:size-4',\n )}\n data-slot=\"topnav-link\"\n data-active={active ? 'true' : undefined}\n >\n {icon && <Icon name={icon} />}\n {label}\n <ChevronDownIcon className=\"opacity-60\" />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"min-w-48\">\n {items.map((item) => (\n <DropdownMenuItem key={item.url ?? item.label} asChild>\n <Link\n {...extraProps(item.extraAttributes, cn('w-full', item.active && 'font-medium'))}\n href={item.url ?? '#'}\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\nfunction TopNavGroup({ group }: { group: NavigationGroupSchema }) {\n // `NavigationGroup::extraTopbarAttributes()` wrap the group; `class` is merged.\n if (group.extraTopbarAttributes) {\n return (\n <div\n data-slot=\"topnav-group\"\n {...extraProps(group.extraTopbarAttributes, 'flex shrink-0 items-center gap-1')}\n >\n <TopNavGroup group={{ ...group, extraTopbarAttributes: undefined }} />\n </div>\n )\n }\n\n if (!group.label) {\n return (\n <>\n {group.items.map((item) => (\n <TopNavLink key={item.url ?? item.label} item={item} />\n ))}\n </>\n )\n }\n\n return (\n <TopNavMenu\n label={group.label}\n icon={group.icon}\n items={group.items}\n active={group.items.some(isActive)}\n />\n )\n}\n\n/** Vertical navigation for the mobile drawer. */\nfunction MobileNav({ onNavigate }: { onNavigate: () => void }) {\n const panel = usePanel()\n\n return (\n <nav className=\"flex flex-col gap-4 overflow-y-auto p-4 text-sm\">\n {panel.navigation.map((group, index) => (\n <div key={group.label ?? index} className=\"flex flex-col gap-1\">\n {group.label && (\n <p className=\"text-muted-foreground px-2 text-xs font-medium\">{group.label}</p>\n )}\n {group.items\n .flatMap((item) => [item, ...(item.children ?? [])])\n .map((item) => (\n <Link\n key={item.url ?? item.label}\n href={item.url ?? '#'}\n onClick={onNavigate}\n data-active={item.active ? 'true' : undefined}\n className=\"hover:bg-accent data-[active=true]:bg-accent flex items-center gap-2 rounded-md px-2 py-2 font-medium [&_svg]:size-4\"\n >\n {item.icon && <Icon name={item.icon} />}\n <NavLabel item={item} />\n </Link>\n ))}\n </div>\n ))}\n </nav>\n )\n}\n\n/**\n * Navigation across the top instead of a sidebar (`layout: 'topbar'`). Ungrouped items are\n * links; labelled groups and items with children open as dropdowns.\n */\nexport function TopNavLayout({ children, breadcrumbs, maxContentWidth }: TopNavLayoutProps) {\n const panel = usePanel()\n const t = useT()\n const direction = useDirection()\n const registry = useRegistry()\n const [open, setOpen] = useState(false)\n const width = MAX_WIDTHS[maxContentWidth ?? panel.maxContentWidth ?? '7xl'] ?? 'max-w-7xl'\n const Bottom = registry.resolve('panel:bottom-nav') ?? BottomNav\n\n return (\n <div data-slot=\"topnav-layout\" className=\"bg-background flex min-h-svh flex-col\">\n <Slot name=\"body.start\" />\n <Slot name=\"layout.start\" />\n <Slot name=\"topbar.before\" />\n <header\n data-slot=\"topbar\"\n className=\"bg-background/90 supports-[backdrop-filter]:bg-background/70 sticky top-0 z-30 border-b backdrop-blur\"\n >\n <div className={cn('mx-auto flex h-14 w-full items-center gap-3 px-4 md:px-6', width)}>\n <Sheet open={open} onOpenChange={setOpen}>\n <SheetTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"md:hidden\"\n aria-label={t('toggle_sidebar', {}, 'Toggle sidebar')}\n >\n <Icon name=\"alfakit::panels.topbar.open-sidebar-button\" fallback={MenuIcon} />\n </Button>\n </SheetTrigger>\n <SheetContent side={direction === 'rtl' ? 'right' : 'left'} className=\"w-72 p-0\">\n <SheetHeader className=\"border-b\">\n <SheetTitle asChild>\n <div>\n <Brand />\n </div>\n </SheetTitle>\n </SheetHeader>\n <MobileNav onNavigate={() => setOpen(false)} />\n </SheetContent>\n </Sheet>\n <Slot name=\"topbar.logo.before\" />\n <Brand className=\"me-2 shrink-0\" />\n <Slot name=\"topbar.logo.after\" />\n {panel.tenancy?.current && <Slot name=\"tenant-menu.before\" />}\n <TenantMenu variant=\"topbar\" />\n {panel.tenancy?.current && <Slot name=\"tenant-menu.after\" />}\n <nav\n data-slot=\"topnav\"\n aria-label={t('navigation', {}, 'Navigation')}\n className=\"hidden min-w-0 flex-1 items-center gap-1 self-stretch overflow-x-auto md:flex\"\n >\n {panel.navigation.map((group, index) => (\n <TopNavGroup key={group.label ?? `group-${index}`} group={group} />\n ))}\n </nav>\n <Slot name=\"topbar.start\" />\n <div className=\"ms-auto flex shrink-0 items-center gap-1\">\n <Slot name=\"topbar.end\" />\n <GlobalSearch />\n <NotificationsMenu />\n <TopbarLocaleSwitcher />\n <ThemeToggle />\n <DebugPanelSlot />\n <UserMenu variant=\"topbar\" />\n </div>\n </div>\n </header>\n <Slot name=\"topbar.after\" />\n <Slot name=\"content.before\" />\n <main\n data-slot=\"content\"\n className={cn('mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6', width)}\n >\n {panel.breadcrumbs && breadcrumbs && breadcrumbs.length > 0 && (\n <Breadcrumbs items={breadcrumbs} />\n )}\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </main>\n <Slot name=\"content.after\" />\n <Slot name=\"footer\" />\n {hasMobilePreset(panel) && createElement(Bottom)}\n <Slot name=\"layout.end\" />\n <Slot name=\"body.end\" />\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -33,12 +33,19 @@ function AlfakitComponent({ component, components, className, header, footer, ch
|
|
|
33
33
|
const context = useMemo(() => {
|
|
34
34
|
const poll = (target, interval) => state.poll(target, interval);
|
|
35
35
|
const refresh = (targets) => state.refresh(targets);
|
|
36
|
-
if (parent)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
if (parent) {
|
|
37
|
+
const own = createStandaloneContext({
|
|
38
|
+
action: component.endpoints.action,
|
|
39
|
+
modal: component.endpoints.modal
|
|
40
|
+
}, components, poll);
|
|
41
|
+
return {
|
|
42
|
+
...parent,
|
|
43
|
+
actions: own.actions,
|
|
44
|
+
registry: createStandaloneRegistry(components, parent.registry),
|
|
45
|
+
poll,
|
|
46
|
+
refresh
|
|
47
|
+
};
|
|
48
|
+
}
|
|
42
49
|
return {
|
|
43
50
|
...createStandaloneContext({
|
|
44
51
|
action: component.endpoints.action,
|
|
@@ -124,7 +131,10 @@ function AlfakitComponent({ component, components, className, header, footer, ch
|
|
|
124
131
|
]
|
|
125
132
|
}),
|
|
126
133
|
/* @__PURE__ */ jsx(ActionModals, {}),
|
|
127
|
-
ownsToaster && /* @__PURE__ */ jsx(AlfakitToaster, {
|
|
134
|
+
ownsToaster && /* @__PURE__ */ jsx(AlfakitToaster, {
|
|
135
|
+
claimed: true,
|
|
136
|
+
dir: direction
|
|
137
|
+
})
|
|
128
138
|
]
|
|
129
139
|
});
|
|
130
140
|
return /* @__PURE__ */ jsx(AlfakitProvider, {
|
|
@@ -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, 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 */\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 return {\n ...parent,\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 useEffect(() => 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\">\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 shrink-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 />}\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, 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 */\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 useEffect(() => 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\">\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 shrink-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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"}
|
package/dist/standalone.css
CHANGED
|
@@ -54,19 +54,23 @@
|
|
|
54
54
|
--color-gray: var(--muted-foreground);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/* The same status tokens as the panel's styles.css (WCAG AA under their foregrounds). */
|
|
57
58
|
:root {
|
|
58
|
-
--success: var(--alfakit-success, oklch(0.
|
|
59
|
-
--success-foreground: oklch(0.985 0 0);
|
|
60
|
-
--warning: var(--alfakit-warning, oklch(0.
|
|
61
|
-
--warning-foreground: oklch(0.
|
|
62
|
-
--info: var(--alfakit-info, oklch(0.
|
|
63
|
-
--info-foreground: oklch(0.985 0 0);
|
|
59
|
+
--success: var(--alfakit-success, oklch(0.48 0.14 150.5));
|
|
60
|
+
--success-foreground: var(--alfakit-success-foreground, oklch(0.985 0 0));
|
|
61
|
+
--warning: var(--alfakit-warning, oklch(0.52 0.15 48));
|
|
62
|
+
--warning-foreground: var(--alfakit-warning-foreground, oklch(0.985 0 0));
|
|
63
|
+
--info: var(--alfakit-info, oklch(0.51 0.24 263.5));
|
|
64
|
+
--info-foreground: var(--alfakit-info-foreground, oklch(0.985 0 0));
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
.dark {
|
|
67
68
|
--success: var(--alfakit-success, oklch(0.723 0.219 149.579));
|
|
69
|
+
--success-foreground: var(--alfakit-success-foreground, oklch(0.145 0 0));
|
|
68
70
|
--warning: var(--alfakit-warning, oklch(0.828 0.189 84.429));
|
|
71
|
+
--warning-foreground: var(--alfakit-warning-foreground, oklch(0.145 0 0));
|
|
69
72
|
--info: var(--alfakit-info, oklch(0.707 0.165 254.624));
|
|
73
|
+
--info-foreground: var(--alfakit-info-foreground, oklch(0.145 0 0));
|
|
70
74
|
}
|
|
71
75
|
|
|
72
76
|
/*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"starter-kit.d.ts","names":[],"sources":["../../src/starter-kit/starter-kit.tsx"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"starter-kit.d.ts","names":[],"sources":["../../src/starter-kit/starter-kit.tsx"],"mappings":";;;;;;YAsCY,mBAAmB;EAC7B,cAAc;EACd,UAAU;;iBAGK,8BAA8B,KAC7C,eAAe;;;;;;EAQf,SAAS;;;;;;EAMT,WAAW,eAAe;;;;;;;wBAQZ,0BAA0B,UAAU,mBAAmB,iCAAc,IAAA;;;;;;wBAuErE,wBACd,SACA;EAEA,UAAU;EACV,WAAW;oBACZ,IAAA;;;;;;;;;;;;;;wBAmEe,qBAAqB,UAAS;;EAYL,OAAA,OAAA,qCAAuB"}
|
|
@@ -6,6 +6,7 @@ import { createDefaultRegistry } from "../registry.js";
|
|
|
6
6
|
import { setToasterId, showToast } from "../components/notifications/flash-toasts.js";
|
|
7
7
|
import { Slot } from "../components/panel/slot.js";
|
|
8
8
|
import { AlfakitPage } from "../pages/page.js";
|
|
9
|
+
import { DebugPanelSlot } from "../components/panel/debug-panel-slot.js";
|
|
9
10
|
import { MAX_WIDTHS } from "../layouts/max-widths.js";
|
|
10
11
|
import { AuthLayout } from "../layouts/auth-layout.js";
|
|
11
12
|
import { AlfakitRoot } from "../create-alfakit.js";
|
|
@@ -95,9 +96,13 @@ function AlfakitStarterKitRoot({ options, children }) {
|
|
|
95
96
|
dir: panel?.localization?.direction ?? "ltr",
|
|
96
97
|
lang: panel?.localization?.tag,
|
|
97
98
|
className: "contents",
|
|
98
|
-
children: /* @__PURE__ */
|
|
99
|
+
children: /* @__PURE__ */ jsxs(AlfakitRoot, {
|
|
99
100
|
toasterId: STARTER_KIT_TOASTER_ID,
|
|
100
|
-
children
|
|
101
|
+
children: [children, /* @__PURE__ */ jsx("div", {
|
|
102
|
+
"data-slot": "starter-kit-debug-panel",
|
|
103
|
+
className: "fixed end-16 bottom-4 z-40",
|
|
104
|
+
children: /* @__PURE__ */ jsx(DebugPanelSlot, {})
|
|
105
|
+
})]
|
|
101
106
|
})
|
|
102
107
|
})
|
|
103
108
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"starter-kit.js","names":[],"sources":["../../src/starter-kit/starter-kit.tsx"],"sourcesContent":["import {\n ActionRunner,\n STARTER_KIT_TOASTER_ID,\n Translator,\n configureCharts,\n installStarterKitRuntime,\n registerRichEditorExtensions,\n resolveRequestErrorNotification,\n starterKitBreadcrumbsFromProps,\n syncStarterKitPage,\n usesStarterKitHostLayout,\n type AlfakitOptions,\n type AlfakitPageProps,\n type StarterKitBreadcrumb,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { useEffect, useState, type ComponentType, type ReactNode } from 'react'\nimport { setToasterId, showToast } from '../components/notifications/flash-toasts'\nimport { Slot } from '../components/panel/slot'\nimport {\n AlfakitProvider,\n useAlfakitProps,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { AlfakitRoot } from '../create-alfakit'\nimport type { AppLayoutProps } from '../layouts/app-layout'\nimport { AuthLayout } from '../layouts/auth-layout'\nimport { lazyPanelLayouts } from '../layouts/lazy-layouts'\nimport { MAX_WIDTHS } from '../layouts/max-widths'\nimport { discoverComponents } from '../lib/discovery'\nimport { preloadIcons } from '../lib/icons'\nimport { cn } from '../lib/utils'\nimport { AlfakitPage } from '../pages/page'\nimport { createDefaultRegistry } from '../registry'\n\n/** The host's layout, as the starter kits' `AppLayout` takes its props. */\nexport type StarterKitLayout = ComponentType<{\n breadcrumbs?: StarterKitBreadcrumb[]\n children: ReactNode\n}>\n\nexport interface StarterKitPageOptions extends Pick<\n AlfakitOptions<AnyComponent>,\n 'components' | 'pages' | 'chartFormatters' | 'chartPlugins' | 'richEditorExtensions'\n> {\n /**\n * The host layout Alfakit pages render in, usually the starter kit's `AppLayout`\n * (`@/layouts/app-layout`). It receives the page's breadcrumbs as `{ title, href }`.\n * `null` renders the pages without a host layout.\n */\n layout?: StarterKitLayout | null\n /**\n * Auto-registered custom components, as for `createAlfakit({ discover })`: pass\n * `import.meta.glob('../../alfakit/{fields,columns,entries,widgets,pages,components,blocks}/**\\/*.tsx')`\n * from `resources/js/pages/Alfakit/Page.tsx` and every file there registers under its name.\n */\n discover?: AlfakitOptions<AnyComponent>['discover']\n}\n\n/**\n * The content area of an Alfakit page inside the host layout: what `layout:app` renders in\n * starter kit mode instead of Alfakit's sidebar and top bar. Replace it with\n * `createStarterKitPage({ components: { 'layout:app': MyContent } })`.\n */\nexport function StarterKitContentLayout({ children, maxContentWidth }: AppLayoutProps) {\n const props = useAlfakitProps()\n // The host's pages fill the content area, so starter kit pages default to the full width.\n const width = maxContentWidth ?? props.alfakit.maxContentWidth ?? 'full'\n\n return (\n <div\n data-slot=\"content\"\n data-alfakit-embedded=\"\"\n className={cn(\n 'mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6',\n MAX_WIDTHS[width] ?? 'max-w-full',\n )}\n >\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </div>\n )\n}\n\n// One context for the browser session (it survives navigations between host and Alfakit\n// pages); a server render builds one per request so concurrent renders never share a locale.\nlet browserContext: AlfakitContextValue | null = null\n\nfunction createStarterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n // The host's layout replaces the panel's. Auth pages (invitations, tenant registration)\n // keep the panel's card; the fullscreen layout loads on demand.\n // Auto-registered files sit between the built-ins and the explicit `components` / `pages`.\n const discovered = discoverComponents(options.discover)\n const registry = createDefaultRegistry()\n .registerMany(lazyPanelLayouts)\n .register('layout:auth', AuthLayout)\n .register('layout:app', StarterKitContentLayout)\n .registerMany(discovered.components)\n .registerMany(options.components ?? {})\n const translator = new Translator()\n\n return {\n registry,\n pages: { 'Alfakit/Page': AlfakitPage, ...discovered.pages, ...options.pages },\n translator,\n actions: new ActionRunner({\n endpoints: { action: '', modal: '' },\n // A failed action request is a toast, never a silent no-op.\n onError: (error) => {\n const notification = resolveRequestErrorNotification(\n error,\n null,\n (key, replacements, fallback) => translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n },\n }),\n }\n}\n\nfunction starterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n if (typeof window === 'undefined') return createStarterKitContext(options)\n\n browserContext ??= createStarterKitContext(options)\n\n return browserContext\n}\n\n/**\n * The Inertia layout around every Alfakit page in starter kit mode, inside the host layout:\n * Alfakit's context, modals, toasts and the reading direction. It stays mounted while the\n * user moves between Alfakit pages.\n */\nexport function AlfakitStarterKitRoot({\n options,\n children,\n}: {\n options?: StarterKitPageOptions\n children?: ReactNode\n}) {\n const [context] = useState(() => starterKitContext(options ?? {}))\n const props = usePage<AlfakitPageProps>().props\n const panel = props.alfakit\n\n // Endpoints and error notifications follow the open page.\n if (panel) {\n context.actions.setEndpoints({\n action: panel.endpoints.action ?? '',\n modal: panel.endpoints.modal ?? '',\n })\n }\n\n useEffect(() => {\n syncStarterKitPage(props)\n }, [props])\n\n useEffect(() => {\n installStarterKitRuntime(\n (notification) => showToast(notification),\n (key, replacements, fallback) => context.translator.t(key, replacements, fallback),\n )\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => {\n // A host page is open: its errors and toasts are the host's again.\n syncStarterKitPage(null)\n setToasterId(undefined)\n }\n }, [context])\n\n return (\n <AlfakitProvider value={context}>\n {/* The reading direction and language of Alfakit's content only: the host layout around\n it (sidebar, header) and the app's own pages keep the document's. */}\n <div\n data-slot=\"starter-kit-root\"\n dir={panel?.localization?.direction ?? 'ltr'}\n lang={panel?.localization?.tag}\n className=\"contents\"\n >\n <AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>{children}</AlfakitRoot>\n </div>\n </AlfakitProvider>\n )\n}\n\n/**\n * The page component for `Alfakit/Page` in a starter kit app: the host's\n * `resources/js/pages/Alfakit/Page.tsx` (written by `alfakit:install --starter-kit`) exports it,\n * so Inertia's page resolution, the root view's page preload and SSR all find it like any\n * other host page.\n *\n * ```tsx\n * import { createStarterKitPage } from '@alfakit/react'\n * import AppLayout from '@/layouts/app-layout'\n *\n * export default createStarterKitPage({ layout: AppLayout })\n * ```\n */\nexport function createStarterKitPage(options: StarterKitPageOptions = {}) {\n // Panel pages draw named icons: fetch `lucide-react/dynamic` with the page.\n preloadIcons()\n // `'js:name'` chart option callbacks, Chart.js plugins and rich editor extensions.\n configureCharts(options)\n registerRichEditorExtensions(options.richEditorExtensions ?? {})\n\n function AlfakitStarterKitPage() {\n return <AlfakitPage />\n }\n\n // A layout callback: the host layout (with the page's breadcrumbs), then Alfakit's root.\n AlfakitStarterKitPage.layout = (props: Record<string, unknown>) => [\n ...(options.layout && usesStarterKitHostLayout(props)\n ? [[options.layout, { breadcrumbs: starterKitBreadcrumbsFromProps(props) }] as const]\n : []),\n [AlfakitStarterKitRoot, { options }] as const,\n ]\n\n return AlfakitStarterKitPage\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiEA,SAAgB,wBAAwB,EAAE,UAAU,mBAAmC;CACrF,MAAM,QAAQ,gBAAgB;CAE9B,MAAM,QAAQ,mBAAmB,MAAM,QAAQ,mBAAmB;CAElE,OACE,qBAAC,OAAD;EACE,aAAU;EACV,yBAAsB;EACtB,WAAW,GACT,gEACA,WAAW,UAAU,YACvB;EANF,UAAA;GAQE,oBAAC,MAAD,EAAM,MAAK,gBAAiB,CAAA;GAC3B;GACD,oBAAC,MAAD,EAAM,MAAK,cAAe,CAAA;EACvB;;AAET;AAIA,IAAI,iBAA6C;AAEjD,SAAS,wBAAwB,SAAqD;CAIpF,MAAM,aAAa,mBAAmB,QAAQ,QAAQ;CACtD,MAAM,WAAW,sBAAsB,CAAC,CACrC,aAAa,gBAAgB,CAAC,CAC9B,SAAS,eAAe,UAAU,CAAC,CACnC,SAAS,cAAc,uBAAuB,CAAC,CAC/C,aAAa,WAAW,UAAU,CAAC,CACnC,aAAa,QAAQ,cAAc,CAAC,CAAC;CACxC,MAAM,aAAa,IAAI,WAAW;CAElC,OAAO;EACL;EACA,OAAO;GAAE,gBAAgB;GAAa,GAAG,WAAW;GAAO,GAAG,QAAQ;EAAM;EAC5E;EACA,SAAS,IAAI,aAAa;GACxB,WAAW;IAAE,QAAQ;IAAI,OAAO;GAAG;GAEnC,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;CACH;AACF;AAEA,SAAS,kBAAkB,SAAqD;CAC9E,IAAI,OAAO,WAAW,aAAa,OAAO,wBAAwB,OAAO;CAEzE,mBAAmB,wBAAwB,OAAO;CAElD,OAAO;AACT;;;;;;AAOA,SAAgB,sBAAsB,EACpC,SACA,YAIC;CACD,MAAM,CAAC,WAAW,eAAe,kBAAkB,WAAW,CAAC,CAAC,CAAC;CACjE,MAAM,QAAQ,QAA0B,CAAC,CAAC;CAC1C,MAAM,QAAQ,MAAM;CAGpB,IAAI,OACF,QAAQ,QAAQ,aAAa;EAC3B,QAAQ,MAAM,UAAU,UAAU;EAClC,OAAO,MAAM,UAAU,SAAS;CAClC,CAAC;CAGH,gBAAgB;EACd,mBAAmB,KAAK;CAC1B,GAAG,CAAC,KAAK,CAAC;CAEV,gBAAgB;EACd,0BACG,iBAAiB,UAAU,YAAY,IACvC,KAAK,cAAc,aAAa,QAAQ,WAAW,EAAE,KAAK,cAAc,QAAQ,CACnF;EACA,aAAa,sBAAsB;EAEnC,aAAa;GAEX,mBAAmB,IAAI;GACvB,aAAa,KAAA,CAAS;EACxB;CACF,GAAG,CAAC,OAAO,CAAC;CAEZ,OACE,oBAAC,iBAAD;EAAiB,OAAO;EAGtB,UAAA,oBAAC,OAAD;GACE,aAAU;GACV,KAAK,OAAO,cAAc,aAAa;GACvC,MAAM,OAAO,cAAc;GAC3B,WAAU;GAEV,UAAA,oBAAC,aAAD;IAAa,WAAW;IAAyB;GAAsB,CAAA;EACpE,CAAA;CACU,CAAA;AAErB;;;;;;;;;;;;;;AAeA,SAAgB,qBAAqB,UAAiC,CAAC,GAAG;CAExE,aAAa;CAEb,gBAAgB,OAAO;CACvB,6BAA6B,QAAQ,wBAAwB,CAAC,CAAC;CAE/D,SAAS,wBAAwB;EAC/B,OAAO,oBAAC,aAAD,CAAc,CAAA;CACvB;CAGA,sBAAsB,UAAU,UAAmC,CACjE,GAAI,QAAQ,UAAU,yBAAyB,KAAK,IAChD,CAAC,CAAC,QAAQ,QAAQ,EAAE,aAAa,+BAA+B,KAAK,EAAE,CAAC,CAAU,IAClF,CAAC,GACL,CAAC,uBAAuB,EAAE,QAAQ,CAAC,CACrC;CAEA,OAAO;AACT"}
|
|
1
|
+
{"version":3,"file":"starter-kit.js","names":[],"sources":["../../src/starter-kit/starter-kit.tsx"],"sourcesContent":["import {\n ActionRunner,\n STARTER_KIT_TOASTER_ID,\n Translator,\n configureCharts,\n installStarterKitRuntime,\n registerRichEditorExtensions,\n resolveRequestErrorNotification,\n starterKitBreadcrumbsFromProps,\n syncStarterKitPage,\n usesStarterKitHostLayout,\n type AlfakitOptions,\n type AlfakitPageProps,\n type StarterKitBreadcrumb,\n} from '@alfakit/core'\nimport { usePage } from '@inertiajs/react'\nimport { useEffect, useState, type ComponentType, type ReactNode } from 'react'\nimport { setToasterId, showToast } from '../components/notifications/flash-toasts'\nimport { DebugPanelSlot } from '../components/panel/debug-panel-slot'\nimport { Slot } from '../components/panel/slot'\nimport {\n AlfakitProvider,\n useAlfakitProps,\n type AlfakitContextValue,\n type AnyComponent,\n} from '../context'\nimport { AlfakitRoot } from '../create-alfakit'\nimport type { AppLayoutProps } from '../layouts/app-layout'\nimport { AuthLayout } from '../layouts/auth-layout'\nimport { lazyPanelLayouts } from '../layouts/lazy-layouts'\nimport { MAX_WIDTHS } from '../layouts/max-widths'\nimport { discoverComponents } from '../lib/discovery'\nimport { preloadIcons } from '../lib/icons'\nimport { cn } from '../lib/utils'\nimport { AlfakitPage } from '../pages/page'\nimport { createDefaultRegistry } from '../registry'\n\n/** The host's layout, as the starter kits' `AppLayout` takes its props. */\nexport type StarterKitLayout = ComponentType<{\n breadcrumbs?: StarterKitBreadcrumb[]\n children: ReactNode\n}>\n\nexport interface StarterKitPageOptions extends Pick<\n AlfakitOptions<AnyComponent>,\n 'components' | 'pages' | 'chartFormatters' | 'chartPlugins' | 'richEditorExtensions'\n> {\n /**\n * The host layout Alfakit pages render in, usually the starter kit's `AppLayout`\n * (`@/layouts/app-layout`). It receives the page's breadcrumbs as `{ title, href }`.\n * `null` renders the pages without a host layout.\n */\n layout?: StarterKitLayout | null\n /**\n * Auto-registered custom components, as for `createAlfakit({ discover })`: pass\n * `import.meta.glob('../../alfakit/{fields,columns,entries,widgets,pages,components,blocks}/**\\/*.tsx')`\n * from `resources/js/pages/Alfakit/Page.tsx` and every file there registers under its name.\n */\n discover?: AlfakitOptions<AnyComponent>['discover']\n}\n\n/**\n * The content area of an Alfakit page inside the host layout: what `layout:app` renders in\n * starter kit mode instead of Alfakit's sidebar and top bar. Replace it with\n * `createStarterKitPage({ components: { 'layout:app': MyContent } })`.\n */\nexport function StarterKitContentLayout({ children, maxContentWidth }: AppLayoutProps) {\n const props = useAlfakitProps()\n // The host's pages fill the content area, so starter kit pages default to the full width.\n const width = maxContentWidth ?? props.alfakit.maxContentWidth ?? 'full'\n\n return (\n <div\n data-slot=\"content\"\n data-alfakit-embedded=\"\"\n className={cn(\n 'mx-auto flex w-full min-w-0 flex-1 flex-col gap-6 p-4 md:p-6',\n MAX_WIDTHS[width] ?? 'max-w-full',\n )}\n >\n <Slot name=\"content.start\" />\n {children}\n <Slot name=\"content.end\" />\n </div>\n )\n}\n\n// One context for the browser session (it survives navigations between host and Alfakit\n// pages); a server render builds one per request so concurrent renders never share a locale.\nlet browserContext: AlfakitContextValue | null = null\n\nfunction createStarterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n // The host's layout replaces the panel's. Auth pages (invitations, tenant registration)\n // keep the panel's card; the fullscreen layout loads on demand.\n // Auto-registered files sit between the built-ins and the explicit `components` / `pages`.\n const discovered = discoverComponents(options.discover)\n const registry = createDefaultRegistry()\n .registerMany(lazyPanelLayouts)\n .register('layout:auth', AuthLayout)\n .register('layout:app', StarterKitContentLayout)\n .registerMany(discovered.components)\n .registerMany(options.components ?? {})\n const translator = new Translator()\n\n return {\n registry,\n pages: { 'Alfakit/Page': AlfakitPage, ...discovered.pages, ...options.pages },\n translator,\n actions: new ActionRunner({\n endpoints: { action: '', modal: '' },\n // A failed action request is a toast, never a silent no-op.\n onError: (error) => {\n const notification = resolveRequestErrorNotification(\n error,\n null,\n (key, replacements, fallback) => translator.t(key, replacements, fallback),\n )\n\n if (notification) showToast(notification)\n },\n }),\n }\n}\n\nfunction starterKitContext(options: StarterKitPageOptions): AlfakitContextValue {\n if (typeof window === 'undefined') return createStarterKitContext(options)\n\n browserContext ??= createStarterKitContext(options)\n\n return browserContext\n}\n\n/**\n * The Inertia layout around every Alfakit page in starter kit mode, inside the host layout:\n * Alfakit's context, modals, toasts and the reading direction. It stays mounted while the\n * user moves between Alfakit pages.\n */\nexport function AlfakitStarterKitRoot({\n options,\n children,\n}: {\n options?: StarterKitPageOptions\n children?: ReactNode\n}) {\n const [context] = useState(() => starterKitContext(options ?? {}))\n const props = usePage<AlfakitPageProps>().props\n const panel = props.alfakit\n\n // Endpoints and error notifications follow the open page.\n if (panel) {\n context.actions.setEndpoints({\n action: panel.endpoints.action ?? '',\n modal: panel.endpoints.modal ?? '',\n })\n }\n\n useEffect(() => {\n syncStarterKitPage(props)\n }, [props])\n\n useEffect(() => {\n installStarterKitRuntime(\n (notification) => showToast(notification),\n (key, replacements, fallback) => context.translator.t(key, replacements, fallback),\n )\n setToasterId(STARTER_KIT_TOASTER_ID)\n\n return () => {\n // A host page is open: its errors and toasts are the host's again.\n syncStarterKitPage(null)\n setToasterId(undefined)\n }\n }, [context])\n\n return (\n <AlfakitProvider value={context}>\n {/* The reading direction and language of Alfakit's content only: the host layout around\n it (sidebar, header) and the app's own pages keep the document's. */}\n <div\n data-slot=\"starter-kit-root\"\n dir={panel?.localization?.direction ?? 'ltr'}\n lang={panel?.localization?.tag}\n className=\"contents\"\n >\n <AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>\n {children}\n {/* The debug panel's button lives in AlfaKit's top bar, which a starter kit doesn't\n render: float it in the corner instead (nothing without `alfakitDebug`). */}\n <div data-slot=\"starter-kit-debug-panel\" className=\"fixed end-16 bottom-4 z-40\">\n <DebugPanelSlot />\n </div>\n </AlfakitRoot>\n </div>\n </AlfakitProvider>\n )\n}\n\n/**\n * The page component for `Alfakit/Page` in a starter kit app: the host's\n * `resources/js/pages/Alfakit/Page.tsx` (written by `alfakit:install --starter-kit`) exports it,\n * so Inertia's page resolution, the root view's page preload and SSR all find it like any\n * other host page.\n *\n * ```tsx\n * import { createStarterKitPage } from '@alfakit/react'\n * import AppLayout from '@/layouts/app-layout'\n *\n * export default createStarterKitPage({ layout: AppLayout })\n * ```\n */\nexport function createStarterKitPage(options: StarterKitPageOptions = {}) {\n // Panel pages draw named icons: fetch `lucide-react/dynamic` with the page.\n preloadIcons()\n // `'js:name'` chart option callbacks, Chart.js plugins and rich editor extensions.\n configureCharts(options)\n registerRichEditorExtensions(options.richEditorExtensions ?? {})\n\n function AlfakitStarterKitPage() {\n return <AlfakitPage />\n }\n\n // A layout callback: the host layout (with the page's breadcrumbs), then Alfakit's root.\n AlfakitStarterKitPage.layout = (props: Record<string, unknown>) => [\n ...(options.layout && usesStarterKitHostLayout(props)\n ? [[options.layout, { breadcrumbs: starterKitBreadcrumbsFromProps(props) }] as const]\n : []),\n [AlfakitStarterKitRoot, { options }] as const,\n ]\n\n return AlfakitStarterKitPage\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfakit/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
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.9"
|
|
100
100
|
},
|
|
101
101
|
"peerDependencies": {
|
|
102
102
|
"@inertiajs/react": "^3.0.0",
|
|
@@ -469,6 +469,8 @@ function ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boo
|
|
|
469
469
|
// Pinned to the top of the scrolling dialog, edge to edge.
|
|
470
470
|
schema.stickyHeader &&
|
|
471
471
|
'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',
|
|
472
|
+
// Room for the close button (X): a long heading would run under it.
|
|
473
|
+
schema.closeButton !== false && (schema.stickyHeader ? 'pe-12' : 'pe-6'),
|
|
472
474
|
)}
|
|
473
475
|
>
|
|
474
476
|
{header}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
FormStore,
|
|
4
4
|
applyFormRefresh,
|
|
5
5
|
formActionsVisible,
|
|
6
|
+
formIdPrefix,
|
|
6
7
|
formIdentity,
|
|
7
8
|
formRefreshFor,
|
|
8
9
|
isImplicitSubmit,
|
|
@@ -17,8 +18,15 @@ import {
|
|
|
17
18
|
wizardProgressStore,
|
|
18
19
|
} from '@alfakit/core'
|
|
19
20
|
import { router } from '@inertiajs/react'
|
|
20
|
-
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
|
21
|
-
import {
|
|
21
|
+
import { useCallback, useEffect, useId, useMemo, useRef } from 'react'
|
|
22
|
+
import {
|
|
23
|
+
useActionRunner,
|
|
24
|
+
useAlfakitProps,
|
|
25
|
+
usePageSchema,
|
|
26
|
+
usePanel,
|
|
27
|
+
useStandalone,
|
|
28
|
+
useT,
|
|
29
|
+
} from '../../context'
|
|
22
30
|
import { useStore } from '../../hooks/use-store'
|
|
23
31
|
import { ActionList } from '../actions/action-list'
|
|
24
32
|
import { showToast } from '../notifications/flash-toasts'
|
|
@@ -42,12 +50,17 @@ export function FormBlock({ block }: { block: FormBlockSchema }) {
|
|
|
42
50
|
// identity starts from server state again. The token is re-issued per response, so it
|
|
43
51
|
// is swapped in place instead of being part of the identity.
|
|
44
52
|
const identity = formIdentity(page, block)
|
|
53
|
+
// Several standalone components can share a page (and field names): each form gets its
|
|
54
|
+
// own field id prefix, so labels and errors point at their own inputs.
|
|
55
|
+
const standalone = useStandalone()
|
|
56
|
+
const instanceId = useId()
|
|
45
57
|
|
|
46
58
|
const store = useMemo(
|
|
47
59
|
() =>
|
|
48
60
|
new FormStore(block, {
|
|
49
61
|
token,
|
|
50
62
|
component: block.component,
|
|
63
|
+
idPrefix: standalone ? formIdPrefix(`form-${instanceId}`) : undefined,
|
|
51
64
|
refreshUrl: panel.endpoints.schemaRefresh,
|
|
52
65
|
optionsUrl: panel.endpoints.schemaOptions,
|
|
53
66
|
validateUrl: panel.endpoints.schemaValidate,
|
|
@@ -133,7 +133,7 @@ function NavSubItem({ item }: { item: NavigationItemSchema }) {
|
|
|
133
133
|
<CollapsibleContent>
|
|
134
134
|
<SidebarMenuSub>
|
|
135
135
|
{item.children.map((child) => (
|
|
136
|
-
<NavSubItem key={child.label} item={child} />
|
|
136
|
+
<NavSubItem key={child.url ?? child.label} item={child} />
|
|
137
137
|
))}
|
|
138
138
|
</SidebarMenuSub>
|
|
139
139
|
</CollapsibleContent>
|
|
@@ -175,7 +175,7 @@ function NavItem({ item }: { item: NavigationItemSchema }) {
|
|
|
175
175
|
<CollapsibleContent>
|
|
176
176
|
<SidebarMenuSub>
|
|
177
177
|
{item.children.map((child) => (
|
|
178
|
-
<NavSubItem key={child.label} item={child} />
|
|
178
|
+
<NavSubItem key={child.url ?? child.label} item={child} />
|
|
179
179
|
))}
|
|
180
180
|
</SidebarMenuSub>
|
|
181
181
|
</CollapsibleContent>
|
|
@@ -263,7 +263,7 @@ function NavGroup({ group }: { group: NavigationGroupSchema }) {
|
|
|
263
263
|
<SidebarGroupContent className={cn(flyout && 'group-data-[collapsible=icon]:hidden')}>
|
|
264
264
|
<SidebarMenu>
|
|
265
265
|
{group.items.map((item) => (
|
|
266
|
-
<NavItem key={item.label} item={item} />
|
|
266
|
+
<NavItem key={item.url ?? item.label} item={item} />
|
|
267
267
|
))}
|
|
268
268
|
</SidebarMenu>
|
|
269
269
|
</SidebarGroupContent>
|
|
@@ -81,7 +81,7 @@ export function BottomNav() {
|
|
|
81
81
|
className="md:hidden"
|
|
82
82
|
>
|
|
83
83
|
{primary.map((item) => (
|
|
84
|
-
<BottomNavLink key={item.label} item={item} />
|
|
84
|
+
<BottomNavLink key={item.url ?? item.label} item={item} />
|
|
85
85
|
))}
|
|
86
86
|
{overflow.length > 0 && (
|
|
87
87
|
<Sheet open={open} onOpenChange={setOpen}>
|
|
@@ -109,7 +109,7 @@ export function BottomNav() {
|
|
|
109
109
|
.flatMap((item) => [item, ...(item.children ?? [])])
|
|
110
110
|
.map((item) => (
|
|
111
111
|
<Link
|
|
112
|
-
key={item.label}
|
|
112
|
+
key={item.url ?? item.label}
|
|
113
113
|
href={navigationItemUrl(item) ?? '#'}
|
|
114
114
|
onClick={() => setOpen(false)}
|
|
115
115
|
data-active={isNavigationItemActive(item) ? 'true' : undefined}
|
|
@@ -47,7 +47,7 @@ export function KeyboardShortcuts() {
|
|
|
47
47
|
const overrides = JSON.stringify(config ? (config.overrides ?? {}) : {})
|
|
48
48
|
const token = page?.token ?? ''
|
|
49
49
|
const commands = panel.commandPalette?.commands
|
|
50
|
-
const home = panel.url
|
|
50
|
+
const home = panel.dashboardUrl ?? panel.url
|
|
51
51
|
// Strings, so the effect below doesn't re-run on every render (`t` is a new function each time).
|
|
52
52
|
const overlayLabel = t('shortcuts.show_overlay', {}, 'Show keyboard shortcuts')
|
|
53
53
|
const searchLabel = t('shortcuts.focus_search', {}, 'Search the table')
|
|
@@ -20,7 +20,16 @@ function Tooltip({ ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
23
|
-
|
|
23
|
+
// With asChild the child keeps its own data-slot (an action's `button`, which the
|
|
24
|
+
// button-shape and touch-target CSS select).
|
|
25
|
+
// (The key must be left out, not set to undefined: Slot passes it on and the child's own
|
|
26
|
+
// `{...props}` would erase its data-slot.)
|
|
27
|
+
return (
|
|
28
|
+
<TooltipPrimitive.Trigger
|
|
29
|
+
{...(props.asChild ? {} : { 'data-slot': 'tooltip-trigger' })}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
)
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
function TooltipContent({
|
|
@@ -118,7 +118,7 @@ function TopNavMenu({
|
|
|
118
118
|
</DropdownMenuTrigger>
|
|
119
119
|
<DropdownMenuContent align="start" className="min-w-48">
|
|
120
120
|
{items.map((item) => (
|
|
121
|
-
<DropdownMenuItem key={item.label} asChild>
|
|
121
|
+
<DropdownMenuItem key={item.url ?? item.label} asChild>
|
|
122
122
|
<Link
|
|
123
123
|
{...extraProps(item.extraAttributes, cn('w-full', item.active && 'font-medium'))}
|
|
124
124
|
href={item.url ?? '#'}
|
|
@@ -150,7 +150,7 @@ function TopNavGroup({ group }: { group: NavigationGroupSchema }) {
|
|
|
150
150
|
return (
|
|
151
151
|
<>
|
|
152
152
|
{group.items.map((item) => (
|
|
153
|
-
<TopNavLink key={item.label} item={item} />
|
|
153
|
+
<TopNavLink key={item.url ?? item.label} item={item} />
|
|
154
154
|
))}
|
|
155
155
|
</>
|
|
156
156
|
)
|
|
@@ -181,7 +181,7 @@ function MobileNav({ onNavigate }: { onNavigate: () => void }) {
|
|
|
181
181
|
.flatMap((item) => [item, ...(item.children ?? [])])
|
|
182
182
|
.map((item) => (
|
|
183
183
|
<Link
|
|
184
|
-
key={item.label}
|
|
184
|
+
key={item.url ?? item.label}
|
|
185
185
|
href={item.url ?? '#'}
|
|
186
186
|
onClick={onNavigate}
|
|
187
187
|
data-active={item.active ? 'true' : undefined}
|
|
@@ -83,8 +83,18 @@ export function AlfakitComponent({
|
|
|
83
83
|
const refresh = (targets: string[]) => state.refresh(targets)
|
|
84
84
|
|
|
85
85
|
if (parent) {
|
|
86
|
+
// Inside a panel page: the panel's registry and translator, but this component's own
|
|
87
|
+
// action runner, so its actions post to its own endpoints with its own token (sharing
|
|
88
|
+
// the panel's runner sent them to the panel's endpoint: 403).
|
|
89
|
+
const own = createStandaloneContext(
|
|
90
|
+
{ action: component.endpoints.action, modal: component.endpoints.modal },
|
|
91
|
+
components,
|
|
92
|
+
poll,
|
|
93
|
+
)
|
|
94
|
+
|
|
86
95
|
return {
|
|
87
96
|
...parent,
|
|
97
|
+
actions: own.actions,
|
|
88
98
|
registry: createStandaloneRegistry(components, parent.registry),
|
|
89
99
|
poll,
|
|
90
100
|
refresh,
|
|
@@ -201,7 +211,7 @@ export function AlfakitComponent({
|
|
|
201
211
|
{footer}
|
|
202
212
|
</div>
|
|
203
213
|
<ActionModals />
|
|
204
|
-
{ownsToaster && <AlfakitToaster claimed />}
|
|
214
|
+
{ownsToaster && <AlfakitToaster claimed dir={direction} />}
|
|
205
215
|
</PageActionProvider>
|
|
206
216
|
)
|
|
207
217
|
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
import { usePage } from '@inertiajs/react'
|
|
17
17
|
import { useEffect, useState, type ComponentType, type ReactNode } from 'react'
|
|
18
18
|
import { setToasterId, showToast } from '../components/notifications/flash-toasts'
|
|
19
|
+
import { DebugPanelSlot } from '../components/panel/debug-panel-slot'
|
|
19
20
|
import { Slot } from '../components/panel/slot'
|
|
20
21
|
import {
|
|
21
22
|
AlfakitProvider,
|
|
@@ -181,7 +182,14 @@ export function AlfakitStarterKitRoot({
|
|
|
181
182
|
lang={panel?.localization?.tag}
|
|
182
183
|
className="contents"
|
|
183
184
|
>
|
|
184
|
-
<AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>
|
|
185
|
+
<AlfakitRoot toasterId={STARTER_KIT_TOASTER_ID}>
|
|
186
|
+
{children}
|
|
187
|
+
{/* The debug panel's button lives in AlfaKit's top bar, which a starter kit doesn't
|
|
188
|
+
render: float it in the corner instead (nothing without `alfakitDebug`). */}
|
|
189
|
+
<div data-slot="starter-kit-debug-panel" className="fixed end-16 bottom-4 z-40">
|
|
190
|
+
<DebugPanelSlot />
|
|
191
|
+
</div>
|
|
192
|
+
</AlfakitRoot>
|
|
185
193
|
</div>
|
|
186
194
|
</AlfakitProvider>
|
|
187
195
|
)
|