create-einja-app 0.2.17 → 0.2.18

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 (270) hide show
  1. package/package.json +2 -2
  2. package/templates/default/.claude/hooks/einja/playwright-resize.sh +12 -2
  3. package/templates/default/.claude/settings.json +15 -0
  4. package/templates/default/.cursor/commands/task-vibe-kanban-loop.md +107 -42
  5. package/templates/default/.env.develop +0 -4
  6. package/templates/default/.env.example +1 -0
  7. package/templates/default/.env.preview +0 -4
  8. package/templates/default/.env.staging +19 -0
  9. package/templates/default/.github/actions/ci/action.yml +39 -0
  10. package/templates/default/.github/actions/migrate/action.yml +39 -0
  11. package/templates/default/.github/actions/neon-export-env/action.yml +28 -0
  12. package/templates/default/.github/actions/setup/action.yml +20 -0
  13. package/templates/default/.github/workflows/claude.yml +1 -0
  14. package/templates/default/.github/workflows/{cleanup-neon-branches.yml → cleanup-pr-preview-db.yml} +28 -24
  15. package/templates/default/.github/workflows/cleanup-pr-preview-on-close.yml +50 -0
  16. package/templates/default/.github/workflows/deploy-pr-preview.yml +398 -0
  17. package/templates/default/.github/workflows/deploy-stable-branches.yml +259 -0
  18. package/templates/default/.github/workflows/release-create-einja-app.yml +95 -0
  19. package/templates/default/.mcp.json +6 -9
  20. package/templates/default/CLAUDE.md +46 -9
  21. package/templates/default/README.md +5 -14
  22. package/templates/default/apps/admin/next.config.ts +11 -0
  23. package/templates/default/apps/admin/package.json +55 -0
  24. package/templates/default/apps/admin/postcss.config.cjs +5 -0
  25. package/templates/default/apps/admin/src/app/(auth)/forgot-password/page.tsx +97 -0
  26. package/templates/default/apps/admin/src/app/(auth)/layout.tsx +18 -0
  27. package/templates/default/apps/admin/src/app/(auth)/otp/page.tsx +121 -0
  28. package/templates/default/apps/admin/src/app/(auth)/sign-in/page.tsx +145 -0
  29. package/templates/default/apps/admin/src/app/(auth)/sign-up/page.tsx +199 -0
  30. package/templates/default/apps/admin/src/app/(errors)/401/page.tsx +27 -0
  31. package/templates/default/apps/admin/src/app/(errors)/403/page.tsx +28 -0
  32. package/templates/default/apps/admin/src/app/(errors)/500/page.tsx +29 -0
  33. package/templates/default/apps/admin/src/app/(errors)/layout.tsx +7 -0
  34. package/templates/default/apps/admin/src/app/(errors)/maintenance/page.tsx +25 -0
  35. package/templates/default/apps/admin/src/app/dashboard/_components/analytics-chart.tsx +68 -0
  36. package/templates/default/apps/admin/src/app/dashboard/_components/analytics.tsx +182 -0
  37. package/templates/default/apps/admin/src/app/dashboard/_components/dashboard-page.tsx +74 -0
  38. package/templates/default/apps/admin/src/app/dashboard/_components/metric-cards.tsx +49 -0
  39. package/templates/default/apps/admin/src/app/dashboard/_components/overview-chart.tsx +73 -0
  40. package/templates/default/apps/admin/src/app/dashboard/_components/recent-sales.tsx +75 -0
  41. package/templates/default/apps/admin/src/app/dashboard/apps/_components/apps-page.tsx +135 -0
  42. package/templates/default/apps/admin/src/app/dashboard/apps/page.tsx +10 -0
  43. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chat-list.tsx +82 -0
  44. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chat-messages.tsx +194 -0
  45. package/templates/default/apps/admin/src/app/dashboard/chats/_components/chats-page.tsx +99 -0
  46. package/templates/default/apps/admin/src/app/dashboard/chats/_components/new-chat.tsx +118 -0
  47. package/templates/default/apps/admin/src/app/dashboard/chats/page.tsx +10 -0
  48. package/templates/default/apps/admin/src/app/dashboard/layout.tsx +9 -0
  49. package/templates/default/apps/admin/src/app/dashboard/not-found.tsx +14 -0
  50. package/templates/default/apps/admin/src/app/dashboard/page.tsx +10 -0
  51. package/templates/default/apps/admin/src/app/dashboard/settings/_components/content-section.tsx +20 -0
  52. package/templates/default/apps/admin/src/app/dashboard/settings/_components/sidebar-nav.tsx +66 -0
  53. package/templates/default/apps/admin/src/app/dashboard/settings/account/page.tsx +173 -0
  54. package/templates/default/apps/admin/src/app/dashboard/settings/appearance/page.tsx +156 -0
  55. package/templates/default/apps/admin/src/app/dashboard/settings/display/page.tsx +125 -0
  56. package/templates/default/apps/admin/src/app/dashboard/settings/layout.tsx +30 -0
  57. package/templates/default/apps/admin/src/app/dashboard/settings/notifications/page.tsx +196 -0
  58. package/templates/default/apps/admin/src/app/dashboard/settings/page.tsx +5 -0
  59. package/templates/default/apps/admin/src/app/dashboard/settings/profile/page.tsx +176 -0
  60. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/data-table-bulk-actions.tsx +183 -0
  61. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/data-table-row-actions.tsx +79 -0
  62. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-columns.tsx +107 -0
  63. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-dialogs.tsx +71 -0
  64. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-import-dialog.tsx +106 -0
  65. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-multi-delete-dialog.tsx +90 -0
  66. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-mutate-drawer.tsx +207 -0
  67. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-page.tsx +31 -0
  68. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-primary-buttons.tsx +19 -0
  69. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-provider.tsx +37 -0
  70. package/templates/default/apps/admin/src/app/dashboard/tasks/_components/tasks-table.tsx +155 -0
  71. package/templates/default/apps/admin/src/app/dashboard/tasks/page.tsx +14 -0
  72. package/templates/default/apps/admin/src/app/dashboard/users/_components/data-table-bulk-actions.tsx +136 -0
  73. package/templates/default/apps/admin/src/app/dashboard/users/_components/data-table-row-actions.tsx +62 -0
  74. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-action-dialog.tsx +297 -0
  75. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-columns.tsx +121 -0
  76. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-delete-dialog.tsx +72 -0
  77. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-dialogs.tsx +49 -0
  78. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-invite-dialog.tsx +139 -0
  79. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-multi-delete-dialog.tsx +89 -0
  80. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-page.tsx +30 -0
  81. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-primary-buttons.tsx +19 -0
  82. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-provider.tsx +35 -0
  83. package/templates/default/apps/admin/src/app/dashboard/users/_components/users-table.tsx +157 -0
  84. package/templates/default/apps/admin/src/app/dashboard/users/page.tsx +10 -0
  85. package/templates/default/apps/admin/src/app/globals.css +109 -0
  86. package/templates/default/apps/admin/src/app/layout.tsx +32 -0
  87. package/templates/default/apps/admin/src/app/not-found.tsx +14 -0
  88. package/templates/default/apps/admin/src/app/page.tsx +5 -0
  89. package/templates/default/apps/admin/src/components/layout/admin-layout.tsx +16 -0
  90. package/templates/default/apps/admin/src/components/layout/app-sidebar.tsx +52 -0
  91. package/templates/default/apps/admin/src/components/layout/nav-config.ts +131 -0
  92. package/templates/default/apps/admin/src/components/providers/theme-provider.tsx +10 -0
  93. package/templates/default/apps/admin/src/components/shared/long-text.tsx +78 -0
  94. package/templates/default/apps/admin/src/components/shared/search-input.tsx +16 -0
  95. package/templates/default/apps/admin/src/components/shared/select-dropdown.tsx +64 -0
  96. package/templates/default/apps/admin/src/data/apps.tsx +116 -0
  97. package/templates/default/apps/admin/src/data/chats.ts +114 -0
  98. package/templates/default/apps/admin/src/data/tasks.ts +114 -0
  99. package/templates/default/apps/admin/src/data/users.ts +90 -0
  100. package/templates/default/apps/admin/src/hooks/use-dialog-state.ts +17 -0
  101. package/templates/default/apps/admin/src/hooks/use-table-url-state.ts +243 -0
  102. package/templates/default/apps/admin/src/lib/show-submitted-data.tsx +12 -0
  103. package/templates/default/apps/admin/src/types/table.d.ts +9 -0
  104. package/templates/default/apps/admin/tsconfig.json +32 -0
  105. package/templates/default/apps/web/next.config.ts +1 -0
  106. package/templates/default/apps/web/package.json +0 -22
  107. package/templates/default/apps/web/postcss.config.cjs +0 -1
  108. package/templates/default/apps/web/src/app/(authenticated)/dashboard/page.tsx +4 -20
  109. package/templates/default/apps/web/src/app/(authenticated)/data/_components/UserTable.tsx +4 -4
  110. package/templates/default/apps/web/src/app/(authenticated)/data/page.tsx +1 -1
  111. package/templates/default/apps/web/src/app/(authenticated)/profile/page.tsx +1 -1
  112. package/templates/default/apps/web/src/app/error.tsx +8 -70
  113. package/templates/default/apps/web/src/app/global-error.tsx +8 -70
  114. package/templates/default/apps/web/src/app/globals.css +20 -0
  115. package/templates/default/apps/web/src/app/not-found.tsx +5 -39
  116. package/templates/default/apps/web/src/app/page.tsx +27 -203
  117. package/templates/default/apps/web/src/app/signin/page.tsx +27 -191
  118. package/templates/default/apps/web/src/app/signup/page.tsx +33 -240
  119. package/templates/default/apps/web/src/components/dashboard/dashboard-stats.tsx +11 -75
  120. package/templates/default/apps/web/src/components/shared/Sidebar.tsx +3 -3
  121. package/templates/default/apps/web/src/components/shared/header.tsx +17 -112
  122. package/templates/default/apps/web/tsconfig.json +0 -6
  123. package/templates/default/biome.json +1 -2
  124. package/templates/default/components.json +2 -2
  125. package/templates/default/docker-compose.yml +1 -1
  126. package/templates/default/gitignore +4 -0
  127. package/templates/default/package.json +1 -0
  128. package/templates/default/packages/admin-ui/catalog/catalog.css +54 -0
  129. package/templates/default/packages/admin-ui/catalog/catalog.tsx +401 -0
  130. package/templates/default/packages/admin-ui/catalog/index.html +12 -0
  131. package/templates/default/packages/admin-ui/catalog/main.tsx +9 -0
  132. package/templates/default/packages/admin-ui/components.json +21 -0
  133. package/templates/default/packages/admin-ui/package.json +105 -0
  134. package/templates/default/packages/admin-ui/src/command-menu/index.tsx +174 -0
  135. package/templates/default/packages/admin-ui/src/data-table/bulk-actions.tsx +215 -0
  136. package/templates/default/packages/admin-ui/src/data-table/column-header.tsx +73 -0
  137. package/templates/default/packages/admin-ui/src/data-table/data-table.tsx +127 -0
  138. package/templates/default/packages/admin-ui/src/data-table/faceted-filter.tsx +148 -0
  139. package/templates/default/packages/admin-ui/src/data-table/index.tsx +9 -0
  140. package/templates/default/packages/admin-ui/src/data-table/pagination.tsx +101 -0
  141. package/templates/default/packages/admin-ui/src/data-table/toolbar.tsx +87 -0
  142. package/templates/default/packages/admin-ui/src/data-table/view-options.tsx +57 -0
  143. package/templates/default/packages/admin-ui/src/hooks/use-mobile.tsx +23 -0
  144. package/templates/default/packages/admin-ui/src/layout/header.tsx +55 -0
  145. package/templates/default/packages/admin-ui/src/layout/index.ts +10 -0
  146. package/templates/default/packages/admin-ui/src/layout/main.tsx +23 -0
  147. package/templates/default/packages/admin-ui/src/layout/nav-group.tsx +111 -0
  148. package/templates/default/packages/admin-ui/src/layout/nav-user.tsx +114 -0
  149. package/templates/default/packages/admin-ui/src/layout/theme-switch.tsx +40 -0
  150. package/templates/default/packages/admin-ui/src/layout/types.ts +21 -0
  151. package/templates/default/packages/admin-ui/src/lib/utils.ts +6 -0
  152. package/templates/default/packages/admin-ui/src/styles/base.css +65 -0
  153. package/templates/default/packages/admin-ui/src/styles/tokens.css +91 -0
  154. package/templates/default/packages/admin-ui/src/tanstack-table.d.ts +10 -0
  155. package/templates/default/packages/admin-ui/src/ui/alert-dialog.tsx +157 -0
  156. package/templates/default/packages/admin-ui/src/ui/alert.tsx +66 -0
  157. package/templates/default/packages/admin-ui/src/ui/avatar.tsx +53 -0
  158. package/templates/default/packages/admin-ui/src/ui/badge.tsx +46 -0
  159. package/templates/default/packages/admin-ui/src/ui/breadcrumb.tsx +108 -0
  160. package/templates/default/packages/admin-ui/src/ui/button.tsx +59 -0
  161. package/templates/default/packages/admin-ui/src/ui/calendar.tsx +69 -0
  162. package/templates/default/packages/admin-ui/src/ui/card.tsx +92 -0
  163. package/templates/default/packages/admin-ui/src/ui/chart.tsx +345 -0
  164. package/templates/default/packages/admin-ui/src/ui/checkbox.tsx +32 -0
  165. package/templates/default/packages/admin-ui/src/ui/collapsible.tsx +27 -0
  166. package/templates/default/packages/admin-ui/src/ui/command.tsx +161 -0
  167. package/templates/default/packages/admin-ui/src/ui/confirm-dialog.tsx +72 -0
  168. package/templates/default/packages/admin-ui/src/ui/date-picker.tsx +53 -0
  169. package/templates/default/packages/admin-ui/src/ui/dialog.tsx +143 -0
  170. package/templates/default/packages/admin-ui/src/ui/dropdown-menu.tsx +257 -0
  171. package/templates/default/packages/admin-ui/src/ui/form.tsx +168 -0
  172. package/templates/default/packages/admin-ui/src/ui/input-otp.tsx +84 -0
  173. package/templates/default/packages/admin-ui/src/ui/input.tsx +21 -0
  174. package/templates/default/packages/admin-ui/src/ui/label.tsx +24 -0
  175. package/templates/default/packages/admin-ui/src/ui/pagination.tsx +126 -0
  176. package/templates/default/packages/admin-ui/src/ui/password-input.tsx +46 -0
  177. package/templates/default/packages/admin-ui/src/ui/popover.tsx +48 -0
  178. package/templates/default/packages/admin-ui/src/ui/progress.tsx +31 -0
  179. package/templates/default/packages/admin-ui/src/ui/radio-group.tsx +45 -0
  180. package/templates/default/packages/admin-ui/src/ui/scroll-area.tsx +52 -0
  181. package/templates/default/packages/admin-ui/src/ui/select.tsx +185 -0
  182. package/templates/default/packages/admin-ui/src/ui/separator.tsx +28 -0
  183. package/templates/default/packages/admin-ui/src/ui/sheet.tsx +149 -0
  184. package/templates/default/packages/admin-ui/src/ui/sidebar.tsx +728 -0
  185. package/templates/default/packages/admin-ui/src/ui/skeleton.tsx +13 -0
  186. package/templates/default/packages/admin-ui/src/ui/sonner.tsx +25 -0
  187. package/templates/default/packages/admin-ui/src/ui/switch.tsx +31 -0
  188. package/templates/default/packages/admin-ui/src/ui/table.tsx +116 -0
  189. package/templates/default/packages/admin-ui/src/ui/tabs.tsx +66 -0
  190. package/templates/default/packages/admin-ui/src/ui/textarea.tsx +18 -0
  191. package/templates/default/packages/admin-ui/src/ui/toggle-group.tsx +60 -0
  192. package/templates/default/packages/admin-ui/src/ui/toggle.tsx +44 -0
  193. package/templates/default/packages/admin-ui/src/ui/tooltip.tsx +61 -0
  194. package/templates/default/packages/admin-ui/tsconfig.json +8 -0
  195. package/templates/default/packages/admin-ui/vite.config.ts +11 -0
  196. package/templates/default/packages/config/package.json +0 -2
  197. package/templates/default/packages/server-core/package.json +1 -0
  198. package/templates/default/packages/ui/components.json +21 -0
  199. package/templates/default/packages/ui/package.json +42 -5
  200. package/templates/default/packages/ui/src/accordion.tsx +1 -1
  201. package/templates/default/packages/ui/src/alert-dialog.tsx +4 -4
  202. package/templates/default/packages/ui/src/alert.tsx +1 -1
  203. package/templates/default/packages/ui/src/avatar.tsx +1 -1
  204. package/templates/default/packages/ui/src/badge.tsx +1 -1
  205. package/templates/default/packages/ui/src/breadcrumb.tsx +1 -1
  206. package/templates/default/packages/ui/src/button.tsx +1 -1
  207. package/templates/default/packages/ui/src/card.tsx +1 -1
  208. package/templates/default/packages/ui/src/checkbox.tsx +1 -1
  209. package/templates/default/packages/ui/src/dialog.tsx +3 -3
  210. package/templates/default/packages/ui/src/drawer.tsx +3 -3
  211. package/templates/default/packages/ui/src/dropdown-menu.tsx +3 -3
  212. package/templates/default/packages/ui/src/form.tsx +2 -2
  213. package/templates/default/packages/ui/src/hover-card.tsx +2 -2
  214. package/templates/default/packages/ui/src/input.tsx +1 -1
  215. package/templates/default/packages/ui/src/label.tsx +1 -1
  216. package/templates/default/packages/ui/src/pagination.tsx +2 -2
  217. package/templates/default/packages/ui/src/popover.tsx +2 -2
  218. package/templates/default/packages/ui/src/progress.tsx +1 -1
  219. package/templates/default/packages/ui/src/select.tsx +2 -2
  220. package/templates/default/packages/ui/src/separator.tsx +1 -1
  221. package/templates/default/packages/ui/src/skeleton.tsx +1 -1
  222. package/templates/default/packages/ui/src/table.tsx +1 -1
  223. package/templates/default/packages/ui/src/tabs.tsx +1 -1
  224. package/templates/default/packages/ui/src/textarea.tsx +1 -1
  225. package/templates/default/packages/ui/src/tooltip.tsx +3 -3
  226. package/templates/default/packages/ui/src/typography.tsx +1 -1
  227. package/templates/default/packages/ui/tsconfig.json +1 -6
  228. package/templates/default/pnpm-lock.yaml +1319 -936
  229. package/templates/default/postcss.config.cjs +0 -1
  230. package/templates/default/turbo.json +11 -5
  231. package/templates/default/worktree.config.json +5 -0
  232. package/templates/default/.env.ci +0 -32
  233. package/templates/default/.github/workflows/ci.yml +0 -96
  234. package/templates/default/.github/workflows/preview-db.yml +0 -134
  235. package/templates/default/.playwright-mcp/dashboard.png +0 -0
  236. package/templates/default/.playwright-mcp/web-home.png +0 -0
  237. package/templates/default/apps/web/panda.config.ts +0 -114
  238. package/templates/default/apps/web/src/components/ui/accordion.tsx +0 -64
  239. package/templates/default/apps/web/src/components/ui/alert-dialog.tsx +0 -135
  240. package/templates/default/apps/web/src/components/ui/alert.tsx +0 -60
  241. package/templates/default/apps/web/src/components/ui/aspect-ratio.tsx +0 -9
  242. package/templates/default/apps/web/src/components/ui/avatar.tsx +0 -41
  243. package/templates/default/apps/web/src/components/ui/badge.tsx +0 -39
  244. package/templates/default/apps/web/src/components/ui/breadcrumb.tsx +0 -101
  245. package/templates/default/apps/web/src/components/ui/button.tsx +0 -56
  246. package/templates/default/apps/web/src/components/ui/card.tsx +0 -75
  247. package/templates/default/apps/web/src/components/ui/checkbox.tsx +0 -29
  248. package/templates/default/apps/web/src/components/ui/data-table.tsx +0 -189
  249. package/templates/default/apps/web/src/components/ui/dialog-hook.tsx +0 -210
  250. package/templates/default/apps/web/src/components/ui/dialog.tsx +0 -129
  251. package/templates/default/apps/web/src/components/ui/drawer.tsx +0 -124
  252. package/templates/default/apps/web/src/components/ui/dropdown-menu.tsx +0 -228
  253. package/templates/default/apps/web/src/components/ui/form.tsx +0 -152
  254. package/templates/default/apps/web/src/components/ui/hover-card.tsx +0 -38
  255. package/templates/default/apps/web/src/components/ui/input.tsx +0 -21
  256. package/templates/default/apps/web/src/components/ui/label.tsx +0 -21
  257. package/templates/default/apps/web/src/components/ui/pagination.tsx +0 -105
  258. package/templates/default/apps/web/src/components/ui/popover.tsx +0 -42
  259. package/templates/default/apps/web/src/components/ui/progress.tsx +0 -28
  260. package/templates/default/apps/web/src/components/ui/select.tsx +0 -170
  261. package/templates/default/apps/web/src/components/ui/separator.tsx +0 -28
  262. package/templates/default/apps/web/src/components/ui/skeleton.tsx +0 -13
  263. package/templates/default/apps/web/src/components/ui/sonner.tsx +0 -25
  264. package/templates/default/apps/web/src/components/ui/table.tsx +0 -92
  265. package/templates/default/apps/web/src/components/ui/tabs.tsx +0 -54
  266. package/templates/default/apps/web/src/components/ui/textarea.tsx +0 -18
  267. package/templates/default/apps/web/src/components/ui/tooltip.tsx +0 -57
  268. package/templates/default/apps/web/src/components/ui/typography.tsx +0 -158
  269. package/templates/default/packages/config/panda.config.ts +0 -114
  270. package/templates/default/panda.config.ts +0 -114
@@ -0,0 +1,148 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons";
5
+ import type { Column } from "@tanstack/react-table";
6
+ import { cn } from "../lib/utils";
7
+ import { Badge } from "../ui/badge";
8
+ import { Button } from "../ui/button";
9
+ import {
10
+ Command,
11
+ CommandEmpty,
12
+ CommandGroup,
13
+ CommandInput,
14
+ CommandItem,
15
+ CommandList,
16
+ CommandSeparator,
17
+ } from "../ui/command";
18
+ import {
19
+ Popover,
20
+ PopoverContent,
21
+ PopoverTrigger,
22
+ } from "../ui/popover";
23
+ import { Separator } from "../ui/separator";
24
+
25
+ type DataTableFacetedFilterProps<TData, TValue> = {
26
+ column?: Column<TData, TValue>;
27
+ title?: string;
28
+ options: {
29
+ label: string;
30
+ value: string;
31
+ icon?: React.ComponentType<{ className?: string }>;
32
+ }[];
33
+ };
34
+
35
+ export function DataTableFacetedFilter<TData, TValue>({
36
+ column,
37
+ title,
38
+ options,
39
+ }: DataTableFacetedFilterProps<TData, TValue>) {
40
+ const facets = column?.getFacetedUniqueValues();
41
+ const selectedValues = new Set(column?.getFilterValue() as string[]);
42
+
43
+ return (
44
+ <Popover>
45
+ <PopoverTrigger asChild>
46
+ <Button variant="outline" size="sm" className="h-8 border-dashed">
47
+ <PlusCircledIcon className="size-4" />
48
+ {title}
49
+ {selectedValues?.size > 0 && (
50
+ <>
51
+ <Separator orientation="vertical" className="mx-2 h-4" />
52
+ <Badge
53
+ variant="secondary"
54
+ className="rounded-sm px-1 font-normal lg:hidden"
55
+ >
56
+ {selectedValues.size}
57
+ </Badge>
58
+ <div className="hidden space-x-1 lg:flex">
59
+ {selectedValues.size > 2 ? (
60
+ <Badge
61
+ variant="secondary"
62
+ className="rounded-sm px-1 font-normal"
63
+ >
64
+ {selectedValues.size} selected
65
+ </Badge>
66
+ ) : (
67
+ options
68
+ .filter((option) => selectedValues.has(option.value))
69
+ .map((option) => (
70
+ <Badge
71
+ variant="secondary"
72
+ key={option.value}
73
+ className="rounded-sm px-1 font-normal"
74
+ >
75
+ {option.label}
76
+ </Badge>
77
+ ))
78
+ )}
79
+ </div>
80
+ </>
81
+ )}
82
+ </Button>
83
+ </PopoverTrigger>
84
+ <PopoverContent className="w-[200px] p-0" align="start">
85
+ <Command>
86
+ <CommandInput placeholder={title} />
87
+ <CommandList>
88
+ <CommandEmpty>No results found.</CommandEmpty>
89
+ <CommandGroup>
90
+ {options.map((option) => {
91
+ const isSelected = selectedValues.has(option.value);
92
+ return (
93
+ <CommandItem
94
+ key={option.value}
95
+ onSelect={() => {
96
+ if (isSelected) {
97
+ selectedValues.delete(option.value);
98
+ } else {
99
+ selectedValues.add(option.value);
100
+ }
101
+ const filterValues = Array.from(selectedValues);
102
+ column?.setFilterValue(
103
+ filterValues.length ? filterValues : undefined
104
+ );
105
+ }}
106
+ >
107
+ <div
108
+ className={cn(
109
+ "flex size-4 items-center justify-center rounded-sm border border-primary",
110
+ isSelected
111
+ ? "bg-primary text-primary-foreground"
112
+ : "opacity-50 [&_svg]:invisible"
113
+ )}
114
+ >
115
+ <CheckIcon className={cn("h-4 w-4 text-background")} />
116
+ </div>
117
+ {option.icon && (
118
+ <option.icon className="size-4 text-muted-foreground" />
119
+ )}
120
+ <span>{option.label}</span>
121
+ {facets?.get(option.value) && (
122
+ <span className="ms-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
123
+ {facets.get(option.value)}
124
+ </span>
125
+ )}
126
+ </CommandItem>
127
+ );
128
+ })}
129
+ </CommandGroup>
130
+ {selectedValues.size > 0 && (
131
+ <>
132
+ <CommandSeparator />
133
+ <CommandGroup>
134
+ <CommandItem
135
+ onSelect={() => column?.setFilterValue(undefined)}
136
+ className="justify-center text-center"
137
+ >
138
+ Clear filters
139
+ </CommandItem>
140
+ </CommandGroup>
141
+ </>
142
+ )}
143
+ </CommandList>
144
+ </Command>
145
+ </PopoverContent>
146
+ </Popover>
147
+ );
148
+ }
@@ -0,0 +1,9 @@
1
+ "use client";
2
+
3
+ export { DataTable } from "./data-table";
4
+ export { DataTableBulkActions } from "./bulk-actions";
5
+ export { DataTableColumnHeader } from "./column-header";
6
+ export { DataTableFacetedFilter } from "./faceted-filter";
7
+ export { DataTablePagination } from "./pagination";
8
+ export { DataTableToolbar } from "./toolbar";
9
+ export { DataTableViewOptions } from "./view-options";
@@ -0,0 +1,101 @@
1
+ import {
2
+ ChevronLeftIcon,
3
+ ChevronRightIcon,
4
+ ChevronsLeftIcon,
5
+ ChevronsRightIcon,
6
+ } from "lucide-react";
7
+ import type { Table } from "@tanstack/react-table";
8
+
9
+ import { Button } from "../ui/button";
10
+ import {
11
+ Select,
12
+ SelectContent,
13
+ SelectItem,
14
+ SelectTrigger,
15
+ SelectValue,
16
+ } from "../ui/select";
17
+
18
+ interface DataTablePaginationProps<TData> {
19
+ table: Table<TData>;
20
+ className?: string;
21
+ }
22
+
23
+ function DataTablePagination<TData>({
24
+ table,
25
+ className,
26
+ }: DataTablePaginationProps<TData>) {
27
+ return (
28
+ <div className={`flex items-center justify-between px-2 ${className || ""}`}>
29
+ <div className="text-muted-foreground flex-1 text-sm">
30
+ {table.getFilteredSelectedRowModel().rows.length} of{" "}
31
+ {table.getFilteredRowModel().rows.length} row(s) selected.
32
+ </div>
33
+ <div className="flex items-center space-x-6 lg:space-x-8">
34
+ <div className="flex items-center space-x-2">
35
+ <p className="text-sm font-medium">Rows per page</p>
36
+ <Select
37
+ value={`${table.getState().pagination.pageSize}`}
38
+ onValueChange={(value) => {
39
+ table.setPageSize(Number(value));
40
+ }}
41
+ >
42
+ <SelectTrigger className="h-8 w-[70px]">
43
+ <SelectValue placeholder={table.getState().pagination.pageSize} />
44
+ </SelectTrigger>
45
+ <SelectContent side="top">
46
+ {[10, 20, 30, 40, 50].map((pageSize) => (
47
+ <SelectItem key={pageSize} value={`${pageSize}`}>
48
+ {pageSize}
49
+ </SelectItem>
50
+ ))}
51
+ </SelectContent>
52
+ </Select>
53
+ </div>
54
+ <div className="flex w-[100px] items-center justify-center text-sm font-medium">
55
+ Page {table.getState().pagination.pageIndex + 1} of{" "}
56
+ {table.getPageCount()}
57
+ </div>
58
+ <div className="flex items-center space-x-2">
59
+ <Button
60
+ variant="outline"
61
+ className="hidden h-8 w-8 p-0 lg:flex"
62
+ onClick={() => table.setPageIndex(0)}
63
+ disabled={!table.getCanPreviousPage()}
64
+ >
65
+ <span className="sr-only">Go to first page</span>
66
+ <ChevronsLeftIcon className="h-4 w-4" />
67
+ </Button>
68
+ <Button
69
+ variant="outline"
70
+ className="h-8 w-8 p-0"
71
+ onClick={() => table.previousPage()}
72
+ disabled={!table.getCanPreviousPage()}
73
+ >
74
+ <span className="sr-only">Go to previous page</span>
75
+ <ChevronLeftIcon className="h-4 w-4" />
76
+ </Button>
77
+ <Button
78
+ variant="outline"
79
+ className="h-8 w-8 p-0"
80
+ onClick={() => table.nextPage()}
81
+ disabled={!table.getCanNextPage()}
82
+ >
83
+ <span className="sr-only">Go to next page</span>
84
+ <ChevronRightIcon className="h-4 w-4" />
85
+ </Button>
86
+ <Button
87
+ variant="outline"
88
+ className="hidden h-8 w-8 p-0 lg:flex"
89
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
90
+ disabled={!table.getCanNextPage()}
91
+ >
92
+ <span className="sr-only">Go to last page</span>
93
+ <ChevronsRightIcon className="h-4 w-4" />
94
+ </Button>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ );
99
+ }
100
+
101
+ export { DataTablePagination };
@@ -0,0 +1,87 @@
1
+ "use client";
2
+
3
+ import { Cross2Icon } from "@radix-ui/react-icons";
4
+ import type { Table } from "@tanstack/react-table";
5
+ import { Button } from "../ui/button";
6
+ import { Input } from "../ui/input";
7
+ import { DataTableFacetedFilter } from "./faceted-filter";
8
+ import { DataTableViewOptions } from "./view-options";
9
+
10
+ type DataTableToolbarProps<TData> = {
11
+ table: Table<TData>;
12
+ searchPlaceholder?: string;
13
+ searchKey?: string;
14
+ filters?: {
15
+ columnId: string;
16
+ title: string;
17
+ options: {
18
+ label: string;
19
+ value: string;
20
+ icon?: React.ComponentType<{ className?: string }>;
21
+ }[];
22
+ }[];
23
+ };
24
+
25
+ export function DataTableToolbar<TData>({
26
+ table,
27
+ searchPlaceholder = "Filter...",
28
+ searchKey,
29
+ filters = [],
30
+ }: DataTableToolbarProps<TData>) {
31
+ const isFiltered =
32
+ table.getState().columnFilters.length > 0 || table.getState().globalFilter;
33
+
34
+ return (
35
+ <div className="flex items-center justify-between">
36
+ <div className="flex flex-1 flex-col-reverse items-start gap-y-2 sm:flex-row sm:items-center sm:space-x-2">
37
+ {searchKey ? (
38
+ <Input
39
+ placeholder={searchPlaceholder}
40
+ value={
41
+ (table.getColumn(searchKey)?.getFilterValue() as string) ?? ""
42
+ }
43
+ onChange={(event) =>
44
+ table.getColumn(searchKey)?.setFilterValue(event.target.value)
45
+ }
46
+ className="h-8 w-[150px] lg:w-[250px]"
47
+ />
48
+ ) : (
49
+ <Input
50
+ placeholder={searchPlaceholder}
51
+ value={table.getState().globalFilter ?? ""}
52
+ onChange={(event) => table.setGlobalFilter(event.target.value)}
53
+ className="h-8 w-[150px] lg:w-[250px]"
54
+ />
55
+ )}
56
+ <div className="flex gap-x-2">
57
+ {filters.map((filter) => {
58
+ const column = table.getColumn(filter.columnId);
59
+ if (!column) return null;
60
+ return (
61
+ <DataTableFacetedFilter
62
+ key={filter.columnId}
63
+ column={column}
64
+ title={filter.title}
65
+ options={filter.options}
66
+ />
67
+ );
68
+ })}
69
+ </div>
70
+ {isFiltered && (
71
+ <Button
72
+ variant="ghost"
73
+ onClick={() => {
74
+ table.resetColumnFilters();
75
+ table.setGlobalFilter("");
76
+ }}
77
+ className="h-8 px-2 lg:px-3"
78
+ >
79
+ Reset
80
+ <Cross2Icon className="ms-2 h-4 w-4" />
81
+ </Button>
82
+ )}
83
+ </div>
84
+ <DataTableViewOptions table={table} />
85
+ </div>
86
+ );
87
+ }
@@ -0,0 +1,57 @@
1
+ "use client";
2
+
3
+ import { SlidersHorizontalIcon } from "lucide-react";
4
+ import type { Table } from "@tanstack/react-table";
5
+
6
+ import { Button } from "../ui/button";
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuCheckboxItem,
10
+ DropdownMenuContent,
11
+ DropdownMenuLabel,
12
+ DropdownMenuSeparator,
13
+ DropdownMenuTrigger,
14
+ } from "../ui/dropdown-menu";
15
+
16
+ interface DataTableViewOptionsProps<TData> {
17
+ table: Table<TData>;
18
+ }
19
+
20
+ function DataTableViewOptions<TData>({
21
+ table,
22
+ }: DataTableViewOptionsProps<TData>) {
23
+ return (
24
+ <DropdownMenu>
25
+ <DropdownMenuTrigger asChild>
26
+ <Button variant="outline" size="sm" className="ml-auto hidden h-8 lg:flex">
27
+ <SlidersHorizontalIcon className="mr-2 h-4 w-4" />
28
+ View
29
+ </Button>
30
+ </DropdownMenuTrigger>
31
+ <DropdownMenuContent align="end" className="w-[150px]">
32
+ <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
33
+ <DropdownMenuSeparator />
34
+ {table
35
+ .getAllColumns()
36
+ .filter(
37
+ (column) =>
38
+ typeof column.accessorFn !== "undefined" && column.getCanHide()
39
+ )
40
+ .map((column) => {
41
+ return (
42
+ <DropdownMenuCheckboxItem
43
+ key={column.id}
44
+ className="capitalize"
45
+ checked={column.getIsVisible()}
46
+ onCheckedChange={(value) => column.toggleVisibility(!!value)}
47
+ >
48
+ {column.id}
49
+ </DropdownMenuCheckboxItem>
50
+ );
51
+ })}
52
+ </DropdownMenuContent>
53
+ </DropdownMenu>
54
+ );
55
+ }
56
+
57
+ export { DataTableViewOptions };
@@ -0,0 +1,23 @@
1
+ import * as React from "react";
2
+
3
+ const MOBILE_BREAKPOINT = 768;
4
+
5
+ function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
7
+ undefined
8
+ );
9
+
10
+ React.useEffect(() => {
11
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
12
+ const onChange = () => {
13
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
14
+ };
15
+ mql.addEventListener("change", onChange);
16
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
17
+ return () => mql.removeEventListener("change", onChange);
18
+ }, []);
19
+
20
+ return !!isMobile;
21
+ }
22
+
23
+ export { useIsMobile };
@@ -0,0 +1,55 @@
1
+ "use client";
2
+
3
+ import type * as React from "react";
4
+ import { SidebarTrigger } from "../ui/sidebar";
5
+ import { Separator } from "../ui/separator";
6
+ import { Button } from "../ui/button";
7
+ import { Search } from "lucide-react";
8
+ import { cn } from "../lib/utils";
9
+
10
+ interface HeaderProps extends React.ComponentProps<"header"> {
11
+ children?: React.ReactNode;
12
+ }
13
+
14
+ function Header({ className, children, ...props }: HeaderProps) {
15
+ const handleOpenCommand = () => {
16
+ const event = new KeyboardEvent("keydown", {
17
+ key: "k",
18
+ metaKey: true,
19
+ bubbles: true,
20
+ });
21
+ document.dispatchEvent(event);
22
+ };
23
+
24
+ return (
25
+ <header
26
+ className={cn(
27
+ "flex h-16 shrink-0 items-center gap-2 border-b px-4 transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-12",
28
+ className
29
+ )}
30
+ {...props}
31
+ >
32
+ <div className="flex items-center gap-2">
33
+ <SidebarTrigger className="-ml-1" />
34
+ <Separator orientation="vertical" className="mr-2 h-4" />
35
+ </div>
36
+ <div className="flex flex-1 items-center justify-between">
37
+ <Button
38
+ variant="outline"
39
+ className="relative h-9 w-full justify-start text-sm text-muted-foreground sm:pr-12 md:w-64 lg:w-96"
40
+ onClick={handleOpenCommand}
41
+ >
42
+ <Search className="mr-2 h-4 w-4" />
43
+ <span>Search...</span>
44
+ <kbd className="pointer-events-none absolute right-1.5 top-2 hidden h-5 select-none items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium opacity-100 sm:flex">
45
+ <span className="text-xs">⌘</span>K
46
+ </kbd>
47
+ </Button>
48
+ {children}
49
+ </div>
50
+ </header>
51
+ );
52
+ }
53
+
54
+ export { Header };
55
+ export type { HeaderProps };
@@ -0,0 +1,10 @@
1
+ export { Header } from "./header";
2
+ export type { HeaderProps } from "./header";
3
+ export { Main } from "./main";
4
+ export type { MainProps } from "./main";
5
+ export { NavGroup } from "./nav-group";
6
+ export type { NavGroupProps } from "./nav-group";
7
+ export { NavUser } from "./nav-user";
8
+ export type { NavUserProps } from "./nav-user";
9
+ export { ThemeSwitch } from "./theme-switch";
10
+ export type { NavItem, NavSubItem, NavGroup as NavGroupType } from "./types";
@@ -0,0 +1,23 @@
1
+ import type * as React from "react";
2
+ import { cn } from "../lib/utils";
3
+
4
+ interface MainProps extends React.ComponentProps<"main"> {
5
+ fixed?: boolean;
6
+ }
7
+
8
+ function Main({ className, fixed, ...props }: MainProps) {
9
+ return (
10
+ <main
11
+ className={cn(
12
+ "px-4 py-6",
13
+ fixed && "flex grow flex-col overflow-hidden",
14
+ className
15
+ )}
16
+ data-layout={fixed ? "fixed" : "auto"}
17
+ {...props}
18
+ />
19
+ );
20
+ }
21
+
22
+ export { Main };
23
+ export type { MainProps };
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { ChevronRight } from "lucide-react";
6
+ import {
7
+ Collapsible,
8
+ CollapsibleContent,
9
+ CollapsibleTrigger,
10
+ } from "../ui/collapsible";
11
+ import {
12
+ SidebarGroup,
13
+ SidebarGroupLabel,
14
+ SidebarGroupContent,
15
+ SidebarMenu,
16
+ SidebarMenuButton,
17
+ SidebarMenuItem,
18
+ SidebarMenuSub,
19
+ SidebarMenuSubButton,
20
+ SidebarMenuSubItem,
21
+ SidebarMenuBadge,
22
+ } from "../ui/sidebar";
23
+ import type { NavGroup as NavGroupType } from "./types";
24
+
25
+ interface NavGroupProps {
26
+ group: NavGroupType;
27
+ }
28
+
29
+ function NavGroup({ group }: NavGroupProps) {
30
+ const pathname = usePathname();
31
+
32
+ return (
33
+ <SidebarGroup>
34
+ <SidebarGroupLabel>{group.title}</SidebarGroupLabel>
35
+ <SidebarGroupContent>
36
+ <SidebarMenu>
37
+ {group.items.map((item) => {
38
+ const isActive = item.url
39
+ ? pathname === item.url || pathname.startsWith(`${item.url}/`)
40
+ : item.items?.some(
41
+ (sub) => pathname === sub.url || pathname.startsWith(`${sub.url}/`)
42
+ ) ?? false;
43
+
44
+ if (item.items && item.items.length > 0) {
45
+ return (
46
+ <Collapsible
47
+ key={item.title}
48
+ asChild
49
+ defaultOpen={isActive}
50
+ className="group/collapsible"
51
+ >
52
+ <SidebarMenuItem>
53
+ <CollapsibleTrigger asChild>
54
+ <SidebarMenuButton tooltip={item.title}>
55
+ {item.icon && <item.icon />}
56
+ <span>{item.title}</span>
57
+ <ChevronRight className="ml-auto transition-transform duration-200 group-data-[state=open]/collapsible:rotate-90" />
58
+ </SidebarMenuButton>
59
+ </CollapsibleTrigger>
60
+ <CollapsibleContent>
61
+ <SidebarMenuSub>
62
+ {item.items.map((subItem) => (
63
+ <SidebarMenuSubItem key={subItem.title}>
64
+ <SidebarMenuSubButton
65
+ asChild
66
+ isActive={pathname === subItem.url}
67
+ >
68
+ <Link href={subItem.url}>
69
+ <span>{subItem.title}</span>
70
+ {subItem.badge && (
71
+ <SidebarMenuBadge>
72
+ {subItem.badge}
73
+ </SidebarMenuBadge>
74
+ )}
75
+ </Link>
76
+ </SidebarMenuSubButton>
77
+ </SidebarMenuSubItem>
78
+ ))}
79
+ </SidebarMenuSub>
80
+ </CollapsibleContent>
81
+ </SidebarMenuItem>
82
+ </Collapsible>
83
+ );
84
+ }
85
+
86
+ return (
87
+ <SidebarMenuItem key={item.title}>
88
+ <SidebarMenuButton
89
+ asChild
90
+ tooltip={item.title}
91
+ isActive={isActive}
92
+ >
93
+ <Link href={item.url || "#"}>
94
+ {item.icon && <item.icon />}
95
+ <span>{item.title}</span>
96
+ </Link>
97
+ </SidebarMenuButton>
98
+ {item.badge && (
99
+ <SidebarMenuBadge>{item.badge}</SidebarMenuBadge>
100
+ )}
101
+ </SidebarMenuItem>
102
+ );
103
+ })}
104
+ </SidebarMenu>
105
+ </SidebarGroupContent>
106
+ </SidebarGroup>
107
+ );
108
+ }
109
+
110
+ export { NavGroup };
111
+ export type { NavGroupProps };