@majapisoftwares/ui 5.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/AGENTS.md +11 -1
  2. package/README.md +19 -11
  3. package/THIRD_PARTY_NOTICES.md +25 -1
  4. package/components.json +1 -1
  5. package/docs/MIGRATION-6.0.md +33 -0
  6. package/docs/STANDARDS.md +11 -6
  7. package/docs/VERSIONING.md +28 -0
  8. package/package.json +19 -10
  9. package/scripts/check-standards.mjs +2 -0
  10. package/src/components/ui/accordion.tsx +82 -0
  11. package/src/components/ui/alert-dialog.tsx +187 -0
  12. package/src/components/ui/alert.tsx +2 -2
  13. package/src/components/ui/avatar.tsx +108 -0
  14. package/src/components/ui/badge.tsx +1 -1
  15. package/src/components/ui/breadcrumb.tsx +125 -0
  16. package/src/components/ui/button-group.tsx +89 -0
  17. package/src/components/ui/button.tsx +12 -16
  18. package/src/components/ui/card.tsx +101 -0
  19. package/src/components/ui/checkbox.tsx +27 -0
  20. package/src/components/ui/collapsible.tsx +21 -0
  21. package/src/components/ui/context-menu.tsx +271 -0
  22. package/src/components/ui/dialog.tsx +161 -0
  23. package/src/components/ui/dropdown-menu.tsx +271 -0
  24. package/src/components/ui/empty.tsx +4 -4
  25. package/src/components/ui/field.tsx +6 -6
  26. package/src/components/ui/hover-card.tsx +50 -0
  27. package/src/components/ui/input-group.tsx +155 -0
  28. package/src/components/ui/input.tsx +1 -1
  29. package/src/components/ui/kbd.tsx +28 -0
  30. package/src/components/ui/orb.css +1002 -0
  31. package/src/components/ui/orb.tsx +752 -0
  32. package/src/components/ui/popover.tsx +89 -0
  33. package/src/components/ui/progress.tsx +82 -0
  34. package/src/components/ui/radio-group.tsx +37 -0
  35. package/src/components/ui/scroll-area.tsx +54 -0
  36. package/src/components/ui/select.tsx +204 -0
  37. package/src/components/ui/sheet.tsx +8 -6
  38. package/src/components/ui/sidebar.tsx +1035 -0
  39. package/src/components/ui/skeleton.tsx +1 -1
  40. package/src/components/ui/slider.tsx +53 -0
  41. package/src/components/ui/sonner.tsx +44 -0
  42. package/src/components/ui/spinner.tsx +32 -0
  43. package/src/components/ui/switch.tsx +31 -0
  44. package/src/components/ui/table.tsx +10 -4
  45. package/src/components/ui/tabs.tsx +2 -2
  46. package/src/components/ui/textarea.tsx +19 -0
  47. package/src/components/ui/toggle-group.tsx +89 -0
  48. package/src/components/ui/toggle.tsx +44 -0
  49. package/src/components/ui/tooltip.tsx +65 -0
  50. package/src/hooks/use-mobile.ts +20 -0
  51. package/src/lib/i18n.tsx +109 -0
  52. package/src/patterns/app-shell.tsx +158 -53
  53. package/src/patterns/data-table.tsx +2441 -180
  54. package/src/patterns/details-sheet.tsx +8 -6
  55. package/src/patterns/filter-tabs.tsx +53 -0
  56. package/src/patterns/majapi-logo.tsx +32 -0
  57. package/src/patterns/page-header.tsx +44 -22
  58. package/src/patterns/preferences.tsx +172 -0
  59. package/src/patterns/status-badge.tsx +4 -3
  60. package/src/styles.css +41 -341
  61. package/src/templates/admin-dashboard.tsx +14 -33
  62. package/src/templates/auth-layout.tsx +18 -24
  63. package/src/templates/login-form.tsx +97 -65
  64. package/src/templates/signup-form.tsx +139 -99
  65. package/src/components/ui/native-select.tsx +0 -66
  66. package/src/vite-env.d.ts +0 -1
package/src/styles.css CHANGED
@@ -2,99 +2,14 @@
2
2
  @import "tw-animate-css";
3
3
  @import "shadcn/tailwind.css";
4
4
  @import "@fontsource-variable/geist";
5
+ @import "./components/ui/orb.css";
5
6
  @source "./";
6
7
 
7
- @layer components {
8
- .wb-shell,
9
- .wb-auth {
10
- background: var(--background);
11
- color: var(--foreground);
12
- }
13
- .wb-auth {
14
- display: grid;
15
- grid-template-columns: 1fr 1fr;
16
- min-height: 100dvh;
17
- }
18
- .wb-auth-intro {
19
- background: var(--foreground);
20
- color: var(--background);
21
- padding: clamp(2rem, 6vw, 6rem);
22
- display: flex;
23
- flex-direction: column;
24
- justify-content: space-between;
25
- gap: 3rem;
26
- }
27
- .wb-auth-intro h1 {
28
- font: 3.5rem/1.1 var(--font-heading);
29
- letter-spacing: -0.04em;
30
- max-width: 12ch;
31
- }
32
- .wb-auth-intro p {
33
- margin-top: 1.5rem;
34
- max-width: 36ch;
35
- opacity: 0.7;
36
- line-height: 1.8;
37
- }
38
- .wb-auth-intro footer {
39
- font-size: 0.75rem;
40
- opacity: 0.7;
41
- }
42
- .wb-auth-panel {
43
- display: flex;
44
- align-items: center;
45
- justify-content: center;
46
- padding: 2rem;
47
- }
48
- .wb-auth-form {
49
- display: flex;
50
- flex-direction: column;
51
- gap: 1.5rem;
52
- width: min(100%, 380px);
53
- }
54
- .wb-auth-form h2 {
55
- font-size: 1.6rem;
56
- font-weight: 600;
57
- letter-spacing: -0.04em;
58
- }
59
- .wb-auth-form header p,
60
- .wb-auth-form footer {
61
- font-size: 0.875rem;
62
- color: var(--muted-foreground);
63
- margin-top: 0.75rem;
64
- }
65
- @media (max-width: 767px) {
66
- .wb-auth {
67
- grid-template-columns: 1fr;
68
- }
69
- .wb-auth-intro {
70
- padding: 1.5rem;
71
- gap: 1.5rem;
72
- }
73
- .wb-auth-intro h1 {
74
- font-size: 2rem;
75
- max-width: none;
76
- }
77
- .wb-auth-intro p,
78
- .wb-auth-intro footer {
79
- display: none;
80
- }
81
- .wb-auth-panel {
82
- padding: 2rem 1.5rem;
83
- }
84
- .wb-sidebar {
85
- flex-wrap: wrap;
86
- }
87
- .wb-sidebar nav {
88
- flex-wrap: wrap;
89
- }
90
- }
91
- }
92
-
93
8
  @custom-variant dark (&:is(.dark *));
94
9
 
95
10
  @theme inline {
96
- --font-heading: Georgia, serif;
97
- --font-sans: "Geist Variable", sans-serif;
11
+ --font-heading: var(--font-sans);
12
+ --font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif;
98
13
  --color-sidebar-ring: var(--sidebar-ring);
99
14
  --color-sidebar-border: var(--sidebar-border);
100
15
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -140,13 +55,14 @@
140
55
  }
141
56
 
142
57
  :root {
143
- --background: oklch(0.982 0.006 105);
58
+ --radius: 0.625rem;
59
+ --background: oklch(1 0 0);
144
60
  --foreground: oklch(0.145 0 0);
145
61
  --card: oklch(1 0 0);
146
62
  --card-foreground: oklch(0.145 0 0);
147
63
  --popover: oklch(1 0 0);
148
64
  --popover-foreground: oklch(0.145 0 0);
149
- --primary: oklch(0.52 0.15 42);
65
+ --primary: oklch(0.205 0 0);
150
66
  --primary-foreground: oklch(0.985 0 0);
151
67
  --secondary: oklch(0.97 0 0);
152
68
  --secondary-foreground: oklch(0.205 0 0);
@@ -158,16 +74,15 @@
158
74
  --border: oklch(0.922 0 0);
159
75
  --input: oklch(0.922 0 0);
160
76
  --ring: oklch(0.708 0 0);
161
- --chart-1: oklch(0.87 0 0);
162
- --chart-2: oklch(0.556 0 0);
163
- --chart-3: oklch(0.439 0 0);
164
- --chart-4: oklch(0.371 0 0);
165
- --chart-5: oklch(0.269 0 0);
166
- --radius: 0.5rem;
167
- --success: oklch(0.94 0.03 145);
168
- --success-foreground: oklch(0.36 0.08 145);
169
- --warning: oklch(0.96 0.045 85);
170
- --warning-foreground: oklch(0.4 0.08 65);
77
+ --chart-1: oklch(0.646 0.222 41.116);
78
+ --chart-2: oklch(0.6 0.118 184.704);
79
+ --chart-3: oklch(0.398 0.07 227.392);
80
+ --chart-4: oklch(0.828 0.189 84.429);
81
+ --chart-5: oklch(0.769 0.188 70.08);
82
+ --success: oklch(0.95 0.05 155);
83
+ --success-foreground: oklch(0.42 0.1 155);
84
+ --warning: oklch(0.96 0.06 85);
85
+ --warning-foreground: oklch(0.45 0.1 65);
171
86
  --sidebar: oklch(0.985 0 0);
172
87
  --sidebar-foreground: oklch(0.145 0 0);
173
88
  --sidebar-primary: oklch(0.205 0 0);
@@ -179,10 +94,6 @@
179
94
  }
180
95
 
181
96
  .dark {
182
- --success: oklch(0.25 0.035 145);
183
- --success-foreground: oklch(0.83 0.1 145);
184
- --warning: oklch(0.28 0.035 85);
185
- --warning-foreground: oklch(0.87 0.1 85);
186
97
  --background: oklch(0.145 0 0);
187
98
  --foreground: oklch(0.985 0 0);
188
99
  --card: oklch(0.205 0 0);
@@ -201,11 +112,15 @@
201
112
  --border: oklch(1 0 0 / 10%);
202
113
  --input: oklch(1 0 0 / 15%);
203
114
  --ring: oklch(0.556 0 0);
204
- --chart-1: oklch(0.87 0 0);
205
- --chart-2: oklch(0.556 0 0);
206
- --chart-3: oklch(0.439 0 0);
207
- --chart-4: oklch(0.371 0 0);
208
- --chart-5: oklch(0.269 0 0);
115
+ --chart-1: oklch(0.488 0.243 264.376);
116
+ --chart-2: oklch(0.696 0.17 162.48);
117
+ --chart-3: oklch(0.769 0.188 70.08);
118
+ --chart-4: oklch(0.627 0.265 303.9);
119
+ --chart-5: oklch(0.645 0.246 16.439);
120
+ --success: oklch(0.3 0.06 155);
121
+ --success-foreground: oklch(0.85 0.12 155);
122
+ --warning: oklch(0.32 0.06 85);
123
+ --warning-foreground: oklch(0.88 0.12 85);
209
124
  --sidebar: oklch(0.205 0 0);
210
125
  --sidebar-foreground: oklch(0.985 0 0);
211
126
  --sidebar-primary: oklch(0.488 0.243 264.376);
@@ -216,243 +131,28 @@
216
131
  --sidebar-ring: oklch(0.556 0 0);
217
132
  }
218
133
 
219
- @layer components {
220
- .wb-shell {
221
- min-height: 100dvh;
222
- display: grid;
223
- grid-template-columns: 224px minmax(0, 1fr);
224
- }
225
- .wb-sidebar {
226
- --sidebar-foreground: var(--background);
227
- --sidebar-accent: color-mix(
228
- in oklch,
229
- var(--background) 12%,
230
- var(--foreground)
231
- );
232
- --sidebar-accent-foreground: var(--background);
233
- display: flex;
234
- flex-direction: column;
235
- gap: 2rem;
236
- padding: 2rem 1.25rem;
237
- background: var(--foreground);
238
- color: var(--background);
239
- }
240
- .wb-brand {
241
- font-weight: 600;
242
- font-size: 1.25rem;
243
- letter-spacing: -0.04em;
244
- }
245
- .wb-brand span {
246
- color: oklch(0.78 0.13 45);
247
- font:
248
- italic 2rem Georgia,
249
- serif;
250
- margin-right: 0.6rem;
251
- }
252
- .wb-sidebar nav {
253
- display: flex;
254
- flex-direction: column;
255
- gap: 0.5rem;
256
- }
257
- .wb-sidebar [data-slot="button"] {
258
- justify-content: flex-start;
259
- color: inherit;
260
- }
261
- .wb-sidebar [data-slot="button"]:hover,
262
- .wb-sidebar [data-active="true"] {
263
- background: color-mix(in oklch, var(--background) 12%, transparent);
264
- color: inherit;
265
- }
266
- .wb-sidebar-footer {
267
- margin-top: auto;
268
- font-size: 0.75rem;
269
- opacity: 0.7;
270
- line-height: 1.8;
271
- }
272
- .wb-topbar {
273
- display: flex;
274
- gap: 1rem;
275
- align-items: center;
276
- justify-content: space-between;
277
- min-height: 4.5rem;
278
- padding: 1rem 2.5rem;
279
- border-bottom: 1px solid var(--border);
280
- font-size: 0.75rem;
281
- color: var(--muted-foreground);
282
- }
283
- .wb-main {
284
- display: flex;
285
- flex-direction: column;
286
- gap: 2rem;
287
- padding: 2.5rem;
288
- max-width: 1600px;
289
- margin-inline: auto;
290
- }
291
- .wb-page-header {
292
- display: flex;
293
- align-items: center;
294
- justify-content: space-between;
295
- gap: 1rem;
296
- }
297
- .wb-page-header h1 {
298
- font: 2.75rem/1.15 var(--font-heading);
299
- letter-spacing: -0.04em;
300
- }
301
- .wb-page-header p {
302
- color: var(--muted-foreground);
303
- font-size: 0.875rem;
304
- margin-top: 0.75rem;
305
- }
306
- .wb-eyebrow {
307
- color: var(--primary);
308
- font: 0.65rem/1.5 monospace;
309
- letter-spacing: 0.14em;
310
- text-transform: uppercase;
311
- margin-bottom: 0.75rem;
312
- }
313
- .wb-metrics {
314
- display: grid;
315
- grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
316
- border-block: 1px solid var(--border);
317
- padding-block: 1.5rem;
318
- gap: 1.5rem;
319
- }
320
- .wb-metric-label {
321
- color: var(--muted-foreground);
322
- text-transform: uppercase;
323
- letter-spacing: 0.08em;
324
- font: 0.65rem/1.6 monospace;
325
- }
326
- .wb-metric-value {
327
- font-size: 1.8rem;
328
- font-weight: 400;
329
- margin-block: 0.5rem;
330
- }
331
- .wb-metric-note {
332
- font-size: 0.7rem;
333
- color: var(--muted-foreground);
334
- }
335
- .wb-status[data-tone="success"] {
336
- background: var(--success);
337
- color: var(--success-foreground);
338
- }
339
- .wb-status[data-tone="warning"] {
340
- background: var(--warning);
341
- color: var(--warning-foreground);
342
- }
343
- .wb-status[data-tone="danger"] {
344
- background: var(--destructive);
345
- color: var(--background);
346
- }
347
- .wb-status-dot {
348
- width: 0.35rem;
349
- height: 0.35rem;
350
- border-radius: 50%;
351
- background: currentColor;
352
- }
353
- .wb-table-toolbar {
354
- display: flex;
355
- flex-wrap: wrap;
356
- align-items: center;
357
- gap: 0.75rem;
358
- padding: 1rem;
359
- background: var(--muted);
360
- border-radius: var(--radius) var(--radius) 0 0;
361
- }
362
- .wb-table-toolbar [data-slot="input"] {
363
- flex: 1;
364
- min-width: 200px;
365
- }
366
- .wb-table-footer {
367
- display: flex;
368
- align-items: center;
369
- justify-content: space-between;
370
- gap: 1rem;
371
- padding: 1rem 0;
372
- font-size: 0.75rem;
373
- color: var(--muted-foreground);
374
- }
375
- .wb-details {
376
- display: grid;
377
- gap: 1.5rem;
378
- }
379
- .wb-details dt {
380
- font-size: 0.7rem;
381
- color: var(--muted-foreground);
382
- text-transform: uppercase;
383
- letter-spacing: 0.06em;
384
- }
385
- .wb-details dd {
386
- font-size: 0.875rem;
387
- margin-top: 0.35rem;
388
- overflow-wrap: anywhere;
134
+ @layer base {
135
+ /* Native controls (scrollbars, date pickers) follow the active theme. */
136
+ :root {
137
+ color-scheme: light;
389
138
  }
390
- .wb-catalog-section {
391
- display: flex;
392
- flex-direction: column;
393
- gap: 1rem;
139
+ .dark {
140
+ color-scheme: dark;
394
141
  }
395
- .wb-catalog-section h2 {
396
- font-size: 1.125rem;
397
- font-weight: 600;
142
+ /* Thin scrollbars that stay hidden until the pointer is over the scrolling
143
+ area, tinted with the theme foreground. */
144
+ * {
145
+ scrollbar-width: thin;
146
+ scrollbar-color: transparent transparent;
398
147
  }
399
- @media (max-width: 767px) {
400
- .wb-shell {
401
- grid-template-columns: minmax(0, 1fr);
402
- }
403
- .wb-sidebar {
404
- padding: 1rem;
405
- flex-direction: row;
406
- align-items: center;
407
- justify-content: space-between;
408
- gap: 1rem;
409
- }
410
- .wb-sidebar nav {
411
- flex-direction: row;
412
- }
413
- .wb-sidebar-footer {
414
- display: none;
415
- }
416
- .wb-main {
417
- padding: 1.5rem 1rem;
418
- gap: 1.5rem;
419
- }
420
- .wb-topbar {
421
- padding: 1rem;
422
- }
423
- .wb-page-header {
424
- align-items: flex-start;
425
- flex-direction: column;
426
- }
427
- .wb-page-header h1 {
428
- font-size: 2.2rem;
429
- }
430
- .wb-metrics {
431
- grid-template-columns: 1fr 1fr;
432
- }
433
- .wb-table-footer {
434
- flex-wrap: wrap;
435
- }
436
- [data-slot="input"],
437
- [data-slot="native-select"] {
438
- font-size: 16px;
439
- min-height: 44px;
440
- }
441
- [data-slot="button"] {
442
- min-height: 44px;
443
- }
148
+ *:hover {
149
+ scrollbar-color: color-mix(in oklch, var(--foreground) 25%, transparent)
150
+ transparent;
444
151
  }
445
- @media (prefers-reduced-motion: reduce) {
446
- *,
447
- *::before,
448
- *::after {
449
- animation-duration: 0.01ms !important;
450
- transition-duration: 0.01ms !important;
451
- }
152
+ /* Set by preferencesScript until the saved language has rendered. */
153
+ html[data-locale-pending] body {
154
+ visibility: hidden;
452
155
  }
453
- }
454
-
455
- @layer base {
456
156
  * {
457
157
  @apply border-border outline-ring/50;
458
158
  }
@@ -1,58 +1,39 @@
1
1
  "use client";
2
2
 
3
3
  import type { ReactNode } from "react";
4
- import { AppShell, type NavigationItem } from "../patterns/app-shell";
5
- import { PageHeader, MetricStrip } from "../patterns/page-header";
4
+ import { AppShell, type AppShellProps } from "../patterns/app-shell";
5
+ import { PageHeader, MetricStrip, type Metric } from "../patterns/page-header";
6
6
  import { DataTable, type DataTableProps } from "../patterns/data-table";
7
7
 
8
- export interface AdminDashboardProps<T> {
8
+ export interface AdminDashboardProps<T> extends Omit<
9
+ AppShellProps,
10
+ "children"
11
+ > {
9
12
  title: string;
10
- description: string;
11
- navigation: NavigationItem[];
12
- activeId: string;
13
- onNavigate: (id: string) => void;
14
- metrics: Parameters<typeof MetricStrip>[0]["items"];
13
+ description?: string;
14
+ metrics: Metric[];
15
15
  table: DataTableProps<T>;
16
- brand?: string;
17
- brandMark?: ReactNode;
18
- context?: ReactNode;
19
- actions?: ReactNode;
16
+ /** Page-level actions next to the title. */
17
+ pageActions?: ReactNode;
20
18
  beforeTable?: ReactNode;
21
19
  afterTable?: ReactNode;
22
- footer?: ReactNode;
23
20
  }
24
21
  export function AdminDashboard<T>({
25
22
  title,
26
23
  description,
27
- navigation,
28
- activeId,
29
- onNavigate,
30
24
  metrics,
31
25
  table,
32
- brand,
33
- brandMark,
34
- context,
35
- actions,
26
+ pageActions,
36
27
  beforeTable,
37
28
  afterTable,
38
- footer,
29
+ ...shell
39
30
  }: AdminDashboardProps<T>) {
40
31
  return (
41
- <AppShell
42
- title={title}
43
- context={context}
44
- navigation={navigation}
45
- activeId={activeId}
46
- onNavigate={onNavigate}
47
- brand={brand}
48
- brandMark={brandMark}
49
- footer={footer}
50
- >
32
+ <AppShell {...shell}>
51
33
  <PageHeader
52
- eyebrow="Painel administrativo"
53
34
  title={title}
54
35
  description={description}
55
- actions={actions}
36
+ actions={pageActions}
56
37
  />
57
38
  <MetricStrip items={metrics} />
58
39
  {beforeTable}
@@ -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="wb-auth">
21
- <section className="wb-auth-intro">
22
- <div className="wb-brand">
23
- <span>{brandMark}</span>
24
- {brand}
25
- </div>
26
- <div>
27
- <h1>{title}</h1>
28
- <p>{description}</p>
29
- </div>
30
- {aside}
31
- <footer>{footer}</footer>
32
- </section>
33
- <section className="wb-auth-panel">{children}</section>
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
  }