@xsolla/xui-b2c-top-navigation 0.0.0-stage → 0.223.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.
@@ -0,0 +1,957 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.tsx
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ TopNavigation: () => TopNavigation
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+
37
+ // src/TopNavigation.tsx
38
+ var import_react3 = __toESM(require("react"));
39
+
40
+ // ../../foundation/primitives-native/src/Box.tsx
41
+ var import_react = __toESM(require("react"));
42
+ var import_react_native = require("react-native");
43
+ var roleToAccessibilityRole = {
44
+ button: "button",
45
+ link: "link",
46
+ img: "image",
47
+ image: "image",
48
+ summary: "summary",
49
+ switch: "switch",
50
+ checkbox: "checkbox",
51
+ radio: "radio",
52
+ heading: "header",
53
+ text: "text",
54
+ search: "search",
55
+ alert: "alert",
56
+ none: "none",
57
+ presentation: "none",
58
+ progressbar: "progressbar",
59
+ scrollbar: "scrollbar",
60
+ adjustable: "adjustable",
61
+ tab: "tab",
62
+ menu: "menu",
63
+ menuitem: "menuitem",
64
+ list: "list",
65
+ timer: "timer"
66
+ };
67
+ var toGapValue = (gap) => {
68
+ if (gap === void 0) return void 0;
69
+ if (typeof gap === "number") return gap;
70
+ const pxMatch = /^(\d+(?:\.\d+)?)px$/.exec(gap);
71
+ if (pxMatch) return Number(pxMatch[1]);
72
+ const asNumber = Number(gap);
73
+ return Number.isFinite(asNumber) ? asNumber : void 0;
74
+ };
75
+ var toNativeDisplay = (display) => display === "none" ? "none" : void 0;
76
+ var Box = ({
77
+ children,
78
+ onPress,
79
+ onLayout,
80
+ onMoveShouldSetResponder,
81
+ onResponderGrant,
82
+ onResponderMove,
83
+ onResponderRelease,
84
+ onResponderTerminate,
85
+ backgroundColor,
86
+ borderColor,
87
+ borderWidth,
88
+ borderBottomWidth,
89
+ borderBottomColor,
90
+ borderTopWidth,
91
+ borderTopColor,
92
+ borderLeftWidth,
93
+ borderLeftColor,
94
+ borderRightWidth,
95
+ borderRightColor,
96
+ borderRadius,
97
+ borderStyle,
98
+ height,
99
+ padding,
100
+ paddingHorizontal,
101
+ paddingVertical,
102
+ margin,
103
+ marginTop,
104
+ marginBottom,
105
+ marginLeft,
106
+ marginRight,
107
+ display,
108
+ flexDirection,
109
+ flexWrap,
110
+ alignItems,
111
+ justifyContent,
112
+ alignSelf,
113
+ position,
114
+ top,
115
+ bottom,
116
+ left,
117
+ right,
118
+ width,
119
+ minWidth,
120
+ minHeight,
121
+ maxWidth,
122
+ maxHeight,
123
+ flex,
124
+ flexShrink,
125
+ gap,
126
+ overflow,
127
+ opacity,
128
+ zIndex,
129
+ hoverStyle,
130
+ pressStyle,
131
+ style,
132
+ disabled,
133
+ "data-testid": dataTestId,
134
+ testID,
135
+ as,
136
+ src,
137
+ alt,
138
+ ...rest
139
+ }) => {
140
+ const getContainerStyle = (pressed) => ({
141
+ backgroundColor: pressed && pressStyle?.backgroundColor ? pressStyle.backgroundColor : backgroundColor,
142
+ borderColor,
143
+ borderWidth,
144
+ borderBottomWidth,
145
+ borderBottomColor,
146
+ borderTopWidth,
147
+ borderTopColor,
148
+ borderLeftWidth,
149
+ borderLeftColor,
150
+ borderRightWidth,
151
+ borderRightColor,
152
+ borderRadius,
153
+ borderStyle,
154
+ overflow,
155
+ opacity: opacity ?? (disabled ? 0.5 : void 0),
156
+ zIndex,
157
+ height,
158
+ width,
159
+ minWidth,
160
+ minHeight,
161
+ maxWidth,
162
+ maxHeight,
163
+ padding,
164
+ paddingHorizontal,
165
+ paddingVertical,
166
+ margin,
167
+ marginTop,
168
+ marginBottom,
169
+ marginLeft,
170
+ marginRight,
171
+ display: toNativeDisplay(display),
172
+ flexDirection,
173
+ flexWrap,
174
+ alignItems,
175
+ justifyContent,
176
+ alignSelf,
177
+ position,
178
+ top,
179
+ bottom,
180
+ left,
181
+ right,
182
+ flex,
183
+ flexShrink,
184
+ gap: toGapValue(gap),
185
+ ...style
186
+ });
187
+ const finalTestID = dataTestId || testID;
188
+ const {
189
+ role,
190
+ tabIndex,
191
+ onKeyDown,
192
+ onKeyUp,
193
+ "aria-label": ariaLabel,
194
+ "aria-labelledby": _ariaLabelledBy,
195
+ "aria-current": _ariaCurrent,
196
+ "aria-disabled": ariaDisabled,
197
+ "aria-live": ariaLive,
198
+ "aria-busy": ariaBusy,
199
+ className,
200
+ "data-testid": _dataTestId,
201
+ cursor: _cursor,
202
+ type: _type,
203
+ ...nativeRest
204
+ } = rest;
205
+ const accessibilityRole = typeof role === "string" && roleToAccessibilityRole[role] || (as === "button" ? "button" : void 0);
206
+ const isDisabled = Boolean(disabled || ariaDisabled);
207
+ const accessibilityState = {
208
+ disabled: isDisabled || void 0,
209
+ busy: Boolean(ariaBusy) || void 0
210
+ };
211
+ const accessibilityLiveRegion = ariaLive === "polite" || ariaLive === "assertive" ? ariaLive : void 0;
212
+ if (as === "img" && src) {
213
+ const imageStyle = {
214
+ width,
215
+ height,
216
+ borderRadius,
217
+ position,
218
+ top,
219
+ bottom,
220
+ left,
221
+ right,
222
+ ...style
223
+ };
224
+ return /* @__PURE__ */ import_react.default.createElement(
225
+ import_react_native.Image,
226
+ {
227
+ source: { uri: src },
228
+ style: imageStyle,
229
+ testID: finalTestID,
230
+ accessibilityLabel: typeof ariaLabel === "string" ? ariaLabel : alt,
231
+ resizeMode: "cover",
232
+ ...nativeRest
233
+ }
234
+ );
235
+ }
236
+ if (onPress || as === "button") {
237
+ return /* @__PURE__ */ import_react.default.createElement(
238
+ import_react_native.Pressable,
239
+ {
240
+ onPress: !isDisabled && onPress ? onPress : void 0,
241
+ disabled: isDisabled,
242
+ onLayout,
243
+ onMoveShouldSetResponder,
244
+ onResponderGrant,
245
+ onResponderMove,
246
+ onResponderRelease,
247
+ onResponderTerminate,
248
+ style: ({ pressed }) => getContainerStyle(pressed),
249
+ testID: finalTestID,
250
+ accessibilityRole,
251
+ accessibilityLabel: typeof ariaLabel === "string" ? ariaLabel : void 0,
252
+ accessibilityState,
253
+ accessibilityLiveRegion,
254
+ ...nativeRest
255
+ },
256
+ children
257
+ );
258
+ }
259
+ return /* @__PURE__ */ import_react.default.createElement(
260
+ import_react_native.View,
261
+ {
262
+ style: getContainerStyle(),
263
+ testID: finalTestID,
264
+ onLayout,
265
+ onMoveShouldSetResponder,
266
+ onResponderGrant,
267
+ onResponderMove,
268
+ onResponderRelease,
269
+ onResponderTerminate,
270
+ accessibilityRole,
271
+ accessibilityLabel: typeof ariaLabel === "string" ? ariaLabel : void 0,
272
+ accessibilityState,
273
+ accessibilityLiveRegion,
274
+ ...nativeRest
275
+ },
276
+ children
277
+ );
278
+ };
279
+
280
+ // ../../foundation/primitives-native/src/Text.tsx
281
+ var import_react2 = __toESM(require("react"));
282
+ var import_react_native2 = require("react-native");
283
+ var roleMap = {
284
+ alert: "alert",
285
+ heading: "header",
286
+ button: "button",
287
+ link: "link",
288
+ text: "text"
289
+ };
290
+ var parseNumericValue = (value) => {
291
+ if (value === void 0) return void 0;
292
+ if (typeof value === "number") return value;
293
+ const parsed = parseFloat(value);
294
+ return isNaN(parsed) ? void 0 : parsed;
295
+ };
296
+ var Text = ({
297
+ children,
298
+ color,
299
+ fontSize,
300
+ fontWeight,
301
+ fontFamily,
302
+ textAlign,
303
+ lineHeight,
304
+ numberOfLines,
305
+ id,
306
+ role,
307
+ testID,
308
+ "data-testid": dataTestId,
309
+ style: styleProp,
310
+ ...props
311
+ }) => {
312
+ let resolvedFontFamily = fontFamily ? fontFamily.split(",")[0].replace(/['"]/g, "").trim() : void 0;
313
+ if (resolvedFontFamily === "Pilat Wide" || resolvedFontFamily === "Pilat Wide Bold" || resolvedFontFamily === "Aktiv Grotesk") {
314
+ resolvedFontFamily = void 0;
315
+ }
316
+ const incomingStyle = import_react_native2.StyleSheet.flatten(styleProp);
317
+ const baseStyle = {
318
+ color: color ?? incomingStyle?.color,
319
+ fontSize: typeof fontSize === "number" ? fontSize : void 0,
320
+ fontWeight,
321
+ fontFamily: resolvedFontFamily,
322
+ textDecorationLine: props.textDecoration,
323
+ textAlign: textAlign ?? incomingStyle?.textAlign,
324
+ lineHeight: parseNumericValue(lineHeight ?? incomingStyle?.lineHeight),
325
+ marginTop: parseNumericValue(
326
+ incomingStyle?.marginTop
327
+ ),
328
+ marginBottom: parseNumericValue(
329
+ incomingStyle?.marginBottom
330
+ )
331
+ };
332
+ const accessibilityRole = role ? roleMap[role] : void 0;
333
+ return /* @__PURE__ */ import_react2.default.createElement(
334
+ import_react_native2.Text,
335
+ {
336
+ style: baseStyle,
337
+ numberOfLines,
338
+ testID: dataTestId || testID || id,
339
+ accessibilityRole
340
+ },
341
+ children
342
+ );
343
+ };
344
+
345
+ // src/TopNavigation.tsx
346
+ var import_xui_core = require("@xsolla/xui-core");
347
+ var import_xui_avatar = require("@xsolla/xui-avatar");
348
+ var import_xui_breadcrumbs = require("@xsolla/xui-breadcrumbs");
349
+ var import_xui_button = require("@xsolla/xui-button");
350
+ var import_xui_context_menu = require("@xsolla/xui-context-menu");
351
+ var import_xui_icons_base = require("@xsolla/xui-icons-base");
352
+ var BAR_HEIGHT = 88;
353
+ var BAR_PADDING_VERTICAL = 24;
354
+ var CLUSTER_GAP = 40;
355
+ var MENU_GAP = 24;
356
+ var ACCOUNT_GAP = 8;
357
+ var WINDOWS_LOGO_GAP = 88;
358
+ var LOGO_HEIGHT = 40;
359
+ var NAV_ICON_SIZE = 20;
360
+ var BACK_ICON_SIZE = 18;
361
+ var CURRENCY_ICON_SIZE = 18;
362
+ var MENU_DIVIDER_HEIGHT = 16;
363
+ var ICON_SLIDE_MS = 360;
364
+ var ICON_SLIDE_EASE = "cubic-bezier(0.22, 1, 0.36, 1)";
365
+ var prefersReducedMotion = () => typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
366
+ var useIsomorphicLayoutEffect = typeof window !== "undefined" ? import_react3.useLayoutEffect : import_react3.useEffect;
367
+ var isDarkMode = (mode) => !(mode ?? "dark").includes("light");
368
+ var tintIcon = (icon, color, size) => {
369
+ if (!import_react3.default.isValidElement(icon)) return icon;
370
+ const existing = icon.props;
371
+ return import_react3.default.cloneElement(
372
+ icon,
373
+ {
374
+ size: existing.size ?? size,
375
+ color,
376
+ "aria-hidden": true
377
+ }
378
+ );
379
+ };
380
+ var MaximizeGlyph = ({
381
+ size = NAV_ICON_SIZE,
382
+ color
383
+ }) => /* @__PURE__ */ import_react3.default.createElement(
384
+ Box,
385
+ {
386
+ width: Math.round(size * 0.7),
387
+ height: Math.round(size * 0.7),
388
+ borderWidth: 1.5,
389
+ borderColor: color,
390
+ borderRadius: 2
391
+ }
392
+ );
393
+ var ActiveIcon = ({ icon, color, phase, play, travel }) => {
394
+ const moving = phase === "in" || phase === "out";
395
+ const collapsed = phase === "out" && play || phase === "in" && !play;
396
+ const offset = phase === "out" && play ? travel : phase === "in" && !play ? -travel : 0;
397
+ return /* @__PURE__ */ import_react3.default.createElement(
398
+ Box,
399
+ {
400
+ height: NAV_ICON_SIZE,
401
+ flexShrink: 0,
402
+ alignItems: "center",
403
+ justifyContent: "center",
404
+ overflow: "visible",
405
+ "aria-hidden": true,
406
+ style: {
407
+ width: collapsed ? 0 : NAV_ICON_SIZE,
408
+ marginRight: collapsed ? 0 : 4,
409
+ transform: `translateX(${offset}px)`,
410
+ opacity: collapsed ? 0 : 1,
411
+ pointerEvents: "none",
412
+ transition: moving ? `transform ${ICON_SLIDE_MS}ms ${ICON_SLIDE_EASE}, opacity ${ICON_SLIDE_MS}ms ${ICON_SLIDE_EASE}, width ${ICON_SLIDE_MS}ms ${ICON_SLIDE_EASE}, margin-right ${ICON_SLIDE_MS}ms ${ICON_SLIDE_EASE}` : void 0
413
+ }
414
+ },
415
+ tintIcon(icon, color, NAV_ICON_SIZE)
416
+ );
417
+ };
418
+ var NavPoint = ({
419
+ item,
420
+ isActive,
421
+ isOpen,
422
+ iconSlide,
423
+ iconPlaying,
424
+ registerAnchor,
425
+ theme,
426
+ themeMode,
427
+ themeProductContext,
428
+ onOpenChange,
429
+ onPress,
430
+ onSelect,
431
+ linkComponent: LinkComponent
432
+ }) => {
433
+ const [hovered, setHovered] = (0, import_react3.useState)(false);
434
+ const hasMenu = Boolean(item.children?.length);
435
+ const isDisabled = Boolean(item.disabled);
436
+ const emphasized = isActive || hovered || isOpen;
437
+ const color = emphasized ? theme.colors.content.brand.secondary : theme.colors.content.secondary;
438
+ const body = theme.typographyTokens.basic["body-sm"];
439
+ const iconPhase = item.icon === void 0 || item.icon === null ? "off" : iconSlide?.to === item.key ? "in" : iconSlide?.from === item.key ? "out" : isActive ? "rest" : "off";
440
+ const showIcon = iconPhase !== "off";
441
+ const anchorRef = (0, import_react3.useCallback)(
442
+ (node) => {
443
+ registerAnchor(item.key, node);
444
+ },
445
+ [item.key, registerAnchor]
446
+ );
447
+ const trigger = /* @__PURE__ */ import_react3.default.createElement(
448
+ Box,
449
+ {
450
+ as: !LinkComponent || hasMenu ? "button" : void 0,
451
+ type: !LinkComponent || hasMenu ? "button" : void 0,
452
+ position: "relative",
453
+ zIndex: iconPhase === "out" ? 2 : iconPhase === "in" ? 1 : void 0,
454
+ flexDirection: "row",
455
+ alignItems: "center",
456
+ paddingVertical: !hasMenu && !showIcon ? 2 : 0,
457
+ cursor: isDisabled ? "not-allowed" : "pointer",
458
+ "aria-label": item.accessibilityLabel || item.label,
459
+ "aria-current": isActive && !hasMenu ? "page" : void 0,
460
+ "aria-disabled": isDisabled || void 0,
461
+ disabled: isDisabled,
462
+ onMouseEnter: () => setHovered(true),
463
+ onMouseLeave: () => setHovered(false),
464
+ onPress: !isDisabled && !hasMenu && !LinkComponent ? onPress : void 0,
465
+ testID: item.testID || `top-navigation-item-${item.key}`
466
+ },
467
+ item.icon !== void 0 && item.icon !== null && /* @__PURE__ */ import_react3.default.createElement(
468
+ Box,
469
+ {
470
+ ref: anchorRef,
471
+ position: "absolute",
472
+ top: 0,
473
+ left: 0,
474
+ width: 0,
475
+ height: 0,
476
+ "aria-hidden": true
477
+ }
478
+ ),
479
+ showIcon && /* @__PURE__ */ import_react3.default.createElement(
480
+ ActiveIcon,
481
+ {
482
+ icon: item.icon,
483
+ color: theme.colors.content.brand.secondary,
484
+ phase: iconPhase,
485
+ play: iconPlaying,
486
+ travel: iconSlide?.travel ?? 0
487
+ }
488
+ ),
489
+ /* @__PURE__ */ import_react3.default.createElement(
490
+ Text,
491
+ {
492
+ color,
493
+ fontFamily: theme.fonts.body,
494
+ fontSize: body.fontSize,
495
+ lineHeight: body.lineHeight,
496
+ fontWeight: String(body.accent?.fontWeight ?? 500),
497
+ numberOfLines: 1
498
+ },
499
+ item.label
500
+ ),
501
+ hasMenu && /* @__PURE__ */ import_react3.default.createElement(Box, { marginLeft: 4, alignItems: "center" }, isOpen ? /* @__PURE__ */ import_react3.default.createElement(import_xui_icons_base.ChevronUp, { size: NAV_ICON_SIZE, color, "aria-hidden": true }) : /* @__PURE__ */ import_react3.default.createElement(import_xui_icons_base.ChevronDown, { size: NAV_ICON_SIZE, color, "aria-hidden": true }))
502
+ );
503
+ const content = LinkComponent && !hasMenu ? /* @__PURE__ */ import_react3.default.createElement(
504
+ LinkComponent,
505
+ {
506
+ item,
507
+ href: item.href,
508
+ isActive,
509
+ disabled: isDisabled,
510
+ onPress,
511
+ testID: item.testID || `top-navigation-link-${item.key}`
512
+ },
513
+ trigger
514
+ ) : trigger;
515
+ if (!hasMenu) return content;
516
+ return /* @__PURE__ */ import_react3.default.createElement(
517
+ import_xui_context_menu.ContextMenu,
518
+ {
519
+ isOpen,
520
+ onOpenChange,
521
+ placement: "bottom-start",
522
+ offset: 0,
523
+ trigger: content,
524
+ "aria-label": item.label,
525
+ themeMode,
526
+ themeProductContext,
527
+ items: item.children.map((child) => ({
528
+ type: "option",
529
+ label: child.label,
530
+ disabled: child.disabled,
531
+ onSelect: () => onSelect(child)
532
+ })),
533
+ testID: `top-navigation-menu-${item.key}`
534
+ }
535
+ );
536
+ };
537
+ var Currencies = ({ currencies, theme }) => {
538
+ const body = theme.typographyTokens.basic["body-sm"];
539
+ const radius = theme.shape.button.sm.borderRadius;
540
+ return /* @__PURE__ */ import_react3.default.createElement(
541
+ Box,
542
+ {
543
+ role: "group",
544
+ "aria-label": "Currencies",
545
+ flexDirection: "row",
546
+ alignItems: "center",
547
+ gap: 8,
548
+ paddingHorizontal: 8,
549
+ paddingVertical: 11,
550
+ borderRadius: radius,
551
+ position: "relative",
552
+ style: { backdropFilter: "blur(6px)" },
553
+ testID: "top-navigation-currencies"
554
+ },
555
+ /* @__PURE__ */ import_react3.default.createElement(
556
+ Box,
557
+ {
558
+ position: "absolute",
559
+ top: 0,
560
+ right: 0,
561
+ bottom: 0,
562
+ left: 0,
563
+ backgroundColor: theme.colors.background.neutral.secondary,
564
+ opacity: 0.5,
565
+ borderRadius: radius
566
+ }
567
+ ),
568
+ currencies.map((currency, index) => /* @__PURE__ */ import_react3.default.createElement(
569
+ Box,
570
+ {
571
+ key: `${currency.amount}-${index}`,
572
+ as: currency.onPress ? "button" : void 0,
573
+ type: currency.onPress ? "button" : void 0,
574
+ flexDirection: "row",
575
+ alignItems: "center",
576
+ gap: 4,
577
+ zIndex: 1,
578
+ cursor: currency.onPress ? "pointer" : void 0,
579
+ "aria-label": currency.accessibilityLabel,
580
+ onPress: currency.onPress,
581
+ testID: `top-navigation-currency-${index}`
582
+ },
583
+ currency.icon && /* @__PURE__ */ import_react3.default.createElement(
584
+ Box,
585
+ {
586
+ width: CURRENCY_ICON_SIZE,
587
+ height: CURRENCY_ICON_SIZE,
588
+ alignItems: "center",
589
+ justifyContent: "center"
590
+ },
591
+ currency.icon
592
+ ),
593
+ /* @__PURE__ */ import_react3.default.createElement(
594
+ Text,
595
+ {
596
+ color: theme.colors.content.primary,
597
+ fontFamily: theme.fonts.body,
598
+ fontSize: body.fontSize,
599
+ lineHeight: body.lineHeight,
600
+ fontWeight: String(body.fontWeight),
601
+ numberOfLines: 1
602
+ },
603
+ currency.amount
604
+ )
605
+ ))
606
+ );
607
+ };
608
+ var AccountAvatar = ({ user, themeMode, themeProductContext }) => {
609
+ const showInitials = !user.src && Boolean(user.text);
610
+ const icon = showInitials ? void 0 : user.icon ?? /* @__PURE__ */ import_react3.default.createElement(import_xui_icons_base.User, { variant: "solid", "aria-hidden": true });
611
+ return /* @__PURE__ */ import_react3.default.createElement(
612
+ import_xui_avatar.Avatar,
613
+ {
614
+ size: "sm",
615
+ square: true,
616
+ tone: "brand",
617
+ badge: false,
618
+ src: user.src,
619
+ text: showInitials ? user.text : void 0,
620
+ icon: user.src ? void 0 : icon,
621
+ disableHover: !user.onPress,
622
+ "aria-label": user.onPress ? `Account: ${user.name}` : user.name,
623
+ onClick: user.onPress,
624
+ testID: "top-navigation-avatar",
625
+ themeMode,
626
+ themeProductContext
627
+ }
628
+ );
629
+ };
630
+ var WindowControls = ({ controls, themeMode, themeProductContext }) => /* @__PURE__ */ import_react3.default.createElement(
631
+ Box,
632
+ {
633
+ role: "group",
634
+ "aria-label": "Window controls",
635
+ flexDirection: "row",
636
+ alignItems: "center",
637
+ flexShrink: 0,
638
+ testID: "top-navigation-window-controls"
639
+ },
640
+ /* @__PURE__ */ import_react3.default.createElement(
641
+ import_xui_button.IconButton,
642
+ {
643
+ size: "sm",
644
+ variant: "tertiary",
645
+ tone: "mono",
646
+ icon: /* @__PURE__ */ import_react3.default.createElement(import_xui_icons_base.Minus, null),
647
+ "aria-label": "Minimize window",
648
+ disabled: !controls.onMinimize,
649
+ onPress: controls.onMinimize,
650
+ testID: "top-navigation-window-minimize",
651
+ themeMode,
652
+ themeProductContext
653
+ }
654
+ ),
655
+ /* @__PURE__ */ import_react3.default.createElement(
656
+ import_xui_button.IconButton,
657
+ {
658
+ size: "sm",
659
+ variant: "tertiary",
660
+ tone: "mono",
661
+ icon: /* @__PURE__ */ import_react3.default.createElement(MaximizeGlyph, null),
662
+ "aria-label": "Maximize window",
663
+ disabled: !controls.onMaximize,
664
+ onPress: controls.onMaximize,
665
+ testID: "top-navigation-window-maximize",
666
+ themeMode,
667
+ themeProductContext
668
+ }
669
+ ),
670
+ /* @__PURE__ */ import_react3.default.createElement(
671
+ import_xui_button.IconButton,
672
+ {
673
+ size: "sm",
674
+ variant: "tertiary",
675
+ tone: "mono",
676
+ icon: /* @__PURE__ */ import_react3.default.createElement(import_xui_icons_base.Remove, null),
677
+ "aria-label": "Close window",
678
+ disabled: !controls.onClose,
679
+ onPress: controls.onClose,
680
+ testID: "top-navigation-window-close",
681
+ themeMode,
682
+ themeProductContext
683
+ }
684
+ )
685
+ );
686
+ var TopNavigation = ({
687
+ platform = "web",
688
+ logo,
689
+ items = [],
690
+ extraItems = [],
691
+ activeKey,
692
+ onItemClick,
693
+ linkComponent,
694
+ showMenu = true,
695
+ currencies,
696
+ user,
697
+ showAccount = true,
698
+ breadcrumbs = [],
699
+ onBack,
700
+ windowControls,
701
+ "aria-label": ariaLabel = "Main navigation",
702
+ testID,
703
+ themeMode,
704
+ themeProductContext = "b2c"
705
+ }) => {
706
+ const { theme, mode } = (0, import_xui_core.useResolvedTheme)({ themeMode, themeProductContext });
707
+ const [openKey, setOpenKey] = (0, import_react3.useState)(null);
708
+ const [iconMotion, setIconMotion] = (0, import_react3.useState)({ slide: null, playing: false });
709
+ const iconSlide = iconMotion.slide;
710
+ const iconPlaying = iconMotion.playing;
711
+ const previousKey = (0, import_react3.useRef)(activeKey);
712
+ const iconPositions = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
713
+ const iconAnchors = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
714
+ const itemsRef = (0, import_react3.useRef)(items);
715
+ const extraItemsRef = (0, import_react3.useRef)(extraItems);
716
+ itemsRef.current = items;
717
+ extraItemsRef.current = extraItems;
718
+ const isWindows = platform === "windows";
719
+ const isDark = isDarkMode(mode);
720
+ const registerAnchor = (0, import_react3.useCallback)(
721
+ (key, node) => {
722
+ if (node) iconAnchors.current.set(key, node);
723
+ else iconAnchors.current.delete(key);
724
+ },
725
+ []
726
+ );
727
+ const recordIconPositions = () => {
728
+ const next = /* @__PURE__ */ new Map();
729
+ iconAnchors.current.forEach((node, key) => {
730
+ if (typeof node.getBoundingClientRect !== "function") return;
731
+ next.set(key, node.getBoundingClientRect().left);
732
+ });
733
+ iconPositions.current = next;
734
+ };
735
+ useIsomorphicLayoutEffect(() => {
736
+ const from = previousKey.current;
737
+ if (from !== activeKey) {
738
+ const fromX = from ? iconPositions.current.get(from) : void 0;
739
+ const toX = activeKey ? iconPositions.current.get(activeKey) : void 0;
740
+ previousKey.current = activeKey;
741
+ if (from && activeKey && fromX !== void 0 && !prefersReducedMotion() && (toX === void 0 || Math.abs(toX - fromX) >= 1)) {
742
+ const keys = [...itemsRef.current, ...extraItemsRef.current].map(
743
+ (item) => item.key
744
+ );
745
+ const fallback = (keys.indexOf(activeKey) >= keys.indexOf(from) ? 1 : -1) * 28;
746
+ setIconMotion({
747
+ playing: false,
748
+ slide: {
749
+ from,
750
+ to: activeKey,
751
+ travel: toX === void 0 ? fallback : toX - fromX
752
+ }
753
+ });
754
+ return;
755
+ }
756
+ setIconMotion(
757
+ (current) => current.slide === null && !current.playing ? current : { slide: null, playing: false }
758
+ );
759
+ }
760
+ if (!iconSlide) recordIconPositions();
761
+ }, [activeKey, iconSlide]);
762
+ (0, import_react3.useEffect)(() => {
763
+ if (!iconSlide) return;
764
+ const frame = requestAnimationFrame(() => {
765
+ setIconMotion(
766
+ (current) => current.slide ? { slide: current.slide, playing: true } : current
767
+ );
768
+ });
769
+ const timeout = setTimeout(() => {
770
+ setIconMotion({ slide: null, playing: false });
771
+ }, ICON_SLIDE_MS + 40);
772
+ return () => {
773
+ cancelAnimationFrame(frame);
774
+ clearTimeout(timeout);
775
+ };
776
+ }, [iconSlide]);
777
+ const resolvedLogo = typeof logo === "function" ? logo({ isDark }) : logo ?? null;
778
+ const handlePress = (item) => {
779
+ setOpenKey(null);
780
+ item.onPress?.();
781
+ onItemClick?.(item);
782
+ };
783
+ const renderPoint = (item) => /* @__PURE__ */ import_react3.default.createElement(
784
+ NavPoint,
785
+ {
786
+ key: item.key,
787
+ item,
788
+ isActive: item.key === activeKey,
789
+ isOpen: openKey === item.key,
790
+ iconSlide,
791
+ iconPlaying,
792
+ registerAnchor,
793
+ theme,
794
+ themeMode,
795
+ themeProductContext,
796
+ onOpenChange: (open) => setOpenKey(open ? item.key : null),
797
+ onPress: () => handlePress(item),
798
+ onSelect: handlePress,
799
+ linkComponent
800
+ }
801
+ );
802
+ const menu = showMenu && (items.length > 0 || extraItems.length > 0) ? /* @__PURE__ */ import_react3.default.createElement(
803
+ Box,
804
+ {
805
+ as: "nav",
806
+ "aria-label": ariaLabel,
807
+ flexDirection: "row",
808
+ alignItems: "center",
809
+ flexShrink: 1,
810
+ minWidth: 0,
811
+ gap: MENU_GAP,
812
+ testID: "top-navigation-menu"
813
+ },
814
+ items.map(renderPoint),
815
+ items.length > 0 && extraItems.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
816
+ Box,
817
+ {
818
+ role: "separator",
819
+ "aria-orientation": "vertical",
820
+ width: 1,
821
+ height: MENU_DIVIDER_HEIGHT,
822
+ backgroundColor: theme.colors.border.secondary,
823
+ testID: "top-navigation-menu-divider"
824
+ }
825
+ ),
826
+ extraItems.map(renderPoint)
827
+ ) : null;
828
+ const account = showAccount && (currencies && currencies.length > 0 || user) ? /* @__PURE__ */ import_react3.default.createElement(
829
+ Box,
830
+ {
831
+ flexDirection: "row",
832
+ alignItems: "center",
833
+ flexShrink: 0,
834
+ gap: ACCOUNT_GAP,
835
+ testID: "top-navigation-account"
836
+ },
837
+ currencies && currencies.length > 0 && /* @__PURE__ */ import_react3.default.createElement(Currencies, { currencies, theme }),
838
+ user && /* @__PURE__ */ import_react3.default.createElement(
839
+ AccountAvatar,
840
+ {
841
+ user,
842
+ themeMode,
843
+ themeProductContext
844
+ }
845
+ )
846
+ ) : null;
847
+ const windowsTrail = isWindows && (onBack || breadcrumbs.length > 0) ? /* @__PURE__ */ import_react3.default.createElement(
848
+ Box,
849
+ {
850
+ flexDirection: "row",
851
+ alignItems: "center",
852
+ gap: 4,
853
+ paddingVertical: 2,
854
+ testID: "top-navigation-windows-trail"
855
+ },
856
+ onBack && /* @__PURE__ */ import_react3.default.createElement(
857
+ Box,
858
+ {
859
+ as: "button",
860
+ type: "button",
861
+ "aria-label": "Go back",
862
+ cursor: "pointer",
863
+ onPress: onBack,
864
+ testID: "top-navigation-back"
865
+ },
866
+ /* @__PURE__ */ import_react3.default.createElement(
867
+ import_xui_icons_base.ArrowLeft,
868
+ {
869
+ size: BACK_ICON_SIZE,
870
+ color: theme.colors.content.primary,
871
+ "aria-hidden": true
872
+ }
873
+ )
874
+ ),
875
+ breadcrumbs.length > 0 && /* @__PURE__ */ import_react3.default.createElement(
876
+ import_xui_breadcrumbs.Breadcrumbs,
877
+ {
878
+ size: "md",
879
+ items: breadcrumbs.map((crumb) => ({
880
+ label: crumb.label,
881
+ href: crumb.href,
882
+ onClick: crumb.onPress
883
+ })),
884
+ testID: "top-navigation-breadcrumbs",
885
+ themeMode,
886
+ themeProductContext
887
+ }
888
+ )
889
+ ) : null;
890
+ return /* @__PURE__ */ import_react3.default.createElement(
891
+ Box,
892
+ {
893
+ as: "header",
894
+ width: "100%",
895
+ minWidth: 0,
896
+ maxWidth: 2560,
897
+ flex: 1,
898
+ height: BAR_HEIGHT,
899
+ flexDirection: "row",
900
+ alignItems: "center",
901
+ paddingVertical: BAR_PADDING_VERTICAL,
902
+ backgroundColor: theme.colors.background.secondary,
903
+ "data-platform": platform,
904
+ testID: testID || "top-navigation"
905
+ },
906
+ (resolvedLogo || windowsTrail) && /* @__PURE__ */ import_react3.default.createElement(
907
+ Box,
908
+ {
909
+ flexShrink: windowsTrail ? 1 : 0,
910
+ minWidth: windowsTrail ? 0 : void 0,
911
+ flexDirection: "row",
912
+ alignItems: "center",
913
+ gap: windowsTrail ? WINDOWS_LOGO_GAP : 0
914
+ },
915
+ resolvedLogo && /* @__PURE__ */ import_react3.default.createElement(
916
+ Box,
917
+ {
918
+ height: LOGO_HEIGHT,
919
+ justifyContent: "center",
920
+ flexShrink: 0,
921
+ testID: "top-navigation-logo"
922
+ },
923
+ resolvedLogo
924
+ ),
925
+ windowsTrail && /* @__PURE__ */ import_react3.default.createElement(Box, { flexShrink: 1, minWidth: 0, overflow: "hidden" }, windowsTrail)
926
+ ),
927
+ /* @__PURE__ */ import_react3.default.createElement(
928
+ Box,
929
+ {
930
+ flexDirection: "row",
931
+ alignItems: "center",
932
+ justifyContent: "flex-end",
933
+ gap: CLUSTER_GAP,
934
+ flexShrink: 1,
935
+ minWidth: 0,
936
+ overflow: "hidden",
937
+ marginLeft: "auto"
938
+ },
939
+ menu,
940
+ account,
941
+ isWindows && windowControls && /* @__PURE__ */ import_react3.default.createElement(
942
+ WindowControls,
943
+ {
944
+ controls: windowControls,
945
+ themeMode,
946
+ themeProductContext
947
+ }
948
+ )
949
+ )
950
+ );
951
+ };
952
+ TopNavigation.displayName = "TopNavigation";
953
+ // Annotate the CommonJS export names for ESM import in node:
954
+ 0 && (module.exports = {
955
+ TopNavigation
956
+ });
957
+ //# sourceMappingURL=index.js.map