@timmbr/ui 1.2.0 → 1.3.1
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/dist/components/NavHeader/NavHeader.context.d.ts +20 -0
- package/dist/components/NavHeader/NavHeader.context.d.ts.map +1 -0
- package/dist/components/NavHeader/NavHeader.context.js +13 -0
- package/dist/components/NavHeader/NavHeader.d.ts +8 -0
- package/dist/components/NavHeader/NavHeader.d.ts.map +1 -0
- package/dist/components/NavHeader/NavHeader.helpers.d.ts +13 -0
- package/dist/components/NavHeader/NavHeader.helpers.d.ts.map +1 -0
- package/dist/components/NavHeader/NavHeader.helpers.js +14 -0
- package/dist/components/NavHeader/NavHeader.js +547 -0
- package/dist/components/NavHeader/NavHeader.styles.d.ts +67 -0
- package/dist/components/NavHeader/NavHeader.styles.d.ts.map +1 -0
- package/dist/components/NavHeader/NavHeader.styles.js +123 -0
- package/dist/components/NavHeader/NavHeader.types.d.ts +321 -0
- package/dist/components/NavHeader/NavHeader.types.d.ts.map +1 -0
- package/dist/components/NavHeader/index.d.ts +11 -0
- package/dist/components/NavHeader/index.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavActionOverflow.d.ts +13 -0
- package/dist/components/NavHeader/subcomponents/NavActionOverflow.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavActionOverflow.js +169 -0
- package/dist/components/NavHeader/subcomponents/NavActions.d.ts +22 -0
- package/dist/components/NavHeader/subcomponents/NavActions.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavActions.js +228 -0
- package/dist/components/NavHeader/subcomponents/NavBrand.d.ts +12 -0
- package/dist/components/NavHeader/subcomponents/NavBrand.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavBrand.js +94 -0
- package/dist/components/NavHeader/subcomponents/NavItems.d.ts +15 -0
- package/dist/components/NavHeader/subcomponents/NavItems.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavItems.js +141 -0
- package/dist/components/NavHeader/subcomponents/NavItemsOverflow.d.ts +13 -0
- package/dist/components/NavHeader/subcomponents/NavItemsOverflow.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavItemsOverflow.js +165 -0
- package/dist/components/NavHeader/subcomponents/NavMegaMenu.d.ts +12 -0
- package/dist/components/NavHeader/subcomponents/NavMegaMenu.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavMegaMenu.js +73 -0
- package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.d.ts +16 -0
- package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.js +149 -0
- package/dist/components/NavHeader/subcomponents/NavMobileTabContent.d.ts +11 -0
- package/dist/components/NavHeader/subcomponents/NavMobileTabContent.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavMobileTabContent.js +40 -0
- package/dist/components/NavHeader/subcomponents/NavMobileTabSlider.d.ts +13 -0
- package/dist/components/NavHeader/subcomponents/NavMobileTabSlider.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavMobileTabSlider.js +111 -0
- package/dist/components/NavHeader/subcomponents/NavSearch.d.ts +15 -0
- package/dist/components/NavHeader/subcomponents/NavSearch.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/NavSearch.js +222 -0
- package/dist/components/NavHeader/subcomponents/OfferBanner.d.ts +11 -0
- package/dist/components/NavHeader/subcomponents/OfferBanner.d.ts.map +1 -0
- package/dist/components/NavHeader/subcomponents/OfferBanner.js +92 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/package.json +3 -3
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { NavHeaderProps, NavHeaderItem } from './NavHeader.types';
|
|
3
|
+
export interface NavHeaderContextValue {
|
|
4
|
+
activeItemId: string | null;
|
|
5
|
+
setActiveItemId: (id: string | null) => void;
|
|
6
|
+
isSearchOpen: boolean;
|
|
7
|
+
setIsSearchOpen: (open: boolean) => void;
|
|
8
|
+
searchValue: string;
|
|
9
|
+
setSearchValue: (value: string) => void;
|
|
10
|
+
shouldAnimate: boolean;
|
|
11
|
+
motionClass?: string;
|
|
12
|
+
linkComponent?: NavHeaderProps['linkComponent'];
|
|
13
|
+
handleItemMouseEnter: (id: string) => void;
|
|
14
|
+
handleItemMouseLeave: () => void;
|
|
15
|
+
handleItemClick: (id: string, item: NavHeaderItem) => void;
|
|
16
|
+
handleSearchSubmit: (query: string) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare const NavHeaderContext: React.Context<NavHeaderContextValue | null>;
|
|
19
|
+
export declare function useNavHeaderContext(): NavHeaderContextValue;
|
|
20
|
+
//# sourceMappingURL=NavHeader.context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavHeader.context.d.ts","sourceRoot":"","sources":["../../../src/components/NavHeader/NavHeader.context.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvE,MAAM,WAAW,qBAAqB;IACpC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,YAAY,EAAE,OAAO,CAAC;IACtB,eAAe,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;IAChD,oBAAoB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,oBAAoB,EAAE,MAAM,IAAI,CAAC;IACjC,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IAC3D,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C;AAED,eAAO,MAAM,gBAAgB,6CAA0D,CAAC;AAExF,wBAAgB,mBAAmB,IAAI,qBAAqB,CAM3D"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
|
|
4
|
+
const NavHeaderContext = React.createContext(null);
|
|
5
|
+
function useNavHeaderContext() {
|
|
6
|
+
const context = React.useContext(NavHeaderContext);
|
|
7
|
+
if (!context) {
|
|
8
|
+
throw new Error("useNavHeaderContext must be used within a NavHeader component tree");
|
|
9
|
+
}
|
|
10
|
+
return context;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export { NavHeaderContext, useNavHeaderContext };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { NavHeaderProps } from './NavHeader.types';
|
|
3
|
+
export declare const NavHeader: React.ForwardRefExoticComponent<NavHeaderProps & React.RefAttributes<HTMLElement>>;
|
|
4
|
+
/**
|
|
5
|
+
* Convenience alias for NavHeader.
|
|
6
|
+
*/
|
|
7
|
+
export declare const Header: React.ForwardRefExoticComponent<NavHeaderProps & React.RefAttributes<HTMLElement>>;
|
|
8
|
+
//# sourceMappingURL=NavHeader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavHeader.d.ts","sourceRoot":"","sources":["../../../src/components/NavHeader/NavHeader.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EACV,cAAc,EAMf,MAAM,mBAAmB,CAAC;AAa3B,eAAO,MAAM,SAAS,oFAqpBrB,CAAC;AAIF;;GAEG;AACH,eAAO,MAAM,MAAM,oFAAY,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks whether a given URL is external (protocol-relative, mailto, tel, or http(s)).
|
|
3
|
+
*/
|
|
4
|
+
export declare function isExternalUrl(url?: string): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Determines whether navigation requires cross-zone hard link behavior.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isCrossZoneNavigation(zone?: string, currentZone?: string, explicitCrossZone?: boolean): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Resolves the destination URL taking into account zone configuration.
|
|
11
|
+
*/
|
|
12
|
+
export declare function resolveNavDestination(href?: string, zone?: string, zones?: Record<string, string>): string | undefined;
|
|
13
|
+
//# sourceMappingURL=NavHeader.helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NavHeader.helpers.d.ts","sourceRoot":"","sources":["../../../src/components/NavHeader/NavHeader.helpers.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CASnD;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,CAAC,EAAE,MAAM,EACb,WAAW,CAAC,EAAE,MAAM,EACpB,iBAAiB,CAAC,EAAE,OAAO,GAC1B,OAAO,CAKT;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,CAAC,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,MAAM,EACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,MAAM,GAAG,SAAS,CAGpB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { resolveZoneHref } from '../../providers/TimmbrConfigProvider.js';
|
|
2
|
+
|
|
3
|
+
function isCrossZoneNavigation(zone, currentZone, explicitCrossZone) {
|
|
4
|
+
if (explicitCrossZone) return true;
|
|
5
|
+
if (!zone) return false;
|
|
6
|
+
if (!currentZone) return true;
|
|
7
|
+
return zone !== currentZone;
|
|
8
|
+
}
|
|
9
|
+
function resolveNavDestination(href, zone, zones) {
|
|
10
|
+
if (!href) return void 0;
|
|
11
|
+
return resolveZoneHref(href, zone, zones);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { isCrossZoneNavigation, resolveNavDestination };
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cn } from '@timmbr/utils';
|
|
5
|
+
import { resolveMotion } from '../../types/motion.js';
|
|
6
|
+
import { navHeaderContainerVariants, navHeaderVariants } from './NavHeader.styles.js';
|
|
7
|
+
import { Search } from '@timmbr/icons';
|
|
8
|
+
import { NavHeaderContext } from './NavHeader.context.js';
|
|
9
|
+
import { OfferBanner } from './subcomponents/OfferBanner.js';
|
|
10
|
+
import { NavBrand } from './subcomponents/NavBrand.js';
|
|
11
|
+
import { NavItems } from './subcomponents/NavItems.js';
|
|
12
|
+
import { NavMegaMenu } from './subcomponents/NavMegaMenu.js';
|
|
13
|
+
import { NavSearch } from './subcomponents/NavSearch.js';
|
|
14
|
+
import { NavActions } from './subcomponents/NavActions.js';
|
|
15
|
+
import { NavMobileTabSlider } from './subcomponents/NavMobileTabSlider.js';
|
|
16
|
+
import { NavMobileTabContent } from './subcomponents/NavMobileTabContent.js';
|
|
17
|
+
import { NavMobileSearchDrawer } from './subcomponents/NavMobileSearchDrawer.js';
|
|
18
|
+
import { useGlobalAnimation } from '../../providers/AnimationProvider.js';
|
|
19
|
+
|
|
20
|
+
const NavHeader = React.forwardRef(
|
|
21
|
+
({
|
|
22
|
+
// 1. Branding & Global Layout
|
|
23
|
+
branding,
|
|
24
|
+
containerMaxWidth = "2xl",
|
|
25
|
+
sticky = false,
|
|
26
|
+
linkComponent,
|
|
27
|
+
rightContent,
|
|
28
|
+
// 2. Modular Configuration Objects
|
|
29
|
+
offerBanner = {
|
|
30
|
+
content: "Extra 15% Off on All Home and Kitchen Orders*"
|
|
31
|
+
},
|
|
32
|
+
navigation,
|
|
33
|
+
search = true,
|
|
34
|
+
actions,
|
|
35
|
+
mobile,
|
|
36
|
+
// 3. Shorthand & Convenience Props (Backwards Compatible)
|
|
37
|
+
showOfferBanner: shorthandShowOfferBanner,
|
|
38
|
+
items: shorthandItems,
|
|
39
|
+
showNavItems: shorthandShowNavItems,
|
|
40
|
+
maxVisibleNavItems: shorthandMaxVisibleNavItems,
|
|
41
|
+
activeItemId: shorthandActiveItemId,
|
|
42
|
+
defaultActiveItemId: shorthandDefaultActiveItemId,
|
|
43
|
+
onActiveItemChange: shorthandOnActiveItemChange,
|
|
44
|
+
showSearch: shorthandShowSearch,
|
|
45
|
+
showActions: shorthandShowActions,
|
|
46
|
+
maxVisibleActions: shorthandMaxVisibleActions,
|
|
47
|
+
overflowActionLabel: shorthandOverflowActionLabel,
|
|
48
|
+
overflowActionIcon: shorthandOverflowActionIcon,
|
|
49
|
+
profileAction: shorthandProfileAction,
|
|
50
|
+
wishlistAction: shorthandWishlistAction,
|
|
51
|
+
cartAction: shorthandCartAction,
|
|
52
|
+
mobileTabSlider: shorthandMobileTabSlider,
|
|
53
|
+
mobileSearchMode: shorthandMobileSearchMode,
|
|
54
|
+
mobileActiveItemId: shorthandMobileActiveItemId,
|
|
55
|
+
defaultMobileActiveItemId: shorthandDefaultMobileActiveItemId,
|
|
56
|
+
onMobileActiveItemChange: shorthandOnMobileActiveItemChange,
|
|
57
|
+
mobileSearchOpen: shorthandMobileSearchOpen,
|
|
58
|
+
defaultMobileSearchOpen: shorthandDefaultMobileSearchOpen,
|
|
59
|
+
onMobileSearchOpenChange: shorthandOnMobileSearchOpenChange,
|
|
60
|
+
// 4. Motion & Styling Overrides
|
|
61
|
+
motion: motionProp,
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
}, ref) => {
|
|
65
|
+
const globalMotion = useGlobalAnimation();
|
|
66
|
+
const { shouldAnimate, motionClass } = resolveMotion(motionProp, globalMotion);
|
|
67
|
+
const isOfferBannerVisible = shorthandShowOfferBanner !== void 0 ? shorthandShowOfferBanner : typeof offerBanner === "object" && offerBanner !== null && "show" in offerBanner ? offerBanner.show ?? true : Boolean(offerBanner);
|
|
68
|
+
const navigationConfig = React.useMemo(
|
|
69
|
+
() => Array.isArray(navigation) ? { items: navigation } : navigation ?? {},
|
|
70
|
+
[navigation]
|
|
71
|
+
);
|
|
72
|
+
const navItems = React.useMemo(
|
|
73
|
+
() => shorthandItems ?? navigationConfig.items ?? [],
|
|
74
|
+
[shorthandItems, navigationConfig.items]
|
|
75
|
+
);
|
|
76
|
+
const isNavItemsVisible = shorthandShowNavItems !== void 0 ? shorthandShowNavItems : navigationConfig.show ?? true;
|
|
77
|
+
const maxVisibleNavItems = shorthandMaxVisibleNavItems ?? navigationConfig.maxVisibleItems;
|
|
78
|
+
const controlledActiveId = shorthandActiveItemId !== void 0 ? shorthandActiveItemId : navigationConfig.activeItemId;
|
|
79
|
+
const defaultActiveItemId = shorthandDefaultActiveItemId !== void 0 ? shorthandDefaultActiveItemId : navigationConfig.defaultActiveItemId ?? null;
|
|
80
|
+
const onActiveItemChange = shorthandOnActiveItemChange ?? navigationConfig.onActiveItemChange;
|
|
81
|
+
const searchConfig = React.useMemo(
|
|
82
|
+
() => typeof search === "object" && search !== null ? search : {},
|
|
83
|
+
[search]
|
|
84
|
+
);
|
|
85
|
+
const isSearchVisible = shorthandShowSearch !== void 0 ? shorthandShowSearch : searchConfig.show ?? Boolean(search);
|
|
86
|
+
const actionsConfig = React.useMemo(
|
|
87
|
+
() => Array.isArray(actions) ? { items: actions } : actions ?? {},
|
|
88
|
+
[actions]
|
|
89
|
+
);
|
|
90
|
+
const actionItems = actionsConfig.items;
|
|
91
|
+
const isActionsVisible = shorthandShowActions !== void 0 ? shorthandShowActions : actionsConfig.show ?? true;
|
|
92
|
+
const maxVisibleActions = shorthandMaxVisibleActions !== void 0 ? shorthandMaxVisibleActions : actionsConfig.maxVisible;
|
|
93
|
+
const overflowActionLabel = shorthandOverflowActionLabel ?? actionsConfig.overflow?.label;
|
|
94
|
+
const overflowActionIcon = shorthandOverflowActionIcon ?? actionsConfig.overflow?.icon;
|
|
95
|
+
const profileAction = shorthandProfileAction ?? actionsConfig.profile;
|
|
96
|
+
const wishlistAction = shorthandWishlistAction ?? actionsConfig.wishlist;
|
|
97
|
+
const cartAction = shorthandCartAction ?? actionsConfig.cart;
|
|
98
|
+
const mobileConfig = React.useMemo(
|
|
99
|
+
() => mobile ?? {},
|
|
100
|
+
[mobile]
|
|
101
|
+
);
|
|
102
|
+
const tabSliderOption = mobileConfig.tabSlider;
|
|
103
|
+
const tabSliderConfig = typeof tabSliderOption === "object" && tabSliderOption !== null ? tabSliderOption : {};
|
|
104
|
+
const isMobileTabSliderEnabled = shorthandMobileTabSlider !== void 0 ? shorthandMobileTabSlider : typeof tabSliderOption === "boolean" ? tabSliderOption : tabSliderConfig.show ?? true;
|
|
105
|
+
const controlledMobileActiveId = shorthandMobileActiveItemId !== void 0 ? shorthandMobileActiveItemId : tabSliderConfig.activeItemId;
|
|
106
|
+
const defaultMobileActiveItemId = shorthandDefaultMobileActiveItemId !== void 0 ? shorthandDefaultMobileActiveItemId : tabSliderConfig.defaultActiveItemId ?? null;
|
|
107
|
+
const onMobileActiveItemChange = shorthandOnMobileActiveItemChange ?? tabSliderConfig.onActiveItemChange;
|
|
108
|
+
const mobileSearchConfig = mobileConfig.search ?? {};
|
|
109
|
+
const resolvedMobileSearchMode = shorthandMobileSearchMode ?? mobileSearchConfig.mode ?? searchConfig.mobileMode ?? "bar";
|
|
110
|
+
const controlledMobileSearchOpen = shorthandMobileSearchOpen !== void 0 ? shorthandMobileSearchOpen : mobileSearchConfig.open;
|
|
111
|
+
const defaultMobileSearchOpen = shorthandDefaultMobileSearchOpen !== void 0 ? shorthandDefaultMobileSearchOpen : mobileSearchConfig.defaultOpen ?? false;
|
|
112
|
+
const onMobileSearchOpenChange = shorthandOnMobileSearchOpenChange ?? mobileSearchConfig.onOpenChange;
|
|
113
|
+
const [uncontrolledActiveId, setUncontrolledActiveId] = React.useState(
|
|
114
|
+
defaultActiveItemId
|
|
115
|
+
);
|
|
116
|
+
const activeItemId = controlledActiveId !== void 0 ? controlledActiveId : uncontrolledActiveId;
|
|
117
|
+
const setActiveItemId = React.useCallback(
|
|
118
|
+
(id) => {
|
|
119
|
+
if (controlledActiveId === void 0) {
|
|
120
|
+
setUncontrolledActiveId(id);
|
|
121
|
+
}
|
|
122
|
+
onActiveItemChange?.(id);
|
|
123
|
+
},
|
|
124
|
+
[controlledActiveId, onActiveItemChange]
|
|
125
|
+
);
|
|
126
|
+
const [uncontrolledSearchOpen, setUncontrolledSearchOpen] = React.useState(
|
|
127
|
+
searchConfig.defaultOpen ?? false
|
|
128
|
+
);
|
|
129
|
+
const isSearchOpen = searchConfig.isOpen !== void 0 ? searchConfig.isOpen : uncontrolledSearchOpen;
|
|
130
|
+
const setIsSearchOpen = React.useCallback(
|
|
131
|
+
(open) => {
|
|
132
|
+
if (searchConfig.isOpen === void 0) {
|
|
133
|
+
setUncontrolledSearchOpen(open);
|
|
134
|
+
}
|
|
135
|
+
searchConfig.onOpenChange?.(open);
|
|
136
|
+
},
|
|
137
|
+
[searchConfig]
|
|
138
|
+
);
|
|
139
|
+
const [uncontrolledSearchValue, setUncontrolledSearchValue] = React.useState(
|
|
140
|
+
searchConfig.defaultValue ?? ""
|
|
141
|
+
);
|
|
142
|
+
const searchValue = searchConfig.value !== void 0 ? searchConfig.value : uncontrolledSearchValue;
|
|
143
|
+
const setSearchValue = React.useCallback(
|
|
144
|
+
(val) => {
|
|
145
|
+
if (searchConfig.value === void 0) {
|
|
146
|
+
setUncontrolledSearchValue(val);
|
|
147
|
+
}
|
|
148
|
+
searchConfig.onChange?.(val);
|
|
149
|
+
},
|
|
150
|
+
[searchConfig]
|
|
151
|
+
);
|
|
152
|
+
const leaveTimeoutRef = React.useRef(null);
|
|
153
|
+
const cancelLeaveTimeout = React.useCallback(() => {
|
|
154
|
+
if (leaveTimeoutRef.current) {
|
|
155
|
+
clearTimeout(leaveTimeoutRef.current);
|
|
156
|
+
leaveTimeoutRef.current = null;
|
|
157
|
+
}
|
|
158
|
+
}, []);
|
|
159
|
+
const handleItemMouseEnter = React.useCallback(
|
|
160
|
+
(id) => {
|
|
161
|
+
cancelLeaveTimeout();
|
|
162
|
+
setActiveItemId(id);
|
|
163
|
+
},
|
|
164
|
+
[cancelLeaveTimeout, setActiveItemId]
|
|
165
|
+
);
|
|
166
|
+
const handleItemMouseLeave = React.useCallback(() => {
|
|
167
|
+
cancelLeaveTimeout();
|
|
168
|
+
leaveTimeoutRef.current = setTimeout(() => {
|
|
169
|
+
setActiveItemId(null);
|
|
170
|
+
}, 180);
|
|
171
|
+
}, [cancelLeaveTimeout, setActiveItemId]);
|
|
172
|
+
const handleMegaMenuMouseEnter = React.useCallback(() => {
|
|
173
|
+
cancelLeaveTimeout();
|
|
174
|
+
}, [cancelLeaveTimeout]);
|
|
175
|
+
const handleMegaMenuMouseLeave = React.useCallback(() => {
|
|
176
|
+
cancelLeaveTimeout();
|
|
177
|
+
leaveTimeoutRef.current = setTimeout(() => {
|
|
178
|
+
setActiveItemId(null);
|
|
179
|
+
}, 180);
|
|
180
|
+
}, [cancelLeaveTimeout, setActiveItemId]);
|
|
181
|
+
const handleMegaMenuClose = React.useCallback(() => {
|
|
182
|
+
cancelLeaveTimeout();
|
|
183
|
+
setActiveItemId(null);
|
|
184
|
+
}, [cancelLeaveTimeout, setActiveItemId]);
|
|
185
|
+
const handleItemClick = React.useCallback(
|
|
186
|
+
(id, item) => {
|
|
187
|
+
if (item.content) {
|
|
188
|
+
setActiveItemId(activeItemId === id ? null : id);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
[activeItemId, setActiveItemId]
|
|
192
|
+
);
|
|
193
|
+
const handleSearchSubmit = React.useCallback(
|
|
194
|
+
(query) => {
|
|
195
|
+
searchConfig.onSubmit?.(query);
|
|
196
|
+
},
|
|
197
|
+
[searchConfig]
|
|
198
|
+
);
|
|
199
|
+
React.useEffect(() => {
|
|
200
|
+
return () => {
|
|
201
|
+
if (leaveTimeoutRef.current) {
|
|
202
|
+
clearTimeout(leaveTimeoutRef.current);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
}, []);
|
|
206
|
+
const innerRef = React.useRef(null);
|
|
207
|
+
const leftRef = React.useRef(null);
|
|
208
|
+
const centerRef = React.useRef(null);
|
|
209
|
+
const searchContainerRef = React.useRef(null);
|
|
210
|
+
const [autoMaxVisible, setAutoMaxVisible] = React.useState(void 0);
|
|
211
|
+
React.useEffect(() => {
|
|
212
|
+
if (maxVisibleActions !== void 0 && typeof maxVisibleActions === "number") {
|
|
213
|
+
setAutoMaxVisible(maxVisibleActions);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const updateFit = () => {
|
|
217
|
+
if (!innerRef.current) return;
|
|
218
|
+
const innerWidth = innerRef.current.clientWidth;
|
|
219
|
+
if (innerWidth <= 0) return;
|
|
220
|
+
const leftWidth = leftRef.current?.offsetWidth || 0;
|
|
221
|
+
const centerWidth = centerRef.current?.offsetWidth || 0;
|
|
222
|
+
const searchBaseWidth = isSearchVisible ? 52 : 0;
|
|
223
|
+
const columnGap = innerWidth >= 1024 ? 32 : 16;
|
|
224
|
+
const columnGapsTotal = columnGap * 2;
|
|
225
|
+
const rightSectionGap = innerWidth >= 1024 ? 20 : 12;
|
|
226
|
+
const availableWidthForActions = Math.max(
|
|
227
|
+
0,
|
|
228
|
+
innerWidth - leftWidth - centerWidth - searchBaseWidth - columnGapsTotal - rightSectionGap
|
|
229
|
+
);
|
|
230
|
+
const itemFootprint = innerWidth >= 640 ? 74 : 64;
|
|
231
|
+
const moreButtonFootprint = itemFootprint;
|
|
232
|
+
if (availableWidthForActions <= moreButtonFootprint) {
|
|
233
|
+
setAutoMaxVisible(1);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const calculatedSlots = Math.floor(availableWidthForActions / itemFootprint);
|
|
237
|
+
setAutoMaxVisible(Math.max(1, calculatedSlots));
|
|
238
|
+
};
|
|
239
|
+
updateFit();
|
|
240
|
+
const handleResize = () => {
|
|
241
|
+
requestAnimationFrame(updateFit);
|
|
242
|
+
};
|
|
243
|
+
window.addEventListener("resize", handleResize);
|
|
244
|
+
let resizeObserver = null;
|
|
245
|
+
if (typeof ResizeObserver !== "undefined" && innerRef.current) {
|
|
246
|
+
resizeObserver = new ResizeObserver(handleResize);
|
|
247
|
+
resizeObserver.observe(innerRef.current);
|
|
248
|
+
if (leftRef.current) resizeObserver.observe(leftRef.current);
|
|
249
|
+
if (centerRef.current) resizeObserver.observe(centerRef.current);
|
|
250
|
+
}
|
|
251
|
+
return () => {
|
|
252
|
+
window.removeEventListener("resize", handleResize);
|
|
253
|
+
resizeObserver?.disconnect();
|
|
254
|
+
};
|
|
255
|
+
}, [maxVisibleActions, navItems.length, search, isSearchVisible]);
|
|
256
|
+
const [uncontrolledMobileActiveId, setUncontrolledMobileActiveId] = React.useState(
|
|
257
|
+
defaultMobileActiveItemId
|
|
258
|
+
);
|
|
259
|
+
const mobileActiveItemId = controlledMobileActiveId !== void 0 ? controlledMobileActiveId : uncontrolledMobileActiveId;
|
|
260
|
+
const handleMobileItemClick = React.useCallback(
|
|
261
|
+
(id, item) => {
|
|
262
|
+
if (item.content) {
|
|
263
|
+
const nextId = mobileActiveItemId === id ? null : id;
|
|
264
|
+
if (controlledMobileActiveId === void 0) {
|
|
265
|
+
setUncontrolledMobileActiveId(nextId);
|
|
266
|
+
}
|
|
267
|
+
onMobileActiveItemChange?.(nextId);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
[mobileActiveItemId, controlledMobileActiveId, onMobileActiveItemChange]
|
|
271
|
+
);
|
|
272
|
+
const activeMobileItem = React.useMemo(() => {
|
|
273
|
+
return navItems.find((item) => item.id === mobileActiveItemId) ?? null;
|
|
274
|
+
}, [navItems, mobileActiveItemId]);
|
|
275
|
+
const [uncontrolledMobileSearchOpen, setUncontrolledMobileSearchOpen] = React.useState(
|
|
276
|
+
defaultMobileSearchOpen
|
|
277
|
+
);
|
|
278
|
+
const isMobileSearchOpen = controlledMobileSearchOpen !== void 0 ? controlledMobileSearchOpen : uncontrolledMobileSearchOpen;
|
|
279
|
+
const setIsMobileSearchOpen = React.useCallback(
|
|
280
|
+
(open) => {
|
|
281
|
+
if (controlledMobileSearchOpen === void 0) {
|
|
282
|
+
setUncontrolledMobileSearchOpen(open);
|
|
283
|
+
}
|
|
284
|
+
onMobileSearchOpenChange?.(open);
|
|
285
|
+
},
|
|
286
|
+
[controlledMobileSearchOpen, onMobileSearchOpenChange]
|
|
287
|
+
);
|
|
288
|
+
const activeItem = React.useMemo(() => {
|
|
289
|
+
return navItems.find((item) => item.id === activeItemId) ?? null;
|
|
290
|
+
}, [navItems, activeItemId]);
|
|
291
|
+
const contextValue = React.useMemo(
|
|
292
|
+
() => ({
|
|
293
|
+
activeItemId,
|
|
294
|
+
setActiveItemId,
|
|
295
|
+
isSearchOpen,
|
|
296
|
+
setIsSearchOpen,
|
|
297
|
+
searchValue,
|
|
298
|
+
setSearchValue,
|
|
299
|
+
shouldAnimate,
|
|
300
|
+
motionClass,
|
|
301
|
+
linkComponent,
|
|
302
|
+
handleItemMouseEnter,
|
|
303
|
+
handleItemMouseLeave,
|
|
304
|
+
handleItemClick,
|
|
305
|
+
handleSearchSubmit,
|
|
306
|
+
autoMaxVisibleActions: autoMaxVisible
|
|
307
|
+
}),
|
|
308
|
+
[
|
|
309
|
+
activeItemId,
|
|
310
|
+
setActiveItemId,
|
|
311
|
+
isSearchOpen,
|
|
312
|
+
setIsSearchOpen,
|
|
313
|
+
searchValue,
|
|
314
|
+
setSearchValue,
|
|
315
|
+
shouldAnimate,
|
|
316
|
+
motionClass,
|
|
317
|
+
linkComponent,
|
|
318
|
+
handleItemMouseEnter,
|
|
319
|
+
handleItemMouseLeave,
|
|
320
|
+
handleItemClick,
|
|
321
|
+
handleSearchSubmit,
|
|
322
|
+
autoMaxVisible
|
|
323
|
+
]
|
|
324
|
+
);
|
|
325
|
+
return /* @__PURE__ */ jsx(NavHeaderContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs(
|
|
326
|
+
"header",
|
|
327
|
+
{
|
|
328
|
+
ref,
|
|
329
|
+
role: "banner",
|
|
330
|
+
"data-slot": "nav-header",
|
|
331
|
+
className: cn(navHeaderVariants({ sticky }), className),
|
|
332
|
+
...props,
|
|
333
|
+
children: [
|
|
334
|
+
/* @__PURE__ */ jsx(
|
|
335
|
+
OfferBanner,
|
|
336
|
+
{
|
|
337
|
+
banner: offerBanner,
|
|
338
|
+
show: isOfferBannerVisible,
|
|
339
|
+
shouldAnimate,
|
|
340
|
+
motionClass,
|
|
341
|
+
linkComponent
|
|
342
|
+
}
|
|
343
|
+
),
|
|
344
|
+
/* @__PURE__ */ jsxs("div", { className: "hidden lg:block", children: [
|
|
345
|
+
/* @__PURE__ */ jsxs(
|
|
346
|
+
"div",
|
|
347
|
+
{
|
|
348
|
+
ref: innerRef,
|
|
349
|
+
className: cn(
|
|
350
|
+
navHeaderContainerVariants({ maxWidth: containerMaxWidth }),
|
|
351
|
+
"relative"
|
|
352
|
+
),
|
|
353
|
+
"data-slot": "nav-header-inner",
|
|
354
|
+
children: [
|
|
355
|
+
/* @__PURE__ */ jsx("div", { ref: leftRef, className: "flex-1 flex items-center justify-start h-full min-w-0 pr-8 xl:pr-14", children: isNavItemsVisible && navItems.length > 0 && /* @__PURE__ */ jsx(
|
|
356
|
+
NavItems,
|
|
357
|
+
{
|
|
358
|
+
items: navItems,
|
|
359
|
+
activeItemId,
|
|
360
|
+
maxVisibleItems: maxVisibleNavItems,
|
|
361
|
+
onItemMouseEnter: handleItemMouseEnter,
|
|
362
|
+
onItemMouseLeave: handleItemMouseLeave,
|
|
363
|
+
onItemClick: handleItemClick,
|
|
364
|
+
shouldAnimate,
|
|
365
|
+
motionClass,
|
|
366
|
+
linkComponent
|
|
367
|
+
}
|
|
368
|
+
) }),
|
|
369
|
+
/* @__PURE__ */ jsx(
|
|
370
|
+
"div",
|
|
371
|
+
{
|
|
372
|
+
ref: centerRef,
|
|
373
|
+
className: "absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 flex items-center justify-center shrink-0 z-10 pointer-events-auto",
|
|
374
|
+
children: /* @__PURE__ */ jsx(
|
|
375
|
+
NavBrand,
|
|
376
|
+
{
|
|
377
|
+
branding,
|
|
378
|
+
isSearchOpen,
|
|
379
|
+
shouldAnimate,
|
|
380
|
+
motionClass,
|
|
381
|
+
linkComponent
|
|
382
|
+
}
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
),
|
|
386
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 flex items-center gap-2.5 lg:gap-4 justify-end min-w-0 pl-8 xl:pl-14", children: [
|
|
387
|
+
isSearchVisible && /* @__PURE__ */ jsx("div", { ref: searchContainerRef, className: "flex items-center", children: /* @__PURE__ */ jsx(
|
|
388
|
+
NavSearch,
|
|
389
|
+
{
|
|
390
|
+
config: search,
|
|
391
|
+
isOpen: isSearchOpen,
|
|
392
|
+
onOpenChange: setIsSearchOpen,
|
|
393
|
+
value: searchValue,
|
|
394
|
+
onChange: setSearchValue,
|
|
395
|
+
onSubmit: handleSearchSubmit,
|
|
396
|
+
shouldAnimate,
|
|
397
|
+
motionClass,
|
|
398
|
+
linkComponent
|
|
399
|
+
}
|
|
400
|
+
) }),
|
|
401
|
+
isActionsVisible && /* @__PURE__ */ jsx(
|
|
402
|
+
NavActions,
|
|
403
|
+
{
|
|
404
|
+
actions: actionItems,
|
|
405
|
+
maxVisibleActions: maxVisibleActions === "auto" ? autoMaxVisible : maxVisibleActions ?? autoMaxVisible,
|
|
406
|
+
overflowActionLabel,
|
|
407
|
+
overflowActionIcon,
|
|
408
|
+
profileAction,
|
|
409
|
+
wishlistAction,
|
|
410
|
+
cartAction,
|
|
411
|
+
rightContent,
|
|
412
|
+
shouldAnimate,
|
|
413
|
+
motionClass,
|
|
414
|
+
linkComponent
|
|
415
|
+
}
|
|
416
|
+
)
|
|
417
|
+
] })
|
|
418
|
+
]
|
|
419
|
+
}
|
|
420
|
+
),
|
|
421
|
+
isNavItemsVisible && /* @__PURE__ */ jsx(
|
|
422
|
+
NavMegaMenu,
|
|
423
|
+
{
|
|
424
|
+
activeItem,
|
|
425
|
+
onMouseEnter: handleMegaMenuMouseEnter,
|
|
426
|
+
onMouseLeave: handleMegaMenuMouseLeave,
|
|
427
|
+
onClose: handleMegaMenuClose,
|
|
428
|
+
shouldAnimate,
|
|
429
|
+
motionClass
|
|
430
|
+
}
|
|
431
|
+
)
|
|
432
|
+
] }),
|
|
433
|
+
/* @__PURE__ */ jsxs("div", { className: "lg:hidden", "data-slot": "nav-mobile-container", children: [
|
|
434
|
+
/* @__PURE__ */ jsxs(
|
|
435
|
+
"div",
|
|
436
|
+
{
|
|
437
|
+
className: cn(
|
|
438
|
+
navHeaderContainerVariants({ maxWidth: containerMaxWidth }),
|
|
439
|
+
"h-[62px] sm:h-[70px] px-3 sm:px-6"
|
|
440
|
+
),
|
|
441
|
+
"data-slot": "nav-mobile-top-bar",
|
|
442
|
+
children: [
|
|
443
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-start shrink-0", children: /* @__PURE__ */ jsx(
|
|
444
|
+
NavBrand,
|
|
445
|
+
{
|
|
446
|
+
branding,
|
|
447
|
+
isSearchOpen: false,
|
|
448
|
+
shouldAnimate,
|
|
449
|
+
motionClass,
|
|
450
|
+
linkComponent
|
|
451
|
+
}
|
|
452
|
+
) }),
|
|
453
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 sm:gap-2.5 lg:gap-4 justify-end", children: [
|
|
454
|
+
isSearchVisible && resolvedMobileSearchMode === "icon" && /* @__PURE__ */ jsxs(
|
|
455
|
+
"button",
|
|
456
|
+
{
|
|
457
|
+
type: "button",
|
|
458
|
+
onClick: () => setIsMobileSearchOpen(true),
|
|
459
|
+
"aria-label": "Open search drawer",
|
|
460
|
+
className: "group flex flex-col items-center justify-center min-w-[36px] sm:min-w-[40px] lg:min-w-[52px] text-[#3C3C3C] hover:text-[#C0643A] transition-colors py-1 px-1 lg:px-1.5 cursor-pointer",
|
|
461
|
+
"data-slot": "nav-mobile-search-icon-trigger",
|
|
462
|
+
children: [
|
|
463
|
+
/* @__PURE__ */ jsx(Search, { size: 22, className: "shrink-0 stroke-[1.8]" }),
|
|
464
|
+
/* @__PURE__ */ jsx("span", { className: "hidden lg:inline font-sans font-bold text-[10px] tracking-[0.0926em] uppercase", children: "SEARCH" })
|
|
465
|
+
]
|
|
466
|
+
}
|
|
467
|
+
),
|
|
468
|
+
isActionsVisible && /* @__PURE__ */ jsx(
|
|
469
|
+
NavActions,
|
|
470
|
+
{
|
|
471
|
+
actions: actionItems,
|
|
472
|
+
maxVisibleActions: isSearchVisible && resolvedMobileSearchMode === "icon" ? 2 : 3,
|
|
473
|
+
overflowActionLabel,
|
|
474
|
+
overflowActionIcon,
|
|
475
|
+
profileAction,
|
|
476
|
+
wishlistAction,
|
|
477
|
+
cartAction,
|
|
478
|
+
rightContent,
|
|
479
|
+
shouldAnimate,
|
|
480
|
+
motionClass,
|
|
481
|
+
linkComponent
|
|
482
|
+
}
|
|
483
|
+
)
|
|
484
|
+
] })
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
),
|
|
488
|
+
isSearchVisible && resolvedMobileSearchMode !== "icon" && /* @__PURE__ */ jsx("div", { className: "px-3 sm:px-6 pb-2.5 pt-0.5 bg-[var(--color-bg-2,#F7F1E6)]", children: /* @__PURE__ */ jsxs(
|
|
489
|
+
"button",
|
|
490
|
+
{
|
|
491
|
+
type: "button",
|
|
492
|
+
onClick: () => setIsMobileSearchOpen(true),
|
|
493
|
+
"aria-label": "Open search drawer",
|
|
494
|
+
className: "w-full h-10 px-4 flex items-center gap-2.5 rounded-full bg-white border border-[#E7DFD3] text-left text-sm text-grey-400 hover:border-grey-400 shadow-xs transition-all cursor-pointer select-none",
|
|
495
|
+
"data-slot": "nav-mobile-search-pill",
|
|
496
|
+
children: [
|
|
497
|
+
/* @__PURE__ */ jsx(Search, { size: 18, className: "text-grey-500 shrink-0" }),
|
|
498
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: typeof search === "object" && search.placeholder ? search.placeholder : "Search for furniture, decor, and more..." })
|
|
499
|
+
]
|
|
500
|
+
}
|
|
501
|
+
) }),
|
|
502
|
+
isNavItemsVisible && isMobileTabSliderEnabled && navItems.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
503
|
+
/* @__PURE__ */ jsx(
|
|
504
|
+
NavMobileTabSlider,
|
|
505
|
+
{
|
|
506
|
+
items: navItems,
|
|
507
|
+
activeItemId: mobileActiveItemId,
|
|
508
|
+
onItemClick: handleMobileItemClick,
|
|
509
|
+
shouldAnimate,
|
|
510
|
+
motionClass,
|
|
511
|
+
linkComponent
|
|
512
|
+
}
|
|
513
|
+
),
|
|
514
|
+
/* @__PURE__ */ jsx(
|
|
515
|
+
NavMobileTabContent,
|
|
516
|
+
{
|
|
517
|
+
activeItem: activeMobileItem,
|
|
518
|
+
onClose: () => handleMobileItemClick(mobileActiveItemId ?? "", activeMobileItem),
|
|
519
|
+
shouldAnimate,
|
|
520
|
+
motionClass
|
|
521
|
+
}
|
|
522
|
+
)
|
|
523
|
+
] })
|
|
524
|
+
] }),
|
|
525
|
+
isSearchVisible && /* @__PURE__ */ jsx(
|
|
526
|
+
NavMobileSearchDrawer,
|
|
527
|
+
{
|
|
528
|
+
isOpen: isMobileSearchOpen,
|
|
529
|
+
onClose: () => setIsMobileSearchOpen(false),
|
|
530
|
+
value: searchValue,
|
|
531
|
+
onChange: setSearchValue,
|
|
532
|
+
onSubmit: handleSearchSubmit,
|
|
533
|
+
config: search,
|
|
534
|
+
shouldAnimate,
|
|
535
|
+
motionClass,
|
|
536
|
+
linkComponent
|
|
537
|
+
}
|
|
538
|
+
)
|
|
539
|
+
]
|
|
540
|
+
}
|
|
541
|
+
) });
|
|
542
|
+
}
|
|
543
|
+
);
|
|
544
|
+
NavHeader.displayName = "NavHeader";
|
|
545
|
+
const Header = NavHeader;
|
|
546
|
+
|
|
547
|
+
export { Header, NavHeader };
|