@majapisoftwares/ui 6.1.0 → 6.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/AGENTS.md +27 -27
  2. package/README.md +79 -79
  3. package/THIRD_PARTY_NOTICES.md +39 -39
  4. package/components.json +25 -25
  5. package/docs/MIGRATION-0.2.md +20 -20
  6. package/docs/MIGRATION-6.0.md +33 -33
  7. package/docs/NEXT-STARTER-VALIDATION.md +14 -14
  8. package/docs/STANDARDS.md +41 -41
  9. package/docs/VALIDATION.md +11 -11
  10. package/docs/VERSIONING.md +28 -28
  11. package/docs/validation/preference-titles.md +22 -0
  12. package/package.json +88 -84
  13. package/scripts/check-standards.mjs +41 -41
  14. package/src/components/ui/accordion.tsx +82 -82
  15. package/src/components/ui/alert-dialog.tsx +187 -187
  16. package/src/components/ui/alert.tsx +77 -77
  17. package/src/components/ui/avatar.tsx +108 -108
  18. package/src/components/ui/badge.tsx +55 -55
  19. package/src/components/ui/breadcrumb.tsx +125 -125
  20. package/src/components/ui/button-group.tsx +89 -89
  21. package/src/components/ui/button.tsx +59 -59
  22. package/src/components/ui/card.tsx +101 -101
  23. package/src/components/ui/checkbox.tsx +27 -27
  24. package/src/components/ui/collapsible.tsx +21 -21
  25. package/src/components/ui/context-menu.tsx +271 -271
  26. package/src/components/ui/dialog.tsx +161 -161
  27. package/src/components/ui/dropdown-menu.tsx +271 -271
  28. package/src/components/ui/empty.tsx +105 -105
  29. package/src/components/ui/field.tsx +238 -238
  30. package/src/components/ui/hover-card.tsx +50 -50
  31. package/src/components/ui/input-group.tsx +155 -155
  32. package/src/components/ui/input.tsx +21 -21
  33. package/src/components/ui/kbd.tsx +28 -28
  34. package/src/components/ui/label.tsx +19 -19
  35. package/src/components/ui/orb.css +1002 -1002
  36. package/src/components/ui/orb.tsx +752 -752
  37. package/src/components/ui/popover.tsx +89 -89
  38. package/src/components/ui/progress.tsx +82 -82
  39. package/src/components/ui/radio-group.tsx +37 -37
  40. package/src/components/ui/scroll-area.tsx +54 -54
  41. package/src/components/ui/select.tsx +204 -204
  42. package/src/components/ui/separator.tsx +24 -24
  43. package/src/components/ui/sheet.tsx +139 -139
  44. package/src/components/ui/sidebar.tsx +1039 -1035
  45. package/src/components/ui/skeleton.tsx +15 -15
  46. package/src/components/ui/slider.tsx +53 -53
  47. package/src/components/ui/sonner.tsx +44 -44
  48. package/src/components/ui/spinner.tsx +32 -32
  49. package/src/components/ui/switch.tsx +31 -31
  50. package/src/components/ui/table.tsx +121 -121
  51. package/src/components/ui/tabs.tsx +81 -81
  52. package/src/components/ui/textarea.tsx +19 -19
  53. package/src/components/ui/toggle-group.tsx +89 -89
  54. package/src/components/ui/toggle.tsx +44 -44
  55. package/src/components/ui/tooltip.tsx +65 -65
  56. package/src/hooks/use-mobile.ts +20 -20
  57. package/src/lib/i18n.tsx +109 -109
  58. package/src/lib/utils.ts +1 -1
  59. package/src/patterns/app-shell.tsx +177 -179
  60. package/src/patterns/data-table.tsx +2486 -2486
  61. package/src/patterns/details-sheet.tsx +54 -54
  62. package/src/patterns/filter-tabs.tsx +53 -53
  63. package/src/patterns/majapi-logo.tsx +32 -32
  64. package/src/patterns/page-header.tsx +62 -62
  65. package/src/patterns/preferences.tsx +15 -7
  66. package/src/patterns/status-badge.tsx +24 -24
  67. package/src/patterns/user-menu.tsx +218 -218
  68. package/src/patterns/workspace-switcher.tsx +176 -173
  69. package/src/styles.css +165 -165
  70. package/src/templates/admin-dashboard.tsx +44 -44
  71. package/src/templates/auth-layout.tsx +30 -30
  72. package/src/templates/login-form.tsx +129 -129
  73. package/src/templates/signup-form.tsx +177 -177
@@ -1,129 +1,129 @@
1
- "use client";
2
-
3
- import { useId, type ReactNode } from "react";
4
- import { Button } from "../components/ui/button";
5
- import { Input } from "../components/ui/input";
6
- import { Field, FieldGroup, FieldLabel } from "../components/ui/field";
7
- import { Alert, AlertDescription } from "../components/ui/alert";
8
- import {
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
- });
33
-
34
- export interface LoginValues {
35
- email: string;
36
- password: string;
37
- }
38
- export interface LoginFormProps {
39
- onSubmit: (values: LoginValues) => void;
40
- pending?: boolean;
41
- error?: string;
42
- title?: string;
43
- description?: string;
44
- submitLabel?: string;
45
- /** Shown next to the password label, e.g. a "Forgot password?" link. */
46
- recovery?: ReactNode;
47
- /** Shown above the fields, e.g. OAuth buttons. */
48
- alternative?: ReactNode;
49
- footer?: ReactNode;
50
- }
51
- export function LoginForm({
52
- onSubmit,
53
- pending = false,
54
- error,
55
- title,
56
- description,
57
- submitLabel,
58
- recovery,
59
- alternative,
60
- footer,
61
- }: LoginFormProps) {
62
- const id = useId();
63
- const t = useMessages(messages);
64
- return (
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>
128
- );
129
- }
1
+ "use client";
2
+
3
+ import { useId, type ReactNode } from "react";
4
+ import { Button } from "../components/ui/button";
5
+ import { Input } from "../components/ui/input";
6
+ import { Field, FieldGroup, FieldLabel } from "../components/ui/field";
7
+ import { Alert, AlertDescription } from "../components/ui/alert";
8
+ import {
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
+ });
33
+
34
+ export interface LoginValues {
35
+ email: string;
36
+ password: string;
37
+ }
38
+ export interface LoginFormProps {
39
+ onSubmit: (values: LoginValues) => void;
40
+ pending?: boolean;
41
+ error?: string;
42
+ title?: string;
43
+ description?: string;
44
+ submitLabel?: string;
45
+ /** Shown next to the password label, e.g. a "Forgot password?" link. */
46
+ recovery?: ReactNode;
47
+ /** Shown above the fields, e.g. OAuth buttons. */
48
+ alternative?: ReactNode;
49
+ footer?: ReactNode;
50
+ }
51
+ export function LoginForm({
52
+ onSubmit,
53
+ pending = false,
54
+ error,
55
+ title,
56
+ description,
57
+ submitLabel,
58
+ recovery,
59
+ alternative,
60
+ footer,
61
+ }: LoginFormProps) {
62
+ const id = useId();
63
+ const t = useMessages(messages);
64
+ return (
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>
128
+ );
129
+ }
@@ -1,177 +1,177 @@
1
- "use client";
2
-
3
- import { useId, useState, type ReactNode } from "react";
4
- import { Button } from "../components/ui/button";
5
- import { Input } from "../components/ui/input";
6
- import {
7
- Field,
8
- FieldDescription,
9
- FieldGroup,
10
- FieldLabel,
11
- } from "../components/ui/field";
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
- });
46
-
47
- export interface SignupValues {
48
- name: string;
49
- email: string;
50
- password: string;
51
- }
52
- export interface SignupFormProps {
53
- onSubmit: (values: SignupValues) => void;
54
- pending?: boolean;
55
- error?: string;
56
- title?: string;
57
- description?: string;
58
- submitLabel?: string;
59
- minPasswordLength?: number;
60
- terms?: ReactNode;
61
- footer?: ReactNode;
62
- }
63
- export function SignupForm({
64
- onSubmit,
65
- pending = false,
66
- error,
67
- title,
68
- description,
69
- submitLabel,
70
- minPasswordLength = 12,
71
- terms,
72
- footer,
73
- }: SignupFormProps) {
74
- const id = useId(),
75
- t = useMessages(messages),
76
- [mismatch, setMismatch] = useState(false);
77
- return (
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>
176
- );
177
- }
1
+ "use client";
2
+
3
+ import { useId, useState, type ReactNode } from "react";
4
+ import { Button } from "../components/ui/button";
5
+ import { Input } from "../components/ui/input";
6
+ import {
7
+ Field,
8
+ FieldDescription,
9
+ FieldGroup,
10
+ FieldLabel,
11
+ } from "../components/ui/field";
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
+ });
46
+
47
+ export interface SignupValues {
48
+ name: string;
49
+ email: string;
50
+ password: string;
51
+ }
52
+ export interface SignupFormProps {
53
+ onSubmit: (values: SignupValues) => void;
54
+ pending?: boolean;
55
+ error?: string;
56
+ title?: string;
57
+ description?: string;
58
+ submitLabel?: string;
59
+ minPasswordLength?: number;
60
+ terms?: ReactNode;
61
+ footer?: ReactNode;
62
+ }
63
+ export function SignupForm({
64
+ onSubmit,
65
+ pending = false,
66
+ error,
67
+ title,
68
+ description,
69
+ submitLabel,
70
+ minPasswordLength = 12,
71
+ terms,
72
+ footer,
73
+ }: SignupFormProps) {
74
+ const id = useId(),
75
+ t = useMessages(messages),
76
+ [mismatch, setMismatch] = useState(false);
77
+ return (
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>
176
+ );
177
+ }