create-ej-app 1.0.0 → 2.0.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 (198) hide show
  1. package/cli.js +21 -3
  2. package/package.json +1 -1
  3. package/template/{.env.example → advanced/.env.example} +4 -0
  4. package/template/{components.json → advanced/components.json} +1 -1
  5. package/template/{package.json → advanced/package.json} +11 -0
  6. package/template/{prisma → advanced/prisma}/schema.prisma +18 -1
  7. package/template/{src → advanced/src}/app/(app)/tasks/[id]/actions.ts +46 -4
  8. package/template/{src → advanced/src}/app/(app)/tasks/[id]/render.tsx +136 -25
  9. package/template/{src → advanced/src}/app/(app)/tasks/actions.ts +0 -6
  10. package/template/{src → advanced/src}/app/(app)/tasks/page.tsx +0 -3
  11. package/template/{src → advanced/src}/app/(app)/tasks/render.tsx +0 -10
  12. package/template/advanced/src/app/api/upload/route.ts +45 -0
  13. package/template/{src → advanced/src}/app/layout.tsx +1 -1
  14. package/template/{src → advanced/src}/env.mjs +10 -2
  15. package/template/advanced/src/styles/editor.css +61 -0
  16. package/template/{tailwind.config.ts → advanced/tailwind.config.ts} +1 -1
  17. package/template/base/.env.example +16 -0
  18. package/template/base/.eslintrc.cjs +24 -0
  19. package/template/base/components.json +16 -0
  20. package/template/base/next.config.mjs +18 -0
  21. package/template/base/package.json +79 -0
  22. package/template/base/postcss.config.cjs +6 -0
  23. package/template/base/prettier.config.ts +11 -0
  24. package/template/base/prisma/schema.prisma +115 -0
  25. package/template/base/public/next.svg +1 -0
  26. package/template/base/src/app/(app)/dashboard/page.tsx +19 -0
  27. package/template/base/src/app/(app)/layout.tsx +90 -0
  28. package/template/base/src/app/(app)/settings/actions.ts +18 -0
  29. package/template/base/src/app/(app)/settings/loading.tsx +28 -0
  30. package/template/base/src/app/(app)/settings/page.tsx +28 -0
  31. package/template/base/src/app/(app)/settings/render.tsx +113 -0
  32. package/template/base/src/app/(app)/tasks/[id]/actions.ts +149 -0
  33. package/template/base/src/app/(app)/tasks/[id]/page.tsx +25 -0
  34. package/template/base/src/app/(app)/tasks/[id]/render.tsx +413 -0
  35. package/template/base/src/app/(app)/tasks/actions.ts +69 -0
  36. package/template/base/src/app/(app)/tasks/page.tsx +53 -0
  37. package/template/base/src/app/(app)/tasks/render.tsx +107 -0
  38. package/template/base/src/app/(app)/users/[id]/actions.ts +83 -0
  39. package/template/base/src/app/(app)/users/[id]/page.tsx +32 -0
  40. package/template/base/src/app/(app)/users/[id]/render.tsx +240 -0
  41. package/template/base/src/app/(app)/users/actions.ts +55 -0
  42. package/template/base/src/app/(app)/users/page.tsx +49 -0
  43. package/template/base/src/app/(app)/users/render.tsx +80 -0
  44. package/template/base/src/app/(auth)/layout.tsx +20 -0
  45. package/template/base/src/app/(auth)/login/page.tsx +49 -0
  46. package/template/base/src/app/(auth)/login/user-auth-form.tsx +152 -0
  47. package/template/base/src/app/(marketing)/layout.tsx +42 -0
  48. package/template/base/src/app/(marketing)/page.tsx +96 -0
  49. package/template/base/src/app/api/auth/[...nextauth]/route.ts +7 -0
  50. package/template/base/src/app/api/upload/route.ts +45 -0
  51. package/template/base/src/app/favicon.ico +0 -0
  52. package/template/base/src/app/layout.tsx +32 -0
  53. package/template/base/src/components/app-nav-command.tsx +160 -0
  54. package/template/base/src/components/dark-mode-toggle.tsx +43 -0
  55. package/template/base/src/components/data-table/data-table-column-header.tsx +91 -0
  56. package/template/base/src/components/data-table/data-table-faceted-filter.tsx +149 -0
  57. package/template/base/src/components/data-table/data-table-loading.tsx +77 -0
  58. package/template/base/src/components/data-table/data-table-pagination.tsx +94 -0
  59. package/template/base/src/components/data-table/data-table-toolbar.tsx +78 -0
  60. package/template/base/src/components/data-table/data-table-types.ts +17 -0
  61. package/template/base/src/components/data-table/data-table-view-options.tsx +59 -0
  62. package/template/base/src/components/data-table/data-table.tsx +301 -0
  63. package/template/base/src/components/data-table/simple-data-table.tsx +109 -0
  64. package/template/base/src/components/empty-placeholder.tsx +78 -0
  65. package/template/base/src/components/heading.tsx +17 -0
  66. package/template/base/src/components/icons.tsx +83 -0
  67. package/template/base/src/components/main-nav.tsx +64 -0
  68. package/template/base/src/components/mobile-nav.tsx +50 -0
  69. package/template/base/src/components/shell.tsx +13 -0
  70. package/template/base/src/components/side-nav.tsx +51 -0
  71. package/template/base/src/components/site-footer.tsx +31 -0
  72. package/template/base/src/components/system-info.tsx +36 -0
  73. package/template/base/src/components/theme-provider.tsx +13 -0
  74. package/template/base/src/components/ui/alert-dialog.tsx +145 -0
  75. package/template/base/src/components/ui/avatar.tsx +50 -0
  76. package/template/base/src/components/ui/badge.tsx +36 -0
  77. package/template/base/src/components/ui/button.tsx +56 -0
  78. package/template/base/src/components/ui/card.tsx +79 -0
  79. package/template/base/src/components/ui/checkbox.tsx +30 -0
  80. package/template/base/src/components/ui/command.tsx +155 -0
  81. package/template/base/src/components/ui/dialog.tsx +123 -0
  82. package/template/base/src/components/ui/dropdown-menu.tsx +200 -0
  83. package/template/base/src/components/ui/form.tsx +176 -0
  84. package/template/base/src/components/ui/input.tsx +25 -0
  85. package/template/base/src/components/ui/label.tsx +26 -0
  86. package/template/base/src/components/ui/popover.tsx +31 -0
  87. package/template/base/src/components/ui/select.tsx +121 -0
  88. package/template/base/src/components/ui/separator.tsx +31 -0
  89. package/template/base/src/components/ui/skeleton.tsx +15 -0
  90. package/template/base/src/components/ui/table.tsx +114 -0
  91. package/template/base/src/components/ui/textarea.tsx +24 -0
  92. package/template/base/src/components/ui/toast.tsx +127 -0
  93. package/template/base/src/components/ui/toaster.tsx +35 -0
  94. package/template/base/src/components/ui/use-toast.ts +189 -0
  95. package/template/base/src/components/user-account-nav.tsx +60 -0
  96. package/template/base/src/components/user-avatar.tsx +25 -0
  97. package/template/base/src/env.mjs +75 -0
  98. package/template/base/src/hooks/use-debounce.ts +17 -0
  99. package/template/base/src/hooks/use-lock-body.tsx +12 -0
  100. package/template/base/src/lib/auth.ts +79 -0
  101. package/template/base/src/lib/cn.ts +4 -0
  102. package/template/base/src/lib/db.ts +20 -0
  103. package/template/base/src/lib/delay.ts +2 -0
  104. package/template/base/src/lib/formatDate.ts +11 -0
  105. package/template/base/src/lib/generateLabel.ts +5 -0
  106. package/template/base/src/lib/siteConfig.ts +4 -0
  107. package/template/base/src/styles/editor.css +61 -0
  108. package/template/base/src/styles/globals.css +76 -0
  109. package/template/base/src/types/UnwrapPromise.ts +1 -0
  110. package/template/base/tailwind.config.ts +79 -0
  111. package/template/base/tsconfig.json +46 -0
  112. /package/template/{.eslintrc.cjs → advanced/.eslintrc.cjs} +0 -0
  113. /package/template/{next.config.mjs → advanced/next.config.mjs} +0 -0
  114. /package/template/{postcss.config.cjs → advanced/postcss.config.cjs} +0 -0
  115. /package/template/{prettier.config.ts → advanced/prettier.config.ts} +0 -0
  116. /package/template/{public → advanced/public}/next.svg +0 -0
  117. /package/template/{src → advanced/src}/app/(app)/dashboard/page.tsx +0 -0
  118. /package/template/{src → advanced/src}/app/(app)/layout.tsx +0 -0
  119. /package/template/{src → advanced/src}/app/(app)/roles/[id]/actions.ts +0 -0
  120. /package/template/{src → advanced/src}/app/(app)/roles/[id]/page.tsx +0 -0
  121. /package/template/{src → advanced/src}/app/(app)/roles/[id]/render.tsx +0 -0
  122. /package/template/{src → advanced/src}/app/(app)/roles/actions.ts +0 -0
  123. /package/template/{src → advanced/src}/app/(app)/roles/page.tsx +0 -0
  124. /package/template/{src → advanced/src}/app/(app)/roles/render.tsx +0 -0
  125. /package/template/{src → advanced/src}/app/(app)/settings/actions.ts +0 -0
  126. /package/template/{src → advanced/src}/app/(app)/settings/loading.tsx +0 -0
  127. /package/template/{src → advanced/src}/app/(app)/settings/page.tsx +0 -0
  128. /package/template/{src → advanced/src}/app/(app)/settings/render.tsx +0 -0
  129. /package/template/{src → advanced/src}/app/(app)/tasks/[id]/page.tsx +0 -0
  130. /package/template/{src → advanced/src}/app/(app)/users/[id]/actions.ts +0 -0
  131. /package/template/{src → advanced/src}/app/(app)/users/[id]/page.tsx +0 -0
  132. /package/template/{src → advanced/src}/app/(app)/users/[id]/render.tsx +0 -0
  133. /package/template/{src → advanced/src}/app/(app)/users/actions.ts +0 -0
  134. /package/template/{src → advanced/src}/app/(app)/users/page.tsx +0 -0
  135. /package/template/{src → advanced/src}/app/(app)/users/render.tsx +0 -0
  136. /package/template/{src → advanced/src}/app/(auth)/layout.tsx +0 -0
  137. /package/template/{src → advanced/src}/app/(auth)/login/page.tsx +0 -0
  138. /package/template/{src → advanced/src}/app/(auth)/login/user-auth-form.tsx +0 -0
  139. /package/template/{src → advanced/src}/app/(marketing)/layout.tsx +0 -0
  140. /package/template/{src → advanced/src}/app/(marketing)/page.tsx +0 -0
  141. /package/template/{src → advanced/src}/app/api/auth/[...nextauth]/route.ts +0 -0
  142. /package/template/{src → advanced/src}/app/favicon.ico +0 -0
  143. /package/template/{src → advanced/src}/components/app-nav-command.tsx +0 -0
  144. /package/template/{src → advanced/src}/components/dark-mode-toggle.tsx +0 -0
  145. /package/template/{src → advanced/src}/components/data-table/data-table-column-header.tsx +0 -0
  146. /package/template/{src → advanced/src}/components/data-table/data-table-faceted-filter.tsx +0 -0
  147. /package/template/{src → advanced/src}/components/data-table/data-table-loading.tsx +0 -0
  148. /package/template/{src → advanced/src}/components/data-table/data-table-pagination.tsx +0 -0
  149. /package/template/{src → advanced/src}/components/data-table/data-table-toolbar.tsx +0 -0
  150. /package/template/{src → advanced/src}/components/data-table/data-table-types.ts +0 -0
  151. /package/template/{src → advanced/src}/components/data-table/data-table-view-options.tsx +0 -0
  152. /package/template/{src → advanced/src}/components/data-table/data-table.tsx +0 -0
  153. /package/template/{src → advanced/src}/components/data-table/simple-data-table.tsx +0 -0
  154. /package/template/{src → advanced/src}/components/empty-placeholder.tsx +0 -0
  155. /package/template/{src → advanced/src}/components/heading.tsx +0 -0
  156. /package/template/{src → advanced/src}/components/icons.tsx +0 -0
  157. /package/template/{src → advanced/src}/components/main-nav.tsx +0 -0
  158. /package/template/{src → advanced/src}/components/mobile-nav.tsx +0 -0
  159. /package/template/{src → advanced/src}/components/shell.tsx +0 -0
  160. /package/template/{src → advanced/src}/components/side-nav.tsx +0 -0
  161. /package/template/{src → advanced/src}/components/site-footer.tsx +0 -0
  162. /package/template/{src → advanced/src}/components/system-info.tsx +0 -0
  163. /package/template/{src → advanced/src}/components/theme-provider.tsx +0 -0
  164. /package/template/{src → advanced/src}/components/ui/alert-dialog.tsx +0 -0
  165. /package/template/{src → advanced/src}/components/ui/avatar.tsx +0 -0
  166. /package/template/{src → advanced/src}/components/ui/badge.tsx +0 -0
  167. /package/template/{src → advanced/src}/components/ui/button.tsx +0 -0
  168. /package/template/{src → advanced/src}/components/ui/card.tsx +0 -0
  169. /package/template/{src → advanced/src}/components/ui/checkbox.tsx +0 -0
  170. /package/template/{src → advanced/src}/components/ui/command.tsx +0 -0
  171. /package/template/{src → advanced/src}/components/ui/dialog.tsx +0 -0
  172. /package/template/{src → advanced/src}/components/ui/dropdown-menu.tsx +0 -0
  173. /package/template/{src → advanced/src}/components/ui/form.tsx +0 -0
  174. /package/template/{src → advanced/src}/components/ui/input.tsx +0 -0
  175. /package/template/{src → advanced/src}/components/ui/label.tsx +0 -0
  176. /package/template/{src → advanced/src}/components/ui/popover.tsx +0 -0
  177. /package/template/{src → advanced/src}/components/ui/select.tsx +0 -0
  178. /package/template/{src → advanced/src}/components/ui/separator.tsx +0 -0
  179. /package/template/{src → advanced/src}/components/ui/skeleton.tsx +0 -0
  180. /package/template/{src → advanced/src}/components/ui/table.tsx +0 -0
  181. /package/template/{src → advanced/src}/components/ui/textarea.tsx +0 -0
  182. /package/template/{src → advanced/src}/components/ui/toast.tsx +0 -0
  183. /package/template/{src → advanced/src}/components/ui/toaster.tsx +0 -0
  184. /package/template/{src → advanced/src}/components/ui/use-toast.ts +0 -0
  185. /package/template/{src → advanced/src}/components/user-account-nav.tsx +0 -0
  186. /package/template/{src → advanced/src}/components/user-avatar.tsx +0 -0
  187. /package/template/{src → advanced/src}/hooks/use-debounce.ts +0 -0
  188. /package/template/{src → advanced/src}/hooks/use-lock-body.tsx +0 -0
  189. /package/template/{src → advanced/src}/lib/auth.ts +0 -0
  190. /package/template/{src → advanced/src}/lib/cn.ts +0 -0
  191. /package/template/{src → advanced/src}/lib/db.ts +0 -0
  192. /package/template/{src → advanced/src}/lib/delay.ts +0 -0
  193. /package/template/{src → advanced/src}/lib/formatDate.ts +0 -0
  194. /package/template/{src → advanced/src}/lib/generateLabel.ts +0 -0
  195. /package/template/{src → advanced/src}/lib/siteConfig.ts +0 -0
  196. /package/template/{src/app → advanced/src/styles}/globals.css +0 -0
  197. /package/template/{src → advanced/src}/types/UnwrapPromise.ts +0 -0
  198. /package/template/{tsconfig.json → advanced/tsconfig.json} +0 -0
@@ -0,0 +1,91 @@
1
+ import { type Column } from '@tanstack/react-table'
2
+
3
+ import { Icons } from '@/components/icons'
4
+ import { Button } from '@/components/ui/button'
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuSeparator,
10
+ DropdownMenuTrigger
11
+ } from '@/components/ui/dropdown-menu'
12
+ import { cn } from '@/lib/cn'
13
+
14
+ interface DataTableColumnHeaderProps<TData, TValue>
15
+ extends React.HTMLAttributes<HTMLDivElement> {
16
+ column: Column<TData, TValue>
17
+ title: string
18
+ }
19
+
20
+ export function DataTableColumnHeader<TData, TValue>({
21
+ column,
22
+ title,
23
+ className
24
+ }: DataTableColumnHeaderProps<TData, TValue>) {
25
+ if (!column.getCanSort()) {
26
+ return <div className={cn(className)}>{title}</div>
27
+ }
28
+
29
+ return (
30
+ <div className={cn('flex items-center space-x-2', className)}>
31
+ <DropdownMenu>
32
+ <DropdownMenuTrigger asChild>
33
+ <Button
34
+ aria-label={
35
+ column.getIsSorted() === 'desc'
36
+ ? `Sorted descending. Click to sort ascending.`
37
+ : column.getIsSorted() === 'asc'
38
+ ? `Sorted ascending. Click to sort descending.`
39
+ : `Not sorted. Click to sort ascending.`
40
+ }
41
+ variant="ghost"
42
+ size="sm"
43
+ className="-ml-3 h-8 data-[state=open]:bg-accent"
44
+ >
45
+ <span>{title}</span>
46
+ {column.getIsSorted() === 'desc' ? (
47
+ <Icons.arrowDown className="ml-2 h-4 w-4" aria-hidden="true" />
48
+ ) : column.getIsSorted() === 'asc' ? (
49
+ <Icons.arrowUp className="ml-2 h-4 w-4" aria-hidden="true" />
50
+ ) : (
51
+ <Icons.arrowUpDown className="ml-2 h-4 w-4" aria-hidden="true" />
52
+ )}
53
+ </Button>
54
+ </DropdownMenuTrigger>
55
+ <DropdownMenuContent align="start">
56
+ <DropdownMenuItem
57
+ aria-label="Sort ascending"
58
+ onClick={() => column.toggleSorting(false)}
59
+ >
60
+ <Icons.arrowUp
61
+ className="mr-2 h-3.5 w-3.5 text-muted-foreground/70"
62
+ aria-hidden="true"
63
+ />
64
+ Asc
65
+ </DropdownMenuItem>
66
+ <DropdownMenuItem
67
+ aria-label="Sort descending"
68
+ onClick={() => column.toggleSorting(true)}
69
+ >
70
+ <Icons.arrowDown
71
+ className="mr-2 h-3.5 w-3.5 text-muted-foreground/70"
72
+ aria-hidden="true"
73
+ />
74
+ Desc
75
+ </DropdownMenuItem>
76
+ <DropdownMenuSeparator />
77
+ <DropdownMenuItem
78
+ aria-label="Hide column"
79
+ onClick={() => column.toggleVisibility(false)}
80
+ >
81
+ <Icons.eyeOff
82
+ className="mr-2 h-3.5 w-3.5 text-muted-foreground/70"
83
+ aria-hidden="true"
84
+ />
85
+ Hide
86
+ </DropdownMenuItem>
87
+ </DropdownMenuContent>
88
+ </DropdownMenu>
89
+ </div>
90
+ )
91
+ }
@@ -0,0 +1,149 @@
1
+ import { type Column } from '@tanstack/react-table'
2
+ import { ComponentType } from 'react'
3
+
4
+ import { Icons } from '@/components/icons'
5
+ import { Badge } from '@/components/ui/badge'
6
+ import { Button } from '@/components/ui/button'
7
+ import {
8
+ Command,
9
+ CommandEmpty,
10
+ CommandGroup,
11
+ CommandInput,
12
+ CommandItem,
13
+ CommandList,
14
+ CommandSeparator
15
+ } from '@/components/ui/command'
16
+ import {
17
+ Popover,
18
+ PopoverContent,
19
+ PopoverTrigger
20
+ } from '@/components/ui/popover'
21
+ import { Separator } from '@/components/ui/separator'
22
+ import { cn } from '@/lib/cn'
23
+
24
+ export type FilterOption = {
25
+ label: string
26
+ value: string
27
+ icon?: ComponentType<{ className?: string }>
28
+ }
29
+
30
+ interface DataTableFacetedFilter<TData, TValue> {
31
+ column?: Column<TData, TValue>
32
+ title?: string
33
+ options: FilterOption[]
34
+ }
35
+
36
+ export function DataTableFacetedFilter<TData, TValue>({
37
+ column,
38
+ title,
39
+ options
40
+ }: DataTableFacetedFilter<TData, TValue>) {
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
+ <Icons.plusCircle className="mr-2 h-4 w-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
+ 'mr-2 flex h-4 w-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
+ <Icons.check
116
+ className={cn('h-4 w-4')}
117
+ aria-hidden="true"
118
+ />
119
+ </div>
120
+ {option.icon && (
121
+ <option.icon
122
+ className="mr-2 h-4 w-4 text-muted-foreground"
123
+ aria-hidden="true"
124
+ />
125
+ )}
126
+ <span>{option.label}</span>
127
+ </CommandItem>
128
+ )
129
+ })}
130
+ </CommandGroup>
131
+ {selectedValues.size > 0 && (
132
+ <>
133
+ <CommandSeparator />
134
+ <CommandGroup>
135
+ <CommandItem
136
+ onSelect={() => column?.setFilterValue(undefined)}
137
+ className="justify-center text-center"
138
+ >
139
+ Clear filters
140
+ </CommandItem>
141
+ </CommandGroup>
142
+ </>
143
+ )}
144
+ </CommandList>
145
+ </Command>
146
+ </PopoverContent>
147
+ </Popover>
148
+ )
149
+ }
@@ -0,0 +1,77 @@
1
+ import { Skeleton } from '@/components/ui/skeleton'
2
+ import {
3
+ Table,
4
+ TableBody,
5
+ TableCell,
6
+ TableHead,
7
+ TableHeader,
8
+ TableRow
9
+ } from '@/components/ui/table'
10
+
11
+ interface DataTableLoadingProps {
12
+ columnCount: number
13
+ rowCount?: number
14
+ }
15
+
16
+ export function DataTableLoading({
17
+ columnCount,
18
+ rowCount = 10
19
+ }: DataTableLoadingProps) {
20
+ return (
21
+ <div className="w-full space-y-3 overflow-auto">
22
+ <div className="flex w-full items-center justify-between space-x-2 overflow-auto p-1">
23
+ <div className="flex flex-1 items-center space-x-2">
24
+ <Skeleton className="h-7 w-[150px] lg:w-[250px]" />
25
+ <Skeleton className="h-7 w-[70px] border-dashed" />
26
+ </div>
27
+ <Skeleton className="ml-auto hidden h-7 w-[70px] lg:flex" />
28
+ </div>
29
+ <div className="rounded-md border">
30
+ <Table>
31
+ <TableHeader>
32
+ {Array.from({ length: 1 }).map((_, i) => (
33
+ <TableRow key={i} className="hover:bg-transparent">
34
+ {Array.from({ length: columnCount }).map((_, i) => (
35
+ <TableHead key={i}>
36
+ <Skeleton className="h-6 w-full" />
37
+ </TableHead>
38
+ ))}
39
+ </TableRow>
40
+ ))}
41
+ </TableHeader>
42
+ <TableBody>
43
+ {Array.from({ length: rowCount }).map((_, i) => (
44
+ <TableRow key={i} className="hover:bg-transparent">
45
+ {Array.from({ length: columnCount }).map((_, i) => (
46
+ <TableCell key={i}>
47
+ <Skeleton className="h-6 w-full" />
48
+ </TableCell>
49
+ ))}
50
+ </TableRow>
51
+ ))}
52
+ </TableBody>
53
+ </Table>
54
+ </div>
55
+ <div className="flex w-full flex-col items-center justify-between gap-4 overflow-auto px-2 py-1 sm:flex-row sm:gap-8">
56
+ <div className="flex-1">
57
+ <Skeleton className="h-8 w-40" />
58
+ </div>
59
+ <div className="flex flex-col items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
60
+ <div className="flex items-center space-x-2">
61
+ <Skeleton className="h-8 w-24" />
62
+ <Skeleton className="h-8 w-[70px]" />
63
+ </div>
64
+ <div className="flex w-[100px] items-center justify-center text-sm font-medium">
65
+ <Skeleton className="h-8 w-20" />
66
+ </div>
67
+ <div className="flex items-center space-x-2">
68
+ <Skeleton className="hidden h-8 w-8 lg:block" />
69
+ <Skeleton className="h-8 w-8" />
70
+ <Skeleton className="h-8 w-8" />
71
+ <Skeleton className="hidden h-8 w-8 lg:block" />
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+ )
77
+ }
@@ -0,0 +1,94 @@
1
+ import { type Table } from '@tanstack/react-table'
2
+
3
+ import { Icons } from '@/components/icons'
4
+ import { Button } from '@/components/ui/button'
5
+ import {
6
+ Select,
7
+ SelectContent,
8
+ SelectItem,
9
+ SelectTrigger,
10
+ SelectValue
11
+ } from '@/components/ui/select'
12
+
13
+ interface DataTablePaginationProps<TData> {
14
+ table: Table<TData>
15
+ pageSizeOptions?: number[]
16
+ }
17
+
18
+ export function DataTablePagination<TData>({
19
+ table,
20
+ pageSizeOptions = [10, 20, 30, 50]
21
+ }: DataTablePaginationProps<TData>) {
22
+ return (
23
+ <div className="flex w-full flex-col items-center justify-between gap-4 overflow-auto px-2 py-1 sm:flex-row sm:gap-8">
24
+ <div className="flex-1 whitespace-nowrap text-sm text-muted-foreground">
25
+ {table.getFilteredSelectedRowModel().rows.length} of{' '}
26
+ {table.getFilteredRowModel().rows.length} row(s) selected.
27
+ </div>
28
+ <div className="flex flex-col items-center gap-4 sm:flex-row sm:gap-6 lg:gap-8">
29
+ <div className="flex items-center space-x-2">
30
+ <p className="whitespace-nowrap text-sm font-medium">Rows per page</p>
31
+ <Select
32
+ value={`${table.getState().pagination.pageSize}`}
33
+ onValueChange={value => {
34
+ table.setPageSize(Number(value))
35
+ }}
36
+ >
37
+ <SelectTrigger className="h-8 w-[70px]">
38
+ <SelectValue placeholder={table.getState().pagination.pageSize} />
39
+ </SelectTrigger>
40
+ <SelectContent side="top">
41
+ {pageSizeOptions.map(pageSize => (
42
+ <SelectItem key={pageSize} value={`${pageSize}`}>
43
+ {pageSize}
44
+ </SelectItem>
45
+ ))}
46
+ </SelectContent>
47
+ </Select>
48
+ </div>
49
+ <div className="flex w-[100px] items-center justify-center text-sm font-medium">
50
+ Page {table.getState().pagination.pageIndex + 1} of{' '}
51
+ {table.getPageCount()}
52
+ </div>
53
+ <div className="flex items-center space-x-2">
54
+ <Button
55
+ aria-label="Go to first page"
56
+ variant="outline"
57
+ className="hidden h-8 w-8 p-0 lg:flex"
58
+ onClick={() => table.setPageIndex(0)}
59
+ disabled={!table.getCanPreviousPage()}
60
+ >
61
+ <Icons.chevronsLeft className="h-4 w-4" aria-hidden="true" />
62
+ </Button>
63
+ <Button
64
+ aria-label="Go to previous page"
65
+ variant="outline"
66
+ className="h-8 w-8 p-0"
67
+ onClick={() => table.previousPage()}
68
+ disabled={!table.getCanPreviousPage()}
69
+ >
70
+ <Icons.chevronLeft className="h-4 w-4" aria-hidden="true" />
71
+ </Button>
72
+ <Button
73
+ aria-label="Go to next page"
74
+ variant="outline"
75
+ className="h-8 w-8 p-0"
76
+ onClick={() => table.nextPage()}
77
+ disabled={!table.getCanNextPage()}
78
+ >
79
+ <Icons.chevronRight className="h-4 w-4" aria-hidden="true" />
80
+ </Button>
81
+ <Button
82
+ aria-label="Go to last page"
83
+ variant="outline"
84
+ className="hidden h-8 w-8 p-0 lg:flex"
85
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
86
+ disabled={!table.getCanNextPage()}
87
+ >
88
+ <Icons.chevronsRight className="h-4 w-4" aria-hidden="true" />
89
+ </Button>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ )
94
+ }
@@ -0,0 +1,78 @@
1
+ 'use client'
2
+
3
+ import type { Table } from '@tanstack/react-table'
4
+
5
+ import { DataTableFacetedFilter } from '@/components/data-table/data-table-faceted-filter'
6
+ import { DataTableViewOptions } from '@/components/data-table/data-table-view-options'
7
+ import { Icons } from '@/components/icons'
8
+ import { Button } from '@/components/ui/button'
9
+ import { Input } from '@/components/ui/input'
10
+ import {
11
+ DataTableFilterableColumn,
12
+ DataTableSearchableColumn
13
+ } from './data-table-types'
14
+
15
+ interface DataTableToolbarProps<TData> {
16
+ table: Table<TData>
17
+ filterableColumns?: DataTableFilterableColumn<TData>[]
18
+ searchableColumns?: DataTableSearchableColumn<TData>[]
19
+ }
20
+
21
+ export function DataTableToolbar<TData>({
22
+ table,
23
+ filterableColumns = [],
24
+ searchableColumns = []
25
+ }: DataTableToolbarProps<TData>) {
26
+ const isFiltered = table.getState().columnFilters.length > 0
27
+
28
+ return (
29
+ <div className="flex items-center justify-between p-1">
30
+ <div className="flex flex-1 items-center space-x-2">
31
+ {searchableColumns.length > 0 &&
32
+ searchableColumns.map(
33
+ column =>
34
+ table.getColumn(column.id ? String(column.id) : '') && (
35
+ <Input
36
+ key={String(column.id)}
37
+ placeholder={`Filter ${column.title}...`}
38
+ value={
39
+ (table
40
+ .getColumn(String(column.id))
41
+ ?.getFilterValue() as string) ?? ''
42
+ }
43
+ onChange={event =>
44
+ table
45
+ .getColumn(String(column.id))
46
+ ?.setFilterValue(event.target.value)
47
+ }
48
+ className="h-8 w-[150px] lg:w-[250px]"
49
+ />
50
+ )
51
+ )}
52
+ {filterableColumns.length > 0 &&
53
+ filterableColumns.map(
54
+ column =>
55
+ table.getColumn(column.id ? String(column.id) : '') && (
56
+ <DataTableFacetedFilter
57
+ key={String(column.id)}
58
+ column={table.getColumn(column.id ? String(column.id) : '')}
59
+ title={column.title}
60
+ options={column.options}
61
+ />
62
+ )
63
+ )}
64
+ {isFiltered && (
65
+ <Button
66
+ variant="ghost"
67
+ onClick={() => table.resetColumnFilters()}
68
+ className="h-8 px-2 lg:px-3"
69
+ >
70
+ Reset
71
+ <Icons.close className="ml-2 h-4 w-4" />
72
+ </Button>
73
+ )}
74
+ </div>
75
+ <DataTableViewOptions table={table} />
76
+ </div>
77
+ )
78
+ }
@@ -0,0 +1,17 @@
1
+ import { ComponentType } from 'react'
2
+
3
+ export type Option = {
4
+ label: string
5
+ value: string
6
+ icon?: ComponentType<{ className?: string }>
7
+ }
8
+
9
+ export interface DataTableSearchableColumn<TData> {
10
+ id: keyof TData
11
+ title: string
12
+ }
13
+
14
+ export interface DataTableFilterableColumn<TData>
15
+ extends DataTableSearchableColumn<TData> {
16
+ options: Option[]
17
+ }
@@ -0,0 +1,59 @@
1
+ 'use client'
2
+
3
+ import { DropdownMenuTrigger } from '@radix-ui/react-dropdown-menu'
4
+ import { type Table } from '@tanstack/react-table'
5
+
6
+ import { Icons } from '@/components/icons'
7
+ import { Button } from '@/components/ui/button'
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuCheckboxItem,
11
+ DropdownMenuContent,
12
+ DropdownMenuLabel,
13
+ DropdownMenuSeparator
14
+ } from '@/components/ui/dropdown-menu'
15
+
16
+ interface DataTableViewOptionsProps<TData> {
17
+ table: Table<TData>
18
+ }
19
+
20
+ export function DataTableViewOptions<TData>({
21
+ table
22
+ }: DataTableViewOptionsProps<TData>) {
23
+ return (
24
+ <DropdownMenu>
25
+ <DropdownMenuTrigger asChild>
26
+ <Button
27
+ variant="outline"
28
+ size="sm"
29
+ className="ml-auto hidden h-8 lg:flex"
30
+ >
31
+ <Icons.mixer className="mr-2 h-4 w-4" />
32
+ View
33
+ </Button>
34
+ </DropdownMenuTrigger>
35
+ <DropdownMenuContent align="end" className="w-[150px]">
36
+ <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
37
+ <DropdownMenuSeparator />
38
+ {table
39
+ .getAllColumns()
40
+ .filter(
41
+ column =>
42
+ typeof column.accessorFn !== 'undefined' && column.getCanHide()
43
+ )
44
+ .map(column => {
45
+ return (
46
+ <DropdownMenuCheckboxItem
47
+ key={column.id}
48
+ className="capitalize"
49
+ checked={column.getIsVisible()}
50
+ onCheckedChange={value => column.toggleVisibility(!!value)}
51
+ >
52
+ {column.id}
53
+ </DropdownMenuCheckboxItem>
54
+ )
55
+ })}
56
+ </DropdownMenuContent>
57
+ </DropdownMenu>
58
+ )
59
+ }