@majapisoftwares/ui 5.0.0 → 6.1.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/AGENTS.md +11 -1
- package/README.md +19 -11
- package/THIRD_PARTY_NOTICES.md +25 -1
- package/components.json +1 -1
- package/docs/MIGRATION-6.0.md +33 -0
- package/docs/STANDARDS.md +11 -6
- package/docs/VERSIONING.md +28 -0
- package/package.json +19 -10
- package/scripts/check-standards.mjs +2 -0
- package/src/components/ui/accordion.tsx +82 -0
- package/src/components/ui/alert-dialog.tsx +187 -0
- package/src/components/ui/alert.tsx +2 -2
- package/src/components/ui/avatar.tsx +108 -0
- package/src/components/ui/badge.tsx +1 -1
- package/src/components/ui/breadcrumb.tsx +125 -0
- package/src/components/ui/button-group.tsx +89 -0
- package/src/components/ui/button.tsx +12 -16
- package/src/components/ui/card.tsx +101 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/collapsible.tsx +21 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/dialog.tsx +161 -0
- package/src/components/ui/dropdown-menu.tsx +271 -0
- package/src/components/ui/empty.tsx +4 -4
- package/src/components/ui/field.tsx +6 -6
- package/src/components/ui/hover-card.tsx +50 -0
- package/src/components/ui/input-group.tsx +155 -0
- package/src/components/ui/input.tsx +1 -1
- package/src/components/ui/kbd.tsx +28 -0
- package/src/components/ui/orb.css +1002 -0
- package/src/components/ui/orb.tsx +752 -0
- package/src/components/ui/popover.tsx +89 -0
- package/src/components/ui/progress.tsx +82 -0
- package/src/components/ui/radio-group.tsx +37 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/select.tsx +204 -0
- package/src/components/ui/sheet.tsx +8 -6
- package/src/components/ui/sidebar.tsx +1035 -0
- package/src/components/ui/skeleton.tsx +1 -1
- package/src/components/ui/slider.tsx +53 -0
- package/src/components/ui/sonner.tsx +44 -0
- package/src/components/ui/spinner.tsx +32 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +10 -4
- package/src/components/ui/tabs.tsx +2 -2
- package/src/components/ui/textarea.tsx +19 -0
- package/src/components/ui/toggle-group.tsx +89 -0
- package/src/components/ui/toggle.tsx +44 -0
- package/src/components/ui/tooltip.tsx +65 -0
- package/src/hooks/use-mobile.ts +20 -0
- package/src/lib/i18n.tsx +109 -0
- package/src/patterns/app-shell.tsx +162 -53
- package/src/patterns/data-table.tsx +2441 -180
- package/src/patterns/details-sheet.tsx +10 -7
- package/src/patterns/filter-tabs.tsx +53 -0
- package/src/patterns/majapi-logo.tsx +32 -0
- package/src/patterns/page-header.tsx +44 -22
- package/src/patterns/preferences.tsx +172 -0
- package/src/patterns/status-badge.tsx +4 -3
- package/src/patterns/user-menu.tsx +218 -0
- package/src/patterns/workspace-switcher.tsx +173 -0
- package/src/styles.css +41 -341
- package/src/templates/admin-dashboard.tsx +14 -33
- package/src/templates/auth-layout.tsx +18 -24
- package/src/templates/login-form.tsx +97 -65
- package/src/templates/signup-form.tsx +139 -99
- package/src/components/ui/native-select.tsx +0 -66
- package/src/vite-env.d.ts +0 -1
|
@@ -1,36 +1,30 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
/** Centered auth page: brand on top, form card below, optional footer. */
|
|
2
4
|
export function AuthLayout({
|
|
5
|
+
brand,
|
|
3
6
|
children,
|
|
4
|
-
brand = "workbench",
|
|
5
|
-
brandMark = "w.",
|
|
6
|
-
title,
|
|
7
|
-
description,
|
|
8
|
-
aside,
|
|
9
7
|
footer,
|
|
10
8
|
}: {
|
|
9
|
+
brand?: ReactNode;
|
|
11
10
|
children: ReactNode;
|
|
12
|
-
brand?: string;
|
|
13
|
-
brandMark?: ReactNode;
|
|
14
|
-
title: string;
|
|
15
|
-
description: string;
|
|
16
|
-
aside?: ReactNode;
|
|
17
11
|
footer?: ReactNode;
|
|
18
12
|
}) {
|
|
19
13
|
return (
|
|
20
|
-
<main className="
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
14
|
+
<main className="flex min-h-svh flex-col items-center justify-center gap-6 bg-muted p-6 md:p-10">
|
|
15
|
+
<div className="flex w-full max-w-sm flex-col gap-6">
|
|
16
|
+
{brand ? (
|
|
17
|
+
<div className="flex items-center gap-2 self-center font-medium">
|
|
18
|
+
{brand}
|
|
19
|
+
</div>
|
|
20
|
+
) : null}
|
|
21
|
+
{children}
|
|
22
|
+
{footer ? (
|
|
23
|
+
<div className="text-center text-xs text-balance text-muted-foreground">
|
|
24
|
+
{footer}
|
|
25
|
+
</div>
|
|
26
|
+
) : null}
|
|
27
|
+
</div>
|
|
34
28
|
</main>
|
|
35
29
|
);
|
|
36
30
|
}
|
|
@@ -3,13 +3,33 @@
|
|
|
3
3
|
import { useId, type ReactNode } from "react";
|
|
4
4
|
import { Button } from "../components/ui/button";
|
|
5
5
|
import { Input } from "../components/ui/input";
|
|
6
|
+
import { Field, FieldGroup, FieldLabel } from "../components/ui/field";
|
|
7
|
+
import { Alert, AlertDescription } from "../components/ui/alert";
|
|
6
8
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Card,
|
|
10
|
+
CardContent,
|
|
11
|
+
CardDescription,
|
|
12
|
+
CardHeader,
|
|
13
|
+
CardTitle,
|
|
14
|
+
} from "../components/ui/card";
|
|
15
|
+
import { defineMessages, useMessages } from "../lib/i18n";
|
|
16
|
+
|
|
17
|
+
const messages = defineMessages({
|
|
18
|
+
en: {
|
|
19
|
+
title: "Welcome back",
|
|
20
|
+
email: "Email",
|
|
21
|
+
password: "Password",
|
|
22
|
+
submit: "Log in",
|
|
23
|
+
pending: "Logging in…",
|
|
24
|
+
},
|
|
25
|
+
"pt-BR": {
|
|
26
|
+
title: "Bem-vindo de volta",
|
|
27
|
+
email: "E-mail",
|
|
28
|
+
password: "Senha",
|
|
29
|
+
submit: "Entrar",
|
|
30
|
+
pending: "Entrando…",
|
|
31
|
+
},
|
|
32
|
+
});
|
|
13
33
|
|
|
14
34
|
export interface LoginValues {
|
|
15
35
|
email: string;
|
|
@@ -22,76 +42,88 @@ export interface LoginFormProps {
|
|
|
22
42
|
title?: string;
|
|
23
43
|
description?: string;
|
|
24
44
|
submitLabel?: string;
|
|
25
|
-
|
|
45
|
+
/** Shown next to the password label, e.g. a "Forgot password?" link. */
|
|
26
46
|
recovery?: ReactNode;
|
|
47
|
+
/** Shown above the fields, e.g. OAuth buttons. */
|
|
27
48
|
alternative?: ReactNode;
|
|
49
|
+
footer?: ReactNode;
|
|
28
50
|
}
|
|
29
51
|
export function LoginForm({
|
|
30
52
|
onSubmit,
|
|
31
53
|
pending = false,
|
|
32
54
|
error,
|
|
33
|
-
title
|
|
34
|
-
description
|
|
35
|
-
submitLabel
|
|
36
|
-
footer,
|
|
55
|
+
title,
|
|
56
|
+
description,
|
|
57
|
+
submitLabel,
|
|
37
58
|
recovery,
|
|
38
59
|
alternative,
|
|
60
|
+
footer,
|
|
39
61
|
}: LoginFormProps) {
|
|
40
62
|
const id = useId();
|
|
63
|
+
const t = useMessages(messages);
|
|
41
64
|
return (
|
|
42
|
-
<
|
|
43
|
-
className="
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
65
|
+
<Card>
|
|
66
|
+
<CardHeader className="text-center">
|
|
67
|
+
<CardTitle className="text-xl">{title ?? t.title}</CardTitle>
|
|
68
|
+
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
69
|
+
</CardHeader>
|
|
70
|
+
<CardContent>
|
|
71
|
+
<form
|
|
72
|
+
aria-busy={pending}
|
|
73
|
+
onSubmit={(event) => {
|
|
74
|
+
event.preventDefault();
|
|
75
|
+
const data = new FormData(event.currentTarget);
|
|
76
|
+
onSubmit({
|
|
77
|
+
email: String(data.get("email") ?? ""),
|
|
78
|
+
password: String(data.get("password") ?? ""),
|
|
79
|
+
});
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
<FieldGroup>
|
|
83
|
+
{alternative}
|
|
84
|
+
{error ? (
|
|
85
|
+
<Alert variant="destructive">
|
|
86
|
+
<AlertDescription>{error}</AlertDescription>
|
|
87
|
+
</Alert>
|
|
88
|
+
) : null}
|
|
89
|
+
<Field>
|
|
90
|
+
<FieldLabel htmlFor={`${id}-email`}>{t.email}</FieldLabel>
|
|
91
|
+
<Input
|
|
92
|
+
id={`${id}-email`}
|
|
93
|
+
name="email"
|
|
94
|
+
type="email"
|
|
95
|
+
autoComplete="username"
|
|
96
|
+
required
|
|
97
|
+
disabled={pending}
|
|
98
|
+
/>
|
|
99
|
+
</Field>
|
|
100
|
+
<Field>
|
|
101
|
+
<div className="flex items-center">
|
|
102
|
+
<FieldLabel htmlFor={`${id}-password`}>{t.password}</FieldLabel>
|
|
103
|
+
{recovery ? (
|
|
104
|
+
<div className="ml-auto text-sm">{recovery}</div>
|
|
105
|
+
) : null}
|
|
106
|
+
</div>
|
|
107
|
+
<Input
|
|
108
|
+
id={`${id}-password`}
|
|
109
|
+
name="password"
|
|
110
|
+
type="password"
|
|
111
|
+
autoComplete="current-password"
|
|
112
|
+
required
|
|
113
|
+
disabled={pending}
|
|
114
|
+
/>
|
|
115
|
+
</Field>
|
|
116
|
+
<Button type="submit" disabled={pending}>
|
|
117
|
+
{pending ? t.pending : (submitLabel ?? t.submit)}
|
|
118
|
+
</Button>
|
|
119
|
+
{footer ? (
|
|
120
|
+
<div className="text-center text-sm text-muted-foreground">
|
|
121
|
+
{footer}
|
|
122
|
+
</div>
|
|
123
|
+
) : null}
|
|
124
|
+
</FieldGroup>
|
|
125
|
+
</form>
|
|
126
|
+
</CardContent>
|
|
127
|
+
</Card>
|
|
96
128
|
);
|
|
97
129
|
}
|
|
@@ -5,11 +5,44 @@ import { Button } from "../components/ui/button";
|
|
|
5
5
|
import { Input } from "../components/ui/input";
|
|
6
6
|
import {
|
|
7
7
|
Field,
|
|
8
|
+
FieldDescription,
|
|
8
9
|
FieldGroup,
|
|
9
10
|
FieldLabel,
|
|
10
|
-
FieldDescription,
|
|
11
11
|
} from "../components/ui/field";
|
|
12
|
-
import { Alert,
|
|
12
|
+
import { Alert, AlertDescription } from "../components/ui/alert";
|
|
13
|
+
import {
|
|
14
|
+
Card,
|
|
15
|
+
CardContent,
|
|
16
|
+
CardDescription,
|
|
17
|
+
CardHeader,
|
|
18
|
+
CardTitle,
|
|
19
|
+
} from "../components/ui/card";
|
|
20
|
+
import { defineMessages, useMessages } from "../lib/i18n";
|
|
21
|
+
|
|
22
|
+
const messages = defineMessages({
|
|
23
|
+
en: {
|
|
24
|
+
title: "Create an account",
|
|
25
|
+
name: "Name",
|
|
26
|
+
email: "Email",
|
|
27
|
+
password: "Password",
|
|
28
|
+
confirm: "Confirm password",
|
|
29
|
+
minLength: (n: number) => `At least ${n} characters.`,
|
|
30
|
+
mismatch: "Passwords don't match.",
|
|
31
|
+
submit: "Create account",
|
|
32
|
+
pending: "Creating account…",
|
|
33
|
+
},
|
|
34
|
+
"pt-BR": {
|
|
35
|
+
title: "Criar conta",
|
|
36
|
+
name: "Nome",
|
|
37
|
+
email: "E-mail",
|
|
38
|
+
password: "Senha",
|
|
39
|
+
confirm: "Confirmar senha",
|
|
40
|
+
minLength: (n: number) => `Pelo menos ${n} caracteres.`,
|
|
41
|
+
mismatch: "As senhas não coincidem.",
|
|
42
|
+
submit: "Criar conta",
|
|
43
|
+
pending: "Criando conta…",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
13
46
|
|
|
14
47
|
export interface SignupValues {
|
|
15
48
|
name: string;
|
|
@@ -24,114 +57,121 @@ export interface SignupFormProps {
|
|
|
24
57
|
description?: string;
|
|
25
58
|
submitLabel?: string;
|
|
26
59
|
minPasswordLength?: number;
|
|
27
|
-
footer?: ReactNode;
|
|
28
60
|
terms?: ReactNode;
|
|
61
|
+
footer?: ReactNode;
|
|
29
62
|
}
|
|
30
63
|
export function SignupForm({
|
|
31
64
|
onSubmit,
|
|
32
65
|
pending = false,
|
|
33
66
|
error,
|
|
34
|
-
title
|
|
35
|
-
description
|
|
36
|
-
submitLabel
|
|
67
|
+
title,
|
|
68
|
+
description,
|
|
69
|
+
submitLabel,
|
|
37
70
|
minPasswordLength = 12,
|
|
38
|
-
footer,
|
|
39
71
|
terms,
|
|
72
|
+
footer,
|
|
40
73
|
}: SignupFormProps) {
|
|
41
74
|
const id = useId(),
|
|
75
|
+
t = useMessages(messages),
|
|
42
76
|
[mismatch, setMismatch] = useState(false);
|
|
43
77
|
return (
|
|
44
|
-
<
|
|
45
|
-
className="
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
78
|
+
<Card>
|
|
79
|
+
<CardHeader className="text-center">
|
|
80
|
+
<CardTitle className="text-xl">{title ?? t.title}</CardTitle>
|
|
81
|
+
{description ? <CardDescription>{description}</CardDescription> : null}
|
|
82
|
+
</CardHeader>
|
|
83
|
+
<CardContent>
|
|
84
|
+
<form
|
|
85
|
+
aria-busy={pending}
|
|
86
|
+
onSubmit={(event) => {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
const data = new FormData(event.currentTarget);
|
|
89
|
+
const password = String(data.get("password") ?? "");
|
|
90
|
+
if (password !== String(data.get("confirmPassword") ?? "")) {
|
|
91
|
+
setMismatch(true);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
setMismatch(false);
|
|
95
|
+
onSubmit({
|
|
96
|
+
name: String(data.get("name") ?? ""),
|
|
97
|
+
email: String(data.get("email") ?? ""),
|
|
98
|
+
password,
|
|
99
|
+
});
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<FieldGroup>
|
|
103
|
+
{error ? (
|
|
104
|
+
<Alert variant="destructive">
|
|
105
|
+
<AlertDescription>{error}</AlertDescription>
|
|
106
|
+
</Alert>
|
|
107
|
+
) : null}
|
|
108
|
+
<Field>
|
|
109
|
+
<FieldLabel htmlFor={`${id}-name`}>{t.name}</FieldLabel>
|
|
110
|
+
<Input
|
|
111
|
+
id={`${id}-name`}
|
|
112
|
+
name="name"
|
|
113
|
+
autoComplete="name"
|
|
114
|
+
required
|
|
115
|
+
disabled={pending}
|
|
116
|
+
/>
|
|
117
|
+
</Field>
|
|
118
|
+
<Field>
|
|
119
|
+
<FieldLabel htmlFor={`${id}-email`}>{t.email}</FieldLabel>
|
|
120
|
+
<Input
|
|
121
|
+
id={`${id}-email`}
|
|
122
|
+
name="email"
|
|
123
|
+
type="email"
|
|
124
|
+
autoComplete="email"
|
|
125
|
+
required
|
|
126
|
+
disabled={pending}
|
|
127
|
+
/>
|
|
128
|
+
</Field>
|
|
129
|
+
<Field>
|
|
130
|
+
<FieldLabel htmlFor={`${id}-password`}>{t.password}</FieldLabel>
|
|
131
|
+
<Input
|
|
132
|
+
id={`${id}-password`}
|
|
133
|
+
name="password"
|
|
134
|
+
type="password"
|
|
135
|
+
autoComplete="new-password"
|
|
136
|
+
required
|
|
137
|
+
minLength={minPasswordLength}
|
|
138
|
+
disabled={pending}
|
|
139
|
+
/>
|
|
140
|
+
<FieldDescription>
|
|
141
|
+
{t.minLength(minPasswordLength)}
|
|
142
|
+
</FieldDescription>
|
|
143
|
+
</Field>
|
|
144
|
+
<Field data-invalid={mismatch || undefined}>
|
|
145
|
+
<FieldLabel htmlFor={`${id}-confirm`}>{t.confirm}</FieldLabel>
|
|
146
|
+
<Input
|
|
147
|
+
id={`${id}-confirm`}
|
|
148
|
+
name="confirmPassword"
|
|
149
|
+
type="password"
|
|
150
|
+
autoComplete="new-password"
|
|
151
|
+
required
|
|
152
|
+
aria-invalid={mismatch || undefined}
|
|
153
|
+
aria-describedby={mismatch ? `${id}-mismatch` : undefined}
|
|
154
|
+
disabled={pending}
|
|
155
|
+
onChange={() => setMismatch(false)}
|
|
156
|
+
/>
|
|
157
|
+
{mismatch ? (
|
|
158
|
+
<FieldDescription id={`${id}-mismatch`} role="alert">
|
|
159
|
+
{t.mismatch}
|
|
160
|
+
</FieldDescription>
|
|
161
|
+
) : null}
|
|
162
|
+
</Field>
|
|
163
|
+
{terms ? <FieldDescription>{terms}</FieldDescription> : null}
|
|
164
|
+
<Button type="submit" disabled={pending}>
|
|
165
|
+
{pending ? t.pending : (submitLabel ?? t.submit)}
|
|
166
|
+
</Button>
|
|
167
|
+
{footer ? (
|
|
168
|
+
<div className="text-center text-sm text-muted-foreground">
|
|
169
|
+
{footer}
|
|
170
|
+
</div>
|
|
171
|
+
) : null}
|
|
172
|
+
</FieldGroup>
|
|
173
|
+
</form>
|
|
174
|
+
</CardContent>
|
|
175
|
+
</Card>
|
|
136
176
|
);
|
|
137
177
|
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { cn } from "cn";
|
|
5
|
-
import { ChevronDownIcon } from "lucide-react";
|
|
6
|
-
|
|
7
|
-
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
|
|
8
|
-
size?: "sm" | "default";
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
function NativeSelect({
|
|
12
|
-
className,
|
|
13
|
-
size = "default",
|
|
14
|
-
...props
|
|
15
|
-
}: NativeSelectProps) {
|
|
16
|
-
return (
|
|
17
|
-
<div
|
|
18
|
-
className={cn(
|
|
19
|
-
"group/native-select relative w-fit has-[select:disabled]:opacity-50",
|
|
20
|
-
className,
|
|
21
|
-
)}
|
|
22
|
-
data-slot="native-select-wrapper"
|
|
23
|
-
data-size={size}
|
|
24
|
-
>
|
|
25
|
-
<select
|
|
26
|
-
data-slot="native-select"
|
|
27
|
-
data-size={size}
|
|
28
|
-
className="h-8 w-full min-w-0 appearance-none rounded-lg border border-input bg-transparent py-1 pr-8 pl-2.5 text-sm transition-colors outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
|
|
29
|
-
{...props}
|
|
30
|
-
/>
|
|
31
|
-
<ChevronDownIcon
|
|
32
|
-
className="pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 text-muted-foreground select-none"
|
|
33
|
-
aria-hidden="true"
|
|
34
|
-
data-slot="native-select-icon"
|
|
35
|
-
/>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function NativeSelectOption({
|
|
41
|
-
className,
|
|
42
|
-
...props
|
|
43
|
-
}: React.ComponentProps<"option">) {
|
|
44
|
-
return (
|
|
45
|
-
<option
|
|
46
|
-
data-slot="native-select-option"
|
|
47
|
-
className={cn("bg-[Canvas] text-[CanvasText]", className)}
|
|
48
|
-
{...props}
|
|
49
|
-
/>
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function NativeSelectOptGroup({
|
|
54
|
-
className,
|
|
55
|
-
...props
|
|
56
|
-
}: React.ComponentProps<"optgroup">) {
|
|
57
|
-
return (
|
|
58
|
-
<optgroup
|
|
59
|
-
data-slot="native-select-optgroup"
|
|
60
|
-
className={cn("bg-[Canvas] text-[CanvasText]", className)}
|
|
61
|
-
{...props}
|
|
62
|
-
/>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { NativeSelect, NativeSelectOptGroup, NativeSelectOption };
|
package/src/vite-env.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/// <reference types="vite/client" />
|