@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.
- 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 +162 -53
- package/src/patterns/data-table.tsx +2441 -180
- package/src/patterns/details-sheet.tsx +10 -7
- 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/patterns/user-menu.tsx +218 -0
- package/src/patterns/workspace-switcher.tsx +173 -0
- 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
|
@@ -0,0 +1,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 workspace={active} />
|
|
121
|
+
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
122
|
+
<span className="truncate font-medium">{active.name}</span>
|
|
123
|
+
{active.description ? (
|
|
124
|
+
<span className="truncate text-xs text-muted-foreground">
|
|
125
|
+
{active.description}
|
|
126
|
+
</span>
|
|
127
|
+
) : null}
|
|
128
|
+
</div>
|
|
129
|
+
<ChevronsUpDownIcon className="ml-auto text-muted-foreground" />
|
|
130
|
+
</DropdownMenuTrigger>
|
|
131
|
+
<DropdownMenuContent
|
|
132
|
+
className="w-(--anchor-width) min-w-60"
|
|
133
|
+
align="start"
|
|
134
|
+
side={isMobile ? "bottom" : "right"}
|
|
135
|
+
sideOffset={4}
|
|
136
|
+
>
|
|
137
|
+
{[...groups.entries()].map(([group, items], index) => (
|
|
138
|
+
<DropdownMenuGroup key={group || index}>
|
|
139
|
+
{index > 0 ? <DropdownMenuSeparator /> : null}
|
|
140
|
+
<DropdownMenuLabel>{group || t.workspaces}</DropdownMenuLabel>
|
|
141
|
+
{items.map((workspace) => (
|
|
142
|
+
<DropdownMenuItem
|
|
143
|
+
key={workspace.id}
|
|
144
|
+
onClick={() => onSelect(workspace)}
|
|
145
|
+
>
|
|
146
|
+
<WorkspaceMark workspace={workspace} className="size-6" />
|
|
147
|
+
<div className="grid flex-1 leading-tight">
|
|
148
|
+
<span className="truncate">{workspace.name}</span>
|
|
149
|
+
{workspace.description ? (
|
|
150
|
+
<span className="truncate text-xs text-muted-foreground">
|
|
151
|
+
{workspace.description}
|
|
152
|
+
</span>
|
|
153
|
+
) : null}
|
|
154
|
+
</div>
|
|
155
|
+
{workspace.id === activeId ? (
|
|
156
|
+
<CheckIcon className="ml-auto" />
|
|
157
|
+
) : null}
|
|
158
|
+
</DropdownMenuItem>
|
|
159
|
+
))}
|
|
160
|
+
</DropdownMenuGroup>
|
|
161
|
+
))}
|
|
162
|
+
{actions ? (
|
|
163
|
+
<>
|
|
164
|
+
<DropdownMenuSeparator />
|
|
165
|
+
<DropdownMenuGroup>{actions}</DropdownMenuGroup>
|
|
166
|
+
</>
|
|
167
|
+
) : null}
|
|
168
|
+
</DropdownMenuContent>
|
|
169
|
+
</DropdownMenu>
|
|
170
|
+
</SidebarMenuItem>
|
|
171
|
+
</SidebarMenu>
|
|
172
|
+
);
|
|
173
|
+
}
|
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:
|
|
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
|
-
--
|
|
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.
|
|
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.
|
|
162
|
-
--chart-2: oklch(0.
|
|
163
|
-
--chart-3: oklch(0.
|
|
164
|
-
--chart-4: oklch(0.
|
|
165
|
-
--chart-5: oklch(0.
|
|
166
|
-
--
|
|
167
|
-
--success: oklch(0.
|
|
168
|
-
--
|
|
169
|
-
--warning: oklch(0.
|
|
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.
|
|
205
|
-
--chart-2: oklch(0.
|
|
206
|
-
--chart-3: oklch(0.
|
|
207
|
-
--chart-4: oklch(0.
|
|
208
|
-
--chart-5: oklch(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
|
|
220
|
-
.
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
.
|
|
391
|
-
|
|
392
|
-
flex-direction: column;
|
|
393
|
-
gap: 1rem;
|
|
139
|
+
.dark {
|
|
140
|
+
color-scheme: dark;
|
|
394
141
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
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
|
|
11
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
33
|
-
brandMark,
|
|
34
|
-
context,
|
|
35
|
-
actions,
|
|
26
|
+
pageActions,
|
|
36
27
|
beforeTable,
|
|
37
28
|
afterTable,
|
|
38
|
-
|
|
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={
|
|
36
|
+
actions={pageActions}
|
|
56
37
|
/>
|
|
57
38
|
<MetricStrip items={metrics} />
|
|
58
39
|
{beforeTable}
|