@alfakit/react 0.1.8 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/components/actions/action-modals.js +1 -1
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/dev-tools/dev-tools-inspector.js +1 -1
- package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
- package/dist/components/fields/checkbox-list.js +2 -2
- package/dist/components/fields/checkbox-list.js.map +1 -1
- package/dist/components/fields/rich-editor-extensions.js +14 -4
- package/dist/components/fields/rich-editor-extensions.js.map +1 -1
- package/dist/components/file-manager/file-manager.js +2 -0
- package/dist/components/file-manager/file-manager.js.map +1 -1
- package/dist/components/form/form-block.d.ts.map +1 -1
- package/dist/components/form/form-block.js +6 -3
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/layout/elements.d.ts.map +1 -1
- package/dist/components/layout/elements.js +16 -5
- package/dist/components/layout/elements.js.map +1 -1
- package/dist/components/layout/mixed-content.d.ts +6 -4
- package/dist/components/layout/mixed-content.d.ts.map +1 -1
- package/dist/components/layout/mixed-content.js +6 -4
- package/dist/components/layout/mixed-content.js.map +1 -1
- package/dist/components/layout/section.js +4 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/notifications/notifications-menu.d.ts.map +1 -1
- package/dist/components/notifications/notifications-menu.js +45 -17
- package/dist/components/notifications/notifications-menu.js.map +1 -1
- package/dist/components/panel/app-sidebar.js +3 -3
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/bottom-nav.js +2 -2
- package/dist/components/panel/bottom-nav.js.map +1 -1
- package/dist/components/panel/keyboard-shortcuts.js +1 -1
- package/dist/components/panel/keyboard-shortcuts.js.map +1 -1
- package/dist/components/panel/shortcuts-overlay.js +13 -10
- package/dist/components/panel/shortcuts-overlay.js.map +1 -1
- package/dist/components/table/column-manager.js +4 -4
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/spreadsheet.js +8 -1
- package/dist/components/table/spreadsheet.js.map +1 -1
- package/dist/components/translation-manager/translation-manager.js +1 -1
- package/dist/components/translation-manager/translation-manager.js.map +1 -1
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/tooltip.js +1 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/components/widgets/stats-overview.js +3 -3
- package/dist/components/widgets/stats-overview.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/layouts/top-nav-layout.js +3 -3
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +4 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.d.ts +0 -10
- package/dist/standalone/alfakit-component.d.ts.map +1 -1
- package/dist/standalone/alfakit-component.js +20 -9
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/runtime.js +2 -1
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone.css +12 -7
- package/dist/starter-kit/navigation.d.ts +6 -1
- package/dist/starter-kit/navigation.d.ts.map +1 -1
- package/dist/starter-kit/navigation.js +3 -2
- package/dist/starter-kit/navigation.js.map +1 -1
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +7 -2
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/styles.css +2 -1
- package/dist/themes.css +4 -0
- package/package.json +2 -2
- package/src/components/actions/action-modals.tsx +2 -0
- package/src/components/dev-tools/dev-tools-inspector.tsx +1 -1
- package/src/components/fields/checkbox-list.tsx +2 -2
- package/src/components/fields/rich-editor-extensions.tsx +24 -2
- package/src/components/file-manager/file-manager.tsx +3 -1
- package/src/components/form/form-block.tsx +15 -2
- package/src/components/layout/elements.tsx +27 -4
- package/src/components/layout/mixed-content.tsx +8 -4
- package/src/components/layout/section.tsx +2 -2
- package/src/components/notifications/notifications-menu.tsx +42 -1
- package/src/components/panel/app-sidebar.tsx +3 -3
- package/src/components/panel/bottom-nav.tsx +2 -2
- package/src/components/panel/keyboard-shortcuts.tsx +1 -1
- package/src/components/panel/shortcuts-overlay.tsx +29 -24
- package/src/components/table/column-manager.tsx +6 -4
- package/src/components/table/spreadsheet.tsx +18 -7
- package/src/components/translation-manager/translation-manager.tsx +1 -1
- package/src/components/ui/tooltip.tsx +10 -1
- package/src/components/widgets/stats-overview.tsx +6 -3
- package/src/layouts/top-nav-layout.tsx +3 -3
- package/src/pages/page.tsx +7 -3
- package/src/standalone/alfakit-component.tsx +18 -3
- package/src/standalone/runtime.ts +3 -0
- package/src/starter-kit/navigation.ts +12 -1
- package/src/starter-kit/starter-kit.tsx +10 -1
- package/src/styles.css +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spreadsheet.js","names":[],"sources":["../../../src/components/table/spreadsheet.tsx"],"sourcesContent":["import type {\n PastePlan,\n SpreadsheetAnnouncement,\n SpreadsheetSaveResult,\n TableSchema,\n} from '@alfakit/core'\nimport {\n SPREADSHEET_CLASSES,\n SPREADSHEET_ISSUE_MESSAGES,\n SpreadsheetGrid,\n VIRTUAL_ESTIMATES,\n inertiaReload,\n saveSpreadsheetChanges,\n spreadsheetAnnouncement,\n spreadsheetAria,\n spreadsheetCellClass,\n spreadsheetCellId,\n spreadsheetColumns,\n spreadsheetRows,\n spreadsheetSelectionSize,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { CheckIcon } from 'lucide-react'\nimport {\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ClipboardEvent,\n type KeyboardEvent,\n} from 'react'\nimport { useChoice, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { useVirtualList } from '../../hooks/use-virtual-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { VirtualSpacerRow } from '../schema/virtual-spacer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Spinner } from '../ui/spinner'\n\nexport interface SpreadsheetProps {\n table: TableSchema\n token: string\n component: string\n /** Leave spreadsheet mode. */\n onClose: () => void\n}\n\ninterface PastePreview {\n plan: PastePlan\n errors: SpreadsheetSaveResult['errors']\n validating: boolean\n}\n\n/**\n * Spreadsheet mode of a `Table::spreadsheet()` table (loaded on first use): a keyboard\n * grid over the page's records. Edits stay local (highlighted) until \"Save\" sends them in\n * one request; the server validates each cell with its column's rules and the record's\n * `update` policy, and its errors show on the cells.\n */\nexport function Spreadsheet({ table, token, component, onClose }: SpreadsheetProps) {\n const t = useT()\n const choice = useChoice()\n const direction = useDirection()\n const panel = usePanel()\n const baseId = useId()\n const columns = useMemo(() => spreadsheetColumns(table), [table])\n const rows = useMemo(\n () => spreadsheetRows(table.records ?? [], columns),\n [table.records, columns],\n )\n const [grid] = useState(() => new SpreadsheetGrid(rows, columns))\n const state = useStore(grid.store)\n const [announcement, setAnnouncement] = useState('')\n const [preview, setPreview] = useState<PastePreview | null>(null)\n const gridRef = useRef<HTMLTableElement | null>(null)\n const endpoint = panel.endpoints.tableCells\n\n // A reload (save, live update, polling) brings new records; edits of rows still on the\n // page are kept.\n useEffect(() => grid.setData(rows, columns), [grid, rows, columns])\n\n const {\n list,\n range,\n indexes,\n attach,\n item: attachRow,\n } = useVirtualList(rows.length, virtualizeOptions(table.virtualize, VIRTUAL_ESTIMATES.row))\n\n const announce = (message: SpreadsheetAnnouncement | null) => {\n if (message) setAnnouncement(choice(message.key, message.count, {}, message.fallback))\n }\n\n // Keep focus on the active cell (roving tabindex), scrolling a virtualised row into view.\n useEffect(() => {\n if (state.editing) return\n\n const root = gridRef.current\n\n if (!root || !root.contains(document.activeElement)) return\n\n const { row, col } = state.active\n const cell = document.getElementById(spreadsheetCellId(baseId, row, col))\n\n if (cell) {\n cell.focus({ preventScroll: false })\n cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n } else {\n list.scrollToIndex(row)\n }\n }, [state.active, state.editing, baseId, list])\n\n const save = async () => {\n const changes = grid.changes()\n\n if (!endpoint || changes.length === 0 || state.saving) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n\n if (result.saved.length > 0) {\n announce(spreadsheetAnnouncement('saved', result.saved.length))\n inertiaReload()\n }\n\n if (result.errors.length > 0) {\n announce(spreadsheetAnnouncement('failed', result.errors.length))\n }\n } catch (error) {\n showToast({\n id: 'alfakit-spreadsheet-failed',\n title: t('table.spreadsheet.request_failed', {}, \"Couldn't save the changes\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n } finally {\n grid.setSaving(false)\n }\n }\n\n const undoSave = async () => {\n const changes = grid.undoSaveChanges()\n\n if (!endpoint || changes.length === 0) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n announce(spreadsheetAnnouncement('undo', result.saved.length))\n\n if (result.saved.length > 0) inertiaReload()\n } finally {\n grid.setSaving(false)\n }\n }\n\n const close = () => {\n if (\n grid.dirtyCount > 0 &&\n !window.confirm(t('table.spreadsheet.discard_confirm', {}, 'Discard your unsaved changes?'))\n ) {\n return\n }\n\n grid.discard()\n onClose()\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLTableElement>) => {\n const result = grid.handleKey(event, direction)\n\n if (result === 'ignored') return\n\n event.preventDefault()\n\n if (result === 'save') {\n void save()\n } else if (result === 'undo') {\n announce(spreadsheetAnnouncement('undo', grid.undo()))\n } else if (result === 'fill' || result === 'clear') {\n announce(spreadsheetAnnouncement(result, grid.lastApplied))\n }\n\n if (result === 'commit' || result === 'cancel' || result === 'toggle') {\n // Back from the editor to the cell.\n requestAnimationFrame(() =>\n document\n .getElementById(spreadsheetCellId(baseId, grid.state.active.row, grid.state.active.col))\n ?.focus(),\n )\n }\n }\n\n const onCopy = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n event.clipboardData.setData('text/plain', grid.copyText())\n announce(spreadsheetAnnouncement('copy', spreadsheetSelectionSize(grid.bounds())))\n }\n\n const onPaste = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n\n const plan = grid.planPaste(event.clipboardData.getData('text/plain'))\n\n if (plan.changes.length === 0 && plan.issues.length === 0) return\n\n setPreview({ plan, errors: [], validating: Boolean(endpoint && plan.changes.length > 0) })\n\n if (!endpoint || plan.changes.length === 0) return\n\n // Validate the pasted values on the server before they are applied.\n void saveSpreadsheetChanges(\n endpoint,\n token,\n component,\n plan.changes.map((change) => {\n const record = grid.getRows()[change.row]\n const column = grid.getColumns()[change.col]\n\n return { record: record?.key ?? '', column: column?.name ?? '', state: change.value }\n }),\n { dryRun: true },\n )\n .then((result) =>\n setPreview(\n (current) => current && { ...current, errors: result.errors, validating: false },\n ),\n )\n .catch(() => setPreview((current) => current && { ...current, validating: false }))\n }\n\n const applyPaste = () => {\n if (!preview) return\n\n const count = grid.apply(preview.plan.changes)\n\n if (preview.errors.length > 0) grid.setErrors(preview.errors)\n\n announce(spreadsheetAnnouncement('paste', count))\n setPreview(null)\n }\n\n const issueText = (issue: PastePlan['issues'][number]) => {\n const message = SPREADSHEET_ISSUE_MESSAGES[issue.reason]\n\n return t(\n message.key,\n { text: issue.text, column: columns[issue.col]?.label ?? '', row: issue.row + 1 },\n message.fallback,\n )\n }\n\n const errorList = grid.errorCells()\n\n const helpId = `${baseId}-help`\n\n return (\n <div data-slot=\"spreadsheet\" className=\"flex flex-col gap-3\">\n <div data-slot=\"spreadsheet-toolbar\" className=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => void save()}\n >\n {state.saving && <Spinner />}\n {t('table.spreadsheet.save', {}, 'Save changes')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={!grid.canUndo || state.saving}\n onClick={() => announce(spreadsheetAnnouncement('undo', grid.undo()))}\n >\n {t('table.spreadsheet.undo', {}, 'Undo')}\n </Button>\n {grid.canUndoSave && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={state.saving}\n onClick={() => void undoSave()}\n >\n {t('table.spreadsheet.undo_save', {}, 'Undo last save')}\n </Button>\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => grid.discard()}\n >\n {t('table.spreadsheet.discard', {}, 'Discard')}\n </Button>\n <span className=\"text-muted-foreground text-sm\" aria-live=\"polite\">\n {grid.dirtyCount > 0 &&\n choice(\n 'table.spreadsheet.unsaved',\n grid.dirtyCount,\n {},\n ':count unsaved change|:count unsaved changes',\n )}\n {grid.errorCount > 0 && (\n <span className=\"text-destructive ms-2\">\n {choice(\n 'table.spreadsheet.errors',\n grid.errorCount,\n {},\n ':count error|:count errors',\n )}\n </span>\n )}\n </span>\n <span className=\"flex-1\" />\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={close}>\n {t('table.spreadsheet.close', {}, 'Done')}\n </Button>\n </div>\n <p id={helpId} className=\"sr-only\">\n {t(\n 'table.spreadsheet.help',\n {},\n 'Arrow keys move between cells. Enter or F2 edits a cell, Escape cancels, Tab moves to the next cell. Ctrl+C copies, Ctrl+V pastes, Ctrl+D fills down, Ctrl+Z undoes and Ctrl+S saves.',\n )}\n </p>\n <div data-surface=\"table\" className={SPREADSHEET_CLASSES.root} style={{ maxHeight: '70vh' }}>\n <table\n ref={gridRef}\n role=\"grid\"\n aria-label={table.heading ?? t('table.spreadsheet.label', {}, 'Spreadsheet')}\n aria-describedby={helpId}\n aria-rowcount={rows.length + 1}\n aria-colcount={columns.length}\n aria-multiselectable=\"true\"\n aria-busy={state.saving || undefined}\n data-slot=\"spreadsheet-grid\"\n className={SPREADSHEET_CLASSES.table}\n onKeyDown={onKeyDown}\n onCopy={onCopy}\n onPaste={onPaste}\n >\n <thead>\n <tr aria-rowindex={1}>\n {columns.map((column, col) => (\n <th\n key={column.name}\n role=\"columnheader\"\n scope=\"col\"\n aria-colindex={col + 1}\n className={SPREADSHEET_CLASSES.header}\n >\n {column.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody ref={attach}>\n <VirtualSpacerRow height={range.before} colSpan={columns.length} />\n {indexes.map((row) => {\n const record = rows[row]\n\n if (!record) return null\n\n return (\n <tr\n key={String(record.key)}\n ref={attachRow}\n data-index={row}\n aria-rowindex={row + 2}\n >\n {columns.map((column, col) => {\n const active = grid.isActive(row, col)\n const editing =\n state.editing && state.editing.row === row && state.editing.col === col\n const errors = grid.errorsAt(row, col)\n const value = grid.value(row, col)\n const editable = grid.isEditable(row, col)\n\n return (\n <td\n key={column.name}\n id={spreadsheetCellId(baseId, row, col)}\n role=\"gridcell\"\n {...spreadsheetAria(row, col)}\n tabIndex={active ? 0 : -1}\n aria-selected={grid.isSelected(row, col)}\n aria-readonly={!editable || undefined}\n aria-invalid={errors.length > 0 || undefined}\n title={errors.join(' ') || undefined}\n data-slot=\"spreadsheet-cell\"\n data-dirty={grid.isDirty(row, col) || undefined}\n data-active={active || undefined}\n className={spreadsheetCellClass(grid, row, col)}\n onMouseDown={(event) => {\n if (editing) return\n\n grid.moveTo(row, col, event.shiftKey)\n }}\n onDoubleClick={() => grid.beginEdit()}\n >\n {editing && column.kind === 'select' ? (\n <select\n autoFocus\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => {\n grid.updateDraft(event.target.value)\n grid.commitEdit()\n document.getElementById(spreadsheetCellId(baseId, row, col))?.focus()\n }}\n onBlur={() => grid.cancelEdit()}\n >\n <option value=\"\">{t('table.spreadsheet.no_value', {}, '—')}</option>\n {column.options?.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n >\n {option.label.replace(/<[^>]*>/g, '')}\n </option>\n ))}\n </select>\n ) : editing ? (\n <input\n autoFocus\n type={column.kind === 'number' ? 'text' : (column.inputType ?? 'text')}\n inputMode={column.kind === 'number' ? 'decimal' : undefined}\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => grid.updateDraft(event.target.value)}\n onBlur={() => grid.commitEdit()}\n />\n ) : column.kind === 'boolean' ? (\n <span className=\"inline-flex items-center gap-1\">\n <span\n aria-hidden=\"true\"\n className=\"border-input data-[checked=true]:bg-primary data-[checked=true]:text-primary-foreground inline-flex size-4 items-center justify-center rounded-[4px] border\"\n data-checked={Boolean(value)}\n >\n {Boolean(value) && <CheckIcon className=\"size-3\" />}\n </span>\n <span className=\"sr-only\">\n {value\n ? t('table.spreadsheet.yes', {}, 'Yes')\n : t('table.spreadsheet.no', {}, 'No')}\n </span>\n </span>\n ) : (\n grid.text(row, col)\n )}\n {errors.length > 0 && <span className=\"sr-only\">{errors.join(' ')}</span>}\n </td>\n )\n })}\n </tr>\n )\n })}\n <VirtualSpacerRow height={range.after} colSpan={columns.length} />\n </tbody>\n </table>\n {rows.length === 0 && (\n <p className=\"text-muted-foreground p-6 text-center text-sm\">\n {t('table.spreadsheet.empty', {}, 'No records to edit.')}\n </p>\n )}\n </div>\n {errorList.length > 0 && (\n <ul data-slot=\"spreadsheet-errors\" className=\"text-destructive flex flex-col gap-1 text-sm\">\n {errorList.map((error) => (\n <li key={error.key}>\n <button\n type=\"button\"\n className=\"text-start hover:underline\"\n onClick={() => {\n grid.moveTo(error.row, error.col)\n document.getElementById(spreadsheetCellId(baseId, error.row, error.col))?.focus()\n }}\n >\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: error.row + 1,\n column: columns[error.col]?.label ?? '',\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </button>\n </li>\n ))}\n </ul>\n )}\n <span\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n data-slot=\"spreadsheet-announcement\"\n >\n {announcement}\n </span>\n\n <Dialog open={preview !== null} onOpenChange={(open) => !open && setPreview(null)}>\n <DialogContent className=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{t('table.spreadsheet.paste_title', {}, 'Paste cells')}</DialogTitle>\n <DialogDescription>\n {choice(\n 'table.spreadsheet.paste_summary',\n preview?.plan.changes.length ?? 0,\n {},\n ':count cell will change|:count cells will change',\n )}\n {(preview?.plan.skipped ?? 0) > 0 &&\n ` ${choice('table.spreadsheet.paste_skipped', preview?.plan.skipped ?? 0, {}, ':count read-only cell is skipped|:count read-only cells are skipped')}`}\n </DialogDescription>\n </DialogHeader>\n <div data-slot=\"spreadsheet-paste\" className=\"flex flex-col gap-3\">\n {preview?.validating && (\n <p className=\"text-muted-foreground flex items-center gap-2 text-sm\">\n <Spinner />\n {t('table.spreadsheet.paste_validating', {}, 'Checking the values…')}\n </p>\n )}\n {preview && (preview.plan.issues.length > 0 || preview.errors.length > 0) && (\n <ul className=\"text-destructive flex max-h-48 flex-col gap-1 overflow-auto text-sm\">\n {preview.plan.issues.map((issue) => (\n <li key={`issue-${issue.row}-${issue.col}`}>{issueText(issue)}</li>\n ))}\n {preview.errors.map((error) => {\n const position = grid.position(error.record, error.column)\n const column = columns.find((item) => item.name === error.column)\n const row = position?.row ?? -1\n\n return (\n <li key={`error-${String(error.record)}-${error.column}`}>\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: row + 1,\n column: column?.label ?? error.column,\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </li>\n )\n })}\n </ul>\n )}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setPreview(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n disabled={!preview || preview.plan.changes.length === 0}\n onClick={applyPaste}\n >\n {t('table.spreadsheet.paste_apply', {}, 'Apply')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"spreadsheet.js","names":[],"sources":["../../../src/components/table/spreadsheet.tsx"],"sourcesContent":["import type {\n PastePlan,\n SpreadsheetAnnouncement,\n SpreadsheetSaveResult,\n TableSchema,\n} from '@alfakit/core'\nimport {\n SPREADSHEET_CLASSES,\n SPREADSHEET_ISSUE_MESSAGES,\n SpreadsheetGrid,\n VIRTUAL_ESTIMATES,\n inertiaReload,\n saveSpreadsheetChanges,\n spreadsheetAnnouncement,\n spreadsheetAria,\n spreadsheetCellClass,\n spreadsheetCellId,\n spreadsheetColumns,\n spreadsheetRows,\n spreadsheetSelectionSize,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { CheckIcon } from 'lucide-react'\nimport {\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ClipboardEvent,\n type KeyboardEvent,\n} from 'react'\nimport { useChoice, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { useVirtualList } from '../../hooks/use-virtual-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { VirtualSpacerRow } from '../schema/virtual-spacer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Spinner } from '../ui/spinner'\n\nexport interface SpreadsheetProps {\n table: TableSchema\n token: string\n component: string\n /** Leave spreadsheet mode. */\n onClose: () => void\n}\n\ninterface PastePreview {\n plan: PastePlan\n errors: SpreadsheetSaveResult['errors']\n validating: boolean\n}\n\n/**\n * Spreadsheet mode of a `Table::spreadsheet()` table (loaded on first use): a keyboard\n * grid over the page's records. Edits stay local (highlighted) until \"Save\" sends them in\n * one request; the server validates each cell with its column's rules and the record's\n * `update` policy, and its errors show on the cells.\n */\nexport function Spreadsheet({ table, token, component, onClose }: SpreadsheetProps) {\n const t = useT()\n const choice = useChoice()\n const direction = useDirection()\n const panel = usePanel()\n const baseId = useId()\n const columns = useMemo(() => spreadsheetColumns(table), [table])\n const rows = useMemo(\n () => spreadsheetRows(table.records ?? [], columns),\n [table.records, columns],\n )\n const [grid] = useState(() => new SpreadsheetGrid(rows, columns))\n const state = useStore(grid.store)\n const [announcement, setAnnouncement] = useState('')\n const [preview, setPreview] = useState<PastePreview | null>(null)\n const gridRef = useRef<HTMLTableElement | null>(null)\n const endpoint = panel.endpoints.tableCells\n\n // A reload (save, live update, polling) brings new records; edits of rows still on the\n // page are kept.\n useEffect(() => grid.setData(rows, columns), [grid, rows, columns])\n\n const {\n list,\n range,\n indexes,\n attach,\n item: attachRow,\n } = useVirtualList(rows.length, virtualizeOptions(table.virtualize, VIRTUAL_ESTIMATES.row))\n\n const announce = (message: SpreadsheetAnnouncement | null) => {\n if (message) setAnnouncement(choice(message.key, message.count, {}, message.fallback))\n }\n\n // Keep focus on the active cell (roving tabindex), scrolling a virtualised row into view.\n useEffect(() => {\n if (state.editing) return\n\n const root = gridRef.current\n\n if (!root || !root.contains(document.activeElement)) return\n\n const { row, col } = state.active\n const cell = document.getElementById(spreadsheetCellId(baseId, row, col))\n\n if (cell) {\n cell.focus({ preventScroll: false })\n cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n } else {\n list.scrollToIndex(row)\n }\n }, [state.active, state.editing, baseId, list])\n\n const save = async () => {\n const changes = grid.changes()\n\n if (!endpoint || changes.length === 0 || state.saving) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n\n if (result.saved.length > 0) {\n announce(spreadsheetAnnouncement('saved', result.saved.length))\n inertiaReload()\n }\n\n if (result.errors.length > 0) {\n announce(spreadsheetAnnouncement('failed', result.errors.length))\n }\n } catch (error) {\n showToast({\n id: 'alfakit-spreadsheet-failed',\n title: t('table.spreadsheet.request_failed', {}, \"Couldn't save the changes\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n } finally {\n grid.setSaving(false)\n }\n }\n\n const undoSave = async () => {\n const changes = grid.undoSaveChanges()\n\n if (!endpoint || changes.length === 0) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n announce(spreadsheetAnnouncement('undo', result.saved.length))\n\n if (result.saved.length > 0) inertiaReload()\n } finally {\n grid.setSaving(false)\n }\n }\n\n const close = () => {\n if (\n grid.dirtyCount > 0 &&\n !window.confirm(t('table.spreadsheet.discard_confirm', {}, 'Discard your unsaved changes?'))\n ) {\n return\n }\n\n grid.discard()\n onClose()\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLTableElement>) => {\n const result = grid.handleKey(event, direction)\n\n if (result === 'ignored') return\n\n event.preventDefault()\n\n if (result === 'save') {\n void save()\n } else if (result === 'undo') {\n announce(spreadsheetAnnouncement('undo', grid.undo()))\n } else if (result === 'fill' || result === 'clear') {\n announce(spreadsheetAnnouncement(result, grid.lastApplied))\n }\n\n if (result === 'commit' || result === 'cancel' || result === 'toggle') {\n // Back from the editor to the cell.\n requestAnimationFrame(() =>\n document\n .getElementById(spreadsheetCellId(baseId, grid.state.active.row, grid.state.active.col))\n ?.focus(),\n )\n }\n }\n\n const onCopy = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n event.clipboardData.setData('text/plain', grid.copyText())\n announce(spreadsheetAnnouncement('copy', spreadsheetSelectionSize(grid.bounds())))\n }\n\n const onPaste = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n\n const plan = grid.planPaste(event.clipboardData.getData('text/plain'))\n\n if (plan.changes.length === 0 && plan.issues.length === 0) return\n\n setPreview({ plan, errors: [], validating: Boolean(endpoint && plan.changes.length > 0) })\n\n if (!endpoint || plan.changes.length === 0) return\n\n // Validate the pasted values on the server before they are applied.\n void saveSpreadsheetChanges(\n endpoint,\n token,\n component,\n plan.changes.map((change) => {\n const record = grid.getRows()[change.row]\n const column = grid.getColumns()[change.col]\n\n return { record: record?.key ?? '', column: column?.name ?? '', state: change.value }\n }),\n { dryRun: true },\n )\n .then((result) =>\n setPreview(\n (current) => current && { ...current, errors: result.errors, validating: false },\n ),\n )\n .catch(() => setPreview((current) => current && { ...current, validating: false }))\n }\n\n const applyPaste = () => {\n if (!preview) return\n\n const count = grid.apply(preview.plan.changes)\n\n if (preview.errors.length > 0) grid.setErrors(preview.errors)\n\n announce(spreadsheetAnnouncement('paste', count))\n setPreview(null)\n }\n\n const issueText = (issue: PastePlan['issues'][number]) => {\n const message = SPREADSHEET_ISSUE_MESSAGES[issue.reason]\n\n return t(\n message.key,\n { text: issue.text, column: columns[issue.col]?.label ?? '', row: issue.row + 1 },\n message.fallback,\n )\n }\n\n const errorList = grid.errorCells()\n\n const helpId = `${baseId}-help`\n\n return (\n <div data-slot=\"spreadsheet\" className=\"flex flex-col gap-3\">\n <div data-slot=\"spreadsheet-toolbar\" className=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => void save()}\n >\n {state.saving && <Spinner />}\n {t('table.spreadsheet.save', {}, 'Save changes')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={!grid.canUndo || state.saving}\n onClick={() => announce(spreadsheetAnnouncement('undo', grid.undo()))}\n >\n {t('table.spreadsheet.undo', {}, 'Undo')}\n </Button>\n {grid.canUndoSave && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={state.saving}\n onClick={() => void undoSave()}\n >\n {t('table.spreadsheet.undo_save', {}, 'Undo last save')}\n </Button>\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => grid.discard()}\n >\n {t('table.spreadsheet.discard', {}, 'Discard')}\n </Button>\n <span className=\"text-muted-foreground text-sm\" aria-live=\"polite\">\n {grid.dirtyCount > 0 &&\n choice(\n 'table.spreadsheet.unsaved',\n grid.dirtyCount,\n {},\n ':count unsaved change|:count unsaved changes',\n )}\n {grid.errorCount > 0 && (\n <span className=\"text-destructive ms-2\">\n {choice(\n 'table.spreadsheet.errors',\n grid.errorCount,\n {},\n ':count error|:count errors',\n )}\n </span>\n )}\n </span>\n <span className=\"flex-1\" />\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={close}>\n {t('table.spreadsheet.close', {}, 'Done')}\n </Button>\n </div>\n <p id={helpId} className=\"sr-only\">\n {t(\n 'table.spreadsheet.help',\n {},\n 'Arrow keys move between cells. Enter or F2 edits a cell, Escape cancels, Tab moves to the next cell. Ctrl+C copies, Ctrl+V pastes, Ctrl+D fills down, Ctrl+Z undoes and Ctrl+S saves.',\n )}\n </p>\n <div data-surface=\"table\" className={SPREADSHEET_CLASSES.root} style={{ maxHeight: '70vh' }}>\n <table\n ref={gridRef}\n role=\"grid\"\n aria-label={table.heading ?? t('table.spreadsheet.label', {}, 'Spreadsheet')}\n aria-describedby={helpId}\n aria-rowcount={rows.length + 1}\n aria-colcount={columns.length}\n aria-multiselectable=\"true\"\n aria-busy={state.saving || undefined}\n data-slot=\"spreadsheet-grid\"\n className={SPREADSHEET_CLASSES.table}\n onKeyDown={onKeyDown}\n onCopy={onCopy}\n onPaste={onPaste}\n >\n <thead>\n <tr aria-rowindex={1}>\n {columns.map((column, col) => (\n <th\n key={column.name}\n role=\"columnheader\"\n scope=\"col\"\n aria-colindex={col + 1}\n className={SPREADSHEET_CLASSES.header}\n >\n {column.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody ref={attach}>\n <VirtualSpacerRow height={range.before} colSpan={columns.length} />\n {indexes.map((row) => {\n const record = rows[row]\n\n if (!record) return null\n\n return (\n <tr\n key={String(record.key)}\n ref={attachRow}\n data-index={row}\n aria-rowindex={row + 2}\n >\n {columns.map((column, col) => {\n const active = grid.isActive(row, col)\n const editing =\n state.editing && state.editing.row === row && state.editing.col === col\n const errors = grid.errorsAt(row, col)\n const value = grid.value(row, col)\n const editable = grid.isEditable(row, col)\n\n return (\n <td\n key={column.name}\n id={spreadsheetCellId(baseId, row, col)}\n role=\"gridcell\"\n {...spreadsheetAria(row, col)}\n tabIndex={active ? 0 : -1}\n aria-selected={grid.isSelected(row, col)}\n aria-readonly={!editable || undefined}\n aria-invalid={errors.length > 0 || undefined}\n title={errors.join(' ') || undefined}\n data-slot=\"spreadsheet-cell\"\n data-dirty={grid.isDirty(row, col) || undefined}\n data-active={active || undefined}\n className={spreadsheetCellClass(grid, row, col)}\n onMouseDown={(event) => {\n if (editing) return\n\n grid.moveTo(row, col, event.shiftKey)\n }}\n onDoubleClick={() => grid.beginEdit()}\n >\n {editing && column.kind === 'select' ? (\n <select\n autoFocus\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => {\n grid.updateDraft(event.target.value)\n grid.commitEdit()\n document.getElementById(spreadsheetCellId(baseId, row, col))?.focus()\n }}\n onBlur={() => grid.cancelEdit()}\n >\n <option value=\"\">{t('table.spreadsheet.no_value', {}, '—')}</option>\n {column.options?.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n >\n {option.label.replace(/<[^>]*>/g, '')}\n </option>\n ))}\n </select>\n ) : editing ? (\n <input\n autoFocus\n type={column.kind === 'number' ? 'text' : (column.inputType ?? 'text')}\n inputMode={column.kind === 'number' ? 'decimal' : undefined}\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => grid.updateDraft(event.target.value)}\n onBlur={() => grid.commitEdit()}\n />\n ) : column.kind === 'boolean' ? (\n <span className=\"inline-flex items-center gap-1\">\n <span\n aria-hidden=\"true\"\n className=\"border-input data-[checked=true]:bg-primary data-[checked=true]:text-primary-foreground inline-flex size-4 items-center justify-center rounded-[4px] border\"\n data-checked={Boolean(value)}\n >\n {Boolean(value) && <CheckIcon className=\"size-3\" />}\n </span>\n <span className=\"sr-only\">\n {value\n ? t('table.spreadsheet.yes', {}, 'Yes')\n : t('table.spreadsheet.no', {}, 'No')}\n </span>\n </span>\n ) : (\n grid.text(row, col)\n )}\n {errors.length > 0 && <span className=\"sr-only\">{errors.join(' ')}</span>}\n </td>\n )\n })}\n </tr>\n )\n })}\n <VirtualSpacerRow height={range.after} colSpan={columns.length} />\n </tbody>\n </table>\n {rows.length === 0 && (\n <p className=\"text-muted-foreground p-6 text-center text-sm\">\n {t('table.spreadsheet.empty', {}, 'No records to edit.')}\n </p>\n )}\n </div>\n {errorList.length > 0 && (\n <ul data-slot=\"spreadsheet-errors\" className=\"text-destructive flex flex-col gap-1 text-sm\">\n {errorList.map((error) => (\n <li key={error.key}>\n <button\n type=\"button\"\n className=\"text-start hover:underline\"\n onClick={() => {\n grid.moveTo(error.row, error.col)\n document.getElementById(spreadsheetCellId(baseId, error.row, error.col))?.focus()\n }}\n >\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: error.row + 1,\n column: columns[error.col]?.label ?? '',\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </button>\n </li>\n ))}\n </ul>\n )}\n <span\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n data-slot=\"spreadsheet-announcement\"\n >\n {announcement}\n </span>\n\n <Dialog open={preview !== null} onOpenChange={(open) => !open && setPreview(null)}>\n <DialogContent className=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{t('table.spreadsheet.paste_title', {}, 'Paste cells')}</DialogTitle>\n <DialogDescription>\n {/* One line per count: translations can't join two sentences. */}\n <span className=\"block\">\n {choice(\n 'table.spreadsheet.paste_summary',\n preview?.plan.changes.length ?? 0,\n {},\n ':count cell will change|:count cells will change',\n )}\n </span>\n {(preview?.plan.skipped ?? 0) > 0 && (\n <span className=\"block\" data-slot=\"spreadsheet-paste-skipped\">\n {choice(\n 'table.spreadsheet.paste_skipped',\n preview?.plan.skipped ?? 0,\n {},\n ':count read-only cell is skipped|:count read-only cells are skipped',\n )}\n </span>\n )}\n </DialogDescription>\n </DialogHeader>\n <div data-slot=\"spreadsheet-paste\" className=\"flex flex-col gap-3\">\n {preview?.validating && (\n <p className=\"text-muted-foreground flex items-center gap-2 text-sm\">\n <Spinner />\n {t('table.spreadsheet.paste_validating', {}, 'Checking the values…')}\n </p>\n )}\n {preview && (preview.plan.issues.length > 0 || preview.errors.length > 0) && (\n <ul className=\"text-destructive flex max-h-48 flex-col gap-1 overflow-auto text-sm\">\n {preview.plan.issues.map((issue) => (\n <li key={`issue-${issue.row}-${issue.col}`}>{issueText(issue)}</li>\n ))}\n {preview.errors.map((error) => {\n const position = grid.position(error.record, error.column)\n const column = columns.find((item) => item.name === error.column)\n const row = position?.row ?? -1\n\n return (\n <li key={`error-${String(error.record)}-${error.column}`}>\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: row + 1,\n column: column?.label ?? error.column,\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </li>\n )\n })}\n </ul>\n )}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setPreview(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n disabled={!preview || preview.plan.changes.length === 0}\n onClick={applyPaste}\n >\n {t('table.spreadsheet.paste_apply', {}, 'Apply')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -288,7 +288,7 @@ function TranslationManagerView({ schema }) {
|
|
|
288
288
|
className: "bg-muted/50",
|
|
289
289
|
children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
|
|
290
290
|
scope: "col",
|
|
291
|
-
className: "bg-
|
|
291
|
+
className: "bg-card sticky start-0 z-10 min-w-56 border-b [background-image:linear-gradient(var(--muted),var(--muted))] px-3 py-2 text-start font-medium",
|
|
292
292
|
children: t("translation-manager.key", {}, "Key")
|
|
293
293
|
}), locales.map((locale) => /* @__PURE__ */ jsx("th", {
|
|
294
294
|
scope: "col",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-manager.js","names":[],"sources":["../../../src/components/translation-manager/translation-manager.tsx"],"sourcesContent":["import type {\n TranslationCellRef,\n TranslationFilter,\n TranslationLocaleSchema,\n TranslationManagerEnvironment,\n TranslationManagerSchema,\n TranslationRowSchema,\n} from '@alfakit/core'\nimport { TranslationManagerController, clientErrorNotification } from '@alfakit/core'\nimport {\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n HistoryIcon,\n LanguagesIcon,\n SearchIcon,\n SparklesIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'\nimport { useActionRunner, useDirection, useFormatter, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Card, CardContent, CardHeader } from '../ui/card'\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\nimport { Textarea } from '../ui/textarea'\n\nconst SELECT =\n 'border-input focus-visible:border-ring focus-visible:ring-ring/50 h-9 min-w-0 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]'\n\nconst cellKey = (ref: TranslationCellRef) => `${ref.row}\\u0000${ref.locale}`\n\n/**\n * The translation editor of alfasix/alfakit-translation-manager: keys by locale with search,\n * filters, inline editing (plural forms, right-to-left inputs), keyboard navigation, AI\n * suggestion review and history. Everything is computed and saved by the server.\n */\nexport function TranslationManager({ component }: { component?: TranslationManagerSchema }) {\n return component ? (\n <TranslationManagerView key={component.actionComponent} schema={component} />\n ) : null\n}\n\nfunction TranslationManagerView({ schema }: { schema: TranslationManagerSchema }) {\n const runner = useActionRunner()\n const page = usePageActionContext()\n const t = useT()\n const direction = useDirection()\n const formatter = useFormatter()\n const labelId = useId()\n const hintId = useId()\n const environment = (): TranslationManagerEnvironment => ({\n runner,\n token: page.token,\n t,\n notify: (notifications) => notifications.forEach((notification) => showToast(notification)),\n error: (message) => showToast(clientErrorNotification('translation-manager', message)),\n })\n const [controller] = useState(() => new TranslationManagerController(schema, environment()))\n\n useEffect(() => {\n controller.setEnvironment(environment())\n })\n\n const first = useRef(true)\n\n useEffect(() => {\n if (first.current) {\n first.current = false\n\n return\n }\n\n controller.setSchema(schema)\n }, [controller, schema])\n\n useEffect(() => () => controller.dispose(), [controller])\n\n const state = useStore(controller.store)\n const cells = useRef(new Map<string, HTMLElement>())\n const editor = useRef<HTMLTextAreaElement | null>(null)\n const locales = controller.visibleLocales()\n const active =\n state.active && state.rows.some((row) => row.id === state.active?.row)\n ? state.active\n : state.rows[0] && locales[0]\n ? { row: state.rows[0].id, locale: locales[0].code }\n : null\n\n const focusCell = (ref: TranslationCellRef | undefined) => {\n if (!ref) return\n controller.focus(ref)\n requestAnimationFrame(() => cells.current.get(cellKey(ref))?.focus())\n }\n\n useEffect(() => {\n if (state.editing) {\n requestAnimationFrame(() => editor.current?.focus())\n }\n }, [state.editing])\n\n const onCellKeyDown = (ref: TranslationCellRef) => (event: KeyboardEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return\n\n const result = controller.handleCellKey(ref, event, direction)\n\n if (result.handled) {\n event.preventDefault()\n if (!controller.state.editing) focusCell(result.focus)\n }\n }\n\n const onEditorKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (\n event.key === 'Escape' ||\n (event.key === 'Enter' && !event.shiftKey) ||\n event.key === 'Tab'\n ) {\n event.preventDefault()\n void controller\n .handleEditorKey({\n key: event.key,\n shiftKey: event.shiftKey,\n isComposing: event.nativeEvent.isComposing,\n })\n .then((result) => focusCell(result.focus))\n }\n }\n\n const range = controller.range()\n const suggestionLocales = controller.suggestionLocales()\n const activeRow = active ? controller.row(active.row) : undefined\n const activeLocale = active ? controller.locale(active.locale) : undefined\n\n return (\n <Card className=\"gap-4 py-4\" data-slot=\"translation-manager\">\n <CardHeader className=\"flex flex-col gap-4 px-5\">\n <span id={labelId} className=\"sr-only\">\n {t('translation-manager.label', {}, 'Translations')}\n </span>\n <ul\n className=\"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6\"\n data-slot=\"translation-progress\"\n >\n {schema.locales.map((locale) => {\n const progress = state.progress[locale.code]\n const percent = progress ? progress.percent : 0\n\n return (\n <li\n key={locale.code}\n className=\"flex min-w-0 flex-col gap-1 rounded-md border px-3 py-2\"\n >\n <div className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"truncate font-medium\" lang={locale.code}>\n {locale.label}\n </span>\n <span className=\"text-muted-foreground font-mono text-xs\">{locale.code}</span>\n </div>\n <div\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={Math.round(percent * 100)}\n aria-label={t(\n 'translation-manager.progress_label',\n {\n locale: locale.label,\n translated: progress?.translated ?? 0,\n total: progress?.total ?? 0,\n },\n ':locale: :translated of :total translated',\n )}\n className=\"bg-muted h-1.5 overflow-hidden rounded-full\"\n >\n <div\n className=\"bg-primary h-full rounded-full transition-[width]\"\n style={{ width: `${percent * 100}%` }}\n />\n </div>\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.progress',\n { percent: formatter.percent(percent) },\n ':percent translated',\n )}\n </span>\n </li>\n )\n })}\n </ul>\n <div className=\"flex flex-wrap items-center gap-2\" data-slot=\"translation-toolbar\">\n <div className=\"relative min-w-48 flex-1\">\n <SearchIcon\n className=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={state.search}\n onChange={(event) => controller.setSearch(event.target.value)}\n placeholder={t('translation-manager.search', {}, 'Search keys and texts…')}\n aria-label={t('translation-manager.search_label', {}, 'Search translations')}\n className=\"ps-8\"\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.filter_label', {}, 'Show')}</span>\n <select\n className={SELECT}\n value={state.filter}\n onChange={(event) => controller.setFilter(event.target.value as TranslationFilter)}\n >\n {schema.filters.map((filter) => (\n <option key={filter} value={filter}>\n {t(`translation-manager.filters.${filter}`, {}, filter)}\n </option>\n ))}\n </select>\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.group_label', {}, 'Group')}</span>\n <select\n className={SELECT}\n value={state.group}\n onChange={(event) => controller.setGroup(event.target.value)}\n >\n <option value=\"\">{t('translation-manager.all_groups', {}, 'All groups')}</option>\n {schema.groups.map((group) => (\n <option key={group.value} value={group.value}>\n {group.label}\n </option>\n ))}\n </select>\n </label>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"h-9\">\n <LanguagesIcon aria-hidden=\"true\" />\n {t('translation-manager.locales_label', {}, 'Languages')}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {schema.locales.map((locale) => (\n <DropdownMenuCheckboxItem\n key={locale.code}\n checked={state.visibleLocales.includes(locale.code)}\n onCheckedChange={(checked) =>\n controller.toggleLocale(locale.code, checked === true)\n }\n onSelect={(event) => event.preventDefault()}\n >\n {locale.label}{' '}\n <span className=\"text-muted-foreground ms-auto font-mono text-xs\">\n {locale.code}\n </span>\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n {suggestionLocales.length > 0 && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md border px-3 py-2 text-sm\"\n data-slot=\"translation-suggestions\"\n >\n {suggestionLocales.map((code) => (\n <div key={code} className=\"flex flex-wrap items-center gap-2\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n <span className=\"flex-1 font-medium\">\n {t('translation-manager.suggestion', {}, 'AI suggestion')} ·{' '}\n {controller.locale(code)?.label}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void controller.decideAll(code, 'reject')}\n >\n {t('translation-manager.reject_all', {}, 'Reject all')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void controller.decideAll(code, 'accept')}\n >\n {t('translation-manager.accept_all', {}, 'Accept all without warnings')}\n </Button>\n </div>\n ))}\n </div>\n )}\n </CardHeader>\n <CardContent className=\"flex flex-col gap-4 px-5\">\n <p id={hintId} className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.keyboard',\n {},\n 'Arrow keys move between texts. Enter edits and saves, Shift + Enter adds a line break, Escape cancels.',\n )}\n </p>\n {state.rows.length === 0 ? (\n <p className=\"text-muted-foreground py-10 text-center text-sm\">\n {t('translation-manager.empty', {}, 'No translations match your search.')}\n </p>\n ) : (\n <div className=\"overflow-x-auto rounded-md border\" data-slot=\"translation-grid-scroll\">\n <table\n role=\"grid\"\n aria-labelledby={labelId}\n aria-describedby={hintId}\n aria-busy={state.loading || undefined}\n aria-rowcount={state.total + 1}\n className={cn('w-full border-collapse text-sm', state.loading && 'opacity-60')}\n data-slot=\"translation-grid\"\n >\n <thead className=\"bg-muted/50\">\n <tr>\n <th\n scope=\"col\"\n className=\"bg-muted sticky start-0 z-10 min-w-56 border-b px-3 py-2 text-start font-medium\"\n >\n {t('translation-manager.key', {}, 'Key')}\n </th>\n {locales.map((locale) => (\n <th\n key={locale.code}\n scope=\"col\"\n className=\"min-w-64 border-s border-b px-3 py-2 text-start font-medium\"\n >\n <span className=\"flex items-center gap-2\">\n <span lang={locale.code}>{locale.label}</span>\n <span className=\"text-muted-foreground font-mono text-xs\">\n {locale.code}\n </span>\n {locale.base && (\n <Badge variant=\"secondary\">\n {t('translation-manager.base', {}, 'Base')}\n </Badge>\n )}\n {!locale.editable && (\n <Badge variant=\"outline\">\n {t('translation-manager.read_only', {}, 'Read only')}\n </Badge>\n )}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {state.rows.map((row) => (\n <tr key={row.id} className=\"border-b last:border-b-0\" data-slot=\"translation-row\">\n <th\n scope=\"row\"\n className=\"bg-card sticky start-0 z-10 px-3 py-2 text-start align-top font-normal\"\n >\n <KeyCell row={row} />\n </th>\n {locales.map((locale) => {\n const ref = { row: row.id, locale: locale.code }\n const cell = row.cells[locale.code]\n const isActive = active?.row === row.id && active.locale === locale.code\n const isEditing =\n state.editing?.row === row.id && state.editing.locale === locale.code\n\n return (\n <td\n key={locale.code}\n ref={(element) => {\n if (element) cells.current.set(cellKey(ref), element)\n else cells.current.delete(cellKey(ref))\n }}\n role=\"gridcell\"\n tabIndex={isActive && !isEditing ? 0 : -1}\n aria-selected={isActive}\n aria-readonly={!controller.isEditable(ref) || undefined}\n aria-label={\n isEditing\n ? undefined\n : t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n }\n data-slot=\"translation-cell\"\n data-missing={cell?.value ? undefined : ''}\n data-changed={cell?.changed ? '' : undefined}\n data-active={isActive ? '' : undefined}\n onFocus={(event) =>\n event.target === event.currentTarget && controller.focus(ref)\n }\n onClick={() => {\n if (!isEditing) {\n controller.focus(ref)\n if (isActive) controller.startEdit(ref)\n }\n }}\n onDoubleClick={() => controller.startEdit(ref)}\n onKeyDown={onCellKeyDown(ref)}\n className={cn(\n 'hover:bg-accent/40 border-s px-3 py-2 align-top outline-none',\n isActive && 'ring-ring/50 ring-2 ring-inset',\n controller.isEditable(ref) && 'cursor-text',\n )}\n >\n {isEditing ? (\n <CellEditor\n row={row}\n locale={locale}\n draft={state.draft}\n forms={state.draftForms}\n labels={cell?.formLabels}\n saving={state.saving}\n error={state.error}\n editorRef={editor}\n onDraft={(value) => controller.setDraft(value)}\n onForm={(index, value) => controller.setDraftForm(index, value)}\n onKeyDown={onEditorKeyDown}\n onSave={() =>\n void controller.save().then((saved) => saved && focusCell(ref))\n }\n onCancel={() => {\n controller.cancelEdit()\n focusCell(ref)\n }}\n onCopyBase={() => controller.copyBase()}\n />\n ) : (\n <CellValue locale={locale} cell={cell} />\n )}\n </td>\n )\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n <div\n className=\"flex flex-wrap items-center justify-between gap-2 text-sm\"\n data-slot=\"translation-pagination\"\n >\n <span className=\"text-muted-foreground\">\n {t(\n 'translation-manager.pagination',\n { ...range, total: formatter.number(state.total) },\n ':from–:to of :total',\n )}\n </span>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.previous', {}, 'Previous page')}\n disabled={state.page <= 1 || state.loading}\n onClick={() => controller.goToPage(state.page - 1)}\n >\n <ChevronLeftIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n <span className=\"px-2 tabular-nums\">\n {t(\n 'translation-manager.page',\n { page: state.page, pages: state.lastPage },\n 'Page :page of :pages',\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.next', {}, 'Next page')}\n disabled={state.page >= state.lastPage || state.loading}\n onClick={() => controller.goToPage(state.page + 1)}\n >\n <ChevronRightIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n {activeRow && activeLocale ? (\n <section\n className=\"flex flex-col gap-3 rounded-md border p-4 text-sm\"\n data-slot=\"translation-details\"\n aria-label={t('translation-manager.details', {}, 'Details')}\n >\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <code className=\"font-mono text-xs break-all\">{activeRow.key}</code>\n <span className=\"text-muted-foreground\">· {activeLocale.label}</span>\n </div>\n {activeRow.placeholders && activeRow.placeholders.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1\">\n <span className=\"text-muted-foreground me-1\">\n {t('translation-manager.placeholders', {}, 'Placeholders')}\n </span>\n {activeRow.placeholders.map((placeholder) => (\n <Badge key={placeholder} variant=\"outline\" className=\"font-mono\">\n {placeholder}\n </Badge>\n ))}\n </div>\n )}\n {activeRow.files && activeRow.files.length > 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.used_in', {}, 'Used in')}:{' '}\n <span className=\"font-mono text-xs\">{activeRow.files.join(', ')}</span>\n </p>\n )}\n {activeRow.cells[activeLocale.code]?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive flex items-start gap-2\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n {warning}\n </p>\n ))}\n {activeRow.cells[activeLocale.code]?.suggestion && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md p-3\"\n data-slot=\"translation-suggestion\"\n >\n <span className=\"flex items-center gap-2 font-medium\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {activeRow.cells[activeLocale.code]?.suggestion?.value}\n </p>\n {activeRow.cells[activeLocale.code]?.suggestion?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive text-xs\">\n {warning}\n </p>\n ))}\n {activeLocale.editable && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'accept',\n )\n }\n >\n <CheckIcon aria-hidden=\"true\" />\n {t('translation-manager.accept', {}, 'Accept')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'reject',\n )\n }\n >\n <XIcon aria-hidden=\"true\" />\n {t('translation-manager.reject', {}, 'Reject')}\n </Button>\n </div>\n )}\n </div>\n )}\n {state.history?.row === activeRow.id && state.history.locale === activeLocale.code ? (\n <div className=\"flex flex-col gap-2\" data-slot=\"translation-history\">\n <span className=\"font-medium\">\n {t('translation-manager.history', {}, 'History')}\n </span>\n {state.history.entries?.length === 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.history_empty', {}, 'No changes yet.')}\n </p>\n )}\n <ol className=\"flex flex-col gap-2\">\n {state.history.entries?.map((entry) => (\n <li key={entry.id} className=\"border-s-2 ps-3\">\n <span className=\"text-muted-foreground text-xs\">\n {t(`translation-manager.sources.${entry.source}`, {}, entry.source)}\n {entry.user\n ? ` · ${t('translation-manager.by', { name: entry.user }, 'by :name')}`\n : ''}\n {entry.at ? ` · ${formatter.relative(entry.at)}` : ''}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {entry.new ?? (\n <em className=\"text-muted-foreground\">\n {t('translation-manager.empty_value', {}, 'Empty')}\n </em>\n )}\n </p>\n </li>\n ))}\n </ol>\n </div>\n ) : (\n <div>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={() =>\n void controller.loadHistory({ row: activeRow.id, locale: activeLocale.code })\n }\n >\n <HistoryIcon aria-hidden=\"true\" />\n {t('translation-manager.show_history', {}, 'Show history')}\n </Button>\n </div>\n )}\n </section>\n ) : null}\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state.announcement}\n </div>\n </CardContent>\n </Card>\n )\n}\n\nfunction KeyCell({ row }: { row: TranslationRowSchema }) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n <code className=\"font-mono text-xs break-all\">{row.key}</code>\n <div className=\"flex flex-wrap gap-1\">\n <span className=\"text-muted-foreground text-xs\">{row.groupLabel}</span>\n {row.undefined && (\n <Badge variant=\"destructive\">\n {t('translation-manager.undefined', {}, 'Not defined')}\n </Badge>\n )}\n {row.unused && (\n <Badge variant=\"outline\">{t('translation-manager.unused', {}, 'Unused')}</Badge>\n )}\n </div>\n </div>\n )\n}\n\nfunction CellValue({\n locale,\n cell,\n}: {\n locale: TranslationLocaleSchema\n cell: TranslationManagerSchema['rows'][number]['cells'][string] | undefined\n}) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n {cell?.value ? (\n <span\n className=\"line-clamp-4 whitespace-pre-wrap\"\n dir={locale.direction}\n lang={locale.code}\n >\n {cell.value}\n </span>\n ) : (\n <span className=\"text-muted-foreground italic\">\n {t('translation-manager.missing', {}, 'Missing')}\n </span>\n )}\n {(cell?.changed || cell?.warnings || cell?.suggestion) && (\n <span className=\"flex flex-wrap gap-1\">\n {cell.changed && (\n <Badge variant=\"secondary\">\n {t('translation-manager.changed', {}, 'Changed in the panel')}\n </Badge>\n )}\n {cell.warnings && (\n <Badge variant=\"destructive\" title={cell.warnings.join('\\n')}>\n <TriangleAlertIcon aria-hidden=\"true\" />\n {t('translation-manager.warnings', {}, 'Warnings')}\n </Badge>\n )}\n {cell.suggestion && (\n <Badge variant=\"outline\">\n <SparklesIcon aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </Badge>\n )}\n </span>\n )}\n </div>\n )\n}\n\nfunction CellEditor({\n row,\n locale,\n draft,\n forms,\n labels,\n saving,\n error,\n editorRef,\n onDraft,\n onForm,\n onKeyDown,\n onSave,\n onCancel,\n onCopyBase,\n}: {\n row: TranslationRowSchema\n locale: TranslationLocaleSchema\n draft: string\n forms: string[] | null\n labels: string[] | undefined\n saving: boolean\n error: string | null\n editorRef: { current: HTMLTextAreaElement | null }\n onDraft: (value: string) => void\n onForm: (index: number, value: string) => void\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => void\n onSave: () => void\n onCancel: () => void\n onCopyBase: () => void\n}) {\n const t = useT()\n const errorId = useId()\n const label = t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n\n return (\n <div\n className=\"flex flex-col gap-2\"\n data-slot=\"translation-editor\"\n onClick={(event) => event.stopPropagation()}\n >\n {forms ? (\n forms.map((form, index) => (\n <label key={index} className=\"flex flex-col gap-1\">\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.plural_form',\n { numbers: labels?.[index] ?? String(index + 1) },\n 'For :numbers',\n )}\n </span>\n <Textarea\n ref={index === 0 ? editorRef : undefined}\n value={form}\n rows={1}\n dir={locale.direction}\n lang={locale.code}\n aria-label={`${label} (${labels?.[index] ?? index + 1})`}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n className=\"min-h-9\"\n onChange={(event) => onForm(index, event.target.value)}\n onKeyDown={onKeyDown}\n />\n </label>\n ))\n ) : (\n <Textarea\n ref={editorRef}\n value={draft}\n rows={Math.min(8, Math.max(2, draft.split('\\n').length))}\n dir={locale.direction}\n lang={locale.code}\n aria-label={label}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n onChange={(event) => onDraft(event.target.value)}\n onKeyDown={onKeyDown}\n />\n )}\n {error && (\n <p id={errorId} className=\"text-destructive text-xs\">\n {error}\n </p>\n )}\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button type=\"button\" size=\"sm\" disabled={saving} onClick={onSave}>\n {saving\n ? t('translation-manager.saving', {}, 'Saving…')\n : t('translation-manager.save', {}, 'Save')}\n </Button>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={onCancel}>\n {t('translation-manager.cancel', {}, 'Cancel')}\n </Button>\n {!locale.base && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"ms-auto\"\n disabled={saving}\n onClick={onCopyBase}\n >\n <CopyIcon aria-hidden=\"true\" />\n {t('translation-manager.copy_base', {}, 'Copy the base text')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translation-manager.js","names":[],"sources":["../../../src/components/translation-manager/translation-manager.tsx"],"sourcesContent":["import type {\n TranslationCellRef,\n TranslationFilter,\n TranslationLocaleSchema,\n TranslationManagerEnvironment,\n TranslationManagerSchema,\n TranslationRowSchema,\n} from '@alfakit/core'\nimport { TranslationManagerController, clientErrorNotification } from '@alfakit/core'\nimport {\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n HistoryIcon,\n LanguagesIcon,\n SearchIcon,\n SparklesIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'\nimport { useActionRunner, useDirection, useFormatter, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Card, CardContent, CardHeader } from '../ui/card'\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\nimport { Textarea } from '../ui/textarea'\n\nconst SELECT =\n 'border-input focus-visible:border-ring focus-visible:ring-ring/50 h-9 min-w-0 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]'\n\nconst cellKey = (ref: TranslationCellRef) => `${ref.row}\\u0000${ref.locale}`\n\n/**\n * The translation editor of alfasix/alfakit-translation-manager: keys by locale with search,\n * filters, inline editing (plural forms, right-to-left inputs), keyboard navigation, AI\n * suggestion review and history. Everything is computed and saved by the server.\n */\nexport function TranslationManager({ component }: { component?: TranslationManagerSchema }) {\n return component ? (\n <TranslationManagerView key={component.actionComponent} schema={component} />\n ) : null\n}\n\nfunction TranslationManagerView({ schema }: { schema: TranslationManagerSchema }) {\n const runner = useActionRunner()\n const page = usePageActionContext()\n const t = useT()\n const direction = useDirection()\n const formatter = useFormatter()\n const labelId = useId()\n const hintId = useId()\n const environment = (): TranslationManagerEnvironment => ({\n runner,\n token: page.token,\n t,\n notify: (notifications) => notifications.forEach((notification) => showToast(notification)),\n error: (message) => showToast(clientErrorNotification('translation-manager', message)),\n })\n const [controller] = useState(() => new TranslationManagerController(schema, environment()))\n\n useEffect(() => {\n controller.setEnvironment(environment())\n })\n\n const first = useRef(true)\n\n useEffect(() => {\n if (first.current) {\n first.current = false\n\n return\n }\n\n controller.setSchema(schema)\n }, [controller, schema])\n\n useEffect(() => () => controller.dispose(), [controller])\n\n const state = useStore(controller.store)\n const cells = useRef(new Map<string, HTMLElement>())\n const editor = useRef<HTMLTextAreaElement | null>(null)\n const locales = controller.visibleLocales()\n const active =\n state.active && state.rows.some((row) => row.id === state.active?.row)\n ? state.active\n : state.rows[0] && locales[0]\n ? { row: state.rows[0].id, locale: locales[0].code }\n : null\n\n const focusCell = (ref: TranslationCellRef | undefined) => {\n if (!ref) return\n controller.focus(ref)\n requestAnimationFrame(() => cells.current.get(cellKey(ref))?.focus())\n }\n\n useEffect(() => {\n if (state.editing) {\n requestAnimationFrame(() => editor.current?.focus())\n }\n }, [state.editing])\n\n const onCellKeyDown = (ref: TranslationCellRef) => (event: KeyboardEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return\n\n const result = controller.handleCellKey(ref, event, direction)\n\n if (result.handled) {\n event.preventDefault()\n if (!controller.state.editing) focusCell(result.focus)\n }\n }\n\n const onEditorKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (\n event.key === 'Escape' ||\n (event.key === 'Enter' && !event.shiftKey) ||\n event.key === 'Tab'\n ) {\n event.preventDefault()\n void controller\n .handleEditorKey({\n key: event.key,\n shiftKey: event.shiftKey,\n isComposing: event.nativeEvent.isComposing,\n })\n .then((result) => focusCell(result.focus))\n }\n }\n\n const range = controller.range()\n const suggestionLocales = controller.suggestionLocales()\n const activeRow = active ? controller.row(active.row) : undefined\n const activeLocale = active ? controller.locale(active.locale) : undefined\n\n return (\n <Card className=\"gap-4 py-4\" data-slot=\"translation-manager\">\n <CardHeader className=\"flex flex-col gap-4 px-5\">\n <span id={labelId} className=\"sr-only\">\n {t('translation-manager.label', {}, 'Translations')}\n </span>\n <ul\n className=\"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6\"\n data-slot=\"translation-progress\"\n >\n {schema.locales.map((locale) => {\n const progress = state.progress[locale.code]\n const percent = progress ? progress.percent : 0\n\n return (\n <li\n key={locale.code}\n className=\"flex min-w-0 flex-col gap-1 rounded-md border px-3 py-2\"\n >\n <div className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"truncate font-medium\" lang={locale.code}>\n {locale.label}\n </span>\n <span className=\"text-muted-foreground font-mono text-xs\">{locale.code}</span>\n </div>\n <div\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={Math.round(percent * 100)}\n aria-label={t(\n 'translation-manager.progress_label',\n {\n locale: locale.label,\n translated: progress?.translated ?? 0,\n total: progress?.total ?? 0,\n },\n ':locale: :translated of :total translated',\n )}\n className=\"bg-muted h-1.5 overflow-hidden rounded-full\"\n >\n <div\n className=\"bg-primary h-full rounded-full transition-[width]\"\n style={{ width: `${percent * 100}%` }}\n />\n </div>\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.progress',\n { percent: formatter.percent(percent) },\n ':percent translated',\n )}\n </span>\n </li>\n )\n })}\n </ul>\n <div className=\"flex flex-wrap items-center gap-2\" data-slot=\"translation-toolbar\">\n <div className=\"relative min-w-48 flex-1\">\n <SearchIcon\n className=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={state.search}\n onChange={(event) => controller.setSearch(event.target.value)}\n placeholder={t('translation-manager.search', {}, 'Search keys and texts…')}\n aria-label={t('translation-manager.search_label', {}, 'Search translations')}\n className=\"ps-8\"\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.filter_label', {}, 'Show')}</span>\n <select\n className={SELECT}\n value={state.filter}\n onChange={(event) => controller.setFilter(event.target.value as TranslationFilter)}\n >\n {schema.filters.map((filter) => (\n <option key={filter} value={filter}>\n {t(`translation-manager.filters.${filter}`, {}, filter)}\n </option>\n ))}\n </select>\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.group_label', {}, 'Group')}</span>\n <select\n className={SELECT}\n value={state.group}\n onChange={(event) => controller.setGroup(event.target.value)}\n >\n <option value=\"\">{t('translation-manager.all_groups', {}, 'All groups')}</option>\n {schema.groups.map((group) => (\n <option key={group.value} value={group.value}>\n {group.label}\n </option>\n ))}\n </select>\n </label>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"h-9\">\n <LanguagesIcon aria-hidden=\"true\" />\n {t('translation-manager.locales_label', {}, 'Languages')}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {schema.locales.map((locale) => (\n <DropdownMenuCheckboxItem\n key={locale.code}\n checked={state.visibleLocales.includes(locale.code)}\n onCheckedChange={(checked) =>\n controller.toggleLocale(locale.code, checked === true)\n }\n onSelect={(event) => event.preventDefault()}\n >\n {locale.label}{' '}\n <span className=\"text-muted-foreground ms-auto font-mono text-xs\">\n {locale.code}\n </span>\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n {suggestionLocales.length > 0 && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md border px-3 py-2 text-sm\"\n data-slot=\"translation-suggestions\"\n >\n {suggestionLocales.map((code) => (\n <div key={code} className=\"flex flex-wrap items-center gap-2\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n <span className=\"flex-1 font-medium\">\n {t('translation-manager.suggestion', {}, 'AI suggestion')} ·{' '}\n {controller.locale(code)?.label}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void controller.decideAll(code, 'reject')}\n >\n {t('translation-manager.reject_all', {}, 'Reject all')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void controller.decideAll(code, 'accept')}\n >\n {t('translation-manager.accept_all', {}, 'Accept all without warnings')}\n </Button>\n </div>\n ))}\n </div>\n )}\n </CardHeader>\n <CardContent className=\"flex flex-col gap-4 px-5\">\n <p id={hintId} className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.keyboard',\n {},\n 'Arrow keys move between texts. Enter edits and saves, Shift + Enter adds a line break, Escape cancels.',\n )}\n </p>\n {state.rows.length === 0 ? (\n <p className=\"text-muted-foreground py-10 text-center text-sm\">\n {t('translation-manager.empty', {}, 'No translations match your search.')}\n </p>\n ) : (\n <div className=\"overflow-x-auto rounded-md border\" data-slot=\"translation-grid-scroll\">\n <table\n role=\"grid\"\n aria-labelledby={labelId}\n aria-describedby={hintId}\n aria-busy={state.loading || undefined}\n aria-rowcount={state.total + 1}\n className={cn('w-full border-collapse text-sm', state.loading && 'opacity-60')}\n data-slot=\"translation-grid\"\n >\n <thead className=\"bg-muted/50\">\n <tr>\n <th\n scope=\"col\"\n className=\"bg-card sticky start-0 z-10 min-w-56 border-b [background-image:linear-gradient(var(--muted),var(--muted))] px-3 py-2 text-start font-medium\"\n >\n {t('translation-manager.key', {}, 'Key')}\n </th>\n {locales.map((locale) => (\n <th\n key={locale.code}\n scope=\"col\"\n className=\"min-w-64 border-s border-b px-3 py-2 text-start font-medium\"\n >\n <span className=\"flex items-center gap-2\">\n <span lang={locale.code}>{locale.label}</span>\n <span className=\"text-muted-foreground font-mono text-xs\">\n {locale.code}\n </span>\n {locale.base && (\n <Badge variant=\"secondary\">\n {t('translation-manager.base', {}, 'Base')}\n </Badge>\n )}\n {!locale.editable && (\n <Badge variant=\"outline\">\n {t('translation-manager.read_only', {}, 'Read only')}\n </Badge>\n )}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {state.rows.map((row) => (\n <tr key={row.id} className=\"border-b last:border-b-0\" data-slot=\"translation-row\">\n <th\n scope=\"row\"\n className=\"bg-card sticky start-0 z-10 px-3 py-2 text-start align-top font-normal\"\n >\n <KeyCell row={row} />\n </th>\n {locales.map((locale) => {\n const ref = { row: row.id, locale: locale.code }\n const cell = row.cells[locale.code]\n const isActive = active?.row === row.id && active.locale === locale.code\n const isEditing =\n state.editing?.row === row.id && state.editing.locale === locale.code\n\n return (\n <td\n key={locale.code}\n ref={(element) => {\n if (element) cells.current.set(cellKey(ref), element)\n else cells.current.delete(cellKey(ref))\n }}\n role=\"gridcell\"\n tabIndex={isActive && !isEditing ? 0 : -1}\n aria-selected={isActive}\n aria-readonly={!controller.isEditable(ref) || undefined}\n aria-label={\n isEditing\n ? undefined\n : t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n }\n data-slot=\"translation-cell\"\n data-missing={cell?.value ? undefined : ''}\n data-changed={cell?.changed ? '' : undefined}\n data-active={isActive ? '' : undefined}\n onFocus={(event) =>\n event.target === event.currentTarget && controller.focus(ref)\n }\n onClick={() => {\n if (!isEditing) {\n controller.focus(ref)\n if (isActive) controller.startEdit(ref)\n }\n }}\n onDoubleClick={() => controller.startEdit(ref)}\n onKeyDown={onCellKeyDown(ref)}\n className={cn(\n 'hover:bg-accent/40 border-s px-3 py-2 align-top outline-none',\n isActive && 'ring-ring/50 ring-2 ring-inset',\n controller.isEditable(ref) && 'cursor-text',\n )}\n >\n {isEditing ? (\n <CellEditor\n row={row}\n locale={locale}\n draft={state.draft}\n forms={state.draftForms}\n labels={cell?.formLabels}\n saving={state.saving}\n error={state.error}\n editorRef={editor}\n onDraft={(value) => controller.setDraft(value)}\n onForm={(index, value) => controller.setDraftForm(index, value)}\n onKeyDown={onEditorKeyDown}\n onSave={() =>\n void controller.save().then((saved) => saved && focusCell(ref))\n }\n onCancel={() => {\n controller.cancelEdit()\n focusCell(ref)\n }}\n onCopyBase={() => controller.copyBase()}\n />\n ) : (\n <CellValue locale={locale} cell={cell} />\n )}\n </td>\n )\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n <div\n className=\"flex flex-wrap items-center justify-between gap-2 text-sm\"\n data-slot=\"translation-pagination\"\n >\n <span className=\"text-muted-foreground\">\n {t(\n 'translation-manager.pagination',\n { ...range, total: formatter.number(state.total) },\n ':from–:to of :total',\n )}\n </span>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.previous', {}, 'Previous page')}\n disabled={state.page <= 1 || state.loading}\n onClick={() => controller.goToPage(state.page - 1)}\n >\n <ChevronLeftIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n <span className=\"px-2 tabular-nums\">\n {t(\n 'translation-manager.page',\n { page: state.page, pages: state.lastPage },\n 'Page :page of :pages',\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.next', {}, 'Next page')}\n disabled={state.page >= state.lastPage || state.loading}\n onClick={() => controller.goToPage(state.page + 1)}\n >\n <ChevronRightIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n {activeRow && activeLocale ? (\n <section\n className=\"flex flex-col gap-3 rounded-md border p-4 text-sm\"\n data-slot=\"translation-details\"\n aria-label={t('translation-manager.details', {}, 'Details')}\n >\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <code className=\"font-mono text-xs break-all\">{activeRow.key}</code>\n <span className=\"text-muted-foreground\">· {activeLocale.label}</span>\n </div>\n {activeRow.placeholders && activeRow.placeholders.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1\">\n <span className=\"text-muted-foreground me-1\">\n {t('translation-manager.placeholders', {}, 'Placeholders')}\n </span>\n {activeRow.placeholders.map((placeholder) => (\n <Badge key={placeholder} variant=\"outline\" className=\"font-mono\">\n {placeholder}\n </Badge>\n ))}\n </div>\n )}\n {activeRow.files && activeRow.files.length > 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.used_in', {}, 'Used in')}:{' '}\n <span className=\"font-mono text-xs\">{activeRow.files.join(', ')}</span>\n </p>\n )}\n {activeRow.cells[activeLocale.code]?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive flex items-start gap-2\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n {warning}\n </p>\n ))}\n {activeRow.cells[activeLocale.code]?.suggestion && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md p-3\"\n data-slot=\"translation-suggestion\"\n >\n <span className=\"flex items-center gap-2 font-medium\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {activeRow.cells[activeLocale.code]?.suggestion?.value}\n </p>\n {activeRow.cells[activeLocale.code]?.suggestion?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive text-xs\">\n {warning}\n </p>\n ))}\n {activeLocale.editable && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'accept',\n )\n }\n >\n <CheckIcon aria-hidden=\"true\" />\n {t('translation-manager.accept', {}, 'Accept')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'reject',\n )\n }\n >\n <XIcon aria-hidden=\"true\" />\n {t('translation-manager.reject', {}, 'Reject')}\n </Button>\n </div>\n )}\n </div>\n )}\n {state.history?.row === activeRow.id && state.history.locale === activeLocale.code ? (\n <div className=\"flex flex-col gap-2\" data-slot=\"translation-history\">\n <span className=\"font-medium\">\n {t('translation-manager.history', {}, 'History')}\n </span>\n {state.history.entries?.length === 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.history_empty', {}, 'No changes yet.')}\n </p>\n )}\n <ol className=\"flex flex-col gap-2\">\n {state.history.entries?.map((entry) => (\n <li key={entry.id} className=\"border-s-2 ps-3\">\n <span className=\"text-muted-foreground text-xs\">\n {t(`translation-manager.sources.${entry.source}`, {}, entry.source)}\n {entry.user\n ? ` · ${t('translation-manager.by', { name: entry.user }, 'by :name')}`\n : ''}\n {entry.at ? ` · ${formatter.relative(entry.at)}` : ''}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {entry.new ?? (\n <em className=\"text-muted-foreground\">\n {t('translation-manager.empty_value', {}, 'Empty')}\n </em>\n )}\n </p>\n </li>\n ))}\n </ol>\n </div>\n ) : (\n <div>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={() =>\n void controller.loadHistory({ row: activeRow.id, locale: activeLocale.code })\n }\n >\n <HistoryIcon aria-hidden=\"true\" />\n {t('translation-manager.show_history', {}, 'Show history')}\n </Button>\n </div>\n )}\n </section>\n ) : null}\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state.announcement}\n </div>\n </CardContent>\n </Card>\n )\n}\n\nfunction KeyCell({ row }: { row: TranslationRowSchema }) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n <code className=\"font-mono text-xs break-all\">{row.key}</code>\n <div className=\"flex flex-wrap gap-1\">\n <span className=\"text-muted-foreground text-xs\">{row.groupLabel}</span>\n {row.undefined && (\n <Badge variant=\"destructive\">\n {t('translation-manager.undefined', {}, 'Not defined')}\n </Badge>\n )}\n {row.unused && (\n <Badge variant=\"outline\">{t('translation-manager.unused', {}, 'Unused')}</Badge>\n )}\n </div>\n </div>\n )\n}\n\nfunction CellValue({\n locale,\n cell,\n}: {\n locale: TranslationLocaleSchema\n cell: TranslationManagerSchema['rows'][number]['cells'][string] | undefined\n}) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n {cell?.value ? (\n <span\n className=\"line-clamp-4 whitespace-pre-wrap\"\n dir={locale.direction}\n lang={locale.code}\n >\n {cell.value}\n </span>\n ) : (\n <span className=\"text-muted-foreground italic\">\n {t('translation-manager.missing', {}, 'Missing')}\n </span>\n )}\n {(cell?.changed || cell?.warnings || cell?.suggestion) && (\n <span className=\"flex flex-wrap gap-1\">\n {cell.changed && (\n <Badge variant=\"secondary\">\n {t('translation-manager.changed', {}, 'Changed in the panel')}\n </Badge>\n )}\n {cell.warnings && (\n <Badge variant=\"destructive\" title={cell.warnings.join('\\n')}>\n <TriangleAlertIcon aria-hidden=\"true\" />\n {t('translation-manager.warnings', {}, 'Warnings')}\n </Badge>\n )}\n {cell.suggestion && (\n <Badge variant=\"outline\">\n <SparklesIcon aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </Badge>\n )}\n </span>\n )}\n </div>\n )\n}\n\nfunction CellEditor({\n row,\n locale,\n draft,\n forms,\n labels,\n saving,\n error,\n editorRef,\n onDraft,\n onForm,\n onKeyDown,\n onSave,\n onCancel,\n onCopyBase,\n}: {\n row: TranslationRowSchema\n locale: TranslationLocaleSchema\n draft: string\n forms: string[] | null\n labels: string[] | undefined\n saving: boolean\n error: string | null\n editorRef: { current: HTMLTextAreaElement | null }\n onDraft: (value: string) => void\n onForm: (index: number, value: string) => void\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => void\n onSave: () => void\n onCancel: () => void\n onCopyBase: () => void\n}) {\n const t = useT()\n const errorId = useId()\n const label = t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n\n return (\n <div\n className=\"flex flex-col gap-2\"\n data-slot=\"translation-editor\"\n onClick={(event) => event.stopPropagation()}\n >\n {forms ? (\n forms.map((form, index) => (\n <label key={index} className=\"flex flex-col gap-1\">\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.plural_form',\n { numbers: labels?.[index] ?? String(index + 1) },\n 'For :numbers',\n )}\n </span>\n <Textarea\n ref={index === 0 ? editorRef : undefined}\n value={form}\n rows={1}\n dir={locale.direction}\n lang={locale.code}\n aria-label={`${label} (${labels?.[index] ?? index + 1})`}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n className=\"min-h-9\"\n onChange={(event) => onForm(index, event.target.value)}\n onKeyDown={onKeyDown}\n />\n </label>\n ))\n ) : (\n <Textarea\n ref={editorRef}\n value={draft}\n rows={Math.min(8, Math.max(2, draft.split('\\n').length))}\n dir={locale.direction}\n lang={locale.code}\n aria-label={label}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n onChange={(event) => onDraft(event.target.value)}\n onKeyDown={onKeyDown}\n />\n )}\n {error && (\n <p id={errorId} className=\"text-destructive text-xs\">\n {error}\n </p>\n )}\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button type=\"button\" size=\"sm\" disabled={saving} onClick={onSave}>\n {saving\n ? t('translation-manager.saving', {}, 'Saving…')\n : t('translation-manager.save', {}, 'Save')}\n </Button>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={onCancel}>\n {t('translation-manager.cancel', {}, 'Cancel')}\n </Button>\n {!locale.base && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"ms-auto\"\n disabled={saving}\n onClick={onCopyBase}\n >\n <CopyIcon aria-hidden=\"true\" />\n {t('translation-manager.copy_base', {}, 'Copy the base text')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.d.ts","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"mappings":";;;iBAIS,kBACP,kBACG,SACF,sBAAsBA,QAAiB,4BAAS,IAAA;iBAU1CC,eAAa,SAAS,sBAAsBD,QAAiB,wBAAK,IAAA;iBAIlE,oBAAoB,SAAS,sBAAsBA,QAAiB,2BAAQ,IAAA;
|
|
1
|
+
{"version":3,"file":"tooltip.d.ts","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"mappings":";;;iBAIS,kBACP,kBACG,SACF,sBAAsBA,QAAiB,4BAAS,IAAA;iBAU1CC,eAAa,SAAS,sBAAsBD,QAAiB,wBAAK,IAAA;iBAIlE,oBAAoB,SAAS,sBAAsBA,QAAiB,2BAAQ,IAAA;iBAa5E,iBACP,WACA,YACA,aACG,SACF,sBAAsBA,QAAiB,2BAAQ,IAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"sourcesContent":["import { Direction, Tooltip as TooltipPrimitive } from 'radix-ui'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/utils'\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: ComponentProps<typeof TooltipPrimitive.Provider>) {\n return (\n <TooltipPrimitive.Provider\n data-slot=\"tooltip-provider\"\n delayDuration={delayDuration}\n {...props}\n />\n )\n}\n\nfunction Tooltip({ ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {\n return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n}\n\nfunction TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {\n return <TooltipPrimitive.Trigger data-slot
|
|
1
|
+
{"version":3,"file":"tooltip.js","names":["TooltipPrimitive","Tooltip"],"sources":["../../../src/components/ui/tooltip.tsx"],"sourcesContent":["import { Direction, Tooltip as TooltipPrimitive } from 'radix-ui'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/utils'\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: ComponentProps<typeof TooltipPrimitive.Provider>) {\n return (\n <TooltipPrimitive.Provider\n data-slot=\"tooltip-provider\"\n delayDuration={delayDuration}\n {...props}\n />\n )\n}\n\nfunction Tooltip({ ...props }: ComponentProps<typeof TooltipPrimitive.Root>) {\n return <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n}\n\nfunction TooltipTrigger({ ...props }: ComponentProps<typeof TooltipPrimitive.Trigger>) {\n // With asChild the child keeps its own data-slot (an action's `button`, which the\n // button-shape and touch-target CSS select).\n // (The key must be left out, not set to undefined: Slot passes it on and the child's own\n // `{...props}` would erase its data-slot.)\n return (\n <TooltipPrimitive.Trigger\n {...(props.asChild ? {} : { 'data-slot': 'tooltip-trigger' })}\n {...props}\n />\n )\n}\n\nfunction TooltipContent({\n className,\n sideOffset = 4,\n children,\n ...props\n}: ComponentProps<typeof TooltipPrimitive.Content>) {\n // Portaled outside Alfakit's root: carry the reading direction (starter kit pages and\n // standalone components leave <html dir> to the host).\n const dir = Direction.useDirection()\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n dir={dir}\n data-slot=\"tooltip-content\"\n sideOffset={sideOffset}\n className={cn(\n 'bg-foreground text-background animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance',\n className,\n )}\n {...props}\n >\n {children}\n <TooltipPrimitive.Arrow className=\"bg-foreground fill-foreground z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]\" />\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n )\n}\n\nexport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger }\n"],"mappings":";;;;AAIA,SAAS,gBAAgB,EACvB,gBAAgB,GAChB,GAAG,SACgD;CACnD,OACE,oBAACA,QAAiB,UAAlB;EACE,aAAU;EACK;EACf,GAAI;CACL,CAAA;AAEL;AAEA,SAASC,UAAQ,EAAE,GAAG,SAAuD;CAC3E,OAAO,oBAACD,QAAiB,MAAlB;EAAuB,aAAU;EAAU,GAAI;CAAQ,CAAA;AAChE;AAEA,SAAS,eAAe,EAAE,GAAG,SAA0D;CAKrF,OACE,oBAACA,QAAiB,SAAlB;EACE,GAAK,MAAM,UAAU,CAAC,IAAI,EAAE,aAAa,kBAAkB;EAC3D,GAAI;CACL,CAAA;AAEL;AAEA,SAAS,eAAe,EACtB,WACA,aAAa,GACb,UACA,GAAG,SAC+C;CAGlD,MAAM,MAAM,UAAU,aAAa;CACnC,OACE,oBAACA,QAAiB,QAAlB,EAAA,UACE,qBAACA,QAAiB,SAAlB;EACO;EACL,aAAU;EACE;EACZ,WAAW,GACT,qaACA,SACF;EACA,GAAI;EARN,UAAA,CAUG,UACD,oBAACA,QAAiB,OAAlB,EAAwB,WAAU,qGAAsG,CAAA,CAChH;CACH,CAAA,EAAA,CAAA;AAE7B"}
|
|
@@ -50,7 +50,7 @@ function StatCard({ stat }) {
|
|
|
50
50
|
children: [
|
|
51
51
|
/* @__PURE__ */ jsx("span", {
|
|
52
52
|
className: "text-muted-foreground text-sm font-medium",
|
|
53
|
-
children: stat.label
|
|
53
|
+
children: /* @__PURE__ */ jsx("bdi", { children: stat.label })
|
|
54
54
|
}),
|
|
55
55
|
(stat.value === null || stat.value === "") && stat.placeholder ? /* @__PURE__ */ jsx("span", {
|
|
56
56
|
className: "text-muted-foreground text-2xl font-semibold tracking-tight",
|
|
@@ -58,7 +58,7 @@ function StatCard({ stat }) {
|
|
|
58
58
|
children: stat.placeholder
|
|
59
59
|
}) : /* @__PURE__ */ jsx("span", {
|
|
60
60
|
className: cn("text-2xl font-semibold tracking-tight", stat.color && textColorClasses[stat.color]),
|
|
61
|
-
children: stat.value
|
|
61
|
+
children: /* @__PURE__ */ jsx("bdi", { children: stat.value })
|
|
62
62
|
}),
|
|
63
63
|
stat.description && /* @__PURE__ */ jsxs("span", {
|
|
64
64
|
className: cn("inline-flex items-center gap-1 text-xs", stat.descriptionColor ? textColorClasses[stat.descriptionColor] : "text-muted-foreground"),
|
|
@@ -67,7 +67,7 @@ function StatCard({ stat }) {
|
|
|
67
67
|
name: stat.descriptionIcon,
|
|
68
68
|
className: "size-3.5"
|
|
69
69
|
}),
|
|
70
|
-
stat.description,
|
|
70
|
+
/* @__PURE__ */ jsx("bdi", { children: stat.description }),
|
|
71
71
|
stat.descriptionIcon && stat.descriptionIconPosition === "after" && /* @__PURE__ */ jsx(Icon, {
|
|
72
72
|
name: stat.descriptionIcon,
|
|
73
73
|
className: "size-3.5"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stats-overview.js","names":[],"sources":["../../../src/components/widgets/stats-overview.tsx"],"sourcesContent":["import type { StatSchema, StatsOverviewWidgetSchema } from '@alfakit/core'\nimport { gridColumnsClasses, widgetPollTarget } from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { useEffect } from 'react'\nimport { usePollComponent } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n cssColor,\n iconBackgroundColorClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { useLiveUpdates } from '../realtime/live-records-pill'\nimport { Card } from '../ui/card'\n\nexport function StatsOverviewWidget({ widget }: { widget: StatsOverviewWidgetSchema }) {\n const columns = widget.columns ?? {\n default: 1,\n md: Math.min(widget.stats.length, 2),\n lg: Math.min(widget.stats.length, 4),\n }\n\n // Polls refresh only this widget, share one request with other components on the same\n // interval and pause while the tab is hidden.\n const pollTarget = widgetPollTarget(widget)\n const pollComponent = usePollComponent()\n\n useEffect(\n () => pollComponent(pollTarget, widget.pollingInterval),\n [pollComponent, pollTarget, widget.pollingInterval],\n )\n\n // `live()`: refresh when the followed records change (broadcast), else poll as a fallback.\n useLiveUpdates(widget.live, pollTarget, { polling: Boolean(widget.pollingInterval) })\n\n return (\n <section\n className=\"flex flex-col gap-3\"\n {...(widget.heading ? { 'aria-label': widget.heading } : {})}\n >\n {(widget.heading || widget.description) && (\n <div>\n {widget.heading && <h3 className=\"text-base font-semibold\">{widget.heading}</h3>}\n {widget.description && (\n <p className=\"text-muted-foreground text-sm\">{widget.description}</p>\n )}\n </div>\n )}\n <div className={cn('grid gap-4', gridColumnsClasses(columns))}>\n {widget.stats.map((stat, index) => (\n <StatCard key={index} stat={stat} />\n ))}\n </div>\n </section>\n )\n}\n\nfunction StatCard({ stat }: { stat: StatSchema }) {\n const content = (\n <Card\n {...extraProps(\n stat.extraAttributes,\n cn(\n 'relative gap-2 overflow-hidden px-5 py-4',\n stat.url && 'hover:bg-accent/40 transition-colors',\n ),\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-muted-foreground text-sm font-medium\">{stat.label}</span>\n {(stat.value === null || stat.value === '') && stat.placeholder ? (\n <span\n className=\"text-muted-foreground text-2xl font-semibold tracking-tight\"\n data-slot=\"stat-placeholder\"\n >\n {stat.placeholder}\n </span>\n ) : (\n <span\n className={cn(\n 'text-2xl font-semibold tracking-tight',\n stat.color && textColorClasses[stat.color],\n )}\n >\n {stat.value}
|
|
1
|
+
{"version":3,"file":"stats-overview.js","names":[],"sources":["../../../src/components/widgets/stats-overview.tsx"],"sourcesContent":["import type { StatSchema, StatsOverviewWidgetSchema } from '@alfakit/core'\nimport { gridColumnsClasses, widgetPollTarget } from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { useEffect } from 'react'\nimport { usePollComponent } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n cssColor,\n iconBackgroundColorClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { useLiveUpdates } from '../realtime/live-records-pill'\nimport { Card } from '../ui/card'\n\nexport function StatsOverviewWidget({ widget }: { widget: StatsOverviewWidgetSchema }) {\n const columns = widget.columns ?? {\n default: 1,\n md: Math.min(widget.stats.length, 2),\n lg: Math.min(widget.stats.length, 4),\n }\n\n // Polls refresh only this widget, share one request with other components on the same\n // interval and pause while the tab is hidden.\n const pollTarget = widgetPollTarget(widget)\n const pollComponent = usePollComponent()\n\n useEffect(\n () => pollComponent(pollTarget, widget.pollingInterval),\n [pollComponent, pollTarget, widget.pollingInterval],\n )\n\n // `live()`: refresh when the followed records change (broadcast), else poll as a fallback.\n useLiveUpdates(widget.live, pollTarget, { polling: Boolean(widget.pollingInterval) })\n\n return (\n <section\n className=\"flex flex-col gap-3\"\n {...(widget.heading ? { 'aria-label': widget.heading } : {})}\n >\n {(widget.heading || widget.description) && (\n <div>\n {widget.heading && <h3 className=\"text-base font-semibold\">{widget.heading}</h3>}\n {widget.description && (\n <p className=\"text-muted-foreground text-sm\">{widget.description}</p>\n )}\n </div>\n )}\n <div className={cn('grid gap-4', gridColumnsClasses(columns))}>\n {widget.stats.map((stat, index) => (\n <StatCard key={index} stat={stat} />\n ))}\n </div>\n </section>\n )\n}\n\nfunction StatCard({ stat }: { stat: StatSchema }) {\n const content = (\n <Card\n {...extraProps(\n stat.extraAttributes,\n cn(\n 'relative gap-2 overflow-hidden px-5 py-4',\n stat.url && 'hover:bg-accent/40 transition-colors',\n ),\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex flex-col gap-1\">\n <span className=\"text-muted-foreground text-sm font-medium\">\n <bdi>{stat.label}</bdi>\n </span>\n {(stat.value === null || stat.value === '') && stat.placeholder ? (\n <span\n className=\"text-muted-foreground text-2xl font-semibold tracking-tight\"\n data-slot=\"stat-placeholder\"\n >\n {stat.placeholder}\n </span>\n ) : (\n <span\n className={cn(\n 'text-2xl font-semibold tracking-tight',\n stat.color && textColorClasses[stat.color],\n )}\n >\n <bdi>{stat.value}</bdi>\n </span>\n )}\n {stat.description && (\n <span\n className={cn(\n 'inline-flex items-center gap-1 text-xs',\n stat.descriptionColor\n ? textColorClasses[stat.descriptionColor]\n : 'text-muted-foreground',\n )}\n >\n {stat.descriptionIcon && stat.descriptionIconPosition !== 'after' && (\n <Icon name={stat.descriptionIcon} className=\"size-3.5\" />\n )}\n {/* Mixed-direction text (a Latin \"-3% this month\" in Arabic) keeps its order. */}\n <bdi>{stat.description}</bdi>\n {stat.descriptionIcon && stat.descriptionIconPosition === 'after' && (\n <Icon name={stat.descriptionIcon} className=\"size-3.5\" />\n )}\n </span>\n )}\n </div>\n {stat.icon && (\n <span\n className={cn(\n 'flex size-9 shrink-0 items-center justify-center rounded-md',\n colorClass(iconBackgroundColorClasses, stat.color, 'gray'),\n )}\n >\n <Icon name={stat.icon} className=\"size-5\" />\n </span>\n )}\n </div>\n {stat.chart && stat.chart.length > 1 && (\n <Sparkline data={stat.chart} color={cssColor(stat.chartColor ?? stat.color)} />\n )}\n </Card>\n )\n\n if (stat.url) {\n return stat.openUrlInNewTab ? (\n <a href={stat.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {content}\n <NewTabHint />\n </a>\n ) : (\n <Link href={stat.url}>{content}</Link>\n )\n }\n\n return content\n}\n\nfunction Sparkline({ data, color }: { data: number[]; color: string }) {\n const width = 200\n const height = 40\n const min = Math.min(...data)\n const max = Math.max(...data)\n const range = max - min || 1\n const points = data.map(\n (value, index) =>\n [\n (index / (data.length - 1)) * width,\n height - ((value - min) / range) * (height - 4) - 2,\n ] as const,\n )\n const path = points\n .map(([x, y], index) => `${index === 0 ? 'M' : 'L'}${x.toFixed(1)},${y.toFixed(1)}`)\n .join(' ')\n const area = `${path} L${width},${height} L0,${height} Z`\n\n return (\n <svg\n viewBox={`0 0 ${width} ${height}`}\n className=\"mt-1 h-10 w-full\"\n preserveAspectRatio=\"none\"\n aria-hidden=\"true\"\n data-slot=\"stat-chart\"\n >\n <path\n d={area}\n fill={color}\n data-slot=\"stat-chart-area\"\n style={{ opacity: 'var(--stat-chart-fill-opacity, 0.12)' }}\n />\n <path\n d={path}\n fill=\"none\"\n stroke={color}\n strokeLinejoin=\"round\"\n strokeLinecap=\"round\"\n data-slot=\"stat-chart-line\"\n style={{ strokeWidth: 'var(--stat-chart-stroke-width, 2)' }}\n />\n </svg>\n )\n}\n"],"mappings":"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"}
|
package/dist/eject-manifest.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package": "@alfakit/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"adapter": "react",
|
|
5
|
-
"generatedAt": "2026-10-
|
|
5
|
+
"generatedAt": "2026-10-25T11:21:24.983Z",
|
|
6
6
|
"components": {
|
|
7
7
|
"TextInput": {
|
|
8
8
|
"file": "src/components/fields/text-input.tsx",
|
|
@@ -76,7 +76,7 @@ function TopNavMenu({ label, icon, items, active, attributes }) {
|
|
|
76
76
|
href: item.url ?? "#",
|
|
77
77
|
children: [item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon }), /* @__PURE__ */ jsx(NavLabel, { item })]
|
|
78
78
|
})
|
|
79
|
-
}, item.label))
|
|
79
|
+
}, item.url ?? item.label))
|
|
80
80
|
})] });
|
|
81
81
|
}
|
|
82
82
|
function TopNavGroup({ group }) {
|
|
@@ -88,7 +88,7 @@ function TopNavGroup({ group }) {
|
|
|
88
88
|
extraTopbarAttributes: void 0
|
|
89
89
|
} })
|
|
90
90
|
});
|
|
91
|
-
if (!group.label) return /* @__PURE__ */ jsx(Fragment$1, { children: group.items.map((item) => /* @__PURE__ */ jsx(TopNavLink, { item }, item.label)) });
|
|
91
|
+
if (!group.label) return /* @__PURE__ */ jsx(Fragment$1, { children: group.items.map((item) => /* @__PURE__ */ jsx(TopNavLink, { item }, item.url ?? item.label)) });
|
|
92
92
|
return /* @__PURE__ */ jsx(TopNavMenu, {
|
|
93
93
|
label: group.label,
|
|
94
94
|
icon: group.icon,
|
|
@@ -112,7 +112,7 @@ function MobileNav({ onNavigate }) {
|
|
|
112
112
|
"data-active": item.active ? "true" : void 0,
|
|
113
113
|
className: "hover:bg-accent data-[active=true]:bg-accent flex items-center gap-2 rounded-md px-2 py-2 font-medium [&_svg]:size-4",
|
|
114
114
|
children: [item.icon && /* @__PURE__ */ jsx(Icon, { name: item.icon }), /* @__PURE__ */ jsx(NavLabel, { item })]
|
|
115
|
-
}, item.label))]
|
|
115
|
+
}, item.url ?? item.label))]
|
|
116
116
|
}, group.label ?? index))
|
|
117
117
|
});
|
|
118
118
|
}
|