@thinhnguyencth1204/nextcli 0.2.1 → 0.4.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 (98) hide show
  1. package/README.md +6 -2
  2. package/dist/cli.js +778 -101
  3. package/package.json +2 -1
  4. package/templates/next-base/PROJECT_STRUCTURE.md +88 -0
  5. package/templates/next-base/SETUP.md +86 -0
  6. package/templates/next-base/bun.lock +1443 -0
  7. package/templates/next-base/components.json +21 -0
  8. package/templates/next-base/messages/vi/auth.json +42 -0
  9. package/templates/next-base/messages/vi/common.json +34 -0
  10. package/templates/next-base/messages/vi/example.json +10 -0
  11. package/templates/next-base/next-env.d.ts +3 -1
  12. package/templates/next-base/next.config.ts +11 -1
  13. package/templates/next-base/nextcli.json +8 -0
  14. package/templates/next-base/package.json +21 -1
  15. package/templates/next-base/postcss.config.mjs +5 -0
  16. package/templates/next-base/prisma/migrations/20260612000000_init/migration.sql +104 -0
  17. package/templates/next-base/prisma/migrations/migration_lock.toml +3 -0
  18. package/templates/next-base/prisma/schema.prisma +23 -9
  19. package/templates/next-base/public/logo.svg +4 -0
  20. package/templates/next-base/src/app/(auth)/change-password/layout.tsx +21 -0
  21. package/templates/next-base/src/app/(auth)/change-password/page.tsx +14 -0
  22. package/templates/next-base/src/app/(auth)/layout.tsx +9 -0
  23. package/templates/next-base/src/app/(auth)/sign-in/layout.tsx +17 -0
  24. package/templates/next-base/src/app/(auth)/sign-in/page.tsx +6 -3
  25. package/templates/next-base/src/app/(dashboard)/account/page.tsx +9 -5
  26. package/templates/next-base/src/app/(dashboard)/dashboard/page.tsx +17 -0
  27. package/templates/next-base/src/app/(dashboard)/example/page.tsx +5 -2
  28. package/templates/next-base/src/app/(dashboard)/layout.tsx +22 -0
  29. package/templates/next-base/src/app/api/v1/auth/change-password/route.ts +55 -0
  30. package/templates/next-base/src/app/api/v1/auth/login/route.ts +15 -5
  31. package/templates/next-base/src/app/api/v1/auth/me/route.ts +17 -19
  32. package/templates/next-base/src/app/api/v1/users/[id]/route.ts +104 -0
  33. package/templates/next-base/src/app/api/v1/users/route.ts +58 -0
  34. package/templates/next-base/src/app/globals.css +111 -0
  35. package/templates/next-base/src/app/layout.tsx +24 -10
  36. package/templates/next-base/src/app/page.tsx +2 -18
  37. package/templates/next-base/src/components/branding/logo.tsx +27 -0
  38. package/templates/next-base/src/components/layout/private/app-sidebar.tsx +44 -0
  39. package/templates/next-base/src/components/layout/private/dashboard-layout.tsx +54 -0
  40. package/templates/next-base/src/components/layout/private/locale-switcher.tsx +45 -0
  41. package/templates/next-base/src/components/layout/private/nav-sidebar.tsx +55 -0
  42. package/templates/next-base/src/components/layout/private/nav-user.tsx +99 -0
  43. package/templates/next-base/src/components/providers/theme-provider.tsx +11 -0
  44. package/templates/next-base/src/components/ui/alert-dialog.tsx +11 -0
  45. package/templates/next-base/src/components/ui/avatar.tsx +45 -0
  46. package/templates/next-base/src/components/ui/badge.tsx +29 -0
  47. package/templates/next-base/src/components/ui/button.tsx +47 -7
  48. package/templates/next-base/src/components/ui/card.tsx +54 -0
  49. package/templates/next-base/src/components/ui/data-table/data-table-column-header.tsx +23 -0
  50. package/templates/next-base/src/components/ui/data-table/data-table-filter-list.tsx +3 -0
  51. package/templates/next-base/src/components/ui/data-table/data-table-pagination.tsx +35 -0
  52. package/templates/next-base/src/components/ui/data-table/data-table-skeleton.tsx +11 -0
  53. package/templates/next-base/src/components/ui/data-table/data-table-toolbar.tsx +14 -0
  54. package/templates/next-base/src/components/ui/data-table/data-table-view-options.tsx +3 -0
  55. package/templates/next-base/src/components/ui/data-table/data-table.tsx +72 -0
  56. package/templates/next-base/src/components/ui/dialog.tsx +105 -0
  57. package/templates/next-base/src/components/ui/dropdown-menu.tsx +44 -0
  58. package/templates/next-base/src/components/ui/input.tsx +19 -0
  59. package/templates/next-base/src/components/ui/label.tsx +15 -0
  60. package/templates/next-base/src/components/ui/popover.tsx +30 -0
  61. package/templates/next-base/src/components/ui/scroll-area.tsx +47 -0
  62. package/templates/next-base/src/components/ui/select.tsx +76 -0
  63. package/templates/next-base/src/components/ui/separator.tsx +23 -0
  64. package/templates/next-base/src/components/ui/sheet.tsx +117 -0
  65. package/templates/next-base/src/components/ui/sidebar.tsx +215 -0
  66. package/templates/next-base/src/components/ui/skeleton.tsx +10 -0
  67. package/templates/next-base/src/components/ui/sonner.tsx +3 -0
  68. package/templates/next-base/src/components/ui/table.tsx +54 -0
  69. package/templates/next-base/src/components/ui/tabs.tsx +52 -0
  70. package/templates/next-base/src/components/ui/textarea.tsx +17 -0
  71. package/templates/next-base/src/components/ui/tooltip.tsx +26 -0
  72. package/templates/next-base/src/config/branding.ts +14 -0
  73. package/templates/next-base/src/data/sidebar-modules.ts +11 -0
  74. package/templates/next-base/src/example/components/example-table.tsx +25 -40
  75. package/templates/next-base/src/features/auth/components/account-panel.tsx +32 -14
  76. package/templates/next-base/src/features/auth/components/change-password-form.tsx +82 -0
  77. package/templates/next-base/src/features/auth/components/sign-in-form.tsx +53 -35
  78. package/templates/next-base/src/features/auth/validations.ts +7 -1
  79. package/templates/next-base/src/features/users/services.ts +132 -0
  80. package/templates/next-base/src/features/users/validations.ts +21 -0
  81. package/templates/next-base/src/hooks/index.ts +1 -1
  82. package/templates/next-base/src/hooks/table/use-data-table.ts +33 -0
  83. package/templates/next-base/src/hooks/use-mobile.ts +25 -0
  84. package/templates/next-base/src/i18n/config.ts +7 -0
  85. package/templates/next-base/src/i18n/namespaces.ts +5 -0
  86. package/templates/next-base/src/i18n/request.ts +19 -2
  87. package/templates/next-base/src/instrumentation.ts +14 -0
  88. package/templates/next-base/src/lib/auth-client.ts +2 -2
  89. package/templates/next-base/src/lib/auth.ts +2 -2
  90. package/templates/next-base/src/lib/bootstrap.ts +96 -0
  91. package/templates/next-base/src/lib/constants.ts +7 -0
  92. package/templates/next-base/src/lib/prisma.ts +11 -1
  93. package/templates/next-base/src/lib/rbac.ts +62 -0
  94. package/templates/next-base/src/types/data-table.ts +4 -0
  95. package/templates/next-base/src/types/index.ts +2 -0
  96. package/templates/next-base/tsconfig.json +29 -7
  97. package/templates/next-base/middleware.ts +0 -10
  98. package/templates/next-base/src/app/styles.css +0 -12
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ import { LogOut, Monitor, Moon, Sun } from "lucide-react";
4
+ import { useTheme } from "next-themes";
5
+ import { useTranslations } from "next-intl";
6
+ import { authClient } from "@/lib/auth-client";
7
+ import { Avatar, AvatarFallback } from "@/components/ui/avatar";
8
+ import { Button } from "@/components/ui/button";
9
+ import { ScrollArea } from "@/components/ui/scroll-area";
10
+ import {
11
+ Sheet,
12
+ SheetContent,
13
+ SheetFooter,
14
+ SheetHeader,
15
+ SheetTitle,
16
+ SheetTrigger,
17
+ } from "@/components/ui/sheet";
18
+ import { LocaleSwitcher } from "@/components/layout/private/locale-switcher";
19
+
20
+ export function NavUser() {
21
+ const { theme, setTheme } = useTheme();
22
+ const t = useTranslations("common");
23
+
24
+ const handleLogout = async () => {
25
+ await authClient.signOut();
26
+ window.location.href = "/sign-in";
27
+ };
28
+
29
+ return (
30
+ <Sheet>
31
+ <SheetTrigger asChild>
32
+ <Button variant="ghost" className="h-fit w-fit px-2 py-1">
33
+ <div className="grid text-right text-sm">
34
+ <span className="font-semibold">{t("userMenu.anonymousName")}</span>
35
+ <span className="text-xs text-muted-foreground">
36
+ {t("userMenu.anonymousEmail")}
37
+ </span>
38
+ </div>
39
+ <Avatar className="h-9 w-9">
40
+ <AvatarFallback>U</AvatarFallback>
41
+ </Avatar>
42
+ </Button>
43
+ </SheetTrigger>
44
+ <SheetContent side="right" className="w-full max-w-sm p-0">
45
+ <SheetHeader className="border-b p-4">
46
+ <SheetTitle>{t("userMenu.title")}</SheetTitle>
47
+ </SheetHeader>
48
+
49
+ <ScrollArea className="h-[calc(100vh-10rem)] p-4">
50
+ <div className="space-y-4">
51
+ <div className="rounded-md border bg-card p-4">
52
+ <p className="mb-3 text-sm font-medium">
53
+ {t("userMenu.appearance")}
54
+ </p>
55
+ <div className="grid grid-cols-3 gap-2">
56
+ <Button
57
+ variant={theme === "light" ? "default" : "outline"}
58
+ size="sm"
59
+ onClick={() => setTheme("light")}
60
+ >
61
+ <Sun className="h-4 w-4" />
62
+ {t("header.themeLight")}
63
+ </Button>
64
+ <Button
65
+ variant={theme === "dark" ? "default" : "outline"}
66
+ size="sm"
67
+ onClick={() => setTheme("dark")}
68
+ >
69
+ <Moon className="h-4 w-4" />
70
+ {t("header.themeDark")}
71
+ </Button>
72
+ <Button
73
+ variant={theme === "system" ? "default" : "outline"}
74
+ size="sm"
75
+ onClick={() => setTheme("system")}
76
+ >
77
+ <Monitor className="h-4 w-4" />
78
+ {t("header.themeSystem")}
79
+ </Button>
80
+ </div>
81
+ </div>
82
+ <LocaleSwitcher />
83
+ </div>
84
+ </ScrollArea>
85
+
86
+ <SheetFooter className="border-t p-0">
87
+ <Button
88
+ variant="ghost"
89
+ className="w-full justify-center rounded-none py-6 text-destructive hover:bg-destructive/10"
90
+ onClick={handleLogout}
91
+ >
92
+ <LogOut className="h-4 w-4" />
93
+ {t("userMenu.logout")}
94
+ </Button>
95
+ </SheetFooter>
96
+ </SheetContent>
97
+ </Sheet>
98
+ );
99
+ }
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { ThemeProvider as NextThemesProvider } from "next-themes";
5
+
6
+ export function ThemeProvider({
7
+ children,
8
+ ...props
9
+ }: React.ComponentProps<typeof NextThemesProvider>) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
11
+ }
@@ -0,0 +1,11 @@
1
+ "use client";
2
+
3
+ export {
4
+ Dialog as AlertDialog,
5
+ DialogTrigger as AlertDialogTrigger,
6
+ DialogContent as AlertDialogContent,
7
+ DialogHeader as AlertDialogHeader,
8
+ DialogFooter as AlertDialogFooter,
9
+ DialogTitle as AlertDialogTitle,
10
+ DialogDescription as AlertDialogDescription,
11
+ } from "@/components/ui/dialog";
@@ -0,0 +1,45 @@
1
+ import * as React from "react";
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar";
3
+ import { cn } from "@/utils/cn";
4
+
5
+ export function Avatar({
6
+ className,
7
+ ...props
8
+ }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
9
+ return (
10
+ <AvatarPrimitive.Root
11
+ className={cn(
12
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
13
+ className,
14
+ )}
15
+ {...props}
16
+ />
17
+ );
18
+ }
19
+
20
+ export function AvatarImage({
21
+ className,
22
+ ...props
23
+ }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
24
+ return (
25
+ <AvatarPrimitive.Image
26
+ className={cn("aspect-square h-full w-full", className)}
27
+ {...props}
28
+ />
29
+ );
30
+ }
31
+
32
+ export function AvatarFallback({
33
+ className,
34
+ ...props
35
+ }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
36
+ return (
37
+ <AvatarPrimitive.Fallback
38
+ className={cn(
39
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ );
45
+ }
@@ -0,0 +1,29 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+ import { cn } from "@/utils/cn";
4
+
5
+ const badgeVariants = cva(
6
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default: "border-transparent bg-primary text-primary-foreground",
11
+ secondary: "border-transparent bg-secondary text-secondary-foreground",
12
+ outline: "text-foreground",
13
+ },
14
+ },
15
+ defaultVariants: {
16
+ variant: "default",
17
+ },
18
+ },
19
+ );
20
+
21
+ export function Badge({
22
+ className,
23
+ variant,
24
+ ...props
25
+ }: React.ComponentProps<"div"> & VariantProps<typeof badgeVariants>) {
26
+ return (
27
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
28
+ );
29
+ }
@@ -1,16 +1,56 @@
1
1
  import * as React from "react";
2
+ import { Slot } from "@radix-ui/react-slot";
3
+ import { cva, type VariantProps } from "class-variance-authority";
2
4
  import { cn } from "@/utils/cn";
3
5
 
4
- export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement>;
6
+ const buttonVariants = cva(
7
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
+ destructive:
13
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
14
+ outline:
15
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
18
+ ghost: "hover:bg-accent hover:text-accent-foreground",
19
+ },
20
+ size: {
21
+ default: "h-9 px-4 py-2",
22
+ sm: "h-8 rounded-md px-3",
23
+ lg: "h-10 rounded-md px-8",
24
+ icon: "h-9 w-9",
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: "default",
29
+ size: "default",
30
+ },
31
+ },
32
+ );
33
+
34
+ export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
35
+ VariantProps<typeof buttonVariants> & {
36
+ asChild?: boolean;
37
+ };
38
+
39
+ export function Button({
40
+ className,
41
+ variant,
42
+ size,
43
+ asChild = false,
44
+ ...props
45
+ }: ButtonProps) {
46
+ const Comp = asChild ? Slot : "button";
5
47
 
6
- export function Button({ className, ...props }: ButtonProps) {
7
48
  return (
8
- <button
9
- className={cn(
10
- "inline-flex items-center justify-center rounded-md border px-3 py-2 text-sm",
11
- className,
12
- )}
49
+ <Comp
50
+ className={cn(buttonVariants({ variant, size }), className)}
13
51
  {...props}
14
52
  />
15
53
  );
16
54
  }
55
+
56
+ export { buttonVariants };
@@ -0,0 +1,54 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/utils/cn";
3
+
4
+ export function Card({ className, ...props }: React.ComponentProps<"div">) {
5
+ return (
6
+ <div
7
+ className={cn(
8
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
9
+ className,
10
+ )}
11
+ {...props}
12
+ />
13
+ );
14
+ }
15
+
16
+ export function CardHeader({
17
+ className,
18
+ ...props
19
+ }: React.ComponentProps<"div">) {
20
+ return (
21
+ <div
22
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ export function CardTitle({ className, ...props }: React.ComponentProps<"h3">) {
29
+ return (
30
+ <h3
31
+ className={cn(
32
+ "text-2xl font-semibold leading-none tracking-tight",
33
+ className,
34
+ )}
35
+ {...props}
36
+ />
37
+ );
38
+ }
39
+
40
+ export function CardDescription({
41
+ className,
42
+ ...props
43
+ }: React.ComponentProps<"p">) {
44
+ return (
45
+ <p className={cn("text-sm text-muted-foreground", className)} {...props} />
46
+ );
47
+ }
48
+
49
+ export function CardContent({
50
+ className,
51
+ ...props
52
+ }: React.ComponentProps<"div">) {
53
+ return <div className={cn("p-6 pt-0", className)} {...props} />;
54
+ }
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import type { Column } from "@tanstack/react-table";
4
+ import { Button } from "@/components/ui/button";
5
+
6
+ export function DataTableColumnHeader<TData, TValue>({
7
+ column,
8
+ title,
9
+ }: {
10
+ column: Column<TData, TValue>;
11
+ title: string;
12
+ }) {
13
+ return (
14
+ <Button
15
+ variant="ghost"
16
+ size="sm"
17
+ className="h-8 px-2"
18
+ onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
19
+ >
20
+ {title}
21
+ </Button>
22
+ );
23
+ }
@@ -0,0 +1,3 @@
1
+ export function DataTableFilterList() {
2
+ return null;
3
+ }
@@ -0,0 +1,35 @@
1
+ "use client";
2
+
3
+ import type { Table } from "@tanstack/react-table";
4
+ import { useTranslations } from "next-intl";
5
+ import { Button } from "@/components/ui/button";
6
+
7
+ export function DataTablePagination<TData>({ table }: { table: Table<TData> }) {
8
+ const t = useTranslations("common.table");
9
+ const pageCount = table.getPageCount();
10
+ const pageIndex = table.getState().pagination.pageIndex;
11
+
12
+ return (
13
+ <div className="flex items-center justify-end gap-2">
14
+ <span className="text-xs text-muted-foreground">
15
+ {pageCount === 0 ? 0 : pageIndex + 1}/{Math.max(pageCount, 1)}
16
+ </span>
17
+ <Button
18
+ variant="outline"
19
+ size="sm"
20
+ onClick={() => table.previousPage()}
21
+ disabled={!table.getCanPreviousPage()}
22
+ >
23
+ {t("previous")}
24
+ </Button>
25
+ <Button
26
+ variant="outline"
27
+ size="sm"
28
+ onClick={() => table.nextPage()}
29
+ disabled={!table.getCanNextPage()}
30
+ >
31
+ {t("next")}
32
+ </Button>
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,11 @@
1
+ import { Skeleton } from "@/components/ui/skeleton";
2
+
3
+ export function DataTableSkeleton() {
4
+ return (
5
+ <div className="space-y-2">
6
+ <Skeleton className="h-8 w-full" />
7
+ <Skeleton className="h-8 w-full" />
8
+ <Skeleton className="h-8 w-full" />
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,14 @@
1
+ "use client";
2
+
3
+ import type { Table } from "@tanstack/react-table";
4
+ import type { ReactNode } from "react";
5
+
6
+ export function DataTableToolbar<TData>({
7
+ _table,
8
+ children,
9
+ }: {
10
+ _table: Table<TData>;
11
+ children?: ReactNode;
12
+ }) {
13
+ return <div className="flex items-center justify-between">{children}</div>;
14
+ }
@@ -0,0 +1,3 @@
1
+ export function DataTableViewOptions() {
2
+ return null;
3
+ }
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import { flexRender, type Table as TanstackTable } from "@tanstack/react-table";
4
+ import { useTranslations } from "next-intl";
5
+ import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination";
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from "@/components/ui/table";
14
+
15
+ type DataTableProps<TData> = {
16
+ table: TanstackTable<TData>;
17
+ };
18
+
19
+ export function DataTable<TData>({ table }: DataTableProps<TData>) {
20
+ const t = useTranslations("common.table");
21
+
22
+ return (
23
+ <div className="space-y-3">
24
+ <div className="overflow-hidden rounded-md border">
25
+ <Table>
26
+ <TableHeader>
27
+ {table.getHeaderGroups().map((headerGroup) => (
28
+ <TableRow key={headerGroup.id}>
29
+ {headerGroup.headers.map((header) => (
30
+ <TableHead key={header.id}>
31
+ {header.isPlaceholder
32
+ ? null
33
+ : flexRender(
34
+ header.column.columnDef.header,
35
+ header.getContext(),
36
+ )}
37
+ </TableHead>
38
+ ))}
39
+ </TableRow>
40
+ ))}
41
+ </TableHeader>
42
+ <TableBody>
43
+ {table.getRowModel().rows.length > 0 ? (
44
+ table.getRowModel().rows.map((row) => (
45
+ <TableRow key={row.id}>
46
+ {row.getVisibleCells().map((cell) => (
47
+ <TableCell key={cell.id}>
48
+ {flexRender(
49
+ cell.column.columnDef.cell,
50
+ cell.getContext(),
51
+ )}
52
+ </TableCell>
53
+ ))}
54
+ </TableRow>
55
+ ))
56
+ ) : (
57
+ <TableRow>
58
+ <TableCell
59
+ colSpan={table.getAllColumns().length}
60
+ className="h-24 text-center"
61
+ >
62
+ {t("noResults")}
63
+ </TableCell>
64
+ </TableRow>
65
+ )}
66
+ </TableBody>
67
+ </Table>
68
+ </div>
69
+ <DataTablePagination table={table} />
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,105 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
5
+ import { X } from "lucide-react";
6
+ import { cn } from "@/utils/cn";
7
+
8
+ export const Dialog = DialogPrimitive.Root;
9
+ export const DialogTrigger = DialogPrimitive.Trigger;
10
+ export const DialogPortal = DialogPrimitive.Portal;
11
+ export const DialogClose = DialogPrimitive.Close;
12
+
13
+ export function DialogOverlay({
14
+ className,
15
+ ...props
16
+ }: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
17
+ return (
18
+ <DialogPrimitive.Overlay
19
+ className={cn("fixed inset-0 z-50 bg-black/50", className)}
20
+ {...props}
21
+ />
22
+ );
23
+ }
24
+
25
+ export function DialogContent({
26
+ className,
27
+ children,
28
+ ...props
29
+ }: React.ComponentProps<typeof DialogPrimitive.Content>) {
30
+ return (
31
+ <DialogPortal>
32
+ <DialogOverlay />
33
+ <DialogPrimitive.Content
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 rounded-lg border bg-background p-6 shadow-lg",
36
+ className,
37
+ )}
38
+ {...props}
39
+ >
40
+ {children}
41
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100">
42
+ <X className="h-4 w-4" />
43
+ <span className="sr-only">Close</span>
44
+ </DialogPrimitive.Close>
45
+ </DialogPrimitive.Content>
46
+ </DialogPortal>
47
+ );
48
+ }
49
+
50
+ export function DialogHeader({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<"div">) {
54
+ return (
55
+ <div
56
+ className={cn(
57
+ "flex flex-col space-y-1.5 text-center sm:text-left",
58
+ className,
59
+ )}
60
+ {...props}
61
+ />
62
+ );
63
+ }
64
+
65
+ export function DialogFooter({
66
+ className,
67
+ ...props
68
+ }: React.ComponentProps<"div">) {
69
+ return (
70
+ <div
71
+ className={cn(
72
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
73
+ className,
74
+ )}
75
+ {...props}
76
+ />
77
+ );
78
+ }
79
+
80
+ export function DialogTitle({
81
+ className,
82
+ ...props
83
+ }: React.ComponentProps<typeof DialogPrimitive.Title>) {
84
+ return (
85
+ <DialogPrimitive.Title
86
+ className={cn(
87
+ "text-lg font-semibold leading-none tracking-tight",
88
+ className,
89
+ )}
90
+ {...props}
91
+ />
92
+ );
93
+ }
94
+
95
+ export function DialogDescription({
96
+ className,
97
+ ...props
98
+ }: React.ComponentProps<typeof DialogPrimitive.Description>) {
99
+ return (
100
+ <DialogPrimitive.Description
101
+ className={cn("text-sm text-muted-foreground", className)}
102
+ {...props}
103
+ />
104
+ );
105
+ }
@@ -0,0 +1,44 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { cn } from "@/utils/cn";
6
+
7
+ export const DropdownMenu = DropdownMenuPrimitive.Root;
8
+ export const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
9
+ export const DropdownMenuGroup = DropdownMenuPrimitive.Group;
10
+ export const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
11
+
12
+ export function DropdownMenuContent({
13
+ className,
14
+ sideOffset = 4,
15
+ ...props
16
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
17
+ return (
18
+ <DropdownMenuPrimitive.Portal>
19
+ <DropdownMenuPrimitive.Content
20
+ sideOffset={sideOffset}
21
+ className={cn(
22
+ "z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
23
+ className,
24
+ )}
25
+ {...props}
26
+ />
27
+ </DropdownMenuPrimitive.Portal>
28
+ );
29
+ }
30
+
31
+ export function DropdownMenuItem({
32
+ className,
33
+ ...props
34
+ }: React.ComponentProps<typeof DropdownMenuPrimitive.Item>) {
35
+ return (
36
+ <DropdownMenuPrimitive.Item
37
+ className={cn(
38
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent",
39
+ className,
40
+ )}
41
+ {...props}
42
+ />
43
+ );
44
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/utils/cn";
3
+
4
+ export function Input({
5
+ className,
6
+ type,
7
+ ...props
8
+ }: React.ComponentProps<"input">) {
9
+ return (
10
+ <input
11
+ type={type}
12
+ className={cn(
13
+ "flex h-9 w-full rounded-md border border-input bg-background px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from "react";
2
+ import * as LabelPrimitive from "@radix-ui/react-label";
3
+ import { cn } from "@/utils/cn";
4
+
5
+ export function Label({
6
+ className,
7
+ ...props
8
+ }: React.ComponentProps<typeof LabelPrimitive.Root>) {
9
+ return (
10
+ <LabelPrimitive.Root
11
+ className={cn("text-sm font-medium leading-none", className)}
12
+ {...props}
13
+ />
14
+ );
15
+ }