@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.
- 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 +158 -53
- package/src/patterns/data-table.tsx +2441 -180
- package/src/patterns/details-sheet.tsx +8 -6
- 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/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,70 +1,175 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
-
import {
|
|
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
|
+
/** Sidebar footer, e.g. a user menu. */
|
|
50
|
+
footer?: ReactNode;
|
|
51
|
+
defaultMode?: SidebarMode;
|
|
52
|
+
defaultWidth?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Locks the shell to the viewport height so content (e.g. a `DataTable
|
|
55
|
+
* fillHeight`) fills the remaining space and scrolls internally.
|
|
56
|
+
*/
|
|
57
|
+
fillHeight?: boolean;
|
|
58
|
+
children: ReactNode;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Items without an icon still need something to show in compact mode.
|
|
62
|
+
function ItemIcon({ item }: { item: NavigationItem }) {
|
|
63
|
+
return (
|
|
64
|
+
item.icon ?? (
|
|
65
|
+
<span
|
|
66
|
+
aria-hidden="true"
|
|
67
|
+
className="flex size-4 items-center justify-center text-xs font-semibold"
|
|
68
|
+
>
|
|
69
|
+
{item.label.charAt(0).toUpperCase()}
|
|
70
|
+
</span>
|
|
71
|
+
)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The collapse control lives inside the sidebar; reopening needs one outside.
|
|
76
|
+
// Shown on mobile and, on desktop, only while the sidebar is hidden (CSS, so
|
|
77
|
+
// it is right from the first paint).
|
|
78
|
+
function HeaderTrigger() {
|
|
79
|
+
return (
|
|
80
|
+
<div className="flex items-center md:hidden md:group-has-data-[collapsible=offcanvas]/sidebar-wrapper:flex">
|
|
81
|
+
<SidebarTrigger className="-ml-1" />
|
|
82
|
+
<Separator
|
|
83
|
+
orientation="vertical"
|
|
84
|
+
className="mr-2 ml-2 h-4 self-center!"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
11
88
|
}
|
|
89
|
+
|
|
12
90
|
export function AppShell({
|
|
13
|
-
|
|
14
|
-
|
|
91
|
+
logo,
|
|
92
|
+
name,
|
|
15
93
|
navigation,
|
|
16
94
|
activeId,
|
|
17
95
|
onNavigate,
|
|
96
|
+
header,
|
|
97
|
+
actions,
|
|
98
|
+
footer,
|
|
99
|
+
defaultMode,
|
|
100
|
+
defaultWidth,
|
|
101
|
+
fillHeight = false,
|
|
18
102
|
children,
|
|
19
|
-
|
|
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
|
-
}) {
|
|
103
|
+
}: AppShellProps) {
|
|
33
104
|
return (
|
|
34
|
-
<
|
|
35
|
-
<
|
|
36
|
-
<
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
data-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
105
|
+
<TooltipProvider delay={600}>
|
|
106
|
+
<SidebarProvider defaultMode={defaultMode} defaultWidth={defaultWidth}>
|
|
107
|
+
<Sidebar>
|
|
108
|
+
{/* Same height as the page header so their controls line up. */}
|
|
109
|
+
<SidebarHeader className="min-h-14 justify-center">
|
|
110
|
+
{/* The logo keeps its position across modes; only the name fades,
|
|
111
|
+
and it is clipped (never squeezed) by the sidebar width. */}
|
|
112
|
+
<div className="flex h-8 items-center gap-2 overflow-hidden">
|
|
113
|
+
<div className="flex size-8 shrink-0 items-center justify-center [&>*]:shrink-0">
|
|
114
|
+
{logo}
|
|
115
|
+
</div>
|
|
116
|
+
<span className="flex-1 truncate font-semibold whitespace-nowrap transition-opacity duration-200 ease-linear group-data-[collapsible=icon]:opacity-0">
|
|
117
|
+
{name}
|
|
118
|
+
</span>
|
|
119
|
+
<SidebarCollapse className="group-data-[collapsible=icon]:hidden" />
|
|
120
|
+
</div>
|
|
121
|
+
<div className="hidden group-data-[collapsible=icon]:block">
|
|
122
|
+
<SidebarCollapse />
|
|
123
|
+
</div>
|
|
124
|
+
</SidebarHeader>
|
|
125
|
+
<SidebarContent>
|
|
126
|
+
{navigation.map((group, index) => (
|
|
127
|
+
<SidebarGroup key={group.label ?? index}>
|
|
128
|
+
{group.label ? (
|
|
129
|
+
<SidebarGroupLabel>{group.label}</SidebarGroupLabel>
|
|
130
|
+
) : null}
|
|
131
|
+
<SidebarMenu>
|
|
132
|
+
{group.items.map((item) => (
|
|
133
|
+
<SidebarMenuItem key={item.id}>
|
|
134
|
+
<SidebarMenuButton
|
|
135
|
+
isActive={item.id === activeId}
|
|
136
|
+
tooltip={item.label}
|
|
137
|
+
aria-current={item.id === activeId ? "page" : undefined}
|
|
138
|
+
onClick={() => onNavigate(item.id)}
|
|
139
|
+
>
|
|
140
|
+
<ItemIcon item={item} />
|
|
141
|
+
<span>{item.label}</span>
|
|
142
|
+
</SidebarMenuButton>
|
|
143
|
+
{item.count !== undefined ? (
|
|
144
|
+
<SidebarMenuBadge>{item.count}</SidebarMenuBadge>
|
|
145
|
+
) : null}
|
|
146
|
+
</SidebarMenuItem>
|
|
147
|
+
))}
|
|
148
|
+
</SidebarMenu>
|
|
149
|
+
</SidebarGroup>
|
|
150
|
+
))}
|
|
151
|
+
</SidebarContent>
|
|
152
|
+
{footer ? <SidebarFooter>{footer}</SidebarFooter> : null}
|
|
153
|
+
<SidebarRail />
|
|
154
|
+
</Sidebar>
|
|
155
|
+
<SidebarInset className={fillHeight ? "h-svh min-h-0" : undefined}>
|
|
156
|
+
<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">
|
|
157
|
+
<HeaderTrigger />
|
|
158
|
+
<div className="min-w-0 flex-1">{header}</div>
|
|
159
|
+
{actions ? (
|
|
160
|
+
<div className="flex items-center gap-2">{actions}</div>
|
|
161
|
+
) : null}
|
|
162
|
+
</header>
|
|
163
|
+
<div
|
|
164
|
+
className={cn(
|
|
165
|
+
"flex flex-1 flex-col gap-4 p-4 md:gap-6 md:p-6",
|
|
166
|
+
fillHeight && "min-h-0 overflow-hidden",
|
|
167
|
+
)}
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</div>
|
|
171
|
+
</SidebarInset>
|
|
172
|
+
</SidebarProvider>
|
|
173
|
+
</TooltipProvider>
|
|
69
174
|
);
|
|
70
175
|
}
|