@actuate-media/cms-admin 0.69.0 → 0.70.0

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 (188) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/AdminRoot.d.ts.map +1 -1
  3. package/dist/AdminRoot.js +4 -1
  4. package/dist/AdminRoot.js.map +1 -1
  5. package/dist/__tests__/lib/pages-tree.test.js +40 -1
  6. package/dist/__tests__/lib/pages-tree.test.js.map +1 -1
  7. package/dist/__tests__/views/form-entries.render.test.js +14 -4
  8. package/dist/__tests__/views/form-entries.render.test.js.map +1 -1
  9. package/dist/__tests__/views/form-submissions.render.test.js +14 -4
  10. package/dist/__tests__/views/form-submissions.render.test.js.map +1 -1
  11. package/dist/__tests__/views/media-seo-audit.render.test.js +26 -0
  12. package/dist/__tests__/views/media-seo-audit.render.test.js.map +1 -1
  13. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts +2 -0
  14. package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts.map +1 -0
  15. package/dist/__tests__/views/page-builder-template-seed.render.test.js +48 -0
  16. package/dist/__tests__/views/page-builder-template-seed.render.test.js.map +1 -0
  17. package/dist/__tests__/views/page-templates.render.test.js +21 -0
  18. package/dist/__tests__/views/page-templates.render.test.js.map +1 -1
  19. package/dist/__tests__/views/redirects-pane.render.test.d.ts +2 -0
  20. package/dist/__tests__/views/redirects-pane.render.test.d.ts.map +1 -0
  21. package/dist/__tests__/views/redirects-pane.render.test.js +121 -0
  22. package/dist/__tests__/views/redirects-pane.render.test.js.map +1 -0
  23. package/dist/actuate-admin.css +1 -1
  24. package/dist/components/SEOPanel.js +3 -3
  25. package/dist/components/SEOPanel.js.map +1 -1
  26. package/dist/components/TipTapEditor.d.ts.map +1 -1
  27. package/dist/components/TipTapEditor.js +11 -9
  28. package/dist/components/TipTapEditor.js.map +1 -1
  29. package/dist/components/collections/EntryDetailPane.js +1 -1
  30. package/dist/components/collections/EntryDetailPane.js.map +1 -1
  31. package/dist/components/forms/EntriesTable.d.ts +3 -1
  32. package/dist/components/forms/EntriesTable.d.ts.map +1 -1
  33. package/dist/components/forms/EntriesTable.js +5 -2
  34. package/dist/components/forms/EntriesTable.js.map +1 -1
  35. package/dist/components/forms/FormEntryDetailPane.d.ts +10 -0
  36. package/dist/components/forms/FormEntryDetailPane.d.ts.map +1 -0
  37. package/dist/components/forms/FormEntryDetailPane.js +137 -0
  38. package/dist/components/forms/FormEntryDetailPane.js.map +1 -0
  39. package/dist/components/forms/FormSchemaPane.js +1 -1
  40. package/dist/components/forms/FormSchemaPane.js.map +1 -1
  41. package/dist/components/script-tags/ScriptTagEditorPane.js +1 -1
  42. package/dist/components/script-tags/ScriptTagEditorPane.js.map +1 -1
  43. package/dist/components/seo/RedirectEditorPane.d.ts +12 -0
  44. package/dist/components/seo/RedirectEditorPane.d.ts.map +1 -0
  45. package/dist/components/seo/RedirectEditorPane.js +89 -0
  46. package/dist/components/seo/RedirectEditorPane.js.map +1 -0
  47. package/dist/components/seo/SeoEditorPane.js +1 -1
  48. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  49. package/dist/components/ui/DataTable.js +2 -2
  50. package/dist/components/ui/DataTable.js.map +1 -1
  51. package/dist/components/ui/Skeleton.js +3 -3
  52. package/dist/components/ui/Skeleton.js.map +1 -1
  53. package/dist/components/ui/Toggle.js +1 -1
  54. package/dist/components/ui/Toggle.js.map +1 -1
  55. package/dist/fields/BlockBuilderField.js +1 -1
  56. package/dist/fields/BlockBuilderField.js.map +1 -1
  57. package/dist/fields/MediaField.js +1 -1
  58. package/dist/fields/MediaField.js.map +1 -1
  59. package/dist/fields/PropInput.js +1 -1
  60. package/dist/fields/PropInput.js.map +1 -1
  61. package/dist/index.d.ts +3 -0
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +3 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/layout/Header.js +1 -1
  66. package/dist/layout/Header.js.map +1 -1
  67. package/dist/lib/pages-service.d.ts +7 -0
  68. package/dist/lib/pages-service.d.ts.map +1 -1
  69. package/dist/lib/pages-service.js +11 -0
  70. package/dist/lib/pages-service.js.map +1 -1
  71. package/dist/lib/pages-tree.d.ts +14 -0
  72. package/dist/lib/pages-tree.d.ts.map +1 -1
  73. package/dist/lib/pages-tree.js +51 -0
  74. package/dist/lib/pages-tree.js.map +1 -1
  75. package/dist/views/DocumentEdit.js +1 -1
  76. package/dist/views/DocumentEdit.js.map +1 -1
  77. package/dist/views/FormEditor.js +5 -7
  78. package/dist/views/FormEditor.js.map +1 -1
  79. package/dist/views/FormEntries.d.ts.map +1 -1
  80. package/dist/views/FormEntries.js +18 -14
  81. package/dist/views/FormEntries.js.map +1 -1
  82. package/dist/views/FormSubmissions.d.ts.map +1 -1
  83. package/dist/views/FormSubmissions.js +18 -15
  84. package/dist/views/FormSubmissions.js.map +1 -1
  85. package/dist/views/Forms.js +5 -5
  86. package/dist/views/Forms.js.map +1 -1
  87. package/dist/views/HiddenLoginNotFound.d.ts +6 -0
  88. package/dist/views/HiddenLoginNotFound.d.ts.map +1 -1
  89. package/dist/views/HiddenLoginNotFound.js +6 -0
  90. package/dist/views/HiddenLoginNotFound.js.map +1 -1
  91. package/dist/views/MediaBrowser.d.ts.map +1 -1
  92. package/dist/views/MediaBrowser.js +3 -2
  93. package/dist/views/MediaBrowser.js.map +1 -1
  94. package/dist/views/MediaSeoAudit.d.ts +4 -1
  95. package/dist/views/MediaSeoAudit.d.ts.map +1 -1
  96. package/dist/views/MediaSeoAudit.js +12 -2
  97. package/dist/views/MediaSeoAudit.js.map +1 -1
  98. package/dist/views/PageEditor.d.ts +4 -0
  99. package/dist/views/PageEditor.d.ts.map +1 -1
  100. package/dist/views/PageEditor.js +4 -0
  101. package/dist/views/PageEditor.js.map +1 -1
  102. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  103. package/dist/views/Pages/PagesListView.js +52 -17
  104. package/dist/views/Pages/PagesListView.js.map +1 -1
  105. package/dist/views/Pages/PagesTreeTable.d.ts +11 -1
  106. package/dist/views/Pages/PagesTreeTable.d.ts.map +1 -1
  107. package/dist/views/Pages/PagesTreeTable.js +40 -9
  108. package/dist/views/Pages/PagesTreeTable.js.map +1 -1
  109. package/dist/views/Pages.d.ts +4 -0
  110. package/dist/views/Pages.d.ts.map +1 -1
  111. package/dist/views/Pages.js +4 -0
  112. package/dist/views/Pages.js.map +1 -1
  113. package/dist/views/Redirects.d.ts +4 -0
  114. package/dist/views/Redirects.d.ts.map +1 -1
  115. package/dist/views/Redirects.js +4 -0
  116. package/dist/views/Redirects.js.map +1 -1
  117. package/dist/views/ScriptTags.d.ts.map +1 -1
  118. package/dist/views/ScriptTags.js +4 -3
  119. package/dist/views/ScriptTags.js.map +1 -1
  120. package/dist/views/page-builder/PageBuilder.d.ts +3 -1
  121. package/dist/views/page-builder/PageBuilder.d.ts.map +1 -1
  122. package/dist/views/page-builder/PageBuilder.js +24 -2
  123. package/dist/views/page-builder/PageBuilder.js.map +1 -1
  124. package/dist/views/page-builder/PageTemplates.d.ts.map +1 -1
  125. package/dist/views/page-builder/PageTemplates.js +30 -4
  126. package/dist/views/page-builder/PageTemplates.js.map +1 -1
  127. package/dist/views/seo/ContentTab.js +6 -6
  128. package/dist/views/seo/ContentTab.js.map +1 -1
  129. package/dist/views/seo/RedirectsTab.d.ts.map +1 -1
  130. package/dist/views/seo/RedirectsTab.js +23 -8
  131. package/dist/views/seo/RedirectsTab.js.map +1 -1
  132. package/package.json +1 -1
  133. package/src/AdminRoot.tsx +11 -1
  134. package/src/__tests__/lib/pages-tree.test.ts +49 -0
  135. package/src/__tests__/views/form-entries.render.test.tsx +16 -4
  136. package/src/__tests__/views/form-submissions.render.test.tsx +16 -4
  137. package/src/__tests__/views/media-seo-audit.render.test.tsx +34 -0
  138. package/src/__tests__/views/page-builder-template-seed.render.test.tsx +59 -0
  139. package/src/__tests__/views/page-templates.render.test.tsx +40 -0
  140. package/src/__tests__/views/redirects-pane.render.test.tsx +156 -0
  141. package/src/components/SEOPanel.tsx +9 -9
  142. package/src/components/TipTapEditor.tsx +11 -9
  143. package/src/components/collections/EntryDetailPane.tsx +1 -1
  144. package/src/components/forms/EntriesTable.tsx +12 -3
  145. package/src/components/forms/{EntryDetailDrawer.tsx → FormEntryDetailPane.tsx} +112 -102
  146. package/src/components/forms/FormSchemaPane.tsx +1 -1
  147. package/src/components/script-tags/ScriptTagEditorPane.tsx +1 -1
  148. package/src/components/seo/RedirectEditorPane.tsx +271 -0
  149. package/src/components/seo/SeoEditorPane.tsx +1 -1
  150. package/src/components/ui/DataTable.tsx +3 -3
  151. package/src/components/ui/Skeleton.tsx +8 -8
  152. package/src/components/ui/Toggle.tsx +1 -1
  153. package/src/fields/BlockBuilderField.tsx +1 -1
  154. package/src/fields/MediaField.tsx +1 -1
  155. package/src/fields/PropInput.tsx +1 -1
  156. package/src/index.ts +3 -0
  157. package/src/layout/Header.tsx +2 -2
  158. package/src/lib/pages-service.ts +15 -0
  159. package/src/lib/pages-tree.ts +54 -0
  160. package/src/styles/actuate-tokens.css +4 -0
  161. package/src/styles/theme.css +9 -3
  162. package/src/views/DocumentEdit.tsx +4 -4
  163. package/src/views/FormEditor.tsx +10 -10
  164. package/src/views/FormEntries.tsx +81 -66
  165. package/src/views/FormSubmissions.tsx +78 -64
  166. package/src/views/Forms.tsx +16 -16
  167. package/src/views/HiddenLoginNotFound.tsx +6 -0
  168. package/src/views/MediaBrowser.tsx +14 -10
  169. package/src/views/MediaSeoAudit.tsx +39 -2
  170. package/src/views/PageEditor.tsx +4 -0
  171. package/src/views/Pages/PagesListView.tsx +99 -54
  172. package/src/views/Pages/PagesTreeTable.tsx +204 -109
  173. package/src/views/Pages.tsx +4 -0
  174. package/src/views/Redirects.tsx +4 -0
  175. package/src/views/ScriptTags.tsx +36 -42
  176. package/src/views/page-builder/PageBuilder.tsx +30 -2
  177. package/src/views/page-builder/PageTemplates.tsx +56 -15
  178. package/src/views/seo/ContentTab.tsx +10 -10
  179. package/src/views/seo/RedirectsTab.tsx +145 -109
  180. package/dist/components/forms/EntryDetailDrawer.d.ts +0 -11
  181. package/dist/components/forms/EntryDetailDrawer.d.ts.map +0 -1
  182. package/dist/components/forms/EntryDetailDrawer.js +0 -145
  183. package/dist/components/forms/EntryDetailDrawer.js.map +0 -1
  184. package/dist/components/seo/RedirectEditorDrawer.d.ts +0 -9
  185. package/dist/components/seo/RedirectEditorDrawer.d.ts.map +0 -1
  186. package/dist/components/seo/RedirectEditorDrawer.js +0 -95
  187. package/dist/components/seo/RedirectEditorDrawer.js.map +0 -1
  188. package/src/components/seo/RedirectEditorDrawer.tsx +0 -231
@@ -28,6 +28,7 @@ import {
28
28
  duplicatePage,
29
29
  fetchPagesDataset,
30
30
  groupPages,
31
+ movePage,
31
32
  type BulkResult,
32
33
  type Page,
33
34
  type PageGroupBy,
@@ -35,9 +36,15 @@ import {
35
36
  type PageStatus,
36
37
  type PageTag,
37
38
  } from '../../lib/pages-service.js'
38
- import { buildPageTree, defaultExpandedIds, flattenPageTree } from '../../lib/pages-tree.js'
39
+ import {
40
+ buildPageTree,
41
+ checkReparent,
42
+ defaultExpandedIds,
43
+ flattenPageTree,
44
+ } from '../../lib/pages-tree.js'
39
45
  import { PostStatusBadge, SeoScoreIndicator } from '../../components/posts/badges.js'
40
46
  import { PageTagChip, PageTagsCell } from '../../components/pages/tag-badges.js'
47
+ import { Button } from '../../components/ui/Button.js'
41
48
  import { ConfirmDialog } from '../../components/ui/ConfirmDialog.js'
42
49
  import { RowActionButton, RowActions } from '../../components/ui/RowActions.js'
43
50
  import { PagesTreeTable } from './PagesTreeTable.js'
@@ -382,6 +389,38 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
382
389
  }
383
390
  }
384
391
 
392
+ /** Drag-reparent from the tree view: `sourceId` becomes a child of `targetId`. */
393
+ const runReparent = async (sourceId: string, targetId: string) => {
394
+ const source = allPages.find((p) => p.id === sourceId)
395
+ const target = allPages.find((p) => p.id === targetId)
396
+ if (!source || !target) return
397
+ const check = checkReparent(treeRoots, sourceId, targetId)
398
+ if (!check.ok) {
399
+ toast.error(check.reason)
400
+ return
401
+ }
402
+ const promise = movePage(sourceId, targetId).then((r) => {
403
+ if (r.error) throw new Error(r.error)
404
+ })
405
+ toast.promise(promise, {
406
+ loading: `Moving "${source.title}"…`,
407
+ success: `"${source.title}" moved under "${target.title}"`,
408
+ error: (e: Error) => e.message || 'Move failed',
409
+ })
410
+ try {
411
+ await promise
412
+ // Make sure the new parent is expanded so the moved row stays visible.
413
+ setExpandedOverride((prev) => {
414
+ const next = new Set(prev ?? defaultExpanded)
415
+ next.add(targetId)
416
+ return next
417
+ })
418
+ setRefreshToken((t) => t + 1)
419
+ } catch {
420
+ /* surfaced via toast.promise */
421
+ }
422
+ }
423
+
385
424
  const runRowDelete = async (p: Page) => {
386
425
  const promise = deletePage(p.id).then((r) => {
387
426
  if (r.error) throw new Error(r.error)
@@ -412,28 +451,30 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
412
451
  the design's `.page-hdr` / `.hdr-r` layout. */}
413
452
  <div className="flex flex-wrap items-start justify-between gap-3">
414
453
  <div className="flex flex-col gap-1">
415
- <h1 className="text-foreground">Pages</h1>
416
- <p className="text-muted-foreground text-sm">
454
+ <h1 className="text-[var(--txt)]">Pages</h1>
455
+ <p className="text-sm text-[var(--sub)]">
417
456
  {treeMode
418
457
  ? `${grandTotal} page${grandTotal === 1 ? '' : 's'} total`
419
458
  : `${grandTotal} page${grandTotal === 1 ? '' : 's'} · organized by tags`}
420
459
  </p>
421
460
  </div>
422
- <button
461
+ <Button
423
462
  type="button"
463
+ variant="primary"
464
+ size="sm"
465
+ className="shadow-sm"
424
466
  onClick={() => onNavigate('/pages/new')}
425
- className="bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-medium shadow-sm transition-colors focus:outline-none focus-visible:ring-2"
467
+ leftIcon={<Plus size={16} aria-hidden />}
426
468
  >
427
- <Plus className="h-3.5 w-3.5" aria-hidden />
428
469
  New page
429
- </button>
470
+ </Button>
430
471
  </div>
431
472
 
432
473
  {/* Controls row */}
433
474
  <div className="flex flex-wrap items-center gap-2">
434
475
  <div className="relative max-w-sm flex-1">
435
476
  <Search
436
- className="text-muted-foreground absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2"
477
+ className="absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-[var(--sub)]"
437
478
  aria-hidden
438
479
  />
439
480
  <input
@@ -442,7 +483,7 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
442
483
  onChange={(e) => setSearch(e.target.value)}
443
484
  aria-label="Search pages"
444
485
  placeholder="Search pages…"
445
- className="border-border bg-card text-foreground placeholder:text-muted-foreground focus:border-primary focus:ring-primary/30 w-full rounded-lg border py-2 pr-3 pl-9 text-sm shadow-sm focus:ring-2 focus:outline-none"
486
+ className="focus:border-primary focus:ring-primary/30 w-full rounded-lg border border-[var(--bdr)] bg-[var(--card)] py-2 pr-3 pl-9 text-sm text-[var(--txt)] shadow-sm placeholder:text-[var(--sub)] focus:ring-2 focus:outline-none"
446
487
  />
447
488
  </div>
448
489
 
@@ -479,7 +520,7 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
479
520
 
480
521
  <ViewModeToggle mode={viewMode} onChange={changeViewMode} />
481
522
 
482
- <span className="text-muted-foreground ml-auto text-sm tabular-nums">
523
+ <span className="ml-auto text-sm text-[var(--sub)] tabular-nums">
483
524
  {treeMode
484
525
  ? treeFiltersActive
485
526
  ? `${treeRows.length} result${treeRows.length === 1 ? '' : 's'}`
@@ -492,7 +533,7 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
492
533
  with the page hierarchy) */}
493
534
  {!treeMode && (
494
535
  <div className="flex flex-wrap items-center gap-2">
495
- <span className="text-muted-foreground text-[10px] font-medium tracking-wider uppercase">
536
+ <span className="text-[10px] font-medium tracking-wider text-[var(--sub)] uppercase">
496
537
  Tags
497
538
  </span>
498
539
  {tags.map((t) => (
@@ -509,7 +550,7 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
509
550
  <button
510
551
  type="button"
511
552
  onClick={() => setSelectedTags(new Set())}
512
- className="text-muted-foreground hover:text-foreground inline-flex items-center gap-1 text-xs"
553
+ className="inline-flex items-center gap-1 text-xs text-[var(--sub)] hover:text-[var(--txt)]"
513
554
  >
514
555
  <X className="h-3 w-3" aria-hidden />
515
556
  Clear tags
@@ -543,7 +584,7 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
543
584
  )}
544
585
 
545
586
  {/* Table card */}
546
- <div className="bg-card border-border overflow-hidden rounded-xl border shadow-sm">
587
+ <div className="overflow-hidden rounded-xl border border-[var(--bdr)] bg-[var(--card)] shadow-sm">
547
588
  {treeMode ? (
548
589
  <PagesTreeTable
549
590
  rows={treeRows}
@@ -559,6 +600,8 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
559
600
  onAddSubPage={(p) => onNavigate(`/pages/new?parent=${encodeURIComponent(p.id)}`)}
560
601
  onDuplicate={(p) => void runDuplicate(p)}
561
602
  onDelete={(p) => setRowDelete(p)}
603
+ onReparent={(sourceId, targetId) => void runReparent(sourceId, targetId)}
604
+ canReparent={(sourceId, targetId) => checkReparent(treeRoots, sourceId, targetId).ok}
562
605
  onRetry={() => setRefreshToken((t) => t + 1)}
563
606
  onClearFilters={clearFilters}
564
607
  onNewPage={() => onNavigate('/pages/new')}
@@ -579,15 +622,15 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
579
622
  <col style={{ width: '80px' }} />
580
623
  <col style={{ width: '110px' }} />
581
624
  </colgroup>
582
- <thead className="border-border bg-muted/40 border-b">
583
- <tr className="text-muted-foreground text-xs">
625
+ <thead className="bg-muted/40 border-b border-[var(--bdr)]">
626
+ <tr className="text-xs text-[var(--sub)]">
584
627
  <th scope="col" className="px-3 py-2.5 text-left">
585
628
  <input
586
629
  type="checkbox"
587
630
  aria-label="Select all pages on this page"
588
631
  checked={allOnPageSelected}
589
632
  onChange={(e) => toggleAllOnPage(e.target.checked)}
590
- className="border-border accent-primary rounded"
633
+ className="accent-primary rounded border-[var(--bdr)]"
591
634
  />
592
635
  </th>
593
636
  <th
@@ -668,7 +711,7 @@ export function PagesListView({ onNavigate }: PagesListViewProps) {
668
711
  {!loading && error && (
669
712
  <tr>
670
713
  <td colSpan={8} className="px-3 py-12 text-center">
671
- <div className="text-foreground mb-2 text-sm font-medium">{error}</div>
714
+ <div className="mb-2 text-sm font-medium text-[var(--txt)]">{error}</div>
672
715
  <button
673
716
  onClick={() => setRefreshToken((t) => t + 1)}
674
717
  className="text-primary text-sm hover:underline"
@@ -779,8 +822,8 @@ function SortHeader({
779
822
  <button
780
823
  type="button"
781
824
  onClick={() => onSort(field)}
782
- className={`hover:text-foreground inline-flex items-center gap-1 text-xs font-medium tracking-wider uppercase transition-colors ${
783
- active ? 'text-foreground' : 'text-muted-foreground'
825
+ className={`inline-flex items-center gap-1 text-xs font-medium tracking-wider uppercase transition-colors hover:text-[var(--txt)] ${
826
+ active ? 'text-[var(--txt)]' : 'text-[var(--sub)]'
784
827
  }`}
785
828
  >
786
829
  <span>{label}</span>
@@ -849,12 +892,12 @@ function FilterSelect({
849
892
  options: { value: string; label: string }[]
850
893
  }) {
851
894
  return (
852
- <label className="text-foreground inline-flex items-center gap-1.5 text-sm">
853
- <span className="text-muted-foreground">{label}:</span>
895
+ <label className="inline-flex items-center gap-1.5 text-sm text-[var(--txt)]">
896
+ <span className="text-[var(--sub)]">{label}:</span>
854
897
  <select
855
898
  value={value}
856
899
  onChange={(e) => onChange(e.target.value)}
857
- className="border-border bg-card text-foreground focus:border-primary focus:ring-primary/30 rounded-lg border px-2.5 py-1.5 text-sm shadow-sm focus:ring-2 focus:outline-none"
900
+ className="focus:border-primary focus:ring-primary/30 rounded-lg border border-[var(--bdr)] bg-[var(--card)] px-2.5 py-1.5 text-sm text-[var(--txt)] shadow-sm focus:ring-2 focus:outline-none"
858
901
  >
859
902
  {options.map((o) => (
860
903
  <option key={o.value} value={o.value}>
@@ -882,12 +925,12 @@ function GroupBlock({
882
925
  return (
883
926
  <>
884
927
  {showHeader && (
885
- <tr className="bg-muted/30 border-border border-b">
928
+ <tr className="bg-muted/30 border-b border-[var(--bdr)]">
886
929
  <td colSpan={8} className="px-3 py-2">
887
- <span className="text-foreground inline-flex items-center gap-2 text-xs font-medium tracking-wider uppercase">
930
+ <span className="inline-flex items-center gap-2 text-xs font-medium tracking-wider text-[var(--txt)] uppercase">
888
931
  {color && <span aria-hidden className={`h-2 w-2 rounded-full ${dotFor(color)}`} />}
889
932
  {label}
890
- <span className="text-muted-foreground tabular-nums">{count}</span>
933
+ <span className="text-[var(--sub)] tabular-nums">{count}</span>
891
934
  </span>
892
935
  </td>
893
936
  </tr>
@@ -934,19 +977,19 @@ function BulkActionBar({
934
977
  }) {
935
978
  return (
936
979
  <div className="border-primary/30 bg-primary/5 flex flex-wrap items-center gap-2 rounded-lg border px-4 py-2">
937
- <span className="text-foreground text-sm font-medium">{count} selected</span>
980
+ <span className="text-sm font-medium text-[var(--txt)]">{count} selected</span>
938
981
  <div className="bg-border h-4 w-px" aria-hidden />
939
982
  <button
940
983
  type="button"
941
984
  onClick={onPublish}
942
- className="text-foreground hover:bg-accent rounded-md px-2 py-1 text-sm transition-colors"
985
+ className="rounded-md px-2 py-1 text-sm text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)]"
943
986
  >
944
987
  Publish
945
988
  </button>
946
989
  <button
947
990
  type="button"
948
991
  onClick={onDraft}
949
- className="text-foreground hover:bg-accent rounded-md px-2 py-1 text-sm transition-colors"
992
+ className="rounded-md px-2 py-1 text-sm text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)]"
950
993
  >
951
994
  Move to draft
952
995
  </button>
@@ -965,7 +1008,7 @@ function BulkActionBar({
965
1008
  <button
966
1009
  type="button"
967
1010
  onClick={onClear}
968
- className="text-muted-foreground hover:text-foreground ml-auto inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors"
1011
+ className="ml-auto inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm text-[var(--sub)] transition-colors hover:text-[var(--txt)]"
969
1012
  >
970
1013
  <X className="h-3.5 w-3.5" aria-hidden />
971
1014
  Clear
@@ -988,7 +1031,7 @@ function TagMenu({
988
1031
  <DropdownMenu.Trigger asChild>
989
1032
  <button
990
1033
  type="button"
991
- className="text-foreground hover:bg-accent inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm transition-colors"
1034
+ className="inline-flex items-center gap-1 rounded-md px-2 py-1 text-sm text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)]"
992
1035
  >
993
1036
  {label}
994
1037
  <ChevronDown className="h-3 w-3" aria-hidden />
@@ -998,16 +1041,16 @@ function TagMenu({
998
1041
  <DropdownMenu.Content
999
1042
  align="start"
1000
1043
  sideOffset={4}
1001
- className="border-border bg-popover z-50 max-h-64 w-44 overflow-auto rounded-lg border p-1 shadow-md"
1044
+ className="bg-popover z-50 max-h-64 w-44 overflow-auto rounded-lg border border-[var(--bdr)] p-1 shadow-md"
1002
1045
  >
1003
1046
  {tags.length === 0 ? (
1004
- <div className="text-muted-foreground px-2 py-1.5 text-xs">No tags yet</div>
1047
+ <div className="px-2 py-1.5 text-xs text-[var(--sub)]">No tags yet</div>
1005
1048
  ) : (
1006
1049
  tags.map((t) => (
1007
1050
  <DropdownMenu.Item
1008
1051
  key={t.slug}
1009
1052
  onSelect={() => onPick(t.slug)}
1010
- className="text-foreground focus:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm outline-none"
1053
+ className="focus:bg-accent flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 text-sm text-[var(--txt)] outline-none"
1011
1054
  >
1012
1055
  <span aria-hidden className={`h-2 w-2 rounded-full ${dotFor(t.color ?? 'gray')}`} />
1013
1056
  {t.name}
@@ -1039,8 +1082,8 @@ function PageRow({
1039
1082
  }) {
1040
1083
  return (
1041
1084
  <tr
1042
- className={`border-border hover:bg-muted/30 group border-b transition-colors last:border-b-0 ${
1043
- selected ? 'bg-primary/5' : ''
1085
+ className={`group border-b border-[var(--bdr)] transition-colors last:border-b-0 hover:bg-[var(--acc-l)] ${
1086
+ selected ? 'bg-[var(--acc-l)]' : ''
1044
1087
  }`}
1045
1088
  >
1046
1089
  <td className="px-3 py-3" onClick={(e) => e.stopPropagation()}>
@@ -1049,17 +1092,17 @@ function PageRow({
1049
1092
  aria-label={`Select "${page.title}"`}
1050
1093
  checked={selected}
1051
1094
  onChange={onSelect}
1052
- className="border-border accent-primary rounded"
1095
+ className="accent-primary rounded border-[var(--bdr)]"
1053
1096
  />
1054
1097
  </td>
1055
1098
  <td className="px-3 py-3">
1056
1099
  <button type="button" onClick={onOpen} className="flex w-full items-center gap-3 text-left">
1057
1100
  <span aria-hidden className="bg-muted block h-9 w-9 shrink-0 rounded-md" />
1058
1101
  <span className="min-w-0">
1059
- <span className="text-foreground group-hover:text-primary block truncate text-sm font-medium">
1102
+ <span className="block truncate text-sm font-medium text-[var(--txt)] group-hover:text-[var(--acc)]">
1060
1103
  {page.title}
1061
1104
  </span>
1062
- <span className="text-muted-foreground block truncate text-xs">{page.path}</span>
1105
+ <span className="block truncate text-xs text-[var(--sub)]">{page.path}</span>
1063
1106
  </span>
1064
1107
  </button>
1065
1108
  </td>
@@ -1070,17 +1113,17 @@ function PageRow({
1070
1113
  <div className="flex items-center gap-2">
1071
1114
  <span
1072
1115
  aria-hidden
1073
- className="bg-muted text-muted-foreground inline-flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-medium"
1116
+ className="bg-muted inline-flex h-7 w-7 items-center justify-center rounded-full text-[11px] font-medium text-[var(--sub)]"
1074
1117
  >
1075
1118
  {page.authorInitials}
1076
1119
  </span>
1077
- <span className="text-foreground truncate text-sm">{page.authorName}</span>
1120
+ <span className="truncate text-sm text-[var(--txt)]">{page.authorName}</span>
1078
1121
  </div>
1079
1122
  </td>
1080
1123
  <td className="px-3 py-3">
1081
1124
  <PostStatusBadge status={page.status} />
1082
1125
  </td>
1083
- <td className="text-muted-foreground px-3 py-3 text-sm tabular-nums">
1126
+ <td className="px-3 py-3 text-sm text-[var(--sub)] tabular-nums">
1084
1127
  {formatDateShort(page.lastModified)}
1085
1128
  </td>
1086
1129
  <td className="px-3 py-3">
@@ -1117,7 +1160,7 @@ function LoadingRows() {
1117
1160
  return (
1118
1161
  <>
1119
1162
  {Array.from({ length: 8 }).map((_, i) => (
1120
- <tr key={i} className="border-border border-b last:border-b-0">
1163
+ <tr key={i} className="border-b border-[var(--bdr)] last:border-b-0">
1121
1164
  {Array.from({ length: 8 }).map((__, j) => (
1122
1165
  <td key={j} className="px-3 py-3">
1123
1166
  <div className="bg-muted h-4 w-full animate-pulse rounded" />
@@ -1141,10 +1184,10 @@ function EmptyRow({
1141
1184
  return (
1142
1185
  <tr>
1143
1186
  <td colSpan={8} className="px-3 py-16 text-center">
1144
- <div className="text-foreground mb-2 text-sm font-medium">
1187
+ <div className="mb-2 text-sm font-medium text-[var(--txt)]">
1145
1188
  {filtersActive ? 'No pages match your filters' : 'No pages yet'}
1146
1189
  </div>
1147
- <div className="text-muted-foreground mb-4 text-sm">
1190
+ <div className="mb-4 text-sm text-[var(--sub)]">
1148
1191
  {filtersActive
1149
1192
  ? 'Try clearing the search, tags, or status filter above.'
1150
1193
  : 'Create your first page to get started.'}
@@ -1154,14 +1197,16 @@ function EmptyRow({
1154
1197
  Clear filters
1155
1198
  </button>
1156
1199
  ) : (
1157
- <button
1200
+ <Button
1158
1201
  type="button"
1202
+ variant="primary"
1203
+ size="sm"
1204
+ className="shadow-sm"
1159
1205
  onClick={onNewPage}
1160
- className="bg-primary text-primary-foreground hover:bg-primary/90 inline-flex items-center gap-1.5 rounded-lg px-3.5 py-1.5 text-sm font-medium shadow-sm transition-colors"
1206
+ leftIcon={<Plus size={16} aria-hidden />}
1161
1207
  >
1162
- <Plus className="h-3.5 w-3.5" aria-hidden />
1163
1208
  New page
1164
- </button>
1209
+ </Button>
1165
1210
  )}
1166
1211
  </td>
1167
1212
  </tr>
@@ -1184,8 +1229,8 @@ function PaginationFooter({
1184
1229
  const start = total === 0 ? 0 : (page - 1) * pageSize + 1
1185
1230
  const end = Math.min(total, page * pageSize)
1186
1231
  return (
1187
- <div className="border-border flex items-center justify-between border-t px-4 py-3 text-sm">
1188
- <span className="text-muted-foreground tabular-nums">
1232
+ <div className="flex items-center justify-between border-t border-[var(--bdr)] px-4 py-3 text-sm">
1233
+ <span className="text-[var(--sub)] tabular-nums">
1189
1234
  {start}–{end} of {total} page{total === 1 ? '' : 's'}
1190
1235
  </span>
1191
1236
  <nav className="flex items-center gap-1" aria-label="Pages pagination">
@@ -1194,7 +1239,7 @@ function PaginationFooter({
1194
1239
  onClick={() => onPage(Math.max(1, page - 1))}
1195
1240
  disabled={page === 1}
1196
1241
  aria-label="Previous page"
1197
- className="border-border bg-background text-foreground hover:bg-accent inline-flex h-8 w-8 items-center justify-center rounded-md border transition-colors disabled:cursor-not-allowed disabled:opacity-40"
1242
+ className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-[var(--bdr)] bg-[var(--bg)] text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)] disabled:cursor-not-allowed disabled:opacity-40"
1198
1243
  >
1199
1244
  <ChevronLeft className="h-4 w-4" aria-hidden />
1200
1245
  </button>
@@ -1203,7 +1248,7 @@ function PaginationFooter({
1203
1248
  <span
1204
1249
  key={`gap-${idx}`}
1205
1250
  aria-hidden
1206
- className="text-muted-foreground inline-flex h-8 w-8 items-center justify-center text-sm"
1251
+ className="inline-flex h-8 w-8 items-center justify-center text-sm text-[var(--sub)]"
1207
1252
  >
1208
1253
 
1209
1254
  </span>
@@ -1216,7 +1261,7 @@ function PaginationFooter({
1216
1261
  className={`inline-flex h-8 w-8 items-center justify-center rounded-md text-sm tabular-nums transition-colors ${
1217
1262
  p === page
1218
1263
  ? 'bg-primary text-primary-foreground'
1219
- : 'border-border bg-background text-foreground hover:bg-accent border'
1264
+ : 'border border-[var(--bdr)] bg-[var(--bg)] text-[var(--txt)] hover:bg-[var(--acc-l)]'
1220
1265
  }`}
1221
1266
  >
1222
1267
  {p}
@@ -1228,7 +1273,7 @@ function PaginationFooter({
1228
1273
  onClick={() => onPage(Math.min(totalPages, page + 1))}
1229
1274
  disabled={page === totalPages}
1230
1275
  aria-label="Next page"
1231
- className="border-border bg-background text-foreground hover:bg-accent inline-flex h-8 w-8 items-center justify-center rounded-md border transition-colors disabled:cursor-not-allowed disabled:opacity-40"
1276
+ className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-[var(--bdr)] bg-[var(--bg)] text-[var(--txt)] transition-colors hover:bg-[var(--acc-l)] disabled:cursor-not-allowed disabled:opacity-40"
1232
1277
  >
1233
1278
  <ChevronRight className="h-4 w-4" aria-hidden />
1234
1279
  </button>