create-ej-app 1.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 (103) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +5 -0
  3. package/cli.js +68 -0
  4. package/package.json +36 -0
  5. package/template/.env.example +12 -0
  6. package/template/.eslintrc.cjs +24 -0
  7. package/template/components.json +16 -0
  8. package/template/next.config.mjs +18 -0
  9. package/template/package.json +68 -0
  10. package/template/postcss.config.cjs +6 -0
  11. package/template/prettier.config.ts +11 -0
  12. package/template/prisma/schema.prisma +136 -0
  13. package/template/public/next.svg +1 -0
  14. package/template/src/app/(app)/dashboard/page.tsx +19 -0
  15. package/template/src/app/(app)/layout.tsx +117 -0
  16. package/template/src/app/(app)/roles/[id]/actions.ts +100 -0
  17. package/template/src/app/(app)/roles/[id]/page.tsx +36 -0
  18. package/template/src/app/(app)/roles/[id]/render.tsx +266 -0
  19. package/template/src/app/(app)/roles/actions.ts +67 -0
  20. package/template/src/app/(app)/roles/page.tsx +51 -0
  21. package/template/src/app/(app)/roles/render.tsx +70 -0
  22. package/template/src/app/(app)/settings/actions.ts +18 -0
  23. package/template/src/app/(app)/settings/loading.tsx +28 -0
  24. package/template/src/app/(app)/settings/page.tsx +28 -0
  25. package/template/src/app/(app)/settings/render.tsx +113 -0
  26. package/template/src/app/(app)/tasks/[id]/actions.ts +107 -0
  27. package/template/src/app/(app)/tasks/[id]/page.tsx +55 -0
  28. package/template/src/app/(app)/tasks/[id]/render.tsx +307 -0
  29. package/template/src/app/(app)/tasks/actions.ts +75 -0
  30. package/template/src/app/(app)/tasks/page.tsx +66 -0
  31. package/template/src/app/(app)/tasks/render.tsx +124 -0
  32. package/template/src/app/(app)/users/[id]/actions.ts +104 -0
  33. package/template/src/app/(app)/users/[id]/page.tsx +39 -0
  34. package/template/src/app/(app)/users/[id]/render.tsx +320 -0
  35. package/template/src/app/(app)/users/actions.ts +67 -0
  36. package/template/src/app/(app)/users/page.tsx +61 -0
  37. package/template/src/app/(app)/users/render.tsx +108 -0
  38. package/template/src/app/(auth)/layout.tsx +20 -0
  39. package/template/src/app/(auth)/login/page.tsx +49 -0
  40. package/template/src/app/(auth)/login/user-auth-form.tsx +152 -0
  41. package/template/src/app/(marketing)/layout.tsx +42 -0
  42. package/template/src/app/(marketing)/page.tsx +96 -0
  43. package/template/src/app/api/auth/[...nextauth]/route.ts +7 -0
  44. package/template/src/app/favicon.ico +0 -0
  45. package/template/src/app/globals.css +76 -0
  46. package/template/src/app/layout.tsx +32 -0
  47. package/template/src/components/app-nav-command.tsx +160 -0
  48. package/template/src/components/dark-mode-toggle.tsx +43 -0
  49. package/template/src/components/data-table/data-table-column-header.tsx +91 -0
  50. package/template/src/components/data-table/data-table-faceted-filter.tsx +149 -0
  51. package/template/src/components/data-table/data-table-loading.tsx +77 -0
  52. package/template/src/components/data-table/data-table-pagination.tsx +94 -0
  53. package/template/src/components/data-table/data-table-toolbar.tsx +78 -0
  54. package/template/src/components/data-table/data-table-types.ts +17 -0
  55. package/template/src/components/data-table/data-table-view-options.tsx +59 -0
  56. package/template/src/components/data-table/data-table.tsx +301 -0
  57. package/template/src/components/data-table/simple-data-table.tsx +109 -0
  58. package/template/src/components/empty-placeholder.tsx +78 -0
  59. package/template/src/components/heading.tsx +17 -0
  60. package/template/src/components/icons.tsx +83 -0
  61. package/template/src/components/main-nav.tsx +64 -0
  62. package/template/src/components/mobile-nav.tsx +50 -0
  63. package/template/src/components/shell.tsx +13 -0
  64. package/template/src/components/side-nav.tsx +51 -0
  65. package/template/src/components/site-footer.tsx +31 -0
  66. package/template/src/components/system-info.tsx +36 -0
  67. package/template/src/components/theme-provider.tsx +13 -0
  68. package/template/src/components/ui/alert-dialog.tsx +145 -0
  69. package/template/src/components/ui/avatar.tsx +50 -0
  70. package/template/src/components/ui/badge.tsx +36 -0
  71. package/template/src/components/ui/button.tsx +56 -0
  72. package/template/src/components/ui/card.tsx +79 -0
  73. package/template/src/components/ui/checkbox.tsx +30 -0
  74. package/template/src/components/ui/command.tsx +155 -0
  75. package/template/src/components/ui/dialog.tsx +123 -0
  76. package/template/src/components/ui/dropdown-menu.tsx +200 -0
  77. package/template/src/components/ui/form.tsx +176 -0
  78. package/template/src/components/ui/input.tsx +25 -0
  79. package/template/src/components/ui/label.tsx +26 -0
  80. package/template/src/components/ui/popover.tsx +31 -0
  81. package/template/src/components/ui/select.tsx +121 -0
  82. package/template/src/components/ui/separator.tsx +31 -0
  83. package/template/src/components/ui/skeleton.tsx +15 -0
  84. package/template/src/components/ui/table.tsx +114 -0
  85. package/template/src/components/ui/textarea.tsx +24 -0
  86. package/template/src/components/ui/toast.tsx +127 -0
  87. package/template/src/components/ui/toaster.tsx +35 -0
  88. package/template/src/components/ui/use-toast.ts +189 -0
  89. package/template/src/components/user-account-nav.tsx +60 -0
  90. package/template/src/components/user-avatar.tsx +25 -0
  91. package/template/src/env.mjs +67 -0
  92. package/template/src/hooks/use-debounce.ts +17 -0
  93. package/template/src/hooks/use-lock-body.tsx +12 -0
  94. package/template/src/lib/auth.ts +183 -0
  95. package/template/src/lib/cn.ts +4 -0
  96. package/template/src/lib/db.ts +20 -0
  97. package/template/src/lib/delay.ts +2 -0
  98. package/template/src/lib/formatDate.ts +11 -0
  99. package/template/src/lib/generateLabel.ts +5 -0
  100. package/template/src/lib/siteConfig.ts +4 -0
  101. package/template/src/types/UnwrapPromise.ts +1 -0
  102. package/template/tailwind.config.ts +79 -0
  103. package/template/tsconfig.json +46 -0
@@ -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
+ }
@@ -0,0 +1,301 @@
1
+ 'use client'
2
+
3
+ import {
4
+ flexRender,
5
+ getCoreRowModel,
6
+ getFacetedRowModel,
7
+ getFacetedUniqueValues,
8
+ getFilteredRowModel,
9
+ getPaginationRowModel,
10
+ getSortedRowModel,
11
+ useReactTable,
12
+ type ColumnDef,
13
+ type ColumnFiltersState,
14
+ type PaginationState,
15
+ type SortingState,
16
+ type VisibilityState
17
+ } from '@tanstack/react-table'
18
+ import { usePathname, useRouter, useSearchParams } from 'next/navigation'
19
+
20
+ import { DataTablePagination } from '@/components/data-table/data-table-pagination'
21
+ import { DataTableToolbar } from '@/components/data-table/data-table-toolbar'
22
+ import {
23
+ Table,
24
+ TableBody,
25
+ TableCell,
26
+ TableHead,
27
+ TableHeader,
28
+ TableRow
29
+ } from '@/components/ui/table'
30
+ import { useDebounce } from '@/hooks/use-debounce'
31
+ import { useCallback, useEffect, useMemo, useState } from 'react'
32
+ import {
33
+ DataTableFilterableColumn,
34
+ DataTableSearchableColumn
35
+ } from './data-table-types'
36
+
37
+ interface DataTableProps<TData, TValue> {
38
+ columns: ColumnDef<TData, TValue>[]
39
+ data: TData[]
40
+ total: number
41
+ filterableColumns?: DataTableFilterableColumn<TData>[]
42
+ searchableColumns?: DataTableSearchableColumn<TData>[]
43
+ }
44
+
45
+ export function DataTable<TData, TValue>({
46
+ columns,
47
+ data,
48
+ total,
49
+ filterableColumns = [],
50
+ searchableColumns = []
51
+ }: DataTableProps<TData, TValue>) {
52
+ const router = useRouter()
53
+ const pathname = usePathname()
54
+ const searchParams = useSearchParams()
55
+
56
+ // Search params
57
+ const page = searchParams?.get('page') ?? '1'
58
+ const limit = searchParams?.get('limit') ?? '10'
59
+ const sort = searchParams?.get('sort')
60
+ const [column, order] = sort?.split('.') ?? []
61
+
62
+ // Create query string
63
+ const createQueryString = useCallback(
64
+ (params: Record<string, string | number | null>) => {
65
+ const newSearchParams = new URLSearchParams(searchParams?.toString())
66
+
67
+ for (const [key, value] of Object.entries(params)) {
68
+ if (value === null) {
69
+ newSearchParams.delete(key)
70
+ } else {
71
+ newSearchParams.set(key, String(value))
72
+ }
73
+ }
74
+
75
+ return newSearchParams.toString()
76
+ },
77
+ [searchParams]
78
+ )
79
+
80
+ // Table states
81
+ const [rowSelection, setRowSelection] = useState({})
82
+ const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
83
+ const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
84
+
85
+ // Handle server-side pagination
86
+ const [{ pageIndex, pageSize }, setPagination] = useState<PaginationState>({
87
+ pageIndex: Number(page) - 1,
88
+ pageSize: Number(limit)
89
+ })
90
+
91
+ const pagination = useMemo(
92
+ () => ({
93
+ pageIndex,
94
+ pageSize
95
+ }),
96
+ [pageIndex, pageSize]
97
+ )
98
+
99
+ useEffect(() => {
100
+ setPagination({
101
+ pageIndex: Number(page) - 1,
102
+ pageSize: Number(limit)
103
+ })
104
+ }, [page, limit])
105
+
106
+ useEffect(() => {
107
+ router.push(
108
+ `${pathname}?${createQueryString({
109
+ page: pageIndex + 1,
110
+ limit: pageSize
111
+ })}`
112
+ )
113
+ // eslint-disable-next-line react-hooks/exhaustive-deps
114
+ }, [pageIndex, pageSize])
115
+
116
+ // Handle server-side sorting
117
+ const [sorting, setSorting] = useState<SortingState>([
118
+ {
119
+ id: column ?? '',
120
+ desc: order === 'desc'
121
+ }
122
+ ])
123
+
124
+ useEffect(() => {
125
+ router.push(
126
+ `${pathname}?${createQueryString({
127
+ page,
128
+ sort: sorting[0]?.id
129
+ ? `${sorting[0]?.id}.${sorting[0]?.desc ? 'desc' : 'asc'}`
130
+ : null
131
+ })}`
132
+ )
133
+ // eslint-disable-next-line react-hooks/exhaustive-deps
134
+ }, [sorting])
135
+
136
+ // Handle server-side filtering
137
+ const debouncedSearchableColumnFilters = JSON.parse(
138
+ useDebounce(
139
+ JSON.stringify(
140
+ columnFilters.filter(filter => {
141
+ return searchableColumns.find(column => column.id === filter.id)
142
+ })
143
+ ),
144
+ 500
145
+ )
146
+ ) as ColumnFiltersState
147
+
148
+ const filterableColumnFilters = columnFilters.filter(filter => {
149
+ return filterableColumns.find(column => column.id === filter.id)
150
+ })
151
+
152
+ useEffect(() => {
153
+ for (const column of debouncedSearchableColumnFilters) {
154
+ if (typeof column.value === 'string') {
155
+ router.push(
156
+ `${pathname}?${createQueryString({
157
+ page: 1,
158
+ [column.id]: typeof column.value === 'string' ? column.value : null
159
+ })}`
160
+ )
161
+ }
162
+ }
163
+
164
+ for (const key of searchParams.keys()) {
165
+ if (
166
+ searchableColumns.find(column => column.id === key) &&
167
+ !debouncedSearchableColumnFilters.find(column => column.id === key)
168
+ ) {
169
+ router.push(
170
+ `${pathname}?${createQueryString({
171
+ page: 1,
172
+ [key]: null
173
+ })}`
174
+ )
175
+ }
176
+ }
177
+ // eslint-disable-next-line react-hooks/exhaustive-deps
178
+ }, [JSON.stringify(debouncedSearchableColumnFilters)])
179
+
180
+ useEffect(() => {
181
+ for (const column of filterableColumnFilters) {
182
+ if (typeof column.value === 'object' && Array.isArray(column.value)) {
183
+ router.push(
184
+ `${pathname}?${createQueryString({
185
+ page: 1,
186
+ [column.id]: column.value.join('.')
187
+ })}`
188
+ )
189
+ }
190
+ }
191
+
192
+ for (const key of searchParams.keys()) {
193
+ if (
194
+ filterableColumns.find(column => column.id === key) &&
195
+ !filterableColumnFilters.find(column => column.id === key)
196
+ ) {
197
+ router.push(
198
+ `${pathname}?${createQueryString({
199
+ page: 1,
200
+ [key]: null
201
+ })}`
202
+ )
203
+ }
204
+ }
205
+ // eslint-disable-next-line react-hooks/exhaustive-deps
206
+ }, [JSON.stringify(filterableColumnFilters)])
207
+
208
+ const pageCount = useMemo(
209
+ () => Math.ceil(total / pageSize),
210
+ [total, pageSize]
211
+ )
212
+
213
+ const table = useReactTable({
214
+ data,
215
+ columns,
216
+ pageCount,
217
+ state: {
218
+ pagination,
219
+ sorting,
220
+ columnVisibility,
221
+ rowSelection,
222
+ columnFilters
223
+ },
224
+ enableRowSelection: true,
225
+ onRowSelectionChange: setRowSelection,
226
+ onPaginationChange: setPagination,
227
+ onSortingChange: setSorting,
228
+ onColumnFiltersChange: setColumnFilters,
229
+ onColumnVisibilityChange: setColumnVisibility,
230
+ getCoreRowModel: getCoreRowModel(),
231
+ getFilteredRowModel: getFilteredRowModel(),
232
+ getPaginationRowModel: getPaginationRowModel(),
233
+ getSortedRowModel: getSortedRowModel(),
234
+ getFacetedRowModel: getFacetedRowModel(),
235
+ getFacetedUniqueValues: getFacetedUniqueValues(),
236
+ manualPagination: true,
237
+ manualSorting: true,
238
+ manualFiltering: true
239
+ })
240
+
241
+ return (
242
+ <div className="w-full space-y-4 overflow-auto">
243
+ <DataTableToolbar
244
+ table={table}
245
+ filterableColumns={filterableColumns}
246
+ searchableColumns={searchableColumns}
247
+ />
248
+ <div className="rounded-md border">
249
+ <Table>
250
+ <TableHeader>
251
+ {table.getHeaderGroups().map(headerGroup => (
252
+ <TableRow key={headerGroup.id}>
253
+ {headerGroup.headers.map(header => {
254
+ return (
255
+ <TableHead key={header.id}>
256
+ {header.isPlaceholder
257
+ ? null
258
+ : flexRender(
259
+ header.column.columnDef.header,
260
+ header.getContext()
261
+ )}
262
+ </TableHead>
263
+ )
264
+ })}
265
+ </TableRow>
266
+ ))}
267
+ </TableHeader>
268
+ <TableBody>
269
+ {table.getRowModel().rows?.length ? (
270
+ table.getRowModel().rows.map(row => (
271
+ <TableRow
272
+ key={row.id}
273
+ data-state={row.getIsSelected() && 'selected'}
274
+ >
275
+ {row.getVisibleCells().map(cell => (
276
+ <TableCell key={cell.id}>
277
+ {flexRender(
278
+ cell.column.columnDef.cell,
279
+ cell.getContext()
280
+ )}
281
+ </TableCell>
282
+ ))}
283
+ </TableRow>
284
+ ))
285
+ ) : (
286
+ <TableRow>
287
+ <TableCell
288
+ colSpan={columns.length}
289
+ className="h-24 text-center"
290
+ >
291
+ No results.
292
+ </TableCell>
293
+ </TableRow>
294
+ )}
295
+ </TableBody>
296
+ </Table>
297
+ </div>
298
+ <DataTablePagination table={table} />
299
+ </div>
300
+ )
301
+ }
@@ -0,0 +1,109 @@
1
+ 'use client'
2
+
3
+ import {
4
+ ColumnDef,
5
+ flexRender,
6
+ getCoreRowModel,
7
+ getPaginationRowModel,
8
+ useReactTable
9
+ } from '@tanstack/react-table'
10
+
11
+ import { Button } from '@/components/ui/button'
12
+ import {
13
+ Table,
14
+ TableBody,
15
+ TableCell,
16
+ TableHead,
17
+ TableHeader,
18
+ TableRow
19
+ } from '@/components/ui/table'
20
+
21
+ interface DataTableProps<TData, TValue> {
22
+ columns: ColumnDef<TData, TValue>[]
23
+ data: TData[]
24
+ }
25
+
26
+ export function SimpleDataTable<TData, TValue>({
27
+ columns,
28
+ data
29
+ }: DataTableProps<TData, TValue>) {
30
+ const table = useReactTable({
31
+ data,
32
+ columns,
33
+ getCoreRowModel: getCoreRowModel(),
34
+ getPaginationRowModel: getPaginationRowModel()
35
+ })
36
+
37
+ return (
38
+ <div>
39
+ <div className="rounded-md border">
40
+ <Table>
41
+ <TableHeader>
42
+ {table.getHeaderGroups().map(headerGroup => (
43
+ <TableRow key={headerGroup.id}>
44
+ {headerGroup.headers.map(header => {
45
+ return (
46
+ <TableHead key={header.id}>
47
+ {header.isPlaceholder
48
+ ? null
49
+ : flexRender(
50
+ header.column.columnDef.header,
51
+ header.getContext()
52
+ )}
53
+ </TableHead>
54
+ )
55
+ })}
56
+ </TableRow>
57
+ ))}
58
+ </TableHeader>
59
+ <TableBody>
60
+ {table.getRowModel().rows?.length ? (
61
+ table.getRowModel().rows.map(row => (
62
+ <TableRow
63
+ key={row.id}
64
+ data-state={row.getIsSelected() && 'selected'}
65
+ >
66
+ {row.getVisibleCells().map(cell => (
67
+ <TableCell key={cell.id}>
68
+ {flexRender(
69
+ cell.column.columnDef.cell,
70
+ cell.getContext()
71
+ )}
72
+ </TableCell>
73
+ ))}
74
+ </TableRow>
75
+ ))
76
+ ) : (
77
+ <TableRow>
78
+ <TableCell
79
+ colSpan={columns.length}
80
+ className="h-24 text-center"
81
+ >
82
+ No results.
83
+ </TableCell>
84
+ </TableRow>
85
+ )}
86
+ </TableBody>
87
+ </Table>
88
+ </div>
89
+ <div className="flex items-center justify-end space-x-2 py-4">
90
+ <Button
91
+ variant="outline"
92
+ size="sm"
93
+ onClick={() => table.previousPage()}
94
+ disabled={!table.getCanPreviousPage()}
95
+ >
96
+ Previous
97
+ </Button>
98
+ <Button
99
+ variant="outline"
100
+ size="sm"
101
+ onClick={() => table.nextPage()}
102
+ disabled={!table.getCanNextPage()}
103
+ >
104
+ Next
105
+ </Button>
106
+ </div>
107
+ </div>
108
+ )
109
+ }
@@ -0,0 +1,78 @@
1
+ import * as React from 'react'
2
+
3
+ import { Icons } from '@/components/icons'
4
+ import { cn } from '@/lib/cn'
5
+
6
+ interface EmptyPlaceholderProps extends React.HTMLAttributes<HTMLDivElement> {}
7
+
8
+ export function EmptyPlaceholder({
9
+ className,
10
+ children,
11
+ ...props
12
+ }: EmptyPlaceholderProps) {
13
+ return (
14
+ <div
15
+ className={cn(
16
+ 'flex min-h-[400px] flex-col items-center justify-center rounded-md border border-dashed p-8 text-center animate-in fade-in-50',
17
+ className
18
+ )}
19
+ {...props}
20
+ >
21
+ <div className="mx-auto flex max-w-[420px] flex-col items-center justify-center text-center">
22
+ {children}
23
+ </div>
24
+ </div>
25
+ )
26
+ }
27
+
28
+ interface EmptyPlaceholderIconProps
29
+ extends Partial<React.SVGProps<SVGSVGElement>> {
30
+ name: keyof typeof Icons
31
+ }
32
+
33
+ EmptyPlaceholder.Icon = function EmptyPlaceHolderIcon({
34
+ name,
35
+ className
36
+ }: EmptyPlaceholderIconProps) {
37
+ const Icon = Icons[name]
38
+
39
+ if (!Icon) {
40
+ return null
41
+ }
42
+
43
+ return (
44
+ <div className="flex h-20 w-20 items-center justify-center rounded-full bg-muted">
45
+ <Icon className={cn('h-10 w-10', className)} />
46
+ </div>
47
+ )
48
+ }
49
+
50
+ interface EmptyPlacholderTitleProps
51
+ extends React.HTMLAttributes<HTMLHeadingElement> {}
52
+
53
+ EmptyPlaceholder.Title = function EmptyPlaceholderTitle({
54
+ className,
55
+ ...props
56
+ }: EmptyPlacholderTitleProps) {
57
+ return (
58
+ <h2 className={cn('mt-6 text-xl font-semibold', className)} {...props} />
59
+ )
60
+ }
61
+
62
+ interface EmptyPlacholderDescriptionProps
63
+ extends React.HTMLAttributes<HTMLParagraphElement> {}
64
+
65
+ EmptyPlaceholder.Description = function EmptyPlaceholderDescription({
66
+ className,
67
+ ...props
68
+ }: EmptyPlacholderDescriptionProps) {
69
+ return (
70
+ <p
71
+ className={cn(
72
+ 'mb-8 mt-2 text-center text-sm font-normal leading-6 text-muted-foreground',
73
+ className
74
+ )}
75
+ {...props}
76
+ />
77
+ )
78
+ }
@@ -0,0 +1,17 @@
1
+ import { FC, ReactNode } from 'react'
2
+
3
+ export const Heading: FC<{
4
+ heading: string
5
+ text?: string
6
+ children?: ReactNode
7
+ }> = ({ heading, text, children }) => {
8
+ return (
9
+ <div className="flex items-center justify-between px-2">
10
+ <div className="grid gap-1">
11
+ <h1 className="font-heading text-3xl md:text-4xl">{heading}</h1>
12
+ {text && <p className="text-lg text-muted-foreground">{text}</p>}
13
+ </div>
14
+ {children}
15
+ </div>
16
+ )
17
+ }
@@ -0,0 +1,83 @@
1
+ import {
2
+ ArrowDown,
3
+ ArrowRight,
4
+ ArrowUp,
5
+ ArrowUpDown,
6
+ Biohazard,
7
+ Check,
8
+ ChevronLeft,
9
+ ChevronRight,
10
+ ChevronsLeft,
11
+ ChevronsRight,
12
+ ClipboardList,
13
+ Download,
14
+ Edit,
15
+ EyeOff,
16
+ Github,
17
+ Laptop,
18
+ LayoutDashboard,
19
+ Loader2,
20
+ LogIn,
21
+ LogOut,
22
+ LucideIcon,
23
+ LucideProps,
24
+ Moon,
25
+ Plus,
26
+ PlusCircle,
27
+ Save,
28
+ Settings,
29
+ Settings2,
30
+ Sun,
31
+ Trash,
32
+ User,
33
+ X
34
+ } from 'lucide-react'
35
+
36
+ export type Icon = LucideIcon
37
+
38
+ export const Icons = {
39
+ logo: Biohazard,
40
+ sun: Sun,
41
+ moon: Moon,
42
+ laptop: Laptop,
43
+ chevronLeft: ChevronLeft,
44
+ arrowRight: ArrowRight,
45
+ github: Github,
46
+ google: ({ ...props }: LucideProps) => (
47
+ <svg
48
+ aria-hidden="true"
49
+ focusable="false"
50
+ data-prefix="fab"
51
+ data-icon="google"
52
+ role="img"
53
+ xmlns="http://www.w3.org/2000/svg"
54
+ viewBox="0 0 30 30"
55
+ {...props}
56
+ >
57
+ <path d="M 15.003906 3 C 8.3749062 3 3 8.373 3 15 C 3 21.627 8.3749062 27 15.003906 27 C 25.013906 27 27.269078 17.707 26.330078 13 L 25 13 L 22.732422 13 L 15 13 L 15 17 L 22.738281 17 C 21.848702 20.448251 18.725955 23 15 23 C 10.582 23 7 19.418 7 15 C 7 10.582 10.582 7 15 7 C 17.009 7 18.839141 7.74575 20.244141 8.96875 L 23.085938 6.1289062 C 20.951937 4.1849063 18.116906 3 15.003906 3 z"></path>
58
+ </svg>
59
+ ),
60
+ login: LogIn,
61
+ logout: LogOut,
62
+ user: User,
63
+ spinner: Loader2,
64
+ settings: Settings,
65
+ close: X,
66
+ add: Plus,
67
+ edit: Edit,
68
+ save: Save,
69
+ delete: Trash,
70
+ download: Download,
71
+ dashboard: LayoutDashboard,
72
+ task: ClipboardList,
73
+ arrowUp: ArrowUp,
74
+ arrowDown: ArrowDown,
75
+ arrowUpDown: ArrowUpDown,
76
+ eyeOff: EyeOff,
77
+ plusCircle: PlusCircle,
78
+ check: Check,
79
+ chevronsLeft: ChevronsLeft,
80
+ chevronsRight: ChevronsRight,
81
+ chevronRight: ChevronRight,
82
+ mixer: Settings2
83
+ }