@alfakit/react 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/actions/action-button.js +13 -1
  3. package/dist/components/actions/action-button.js.map +1 -1
  4. package/dist/components/dev-tools/dev-tools-inspector.js +3 -3
  5. package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
  6. package/dist/components/fields/checkbox-list.js +2 -2
  7. package/dist/components/fields/checkbox-list.js.map +1 -1
  8. package/dist/components/fields/money-input.d.ts +5 -0
  9. package/dist/components/fields/money-input.d.ts.map +1 -1
  10. package/dist/components/fields/money-input.js +2 -5
  11. package/dist/components/fields/money-input.js.map +1 -1
  12. package/dist/components/fields/rich-editor-extensions.js +14 -4
  13. package/dist/components/fields/rich-editor-extensions.js.map +1 -1
  14. package/dist/components/file-manager/file-manager.js +2 -0
  15. package/dist/components/file-manager/file-manager.js.map +1 -1
  16. package/dist/components/layout/elements.d.ts.map +1 -1
  17. package/dist/components/layout/elements.js +16 -5
  18. package/dist/components/layout/elements.js.map +1 -1
  19. package/dist/components/layout/mixed-content.d.ts +6 -4
  20. package/dist/components/layout/mixed-content.d.ts.map +1 -1
  21. package/dist/components/layout/mixed-content.js +6 -4
  22. package/dist/components/layout/mixed-content.js.map +1 -1
  23. package/dist/components/layout/section.js +4 -2
  24. package/dist/components/layout/section.js.map +1 -1
  25. package/dist/components/notifications/notifications-menu.d.ts.map +1 -1
  26. package/dist/components/notifications/notifications-menu.js +45 -17
  27. package/dist/components/notifications/notifications-menu.js.map +1 -1
  28. package/dist/components/panel/lazy-placeholder.d.ts +1 -0
  29. package/dist/components/panel/lazy-placeholder.d.ts.map +1 -1
  30. package/dist/components/panel/lazy-placeholder.js +38 -4
  31. package/dist/components/panel/lazy-placeholder.js.map +1 -1
  32. package/dist/components/panel/shortcuts-overlay.js +13 -10
  33. package/dist/components/panel/shortcuts-overlay.js.map +1 -1
  34. package/dist/components/table/cells.js +1 -1
  35. package/dist/components/table/cells.js.map +1 -1
  36. package/dist/components/table/column-manager.js +4 -4
  37. package/dist/components/table/column-manager.js.map +1 -1
  38. package/dist/components/table/spreadsheet.js +8 -1
  39. package/dist/components/table/spreadsheet.js.map +1 -1
  40. package/dist/components/translation-manager/translation-manager.js +1 -1
  41. package/dist/components/translation-manager/translation-manager.js.map +1 -1
  42. package/dist/components/ui/calendar.d.ts.map +1 -1
  43. package/dist/components/ui/calendar.js +7 -0
  44. package/dist/components/ui/calendar.js.map +1 -1
  45. package/dist/components/widgets/dashboard-customizer.js +2 -2
  46. package/dist/components/widgets/dashboard-customizer.js.map +1 -1
  47. package/dist/components/widgets/stats-overview.js +3 -3
  48. package/dist/components/widgets/stats-overview.js.map +1 -1
  49. package/dist/components/widgets/widgets-block.js +2 -2
  50. package/dist/components/widgets/widgets-block.js.map +1 -1
  51. package/dist/create-alfakit.d.ts.map +1 -1
  52. package/dist/create-alfakit.js +3 -2
  53. package/dist/create-alfakit.js.map +1 -1
  54. package/dist/eject-manifest.json +4 -2
  55. package/dist/index.d.ts +2 -2
  56. package/dist/index.js +2 -2
  57. package/dist/pages/page.d.ts.map +1 -1
  58. package/dist/pages/page.js +4 -7
  59. package/dist/pages/page.js.map +1 -1
  60. package/dist/standalone/alfakit-component.d.ts +0 -10
  61. package/dist/standalone/alfakit-component.d.ts.map +1 -1
  62. package/dist/standalone/alfakit-component.js +5 -4
  63. package/dist/standalone/alfakit-component.js.map +1 -1
  64. package/dist/standalone/runtime.js +2 -1
  65. package/dist/standalone/runtime.js.map +1 -1
  66. package/dist/standalone.css +23 -1
  67. package/dist/starter-kit/index.d.ts +2 -2
  68. package/dist/starter-kit/index.js +2 -2
  69. package/dist/starter-kit/navigation.d.ts +6 -1
  70. package/dist/starter-kit/navigation.d.ts.map +1 -1
  71. package/dist/starter-kit/navigation.js +3 -2
  72. package/dist/starter-kit/navigation.js.map +1 -1
  73. package/dist/starter-kit/starter-kit.d.ts +9 -0
  74. package/dist/starter-kit/starter-kit.d.ts.map +1 -1
  75. package/dist/starter-kit/starter-kit.js +22 -3
  76. package/dist/starter-kit/starter-kit.js.map +1 -1
  77. package/dist/styles.css +2 -1
  78. package/dist/themes.css +4 -0
  79. package/package.json +2 -2
  80. package/src/components/actions/action-button.tsx +15 -1
  81. package/src/components/dev-tools/dev-tools-inspector.tsx +7 -2
  82. package/src/components/fields/checkbox-list.tsx +2 -2
  83. package/src/components/fields/money-input.tsx +3 -14
  84. package/src/components/fields/rich-editor-extensions.tsx +24 -2
  85. package/src/components/file-manager/file-manager.tsx +3 -1
  86. package/src/components/layout/elements.tsx +27 -4
  87. package/src/components/layout/mixed-content.tsx +8 -4
  88. package/src/components/layout/section.tsx +2 -2
  89. package/src/components/notifications/notifications-menu.tsx +42 -1
  90. package/src/components/panel/lazy-placeholder.tsx +41 -1
  91. package/src/components/panel/shortcuts-overlay.tsx +29 -24
  92. package/src/components/table/cells.tsx +3 -1
  93. package/src/components/table/column-manager.tsx +6 -4
  94. package/src/components/table/spreadsheet.tsx +18 -7
  95. package/src/components/translation-manager/translation-manager.tsx +1 -1
  96. package/src/components/ui/calendar.tsx +8 -0
  97. package/src/components/widgets/dashboard-customizer.tsx +2 -2
  98. package/src/components/widgets/stats-overview.tsx +6 -3
  99. package/src/components/widgets/widgets-block.tsx +2 -2
  100. package/src/create-alfakit.tsx +9 -1
  101. package/src/pages/page.tsx +7 -3
  102. package/src/standalone/alfakit-component.tsx +9 -4
  103. package/src/standalone/runtime.ts +3 -0
  104. package/src/starter-kit/index.ts +1 -0
  105. package/src/starter-kit/navigation.ts +12 -1
  106. package/src/starter-kit/starter-kit.tsx +25 -2
  107. package/src/styles.css +2 -1
@@ -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":"calendar.d.ts","names":[],"sources":["../../../src/components/ui/calendar.tsx"],"mappings":";;;iBAMS,WACP,WACA,YACA,oBACG,SACF,sBAAsB,6BAAU,IAAA"}
1
+ {"version":3,"file":"calendar.d.ts","names":[],"sources":["../../../src/components/ui/calendar.tsx"],"mappings":";;;iBAOS,WACP,WACA,YACA,oBACG,SACF,sBAAsB,6BAAU,IAAA"}
@@ -1,3 +1,4 @@
1
+ import { useT } from "../../context.js";
1
2
  import { cn } from "../../lib/utils.js";
2
3
  import { buttonVariants } from "./button.js";
3
4
  import { jsx } from "react/jsx-runtime";
@@ -5,8 +6,14 @@ import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
5
6
  import { DayPicker } from "react-day-picker";
6
7
  //#region src/components/ui/calendar.tsx
7
8
  function Calendar({ className, classNames, showOutsideDays = true, ...props }) {
9
+ const t = useT();
8
10
  return /* @__PURE__ */ jsx(DayPicker, {
9
11
  showOutsideDays,
12
+ labels: {
13
+ labelPrevious: () => t("form.previous_month", {}, "Previous month"),
14
+ labelNext: () => t("form.next_month", {}, "Next month"),
15
+ ...props.labels
16
+ },
10
17
  className: cn("p-3", className),
11
18
  classNames: {
12
19
  months: "flex flex-col sm:flex-row gap-2",
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.js","names":[],"sources":["../../../src/components/ui/calendar.tsx"],"sourcesContent":["import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { DayPicker } from 'react-day-picker'\nimport { cn } from '../../lib/utils'\nimport { buttonVariants } from './button'\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n ...props\n}: ComponentProps<typeof DayPicker>) {\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn('p-3', className)}\n classNames={{\n months: 'flex flex-col sm:flex-row gap-2',\n month: 'flex flex-col gap-4',\n month_caption: 'flex justify-center pt-1 relative items-center w-full',\n caption_label: 'text-sm font-medium',\n nav: 'flex items-center gap-1 absolute inset-x-0 top-3 justify-between px-3 pointer-events-none',\n button_previous: cn(\n buttonVariants({ variant: 'outline' }),\n 'pointer-events-auto size-7 bg-transparent p-0 opacity-50 hover:opacity-100',\n ),\n button_next: cn(\n buttonVariants({ variant: 'outline' }),\n 'pointer-events-auto size-7 bg-transparent p-0 opacity-50 hover:opacity-100',\n ),\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',\n week: 'flex w-full mt-2',\n day: 'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-e-md first:[&:has([aria-selected])]:rounded-s-md last:[&:has([aria-selected])]:rounded-e-md',\n day_button: cn(\n buttonVariants({ variant: 'ghost' }),\n 'size-8 p-0 font-normal aria-selected:opacity-100',\n ),\n range_start:\n 'day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground',\n range_end: 'day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground',\n selected:\n 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground rounded-md',\n today: 'bg-accent text-accent-foreground rounded-md',\n outside: 'day-outside text-muted-foreground aria-selected:text-muted-foreground',\n disabled: 'text-muted-foreground opacity-50',\n range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',\n hidden: 'invisible',\n ...classNames,\n }}\n components={{\n Chevron: ({ orientation, className: chevronClassName, ...rest }) =>\n orientation === 'left' ? (\n <ChevronLeftIcon className={cn('size-4', chevronClassName)} {...rest} />\n ) : (\n <ChevronRightIcon className={cn('size-4', chevronClassName)} {...rest} />\n ),\n }}\n {...props}\n />\n )\n}\n\nexport { Calendar }\n"],"mappings":";;;;;;AAMA,SAAS,SAAS,EAChB,WACA,YACA,kBAAkB,MAClB,GAAG,SACgC;CACnC,OACE,oBAAC,WAAD;EACmB;EACjB,WAAW,GAAG,OAAO,SAAS;EAC9B,YAAY;GACV,QAAQ;GACR,OAAO;GACP,eAAe;GACf,eAAe;GACf,KAAK;GACL,iBAAiB,GACf,eAAe,EAAE,SAAS,UAAU,CAAC,GACrC,4EACF;GACA,aAAa,GACX,eAAe,EAAE,SAAS,UAAU,CAAC,GACrC,4EACF;GACA,YAAY;GACZ,UAAU;GACV,SAAS;GACT,MAAM;GACN,KAAK;GACL,YAAY,GACV,eAAe,EAAE,SAAS,QAAQ,CAAC,GACnC,kDACF;GACA,aACE;GACF,WAAW;GACX,UACE;GACF,OAAO;GACP,SAAS;GACT,UAAU;GACV,cAAc;GACd,QAAQ;GACR,GAAG;EACL;EACA,YAAY,EACV,UAAU,EAAE,aAAa,WAAW,kBAAkB,GAAG,WACvD,gBAAgB,SACd,oBAAC,iBAAD;GAAiB,WAAW,GAAG,UAAU,gBAAgB;GAAG,GAAI;EAAO,CAAA,IAEvE,oBAAC,kBAAD;GAAkB,WAAW,GAAG,UAAU,gBAAgB;GAAG,GAAI;EAAO,CAAA,EAE9E;EACA,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"calendar.js","names":[],"sources":["../../../src/components/ui/calendar.tsx"],"sourcesContent":["import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { DayPicker } from 'react-day-picker'\nimport { useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { buttonVariants } from './button'\n\nfunction Calendar({\n className,\n classNames,\n showOutsideDays = true,\n ...props\n}: ComponentProps<typeof DayPicker>) {\n const t = useT()\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n labels={{\n labelPrevious: () => t('form.previous_month', {}, 'Previous month'),\n labelNext: () => t('form.next_month', {}, 'Next month'),\n ...props.labels,\n }}\n className={cn('p-3', className)}\n classNames={{\n months: 'flex flex-col sm:flex-row gap-2',\n month: 'flex flex-col gap-4',\n month_caption: 'flex justify-center pt-1 relative items-center w-full',\n caption_label: 'text-sm font-medium',\n nav: 'flex items-center gap-1 absolute inset-x-0 top-3 justify-between px-3 pointer-events-none',\n button_previous: cn(\n buttonVariants({ variant: 'outline' }),\n 'pointer-events-auto size-7 bg-transparent p-0 opacity-50 hover:opacity-100',\n ),\n button_next: cn(\n buttonVariants({ variant: 'outline' }),\n 'pointer-events-auto size-7 bg-transparent p-0 opacity-50 hover:opacity-100',\n ),\n month_grid: 'w-full border-collapse',\n weekdays: 'flex',\n weekday: 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',\n week: 'flex w-full mt-2',\n day: 'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-range-end)]:rounded-e-md first:[&:has([aria-selected])]:rounded-s-md last:[&:has([aria-selected])]:rounded-e-md',\n day_button: cn(\n buttonVariants({ variant: 'ghost' }),\n 'size-8 p-0 font-normal aria-selected:opacity-100',\n ),\n range_start:\n 'day-range-start aria-selected:bg-primary aria-selected:text-primary-foreground',\n range_end: 'day-range-end aria-selected:bg-primary aria-selected:text-primary-foreground',\n selected:\n 'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground rounded-md',\n today: 'bg-accent text-accent-foreground rounded-md',\n outside: 'day-outside text-muted-foreground aria-selected:text-muted-foreground',\n disabled: 'text-muted-foreground opacity-50',\n range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',\n hidden: 'invisible',\n ...classNames,\n }}\n components={{\n Chevron: ({ orientation, className: chevronClassName, ...rest }) =>\n orientation === 'left' ? (\n <ChevronLeftIcon className={cn('size-4', chevronClassName)} {...rest} />\n ) : (\n <ChevronRightIcon className={cn('size-4', chevronClassName)} {...rest} />\n ),\n }}\n {...props}\n />\n )\n}\n\nexport { Calendar }\n"],"mappings":";;;;;;;AAOA,SAAS,SAAS,EAChB,WACA,YACA,kBAAkB,MAClB,GAAG,SACgC;CACnC,MAAM,IAAI,KAAK;CAEf,OACE,oBAAC,WAAD;EACmB;EACjB,QAAQ;GACN,qBAAqB,EAAE,uBAAuB,CAAC,GAAG,gBAAgB;GAClE,iBAAiB,EAAE,mBAAmB,CAAC,GAAG,YAAY;GACtD,GAAG,MAAM;EACX;EACA,WAAW,GAAG,OAAO,SAAS;EAC9B,YAAY;GACV,QAAQ;GACR,OAAO;GACP,eAAe;GACf,eAAe;GACf,KAAK;GACL,iBAAiB,GACf,eAAe,EAAE,SAAS,UAAU,CAAC,GACrC,4EACF;GACA,aAAa,GACX,eAAe,EAAE,SAAS,UAAU,CAAC,GACrC,4EACF;GACA,YAAY;GACZ,UAAU;GACV,SAAS;GACT,MAAM;GACN,KAAK;GACL,YAAY,GACV,eAAe,EAAE,SAAS,QAAQ,CAAC,GACnC,kDACF;GACA,aACE;GACF,WAAW;GACX,UACE;GACF,OAAO;GACP,SAAS;GACT,UAAU;GACV,cAAc;GACd,QAAQ;GACR,GAAG;EACL;EACA,YAAY,EACV,UAAU,EAAE,aAAa,WAAW,kBAAkB,GAAG,WACvD,gBAAgB,SACd,oBAAC,iBAAD;GAAiB,WAAW,GAAG,UAAU,gBAAgB;GAAG,GAAI;EAAO,CAAA,IAEvE,oBAAC,kBAAD;GAAkB,WAAW,GAAG,UAAU,gBAAgB;GAAG,GAAI;EAAO,CAAA,EAE9E;EACA,GAAI;CACL,CAAA;AAEL"}
@@ -2,7 +2,7 @@ import { useDirection, useT } from "../../context.js";
2
2
  import { cn } from "../../lib/utils.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { useStore } from "../../hooks/use-store.js";
5
- import { LazyPlaceholder } from "../panel/lazy-placeholder.js";
5
+ import { LazyWidgetPlaceholder } from "../panel/lazy-placeholder.js";
6
6
  import { DashboardEditor, columnSpanClasses, dashboardMoveForKey, gridColumnsClasses, isLazyPlaceholder } from "@alfakit/core";
7
7
  import { router } from "@inertiajs/react";
8
8
  import { useEffect, useId, useMemo } from "react";
@@ -94,7 +94,7 @@ function CustomizableWidgetGrid({ block, customize, renderWidget }) {
94
94
  const label = editor.label(entry.id);
95
95
  const span = editor.spanOf(entry.id);
96
96
  const columnSpan = state.editing && library ? library.spans.find((option) => option.value === span)?.columnSpan ?? widget?.columnSpan : widget?.columnSpan;
97
- const content = widget ? isLazyPlaceholder(widget) ? /* @__PURE__ */ jsx(LazyPlaceholder, { variant: widget.type === "TableWidget" ? "table" : "widget" }) : renderWidget(widget) : /* @__PURE__ */ jsx("div", {
97
+ const content = widget ? isLazyPlaceholder(widget) ? /* @__PURE__ */ jsx(LazyWidgetPlaceholder, { widget }) : renderWidget(widget) : /* @__PURE__ */ jsx("div", {
98
98
  className: "bg-card text-muted-foreground flex min-h-24 items-center justify-center rounded-xl border border-dashed p-6 text-sm",
99
99
  children: t("dashboard.customize.pending", { widget: label }, ":widget appears once you save.")
100
100
  });
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard-customizer.js","names":[],"sources":["../../../src/components/widgets/dashboard-customizer.tsx"],"sourcesContent":["import type {\n DashboardCustomizeSchema,\n WidgetsBlock as WidgetsBlockSchema,\n WidgetSchema,\n} from '@alfakit/core'\nimport {\n columnSpanClasses,\n DashboardEditor,\n dashboardMoveForKey,\n gridColumnsClasses,\n isLazyPlaceholder,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport {\n CheckIcon,\n EyeOffIcon,\n GripVerticalIcon,\n LayoutDashboardIcon,\n PlusIcon,\n RotateCcwIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useId, useMemo, type ReactNode } from 'react'\nimport { useDirection, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { LazyPlaceholder } from '../panel/lazy-placeholder'\nimport { Button } from '../ui/button'\n\n/**\n * A customizable dashboard (`Dashboard::customizable()`): the widget grid plus an \"Edit\n * dashboard\" mode where widgets are dragged (pointer) or moved with the arrow keys on their\n * handle, resized, hidden and added from the library. The layout is saved on the server.\n */\nexport function CustomizableWidgetGrid({\n block,\n customize,\n renderWidget,\n}: {\n block: WidgetsBlockSchema\n customize: DashboardCustomizeSchema\n renderWidget: (widget: WidgetSchema) => ReactNode\n}) {\n const t = useT()\n const dir = useDirection()\n const helpId = useId()\n const editor = useMemo(\n () => new DashboardEditor(customize, { t, onSaved: () => router.reload() }),\n // One editor per dashboard; `sync()` follows later schemas.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [customize.endpoint, customize.token],\n )\n\n useEffect(() => editor.sync(customize), [editor, customize])\n\n const state = useStore(editor.store)\n const widgets = new Map(block.widgets.map((widget) => [widget.id, widget]))\n const visible = state.layout.filter((entry) => !entry.hidden)\n const available = editor.available()\n\n return (\n <div\n data-slot=\"dashboard\"\n data-editing={state.editing ? '' : undefined}\n className=\"flex flex-col gap-4\"\n >\n <div\n data-slot=\"dashboard-toolbar\"\n role=\"toolbar\"\n aria-label={t('dashboard.customize.toolbar', {}, 'Dashboard layout')}\n className=\"flex flex-wrap items-center justify-end gap-2\"\n >\n {state.editing ? (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={state.saving}\n onClick={() => {\n editor.resetDraft()\n }}\n >\n <RotateCcwIcon aria-hidden=\"true\" />\n {t('dashboard.customize.reset', {}, 'Reset to default')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={state.saving}\n onClick={() => editor.cancel()}\n >\n <XIcon aria-hidden=\"true\" />\n {t('dashboard.customize.cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={state.saving}\n onClick={() => void editor.save()}\n >\n <CheckIcon aria-hidden=\"true\" />\n {t('dashboard.customize.save', {}, 'Save')}\n </Button>\n </>\n ) : (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => editor.start()}>\n <LayoutDashboardIcon aria-hidden=\"true\" />\n {t('dashboard.customize.edit', {}, 'Edit dashboard')}\n </Button>\n )}\n </div>\n\n {state.error && (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {state.error}\n </p>\n )}\n\n {state.editing && (\n <p id={helpId} className=\"sr-only\">\n {t(\n 'dashboard.customize.move_help',\n {},\n 'Use the arrow keys to move the widget, Home or End to move it to the start or end.',\n )}\n </p>\n )}\n\n {visible.length === 0 ? (\n <p\n data-slot=\"dashboard-empty\"\n className=\"text-muted-foreground rounded-xl border border-dashed p-6 text-center text-sm\"\n >\n {t('dashboard.customize.empty', {}, 'No widgets on this dashboard yet.')}\n </p>\n ) : (\n <div data-slot=\"widgets\" className={cn('grid gap-6', gridColumnsClasses(block.columns))}>\n {visible.map((entry) => {\n const widget = widgets.get(entry.id)\n const library = editor.widget(entry.id)\n const label = editor.label(entry.id)\n const span = editor.spanOf(entry.id)\n const columnSpan =\n state.editing && library\n ? (library.spans.find((option) => option.value === span)?.columnSpan ??\n widget?.columnSpan)\n : widget?.columnSpan\n\n const content = widget ? (\n isLazyPlaceholder(widget) ? (\n <LazyPlaceholder variant={widget.type === 'TableWidget' ? 'table' : 'widget'} />\n ) : (\n renderWidget(widget)\n )\n ) : (\n <div className=\"bg-card text-muted-foreground flex min-h-24 items-center justify-center rounded-xl border border-dashed p-6 text-sm\">\n {t(\n 'dashboard.customize.pending',\n { widget: label },\n ':widget appears once you save.',\n )}\n </div>\n )\n\n if (!state.editing) {\n return (\n <div key={entry.id} className={cn('min-w-0', columnSpanClasses(columnSpan))}>\n {content}\n </div>\n )\n }\n\n return (\n <div\n key={entry.id}\n data-slot=\"dashboard-widget\"\n data-dragging={state.dragging === entry.id ? '' : undefined}\n role=\"group\"\n aria-label={label}\n className={cn(\n 'relative min-w-0 rounded-xl border-2 border-dashed p-1',\n state.dragging === entry.id && 'opacity-50',\n columnSpanClasses(columnSpan),\n )}\n onDragOver={(event) => {\n if (state.dragging !== null) event.preventDefault()\n }}\n onDrop={(event) => {\n event.preventDefault()\n editor.dropOn(entry.id)\n }}\n >\n <div\n data-slot=\"dashboard-widget-controls\"\n className=\"bg-background/95 absolute inset-x-2 top-2 z-10 flex items-center gap-1 rounded-md border p-1 shadow-sm\"\n >\n <button\n type=\"button\"\n data-slot=\"dashboard-widget-handle\"\n draggable\n aria-label={t('dashboard.customize.move', { widget: label }, 'Move :widget')}\n aria-describedby={helpId}\n aria-roledescription={t('dashboard.customize.sortable', {}, 'sortable')}\n className=\"hover:bg-accent focus-visible:ring-ring/50 cursor-grab rounded p-1 outline-none focus-visible:ring-[3px] active:cursor-grabbing\"\n onDragStart={(event) => {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', entry.id)\n editor.dragStart(entry.id)\n }}\n onDragEnd={() => editor.dragEnd()}\n onKeyDown={(event) => {\n const move = dashboardMoveForKey(event.key, dir)\n\n if (move !== null) {\n event.preventDefault()\n editor.move(entry.id, move)\n // Keep focus on the moved widget's handle.\n const target = event.currentTarget\n requestAnimationFrame(() => target.focus())\n }\n }}\n >\n <GripVerticalIcon className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">{label}</span>\n {library && library.spans.length > 1 && (\n <select\n data-slot=\"dashboard-widget-span\"\n aria-label={t(\n 'dashboard.customize.size',\n { widget: label },\n 'Size of :widget',\n )}\n className=\"border-input bg-background h-7 rounded-md border px-1 text-xs\"\n value={span}\n onChange={(event) => editor.setSpan(entry.id, Number(event.target.value))}\n >\n {library.spans.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('dashboard.customize.hide', { widget: label }, 'Hide :widget')}\n onClick={() => editor.hide(entry.id)}\n >\n <EyeOffIcon aria-hidden=\"true\" />\n </Button>\n </div>\n {/* The widget itself is inert while editing: focus stays on the controls. */}\n <div inert className=\"pointer-events-none pt-12 select-none\" aria-hidden=\"true\">\n {content}\n </div>\n </div>\n )\n })}\n </div>\n )}\n\n {state.editing && (\n <section\n data-slot=\"dashboard-library\"\n aria-label={t('dashboard.customize.library', {}, 'Widget library')}\n className=\"bg-muted/40 flex flex-col gap-3 rounded-xl border p-4\"\n >\n <h2 className=\"text-sm font-medium\">{t('dashboard.customize.add', {}, 'Add widget')}</h2>\n {available.length === 0 ? (\n <p className=\"text-muted-foreground text-sm\">\n {t('dashboard.customize.library_empty', {}, 'Every widget is on the dashboard.')}\n </p>\n ) : (\n <ul className=\"flex flex-wrap gap-2\">\n {available.map((widget) => (\n <li key={widget.id}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n title={widget.description}\n aria-label={t(\n 'dashboard.customize.add_widget',\n { widget: widget.label },\n 'Add :widget',\n )}\n onClick={() => editor.add(widget.id)}\n >\n <PlusIcon aria-hidden=\"true\" />\n {widget.label}\n </Button>\n </li>\n ))}\n </ul>\n )}\n </section>\n )}\n\n <div data-slot=\"dashboard-announcer\" role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {state.announcement}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"dashboard-customizer.js","names":[],"sources":["../../../src/components/widgets/dashboard-customizer.tsx"],"sourcesContent":["import type {\n DashboardCustomizeSchema,\n WidgetsBlock as WidgetsBlockSchema,\n WidgetSchema,\n} from '@alfakit/core'\nimport {\n columnSpanClasses,\n DashboardEditor,\n dashboardMoveForKey,\n gridColumnsClasses,\n isLazyPlaceholder,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport {\n CheckIcon,\n EyeOffIcon,\n GripVerticalIcon,\n LayoutDashboardIcon,\n PlusIcon,\n RotateCcwIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useId, useMemo, type ReactNode } from 'react'\nimport { useDirection, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { LazyWidgetPlaceholder } from '../panel/lazy-placeholder'\nimport { Button } from '../ui/button'\n\n/**\n * A customizable dashboard (`Dashboard::customizable()`): the widget grid plus an \"Edit\n * dashboard\" mode where widgets are dragged (pointer) or moved with the arrow keys on their\n * handle, resized, hidden and added from the library. The layout is saved on the server.\n */\nexport function CustomizableWidgetGrid({\n block,\n customize,\n renderWidget,\n}: {\n block: WidgetsBlockSchema\n customize: DashboardCustomizeSchema\n renderWidget: (widget: WidgetSchema) => ReactNode\n}) {\n const t = useT()\n const dir = useDirection()\n const helpId = useId()\n const editor = useMemo(\n () => new DashboardEditor(customize, { t, onSaved: () => router.reload() }),\n // One editor per dashboard; `sync()` follows later schemas.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [customize.endpoint, customize.token],\n )\n\n useEffect(() => editor.sync(customize), [editor, customize])\n\n const state = useStore(editor.store)\n const widgets = new Map(block.widgets.map((widget) => [widget.id, widget]))\n const visible = state.layout.filter((entry) => !entry.hidden)\n const available = editor.available()\n\n return (\n <div\n data-slot=\"dashboard\"\n data-editing={state.editing ? '' : undefined}\n className=\"flex flex-col gap-4\"\n >\n <div\n data-slot=\"dashboard-toolbar\"\n role=\"toolbar\"\n aria-label={t('dashboard.customize.toolbar', {}, 'Dashboard layout')}\n className=\"flex flex-wrap items-center justify-end gap-2\"\n >\n {state.editing ? (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n disabled={state.saving}\n onClick={() => {\n editor.resetDraft()\n }}\n >\n <RotateCcwIcon aria-hidden=\"true\" />\n {t('dashboard.customize.reset', {}, 'Reset to default')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={state.saving}\n onClick={() => editor.cancel()}\n >\n <XIcon aria-hidden=\"true\" />\n {t('dashboard.customize.cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={state.saving}\n onClick={() => void editor.save()}\n >\n <CheckIcon aria-hidden=\"true\" />\n {t('dashboard.customize.save', {}, 'Save')}\n </Button>\n </>\n ) : (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => editor.start()}>\n <LayoutDashboardIcon aria-hidden=\"true\" />\n {t('dashboard.customize.edit', {}, 'Edit dashboard')}\n </Button>\n )}\n </div>\n\n {state.error && (\n <p role=\"alert\" className=\"text-destructive text-sm\">\n {state.error}\n </p>\n )}\n\n {state.editing && (\n <p id={helpId} className=\"sr-only\">\n {t(\n 'dashboard.customize.move_help',\n {},\n 'Use the arrow keys to move the widget, Home or End to move it to the start or end.',\n )}\n </p>\n )}\n\n {visible.length === 0 ? (\n <p\n data-slot=\"dashboard-empty\"\n className=\"text-muted-foreground rounded-xl border border-dashed p-6 text-center text-sm\"\n >\n {t('dashboard.customize.empty', {}, 'No widgets on this dashboard yet.')}\n </p>\n ) : (\n <div data-slot=\"widgets\" className={cn('grid gap-6', gridColumnsClasses(block.columns))}>\n {visible.map((entry) => {\n const widget = widgets.get(entry.id)\n const library = editor.widget(entry.id)\n const label = editor.label(entry.id)\n const span = editor.spanOf(entry.id)\n const columnSpan =\n state.editing && library\n ? (library.spans.find((option) => option.value === span)?.columnSpan ??\n widget?.columnSpan)\n : widget?.columnSpan\n\n const content = widget ? (\n isLazyPlaceholder(widget) ? (\n <LazyWidgetPlaceholder widget={widget} />\n ) : (\n renderWidget(widget)\n )\n ) : (\n <div className=\"bg-card text-muted-foreground flex min-h-24 items-center justify-center rounded-xl border border-dashed p-6 text-sm\">\n {t(\n 'dashboard.customize.pending',\n { widget: label },\n ':widget appears once you save.',\n )}\n </div>\n )\n\n if (!state.editing) {\n return (\n <div key={entry.id} className={cn('min-w-0', columnSpanClasses(columnSpan))}>\n {content}\n </div>\n )\n }\n\n return (\n <div\n key={entry.id}\n data-slot=\"dashboard-widget\"\n data-dragging={state.dragging === entry.id ? '' : undefined}\n role=\"group\"\n aria-label={label}\n className={cn(\n 'relative min-w-0 rounded-xl border-2 border-dashed p-1',\n state.dragging === entry.id && 'opacity-50',\n columnSpanClasses(columnSpan),\n )}\n onDragOver={(event) => {\n if (state.dragging !== null) event.preventDefault()\n }}\n onDrop={(event) => {\n event.preventDefault()\n editor.dropOn(entry.id)\n }}\n >\n <div\n data-slot=\"dashboard-widget-controls\"\n className=\"bg-background/95 absolute inset-x-2 top-2 z-10 flex items-center gap-1 rounded-md border p-1 shadow-sm\"\n >\n <button\n type=\"button\"\n data-slot=\"dashboard-widget-handle\"\n draggable\n aria-label={t('dashboard.customize.move', { widget: label }, 'Move :widget')}\n aria-describedby={helpId}\n aria-roledescription={t('dashboard.customize.sortable', {}, 'sortable')}\n className=\"hover:bg-accent focus-visible:ring-ring/50 cursor-grab rounded p-1 outline-none focus-visible:ring-[3px] active:cursor-grabbing\"\n onDragStart={(event) => {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', entry.id)\n editor.dragStart(entry.id)\n }}\n onDragEnd={() => editor.dragEnd()}\n onKeyDown={(event) => {\n const move = dashboardMoveForKey(event.key, dir)\n\n if (move !== null) {\n event.preventDefault()\n editor.move(entry.id, move)\n // Keep focus on the moved widget's handle.\n const target = event.currentTarget\n requestAnimationFrame(() => target.focus())\n }\n }}\n >\n <GripVerticalIcon className=\"size-4\" aria-hidden=\"true\" />\n </button>\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium\">{label}</span>\n {library && library.spans.length > 1 && (\n <select\n data-slot=\"dashboard-widget-span\"\n aria-label={t(\n 'dashboard.customize.size',\n { widget: label },\n 'Size of :widget',\n )}\n className=\"border-input bg-background h-7 rounded-md border px-1 text-xs\"\n value={span}\n onChange={(event) => editor.setSpan(entry.id, Number(event.target.value))}\n >\n {library.spans.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('dashboard.customize.hide', { widget: label }, 'Hide :widget')}\n onClick={() => editor.hide(entry.id)}\n >\n <EyeOffIcon aria-hidden=\"true\" />\n </Button>\n </div>\n {/* The widget itself is inert while editing: focus stays on the controls. */}\n <div inert className=\"pointer-events-none pt-12 select-none\" aria-hidden=\"true\">\n {content}\n </div>\n </div>\n )\n })}\n </div>\n )}\n\n {state.editing && (\n <section\n data-slot=\"dashboard-library\"\n aria-label={t('dashboard.customize.library', {}, 'Widget library')}\n className=\"bg-muted/40 flex flex-col gap-3 rounded-xl border p-4\"\n >\n <h2 className=\"text-sm font-medium\">{t('dashboard.customize.add', {}, 'Add widget')}</h2>\n {available.length === 0 ? (\n <p className=\"text-muted-foreground text-sm\">\n {t('dashboard.customize.library_empty', {}, 'Every widget is on the dashboard.')}\n </p>\n ) : (\n <ul className=\"flex flex-wrap gap-2\">\n {available.map((widget) => (\n <li key={widget.id}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n title={widget.description}\n aria-label={t(\n 'dashboard.customize.add_widget',\n { widget: widget.label },\n 'Add :widget',\n )}\n onClick={() => editor.add(widget.id)}\n >\n <PlusIcon aria-hidden=\"true\" />\n {widget.label}\n </Button>\n </li>\n ))}\n </ul>\n )}\n </section>\n )}\n\n <div data-slot=\"dashboard-announcer\" role=\"status\" aria-live=\"polite\" className=\"sr-only\">\n {state.announcement}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
@@ -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}\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 {stat.description}\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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"}
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":";;;;;;;;;;;;;AAkBA,SAAgB,oBAAoB,EAAE,UAAiD;CACrF,MAAM,UAAU,OAAO,WAAW;EAChC,SAAS;EACT,IAAI,KAAK,IAAI,OAAO,MAAM,QAAQ,CAAC;EACnC,IAAI,KAAK,IAAI,OAAO,MAAM,QAAQ,CAAC;CACrC;CAIA,MAAM,aAAa,iBAAiB,MAAM;CAC1C,MAAM,gBAAgB,iBAAiB;CAEvC,gBACQ,cAAc,YAAY,OAAO,eAAe,GACtD;EAAC;EAAe;EAAY,OAAO;CAAe,CACpD;CAGA,eAAe,OAAO,MAAM,YAAY,EAAE,SAAS,QAAQ,OAAO,eAAe,EAAE,CAAC;CAEpF,OACE,qBAAC,WAAD;EACE,WAAU;EACV,GAAK,OAAO,UAAU,EAAE,cAAc,OAAO,QAAQ,IAAI,CAAC;EAF5D,UAAA,EAII,OAAO,WAAW,OAAO,gBACzB,qBAAC,OAAD,EAAA,UAAA,CACG,OAAO,WAAW,oBAAC,MAAD;GAAI,WAAU;GAA2B,UAAA,OAAO;EAAY,CAAA,GAC9E,OAAO,eACN,oBAAC,KAAD;GAAG,WAAU;GAAiC,UAAA,OAAO;EAAe,CAAA,CAEnE,EAAA,CAAA,GAEP,oBAAC,OAAD;GAAK,WAAW,GAAG,cAAc,mBAAmB,OAAO,CAAC;GACzD,UAAA,OAAO,MAAM,KAAK,MAAM,UACvB,oBAAC,UAAD,EAA4B,KAAO,GAApB,KAAoB,CACpC;EACE,CAAA,CACE;;AAEb;AAEA,SAAS,SAAS,EAAE,QAA8B;CAChD,MAAM,UACJ,qBAAC,MAAD;EACE,GAAI,WACF,KAAK,iBACL,GACE,4CACA,KAAK,OAAO,sCACd,CACF;EAPF,UAAA,CASE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,QAAD;MAAM,WAAU;MACd,UAAA,oBAAC,OAAD,EAAA,UAAM,KAAK,MAAW,CAAA;KAClB,CAAA;MACJ,KAAK,UAAU,QAAQ,KAAK,UAAU,OAAO,KAAK,cAClD,oBAAC,QAAD;MACE,WAAU;MACV,aAAU;MAET,UAAA,KAAK;KACF,CAAA,IAEN,oBAAC,QAAD;MACE,WAAW,GACT,yCACA,KAAK,SAAS,iBAAiB,KAAK,MACtC;MAEA,UAAA,oBAAC,OAAD,EAAA,UAAM,KAAK,MAAW,CAAA;KAClB,CAAA;KAEP,KAAK,eACJ,qBAAC,QAAD;MACE,WAAW,GACT,0CACA,KAAK,mBACD,iBAAiB,KAAK,oBACtB,uBACN;MANF,UAAA;OAQG,KAAK,mBAAmB,KAAK,4BAA4B,WACxD,oBAAC,MAAD;QAAM,MAAM,KAAK;QAAiB,WAAU;OAAY,CAAA;OAG1D,oBAAC,OAAD,EAAA,UAAM,KAAK,YAAiB,CAAA;OAC3B,KAAK,mBAAmB,KAAK,4BAA4B,WACxD,oBAAC,MAAD;QAAM,MAAM,KAAK;QAAiB,WAAU;OAAY,CAAA;MAEtD;;IAEL;GACJ,CAAA,GAAA,KAAK,QACJ,oBAAC,QAAD;IACE,WAAW,GACT,+DACA,WAAW,4BAA4B,KAAK,OAAO,MAAM,CAC3D;IAEA,UAAA,oBAAC,MAAD;KAAM,MAAM,KAAK;KAAM,WAAU;IAAU,CAAA;GACvC,CAAA,CAEL;EACJ,CAAA,GAAA,KAAK,SAAS,KAAK,MAAM,SAAS,KACjC,oBAAC,WAAD;GAAW,MAAM,KAAK;GAAO,OAAO,SAAS,KAAK,cAAc,KAAK,KAAK;EAAI,CAAA,CAE5E;;CAGR,IAAI,KAAK,KACP,OAAO,KAAK,kBACV,qBAAC,KAAD;EAAG,MAAM,KAAK;EAAK,QAAO;EAAS,KAAI;EAAvC,UAAA,CACG,SACD,oBAAC,YAAD,CAAa,CAAA,CACZ;CAEH,CAAA,IAAA,oBAAC,MAAD;EAAM,MAAM,KAAK;EAAM,UAAA;CAAc,CAAA;CAIzC,OAAO;AACT;AAEA,SAAS,UAAU,EAAE,MAAM,SAA4C;CACrE,MAAM,QAAQ;CACd,MAAM,SAAS;CACf,MAAM,MAAM,KAAK,IAAI,GAAG,IAAI;CAE5B,MAAM,QADM,KAAK,IAAI,GAAG,IACR,IAAI,OAAO;CAQ3B,MAAM,OAPS,KAAK,KACjB,OAAO,UACN,CACG,SAAS,KAAK,SAAS,KAAM,OAC9B,UAAW,QAAQ,OAAO,QAAU,KAAc,CACpD,CAEc,CAAC,CAChB,KAAK,CAAC,GAAG,IAAI,UAAU,GAAG,UAAU,IAAI,MAAM,MAAM,EAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,CACnF,KAAK,GAAG;CACX,MAAM,OAAO,GAAG,KAAK,IAAI,MAAM,GAAG,OAAO,MAAM,OAAO;CAEtD,OACE,qBAAC,OAAD;EACE,SAAS,OAAO,MAAM,GAAG;EACzB,WAAU;EACV,qBAAoB;EACpB,eAAY;EACZ,aAAU;EALZ,UAAA,CAOE,oBAAC,QAAD;GACE,GAAG;GACH,MAAM;GACN,aAAU;GACV,OAAO,EAAE,SAAS,uCAAuC;EAC1D,CAAA,GACD,oBAAC,QAAD;GACE,GAAG;GACH,MAAK;GACL,QAAQ;GACR,gBAAe;GACf,eAAc;GACd,aAAU;GACV,OAAO,EAAE,aAAa,oCAAoC;EAC3D,CAAA,CACE;;AAET"}
@@ -1,7 +1,7 @@
1
1
  import { useRegistry, useT } from "../../context.js";
2
2
  import { cn } from "../../lib/utils.js";
3
3
  import { lazyComponent } from "../../lib/lazy.js";
4
- import { LazyPlaceholder } from "../panel/lazy-placeholder.js";
4
+ import { LazyWidgetPlaceholder } from "../panel/lazy-placeholder.js";
5
5
  import { Slot } from "../panel/slot.js";
6
6
  import { DataTable } from "../table/table-block.js";
7
7
  import { ChartWidget } from "./chart-lazy.js";
@@ -30,7 +30,7 @@ function StaticWidgetGrid({ block }) {
30
30
  className: cn("grid gap-6", gridColumnsClasses(block.columns)),
31
31
  children: block.widgets.map((widget) => /* @__PURE__ */ jsx("div", {
32
32
  className: cn("min-w-0", columnSpanClasses(widget.columnSpan)),
33
- children: isLazyPlaceholder(widget) ? /* @__PURE__ */ jsx(LazyPlaceholder, { variant: widget.type === "TableWidget" ? "table" : "widget" }) : /* @__PURE__ */ jsx(WidgetRenderer, { widget })
33
+ children: isLazyPlaceholder(widget) ? /* @__PURE__ */ jsx(LazyWidgetPlaceholder, { widget }) : /* @__PURE__ */ jsx(WidgetRenderer, { widget })
34
34
  }, widget.key))
35
35
  });
36
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"widgets-block.js","names":[],"sources":["../../../src/components/widgets/widgets-block.tsx"],"sourcesContent":["import type { WidgetSchema, WidgetsBlock as WidgetsBlockSchema } from '@alfakit/core'\nimport {\n columnSpanClasses,\n gridColumnsClasses,\n isLazyPlaceholder,\n resolveFirst,\n widgetRegistryKeys,\n} from '@alfakit/core'\nimport { createElement, type ComponentProps } from 'react'\nimport { useRegistry, useT } from '../../context'\nimport { lazyComponent } from '../../lib/lazy'\nimport { cn } from '../../lib/utils'\nimport { LazyPlaceholder } from '../panel/lazy-placeholder'\nimport { Slot } from '../panel/slot'\nimport { DataTable } from '../table/table-block'\nimport { ChartWidget } from './chart-lazy'\nimport type { CustomizableWidgetGrid as CustomizableWidgetGridComponent } from './dashboard-customizer'\nimport { StatsOverviewWidget } from './stats-overview'\n\n/**\n * `Dashboard::customizable()`: the grid with its \"Edit dashboard\" mode. The editor's code\n * loads with the first customizable dashboard; until then (and on the server) the widgets\n * show in the plain grid.\n */\nconst CustomizableWidgetGrid = lazyComponent<\n ComponentProps<typeof CustomizableWidgetGridComponent>\n>(\n () => import('./dashboard-customizer').then((module) => module.CustomizableWidgetGrid),\n ({ block }) => <StaticWidgetGrid block={block} />,\n)\n\nexport function WidgetGrid({ block }: { block: WidgetsBlockSchema }) {\n if (block.customize) {\n return (\n <CustomizableWidgetGrid\n block={block}\n customize={block.customize}\n renderWidget={(widget) => <WidgetRenderer widget={widget} />}\n />\n )\n }\n\n return <StaticWidgetGrid block={block} />\n}\n\nfunction StaticWidgetGrid({ block }: { block: WidgetsBlockSchema }) {\n return (\n <div data-slot=\"widgets\" className={cn('grid gap-6', gridColumnsClasses(block.columns))}>\n {block.widgets.map((widget) => (\n <div key={widget.key} className={cn('min-w-0', columnSpanClasses(widget.columnSpan))}>\n {isLazyPlaceholder(widget) ? (\n <LazyPlaceholder variant={widget.type === 'TableWidget' ? 'table' : 'widget'} />\n ) : (\n <WidgetRenderer widget={widget} />\n )}\n </div>\n ))}\n </div>\n )\n}\n\nexport function WidgetRenderer({ widget }: { widget: WidgetSchema }) {\n const registry = useRegistry()\n const t = useT()\n // Registered components win over the built-in renderers: `Widget::$component`,\n // then `widget:<type>` (e.g. `widget:Chart`), then a custom widget's own type.\n const Custom = resolveFirst(registry, widgetRegistryKeys(widget))\n\n if (Custom) {\n return createElement(Custom, {\n ...(widget.type === 'View' ? (widget.props ?? {}) : {}),\n widget,\n })\n }\n\n switch (widget.type) {\n case 'StatsOverviewWidget':\n return <StatsOverviewWidget widget={widget} />\n case 'Chart':\n return <ChartWidget widget={widget} />\n case 'TableWidget':\n return (\n <>\n <Slot name=\"table-widget.start\" scope={widget.component} />\n <DataTable table={widget.table} component={widget.component} />\n <Slot name=\"table-widget.end\" scope={widget.component} />\n </>\n )\n case 'View':\n return (\n <div className=\"text-destructive rounded-md border border-dashed p-3 text-xs\">\n {t(\n 'widget_not_registered',\n { component: widget.component },\n 'Widget component :component is not registered.',\n )}\n </div>\n )\n default:\n return null\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,MAAM,yBAAyB,oBAGvB,OAAO,4BAAyB,CAAC,MAAM,WAAW,OAAO,sBAAsB,IACpF,EAAE,YAAY,oBAAC,kBAAD,EAAyB,MAAQ,CAAA,CAClD;AAEA,SAAgB,WAAW,EAAE,SAAwC;CACnE,IAAI,MAAM,WACR,OACE,oBAAC,wBAAD;EACS;EACP,WAAW,MAAM;EACjB,eAAe,WAAW,oBAAC,gBAAD,EAAwB,OAAS,CAAA;CAC5D,CAAA;CAIL,OAAO,oBAAC,kBAAD,EAAyB,MAAQ,CAAA;AAC1C;AAEA,SAAS,iBAAiB,EAAE,SAAwC;CAClE,OACE,oBAAC,OAAD;EAAK,aAAU;EAAU,WAAW,GAAG,cAAc,mBAAmB,MAAM,OAAO,CAAC;EACnF,UAAA,MAAM,QAAQ,KAAK,WAClB,oBAAC,OAAD;GAAsB,WAAW,GAAG,WAAW,kBAAkB,OAAO,UAAU,CAAC;GAChF,UAAA,kBAAkB,MAAM,IACvB,oBAAC,iBAAD,EAAiB,SAAS,OAAO,SAAS,gBAAgB,UAAU,SAAW,CAAA,IAE/E,oBAAC,gBAAD,EAAwB,OAAS,CAAA;EAEhC,GANK,OAAO,GAMZ,CACN;CACE,CAAA;AAET;AAEA,SAAgB,eAAe,EAAE,UAAoC;CACnE,MAAM,WAAW,YAAY;CAC7B,MAAM,IAAI,KAAK;CAGf,MAAM,SAAS,aAAa,UAAU,mBAAmB,MAAM,CAAC;CAEhE,IAAI,QACF,OAAO,cAAc,QAAQ;EAC3B,GAAI,OAAO,SAAS,SAAU,OAAO,SAAS,CAAC,IAAK,CAAC;EACrD;CACF,CAAC;CAGH,QAAQ,OAAO,MAAf;EACE,KAAK,uBACH,OAAO,oBAAC,qBAAD,EAA6B,OAAS,CAAA;EAC/C,KAAK,SACH,OAAO,oBAAC,aAAD,EAAqB,OAAS,CAAA;EACvC,KAAK,eACH,OACE,qBAAA,YAAA,EAAA,UAAA;GACE,oBAAC,MAAD;IAAM,MAAK;IAAqB,OAAO,OAAO;GAAY,CAAA;GAC1D,oBAAC,WAAD;IAAW,OAAO,OAAO;IAAO,WAAW,OAAO;GAAY,CAAA;GAC9D,oBAAC,MAAD;IAAM,MAAK;IAAmB,OAAO,OAAO;GAAY,CAAA;EACxD,EAAA,CAAA;EAEN,KAAK,QACH,OACE,oBAAC,OAAD;GAAK,WAAU;GACZ,UAAA,EACC,yBACA,EAAE,WAAW,OAAO,UAAU,GAC9B,gDACF;EACG,CAAA;EAET,SACE,OAAO;CACX;AACF"}
1
+ {"version":3,"file":"widgets-block.js","names":[],"sources":["../../../src/components/widgets/widgets-block.tsx"],"sourcesContent":["import type { WidgetSchema, WidgetsBlock as WidgetsBlockSchema } from '@alfakit/core'\nimport {\n columnSpanClasses,\n gridColumnsClasses,\n isLazyPlaceholder,\n resolveFirst,\n widgetRegistryKeys,\n} from '@alfakit/core'\nimport { createElement, type ComponentProps } from 'react'\nimport { useRegistry, useT } from '../../context'\nimport { lazyComponent } from '../../lib/lazy'\nimport { cn } from '../../lib/utils'\nimport { LazyWidgetPlaceholder } from '../panel/lazy-placeholder'\nimport { Slot } from '../panel/slot'\nimport { DataTable } from '../table/table-block'\nimport { ChartWidget } from './chart-lazy'\nimport type { CustomizableWidgetGrid as CustomizableWidgetGridComponent } from './dashboard-customizer'\nimport { StatsOverviewWidget } from './stats-overview'\n\n/**\n * `Dashboard::customizable()`: the grid with its \"Edit dashboard\" mode. The editor's code\n * loads with the first customizable dashboard; until then (and on the server) the widgets\n * show in the plain grid.\n */\nconst CustomizableWidgetGrid = lazyComponent<\n ComponentProps<typeof CustomizableWidgetGridComponent>\n>(\n () => import('./dashboard-customizer').then((module) => module.CustomizableWidgetGrid),\n ({ block }) => <StaticWidgetGrid block={block} />,\n)\n\nexport function WidgetGrid({ block }: { block: WidgetsBlockSchema }) {\n if (block.customize) {\n return (\n <CustomizableWidgetGrid\n block={block}\n customize={block.customize}\n renderWidget={(widget) => <WidgetRenderer widget={widget} />}\n />\n )\n }\n\n return <StaticWidgetGrid block={block} />\n}\n\nfunction StaticWidgetGrid({ block }: { block: WidgetsBlockSchema }) {\n return (\n <div data-slot=\"widgets\" className={cn('grid gap-6', gridColumnsClasses(block.columns))}>\n {block.widgets.map((widget) => (\n <div key={widget.key} className={cn('min-w-0', columnSpanClasses(widget.columnSpan))}>\n {isLazyPlaceholder(widget) ? (\n <LazyWidgetPlaceholder widget={widget} />\n ) : (\n <WidgetRenderer widget={widget} />\n )}\n </div>\n ))}\n </div>\n )\n}\n\nexport function WidgetRenderer({ widget }: { widget: WidgetSchema }) {\n const registry = useRegistry()\n const t = useT()\n // Registered components win over the built-in renderers: `Widget::$component`,\n // then `widget:<type>` (e.g. `widget:Chart`), then a custom widget's own type.\n const Custom = resolveFirst(registry, widgetRegistryKeys(widget))\n\n if (Custom) {\n return createElement(Custom, {\n ...(widget.type === 'View' ? (widget.props ?? {}) : {}),\n widget,\n })\n }\n\n switch (widget.type) {\n case 'StatsOverviewWidget':\n return <StatsOverviewWidget widget={widget} />\n case 'Chart':\n return <ChartWidget widget={widget} />\n case 'TableWidget':\n return (\n <>\n <Slot name=\"table-widget.start\" scope={widget.component} />\n <DataTable table={widget.table} component={widget.component} />\n <Slot name=\"table-widget.end\" scope={widget.component} />\n </>\n )\n case 'View':\n return (\n <div className=\"text-destructive rounded-md border border-dashed p-3 text-xs\">\n {t(\n 'widget_not_registered',\n { component: widget.component },\n 'Widget component :component is not registered.',\n )}\n </div>\n )\n default:\n return null\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAwBA,MAAM,yBAAyB,oBAGvB,OAAO,4BAAyB,CAAC,MAAM,WAAW,OAAO,sBAAsB,IACpF,EAAE,YAAY,oBAAC,kBAAD,EAAyB,MAAQ,CAAA,CAClD;AAEA,SAAgB,WAAW,EAAE,SAAwC;CACnE,IAAI,MAAM,WACR,OACE,oBAAC,wBAAD;EACS;EACP,WAAW,MAAM;EACjB,eAAe,WAAW,oBAAC,gBAAD,EAAwB,OAAS,CAAA;CAC5D,CAAA;CAIL,OAAO,oBAAC,kBAAD,EAAyB,MAAQ,CAAA;AAC1C;AAEA,SAAS,iBAAiB,EAAE,SAAwC;CAClE,OACE,oBAAC,OAAD;EAAK,aAAU;EAAU,WAAW,GAAG,cAAc,mBAAmB,MAAM,OAAO,CAAC;EACnF,UAAA,MAAM,QAAQ,KAAK,WAClB,oBAAC,OAAD;GAAsB,WAAW,GAAG,WAAW,kBAAkB,OAAO,UAAU,CAAC;GAChF,UAAA,kBAAkB,MAAM,IACvB,oBAAC,uBAAD,EAA+B,OAAS,CAAA,IAExC,oBAAC,gBAAD,EAAwB,OAAS,CAAA;EAEhC,GANK,OAAO,GAMZ,CACN;CACE,CAAA;AAET;AAEA,SAAgB,eAAe,EAAE,UAAoC;CACnE,MAAM,WAAW,YAAY;CAC7B,MAAM,IAAI,KAAK;CAGf,MAAM,SAAS,aAAa,UAAU,mBAAmB,MAAM,CAAC;CAEhE,IAAI,QACF,OAAO,cAAc,QAAQ;EAC3B,GAAI,OAAO,SAAS,SAAU,OAAO,SAAS,CAAC,IAAK,CAAC;EACrD;CACF,CAAC;CAGH,QAAQ,OAAO,MAAf;EACE,KAAK,uBACH,OAAO,oBAAC,qBAAD,EAA6B,OAAS,CAAA;EAC/C,KAAK,SACH,OAAO,oBAAC,aAAD,EAAqB,OAAS,CAAA;EACvC,KAAK,eACH,OACE,qBAAA,YAAA,EAAA,UAAA;GACE,oBAAC,MAAD;IAAM,MAAK;IAAqB,OAAO,OAAO;GAAY,CAAA;GAC1D,oBAAC,WAAD;IAAW,OAAO,OAAO;IAAO,WAAW,OAAO;GAAY,CAAA;GAC9D,oBAAC,MAAD;IAAM,MAAK;IAAmB,OAAO,OAAO;GAAY,CAAA;EACxD,EAAA,CAAA;EAEN,KAAK,QACH,OACE,oBAAC,OAAD;GAAK,WAAU;GACZ,UAAA,EACC,yBACA,EAAE,WAAW,OAAO,UAAU,GAC9B,gDACF;EACG,CAAA;EAET,SACE,OAAO;CACX;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"create-alfakit.d.ts","names":[],"sources":["../src/create-alfakit.tsx"],"mappings":";;;;;iBAsHiB,6BAA6B,eAAe;;EAE3D,WAAW,KAAK,cAAc;EAC9B;;;;;;;;;;;;;;wBAsLoB,cAAc,UAAS,uBAA4B"}
1
+ {"version":3,"file":"create-alfakit.d.ts","names":[],"sources":["../src/create-alfakit.tsx"],"mappings":";;;;;iBA8HiB,6BAA6B,eAAe;;EAE3D,WAAW,KAAK,cAAc;EAC9B;;;;;;;;;;;;;;wBAsLoB,cAAc,UAAS,uBAA4B"}
@@ -13,7 +13,7 @@ import { DevToolsSlot } from "./components/dev-tools/dev-tools-slot.js";
13
13
  import { SchemaContractGuard } from "./components/panel/schema-contract-guard.js";
14
14
  import { Toaster } from "./components/ui/sonner.js";
15
15
  import { panelLayouts } from "./layouts/panel-layouts.js";
16
- import { ActionRunner, PANEL_TOAST_OFFSET, Translator, applyDocumentLocale, applyPanelTheme, configureCharts, configureRealtime, debugRecorder, documentNonce, effectiveErrorNotifications, initializeAppearance, installErrorNotifications, installPrefetchInvalidation, installSpaUrlExceptions, registerRichEditorExtensions, reloadOnExpiredPage, resolveRequestErrorNotification, resolveToastPosition, setRequestErrorReporter, whenIdle } from "@alfakit/core";
16
+ import { ActionRunner, PANEL_TOAST_OFFSET, Translator, applyDocumentLocale, applyPanelTheme, configureCharts, configureRealtime, debugRecorder, documentNonce, effectiveErrorNotifications, initializeAppearance, installErrorNotifications, installPrefetchInvalidation, installSpaUrlExceptions, overlayPlacementDirection, registerRichEditorExtensions, reloadOnExpiredPage, resolveRequestErrorNotification, resolveToastPosition, setIconAliases, setRequestErrorReporter, whenIdle } from "@alfakit/core";
17
17
  import { createInertiaApp, router, usePage } from "@inertiajs/react";
18
18
  import { StrictMode, createElement, useLayoutEffect } from "react";
19
19
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -31,6 +31,7 @@ function AlfakitRoot({ children, toasterId }) {
31
31
  if (panel) {
32
32
  translator.setTranslations(panel.translations ?? {});
33
33
  translator.setLocale(localization?.tag ?? "en");
34
+ setIconAliases(panel.theme?.icons);
34
35
  }
35
36
  const ownsDocument = panel?.starterKit !== true;
36
37
  useLayoutEffect(() => {
@@ -56,7 +57,7 @@ function AlfakitRoot({ children, toasterId }) {
56
57
  panel?.impersonation && createElement(registry.resolve("panel:impersonation-banner") ?? ImpersonationBanner, { impersonation: panel.impersonation }),
57
58
  /* @__PURE__ */ jsx(Toaster, {
58
59
  id: toasterId,
59
- position: resolveToastPosition(panel?.toastPosition, direction),
60
+ position: resolveToastPosition(panel?.toastPosition, overlayPlacementDirection(direction, panel?.starterKit)),
60
61
  offset: PANEL_TOAST_OFFSET,
61
62
  mobileOffset: PANEL_TOAST_OFFSET,
62
63
  dir: direction,