@syscore/ui-library 1.3.5 → 1.3.7
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/client/components/ui/card.tsx +3 -1
- package/client/components/ui/standard-navigation.tsx +578 -0
- package/client/global.css +1 -0
- package/client/ui/StandardNavigation.stories.tsx +399 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +424 -1
- package/package.json +1 -1
|
@@ -75,15 +75,17 @@ interface CardWithIconProps {
|
|
|
75
75
|
icon?: React.ComponentType<React.SVGProps<SVGSVGElement> | { className?: string }>;
|
|
76
76
|
title: string;
|
|
77
77
|
description: string;
|
|
78
|
+
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
const CardWithIcon = React.forwardRef<
|
|
81
82
|
HTMLDivElement,
|
|
82
83
|
CardWithIconProps
|
|
83
|
-
>(({ icon: Icon, title, description }, ref) => (
|
|
84
|
+
>(({ icon: Icon, title, description, onClick }, ref) => (
|
|
84
85
|
<Card
|
|
85
86
|
ref={ref}
|
|
86
87
|
className="card-with-icon"
|
|
88
|
+
onClick={onClick}
|
|
87
89
|
>
|
|
88
90
|
<div className="card-with-icon__header">
|
|
89
91
|
{Icon && <Icon className="card-with-icon__icon" />}
|
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { AnimatePresence, motion } from "motion/react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { UtilityClose } from "../icons/UtilityClose";
|
|
5
|
+
import { NavBullet } from "../icons/NavBullet";
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { StandardLogo } from "../icons/StandardLogo";
|
|
8
|
+
import { NavLogo } from "../icons/NavLogo";
|
|
9
|
+
|
|
10
|
+
const ALPHA_PATH =
|
|
11
|
+
"M5.3387 0.0229882C5.37971 0.0229882 5.45295 0.0199228 5.5584 0.0137925C5.66386 0.00459714 5.74442 0 5.80007 0C6.72867 0 7.49908 0.269732 8.11131 0.809196C8.72354 1.34559 9.14097 2.09195 9.3636 3.04828C9.44855 3.47433 9.49835 3.96628 9.513 4.52414V5.16782C10.2014 4.07663 10.7287 2.85517 11.0948 1.50345C11.1505 1.29808 11.1959 1.17701 11.2311 1.14023C11.2662 1.10345 11.3761 1.08506 11.5606 1.08506C11.8535 1.08506 12 1.13563 12 1.23678C12 1.25211 11.9722 1.37778 11.9165 1.61379C11.5093 3.24751 10.7931 4.77701 9.76785 6.2023L9.53497 6.51034L9.55694 7.04368C9.59795 8.23295 9.72391 8.9318 9.93482 9.14023C9.97583 9.16782 10.0388 9.18161 10.1238 9.18161C10.32 9.15402 10.5031 9.06973 10.673 8.92874C10.8429 8.78774 10.963 8.62222 11.0333 8.43218C11.0597 8.32797 11.0948 8.26513 11.1388 8.24368C11.1798 8.22222 11.2852 8.21149 11.4551 8.21149C11.7364 8.21149 11.877 8.27739 11.877 8.40919C11.877 8.49808 11.8345 8.63142 11.7495 8.8092C11.5943 9.13103 11.3687 9.4069 11.0729 9.63678C10.777 9.8636 10.4475 9.97701 10.0842 9.97701H9.93482C8.97986 9.97701 8.3398 9.44674 8.01465 8.38621L7.95313 8.23448C7.20615 8.74942 6.79165 9.02835 6.70963 9.07126C5.66679 9.69042 4.61809 10 3.56353 10C2.56756 10 1.76346 9.70575 1.15123 9.11724C0.538997 8.52874 0.162578 7.75632 0.02197 6.8C0.0073233 6.71111 0 6.54866 0 6.31264C0 5.9295 0.02197 5.62146 0.0659099 5.38851C0.276822 4.06437 0.887587 2.8751 1.89821 1.82069C2.91175 0.769348 4.05859 0.170115 5.3387 0.0229882ZM1.83669 7.10805C1.83669 7.73946 1.9978 8.24368 2.32003 8.62069C2.64518 8.99464 3.09484 9.18161 3.66899 9.18161C4.03515 9.18161 4.44379 9.12337 4.89491 9.0069C5.6829 8.80153 6.44892 8.4046 7.19297 7.81609C7.29257 7.74253 7.38777 7.66437 7.47858 7.58161C7.56939 7.50192 7.64262 7.43295 7.69828 7.37471L7.78176 7.28276L7.76419 7.08506C7.76419 6.95326 7.75979 6.75862 7.75101 6.50115C7.74515 6.24368 7.74222 5.98927 7.74222 5.73793C7.72757 5.18008 7.71732 4.74636 7.71146 4.43678C7.70267 4.1272 7.67338 3.74866 7.62358 3.30115C7.57671 2.85364 7.5108 2.50728 7.42585 2.26207C7.3409 2.01992 7.22519 1.77471 7.07873 1.52644C6.92933 1.2751 6.73892 1.09425 6.50751 0.983908C6.27609 0.873563 6.00513 0.81839 5.69462 0.81839C4.72501 0.81839 3.87404 1.35479 3.14171 2.42759C2.79019 2.97318 2.49579 3.71647 2.25851 4.65747C1.9773 5.73333 1.83669 6.55019 1.83669 7.10805Z";
|
|
12
|
+
|
|
13
|
+
const PATH_LENGTH = 60;
|
|
14
|
+
|
|
15
|
+
const AlphaIcon = ({ dark }: { dark?: boolean }) => {
|
|
16
|
+
if (dark) {
|
|
17
|
+
return (
|
|
18
|
+
<svg
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
width="12"
|
|
21
|
+
height="10"
|
|
22
|
+
viewBox="0 0 12 10"
|
|
23
|
+
fill="none"
|
|
24
|
+
className="mt-1.5"
|
|
25
|
+
style={{ overflow: "visible" }}
|
|
26
|
+
>
|
|
27
|
+
<path d={ALPHA_PATH} fill="currentColor" />
|
|
28
|
+
{/* Animated stroke trace */}
|
|
29
|
+
{/* <motion.path
|
|
30
|
+
d={ALPHA_PATH}
|
|
31
|
+
fill="none"
|
|
32
|
+
stroke="#282A31"
|
|
33
|
+
strokeWidth="0.4"
|
|
34
|
+
strokeLinecap="round"
|
|
35
|
+
strokeLinejoin="round"
|
|
36
|
+
initial={{ strokeDashoffset: PATH_LENGTH }}
|
|
37
|
+
animate={{ strokeDashoffset: -PATH_LENGTH }}
|
|
38
|
+
style={{ strokeDasharray: `8 ${PATH_LENGTH - 8}` }}
|
|
39
|
+
transition={{
|
|
40
|
+
duration: 4,
|
|
41
|
+
repeat: Infinity,
|
|
42
|
+
ease: "linear",
|
|
43
|
+
}}
|
|
44
|
+
/> */}
|
|
45
|
+
</svg>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<svg
|
|
51
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
52
|
+
width="12"
|
|
53
|
+
height="10"
|
|
54
|
+
viewBox="0 0 12 10"
|
|
55
|
+
fill="none"
|
|
56
|
+
className="mt-1.5"
|
|
57
|
+
style={{ overflow: "visible" }}
|
|
58
|
+
>
|
|
59
|
+
{/* Base fill with gradient */}
|
|
60
|
+
<path
|
|
61
|
+
d={ALPHA_PATH}
|
|
62
|
+
fill="url(#paint0_linear_10081_30848)"
|
|
63
|
+
opacity="0.4"
|
|
64
|
+
/>
|
|
65
|
+
{/* Animated stroke trace with gradient */}
|
|
66
|
+
<motion.path
|
|
67
|
+
d={ALPHA_PATH}
|
|
68
|
+
fill="none"
|
|
69
|
+
stroke="url(#stroke_gradient)"
|
|
70
|
+
strokeWidth="0.4"
|
|
71
|
+
strokeLinecap="round"
|
|
72
|
+
strokeLinejoin="round"
|
|
73
|
+
initial={{ strokeDashoffset: PATH_LENGTH }}
|
|
74
|
+
animate={{ strokeDashoffset: -PATH_LENGTH }}
|
|
75
|
+
style={{ strokeDasharray: `8 ${PATH_LENGTH - 8}` }}
|
|
76
|
+
transition={{
|
|
77
|
+
duration: 2,
|
|
78
|
+
repeat: Infinity,
|
|
79
|
+
repeatDelay: 8,
|
|
80
|
+
ease: "linear",
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
<defs>
|
|
84
|
+
<linearGradient
|
|
85
|
+
id="paint0_linear_10081_30848"
|
|
86
|
+
x1="-3.27254"
|
|
87
|
+
y1="4.99923"
|
|
88
|
+
x2="6.36738"
|
|
89
|
+
y2="-2.36962"
|
|
90
|
+
gradientUnits="userSpaceOnUse"
|
|
91
|
+
>
|
|
92
|
+
<stop stopColor="#8AEFDB" />
|
|
93
|
+
<stop offset="0.5" stopColor="#D4BACE" />
|
|
94
|
+
<stop offset="1" stopColor="#CBE0F1" />
|
|
95
|
+
</linearGradient>
|
|
96
|
+
<linearGradient
|
|
97
|
+
id="stroke_gradient"
|
|
98
|
+
x1="0"
|
|
99
|
+
y1="0"
|
|
100
|
+
x2="12"
|
|
101
|
+
y2="10"
|
|
102
|
+
gradientUnits="userSpaceOnUse"
|
|
103
|
+
>
|
|
104
|
+
<stop stopColor="#8AEFDB" />
|
|
105
|
+
<stop offset="0.5" stopColor="#D4BACE" />
|
|
106
|
+
<stop offset="1" stopColor="#CBE0F1" />
|
|
107
|
+
</linearGradient>
|
|
108
|
+
</defs>
|
|
109
|
+
</svg>
|
|
110
|
+
);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// Define a Link component type that matches common routing patterns
|
|
114
|
+
export type LinkComponent = React.ComponentType<{
|
|
115
|
+
href: string;
|
|
116
|
+
className?: string;
|
|
117
|
+
"aria-label"?: string;
|
|
118
|
+
children: React.ReactNode;
|
|
119
|
+
onClick?: (e: React.MouseEvent) => void;
|
|
120
|
+
}>;
|
|
121
|
+
|
|
122
|
+
// Navigation link structure supporting nested links
|
|
123
|
+
export interface NavLinkItem {
|
|
124
|
+
label: string;
|
|
125
|
+
href?: string;
|
|
126
|
+
badge?: string;
|
|
127
|
+
bulletColor?: string;
|
|
128
|
+
children?: NavLinkItem[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Navigation section structure for tray content
|
|
132
|
+
export interface NavSection {
|
|
133
|
+
heading?: string;
|
|
134
|
+
headingGradient?: string;
|
|
135
|
+
columns?: NavColumn[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface NavColumn {
|
|
139
|
+
title?: string;
|
|
140
|
+
links: NavLinkItem[];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Main navigation item that can have nested content
|
|
144
|
+
export interface NavItem {
|
|
145
|
+
label: string;
|
|
146
|
+
href?: string;
|
|
147
|
+
section?: NavSection;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface NavigationProps {
|
|
151
|
+
navLinks?: NavItem[];
|
|
152
|
+
logo?: React.ComponentType<{ className?: string; dark?: boolean }>;
|
|
153
|
+
userIcon?: React.ComponentType<{ className?: string }>;
|
|
154
|
+
beforeActions?: React.ReactNode; // Slot for custom items before user icon (e.g., language switcher)
|
|
155
|
+
userDropdown?: React.ReactNode; // Dropdown content to show when user icon is clicked
|
|
156
|
+
isStrategy?: boolean;
|
|
157
|
+
Link?: LinkComponent; // Optional Link component from routing library
|
|
158
|
+
onLinkClick?: (href: string) => void; // Fallback callback for navigation
|
|
159
|
+
onClose?: () => void;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export const StandardNavigation: React.FC<NavigationProps> = ({
|
|
163
|
+
navLinks = [],
|
|
164
|
+
logo: Logo,
|
|
165
|
+
userIcon: UserIcon,
|
|
166
|
+
beforeActions,
|
|
167
|
+
userDropdown,
|
|
168
|
+
isStrategy = false,
|
|
169
|
+
Link: LinkComponent,
|
|
170
|
+
onLinkClick,
|
|
171
|
+
onClose,
|
|
172
|
+
}) => {
|
|
173
|
+
const [isOpen, setIsOpen] = React.useState(false);
|
|
174
|
+
const [activeItem, setActiveItem] = React.useState<number | null>(null);
|
|
175
|
+
const [isUserDropdownOpen, setIsUserDropdownOpen] = React.useState(false);
|
|
176
|
+
|
|
177
|
+
const containerRef = React.useRef<HTMLElement>(null);
|
|
178
|
+
const menuRef = React.useRef<HTMLDivElement>(null);
|
|
179
|
+
const menuItemsRef = React.useRef<HTMLDivElement>(null);
|
|
180
|
+
const overlayRef = React.useRef<HTMLDivElement>(null);
|
|
181
|
+
const userDropdownRef = React.useRef<HTMLDivElement>(null);
|
|
182
|
+
|
|
183
|
+
const handleMouseLeave = () => {
|
|
184
|
+
setIsOpen(false);
|
|
185
|
+
setActiveItem(null);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const handleNavItemMouseEnter = (index: number) => {
|
|
189
|
+
const item = navLinks[index];
|
|
190
|
+
if (item?.section) {
|
|
191
|
+
setIsOpen(true);
|
|
192
|
+
setActiveItem(index + 1);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const handleUserIconClick = (e: React.MouseEvent) => {
|
|
197
|
+
e.preventDefault();
|
|
198
|
+
if (userDropdown) {
|
|
199
|
+
setIsUserDropdownOpen(!isUserDropdownOpen);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Close user dropdown when clicking outside
|
|
204
|
+
React.useEffect(() => {
|
|
205
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
206
|
+
const target = event.target as Node;
|
|
207
|
+
if (
|
|
208
|
+
userDropdownRef.current &&
|
|
209
|
+
!userDropdownRef.current.contains(target)
|
|
210
|
+
) {
|
|
211
|
+
setIsUserDropdownOpen(false);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (isUserDropdownOpen) {
|
|
216
|
+
// Use setTimeout to avoid immediate closure on the click that opened it
|
|
217
|
+
setTimeout(() => {
|
|
218
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
219
|
+
}, 0);
|
|
220
|
+
return () => {
|
|
221
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}, [isUserDropdownOpen]);
|
|
225
|
+
|
|
226
|
+
// Internal NavLink component that handles different routing scenarios
|
|
227
|
+
const NavLink: React.FC<{
|
|
228
|
+
href: string;
|
|
229
|
+
className?: string;
|
|
230
|
+
"aria-label"?: string;
|
|
231
|
+
children: React.ReactNode;
|
|
232
|
+
}> = ({ href, className, "aria-label": ariaLabel, children }) => {
|
|
233
|
+
// If Link component is provided, use it
|
|
234
|
+
if (LinkComponent) {
|
|
235
|
+
return (
|
|
236
|
+
<LinkComponent href={href} className={className} aria-label={ariaLabel}>
|
|
237
|
+
{children}
|
|
238
|
+
</LinkComponent>
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// If onLinkClick callback is provided, use anchor with click handler
|
|
243
|
+
if (onLinkClick) {
|
|
244
|
+
return (
|
|
245
|
+
<a
|
|
246
|
+
href={href}
|
|
247
|
+
className={className}
|
|
248
|
+
aria-label={ariaLabel}
|
|
249
|
+
onClick={(e) => {
|
|
250
|
+
e.preventDefault();
|
|
251
|
+
onLinkClick(href);
|
|
252
|
+
}}
|
|
253
|
+
>
|
|
254
|
+
{children}
|
|
255
|
+
</a>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Default: plain anchor tag (browser navigation)
|
|
260
|
+
return (
|
|
261
|
+
<a href={href} className={className} aria-label={ariaLabel}>
|
|
262
|
+
{children}
|
|
263
|
+
</a>
|
|
264
|
+
);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Render a navigation link item
|
|
268
|
+
const renderNavLinkItem = (link: NavLinkItem) => {
|
|
269
|
+
const href = link.href || "#";
|
|
270
|
+
const hasBullet = link.bulletColor !== undefined;
|
|
271
|
+
const hasBadge = !!link.badge;
|
|
272
|
+
const isSmall = hasBullet || link.children;
|
|
273
|
+
|
|
274
|
+
if (hasBullet) {
|
|
275
|
+
return (
|
|
276
|
+
<div className="navigation-tray-bullet-item">
|
|
277
|
+
{link.bulletColor && link.bulletColor !== "" ? (
|
|
278
|
+
<NavBullet color={link.bulletColor} />
|
|
279
|
+
) : (
|
|
280
|
+
<div className="navigation-tray-bullet" />
|
|
281
|
+
)}
|
|
282
|
+
<NavLink
|
|
283
|
+
href={href}
|
|
284
|
+
className="navigation-tray-link-small"
|
|
285
|
+
>
|
|
286
|
+
{link.label}
|
|
287
|
+
</NavLink>
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (hasBadge) {
|
|
293
|
+
return (
|
|
294
|
+
<NavLink
|
|
295
|
+
href={href}
|
|
296
|
+
className="navigation-tray-link navigation-tray-link--with-badge"
|
|
297
|
+
>
|
|
298
|
+
{link.label}
|
|
299
|
+
<span className="navigation-tray-badge">{link.badge}</span>
|
|
300
|
+
</NavLink>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<NavLink
|
|
306
|
+
href={href}
|
|
307
|
+
className={isSmall ? "navigation-tray-link-small" : "navigation-tray-link"}
|
|
308
|
+
>
|
|
309
|
+
{link.label}
|
|
310
|
+
</NavLink>
|
|
311
|
+
);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// Render a navigation column
|
|
315
|
+
const renderNavColumn = (column: NavColumn) => {
|
|
316
|
+
if (!column.links || column.links.length === 0) return null;
|
|
317
|
+
|
|
318
|
+
const hasSpacing = column.links.some(
|
|
319
|
+
(link) => !link.bulletColor && !link.children,
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return (
|
|
323
|
+
<div key={column.title || "column"} className="navigation-tray-column">
|
|
324
|
+
{column.title && (
|
|
325
|
+
<h3 className="navigation-tray-column-title">{column.title}</h3>
|
|
326
|
+
)}
|
|
327
|
+
<ul
|
|
328
|
+
className={cn(
|
|
329
|
+
"navigation-tray-column-list",
|
|
330
|
+
hasSpacing && "navigation-tray-column-list--spacing-5",
|
|
331
|
+
)}
|
|
332
|
+
>
|
|
333
|
+
{column.links.map((link, idx) => (
|
|
334
|
+
<li key={idx}>{renderNavLinkItem(link)}</li>
|
|
335
|
+
))}
|
|
336
|
+
</ul>
|
|
337
|
+
</div>
|
|
338
|
+
);
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// Render the active tray section
|
|
342
|
+
const renderTraySection = () => {
|
|
343
|
+
if (!activeItem) return null;
|
|
344
|
+
|
|
345
|
+
const activeNavItem = navLinks[activeItem - 1];
|
|
346
|
+
if (!activeNavItem?.section) return null;
|
|
347
|
+
|
|
348
|
+
const section = activeNavItem.section;
|
|
349
|
+
|
|
350
|
+
return (
|
|
351
|
+
<div className="navigation-tray-section">
|
|
352
|
+
{section.heading && (
|
|
353
|
+
<h2
|
|
354
|
+
className="navigation-tray-heading"
|
|
355
|
+
style={
|
|
356
|
+
section.headingGradient
|
|
357
|
+
? {
|
|
358
|
+
background: section.headingGradient,
|
|
359
|
+
WebkitBackgroundClip: "text",
|
|
360
|
+
WebkitTextFillColor: "transparent",
|
|
361
|
+
backgroundClip: "text",
|
|
362
|
+
}
|
|
363
|
+
: undefined
|
|
364
|
+
}
|
|
365
|
+
>
|
|
366
|
+
{section.heading}
|
|
367
|
+
</h2>
|
|
368
|
+
)}
|
|
369
|
+
|
|
370
|
+
{section.columns && section.columns.length > 0 && (
|
|
371
|
+
<div className="navigation-tray-columns">
|
|
372
|
+
{section.columns.map((column, idx) => renderNavColumn(column))}
|
|
373
|
+
</div>
|
|
374
|
+
)}
|
|
375
|
+
</div>
|
|
376
|
+
);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<nav
|
|
381
|
+
ref={containerRef}
|
|
382
|
+
className={cn("navigation", isStrategy && "navigation--strategy")}
|
|
383
|
+
data-strategy={isStrategy}
|
|
384
|
+
onMouseLeave={handleMouseLeave}
|
|
385
|
+
>
|
|
386
|
+
{!isStrategy && (
|
|
387
|
+
<div
|
|
388
|
+
ref={overlayRef}
|
|
389
|
+
className="navigation-overlay"
|
|
390
|
+
data-open={isOpen}
|
|
391
|
+
/>
|
|
392
|
+
)}
|
|
393
|
+
|
|
394
|
+
<div className="navigation-container">
|
|
395
|
+
<div className="navigation-logo-container">
|
|
396
|
+
{isStrategy ? (
|
|
397
|
+
<Button
|
|
398
|
+
className="navigation-close-button"
|
|
399
|
+
size="icon"
|
|
400
|
+
onClick={(e) => {
|
|
401
|
+
e.preventDefault();
|
|
402
|
+
if (onClose) {
|
|
403
|
+
onClose();
|
|
404
|
+
} else if (onLinkClick) {
|
|
405
|
+
onLinkClick("/");
|
|
406
|
+
}
|
|
407
|
+
}}
|
|
408
|
+
>
|
|
409
|
+
<UtilityClose className="navigation-close-icon" />
|
|
410
|
+
</Button>
|
|
411
|
+
) : null}
|
|
412
|
+
|
|
413
|
+
<NavLink
|
|
414
|
+
href="/"
|
|
415
|
+
className={cn(
|
|
416
|
+
"navigation-logo-link",
|
|
417
|
+
isStrategy && "navigation-logo-link--strategy",
|
|
418
|
+
)}
|
|
419
|
+
data-strategy={isStrategy}
|
|
420
|
+
aria-label="Home"
|
|
421
|
+
>
|
|
422
|
+
{Logo ? (
|
|
423
|
+
<>
|
|
424
|
+
|
|
425
|
+
<NavLogo dark={isStrategy} />
|
|
426
|
+
<StandardLogo className={cn(!isStrategy ? "text-white" : "text-black")} />
|
|
427
|
+
<AlphaIcon dark={isStrategy} />
|
|
428
|
+
</>
|
|
429
|
+
|
|
430
|
+
) : null}
|
|
431
|
+
</NavLink>
|
|
432
|
+
</div>
|
|
433
|
+
|
|
434
|
+
{!isStrategy ? (
|
|
435
|
+
<div className="navigation-nav-container">
|
|
436
|
+
<ul className="navigation-nav-list">
|
|
437
|
+
{navLinks.map((item, index) => (
|
|
438
|
+
<li
|
|
439
|
+
key={index}
|
|
440
|
+
className="navigation-nav-item"
|
|
441
|
+
onMouseEnter={() => handleNavItemMouseEnter(index)}
|
|
442
|
+
>
|
|
443
|
+
{item.href ? (
|
|
444
|
+
<NavLink href={item.href} className="navigation-nav-link">
|
|
445
|
+
{item.label}
|
|
446
|
+
</NavLink>
|
|
447
|
+
) : (
|
|
448
|
+
<span className="navigation-nav-link">{item.label}</span>
|
|
449
|
+
)}
|
|
450
|
+
|
|
451
|
+
{/* Underline */}
|
|
452
|
+
{activeItem === index + 1 && (
|
|
453
|
+
<motion.div
|
|
454
|
+
id="underline"
|
|
455
|
+
className="navigation-underline"
|
|
456
|
+
layoutId="underline"
|
|
457
|
+
transition={{ duration: 0.2, ease: "easeOut" }}
|
|
458
|
+
>
|
|
459
|
+
<div
|
|
460
|
+
className="navigation-underline-circle navigation-underline-circle--gradient"
|
|
461
|
+
style={{
|
|
462
|
+
width: "10px",
|
|
463
|
+
height: "5px",
|
|
464
|
+
borderBottomLeftRadius: "100px",
|
|
465
|
+
borderBottomRightRadius: "100px",
|
|
466
|
+
borderBottom: "0",
|
|
467
|
+
boxSizing: "border-box",
|
|
468
|
+
}}
|
|
469
|
+
/>
|
|
470
|
+
</motion.div>
|
|
471
|
+
)}
|
|
472
|
+
{index === 0 && (
|
|
473
|
+
<div
|
|
474
|
+
className="navigation-underline navigation-underline--static"
|
|
475
|
+
style={{ transition: "all 0.2s ease-out" }}
|
|
476
|
+
>
|
|
477
|
+
<div
|
|
478
|
+
className="navigation-underline-circle navigation-underline-circle--white"
|
|
479
|
+
style={{
|
|
480
|
+
width: "10px",
|
|
481
|
+
height: "5px",
|
|
482
|
+
borderTopLeftRadius: "100px",
|
|
483
|
+
borderTopRightRadius: "100px",
|
|
484
|
+
borderBottom: "0",
|
|
485
|
+
boxSizing: "border-box",
|
|
486
|
+
}}
|
|
487
|
+
/>
|
|
488
|
+
</div>
|
|
489
|
+
)}
|
|
490
|
+
</li>
|
|
491
|
+
))}
|
|
492
|
+
</ul>
|
|
493
|
+
</div>
|
|
494
|
+
) : null}
|
|
495
|
+
|
|
496
|
+
<div className="navigation-actions">
|
|
497
|
+
{beforeActions && (
|
|
498
|
+
<div className="navigation-actions-before">{beforeActions}</div>
|
|
499
|
+
)}
|
|
500
|
+
<div className="navigation-account-wrapper" ref={userDropdownRef}>
|
|
501
|
+
{userDropdown ? (
|
|
502
|
+
<button
|
|
503
|
+
type="button"
|
|
504
|
+
aria-label="Account menu"
|
|
505
|
+
aria-expanded={isUserDropdownOpen}
|
|
506
|
+
className={cn(
|
|
507
|
+
"navigation-account-link",
|
|
508
|
+
isUserDropdownOpen && "navigation-account-link--open",
|
|
509
|
+
)}
|
|
510
|
+
data-open={isOpen}
|
|
511
|
+
data-strategy={isStrategy}
|
|
512
|
+
onClick={handleUserIconClick}
|
|
513
|
+
>
|
|
514
|
+
{UserIcon ? (
|
|
515
|
+
<UserIcon
|
|
516
|
+
className={cn(
|
|
517
|
+
"navigation-account-icon",
|
|
518
|
+
isStrategy
|
|
519
|
+
? "navigation-account-icon--strategy"
|
|
520
|
+
: "navigation-account-icon--default",
|
|
521
|
+
)}
|
|
522
|
+
/>
|
|
523
|
+
) : (
|
|
524
|
+
<div className="navigation-account-icon-placeholder">User</div>
|
|
525
|
+
)}
|
|
526
|
+
</button>
|
|
527
|
+
) : (
|
|
528
|
+
<NavLink
|
|
529
|
+
href="/"
|
|
530
|
+
aria-label="Account link"
|
|
531
|
+
className="navigation-account-link"
|
|
532
|
+
data-open={isOpen}
|
|
533
|
+
data-strategy={isStrategy}
|
|
534
|
+
>
|
|
535
|
+
{UserIcon ? (
|
|
536
|
+
<UserIcon
|
|
537
|
+
className={cn(
|
|
538
|
+
"navigation-account-icon",
|
|
539
|
+
isStrategy
|
|
540
|
+
? "navigation-account-icon--strategy"
|
|
541
|
+
: "navigation-account-icon--default",
|
|
542
|
+
)}
|
|
543
|
+
/>
|
|
544
|
+
) : (
|
|
545
|
+
<div className="navigation-account-icon-placeholder">User</div>
|
|
546
|
+
)}
|
|
547
|
+
</NavLink>
|
|
548
|
+
)}
|
|
549
|
+
{isUserDropdownOpen && userDropdown && (
|
|
550
|
+
<div className="navigation-user-dropdown">{userDropdown}</div>
|
|
551
|
+
)}
|
|
552
|
+
</div>
|
|
553
|
+
</div>
|
|
554
|
+
</div>
|
|
555
|
+
|
|
556
|
+
{isOpen && <div className="navigation-divider" />}
|
|
557
|
+
|
|
558
|
+
{/* Tray */}
|
|
559
|
+
<div ref={menuRef} className="navigation-tray" data-open={isOpen}>
|
|
560
|
+
<div className="navigation-tray-content">
|
|
561
|
+
<div ref={menuItemsRef} className="navigation-tray-grid">
|
|
562
|
+
<AnimatePresence mode="wait">
|
|
563
|
+
<motion.div
|
|
564
|
+
key={activeItem ? activeItem : "empty"}
|
|
565
|
+
initial={{ y: 10, opacity: 0 }}
|
|
566
|
+
animate={{ y: 0, opacity: 1 }}
|
|
567
|
+
exit={{ y: -10, opacity: 0 }}
|
|
568
|
+
transition={{ duration: 0.2 }}
|
|
569
|
+
>
|
|
570
|
+
{renderTraySection()}
|
|
571
|
+
</motion.div>
|
|
572
|
+
</AnimatePresence>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
</div>
|
|
576
|
+
</nav>
|
|
577
|
+
);
|
|
578
|
+
};
|