@alfakit/react 0.1.1 → 0.1.6
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 +16 -0
- package/dist/components/actions/action-button.d.ts.map +1 -1
- package/dist/components/actions/action-button.js +5 -4
- package/dist/components/actions/action-button.js.map +1 -1
- package/dist/components/actions/action-modals.d.ts.map +1 -1
- package/dist/components/actions/action-modals.js +6 -5
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/ai/copilot.d.ts +3 -1
- package/dist/components/ai/copilot.d.ts.map +1 -1
- package/dist/components/ai/copilot.js +4 -1
- package/dist/components/ai/copilot.js.map +1 -1
- package/dist/components/cms/live-preview.js +3 -2
- package/dist/components/cms/live-preview.js.map +1 -1
- package/dist/components/cms/page.js +1 -1
- package/dist/components/fields/builder.js +15 -6
- package/dist/components/fields/builder.js.map +1 -1
- package/dist/components/fields/color-picker.js +1 -0
- package/dist/components/fields/color-picker.js.map +1 -1
- package/dist/components/fields/file-upload.js +11 -10
- package/dist/components/fields/file-upload.js.map +1 -1
- package/dist/components/fields/image-editor-dialog.js +4 -2
- package/dist/components/fields/image-editor-dialog.js.map +1 -1
- package/dist/components/fields/item-actions.js +10 -10
- package/dist/components/fields/item-actions.js.map +1 -1
- package/dist/components/fields/key-value.d.ts.map +1 -1
- package/dist/components/fields/key-value.js +3 -3
- package/dist/components/fields/key-value.js.map +1 -1
- package/dist/components/fields/modal-table-select.js +6 -5
- package/dist/components/fields/modal-table-select.js.map +1 -1
- package/dist/components/fields/one-time-code-input.js +1 -1
- package/dist/components/fields/one-time-code-input.js.map +1 -1
- package/dist/components/fields/repeater-table.js +12 -1
- package/dist/components/fields/repeater-table.js.map +1 -1
- package/dist/components/fields/repeater.js +2 -2
- package/dist/components/fields/repeater.js.map +1 -1
- package/dist/components/fields/rich-editor.js +2 -1
- package/dist/components/fields/rich-editor.js.map +1 -1
- package/dist/components/fields/select.js +3 -3
- package/dist/components/fields/select.js.map +1 -1
- package/dist/components/file-manager/file-manager.js +1 -1
- package/dist/components/file-manager/file-manager.js.map +1 -1
- package/dist/components/form/form-block.d.ts.map +1 -1
- package/dist/components/form/form-block.js +14 -2
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/form/form-context.js +7 -3
- package/dist/components/form/form-context.js.map +1 -1
- package/dist/components/kanban/kanban.js +4 -3
- package/dist/components/kanban/kanban.js.map +1 -1
- package/dist/components/layout/fused-group.js +1 -0
- package/dist/components/layout/fused-group.js.map +1 -1
- package/dist/components/layout/section.js +2 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/layout/tabs.d.ts.map +1 -1
- package/dist/components/layout/tabs.js +54 -15
- package/dist/components/layout/tabs.js.map +1 -1
- package/dist/components/panel/app-sidebar.d.ts +2 -3
- package/dist/components/panel/app-sidebar.d.ts.map +1 -1
- package/dist/components/panel/app-sidebar.js +1 -24
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/brand.d.ts +10 -0
- package/dist/components/panel/brand.d.ts.map +1 -0
- package/dist/components/panel/brand.js +37 -0
- package/dist/components/panel/brand.js.map +1 -0
- package/dist/components/panel/global-search.js +1 -0
- package/dist/components/panel/global-search.js.map +1 -1
- package/dist/components/panel/index.d.ts +2 -1
- package/dist/components/panel/shortcut-keys.js +1 -0
- package/dist/components/panel/shortcut-keys.js.map +1 -1
- package/dist/components/panel/tenant-menu.js +2 -2
- package/dist/components/panel/tenant-menu.js.map +1 -1
- package/dist/components/panel/topbar.js +1 -1
- package/dist/components/realtime/live-records-pill.d.ts.map +1 -1
- package/dist/components/realtime/live-records-pill.js +6 -3
- package/dist/components/realtime/live-records-pill.js.map +1 -1
- package/dist/components/table/cells.d.ts +6 -0
- package/dist/components/table/cells.d.ts.map +1 -1
- package/dist/components/table/cells.js +15 -3
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/column-manager.js +8 -7
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/content-layout.js +2 -1
- package/dist/components/table/content-layout.js.map +1 -1
- package/dist/components/table/grouping.js +11 -1
- package/dist/components/table/grouping.js.map +1 -1
- package/dist/components/table/pagination.js +16 -13
- package/dist/components/table/pagination.js.map +1 -1
- package/dist/components/table/table-block.js +14 -4
- package/dist/components/table/table-block.js.map +1 -1
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +3 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +3 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +3 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/tooltip.js +3 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/components.css +347 -0
- package/dist/context.d.ts +5 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +5 -1
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +8 -5
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +16 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +24 -21
- package/dist/layouts/app-layout.d.ts.map +1 -1
- package/dist/layouts/app-layout.js +2 -29
- package/dist/layouts/app-layout.js.map +1 -1
- package/dist/layouts/auth-layout.js +2 -2
- package/dist/layouts/auth-layout.js.map +1 -1
- package/dist/layouts/fullscreen-layout.js +1 -1
- package/dist/layouts/fullscreen-layout.js.map +1 -1
- package/dist/layouts/lazy-layouts.js +15 -0
- package/dist/layouts/lazy-layouts.js.map +1 -0
- package/dist/layouts/max-widths.js +34 -0
- package/dist/layouts/max-widths.js.map +1 -0
- package/dist/layouts/panel-layouts.d.ts +10 -0
- package/dist/layouts/panel-layouts.d.ts.map +1 -0
- package/dist/layouts/panel-layouts.js +20 -0
- package/dist/layouts/panel-layouts.js.map +1 -0
- package/dist/layouts/top-nav-layout.js +2 -2
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +12 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +11 -6
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/alfakit-toaster.d.ts.map +1 -1
- package/dist/standalone/alfakit-toaster.js +8 -1
- package/dist/standalone/alfakit-toaster.js.map +1 -1
- package/dist/standalone.css +54 -0
- package/dist/starter-kit/dashboard-widgets.d.ts +33 -0
- package/dist/starter-kit/dashboard-widgets.d.ts.map +1 -0
- package/dist/starter-kit/dashboard-widgets.js +53 -0
- package/dist/starter-kit/dashboard-widgets.js.map +1 -0
- package/dist/starter-kit/impersonation-banner.js +34 -0
- package/dist/starter-kit/impersonation-banner.js.map +1 -0
- package/dist/starter-kit/index.d.ts +3 -2
- package/dist/starter-kit/index.js +3 -2
- package/dist/starter-kit/starter-kit.d.ts +6 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +18 -7
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/starter-kit/widgets-loaded.js +21 -5
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +35 -3
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/starter-kit/widgets.d.ts +13 -5
- package/dist/starter-kit/widgets.d.ts.map +1 -1
- package/dist/starter-kit/widgets.js +56 -18
- package/dist/starter-kit/widgets.js.map +1 -1
- package/dist/styles.css +15 -2
- package/dist/themes.css +8 -335
- package/package.json +2 -2
- package/src/components/actions/action-button.tsx +7 -4
- package/src/components/actions/action-modals.tsx +13 -1
- package/src/components/ai/copilot.tsx +14 -1
- package/src/components/cms/live-preview.tsx +5 -2
- package/src/components/fields/builder.tsx +14 -5
- package/src/components/fields/color-picker.tsx +1 -0
- package/src/components/fields/file-upload.tsx +21 -10
- package/src/components/fields/image-editor-dialog.tsx +7 -2
- package/src/components/fields/item-actions.tsx +32 -9
- package/src/components/fields/key-value.tsx +3 -2
- package/src/components/fields/modal-table-select.tsx +8 -3
- package/src/components/fields/one-time-code-input.tsx +1 -1
- package/src/components/fields/repeater-table.tsx +12 -1
- package/src/components/fields/repeater.tsx +2 -3
- package/src/components/fields/rich-editor.tsx +3 -0
- package/src/components/fields/select.tsx +7 -3
- package/src/components/file-manager/file-manager.tsx +1 -1
- package/src/components/form/form-block.tsx +26 -1
- package/src/components/form/form-context.tsx +3 -6
- package/src/components/kanban/kanban.tsx +16 -3
- package/src/components/layout/fused-group.tsx +5 -1
- package/src/components/layout/section.tsx +2 -2
- package/src/components/layout/tabs.tsx +72 -17
- package/src/components/panel/app-sidebar.tsx +2 -38
- package/src/components/panel/brand.tsx +46 -0
- package/src/components/panel/global-search.tsx +1 -1
- package/src/components/panel/shortcut-keys.tsx +4 -1
- package/src/components/panel/tenant-menu.tsx +2 -2
- package/src/components/realtime/live-records-pill.tsx +4 -3
- package/src/components/table/cells.tsx +27 -5
- package/src/components/table/column-manager.tsx +12 -10
- package/src/components/table/content-layout.tsx +5 -1
- package/src/components/table/grouping.tsx +15 -1
- package/src/components/table/pagination.tsx +16 -9
- package/src/components/table/table-block.tsx +25 -12
- package/src/components/ui/dialog.tsx +5 -1
- package/src/components/ui/popover.tsx +5 -1
- package/src/components/ui/sheet.tsx +5 -1
- package/src/components/ui/tooltip.tsx +5 -1
- package/src/context.tsx +10 -0
- package/src/create-alfakit.tsx +11 -2
- package/src/index.ts +2 -0
- package/src/layouts/app-layout.tsx +4 -28
- package/src/layouts/auth-layout.tsx +2 -2
- package/src/layouts/fullscreen-layout.tsx +1 -1
- package/src/layouts/lazy-layouts.ts +14 -0
- package/src/layouts/max-widths.ts +29 -0
- package/src/layouts/panel-layouts.ts +17 -0
- package/src/layouts/top-nav-layout.tsx +2 -2
- package/src/pages/page.tsx +21 -12
- package/src/standalone/alfakit-component.tsx +18 -7
- package/src/standalone/alfakit-toaster.tsx +21 -2
- package/src/starter-kit/dashboard-widgets.tsx +78 -0
- package/src/starter-kit/impersonation-banner.tsx +48 -0
- package/src/starter-kit/index.ts +2 -0
- package/src/starter-kit/starter-kit.tsx +31 -4
- package/src/starter-kit/widgets-loaded.tsx +23 -1
- package/src/starter-kit/widgets-triggers.tsx +37 -2
- package/src/starter-kit/widgets.tsx +95 -20
- package/src/styles.css +15 -2
|
@@ -6,7 +6,7 @@ import { Button } from "../ui/button.js";
|
|
|
6
6
|
import { extraProps } from "../../lib/attributes.js";
|
|
7
7
|
import { ActionButton } from "../actions/action-button.js";
|
|
8
8
|
import { ActionList } from "../actions/action-list.js";
|
|
9
|
-
import { ITEM_MOVE_ATTRIBUTE, ItemMoveTracker, addActionJustify, itemActionContext, resolveItemAction, withItemActionState } from "@alfakit/core";
|
|
9
|
+
import { ITEM_MOVE_ATTRIBUTE, ItemMoveTracker, addActionJustify, itemActionContext, itemActionIcon, resolveItemAction, withItemActionState } from "@alfakit/core";
|
|
10
10
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
11
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
import { ChevronDownIcon, ChevronUpIcon, CopyIcon, GripVerticalIcon, PlusIcon, Trash2Icon } from "lucide-react";
|
|
@@ -80,8 +80,8 @@ function useRunItemAction(context) {
|
|
|
80
80
|
else onClient();
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
-
/** Default looks of the built-in actions, for payloads without `itemActions
|
|
84
|
-
function useItemActionFallbacks(addLabel = "") {
|
|
83
|
+
/** Default looks of the built-in actions, for payloads without `itemActions` (icons follow their `alfakit::` aliases). */
|
|
84
|
+
function useItemActionFallbacks(addLabel = "", kind = "repeater") {
|
|
85
85
|
const t = useT();
|
|
86
86
|
return {
|
|
87
87
|
add: {
|
|
@@ -91,32 +91,32 @@ function useItemActionFallbacks(addLabel = "") {
|
|
|
91
91
|
},
|
|
92
92
|
delete: {
|
|
93
93
|
label: t("form.remove_item", {}, "Remove"),
|
|
94
|
-
icon: "trash-2",
|
|
94
|
+
icon: itemActionIcon(kind, "delete", "trash-2"),
|
|
95
95
|
color: "danger"
|
|
96
96
|
},
|
|
97
97
|
clone: {
|
|
98
98
|
label: t("form.clone", {}, "Clone"),
|
|
99
|
-
icon: "copy"
|
|
99
|
+
icon: itemActionIcon(kind, "clone", "copy")
|
|
100
100
|
},
|
|
101
101
|
reorder: {
|
|
102
102
|
label: t("form.reorder_item", {}, "Reorder"),
|
|
103
|
-
icon: "grip-vertical"
|
|
103
|
+
icon: itemActionIcon(kind, "reorder", "grip-vertical")
|
|
104
104
|
},
|
|
105
105
|
moveUp: {
|
|
106
106
|
label: t("form.move_up", {}, "Move up"),
|
|
107
|
-
icon: "chevron-up"
|
|
107
|
+
icon: itemActionIcon(kind, "move-up", "chevron-up")
|
|
108
108
|
},
|
|
109
109
|
moveDown: {
|
|
110
110
|
label: t("form.move_down", {}, "Move down"),
|
|
111
|
-
icon: "chevron-down"
|
|
111
|
+
icon: itemActionIcon(kind, "move-down", "chevron-down")
|
|
112
112
|
},
|
|
113
113
|
collapse: {
|
|
114
114
|
label: t("form.collapse_item", {}, "Collapse"),
|
|
115
|
-
icon: "chevron-up"
|
|
115
|
+
icon: itemActionIcon(kind, "collapse", "chevron-up")
|
|
116
116
|
},
|
|
117
117
|
expand: {
|
|
118
118
|
label: t("form.expand_item", {}, "Expand"),
|
|
119
|
-
icon: "chevron-down"
|
|
119
|
+
icon: itemActionIcon(kind, "expand", "chevron-down")
|
|
120
120
|
},
|
|
121
121
|
collapseAll: {
|
|
122
122
|
label: t("form.collapse_all", {}, "Collapse all"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"item-actions.js","names":[],"sources":["../../../src/components/fields/item-actions.tsx"],"sourcesContent":["import type {\n ActionContext,\n ActionSchema,\n ItemActionFallback,\n ItemActionName,\n ItemActionSchema,\n ItemActionsItemProps,\n ItemActionsProps,\n} from '@alfakit/core'\nimport {\n ITEM_MOVE_ATTRIBUTE,\n ItemMoveTracker,\n addActionJustify,\n itemActionContext,\n resolveItemAction,\n withItemActionState,\n} from '@alfakit/core'\nimport {\n ChevronDownIcon,\n ChevronUpIcon,\n CopyIcon,\n GripVerticalIcon,\n PlusIcon,\n Trash2Icon,\n type LucideIcon,\n} from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { colorClass, ghostButtonColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { ActionButton } from '../actions/action-button'\nimport { ActionList } from '../actions/action-list'\nimport { Button } from '../ui/button'\n\n/**\n * A built-in item action (Repeater, Builder, KeyValue). Uncustomised actions apply their\n * change in the browser (`onClient`); customised ones (`server`) run through the field\n * action endpoint with `args` (`item`, `items`, `block`).\n */\nexport function ItemActionButton({\n action,\n context,\n args,\n onClient,\n disabled,\n attributes,\n size = 'xs',\n className,\n}: {\n action: ItemActionSchema | null\n context: ActionContext\n args?: Record<string, unknown>\n onClient?: () => void\n disabled?: boolean\n attributes?: Record<string, string>\n size?: 'xs' | 'sm' | 'default'\n className?: string\n}) {\n if (!action) return null\n\n // Plain icon buttons that change the state in the browser repeat on every item: render\n // them light (no runner subscription, no tooltip portal, static icons).\n if (!action.server && action.variant === 'icon-button' && !action.badge) {\n const StaticIcon = action.icon ? STATIC_ICONS[action.icon] : undefined\n const label = action.label ?? action.tooltip ?? action.name\n\n return (\n <Button\n type=\"button\"\n variant=\"plain\"\n size={size === 'xs' ? 'icon-xs' : size === 'sm' ? 'icon-sm' : 'icon'}\n disabled={disabled || action.disabled}\n aria-label={label}\n title={action.tooltip ?? label}\n onClick={() => onClient?.()}\n {...extraProps(\n { ...action.extraAttributes, ...attributes },\n cn(colorClass(ghostButtonColorClasses, action.color, 'gray'), className),\n )}\n >\n {StaticIcon ? <StaticIcon /> : <Icon name={action.icon} />}\n </Button>\n )\n }\n\n return (\n <ServerItemActionButton\n action={action}\n context={context}\n args={args}\n onClient={onClient}\n disabled={disabled}\n attributes={attributes}\n size={size}\n className={className}\n />\n )\n}\n\n/** The common item action icons, bundled so headers don't wait for dynamic icons. */\nconst STATIC_ICONS: Record<string, LucideIcon> = {\n 'chevron-up': ChevronUpIcon,\n 'chevron-down': ChevronDownIcon,\n copy: CopyIcon,\n 'trash-2': Trash2Icon,\n 'grip-vertical': GripVerticalIcon,\n plus: PlusIcon,\n}\n\nfunction ServerItemActionButton({\n action,\n context,\n args,\n onClient,\n disabled,\n attributes,\n size = 'xs',\n className,\n}: {\n action: ItemActionSchema\n context: ActionContext\n args?: Record<string, unknown>\n onClient?: () => void\n disabled?: boolean\n attributes?: Record<string, string>\n size?: 'xs' | 'sm' | 'default'\n className?: string\n}) {\n const runner = useActionRunner()\n const scoped = useMemo(() => itemActionContext(context, args ?? {}), [context, args])\n\n return (\n <ActionButton\n action={withItemActionState(action, { disabled, attributes })}\n context={scoped}\n size={size}\n className={className}\n withKeyBindings={false}\n onTrigger={(schema, ctx) => (action.server ? runner.trigger(schema, ctx) : onClient?.())}\n />\n )\n}\n\n/** Run a built-in action: on the server when customised, else `onClient`. */\nexport function useRunItemAction(context: ActionContext) {\n const runner = useActionRunner()\n\n return (action: ItemActionSchema | null, args: Record<string, unknown>, onClient: () => void) => {\n if (!action) return\n if (action.server) void runner.trigger(action, itemActionContext(context, args))\n else onClient()\n }\n}\n\n/** Default looks of the built-in actions, for payloads without `itemActions`. */\nexport function useItemActionFallbacks(addLabel = ''): Record<ItemActionName, ItemActionFallback> {\n const t = useT()\n\n return {\n add: { label: addLabel, icon: 'plus', variant: 'outline' },\n delete: { label: t('form.remove_item', {}, 'Remove'), icon: 'trash-2', color: 'danger' },\n clone: { label: t('form.clone', {}, 'Clone'), icon: 'copy' },\n reorder: { label: t('form.reorder_item', {}, 'Reorder'), icon: 'grip-vertical' },\n moveUp: { label: t('form.move_up', {}, 'Move up'), icon: 'chevron-up' },\n moveDown: { label: t('form.move_down', {}, 'Move down'), icon: 'chevron-down' },\n collapse: { label: t('form.collapse_item', {}, 'Collapse'), icon: 'chevron-up' },\n expand: { label: t('form.expand_item', {}, 'Expand'), icon: 'chevron-down' },\n collapseAll: {\n label: t('form.collapse_all', {}, 'Collapse all'),\n icon: 'chevrons-down-up',\n variant: 'link',\n },\n expandAll: {\n label: t('form.expand_all', {}, 'Expand all'),\n icon: 'chevrons-up-down',\n variant: 'link',\n },\n }\n}\n\n/** Resolve every built-in action of a field (and optionally one of its items). */\nexport function useItemActions(\n field: ItemActionsProps,\n fallbacks: Record<ItemActionName, ItemActionFallback>,\n) {\n return (name: ItemActionName, item?: ItemActionsItemProps): ItemActionSchema | null =>\n resolveItemAction(name, field, item, fallbacks[name])\n}\n\nexport interface ItemHeaderActionsProps {\n context: ActionContext\n itemKey: string\n index: number\n count: number\n resolve: (name: ItemActionName) => ItemActionSchema | null\n extraActions?: ActionSchema[]\n buttons: boolean\n canClone: boolean\n canDelete: boolean\n collapsible: boolean\n collapsed: boolean\n onMove: (from: number, to: number) => void\n onClone: () => void\n onDelete: () => void\n onToggle: () => void\n children?: ReactNode\n}\n\n/** Marks the move buttons so focus can return to them after a move (`ItemMoveTracker`). */\nconst MOVE_UP_ATTRIBUTES = { [ITEM_MOVE_ATTRIBUTE]: 'up' }\nconst MOVE_DOWN_ATTRIBUTES = { [ITEM_MOVE_ATTRIBUTE]: 'down' }\n\n/**\n * The end of an item header: move up / down, extra item actions, clone, delete and the\n * collapse toggle, in Filament's order.\n */\nexport function ItemHeaderActions({\n context,\n itemKey,\n index,\n count,\n resolve,\n extraActions,\n buttons,\n canClone,\n canDelete,\n collapsible,\n collapsed,\n onMove,\n onClone,\n onDelete,\n onToggle,\n children,\n}: ItemHeaderActionsProps) {\n const args = useMemo(() => ({ item: itemKey }), [itemKey])\n const extraContext = useMemo(() => itemActionContext(context, args), [context, args])\n\n return (\n <div className=\"ms-auto flex items-center gap-0.5\" data-slot=\"item-actions\">\n {children}\n {buttons && (\n <>\n <ItemActionButton\n action={resolve('moveUp')}\n context={context}\n args={args}\n disabled={index === 0}\n attributes={MOVE_UP_ATTRIBUTES}\n onClient={() => onMove(index, index - 1)}\n />\n <ItemActionButton\n action={resolve('moveDown')}\n context={context}\n args={args}\n disabled={index === count - 1}\n attributes={MOVE_DOWN_ATTRIBUTES}\n onClient={() => onMove(index, index + 1)}\n />\n </>\n )}\n {extraActions && extraActions.length > 0 && (\n <ActionList actions={extraActions} context={extraContext} size=\"xs\" className=\"gap-0.5\" />\n )}\n {canClone && (\n <ItemActionButton\n action={resolve('clone')}\n context={context}\n args={args}\n onClient={onClone}\n />\n )}\n {canDelete && (\n <ItemActionButton\n action={resolve('delete')}\n context={context}\n args={args}\n className=\"text-destructive hover:text-destructive\"\n onClient={onDelete}\n />\n )}\n {collapsible && (\n <ItemActionButton\n action={resolve(collapsed ? 'expand' : 'collapse')}\n context={context}\n args={args}\n attributes={{ 'aria-expanded': collapsed ? 'false' : 'true' }}\n onClient={onToggle}\n />\n )}\n </div>\n )\n}\n\n/** \"Collapse all\" / \"Expand all\" above collapsible items. */\nexport function CollapseAllActions({\n context,\n resolve,\n onCollapseAll,\n onExpandAll,\n}: {\n context: ActionContext\n resolve: (name: ItemActionName) => ItemActionSchema | null\n onCollapseAll: () => void\n onExpandAll: () => void\n}) {\n const collapseAll = resolve('collapseAll')\n const expandAll = resolve('expandAll')\n\n if (!collapseAll && !expandAll) return null\n\n return (\n <div className=\"flex justify-end gap-3\" data-slot=\"item-collapse-actions\">\n <ItemActionButton action={collapseAll} context={context} onClient={onCollapseAll} />\n <ItemActionButton action={expandAll} context={context} onClient={onExpandAll} />\n </div>\n )\n}\n\nconst JUSTIFY = { start: 'justify-start', center: 'justify-center', end: 'justify-end' } as const\n\n/** The row holding the add button, aligned with `addActionAlignment()`. */\nexport function AddActionRow({ alignment, children }: { alignment?: string; children: ReactNode }) {\n return (\n <div className={cn('flex', JUSTIFY[addActionJustify(alignment)])} data-slot=\"item-add-action\">\n {children}\n </div>\n )\n}\n\n/**\n * Moves made with the item move buttons: one at a time, announced in a live region\n * (\"Moved to position 2 of 5\") and with focus kept on the moved item's move button.\n * Render `region` once in the field and put `containerRef` on an element holding the\n * items (they carry `data-index`).\n */\nexport function useItemMoves<T>(\n value: T,\n move: (from: number, to: number) => void,\n labelFor: (index: number) => string | null | undefined,\n count: number,\n) {\n const t = useT()\n const [tracker] = useState(() => new ItemMoveTracker())\n const [announcement, setAnnouncement] = useState('')\n const containerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (tracker.isMoving) tracker.finish(containerRef.current)\n }, [value, tracker])\n\n const moveWithButton = (from: number, to: number) => {\n if (to < 0 || to >= count || !tracker.begin(from, to)) return\n\n const label = labelFor(from)\n\n move(from, to)\n setAnnouncement(\n label\n ? t(\n 'form.labelled_item_moved',\n { item: label, position: to + 1, total: count },\n ':item moved to position :position of :total',\n )\n : t(\n 'form.item_moved',\n { position: to + 1, total: count },\n 'Item moved to position :position of :total',\n ),\n )\n }\n\n const region = (\n <span aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\" data-slot=\"item-move-status\">\n {announcement}\n </span>\n )\n\n return { moveWithButton, region, containerRef }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"item-actions.js","names":[],"sources":["../../../src/components/fields/item-actions.tsx"],"sourcesContent":["import type {\n ActionContext,\n ActionSchema,\n ItemActionFallback,\n ItemActionName,\n ItemActionSchema,\n ItemActionsItemProps,\n ItemActionsProps,\n} from '@alfakit/core'\nimport {\n ITEM_MOVE_ATTRIBUTE,\n ItemMoveTracker,\n addActionJustify,\n itemActionContext,\n itemActionIcon,\n resolveItemAction,\n withItemActionState,\n} from '@alfakit/core'\nimport {\n ChevronDownIcon,\n ChevronUpIcon,\n CopyIcon,\n GripVerticalIcon,\n PlusIcon,\n Trash2Icon,\n type LucideIcon,\n} from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { colorClass, ghostButtonColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { ActionButton } from '../actions/action-button'\nimport { ActionList } from '../actions/action-list'\nimport { Button } from '../ui/button'\n\n/**\n * A built-in item action (Repeater, Builder, KeyValue). Uncustomised actions apply their\n * change in the browser (`onClient`); customised ones (`server`) run through the field\n * action endpoint with `args` (`item`, `items`, `block`).\n */\nexport function ItemActionButton({\n action,\n context,\n args,\n onClient,\n disabled,\n attributes,\n size = 'xs',\n className,\n}: {\n action: ItemActionSchema | null\n context: ActionContext\n args?: Record<string, unknown>\n onClient?: () => void\n disabled?: boolean\n attributes?: Record<string, string>\n size?: 'xs' | 'sm' | 'default'\n className?: string\n}) {\n if (!action) return null\n\n // Plain icon buttons that change the state in the browser repeat on every item: render\n // them light (no runner subscription, no tooltip portal, static icons).\n if (!action.server && action.variant === 'icon-button' && !action.badge) {\n const StaticIcon = action.icon ? STATIC_ICONS[action.icon] : undefined\n const label = action.label ?? action.tooltip ?? action.name\n\n return (\n <Button\n type=\"button\"\n variant=\"plain\"\n size={size === 'xs' ? 'icon-xs' : size === 'sm' ? 'icon-sm' : 'icon'}\n disabled={disabled || action.disabled}\n aria-label={label}\n title={action.tooltip ?? label}\n onClick={() => onClient?.()}\n {...extraProps(\n { ...action.extraAttributes, ...attributes },\n cn(colorClass(ghostButtonColorClasses, action.color, 'gray'), className),\n )}\n >\n {StaticIcon ? <StaticIcon /> : <Icon name={action.icon} />}\n </Button>\n )\n }\n\n return (\n <ServerItemActionButton\n action={action}\n context={context}\n args={args}\n onClient={onClient}\n disabled={disabled}\n attributes={attributes}\n size={size}\n className={className}\n />\n )\n}\n\n/** The common item action icons, bundled so headers don't wait for dynamic icons. */\nconst STATIC_ICONS: Record<string, LucideIcon> = {\n 'chevron-up': ChevronUpIcon,\n 'chevron-down': ChevronDownIcon,\n copy: CopyIcon,\n 'trash-2': Trash2Icon,\n 'grip-vertical': GripVerticalIcon,\n plus: PlusIcon,\n}\n\nfunction ServerItemActionButton({\n action,\n context,\n args,\n onClient,\n disabled,\n attributes,\n size = 'xs',\n className,\n}: {\n action: ItemActionSchema\n context: ActionContext\n args?: Record<string, unknown>\n onClient?: () => void\n disabled?: boolean\n attributes?: Record<string, string>\n size?: 'xs' | 'sm' | 'default'\n className?: string\n}) {\n const runner = useActionRunner()\n const scoped = useMemo(() => itemActionContext(context, args ?? {}), [context, args])\n\n return (\n <ActionButton\n action={withItemActionState(action, { disabled, attributes })}\n context={scoped}\n size={size}\n className={className}\n withKeyBindings={false}\n onTrigger={(schema, ctx) => (action.server ? runner.trigger(schema, ctx) : onClient?.())}\n />\n )\n}\n\n/** Run a built-in action: on the server when customised, else `onClient`. */\nexport function useRunItemAction(context: ActionContext) {\n const runner = useActionRunner()\n\n return (action: ItemActionSchema | null, args: Record<string, unknown>, onClient: () => void) => {\n if (!action) return\n if (action.server) void runner.trigger(action, itemActionContext(context, args))\n else onClient()\n }\n}\n\n/** Default looks of the built-in actions, for payloads without `itemActions` (icons follow their `alfakit::` aliases). */\nexport function useItemActionFallbacks(\n addLabel = '',\n kind: 'repeater' | 'builder' = 'repeater',\n): Record<ItemActionName, ItemActionFallback> {\n const t = useT()\n\n return {\n add: { label: addLabel, icon: 'plus', variant: 'outline' },\n delete: {\n label: t('form.remove_item', {}, 'Remove'),\n icon: itemActionIcon(kind, 'delete', 'trash-2'),\n color: 'danger',\n },\n clone: { label: t('form.clone', {}, 'Clone'), icon: itemActionIcon(kind, 'clone', 'copy') },\n reorder: {\n label: t('form.reorder_item', {}, 'Reorder'),\n icon: itemActionIcon(kind, 'reorder', 'grip-vertical'),\n },\n moveUp: {\n label: t('form.move_up', {}, 'Move up'),\n icon: itemActionIcon(kind, 'move-up', 'chevron-up'),\n },\n moveDown: {\n label: t('form.move_down', {}, 'Move down'),\n icon: itemActionIcon(kind, 'move-down', 'chevron-down'),\n },\n collapse: {\n label: t('form.collapse_item', {}, 'Collapse'),\n icon: itemActionIcon(kind, 'collapse', 'chevron-up'),\n },\n expand: {\n label: t('form.expand_item', {}, 'Expand'),\n icon: itemActionIcon(kind, 'expand', 'chevron-down'),\n },\n collapseAll: {\n label: t('form.collapse_all', {}, 'Collapse all'),\n icon: 'chevrons-down-up',\n variant: 'link',\n },\n expandAll: {\n label: t('form.expand_all', {}, 'Expand all'),\n icon: 'chevrons-up-down',\n variant: 'link',\n },\n }\n}\n\n/** Resolve every built-in action of a field (and optionally one of its items). */\nexport function useItemActions(\n field: ItemActionsProps,\n fallbacks: Record<ItemActionName, ItemActionFallback>,\n) {\n return (name: ItemActionName, item?: ItemActionsItemProps): ItemActionSchema | null =>\n resolveItemAction(name, field, item, fallbacks[name])\n}\n\nexport interface ItemHeaderActionsProps {\n context: ActionContext\n itemKey: string\n index: number\n count: number\n resolve: (name: ItemActionName) => ItemActionSchema | null\n extraActions?: ActionSchema[]\n buttons: boolean\n canClone: boolean\n canDelete: boolean\n collapsible: boolean\n collapsed: boolean\n onMove: (from: number, to: number) => void\n onClone: () => void\n onDelete: () => void\n onToggle: () => void\n children?: ReactNode\n}\n\n/** Marks the move buttons so focus can return to them after a move (`ItemMoveTracker`). */\nconst MOVE_UP_ATTRIBUTES = { [ITEM_MOVE_ATTRIBUTE]: 'up' }\nconst MOVE_DOWN_ATTRIBUTES = { [ITEM_MOVE_ATTRIBUTE]: 'down' }\n\n/**\n * The end of an item header: move up / down, extra item actions, clone, delete and the\n * collapse toggle, in Filament's order.\n */\nexport function ItemHeaderActions({\n context,\n itemKey,\n index,\n count,\n resolve,\n extraActions,\n buttons,\n canClone,\n canDelete,\n collapsible,\n collapsed,\n onMove,\n onClone,\n onDelete,\n onToggle,\n children,\n}: ItemHeaderActionsProps) {\n const args = useMemo(() => ({ item: itemKey }), [itemKey])\n const extraContext = useMemo(() => itemActionContext(context, args), [context, args])\n\n return (\n <div className=\"ms-auto flex items-center gap-0.5\" data-slot=\"item-actions\">\n {children}\n {buttons && (\n <>\n <ItemActionButton\n action={resolve('moveUp')}\n context={context}\n args={args}\n disabled={index === 0}\n attributes={MOVE_UP_ATTRIBUTES}\n onClient={() => onMove(index, index - 1)}\n />\n <ItemActionButton\n action={resolve('moveDown')}\n context={context}\n args={args}\n disabled={index === count - 1}\n attributes={MOVE_DOWN_ATTRIBUTES}\n onClient={() => onMove(index, index + 1)}\n />\n </>\n )}\n {extraActions && extraActions.length > 0 && (\n <ActionList actions={extraActions} context={extraContext} size=\"xs\" className=\"gap-0.5\" />\n )}\n {canClone && (\n <ItemActionButton\n action={resolve('clone')}\n context={context}\n args={args}\n onClient={onClone}\n />\n )}\n {canDelete && (\n <ItemActionButton\n action={resolve('delete')}\n context={context}\n args={args}\n className=\"text-destructive hover:text-destructive\"\n onClient={onDelete}\n />\n )}\n {collapsible && (\n <ItemActionButton\n action={resolve(collapsed ? 'expand' : 'collapse')}\n context={context}\n args={args}\n attributes={{ 'aria-expanded': collapsed ? 'false' : 'true' }}\n onClient={onToggle}\n />\n )}\n </div>\n )\n}\n\n/** \"Collapse all\" / \"Expand all\" above collapsible items. */\nexport function CollapseAllActions({\n context,\n resolve,\n onCollapseAll,\n onExpandAll,\n}: {\n context: ActionContext\n resolve: (name: ItemActionName) => ItemActionSchema | null\n onCollapseAll: () => void\n onExpandAll: () => void\n}) {\n const collapseAll = resolve('collapseAll')\n const expandAll = resolve('expandAll')\n\n if (!collapseAll && !expandAll) return null\n\n return (\n <div className=\"flex justify-end gap-3\" data-slot=\"item-collapse-actions\">\n <ItemActionButton action={collapseAll} context={context} onClient={onCollapseAll} />\n <ItemActionButton action={expandAll} context={context} onClient={onExpandAll} />\n </div>\n )\n}\n\nconst JUSTIFY = { start: 'justify-start', center: 'justify-center', end: 'justify-end' } as const\n\n/** The row holding the add button, aligned with `addActionAlignment()`. */\nexport function AddActionRow({ alignment, children }: { alignment?: string; children: ReactNode }) {\n return (\n <div className={cn('flex', JUSTIFY[addActionJustify(alignment)])} data-slot=\"item-add-action\">\n {children}\n </div>\n )\n}\n\n/**\n * Moves made with the item move buttons: one at a time, announced in a live region\n * (\"Moved to position 2 of 5\") and with focus kept on the moved item's move button.\n * Render `region` once in the field and put `containerRef` on an element holding the\n * items (they carry `data-index`).\n */\nexport function useItemMoves<T>(\n value: T,\n move: (from: number, to: number) => void,\n labelFor: (index: number) => string | null | undefined,\n count: number,\n) {\n const t = useT()\n const [tracker] = useState(() => new ItemMoveTracker())\n const [announcement, setAnnouncement] = useState('')\n const containerRef = useRef<HTMLDivElement>(null)\n\n useEffect(() => {\n if (tracker.isMoving) tracker.finish(containerRef.current)\n }, [value, tracker])\n\n const moveWithButton = (from: number, to: number) => {\n if (to < 0 || to >= count || !tracker.begin(from, to)) return\n\n const label = labelFor(from)\n\n move(from, to)\n setAnnouncement(\n label\n ? t(\n 'form.labelled_item_moved',\n { item: label, position: to + 1, total: count },\n ':item moved to position :position of :total',\n )\n : t(\n 'form.item_moved',\n { position: to + 1, total: count },\n 'Item moved to position :position of :total',\n ),\n )\n }\n\n const region = (\n <span aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\" data-slot=\"item-move-status\">\n {announcement}\n </span>\n )\n\n return { moveWithButton, region, containerRef }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"key-value.d.ts","names":[],"sources":["../../../src/components/fields/key-value.tsx"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"key-value.d.ts","names":[],"sources":["../../../src/components/fields/key-value.tsx"],"mappings":";;;;;;wBAyBgB,gBAAgB;EAAe,WAAW;oBAAgB,IAAA"}
|
|
@@ -6,7 +6,7 @@ import { useFieldActionContext } from "../schema/field-actions.js";
|
|
|
6
6
|
import { FieldWrapper } from "../schema/field-wrapper.js";
|
|
7
7
|
import { Input } from "../ui/input.js";
|
|
8
8
|
import { ItemActionButton, useItemMoves, useRunItemAction } from "./item-actions.js";
|
|
9
|
-
import { fieldIds, moveRepeaterItem, reorderedItemKeys, resolveItemAction } from "@alfakit/core";
|
|
9
|
+
import { fieldIds, itemActionIcon, moveRepeaterItem, reorderedItemKeys, resolveItemAction } from "@alfakit/core";
|
|
10
10
|
import { useMemo, useState } from "react";
|
|
11
11
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
import { ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
@@ -37,11 +37,11 @@ function KeyValueField({ component }) {
|
|
|
37
37
|
});
|
|
38
38
|
const deleteAction = resolveItemAction("delete", component, void 0, {
|
|
39
39
|
label: t("form.remove_row", {}, "Remove row"),
|
|
40
|
-
icon: "trash-2"
|
|
40
|
+
icon: itemActionIcon("key-value", "delete", "trash-2")
|
|
41
41
|
});
|
|
42
42
|
const reorderAction = resolveItemAction("reorder", component, void 0, {
|
|
43
43
|
label: t("form.reorder_item", {}, "Reorder"),
|
|
44
|
-
icon: "grip-vertical"
|
|
44
|
+
icon: itemActionIcon("key-value", "reorder", "grip-vertical")
|
|
45
45
|
});
|
|
46
46
|
const update = (next) => setValue(next);
|
|
47
47
|
const deletable = component.deletable && !readOnly && deleteAction !== null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"key-value.js","names":[],"sources":["../../../src/components/fields/key-value.tsx"],"sourcesContent":["import {\n fieldIds,\n moveRepeaterItem,\n reorderedItemKeys,\n resolveItemAction,\n type KeyValueSchema,\n} from '@alfakit/core'\nimport { ChevronDownIcon, ChevronUpIcon } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Input } from '../ui/input'\nimport { ItemActionButton, useItemMoves, useRunItemAction } from './item-actions'\n\ntype Row = { key: string; value: string }\n\n/**\n * The server stores an object; while editing we keep an ordered list of rows so keys\n * can be typed freely (including temporarily duplicate or empty keys).\n */\nexport function KeyValueField({ component }: { component: KeyValueSchema }) {\n const t = useT()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<\n Record<string, unknown> | Row[] | null\n >(component)\n\n const rows = useMemo<Row[]>(() => {\n if (Array.isArray(value)) {\n return value.map((row) => ({\n key: String(row.key ?? ''),\n value: row.value === null || row.value === undefined ? '' : String(row.value),\n }))\n }\n\n return Object.entries(value ?? {}).map(([key, rowValue]) => ({\n key,\n value: rowValue === null || rowValue === undefined ? '' : String(rowValue),\n }))\n }, [value])\n\n const context = useFieldActionContext(component)\n const runItemAction = useRunItemAction(context)\n const addAction = resolveItemAction('add', component, undefined, {\n label: component.addActionLabel,\n icon: 'plus',\n variant: 'ghost',\n })\n const deleteAction = resolveItemAction('delete', component, undefined, {\n label: t('form.remove_row', {}, 'Remove row'),\n icon: 'trash-2',\n })\n const reorderAction = resolveItemAction('reorder', component, undefined, {\n label: t('form.reorder_item', {}, 'Reorder'),\n icon: 'grip-vertical',\n })\n\n const update = (next: Row[]) => setValue(next)\n const deletable = component.deletable && !readOnly && deleteAction !== null\n const reorderable = component.reorderable && !readOnly && !disabled && reorderAction !== null\n const [dragIndex, setDragIndex] = useState<number | null>(null)\n // Moving runs the reorder action: in the browser, or on the server when customised.\n const move = (from: number, to: number) =>\n runItemAction(reorderAction, { items: reorderedItemKeys(rows.length, from, to) }, () =>\n update(moveRepeaterItem(rows, from, to) as Row[]),\n )\n // Move buttons: announced, one at a time, focus kept on the moved row.\n const {\n moveWithButton,\n region: moveRegion,\n containerRef: moveContainerRef,\n } = useItemMoves(value, move, (index) => rows[index]?.key || null, rows.length)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div ref={moveContainerRef} className=\"overflow-hidden rounded-md border\">\n {moveRegion}\n <table\n className=\"w-full text-sm\"\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n >\n <thead className=\"bg-muted/50 text-muted-foreground text-xs\">\n <tr>\n {reorderable && <th className=\"w-16\" />}\n <th className=\"px-3 py-2 text-start font-medium\">{component.keyLabel}</th>\n <th className=\"px-3 py-2 text-start font-medium\">{component.valueLabel}</th>\n {deletable && <th className=\"w-10\" />}\n </tr>\n </thead>\n <tbody>\n {rows.map((row, index) => (\n <tr\n key={index}\n data-index={index}\n draggable={reorderable}\n onDragStart={() => setDragIndex(index)}\n onDragOver={(event) => reorderable && event.preventDefault()}\n onDrop={() => {\n if (dragIndex !== null && dragIndex !== index) move(dragIndex, index)\n setDragIndex(null)\n }}\n onDragEnd={() => setDragIndex(null)}\n className={cn('border-t', dragIndex === index && 'opacity-50')}\n >\n {reorderable && (\n <td className=\"p-1.5\">\n <div className=\"flex items-center\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('form.move_up', {}, 'Move up')}\n disabled={index === 0}\n data-item-move=\"up\"\n onClick={() => moveWithButton(index, index - 1)}\n >\n <ChevronUpIcon />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('form.move_down', {}, 'Move down')}\n disabled={index === rows.length - 1}\n data-item-move=\"down\"\n onClick={() => moveWithButton(index, index + 1)}\n >\n <ChevronDownIcon />\n </Button>\n </div>\n </td>\n )}\n <td className=\"p-1.5\">\n <Input\n dir=\"auto\"\n value={row.key}\n readOnly={readOnly || !component.editableKeys}\n disabled={disabled}\n placeholder={component.keyPlaceholder}\n aria-label={component.keyLabel}\n aria-invalid={aria['aria-invalid']}\n className=\"h-8\"\n onChange={(event) =>\n update(\n rows.map((item, i) =>\n i === index ? { ...item, key: event.target.value } : item,\n ),\n )\n }\n onBlur={blur}\n />\n </td>\n <td className=\"p-1.5\">\n <Input\n dir=\"auto\"\n value={row.value}\n readOnly={readOnly || !component.editableValues}\n disabled={disabled}\n placeholder={component.valuePlaceholder}\n aria-label={component.valueLabel}\n aria-invalid={aria['aria-invalid']}\n className=\"h-8\"\n onChange={(event) =>\n update(\n rows.map((item, i) =>\n i === index ? { ...item, value: event.target.value } : item,\n ),\n )\n }\n onBlur={blur}\n />\n </td>\n {deletable && (\n <td className=\"p-1.5\">\n <ItemActionButton\n action={deleteAction}\n context={context}\n args={{ item: String(index) }}\n size=\"sm\"\n disabled={disabled}\n className=\"text-muted-foreground\"\n onClient={() => update(rows.filter((_, i) => i !== index))}\n />\n </td>\n )}\n </tr>\n ))}\n {rows.length === 0 && (\n <tr className=\"border-t\">\n <td\n colSpan={reorderable ? 4 : 3}\n className=\"text-muted-foreground px-3 py-4 text-center text-xs\"\n >\n —\n </td>\n </tr>\n )}\n </tbody>\n </table>\n {component.addable && !readOnly && addAction && (\n <div className=\"border-t p-1.5\">\n <ItemActionButton\n action={addAction}\n context={context}\n size=\"sm\"\n disabled={disabled}\n onClient={() => update([...rows, { key: '', value: '' }])}\n />\n </div>\n )}\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"key-value.js","names":[],"sources":["../../../src/components/fields/key-value.tsx"],"sourcesContent":["import {\n fieldIds,\n itemActionIcon,\n moveRepeaterItem,\n reorderedItemKeys,\n resolveItemAction,\n type KeyValueSchema,\n} from '@alfakit/core'\nimport { ChevronDownIcon, ChevronUpIcon } from 'lucide-react'\nimport { useMemo, useState } from 'react'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Input } from '../ui/input'\nimport { ItemActionButton, useItemMoves, useRunItemAction } from './item-actions'\n\ntype Row = { key: string; value: string }\n\n/**\n * The server stores an object; while editing we keep an ordered list of rows so keys\n * can be typed freely (including temporarily duplicate or empty keys).\n */\nexport function KeyValueField({ component }: { component: KeyValueSchema }) {\n const t = useT()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<\n Record<string, unknown> | Row[] | null\n >(component)\n\n const rows = useMemo<Row[]>(() => {\n if (Array.isArray(value)) {\n return value.map((row) => ({\n key: String(row.key ?? ''),\n value: row.value === null || row.value === undefined ? '' : String(row.value),\n }))\n }\n\n return Object.entries(value ?? {}).map(([key, rowValue]) => ({\n key,\n value: rowValue === null || rowValue === undefined ? '' : String(rowValue),\n }))\n }, [value])\n\n const context = useFieldActionContext(component)\n const runItemAction = useRunItemAction(context)\n const addAction = resolveItemAction('add', component, undefined, {\n label: component.addActionLabel,\n icon: 'plus',\n variant: 'ghost',\n })\n const deleteAction = resolveItemAction('delete', component, undefined, {\n label: t('form.remove_row', {}, 'Remove row'),\n icon: itemActionIcon('key-value', 'delete', 'trash-2'),\n })\n const reorderAction = resolveItemAction('reorder', component, undefined, {\n label: t('form.reorder_item', {}, 'Reorder'),\n icon: itemActionIcon('key-value', 'reorder', 'grip-vertical'),\n })\n\n const update = (next: Row[]) => setValue(next)\n const deletable = component.deletable && !readOnly && deleteAction !== null\n const reorderable = component.reorderable && !readOnly && !disabled && reorderAction !== null\n const [dragIndex, setDragIndex] = useState<number | null>(null)\n // Moving runs the reorder action: in the browser, or on the server when customised.\n const move = (from: number, to: number) =>\n runItemAction(reorderAction, { items: reorderedItemKeys(rows.length, from, to) }, () =>\n update(moveRepeaterItem(rows, from, to) as Row[]),\n )\n // Move buttons: announced, one at a time, focus kept on the moved row.\n const {\n moveWithButton,\n region: moveRegion,\n containerRef: moveContainerRef,\n } = useItemMoves(value, move, (index) => rows[index]?.key || null, rows.length)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div ref={moveContainerRef} className=\"overflow-hidden rounded-md border\">\n {moveRegion}\n <table\n className=\"w-full text-sm\"\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n >\n <thead className=\"bg-muted/50 text-muted-foreground text-xs\">\n <tr>\n {reorderable && <th className=\"w-16\" />}\n <th className=\"px-3 py-2 text-start font-medium\">{component.keyLabel}</th>\n <th className=\"px-3 py-2 text-start font-medium\">{component.valueLabel}</th>\n {deletable && <th className=\"w-10\" />}\n </tr>\n </thead>\n <tbody>\n {rows.map((row, index) => (\n <tr\n key={index}\n data-index={index}\n draggable={reorderable}\n onDragStart={() => setDragIndex(index)}\n onDragOver={(event) => reorderable && event.preventDefault()}\n onDrop={() => {\n if (dragIndex !== null && dragIndex !== index) move(dragIndex, index)\n setDragIndex(null)\n }}\n onDragEnd={() => setDragIndex(null)}\n className={cn('border-t', dragIndex === index && 'opacity-50')}\n >\n {reorderable && (\n <td className=\"p-1.5\">\n <div className=\"flex items-center\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('form.move_up', {}, 'Move up')}\n disabled={index === 0}\n data-item-move=\"up\"\n onClick={() => moveWithButton(index, index - 1)}\n >\n <ChevronUpIcon />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('form.move_down', {}, 'Move down')}\n disabled={index === rows.length - 1}\n data-item-move=\"down\"\n onClick={() => moveWithButton(index, index + 1)}\n >\n <ChevronDownIcon />\n </Button>\n </div>\n </td>\n )}\n <td className=\"p-1.5\">\n <Input\n dir=\"auto\"\n value={row.key}\n readOnly={readOnly || !component.editableKeys}\n disabled={disabled}\n placeholder={component.keyPlaceholder}\n aria-label={component.keyLabel}\n aria-invalid={aria['aria-invalid']}\n className=\"h-8\"\n onChange={(event) =>\n update(\n rows.map((item, i) =>\n i === index ? { ...item, key: event.target.value } : item,\n ),\n )\n }\n onBlur={blur}\n />\n </td>\n <td className=\"p-1.5\">\n <Input\n dir=\"auto\"\n value={row.value}\n readOnly={readOnly || !component.editableValues}\n disabled={disabled}\n placeholder={component.valuePlaceholder}\n aria-label={component.valueLabel}\n aria-invalid={aria['aria-invalid']}\n className=\"h-8\"\n onChange={(event) =>\n update(\n rows.map((item, i) =>\n i === index ? { ...item, value: event.target.value } : item,\n ),\n )\n }\n onBlur={blur}\n />\n </td>\n {deletable && (\n <td className=\"p-1.5\">\n <ItemActionButton\n action={deleteAction}\n context={context}\n args={{ item: String(index) }}\n size=\"sm\"\n disabled={disabled}\n className=\"text-muted-foreground\"\n onClient={() => update(rows.filter((_, i) => i !== index))}\n />\n </td>\n )}\n </tr>\n ))}\n {rows.length === 0 && (\n <tr className=\"border-t\">\n <td\n colSpan={reorderable ? 4 : 3}\n className=\"text-muted-foreground px-3 py-4 text-center text-xs\"\n >\n —\n </td>\n </tr>\n )}\n </tbody>\n </table>\n {component.addable && !readOnly && addAction && (\n <div className=\"border-t p-1.5\">\n <ItemActionButton\n action={addAction}\n context={context}\n size=\"sm\"\n disabled={disabled}\n onClient={() => update([...rows, { key: '', value: '' }])}\n />\n </div>\n )}\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useChoice, useT } from "../../context.js";
|
|
1
|
+
import { useChoice, useFormatter, useT } from "../../context.js";
|
|
2
2
|
import { cn } from "../../lib/utils.js";
|
|
3
3
|
import { Icon } from "../../lib/icons.js";
|
|
4
4
|
import { Badge } from "../ui/badge.js";
|
|
@@ -85,6 +85,7 @@ function ModalTableSelectField({ component }) {
|
|
|
85
85
|
}
|
|
86
86
|
function ModalTablePicker({ component, initial, onClose, onConfirm }) {
|
|
87
87
|
const t = useT();
|
|
88
|
+
const format = useFormatter();
|
|
88
89
|
const choice = useChoice();
|
|
89
90
|
const form = useFormStore();
|
|
90
91
|
const multiple = Boolean(component.multiple);
|
|
@@ -283,10 +284,10 @@ function ModalTablePicker({ component, initial, onClose, onConfirm }) {
|
|
|
283
284
|
/* @__PURE__ */ jsx("span", {
|
|
284
285
|
className: "text-muted-foreground text-sm",
|
|
285
286
|
children: paging.total !== void 0 ? t("table.showing", {
|
|
286
|
-
first: paging.from,
|
|
287
|
-
last: paging.to,
|
|
288
|
-
total: paging.total
|
|
289
|
-
},
|
|
287
|
+
first: format.number(paging.from),
|
|
288
|
+
last: format.number(paging.to),
|
|
289
|
+
total: format.number(paging.total)
|
|
290
|
+
}, "Showing :first–:last of :total") : `${paging.from}–${paging.to}`
|
|
290
291
|
}),
|
|
291
292
|
/* @__PURE__ */ jsx(Button, {
|
|
292
293
|
type: "button",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal-table-select.js","names":[],"sources":["../../../src/components/fields/modal-table-select.tsx"],"sourcesContent":["import type { ModalTableSelectResponse, ModalTableSelectSchema, TableQuery } from '@alfakit/core'\nimport {\n FILTERS_MODAL_WIDTH_CLASSES,\n FormStore,\n debounce,\n fieldIds,\n filterFormComponents,\n modalTablePaging,\n modalTableSelected,\n pickerFilterFormOptions,\n toggleModalTableKey,\n} from '@alfakit/core'\nimport {\n ArrowDownIcon,\n ArrowUpDownIcon,\n ArrowUpIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n SearchIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { useChoice, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider, useField, useFormStore } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { TableCellContent } from '../table/cells'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Skeleton } from '../ui/skeleton'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'\n\n/**\n * Select records from a table in a modal. The table (columns, search, filters, sorting,\n * pagination) comes from the server's picker endpoint; the field keeps only keys, with\n * labels from the server.\n */\nexport function ModalTableSelectField({ component }: { component: ModalTableSelectSchema }) {\n const t = useT()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<unknown>(component)\n const multiple = Boolean(component.multiple)\n const selected = modalTableSelected(value, multiple)\n const locked = disabled || readOnly\n const [open, setOpen] = useState(false)\n const [picked, setPicked] = useState<Record<string, string>>({})\n const labels = { ...picked, ...component.selectedLabels }\n\n const store = (keys: string[]) => setValue(multiple ? keys : (keys[0] ?? null))\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n id={id}\n role=\"group\"\n data-slot=\"modal-table-select\"\n className=\"flex flex-wrap items-center gap-2\"\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n >\n {selected.length === 0 ? (\n <span className=\"text-muted-foreground text-sm\">\n {component.placeholder ?? t('table_select.none', {}, 'Nothing selected')}\n </span>\n ) : (\n selected.map((key) => (\n <Badge key={key} variant=\"secondary\" className=\"gap-1 pe-1\">\n <span>{labels[key] ?? key}</span>\n {!locked && (\n <button\n type=\"button\"\n className=\"hover:bg-foreground/10 rounded-sm\"\n aria-label={t(\n 'table_select.remove',\n { label: labels[key] ?? key },\n 'Remove :label',\n )}\n onClick={() => store(selected.filter((candidate) => candidate !== key))}\n >\n <XIcon className=\"size-3\" />\n </button>\n )}\n </Badge>\n ))\n )}\n {!locked && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-haspopup=\"dialog\"\n onClick={() => setOpen(true)}\n >\n {component.selectActionIcon && <Icon name={component.selectActionIcon} />}\n {selected.length > 0 && !multiple\n ? t('table_select.change', {}, 'Change')\n : component.selectActionLabel}\n </Button>\n )}\n </div>\n {open && (\n <ModalTablePicker\n component={component}\n initial={selected}\n onClose={() => setOpen(false)}\n onConfirm={(keys, newLabels) => {\n setPicked((state) => ({ ...state, ...newLabels }))\n store(keys)\n setOpen(false)\n }}\n />\n )}\n </FieldWrapper>\n )\n}\n\ninterface PickerProps {\n component: ModalTableSelectSchema\n initial: string[]\n onClose: () => void\n onConfirm: (keys: string[], labels: Record<string, string>) => void\n}\n\nfunction ModalTablePicker({ component, initial, onClose, onConfirm }: PickerProps) {\n const t = useT()\n const choice = useChoice()\n const form = useFormStore()\n const multiple = Boolean(component.multiple)\n const [query, setQuery] = useState<TableQuery>({})\n const [response, setResponse] = useState<ModalTableSelectResponse | null>(null)\n const [failed, setFailed] = useState(false)\n // The query the shown records answer; anything else means a request is in flight.\n const [loadedQuery, setLoadedQuery] = useState<TableQuery | null>(null)\n const loading = loadedQuery !== query\n const [draft, setDraft] = useState<string[]>(initial)\n const [labels, setLabels] = useState<Record<string, string>>({})\n const [search, setSearch] = useState('')\n\n useEffect(() => {\n const controller = new AbortController()\n\n form\n .modalTable(component.statePath, component.tableStateIdentifier, query, controller.signal)\n .then((next) => {\n if (next) {\n setResponse(next)\n setLabels((state) => ({ ...state, ...next.labels }))\n }\n setFailed(false)\n setLoadedQuery(query)\n })\n .catch((error: unknown) => {\n if ((error as { name?: string }).name === 'AbortError') return\n setFailed(true)\n setLoadedQuery(query)\n })\n\n return () => controller.abort()\n }, [form, component.statePath, component.tableStateIdentifier, query])\n\n const table = response?.table\n const pushSearch = useMemo(\n () =>\n debounce(\n (next: string) => setQuery((state) => ({ ...state, search: next || null, page: null })),\n table?.searchDebounce ?? 400,\n ),\n [table?.searchDebounce],\n )\n useEffect(() => () => pushSearch.cancel(), [pushSearch])\n\n const columns = table\n ? table.columns.filter((column) => table.visibleColumns.includes(column.name))\n : []\n const records = table?.records ?? []\n const paging = modalTablePaging(table?.pagination)\n const atMax = multiple && component.maxItems != null && draft.length >= component.maxItems\n\n const toggle = (key: string) =>\n setDraft((state) => toggleModalTableKey(state, key, multiple, component.maxItems))\n\n const sort = (column: string) => {\n const direction =\n table?.sort?.column === column && table.sort.direction === 'asc' ? 'desc' : 'asc'\n setQuery((state) => ({ ...state, sort: column, direction, page: null }))\n }\n\n return (\n <Dialog open onOpenChange={(next) => !next && onClose()}>\n <DialogContent\n className={cn(\n 'flex max-h-[90vh] flex-col gap-4',\n FILTERS_MODAL_WIDTH_CLASSES[component.modalWidth ?? '3xl'] ?? 'sm:max-w-3xl',\n )}\n data-slot=\"modal-table-select-dialog\"\n >\n <DialogHeader>\n <DialogTitle>{component.modalHeading}</DialogTitle>\n {component.modalDescription && (\n // Escaped on the server (`Html::from()`), like action modal descriptions.\n <p\n className=\"text-muted-foreground text-sm\"\n data-slot=\"modal-table-select-description\"\n dangerouslySetInnerHTML={{ __html: component.modalDescription }}\n />\n )}\n <DialogDescription aria-live=\"polite\">\n {choice(\n 'table.selected',\n draft.length,\n { count: draft.length },\n `${draft.length} selected`,\n )}\n </DialogDescription>\n </DialogHeader>\n {table && (table.searchable || (table.filters?.length ?? 0) > 0) && (\n <div className=\"flex flex-col gap-3\">\n {table.searchable && (\n <div className=\"relative\">\n <SearchIcon className=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\" />\n <Input\n type=\"search\"\n value={search}\n aria-label={table.searchPlaceholder}\n placeholder={table.searchPlaceholder}\n className=\"ps-8\"\n onChange={(event) => {\n setSearch(event.target.value)\n pushSearch(event.target.value)\n }}\n />\n </div>\n )}\n {(table.filters?.length ?? 0) > 0 && (\n <PickerFilters\n table={table}\n owner={form}\n path={component.statePath}\n onChange={(filters) => setQuery((state) => ({ ...state, filters, page: null }))}\n />\n )}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-auto rounded-md border\" aria-busy={loading}>\n {failed ? (\n <p role=\"alert\" className=\"text-destructive p-6 text-center text-sm\">\n {t('table_select.load_failed', {}, \"Couldn't load the records.\")}\n </p>\n ) : !table ? (\n <div className=\"flex flex-col gap-2 p-4\" aria-hidden=\"true\">\n <Skeleton className=\"h-8 w-full\" />\n <Skeleton className=\"h-8 w-full\" />\n <Skeleton className=\"h-8 w-full\" />\n </div>\n ) : records.length === 0 ? (\n <p className=\"text-muted-foreground p-6 text-center text-sm\">\n {table.emptyState.heading}\n </p>\n ) : (\n <Table className={cn(loading && 'opacity-60')}>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-10\">\n <span className=\"sr-only\">{t('table.select_row', {}, 'Select row')}</span>\n </TableHead>\n {columns.map((column) => (\n <TableHead\n key={column.name}\n aria-sort={\n table.sort?.column === column.name\n ? table.sort.direction === 'asc'\n ? 'ascending'\n : 'descending'\n : undefined\n }\n >\n {column.sortable ? (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1\"\n onClick={() => sort(column.name)}\n >\n {column.label}\n {table.sort?.column === column.name ? (\n table.sort.direction === 'asc' ? (\n <ArrowUpIcon className=\"size-3.5\" />\n ) : (\n <ArrowDownIcon className=\"size-3.5\" />\n )\n ) : (\n <ArrowUpDownIcon className=\"text-muted-foreground size-3.5\" />\n )}\n </button>\n ) : (\n column.label\n )}\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {records.map((record) => {\n const key = String(record.key)\n const checked = draft.includes(key)\n const selectable = record.selectable === true && (checked || !atMax)\n const label = labels[key] ?? key\n\n return (\n <TableRow\n key={key}\n data-state={checked ? 'selected' : undefined}\n className={cn(selectable && 'cursor-pointer')}\n onClick={() => selectable && toggle(key)}\n >\n <TableCell className=\"w-10\" onClick={(event) => event.stopPropagation()}>\n {multiple ? (\n <Checkbox\n checked={checked}\n disabled={!selectable}\n aria-label={t(\n 'table_select.select_record',\n { label },\n `Select ${label}`,\n )}\n onCheckedChange={() => toggle(key)}\n />\n ) : (\n <input\n type=\"radio\"\n name={`${component.statePath}-picker`}\n className=\"accent-primary size-4\"\n checked={checked}\n disabled={!selectable}\n aria-label={t(\n 'table_select.select_record',\n { label },\n `Select ${label}`,\n )}\n onChange={() => toggle(key)}\n />\n )}\n </TableCell>\n {columns.map((column) => (\n <TableCell key={column.name}>\n {record.cells[column.name] && (\n <TableCellContent\n column={column}\n cell={record.cells[column.name]!}\n recordKey={record.key}\n token=\"\"\n component={`picker:${component.statePath}`}\n />\n )}\n </TableCell>\n ))}\n </TableRow>\n )\n })}\n </TableBody>\n </Table>\n )}\n </div>\n {atMax && (\n <p className=\"text-muted-foreground text-sm\" role=\"status\">\n {choice(\n 'table_select.max_reached',\n component.maxItems ?? 0,\n { count: component.maxItems ?? 0 },\n 'You can select up to :count records.',\n )}\n </p>\n )}\n <DialogFooter className=\"items-center sm:justify-between\">\n {paging ? (\n <nav className=\"flex items-center gap-2\" aria-label={t('pagination', {}, 'Pagination')}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n disabled={!paging.hasPrevious || loading}\n aria-label={t('table.previous', {}, 'Previous')}\n onClick={() => setQuery((state) => ({ ...state, page: paging.page - 1 }))}\n >\n <ChevronLeftIcon className=\"rtl:rotate-180\" />\n </Button>\n <span className=\"text-muted-foreground text-sm\">\n {paging.total !== undefined\n ? t(\n 'table.showing',\n { first: paging.from, last: paging.to, total: paging.total },\n `Showing ${paging.from}–${paging.to} of ${paging.total}`,\n )\n : `${paging.from}–${paging.to}`}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n disabled={!paging.hasNext || loading}\n aria-label={t('table.next', {}, 'Next')}\n onClick={() => setQuery((state) => ({ ...state, page: paging.page + 1 }))}\n >\n <ChevronRightIcon className=\"rtl:rotate-180\" />\n </Button>\n </nav>\n ) : (\n <span />\n )}\n <div className=\"flex gap-2\">\n {draft.length > 0 && (\n <Button type=\"button\" variant=\"ghost\" onClick={() => setDraft([])}>\n {t('table_select.clear', {}, 'Clear selection')}\n </Button>\n )}\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() =>\n onConfirm(\n draft,\n Object.fromEntries(\n draft.filter((key) => labels[key]).map((key) => [key, labels[key]!]),\n ),\n )\n }\n >\n {component.modalSubmitActionLabel ?? t('table_select.confirm', {}, 'Select')}\n </Button>\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n/**\n * The picker's filters: a small form whose state becomes the table's `filters`. Searchable\n * selects among them load options through the owning form's endpoint (`pickerFilters`).\n */\nfunction PickerFilters({\n table,\n owner,\n path,\n onChange,\n}: {\n table: NonNullable<ModalTableSelectResponse['table']>\n owner: FormStore\n path: string\n onChange: (filters: Record<string, Record<string, unknown>> | null) => void\n}) {\n const t = useT()\n const components = useMemo(() => filterFormComponents(table.filters ?? []), [table.filters])\n const [store] = useState(\n () =>\n new FormStore(\n {\n components,\n state: table.filterState ?? {},\n columns: table.filtersFormColumns ?? { default: 1, sm: 2 },\n },\n pickerFilterFormOptions(owner, path),\n ),\n )\n const state = useStore(store.store)\n const latest = useRef(onChange)\n\n useEffect(() => {\n latest.current = onChange\n })\n\n useEffect(() => {\n if (!state.dirty.size) return\n\n const timer = setTimeout(\n () => latest.current(state.values as Record<string, Record<string, unknown>>),\n 400,\n )\n\n return () => clearTimeout(timer)\n }, [state.values, state.dirty])\n\n useEffect(() => () => store.destroy(), [store])\n\n return (\n <FormProvider store={store}>\n <div className=\"bg-muted/30 flex flex-col gap-2 rounded-lg border p-3\">\n <SchemaRenderer components={state.components} columns={state.columns} className=\"gap-3\" />\n {(table.filterIndicators?.length ?? 0) > 0 && (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"self-end\"\n onClick={() => {\n store.fill({ components, state: {}, columns: state.columns })\n onChange(null)\n }}\n >\n {t('table.reset_filters', {}, 'Reset')}\n </Button>\n )}\n </div>\n </FormProvider>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAgB,sBAAsB,EAAE,aAAoD;CAC1F,MAAM,IAAI,KAAK;CACf,MAAM,EAAE,OAAO,UAAU,OAAO,IAAI,UAAU,UAAU,SAAS,SAAkB,SAAS;CAC5F,MAAM,WAAW,QAAQ,UAAU,QAAQ;CAC3C,MAAM,WAAW,mBAAmB,OAAO,QAAQ;CACnD,MAAM,SAAS,YAAY;CAC3B,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,CAAC,QAAQ,aAAa,SAAiC,CAAC,CAAC;CAC/D,MAAM,SAAS;EAAE,GAAG;EAAQ,GAAG,UAAU;CAAe;CAExD,MAAM,SAAS,SAAmB,SAAS,WAAW,OAAQ,KAAK,MAAM,IAAK;CAE9E,OACE,qBAAC,cAAD;EAAc,OAAO;EAAW,SAAS;EAAW;EAApD,UAAA,CACE,qBAAC,OAAD;GACM;GACJ,MAAK;GACL,aAAU;GACV,WAAU;GACV,mBAAiB,UAAU,QAAQ,SAAS,EAAE,CAAC,CAAC,QAAQ,KAAA;GACxD,oBAAkB,KAAK;GANzB,UAAA,CAQG,SAAS,WAAW,IACnB,oBAAC,QAAD;IAAM,WAAU;IACb,UAAA,UAAU,eAAe,EAAE,qBAAqB,CAAC,GAAG,kBAAkB;GACnE,CAAA,IAEN,SAAS,KAAK,QACZ,qBAAC,OAAD;IAAiB,SAAQ;IAAY,WAAU;IAA/C,UAAA,CACE,oBAAC,QAAD,EAAA,UAAO,OAAO,QAAQ,IAAU,CAAA,GAC/B,CAAC,UACA,oBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,cAAY,EACV,uBACA,EAAE,OAAO,OAAO,QAAQ,IAAI,GAC5B,eACF;KACA,eAAe,MAAM,SAAS,QAAQ,cAAc,cAAc,GAAG,CAAC;KAEtE,UAAA,oBAAC,OAAD,EAAO,WAAU,SAAU,CAAA;IACrB,CAAA,CAEL;GAhBK,GAAA,GAgBL,CACR,GAEF,CAAC,UACA,qBAAC,QAAD;IACE,MAAK;IACL,SAAQ;IACR,MAAK;IACL,iBAAc;IACd,eAAe,QAAQ,IAAI;IAL7B,UAAA,CAOG,UAAU,oBAAoB,oBAAC,MAAD,EAAM,MAAM,UAAU,iBAAmB,CAAA,GACvE,SAAS,SAAS,KAAK,CAAC,WACrB,EAAE,uBAAuB,CAAC,GAAG,QAAQ,IACrC,UAAU,iBACR;GAEP,CAAA,CAAA;EACJ,CAAA,GAAA,QACC,oBAAC,kBAAD;GACa;GACX,SAAS;GACT,eAAe,QAAQ,KAAK;GAC5B,YAAY,MAAM,cAAc;IAC9B,WAAW,WAAW;KAAE,GAAG;KAAO,GAAG;IAAU,EAAE;IACjD,MAAM,IAAI;IACV,QAAQ,KAAK;GACf;EACD,CAAA,CAES;;AAElB;AASA,SAAS,iBAAiB,EAAE,WAAW,SAAS,SAAS,aAA0B;CACjF,MAAM,IAAI,KAAK;CACf,MAAM,SAAS,UAAU;CACzB,MAAM,OAAO,aAAa;CAC1B,MAAM,WAAW,QAAQ,UAAU,QAAQ;CAC3C,MAAM,CAAC,OAAO,YAAY,SAAqB,CAAC,CAAC;CACjD,MAAM,CAAC,UAAU,eAAe,SAA0C,IAAI;CAC9E,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAE1C,MAAM,CAAC,aAAa,kBAAkB,SAA4B,IAAI;CACtE,MAAM,UAAU,gBAAgB;CAChC,MAAM,CAAC,OAAO,YAAY,SAAmB,OAAO;CACpD,MAAM,CAAC,QAAQ,aAAa,SAAiC,CAAC,CAAC;CAC/D,MAAM,CAAC,QAAQ,aAAa,SAAS,EAAE;CAEvC,gBAAgB;EACd,MAAM,aAAa,IAAI,gBAAgB;EAEvC,KACG,WAAW,UAAU,WAAW,UAAU,sBAAsB,OAAO,WAAW,MAAM,CAAC,CACzF,MAAM,SAAS;GACd,IAAI,MAAM;IACR,YAAY,IAAI;IAChB,WAAW,WAAW;KAAE,GAAG;KAAO,GAAG,KAAK;IAAO,EAAE;GACrD;GACA,UAAU,KAAK;GACf,eAAe,KAAK;EACtB,CAAC,CAAC,CACD,OAAO,UAAmB;GACzB,IAAK,MAA4B,SAAS,cAAc;GACxD,UAAU,IAAI;GACd,eAAe,KAAK;EACtB,CAAC;EAEH,aAAa,WAAW,MAAM;CAChC,GAAG;EAAC;EAAM,UAAU;EAAW,UAAU;EAAsB;CAAK,CAAC;CAErE,MAAM,QAAQ,UAAU;CACxB,MAAM,aAAa,cAEf,UACG,SAAiB,UAAU,WAAW;EAAE,GAAG;EAAO,QAAQ,QAAQ;EAAM,MAAM;CAAK,EAAE,GACtF,OAAO,kBAAkB,GAC3B,GACF,CAAC,OAAO,cAAc,CACxB;CACA,sBAAsB,WAAW,OAAO,GAAG,CAAC,UAAU,CAAC;CAEvD,MAAM,UAAU,QACZ,MAAM,QAAQ,QAAQ,WAAW,MAAM,eAAe,SAAS,OAAO,IAAI,CAAC,IAC3E,CAAC;CACL,MAAM,UAAU,OAAO,WAAW,CAAC;CACnC,MAAM,SAAS,iBAAiB,OAAO,UAAU;CACjD,MAAM,QAAQ,YAAY,UAAU,YAAY,QAAQ,MAAM,UAAU,UAAU;CAElF,MAAM,UAAU,QACd,UAAU,UAAU,oBAAoB,OAAO,KAAK,UAAU,UAAU,QAAQ,CAAC;CAEnF,MAAM,QAAQ,WAAmB;EAC/B,MAAM,YACJ,OAAO,MAAM,WAAW,UAAU,MAAM,KAAK,cAAc,QAAQ,SAAS;EAC9E,UAAU,WAAW;GAAE,GAAG;GAAO,MAAM;GAAQ;GAAW,MAAM;EAAK,EAAE;CACzE;CAEA,OACE,oBAAC,QAAD;EAAQ,MAAA;EAAK,eAAe,SAAS,CAAC,QAAQ,QAAQ;EACpD,UAAA,qBAAC,eAAD;GACE,WAAW,GACT,oCACA,4BAA4B,UAAU,cAAc,UAAU,cAChE;GACA,aAAU;GALZ,UAAA;IAOE,qBAAC,cAAD,EAAA,UAAA;KACE,oBAAC,aAAD,EAAA,UAAc,UAAU,aAA0B,CAAA;KACjD,UAAU,oBAET,oBAAC,KAAD;MACE,WAAU;MACV,aAAU;MACV,yBAAyB,EAAE,QAAQ,UAAU,iBAAiB;KAC/D,CAAA;KAEH,oBAAC,mBAAD;MAAmB,aAAU;MAC1B,UAAA,OACC,kBACA,MAAM,QACN,EAAE,OAAO,MAAM,OAAO,GACtB,GAAG,MAAM,OAAO,UAClB;KACiB,CAAA;IACP,EAAA,CAAA;IACb,UAAU,MAAM,eAAe,MAAM,SAAS,UAAU,KAAK,MAC5D,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACG,MAAM,cACL,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAAC,YAAD,EAAY,WAAU,+FAAgG,CAAA,GACtH,oBAAC,OAAD;OACE,MAAK;OACL,OAAO;OACP,cAAY,MAAM;OAClB,aAAa,MAAM;OACnB,WAAU;OACV,WAAW,UAAU;QACnB,UAAU,MAAM,OAAO,KAAK;QAC5B,WAAW,MAAM,OAAO,KAAK;OAC/B;MACD,CAAA,CACE;KAEL,CAAA,IAAA,MAAM,SAAS,UAAU,KAAK,KAC9B,oBAAC,eAAD;MACS;MACP,OAAO;MACP,MAAM,UAAU;MAChB,WAAW,YAAY,UAAU,WAAW;OAAE,GAAG;OAAO;OAAS,MAAM;MAAK,EAAE;KAC/E,CAAA,CAEA;;IAEP,oBAAC,OAAD;KAAK,WAAU;KAAiD,aAAW;KACxE,UAAA,SACC,oBAAC,KAAD;MAAG,MAAK;MAAQ,WAAU;MACvB,UAAA,EAAE,4BAA4B,CAAC,GAAG,4BAA4B;KAC9D,CAAA,IACD,CAAC,QACH,qBAAC,OAAD;MAAK,WAAU;MAA0B,eAAY;MAArD,UAAA;OACE,oBAAC,UAAD,EAAU,WAAU,aAAc,CAAA;OAClC,oBAAC,UAAD,EAAU,WAAU,aAAc,CAAA;OAClC,oBAAC,UAAD,EAAU,WAAU,aAAc,CAAA;MAC/B;KACH,CAAA,IAAA,QAAQ,WAAW,IACrB,oBAAC,KAAD;MAAG,WAAU;MACV,UAAA,MAAM,WAAW;KACjB,CAAA,IAEH,qBAAC,OAAD;MAAO,WAAW,GAAG,WAAW,YAAY;MAA5C,UAAA,CACE,oBAAC,aAAD,EAAA,UACE,qBAAC,UAAD,EAAA,UAAA,CACE,oBAAC,WAAD;OAAW,WAAU;OACnB,UAAA,oBAAC,QAAD;QAAM,WAAU;QAAW,UAAA,EAAE,oBAAoB,CAAC,GAAG,YAAY;OAAQ,CAAA;MAChE,CAAA,GACV,QAAQ,KAAK,WACZ,oBAAC,WAAD;OAEE,aACE,MAAM,MAAM,WAAW,OAAO,OAC1B,MAAM,KAAK,cAAc,QACvB,cACA,eACF,KAAA;OAGL,UAAA,OAAO,WACN,qBAAC,UAAD;QACE,MAAK;QACL,WAAU;QACV,eAAe,KAAK,OAAO,IAAI;QAHjC,UAAA,CAKG,OAAO,OACP,MAAM,MAAM,WAAW,OAAO,OAC7B,MAAM,KAAK,cAAc,QACvB,oBAAC,aAAD,EAAa,WAAU,WAAY,CAAA,IAEnC,oBAAC,eAAD,EAAe,WAAU,WAAY,CAAA,IAGvC,oBAAC,iBAAD,EAAiB,WAAU,iCAAkC,CAAA,CAEzD;OAER,CAAA,IAAA,OAAO;MAEA,GA7BJ,OAAO,IA6BH,CACZ,CACO,EAAA,CAAA,EACC,CAAA,GACb,oBAAC,WAAD,EAAA,UACG,QAAQ,KAAK,WAAW;OACvB,MAAM,MAAM,OAAO,OAAO,GAAG;OAC7B,MAAM,UAAU,MAAM,SAAS,GAAG;OAClC,MAAM,aAAa,OAAO,eAAe,SAAS,WAAW,CAAC;OAC9D,MAAM,QAAQ,OAAO,QAAQ;OAE7B,OACE,qBAAC,UAAD;QAEE,cAAY,UAAU,aAAa,KAAA;QACnC,WAAW,GAAG,cAAc,gBAAgB;QAC5C,eAAe,cAAc,OAAO,GAAG;QAJzC,UAAA,CAME,oBAAC,WAAD;SAAW,WAAU;SAAO,UAAU,UAAU,MAAM,gBAAgB;SACnE,UAAA,WACC,oBAAC,UAAD;UACW;UACT,UAAU,CAAC;UACX,cAAY,EACV,8BACA,EAAE,MAAM,GACR,UAAU,OACZ;UACA,uBAAuB,OAAO,GAAG;SAClC,CAAA,IAED,oBAAC,SAAD;UACE,MAAK;UACL,MAAM,GAAG,UAAU,UAAU;UAC7B,WAAU;UACD;UACT,UAAU,CAAC;UACX,cAAY,EACV,8BACA,EAAE,MAAM,GACR,UAAU,OACZ;UACA,gBAAgB,OAAO,GAAG;SAC3B,CAAA;QAEM,CAAA,GACV,QAAQ,KAAK,WACZ,oBAAC,WAAD,EAAA,UACG,OAAO,MAAM,OAAO,SACnB,oBAAC,kBAAD;SACU;SACR,MAAM,OAAO,MAAM,OAAO;SAC1B,WAAW,OAAO;SAClB,OAAM;SACN,WAAW,UAAU,UAAU;QAChC,CAAA,EAEM,GAVK,OAAO,IAUZ,CACZ,CACO;OA9CH,GAAA,GA8CG;MAEd,CAAC,EACQ,CAAA,CACN;;IAEN,CAAA;IACJ,SACC,oBAAC,KAAD;KAAG,WAAU;KAAgC,MAAK;KAC/C,UAAA,OACC,4BACA,UAAU,YAAY,GACtB,EAAE,OAAO,UAAU,YAAY,EAAE,GACjC,sCACF;IACC,CAAA;IAEL,qBAAC,cAAD;KAAc,WAAU;KAAxB,UAAA,CACG,SACC,qBAAC,OAAD;MAAK,WAAU;MAA0B,cAAY,EAAE,cAAc,CAAC,GAAG,YAAY;MAArF,UAAA;OACE,oBAAC,QAAD;QACE,MAAK;QACL,SAAQ;QACR,MAAK;QACL,UAAU,CAAC,OAAO,eAAe;QACjC,cAAY,EAAE,kBAAkB,CAAC,GAAG,UAAU;QAC9C,eAAe,UAAU,WAAW;SAAE,GAAG;SAAO,MAAM,OAAO,OAAO;QAAE,EAAE;QAExE,UAAA,oBAAC,iBAAD,EAAiB,WAAU,iBAAkB,CAAA;OACvC,CAAA;OACR,oBAAC,QAAD;QAAM,WAAU;QACb,UAAA,OAAO,UAAU,KAAA,IACd,EACE,iBACA;SAAE,OAAO,OAAO;SAAM,MAAM,OAAO;SAAI,OAAO,OAAO;QAAM,GAC3D,WAAW,OAAO,KAAK,GAAG,OAAO,GAAG,MAAM,OAAO,OACnD,IACA,GAAG,OAAO,KAAK,GAAG,OAAO;OACzB,CAAA;OACN,oBAAC,QAAD;QACE,MAAK;QACL,SAAQ;QACR,MAAK;QACL,UAAU,CAAC,OAAO,WAAW;QAC7B,cAAY,EAAE,cAAc,CAAC,GAAG,MAAM;QACtC,eAAe,UAAU,WAAW;SAAE,GAAG;SAAO,MAAM,OAAO,OAAO;QAAE,EAAE;QAExE,UAAA,oBAAC,kBAAD,EAAkB,WAAU,iBAAkB,CAAA;OACxC,CAAA;MACL;KAEL,CAAA,IAAA,oBAAC,QAAD,CAAO,CAAA,GAET,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACG,MAAM,SAAS,KACd,oBAAC,QAAD;QAAQ,MAAK;QAAS,SAAQ;QAAQ,eAAe,SAAS,CAAC,CAAC;QAC7D,UAAA,EAAE,sBAAsB,CAAC,GAAG,iBAAiB;OACxC,CAAA;OAEV,oBAAC,QAAD;QAAQ,MAAK;QAAS,SAAQ;QAAU,SAAS;QAC9C,UAAA,EAAE,UAAU,CAAC,GAAG,QAAQ;OACnB,CAAA;OACR,oBAAC,QAAD;QACE,MAAK;QACL,eACE,UACE,OACA,OAAO,YACL,MAAM,QAAQ,QAAQ,OAAO,IAAI,CAAC,CAAC,KAAK,QAAQ,CAAC,KAAK,OAAO,IAAK,CAAC,CACrE,CACF;QAGD,UAAA,UAAU,0BAA0B,EAAE,wBAAwB,CAAC,GAAG,QAAQ;OACrE,CAAA;MACL;KACO,CAAA,CAAA;;GACD;;CACT,CAAA;AAEZ;;;;;AAMA,SAAS,cAAc,EACrB,OACA,OACA,MACA,YAMC;CACD,MAAM,IAAI,KAAK;CACf,MAAM,aAAa,cAAc,qBAAqB,MAAM,WAAW,CAAC,CAAC,GAAG,CAAC,MAAM,OAAO,CAAC;CAC3F,MAAM,CAAC,SAAS,eAEZ,IAAI,UACF;EACE;EACA,OAAO,MAAM,eAAe,CAAC;EAC7B,SAAS,MAAM,sBAAsB;GAAE,SAAS;GAAG,IAAI;EAAE;CAC3D,GACA,wBAAwB,OAAO,IAAI,CACrC,CACJ;CACA,MAAM,QAAQ,SAAS,MAAM,KAAK;CAClC,MAAM,SAAS,OAAO,QAAQ;CAE9B,gBAAgB;EACd,OAAO,UAAU;CACnB,CAAC;CAED,gBAAgB;EACd,IAAI,CAAC,MAAM,MAAM,MAAM;EAEvB,MAAM,QAAQ,iBACN,OAAO,QAAQ,MAAM,MAAiD,GAC5E,GACF;EAEA,aAAa,aAAa,KAAK;CACjC,GAAG,CAAC,MAAM,QAAQ,MAAM,KAAK,CAAC;CAE9B,sBAAsB,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC;CAE9C,OACE,oBAAC,cAAD;EAAqB;EACnB,UAAA,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,oBAAC,gBAAD;IAAgB,YAAY,MAAM;IAAY,SAAS,MAAM;IAAS,WAAU;GAAS,CAAA,IACvF,MAAM,kBAAkB,UAAU,KAAK,KACvC,oBAAC,QAAD;IACE,MAAK;IACL,SAAQ;IACR,MAAK;IACL,WAAU;IACV,eAAe;KACb,MAAM,KAAK;MAAE;MAAY,OAAO,CAAC;MAAG,SAAS,MAAM;KAAQ,CAAC;KAC5D,SAAS,IAAI;IACf;IAEC,UAAA,EAAE,uBAAuB,CAAC,GAAG,OAAO;GAC/B,CAAA,CAEP;;CACO,CAAA;AAElB"}
|
|
1
|
+
{"version":3,"file":"modal-table-select.js","names":[],"sources":["../../../src/components/fields/modal-table-select.tsx"],"sourcesContent":["import type { ModalTableSelectResponse, ModalTableSelectSchema, TableQuery } from '@alfakit/core'\nimport {\n FILTERS_MODAL_WIDTH_CLASSES,\n FormStore,\n debounce,\n fieldIds,\n filterFormComponents,\n modalTablePaging,\n modalTableSelected,\n pickerFilterFormOptions,\n toggleModalTableKey,\n} from '@alfakit/core'\nimport {\n ArrowDownIcon,\n ArrowUpDownIcon,\n ArrowUpIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n SearchIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { useChoice, useFormatter, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider, useField, useFormStore } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { TableCellContent } from '../table/cells'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Skeleton } from '../ui/skeleton'\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'\n\n/**\n * Select records from a table in a modal. The table (columns, search, filters, sorting,\n * pagination) comes from the server's picker endpoint; the field keeps only keys, with\n * labels from the server.\n */\nexport function ModalTableSelectField({ component }: { component: ModalTableSelectSchema }) {\n const t = useT()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<unknown>(component)\n const multiple = Boolean(component.multiple)\n const selected = modalTableSelected(value, multiple)\n const locked = disabled || readOnly\n const [open, setOpen] = useState(false)\n const [picked, setPicked] = useState<Record<string, string>>({})\n const labels = { ...picked, ...component.selectedLabels }\n\n const store = (keys: string[]) => setValue(multiple ? keys : (keys[0] ?? null))\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n id={id}\n role=\"group\"\n data-slot=\"modal-table-select\"\n className=\"flex flex-wrap items-center gap-2\"\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n >\n {selected.length === 0 ? (\n <span className=\"text-muted-foreground text-sm\">\n {component.placeholder ?? t('table_select.none', {}, 'Nothing selected')}\n </span>\n ) : (\n selected.map((key) => (\n <Badge key={key} variant=\"secondary\" className=\"gap-1 pe-1\">\n <span>{labels[key] ?? key}</span>\n {!locked && (\n <button\n type=\"button\"\n className=\"hover:bg-foreground/10 rounded-sm\"\n aria-label={t(\n 'table_select.remove',\n { label: labels[key] ?? key },\n 'Remove :label',\n )}\n onClick={() => store(selected.filter((candidate) => candidate !== key))}\n >\n <XIcon className=\"size-3\" />\n </button>\n )}\n </Badge>\n ))\n )}\n {!locked && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n aria-haspopup=\"dialog\"\n onClick={() => setOpen(true)}\n >\n {component.selectActionIcon && <Icon name={component.selectActionIcon} />}\n {selected.length > 0 && !multiple\n ? t('table_select.change', {}, 'Change')\n : component.selectActionLabel}\n </Button>\n )}\n </div>\n {open && (\n <ModalTablePicker\n component={component}\n initial={selected}\n onClose={() => setOpen(false)}\n onConfirm={(keys, newLabels) => {\n setPicked((state) => ({ ...state, ...newLabels }))\n store(keys)\n setOpen(false)\n }}\n />\n )}\n </FieldWrapper>\n )\n}\n\ninterface PickerProps {\n component: ModalTableSelectSchema\n initial: string[]\n onClose: () => void\n onConfirm: (keys: string[], labels: Record<string, string>) => void\n}\n\nfunction ModalTablePicker({ component, initial, onClose, onConfirm }: PickerProps) {\n const t = useT()\n const format = useFormatter()\n const choice = useChoice()\n const form = useFormStore()\n const multiple = Boolean(component.multiple)\n const [query, setQuery] = useState<TableQuery>({})\n const [response, setResponse] = useState<ModalTableSelectResponse | null>(null)\n const [failed, setFailed] = useState(false)\n // The query the shown records answer; anything else means a request is in flight.\n const [loadedQuery, setLoadedQuery] = useState<TableQuery | null>(null)\n const loading = loadedQuery !== query\n const [draft, setDraft] = useState<string[]>(initial)\n const [labels, setLabels] = useState<Record<string, string>>({})\n const [search, setSearch] = useState('')\n\n useEffect(() => {\n const controller = new AbortController()\n\n form\n .modalTable(component.statePath, component.tableStateIdentifier, query, controller.signal)\n .then((next) => {\n if (next) {\n setResponse(next)\n setLabels((state) => ({ ...state, ...next.labels }))\n }\n setFailed(false)\n setLoadedQuery(query)\n })\n .catch((error: unknown) => {\n if ((error as { name?: string }).name === 'AbortError') return\n setFailed(true)\n setLoadedQuery(query)\n })\n\n return () => controller.abort()\n }, [form, component.statePath, component.tableStateIdentifier, query])\n\n const table = response?.table\n const pushSearch = useMemo(\n () =>\n debounce(\n (next: string) => setQuery((state) => ({ ...state, search: next || null, page: null })),\n table?.searchDebounce ?? 400,\n ),\n [table?.searchDebounce],\n )\n useEffect(() => () => pushSearch.cancel(), [pushSearch])\n\n const columns = table\n ? table.columns.filter((column) => table.visibleColumns.includes(column.name))\n : []\n const records = table?.records ?? []\n const paging = modalTablePaging(table?.pagination)\n const atMax = multiple && component.maxItems != null && draft.length >= component.maxItems\n\n const toggle = (key: string) =>\n setDraft((state) => toggleModalTableKey(state, key, multiple, component.maxItems))\n\n const sort = (column: string) => {\n const direction =\n table?.sort?.column === column && table.sort.direction === 'asc' ? 'desc' : 'asc'\n setQuery((state) => ({ ...state, sort: column, direction, page: null }))\n }\n\n return (\n <Dialog open onOpenChange={(next) => !next && onClose()}>\n <DialogContent\n className={cn(\n 'flex max-h-[90vh] flex-col gap-4',\n FILTERS_MODAL_WIDTH_CLASSES[component.modalWidth ?? '3xl'] ?? 'sm:max-w-3xl',\n )}\n data-slot=\"modal-table-select-dialog\"\n >\n <DialogHeader>\n <DialogTitle>{component.modalHeading}</DialogTitle>\n {component.modalDescription && (\n // Escaped on the server (`Html::from()`), like action modal descriptions.\n <p\n className=\"text-muted-foreground text-sm\"\n data-slot=\"modal-table-select-description\"\n dangerouslySetInnerHTML={{ __html: component.modalDescription }}\n />\n )}\n <DialogDescription aria-live=\"polite\">\n {choice(\n 'table.selected',\n draft.length,\n { count: draft.length },\n `${draft.length} selected`,\n )}\n </DialogDescription>\n </DialogHeader>\n {table && (table.searchable || (table.filters?.length ?? 0) > 0) && (\n <div className=\"flex flex-col gap-3\">\n {table.searchable && (\n <div className=\"relative\">\n <SearchIcon className=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\" />\n <Input\n type=\"search\"\n value={search}\n aria-label={table.searchPlaceholder}\n placeholder={table.searchPlaceholder}\n className=\"ps-8\"\n onChange={(event) => {\n setSearch(event.target.value)\n pushSearch(event.target.value)\n }}\n />\n </div>\n )}\n {(table.filters?.length ?? 0) > 0 && (\n <PickerFilters\n table={table}\n owner={form}\n path={component.statePath}\n onChange={(filters) => setQuery((state) => ({ ...state, filters, page: null }))}\n />\n )}\n </div>\n )}\n <div className=\"min-h-0 flex-1 overflow-auto rounded-md border\" aria-busy={loading}>\n {failed ? (\n <p role=\"alert\" className=\"text-destructive p-6 text-center text-sm\">\n {t('table_select.load_failed', {}, \"Couldn't load the records.\")}\n </p>\n ) : !table ? (\n <div className=\"flex flex-col gap-2 p-4\" aria-hidden=\"true\">\n <Skeleton className=\"h-8 w-full\" />\n <Skeleton className=\"h-8 w-full\" />\n <Skeleton className=\"h-8 w-full\" />\n </div>\n ) : records.length === 0 ? (\n <p className=\"text-muted-foreground p-6 text-center text-sm\">\n {table.emptyState.heading}\n </p>\n ) : (\n <Table className={cn(loading && 'opacity-60')}>\n <TableHeader>\n <TableRow>\n <TableHead className=\"w-10\">\n <span className=\"sr-only\">{t('table.select_row', {}, 'Select row')}</span>\n </TableHead>\n {columns.map((column) => (\n <TableHead\n key={column.name}\n aria-sort={\n table.sort?.column === column.name\n ? table.sort.direction === 'asc'\n ? 'ascending'\n : 'descending'\n : undefined\n }\n >\n {column.sortable ? (\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1\"\n onClick={() => sort(column.name)}\n >\n {column.label}\n {table.sort?.column === column.name ? (\n table.sort.direction === 'asc' ? (\n <ArrowUpIcon className=\"size-3.5\" />\n ) : (\n <ArrowDownIcon className=\"size-3.5\" />\n )\n ) : (\n <ArrowUpDownIcon className=\"text-muted-foreground size-3.5\" />\n )}\n </button>\n ) : (\n column.label\n )}\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {records.map((record) => {\n const key = String(record.key)\n const checked = draft.includes(key)\n const selectable = record.selectable === true && (checked || !atMax)\n const label = labels[key] ?? key\n\n return (\n <TableRow\n key={key}\n data-state={checked ? 'selected' : undefined}\n className={cn(selectable && 'cursor-pointer')}\n onClick={() => selectable && toggle(key)}\n >\n <TableCell className=\"w-10\" onClick={(event) => event.stopPropagation()}>\n {multiple ? (\n <Checkbox\n checked={checked}\n disabled={!selectable}\n aria-label={t(\n 'table_select.select_record',\n { label },\n `Select ${label}`,\n )}\n onCheckedChange={() => toggle(key)}\n />\n ) : (\n <input\n type=\"radio\"\n name={`${component.statePath}-picker`}\n className=\"accent-primary size-4\"\n checked={checked}\n disabled={!selectable}\n aria-label={t(\n 'table_select.select_record',\n { label },\n `Select ${label}`,\n )}\n onChange={() => toggle(key)}\n />\n )}\n </TableCell>\n {columns.map((column) => (\n <TableCell key={column.name}>\n {record.cells[column.name] && (\n <TableCellContent\n column={column}\n cell={record.cells[column.name]!}\n recordKey={record.key}\n token=\"\"\n component={`picker:${component.statePath}`}\n />\n )}\n </TableCell>\n ))}\n </TableRow>\n )\n })}\n </TableBody>\n </Table>\n )}\n </div>\n {atMax && (\n <p className=\"text-muted-foreground text-sm\" role=\"status\">\n {choice(\n 'table_select.max_reached',\n component.maxItems ?? 0,\n { count: component.maxItems ?? 0 },\n 'You can select up to :count records.',\n )}\n </p>\n )}\n <DialogFooter className=\"items-center sm:justify-between\">\n {paging ? (\n <nav className=\"flex items-center gap-2\" aria-label={t('pagination', {}, 'Pagination')}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n disabled={!paging.hasPrevious || loading}\n aria-label={t('table.previous', {}, 'Previous')}\n onClick={() => setQuery((state) => ({ ...state, page: paging.page - 1 }))}\n >\n <ChevronLeftIcon className=\"rtl:rotate-180\" />\n </Button>\n <span className=\"text-muted-foreground text-sm\">\n {paging.total !== undefined\n ? t(\n 'table.showing',\n {\n first: format.number(paging.from),\n last: format.number(paging.to),\n total: format.number(paging.total),\n },\n 'Showing :first–:last of :total',\n )\n : `${paging.from}–${paging.to}`}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n disabled={!paging.hasNext || loading}\n aria-label={t('table.next', {}, 'Next')}\n onClick={() => setQuery((state) => ({ ...state, page: paging.page + 1 }))}\n >\n <ChevronRightIcon className=\"rtl:rotate-180\" />\n </Button>\n </nav>\n ) : (\n <span />\n )}\n <div className=\"flex gap-2\">\n {draft.length > 0 && (\n <Button type=\"button\" variant=\"ghost\" onClick={() => setDraft([])}>\n {t('table_select.clear', {}, 'Clear selection')}\n </Button>\n )}\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n onClick={() =>\n onConfirm(\n draft,\n Object.fromEntries(\n draft.filter((key) => labels[key]).map((key) => [key, labels[key]!]),\n ),\n )\n }\n >\n {component.modalSubmitActionLabel ?? t('table_select.confirm', {}, 'Select')}\n </Button>\n </div>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n\n/**\n * The picker's filters: a small form whose state becomes the table's `filters`. Searchable\n * selects among them load options through the owning form's endpoint (`pickerFilters`).\n */\nfunction PickerFilters({\n table,\n owner,\n path,\n onChange,\n}: {\n table: NonNullable<ModalTableSelectResponse['table']>\n owner: FormStore\n path: string\n onChange: (filters: Record<string, Record<string, unknown>> | null) => void\n}) {\n const t = useT()\n const components = useMemo(() => filterFormComponents(table.filters ?? []), [table.filters])\n const [store] = useState(\n () =>\n new FormStore(\n {\n components,\n state: table.filterState ?? {},\n columns: table.filtersFormColumns ?? { default: 1, sm: 2 },\n },\n pickerFilterFormOptions(owner, path),\n ),\n )\n const state = useStore(store.store)\n const latest = useRef(onChange)\n\n useEffect(() => {\n latest.current = onChange\n })\n\n useEffect(() => {\n if (!state.dirty.size) return\n\n const timer = setTimeout(\n () => latest.current(state.values as Record<string, Record<string, unknown>>),\n 400,\n )\n\n return () => clearTimeout(timer)\n }, [state.values, state.dirty])\n\n useEffect(() => () => store.destroy(), [store])\n\n return (\n <FormProvider store={store}>\n <div className=\"bg-muted/30 flex flex-col gap-2 rounded-lg border p-3\">\n <SchemaRenderer components={state.components} columns={state.columns} className=\"gap-3\" />\n {(table.filterIndicators?.length ?? 0) > 0 && (\n <Button\n type=\"button\"\n variant=\"link\"\n size=\"sm\"\n className=\"self-end\"\n onClick={() => {\n store.fill({ components, state: {}, columns: state.columns })\n onChange(null)\n }}\n >\n {t('table.reset_filters', {}, 'Reset')}\n </Button>\n )}\n </div>\n </FormProvider>\n )\n}\n"],"mappings":"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"}
|
|
@@ -27,7 +27,6 @@ function OneTimeCodeInputField({ component }) {
|
|
|
27
27
|
inputMode: "numeric",
|
|
28
28
|
autoComplete: "one-time-code",
|
|
29
29
|
pattern: "[0-9]*",
|
|
30
|
-
maxLength: component.length,
|
|
31
30
|
value: value ?? "",
|
|
32
31
|
onChange: (event) => {
|
|
33
32
|
const code = oneTimeCodeDigits(event.target.value, component.length);
|
|
@@ -42,6 +41,7 @@ function OneTimeCodeInputField({ component }) {
|
|
|
42
41
|
required: component.required,
|
|
43
42
|
autoFocus: component.autofocus,
|
|
44
43
|
"data-slot": "one-time-code-input",
|
|
44
|
+
"data-input": "",
|
|
45
45
|
...aria,
|
|
46
46
|
...extraProps(component.extraInputAttributes, cn("max-w-56 text-center font-mono text-lg tracking-[0.5em] tabular-nums"))
|
|
47
47
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"one-time-code-input.js","names":[],"sources":["../../../src/components/fields/one-time-code-input.tsx"],"sourcesContent":["import { OneTimeCodeSubmitter, oneTimeCodeDigits, type OneTimeCodeInputSchema } from '@alfakit/core'\nimport { useState } from 'react'\nimport { extraProps } from '../../lib/attributes'\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 * `OneTimeCodeInput`: one numeric input with `autocomplete=\"one-time-code\"` (browsers and\n * password managers fill it from an SMS or email), digits only, left to right in every\n * language. `submitOnCompletion()` submits the form once the last digit is in.\n */\nexport function OneTimeCodeInputField({ component }: { component: OneTimeCodeInputSchema }) {\n const { value, setValue, blur, error, id, disabled, aria } = useField<string | null>(component)\n const [submitter] = useState(() => new OneTimeCodeSubmitter())\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <Input\n id={id}\n name={component.name}\n type=\"text\"\n dir=\"ltr\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n pattern=\"[0-9]*\"\n
|
|
1
|
+
{"version":3,"file":"one-time-code-input.js","names":[],"sources":["../../../src/components/fields/one-time-code-input.tsx"],"sourcesContent":["import { OneTimeCodeSubmitter, oneTimeCodeDigits, type OneTimeCodeInputSchema } from '@alfakit/core'\nimport { useState } from 'react'\nimport { extraProps } from '../../lib/attributes'\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 * `OneTimeCodeInput`: one numeric input with `autocomplete=\"one-time-code\"` (browsers and\n * password managers fill it from an SMS or email), digits only, left to right in every\n * language. `submitOnCompletion()` submits the form once the last digit is in.\n */\nexport function OneTimeCodeInputField({ component }: { component: OneTimeCodeInputSchema }) {\n const { value, setValue, blur, error, id, disabled, aria } = useField<string | null>(component)\n const [submitter] = useState(() => new OneTimeCodeSubmitter())\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <Input\n id={id}\n name={component.name}\n type=\"text\"\n dir=\"ltr\"\n inputMode=\"numeric\"\n autoComplete=\"one-time-code\"\n pattern=\"[0-9]*\"\n value={value ?? ''}\n onChange={(event) => {\n const code = oneTimeCodeDigits(event.target.value, component.length)\n const form = event.currentTarget.form\n\n setValue(code === '' ? null : code)\n\n // After the state reached the form store, so the submitted values include it.\n if (component.submitOnCompletion) {\n queueMicrotask(() => submitter.update(code, component.length, form))\n }\n }}\n onBlur={blur}\n placeholder={component.placeholder}\n disabled={disabled}\n readOnly={component.readOnly}\n required={component.required}\n autoFocus={component.autofocus}\n data-slot=\"one-time-code-input\"\n data-input=\"\"\n {...aria}\n {...extraProps(\n component.extraInputAttributes,\n cn('max-w-56 text-center font-mono text-lg tracking-[0.5em] tabular-nums'),\n )}\n />\n </FieldWrapper>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,sBAAsB,EAAE,aAAoD;CAC1F,MAAM,EAAE,OAAO,UAAU,MAAM,OAAO,IAAI,UAAU,SAAS,SAAwB,SAAS;CAC9F,MAAM,CAAC,aAAa,eAAe,IAAI,qBAAqB,CAAC;CAE7D,OACE,oBAAC,cAAD;EAAc,OAAO;EAAW,SAAS;EAAW;EAClD,UAAA,oBAAC,OAAD;GACM;GACJ,MAAM,UAAU;GAChB,MAAK;GACL,KAAI;GACJ,WAAU;GACV,cAAa;GACb,SAAQ;GACR,OAAO,SAAS;GAChB,WAAW,UAAU;IACnB,MAAM,OAAO,kBAAkB,MAAM,OAAO,OAAO,UAAU,MAAM;IACnE,MAAM,OAAO,MAAM,cAAc;IAEjC,SAAS,SAAS,KAAK,OAAO,IAAI;IAGlC,IAAI,UAAU,oBACZ,qBAAqB,UAAU,OAAO,MAAM,UAAU,QAAQ,IAAI,CAAC;GAEvE;GACA,QAAQ;GACR,aAAa,UAAU;GACb;GACV,UAAU,UAAU;GACpB,UAAU,UAAU;GACpB,WAAW,UAAU;GACrB,aAAU;GACV,cAAW;GACX,GAAI;GACJ,GAAI,WACF,UAAU,sBACV,GAAG,sEAAsE,CAC3E;EACD,CAAA;CACW,CAAA;AAElB"}
|
|
@@ -6,11 +6,22 @@ import { VirtualSpacerRow } from "../schema/virtual-spacer.js";
|
|
|
6
6
|
import { resolveItemAction, tableColumnAlignmentClass, tableColumnCells } from "@alfakit/core";
|
|
7
7
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/fields/repeater-table.tsx
|
|
9
|
+
/**
|
|
10
|
+
* A fixed `width()` (`9rem`, `120px`) is also the column's minimum, so the table scrolls
|
|
11
|
+
* sideways instead of squeezing its fields; percentages stay flexible.
|
|
12
|
+
*/
|
|
13
|
+
function columnWidthStyle(width) {
|
|
14
|
+
if (!width) return void 0;
|
|
15
|
+
return width.trim().endsWith("%") ? { width } : {
|
|
16
|
+
width,
|
|
17
|
+
minWidth: width
|
|
18
|
+
};
|
|
19
|
+
}
|
|
9
20
|
/** Header cells shared by the Repeater and RepeatableEntry tables. */
|
|
10
21
|
function TableColumnHeaders({ columns }) {
|
|
11
22
|
return /* @__PURE__ */ jsx(Fragment, { children: columns.map((column, index) => /* @__PURE__ */ jsxs("th", {
|
|
12
23
|
scope: "col",
|
|
13
|
-
style: column.width
|
|
24
|
+
style: columnWidthStyle(column.width),
|
|
14
25
|
className: cn("text-muted-foreground px-3 py-2 text-xs font-medium", tableColumnAlignmentClass(column), !column.wrapHeader && "whitespace-nowrap"),
|
|
15
26
|
children: [/* @__PURE__ */ jsx("span", {
|
|
16
27
|
className: cn(column.hiddenLabel && "sr-only"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repeater-table.js","names":[],"sources":["../../../src/components/fields/repeater-table.tsx"],"sourcesContent":["import type {\n ActionContext,\n ActionSchema,\n ItemActionName,\n ItemActionSchema,\n SchemaComponent,\n TableColumnSchema,\n} from '@alfakit/core'\nimport { resolveItemAction, tableColumnAlignmentClass, tableColumnCells } from '@alfakit/core'\nimport { useT } from '../../context'\nimport type { VirtualListBinding } from '../../hooks/use-virtual-list'\nimport { cn } from '../../lib/utils'\nimport { RenderComponent } from '../schema/schema-renderer'\nimport { VirtualSpacerRow } from '../schema/virtual-spacer'\nimport { ItemHeaderActions, useItemActionFallbacks } from './item-actions'\n\nexport interface RepeaterTableProps {\n id: string\n columns: TableColumnSchema[]\n /** The item state path prefix for an index (`lines.0.`). */\n prefixFor: (index: number) => string\n count: number\n schemaFor: (index: number) => SchemaComponent[]\n labelledBy?: string\n describedBy?: string\n reorderable: boolean\n cloneable: boolean\n deletable: boolean\n /** `compact()`: tighter cells. */\n compact?: boolean\n onMove: (from: number, to: number) => void\n onClone: (index: number) => void\n onRemove: (index: number) => void\n /** Windowed rows for long lists (see `useVirtualList`). */\n virtual: VirtualListBinding\n /** The field action context item actions run in. */\n context?: ActionContext\n /** The built-in action `name` of the row at `index` (null when hidden). */\n resolve?: (name: ItemActionName, index: number) => ItemActionSchema | null\n /** `extraItemActions()` of the row at `index`. */\n extraActionsFor?: (index: number) => ActionSchema[] | undefined\n}\n\n/** Header cells shared by the Repeater and RepeatableEntry tables. */\nexport function TableColumnHeaders({ columns }: { columns: TableColumnSchema[] }) {\n return (\n <>\n {columns.map((column, index) => (\n <th\n key={`${column.name ?? 'column'}-${index}`}\n scope=\"col\"\n style={column.width
|
|
1
|
+
{"version":3,"file":"repeater-table.js","names":[],"sources":["../../../src/components/fields/repeater-table.tsx"],"sourcesContent":["import type {\n ActionContext,\n ActionSchema,\n ItemActionName,\n ItemActionSchema,\n SchemaComponent,\n TableColumnSchema,\n} from '@alfakit/core'\nimport { resolveItemAction, tableColumnAlignmentClass, tableColumnCells } from '@alfakit/core'\nimport type { CSSProperties } from 'react'\nimport { useT } from '../../context'\nimport type { VirtualListBinding } from '../../hooks/use-virtual-list'\nimport { cn } from '../../lib/utils'\nimport { RenderComponent } from '../schema/schema-renderer'\nimport { VirtualSpacerRow } from '../schema/virtual-spacer'\nimport { ItemHeaderActions, useItemActionFallbacks } from './item-actions'\n\nexport interface RepeaterTableProps {\n id: string\n columns: TableColumnSchema[]\n /** The item state path prefix for an index (`lines.0.`). */\n prefixFor: (index: number) => string\n count: number\n schemaFor: (index: number) => SchemaComponent[]\n labelledBy?: string\n describedBy?: string\n reorderable: boolean\n cloneable: boolean\n deletable: boolean\n /** `compact()`: tighter cells. */\n compact?: boolean\n onMove: (from: number, to: number) => void\n onClone: (index: number) => void\n onRemove: (index: number) => void\n /** Windowed rows for long lists (see `useVirtualList`). */\n virtual: VirtualListBinding\n /** The field action context item actions run in. */\n context?: ActionContext\n /** The built-in action `name` of the row at `index` (null when hidden). */\n resolve?: (name: ItemActionName, index: number) => ItemActionSchema | null\n /** `extraItemActions()` of the row at `index`. */\n extraActionsFor?: (index: number) => ActionSchema[] | undefined\n}\n\n/**\n * A fixed `width()` (`9rem`, `120px`) is also the column's minimum, so the table scrolls\n * sideways instead of squeezing its fields; percentages stay flexible.\n */\nfunction columnWidthStyle(width?: string | null): CSSProperties | undefined {\n if (!width) return undefined\n\n return width.trim().endsWith('%') ? { width } : { width, minWidth: width }\n}\n\n/** Header cells shared by the Repeater and RepeatableEntry tables. */\nexport function TableColumnHeaders({ columns }: { columns: TableColumnSchema[] }) {\n return (\n <>\n {columns.map((column, index) => (\n <th\n key={`${column.name ?? 'column'}-${index}`}\n scope=\"col\"\n style={columnWidthStyle(column.width)}\n className={cn(\n 'text-muted-foreground px-3 py-2 text-xs font-medium',\n tableColumnAlignmentClass(column),\n !column.wrapHeader && 'whitespace-nowrap',\n )}\n >\n <span className={cn(column.hiddenLabel && 'sr-only')}>{column.label}</span>\n {column.required && (\n <span className=\"text-destructive ms-0.5\" aria-hidden=\"true\">\n *\n </span>\n )}\n </th>\n ))}\n </>\n )\n}\n\n/**\n * Repeater items as table rows (`->table([...])`): one cell per item field, labels kept\n * for screen readers only, and the row controls in the last column.\n */\nexport function RepeaterTable({\n id,\n columns,\n prefixFor,\n count,\n schemaFor,\n labelledBy,\n describedBy,\n reorderable,\n cloneable,\n deletable,\n compact = false,\n onMove,\n onClone,\n onRemove,\n virtual: { range, indexes, attach, item },\n context = { token: '' },\n resolve,\n extraActionsFor,\n}: RepeaterTableProps) {\n const t = useT()\n const fallbacks = useItemActionFallbacks()\n const resolveAction = (name: ItemActionName, index: number) =>\n resolve ? resolve(name, index) : resolveItemAction(name, {}, undefined, fallbacks[name])\n const hasExtraActions = Boolean(\n extraActionsFor && indexes.some((i) => extraActionsFor(i)?.length),\n )\n const hasControls = reorderable || cloneable || deletable || hasExtraActions\n const cellCount = columns.length + Number(hasControls)\n\n return (\n <div\n data-slot=\"repeater-table\"\n data-compact={compact || undefined}\n className=\"bg-card relative overflow-x-auto rounded-lg border shadow-xs\"\n >\n <table\n id={id}\n aria-labelledby={labelledBy}\n aria-describedby={describedBy}\n aria-rowcount={range.active ? count + 1 : undefined}\n className=\"w-full border-collapse text-sm\"\n >\n <thead className=\"bg-muted/40\">\n <tr>\n <TableColumnHeaders columns={columns} />\n {hasControls && (\n <th scope=\"col\" className=\"w-px px-2 py-2\">\n <span className=\"sr-only\">{t('actions', {}, 'Actions')}</span>\n </th>\n )}\n </tr>\n </thead>\n <tbody ref={attach}>\n <VirtualSpacerRow height={range.before} colSpan={cellCount} />\n {indexes.map((index) => {\n const cells = tableColumnCells(columns, schemaFor(index), prefixFor(index))\n\n return (\n <tr\n key={index}\n ref={item}\n data-index={index}\n aria-rowindex={range.active ? index + 2 : undefined}\n data-slot=\"repeater-row\"\n className=\"border-t align-top\"\n >\n {cells.map((cell, cellIndex) => (\n <td\n key={cell?.key ?? `empty-${cellIndex}`}\n className={cn(\n compact ? 'px-1.5 py-1' : 'px-3 py-2',\n '[&_[data-slot=label]]:sr-only',\n tableColumnAlignmentClass(columns[cellIndex] ?? {}),\n )}\n >\n {cell && <RenderComponent component={cell} />}\n </td>\n ))}\n {hasControls && (\n <td className={compact ? 'px-1 py-1' : 'px-2 py-2'}>\n <div className=\"flex items-center justify-end\">\n <ItemHeaderActions\n context={context}\n itemKey={String(index)}\n index={index}\n count={count}\n resolve={(name) => resolveAction(name, index)}\n extraActions={extraActionsFor?.(index)}\n buttons={reorderable}\n canClone={cloneable}\n canDelete={deletable}\n collapsible={false}\n collapsed={false}\n onMove={onMove}\n onClone={() => onClone(index)}\n onDelete={() => onRemove(index)}\n onToggle={() => undefined}\n />\n </div>\n </td>\n )}\n </tr>\n )\n })}\n <VirtualSpacerRow height={range.after} colSpan={cellCount} />\n </tbody>\n </table>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -10,7 +10,7 @@ import { FieldWrapper } from "../schema/field-wrapper.js";
|
|
|
10
10
|
import { Collapsible, CollapsibleContent } from "../ui/collapsible.js";
|
|
11
11
|
import { AddActionRow, CollapseAllActions, ItemActionButton, ItemHeaderActions, useItemActionFallbacks, useItemActions, useItemMoves, useRunItemAction } from "./item-actions.js";
|
|
12
12
|
import { RepeaterTable } from "./repeater-table.js";
|
|
13
|
-
import { VIRTUAL_ESTIMATES, appendRepeaterItem, cloneIndexedState, cloneRepeaterItem, fieldIds, gridColumnsClasses, isDeferredPlaceholder, moveIndexedState, moveRepeaterItem, removeIndexedState, removeRepeaterItem, reorderedItemKeys,
|
|
13
|
+
import { VIRTUAL_ESTIMATES, appendRepeaterItem, cloneIndexedState, cloneRepeaterItem, fieldIds, gridColumnsClasses, isDeferredPlaceholder, moveIndexedState, moveRepeaterItem, removeIndexedState, removeRepeaterItem, reorderedItemKeys, templateForIndex, virtualizeOptions } from "@alfakit/core";
|
|
14
14
|
import { useState } from "react";
|
|
15
15
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
16
16
|
//#region src/components/fields/repeater.tsx
|
|
@@ -59,7 +59,7 @@ function RepeaterFieldContent({ component }) {
|
|
|
59
59
|
const dropItem = (from, to) => runItemAction(resolve("reorder"), { items: reorderedItemKeys(items.length, from, to) }, () => moveItem(from, to));
|
|
60
60
|
const setAllCollapsed = (value) => setCollapsed(Object.fromEntries(items.map((_, index) => [index, value])));
|
|
61
61
|
const addItem = () => setValue(appendRepeaterItem(items, component.simple ? { [Object.keys(component.templateState)[0] ?? "value"]: null } : component.templateState));
|
|
62
|
-
const schemaFor = (index) => component.items[index]?.schema ??
|
|
62
|
+
const schemaFor = (index) => component.items[index]?.schema ?? templateForIndex(component.template, index);
|
|
63
63
|
const canAdd = component.addable && !locked && (component.maxItems == null || items.length < component.maxItems);
|
|
64
64
|
const canDelete = component.deletable && !locked && (component.minItems == null || items.length > component.minItems);
|
|
65
65
|
const draggable = component.reorderable && component.reorderableWithDragAndDrop !== false && !locked && resolve("reorder") !== null;
|