@alfakit/react 0.1.9 → 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/components/actions/action-button.js +13 -1
- package/dist/components/actions/action-button.js.map +1 -1
- package/dist/components/dev-tools/dev-tools-inspector.js +3 -3
- package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
- package/dist/components/fields/checkbox-list.js +2 -2
- package/dist/components/fields/checkbox-list.js.map +1 -1
- package/dist/components/fields/money-input.d.ts +5 -0
- package/dist/components/fields/money-input.d.ts.map +1 -1
- package/dist/components/fields/money-input.js +2 -5
- package/dist/components/fields/money-input.js.map +1 -1
- package/dist/components/fields/rich-editor-extensions.js +14 -4
- package/dist/components/fields/rich-editor-extensions.js.map +1 -1
- package/dist/components/file-manager/file-manager.js +2 -0
- package/dist/components/file-manager/file-manager.js.map +1 -1
- package/dist/components/layout/elements.d.ts.map +1 -1
- package/dist/components/layout/elements.js +16 -5
- package/dist/components/layout/elements.js.map +1 -1
- package/dist/components/layout/mixed-content.d.ts +6 -4
- package/dist/components/layout/mixed-content.d.ts.map +1 -1
- package/dist/components/layout/mixed-content.js +6 -4
- package/dist/components/layout/mixed-content.js.map +1 -1
- package/dist/components/layout/section.js +4 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/notifications/notifications-menu.d.ts.map +1 -1
- package/dist/components/notifications/notifications-menu.js +45 -17
- package/dist/components/notifications/notifications-menu.js.map +1 -1
- package/dist/components/panel/lazy-placeholder.d.ts +1 -0
- package/dist/components/panel/lazy-placeholder.d.ts.map +1 -1
- package/dist/components/panel/lazy-placeholder.js +38 -4
- package/dist/components/panel/lazy-placeholder.js.map +1 -1
- package/dist/components/panel/shortcuts-overlay.js +13 -10
- package/dist/components/panel/shortcuts-overlay.js.map +1 -1
- package/dist/components/table/cells.js +1 -1
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/column-manager.js +4 -4
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/spreadsheet.js +8 -1
- package/dist/components/table/spreadsheet.js.map +1 -1
- package/dist/components/translation-manager/translation-manager.js +1 -1
- package/dist/components/translation-manager/translation-manager.js.map +1 -1
- package/dist/components/ui/calendar.d.ts.map +1 -1
- package/dist/components/ui/calendar.js +7 -0
- package/dist/components/ui/calendar.js.map +1 -1
- package/dist/components/widgets/dashboard-customizer.js +2 -2
- package/dist/components/widgets/dashboard-customizer.js.map +1 -1
- package/dist/components/widgets/stats-overview.js +3 -3
- package/dist/components/widgets/stats-overview.js.map +1 -1
- package/dist/components/widgets/widgets-block.js +2 -2
- package/dist/components/widgets/widgets-block.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +3 -2
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +4 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +4 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.d.ts +0 -10
- package/dist/standalone/alfakit-component.d.ts.map +1 -1
- package/dist/standalone/alfakit-component.js +5 -4
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/runtime.js +2 -1
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone.css +23 -1
- package/dist/starter-kit/index.d.ts +2 -2
- package/dist/starter-kit/index.js +2 -2
- package/dist/starter-kit/navigation.d.ts +6 -1
- package/dist/starter-kit/navigation.d.ts.map +1 -1
- package/dist/starter-kit/navigation.js +3 -2
- package/dist/starter-kit/navigation.js.map +1 -1
- package/dist/starter-kit/starter-kit.d.ts +9 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +22 -3
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/styles.css +2 -1
- package/dist/themes.css +4 -0
- package/package.json +2 -2
- package/src/components/actions/action-button.tsx +15 -1
- package/src/components/dev-tools/dev-tools-inspector.tsx +7 -2
- package/src/components/fields/checkbox-list.tsx +2 -2
- package/src/components/fields/money-input.tsx +3 -14
- package/src/components/fields/rich-editor-extensions.tsx +24 -2
- package/src/components/file-manager/file-manager.tsx +3 -1
- package/src/components/layout/elements.tsx +27 -4
- package/src/components/layout/mixed-content.tsx +8 -4
- package/src/components/layout/section.tsx +2 -2
- package/src/components/notifications/notifications-menu.tsx +42 -1
- package/src/components/panel/lazy-placeholder.tsx +41 -1
- package/src/components/panel/shortcuts-overlay.tsx +29 -24
- package/src/components/table/cells.tsx +3 -1
- package/src/components/table/column-manager.tsx +6 -4
- package/src/components/table/spreadsheet.tsx +18 -7
- package/src/components/translation-manager/translation-manager.tsx +1 -1
- package/src/components/ui/calendar.tsx +8 -0
- package/src/components/widgets/dashboard-customizer.tsx +2 -2
- package/src/components/widgets/stats-overview.tsx +6 -3
- package/src/components/widgets/widgets-block.tsx +2 -2
- package/src/create-alfakit.tsx +9 -1
- package/src/pages/page.tsx +7 -3
- package/src/standalone/alfakit-component.tsx +9 -4
- package/src/standalone/runtime.ts +3 -0
- package/src/starter-kit/index.ts +1 -0
- package/src/starter-kit/navigation.ts +12 -1
- package/src/starter-kit/starter-kit.tsx +25 -2
- package/src/styles.css +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @alfakit/react
|
|
2
2
|
|
|
3
|
+
## 0.1.11
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- File downloads from actions and `Action::download()` links; MoneyInput keeps out-of-range amounts for validation; translated calendar month buttons; image cells named for screen readers; `StarterKitFullscreenLayout`; customizable dashboards on the kit's `/dashboard`; failed lazy widgets show Retry; standalone headings keep their width.
|
|
8
|
+
|
|
9
|
+
## 0.1.10
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Back after a save shows fresh data; `useAlfakitNavigation().userMenuItems` for starter kits; push toggle in `<AlfakitNotifications />`; rich editor keeps focus after a toolbar group pick; Callout/EmptyState/section footer actions run with the form's state; column manager Reset fix; `<AlfakitComponent>` deferred loading fix.
|
|
14
|
+
|
|
3
15
|
## 0.1.9
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -90,7 +90,19 @@ function ActionButton({ action, context, size = "default", className, variant: v
|
|
|
90
90
|
const sizeProp = isIconOnly ? iconButtonSize(buttonSize) : buttonSize;
|
|
91
91
|
const classes = cn(buttonClasses(action, variant), labeledFrom && ICON_ONLY_BELOW[labeledFrom], className);
|
|
92
92
|
const ariaLabel = isIconOnly || labeledFrom ? accessibleName : void 0;
|
|
93
|
-
const button = isPlainLink ? action.
|
|
93
|
+
const button = isPlainLink ? action.download ? /* @__PURE__ */ jsx(Button, {
|
|
94
|
+
asChild: true,
|
|
95
|
+
variant: "plain",
|
|
96
|
+
size: sizeProp,
|
|
97
|
+
disabled: action.disabled,
|
|
98
|
+
"aria-label": ariaLabel,
|
|
99
|
+
...extraProps(action.extraAttributes, classes),
|
|
100
|
+
children: /* @__PURE__ */ jsx("a", {
|
|
101
|
+
href: action.url,
|
|
102
|
+
download: "",
|
|
103
|
+
children: content
|
|
104
|
+
})
|
|
105
|
+
}) : action.openUrlInNewTab ? /* @__PURE__ */ jsx(Button, {
|
|
94
106
|
asChild: true,
|
|
95
107
|
variant: "plain",
|
|
96
108
|
size: sizeProp,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-button.js","names":[],"sources":["../../../src/components/actions/action-button.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema } from '@alfakit/core'\nimport {\n bindsKeyBindings,\n formatKeyBinding,\n iconButtonSize,\n isActionLoading,\n isActionProcessing,\n registerKeyBindings,\n resolveActionSize,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { useEffect, type MouseEvent } from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { useStoreSelector } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n badgeColorClasses,\n colorClass,\n ghostButtonColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Spinner } from '../ui/spinner'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\n\n/** `labeledFrom()`: the label shows from the breakpoint up. */\nconst LABEL_FROM: Record<string, string> = {\n sm: 'hidden sm:inline',\n md: 'hidden md:inline',\n lg: 'hidden lg:inline',\n xl: 'hidden xl:inline',\n '2xl': 'hidden 2xl:inline',\n}\n\n/** `labeledFrom()`: square (icon-only) padding below the breakpoint. */\nconst ICON_ONLY_BELOW: Record<string, string> = {\n sm: 'max-sm:aspect-square max-sm:px-0',\n md: 'max-md:aspect-square max-md:px-0',\n lg: 'max-lg:aspect-square max-lg:px-0',\n xl: 'max-xl:aspect-square max-xl:px-0',\n '2xl': 'max-2xl:aspect-square max-2xl:px-0',\n}\n\nexport interface ActionButtonProps {\n action: ActionSchema\n context: ActionContext\n size?: 'xs' | 'sm' | 'default' | 'lg'\n className?: string\n /** Force a variant (e.g. menu items render as plain rows). */\n variant?: ActionSchema['variant']\n /** Bind the action's key bindings globally while mounted. */\n withKeyBindings?: boolean\n /** Override how the action runs (e.g. a form submits its values). */\n onTrigger?: (action: ActionSchema, context: ActionContext) => void | Promise<void>\n}\n\n/** The badge trigger style (`badge()` without an argument). */\nconst BADGE_TRIGGER_CLASSES =\n \"h-auto min-h-6 gap-1 rounded-md border px-2 py-0.5 text-xs font-medium [&_svg:not([class*='size-'])]:size-3.5\"\n\nfunction buttonClasses(action: ActionSchema, variant: ActionSchema['variant']): string {\n const color = action.color ?? 'primary'\n\n switch (variant) {\n case 'outline':\n return cn('bg-background border', colorClass(outlineButtonColorClasses, color, 'gray'))\n case 'ghost':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'link':\n return cn(\n 'h-auto p-0 underline-offset-4 hover:underline has-[>svg]:px-0',\n colorClass(textColorClasses, color, 'primary'),\n )\n case 'icon-button':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'badge':\n // `badge()`: a tinted pill in the action's colour.\n return cn(BADGE_TRIGGER_CLASSES, colorClass(badgeColorClasses, color, 'primary'))\n default:\n return colorClass(solidButtonColorClasses, color, 'primary')\n }\n}\n\nexport function ActionButton({\n action,\n context,\n size = 'default',\n className,\n variant: variantProp,\n withKeyBindings = true,\n onTrigger,\n}: ActionButtonProps) {\n const runner = useActionRunner()\n const t = useT()\n // Only this run (this record's button), not the same action in every table row.\n const loading = useStoreSelector(runner.store, (state) => isActionLoading(state, action, context))\n const processing = useStoreSelector(runner.store, (state) =>\n isActionProcessing(state, action, context),\n )\n\n const variant =\n variantProp ??\n (action.outlined\n ? 'outline'\n : (action.variant ?? (action.hasAction || action.hasModal || action.url ? 'link' : 'button')))\n const isIconOnly = variant === 'icon-button' || !action.label\n // `labeledFrom()`: icon only on small screens, so the label must be the accessible name.\n const labeledFrom = !isIconOnly && action.icon && action.labeledFrom ? action.labeledFrom : null\n const busy = loading || processing\n // Record actions repeat per row: a global shortcut would run them for every record.\n const bindsKeys = withKeyBindings && bindsKeyBindings(action, context)\n\n useEffect(() => {\n if (!bindsKeys || !action.keyBindings) {\n return\n }\n\n return registerKeyBindings([\n {\n keys: action.keyBindings,\n handler: () =>\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context)),\n },\n ])\n }, [action, context, runner, bindsKeys, onTrigger])\n\n // A disabled link renders as a disabled button so it cannot navigate.\n const isPlainLink = Boolean(\n action.url && !action.hasAction && !action.hasModal && !action.disabled,\n )\n\n const onClick = (event: MouseEvent) => {\n if (isPlainLink) {\n return // Plain link: let Inertia Link / anchor handle it.\n }\n\n event.preventDefault()\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context))\n }\n\n const content = (\n <>\n {busy ? (\n <Spinner />\n ) : action.icon && action.iconPosition !== 'after' ? (\n <Icon name={action.icon} />\n ) : null}\n {!isIconOnly && (\n <span className={labeledFrom ? LABEL_FROM[labeledFrom] : undefined}>{action.label}</span>\n )}\n {action.icon && action.iconPosition === 'after' && !busy && <Icon name={action.icon} />}\n {action.badge !== undefined && action.badge !== null && (\n <Badge\n variant=\"outline\"\n className={cn(\n 'ms-1 px-1.5 py-0',\n colorClass(badgeColorClasses, action.badgeColor, 'gray'),\n )}\n >\n {action.badge}\n </Badge>\n )}\n </>\n )\n\n const accessibleName = action.label ?? action.tooltip ?? action.name\n const newTabHint = t('opens_in_new_tab', {}, '(opens in a new tab)')\n const buttonSize = variant === 'badge' ? 'xs' : resolveActionSize(action.size, size)\n const sizeProp = isIconOnly ? iconButtonSize(buttonSize) : buttonSize\n const classes = cn(\n buttonClasses(action, variant),\n labeledFrom && ICON_ONLY_BELOW[labeledFrom],\n className,\n )\n const ariaLabel = isIconOnly || labeledFrom ? accessibleName : undefined\n\n const button = isPlainLink ? (\n action.openUrlInNewTab ? (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel ? `${ariaLabel} ${newTabHint}` : undefined}\n {...extraProps(action.extraAttributes, classes)}\n >\n <a href={action.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {content}\n {!isIconOnly && <span className=\"sr-only\"> {newTabHint}</span>}\n </a>\n </Button>\n ) : (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n <Link href={action.url ?? '#'}>{content}</Link>\n </Button>\n )\n ) : (\n <Button\n type=\"button\"\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled || busy}\n onClick={onClick}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n {content}\n </Button>\n )\n\n const tooltip = action.tooltip ?? (isIconOnly ? action.label : undefined)\n const shortcut = bindsKeys ? action.keyBindings?.[0] : undefined\n\n if (!tooltip && !shortcut) {\n return button\n }\n\n // A disabled button gets no pointer or focus events, so a focusable wrapper carries the\n // tooltip (e.g. the reason from `authorizationTooltip()`).\n const trigger =\n action.disabled && tooltip ? (\n <span tabIndex={0} className=\"inline-flex\" data-slot=\"action-disabled-tooltip\">\n {button}\n </span>\n ) : (\n button\n )\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {tooltip}\n {shortcut && (\n <kbd dir=\"ltr\" className=\"ms-2 rounded border border-white/20 px-1 font-mono text-[10px]\">\n {formatKeyBinding(shortcut)}\n </kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"action-button.js","names":[],"sources":["../../../src/components/actions/action-button.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema } from '@alfakit/core'\nimport {\n bindsKeyBindings,\n formatKeyBinding,\n iconButtonSize,\n isActionLoading,\n isActionProcessing,\n registerKeyBindings,\n resolveActionSize,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { useEffect, type MouseEvent } from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { useStoreSelector } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n badgeColorClasses,\n colorClass,\n ghostButtonColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Spinner } from '../ui/spinner'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\n\n/** `labeledFrom()`: the label shows from the breakpoint up. */\nconst LABEL_FROM: Record<string, string> = {\n sm: 'hidden sm:inline',\n md: 'hidden md:inline',\n lg: 'hidden lg:inline',\n xl: 'hidden xl:inline',\n '2xl': 'hidden 2xl:inline',\n}\n\n/** `labeledFrom()`: square (icon-only) padding below the breakpoint. */\nconst ICON_ONLY_BELOW: Record<string, string> = {\n sm: 'max-sm:aspect-square max-sm:px-0',\n md: 'max-md:aspect-square max-md:px-0',\n lg: 'max-lg:aspect-square max-lg:px-0',\n xl: 'max-xl:aspect-square max-xl:px-0',\n '2xl': 'max-2xl:aspect-square max-2xl:px-0',\n}\n\nexport interface ActionButtonProps {\n action: ActionSchema\n context: ActionContext\n size?: 'xs' | 'sm' | 'default' | 'lg'\n className?: string\n /** Force a variant (e.g. menu items render as plain rows). */\n variant?: ActionSchema['variant']\n /** Bind the action's key bindings globally while mounted. */\n withKeyBindings?: boolean\n /** Override how the action runs (e.g. a form submits its values). */\n onTrigger?: (action: ActionSchema, context: ActionContext) => void | Promise<void>\n}\n\n/** The badge trigger style (`badge()` without an argument). */\nconst BADGE_TRIGGER_CLASSES =\n \"h-auto min-h-6 gap-1 rounded-md border px-2 py-0.5 text-xs font-medium [&_svg:not([class*='size-'])]:size-3.5\"\n\nfunction buttonClasses(action: ActionSchema, variant: ActionSchema['variant']): string {\n const color = action.color ?? 'primary'\n\n switch (variant) {\n case 'outline':\n return cn('bg-background border', colorClass(outlineButtonColorClasses, color, 'gray'))\n case 'ghost':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'link':\n return cn(\n 'h-auto p-0 underline-offset-4 hover:underline has-[>svg]:px-0',\n colorClass(textColorClasses, color, 'primary'),\n )\n case 'icon-button':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'badge':\n // `badge()`: a tinted pill in the action's colour.\n return cn(BADGE_TRIGGER_CLASSES, colorClass(badgeColorClasses, color, 'primary'))\n default:\n return colorClass(solidButtonColorClasses, color, 'primary')\n }\n}\n\nexport function ActionButton({\n action,\n context,\n size = 'default',\n className,\n variant: variantProp,\n withKeyBindings = true,\n onTrigger,\n}: ActionButtonProps) {\n const runner = useActionRunner()\n const t = useT()\n // Only this run (this record's button), not the same action in every table row.\n const loading = useStoreSelector(runner.store, (state) => isActionLoading(state, action, context))\n const processing = useStoreSelector(runner.store, (state) =>\n isActionProcessing(state, action, context),\n )\n\n const variant =\n variantProp ??\n (action.outlined\n ? 'outline'\n : (action.variant ?? (action.hasAction || action.hasModal || action.url ? 'link' : 'button')))\n const isIconOnly = variant === 'icon-button' || !action.label\n // `labeledFrom()`: icon only on small screens, so the label must be the accessible name.\n const labeledFrom = !isIconOnly && action.icon && action.labeledFrom ? action.labeledFrom : null\n const busy = loading || processing\n // Record actions repeat per row: a global shortcut would run them for every record.\n const bindsKeys = withKeyBindings && bindsKeyBindings(action, context)\n\n useEffect(() => {\n if (!bindsKeys || !action.keyBindings) {\n return\n }\n\n return registerKeyBindings([\n {\n keys: action.keyBindings,\n handler: () =>\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context)),\n },\n ])\n }, [action, context, runner, bindsKeys, onTrigger])\n\n // A disabled link renders as a disabled button so it cannot navigate.\n const isPlainLink = Boolean(\n action.url && !action.hasAction && !action.hasModal && !action.disabled,\n )\n\n const onClick = (event: MouseEvent) => {\n if (isPlainLink) {\n return // Plain link: let Inertia Link / anchor handle it.\n }\n\n event.preventDefault()\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context))\n }\n\n const content = (\n <>\n {busy ? (\n <Spinner />\n ) : action.icon && action.iconPosition !== 'after' ? (\n <Icon name={action.icon} />\n ) : null}\n {!isIconOnly && (\n <span className={labeledFrom ? LABEL_FROM[labeledFrom] : undefined}>{action.label}</span>\n )}\n {action.icon && action.iconPosition === 'after' && !busy && <Icon name={action.icon} />}\n {action.badge !== undefined && action.badge !== null && (\n <Badge\n variant=\"outline\"\n className={cn(\n 'ms-1 px-1.5 py-0',\n colorClass(badgeColorClasses, action.badgeColor, 'gray'),\n )}\n >\n {action.badge}\n </Badge>\n )}\n </>\n )\n\n const accessibleName = action.label ?? action.tooltip ?? action.name\n const newTabHint = t('opens_in_new_tab', {}, '(opens in a new tab)')\n const buttonSize = variant === 'badge' ? 'xs' : resolveActionSize(action.size, size)\n const sizeProp = isIconOnly ? iconButtonSize(buttonSize) : buttonSize\n const classes = cn(\n buttonClasses(action, variant),\n labeledFrom && ICON_ONLY_BELOW[labeledFrom],\n className,\n )\n const ariaLabel = isIconOnly || labeledFrom ? accessibleName : undefined\n\n const button = isPlainLink ? (\n action.download ? (\n // `download()`: a plain link that saves the file (an Inertia visit would discard it).\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n disabled={action.disabled}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n <a href={action.url} download=\"\">\n {content}\n </a>\n </Button>\n ) : action.openUrlInNewTab ? (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel ? `${ariaLabel} ${newTabHint}` : undefined}\n {...extraProps(action.extraAttributes, classes)}\n >\n <a href={action.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {content}\n {!isIconOnly && <span className=\"sr-only\"> {newTabHint}</span>}\n </a>\n </Button>\n ) : (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n <Link href={action.url ?? '#'}>{content}</Link>\n </Button>\n )\n ) : (\n <Button\n type=\"button\"\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled || busy}\n onClick={onClick}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n {content}\n </Button>\n )\n\n const tooltip = action.tooltip ?? (isIconOnly ? action.label : undefined)\n const shortcut = bindsKeys ? action.keyBindings?.[0] : undefined\n\n if (!tooltip && !shortcut) {\n return button\n }\n\n // A disabled button gets no pointer or focus events, so a focusable wrapper carries the\n // tooltip (e.g. the reason from `authorizationTooltip()`).\n const trigger =\n action.disabled && tooltip ? (\n <span tabIndex={0} className=\"inline-flex\" data-slot=\"action-disabled-tooltip\">\n {button}\n </span>\n ) : (\n button\n )\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {tooltip}\n {shortcut && (\n <kbd dir=\"ltr\" className=\"ms-2 rounded border border-white/20 px-1 font-mono text-[10px]\">\n {formatKeyBinding(shortcut)}\n </kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n}\n"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import { Button } from "../ui/button.js";
|
|
|
4
4
|
import { Input } from "../ui/input.js";
|
|
5
5
|
import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, SheetTrigger } from "../ui/sheet.js";
|
|
6
6
|
import { ScrollArea } from "../ui/scroll-area.js";
|
|
7
|
-
import { byteLength, formatBytes, inspectableProps, jsonEntries, jsonScalar, jsonSize, latestServerTiming, prettyJson, searchJson } from "@alfakit/core";
|
|
7
|
+
import { byteLength, formatBytes, inspectableProps, jsonEntries, jsonScalar, jsonSize, latestServerTiming, overlayPlacementDirection, prettyJson, searchJson } from "@alfakit/core";
|
|
8
8
|
import { router } from "@inertiajs/react";
|
|
9
9
|
import { useEffect, useMemo, useState } from "react";
|
|
10
10
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -48,11 +48,11 @@ function DevToolsInspector() {
|
|
|
48
48
|
size: "icon",
|
|
49
49
|
"aria-label": t("dev_tools.open", {}, "Open schema inspector"),
|
|
50
50
|
"data-slot": "dev-tools-toggle",
|
|
51
|
-
className: "fixed end-4 bottom-
|
|
51
|
+
className: "fixed end-4 bottom-16 z-40 rounded-full shadow-md [[data-slot=starter-kit-root]_&]:end-auto [[data-slot=starter-kit-root]_&]:right-4",
|
|
52
52
|
children: /* @__PURE__ */ jsx(BracesIcon, {})
|
|
53
53
|
})
|
|
54
54
|
}), /* @__PURE__ */ jsx(SheetContent, {
|
|
55
|
-
side: direction === "rtl" ? "left" : "right",
|
|
55
|
+
side: overlayPlacementDirection(direction, props.alfakit?.starterKit) === "rtl" ? "left" : "right",
|
|
56
56
|
className: "flex w-full flex-col gap-0 p-0 sm:max-w-xl",
|
|
57
57
|
children: /* @__PURE__ */ jsxs("div", {
|
|
58
58
|
"data-slot": "dev-tools",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev-tools-inspector.js","names":[],"sources":["../../../src/components/dev-tools/dev-tools-inspector.tsx"],"sourcesContent":["import {\n byteLength,\n formatBytes,\n inspectableProps,\n jsonEntries,\n jsonScalar,\n jsonSize,\n latestServerTiming,\n prettyJson,\n searchJson,\n type JsonEntry,\n type JsonSearchResult,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { BracesIcon, CheckIcon, ChevronRightIcon, CopyIcon, ExternalLinkIcon } from 'lucide-react'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useAlfakitProps, useDirection, useFormatter, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../ui/button'\nimport { Input } from '../ui/input'\nimport { ScrollArea } from '../ui/scroll-area'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from '../ui/sheet'\n\n/**\n * The schema inspector: a floating developer button that opens a drawer with the page's\n * props as a searchable JSON tree (with a copy button), what the context token identifies,\n * the server time reported in `Server-Timing`, the payload size and editor links for the\n * PHP classes behind the page. Rendered only for pages that carry `alfakitDevTools`.\n */\nexport function DevToolsInspector() {\n const props = useAlfakitProps()\n const t = useT()\n const formatter = useFormatter()\n const direction = useDirection()\n const tools = props.alfakitDevTools\n const data = useMemo(() => inspectableProps(props as unknown as Record<string, unknown>), [props])\n const payload = useMemo(() => byteLength(props), [props])\n const [query, setQuery] = useState('')\n const [copied, setCopied] = useState(false)\n // The inspector renders in the browser only (lazy chunk), so the Performance API is there.\n const [serverTime, setServerTime] = useState<number | null>(() => latestServerTiming())\n const search = useMemo(() => searchJson(data, query), [data, query])\n\n // Re-read the timing after every visit: the latest response carries the newest header.\n useEffect(() => router.on('finish', () => setServerTime(latestServerTiming())), [])\n\n useEffect(() => {\n if (!copied) return\n\n const timer = setTimeout(() => setCopied(false), 2000)\n\n return () => clearTimeout(timer)\n }, [copied])\n\n if (!tools) {\n return null\n }\n\n const copy = () => {\n void navigator.clipboard?.writeText(prettyJson(data)).then(() => setCopied(true))\n }\n\n const time = (value: number | null) =>\n value === null ? t('dev_tools.never', {}, 'Never') : formatter.dateTime(value * 1000)\n const parameters = Object.entries(tools.context.parameters)\n\n return (\n <Sheet>\n <SheetTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n aria-label={t('dev_tools.open', {}, 'Open schema inspector')}\n data-slot=\"dev-tools-toggle\"\n className=\"fixed end-4 bottom-4 z-40 rounded-full shadow-md\"\n >\n <BracesIcon />\n </Button>\n </SheetTrigger>\n <SheetContent\n side={direction === 'rtl' ? 'left' : 'right'}\n className=\"flex w-full flex-col gap-0 p-0 sm:max-w-xl\"\n >\n <div data-slot=\"dev-tools\" className=\"flex min-h-0 flex-1 flex-col\">\n <SheetHeader className=\"border-b\">\n <SheetTitle className=\"flex items-center gap-2\">\n <BracesIcon className=\"size-4\" />\n {t('dev_tools.title', {}, 'Schema inspector')}\n </SheetTitle>\n <SheetDescription className=\"truncate font-mono text-xs\" title={tools.context.page}>\n {tools.context.page}\n </SheetDescription>\n </SheetHeader>\n <ScrollArea className=\"min-h-0 flex-1\">\n <div className=\"space-y-5 p-4 text-sm\">\n <dl className=\"grid grid-cols-2 gap-3\">\n <Stat\n label={t('dev_tools.server_time', {}, 'Server time')}\n value={\n serverTime === null\n ? t('dev_tools.not_measured', {}, 'Not measured')\n : t(\n 'dev_tools.milliseconds',\n { value: formatter.number(serverTime) },\n ':value ms',\n )\n }\n />\n <Stat\n label={t('dev_tools.payload', {}, 'Payload size')}\n value={formatBytes(payload)}\n />\n </dl>\n\n <section data-slot=\"dev-tools-context\">\n <h3 className=\"mb-2 text-sm font-medium\">\n {t('dev_tools.context', {}, 'Context token')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-xs\">\n <Row label={t('dev_tools.panel', {}, 'Panel')} value={tools.context.panel} />\n <Row label={t('dev_tools.page', {}, 'Page')} value={tools.context.page} />\n {tools.context.component && (\n <Row\n label={t('dev_tools.component', {}, 'Component')}\n value={tools.context.component}\n />\n )}\n {tools.tenant !== null && (\n <Row label={t('dev_tools.tenant', {}, 'Tenant')} value={tools.tenant} />\n )}\n {parameters.map(([key, value]) => (\n <Row key={key} label={key} value={jsonScalar(value)} />\n ))}\n <Row\n label={t('dev_tools.bound_to_user', {}, 'Bound to the user')}\n value={\n tools.context.boundToUser\n ? t('dev_tools.yes', {}, 'Yes')\n : t('dev_tools.no', {}, 'No')\n }\n plain\n />\n <Row\n label={t('dev_tools.issued_at', {}, 'Issued')}\n value={time(tools.context.issuedAt)}\n plain\n />\n <Row\n label={t('dev_tools.expires_at', {}, 'Expires')}\n value={time(tools.context.expiresAt)}\n plain\n />\n </dl>\n </section>\n\n <section data-slot=\"dev-tools-classes\">\n <h3 className=\"mb-2 text-sm font-medium\">\n {t('dev_tools.classes', {}, 'Classes')}\n </h3>\n <ul className=\"divide-y rounded-md border text-xs\">\n {tools.classes.map((item) => (\n <li key={item.role} className=\"flex items-center gap-2 px-3 py-1.5\">\n <span className=\"text-muted-foreground w-16 shrink-0\">\n {t(`dev_tools.roles.${item.role}`, {}, item.role)}\n </span>\n <span\n className=\"min-w-0 flex-1 truncate font-mono\"\n title={item.file ? `${item.file}:${item.line ?? 1}` : item.class}\n >\n {item.class}\n </span>\n {item.url && (\n <a\n href={item.url}\n className=\"text-primary inline-flex shrink-0 items-center gap-1 hover:underline\"\n aria-label={t(\n 'dev_tools.open_in_editor',\n { class: item.class, editor: tools.editor ?? '' },\n 'Open :class in :editor',\n )}\n >\n <ExternalLinkIcon className=\"size-3.5\" />\n </a>\n )}\n </li>\n ))}\n </ul>\n {tools.editor === null && (\n <p className=\"text-muted-foreground mt-2 text-xs\">\n {t(\n 'dev_tools.no_editor',\n {},\n 'Set APP_EDITOR (config app.editor) to open these classes in your editor.',\n )}\n </p>\n )}\n </section>\n\n <section data-slot=\"dev-tools-schema\">\n <div className=\"mb-2 flex items-center gap-2\">\n <h3 className=\"me-auto text-sm font-medium\">\n {t('dev_tools.schema', {}, 'Schema')}\n </h3>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={copy}>\n {copied ? <CheckIcon /> : <CopyIcon />}\n {copied\n ? t('dev_tools.copied', {}, 'Copied')\n : t('dev_tools.copy', {}, 'Copy JSON')}\n </Button>\n </div>\n <Input\n type=\"search\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={t('dev_tools.search', {}, 'Search keys and values')}\n aria-label={t('dev_tools.search', {}, 'Search keys and values')}\n className=\"mb-2 h-8\"\n />\n {query.trim() !== '' && search.matches.size === 0 ? (\n <p className=\"text-muted-foreground text-sm\">\n {t(\n 'dev_tools.no_matches',\n { query: query.trim() },\n 'Nothing matches “:query”.',\n )}\n </p>\n ) : (\n <ul\n dir=\"ltr\"\n role=\"tree\"\n aria-label={t('dev_tools.schema', {}, 'Schema')}\n className=\"bg-muted/40 overflow-x-auto rounded-md p-2 text-start font-mono text-xs leading-relaxed\"\n >\n {jsonEntries(data).map((entry) => (\n <JsonNode\n key={entry.path}\n entry={entry}\n depth={0}\n search={search}\n query={query}\n />\n ))}\n </ul>\n )}\n </section>\n </div>\n </ScrollArea>\n </div>\n </SheetContent>\n </Sheet>\n )\n}\n\nfunction JsonNode({\n entry,\n depth,\n search,\n query,\n}: {\n entry: JsonEntry\n depth: number\n search: JsonSearchResult\n query: string\n}) {\n const t = useT()\n const formatter = useFormatter()\n const searching = query.trim() !== ''\n const [open, setOpen] = useState(depth < 1)\n\n if (searching && !search.visible.has(entry.path)) {\n return null\n }\n\n const container = entry.kind === 'object' || entry.kind === 'array'\n const expanded = searching || open\n const matched = search.matches.has(entry.path)\n const size = jsonSize(entry.value)\n const key = (\n <span className={cn('text-foreground', matched && 'bg-primary/15 rounded-sm')}>\n {entry.key}\n </span>\n )\n\n if (!container) {\n return (\n <li\n role=\"treeitem\"\n aria-selected={false}\n className=\"py-px\"\n style={{ paddingInlineStart: `${depth * 12 + 16}px` }}\n >\n {key}\n <span className=\"text-muted-foreground\">: </span>\n <span\n className={cn(\n entry.kind === 'string' && 'text-emerald-700 dark:text-emerald-400',\n entry.kind === 'number' && 'text-sky-700 dark:text-sky-400',\n entry.kind === 'boolean' && 'text-violet-700 dark:text-violet-400',\n entry.kind === 'null' && 'text-muted-foreground',\n 'break-all',\n )}\n >\n {jsonScalar(entry.value)}\n </span>\n </li>\n )\n }\n\n return (\n <li role=\"treeitem\" aria-selected={false} aria-expanded={expanded}>\n <button\n type=\"button\"\n className=\"hover:bg-muted flex w-full items-center gap-1 rounded py-px text-start\"\n style={{ paddingInlineStart: `${depth * 12}px` }}\n onClick={() => setOpen(!open)}\n aria-label={\n expanded\n ? t('dev_tools.collapse', { key: entry.key }, 'Collapse :key')\n : t('dev_tools.expand', { key: entry.key }, 'Expand :key')\n }\n >\n {/* The tree is always left to right (it is code), so the chevron does not flip. */}\n <ChevronRightIcon\n className={cn('size-3 shrink-0 transition-transform', expanded && 'rotate-90')}\n />\n {key}\n <span className=\"text-muted-foreground\">\n {entry.kind === 'array' ? `[${formatter.number(size)}]` : `{${formatter.number(size)}}`}\n </span>\n </button>\n {expanded && size > 0 && (\n <ul role=\"group\">\n {jsonEntries(entry.value, entry.path).map((child) => (\n <JsonNode\n key={child.path}\n entry={child}\n depth={depth + 1}\n search={search}\n query={query}\n />\n ))}\n </ul>\n )}\n </li>\n )\n}\n\nfunction Stat({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"rounded-md border p-3\">\n <dt className=\"text-muted-foreground text-xs\">{label}</dt>\n <dd className=\"font-mono text-sm font-semibold tabular-nums\">{value}</dd>\n </div>\n )\n}\n\nfunction Row({ label, value, plain = false }: { label: string; value: string; plain?: boolean }) {\n return (\n <>\n <dt className=\"text-muted-foreground\">{label}</dt>\n <dd className={cn('min-w-0 truncate', !plain && 'font-mono')} title={value}>\n {value}\n </dd>\n </>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dev-tools-inspector.js","names":[],"sources":["../../../src/components/dev-tools/dev-tools-inspector.tsx"],"sourcesContent":["import {\n byteLength,\n formatBytes,\n inspectableProps,\n jsonEntries,\n jsonScalar,\n jsonSize,\n latestServerTiming,\n overlayPlacementDirection,\n prettyJson,\n searchJson,\n type JsonEntry,\n type JsonSearchResult,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { BracesIcon, CheckIcon, ChevronRightIcon, CopyIcon, ExternalLinkIcon } from 'lucide-react'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useAlfakitProps, useDirection, useFormatter, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { Button } from '../ui/button'\nimport { Input } from '../ui/input'\nimport { ScrollArea } from '../ui/scroll-area'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from '../ui/sheet'\n\n/**\n * The schema inspector: a floating developer button that opens a drawer with the page's\n * props as a searchable JSON tree (with a copy button), what the context token identifies,\n * the server time reported in `Server-Timing`, the payload size and editor links for the\n * PHP classes behind the page. Rendered only for pages that carry `alfakitDevTools`.\n */\nexport function DevToolsInspector() {\n const props = useAlfakitProps()\n const t = useT()\n const formatter = useFormatter()\n const direction = useDirection()\n const tools = props.alfakitDevTools\n const data = useMemo(() => inspectableProps(props as unknown as Record<string, unknown>), [props])\n const payload = useMemo(() => byteLength(props), [props])\n const [query, setQuery] = useState('')\n const [copied, setCopied] = useState(false)\n // The inspector renders in the browser only (lazy chunk), so the Performance API is there.\n const [serverTime, setServerTime] = useState<number | null>(() => latestServerTiming())\n const search = useMemo(() => searchJson(data, query), [data, query])\n\n // Re-read the timing after every visit: the latest response carries the newest header.\n useEffect(() => router.on('finish', () => setServerTime(latestServerTiming())), [])\n\n useEffect(() => {\n if (!copied) return\n\n const timer = setTimeout(() => setCopied(false), 2000)\n\n return () => clearTimeout(timer)\n }, [copied])\n\n if (!tools) {\n return null\n }\n\n const copy = () => {\n void navigator.clipboard?.writeText(prettyJson(data)).then(() => setCopied(true))\n }\n\n const time = (value: number | null) =>\n value === null ? t('dev_tools.never', {}, 'Never') : formatter.dateTime(value * 1000)\n const parameters = Object.entries(tools.context.parameters)\n\n return (\n <Sheet>\n <SheetTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n aria-label={t('dev_tools.open', {}, 'Open schema inspector')}\n data-slot=\"dev-tools-toggle\"\n className=\"fixed end-4 bottom-16 z-40 rounded-full shadow-md [[data-slot=starter-kit-root]_&]:end-auto [[data-slot=starter-kit-root]_&]:right-4\"\n >\n <BracesIcon />\n </Button>\n </SheetTrigger>\n <SheetContent\n side={\n overlayPlacementDirection(direction, props.alfakit?.starterKit) === 'rtl'\n ? 'left'\n : 'right'\n }\n className=\"flex w-full flex-col gap-0 p-0 sm:max-w-xl\"\n >\n <div data-slot=\"dev-tools\" className=\"flex min-h-0 flex-1 flex-col\">\n <SheetHeader className=\"border-b\">\n <SheetTitle className=\"flex items-center gap-2\">\n <BracesIcon className=\"size-4\" />\n {t('dev_tools.title', {}, 'Schema inspector')}\n </SheetTitle>\n <SheetDescription className=\"truncate font-mono text-xs\" title={tools.context.page}>\n {tools.context.page}\n </SheetDescription>\n </SheetHeader>\n <ScrollArea className=\"min-h-0 flex-1\">\n <div className=\"space-y-5 p-4 text-sm\">\n <dl className=\"grid grid-cols-2 gap-3\">\n <Stat\n label={t('dev_tools.server_time', {}, 'Server time')}\n value={\n serverTime === null\n ? t('dev_tools.not_measured', {}, 'Not measured')\n : t(\n 'dev_tools.milliseconds',\n { value: formatter.number(serverTime) },\n ':value ms',\n )\n }\n />\n <Stat\n label={t('dev_tools.payload', {}, 'Payload size')}\n value={formatBytes(payload)}\n />\n </dl>\n\n <section data-slot=\"dev-tools-context\">\n <h3 className=\"mb-2 text-sm font-medium\">\n {t('dev_tools.context', {}, 'Context token')}\n </h3>\n <dl className=\"grid grid-cols-[auto_1fr] gap-x-4 gap-y-1 text-xs\">\n <Row label={t('dev_tools.panel', {}, 'Panel')} value={tools.context.panel} />\n <Row label={t('dev_tools.page', {}, 'Page')} value={tools.context.page} />\n {tools.context.component && (\n <Row\n label={t('dev_tools.component', {}, 'Component')}\n value={tools.context.component}\n />\n )}\n {tools.tenant !== null && (\n <Row label={t('dev_tools.tenant', {}, 'Tenant')} value={tools.tenant} />\n )}\n {parameters.map(([key, value]) => (\n <Row key={key} label={key} value={jsonScalar(value)} />\n ))}\n <Row\n label={t('dev_tools.bound_to_user', {}, 'Bound to the user')}\n value={\n tools.context.boundToUser\n ? t('dev_tools.yes', {}, 'Yes')\n : t('dev_tools.no', {}, 'No')\n }\n plain\n />\n <Row\n label={t('dev_tools.issued_at', {}, 'Issued')}\n value={time(tools.context.issuedAt)}\n plain\n />\n <Row\n label={t('dev_tools.expires_at', {}, 'Expires')}\n value={time(tools.context.expiresAt)}\n plain\n />\n </dl>\n </section>\n\n <section data-slot=\"dev-tools-classes\">\n <h3 className=\"mb-2 text-sm font-medium\">\n {t('dev_tools.classes', {}, 'Classes')}\n </h3>\n <ul className=\"divide-y rounded-md border text-xs\">\n {tools.classes.map((item) => (\n <li key={item.role} className=\"flex items-center gap-2 px-3 py-1.5\">\n <span className=\"text-muted-foreground w-16 shrink-0\">\n {t(`dev_tools.roles.${item.role}`, {}, item.role)}\n </span>\n <span\n className=\"min-w-0 flex-1 truncate font-mono\"\n title={item.file ? `${item.file}:${item.line ?? 1}` : item.class}\n >\n {item.class}\n </span>\n {item.url && (\n <a\n href={item.url}\n className=\"text-primary inline-flex shrink-0 items-center gap-1 hover:underline\"\n aria-label={t(\n 'dev_tools.open_in_editor',\n { class: item.class, editor: tools.editor ?? '' },\n 'Open :class in :editor',\n )}\n >\n <ExternalLinkIcon className=\"size-3.5\" />\n </a>\n )}\n </li>\n ))}\n </ul>\n {tools.editor === null && (\n <p className=\"text-muted-foreground mt-2 text-xs\">\n {t(\n 'dev_tools.no_editor',\n {},\n 'Set APP_EDITOR (config app.editor) to open these classes in your editor.',\n )}\n </p>\n )}\n </section>\n\n <section data-slot=\"dev-tools-schema\">\n <div className=\"mb-2 flex items-center gap-2\">\n <h3 className=\"me-auto text-sm font-medium\">\n {t('dev_tools.schema', {}, 'Schema')}\n </h3>\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={copy}>\n {copied ? <CheckIcon /> : <CopyIcon />}\n {copied\n ? t('dev_tools.copied', {}, 'Copied')\n : t('dev_tools.copy', {}, 'Copy JSON')}\n </Button>\n </div>\n <Input\n type=\"search\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={t('dev_tools.search', {}, 'Search keys and values')}\n aria-label={t('dev_tools.search', {}, 'Search keys and values')}\n className=\"mb-2 h-8\"\n />\n {query.trim() !== '' && search.matches.size === 0 ? (\n <p className=\"text-muted-foreground text-sm\">\n {t(\n 'dev_tools.no_matches',\n { query: query.trim() },\n 'Nothing matches “:query”.',\n )}\n </p>\n ) : (\n <ul\n dir=\"ltr\"\n role=\"tree\"\n aria-label={t('dev_tools.schema', {}, 'Schema')}\n className=\"bg-muted/40 overflow-x-auto rounded-md p-2 text-start font-mono text-xs leading-relaxed\"\n >\n {jsonEntries(data).map((entry) => (\n <JsonNode\n key={entry.path}\n entry={entry}\n depth={0}\n search={search}\n query={query}\n />\n ))}\n </ul>\n )}\n </section>\n </div>\n </ScrollArea>\n </div>\n </SheetContent>\n </Sheet>\n )\n}\n\nfunction JsonNode({\n entry,\n depth,\n search,\n query,\n}: {\n entry: JsonEntry\n depth: number\n search: JsonSearchResult\n query: string\n}) {\n const t = useT()\n const formatter = useFormatter()\n const searching = query.trim() !== ''\n const [open, setOpen] = useState(depth < 1)\n\n if (searching && !search.visible.has(entry.path)) {\n return null\n }\n\n const container = entry.kind === 'object' || entry.kind === 'array'\n const expanded = searching || open\n const matched = search.matches.has(entry.path)\n const size = jsonSize(entry.value)\n const key = (\n <span className={cn('text-foreground', matched && 'bg-primary/15 rounded-sm')}>\n {entry.key}\n </span>\n )\n\n if (!container) {\n return (\n <li\n role=\"treeitem\"\n aria-selected={false}\n className=\"py-px\"\n style={{ paddingInlineStart: `${depth * 12 + 16}px` }}\n >\n {key}\n <span className=\"text-muted-foreground\">: </span>\n <span\n className={cn(\n entry.kind === 'string' && 'text-emerald-700 dark:text-emerald-400',\n entry.kind === 'number' && 'text-sky-700 dark:text-sky-400',\n entry.kind === 'boolean' && 'text-violet-700 dark:text-violet-400',\n entry.kind === 'null' && 'text-muted-foreground',\n 'break-all',\n )}\n >\n {jsonScalar(entry.value)}\n </span>\n </li>\n )\n }\n\n return (\n <li role=\"treeitem\" aria-selected={false} aria-expanded={expanded}>\n <button\n type=\"button\"\n className=\"hover:bg-muted flex w-full items-center gap-1 rounded py-px text-start\"\n style={{ paddingInlineStart: `${depth * 12}px` }}\n onClick={() => setOpen(!open)}\n aria-label={\n expanded\n ? t('dev_tools.collapse', { key: entry.key }, 'Collapse :key')\n : t('dev_tools.expand', { key: entry.key }, 'Expand :key')\n }\n >\n {/* The tree is always left to right (it is code), so the chevron does not flip. */}\n <ChevronRightIcon\n className={cn('size-3 shrink-0 transition-transform', expanded && 'rotate-90')}\n />\n {key}\n <span className=\"text-muted-foreground\">\n {entry.kind === 'array' ? `[${formatter.number(size)}]` : `{${formatter.number(size)}}`}\n </span>\n </button>\n {expanded && size > 0 && (\n <ul role=\"group\">\n {jsonEntries(entry.value, entry.path).map((child) => (\n <JsonNode\n key={child.path}\n entry={child}\n depth={depth + 1}\n search={search}\n query={query}\n />\n ))}\n </ul>\n )}\n </li>\n )\n}\n\nfunction Stat({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"rounded-md border p-3\">\n <dt className=\"text-muted-foreground text-xs\">{label}</dt>\n <dd className=\"font-mono text-sm font-semibold tabular-nums\">{value}</dd>\n </div>\n )\n}\n\nfunction Row({ label, value, plain = false }: { label: string; value: string; plain?: boolean }) {\n return (\n <>\n <dt className=\"text-muted-foreground\">{label}</dt>\n <dd className={cn('min-w-0 truncate', !plain && 'font-mono')} title={value}>\n {value}\n </dd>\n </>\n )\n}\n"],"mappings":"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"}
|
|
@@ -52,7 +52,7 @@ function CheckboxListField({ component }) {
|
|
|
52
52
|
className: "flex flex-col gap-2",
|
|
53
53
|
children: [
|
|
54
54
|
(component.searchable || component.bulkToggleable && !readOnly) && /* @__PURE__ */ jsxs("div", {
|
|
55
|
-
className: "flex items-center gap-2",
|
|
55
|
+
className: "flex min-w-0 flex-wrap items-center gap-2",
|
|
56
56
|
children: [component.searchable && /* @__PURE__ */ jsx(Input, {
|
|
57
57
|
value: search,
|
|
58
58
|
onChange: (event) => setSearch(event.target.value),
|
|
@@ -60,7 +60,7 @@ function CheckboxListField({ component }) {
|
|
|
60
60
|
"aria-label": component.searchPrompt ?? t("search", {}, "Search"),
|
|
61
61
|
className: "h-8"
|
|
62
62
|
}), component.bulkToggleable && !readOnly && /* @__PURE__ */ jsxs("div", {
|
|
63
|
-
className: "flex gap-1",
|
|
63
|
+
className: "flex flex-wrap gap-1",
|
|
64
64
|
children: [!selectAll?.hidden && /* @__PURE__ */ jsxs(Button, {
|
|
65
65
|
type: "button",
|
|
66
66
|
variant: "link",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkbox-list.js","names":[],"sources":["../../../src/components/fields/checkbox-list.tsx"],"sourcesContent":["import type { CheckboxListSchema } from '@alfakit/core'\nimport { fieldIds, gridColumnsClasses, htmlLabelToText, isOptionGroup } from '@alfakit/core'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useT } from '../../context'\nimport { textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\n\n/**\n * `gridDirection('column')`: options flow down each column (CSS columns). Classes are\n * spelled out so Tailwind generates them.\n */\nconst COLUMN_FLOW_CLASSES: Record<number, string> = {\n 2: 'md:columns-2',\n 3: 'md:columns-3',\n 4: 'md:columns-4',\n 5: 'md:columns-5',\n 6: 'md:columns-6',\n}\n\nexport function CheckboxListField({ component }: { component: CheckboxListSchema }) {\n const t = useT()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<Array<\n string | number\n > | null>(component)\n const [search, setSearch] = useState('')\n // `searchDebounce()`: the list is filtered by `query`, which trails the input.\n const [debounced, setDebounced] = useState('')\n const query = component.searchDebounce ? debounced : search\n const locked = disabled || readOnly\n\n const selected = useMemo(() => (value ?? []).map(String), [value])\n const options = useMemo(\n () =>\n component.options.flatMap((option) => (isOptionGroup(option) ? option.options : [option])),\n [component.options],\n )\n const visible = options.filter((option) =>\n (component.allowHtml ? htmlLabelToText(option.label) : option.label)\n .toLowerCase()\n .includes(query.toLowerCase()),\n )\n\n useEffect(() => {\n if (!component.searchDebounce) return\n\n const timer = setTimeout(() => setDebounced(search), component.searchDebounce)\n\n return () => clearTimeout(timer)\n }, [search, component.searchDebounce])\n const columnFlow = component.gridDirection === 'column' && (component.gridColumns ?? 1) > 1\n const selectAll = component.selectAllAction\n const deselectAll = component.deselectAllAction\n\n const toggle = (key: string, checked: boolean) => {\n setValue(checked ? [...selected, key] : selected.filter((item) => item !== key))\n }\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div className=\"flex flex-col gap-2\">\n {(component.searchable || (component.bulkToggleable && !readOnly)) && (\n <div className=\"flex items-center gap-2\">\n {component.searchable && (\n <Input\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={component.searchPrompt ?? t('search', {}, 'Search')}\n aria-label={component.searchPrompt ?? t('search', {}, 'Search')}\n className=\"h-8\"\n />\n )}\n {component.bulkToggleable && !readOnly && (\n <div className=\"flex gap-1\">\n {!selectAll?.hidden && (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n data-slot=\"select-all\"\n disabled={disabled}\n onClick={() =>\n setValue(\n options.filter((option) => !option.disabled).map((option) => option.value),\n )\n }\n >\n {selectAll?.icon && <Icon name={selectAll.icon} />}\n {selectAll?.label ?? t('table.select_all', {}, 'Select all')}\n </Button>\n )}\n {!deselectAll?.hidden && (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n data-slot=\"deselect-all\"\n disabled={disabled}\n onClick={() => setValue([])}\n >\n {deselectAll?.icon && <Icon name={deselectAll.icon} />}\n {deselectAll?.label ?? t('table.deselect_all', {}, 'Deselect all')}\n </Button>\n )}\n </div>\n )}\n </div>\n )}\n <div\n data-grid-direction={component.gridDirection}\n className={cn(\n component.inline\n ? 'flex flex-wrap gap-4'\n : columnFlow\n ? cn(\n 'gap-x-3 *:mb-3 *:break-inside-avoid',\n COLUMN_FLOW_CLASSES[component.gridColumns ?? 1],\n )\n : 'grid gap-3',\n !component.inline &&\n !columnFlow &&\n gridColumnsClasses(\n component.gridColumns ? { default: 1, md: component.gridColumns } : { default: 1 },\n ),\n )}\n role=\"group\"\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n >\n {visible.map((option) => {\n const optionId = `${id}-${option.value}`\n\n return (\n <div key={option.value} className=\"flex items-start gap-2\">\n <Checkbox\n id={optionId}\n checked={selected.includes(option.value)}\n disabled={locked || option.disabled}\n onCheckedChange={(checked) => toggle(option.value, checked === true)}\n aria-invalid={aria['aria-invalid']}\n className=\"mt-0.5\"\n />\n <div className=\"grid gap-0.5\">\n <Label\n htmlFor={optionId}\n className={cn('font-normal', option.color && textColorClasses[option.color])}\n >\n {option.icon && <Icon name={option.icon} className=\"size-4\" />}\n {component.allowHtml ? (\n <span dangerouslySetInnerHTML={{ __html: option.label }} />\n ) : (\n option.label\n )}\n </Label>\n {option.description && (\n <p className=\"text-muted-foreground text-xs\">{option.description}</p>\n )}\n </div>\n </div>\n )\n })}\n </div>\n {component.searchable && query !== '' && visible.length === 0 && (\n <p\n data-slot=\"checkbox-list-empty\"\n role=\"status\"\n className=\"text-muted-foreground text-sm\"\n >\n {component.noSearchResultsMessage ?? t('no_results', {}, 'No results.')}\n </p>\n )}\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"checkbox-list.js","names":[],"sources":["../../../src/components/fields/checkbox-list.tsx"],"sourcesContent":["import type { CheckboxListSchema } from '@alfakit/core'\nimport { fieldIds, gridColumnsClasses, htmlLabelToText, isOptionGroup } from '@alfakit/core'\nimport { useEffect, useMemo, useState } from 'react'\nimport { useT } from '../../context'\nimport { textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\n\n/**\n * `gridDirection('column')`: options flow down each column (CSS columns). Classes are\n * spelled out so Tailwind generates them.\n */\nconst COLUMN_FLOW_CLASSES: Record<number, string> = {\n 2: 'md:columns-2',\n 3: 'md:columns-3',\n 4: 'md:columns-4',\n 5: 'md:columns-5',\n 6: 'md:columns-6',\n}\n\nexport function CheckboxListField({ component }: { component: CheckboxListSchema }) {\n const t = useT()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<Array<\n string | number\n > | null>(component)\n const [search, setSearch] = useState('')\n // `searchDebounce()`: the list is filtered by `query`, which trails the input.\n const [debounced, setDebounced] = useState('')\n const query = component.searchDebounce ? debounced : search\n const locked = disabled || readOnly\n\n const selected = useMemo(() => (value ?? []).map(String), [value])\n const options = useMemo(\n () =>\n component.options.flatMap((option) => (isOptionGroup(option) ? option.options : [option])),\n [component.options],\n )\n const visible = options.filter((option) =>\n (component.allowHtml ? htmlLabelToText(option.label) : option.label)\n .toLowerCase()\n .includes(query.toLowerCase()),\n )\n\n useEffect(() => {\n if (!component.searchDebounce) return\n\n const timer = setTimeout(() => setDebounced(search), component.searchDebounce)\n\n return () => clearTimeout(timer)\n }, [search, component.searchDebounce])\n const columnFlow = component.gridDirection === 'column' && (component.gridColumns ?? 1) > 1\n const selectAll = component.selectAllAction\n const deselectAll = component.deselectAllAction\n\n const toggle = (key: string, checked: boolean) => {\n setValue(checked ? [...selected, key] : selected.filter((item) => item !== key))\n }\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div className=\"flex flex-col gap-2\">\n {(component.searchable || (component.bulkToggleable && !readOnly)) && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n {component.searchable && (\n <Input\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n placeholder={component.searchPrompt ?? t('search', {}, 'Search')}\n aria-label={component.searchPrompt ?? t('search', {}, 'Search')}\n className=\"h-8\"\n />\n )}\n {component.bulkToggleable && !readOnly && (\n <div className=\"flex flex-wrap gap-1\">\n {!selectAll?.hidden && (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n data-slot=\"select-all\"\n disabled={disabled}\n onClick={() =>\n setValue(\n options.filter((option) => !option.disabled).map((option) => option.value),\n )\n }\n >\n {selectAll?.icon && <Icon name={selectAll.icon} />}\n {selectAll?.label ?? t('table.select_all', {}, 'Select all')}\n </Button>\n )}\n {!deselectAll?.hidden && (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n data-slot=\"deselect-all\"\n disabled={disabled}\n onClick={() => setValue([])}\n >\n {deselectAll?.icon && <Icon name={deselectAll.icon} />}\n {deselectAll?.label ?? t('table.deselect_all', {}, 'Deselect all')}\n </Button>\n )}\n </div>\n )}\n </div>\n )}\n <div\n data-grid-direction={component.gridDirection}\n className={cn(\n component.inline\n ? 'flex flex-wrap gap-4'\n : columnFlow\n ? cn(\n 'gap-x-3 *:mb-3 *:break-inside-avoid',\n COLUMN_FLOW_CLASSES[component.gridColumns ?? 1],\n )\n : 'grid gap-3',\n !component.inline &&\n !columnFlow &&\n gridColumnsClasses(\n component.gridColumns ? { default: 1, md: component.gridColumns } : { default: 1 },\n ),\n )}\n role=\"group\"\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n >\n {visible.map((option) => {\n const optionId = `${id}-${option.value}`\n\n return (\n <div key={option.value} className=\"flex items-start gap-2\">\n <Checkbox\n id={optionId}\n checked={selected.includes(option.value)}\n disabled={locked || option.disabled}\n onCheckedChange={(checked) => toggle(option.value, checked === true)}\n aria-invalid={aria['aria-invalid']}\n className=\"mt-0.5\"\n />\n <div className=\"grid gap-0.5\">\n <Label\n htmlFor={optionId}\n className={cn('font-normal', option.color && textColorClasses[option.color])}\n >\n {option.icon && <Icon name={option.icon} className=\"size-4\" />}\n {component.allowHtml ? (\n <span dangerouslySetInnerHTML={{ __html: option.label }} />\n ) : (\n option.label\n )}\n </Label>\n {option.description && (\n <p className=\"text-muted-foreground text-xs\">{option.description}</p>\n )}\n </div>\n </div>\n )\n })}\n </div>\n {component.searchable && query !== '' && visible.length === 0 && (\n <p\n data-slot=\"checkbox-list-empty\"\n role=\"status\"\n className=\"text-muted-foreground text-sm\"\n >\n {component.noSearchResultsMessage ?? t('no_results', {}, 'No results.')}\n </p>\n )}\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { MoneyInputSchema } from "@alfakit/core";
|
|
2
2
|
//#region src/components/fields/money-input.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Amount input for any ISO 4217 currency. Shows the symbol where the user's locale puts
|
|
5
|
+
* it, accepts the locale's separators and digits ("1.234,50", "١٢٫٥"), and rounds to
|
|
6
|
+
* the currency's precision on blur. The form value is a plain decimal string ("1234.50").
|
|
7
|
+
*/
|
|
3
8
|
export declare function MoneyInputField({ component }: {
|
|
4
9
|
component: MoneyInputSchema;
|
|
5
10
|
}): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"money-input.d.ts","names":[],"sources":["../../../src/components/fields/money-input.tsx"],"mappings":"
|
|
1
|
+
{"version":3,"file":"money-input.d.ts","names":[],"sources":["../../../src/components/fields/money-input.tsx"],"mappings":";;;;;;;wBAegB,kBAAkB;EAAe,WAAW;oBAAkB,IAAA"}
|
|
@@ -13,9 +13,6 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
* it, accepts the locale's separators and digits ("1.234,50", "١٢٫٥"), and rounds to
|
|
14
14
|
* the currency's precision on blur. The form value is a plain decimal string ("1234.50").
|
|
15
15
|
*/
|
|
16
|
-
function clampAmount(amount, min, max) {
|
|
17
|
-
return Math.min(max ?? Number.POSITIVE_INFINITY, Math.max(min ?? Number.NEGATIVE_INFINITY, amount));
|
|
18
|
-
}
|
|
19
16
|
function MoneyInputField({ component }) {
|
|
20
17
|
const { value, setValue, blur, error, id, disabled, aria } = useField(component);
|
|
21
18
|
const format = useFormatter();
|
|
@@ -32,7 +29,7 @@ function MoneyInputField({ component }) {
|
|
|
32
29
|
return;
|
|
33
30
|
}
|
|
34
31
|
const parsed = format.parseNumber(text);
|
|
35
|
-
setValue(parsed === null ? text :
|
|
32
|
+
setValue(parsed === null ? text : parsed.toFixed(component.decimals));
|
|
36
33
|
};
|
|
37
34
|
const affix = (text, icon, side) => text || icon ? /* @__PURE__ */ jsxs("span", {
|
|
38
35
|
className: cn("text-muted-foreground bg-muted/50 flex items-center gap-1 px-3 text-sm whitespace-nowrap", side === "start" ? "border-e" : "border-s"),
|
|
@@ -86,6 +83,6 @@ function MoneyInputField({ component }) {
|
|
|
86
83
|
});
|
|
87
84
|
}
|
|
88
85
|
//#endregion
|
|
89
|
-
export { MoneyInputField
|
|
86
|
+
export { MoneyInputField };
|
|
90
87
|
|
|
91
88
|
//# sourceMappingURL=money-input.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"money-input.js","names":[],"sources":["../../../src/components/fields/money-input.tsx"],"sourcesContent":["import type { MoneyInputSchema } from '@alfakit/core'\nimport { useState } from 'react'\nimport { useFormatter } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Input } from '../ui/input'\n\n/**\n * Amount input for any ISO 4217 currency. Shows the symbol where the user's locale puts\n * it, accepts the locale's separators and digits (\"1.234,50\", \"١٢٫٥\"), and rounds to\n * the currency's precision on blur. The form value is a plain decimal string (\"1234.50\").\n */\nexport function
|
|
1
|
+
{"version":3,"file":"money-input.js","names":[],"sources":["../../../src/components/fields/money-input.tsx"],"sourcesContent":["import type { MoneyInputSchema } from '@alfakit/core'\nimport { useState } from 'react'\nimport { useFormatter } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Input } from '../ui/input'\n\n/**\n * Amount input for any ISO 4217 currency. Shows the symbol where the user's locale puts\n * it, accepts the locale's separators and digits (\"1.234,50\", \"١٢٫٥\"), and rounds to\n * the currency's precision on blur. The form value is a plain decimal string (\"1234.50\").\n */\nexport function MoneyInputField({ component }: { component: MoneyInputSchema }) {\n const { value, setValue, blur, error, id, disabled, aria } = useField<string | number | null>(\n component,\n )\n const format = useFormatter()\n const [draft, setDraft] = useState<string | null>(null)\n\n const { prefix, suffix } = format.currencyAffixes(component.currency)\n const amount = value === null || value === undefined || value === '' ? null : Number(value)\n const display =\n amount === null || Number.isNaN(amount)\n ? String(value ?? '')\n : format.number(amount, {\n minimumFractionDigits: component.decimals,\n maximumFractionDigits: component.decimals,\n })\n\n const commit = (text: string) => {\n if (text.trim() === '') {\n setValue(null)\n\n return\n }\n\n const parsed = format.parseNumber(text)\n\n // Unparseable input is kept as typed and out-of-range amounts are kept as well, so the\n // server's `minValue()` / `maxValue()` rules report them (W-V3-11).\n setValue(parsed === null ? text : parsed.toFixed(component.decimals))\n }\n\n const affix = (text: string, icon: string | undefined, side: 'start' | 'end') =>\n text || icon ? (\n <span\n className={cn(\n 'text-muted-foreground bg-muted/50 flex items-center gap-1 px-3 text-sm whitespace-nowrap',\n side === 'start' ? 'border-e' : 'border-s',\n )}\n >\n {side === 'start' && <Icon name={icon} />}\n {text}\n {side === 'end' && <Icon name={icon} />}\n </span>\n ) : null\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n data-slot=\"input-group\"\n className={cn(\n 'border-input dark:bg-input/30 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-ring/50 flex h-9 w-full items-stretch overflow-hidden rounded-md border shadow-xs has-[input:focus-visible]:ring-[3px]',\n error && 'border-destructive',\n disabled && 'opacity-50',\n )}\n >\n {affix([component.prefix, prefix].filter(Boolean).join(' '), component.prefixIcon, 'start')}\n <Input\n id={id}\n name={component.name}\n type=\"text\"\n inputMode={component.decimals > 0 ? 'decimal' : 'numeric'}\n dir=\"ltr\"\n value={draft ?? display}\n onFocus={() =>\n setDraft(\n amount === null || Number.isNaN(amount)\n ? String(value ?? '')\n : format.number(amount, {\n useGrouping: false,\n maximumFractionDigits: component.decimals,\n }),\n )\n }\n onChange={(event) => {\n setDraft(event.target.value)\n const parsed = format.parseNumber(event.target.value)\n setValue(\n event.target.value.trim() === ''\n ? null\n : parsed === null\n ? event.target.value\n : String(parsed),\n )\n }}\n onBlur={(event) => {\n commit(event.target.value)\n setDraft(null)\n blur()\n }}\n placeholder={component.placeholder}\n disabled={disabled}\n readOnly={component.readOnly}\n required={component.required}\n autoFocus={component.autofocus}\n {...aria}\n\n {...extraProps(\n component.extraInputAttributes,\n 'h-full border-0 bg-transparent text-end tabular-nums shadow-none focus-visible:ring-0 dark:bg-transparent',\n )}\n />\n {affix([suffix, component.suffix].filter(Boolean).join(' '), component.suffixIcon, 'end')}\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;AAeA,SAAgB,gBAAgB,EAAE,aAA8C;CAC9E,MAAM,EAAE,OAAO,UAAU,MAAM,OAAO,IAAI,UAAU,SAAS,SAC3D,SACF;CACA,MAAM,SAAS,aAAa;CAC5B,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CAEtD,MAAM,EAAE,QAAQ,WAAW,OAAO,gBAAgB,UAAU,QAAQ;CACpE,MAAM,SAAS,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,KAAK,OAAO,OAAO,KAAK;CAC1F,MAAM,UACJ,WAAW,QAAQ,OAAO,MAAM,MAAM,IAClC,OAAO,SAAS,EAAE,IAClB,OAAO,OAAO,QAAQ;EACpB,uBAAuB,UAAU;EACjC,uBAAuB,UAAU;CACnC,CAAC;CAEP,MAAM,UAAU,SAAiB;EAC/B,IAAI,KAAK,KAAK,MAAM,IAAI;GACtB,SAAS,IAAI;GAEb;EACF;EAEA,MAAM,SAAS,OAAO,YAAY,IAAI;EAItC,SAAS,WAAW,OAAO,OAAO,OAAO,QAAQ,UAAU,QAAQ,CAAC;CACtE;CAEA,MAAM,SAAS,MAAc,MAA0B,SACrD,QAAQ,OACN,qBAAC,QAAD;EACE,WAAW,GACT,4FACA,SAAS,UAAU,aAAa,UAClC;EAJF,UAAA;GAMG,SAAS,WAAW,oBAAC,MAAD,EAAM,MAAM,KAAO,CAAA;GACvC;GACA,SAAS,SAAS,oBAAC,MAAD,EAAM,MAAM,KAAO,CAAA;EAClC;CACJ,CAAA,IAAA;CAEN,OACE,oBAAC,cAAD;EAAc,OAAO;EAAW,SAAS;EAAW;EAClD,UAAA,qBAAC,OAAD;GACE,aAAU;GACV,WAAW,GACT,6NACA,SAAS,sBACT,YAAY,YACd;GANF,UAAA;IAQG,MAAM,CAAC,UAAU,QAAQ,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,GAAG,UAAU,YAAY,OAAO;IAC1F,oBAAC,OAAD;KACM;KACJ,MAAM,UAAU;KAChB,MAAK;KACL,WAAW,UAAU,WAAW,IAAI,YAAY;KAChD,KAAI;KACJ,OAAO,SAAS;KAChB,eACE,SACE,WAAW,QAAQ,OAAO,MAAM,MAAM,IAClC,OAAO,SAAS,EAAE,IAClB,OAAO,OAAO,QAAQ;MACpB,aAAa;MACb,uBAAuB,UAAU;KACnC,CAAC,CACP;KAEF,WAAW,UAAU;MACnB,SAAS,MAAM,OAAO,KAAK;MAC3B,MAAM,SAAS,OAAO,YAAY,MAAM,OAAO,KAAK;MACpD,SACE,MAAM,OAAO,MAAM,KAAK,MAAM,KAC1B,OACA,WAAW,OACT,MAAM,OAAO,QACb,OAAO,MAAM,CACrB;KACF;KACA,SAAS,UAAU;MACjB,OAAO,MAAM,OAAO,KAAK;MACzB,SAAS,IAAI;MACb,KAAK;KACP;KACA,aAAa,UAAU;KACb;KACV,UAAU,UAAU;KACpB,UAAU,UAAU;KACpB,WAAW,UAAU;KACrB,GAAI;KAEJ,GAAI,WACF,UAAU,sBACV,2GACF;IACD,CAAA;IACA,MAAM,CAAC,QAAQ,UAAU,MAAM,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,GAAG,UAAU,YAAY,KAAK;GACrF;;CACO,CAAA;AAElB"}
|
|
@@ -7,7 +7,7 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
|
|
7
7
|
import { useStore } from "../../hooks/use-store.js";
|
|
8
8
|
import { useOptionalFormStore } from "../form/form-context.js";
|
|
9
9
|
import { Input } from "../ui/input.js";
|
|
10
|
-
import { EditorSuggestions, RichEditorRemote, commandLabel, customBlockNodeConfig, fieldActionContext, groupCustomBlocks, groupTriggerButton, isCustomBlockResult, mentionNodeConfig, mergeTagNodeConfig, mergeTagText } from "@alfakit/core";
|
|
10
|
+
import { EditorSuggestions, RichEditorRemote, commandLabel, customBlockNodeConfig, fieldActionContext, fixedContainingBlockOffset, groupCustomBlocks, groupTriggerButton, isCustomBlockResult, mentionNodeConfig, mergeTagNodeConfig, mergeTagText } from "@alfakit/core";
|
|
11
11
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
12
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { BlocksIcon, BracesIcon, ChevronDownIcon, XIcon } from "lucide-react";
|
|
@@ -219,6 +219,7 @@ function RichEditorSidePanel({ panel, component, editable, sticky, onInsertBlock
|
|
|
219
219
|
/** A `ToolbarButtonGroup`: a dropdown of buttons (icons, or labels with `textualButtons()`). */
|
|
220
220
|
function ToolbarGroupButton({ group, editor, editable, commands, icons, translate, onRun }) {
|
|
221
221
|
const buttons = group.buttons.filter((name) => commands[name]);
|
|
222
|
+
const picked = useRef(false);
|
|
222
223
|
if (buttons.length === 0) return null;
|
|
223
224
|
const trigger = groupTriggerButton(buttons, editor, commands);
|
|
224
225
|
const active = buttons.some((name) => commands[name]?.active?.(editor));
|
|
@@ -239,6 +240,12 @@ function ToolbarGroupButton({ group, editor, editable, commands, icons, translat
|
|
|
239
240
|
align: "start",
|
|
240
241
|
"aria-label": group.label,
|
|
241
242
|
className: cn(!group.textual && "flex min-w-0 flex-row gap-0.5"),
|
|
243
|
+
onCloseAutoFocus: (event) => {
|
|
244
|
+
if (!picked.current) return;
|
|
245
|
+
picked.current = false;
|
|
246
|
+
event.preventDefault();
|
|
247
|
+
editor.commands.focus();
|
|
248
|
+
},
|
|
242
249
|
children: buttons.map((name) => {
|
|
243
250
|
const label = commandLabel(name, commands, translate);
|
|
244
251
|
const pressed = commands[name]?.active?.(editor);
|
|
@@ -246,7 +253,10 @@ function ToolbarGroupButton({ group, editor, editable, commands, icons, translat
|
|
|
246
253
|
"aria-label": group.textual ? void 0 : label,
|
|
247
254
|
title: label,
|
|
248
255
|
className: cn(pressed && "bg-accent text-accent-foreground"),
|
|
249
|
-
onSelect: () =>
|
|
256
|
+
onSelect: () => {
|
|
257
|
+
picked.current = true;
|
|
258
|
+
onRun(name);
|
|
259
|
+
},
|
|
250
260
|
children: [icons(name), group.textual && /* @__PURE__ */ jsx("span", { children: label })]
|
|
251
261
|
}, name);
|
|
252
262
|
})
|
|
@@ -268,8 +278,8 @@ function SuggestionPopup({ suggestions, editor, id }) {
|
|
|
268
278
|
"data-slot": "rich-editor-suggestions",
|
|
269
279
|
className: "bg-popover text-popover-foreground fixed z-50 min-w-48 overflow-hidden rounded-md border p-1 shadow-md",
|
|
270
280
|
style: state.coords ? {
|
|
271
|
-
left: state.coords.left,
|
|
272
|
-
top: state.coords.bottom + 4
|
|
281
|
+
left: state.coords.left - fixedContainingBlockOffset(editor.view.dom).x,
|
|
282
|
+
top: state.coords.bottom + 4 - fixedContainingBlockOffset(editor.view.dom).y
|
|
273
283
|
} : void 0,
|
|
274
284
|
children: state.loading && state.items.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
275
285
|
className: "text-muted-foreground px-2 py-1.5 text-sm",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-editor-extensions.js","names":[],"sources":["../../../src/components/fields/rich-editor-extensions.tsx"],"sourcesContent":["import type {\n ActionContext,\n RichEditorPanel,\n RichEditorSchema,\n RichEditorToolbarGroupSchema,\n SuggestionState,\n ToolbarCommand,\n} from '@alfakit/core'\nimport {\n commandLabel,\n customBlockNodeConfig,\n EditorSuggestions,\n fieldActionContext,\n groupCustomBlocks,\n groupTriggerButton,\n isCustomBlockResult,\n mentionNodeConfig,\n mergeTagNodeConfig,\n mergeTagText,\n RichEditorRemote,\n} from '@alfakit/core'\nimport { Node, type Editor, type Extensions } from '@tiptap/react'\nimport { BlocksIcon, BracesIcon, ChevronDownIcon, XIcon } from 'lucide-react'\nimport {\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type MutableRefObject,\n type ReactNode,\n} from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { Button } from '../ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\n\ntype NodeConfig = Parameters<typeof Node.create>[0]\n\nexport interface RichEditorExtensions {\n extensions: Extensions\n suggestions: EditorSuggestions\n /** Insert a custom block (opening its modal first when it has a form). */\n insertBlock: (id: string) => Promise<void>\n context: ActionContext\n}\n\n/**\n * Mentions, merge tags and custom blocks for a RichEditor. `editorRef` points at the live\n * editor, which the custom block node views and suggestions act on.\n */\nexport function useRichEditorExtensions(\n component: RichEditorSchema,\n editorRef: MutableRefObject<Editor | null>,\n): RichEditorExtensions {\n const t = useT()\n const form = useOptionalFormStore()\n const page = usePageActionContext()\n const runner = useActionRunner()\n const context = useMemo(\n () => fieldActionContext({ token: page.token, form, key: component.key }),\n [page.token, form, component.key],\n )\n const contextRef = useRef(context)\n const componentRef = useRef(component)\n\n const [remote] = useState(() => new RichEditorRemote(form, component.statePath))\n const [suggestions] = useState(\n () =>\n new EditorSuggestions({\n mentions: component.mentions ?? [],\n mergeTags: component.mergeTags ?? [],\n search: (trigger, query) => remote.searchMentions(trigger, query),\n }),\n )\n\n useEffect(() => () => suggestions.destroy(), [suggestions])\n\n const runBlock = async (id: string, config?: Record<string, unknown>) => {\n const block = componentRef.current.customBlocks?.find((candidate) => candidate.id === id)\n\n if (!block) return null\n\n const result = await runner.runField(block.action, {\n ...contextRef.current,\n arguments: config ? { config, editing: true } : {},\n })\n\n if (!isCustomBlockResult(result)) return null\n\n if (result.preview !== undefined) {\n remote.primePreview(result.id, result.config, result.preview, result.prose === true)\n }\n\n return result\n }\n\n const runBlockRef = useRef(runBlock)\n\n useEffect(() => {\n contextRef.current = context\n componentRef.current = component\n runBlockRef.current = runBlock\n })\n\n // The editor is created once; the node views read the latest props through refs when\n // they run (on edit, on preview), never during render.\n // eslint-disable-next-line react-hooks/refs\n const [extensions] = useState<Extensions>(() => [\n Node.create(mentionNodeConfig() as NodeConfig),\n Node.create(mergeTagNodeConfig() as NodeConfig),\n Node.create(\n customBlockNodeConfig({\n get blocks() {\n return componentRef.current.customBlocks ?? []\n },\n loadPreview: (id, config) => remote.loadPreview(id, config),\n onEdit: ({ id, config, pos }) => {\n void runBlockRef.current(id, config).then((result) => {\n const editor = editorRef.current\n\n if (!result || !editor || editor.isDestroyed) return\n\n editor.view.dispatch(\n editor.state.tr.setNodeMarkup(pos, undefined, { id, config: result.config }),\n )\n })\n },\n labels: {\n edit: t('editor.edit_block', {}, 'Edit block'),\n remove: t('editor.remove_block', {}, 'Remove block'),\n },\n minimal: Boolean(component.minimalCustomBlockControls),\n }) as NodeConfig,\n ),\n ])\n\n const insertBlock = async (id: string) => {\n const result = await runBlock(id)\n const editor = editorRef.current\n\n if (!result || !editor || editor.isDestroyed) return\n\n editor\n .chain()\n .focus()\n .insertContent({ type: 'customBlock', attrs: { id: result.id, config: result.config } })\n .run()\n }\n\n return { extensions, suggestions, insertBlock, context }\n}\n\n/** The toolbar button that opens the custom blocks or merge tags panel. */\nexport function ExtensionToolbarButton({\n name,\n component,\n editable,\n open,\n onToggle,\n}: {\n name: RichEditorPanel\n component: RichEditorSchema\n editable: boolean\n open: boolean\n onToggle: () => void\n}) {\n const t = useT()\n const count =\n name === 'customBlocks'\n ? (component.customBlocks ?? []).length\n : (component.mergeTags ?? []).length\n\n if (count === 0) return null\n\n const label =\n name === 'customBlocks'\n ? t('editor.custom_blocks', {}, 'Insert block')\n : t('editor.merge_tags', {}, 'Insert merge tag')\n\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={label}\n title={label}\n aria-pressed={open}\n aria-expanded={open}\n disabled={!editable}\n className={cn(open && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={onToggle}\n >\n {name === 'customBlocks' ? <BlocksIcon /> : <BracesIcon />}\n </Button>\n )\n}\n\n/**\n * The custom blocks or merge tags panel: beside the content on wide editors, above it on\n * narrow ones. Blocks are grouped, optionally searchable and shown as a grid.\n */\nexport function RichEditorSidePanel({\n panel,\n component,\n editable,\n sticky,\n onInsertBlock,\n onInsertMergeTag,\n onClose,\n}: {\n panel: RichEditorPanel\n component: RichEditorSchema\n editable: boolean\n /** `top` of a sticky panel, or null. */\n sticky: string | null\n onInsertBlock: (id: string) => void\n onInsertMergeTag: (id: string) => void\n onClose: () => void\n}) {\n const t = useT()\n const [search, setSearch] = useState('')\n const heading =\n panel === 'customBlocks'\n ? t('editor.custom_blocks_heading', {}, 'Custom blocks')\n : t('editor.merge_tags_heading', {}, 'Merge tags')\n const groups =\n panel === 'customBlocks' ? groupCustomBlocks(component.customBlocks ?? [], search) : []\n const grid = Boolean(component.customBlocksGrid)\n const itemClass =\n 'hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/50 flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50'\n\n return (\n <aside\n aria-label={heading}\n data-slot=\"rich-editor-panel\"\n data-panel={panel}\n className=\"bg-muted/20 shrink-0 border-b @2xl:w-64 @2xl:border-s @2xl:border-b-0\"\n >\n <div\n className={cn('flex flex-col gap-2 p-2', sticky !== null && '@2xl:sticky')}\n // The offset is a runtime value: no Tailwind class can hold it.\n style={sticky !== null ? { top: sticky } : undefined}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-muted-foreground text-xs font-medium\">{heading}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('editor.close_panel', {}, 'Close panel')}\n title={t('editor.close_panel', {}, 'Close panel')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={onClose}\n >\n <XIcon />\n </Button>\n </div>\n {panel === 'customBlocks' && component.searchableCustomBlocks && (\n <Input\n type=\"search\"\n value={search}\n aria-label={t('editor.search_blocks', {}, 'Search blocks')}\n placeholder={t('editor.search_blocks', {}, 'Search blocks')}\n className=\"h-8\"\n onChange={(event) => setSearch(event.target.value)}\n />\n )}\n {panel === 'customBlocks' ? (\n groups.length === 0 ? (\n <p className=\"text-muted-foreground px-2 py-1.5 text-sm\">\n {t('no_results', {}, 'No results.')}\n </p>\n ) : (\n groups.map(({ group, blocks }) => (\n <div key={group ?? ''} role=\"group\" aria-label={group ?? heading}>\n {group && (\n <div className=\"text-muted-foreground px-2 pt-1 pb-0.5 text-xs font-medium\">\n {group}\n </div>\n )}\n <div className={cn(grid ? 'grid grid-cols-2 gap-1' : 'flex flex-col gap-0.5')}>\n {blocks.map((block) => (\n <button\n key={block.id}\n type=\"button\"\n disabled={!editable}\n className={cn(\n itemClass,\n grid && 'flex-col justify-center border py-3 text-center',\n )}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onInsertBlock(block.id)}\n >\n {block.icon ? (\n <Icon name={block.icon} className=\"size-4 shrink-0\" />\n ) : (\n <BlocksIcon className=\"size-4 shrink-0\" />\n )}\n <span className=\"truncate\">{block.label}</span>\n </button>\n ))}\n </div>\n </div>\n ))\n )\n ) : (\n <div className=\"flex flex-col gap-0.5\">\n {(component.mergeTags ?? []).map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n disabled={!editable}\n className={itemClass}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onInsertMergeTag(tag.id)}\n >\n <span className=\"truncate\">{tag.label}</span>\n <span className=\"text-muted-foreground ms-auto font-mono text-xs\" dir=\"ltr\">\n {mergeTagText({ id: tag.id })}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n </aside>\n )\n}\n\n/** A `ToolbarButtonGroup`: a dropdown of buttons (icons, or labels with `textualButtons()`). */\nexport function ToolbarGroupButton({\n group,\n editor,\n editable,\n commands,\n icons,\n translate,\n onRun,\n}: {\n group: RichEditorToolbarGroupSchema\n editor: Editor\n editable: boolean\n commands: Record<string, ToolbarCommand>\n icons: (name: string) => ReactNode\n translate: (key: string, fallback: string) => string\n onRun: (name: string) => void\n}) {\n const buttons = group.buttons.filter((name) => commands[name])\n\n if (buttons.length === 0) return null\n\n const trigger = groupTriggerButton(buttons, editor, commands)\n const active = buttons.some((name) => commands[name]?.active?.(editor))\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={group.label}\n title={group.label}\n disabled={!editable}\n className={cn('h-8 gap-0.5 px-1.5', active && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n >\n {group.icon ? <Icon name={group.icon} /> : trigger ? icons(trigger) : group.label}\n <ChevronDownIcon className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"start\"\n aria-label={group.label}\n className={cn(!group.textual && 'flex min-w-0 flex-row gap-0.5')}\n >\n {buttons.map((name) => {\n const label = commandLabel(name, commands, translate)\n const pressed = commands[name]?.active?.(editor)\n\n return (\n <DropdownMenuItem\n key={name}\n aria-label={group.textual ? undefined : label}\n title={label}\n className={cn(pressed && 'bg-accent text-accent-foreground')}\n onSelect={() => onRun(name)}\n >\n {icons(name)}\n {group.textual && <span>{label}</span>}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/** Inline style of a sticky toolbar. */\nexport function stickyStyle(top: string | null): CSSProperties | undefined {\n return top === null ? undefined : { top }\n}\n\n/** The mention / merge tag listbox under the trigger. */\nexport function SuggestionPopup({\n suggestions,\n editor,\n id,\n}: {\n suggestions: EditorSuggestions\n editor: Editor | null\n id: string\n}) {\n const t = useT()\n const state: SuggestionState = useStore(suggestions.store)\n\n if (!state.open || !editor) return null\n\n return (\n <div\n id={id}\n role=\"listbox\"\n aria-label={t('editor.suggestions', {}, 'Suggestions')}\n data-slot=\"rich-editor-suggestions\"\n className=\"bg-popover text-popover-foreground fixed z-50 min-w-48 overflow-hidden rounded-md border p-1 shadow-md\"\n // Runtime coordinates cannot be Tailwind classes.\n style={state.coords ? { left: state.coords.left, top: state.coords.bottom + 4 } : undefined}\n >\n {state.loading && state.items.length === 0 ? (\n <div className=\"text-muted-foreground px-2 py-1.5 text-sm\">\n {t('loading', {}, 'Loading…')}\n </div>\n ) : state.items.length === 0 ? (\n <div className=\"text-muted-foreground px-2 py-1.5 text-sm\">\n {t('no_results', {}, 'No results.')}\n </div>\n ) : (\n state.items.map((item, index) => (\n <div\n key={item.id}\n id={`${id}-${index}`}\n role=\"option\"\n aria-selected={index === state.index}\n className={cn(\n 'cursor-pointer rounded-sm px-2 py-1.5 text-sm',\n index === state.index && 'bg-accent text-accent-foreground',\n )}\n onMouseDown={(event) => event.preventDefault()}\n onMouseEnter={() => suggestions.highlight(index)}\n onClick={() => suggestions.select(index, editor)}\n >\n {state.kind === 'mention' ? `${state.trigger}${item.label}` : item.label}\n </div>\n ))\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rich-editor-extensions.js","names":[],"sources":["../../../src/components/fields/rich-editor-extensions.tsx"],"sourcesContent":["import type {\n ActionContext,\n RichEditorPanel,\n RichEditorSchema,\n RichEditorToolbarGroupSchema,\n SuggestionState,\n ToolbarCommand,\n} from '@alfakit/core'\nimport {\n commandLabel,\n customBlockNodeConfig,\n EditorSuggestions,\n fieldActionContext,\n fixedContainingBlockOffset,\n groupCustomBlocks,\n groupTriggerButton,\n isCustomBlockResult,\n mentionNodeConfig,\n mergeTagNodeConfig,\n mergeTagText,\n RichEditorRemote,\n} from '@alfakit/core'\nimport { Node, type Editor, type Extensions } from '@tiptap/react'\nimport { BlocksIcon, BracesIcon, ChevronDownIcon, XIcon } from 'lucide-react'\nimport {\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type MutableRefObject,\n type ReactNode,\n} from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { Button } from '../ui/button'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\n\ntype NodeConfig = Parameters<typeof Node.create>[0]\n\nexport interface RichEditorExtensions {\n extensions: Extensions\n suggestions: EditorSuggestions\n /** Insert a custom block (opening its modal first when it has a form). */\n insertBlock: (id: string) => Promise<void>\n context: ActionContext\n}\n\n/**\n * Mentions, merge tags and custom blocks for a RichEditor. `editorRef` points at the live\n * editor, which the custom block node views and suggestions act on.\n */\nexport function useRichEditorExtensions(\n component: RichEditorSchema,\n editorRef: MutableRefObject<Editor | null>,\n): RichEditorExtensions {\n const t = useT()\n const form = useOptionalFormStore()\n const page = usePageActionContext()\n const runner = useActionRunner()\n const context = useMemo(\n () => fieldActionContext({ token: page.token, form, key: component.key }),\n [page.token, form, component.key],\n )\n const contextRef = useRef(context)\n const componentRef = useRef(component)\n\n const [remote] = useState(() => new RichEditorRemote(form, component.statePath))\n const [suggestions] = useState(\n () =>\n new EditorSuggestions({\n mentions: component.mentions ?? [],\n mergeTags: component.mergeTags ?? [],\n search: (trigger, query) => remote.searchMentions(trigger, query),\n }),\n )\n\n useEffect(() => () => suggestions.destroy(), [suggestions])\n\n const runBlock = async (id: string, config?: Record<string, unknown>) => {\n const block = componentRef.current.customBlocks?.find((candidate) => candidate.id === id)\n\n if (!block) return null\n\n const result = await runner.runField(block.action, {\n ...contextRef.current,\n arguments: config ? { config, editing: true } : {},\n })\n\n if (!isCustomBlockResult(result)) return null\n\n if (result.preview !== undefined) {\n remote.primePreview(result.id, result.config, result.preview, result.prose === true)\n }\n\n return result\n }\n\n const runBlockRef = useRef(runBlock)\n\n useEffect(() => {\n contextRef.current = context\n componentRef.current = component\n runBlockRef.current = runBlock\n })\n\n // The editor is created once; the node views read the latest props through refs when\n // they run (on edit, on preview), never during render.\n // eslint-disable-next-line react-hooks/refs\n const [extensions] = useState<Extensions>(() => [\n Node.create(mentionNodeConfig() as NodeConfig),\n Node.create(mergeTagNodeConfig() as NodeConfig),\n Node.create(\n customBlockNodeConfig({\n get blocks() {\n return componentRef.current.customBlocks ?? []\n },\n loadPreview: (id, config) => remote.loadPreview(id, config),\n onEdit: ({ id, config, pos }) => {\n void runBlockRef.current(id, config).then((result) => {\n const editor = editorRef.current\n\n if (!result || !editor || editor.isDestroyed) return\n\n editor.view.dispatch(\n editor.state.tr.setNodeMarkup(pos, undefined, { id, config: result.config }),\n )\n })\n },\n labels: {\n edit: t('editor.edit_block', {}, 'Edit block'),\n remove: t('editor.remove_block', {}, 'Remove block'),\n },\n minimal: Boolean(component.minimalCustomBlockControls),\n }) as NodeConfig,\n ),\n ])\n\n const insertBlock = async (id: string) => {\n const result = await runBlock(id)\n const editor = editorRef.current\n\n if (!result || !editor || editor.isDestroyed) return\n\n editor\n .chain()\n .focus()\n .insertContent({ type: 'customBlock', attrs: { id: result.id, config: result.config } })\n .run()\n }\n\n return { extensions, suggestions, insertBlock, context }\n}\n\n/** The toolbar button that opens the custom blocks or merge tags panel. */\nexport function ExtensionToolbarButton({\n name,\n component,\n editable,\n open,\n onToggle,\n}: {\n name: RichEditorPanel\n component: RichEditorSchema\n editable: boolean\n open: boolean\n onToggle: () => void\n}) {\n const t = useT()\n const count =\n name === 'customBlocks'\n ? (component.customBlocks ?? []).length\n : (component.mergeTags ?? []).length\n\n if (count === 0) return null\n\n const label =\n name === 'customBlocks'\n ? t('editor.custom_blocks', {}, 'Insert block')\n : t('editor.merge_tags', {}, 'Insert merge tag')\n\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={label}\n title={label}\n aria-pressed={open}\n aria-expanded={open}\n disabled={!editable}\n className={cn(open && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={onToggle}\n >\n {name === 'customBlocks' ? <BlocksIcon /> : <BracesIcon />}\n </Button>\n )\n}\n\n/**\n * The custom blocks or merge tags panel: beside the content on wide editors, above it on\n * narrow ones. Blocks are grouped, optionally searchable and shown as a grid.\n */\nexport function RichEditorSidePanel({\n panel,\n component,\n editable,\n sticky,\n onInsertBlock,\n onInsertMergeTag,\n onClose,\n}: {\n panel: RichEditorPanel\n component: RichEditorSchema\n editable: boolean\n /** `top` of a sticky panel, or null. */\n sticky: string | null\n onInsertBlock: (id: string) => void\n onInsertMergeTag: (id: string) => void\n onClose: () => void\n}) {\n const t = useT()\n const [search, setSearch] = useState('')\n const heading =\n panel === 'customBlocks'\n ? t('editor.custom_blocks_heading', {}, 'Custom blocks')\n : t('editor.merge_tags_heading', {}, 'Merge tags')\n const groups =\n panel === 'customBlocks' ? groupCustomBlocks(component.customBlocks ?? [], search) : []\n const grid = Boolean(component.customBlocksGrid)\n const itemClass =\n 'hover:bg-accent hover:text-accent-foreground focus-visible:ring-ring/50 flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50'\n\n return (\n <aside\n aria-label={heading}\n data-slot=\"rich-editor-panel\"\n data-panel={panel}\n className=\"bg-muted/20 shrink-0 border-b @2xl:w-64 @2xl:border-s @2xl:border-b-0\"\n >\n <div\n className={cn('flex flex-col gap-2 p-2', sticky !== null && '@2xl:sticky')}\n // The offset is a runtime value: no Tailwind class can hold it.\n style={sticky !== null ? { top: sticky } : undefined}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-muted-foreground text-xs font-medium\">{heading}</span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('editor.close_panel', {}, 'Close panel')}\n title={t('editor.close_panel', {}, 'Close panel')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={onClose}\n >\n <XIcon />\n </Button>\n </div>\n {panel === 'customBlocks' && component.searchableCustomBlocks && (\n <Input\n type=\"search\"\n value={search}\n aria-label={t('editor.search_blocks', {}, 'Search blocks')}\n placeholder={t('editor.search_blocks', {}, 'Search blocks')}\n className=\"h-8\"\n onChange={(event) => setSearch(event.target.value)}\n />\n )}\n {panel === 'customBlocks' ? (\n groups.length === 0 ? (\n <p className=\"text-muted-foreground px-2 py-1.5 text-sm\">\n {t('no_results', {}, 'No results.')}\n </p>\n ) : (\n groups.map(({ group, blocks }) => (\n <div key={group ?? ''} role=\"group\" aria-label={group ?? heading}>\n {group && (\n <div className=\"text-muted-foreground px-2 pt-1 pb-0.5 text-xs font-medium\">\n {group}\n </div>\n )}\n <div className={cn(grid ? 'grid grid-cols-2 gap-1' : 'flex flex-col gap-0.5')}>\n {blocks.map((block) => (\n <button\n key={block.id}\n type=\"button\"\n disabled={!editable}\n className={cn(\n itemClass,\n grid && 'flex-col justify-center border py-3 text-center',\n )}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onInsertBlock(block.id)}\n >\n {block.icon ? (\n <Icon name={block.icon} className=\"size-4 shrink-0\" />\n ) : (\n <BlocksIcon className=\"size-4 shrink-0\" />\n )}\n <span className=\"truncate\">{block.label}</span>\n </button>\n ))}\n </div>\n </div>\n ))\n )\n ) : (\n <div className=\"flex flex-col gap-0.5\">\n {(component.mergeTags ?? []).map((tag) => (\n <button\n key={tag.id}\n type=\"button\"\n disabled={!editable}\n className={itemClass}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => onInsertMergeTag(tag.id)}\n >\n <span className=\"truncate\">{tag.label}</span>\n <span className=\"text-muted-foreground ms-auto font-mono text-xs\" dir=\"ltr\">\n {mergeTagText({ id: tag.id })}\n </span>\n </button>\n ))}\n </div>\n )}\n </div>\n </aside>\n )\n}\n\n/** A `ToolbarButtonGroup`: a dropdown of buttons (icons, or labels with `textualButtons()`). */\nexport function ToolbarGroupButton({\n group,\n editor,\n editable,\n commands,\n icons,\n translate,\n onRun,\n}: {\n group: RichEditorToolbarGroupSchema\n editor: Editor\n editable: boolean\n commands: Record<string, ToolbarCommand>\n icons: (name: string) => ReactNode\n translate: (key: string, fallback: string) => string\n onRun: (name: string) => void\n}) {\n const buttons = group.buttons.filter((name) => commands[name])\n // After a pick, typing goes on in the editor (the command focused it); the menu would\n // otherwise return focus to its trigger. Closing without a pick (Escape) still does.\n const picked = useRef(false)\n\n if (buttons.length === 0) return null\n\n const trigger = groupTriggerButton(buttons, editor, commands)\n const active = buttons.some((name) => commands[name]?.active?.(editor))\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-label={group.label}\n title={group.label}\n disabled={!editable}\n className={cn('h-8 gap-0.5 px-1.5', active && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n >\n {group.icon ? <Icon name={group.icon} /> : trigger ? icons(trigger) : group.label}\n <ChevronDownIcon className=\"size-3 opacity-60\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"start\"\n aria-label={group.label}\n className={cn(!group.textual && 'flex min-w-0 flex-row gap-0.5')}\n onCloseAutoFocus={(event) => {\n if (!picked.current) return\n\n picked.current = false\n event.preventDefault()\n editor.commands.focus()\n }}\n >\n {buttons.map((name) => {\n const label = commandLabel(name, commands, translate)\n const pressed = commands[name]?.active?.(editor)\n\n return (\n <DropdownMenuItem\n key={name}\n aria-label={group.textual ? undefined : label}\n title={label}\n className={cn(pressed && 'bg-accent text-accent-foreground')}\n onSelect={() => {\n picked.current = true\n onRun(name)\n }}\n >\n {icons(name)}\n {group.textual && <span>{label}</span>}\n </DropdownMenuItem>\n )\n })}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/** Inline style of a sticky toolbar. */\nexport function stickyStyle(top: string | null): CSSProperties | undefined {\n return top === null ? undefined : { top }\n}\n\n/** The mention / merge tag listbox under the trigger. */\nexport function SuggestionPopup({\n suggestions,\n editor,\n id,\n}: {\n suggestions: EditorSuggestions\n editor: Editor | null\n id: string\n}) {\n const t = useT()\n const state: SuggestionState = useStore(suggestions.store)\n\n if (!state.open || !editor) return null\n\n return (\n <div\n id={id}\n role=\"listbox\"\n aria-label={t('editor.suggestions', {}, 'Suggestions')}\n data-slot=\"rich-editor-suggestions\"\n className=\"bg-popover text-popover-foreground fixed z-50 min-w-48 overflow-hidden rounded-md border p-1 shadow-md\"\n // Runtime coordinates cannot be Tailwind classes.\n // Inside a modal the dialog's transform is the containing block of this fixed list.\n style={\n state.coords\n ? {\n left: state.coords.left - fixedContainingBlockOffset(editor.view.dom).x,\n top: state.coords.bottom + 4 - fixedContainingBlockOffset(editor.view.dom).y,\n }\n : undefined\n }\n >\n {state.loading && state.items.length === 0 ? (\n <div className=\"text-muted-foreground px-2 py-1.5 text-sm\">\n {t('loading', {}, 'Loading…')}\n </div>\n ) : state.items.length === 0 ? (\n <div className=\"text-muted-foreground px-2 py-1.5 text-sm\">\n {t('no_results', {}, 'No results.')}\n </div>\n ) : (\n state.items.map((item, index) => (\n <div\n key={item.id}\n id={`${id}-${index}`}\n role=\"option\"\n aria-selected={index === state.index}\n className={cn(\n 'cursor-pointer rounded-sm px-2 py-1.5 text-sm',\n index === state.index && 'bg-accent text-accent-foreground',\n )}\n onMouseDown={(event) => event.preventDefault()}\n onMouseEnter={() => suggestions.highlight(index)}\n onClick={() => suggestions.select(index, editor)}\n >\n {state.kind === 'mention' ? `${state.trigger}${item.label}` : item.label}\n </div>\n ))\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1023,6 +1023,7 @@ function Dialogs({ controller, tt, choice, formatter }) {
|
|
|
1023
1023
|
onOpenChange: (open) => !open && close(),
|
|
1024
1024
|
children: /* @__PURE__ */ jsx(DialogContent, {
|
|
1025
1025
|
"data-slot": "file-manager-dialog",
|
|
1026
|
+
"aria-describedby": void 0,
|
|
1026
1027
|
children: /* @__PURE__ */ jsxs("form", {
|
|
1027
1028
|
className: "flex flex-col gap-4",
|
|
1028
1029
|
onSubmit: (event) => {
|
|
@@ -1286,6 +1287,7 @@ function Preview({ controller, tt, direction }) {
|
|
|
1286
1287
|
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
1287
1288
|
className: "flex max-h-[90vh] flex-col sm:max-w-4xl",
|
|
1288
1289
|
"data-slot": "file-manager-preview",
|
|
1290
|
+
"aria-describedby": void 0,
|
|
1289
1291
|
onKeyDown,
|
|
1290
1292
|
children: [
|
|
1291
1293
|
/* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx(DialogTitle, {
|