create-better-t-stack 1.4.5 → 1.6.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/dist/index.js +150 -98
- package/package.json +1 -1
- package/template/base/apps/{web → web-base}/_gitignore +3 -0
- package/template/base/apps/{web → web-base}/components.json +2 -2
- package/template/base/apps/{web → web-base}/src/components/mode-toggle.tsx +1 -1
- package/template/base/apps/{web → web-base}/src/components/theme-provider.tsx +1 -1
- package/template/base/apps/web-base/src/components/ui/button.tsx +59 -0
- package/template/base/apps/{web → web-base}/src/components/ui/card.tsx +10 -10
- package/template/base/apps/{web → web-base}/src/components/ui/checkbox.tsx +6 -6
- package/template/base/apps/web-base/src/components/ui/dropdown-menu.tsx +255 -0
- package/template/base/apps/web-base/src/components/ui/input.tsx +21 -0
- package/template/base/apps/web-base/src/components/ui/label.tsx +24 -0
- package/template/base/apps/web-base/src/components/ui/skeleton.tsx +13 -0
- package/template/base/apps/web-base/src/components/ui/sonner.tsx +23 -0
- package/template/base/apps/web-base/src/index.css +134 -0
- package/template/base/apps/web-react-router/package.json +49 -0
- package/template/base/apps/web-react-router/public/favicon.ico +0 -0
- package/template/base/apps/web-react-router/react-router.config.ts +6 -0
- package/template/base/apps/web-react-router/src/components/header.tsx +31 -0
- package/template/base/apps/web-react-router/src/root.tsx +91 -0
- package/template/base/apps/web-react-router/src/routes/_index.tsx +89 -0
- package/template/base/apps/web-react-router/src/routes.ts +4 -0
- package/template/base/apps/web-react-router/tsconfig.json +27 -0
- package/template/base/apps/web-react-router/vite.config.ts +8 -0
- package/template/base/apps/{web → web-tanstack-router}/package.json +2 -2
- package/template/base/apps/{web → web-tanstack-router}/src/routes/index.tsx +1 -4
- package/template/examples/ai/apps/web-react-router/src/routes/ai.tsx +64 -0
- package/template/examples/todo/apps/web-react-router/src/routes/todos.tsx +130 -0
- package/template/examples/todo/apps/{web → web-tanstack-router}/src/routes/todos.tsx +6 -8
- package/template/with-auth/apps/web-react-router/src/components/header.tsx +34 -0
- package/template/with-auth/apps/web-react-router/src/components/sign-in-form.tsx +135 -0
- package/template/with-auth/apps/web-react-router/src/components/sign-up-form.tsx +160 -0
- package/template/with-auth/apps/web-react-router/src/components/user-menu.tsx +60 -0
- package/template/with-auth/apps/web-react-router/src/routes/dashboard.tsx +30 -0
- package/template/with-auth/apps/web-react-router/src/routes/login.tsx +13 -0
- package/template/base/apps/web/src/components/ui/button.tsx +0 -57
- package/template/base/apps/web/src/components/ui/dropdown-menu.tsx +0 -199
- package/template/base/apps/web/src/components/ui/input.tsx +0 -22
- package/template/base/apps/web/src/components/ui/label.tsx +0 -24
- package/template/base/apps/web/src/components/ui/skeleton.tsx +0 -15
- package/template/base/apps/web/src/components/ui/sonner.tsx +0 -29
- package/template/base/apps/web/src/index.css +0 -119
- package/template/with-pwa/apps/web/vite.config.ts +0 -35
- /package/template/base/apps/{web → web-base}/src/components/loader.tsx +0 -0
- /package/template/base/apps/{web → web-base}/src/lib/utils.ts +0 -0
- /package/template/base/apps/{web → web-base}/src/utils/trpc.ts +0 -0
- /package/template/base/apps/{web → web-tanstack-router}/index.html +0 -0
- /package/template/base/apps/{web → web-tanstack-router}/src/components/header.tsx +0 -0
- /package/template/base/apps/{web → web-tanstack-router}/src/main.tsx +0 -0
- /package/template/base/apps/{web → web-tanstack-router}/src/routes/__root.tsx +0 -0
- /package/template/base/apps/{web → web-tanstack-router}/tsconfig.json +0 -0
- /package/template/base/apps/{web → web-tanstack-router}/vite.config.ts +0 -0
- /package/template/examples/ai/apps/{web → web-tanstack-router}/src/routes/ai.tsx +0 -0
- /package/template/with-auth/apps/{web → web-base}/src/lib/auth-client.ts +0 -0
- /package/template/with-auth/apps/{web → web-base}/src/utils/trpc.ts +0 -0
- /package/template/with-auth/apps/{web → web-tanstack-router}/src/components/header.tsx +0 -0
- /package/template/with-auth/apps/{web → web-tanstack-router}/src/components/sign-in-form.tsx +0 -0
- /package/template/with-auth/apps/{web → web-tanstack-router}/src/components/sign-up-form.tsx +0 -0
- /package/template/with-auth/apps/{web → web-tanstack-router}/src/components/user-menu.tsx +0 -0
- /package/template/with-auth/apps/{web → web-tanstack-router}/src/routes/dashboard.tsx +0 -0
- /package/template/with-auth/apps/{web → web-tanstack-router}/src/routes/login.tsx +0 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { authClient } from "@/lib/auth-client";
|
|
2
|
+
import { useForm } from "@tanstack/react-form";
|
|
3
|
+
import { useNavigate } from "react-router";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import Loader from "./loader";
|
|
7
|
+
import { Button } from "./ui/button";
|
|
8
|
+
import { Input } from "./ui/input";
|
|
9
|
+
import { Label } from "./ui/label";
|
|
10
|
+
|
|
11
|
+
export default function SignInForm({
|
|
12
|
+
onSwitchToSignUp,
|
|
13
|
+
}: {
|
|
14
|
+
onSwitchToSignUp: () => void;
|
|
15
|
+
}) {
|
|
16
|
+
const navigate = useNavigate();
|
|
17
|
+
const { isPending } = authClient.useSession();
|
|
18
|
+
|
|
19
|
+
const form = useForm({
|
|
20
|
+
defaultValues: {
|
|
21
|
+
email: "",
|
|
22
|
+
password: "",
|
|
23
|
+
},
|
|
24
|
+
onSubmit: async ({ value }) => {
|
|
25
|
+
await authClient.signIn.email(
|
|
26
|
+
{
|
|
27
|
+
email: value.email,
|
|
28
|
+
password: value.password,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
onSuccess: () => {
|
|
32
|
+
navigate("/dashboard");
|
|
33
|
+
toast.success("Sign in successful");
|
|
34
|
+
},
|
|
35
|
+
onError: (error) => {
|
|
36
|
+
toast.error(error.error.message);
|
|
37
|
+
},
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
validators: {
|
|
42
|
+
onSubmit: z.object({
|
|
43
|
+
email: z.string().email("Invalid email address"),
|
|
44
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (isPending) {
|
|
50
|
+
return <Loader />;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div className="mx-auto w-full mt-10 max-w-md p-6">
|
|
55
|
+
<h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
|
|
56
|
+
|
|
57
|
+
<form
|
|
58
|
+
onSubmit={(e) => {
|
|
59
|
+
e.preventDefault();
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
void form.handleSubmit();
|
|
62
|
+
}}
|
|
63
|
+
className="space-y-4"
|
|
64
|
+
>
|
|
65
|
+
<div>
|
|
66
|
+
<form.Field name="email">
|
|
67
|
+
{(field) => (
|
|
68
|
+
<div className="space-y-2">
|
|
69
|
+
<Label htmlFor={field.name}>Email</Label>
|
|
70
|
+
<Input
|
|
71
|
+
id={field.name}
|
|
72
|
+
name={field.name}
|
|
73
|
+
type="email"
|
|
74
|
+
value={field.state.value}
|
|
75
|
+
onBlur={field.handleBlur}
|
|
76
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
77
|
+
/>
|
|
78
|
+
{field.state.meta.errors.map((error) => (
|
|
79
|
+
<p key={error?.message} className="text-red-500">
|
|
80
|
+
{error?.message}
|
|
81
|
+
</p>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
</form.Field>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div>
|
|
89
|
+
<form.Field name="password">
|
|
90
|
+
{(field) => (
|
|
91
|
+
<div className="space-y-2">
|
|
92
|
+
<Label htmlFor={field.name}>Password</Label>
|
|
93
|
+
<Input
|
|
94
|
+
id={field.name}
|
|
95
|
+
name={field.name}
|
|
96
|
+
type="password"
|
|
97
|
+
value={field.state.value}
|
|
98
|
+
onBlur={field.handleBlur}
|
|
99
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
100
|
+
/>
|
|
101
|
+
{field.state.meta.errors.map((error) => (
|
|
102
|
+
<p key={error?.message} className="text-red-500">
|
|
103
|
+
{error?.message}
|
|
104
|
+
</p>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
)}
|
|
108
|
+
</form.Field>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<form.Subscribe>
|
|
112
|
+
{(state) => (
|
|
113
|
+
<Button
|
|
114
|
+
type="submit"
|
|
115
|
+
className="w-full"
|
|
116
|
+
disabled={!state.canSubmit || state.isSubmitting}
|
|
117
|
+
>
|
|
118
|
+
{state.isSubmitting ? "Submitting..." : "Sign In"}
|
|
119
|
+
</Button>
|
|
120
|
+
)}
|
|
121
|
+
</form.Subscribe>
|
|
122
|
+
</form>
|
|
123
|
+
|
|
124
|
+
<div className="mt-4 text-center">
|
|
125
|
+
<Button
|
|
126
|
+
variant="link"
|
|
127
|
+
onClick={onSwitchToSignUp}
|
|
128
|
+
className="text-indigo-600 hover:text-indigo-800"
|
|
129
|
+
>
|
|
130
|
+
Need an account? Sign Up
|
|
131
|
+
</Button>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { authClient } from "@/lib/auth-client";
|
|
2
|
+
import { useForm } from "@tanstack/react-form";
|
|
3
|
+
import { useNavigate } from "react-router";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import Loader from "./loader";
|
|
7
|
+
import { Button } from "./ui/button";
|
|
8
|
+
import { Input } from "./ui/input";
|
|
9
|
+
import { Label } from "./ui/label";
|
|
10
|
+
|
|
11
|
+
export default function SignUpForm({
|
|
12
|
+
onSwitchToSignIn,
|
|
13
|
+
}: {
|
|
14
|
+
onSwitchToSignIn: () => void;
|
|
15
|
+
}) {
|
|
16
|
+
const navigate = useNavigate();
|
|
17
|
+
const { isPending } = authClient.useSession();
|
|
18
|
+
|
|
19
|
+
const form = useForm({
|
|
20
|
+
defaultValues: {
|
|
21
|
+
email: "",
|
|
22
|
+
password: "",
|
|
23
|
+
name: "",
|
|
24
|
+
},
|
|
25
|
+
onSubmit: async ({ value }) => {
|
|
26
|
+
await authClient.signUp.email(
|
|
27
|
+
{
|
|
28
|
+
email: value.email,
|
|
29
|
+
password: value.password,
|
|
30
|
+
name: value.name,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
onSuccess: () => {
|
|
34
|
+
navigate("/dashboard");
|
|
35
|
+
toast.success("Sign up successful");
|
|
36
|
+
},
|
|
37
|
+
onError: (error) => {
|
|
38
|
+
toast.error(error.error.message);
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
},
|
|
43
|
+
validators: {
|
|
44
|
+
onSubmit: z.object({
|
|
45
|
+
name: z.string().min(2, "Name must be at least 2 characters"),
|
|
46
|
+
email: z.string().email("Invalid email address"),
|
|
47
|
+
password: z.string().min(6, "Password must be at least 6 characters"),
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (isPending) {
|
|
53
|
+
return <Loader />;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="mx-auto w-full mt-10 max-w-md p-6">
|
|
58
|
+
<h1 className="mb-6 text-center text-3xl font-bold">Create Account</h1>
|
|
59
|
+
|
|
60
|
+
<form
|
|
61
|
+
onSubmit={(e) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
e.stopPropagation();
|
|
64
|
+
void form.handleSubmit();
|
|
65
|
+
}}
|
|
66
|
+
className="space-y-4"
|
|
67
|
+
>
|
|
68
|
+
<div>
|
|
69
|
+
<form.Field name="name">
|
|
70
|
+
{(field) => (
|
|
71
|
+
<div className="space-y-2">
|
|
72
|
+
<Label htmlFor={field.name}>Name</Label>
|
|
73
|
+
<Input
|
|
74
|
+
id={field.name}
|
|
75
|
+
name={field.name}
|
|
76
|
+
value={field.state.value}
|
|
77
|
+
onBlur={field.handleBlur}
|
|
78
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
79
|
+
/>
|
|
80
|
+
{field.state.meta.errors.map((error) => (
|
|
81
|
+
<p key={error?.message} className="text-red-500">
|
|
82
|
+
{error?.message}
|
|
83
|
+
</p>
|
|
84
|
+
))}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
</form.Field>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div>
|
|
91
|
+
<form.Field name="email">
|
|
92
|
+
{(field) => (
|
|
93
|
+
<div className="space-y-2">
|
|
94
|
+
<Label htmlFor={field.name}>Email</Label>
|
|
95
|
+
<Input
|
|
96
|
+
id={field.name}
|
|
97
|
+
name={field.name}
|
|
98
|
+
type="email"
|
|
99
|
+
value={field.state.value}
|
|
100
|
+
onBlur={field.handleBlur}
|
|
101
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
102
|
+
/>
|
|
103
|
+
{field.state.meta.errors.map((error) => (
|
|
104
|
+
<p key={error?.message} className="text-red-500">
|
|
105
|
+
{error?.message}
|
|
106
|
+
</p>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
</form.Field>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div>
|
|
114
|
+
<form.Field name="password">
|
|
115
|
+
{(field) => (
|
|
116
|
+
<div className="space-y-2">
|
|
117
|
+
<Label htmlFor={field.name}>Password</Label>
|
|
118
|
+
<Input
|
|
119
|
+
id={field.name}
|
|
120
|
+
name={field.name}
|
|
121
|
+
type="password"
|
|
122
|
+
value={field.state.value}
|
|
123
|
+
onBlur={field.handleBlur}
|
|
124
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
125
|
+
/>
|
|
126
|
+
{field.state.meta.errors.map((error) => (
|
|
127
|
+
<p key={error?.message} className="text-red-500">
|
|
128
|
+
{error?.message}
|
|
129
|
+
</p>
|
|
130
|
+
))}
|
|
131
|
+
</div>
|
|
132
|
+
)}
|
|
133
|
+
</form.Field>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<form.Subscribe>
|
|
137
|
+
{(state) => (
|
|
138
|
+
<Button
|
|
139
|
+
type="submit"
|
|
140
|
+
className="w-full"
|
|
141
|
+
disabled={!state.canSubmit || state.isSubmitting}
|
|
142
|
+
>
|
|
143
|
+
{state.isSubmitting ? "Submitting..." : "Sign Up"}
|
|
144
|
+
</Button>
|
|
145
|
+
)}
|
|
146
|
+
</form.Subscribe>
|
|
147
|
+
</form>
|
|
148
|
+
|
|
149
|
+
<div className="mt-4 text-center">
|
|
150
|
+
<Button
|
|
151
|
+
variant="link"
|
|
152
|
+
onClick={onSwitchToSignIn}
|
|
153
|
+
className="text-indigo-600 hover:text-indigo-800"
|
|
154
|
+
>
|
|
155
|
+
Already have an account? Sign In
|
|
156
|
+
</Button>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DropdownMenu,
|
|
3
|
+
DropdownMenuContent,
|
|
4
|
+
DropdownMenuItem,
|
|
5
|
+
DropdownMenuLabel,
|
|
6
|
+
DropdownMenuSeparator,
|
|
7
|
+
DropdownMenuTrigger,
|
|
8
|
+
} from "@/components/ui/dropdown-menu";
|
|
9
|
+
import { authClient } from "@/lib/auth-client";
|
|
10
|
+
import { useNavigate } from "react-router";
|
|
11
|
+
import { Button } from "./ui/button";
|
|
12
|
+
import { Skeleton } from "./ui/skeleton";
|
|
13
|
+
import { Link } from "react-router";
|
|
14
|
+
|
|
15
|
+
export default function UserMenu() {
|
|
16
|
+
const navigate = useNavigate();
|
|
17
|
+
const { data: session, isPending } = authClient.useSession();
|
|
18
|
+
|
|
19
|
+
if (isPending) {
|
|
20
|
+
return <Skeleton className="h-9 w-24" />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!session) {
|
|
24
|
+
return (
|
|
25
|
+
<Button variant="outline" asChild>
|
|
26
|
+
<Link to="/login">Sign In</Link>
|
|
27
|
+
</Button>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<DropdownMenu>
|
|
33
|
+
<DropdownMenuTrigger asChild>
|
|
34
|
+
<Button variant="outline">{session.user.name}</Button>
|
|
35
|
+
</DropdownMenuTrigger>
|
|
36
|
+
<DropdownMenuContent className="bg-card">
|
|
37
|
+
<DropdownMenuLabel>My Account</DropdownMenuLabel>
|
|
38
|
+
<DropdownMenuSeparator />
|
|
39
|
+
<DropdownMenuItem>{session.user.email}</DropdownMenuItem>
|
|
40
|
+
<DropdownMenuItem asChild>
|
|
41
|
+
<Button
|
|
42
|
+
variant="destructive"
|
|
43
|
+
className="w-full"
|
|
44
|
+
onClick={() => {
|
|
45
|
+
authClient.signOut({
|
|
46
|
+
fetchOptions: {
|
|
47
|
+
onSuccess: () => {
|
|
48
|
+
navigate("/");
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}}
|
|
53
|
+
>
|
|
54
|
+
Sign Out
|
|
55
|
+
</Button>
|
|
56
|
+
</DropdownMenuItem>
|
|
57
|
+
</DropdownMenuContent>
|
|
58
|
+
</DropdownMenu>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { authClient } from "@/lib/auth-client";
|
|
2
|
+
import { trpc } from "@/utils/trpc";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useEffect } from "react";
|
|
5
|
+
import { useNavigate } from "react-router";
|
|
6
|
+
|
|
7
|
+
export default function Dashboard() {
|
|
8
|
+
const { data: session, isPending } = authClient.useSession();
|
|
9
|
+
const navigate = useNavigate();
|
|
10
|
+
|
|
11
|
+
const privateData = useQuery(trpc.privateData.queryOptions());
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!session && !isPending) {
|
|
15
|
+
navigate("/login");
|
|
16
|
+
}
|
|
17
|
+
}, [session, isPending]);
|
|
18
|
+
|
|
19
|
+
if (isPending) {
|
|
20
|
+
return <div>Loading...</div>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div>
|
|
25
|
+
<h1>Dashboard</h1>
|
|
26
|
+
<p>Welcome {session?.user.name}</p>
|
|
27
|
+
<p>privateData: {privateData.data?.message}</p>
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import SignInForm from "@/components/sign-in-form";
|
|
2
|
+
import SignUpForm from "@/components/sign-up-form";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
|
|
5
|
+
export default function Login() {
|
|
6
|
+
const [showSignIn, setShowSignIn] = useState(false);
|
|
7
|
+
|
|
8
|
+
return showSignIn ? (
|
|
9
|
+
<SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
|
|
10
|
+
) : (
|
|
11
|
+
<SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
|
|
12
|
+
);
|
|
13
|
+
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import { Slot } from "@radix-ui/react-slot"
|
|
3
|
-
import { cva, type VariantProps } from "class-variance-authority"
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
|
|
7
|
-
const buttonVariants = cva(
|
|
8
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
9
|
-
{
|
|
10
|
-
variants: {
|
|
11
|
-
variant: {
|
|
12
|
-
default:
|
|
13
|
-
"bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
|
|
14
|
-
destructive:
|
|
15
|
-
"bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90",
|
|
16
|
-
outline:
|
|
17
|
-
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
18
|
-
secondary:
|
|
19
|
-
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
20
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
21
|
-
link: "text-primary underline-offset-4 hover:underline",
|
|
22
|
-
},
|
|
23
|
-
size: {
|
|
24
|
-
default: "h-9 px-4 py-2",
|
|
25
|
-
sm: "h-8 rounded-md px-3 text-xs",
|
|
26
|
-
lg: "h-10 rounded-md px-8",
|
|
27
|
-
icon: "h-9 w-9",
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
defaultVariants: {
|
|
31
|
-
variant: "default",
|
|
32
|
-
size: "default",
|
|
33
|
-
},
|
|
34
|
-
}
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
export interface ButtonProps
|
|
38
|
-
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
39
|
-
VariantProps<typeof buttonVariants> {
|
|
40
|
-
asChild?: boolean
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
44
|
-
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
45
|
-
const Comp = asChild ? Slot : "button"
|
|
46
|
-
return (
|
|
47
|
-
<Comp
|
|
48
|
-
className={cn(buttonVariants({ variant, size, className }))}
|
|
49
|
-
ref={ref}
|
|
50
|
-
{...props}
|
|
51
|
-
/>
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
)
|
|
55
|
-
Button.displayName = "Button"
|
|
56
|
-
|
|
57
|
-
export { Button, buttonVariants }
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
|
3
|
-
import { Check, ChevronRight, Circle } from "lucide-react"
|
|
4
|
-
|
|
5
|
-
import { cn } from "@/lib/utils"
|
|
6
|
-
|
|
7
|
-
const DropdownMenu = DropdownMenuPrimitive.Root
|
|
8
|
-
|
|
9
|
-
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
|
10
|
-
|
|
11
|
-
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
|
12
|
-
|
|
13
|
-
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
|
14
|
-
|
|
15
|
-
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
|
16
|
-
|
|
17
|
-
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
|
18
|
-
|
|
19
|
-
const DropdownMenuSubTrigger = React.forwardRef<
|
|
20
|
-
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
21
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
22
|
-
inset?: boolean
|
|
23
|
-
}
|
|
24
|
-
>(({ className, inset, children, ...props }, ref) => (
|
|
25
|
-
<DropdownMenuPrimitive.SubTrigger
|
|
26
|
-
ref={ref}
|
|
27
|
-
className={cn(
|
|
28
|
-
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-hidden focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
29
|
-
inset && "pl-8",
|
|
30
|
-
className
|
|
31
|
-
)}
|
|
32
|
-
{...props}
|
|
33
|
-
>
|
|
34
|
-
{children}
|
|
35
|
-
<ChevronRight className="ml-auto" />
|
|
36
|
-
</DropdownMenuPrimitive.SubTrigger>
|
|
37
|
-
))
|
|
38
|
-
DropdownMenuSubTrigger.displayName =
|
|
39
|
-
DropdownMenuPrimitive.SubTrigger.displayName
|
|
40
|
-
|
|
41
|
-
const DropdownMenuSubContent = React.forwardRef<
|
|
42
|
-
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
43
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
44
|
-
>(({ className, ...props }, ref) => (
|
|
45
|
-
<DropdownMenuPrimitive.SubContent
|
|
46
|
-
ref={ref}
|
|
47
|
-
className={cn(
|
|
48
|
-
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
49
|
-
className
|
|
50
|
-
)}
|
|
51
|
-
{...props}
|
|
52
|
-
/>
|
|
53
|
-
))
|
|
54
|
-
DropdownMenuSubContent.displayName =
|
|
55
|
-
DropdownMenuPrimitive.SubContent.displayName
|
|
56
|
-
|
|
57
|
-
const DropdownMenuContent = React.forwardRef<
|
|
58
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
59
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
60
|
-
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
61
|
-
<DropdownMenuPrimitive.Portal>
|
|
62
|
-
<DropdownMenuPrimitive.Content
|
|
63
|
-
ref={ref}
|
|
64
|
-
sideOffset={sideOffset}
|
|
65
|
-
className={cn(
|
|
66
|
-
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
|
|
67
|
-
"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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
68
|
-
className
|
|
69
|
-
)}
|
|
70
|
-
{...props}
|
|
71
|
-
/>
|
|
72
|
-
</DropdownMenuPrimitive.Portal>
|
|
73
|
-
))
|
|
74
|
-
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
|
75
|
-
|
|
76
|
-
const DropdownMenuItem = React.forwardRef<
|
|
77
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
78
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
79
|
-
inset?: boolean
|
|
80
|
-
}
|
|
81
|
-
>(({ className, inset, ...props }, ref) => (
|
|
82
|
-
<DropdownMenuPrimitive.Item
|
|
83
|
-
ref={ref}
|
|
84
|
-
className={cn(
|
|
85
|
-
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
86
|
-
inset && "pl-8",
|
|
87
|
-
className
|
|
88
|
-
)}
|
|
89
|
-
{...props}
|
|
90
|
-
/>
|
|
91
|
-
))
|
|
92
|
-
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
|
93
|
-
|
|
94
|
-
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
95
|
-
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
96
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
97
|
-
>(({ className, children, checked, ...props }, ref) => (
|
|
98
|
-
<DropdownMenuPrimitive.CheckboxItem
|
|
99
|
-
ref={ref}
|
|
100
|
-
className={cn(
|
|
101
|
-
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
102
|
-
className
|
|
103
|
-
)}
|
|
104
|
-
checked={checked}
|
|
105
|
-
{...props}
|
|
106
|
-
>
|
|
107
|
-
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
108
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
109
|
-
<Check className="h-4 w-4" />
|
|
110
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
111
|
-
</span>
|
|
112
|
-
{children}
|
|
113
|
-
</DropdownMenuPrimitive.CheckboxItem>
|
|
114
|
-
))
|
|
115
|
-
DropdownMenuCheckboxItem.displayName =
|
|
116
|
-
DropdownMenuPrimitive.CheckboxItem.displayName
|
|
117
|
-
|
|
118
|
-
const DropdownMenuRadioItem = React.forwardRef<
|
|
119
|
-
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
120
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
121
|
-
>(({ className, children, ...props }, ref) => (
|
|
122
|
-
<DropdownMenuPrimitive.RadioItem
|
|
123
|
-
ref={ref}
|
|
124
|
-
className={cn(
|
|
125
|
-
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden transition-colors focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50",
|
|
126
|
-
className
|
|
127
|
-
)}
|
|
128
|
-
{...props}
|
|
129
|
-
>
|
|
130
|
-
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
131
|
-
<DropdownMenuPrimitive.ItemIndicator>
|
|
132
|
-
<Circle className="h-2 w-2 fill-current" />
|
|
133
|
-
</DropdownMenuPrimitive.ItemIndicator>
|
|
134
|
-
</span>
|
|
135
|
-
{children}
|
|
136
|
-
</DropdownMenuPrimitive.RadioItem>
|
|
137
|
-
))
|
|
138
|
-
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
|
139
|
-
|
|
140
|
-
const DropdownMenuLabel = React.forwardRef<
|
|
141
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
142
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
143
|
-
inset?: boolean
|
|
144
|
-
}
|
|
145
|
-
>(({ className, inset, ...props }, ref) => (
|
|
146
|
-
<DropdownMenuPrimitive.Label
|
|
147
|
-
ref={ref}
|
|
148
|
-
className={cn(
|
|
149
|
-
"px-2 py-1.5 text-sm font-semibold",
|
|
150
|
-
inset && "pl-8",
|
|
151
|
-
className
|
|
152
|
-
)}
|
|
153
|
-
{...props}
|
|
154
|
-
/>
|
|
155
|
-
))
|
|
156
|
-
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
|
157
|
-
|
|
158
|
-
const DropdownMenuSeparator = React.forwardRef<
|
|
159
|
-
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
160
|
-
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
161
|
-
>(({ className, ...props }, ref) => (
|
|
162
|
-
<DropdownMenuPrimitive.Separator
|
|
163
|
-
ref={ref}
|
|
164
|
-
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
|
165
|
-
{...props}
|
|
166
|
-
/>
|
|
167
|
-
))
|
|
168
|
-
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
|
169
|
-
|
|
170
|
-
const DropdownMenuShortcut = ({
|
|
171
|
-
className,
|
|
172
|
-
...props
|
|
173
|
-
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
174
|
-
return (
|
|
175
|
-
<span
|
|
176
|
-
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
|
177
|
-
{...props}
|
|
178
|
-
/>
|
|
179
|
-
)
|
|
180
|
-
}
|
|
181
|
-
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
|
182
|
-
|
|
183
|
-
export {
|
|
184
|
-
DropdownMenu,
|
|
185
|
-
DropdownMenuTrigger,
|
|
186
|
-
DropdownMenuContent,
|
|
187
|
-
DropdownMenuItem,
|
|
188
|
-
DropdownMenuCheckboxItem,
|
|
189
|
-
DropdownMenuRadioItem,
|
|
190
|
-
DropdownMenuLabel,
|
|
191
|
-
DropdownMenuSeparator,
|
|
192
|
-
DropdownMenuShortcut,
|
|
193
|
-
DropdownMenuGroup,
|
|
194
|
-
DropdownMenuPortal,
|
|
195
|
-
DropdownMenuSub,
|
|
196
|
-
DropdownMenuSubContent,
|
|
197
|
-
DropdownMenuSubTrigger,
|
|
198
|
-
DropdownMenuRadioGroup,
|
|
199
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import * as React from "react"
|
|
2
|
-
|
|
3
|
-
import { cn } from "@/lib/utils"
|
|
4
|
-
|
|
5
|
-
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
6
|
-
({ className, type, ...props }, ref) => {
|
|
7
|
-
return (
|
|
8
|
-
<input
|
|
9
|
-
type={type}
|
|
10
|
-
className={cn(
|
|
11
|
-
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
12
|
-
className
|
|
13
|
-
)}
|
|
14
|
-
ref={ref}
|
|
15
|
-
{...props}
|
|
16
|
-
/>
|
|
17
|
-
)
|
|
18
|
-
}
|
|
19
|
-
)
|
|
20
|
-
Input.displayName = "Input"
|
|
21
|
-
|
|
22
|
-
export { Input }
|