@svadmin/ui 0.38.2 → 0.38.5

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 (144) hide show
  1. package/dist/app.css +8 -4
  2. package/dist/app.theme.css +8 -4
  3. package/dist/components/AICommandBar.svelte +16 -15
  4. package/dist/components/AboutSettings.svelte +12 -11
  5. package/dist/components/AdminApp.svelte +144 -70
  6. package/dist/components/AdminApp.svelte.d.ts +6 -3
  7. package/dist/components/ApiSettings.svelte +19 -16
  8. package/dist/components/AppearanceSettings.svelte +18 -16
  9. package/dist/components/AuditLogDrawer.svelte +6 -3
  10. package/dist/components/AuditLogViewer.svelte +18 -15
  11. package/dist/components/Authenticated.svelte +4 -4
  12. package/dist/components/AutoForm.svelte +19 -16
  13. package/dist/components/AutoSaveIndicator.svelte +7 -5
  14. package/dist/components/AutoTable.svelte +85 -71
  15. package/dist/components/AutoTable.svelte.d.ts +1 -1
  16. package/dist/components/Breadcrumbs.svelte +17 -13
  17. package/dist/components/CancelTaskButton.svelte +5 -2
  18. package/dist/components/CatchAllNavigate.svelte +3 -3
  19. package/dist/components/ChatDialog.svelte +22 -19
  20. package/dist/components/ComboboxField.svelte +6 -4
  21. package/dist/components/CommandPalette.svelte +20 -20
  22. package/dist/components/ConfigErrorScreen.svelte +12 -10
  23. package/dist/components/ConfirmDialog.svelte +6 -4
  24. package/dist/components/CreatePage.svelte +4 -2
  25. package/dist/components/DevTools.svelte +11 -9
  26. package/dist/components/DrawerForm.svelte +5 -2
  27. package/dist/components/EditPage.svelte +8 -5
  28. package/dist/components/EmptyState.svelte +5 -3
  29. package/dist/components/ErrorBoundary.svelte +5 -3
  30. package/dist/components/ErrorComponent.svelte +6 -4
  31. package/dist/components/ErrorPage.svelte +12 -10
  32. package/dist/components/FieldRenderer.svelte +17 -15
  33. package/dist/components/ForgotPasswordPage.svelte +22 -19
  34. package/dist/components/Header.svelte +7 -4
  35. package/dist/components/InfiniteList.svelte +6 -3
  36. package/dist/components/IntegrationsSettings.svelte +9 -6
  37. package/dist/components/KeyboardShortcuts.svelte +4 -2
  38. package/dist/components/Layout.svelte +12 -13
  39. package/dist/components/LazyPage.svelte +47 -0
  40. package/dist/components/LazyPage.svelte.d.ts +14 -0
  41. package/dist/components/ListPage.svelte +8 -5
  42. package/dist/components/LoginPage.svelte +24 -22
  43. package/dist/components/MarkdownRenderer.svelte +89 -35
  44. package/dist/components/ModalForm.svelte +4 -2
  45. package/dist/components/NavigateToResource.svelte +3 -3
  46. package/dist/components/NotificationsSettings.svelte +26 -24
  47. package/dist/components/PasswordInput.svelte +4 -2
  48. package/dist/components/PermissionMatrix.svelte +15 -12
  49. package/dist/components/ProfilePage.svelte +23 -21
  50. package/dist/components/RegisterPage.svelte +21 -19
  51. package/dist/components/ResourceOperationsPage.svelte +3 -2
  52. package/dist/components/RetryTaskButton.svelte +5 -2
  53. package/dist/components/RolesSettings.svelte +10 -8
  54. package/dist/components/SecuritySettings.svelte +26 -23
  55. package/dist/components/SettingsPage.svelte +14 -11
  56. package/dist/components/ShowPage.svelte +9 -7
  57. package/dist/components/Sidebar.svelte +36 -28
  58. package/dist/components/SidebarItem.svelte +4 -4
  59. package/dist/components/SidebarItem.svelte.d.ts +1 -1
  60. package/dist/components/StepsForm.svelte +11 -8
  61. package/dist/components/TaskDetails.svelte +14 -11
  62. package/dist/components/TaskList.svelte +14 -11
  63. package/dist/components/TaskQueueDrawer.svelte +34 -32
  64. package/dist/components/TaskStatusBadge.svelte +5 -2
  65. package/dist/components/Toast.svelte +18 -4
  66. package/dist/components/TooltipButton.svelte +25 -3
  67. package/dist/components/TwoFactorAuthPage.svelte +26 -24
  68. package/dist/components/UndoableNotification.svelte +5 -2
  69. package/dist/components/UpdatePasswordPage.svelte +18 -15
  70. package/dist/components/VirtualTable.svelte +0 -4
  71. package/dist/components/account/CompanyProfilePage.svelte +13 -11
  72. package/dist/components/account/GetStartedPage.svelte +15 -13
  73. package/dist/components/account/ImportMembersPage.svelte +14 -12
  74. package/dist/components/account/MembersStarterPage.svelte +10 -8
  75. package/dist/components/account/SecurityLogPage.svelte +24 -22
  76. package/dist/components/account/SettingsEnterprisePage.svelte +6 -4
  77. package/dist/components/account/SettingsPlainPage.svelte +14 -12
  78. package/dist/components/account/SettingsSidebarPage.svelte +11 -9
  79. package/dist/components/account/TeamMembersPage.svelte +10 -8
  80. package/dist/components/buttons/CloneButton.svelte +4 -2
  81. package/dist/components/buttons/CreateButton.svelte +4 -2
  82. package/dist/components/buttons/DeleteButton.svelte +6 -4
  83. package/dist/components/buttons/EditButton.svelte +4 -2
  84. package/dist/components/buttons/ExportButton.svelte +4 -2
  85. package/dist/components/buttons/ImportButton.svelte +4 -2
  86. package/dist/components/buttons/RefreshButton.svelte +4 -2
  87. package/dist/components/buttons/SaveButton.svelte +4 -2
  88. package/dist/components/buttons/ShowButton.svelte +4 -2
  89. package/dist/components/fieldComponentMap.js +0 -3
  90. package/dist/components/fields/ArrayField.svelte +6 -4
  91. package/dist/components/fields/JsonField.svelte +5 -3
  92. package/dist/components/fields/RelationField.svelte +3 -3
  93. package/dist/components/fields/RichTextField.svelte +4 -2
  94. package/dist/components/network/TeamCrewTablePage.svelte +18 -16
  95. package/dist/components/network/UserCardsNFTPage.svelte +11 -9
  96. package/dist/components/profile/ActivityTimeline.svelte +10 -8
  97. package/dist/components/profile/ProfileCard.svelte +13 -11
  98. package/dist/components/profile/ProjectsGrid.svelte +7 -5
  99. package/dist/components/profile/PublicProfilePage.svelte +18 -16
  100. package/dist/components/profile/TeamsShowcase.svelte +7 -5
  101. package/dist/components/toast-host.svelte.d.ts +5 -0
  102. package/dist/components/toast-host.svelte.js +21 -0
  103. package/dist/components/ui/command/command-dialog.svelte +29 -13
  104. package/dist/components/ui/command/command-dialog.svelte.d.ts +4 -3
  105. package/dist/components/ui/command/command-empty.svelte +18 -0
  106. package/dist/components/ui/command/command-empty.svelte.d.ts +4 -0
  107. package/dist/components/ui/command/command-group.svelte +41 -0
  108. package/dist/components/ui/command/command-group.svelte.d.ts +8 -0
  109. package/dist/components/ui/command/command-input.svelte +23 -0
  110. package/dist/components/ui/command/command-input.svelte.d.ts +4 -0
  111. package/dist/components/ui/command/command-item.svelte +43 -0
  112. package/dist/components/ui/command/command-item.svelte.d.ts +11 -0
  113. package/dist/components/ui/command/command-list.svelte +18 -0
  114. package/dist/components/ui/command/command-list.svelte.d.ts +4 -0
  115. package/dist/components/ui/command/command-loading.svelte +12 -0
  116. package/dist/components/ui/command/command-loading.svelte.d.ts +4 -0
  117. package/dist/components/ui/command/command-separator.svelte +23 -0
  118. package/dist/components/ui/command/command-separator.svelte.d.ts +7 -0
  119. package/dist/components/ui/command/command.svelte +26 -0
  120. package/dist/components/ui/command/command.svelte.d.ts +4 -0
  121. package/dist/components/ui/command/index.d.ts +55 -2
  122. package/dist/components/ui/command/index.js +21 -2
  123. package/dist/components/ui/sheet/sheet-content.svelte +1 -4
  124. package/dist/components/ui/sheet/sheet-content.svelte.d.ts +1 -3
  125. package/dist/components/ui/tabs/tabs-content.svelte +6 -2
  126. package/dist/components/ui/tabs/tabs-context.svelte.d.ts +5 -0
  127. package/dist/components/ui/tabs/tabs-context.svelte.js +2 -0
  128. package/dist/components/ui/tabs/tabs-trigger.svelte +49 -4
  129. package/dist/components/ui/tabs/tabs.svelte +12 -1
  130. package/dist/components/ui/tooltip/index.d.ts +4 -1
  131. package/dist/components/ui/tooltip/index.js +3 -1
  132. package/dist/components/ui/tooltip/tooltip-content.svelte +308 -35
  133. package/dist/components/ui/tooltip/tooltip-content.svelte.d.ts +42 -3
  134. package/dist/components/ui/tooltip/tooltip-context.svelte.d.ts +37 -0
  135. package/dist/components/ui/tooltip/tooltip-context.svelte.js +220 -0
  136. package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.d.ts +11 -0
  137. package/dist/components/ui/tooltip/tooltip-layer-registry.svelte.js +93 -0
  138. package/dist/components/ui/tooltip/tooltip-trigger.svelte +56 -3
  139. package/dist/components/ui/tooltip/tooltip-trigger.svelte.d.ts +23 -2
  140. package/dist/components/ui/tooltip/tooltip.svelte +63 -3
  141. package/dist/components/ui/tooltip/tooltip.svelte.d.ts +23 -2
  142. package/dist/router-state.svelte.d.ts +13 -0
  143. package/dist/router-state.svelte.js +72 -37
  144. package/package.json +13 -11
@@ -10,7 +10,7 @@
10
10
  type ColumnVisibilityState,
11
11
  type ExpandedState,
12
12
  } from '@tanstack/svelte-table';
13
- import type { Updater } from '@tanstack/table-core';
13
+ import type { Column, Header, TableFeatures, Updater } from '@tanstack/table-core';
14
14
  import {
15
15
  column_getCanSort,
16
16
  column_getIsSorted,
@@ -31,12 +31,19 @@
31
31
  cell_getValue,
32
32
  } from '@tanstack/table-core/static-functions';
33
33
 
34
- import { useList, useDelete, useDeleteMany, getResource, notify } from '@svadmin/core';
35
- import type { Pagination as PaginationState, Sort, Filter, FieldDefinition } from '@svadmin/core';
36
- import { navigate } from '@svadmin/core/router';
34
+ import { captureAdminContext, useList, useDelete, useDeleteMany, getResource, notify } from '@svadmin/core';
35
+ import type {
36
+ BaseRecord,
37
+ FieldDefinition,
38
+ Filter,
39
+ LogicalFilter,
40
+ Pagination as PaginationState,
41
+ Sort,
42
+ } from '@svadmin/core';
37
43
  import { useCan, getAccessControlProvider } from '@svadmin/core';
38
44
  import { readURLState, writeURLState } from '@svadmin/core';
39
- import { t } from '@svadmin/core/i18n';
45
+ import { useTranslation } from '@svadmin/core/i18n';
46
+
40
47
  import { fade } from 'svelte/transition';
41
48
  import { Button } from './ui/button/index.js';
42
49
  import { Input } from './ui/input/index.js';
@@ -50,7 +57,7 @@
50
57
  import * as ContextMenu from './ui/context-menu/index.js';
51
58
  import {
52
59
  Plus, Pencil, Trash2,
53
- Search, Download, Upload, ChevronDown, ChevronUp, SlidersHorizontal, Filter as FilterIcon,
60
+ Search, Download, ChevronDown, ChevronUp, SlidersHorizontal, Filter as FilterIcon,
54
61
  Eye, Copy
55
62
  } from '@lucide/svelte';
56
63
  import ConfirmDialog from './ConfirmDialog.svelte';
@@ -59,6 +66,8 @@
59
66
  import DraggableHeader from './DraggableHeader.svelte';
60
67
  import type { Snippet } from 'svelte';
61
68
 
69
+ const i18n = useTranslation();
70
+
62
71
  // ─── Props with Snippet composability ─────────────────────────
63
72
  interface Props {
64
73
  resourceName: string;
@@ -96,12 +105,13 @@
96
105
  pagination: externalPagination,
97
106
  sorters: externalSorters,
98
107
  }: Props = $props();
108
+ const adminContext = captureAdminContext();
99
109
 
100
110
  const resource = $derived(getResource(resourceName));
101
111
  const primaryKey = $derived(resource.primaryKey ?? 'id');
102
112
 
103
113
  // ─── URL state + server-side state ────────────────────────────
104
- const urlState = readURLState();
114
+ const urlState = readURLState(adminContext);
105
115
 
106
116
  // Snapshot resource values for initial state (untrack to avoid reactive tracking)
107
117
  const storedPageSize = typeof window !== 'undefined' ? parseInt(localStorage.getItem('svadmin-default-page-size') ?? '', 10) : NaN;
@@ -142,7 +152,7 @@
142
152
  sortField: sorters[0]?.field,
143
153
  sortOrder: sorters[0]?.order,
144
154
  search: searchText || undefined,
145
- });
155
+ }, adminContext);
146
156
  });
147
157
 
148
158
  // ─── Build active filters with search ─────────────────────────
@@ -156,14 +166,15 @@
156
166
  if (searchableFields.length === 1) {
157
167
  result.push({ field: searchableFields[0].key, operator: 'contains', value: searchText });
158
168
  } else {
159
- result.push({
169
+ const searchFilter: LogicalFilter = {
160
170
  operator: 'or',
161
171
  value: searchableFields.map(f => ({
162
172
  field: f.key,
163
173
  operator: 'contains',
164
174
  value: searchText
165
175
  }))
166
- } as any);
176
+ };
177
+ result.push(searchFilter);
167
178
  }
168
179
  }
169
180
  for (const [key, value] of Object.entries(filterValues)) {
@@ -353,7 +364,7 @@
353
364
  resource.fields.filter(f => f.showInList !== false)
354
365
  );
355
366
 
356
- const columns = $derived<ColumnDef<any, Record<string, unknown>>[]>([
367
+ const columns = $derived<ColumnDef<TableFeatures, BaseRecord, unknown>[]>([
357
368
  // Selection column
358
369
  ...(selectable && (canDelete || batchActions) ? [{
359
370
  id: '_select',
@@ -361,7 +372,7 @@
361
372
  cell: () => '',
362
373
  size: 40,
363
374
  enableSorting: false,
364
- } satisfies ColumnDef<any, Record<string, unknown>>] : []),
375
+ } satisfies ColumnDef<TableFeatures, BaseRecord, unknown>] : []),
365
376
  // Expand column
366
377
  ...(expandedRowRender ? [{
367
378
  id: '_expand',
@@ -369,9 +380,9 @@
369
380
  cell: () => '',
370
381
  size: 40,
371
382
  enableSorting: false,
372
- } satisfies ColumnDef<any, Record<string, unknown>>] : []),
383
+ } satisfies ColumnDef<TableFeatures, BaseRecord, unknown>] : []),
373
384
  // Data columns
374
- ...visibleFields.map((field): ColumnDef<any, Record<string, unknown>> => ({
385
+ ...visibleFields.map((field): ColumnDef<TableFeatures, BaseRecord, unknown> => ({
375
386
  id: field.key,
376
387
  accessorKey: field.key,
377
388
  header: () => field.label,
@@ -380,7 +391,7 @@
380
391
  // Actions column
381
392
  {
382
393
  id: '_actions',
383
- header: () => t('common.actions'),
394
+ header: () => i18n.t('common.actions'),
384
395
  cell: () => '',
385
396
  size: 100,
386
397
  enableSorting: false,
@@ -388,23 +399,26 @@
388
399
  ]);
389
400
 
390
401
  // ─── Create TanStack Table ────────────────────────────────────
391
- const tbl = createTable({
392
- get data() { return query.data?.data ?? []; },
393
- get columns() { return columns; },
394
- getCoreRowModel: createCoreRowModel(),
395
- manualPagination: true,
396
- manualSorting: true,
397
- getRowId: (row: any) => String(row[primaryKey]),
398
- atoms: {
399
- sorting: sortingAtom,
400
- columnVisibility: columnVisibilityAtom,
401
- rowSelection: rowSelectionAtom,
402
- expanded: expandedAtom,
403
- columnOrder: columnOrderAtom,
402
+ const tbl = createTable(
403
+ {
404
+ get data() { return query.data?.data ?? []; },
405
+ get columns() { return columns; },
406
+ getCoreRowModel: createCoreRowModel(),
407
+ manualPagination: true,
408
+ manualSorting: true,
409
+ getRowId: (row: BaseRecord) => String(row[primaryKey]),
410
+ atoms: {
411
+ sorting: sortingAtom,
412
+ columnVisibility: columnVisibilityAtom,
413
+ rowSelection: rowSelectionAtom,
414
+ expanded: expandedAtom,
415
+ columnOrder: columnOrderAtom,
416
+ },
417
+ get enableRowSelection() { return selectable && (canDelete || batchActions); },
418
+ get enableExpanding() { return !!expandedRowRender; },
404
419
  },
405
- get enableRowSelection() { return selectable && (canDelete || batchActions); },
406
- get enableExpanding() { return !!expandedRowRender; },
407
- });
420
+ () => undefined,
421
+ );
408
422
 
409
423
  const selectedCount = $derived(Object.keys(rowSelection).length);
410
424
  const totalPages = $derived(Math.ceil((query.data?.total ?? 0) / (pagination.pageSize ?? 10)));
@@ -433,7 +447,7 @@
433
447
  let confirmAction = $state<() => void>(() => {});
434
448
 
435
449
  function confirmDelete(id: string | number) {
436
- confirmMessage = t('common.deleteConfirm');
450
+ confirmMessage = i18n.t('common.deleteConfirm');
437
451
  confirmAction = async () => {
438
452
  await deleteMutation.mutateAsync({ id, resource: resourceName });
439
453
  confirmOpen = false;
@@ -443,13 +457,13 @@
443
457
 
444
458
  function confirmBatchDelete() {
445
459
  const ids = Object.keys(rowSelection);
446
- confirmMessage = t('common.batchDeleteConfirm', { count: ids.length });
460
+ confirmMessage = i18n.t('common.batchDeleteConfirm', { count: ids.length });
447
461
  confirmAction = async () => {
448
462
  try {
449
463
  await deleteManyMutation.mutateAsync({ ids, resource: resourceName });
450
- notify({ type: 'success', message: t('common.batchDeleteSuccess', { count: ids.length }) });
464
+ notify({ type: 'success', message: i18n.t('common.batchDeleteSuccess', { count: ids.length }) });
451
465
  } catch {
452
- notify({ type: 'error', message: t('common.batchDeletePartialFail', { failed: 1, total: ids.length }) });
466
+ notify({ type: 'error', message: i18n.t('common.batchDeletePartialFail', { failed: 1, total: ids.length }) });
453
467
  }
454
468
  rowSelection = {};
455
469
  confirmOpen = false;
@@ -491,7 +505,7 @@
491
505
  <div class="flex flex-wrap items-center gap-2">
492
506
  {#if canExport}
493
507
  <Button variant="outline" size="sm" onclick={exportCSV}>
494
- <Download class="h-4 w-4" data-icon="inline-start" /> {t('common.export')}
508
+ <Download class="h-4 w-4" data-icon="inline-start" /> {i18n.t('common.export')}
495
509
  </Button>
496
510
  {/if}
497
511
  {#if selectedCount > 0 && canDelete}
@@ -501,7 +515,7 @@
501
515
  class="h-9 whitespace-nowrap"
502
516
  onclick={confirmBatchDelete}
503
517
  >
504
- <Trash2 class="h-4 w-4" data-icon="inline-start" /> {t('common.batchDelete', { count: selectedCount })}
518
+ <Trash2 class="h-4 w-4" data-icon="inline-start" /> {i18n.t('common.batchDelete', { count: selectedCount })}
505
519
  </Button>
506
520
  {/if}
507
521
 
@@ -514,12 +528,12 @@
514
528
  <DropdownMenu.Trigger>
515
529
  {#snippet child({ props })}
516
530
  <Button variant="outline" size="sm" {...props}>
517
- <SlidersHorizontal class="h-4 w-4" data-icon="inline-start" /> {t('common.columns') || 'Columns'}
531
+ <SlidersHorizontal class="h-4 w-4" data-icon="inline-start" /> {i18n.t('common.columns') || 'Columns'}
518
532
  </Button>
519
533
  {/snippet}
520
534
  </DropdownMenu.Trigger>
521
535
  <DropdownMenu.Content align="end" class="w-48">
522
- {#each table_getAllLeafColumns(tbl).filter((c: any) => !c.id.startsWith('_')) as column, _i (_i)}
536
+ {#each table_getAllLeafColumns(tbl).filter((column: Column<TableFeatures, BaseRecord, unknown>) => !column.id.startsWith('_')) as column, _i (_i)}
523
537
  <DropdownMenu.CheckboxItem
524
538
  checked={column_getIsVisible(column)}
525
539
  onCheckedChange={(v) => column_toggleVisibility(column, !!v)}
@@ -533,8 +547,8 @@
533
547
  {@render headerActions()}
534
548
  {/if}
535
549
  {#if canCreate}
536
- <Button onclick={() => navigate(`/${resourceName}/create`)}>
537
- <Plus class="h-4 w-4" data-icon="inline-start" /> {t('common.create')}
550
+ <Button onclick={() => adminContext.navigate(`/${resourceName}/create`)}>
551
+ <Plus class="h-4 w-4" data-icon="inline-start" /> {i18n.t('common.create')}
538
552
  </Button>
539
553
  {/if}
540
554
  </div>
@@ -549,7 +563,7 @@
549
563
  type="text"
550
564
  bind:value={searchText}
551
565
  oninput={() => { pagination = { ...pagination, current: 1 }; }}
552
- placeholder={t('common.search')}
566
+ placeholder={i18n.t('common.search')}
553
567
  class="pl-10 h-9"
554
568
  />
555
569
  </div>
@@ -561,7 +575,7 @@
561
575
  {#snippet child({ props })}
562
576
  <Button variant="outline" size="sm" class="h-9 px-3" {...props}>
563
577
  <FilterIcon class="h-4 w-4" data-icon="inline-start" />
564
- {t('common.filter')}
578
+ {i18n.t('common.filter')}
565
579
  {#if activeFilterCount > 0}
566
580
  <Badge variant="secondary" class="ml-1 h-5 min-w-5 px-1">{activeFilterCount}</Badge>
567
581
  {/if}
@@ -570,7 +584,7 @@
570
584
  </Popover.Trigger>
571
585
  <Popover.Content class="w-80">
572
586
  <div class="space-y-3">
573
- <h4 class="font-medium text-sm">{t('common.filter')}</h4>
587
+ <h4 class="font-medium text-sm">{i18n.t('common.filter')}</h4>
574
588
  {#each filterableFields as field, _i (_i)}
575
589
  <div class="space-y-1">
576
590
  <label class="text-xs text-muted-foreground" for="filter-{field.key}">{field.label}</label>
@@ -581,7 +595,7 @@
581
595
  value={filterValues[field.key] ?? ''}
582
596
  onchange={(e) => filterValues[field.key] = (e.currentTarget as HTMLSelectElement).value}
583
597
  >
584
- <option value="">{t('common.all')}</option>
598
+ <option value="">{i18n.t('common.all')}</option>
585
599
  {#each field.options as opt, _i (_i)}
586
600
  <option value={opt.value}>{opt.label}</option>
587
601
  {/each}
@@ -600,13 +614,13 @@
600
614
  {/each}
601
615
  <div class="flex gap-2 pt-2">
602
616
  <Button size="sm" class="flex-1" onclick={() => { pagination = { ...pagination, current: 1 }; }}>
603
- {t('common.confirm')}
617
+ {i18n.t('common.confirm')}
604
618
  </Button>
605
619
  <Button variant="outline" size="sm" onclick={() => {
606
620
  filterValues = {};
607
621
  pagination = { ...pagination, current: 1 };
608
622
  }}>
609
- {t('common.reset')}
623
+ {i18n.t('common.reset')}
610
624
  </Button>
611
625
  </div>
612
626
  </div>
@@ -616,7 +630,7 @@
616
630
  </div>
617
631
 
618
632
  <!-- Table (TanStack-powered) -->
619
- <div class="rounded-[24px] bg-card/80 backdrop-blur-3xl shadow-[0_8px_30px_rgb(0,0,0,0.04)] overflow-hidden ring-1 ring-border/30" role="region" aria-label="{resource.label} {t('common.list')}">
633
+ <div class="rounded-[24px] bg-card/80 backdrop-blur-3xl shadow-[0_8px_30px_rgb(0,0,0,0.04)] overflow-hidden ring-1 ring-border/30" role="region" aria-label="{resource.label} {i18n.t('common.list')}">
620
634
  {#if query.isLoading}
621
635
  <div class="p-4 space-y-3">
622
636
  <div class="flex gap-4 mb-2">
@@ -634,7 +648,7 @@
634
648
  </div>
635
649
  {:else if query.error}
636
650
  <div class="flex h-64 items-center justify-center text-destructive text-sm">
637
- {t('common.loadFailed', { message: (query.error as Error).message })}
651
+ {i18n.t('common.loadFailed', { message: (query.error as Error).message })}
638
652
  </div>
639
653
  {:else}
640
654
  <div in:fade={{ duration: 150 }}>
@@ -644,9 +658,9 @@
644
658
  <Table.Header>
645
659
  {#each table_getHeaderGroups(tbl) as headerGroup, _i (_i)}
646
660
  <DraggableHeader
647
- columns={headerGroup.headers.map((h: any) => ({ id: h.column.id, header: h }))}
661
+ columns={headerGroup.headers.map((header: Header<TableFeatures, BaseRecord, unknown>) => ({ id: header.column.id, header }))}
648
662
  resourceName={resourceName}
649
- onReorder={(newOrder) => { columnOrder = newOrder.map((c: any) => c.id); }}
663
+ onReorder={(newOrder) => { columnOrder = newOrder.map((column) => column.id); }}
650
664
  >
651
665
  {#snippet header(col, _index, dragProps)}
652
666
  {@const header = col.header as typeof headerGroup.headers[0]}
@@ -663,7 +677,7 @@
663
677
  {:else if header.id === '_expand'}
664
678
  <!-- empty -->
665
679
  {:else if header.id === '_actions'}
666
- <span class="text-right block">{t('common.actions')}</span>
680
+ <span class="text-right block">{i18n.t('common.actions')}</span>
667
681
  {:else if column_getCanSort(header.column)}
668
682
  <Button
669
683
  variant="ghost"
@@ -703,7 +717,7 @@
703
717
  onCheckedChange={() => row_toggleSelected(row)}
704
718
  />
705
719
  {:else if cell.column.id === '_expand'}
706
- <TooltipButton tooltip={row_getIsExpanded(row) ? t('common.collapse') : t('common.expand')} variant="ghost" size="icon" class="h-7 w-7" onclick={() => row_toggleExpanded(row)}>
720
+ <TooltipButton tooltip={row_getIsExpanded(row) ? i18n.t('common.collapse') : i18n.t('common.expand')} variant="ghost" size="icon" class="h-7 w-7" onclick={() => row_toggleExpanded(row)}>
707
721
  {#if row_getIsExpanded(row)}
708
722
  <ChevronUp class="h-4 w-4" />
709
723
  {:else}
@@ -716,12 +730,12 @@
716
730
  {@render rowActions({ record, id })}
717
731
  {:else}
718
732
  {#if canEdit}
719
- <TooltipButton tooltip={t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigate(`/${resourceName}/edit/${id}`)}>
733
+ <TooltipButton tooltip={i18n.t('common.edit')} variant="ghost" size="icon-sm" onclick={() => adminContext.navigate(`/${resourceName}/edit/${id}`)}>
720
734
  <Pencil class="h-4 w-4" />
721
735
  </TooltipButton>
722
736
  {/if}
723
737
  {#if canDelete}
724
- <TooltipButton tooltip={t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="hover:text-destructive">
738
+ <TooltipButton tooltip={i18n.t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="hover:text-destructive">
725
739
  <Trash2 class="h-4 w-4" />
726
740
  </TooltipButton>
727
741
  {/if}
@@ -767,20 +781,20 @@
767
781
  </ContextMenu.Trigger>
768
782
  <ContextMenu.Content class="w-48">
769
783
  {#if canEdit}
770
- <ContextMenu.Item onclick={() => navigate(`/${resourceName}/edit/${id}`)} class="gap-2">
771
- <Pencil class="h-4 w-4" /> {t('common.edit')}
784
+ <ContextMenu.Item onclick={() => adminContext.navigate(`/${resourceName}/edit/${id}`)} class="gap-2">
785
+ <Pencil class="h-4 w-4" /> {i18n.t('common.edit')}
772
786
  </ContextMenu.Item>
773
787
  {/if}
774
- <ContextMenu.Item onclick={() => navigate(`/${resourceName}/show/${id}`)} class="gap-2">
775
- <Eye class="h-4 w-4" /> {t('common.detail')}
788
+ <ContextMenu.Item onclick={() => adminContext.navigate(`/${resourceName}/show/${id}`)} class="gap-2">
789
+ <Eye class="h-4 w-4" /> {i18n.t('common.detail')}
776
790
  </ContextMenu.Item>
777
791
  <ContextMenu.Item onclick={() => navigator.clipboard?.writeText(String(id))} class="gap-2">
778
- <Copy class="h-4 w-4" /> {t('common.copyId')}
792
+ <Copy class="h-4 w-4" /> {i18n.t('common.copyId')}
779
793
  </ContextMenu.Item>
780
794
  {#if canDelete}
781
795
  <ContextMenu.Separator />
782
796
  <ContextMenu.Item onclick={() => confirmDelete(id)} class="gap-2 text-destructive">
783
- <Trash2 class="h-4 w-4" /> {t('common.delete')}
797
+ <Trash2 class="h-4 w-4" /> {i18n.t('common.delete')}
784
798
  </ContextMenu.Item>
785
799
  {/if}
786
800
  </ContextMenu.Content>
@@ -802,12 +816,12 @@
802
816
  <svg class="h-16 w-16 text-muted-foreground/30 mb-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor">
803
817
  <path stroke-linecap="round" stroke-linejoin="round" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
804
818
  </svg>
805
- <p class="text-sm font-medium text-muted-foreground mb-1">{t('common.noData')}</p>
806
- <p class="text-xs text-muted-foreground/60 mb-4">{t('common.noDataHint') || 'Get started by creating your first record.'}</p>
819
+ <p class="text-sm font-medium text-muted-foreground mb-1">{i18n.t('common.noData')}</p>
820
+ <p class="text-xs text-muted-foreground/60 mb-4">{i18n.t('common.noDataHint') || 'Get started by creating your first record.'}</p>
807
821
  {#if canCreate}
808
- <Button variant="outline" size="sm" class="gap-2" onclick={() => navigate(`/${resourceName}/create`)}>
822
+ <Button variant="outline" size="sm" class="gap-2" onclick={() => adminContext.navigate(`/${resourceName}/create`)}>
809
823
  <Plus class="h-3.5 w-3.5" />
810
- {t('common.create')}
824
+ {i18n.t('common.create')}
811
825
  </Button>
812
826
  {/if}
813
827
  </div>
@@ -843,15 +857,15 @@
843
857
  {@render rowActions({ record, id })}
844
858
  {:else}
845
859
  {#if canEdit}
846
- <TooltipButton tooltip={t('common.edit')} variant="ghost" size="icon-sm" onclick={() => navigate(`/${resourceName}/edit/${id}`)}>
860
+ <TooltipButton tooltip={i18n.t('common.edit')} variant="ghost" size="icon-sm" onclick={() => adminContext.navigate(`/${resourceName}/edit/${id}`)}>
847
861
  <Pencil class="h-4 w-4" />
848
862
  </TooltipButton>
849
863
  {/if}
850
- <TooltipButton tooltip={t('common.detail')} variant="ghost" size="icon-sm" onclick={() => navigate(`/${resourceName}/show/${id}`)}>
864
+ <TooltipButton tooltip={i18n.t('common.detail')} variant="ghost" size="icon-sm" onclick={() => adminContext.navigate(`/${resourceName}/show/${id}`)}>
851
865
  <Eye class="h-4 w-4" />
852
866
  </TooltipButton>
853
867
  {#if canDelete}
854
- <TooltipButton tooltip={t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="hover:text-destructive">
868
+ <TooltipButton tooltip={i18n.t('common.delete')} variant="ghost" size="icon-sm" onclick={() => confirmDelete(id)} class="hover:text-destructive">
855
869
  <Trash2 class="h-4 w-4" />
856
870
  </TooltipButton>
857
871
  {/if}
@@ -895,7 +909,7 @@
895
909
  {#if emptyState}
896
910
  {@render emptyState()}
897
911
  {:else}
898
- {t('common.noData')}
912
+ {i18n.t('common.noData')}
899
913
  {/if}
900
914
  </div>
901
915
  {/each}
@@ -908,7 +922,7 @@
908
922
  {#if totalPages > 0}
909
923
  <div class="flex flex-col sm:flex-row items-center justify-between gap-2 text-sm text-muted-foreground">
910
924
  <div class="flex items-center gap-2">
911
- <span>{t('common.total', { total: query.data?.total ?? 0 })}</span>
925
+ <span>{i18n.t('common.total', { total: query.data?.total ?? 0 })}</span>
912
926
  <select
913
927
  class="h-8 w-[70px] px-1 py-1 flex items-center justify-between rounded-md border border-input bg-background text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
914
928
  value={String(pagination.pageSize ?? 10)}
@@ -962,7 +976,7 @@
962
976
  <ConfirmDialog
963
977
  open={confirmOpen}
964
978
  message={confirmMessage}
965
- confirmText={t('common.delete')}
979
+ confirmText={i18n.t('common.delete')}
966
980
  onconfirm={confirmAction}
967
981
  oncancel={() => { confirmOpen = false; }}
968
982
  />
@@ -1,4 +1,4 @@
1
- import type { Sort, FieldDefinition } from '@svadmin/core';
1
+ import type { FieldDefinition, Sort } from '@svadmin/core';
2
2
  import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  resourceName: string;
@@ -1,17 +1,19 @@
1
1
  <script lang="ts">
2
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
3
- import { getResources } from '@svadmin/core';
2
+ import { captureAdminContext, getResources } from '@svadmin/core';
4
3
  import { getPath } from '../router-state.svelte.js';
5
- import { formatLink } from '@svadmin/core/router';
6
- import { t } from '@svadmin/core/i18n';
4
+ import { useTranslation } from '@svadmin/core/i18n';
5
+
7
6
  import * as Breadcrumb from './ui/breadcrumb/index.js';
8
7
 
8
+ const i18n = useTranslation();
9
+
10
+ const adminContext = captureAdminContext();
9
11
  const resources = $derived((() => { try { return getResources(); } catch { return []; } })());
10
12
 
11
13
  interface Crumb { label: string; href: string; }
12
14
 
13
15
  const crumbs = $derived.by(() => {
14
- const result: Crumb[] = [{ label: t('common.home') ?? 'Home', href: formatLink('/') }];
16
+ const result: Crumb[] = [{ label: i18n.t('common.home') ?? 'Home', href: adminContext.formatLink('/') }];
15
17
  const path = getPath().replace(/^\//, '');
16
18
  if (path === '') return result;
17
19
 
@@ -25,21 +27,23 @@
25
27
  currentPath += `/${seg}`;
26
28
 
27
29
  if (resourceNames.includes(seg)) {
28
- const res = resources.find(r => r.name === seg)!;
29
- result.push({ label: res.label, href: formatLink(currentPath) });
30
+ const res = resources.find(r => r.name === seg);
31
+ if (res) {
32
+ result.push({ label: res.label, href: adminContext.formatLink(currentPath) });
33
+ }
30
34
  } else if (seg === 'create') {
31
- result.push({ label: t('common.create') ?? 'Create', href: formatLink(currentPath) });
35
+ result.push({ label: i18n.t('common.create') ?? 'Create', href: adminContext.formatLink(currentPath) });
32
36
  } else if (['edit', 'show', 'clone'].includes(seg) && segments[i+1]) {
33
- const actionLabel = seg === 'edit' ? (t('common.edit') ?? 'Edit') :
34
- seg === 'show' ? (t('common.detail') ?? 'Details') :
35
- (t('common.clone') ?? 'Clone');
37
+ const actionLabel = seg === 'edit' ? (i18n.t('common.edit') ?? 'Edit') :
38
+ seg === 'show' ? (i18n.t('common.detail') ?? 'Details') :
39
+ (i18n.t('common.clone') ?? 'Clone');
36
40
  const id = segments[i+1];
37
41
  currentPath += `/${id}`; // advance path by id
38
- result.push({ label: `${actionLabel} #${id}`, href: formatLink(currentPath) });
42
+ result.push({ label: `${actionLabel} #${id}`, href: adminContext.formatLink(currentPath) });
39
43
  i++; // skip next segment since we consumed the id
40
44
  } else if (i > 0 && resourceNames.includes(segments[i-1])) {
41
45
  // This is a parent ID (e.g. /teams/123/users) and we are not an action
42
- result.push({ label: `#${seg}`, href: formatLink(currentPath) });
46
+ result.push({ label: `#${seg}`, href: adminContext.formatLink(currentPath) });
43
47
  }
44
48
  }
45
49
 
@@ -1,10 +1,13 @@
1
1
  <script lang="ts">
2
2
  import { getTaskProvider } from '@svadmin/core';
3
3
  import { useQueryClient } from '@tanstack/svelte-query';
4
- import { t } from '@svadmin/core/i18n';
4
+ import { useTranslation } from '@svadmin/core/i18n';
5
+
5
6
  import { Button } from './ui/button/index.js';
6
7
  import { Loader2, Ban } from '@lucide/svelte';
7
8
 
9
+ const i18n = useTranslation();
10
+
8
11
  let {
9
12
  taskId,
10
13
  taskProvider = getTaskProvider({ optional: true }),
@@ -55,6 +58,6 @@
55
58
  {#if children}
56
59
  {@render children()}
57
60
  {:else}
58
- {t('common.cancel')}
61
+ {i18n.t('common.cancel')}
59
62
  {/if}
60
63
  </Button>
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
- import { useIsAuthenticated } from '@svadmin/core';
3
- import { navigate } from '@svadmin/core/router';
2
+ import { captureAdminContext, useIsAuthenticated } from '@svadmin/core';
4
3
  import NavigateToResource from './NavigateToResource.svelte';
5
4
 
5
+ const adminContext = captureAdminContext();
6
6
  const auth = useIsAuthenticated();
7
7
 
8
8
  $effect(() => {
9
9
  if (!auth.isLoading && !auth.isAuthenticated) {
10
- navigate('/login');
10
+ adminContext.navigate('/login');
11
11
  }
12
12
  });
13
13
  </script>