@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.
- package/README.md +6 -2
- package/dist/cli.js +778 -101
- package/package.json +2 -1
- package/templates/next-base/PROJECT_STRUCTURE.md +88 -0
- package/templates/next-base/SETUP.md +86 -0
- package/templates/next-base/bun.lock +1443 -0
- package/templates/next-base/components.json +21 -0
- package/templates/next-base/messages/vi/auth.json +42 -0
- package/templates/next-base/messages/vi/common.json +34 -0
- package/templates/next-base/messages/vi/example.json +10 -0
- package/templates/next-base/next-env.d.ts +3 -1
- package/templates/next-base/next.config.ts +11 -1
- package/templates/next-base/nextcli.json +8 -0
- package/templates/next-base/package.json +21 -1
- package/templates/next-base/postcss.config.mjs +5 -0
- package/templates/next-base/prisma/migrations/20260612000000_init/migration.sql +104 -0
- package/templates/next-base/prisma/migrations/migration_lock.toml +3 -0
- package/templates/next-base/prisma/schema.prisma +23 -9
- package/templates/next-base/public/logo.svg +4 -0
- package/templates/next-base/src/app/(auth)/change-password/layout.tsx +21 -0
- package/templates/next-base/src/app/(auth)/change-password/page.tsx +14 -0
- package/templates/next-base/src/app/(auth)/layout.tsx +9 -0
- package/templates/next-base/src/app/(auth)/sign-in/layout.tsx +17 -0
- package/templates/next-base/src/app/(auth)/sign-in/page.tsx +6 -3
- package/templates/next-base/src/app/(dashboard)/account/page.tsx +9 -5
- package/templates/next-base/src/app/(dashboard)/dashboard/page.tsx +17 -0
- package/templates/next-base/src/app/(dashboard)/example/page.tsx +5 -2
- package/templates/next-base/src/app/(dashboard)/layout.tsx +22 -0
- package/templates/next-base/src/app/api/v1/auth/change-password/route.ts +55 -0
- package/templates/next-base/src/app/api/v1/auth/login/route.ts +15 -5
- package/templates/next-base/src/app/api/v1/auth/me/route.ts +17 -19
- package/templates/next-base/src/app/api/v1/users/[id]/route.ts +104 -0
- package/templates/next-base/src/app/api/v1/users/route.ts +58 -0
- package/templates/next-base/src/app/globals.css +111 -0
- package/templates/next-base/src/app/layout.tsx +24 -10
- package/templates/next-base/src/app/page.tsx +2 -18
- package/templates/next-base/src/components/branding/logo.tsx +27 -0
- package/templates/next-base/src/components/layout/private/app-sidebar.tsx +44 -0
- package/templates/next-base/src/components/layout/private/dashboard-layout.tsx +54 -0
- package/templates/next-base/src/components/layout/private/locale-switcher.tsx +45 -0
- package/templates/next-base/src/components/layout/private/nav-sidebar.tsx +55 -0
- package/templates/next-base/src/components/layout/private/nav-user.tsx +99 -0
- package/templates/next-base/src/components/providers/theme-provider.tsx +11 -0
- package/templates/next-base/src/components/ui/alert-dialog.tsx +11 -0
- package/templates/next-base/src/components/ui/avatar.tsx +45 -0
- package/templates/next-base/src/components/ui/badge.tsx +29 -0
- package/templates/next-base/src/components/ui/button.tsx +47 -7
- package/templates/next-base/src/components/ui/card.tsx +54 -0
- package/templates/next-base/src/components/ui/data-table/data-table-column-header.tsx +23 -0
- package/templates/next-base/src/components/ui/data-table/data-table-filter-list.tsx +3 -0
- package/templates/next-base/src/components/ui/data-table/data-table-pagination.tsx +35 -0
- package/templates/next-base/src/components/ui/data-table/data-table-skeleton.tsx +11 -0
- package/templates/next-base/src/components/ui/data-table/data-table-toolbar.tsx +14 -0
- package/templates/next-base/src/components/ui/data-table/data-table-view-options.tsx +3 -0
- package/templates/next-base/src/components/ui/data-table/data-table.tsx +72 -0
- package/templates/next-base/src/components/ui/dialog.tsx +105 -0
- package/templates/next-base/src/components/ui/dropdown-menu.tsx +44 -0
- package/templates/next-base/src/components/ui/input.tsx +19 -0
- package/templates/next-base/src/components/ui/label.tsx +15 -0
- package/templates/next-base/src/components/ui/popover.tsx +30 -0
- package/templates/next-base/src/components/ui/scroll-area.tsx +47 -0
- package/templates/next-base/src/components/ui/select.tsx +76 -0
- package/templates/next-base/src/components/ui/separator.tsx +23 -0
- package/templates/next-base/src/components/ui/sheet.tsx +117 -0
- package/templates/next-base/src/components/ui/sidebar.tsx +215 -0
- package/templates/next-base/src/components/ui/skeleton.tsx +10 -0
- package/templates/next-base/src/components/ui/sonner.tsx +3 -0
- package/templates/next-base/src/components/ui/table.tsx +54 -0
- package/templates/next-base/src/components/ui/tabs.tsx +52 -0
- package/templates/next-base/src/components/ui/textarea.tsx +17 -0
- package/templates/next-base/src/components/ui/tooltip.tsx +26 -0
- package/templates/next-base/src/config/branding.ts +14 -0
- package/templates/next-base/src/data/sidebar-modules.ts +11 -0
- package/templates/next-base/src/example/components/example-table.tsx +25 -40
- package/templates/next-base/src/features/auth/components/account-panel.tsx +32 -14
- package/templates/next-base/src/features/auth/components/change-password-form.tsx +82 -0
- package/templates/next-base/src/features/auth/components/sign-in-form.tsx +53 -35
- package/templates/next-base/src/features/auth/validations.ts +7 -1
- package/templates/next-base/src/features/users/services.ts +132 -0
- package/templates/next-base/src/features/users/validations.ts +21 -0
- package/templates/next-base/src/hooks/index.ts +1 -1
- package/templates/next-base/src/hooks/table/use-data-table.ts +33 -0
- package/templates/next-base/src/hooks/use-mobile.ts +25 -0
- package/templates/next-base/src/i18n/config.ts +7 -0
- package/templates/next-base/src/i18n/namespaces.ts +5 -0
- package/templates/next-base/src/i18n/request.ts +19 -2
- package/templates/next-base/src/instrumentation.ts +14 -0
- package/templates/next-base/src/lib/auth-client.ts +2 -2
- package/templates/next-base/src/lib/auth.ts +2 -2
- package/templates/next-base/src/lib/bootstrap.ts +96 -0
- package/templates/next-base/src/lib/constants.ts +7 -0
- package/templates/next-base/src/lib/prisma.ts +11 -1
- package/templates/next-base/src/lib/rbac.ts +62 -0
- package/templates/next-base/src/types/data-table.ts +4 -0
- package/templates/next-base/src/types/index.ts +2 -0
- package/templates/next-base/tsconfig.json +29 -7
- package/templates/next-base/middleware.ts +0 -10
- package/templates/next-base/src/app/styles.css +0 -12
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
6
|
+
|
|
7
|
+
export const Popover = PopoverPrimitive.Root;
|
|
8
|
+
export const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
9
|
+
export const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
10
|
+
|
|
11
|
+
export function PopoverContent({
|
|
12
|
+
className,
|
|
13
|
+
align = "center",
|
|
14
|
+
sideOffset = 4,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
|
|
17
|
+
return (
|
|
18
|
+
<PopoverPrimitive.Portal>
|
|
19
|
+
<PopoverPrimitive.Content
|
|
20
|
+
align={align}
|
|
21
|
+
sideOffset={sideOffset}
|
|
22
|
+
className={cn(
|
|
23
|
+
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md",
|
|
24
|
+
className,
|
|
25
|
+
)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
</PopoverPrimitive.Portal>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
6
|
+
|
|
7
|
+
export function ScrollArea({
|
|
8
|
+
className,
|
|
9
|
+
children,
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
|
|
12
|
+
return (
|
|
13
|
+
<ScrollAreaPrimitive.Root
|
|
14
|
+
className={cn("relative overflow-hidden", className)}
|
|
15
|
+
{...props}
|
|
16
|
+
>
|
|
17
|
+
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
|
18
|
+
{children}
|
|
19
|
+
</ScrollAreaPrimitive.Viewport>
|
|
20
|
+
<ScrollBar />
|
|
21
|
+
<ScrollAreaPrimitive.Corner />
|
|
22
|
+
</ScrollAreaPrimitive.Root>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function ScrollBar({
|
|
27
|
+
className,
|
|
28
|
+
orientation = "vertical",
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
|
|
31
|
+
return (
|
|
32
|
+
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
33
|
+
orientation={orientation}
|
|
34
|
+
className={cn(
|
|
35
|
+
"flex touch-none select-none transition-colors",
|
|
36
|
+
orientation === "vertical" &&
|
|
37
|
+
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
|
38
|
+
orientation === "horizontal" &&
|
|
39
|
+
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
44
|
+
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
|
45
|
+
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
5
|
+
import { Check, ChevronDown } from "lucide-react";
|
|
6
|
+
import { cn } from "@/utils/cn";
|
|
7
|
+
|
|
8
|
+
export const Select = SelectPrimitive.Root;
|
|
9
|
+
export const SelectGroup = SelectPrimitive.Group;
|
|
10
|
+
export const SelectValue = SelectPrimitive.Value;
|
|
11
|
+
|
|
12
|
+
export function SelectTrigger({
|
|
13
|
+
className,
|
|
14
|
+
children,
|
|
15
|
+
...props
|
|
16
|
+
}: React.ComponentProps<typeof SelectPrimitive.Trigger>) {
|
|
17
|
+
return (
|
|
18
|
+
<SelectPrimitive.Trigger
|
|
19
|
+
className={cn(
|
|
20
|
+
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
<SelectPrimitive.Icon asChild>
|
|
27
|
+
<ChevronDown className="h-4 w-4 opacity-50" />
|
|
28
|
+
</SelectPrimitive.Icon>
|
|
29
|
+
</SelectPrimitive.Trigger>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function SelectContent({
|
|
34
|
+
className,
|
|
35
|
+
children,
|
|
36
|
+
...props
|
|
37
|
+
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
38
|
+
return (
|
|
39
|
+
<SelectPrimitive.Portal>
|
|
40
|
+
<SelectPrimitive.Content
|
|
41
|
+
className={cn(
|
|
42
|
+
"z-50 min-w-[8rem] rounded-md border bg-popover text-popover-foreground shadow-md",
|
|
43
|
+
className,
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
>
|
|
47
|
+
<SelectPrimitive.Viewport className="p-1">
|
|
48
|
+
{children}
|
|
49
|
+
</SelectPrimitive.Viewport>
|
|
50
|
+
</SelectPrimitive.Content>
|
|
51
|
+
</SelectPrimitive.Portal>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function SelectItem({
|
|
56
|
+
className,
|
|
57
|
+
children,
|
|
58
|
+
...props
|
|
59
|
+
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
60
|
+
return (
|
|
61
|
+
<SelectPrimitive.Item
|
|
62
|
+
className={cn(
|
|
63
|
+
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground",
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
{...props}
|
|
67
|
+
>
|
|
68
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
69
|
+
<SelectPrimitive.ItemIndicator>
|
|
70
|
+
<Check className="h-4 w-4" />
|
|
71
|
+
</SelectPrimitive.ItemIndicator>
|
|
72
|
+
</span>
|
|
73
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
74
|
+
</SelectPrimitive.Item>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
3
|
+
import { cn } from "@/utils/cn";
|
|
4
|
+
|
|
5
|
+
export function Separator({
|
|
6
|
+
className,
|
|
7
|
+
orientation = "horizontal",
|
|
8
|
+
decorative = true,
|
|
9
|
+
...props
|
|
10
|
+
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
11
|
+
return (
|
|
12
|
+
<SeparatorPrimitive.Root
|
|
13
|
+
decorative={decorative}
|
|
14
|
+
orientation={orientation}
|
|
15
|
+
className={cn(
|
|
16
|
+
"shrink-0 bg-border",
|
|
17
|
+
orientation === "horizontal" ? "h-px w-full" : "h-full w-px",
|
|
18
|
+
className,
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
import { buttonVariants } from "@/components/ui/button";
|
|
8
|
+
|
|
9
|
+
export const Sheet = DialogPrimitive.Root;
|
|
10
|
+
export const SheetTrigger = DialogPrimitive.Trigger;
|
|
11
|
+
export const SheetClose = DialogPrimitive.Close;
|
|
12
|
+
export const SheetPortal = DialogPrimitive.Portal;
|
|
13
|
+
|
|
14
|
+
export function SheetOverlay({
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
18
|
+
return (
|
|
19
|
+
<DialogPrimitive.Overlay
|
|
20
|
+
className={cn("fixed inset-0 z-50 bg-black/50", className)}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function SheetContent({
|
|
27
|
+
className,
|
|
28
|
+
children,
|
|
29
|
+
side = "right",
|
|
30
|
+
...props
|
|
31
|
+
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
32
|
+
side?: "top" | "right" | "bottom" | "left";
|
|
33
|
+
}) {
|
|
34
|
+
return (
|
|
35
|
+
<SheetPortal>
|
|
36
|
+
<SheetOverlay />
|
|
37
|
+
<DialogPrimitive.Content
|
|
38
|
+
className={cn(
|
|
39
|
+
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition",
|
|
40
|
+
side === "right" &&
|
|
41
|
+
"inset-y-0 right-0 h-full w-3/4 border-l sm:max-w-sm",
|
|
42
|
+
side === "left" &&
|
|
43
|
+
"inset-y-0 left-0 h-full w-3/4 border-r sm:max-w-sm",
|
|
44
|
+
side === "top" && "inset-x-0 top-0 border-b",
|
|
45
|
+
side === "bottom" && "inset-x-0 bottom-0 border-t",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
<DialogPrimitive.Close
|
|
52
|
+
className={cn(
|
|
53
|
+
buttonVariants({ variant: "ghost", size: "icon" }),
|
|
54
|
+
"absolute right-4 top-4",
|
|
55
|
+
)}
|
|
56
|
+
>
|
|
57
|
+
<X className="h-4 w-4" />
|
|
58
|
+
<span className="sr-only">Close</span>
|
|
59
|
+
</DialogPrimitive.Close>
|
|
60
|
+
</DialogPrimitive.Content>
|
|
61
|
+
</SheetPortal>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function SheetHeader({
|
|
66
|
+
className,
|
|
67
|
+
...props
|
|
68
|
+
}: React.ComponentProps<"div">) {
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className={cn(
|
|
72
|
+
"flex flex-col space-y-2 text-center sm:text-left",
|
|
73
|
+
className,
|
|
74
|
+
)}
|
|
75
|
+
{...props}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function SheetFooter({
|
|
81
|
+
className,
|
|
82
|
+
...props
|
|
83
|
+
}: React.ComponentProps<"div">) {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
className={cn(
|
|
87
|
+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function SheetTitle({
|
|
96
|
+
className,
|
|
97
|
+
...props
|
|
98
|
+
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
99
|
+
return (
|
|
100
|
+
<DialogPrimitive.Title
|
|
101
|
+
className={cn("text-lg font-semibold", className)}
|
|
102
|
+
{...props}
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function SheetDescription({
|
|
108
|
+
className,
|
|
109
|
+
...props
|
|
110
|
+
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
111
|
+
return (
|
|
112
|
+
<DialogPrimitive.Description
|
|
113
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
114
|
+
{...props}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { PanelLeft } from "lucide-react";
|
|
5
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
6
|
+
import { cn } from "@/utils/cn";
|
|
7
|
+
import { Button } from "@/components/ui/button";
|
|
8
|
+
import { Sheet, SheetContent } from "@/components/ui/sheet";
|
|
9
|
+
import { useIsMobile } from "@/hooks/use-mobile";
|
|
10
|
+
|
|
11
|
+
type SidebarContextValue = {
|
|
12
|
+
open: boolean;
|
|
13
|
+
setOpen: (value: boolean) => void;
|
|
14
|
+
openMobile: boolean;
|
|
15
|
+
setOpenMobile: (value: boolean) => void;
|
|
16
|
+
isMobile: boolean;
|
|
17
|
+
state: "expanded" | "collapsed";
|
|
18
|
+
toggleSidebar: () => void;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const SidebarContext = React.createContext<SidebarContextValue | null>(null);
|
|
22
|
+
|
|
23
|
+
export function useSidebar() {
|
|
24
|
+
const context = React.useContext(SidebarContext);
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error("useSidebar must be used within SidebarProvider");
|
|
27
|
+
}
|
|
28
|
+
return context;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function SidebarProvider({
|
|
32
|
+
defaultOpen = true,
|
|
33
|
+
children,
|
|
34
|
+
}: {
|
|
35
|
+
defaultOpen?: boolean;
|
|
36
|
+
children: React.ReactNode;
|
|
37
|
+
}) {
|
|
38
|
+
const isMobile = useIsMobile();
|
|
39
|
+
const [open, setOpen] = React.useState(defaultOpen);
|
|
40
|
+
const [openMobile, setOpenMobile] = React.useState(false);
|
|
41
|
+
const state = open ? "expanded" : "collapsed";
|
|
42
|
+
|
|
43
|
+
const toggleSidebar = React.useCallback(() => {
|
|
44
|
+
if (isMobile) {
|
|
45
|
+
setOpenMobile((value) => !value);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
setOpen((value) => !value);
|
|
49
|
+
}, [isMobile]);
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<SidebarContext.Provider
|
|
53
|
+
value={{
|
|
54
|
+
open,
|
|
55
|
+
setOpen,
|
|
56
|
+
openMobile,
|
|
57
|
+
setOpenMobile,
|
|
58
|
+
isMobile,
|
|
59
|
+
state,
|
|
60
|
+
toggleSidebar,
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
<div className="group/sidebar-wrapper flex min-h-svh w-full">
|
|
64
|
+
{children}
|
|
65
|
+
</div>
|
|
66
|
+
</SidebarContext.Provider>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function Sidebar({
|
|
71
|
+
className,
|
|
72
|
+
children,
|
|
73
|
+
collapsible = "offcanvas",
|
|
74
|
+
}: React.ComponentProps<"div"> & { collapsible?: "offcanvas" | "none" }) {
|
|
75
|
+
const { isMobile, openMobile, setOpenMobile, state } = useSidebar();
|
|
76
|
+
|
|
77
|
+
if (collapsible === "none") {
|
|
78
|
+
return (
|
|
79
|
+
<aside
|
|
80
|
+
className={cn(
|
|
81
|
+
"flex h-full w-64 flex-col border-r bg-sidebar text-sidebar-foreground",
|
|
82
|
+
className,
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{children}
|
|
86
|
+
</aside>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (isMobile) {
|
|
91
|
+
return (
|
|
92
|
+
<Sheet open={openMobile} onOpenChange={setOpenMobile}>
|
|
93
|
+
<SheetContent side="left" className={cn("w-[18rem] p-0", className)}>
|
|
94
|
+
<div className="flex h-full flex-col bg-sidebar text-sidebar-foreground">
|
|
95
|
+
{children}
|
|
96
|
+
</div>
|
|
97
|
+
</SheetContent>
|
|
98
|
+
</Sheet>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<aside
|
|
104
|
+
data-state={state}
|
|
105
|
+
className={cn(
|
|
106
|
+
"hidden h-screen border-r bg-sidebar text-sidebar-foreground md:flex md:flex-col",
|
|
107
|
+
state === "collapsed" ? "w-14" : "w-64",
|
|
108
|
+
className,
|
|
109
|
+
)}
|
|
110
|
+
>
|
|
111
|
+
{children}
|
|
112
|
+
</aside>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function SidebarTrigger({
|
|
117
|
+
className,
|
|
118
|
+
children,
|
|
119
|
+
...props
|
|
120
|
+
}: React.ComponentProps<typeof Button>) {
|
|
121
|
+
const { toggleSidebar } = useSidebar();
|
|
122
|
+
return (
|
|
123
|
+
<Button
|
|
124
|
+
variant="ghost"
|
|
125
|
+
size="icon"
|
|
126
|
+
className={cn("h-8 w-8", className)}
|
|
127
|
+
onClick={(event) => {
|
|
128
|
+
props.onClick?.(event);
|
|
129
|
+
toggleSidebar();
|
|
130
|
+
}}
|
|
131
|
+
{...props}
|
|
132
|
+
>
|
|
133
|
+
{children ?? <PanelLeft className="h-4 w-4" />}
|
|
134
|
+
</Button>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function SidebarHeader({
|
|
139
|
+
className,
|
|
140
|
+
...props
|
|
141
|
+
}: React.ComponentProps<"div">) {
|
|
142
|
+
return <div className={cn("border-b p-3", className)} {...props} />;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function SidebarContent({
|
|
146
|
+
className,
|
|
147
|
+
...props
|
|
148
|
+
}: React.ComponentProps<"div">) {
|
|
149
|
+
return (
|
|
150
|
+
<div className={cn("flex-1 overflow-y-auto p-2", className)} {...props} />
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function SidebarGroup({
|
|
155
|
+
className,
|
|
156
|
+
...props
|
|
157
|
+
}: React.ComponentProps<"div">) {
|
|
158
|
+
return <div className={cn("mb-2", className)} {...props} />;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function SidebarGroupLabel({
|
|
162
|
+
className,
|
|
163
|
+
...props
|
|
164
|
+
}: React.ComponentProps<"div">) {
|
|
165
|
+
return (
|
|
166
|
+
<div
|
|
167
|
+
className={cn(
|
|
168
|
+
"px-2 py-1 text-xs font-medium text-muted-foreground",
|
|
169
|
+
className,
|
|
170
|
+
)}
|
|
171
|
+
{...props}
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function SidebarGroupContent({
|
|
177
|
+
className,
|
|
178
|
+
...props
|
|
179
|
+
}: React.ComponentProps<"div">) {
|
|
180
|
+
return <div className={cn("space-y-1", className)} {...props} />;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function SidebarMenu({
|
|
184
|
+
className,
|
|
185
|
+
...props
|
|
186
|
+
}: React.ComponentProps<"ul">) {
|
|
187
|
+
return <ul className={cn("space-y-1", className)} {...props} />;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function SidebarMenuItem({
|
|
191
|
+
className,
|
|
192
|
+
...props
|
|
193
|
+
}: React.ComponentProps<"li">) {
|
|
194
|
+
return <li className={cn("list-none", className)} {...props} />;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function SidebarMenuButton({
|
|
198
|
+
className,
|
|
199
|
+
asChild = false,
|
|
200
|
+
isActive = false,
|
|
201
|
+
...props
|
|
202
|
+
}: React.ComponentProps<"button"> & { asChild?: boolean; isActive?: boolean }) {
|
|
203
|
+
const Comp = asChild ? Slot : "button";
|
|
204
|
+
return (
|
|
205
|
+
<Comp
|
|
206
|
+
className={cn(
|
|
207
|
+
"flex h-9 w-full items-center gap-2 rounded-md px-2 text-sm hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
|
208
|
+
isActive &&
|
|
209
|
+
"bg-sidebar-primary text-sidebar-primary-foreground hover:bg-sidebar-primary/90",
|
|
210
|
+
className,
|
|
211
|
+
)}
|
|
212
|
+
{...props}
|
|
213
|
+
/>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/utils/cn";
|
|
3
|
+
|
|
4
|
+
export function Table({ className, ...props }: React.ComponentProps<"table">) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="w-full overflow-auto">
|
|
7
|
+
<table
|
|
8
|
+
className={cn("w-full caption-bottom text-sm", className)}
|
|
9
|
+
{...props}
|
|
10
|
+
/>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function TableHeader({
|
|
16
|
+
className,
|
|
17
|
+
...props
|
|
18
|
+
}: React.ComponentProps<"thead">) {
|
|
19
|
+
return <thead className={cn("[&_tr]:border-b", className)} {...props} />;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function TableBody({
|
|
23
|
+
className,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<"tbody">) {
|
|
26
|
+
return (
|
|
27
|
+
<tbody className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
|
32
|
+
return (
|
|
33
|
+
<tr
|
|
34
|
+
className={cn("border-b transition-colors hover:bg-muted/50", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
|
41
|
+
return (
|
|
42
|
+
<th
|
|
43
|
+
className={cn(
|
|
44
|
+
"h-10 px-2 text-left align-middle font-medium text-muted-foreground",
|
|
45
|
+
className,
|
|
46
|
+
)}
|
|
47
|
+
{...props}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
|
53
|
+
return <td className={cn("p-2 align-middle", className)} {...props} />;
|
|
54
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
6
|
+
|
|
7
|
+
export const Tabs = TabsPrimitive.Root;
|
|
8
|
+
|
|
9
|
+
export function TabsList({
|
|
10
|
+
className,
|
|
11
|
+
...props
|
|
12
|
+
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
|
13
|
+
return (
|
|
14
|
+
<TabsPrimitive.List
|
|
15
|
+
className={cn(
|
|
16
|
+
"inline-flex h-9 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
|
17
|
+
className,
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function TabsTrigger({
|
|
25
|
+
className,
|
|
26
|
+
...props
|
|
27
|
+
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
28
|
+
return (
|
|
29
|
+
<TabsPrimitive.Trigger
|
|
30
|
+
className={cn(
|
|
31
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
{...props}
|
|
35
|
+
/>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function TabsContent({
|
|
40
|
+
className,
|
|
41
|
+
...props
|
|
42
|
+
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
43
|
+
return (
|
|
44
|
+
<TabsPrimitive.Content
|
|
45
|
+
className={cn(
|
|
46
|
+
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
47
|
+
className,
|
|
48
|
+
)}
|
|
49
|
+
{...props}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "@/utils/cn";
|
|
3
|
+
|
|
4
|
+
export function Textarea({
|
|
5
|
+
className,
|
|
6
|
+
...props
|
|
7
|
+
}: React.ComponentProps<"textarea">) {
|
|
8
|
+
return (
|
|
9
|
+
<textarea
|
|
10
|
+
className={cn(
|
|
11
|
+
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
5
|
+
import { cn } from "@/utils/cn";
|
|
6
|
+
|
|
7
|
+
export const TooltipProvider = TooltipPrimitive.Provider;
|
|
8
|
+
export const Tooltip = TooltipPrimitive.Root;
|
|
9
|
+
export const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
10
|
+
|
|
11
|
+
export function TooltipContent({
|
|
12
|
+
className,
|
|
13
|
+
sideOffset = 4,
|
|
14
|
+
...props
|
|
15
|
+
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
16
|
+
return (
|
|
17
|
+
<TooltipPrimitive.Content
|
|
18
|
+
sideOffset={sideOffset}
|
|
19
|
+
className={cn(
|
|
20
|
+
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground",
|
|
21
|
+
className,
|
|
22
|
+
)}
|
|
23
|
+
{...props}
|
|
24
|
+
/>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Central branding config — edit these values after scaffolding.
|
|
3
|
+
* See SETUP.md § Branding for env vs display settings.
|
|
4
|
+
*/
|
|
5
|
+
export const branding = {
|
|
6
|
+
/** Display name shown in UI (header, metadata, sidebar). */
|
|
7
|
+
projectName: "__PROJECT_NAME__",
|
|
8
|
+
/** URL-safe slug (package name, DB name). Set at create time. */
|
|
9
|
+
projectSlug: "__PROJECT_NAME__",
|
|
10
|
+
/** Short app description for metadata. */
|
|
11
|
+
description: "Outsource-ready Next.js scaffolded by NexTCLI",
|
|
12
|
+
/** Public path to logo asset under /public. */
|
|
13
|
+
logoPath: "/logo.svg",
|
|
14
|
+
} as const;
|