doo-boilerplate 0.1.16 → 0.2.2

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 (36) hide show
  1. package/dist/index.js +43 -486
  2. package/package.json +1 -1
  3. package/templates/template-vite/_env.example +8 -0
  4. package/templates/template-vite/package.json +5 -2
  5. package/templates/template-vite/src/components/data-table/data-table-column-header.tsx +62 -0
  6. package/templates/template-vite/src/components/data-table/data-table-faceted-filter.tsx +129 -0
  7. package/templates/template-vite/src/components/data-table/data-table-pagination.tsx +80 -0
  8. package/templates/template-vite/src/components/data-table/data-table-toolbar.tsx +66 -0
  9. package/templates/template-vite/src/components/data-table/data-table-view-options.tsx +46 -0
  10. package/templates/template-vite/src/components/data-table/data-table.tsx +63 -0
  11. package/templates/template-vite/src/components/layout/sidebar.tsx +2 -1
  12. package/templates/template-vite/src/components/ui/alert-dialog.tsx +106 -0
  13. package/templates/template-vite/src/components/ui/command.tsx +118 -0
  14. package/templates/template-vite/src/components/ui/popover.tsx +28 -0
  15. package/templates/template-vite/src/components/ui/table.tsx +77 -0
  16. package/templates/template-vite/src/features/dashboard/components/overview-chart.tsx +61 -0
  17. package/templates/template-vite/src/features/dashboard/components/recent-activity.tsx +37 -0
  18. package/templates/template-vite/src/features/dashboard/components/stats-cards.tsx +37 -0
  19. package/templates/template-vite/src/features/users/components/user-delete-confirmation-dialog.tsx +48 -0
  20. package/templates/template-vite/src/features/users/components/user-form-dialog.tsx +143 -0
  21. package/templates/template-vite/src/features/users/components/users-table-columns.tsx +154 -0
  22. package/templates/template-vite/src/features/users/components/users-table.tsx +143 -0
  23. package/templates/template-vite/src/features/users/data/users-mock-data.ts +55 -0
  24. package/templates/template-vite/src/features/users/schemas/user-form-schema.ts +14 -0
  25. package/templates/template-vite/src/features/users/types/user.ts +12 -0
  26. package/templates/template-vite/src/lib/sentry.ts +28 -0
  27. package/templates/template-vite/src/main.tsx +3 -0
  28. package/templates/template-vite/src/routes/_authenticated/dashboard.tsx +12 -6
  29. package/templates/template-vite/src/routes/_authenticated/users.tsx +16 -0
  30. package/templates/template-vite/vite.config.ts +8 -0
  31. package/templates/template-vite/optional/charts/deps.json +0 -7
  32. package/templates/template-vite/optional/dark-mode/deps.json +0 -5
  33. package/templates/template-vite/optional/dnd/deps.json +0 -8
  34. package/templates/template-vite/optional/editor/deps.json +0 -10
  35. package/templates/template-vite/optional/i18n/deps.json +0 -7
  36. package/templates/template-vite/optional/sentry/deps.json +0 -6
@@ -19,6 +19,7 @@
19
19
  "docker:scan": "bash scripts/trivy-scan.sh"
20
20
  },
21
21
  "dependencies": {
22
+ "@sentry/react": "^9.0.0",
22
23
  "react": "^19.2.0",
23
24
  "react-dom": "^19.2.0",
24
25
  "i18next": "^25.2.1",
@@ -56,7 +57,8 @@
56
57
  "@radix-ui/react-switch": "^1.1.3",
57
58
  "@radix-ui/react-tabs": "^1.1.3",
58
59
  "@radix-ui/react-tooltip": "^1.1.8",
59
- "cmdk": "^1.1.1"
60
+ "cmdk": "^1.1.1",
61
+ "recharts": "^2.15.0"
60
62
  },
61
63
  "devDependencies": {
62
64
  "@types/react": "^19",
@@ -83,7 +85,8 @@
83
85
  "husky": "^9.1.7",
84
86
  "lint-staged": "^15.2.11",
85
87
  "knip": "^5.64.2",
86
- "swagger-typescript-api": "^13.2.7"
88
+ "swagger-typescript-api": "^13.2.7",
89
+ "@sentry/vite-plugin": "^3.0.0"
87
90
  },
88
91
  "lint-staged": {
89
92
  "*.{js,ts,tsx,css}": ["eslint --fix", "prettier --write"]
@@ -0,0 +1,62 @@
1
+ import * as React from 'react'
2
+ import { type Column } from '@tanstack/react-table'
3
+ import { ArrowDown, ArrowUp, ChevronsUpDown, EyeOff } from 'lucide-react'
4
+
5
+ import { cn } from '@/lib/utils'
6
+ import { Button } from '@/components/ui/button'
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuSeparator,
12
+ DropdownMenuTrigger,
13
+ } from '@/components/ui/dropdown-menu'
14
+
15
+ interface DataTableColumnHeaderProps<TData, TValue> 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 variant='ghost' size='sm' className='-ml-3 h-8 data-[state=open]:bg-accent'>
34
+ <span>{title}</span>
35
+ {column.getIsSorted() === 'desc' ? (
36
+ <ArrowDown className='ml-2 h-4 w-4' />
37
+ ) : column.getIsSorted() === 'asc' ? (
38
+ <ArrowUp className='ml-2 h-4 w-4' />
39
+ ) : (
40
+ <ChevronsUpDown className='ml-2 h-4 w-4' />
41
+ )}
42
+ </Button>
43
+ </DropdownMenuTrigger>
44
+ <DropdownMenuContent align='start'>
45
+ <DropdownMenuItem onClick={() => column.toggleSorting(false)}>
46
+ <ArrowUp className='mr-2 h-3.5 w-3.5 text-muted-foreground/70' />
47
+ Asc
48
+ </DropdownMenuItem>
49
+ <DropdownMenuItem onClick={() => column.toggleSorting(true)}>
50
+ <ArrowDown className='mr-2 h-3.5 w-3.5 text-muted-foreground/70' />
51
+ Desc
52
+ </DropdownMenuItem>
53
+ <DropdownMenuSeparator />
54
+ <DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
55
+ <EyeOff className='mr-2 h-3.5 w-3.5 text-muted-foreground/70' />
56
+ Hide
57
+ </DropdownMenuItem>
58
+ </DropdownMenuContent>
59
+ </DropdownMenu>
60
+ </div>
61
+ )
62
+ }
@@ -0,0 +1,129 @@
1
+ import * as React from 'react'
2
+ import { type Column } from '@tanstack/react-table'
3
+ import { Check, PlusCircle } from 'lucide-react'
4
+
5
+ import { cn } from '@/lib/utils'
6
+ import { Badge } from '@/components/ui/badge'
7
+ import { Button } from '@/components/ui/button'
8
+ import {
9
+ Command,
10
+ CommandEmpty,
11
+ CommandGroup,
12
+ CommandInput,
13
+ CommandItem,
14
+ CommandList,
15
+ CommandSeparator,
16
+ } from '@/components/ui/command'
17
+ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
18
+ import { Separator } from '@/components/ui/separator'
19
+
20
+ interface FacetedFilterOption {
21
+ label: string
22
+ value: string
23
+ icon?: React.ComponentType<{ className?: string }>
24
+ }
25
+
26
+ interface DataTableFacetedFilterProps<TData, TValue> {
27
+ column?: Column<TData, TValue>
28
+ title?: string
29
+ options: FacetedFilterOption[]
30
+ }
31
+
32
+ export function DataTableFacetedFilter<TData, TValue>({
33
+ column,
34
+ title,
35
+ options,
36
+ }: DataTableFacetedFilterProps<TData, TValue>) {
37
+ const facets = column?.getFacetedUniqueValues()
38
+ const selectedValues = new Set(column?.getFilterValue() as string[])
39
+
40
+ return (
41
+ <Popover>
42
+ <PopoverTrigger asChild>
43
+ <Button variant='outline' size='sm' className='h-8 border-dashed'>
44
+ <PlusCircle className='mr-2 h-4 w-4' />
45
+ {title}
46
+ {selectedValues.size > 0 && (
47
+ <>
48
+ <Separator orientation='vertical' className='mx-2 h-4' />
49
+ <Badge variant='secondary' className='rounded-sm px-1 font-normal lg:hidden'>
50
+ {selectedValues.size}
51
+ </Badge>
52
+ <div className='hidden space-x-1 lg:flex'>
53
+ {selectedValues.size > 2 ? (
54
+ <Badge variant='secondary' className='rounded-sm px-1 font-normal'>
55
+ {selectedValues.size} selected
56
+ </Badge>
57
+ ) : (
58
+ options
59
+ .filter((option) => selectedValues.has(option.value))
60
+ .map((option) => (
61
+ <Badge key={option.value} variant='secondary' className='rounded-sm px-1 font-normal'>
62
+ {option.label}
63
+ </Badge>
64
+ ))
65
+ )}
66
+ </div>
67
+ </>
68
+ )}
69
+ </Button>
70
+ </PopoverTrigger>
71
+ <PopoverContent className='w-[200px] p-0' align='start'>
72
+ <Command>
73
+ <CommandInput placeholder={title} />
74
+ <CommandList>
75
+ <CommandEmpty>No results found.</CommandEmpty>
76
+ <CommandGroup>
77
+ {options.map((option) => {
78
+ const isSelected = selectedValues.has(option.value)
79
+ return (
80
+ <CommandItem
81
+ key={option.value}
82
+ onSelect={() => {
83
+ if (isSelected) {
84
+ selectedValues.delete(option.value)
85
+ } else {
86
+ selectedValues.add(option.value)
87
+ }
88
+ const filterValues = Array.from(selectedValues)
89
+ column?.setFilterValue(filterValues.length ? filterValues : undefined)
90
+ }}
91
+ >
92
+ <div
93
+ className={cn(
94
+ 'mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary',
95
+ isSelected ? 'bg-primary text-primary-foreground' : 'opacity-50 [&_svg]:invisible'
96
+ )}
97
+ >
98
+ <Check className='h-4 w-4' />
99
+ </div>
100
+ {option.icon && <option.icon className='mr-2 h-4 w-4 text-muted-foreground' />}
101
+ <span>{option.label}</span>
102
+ {facets?.get(option.value) && (
103
+ <span className='ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs'>
104
+ {facets.get(option.value)}
105
+ </span>
106
+ )}
107
+ </CommandItem>
108
+ )
109
+ })}
110
+ </CommandGroup>
111
+ {selectedValues.size > 0 && (
112
+ <>
113
+ <CommandSeparator />
114
+ <CommandGroup>
115
+ <CommandItem
116
+ onSelect={() => column?.setFilterValue(undefined)}
117
+ className='justify-center text-center'
118
+ >
119
+ Clear filters
120
+ </CommandItem>
121
+ </CommandGroup>
122
+ </>
123
+ )}
124
+ </CommandList>
125
+ </Command>
126
+ </PopoverContent>
127
+ </Popover>
128
+ )
129
+ }
@@ -0,0 +1,80 @@
1
+ import { type Table } from '@tanstack/react-table'
2
+ import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'
3
+
4
+ import { Button } from '@/components/ui/button'
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
6
+
7
+ interface DataTablePaginationProps<TData> {
8
+ table: Table<TData>
9
+ }
10
+
11
+ export function DataTablePagination<TData>({ table }: DataTablePaginationProps<TData>) {
12
+ return (
13
+ <div className='flex items-center justify-between px-2'>
14
+ <div className='flex-1 text-sm text-muted-foreground'>
15
+ {table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) selected.
16
+ </div>
17
+ <div className='flex items-center space-x-6 lg:space-x-8'>
18
+ <div className='flex items-center space-x-2'>
19
+ <p className='text-sm font-medium'>Rows per page</p>
20
+ <Select
21
+ value={`${table.getState().pagination.pageSize}`}
22
+ onValueChange={(value) => table.setPageSize(Number(value))}
23
+ >
24
+ <SelectTrigger className='h-8 w-[70px]'>
25
+ <SelectValue placeholder={table.getState().pagination.pageSize} />
26
+ </SelectTrigger>
27
+ <SelectContent side='top'>
28
+ {[10, 20, 30, 40, 50].map((pageSize) => (
29
+ <SelectItem key={pageSize} value={`${pageSize}`}>
30
+ {pageSize}
31
+ </SelectItem>
32
+ ))}
33
+ </SelectContent>
34
+ </Select>
35
+ </div>
36
+ <div className='flex w-[100px] items-center justify-center text-sm font-medium'>
37
+ Page {table.getState().pagination.pageIndex + 1} of {table.getPageCount()}
38
+ </div>
39
+ <div className='flex items-center space-x-2'>
40
+ <Button
41
+ variant='outline'
42
+ className='hidden h-8 w-8 p-0 lg:flex'
43
+ onClick={() => table.setPageIndex(0)}
44
+ disabled={!table.getCanPreviousPage()}
45
+ >
46
+ <span className='sr-only'>Go to first page</span>
47
+ <ChevronsLeft className='h-4 w-4' />
48
+ </Button>
49
+ <Button
50
+ variant='outline'
51
+ className='h-8 w-8 p-0'
52
+ onClick={() => table.previousPage()}
53
+ disabled={!table.getCanPreviousPage()}
54
+ >
55
+ <span className='sr-only'>Go to previous page</span>
56
+ <ChevronLeft className='h-4 w-4' />
57
+ </Button>
58
+ <Button
59
+ variant='outline'
60
+ className='h-8 w-8 p-0'
61
+ onClick={() => table.nextPage()}
62
+ disabled={!table.getCanNextPage()}
63
+ >
64
+ <span className='sr-only'>Go to next page</span>
65
+ <ChevronRight className='h-4 w-4' />
66
+ </Button>
67
+ <Button
68
+ variant='outline'
69
+ className='hidden h-8 w-8 p-0 lg:flex'
70
+ onClick={() => table.setPageIndex(table.getPageCount() - 1)}
71
+ disabled={!table.getCanNextPage()}
72
+ >
73
+ <span className='sr-only'>Go to last page</span>
74
+ <ChevronsRight className='h-4 w-4' />
75
+ </Button>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ )
80
+ }
@@ -0,0 +1,66 @@
1
+ import * as React from 'react'
2
+ import { type Table } from '@tanstack/react-table'
3
+ import { X } from 'lucide-react'
4
+
5
+ import { Button } from '@/components/ui/button'
6
+ import { Input } from '@/components/ui/input'
7
+ import { DataTableViewOptions } from './data-table-view-options'
8
+ import { DataTableFacetedFilter } from './data-table-faceted-filter'
9
+
10
+ interface FilterOption {
11
+ label: string
12
+ value: string
13
+ icon?: React.ComponentType<{ className?: string }>
14
+ }
15
+
16
+ interface ColumnFilter {
17
+ columnId: string
18
+ title: string
19
+ options: FilterOption[]
20
+ }
21
+
22
+ interface DataTableToolbarProps<TData> {
23
+ table: Table<TData>
24
+ searchColumn?: string
25
+ searchPlaceholder?: string
26
+ filters?: ColumnFilter[]
27
+ }
28
+
29
+ export function DataTableToolbar<TData>({
30
+ table,
31
+ searchColumn = 'name',
32
+ searchPlaceholder = 'Search...',
33
+ filters,
34
+ }: DataTableToolbarProps<TData>) {
35
+ const isFiltered = table.getState().columnFilters.length > 0
36
+
37
+ return (
38
+ <div className='flex items-center justify-between'>
39
+ <div className='flex flex-1 items-center space-x-2'>
40
+ <Input
41
+ placeholder={searchPlaceholder}
42
+ value={(table.getColumn(searchColumn)?.getFilterValue() as string) ?? ''}
43
+ onChange={(event) => table.getColumn(searchColumn)?.setFilterValue(event.target.value)}
44
+ className='h-8 w-[150px] lg:w-[250px]'
45
+ />
46
+ {filters?.map(
47
+ (filter) =>
48
+ table.getColumn(filter.columnId) && (
49
+ <DataTableFacetedFilter
50
+ key={filter.columnId}
51
+ column={table.getColumn(filter.columnId)}
52
+ title={filter.title}
53
+ options={filter.options}
54
+ />
55
+ )
56
+ )}
57
+ {isFiltered && (
58
+ <Button variant='ghost' onClick={() => table.resetColumnFilters()} className='h-8 px-2 lg:px-3'>
59
+ Reset <X className='ml-2 h-4 w-4' />
60
+ </Button>
61
+ )}
62
+ </div>
63
+ <DataTableViewOptions table={table} />
64
+ </div>
65
+ )
66
+ }
@@ -0,0 +1,46 @@
1
+ import { type Table } from '@tanstack/react-table'
2
+ import { Settings2 } from 'lucide-react'
3
+
4
+ import { Button } from '@/components/ui/button'
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuCheckboxItem,
8
+ DropdownMenuContent,
9
+ DropdownMenuLabel,
10
+ DropdownMenuSeparator,
11
+ DropdownMenuTrigger,
12
+ } from '@/components/ui/dropdown-menu'
13
+
14
+ interface DataTableViewOptionsProps<TData> {
15
+ table: Table<TData>
16
+ }
17
+
18
+ export function DataTableViewOptions<TData>({ table }: DataTableViewOptionsProps<TData>) {
19
+ return (
20
+ <DropdownMenu>
21
+ <DropdownMenuTrigger asChild>
22
+ <Button variant='outline' size='sm' className='ml-auto hidden h-8 lg:flex'>
23
+ <Settings2 className='mr-2 h-4 w-4' />
24
+ View
25
+ </Button>
26
+ </DropdownMenuTrigger>
27
+ <DropdownMenuContent align='end' className='w-[150px]'>
28
+ <DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
29
+ <DropdownMenuSeparator />
30
+ {table
31
+ .getAllColumns()
32
+ .filter((column) => typeof column.accessorFn !== 'undefined' && column.getCanHide())
33
+ .map((column) => (
34
+ <DropdownMenuCheckboxItem
35
+ key={column.id}
36
+ className='capitalize'
37
+ checked={column.getIsVisible()}
38
+ onCheckedChange={(value) => column.toggleVisibility(!!value)}
39
+ >
40
+ {column.id}
41
+ </DropdownMenuCheckboxItem>
42
+ ))}
43
+ </DropdownMenuContent>
44
+ </DropdownMenu>
45
+ )
46
+ }
@@ -0,0 +1,63 @@
1
+ import { flexRender, type ColumnDef, type Table as TanstackTable } from '@tanstack/react-table'
2
+
3
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
4
+ import { Skeleton } from '@/components/ui/skeleton'
5
+ import { DataTablePagination } from './data-table-pagination'
6
+
7
+ interface DataTableProps<TData> {
8
+ table: TanstackTable<TData>
9
+ columns: ColumnDef<TData>[]
10
+ isLoading?: boolean
11
+ }
12
+
13
+ export function DataTable<TData>({ table, columns, isLoading }: DataTableProps<TData>) {
14
+ return (
15
+ <div className='space-y-4'>
16
+ <div className='rounded-md border'>
17
+ <Table>
18
+ <TableHeader>
19
+ {table.getHeaderGroups().map((headerGroup) => (
20
+ <TableRow key={headerGroup.id}>
21
+ {headerGroup.headers.map((header) => (
22
+ <TableHead key={header.id} colSpan={header.colSpan}>
23
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
24
+ </TableHead>
25
+ ))}
26
+ </TableRow>
27
+ ))}
28
+ </TableHeader>
29
+ <TableBody>
30
+ {isLoading ? (
31
+ Array.from({ length: 5 }).map((_, i) => (
32
+ <TableRow key={i}>
33
+ {columns.map((_, j) => (
34
+ <TableCell key={j}>
35
+ <Skeleton className='h-4 w-full' />
36
+ </TableCell>
37
+ ))}
38
+ </TableRow>
39
+ ))
40
+ ) : table.getRowModel().rows.length ? (
41
+ table.getRowModel().rows.map((row) => (
42
+ <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}>
43
+ {row.getVisibleCells().map((cell) => (
44
+ <TableCell key={cell.id}>
45
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
46
+ </TableCell>
47
+ ))}
48
+ </TableRow>
49
+ ))
50
+ ) : (
51
+ <TableRow>
52
+ <TableCell colSpan={columns.length} className='h-24 text-center text-muted-foreground'>
53
+ No results.
54
+ </TableCell>
55
+ </TableRow>
56
+ )}
57
+ </TableBody>
58
+ </Table>
59
+ </div>
60
+ <DataTablePagination table={table} />
61
+ </div>
62
+ )
63
+ }
@@ -1,6 +1,6 @@
1
1
  import { useState } from 'react'
2
2
  import { Link } from '@tanstack/react-router'
3
- import { LayoutDashboard, Settings, User, ChevronLeft, ChevronRight } from 'lucide-react'
3
+ import { LayoutDashboard, Settings, User, Users, ChevronLeft, ChevronRight } from 'lucide-react'
4
4
 
5
5
  import { cn } from '@/lib/utils'
6
6
  import { Button } from '@/components/ui/button'
@@ -9,6 +9,7 @@ import { siteConfig } from '@/config/site'
9
9
 
10
10
  const navItems = [
11
11
  { to: '/dashboard', label: 'Dashboard', icon: LayoutDashboard },
12
+ { to: '/users', label: 'Users', icon: Users },
12
13
  { to: '/profile', label: 'Profile', icon: User },
13
14
  { to: '/settings', label: 'Settings', icon: Settings },
14
15
  ] as const
@@ -0,0 +1,106 @@
1
+ import * as React from 'react'
2
+ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'
3
+
4
+ import { cn } from '@/lib/utils'
5
+ import { buttonVariants } from '@/components/ui/button'
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root
8
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
9
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
10
+
11
+ const AlertDialogOverlay = React.forwardRef<
12
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
13
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
14
+ >(({ className, ...props }, ref) => (
15
+ <AlertDialogPrimitive.Overlay
16
+ className={cn(
17
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
18
+ className
19
+ )}
20
+ {...props}
21
+ ref={ref}
22
+ />
23
+ ))
24
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
25
+
26
+ const AlertDialogContent = React.forwardRef<
27
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
28
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
29
+ >(({ className, ...props }, ref) => (
30
+ <AlertDialogPortal>
31
+ <AlertDialogOverlay />
32
+ <AlertDialogPrimitive.Content
33
+ ref={ref}
34
+ className={cn(
35
+ 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
36
+ className
37
+ )}
38
+ {...props}
39
+ />
40
+ </AlertDialogPortal>
41
+ ))
42
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
43
+
44
+ const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
45
+ <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
46
+ )
47
+ AlertDialogHeader.displayName = 'AlertDialogHeader'
48
+
49
+ const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
50
+ <div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
51
+ )
52
+ AlertDialogFooter.displayName = 'AlertDialogFooter'
53
+
54
+ const AlertDialogTitle = React.forwardRef<
55
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
56
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
57
+ >(({ className, ...props }, ref) => (
58
+ <AlertDialogPrimitive.Title ref={ref} className={cn('text-lg font-semibold', className)} {...props} />
59
+ ))
60
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
61
+
62
+ const AlertDialogDescription = React.forwardRef<
63
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
64
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
65
+ >(({ className, ...props }, ref) => (
66
+ <AlertDialogPrimitive.Description
67
+ ref={ref}
68
+ className={cn('text-sm text-muted-foreground', className)}
69
+ {...props}
70
+ />
71
+ ))
72
+ AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
73
+
74
+ const AlertDialogAction = React.forwardRef<
75
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
76
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
77
+ >(({ className, ...props }, ref) => (
78
+ <AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
79
+ ))
80
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
81
+
82
+ const AlertDialogCancel = React.forwardRef<
83
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
84
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
85
+ >(({ className, ...props }, ref) => (
86
+ <AlertDialogPrimitive.Cancel
87
+ ref={ref}
88
+ className={cn(buttonVariants({ variant: 'outline' }), 'mt-2 sm:mt-0', className)}
89
+ {...props}
90
+ />
91
+ ))
92
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
93
+
94
+ export {
95
+ AlertDialog,
96
+ AlertDialogPortal,
97
+ AlertDialogOverlay,
98
+ AlertDialogTrigger,
99
+ AlertDialogContent,
100
+ AlertDialogHeader,
101
+ AlertDialogFooter,
102
+ AlertDialogTitle,
103
+ AlertDialogDescription,
104
+ AlertDialogAction,
105
+ AlertDialogCancel,
106
+ }