convex-cms 0.0.9-alpha.8 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/README.md +27 -0
  2. package/admin/src/components/Header.tsx +1 -1
  3. package/admin/src/components/RouteGuard.tsx +1 -1
  4. package/admin/src/components/UploadDropzone.tsx +1 -1
  5. package/admin/src/components/cmsds/CmsFilterBar.tsx +74 -0
  6. package/admin/src/components/cmsds/CmsInput.tsx +24 -0
  7. package/admin/src/components/cmsds/CmsPagination.tsx +79 -0
  8. package/admin/src/components/cmsds/CmsSelect.tsx +59 -0
  9. package/admin/src/components/cmsds/CmsStatCard.tsx +79 -0
  10. package/admin/src/components/cmsds/CmsStatusBadge.tsx +1 -1
  11. package/admin/src/components/cmsds/index.ts +5 -0
  12. package/admin/src/components/ui/sidebar.tsx +1 -1
  13. package/admin/src/contexts/AuthContext.tsx +1 -1
  14. package/admin/src/contexts/ThemeContext.tsx +85 -17
  15. package/admin/src/embed/components/EmbedHeader.tsx +11 -9
  16. package/admin/src/embed/components/EmbedLayout.tsx +2 -6
  17. package/admin/src/embed/components/EmbedSidebar.tsx +16 -13
  18. package/admin/src/embed/contexts/ApiContext.tsx +1 -1
  19. package/admin/src/embed/index.tsx +3 -2
  20. package/admin/src/embed/types.ts +6 -0
  21. package/admin/src/hooks/usePermissions.ts +1 -1
  22. package/admin/src/index.css +432 -0
  23. package/admin/src/lib/cmsExports.ts +6 -0
  24. package/admin/src/pages/ContentPage.tsx +116 -172
  25. package/admin/src/pages/ContentTypeEntriesPage.tsx +120 -194
  26. package/admin/src/pages/ContentTypesPage.tsx +136 -139
  27. package/admin/src/pages/DashboardPage.tsx +15 -55
  28. package/admin/src/pages/MediaPage.tsx +31 -57
  29. package/admin/src/pages/SettingsPage.tsx +5 -1
  30. package/admin/src/pages/TrashPage.tsx +115 -170
  31. package/admin/src/routes/__root.tsx +1 -1
  32. package/admin-dist/nitro.json +1 -1
  33. package/admin-dist/public/assets/{CmsEmptyState-DTlpzjOI.js → CmsEmptyState-BKeL4DBB.js} +1 -1
  34. package/admin-dist/public/assets/CmsFilterBar-CEpMHd_c.js +1 -0
  35. package/admin-dist/public/assets/{CmsPageHeader-0REGRH4X.js → CmsPageHeader-CIEkTbyH.js} +1 -1
  36. package/admin-dist/public/assets/{CmsStatusBadge-D_n8u8xa.js → CmsStatusBadge-BFMOsfMW.js} +1 -1
  37. package/admin-dist/public/assets/{CmsSurface-BHmvNai4.js → CmsSurface-kqqaFKUI.js} +1 -1
  38. package/admin-dist/public/assets/CmsTable-Db53Exq0.js +1 -0
  39. package/admin-dist/public/assets/ContentEntryEditor-Ct7cHayy.js +4 -0
  40. package/admin-dist/public/assets/TaxonomyFilter-Bm1DI1A7.js +1 -0
  41. package/admin-dist/public/assets/_contentTypeId-BekeCblX.js +1 -0
  42. package/admin-dist/public/assets/{_entryId-jPXz4z9T.js → _entryId-CoZDE0l0.js} +1 -1
  43. package/admin-dist/public/assets/{alert-CG97cMfC.js → alert-CpLdsTGU.js} +1 -1
  44. package/admin-dist/public/assets/{badge-C6qt24oj.js → badge-BQAotc5B.js} +1 -1
  45. package/admin-dist/public/assets/{circle-check-big-PltpxuB1.js → circle-check-big-BF3Y5nES.js} +1 -1
  46. package/admin-dist/public/assets/{command-CJ8i86fd.js → command-lEq6f_Ee.js} +1 -1
  47. package/admin-dist/public/assets/content-DH6k0dN6.js +1 -0
  48. package/admin-dist/public/assets/content-types-DHr9tc2V.js +1 -0
  49. package/admin-dist/public/assets/index-Cf0lbl0G.js +1 -0
  50. package/admin-dist/public/assets/index-D-4wFfgU.css +1 -0
  51. package/admin-dist/public/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  52. package/admin-dist/public/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  53. package/admin-dist/public/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  54. package/admin-dist/public/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  55. package/admin-dist/public/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  56. package/admin-dist/public/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  57. package/admin-dist/public/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  58. package/admin-dist/public/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2 +0 -0
  59. package/admin-dist/public/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff +0 -0
  60. package/admin-dist/public/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff +0 -0
  61. package/admin-dist/public/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2 +0 -0
  62. package/admin-dist/public/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  63. package/admin-dist/public/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  64. package/admin-dist/public/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2 +0 -0
  65. package/admin-dist/public/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff +0 -0
  66. package/admin-dist/public/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff +0 -0
  67. package/admin-dist/public/assets/main-B-6700eG.js +137 -0
  68. package/admin-dist/public/assets/media-DY5zD52L.js +1 -0
  69. package/admin-dist/public/assets/{new._contentTypeId-qsvo01mH.js → new._contentTypeId-Dq_NqTQV.js} +1 -1
  70. package/admin-dist/public/assets/{pencil-gAL0R34f.js → pencil-CI_KfxSx.js} +1 -1
  71. package/admin-dist/public/assets/refresh-cw-BrXg9a2r.js +1 -0
  72. package/admin-dist/public/assets/rotate-ccw-PwzxdPxd.js +1 -0
  73. package/admin-dist/public/assets/{scroll-area-CJBhf9pf.js → scroll-area-DX_nZYp8.js} +1 -1
  74. package/admin-dist/public/assets/{search-WXp6KxDJ.js → search-DlwBH4C5.js} +1 -1
  75. package/admin-dist/public/assets/settings-2mx3_ORG.js +1 -0
  76. package/admin-dist/public/assets/{switch-Ck9ecqEX.js → switch-CjPi4DKH.js} +1 -1
  77. package/admin-dist/public/assets/{tabs-vQYu8rjC.js → tabs-B5X37GEM.js} +1 -1
  78. package/admin-dist/public/assets/tanstack-adapter-KSm-nO5L.js +1 -0
  79. package/admin-dist/public/assets/{taxonomies-DvILUNvr.js → taxonomies-CHjJKNlR.js} +1 -1
  80. package/admin-dist/public/assets/trash-Cle-tcqq.js +1 -0
  81. package/admin-dist/public/assets/{useBreadcrumbLabel-tlSh7dtO.js → useBreadcrumbLabel-yZQG_N_3.js} +1 -1
  82. package/admin-dist/public/assets/{usePermissions-BTGdTOJS.js → usePermissions-D6vsoaJf.js} +1 -1
  83. package/admin-dist/server/_libs/convex-helpers.mjs +1077 -2
  84. package/admin-dist/server/_libs/convex.mjs +222 -13
  85. package/admin-dist/server/_libs/lucide-react.mjs +57 -51
  86. package/admin-dist/server/_ssr/{CmsEmptyState-CB6e53i5.mjs → CmsEmptyState-DzzuQG0S.mjs} +1 -1
  87. package/admin-dist/server/_ssr/CmsFilterBar-C5XADS12.mjs +81 -0
  88. package/admin-dist/server/_ssr/{CmsPageHeader-COUHuECp.mjs → CmsPageHeader-DZ6h7smh.mjs} +1 -1
  89. package/admin-dist/server/_ssr/{CmsStatusBadge-kMTL6koE.mjs → CmsStatusBadge-D-YFSAa1.mjs} +3 -3
  90. package/admin-dist/server/_ssr/{CmsSurface-D1HDYjRg.mjs → CmsSurface-Cv51NBLZ.mjs} +1 -1
  91. package/admin-dist/server/_ssr/CmsTable-DG88C5nO.mjs +189 -0
  92. package/admin-dist/server/_ssr/{ContentEntryEditor-Bq8FR_uK.mjs → ContentEntryEditor-CRjwXB17.mjs} +10 -10
  93. package/admin-dist/server/_ssr/{TaxonomyFilter-bm_p4ADg.mjs → TaxonomyFilter-xGwcgtjr.mjs} +3 -3
  94. package/admin-dist/server/_ssr/{_contentTypeId-B7obLmi_.mjs → _contentTypeId-DRCfeKkm.mjs} +53 -12
  95. package/admin-dist/server/_ssr/{_entryId-B4zhQqFg.mjs → _entryId-DULm2TDy.mjs} +11 -11
  96. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-iX3K33p1.mjs +4 -0
  97. package/admin-dist/server/_ssr/{badge-NOEC9bkk.mjs → badge-CbjIvhb6.mjs} +1 -1
  98. package/admin-dist/server/_ssr/{command-h4-OYNBo.mjs → command-xB2uiYps.mjs} +2 -2
  99. package/admin-dist/server/_ssr/{content-CShtLuhK.mjs → content-BfLBaJCZ.mjs} +108 -138
  100. package/admin-dist/server/_ssr/{content-types-PeyRyfbc.mjs → content-types-DZbF6O2q.mjs} +130 -119
  101. package/admin-dist/server/_ssr/{index-CplFXpGg.mjs → index-Cfe8sZv5.mjs} +65 -39
  102. package/admin-dist/server/_ssr/index.mjs +2 -2
  103. package/admin-dist/server/_ssr/{media-QAkNdX54.mjs → media-Bds2AnPC.mjs} +36 -56
  104. package/admin-dist/server/_ssr/{new._contentTypeId-DEJyMphJ.mjs → new._contentTypeId-DGvz_tlW.mjs} +10 -10
  105. package/admin-dist/server/_ssr/{router-CQXMuGMF.mjs → router-DxF7GBcO.mjs} +8804 -4995
  106. package/admin-dist/server/_ssr/{scroll-area-B7zoNyWB.mjs → scroll-area-DLDlXI07.mjs} +1 -1
  107. package/admin-dist/server/_ssr/{settings-CNaqVa4D.mjs → settings-BbaiS6z9.mjs} +13 -10
  108. package/admin-dist/server/_ssr/{switch-BKZhvryc.mjs → switch-Bl89Pfxu.mjs} +1 -1
  109. package/admin-dist/server/_ssr/{tabs-DtIIQxiD.mjs → tabs-QkbR0iir.mjs} +3 -3
  110. package/admin-dist/server/_ssr/{tanstack-adapter-CLavdbUY.mjs → tanstack-adapter-CKknPtcU.mjs} +19 -1
  111. package/admin-dist/server/_ssr/{taxonomies-vIZYICzr.mjs → taxonomies-S_Ontd0z.mjs} +9 -9
  112. package/admin-dist/server/_ssr/{trash-7yGR4-dF.mjs → trash-BzAIsbbN.mjs} +109 -132
  113. package/admin-dist/server/_ssr/{useBreadcrumbLabel-DR5FaAMf.mjs → useBreadcrumbLabel-BjiR1fM_.mjs} +1 -1
  114. package/admin-dist/server/_ssr/{usePermissions-DKkpETj_.mjs → usePermissions-CDHN95Nz.mjs} +1 -1
  115. package/admin-dist/server/index.mjs +284 -165
  116. package/package.json +3 -2
  117. package/admin/src/styles/globals.css +0 -104
  118. package/admin/src/styles/tailwind-config.css +0 -99
  119. package/admin/src/styles/theme.css +0 -261
  120. package/admin-dist/public/assets/CmsToolbar-CY6GV2L8.js +0 -1
  121. package/admin-dist/public/assets/ContentEntryEditor-CRgcRkk5.js +0 -4
  122. package/admin-dist/public/assets/TaxonomyFilter-Ohv5Jg9c.js +0 -1
  123. package/admin-dist/public/assets/_contentTypeId-C_vJq22X.js +0 -1
  124. package/admin-dist/public/assets/content-pKaIL2ru.js +0 -1
  125. package/admin-dist/public/assets/content-types-Bl_8I1Re.js +0 -1
  126. package/admin-dist/public/assets/globals-CoCRjt0K.css +0 -1
  127. package/admin-dist/public/assets/index-CtHq_P5q.js +0 -1
  128. package/admin-dist/public/assets/main-CA-4LyFT.js +0 -107
  129. package/admin-dist/public/assets/media-Bl1tBbJQ.js +0 -1
  130. package/admin-dist/public/assets/refresh-cw-sdVUGJNs.js +0 -1
  131. package/admin-dist/public/assets/rotate-ccw-6OcXCcxb.js +0 -1
  132. package/admin-dist/public/assets/settings-D8crrFCn.js +0 -1
  133. package/admin-dist/public/assets/tanstack-adapter-BRt2CUCw.js +0 -1
  134. package/admin-dist/public/assets/trash-YyYaC3L9.js +0 -1
  135. package/admin-dist/server/_ssr/CmsToolbar-NB014hsd.mjs +0 -48
  136. package/admin-dist/server/_ssr/_tanstack-start-manifest_v-DndoqCo7.mjs +0 -4
@@ -13,8 +13,7 @@ import {
13
13
  CardHeader,
14
14
  CardTitle,
15
15
  } from "~/components/ui/card";
16
- import { Skeleton } from "~/components/ui/skeleton";
17
- import { CmsPageHeader } from "~/components/cmsds";
16
+ import { CmsPageHeader, CmsStatCard } from "~/components/cmsds";
18
17
  import { SchemaDriftWarning } from "~/components/SchemaDriftWarning";
19
18
  import { FileText, Image, Layers, Settings, TrendingUp } from "lucide-react";
20
19
  import type { AdminNavigation } from "~/lib/navigation";
@@ -72,40 +71,24 @@ export function DashboardPage({ api, navigation }: DashboardPageProps) {
72
71
  <h2 className="text-lg font-semibold">Quick Stats</h2>
73
72
  </div>
74
73
  <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
75
- <StatCard
76
- label="Content Types"
77
- value={
78
- isLoading
79
- ? undefined
80
- : hasError
81
- ? "—"
82
- : String(stats.contentTypes)
83
- }
74
+ <CmsStatCard
75
+ title="Content Types"
76
+ value={hasError ? "—" : String(stats?.contentTypes ?? 0)}
84
77
  isLoading={isLoading}
85
78
  />
86
- <StatCard
87
- label="Content Entries"
88
- value={
89
- isLoading
90
- ? undefined
91
- : hasError
92
- ? "—"
93
- : String(stats.contentEntries)
94
- }
79
+ <CmsStatCard
80
+ title="Content Entries"
81
+ value={hasError ? "—" : String(stats?.contentEntries ?? 0)}
95
82
  isLoading={isLoading}
96
83
  />
97
- <StatCard
98
- label="Media Assets"
99
- value={
100
- isLoading ? undefined : hasError ? "—" : String(stats.mediaAssets)
101
- }
84
+ <CmsStatCard
85
+ title="Media Assets"
86
+ value={hasError ? "—" : String(stats?.mediaAssets ?? 0)}
102
87
  isLoading={isLoading}
103
88
  />
104
- <StatCard
105
- label="Published"
106
- value={
107
- isLoading ? undefined : hasError ? "—" : String(stats.published)
108
- }
89
+ <CmsStatCard
90
+ title="Published"
91
+ value={hasError ? "—" : String(stats?.published ?? 0)}
109
92
  isLoading={isLoading}
110
93
  />
111
94
  </div>
@@ -127,9 +110,9 @@ function DashboardCard({
127
110
  }) {
128
111
  return (
129
112
  <button type="button" onClick={onClick} className="text-left">
130
- <Card className="h-full transition-colors hover:bg-accent/50">
113
+ <Card className="h-full transition-colors hover:border-primary/75 hover:cursor-pointer">
131
114
  <CardHeader className="pb-2">
132
- <div className="mb-2 flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
115
+ <div className="mb-2 flex size-10 items-center justify-center rounded-lg bg-primary/5 text-primary">
133
116
  {icon}
134
117
  </div>
135
118
  <CardTitle className="text-base">{title}</CardTitle>
@@ -141,26 +124,3 @@ function DashboardCard({
141
124
  </button>
142
125
  );
143
126
  }
144
-
145
- function StatCard({
146
- label,
147
- value,
148
- isLoading = false,
149
- }: {
150
- label: string;
151
- value?: string;
152
- isLoading?: boolean;
153
- }) {
154
- return (
155
- <Card>
156
- <CardContent className="p-4">
157
- {isLoading ? (
158
- <Skeleton className="mb-1 h-8 w-16" />
159
- ) : (
160
- <div className="text-2xl font-bold">{value}</div>
161
- )}
162
- <div className="text-sm text-muted-foreground">{label}</div>
163
- </CardContent>
164
- </Card>
165
- );
166
- }
@@ -8,10 +8,12 @@
8
8
  import { useState, useMemo, useCallback, useEffect } from "react";
9
9
  import { useQuery, useMutation } from "convex/react";
10
10
  import { UploadDropzone, type UploadedFile } from "~/components/UploadDropzone";
11
- import { CmsPageHeader } from "~/components/cmsds/CmsPageHeader";
12
- import { CmsToolbar } from "~/components/cmsds/CmsToolbar";
13
- import { CmsEmptyState } from "~/components/cmsds/CmsEmptyState";
14
- import { CmsButton } from "~/components/cmsds/CmsButton";
11
+ import {
12
+ CmsPageHeader,
13
+ CmsEmptyState,
14
+ CmsButton,
15
+ CmsFilterBar,
16
+ } from "~/components/cmsds";
15
17
  import { TaxonomyFilter } from "~/components/filters/TaxonomyFilter";
16
18
  import {
17
19
  Dialog,
@@ -22,13 +24,6 @@ import {
22
24
  } from "~/components/ui/dialog";
23
25
  import { Input } from "~/components/ui/input";
24
26
  import { Label } from "~/components/ui/label";
25
- import {
26
- Select,
27
- SelectContent,
28
- SelectItem,
29
- SelectTrigger,
30
- SelectValue,
31
- } from "~/components/ui/select";
32
27
  import { Checkbox } from "~/components/ui/checkbox";
33
28
  import { cn } from "~/lib/cn";
34
29
  import {
@@ -43,7 +38,6 @@ import {
43
38
  FolderPlus,
44
39
  Upload,
45
40
  Search,
46
- X,
47
41
  Trash2,
48
42
  RotateCcw,
49
43
  } from "lucide-react";
@@ -544,48 +538,31 @@ export function MediaPage({ api, navigation, settings }: MediaPageProps) {
544
538
  </nav>
545
539
  )}
546
540
 
547
- <CmsToolbar
548
- left={
541
+ <CmsFilterBar
542
+ search={{
543
+ value: searchQuery,
544
+ onChange: setSearchQuery,
545
+ placeholder: "Search files...",
546
+ className: "w-64",
547
+ }}
548
+ filters={[
549
+ {
550
+ key: "type",
551
+ value: typeFilter || "all",
552
+ onChange: (v) => setTypeFilter(v === "all" ? "" : (v as MediaType)),
553
+ options: [
554
+ { value: "all", label: "All Types" },
555
+ { value: "image", label: "Images" },
556
+ { value: "video", label: "Videos" },
557
+ { value: "audio", label: "Audio" },
558
+ { value: "document", label: "Documents" },
559
+ { value: "other", label: "Other" },
560
+ ],
561
+ className: "w-36",
562
+ },
563
+ ]}
564
+ actions={
549
565
  <div className="flex items-center gap-3">
550
- <div className="relative">
551
- <Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
552
- <Input
553
- type="search"
554
- placeholder="Search files..."
555
- value={searchQuery}
556
- onChange={(e) => setSearchQuery(e.target.value)}
557
- className="w-64 pl-9"
558
- />
559
- {searchQuery && (
560
- <button
561
- className="absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 hover:bg-muted"
562
- onClick={() => setSearchQuery("")}
563
- aria-label="Clear search"
564
- >
565
- <X className="size-4 text-muted-foreground" />
566
- </button>
567
- )}
568
- </div>
569
-
570
- <Select
571
- value={typeFilter || "all"}
572
- onValueChange={(v) =>
573
- setTypeFilter(v === "all" ? "" : (v as MediaType))
574
- }
575
- >
576
- <SelectTrigger className="w-36">
577
- <SelectValue placeholder="All Types" />
578
- </SelectTrigger>
579
- <SelectContent>
580
- <SelectItem value="all">All Types</SelectItem>
581
- <SelectItem value="image">Images</SelectItem>
582
- <SelectItem value="video">Videos</SelectItem>
583
- <SelectItem value="audio">Audio</SelectItem>
584
- <SelectItem value="document">Documents</SelectItem>
585
- <SelectItem value="other">Other</SelectItem>
586
- </SelectContent>
587
- </Select>
588
-
589
566
  <TaxonomyFilter
590
567
  selectedTermIds={selectedTermIds}
591
568
  onChange={setSelectedTermIds}
@@ -606,10 +583,7 @@ export function MediaPage({ api, navigation, settings }: MediaPageProps) {
606
583
  Selection Mode
607
584
  </label>
608
585
  )}
609
- </div>
610
- }
611
- right={
612
- <div className="flex items-center gap-2">
586
+
613
587
  {isSelectionMode && selectedAssets.size > 0 && (
614
588
  <span className="text-sm text-muted-foreground">
615
589
  {selectedAssets.size} selected
@@ -84,7 +84,11 @@ const THEME_OPTIONS: {
84
84
  ];
85
85
 
86
86
  function AppearanceSection() {
87
- const { theme, setTheme } = useTheme();
87
+ const { theme, setTheme, canToggleDarkMode } = useTheme();
88
+
89
+ if (!canToggleDarkMode) {
90
+ return null;
91
+ }
88
92
 
89
93
  return (
90
94
  <CmsSurface elevation="base" className="p-6">
@@ -5,27 +5,21 @@
5
5
  * Used by both CLI routes and embed pages.
6
6
  */
7
7
 
8
- import { useState, useCallback } from "react";
8
+ import { useState, useCallback, useMemo } from "react";
9
9
  import { useQuery, useMutation } from "convex/react";
10
- import { CmsPageHeader } from "~/components/cmsds/CmsPageHeader";
11
- import { CmsToolbar } from "~/components/cmsds/CmsToolbar";
12
- import { CmsEmptyState } from "~/components/cmsds/CmsEmptyState";
13
- import { CmsSurface } from "~/components/cmsds/CmsSurface";
14
- import { CmsButton } from "~/components/cmsds/CmsButton";
15
- import { CmsConfirmDialog } from "~/components/cmsds/CmsDialog";
16
- import { Input } from "~/components/ui/input";
17
10
  import {
18
- Select,
19
- SelectContent,
20
- SelectItem,
21
- SelectTrigger,
22
- SelectValue,
23
- } from "~/components/ui/select";
24
- import { Checkbox } from "~/components/ui/checkbox";
11
+ CmsPageHeader,
12
+ CmsEmptyState,
13
+ CmsSurface,
14
+ CmsButton,
15
+ CmsConfirmDialog,
16
+ CmsFilterBar,
17
+ CmsTable,
18
+ type CmsTableColumn,
19
+ } from "~/components/cmsds";
25
20
  import { Badge } from "~/components/ui/badge";
26
21
  import { Alert, AlertDescription } from "~/components/ui/alert";
27
- import { cn } from "~/lib/cn";
28
- import { Search, Trash2, RotateCcw, AlertTriangle, X } from "lucide-react";
22
+ import { Trash2, RotateCcw, AlertTriangle, X } from "lucide-react";
29
23
  import type { AdminNavigation } from "~/lib/navigation";
30
24
  import { CmsAdminApi } from "~/embed/contexts/ApiContext";
31
25
 
@@ -77,26 +71,6 @@ export function TrashPage({ api, navigation: _navigation }: TrashPageProps) {
77
71
  const config = configQuery;
78
72
  const stats = statsQuery;
79
73
 
80
- const handleSelectItem = useCallback((itemId: string, selected: boolean) => {
81
- setSelectedItems((prev) => {
82
- const next = new Set(prev);
83
- if (selected) {
84
- next.add(itemId);
85
- } else {
86
- next.delete(itemId);
87
- }
88
- return next;
89
- });
90
- }, []);
91
-
92
- const handleSelectAll = useCallback(() => {
93
- if (selectedItems.size === trashItems.length) {
94
- setSelectedItems(new Set());
95
- } else {
96
- setSelectedItems(new Set(trashItems.map((item) => item._id)));
97
- }
98
- }, [selectedItems.size, trashItems]);
99
-
100
74
  const handleRestore = useCallback(
101
75
  async (ids: string[]) => {
102
76
  setIsRestoring(true);
@@ -166,6 +140,79 @@ export function TrashPage({ api, navigation: _navigation }: TrashPageProps) {
166
140
  return item.slug || item._id;
167
141
  };
168
142
 
143
+ const trashColumns: CmsTableColumn<TrashItem>[] = useMemo(
144
+ () => [
145
+ {
146
+ key: "name",
147
+ header: "Name",
148
+ cell: (item) => (
149
+ <>
150
+ <span className="font-medium text-foreground">{getItemTitle(item)}</span>
151
+ {item.slug && (
152
+ <span className="block text-xs text-muted-foreground">{item.slug}</span>
153
+ )}
154
+ </>
155
+ ),
156
+ },
157
+ {
158
+ key: "type",
159
+ header: "Type",
160
+ cell: (item) => (
161
+ <span className="text-sm text-muted-foreground">
162
+ {item.contentTypeName || "Unknown"}
163
+ </span>
164
+ ),
165
+ },
166
+ {
167
+ key: "deleted",
168
+ header: "Deleted",
169
+ cell: (item) => (
170
+ <>
171
+ <span className="text-sm text-muted-foreground">
172
+ {formatDate(item.deletedAt)}
173
+ </span>
174
+ {item.deletedBy && (
175
+ <span className="block text-xs text-muted-foreground">
176
+ by {item.deletedBy}
177
+ </span>
178
+ )}
179
+ </>
180
+ ),
181
+ },
182
+ {
183
+ key: "expires",
184
+ header: "Expires In",
185
+ cell: (item) => {
186
+ const daysLeft = getDaysUntilDeletion(item.deletedAt);
187
+ return daysLeft !== null ? (
188
+ <Badge
189
+ variant={daysLeft <= 3 ? "destructive" : "secondary"}
190
+ className="font-normal"
191
+ >
192
+ {daysLeft} {daysLeft === 1 ? "day" : "days"}
193
+ </Badge>
194
+ ) : null;
195
+ },
196
+ },
197
+ {
198
+ key: "actions",
199
+ header: "Actions",
200
+ cell: (item) => (
201
+ <CmsButton
202
+ variant="outline"
203
+ size="sm"
204
+ onClick={() => handleRestore([item._id])}
205
+ loading={isRestoring}
206
+ >
207
+ <RotateCcw className="size-4" />
208
+ Restore
209
+ </CmsButton>
210
+ ),
211
+ },
212
+ ],
213
+ [isRestoring, handleRestore, config?.retentionDays]
214
+ );
215
+
169
216
  return (
170
217
  <div className="space-y-6 p-6">
171
218
  <div className="flex items-start justify-between">
@@ -200,39 +247,28 @@ export function TrashPage({ api, navigation: _navigation }: TrashPageProps) {
200
247
  </div>
201
248
  )}
202
249
 
203
- <CmsToolbar
204
- left={
205
- <div className="flex items-center gap-3">
206
- <div className="relative">
207
- <Search className="absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground" />
208
- <Input
209
- type="text"
210
- placeholder="Search deleted items..."
211
- value={searchQuery}
212
- onChange={(e) => setSearchQuery(e.target.value)}
213
- className="w-64 pl-9"
214
- />
215
- </div>
216
- <Select
217
- value={selectedContentType || "all"}
218
- onValueChange={(v) =>
219
- setSelectedContentType(v === "all" ? "" : v)
220
- }
221
- >
222
- <SelectTrigger className="w-48">
223
- <SelectValue placeholder="All Content Types" />
224
- </SelectTrigger>
225
- <SelectContent>
226
- <SelectItem value="all">All Content Types</SelectItem>
227
- {contentTypes.map((type: any) => (
228
- <SelectItem key={type._id} value={type._id}>
229
- {type.displayName}
230
- </SelectItem>
231
- ))}
232
- </SelectContent>
233
- </Select>
234
- </div>
235
- }
250
+ <CmsFilterBar
251
+ search={{
252
+ value: searchQuery,
253
+ onChange: setSearchQuery,
254
+ placeholder: "Search deleted items...",
255
+ className: "w-64",
256
+ }}
257
+ filters={[
258
+ {
259
+ key: "contentType",
260
+ value: selectedContentType || "all",
261
+ onChange: (v) => setSelectedContentType(v === "all" ? "" : v),
262
+ options: [
263
+ { value: "all", label: "All Content Types" },
264
+ ...contentTypes.map((type: { _id: string; displayName: string }) => ({
265
+ value: type._id,
266
+ label: type.displayName,
267
+ })),
268
+ ],
269
+ className: "w-48",
270
+ },
271
+ ]}
236
272
  />
237
273
 
238
274
  {restoreError && (
@@ -304,106 +340,15 @@ export function TrashPage({ api, navigation: _navigation }: TrashPageProps) {
304
340
  description="Deleted items will appear here"
305
341
  />
306
342
  ) : (
307
- <div className="rounded-lg border bg-card">
308
- <table className="w-full">
309
- <thead>
310
- <tr className="border-b">
311
- <th className="w-10 p-3 text-left">
312
- <Checkbox
313
- checked={
314
- selectedItems.size === trashItems.length &&
315
- trashItems.length > 0
316
- }
317
- onCheckedChange={handleSelectAll}
318
- />
319
- </th>
320
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
321
- Name
322
- </th>
323
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
324
- Type
325
- </th>
326
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
327
- Deleted
328
- </th>
329
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
330
- Expires In
331
- </th>
332
- <th className="p-3 text-left text-sm font-medium text-muted-foreground">
333
- Actions
334
- </th>
335
- </tr>
336
- </thead>
337
- <tbody>
338
- {trashItems.map((item) => {
339
- const daysLeft = getDaysUntilDeletion(item.deletedAt);
340
-
341
- return (
342
- <tr
343
- key={item._id}
344
- className={cn(
345
- "border-b last:border-0 transition-colors hover:bg-muted/50",
346
- selectedItems.has(item._id) && "bg-primary/5",
347
- )}
348
- >
349
- <td className="p-3">
350
- <Checkbox
351
- checked={selectedItems.has(item._id)}
352
- onCheckedChange={(checked) =>
353
- handleSelectItem(item._id, checked as boolean)
354
- }
355
- />
356
- </td>
357
- <td className="p-3">
358
- <span className="font-medium text-foreground">
359
- {getItemTitle(item)}
360
- </span>
361
- {item.slug && (
362
- <span className="block text-xs text-muted-foreground">
363
- {item.slug}
364
- </span>
365
- )}
366
- </td>
367
- <td className="p-3 text-sm text-muted-foreground">
368
- {item.contentTypeName || "Unknown"}
369
- </td>
370
- <td className="p-3">
371
- <span className="text-sm text-muted-foreground">
372
- {formatDate(item.deletedAt)}
373
- </span>
374
- {item.deletedBy && (
375
- <span className="block text-xs text-muted-foreground">
376
- by {item.deletedBy}
377
- </span>
378
- )}
379
- </td>
380
- <td className="p-3">
381
- {daysLeft !== null && (
382
- <Badge
383
- variant={daysLeft <= 3 ? "destructive" : "secondary"}
384
- className="font-normal"
385
- >
386
- {daysLeft} {daysLeft === 1 ? "day" : "days"}
387
- </Badge>
388
- )}
389
- </td>
390
- <td className="p-3">
391
- <CmsButton
392
- variant="outline"
393
- size="sm"
394
- onClick={() => handleRestore([item._id])}
395
- loading={isRestoring}
396
- >
397
- <RotateCcw className="size-4" />
398
- Restore
399
- </CmsButton>
400
- </td>
401
- </tr>
402
- );
403
- })}
404
- </tbody>
405
- </table>
406
- </div>
343
+ <CmsTable
344
+ columns={trashColumns}
345
+ data={trashItems}
346
+ getRowId={(item) => item._id}
347
+ selectable
348
+ selectedIds={selectedItems}
349
+ onSelectionChange={setSelectedItems}
350
+ emptyMessage="No items in trash"
351
+ />
407
352
  )}
408
353
 
409
354
  <CmsConfirmDialog
@@ -6,7 +6,7 @@ import {
6
6
  } from "@tanstack/react-router";
7
7
  import { ConvexProvider, ConvexReactClient } from "convex/react";
8
8
  import { useMemo, type ReactNode } from "react";
9
- import globalsCss from "~/styles/globals.css?url";
9
+ import globalsCss from "~/index.css?url";
10
10
  import { AdminLayout, RouteGuard } from "~/components";
11
11
  import {
12
12
  AuthProvider,
@@ -1,5 +1,5 @@
1
1
  {
2
- "date": "2026-01-28T14:07:30.300Z",
2
+ "date": "2026-01-29T00:22:17.698Z",
3
3
  "preset": "node-server",
4
4
  "framework": {
5
5
  "name": "nitro",
@@ -1 +1 @@
1
- import{j as e,C as n,h as x}from"./main-CA-4LyFT.js";function d({icon:s,title:m,description:r,action:t,className:l,...a}){return e.jsxs("div",{className:x("flex flex-col items-center justify-center py-12 text-center",l),...a,children:[s&&e.jsx("div",{className:"mb-4 flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground",children:s}),e.jsx("h3",{className:"text-base font-semibold text-foreground",children:m}),r&&e.jsx("p",{className:"mt-1 max-w-sm text-sm text-muted-foreground",children:r}),t&&e.jsx(n,{variant:t.variant??"primary",onClick:t.onClick,className:"mt-4",children:t.label})]})}export{d as C};
1
+ import{j as e,C as a,n as x}from"./main-B-6700eG.js";function d({icon:s,title:m,description:r,action:t,className:l,...n}){return e.jsxs("div",{className:x("flex flex-col items-center justify-center py-12 text-center",l),...n,children:[s&&e.jsx("div",{className:"mb-4 flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground",children:s}),e.jsx("h3",{className:"text-base font-semibold text-foreground",children:m}),r&&e.jsx("p",{className:"mt-1 max-w-sm text-sm text-muted-foreground",children:r}),t&&e.jsx(a,{variant:t.variant??"primary",onClick:t.onClick,className:"mt-4",children:t.label})]})}export{d as C};
@@ -0,0 +1 @@
1
+ import{j as e,S as d,s as x,t as u,n,v as o,w as p,C as v,X as h}from"./main-B-6700eG.js";import{C as j}from"./tanstack-adapter-KSm-nO5L.js";import{S as g}from"./search-DlwBH4C5.js";function C({value:a,onValueChange:m,options:l,placeholder:t="Select...",disabled:i,error:c,className:s}){return e.jsxs(d,{value:a,onValueChange:m,disabled:i,children:[e.jsx(x,{className:n(c&&"border-destructive focus:ring-destructive",s),"aria-invalid":c,children:e.jsx(u,{placeholder:t})}),e.jsx(o,{children:l.map(r=>e.jsx(p,{value:r.value,disabled:r.disabled,children:r.label},r.value))})]})}function b({search:a,filters:m,actions:l,onClearFilters:t,hasActiveFilters:i,className:c}){return e.jsxs("div",{className:n("flex flex-wrap items-center gap-3 pb-4",c),children:[e.jsxs("div",{className:"flex flex-1 flex-wrap items-center gap-2",children:[a&&e.jsxs("div",{className:"relative w-full max-w-xs",children:[e.jsx(g,{className:"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground"}),e.jsx(j,{type:"search",placeholder:a.placeholder??"Search...",value:a.value,onChange:s=>a.onChange(s.target.value),className:n("pl-9",a.className)})]}),m?.map(s=>e.jsx(C,{value:s.value,onValueChange:s.onChange,options:s.options,placeholder:s.placeholder,className:n("w-[150px]",s.className)},s.key)),i&&t&&e.jsxs(v,{variant:"ghost",size:"sm",onClick:t,children:[e.jsx(h,{className:"mr-1 size-4"}),"Clear"]})]}),l&&e.jsx("div",{className:"flex items-center gap-2",children:l})]})}export{b as C};
@@ -1 +1 @@
1
- import{j as e,h as m}from"./main-CA-4LyFT.js";function c({title:i,description:s,actions:t,breadcrumbs:a,className:l,...r}){return e.jsxs("div",{className:m("mb-6",l),...r,children:[a&&e.jsx("div",{className:"mb-2",children:a}),e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("h1",{className:"text-2xl font-semibold tracking-tight text-foreground",children:i}),s&&e.jsx("p",{className:"text-sm text-muted-foreground",children:s})]}),t&&e.jsx("div",{className:"flex items-center gap-2",children:t})]})]})}export{c as C};
1
+ import{j as e,n as m}from"./main-B-6700eG.js";function x({title:i,description:s,actions:t,breadcrumbs:a,className:l,...r}){return e.jsxs("div",{className:m("mb-6",l),...r,children:[a&&e.jsx("div",{className:"mb-2",children:a}),e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("h1",{className:"text-2xl font-semibold tracking-tight text-foreground",children:i}),s&&e.jsx("p",{className:"text-sm text-muted-foreground",children:s})]}),t&&e.jsx("div",{className:"flex items-center gap-2",children:t})]})]})}export{x as C};
@@ -1 +1 @@
1
- import{j as e,h as t}from"./main-CA-4LyFT.js";import{B as a}from"./badge-C6qt24oj.js";const c={draft:{label:"Draft",className:"status-draft",icon:e.jsx("svg",{className:"size-3",fill:"currentColor",viewBox:"0 0 8 8",children:e.jsx("circle",{cx:"4",cy:"4",r:"3"})})},published:{label:"Published",className:"status-published",icon:e.jsx("svg",{className:"size-3",fill:"none",stroke:"currentColor",strokeWidth:"2",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 13l4 4L19 7"})})},scheduled:{label:"Scheduled",className:"status-scheduled",icon:e.jsxs("svg",{className:"size-3",fill:"none",stroke:"currentColor",strokeWidth:"2",viewBox:"0 0 24 24",children:[e.jsx("circle",{cx:"12",cy:"12",r:"10"}),e.jsx("path",{strokeLinecap:"round",d:"M12 6v6l4 2"})]})},archived:{label:"Archived",className:"status-archived",icon:e.jsx("svg",{className:"size-3",fill:"none",stroke:"currentColor",strokeWidth:"2",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"})})}},n={gray:"bg-muted text-muted-foreground",yellow:"bg-diff-modified-bg text-diff-modified-foreground",blue:"bg-info-bg text-info-foreground",green:"bg-diff-added-bg text-diff-added-foreground",red:"bg-diff-removed-bg text-diff-removed-foreground",purple:"bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400",orange:"bg-diff-modified-bg text-diff-modified-foreground"};function l(){return e.jsx("svg",{className:"size-3",fill:"currentColor",viewBox:"0 0 8 8",children:e.jsx("circle",{cx:"4",cy:"4",r:"3"})})}function x({status:d,customConfig:s,className:o,...i}){if(s)return e.jsxs(a,{variant:"secondary",className:t("gap-1.5 px-2 py-0.5 text-xs font-medium",n[s.color],o),...i,children:[l(),s.displayName]});const r=c[d];return r?e.jsxs(a,{variant:"secondary",className:t("gap-1.5 px-2 py-0.5 text-xs font-medium",r.className,o),...i,children:[r.icon,r.label]}):e.jsxs(a,{variant:"secondary",className:t("gap-1.5 px-2 py-0.5 text-xs font-medium",n.gray,o),...i,children:[l(),d]})}export{x as C};
1
+ import{j as e,n as t}from"./main-B-6700eG.js";import{B as d}from"./badge-BQAotc5B.js";const c={draft:{label:"Draft",className:"status-draft",icon:e.jsx("svg",{className:"size-3",fill:"currentColor",viewBox:"0 0 8 8",children:e.jsx("circle",{cx:"4",cy:"4",r:"3"})})},published:{label:"Published",className:"status-published",icon:e.jsx("svg",{className:"size-3",fill:"none",stroke:"currentColor",strokeWidth:"2",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 13l4 4L19 7"})})},scheduled:{label:"Scheduled",className:"status-scheduled",icon:e.jsxs("svg",{className:"size-3",fill:"none",stroke:"currentColor",strokeWidth:"2",viewBox:"0 0 24 24",children:[e.jsx("circle",{cx:"12",cy:"12",r:"10"}),e.jsx("path",{strokeLinecap:"round",d:"M12 6v6l4 2"})]})},archived:{label:"Archived",className:"status-archived",icon:e.jsx("svg",{className:"size-3",fill:"none",stroke:"currentColor",strokeWidth:"2",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"})})}},n={gray:"bg-muted text-muted-foreground",yellow:"bg-diff-modified-bg text-diff-modified-foreground",blue:"bg-info-bg text-info-foreground",green:"bg-diff-added-bg text-diff-added-foreground",red:"bg-diff-removed-bg text-diff-removed-foreground",purple:"bg-purple-bg text-purple-foreground",orange:"bg-diff-modified-bg text-diff-modified-foreground"};function l(){return e.jsx("svg",{className:"size-3",fill:"currentColor",viewBox:"0 0 8 8",children:e.jsx("circle",{cx:"4",cy:"4",r:"3"})})}function x({status:a,customConfig:s,className:o,...i}){if(s)return e.jsxs(d,{variant:"secondary",className:t("gap-1.5 px-2 py-0.5 text-xs font-medium",n[s.color],o),...i,children:[l(),s.displayName]});const r=c[a];return r?e.jsxs(d,{variant:"secondary",className:t("gap-1.5 px-2 py-0.5 text-xs font-medium",r.className,o),...i,children:[r.icon,r.label]}):e.jsxs(d,{variant:"secondary",className:t("gap-1.5 px-2 py-0.5 text-xs font-medium",n.gray,o),...i,children:[l(),a]})}export{x as C};
@@ -1 +1 @@
1
- import{j as r,h as t}from"./main-CA-4LyFT.js";const l={base:"surface-base",elevated:"surface-elevated",floating:"surface-floating"},m={none:"",sm:"p-3",md:"p-4",lg:"p-6"},u={none:"rounded-none",sm:"rounded-sm",md:"rounded-md",lg:"rounded-lg"};function f({elevation:e="base",padding:s="md",rounded:n="lg",className:a,children:d,...o}){return r.jsx("div",{className:t(l[e],m[s],u[n],a),...o,children:d})}export{f as C};
1
+ import{j as r,n as t}from"./main-B-6700eG.js";const l={base:"surface-base",elevated:"surface-elevated",floating:"surface-floating"},m={none:"",sm:"p-3",md:"p-4",lg:"p-6"},u={none:"rounded-none",sm:"rounded-sm",md:"rounded-md",lg:"rounded-lg"};function f({elevation:e="base",padding:s="md",rounded:n="lg",className:a,children:d,...o}){return r.jsx("div",{className:t(l[e],m[s],u[n],a),...o,children:d})}export{f as C};
@@ -0,0 +1 @@
1
+ import{e as T,j as e,n as r,i as u,a9 as _,l as z}from"./main-B-6700eG.js";const D=[["path",{d:"m7 15 5 5 5-5",key:"1hf1tw"}],["path",{d:"m7 9 5-5 5 5",key:"sgt6xg"}]],H=T("chevrons-up-down",D);function U({className:s,...t}){return e.jsx("div",{"data-slot":"table-container",className:"relative w-full overflow-x-auto",children:e.jsx("table",{"data-slot":"table",className:r("w-full caption-bottom text-sm",s),...t})})}function A({className:s,...t}){return e.jsx("thead",{"data-slot":"table-header",className:r("[&_tr]:border-b",s),...t})}function B({className:s,...t}){return e.jsx("tbody",{"data-slot":"table-body",className:r("[&_tr:last-child]:border-0",s),...t})}function x({className:s,...t}){return e.jsx("tr",{"data-slot":"table-row",className:r("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",s),...t})}function p({className:s,...t}){return e.jsx("th",{"data-slot":"table-head",className:r("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",s),...t})}function m({className:s,...t}){return e.jsx("td",{"data-slot":"table-cell",className:r("p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",s),...t})}function L({columns:s,data:t,getRowId:i,selectable:h,selectedIds:d=new Set,onSelectionChange:o,sortColumn:j,sortDirection:f,onSort:N,onRowClick:b,emptyMessage:k="No items found",className:w}){const y=t.length>0&&t.every(a=>d.has(i(a))),v=t.some(a=>d.has(i(a))),g=a=>{o&&o(a?new Set(t.map(i)):new Set)},S=(a,n)=>{if(!o)return;const c=new Set(d);n?c.add(a):c.delete(a),o(c)},C=a=>a.sortable?j!==a.key?e.jsx(H,{className:"ml-1 inline size-3.5 text-muted-foreground"}):f==="asc"?e.jsx(_,{className:"ml-1 inline size-3.5"}):e.jsx(z,{className:"ml-1 inline size-3.5"}):null;return e.jsx("div",{className:r("rounded-lg border",w),children:e.jsxs(U,{children:[e.jsx(A,{children:e.jsxs(x,{className:"hover:bg-transparent",children:[h&&e.jsx(p,{className:"w-12",children:e.jsx(u,{checked:y?!0:v?"indeterminate":!1,onCheckedChange:g,"aria-label":"Select all"})}),s.map(a=>e.jsxs(p,{className:r(a.sortable&&"cursor-pointer select-none",a.className),onClick:()=>a.sortable&&N?.(a.key),children:[a.header,C(a)]},a.key))]})}),e.jsx(B,{children:t.length===0?e.jsx(x,{children:e.jsx(m,{colSpan:s.length+(h?1:0),className:"h-24 text-center text-muted-foreground",children:k})}):t.map(a=>{const n=i(a),c=d.has(n);return e.jsxs(x,{"data-state":c&&"selected",className:r(b&&"cursor-pointer"),onClick:()=>b?.(a),children:[h&&e.jsx(m,{onClick:l=>l.stopPropagation(),className:"w-12",children:e.jsx(u,{checked:c,onCheckedChange:l=>S(n,l===!0),"aria-label":`Select row ${n}`})}),s.map(l=>e.jsx(m,{className:l.className,children:l.cell(a)},l.key))]},n)})})]})})}export{L as C};