@fayz-ai/ui 0.9.0-next.1 → 0.10.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/README.md +2 -2
- package/dist/analytics/AnalyticsCard.d.ts +10 -0
- package/dist/analytics/AnalyticsCard.d.ts.map +1 -0
- package/dist/analytics/AnalyticsControls.d.ts +18 -0
- package/dist/analytics/AnalyticsControls.d.ts.map +1 -0
- package/dist/analytics/AnalyticsGrid.d.ts +16 -0
- package/dist/analytics/AnalyticsGrid.d.ts.map +1 -0
- package/dist/analytics/context.d.ts +58 -0
- package/dist/analytics/context.d.ts.map +1 -0
- package/dist/analytics/derive.d.ts +81 -0
- package/dist/analytics/derive.d.ts.map +1 -0
- package/dist/analytics/index.d.ts +13 -0
- package/dist/analytics/index.d.ts.map +1 -0
- package/dist/analytics/range.d.ts +22 -0
- package/dist/analytics/range.d.ts.map +1 -0
- package/dist/{chunk-PVIB724L.js → chunk-22EVFMS3.js} +5 -3
- package/dist/chunk-22EVFMS3.js.map +1 -0
- package/dist/{chunk-G6OKZ7WL.js → chunk-6V5MGYXH.js} +13 -9
- package/dist/chunk-6V5MGYXH.js.map +1 -0
- package/dist/{chunk-KV3MHB6N.js → chunk-H63TOSQ7.js} +196 -53
- package/dist/chunk-H63TOSQ7.js.map +1 -0
- package/dist/{chunk-OUPJDGKE.js → chunk-I6CPKAOD.js} +134 -37
- package/dist/chunk-I6CPKAOD.js.map +1 -0
- package/dist/{chunk-YRVE4FOD.js → chunk-OKZ6WQB3.js} +3 -3
- package/dist/chunk-OKZ6WQB3.js.map +1 -0
- package/dist/{chunk-S5KNM4D2.js → chunk-SEIY2F5F.js} +235 -107
- package/dist/chunk-SEIY2F5F.js.map +1 -0
- package/dist/{chunk-75EFDLST.js → chunk-VAKWUUVC.js} +157 -6
- package/dist/chunk-VAKWUUVC.js.map +1 -0
- package/dist/dashboard/OnboardingWidget.d.ts +2 -14
- package/dist/dashboard/OnboardingWidget.d.ts.map +1 -1
- package/dist/dashboard/SidebarOnboarding.d.ts +28 -0
- package/dist/dashboard/SidebarOnboarding.d.ts.map +1 -0
- package/dist/dashboard/index.d.ts +2 -0
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/index.js +4 -4
- package/dist/dashboard/types.d.ts +18 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/dashboard/useOnboardingProgress.d.ts +28 -0
- package/dist/dashboard/useOnboardingProgress.d.ts.map +1 -0
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +960 -14
- package/dist/index.js.map +1 -1
- package/dist/layout/AppShell.d.ts +14 -1
- package/dist/layout/AppShell.d.ts.map +1 -1
- package/dist/layout/ModulePage.d.ts +11 -2
- package/dist/layout/ModulePage.d.ts.map +1 -1
- package/dist/layout/Sidebar.d.ts +8 -1
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/index.js +4 -4
- package/dist/lib/cn.d.ts.map +1 -0
- package/dist/preset.d.ts +3 -0
- package/dist/preset.d.ts.map +1 -0
- package/dist/primitives/data-table.d.ts +19 -4
- package/dist/primitives/data-table.d.ts.map +1 -1
- package/dist/primitives/date-picker.d.ts +4 -2
- package/dist/primitives/date-picker.d.ts.map +1 -1
- package/dist/primitives/filter-chip.d.ts +26 -0
- package/dist/primitives/filter-chip.d.ts.map +1 -0
- package/dist/primitives/header-tabs.d.ts +32 -0
- package/dist/primitives/header-tabs.d.ts.map +1 -0
- package/dist/primitives/hint.d.ts +25 -0
- package/dist/primitives/hint.d.ts.map +1 -0
- package/dist/primitives/index.d.ts +7 -0
- package/dist/primitives/index.d.ts.map +1 -1
- package/dist/primitives/index.js +4 -4
- package/dist/primitives/list-view.d.ts +14 -1
- package/dist/primitives/list-view.d.ts.map +1 -1
- package/dist/primitives/page-header.d.ts +23 -0
- package/dist/primitives/page-header.d.ts.map +1 -0
- package/dist/primitives/stat-tiles.d.ts +30 -0
- package/dist/primitives/stat-tiles.d.ts.map +1 -0
- package/dist/primitives/switch.d.ts +11 -0
- package/dist/primitives/switch.d.ts.map +1 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/page-header.store.d.ts +69 -0
- package/dist/stores/page-header.store.d.ts.map +1 -0
- package/package.json +4 -13
- package/dist/chunk-75EFDLST.js.map +0 -1
- package/dist/chunk-G6OKZ7WL.js.map +0 -1
- package/dist/chunk-KV3MHB6N.js.map +0 -1
- package/dist/chunk-OUPJDGKE.js.map +0 -1
- package/dist/chunk-PVIB724L.js.map +0 -1
- package/dist/chunk-S5KNM4D2.js.map +0 -1
- package/dist/chunk-YRVE4FOD.js.map +0 -1
- package/dist/utils/cn.d.ts.map +0 -1
- package/src/dashboard/ChartWidget.tsx +0 -50
- package/src/dashboard/ChartWidgetInner.tsx +0 -83
- package/src/dashboard/DashboardCanvas.tsx +0 -210
- package/src/dashboard/DashboardCustomizeMenu.tsx +0 -53
- package/src/dashboard/DashboardGrid.tsx +0 -35
- package/src/dashboard/DashboardRange.tsx +0 -62
- package/src/dashboard/GoldCard.tsx +0 -38
- package/src/dashboard/KpiCard.tsx +0 -238
- package/src/dashboard/OnboardingWidget.tsx +0 -140
- package/src/dashboard/TableWidget.tsx +0 -51
- package/src/dashboard/define.ts +0 -53
- package/src/dashboard/icon.ts +0 -13
- package/src/dashboard/index.ts +0 -38
- package/src/dashboard/layout.ts +0 -50
- package/src/dashboard/preferences.ts +0 -71
- package/src/dashboard/types.ts +0 -41
- package/src/icons.ts +0 -66
- package/src/index.ts +0 -238
- package/src/layout/AppShell.tsx +0 -987
- package/src/layout/ContentSplit.tsx +0 -153
- package/src/layout/ModulePage.tsx +0 -466
- package/src/layout/PageTransition.tsx +0 -57
- package/src/layout/SaveBar.tsx +0 -247
- package/src/layout/Sidebar.tsx +0 -535
- package/src/layout/Topbar.tsx +0 -462
- package/src/layout/index.ts +0 -39
- package/src/layout/useCrossModuleBack.ts +0 -30
- package/src/primitives/Avatar.tsx +0 -48
- package/src/primitives/SearchCombobox.tsx +0 -216
- package/src/primitives/badge.tsx +0 -68
- package/src/primitives/breadcrumb.tsx +0 -43
- package/src/primitives/button.tsx +0 -60
- package/src/primitives/card.tsx +0 -79
- package/src/primitives/checkbox.tsx +0 -53
- package/src/primitives/confirm-dialog.tsx +0 -125
- package/src/primitives/currency-input.tsx +0 -121
- package/src/primitives/data-table.tsx +0 -160
- package/src/primitives/date-picker.tsx +0 -225
- package/src/primitives/dropdown.tsx +0 -196
- package/src/primitives/index.ts +0 -87
- package/src/primitives/input.tsx +0 -50
- package/src/primitives/list-view.tsx +0 -252
- package/src/primitives/markdown-editor.tsx +0 -75
- package/src/primitives/markdown.tsx +0 -169
- package/src/primitives/modal.tsx +0 -156
- package/src/primitives/phone-link.tsx +0 -138
- package/src/primitives/popover.tsx +0 -30
- package/src/primitives/search-select.tsx +0 -314
- package/src/primitives/segmented-control.tsx +0 -66
- package/src/primitives/select.tsx +0 -157
- package/src/primitives/separator.tsx +0 -29
- package/src/primitives/sheet.tsx +0 -181
- package/src/primitives/skeleton.tsx +0 -46
- package/src/primitives/tabs.tsx +0 -53
- package/src/primitives/time-picker.tsx +0 -148
- package/src/primitives/toast.tsx +0 -46
- package/src/primitives/tooltip.tsx +0 -28
- package/src/stores/index.ts +0 -10
- package/src/stores/layout.store.ts +0 -31
- package/src/stores/theme.store.ts +0 -457
- package/src/styles.css +0 -461
- package/src/tailwind.ts +0 -43
- package/src/theme/index.ts +0 -94
- package/src/theme/preset.ts +0 -209
- package/src/theme/presets.ts +0 -106
- package/src/utils/cn.ts +0 -6
- /package/dist/{utils → lib}/cn.d.ts +0 -0
package/src/layout/Topbar.tsx
DELETED
|
@@ -1,462 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import * as Popover from '@radix-ui/react-popover'
|
|
3
|
-
import {
|
|
4
|
-
Menu,
|
|
5
|
-
Search,
|
|
6
|
-
ChevronDown,
|
|
7
|
-
Filter,
|
|
8
|
-
Home,
|
|
9
|
-
Users,
|
|
10
|
-
Settings,
|
|
11
|
-
CreditCard,
|
|
12
|
-
Bell,
|
|
13
|
-
Calendar,
|
|
14
|
-
CalendarClock,
|
|
15
|
-
Package,
|
|
16
|
-
Activity,
|
|
17
|
-
BarChart3,
|
|
18
|
-
Clapperboard,
|
|
19
|
-
Newspaper,
|
|
20
|
-
FileText,
|
|
21
|
-
Mail,
|
|
22
|
-
DollarSign,
|
|
23
|
-
Megaphone,
|
|
24
|
-
ShoppingCart,
|
|
25
|
-
ShoppingBag,
|
|
26
|
-
Target,
|
|
27
|
-
Wrench,
|
|
28
|
-
ClipboardList,
|
|
29
|
-
ClipboardCheck,
|
|
30
|
-
Briefcase,
|
|
31
|
-
UserCog,
|
|
32
|
-
BookOpen,
|
|
33
|
-
BookOpenCheck,
|
|
34
|
-
GraduationCap,
|
|
35
|
-
Repeat,
|
|
36
|
-
MessageCircle,
|
|
37
|
-
Globe,
|
|
38
|
-
Percent,
|
|
39
|
-
Tag,
|
|
40
|
-
Tags,
|
|
41
|
-
Camera,
|
|
42
|
-
UtensilsCrossed,
|
|
43
|
-
MapPin,
|
|
44
|
-
Handshake,
|
|
45
|
-
Contact,
|
|
46
|
-
Building2,
|
|
47
|
-
Plus,
|
|
48
|
-
Shield,
|
|
49
|
-
List,
|
|
50
|
-
ListChecks,
|
|
51
|
-
FileCheck2,
|
|
52
|
-
FolderOpen,
|
|
53
|
-
Dog,
|
|
54
|
-
Cat,
|
|
55
|
-
PawPrint,
|
|
56
|
-
Heart,
|
|
57
|
-
LayoutTemplate,
|
|
58
|
-
LeafyGreen,
|
|
59
|
-
Apple,
|
|
60
|
-
Egg,
|
|
61
|
-
Wheat,
|
|
62
|
-
ArrowDownCircle,
|
|
63
|
-
ArrowUpCircle,
|
|
64
|
-
Landmark,
|
|
65
|
-
Receipt,
|
|
66
|
-
TrendingUp,
|
|
67
|
-
CircleDollarSign,
|
|
68
|
-
Wallet,
|
|
69
|
-
Clock,
|
|
70
|
-
AlertTriangle,
|
|
71
|
-
Sparkles,
|
|
72
|
-
Warehouse,
|
|
73
|
-
Ruler,
|
|
74
|
-
ArrowUpRight,
|
|
75
|
-
ArrowDownRight,
|
|
76
|
-
ArrowLeftRight,
|
|
77
|
-
Plug,
|
|
78
|
-
SlidersHorizontal,
|
|
79
|
-
TableProperties,
|
|
80
|
-
UserPlus,
|
|
81
|
-
UserCheck,
|
|
82
|
-
UserX,
|
|
83
|
-
TreePalm,
|
|
84
|
-
Boxes,
|
|
85
|
-
Box,
|
|
86
|
-
PartyPopper,
|
|
87
|
-
Radio,
|
|
88
|
-
CalendarDays,
|
|
89
|
-
CalendarCheck2,
|
|
90
|
-
CalendarX,
|
|
91
|
-
BadgeDollarSign,
|
|
92
|
-
Banknote,
|
|
93
|
-
Layers,
|
|
94
|
-
Music,
|
|
95
|
-
Eye,
|
|
96
|
-
ListMusic,
|
|
97
|
-
ListPlus,
|
|
98
|
-
Map,
|
|
99
|
-
Ban,
|
|
100
|
-
Clock3,
|
|
101
|
-
Disc3,
|
|
102
|
-
UsersRound,
|
|
103
|
-
Mic,
|
|
104
|
-
Inbox,
|
|
105
|
-
Star,
|
|
106
|
-
Zap,
|
|
107
|
-
Crown,
|
|
108
|
-
LogOut,
|
|
109
|
-
type LucideIcon,
|
|
110
|
-
} from 'lucide-react'
|
|
111
|
-
import { cn } from '../utils/cn'
|
|
112
|
-
import { useLayoutStore } from '../stores/layout.store'
|
|
113
|
-
|
|
114
|
-
export interface NavigationItem {
|
|
115
|
-
id: string
|
|
116
|
-
label: string
|
|
117
|
-
icon: string
|
|
118
|
-
route: string
|
|
119
|
-
section: 'main' | 'secondary' | 'settings'
|
|
120
|
-
badge?: string | number
|
|
121
|
-
children?: NavigationItem[]
|
|
122
|
-
permission?: { feature: string; action: 'read' | 'create' | 'edit' | 'delete' }
|
|
123
|
-
/** Freemium discovery: role-allowed but not entitled by the current plan.
|
|
124
|
-
* Renders a small Crown badge; clicking leads to the plan's UpgradePrompt. */
|
|
125
|
-
premium?: boolean
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export interface TopbarProps {
|
|
129
|
-
navigation: NavigationItem[]
|
|
130
|
-
logo?: React.ReactNode
|
|
131
|
-
user?: { fullName: string; avatarUrl?: string; email: string }
|
|
132
|
-
onMenuClick?: () => void
|
|
133
|
-
/** Workspace/org switcher rendered right after the logo (desktop). */
|
|
134
|
-
orgSwitcher?: React.ReactNode
|
|
135
|
-
leftContent?: React.ReactNode
|
|
136
|
-
rightContent?: React.ReactNode
|
|
137
|
-
currentPath?: string
|
|
138
|
-
onNavigate?: (route: string) => void
|
|
139
|
-
searchPlaceholder?: string
|
|
140
|
-
onSignOut?: () => void
|
|
141
|
-
onProfile?: () => void
|
|
142
|
-
onSettings?: () => void
|
|
143
|
-
onBilling?: () => void
|
|
144
|
-
/** Current-plan pill shown next to the user in the dropdown header. */
|
|
145
|
-
userPlan?: { label: string; paid: boolean }
|
|
146
|
-
/** i18n label for the billing/subscription menu item (host owns translation). */
|
|
147
|
-
billingLabel?: string
|
|
148
|
-
userMenuExtras?: { label: string; icon?: React.ReactNode; onClick: () => void }[]
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function TopbarUserMenu({
|
|
152
|
-
user,
|
|
153
|
-
userPlan,
|
|
154
|
-
billingLabel = 'Subscription',
|
|
155
|
-
onSignOut,
|
|
156
|
-
onProfile,
|
|
157
|
-
onSettings,
|
|
158
|
-
onBilling,
|
|
159
|
-
userMenuExtras,
|
|
160
|
-
}: Pick<TopbarProps, 'user' | 'userPlan' | 'billingLabel' | 'onSignOut' | 'onProfile' | 'onSettings' | 'onBilling' | 'userMenuExtras'>) {
|
|
161
|
-
const [open, setOpen] = React.useState(false)
|
|
162
|
-
if (!user) return null
|
|
163
|
-
const initial = (user.fullName?.trim()?.[0] ?? user.email?.[0] ?? '?').toUpperCase()
|
|
164
|
-
const item = (label: string, onClick?: () => void, Icon?: LucideIcon) =>
|
|
165
|
-
onClick ? (
|
|
166
|
-
<button
|
|
167
|
-
key={label}
|
|
168
|
-
onClick={() => { setOpen(false); onClick() }}
|
|
169
|
-
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-popover-foreground transition-colors hover:bg-muted/70"
|
|
170
|
-
>
|
|
171
|
-
{Icon && <Icon className="h-4 w-4 text-muted-foreground" />}
|
|
172
|
-
<span>{label}</span>
|
|
173
|
-
</button>
|
|
174
|
-
) : null
|
|
175
|
-
return (
|
|
176
|
-
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
177
|
-
<Popover.Trigger asChild>
|
|
178
|
-
<button
|
|
179
|
-
aria-label="User menu"
|
|
180
|
-
className="ml-1 flex h-9 w-9 items-center justify-center overflow-hidden rounded-full bg-primary text-xs font-semibold text-primary-foreground ring-2 ring-transparent transition hover:ring-sidebar-border"
|
|
181
|
-
>
|
|
182
|
-
{user.avatarUrl ? (
|
|
183
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
184
|
-
<img src={user.avatarUrl} alt="" className="h-full w-full object-cover" />
|
|
185
|
-
) : (
|
|
186
|
-
initial
|
|
187
|
-
)}
|
|
188
|
-
</button>
|
|
189
|
-
</Popover.Trigger>
|
|
190
|
-
<Popover.Portal>
|
|
191
|
-
<Popover.Content
|
|
192
|
-
align="end"
|
|
193
|
-
sideOffset={8}
|
|
194
|
-
className="fayz-glass-surface z-50 min-w-[220px] rounded-xl border border-border bg-popover p-1.5 shadow-lg animate-in fade-in-0 zoom-in-95"
|
|
195
|
-
>
|
|
196
|
-
<div className="border-b border-border px-3 py-2">
|
|
197
|
-
<p className="truncate text-sm font-medium text-foreground">{user.fullName || user.email}</p>
|
|
198
|
-
<p className="truncate text-xs text-muted-foreground">{user.email}</p>
|
|
199
|
-
</div>
|
|
200
|
-
<div className="mt-1 space-y-0.5">
|
|
201
|
-
{item('Perfil', onProfile, UserCog)}
|
|
202
|
-
{item('Configurações', onSettings, Settings)}
|
|
203
|
-
{onBilling && (
|
|
204
|
-
<button
|
|
205
|
-
key="billing"
|
|
206
|
-
onClick={() => { setOpen(false); onBilling() }}
|
|
207
|
-
className="flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-popover-foreground transition-colors hover:bg-muted/70"
|
|
208
|
-
>
|
|
209
|
-
<CreditCard className="h-4 w-4 text-muted-foreground" />
|
|
210
|
-
<span className="flex-1 text-left">{billingLabel}</span>
|
|
211
|
-
{userPlan && (
|
|
212
|
-
<span
|
|
213
|
-
className={
|
|
214
|
-
userPlan.paid
|
|
215
|
-
? 'inline-flex items-center gap-1 rounded-full border border-amber-400/40 bg-amber-400/15 px-2 py-0.5 text-[10px] font-semibold text-amber-600 dark:text-amber-300'
|
|
216
|
-
: 'inline-flex items-center gap-1 rounded-full border border-border bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground'
|
|
217
|
-
}
|
|
218
|
-
>
|
|
219
|
-
{userPlan.paid ? <Crown className="h-3 w-3" /> : <Sparkles className="h-3 w-3" />}
|
|
220
|
-
{userPlan.label}
|
|
221
|
-
</span>
|
|
222
|
-
)}
|
|
223
|
-
</button>
|
|
224
|
-
)}
|
|
225
|
-
{userMenuExtras?.map((e) => item(e.label, e.onClick))}
|
|
226
|
-
{onSignOut && (
|
|
227
|
-
<>
|
|
228
|
-
<div className="my-1 border-t border-border" />
|
|
229
|
-
{item('Sair', onSignOut, LogOut)}
|
|
230
|
-
</>
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
233
|
-
</Popover.Content>
|
|
234
|
-
</Popover.Portal>
|
|
235
|
-
</Popover.Root>
|
|
236
|
-
)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export const ICON_MAP: Record<string, LucideIcon> = {
|
|
240
|
-
Home, Users, Settings, CreditCard, Bell, Calendar, CalendarClock, Package, Activity, BarChart3,
|
|
241
|
-
FileText, Mail, DollarSign, Megaphone, ShoppingCart, ShoppingBag, Target, Wrench,
|
|
242
|
-
ClipboardList, ClipboardCheck, Briefcase, UserCog, BookOpen, MessageCircle, Globe,
|
|
243
|
-
BookOpenCheck, Percent, Tag, Tags, Camera, UtensilsCrossed, Search, MapPin, Handshake,
|
|
244
|
-
Contact, Building2, ChevronDown, Filter, Plus, Shield, List, ListChecks, FolderOpen, Dog, Cat, PawPrint, Heart, LayoutTemplate, LeafyGreen, Apple, Egg, Wheat,
|
|
245
|
-
ArrowDownCircle, ArrowUpCircle, ArrowLeftRight, Plug, SlidersHorizontal, TableProperties, Landmark, Receipt, TrendingUp, CircleDollarSign, Clock, AlertTriangle, Sparkles, Wallet, Warehouse, Ruler, ArrowUpRight, ArrowDownRight, UserPlus, UserX, TreePalm,
|
|
246
|
-
Boxes, Box, FileCheck2, PartyPopper, Radio, CalendarDays, BadgeDollarSign, Banknote, Layers, Music, Eye, ListMusic, Disc3, UsersRound, Mic,
|
|
247
|
-
CalendarCheck2, CalendarX, ListPlus, Map, Ban, Clock3, Inbox, UserCheck, Star, Zap, Clapperboard, Newspaper, GraduationCap, Repeat,
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function getIcon(name: string): LucideIcon {
|
|
251
|
-
return ICON_MAP[name] ?? Home
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function NavDropdown({ item, currentPath, onNavigate }: {
|
|
255
|
-
item: NavigationItem
|
|
256
|
-
currentPath: string
|
|
257
|
-
onNavigate: (route: string) => void
|
|
258
|
-
}) {
|
|
259
|
-
const [open, setOpen] = React.useState(false)
|
|
260
|
-
const Icon = getIcon(item.icon)
|
|
261
|
-
const isChildActive = item.children?.some(
|
|
262
|
-
(child) => currentPath === child.route || currentPath.startsWith(child.route + '/')
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
return (
|
|
266
|
-
<Popover.Root open={open} onOpenChange={setOpen}>
|
|
267
|
-
<Popover.Trigger asChild>
|
|
268
|
-
<button
|
|
269
|
-
data-nav-active={isChildActive || undefined}
|
|
270
|
-
className={cn(
|
|
271
|
-
'flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors',
|
|
272
|
-
isChildActive
|
|
273
|
-
? 'border-sidebar-accent-foreground font-semibold text-sidebar-foreground'
|
|
274
|
-
: 'border-transparent text-sidebar-muted hover:text-sidebar-foreground'
|
|
275
|
-
)}
|
|
276
|
-
>
|
|
277
|
-
<Icon className="h-4 w-4" />
|
|
278
|
-
<span>{item.label}</span>
|
|
279
|
-
<ChevronDown className={cn('h-3 w-3 transition-transform', open && 'rotate-180')} />
|
|
280
|
-
</button>
|
|
281
|
-
</Popover.Trigger>
|
|
282
|
-
<Popover.Portal>
|
|
283
|
-
<Popover.Content
|
|
284
|
-
align="start"
|
|
285
|
-
sideOffset={4}
|
|
286
|
-
className="fayz-glass-surface z-50 min-w-[200px] rounded-xl border border-border bg-popover p-1.5 shadow-lg animate-in fade-in-0 zoom-in-95"
|
|
287
|
-
>
|
|
288
|
-
{item.children?.map((child) => {
|
|
289
|
-
const ChildIcon = getIcon(child.icon)
|
|
290
|
-
const isActive = currentPath === child.route || currentPath.startsWith(child.route + '/')
|
|
291
|
-
return (
|
|
292
|
-
<button
|
|
293
|
-
key={child.id}
|
|
294
|
-
onClick={() => {
|
|
295
|
-
onNavigate(child.route)
|
|
296
|
-
setOpen(false)
|
|
297
|
-
}}
|
|
298
|
-
className={cn(
|
|
299
|
-
'flex w-full items-center gap-2.5 rounded-lg px-3 py-2 text-sm transition-colors',
|
|
300
|
-
isActive
|
|
301
|
-
? 'bg-muted font-medium text-foreground'
|
|
302
|
-
: 'text-popover-foreground hover:bg-muted/70'
|
|
303
|
-
)}
|
|
304
|
-
>
|
|
305
|
-
<ChildIcon className="h-4 w-4 text-muted-foreground" />
|
|
306
|
-
<span>{child.label}</span>
|
|
307
|
-
</button>
|
|
308
|
-
)
|
|
309
|
-
})}
|
|
310
|
-
</Popover.Content>
|
|
311
|
-
</Popover.Portal>
|
|
312
|
-
</Popover.Root>
|
|
313
|
-
)
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
export function Topbar({
|
|
317
|
-
navigation,
|
|
318
|
-
logo,
|
|
319
|
-
user,
|
|
320
|
-
onMenuClick,
|
|
321
|
-
orgSwitcher,
|
|
322
|
-
leftContent,
|
|
323
|
-
rightContent,
|
|
324
|
-
currentPath = '/',
|
|
325
|
-
onNavigate,
|
|
326
|
-
searchPlaceholder = 'Search...',
|
|
327
|
-
onSignOut,
|
|
328
|
-
onProfile,
|
|
329
|
-
onSettings,
|
|
330
|
-
onBilling,
|
|
331
|
-
userPlan,
|
|
332
|
-
billingLabel,
|
|
333
|
-
userMenuExtras,
|
|
334
|
-
}: TopbarProps) {
|
|
335
|
-
const setCommandPaletteOpen = useLayoutStore((s) => s.setCommandPaletteOpen)
|
|
336
|
-
|
|
337
|
-
const mainNav = navigation.filter((item) => item.section === 'main')
|
|
338
|
-
const secondaryNav = navigation.filter((item) => item.section === 'secondary')
|
|
339
|
-
const allNav = [...mainNav, ...secondaryNav]
|
|
340
|
-
|
|
341
|
-
const handleNavigate = (route: string) => {
|
|
342
|
-
onNavigate?.(route)
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
return (
|
|
346
|
-
<header data-print="hide" className="sticky top-0 z-50 w-full shrink-0">
|
|
347
|
-
{/* Row 1: Logo + Search + Actions */}
|
|
348
|
-
<div className="border-b border-sidebar-border bg-sidebar text-sidebar-foreground">
|
|
349
|
-
<div className="flex h-14 w-full items-center justify-between px-4 md:px-6">
|
|
350
|
-
{/* Left: Mobile menu + Logo — the hamburger is only rendered when an
|
|
351
|
-
onMenuClick handler is supplied. Mobile-first apps with a bottom
|
|
352
|
-
nav omit it so the bottom bar is the primary navigation. */}
|
|
353
|
-
<div className="flex items-center gap-3">
|
|
354
|
-
{onMenuClick && (
|
|
355
|
-
<button
|
|
356
|
-
onClick={onMenuClick}
|
|
357
|
-
className="inline-flex items-center justify-center rounded-md p-2 text-sidebar-foreground/70 hover:bg-sidebar/30 hover:text-sidebar-foreground md:hidden"
|
|
358
|
-
aria-label="Open menu"
|
|
359
|
-
>
|
|
360
|
-
<Menu className="h-5 w-5" />
|
|
361
|
-
</button>
|
|
362
|
-
)}
|
|
363
|
-
<div className="hidden shrink-0 items-center md:flex">
|
|
364
|
-
{logo ?? <span className="text-lg font-bold">App</span>}
|
|
365
|
-
</div>
|
|
366
|
-
{orgSwitcher && (
|
|
367
|
-
<div className="hidden min-w-0 items-center md:flex">{orgSwitcher}</div>
|
|
368
|
-
)}
|
|
369
|
-
</div>
|
|
370
|
-
|
|
371
|
-
{/* Center: Search (desktop only) */}
|
|
372
|
-
<div className="mx-8 hidden max-w-sm flex-1 md:flex">
|
|
373
|
-
<button
|
|
374
|
-
onClick={() => setCommandPaletteOpen(true)}
|
|
375
|
-
className="relative flex h-8 w-full items-center rounded-md border border-sidebar-border bg-sidebar-accent px-3 pl-8 text-sm text-sidebar-muted shadow-sm transition-colors hover:border-sidebar-foreground/25 hover:bg-sidebar-foreground/10 hover:text-sidebar-foreground"
|
|
376
|
-
>
|
|
377
|
-
<Search className="absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-sidebar-muted" />
|
|
378
|
-
<span className="text-xs">{searchPlaceholder}</span>
|
|
379
|
-
<kbd className="ml-auto hidden rounded border border-sidebar-border bg-sidebar-accent px-1.5 py-0.5 text-[10px] font-medium text-sidebar-muted sm:inline-block">
|
|
380
|
-
⌘K
|
|
381
|
-
</kbd>
|
|
382
|
-
</button>
|
|
383
|
-
</div>
|
|
384
|
-
|
|
385
|
-
{/* Right: Actions */}
|
|
386
|
-
<div className="flex items-center gap-1">
|
|
387
|
-
{leftContent}
|
|
388
|
-
{rightContent}
|
|
389
|
-
<TopbarUserMenu
|
|
390
|
-
user={user}
|
|
391
|
-
userPlan={userPlan}
|
|
392
|
-
billingLabel={billingLabel}
|
|
393
|
-
onSignOut={onSignOut}
|
|
394
|
-
onProfile={onProfile}
|
|
395
|
-
onSettings={onSettings}
|
|
396
|
-
onBilling={onBilling}
|
|
397
|
-
userMenuExtras={userMenuExtras}
|
|
398
|
-
/>
|
|
399
|
-
</div>
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
|
|
403
|
-
{/* Row 2: Navigation — desktop only */}
|
|
404
|
-
<div className="hidden md:block bg-sidebar">
|
|
405
|
-
<div className="flex h-11 w-full items-center px-4 md:px-6">
|
|
406
|
-
<nav ref={(el) => {
|
|
407
|
-
if (!el) return
|
|
408
|
-
const active = el.querySelector('[data-nav-active="true"]') as HTMLElement
|
|
409
|
-
if (active) {
|
|
410
|
-
const left = active.offsetLeft - el.offsetWidth / 2 + active.offsetWidth / 2
|
|
411
|
-
el.scrollTo({ left: Math.max(0, left), behavior: 'smooth' })
|
|
412
|
-
}
|
|
413
|
-
}} className="flex items-center gap-0.5 overflow-x-auto scrollbar-none">
|
|
414
|
-
{allNav.map((item) => {
|
|
415
|
-
if (item.children && item.children.length > 0) {
|
|
416
|
-
return (
|
|
417
|
-
<NavDropdown
|
|
418
|
-
key={item.id}
|
|
419
|
-
item={item}
|
|
420
|
-
currentPath={currentPath}
|
|
421
|
-
onNavigate={handleNavigate}
|
|
422
|
-
/>
|
|
423
|
-
)
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
const Icon = getIcon(item.icon)
|
|
427
|
-
const isActive =
|
|
428
|
-
item.route === '/'
|
|
429
|
-
? currentPath === '/'
|
|
430
|
-
: currentPath === item.route || currentPath.startsWith(item.route + '/')
|
|
431
|
-
|
|
432
|
-
return (
|
|
433
|
-
<button
|
|
434
|
-
key={item.id}
|
|
435
|
-
data-nav-active={isActive || undefined}
|
|
436
|
-
onClick={() => handleNavigate(item.route)}
|
|
437
|
-
className={cn(
|
|
438
|
-
'flex items-center gap-1.5 whitespace-nowrap border-b-2 px-3 py-2 text-sm transition-colors',
|
|
439
|
-
isActive
|
|
440
|
-
? 'border-primary font-semibold text-sidebar-foreground'
|
|
441
|
-
: 'border-transparent text-sidebar-muted hover:text-sidebar-foreground'
|
|
442
|
-
)}
|
|
443
|
-
>
|
|
444
|
-
<Icon className="h-4 w-4" />
|
|
445
|
-
<span>{item.label}</span>
|
|
446
|
-
{item.premium && (
|
|
447
|
-
<Crown className="h-3 w-3 text-amber-500" aria-hidden />
|
|
448
|
-
)}
|
|
449
|
-
{item.badge !== undefined && (
|
|
450
|
-
<span className="rounded-full bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-primary-foreground">
|
|
451
|
-
{item.badge}
|
|
452
|
-
</span>
|
|
453
|
-
)}
|
|
454
|
-
</button>
|
|
455
|
-
)
|
|
456
|
-
})}
|
|
457
|
-
</nav>
|
|
458
|
-
</div>
|
|
459
|
-
</div>
|
|
460
|
-
</header>
|
|
461
|
-
)
|
|
462
|
-
}
|
package/src/layout/index.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
export {
|
|
2
|
-
AppShell,
|
|
3
|
-
type AppShellProps,
|
|
4
|
-
type AppShellUser,
|
|
5
|
-
type BottomNavItem,
|
|
6
|
-
type BottomNavRouteItem,
|
|
7
|
-
type BottomNavActionItem,
|
|
8
|
-
type MobileHeaderVariant,
|
|
9
|
-
} from './AppShell'
|
|
10
|
-
export { Sidebar, type SidebarProps, type SidebarUser, type NavigationItem, ICON_MAP } from './Sidebar'
|
|
11
|
-
export { Topbar, type TopbarProps } from './Topbar'
|
|
12
|
-
export {
|
|
13
|
-
ModulePage,
|
|
14
|
-
SubpageHeader,
|
|
15
|
-
ModuleLayoutProvider,
|
|
16
|
-
useModuleLayout,
|
|
17
|
-
PageHeaderActions,
|
|
18
|
-
BackStyleProvider,
|
|
19
|
-
useBackStyle,
|
|
20
|
-
type ModulePageProps,
|
|
21
|
-
type ModuleNavItem,
|
|
22
|
-
type ModuleNavVariant,
|
|
23
|
-
type SubpageHeaderProps,
|
|
24
|
-
type BackButtonStyle,
|
|
25
|
-
} from './ModulePage'
|
|
26
|
-
export { useModuleHeaderSlot, useModuleHeaderActionsSlot } from './AppShell'
|
|
27
|
-
export { SaveBar, SaveBarProvider, useSaveBar, useBackHandler, type SaveBarRegistration } from './SaveBar'
|
|
28
|
-
export {
|
|
29
|
-
PageTransition,
|
|
30
|
-
NavTransitionProvider,
|
|
31
|
-
useNavTransition,
|
|
32
|
-
type NavTransition,
|
|
33
|
-
} from './PageTransition'
|
|
34
|
-
export {
|
|
35
|
-
ContentSplit,
|
|
36
|
-
ContentSplitTrigger,
|
|
37
|
-
type ContentSplitProps,
|
|
38
|
-
type ContentSplitTriggerProps,
|
|
39
|
-
} from './ContentSplit'
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { useNavReferrer, navHistoryBack, routeModule } from '@fayz-ai/core'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Context-aware back target shared by all header/breadcrumb components.
|
|
5
|
-
*
|
|
6
|
-
* When the user arrived from a *different* module (tracked in the global
|
|
7
|
-
* navigation history), a page's own onBack/parentLabel point at its local
|
|
8
|
-
* parent — wrong for the referrer. In that case we return the referrer's label
|
|
9
|
-
* and a back action that pops the browser history to the actual previous page.
|
|
10
|
-
* Within the same module the page's own (more specific) values pass through
|
|
11
|
-
* unchanged, so existing behavior is preserved.
|
|
12
|
-
*/
|
|
13
|
-
export function useCrossModuleBack(onBack?: () => void, parentLabel?: string): {
|
|
14
|
-
onBack?: () => void
|
|
15
|
-
parentLabel?: string
|
|
16
|
-
isCrossModule: boolean
|
|
17
|
-
} {
|
|
18
|
-
const currentPath = typeof window !== 'undefined'
|
|
19
|
-
? ((window.location.hash ? window.location.hash.slice(1) : window.location.pathname) || '/')
|
|
20
|
-
: '/'
|
|
21
|
-
const referrer = useNavReferrer(currentPath)
|
|
22
|
-
const cross = referrer && referrer.label && routeModule(referrer.path) !== routeModule(currentPath)
|
|
23
|
-
? referrer
|
|
24
|
-
: null
|
|
25
|
-
return {
|
|
26
|
-
onBack: cross ? () => navHistoryBack(onBack) : onBack,
|
|
27
|
-
parentLabel: cross ? cross.label : parentLabel,
|
|
28
|
-
isCrossModule: !!cross,
|
|
29
|
-
}
|
|
30
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as React from 'react'
|
|
2
|
-
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
|
3
|
-
|
|
4
|
-
import { cn } from '../utils/cn'
|
|
5
|
-
|
|
6
|
-
const Avatar = React.forwardRef<
|
|
7
|
-
React.ElementRef<typeof AvatarPrimitive.Root>,
|
|
8
|
-
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
|
9
|
-
>(({ className, ...props }, ref) => (
|
|
10
|
-
<AvatarPrimitive.Root
|
|
11
|
-
ref={ref}
|
|
12
|
-
className={cn(
|
|
13
|
-
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
|
|
14
|
-
className
|
|
15
|
-
)}
|
|
16
|
-
{...props}
|
|
17
|
-
/>
|
|
18
|
-
))
|
|
19
|
-
Avatar.displayName = AvatarPrimitive.Root.displayName
|
|
20
|
-
|
|
21
|
-
const AvatarImage = React.forwardRef<
|
|
22
|
-
React.ElementRef<typeof AvatarPrimitive.Image>,
|
|
23
|
-
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
|
24
|
-
>(({ className, ...props }, ref) => (
|
|
25
|
-
<AvatarPrimitive.Image
|
|
26
|
-
ref={ref}
|
|
27
|
-
className={cn('aspect-square h-full w-full', className)}
|
|
28
|
-
{...props}
|
|
29
|
-
/>
|
|
30
|
-
))
|
|
31
|
-
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
|
32
|
-
|
|
33
|
-
const AvatarFallback = React.forwardRef<
|
|
34
|
-
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
|
35
|
-
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
|
36
|
-
>(({ className, ...props }, ref) => (
|
|
37
|
-
<AvatarPrimitive.Fallback
|
|
38
|
-
ref={ref}
|
|
39
|
-
className={cn(
|
|
40
|
-
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
|
41
|
-
className
|
|
42
|
-
)}
|
|
43
|
-
{...props}
|
|
44
|
-
/>
|
|
45
|
-
))
|
|
46
|
-
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
|
47
|
-
|
|
48
|
-
export { Avatar, AvatarImage, AvatarFallback }
|