@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.
- package/AGENTS.md +27 -27
- package/README.md +79 -79
- package/THIRD_PARTY_NOTICES.md +39 -39
- package/components.json +25 -25
- package/docs/MIGRATION-0.2.md +20 -20
- package/docs/MIGRATION-6.0.md +33 -33
- package/docs/NEXT-STARTER-VALIDATION.md +14 -14
- package/docs/STANDARDS.md +41 -41
- package/docs/VALIDATION.md +11 -11
- package/docs/VERSIONING.md +28 -28
- package/docs/validation/preference-titles.md +22 -0
- package/package.json +88 -84
- package/scripts/check-standards.mjs +41 -41
- package/src/components/ui/accordion.tsx +82 -82
- package/src/components/ui/alert-dialog.tsx +187 -187
- package/src/components/ui/alert.tsx +77 -77
- package/src/components/ui/avatar.tsx +108 -108
- package/src/components/ui/badge.tsx +55 -55
- package/src/components/ui/breadcrumb.tsx +125 -125
- package/src/components/ui/button-group.tsx +89 -89
- package/src/components/ui/button.tsx +59 -59
- package/src/components/ui/card.tsx +101 -101
- package/src/components/ui/checkbox.tsx +27 -27
- package/src/components/ui/collapsible.tsx +21 -21
- package/src/components/ui/context-menu.tsx +271 -271
- package/src/components/ui/dialog.tsx +161 -161
- package/src/components/ui/dropdown-menu.tsx +271 -271
- package/src/components/ui/empty.tsx +105 -105
- package/src/components/ui/field.tsx +238 -238
- package/src/components/ui/hover-card.tsx +50 -50
- package/src/components/ui/input-group.tsx +155 -155
- package/src/components/ui/input.tsx +21 -21
- package/src/components/ui/kbd.tsx +28 -28
- package/src/components/ui/label.tsx +19 -19
- package/src/components/ui/orb.css +1002 -1002
- package/src/components/ui/orb.tsx +752 -752
- package/src/components/ui/popover.tsx +89 -89
- package/src/components/ui/progress.tsx +82 -82
- package/src/components/ui/radio-group.tsx +37 -37
- package/src/components/ui/scroll-area.tsx +54 -54
- package/src/components/ui/select.tsx +204 -204
- package/src/components/ui/separator.tsx +24 -24
- package/src/components/ui/sheet.tsx +139 -139
- package/src/components/ui/sidebar.tsx +1039 -1035
- package/src/components/ui/skeleton.tsx +15 -15
- package/src/components/ui/slider.tsx +53 -53
- package/src/components/ui/sonner.tsx +44 -44
- package/src/components/ui/spinner.tsx +32 -32
- package/src/components/ui/switch.tsx +31 -31
- package/src/components/ui/table.tsx +121 -121
- package/src/components/ui/tabs.tsx +81 -81
- package/src/components/ui/textarea.tsx +19 -19
- package/src/components/ui/toggle-group.tsx +89 -89
- package/src/components/ui/toggle.tsx +44 -44
- package/src/components/ui/tooltip.tsx +65 -65
- package/src/hooks/use-mobile.ts +20 -20
- package/src/lib/i18n.tsx +109 -109
- package/src/lib/utils.ts +1 -1
- package/src/patterns/app-shell.tsx +177 -179
- package/src/patterns/data-table.tsx +2486 -2486
- package/src/patterns/details-sheet.tsx +54 -54
- package/src/patterns/filter-tabs.tsx +53 -53
- package/src/patterns/majapi-logo.tsx +32 -32
- package/src/patterns/page-header.tsx +62 -62
- package/src/patterns/preferences.tsx +15 -7
- package/src/patterns/status-badge.tsx +24 -24
- package/src/patterns/user-menu.tsx +218 -218
- package/src/patterns/workspace-switcher.tsx +176 -173
- package/src/styles.css +165 -165
- package/src/templates/admin-dashboard.tsx +44 -44
- package/src/templates/auth-layout.tsx +30 -30
- package/src/templates/login-form.tsx +129 -129
- package/src/templates/signup-form.tsx +177 -177
|
@@ -1,173 +1,176 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ReactNode } from "react";
|
|
4
|
-
import { cn } from "cn";
|
|
5
|
-
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
|
|
6
|
-
import {
|
|
7
|
-
DropdownMenu,
|
|
8
|
-
DropdownMenuContent,
|
|
9
|
-
DropdownMenuGroup,
|
|
10
|
-
DropdownMenuItem,
|
|
11
|
-
DropdownMenuLabel,
|
|
12
|
-
DropdownMenuSeparator,
|
|
13
|
-
DropdownMenuTrigger,
|
|
14
|
-
} from "../components/ui/dropdown-menu";
|
|
15
|
-
import {
|
|
16
|
-
SidebarMenu,
|
|
17
|
-
SidebarMenuButton,
|
|
18
|
-
SidebarMenuItem,
|
|
19
|
-
useSidebar,
|
|
20
|
-
} from "../components/ui/sidebar";
|
|
21
|
-
import { Skeleton } from "../components/ui/skeleton";
|
|
22
|
-
import { defineMessages, useMessages } from "../lib/i18n";
|
|
23
|
-
|
|
24
|
-
const messages = defineMessages({
|
|
25
|
-
en: { switchWorkspace: "Switch workspace", workspaces: "Workspaces" },
|
|
26
|
-
"pt-BR": { switchWorkspace: "Trocar workspace", workspaces: "Workspaces" },
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
export interface Workspace {
|
|
30
|
-
id: string;
|
|
31
|
-
name: string;
|
|
32
|
-
/** Second line, e.g. plan or owner. */
|
|
33
|
-
description?: string;
|
|
34
|
-
/** Square mark; defaults to the name's initial. */
|
|
35
|
-
icon?: ReactNode;
|
|
36
|
-
/** Workspaces sharing a group are listed together (e.g. by company). */
|
|
37
|
-
group?: string;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function WorkspaceMark({
|
|
41
|
-
workspace,
|
|
42
|
-
className,
|
|
43
|
-
}: {
|
|
44
|
-
workspace: Workspace;
|
|
45
|
-
className?: string;
|
|
46
|
-
}) {
|
|
47
|
-
return (
|
|
48
|
-
<span
|
|
49
|
-
className={cn(
|
|
50
|
-
// Forced: focused menu items recolor all descendants to the accent color.
|
|
51
|
-
"flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground **:text-primary-foreground! [&_svg]:size-4",
|
|
52
|
-
className,
|
|
53
|
-
)}
|
|
54
|
-
>
|
|
55
|
-
{workspace.icon ?? (
|
|
56
|
-
<span className="text-sm font-semibold">
|
|
57
|
-
{workspace.name.charAt(0).toUpperCase()}
|
|
58
|
-
</span>
|
|
59
|
-
)}
|
|
60
|
-
</span>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Sidebar workspace selector (place it in `AppShell` `sidebarHeader`).
|
|
66
|
-
* Selecting a workspace calls `onSelect`; routing and data stay in the app.
|
|
67
|
-
*/
|
|
68
|
-
export function WorkspaceSwitcher({
|
|
69
|
-
workspaces,
|
|
70
|
-
activeId,
|
|
71
|
-
onSelect,
|
|
72
|
-
actions,
|
|
73
|
-
loading = false,
|
|
74
|
-
}: {
|
|
75
|
-
workspaces: Workspace[];
|
|
76
|
-
activeId: string;
|
|
77
|
-
onSelect: (workspace: Workspace) => void;
|
|
78
|
-
/** Extra menu items after the list, e.g. "Manage workspaces". */
|
|
79
|
-
actions?: ReactNode;
|
|
80
|
-
loading?: boolean;
|
|
81
|
-
}) {
|
|
82
|
-
const t = useMessages(messages);
|
|
83
|
-
const { isMobile } = useSidebar();
|
|
84
|
-
const active = workspaces.find((workspace) => workspace.id === activeId);
|
|
85
|
-
const groups = workspaces.reduce<Map<string, Workspace[]>>(
|
|
86
|
-
(map, workspace) => {
|
|
87
|
-
const key = workspace.group ?? "";
|
|
88
|
-
map.set(key, [...(map.get(key) ?? []), workspace]);
|
|
89
|
-
return map;
|
|
90
|
-
},
|
|
91
|
-
new Map(),
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
if (loading || !active)
|
|
95
|
-
return (
|
|
96
|
-
<SidebarMenu>
|
|
97
|
-
<SidebarMenuItem>
|
|
98
|
-
<div className="flex h-14 items-center gap-2 px-3 group-data-[collapsible=icon]:px-0">
|
|
99
|
-
<Skeleton className="size-8 rounded-lg" />
|
|
100
|
-
<Skeleton className="h-4 flex-1 group-data-[collapsible=icon]:hidden" />
|
|
101
|
-
</div>
|
|
102
|
-
</SidebarMenuItem>
|
|
103
|
-
</SidebarMenu>
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<SidebarMenu>
|
|
108
|
-
<SidebarMenuItem>
|
|
109
|
-
<DropdownMenu>
|
|
110
|
-
<DropdownMenuTrigger
|
|
111
|
-
render={
|
|
112
|
-
<SidebarMenuButton
|
|
113
|
-
size="lg"
|
|
114
|
-
aria-label={t.switchWorkspace}
|
|
115
|
-
// Card-like surface so the current workspace reads as a distinct control.
|
|
116
|
-
className="bg-card shadow-xs ring-1 ring-foreground/5 hover:bg-sidebar-accent data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground dark:ring-foreground/10"
|
|
117
|
-
/>
|
|
118
|
-
}
|
|
119
|
-
>
|
|
120
|
-
<WorkspaceMark
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
className="
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { cn } from "cn";
|
|
5
|
+
import { CheckIcon, ChevronsUpDownIcon } from "lucide-react";
|
|
6
|
+
import {
|
|
7
|
+
DropdownMenu,
|
|
8
|
+
DropdownMenuContent,
|
|
9
|
+
DropdownMenuGroup,
|
|
10
|
+
DropdownMenuItem,
|
|
11
|
+
DropdownMenuLabel,
|
|
12
|
+
DropdownMenuSeparator,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from "../components/ui/dropdown-menu";
|
|
15
|
+
import {
|
|
16
|
+
SidebarMenu,
|
|
17
|
+
SidebarMenuButton,
|
|
18
|
+
SidebarMenuItem,
|
|
19
|
+
useSidebar,
|
|
20
|
+
} from "../components/ui/sidebar";
|
|
21
|
+
import { Skeleton } from "../components/ui/skeleton";
|
|
22
|
+
import { defineMessages, useMessages } from "../lib/i18n";
|
|
23
|
+
|
|
24
|
+
const messages = defineMessages({
|
|
25
|
+
en: { switchWorkspace: "Switch workspace", workspaces: "Workspaces" },
|
|
26
|
+
"pt-BR": { switchWorkspace: "Trocar workspace", workspaces: "Workspaces" },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export interface Workspace {
|
|
30
|
+
id: string;
|
|
31
|
+
name: string;
|
|
32
|
+
/** Second line, e.g. plan or owner. */
|
|
33
|
+
description?: string;
|
|
34
|
+
/** Square mark; defaults to the name's initial. */
|
|
35
|
+
icon?: ReactNode;
|
|
36
|
+
/** Workspaces sharing a group are listed together (e.g. by company). */
|
|
37
|
+
group?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function WorkspaceMark({
|
|
41
|
+
workspace,
|
|
42
|
+
className,
|
|
43
|
+
}: {
|
|
44
|
+
workspace: Workspace;
|
|
45
|
+
className?: string;
|
|
46
|
+
}) {
|
|
47
|
+
return (
|
|
48
|
+
<span
|
|
49
|
+
className={cn(
|
|
50
|
+
// Forced: focused menu items recolor all descendants to the accent color.
|
|
51
|
+
"flex size-8 shrink-0 items-center justify-center rounded-lg bg-primary text-primary-foreground **:text-primary-foreground! [&_svg]:size-4",
|
|
52
|
+
className,
|
|
53
|
+
)}
|
|
54
|
+
>
|
|
55
|
+
{workspace.icon ?? (
|
|
56
|
+
<span className="text-sm font-semibold">
|
|
57
|
+
{workspace.name.charAt(0).toUpperCase()}
|
|
58
|
+
</span>
|
|
59
|
+
)}
|
|
60
|
+
</span>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sidebar workspace selector (place it in `AppShell` `sidebarHeader`).
|
|
66
|
+
* Selecting a workspace calls `onSelect`; routing and data stay in the app.
|
|
67
|
+
*/
|
|
68
|
+
export function WorkspaceSwitcher({
|
|
69
|
+
workspaces,
|
|
70
|
+
activeId,
|
|
71
|
+
onSelect,
|
|
72
|
+
actions,
|
|
73
|
+
loading = false,
|
|
74
|
+
}: {
|
|
75
|
+
workspaces: Workspace[];
|
|
76
|
+
activeId: string;
|
|
77
|
+
onSelect: (workspace: Workspace) => void;
|
|
78
|
+
/** Extra menu items after the list, e.g. "Manage workspaces". */
|
|
79
|
+
actions?: ReactNode;
|
|
80
|
+
loading?: boolean;
|
|
81
|
+
}) {
|
|
82
|
+
const t = useMessages(messages);
|
|
83
|
+
const { isMobile } = useSidebar();
|
|
84
|
+
const active = workspaces.find((workspace) => workspace.id === activeId);
|
|
85
|
+
const groups = workspaces.reduce<Map<string, Workspace[]>>(
|
|
86
|
+
(map, workspace) => {
|
|
87
|
+
const key = workspace.group ?? "";
|
|
88
|
+
map.set(key, [...(map.get(key) ?? []), workspace]);
|
|
89
|
+
return map;
|
|
90
|
+
},
|
|
91
|
+
new Map(),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (loading || !active)
|
|
95
|
+
return (
|
|
96
|
+
<SidebarMenu>
|
|
97
|
+
<SidebarMenuItem>
|
|
98
|
+
<div className="flex h-14 items-center gap-2 px-3 group-data-[collapsible=icon]:px-0">
|
|
99
|
+
<Skeleton className="size-8 rounded-lg" />
|
|
100
|
+
<Skeleton className="h-4 flex-1 group-data-[collapsible=icon]:hidden" />
|
|
101
|
+
</div>
|
|
102
|
+
</SidebarMenuItem>
|
|
103
|
+
</SidebarMenu>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<SidebarMenu>
|
|
108
|
+
<SidebarMenuItem>
|
|
109
|
+
<DropdownMenu>
|
|
110
|
+
<DropdownMenuTrigger
|
|
111
|
+
render={
|
|
112
|
+
<SidebarMenuButton
|
|
113
|
+
size="lg"
|
|
114
|
+
aria-label={t.switchWorkspace}
|
|
115
|
+
// Card-like surface so the current workspace reads as a distinct control.
|
|
116
|
+
className="bg-card shadow-xs ring-1 ring-foreground/5 hover:bg-sidebar-accent data-popup-open:bg-sidebar-accent data-popup-open:text-sidebar-accent-foreground dark:ring-foreground/10"
|
|
117
|
+
/>
|
|
118
|
+
}
|
|
119
|
+
>
|
|
120
|
+
<WorkspaceMark
|
|
121
|
+
workspace={active}
|
|
122
|
+
className="transition-opacity group-data-[collapsible=icon]:group-hover/menu-button:opacity-70 group-data-[collapsible=icon]:group-data-popup-open/menu-button:opacity-70"
|
|
123
|
+
/>
|
|
124
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
125
|
+
<span className="truncate font-medium">{active.name}</span>
|
|
126
|
+
{active.description ? (
|
|
127
|
+
<span className="truncate text-xs text-muted-foreground">
|
|
128
|
+
{active.description}
|
|
129
|
+
</span>
|
|
130
|
+
) : null}
|
|
131
|
+
</div>
|
|
132
|
+
<ChevronsUpDownIcon className="ml-auto text-muted-foreground" />
|
|
133
|
+
</DropdownMenuTrigger>
|
|
134
|
+
<DropdownMenuContent
|
|
135
|
+
className="w-(--anchor-width) min-w-60"
|
|
136
|
+
align="start"
|
|
137
|
+
side={isMobile ? "bottom" : "right"}
|
|
138
|
+
sideOffset={4}
|
|
139
|
+
>
|
|
140
|
+
{[...groups.entries()].map(([group, items], index) => (
|
|
141
|
+
<DropdownMenuGroup key={group || index}>
|
|
142
|
+
{index > 0 ? <DropdownMenuSeparator /> : null}
|
|
143
|
+
<DropdownMenuLabel>{group || t.workspaces}</DropdownMenuLabel>
|
|
144
|
+
{items.map((workspace) => (
|
|
145
|
+
<DropdownMenuItem
|
|
146
|
+
key={workspace.id}
|
|
147
|
+
onClick={() => onSelect(workspace)}
|
|
148
|
+
>
|
|
149
|
+
<WorkspaceMark workspace={workspace} className="size-6" />
|
|
150
|
+
<div className="grid flex-1 leading-tight">
|
|
151
|
+
<span className="truncate">{workspace.name}</span>
|
|
152
|
+
{workspace.description ? (
|
|
153
|
+
<span className="truncate text-xs text-muted-foreground">
|
|
154
|
+
{workspace.description}
|
|
155
|
+
</span>
|
|
156
|
+
) : null}
|
|
157
|
+
</div>
|
|
158
|
+
{workspace.id === activeId ? (
|
|
159
|
+
<CheckIcon className="ml-auto" />
|
|
160
|
+
) : null}
|
|
161
|
+
</DropdownMenuItem>
|
|
162
|
+
))}
|
|
163
|
+
</DropdownMenuGroup>
|
|
164
|
+
))}
|
|
165
|
+
{actions ? (
|
|
166
|
+
<>
|
|
167
|
+
<DropdownMenuSeparator />
|
|
168
|
+
<DropdownMenuGroup>{actions}</DropdownMenuGroup>
|
|
169
|
+
</>
|
|
170
|
+
) : null}
|
|
171
|
+
</DropdownMenuContent>
|
|
172
|
+
</DropdownMenu>
|
|
173
|
+
</SidebarMenuItem>
|
|
174
|
+
</SidebarMenu>
|
|
175
|
+
);
|
|
176
|
+
}
|