@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.
Files changed (68) 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 +162 -53
  53. package/src/patterns/data-table.tsx +2441 -180
  54. package/src/patterns/details-sheet.tsx +10 -7
  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/patterns/user-menu.tsx +218 -0
  61. package/src/patterns/workspace-switcher.tsx +173 -0
  62. package/src/styles.css +41 -341
  63. package/src/templates/admin-dashboard.tsx +14 -33
  64. package/src/templates/auth-layout.tsx +18 -24
  65. package/src/templates/login-form.tsx +97 -65
  66. package/src/templates/signup-form.tsx +139 -99
  67. package/src/components/ui/native-select.tsx +0 -66
  68. package/src/vite-env.d.ts +0 -1
@@ -1,70 +1,179 @@
1
1
  "use client";
2
2
 
3
3
  import type { ReactNode } from "react";
4
- import { Button } from "../components/ui/button";
4
+ import { cn } from "cn";
5
+ import {
6
+ Sidebar,
7
+ SidebarCollapse,
8
+ SidebarContent,
9
+ SidebarFooter,
10
+ SidebarGroup,
11
+ SidebarGroupLabel,
12
+ SidebarHeader,
13
+ SidebarInset,
14
+ SidebarMenu,
15
+ SidebarMenuBadge,
16
+ SidebarMenuButton,
17
+ SidebarMenuItem,
18
+ SidebarProvider,
19
+ SidebarRail,
20
+ SidebarTrigger,
21
+ type SidebarMode,
22
+ } from "../components/ui/sidebar";
23
+ import { Separator } from "../components/ui/separator";
24
+ import { TooltipProvider } from "../components/ui/tooltip";
5
25
 
6
26
  export interface NavigationItem {
7
27
  id: string;
8
28
  label: string;
9
- count?: number;
10
29
  icon?: ReactNode;
30
+ count?: number;
31
+ }
32
+ export interface NavigationGroup {
33
+ label?: string;
34
+ items: NavigationItem[];
35
+ }
36
+
37
+ export interface AppShellProps {
38
+ /** Square logo; stays in place in every sidebar mode. */
39
+ logo: ReactNode;
40
+ /** App name next to the logo; fades out when the sidebar is compact. */
41
+ name: string;
42
+ navigation: NavigationGroup[];
43
+ activeId: string;
44
+ onNavigate: (id: string) => void;
45
+ /** Rendered in the header next to the sidebar trigger, e.g. breadcrumbs. */
46
+ header?: ReactNode;
47
+ /** Rendered at the end of the header, e.g. locale or theme switchers. */
48
+ actions?: ReactNode;
49
+ /** Below the logo in the sidebar header, e.g. `WorkspaceSwitcher`. */
50
+ sidebarHeader?: ReactNode;
51
+ /** Sidebar footer, e.g. `UserMenu`. */
52
+ footer?: ReactNode;
53
+ defaultMode?: SidebarMode;
54
+ defaultWidth?: number;
55
+ /**
56
+ * Locks the shell to the viewport height so content (e.g. a `DataTable
57
+ * fillHeight`) fills the remaining space and scrolls internally.
58
+ */
59
+ fillHeight?: boolean;
60
+ children: ReactNode;
61
+ }
62
+
63
+ // Items without an icon still need something to show in compact mode.
64
+ function ItemIcon({ item }: { item: NavigationItem }) {
65
+ return (
66
+ item.icon ?? (
67
+ <span
68
+ aria-hidden="true"
69
+ className="flex size-4 items-center justify-center text-xs font-semibold"
70
+ >
71
+ {item.label.charAt(0).toUpperCase()}
72
+ </span>
73
+ )
74
+ );
75
+ }
76
+
77
+ // The collapse control lives inside the sidebar; reopening needs one outside.
78
+ // Shown on mobile and, on desktop, only while the sidebar is hidden (CSS, so
79
+ // it is right from the first paint).
80
+ function HeaderTrigger() {
81
+ return (
82
+ <div className="flex items-center md:hidden md:group-has-data-[collapsible=offcanvas]/sidebar-wrapper:flex">
83
+ <SidebarTrigger className="-ml-1" />
84
+ <Separator
85
+ orientation="vertical"
86
+ className="mr-2 ml-2 h-4 self-center!"
87
+ />
88
+ </div>
89
+ );
11
90
  }
91
+
12
92
  export function AppShell({
13
- title,
14
- context,
93
+ logo,
94
+ name,
15
95
  navigation,
16
96
  activeId,
17
97
  onNavigate,
98
+ header,
99
+ actions,
100
+ footer,
101
+ sidebarHeader,
102
+ defaultMode,
103
+ defaultWidth,
104
+ fillHeight = false,
18
105
  children,
19
- footer = "Workspace local",
20
- brand = "workbench",
21
- brandMark = "w.",
22
- }: {
23
- title: string;
24
- context?: ReactNode;
25
- navigation: NavigationItem[];
26
- activeId: string;
27
- onNavigate: (id: string) => void;
28
- children: ReactNode;
29
- footer?: ReactNode;
30
- brand?: string;
31
- brandMark?: ReactNode;
32
- }) {
106
+ }: AppShellProps) {
33
107
  return (
34
- <div className="wb-shell">
35
- <aside className="wb-sidebar">
36
- <div className="wb-brand">
37
- <span>{brandMark}</span>
38
- {brand}
39
- </div>
40
- <nav aria-label="Navegação principal">
41
- {navigation.map((item) => (
42
- <Button
43
- key={item.id}
44
- variant="navigation"
45
- data-active={activeId === item.id}
46
- aria-current={activeId === item.id ? "page" : undefined}
47
- onClick={() => onNavigate(item.id)}
48
- >
49
- {item.icon}
50
- {item.label}
51
- {item.count !== undefined ? (
52
- <span className="ml-auto">{item.count}</span>
53
- ) : null}
54
- </Button>
55
- ))}
56
- </nav>
57
- <div className="wb-sidebar-footer">{footer}</div>
58
- </aside>
59
- <div className="min-w-0">
60
- <header className="wb-topbar">
61
- <span>
62
- {brand} / {title}
63
- </span>
64
- {context}
65
- </header>
66
- <main className="wb-main">{children}</main>
67
- </div>
68
- </div>
108
+ <TooltipProvider delay={600}>
109
+ <SidebarProvider defaultMode={defaultMode} defaultWidth={defaultWidth}>
110
+ <Sidebar>
111
+ {/* Same height as the page header so their controls line up. */}
112
+ <SidebarHeader className="min-h-14 justify-center">
113
+ {/* The logo keeps its position across modes; only the name fades,
114
+ and it is clipped (never squeezed) by the sidebar width. */}
115
+ <div className="flex h-8 items-center gap-2 overflow-hidden">
116
+ <div className="flex size-8 shrink-0 items-center justify-center [&>*]:shrink-0">
117
+ {logo}
118
+ </div>
119
+ <span className="flex-1 truncate font-semibold whitespace-nowrap transition-opacity duration-200 ease-linear group-data-[collapsible=icon]:opacity-0">
120
+ {name}
121
+ </span>
122
+ <SidebarCollapse className="group-data-[collapsible=icon]:hidden" />
123
+ </div>
124
+ <div className="hidden group-data-[collapsible=icon]:block">
125
+ <SidebarCollapse />
126
+ </div>
127
+ {sidebarHeader}
128
+ </SidebarHeader>
129
+ <SidebarContent>
130
+ {navigation.map((group, index) => (
131
+ <SidebarGroup key={group.label ?? index}>
132
+ {group.label ? (
133
+ <SidebarGroupLabel>{group.label}</SidebarGroupLabel>
134
+ ) : null}
135
+ <SidebarMenu>
136
+ {group.items.map((item) => (
137
+ <SidebarMenuItem key={item.id}>
138
+ <SidebarMenuButton
139
+ isActive={item.id === activeId}
140
+ tooltip={item.label}
141
+ aria-current={item.id === activeId ? "page" : undefined}
142
+ onClick={() => onNavigate(item.id)}
143
+ >
144
+ <ItemIcon item={item} />
145
+ <span>{item.label}</span>
146
+ </SidebarMenuButton>
147
+ {item.count !== undefined ? (
148
+ <SidebarMenuBadge>{item.count}</SidebarMenuBadge>
149
+ ) : null}
150
+ </SidebarMenuItem>
151
+ ))}
152
+ </SidebarMenu>
153
+ </SidebarGroup>
154
+ ))}
155
+ </SidebarContent>
156
+ {footer ? <SidebarFooter>{footer}</SidebarFooter> : null}
157
+ <SidebarRail />
158
+ </Sidebar>
159
+ <SidebarInset className={fillHeight ? "h-svh min-h-0" : undefined}>
160
+ <header className="sticky top-0 z-10 flex h-14 shrink-0 items-center gap-2 border-b bg-background px-4 md:px-6">
161
+ <HeaderTrigger />
162
+ <div className="min-w-0 flex-1">{header}</div>
163
+ {actions ? (
164
+ <div className="flex items-center gap-2">{actions}</div>
165
+ ) : null}
166
+ </header>
167
+ <div
168
+ className={cn(
169
+ "flex flex-1 flex-col gap-4 p-4 md:gap-6 md:p-6",
170
+ fillHeight && "min-h-0 overflow-hidden",
171
+ )}
172
+ >
173
+ {children}
174
+ </div>
175
+ </SidebarInset>
176
+ </SidebarProvider>
177
+ </TooltipProvider>
69
178
  );
70
179
  }