@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.
Files changed (53) hide show
  1. package/dist/components/NavHeader/NavHeader.context.d.ts +20 -0
  2. package/dist/components/NavHeader/NavHeader.context.d.ts.map +1 -0
  3. package/dist/components/NavHeader/NavHeader.context.js +13 -0
  4. package/dist/components/NavHeader/NavHeader.d.ts +8 -0
  5. package/dist/components/NavHeader/NavHeader.d.ts.map +1 -0
  6. package/dist/components/NavHeader/NavHeader.helpers.d.ts +13 -0
  7. package/dist/components/NavHeader/NavHeader.helpers.d.ts.map +1 -0
  8. package/dist/components/NavHeader/NavHeader.helpers.js +14 -0
  9. package/dist/components/NavHeader/NavHeader.js +547 -0
  10. package/dist/components/NavHeader/NavHeader.styles.d.ts +67 -0
  11. package/dist/components/NavHeader/NavHeader.styles.d.ts.map +1 -0
  12. package/dist/components/NavHeader/NavHeader.styles.js +123 -0
  13. package/dist/components/NavHeader/NavHeader.types.d.ts +321 -0
  14. package/dist/components/NavHeader/NavHeader.types.d.ts.map +1 -0
  15. package/dist/components/NavHeader/index.d.ts +11 -0
  16. package/dist/components/NavHeader/index.d.ts.map +1 -0
  17. package/dist/components/NavHeader/subcomponents/NavActionOverflow.d.ts +13 -0
  18. package/dist/components/NavHeader/subcomponents/NavActionOverflow.d.ts.map +1 -0
  19. package/dist/components/NavHeader/subcomponents/NavActionOverflow.js +169 -0
  20. package/dist/components/NavHeader/subcomponents/NavActions.d.ts +22 -0
  21. package/dist/components/NavHeader/subcomponents/NavActions.d.ts.map +1 -0
  22. package/dist/components/NavHeader/subcomponents/NavActions.js +228 -0
  23. package/dist/components/NavHeader/subcomponents/NavBrand.d.ts +12 -0
  24. package/dist/components/NavHeader/subcomponents/NavBrand.d.ts.map +1 -0
  25. package/dist/components/NavHeader/subcomponents/NavBrand.js +94 -0
  26. package/dist/components/NavHeader/subcomponents/NavItems.d.ts +15 -0
  27. package/dist/components/NavHeader/subcomponents/NavItems.d.ts.map +1 -0
  28. package/dist/components/NavHeader/subcomponents/NavItems.js +141 -0
  29. package/dist/components/NavHeader/subcomponents/NavItemsOverflow.d.ts +13 -0
  30. package/dist/components/NavHeader/subcomponents/NavItemsOverflow.d.ts.map +1 -0
  31. package/dist/components/NavHeader/subcomponents/NavItemsOverflow.js +165 -0
  32. package/dist/components/NavHeader/subcomponents/NavMegaMenu.d.ts +12 -0
  33. package/dist/components/NavHeader/subcomponents/NavMegaMenu.d.ts.map +1 -0
  34. package/dist/components/NavHeader/subcomponents/NavMegaMenu.js +73 -0
  35. package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.d.ts +16 -0
  36. package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.d.ts.map +1 -0
  37. package/dist/components/NavHeader/subcomponents/NavMobileSearchDrawer.js +149 -0
  38. package/dist/components/NavHeader/subcomponents/NavMobileTabContent.d.ts +11 -0
  39. package/dist/components/NavHeader/subcomponents/NavMobileTabContent.d.ts.map +1 -0
  40. package/dist/components/NavHeader/subcomponents/NavMobileTabContent.js +40 -0
  41. package/dist/components/NavHeader/subcomponents/NavMobileTabSlider.d.ts +13 -0
  42. package/dist/components/NavHeader/subcomponents/NavMobileTabSlider.d.ts.map +1 -0
  43. package/dist/components/NavHeader/subcomponents/NavMobileTabSlider.js +111 -0
  44. package/dist/components/NavHeader/subcomponents/NavSearch.d.ts +15 -0
  45. package/dist/components/NavHeader/subcomponents/NavSearch.d.ts.map +1 -0
  46. package/dist/components/NavHeader/subcomponents/NavSearch.js +222 -0
  47. package/dist/components/NavHeader/subcomponents/OfferBanner.d.ts +11 -0
  48. package/dist/components/NavHeader/subcomponents/OfferBanner.d.ts.map +1 -0
  49. package/dist/components/NavHeader/subcomponents/OfferBanner.js +92 -0
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js +9 -0
  53. package/package.json +3 -3
@@ -0,0 +1,141 @@
1
+ "use client";
2
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { motion } from '@timmbr/motion';
5
+ import { cn } from '@timmbr/utils';
6
+ import { navHeaderItemVariants } from '../NavHeader.styles.js';
7
+ import { resolveNavDestination, isCrossZoneNavigation } from '../NavHeader.helpers.js';
8
+ import { NavItemsOverflow } from './NavItemsOverflow.js';
9
+ import { useTimmbrConfig } from '../../../providers/TimmbrConfigProvider.js';
10
+
11
+ const NavItems = ({
12
+ items,
13
+ activeItemId,
14
+ onItemMouseEnter,
15
+ onItemMouseLeave,
16
+ onItemClick,
17
+ maxVisibleItems,
18
+ shouldAnimate = true,
19
+ motionClass,
20
+ linkComponent: CustomLink
21
+ }) => {
22
+ const config = useTimmbrConfig();
23
+ const { visibleItems, overflowItems } = React.useMemo(() => {
24
+ if (typeof maxVisibleItems !== "number" || items.length <= maxVisibleItems) {
25
+ return { visibleItems: items, overflowItems: [] };
26
+ }
27
+ const visibleCount = Math.max(1, maxVisibleItems);
28
+ return {
29
+ visibleItems: items.slice(0, visibleCount),
30
+ overflowItems: items.slice(visibleCount)
31
+ };
32
+ }, [items, maxVisibleItems]);
33
+ return /* @__PURE__ */ jsxs(
34
+ "nav",
35
+ {
36
+ "aria-label": "Main Navigation",
37
+ className: "flex items-center gap-2.5 xl:gap-4 2xl:gap-6 h-full",
38
+ "data-slot": "nav-items-container",
39
+ onMouseLeave: onItemMouseLeave,
40
+ children: [
41
+ visibleItems.map((item) => {
42
+ const isActive = activeItemId === item.id;
43
+ const hasContent = Boolean(item.content);
44
+ const resolvedHref = resolveNavDestination(item.href, item.zone, config.zones?.zones);
45
+ const requiresCrossZone = isCrossZoneNavigation(item.zone, config.zones?.currentZone, item.crossZone);
46
+ const handleClick = (e) => {
47
+ if (item.disabled) {
48
+ e.preventDefault();
49
+ return;
50
+ }
51
+ onItemClick(item.id, item);
52
+ };
53
+ const handleKeyDown = (e) => {
54
+ if (e.key === "Enter" || e.key === " ") {
55
+ if (hasContent && !item.href) {
56
+ e.preventDefault();
57
+ onItemClick(item.id, item);
58
+ }
59
+ }
60
+ };
61
+ const itemContent = /* @__PURE__ */ jsxs(Fragment, { children: [
62
+ /* @__PURE__ */ jsx("span", { children: item.label }),
63
+ item.badge && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center justify-center text-xs font-semibold px-1.5 py-0.5 rounded-full bg-primary-100 text-primary-800", children: item.badge }),
64
+ isActive && shouldAnimate && /* @__PURE__ */ jsx(
65
+ motion.div,
66
+ {
67
+ layoutId: "nav-header-active-indicator",
68
+ className: "absolute bottom-0 left-0 right-0 h-[2.5px] bg-[#C0643A] rounded-full",
69
+ transition: { type: "spring", stiffness: 500, damping: 35 }
70
+ }
71
+ ),
72
+ isActive && !shouldAnimate && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 h-[2.5px] bg-[#C0643A] rounded-full" })
73
+ ] });
74
+ const className = cn(
75
+ navHeaderItemVariants({ active: isActive, disabled: item.disabled }),
76
+ !shouldAnimate && "transition-none",
77
+ motionClass,
78
+ item.className
79
+ );
80
+ if (!hasContent && resolvedHref) {
81
+ if (CustomLink && !requiresCrossZone) {
82
+ return /* @__PURE__ */ jsx(
83
+ CustomLink,
84
+ {
85
+ href: resolvedHref,
86
+ className,
87
+ onClick: handleClick,
88
+ "data-slot": "nav-item-link",
89
+ children: itemContent
90
+ },
91
+ item.id
92
+ );
93
+ }
94
+ return /* @__PURE__ */ jsx(
95
+ "a",
96
+ {
97
+ href: resolvedHref,
98
+ "data-cross-zone": requiresCrossZone ? "true" : void 0,
99
+ className,
100
+ onClick: handleClick,
101
+ "data-slot": "nav-item-link",
102
+ children: itemContent
103
+ },
104
+ item.id
105
+ );
106
+ }
107
+ return /* @__PURE__ */ jsx(
108
+ "button",
109
+ {
110
+ type: "button",
111
+ className,
112
+ "aria-haspopup": "dialog",
113
+ "aria-expanded": isActive,
114
+ disabled: item.disabled,
115
+ onClick: handleClick,
116
+ onKeyDown: handleKeyDown,
117
+ onMouseEnter: () => onItemMouseEnter(item.id),
118
+ "data-slot": "nav-item-trigger",
119
+ "data-active": isActive ? "true" : void 0,
120
+ children: itemContent
121
+ },
122
+ item.id
123
+ );
124
+ }),
125
+ overflowItems.length > 0 && /* @__PURE__ */ jsx(
126
+ NavItemsOverflow,
127
+ {
128
+ items: overflowItems,
129
+ activeItemId,
130
+ onItemClick,
131
+ shouldAnimate,
132
+ motionClass,
133
+ linkComponent: CustomLink
134
+ }
135
+ )
136
+ ]
137
+ }
138
+ );
139
+ };
140
+
141
+ export { NavItems };
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import type { NavHeaderItem } from '../NavHeader.types';
3
+ export interface NavItemsOverflowProps {
4
+ items: NavHeaderItem[];
5
+ activeItemId: string | null;
6
+ onItemClick: (id: string, item: NavHeaderItem) => void;
7
+ shouldAnimate?: boolean;
8
+ motionClass?: string;
9
+ linkComponent?: React.ComponentType<any>;
10
+ }
11
+ export declare const NavItemsOverflow: React.FC<NavItemsOverflowProps>;
12
+ export default NavItemsOverflow;
13
+ //# sourceMappingURL=NavItemsOverflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavItemsOverflow.d.ts","sourceRoot":"","sources":["../../../../src/components/NavHeader/subcomponents/NavItemsOverflow.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACvD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAkL5D,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,165 @@
1
+ "use client";
2
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { AnimatePresence, motion } from '@timmbr/motion';
5
+ import { MoreHorizontal } from '@timmbr/icons';
6
+ import { cn } from '@timmbr/utils';
7
+ import { navHeaderItemVariants } from '../NavHeader.styles.js';
8
+ import { resolveNavDestination, isCrossZoneNavigation } from '../NavHeader.helpers.js';
9
+ import { useTimmbrConfig } from '../../../providers/TimmbrConfigProvider.js';
10
+
11
+ const NavItemsOverflow = ({
12
+ items,
13
+ activeItemId,
14
+ onItemClick,
15
+ shouldAnimate = true,
16
+ motionClass,
17
+ linkComponent: CustomLink
18
+ }) => {
19
+ const [isOpen, setIsOpen] = React.useState(false);
20
+ const containerRef = React.useRef(null);
21
+ const config = useTimmbrConfig();
22
+ const isChildActive = items.some((item) => item.id === activeItemId);
23
+ React.useEffect(() => {
24
+ if (!isOpen) return;
25
+ const handleClickOutside = (e) => {
26
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
27
+ setIsOpen(false);
28
+ }
29
+ };
30
+ const handleKeyDown = (e) => {
31
+ if (e.key === "Escape") {
32
+ setIsOpen(false);
33
+ }
34
+ };
35
+ document.addEventListener("mousedown", handleClickOutside);
36
+ document.addEventListener("keydown", handleKeyDown);
37
+ return () => {
38
+ document.removeEventListener("mousedown", handleClickOutside);
39
+ document.removeEventListener("keydown", handleKeyDown);
40
+ };
41
+ }, [isOpen]);
42
+ if (!items || items.length === 0) {
43
+ return null;
44
+ }
45
+ return /* @__PURE__ */ jsxs(
46
+ "div",
47
+ {
48
+ ref: containerRef,
49
+ className: "relative flex items-center h-full",
50
+ "data-slot": "nav-items-overflow",
51
+ children: [
52
+ /* @__PURE__ */ jsxs(
53
+ "button",
54
+ {
55
+ type: "button",
56
+ "aria-label": "More navigation categories",
57
+ "aria-expanded": isOpen,
58
+ "aria-haspopup": "menu",
59
+ onClick: () => setIsOpen((prev) => !prev),
60
+ className: cn(
61
+ navHeaderItemVariants({ active: isOpen || isChildActive }),
62
+ "px-2 py-2 flex items-center justify-center rounded-lg hover:bg-black/5 transition-colors",
63
+ !shouldAnimate && "transition-none",
64
+ motionClass
65
+ ),
66
+ "data-slot": "nav-items-overflow-trigger",
67
+ children: [
68
+ /* @__PURE__ */ jsx(MoreHorizontal, { size: 20, className: "stroke-[2]" }),
69
+ (isOpen || isChildActive) && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-2 right-2 h-[2.5px] bg-[#C0643A] rounded-full" })
70
+ ]
71
+ }
72
+ ),
73
+ /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: isOpen && /* @__PURE__ */ jsx(
74
+ motion.div,
75
+ {
76
+ initial: shouldAnimate ? { opacity: 0, y: 6, scale: 0.97 } : false,
77
+ animate: shouldAnimate ? { opacity: 1, y: 0, scale: 1 } : void 0,
78
+ exit: shouldAnimate ? { opacity: 0, y: 6, scale: 0.97 } : void 0,
79
+ transition: { duration: 0.18, ease: [0.25, 1, 0.5, 1] },
80
+ className: cn(
81
+ "absolute top-full left-0 mt-2 w-56 bg-white border border-[#E7DFD3] shadow-2xl rounded-2xl p-2 z-50 text-left overflow-hidden divide-y divide-grey-100",
82
+ !shouldAnimate && "transition-none",
83
+ motionClass
84
+ ),
85
+ role: "menu",
86
+ "aria-orientation": "vertical",
87
+ "data-slot": "nav-items-overflow-popover",
88
+ children: /* @__PURE__ */ jsx("div", { className: "py-1 flex flex-col gap-0.5", children: items.map((item) => {
89
+ const isActive = activeItemId === item.id;
90
+ const resolvedHref = resolveNavDestination(
91
+ item.href,
92
+ item.zone,
93
+ config.zones?.zones
94
+ );
95
+ const requiresCrossZone = isCrossZoneNavigation(
96
+ item.zone,
97
+ config.zones?.currentZone,
98
+ item.crossZone
99
+ );
100
+ const itemRowContent = /* @__PURE__ */ jsxs(Fragment, { children: [
101
+ /* @__PURE__ */ jsx("span", { className: cn("truncate", isActive && "text-[#C0643A] font-bold"), children: item.label }),
102
+ item.badge && /* @__PURE__ */ jsx("span", { className: "shrink-0 px-1.5 py-0.5 rounded-full bg-primary-100 text-primary-800 text-[10px] font-bold", children: item.badge })
103
+ ] });
104
+ const itemClass = cn(
105
+ "w-full flex items-center justify-between gap-3 px-3 py-2 text-xs font-semibold text-[#3C3C3C] hover:text-[#C0643A] hover:bg-[#F7F1E6]/60 rounded-xl transition-colors cursor-pointer text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#C0643A]",
106
+ item.disabled && "opacity-40 cursor-not-allowed pointer-events-none",
107
+ isActive && "bg-[#F7F1E6]/80 text-[#C0643A]",
108
+ item.className
109
+ );
110
+ const handleClick = (e) => {
111
+ if (item.disabled) {
112
+ e.preventDefault();
113
+ return;
114
+ }
115
+ onItemClick(item.id, item);
116
+ setIsOpen(false);
117
+ };
118
+ if (resolvedHref && !item.disabled) {
119
+ if (CustomLink && !requiresCrossZone) {
120
+ return /* @__PURE__ */ jsx(
121
+ CustomLink,
122
+ {
123
+ href: resolvedHref,
124
+ role: "menuitem",
125
+ className: itemClass,
126
+ onClick: handleClick,
127
+ children: itemRowContent
128
+ },
129
+ item.id
130
+ );
131
+ }
132
+ return /* @__PURE__ */ jsx(
133
+ "a",
134
+ {
135
+ href: resolvedHref,
136
+ "data-cross-zone": requiresCrossZone ? "true" : void 0,
137
+ role: "menuitem",
138
+ className: itemClass,
139
+ onClick: handleClick,
140
+ children: itemRowContent
141
+ },
142
+ item.id
143
+ );
144
+ }
145
+ return /* @__PURE__ */ jsx(
146
+ "button",
147
+ {
148
+ type: "button",
149
+ role: "menuitem",
150
+ disabled: item.disabled,
151
+ onClick: handleClick,
152
+ className: itemClass,
153
+ children: itemRowContent
154
+ },
155
+ item.id
156
+ );
157
+ }) })
158
+ }
159
+ ) })
160
+ ]
161
+ }
162
+ );
163
+ };
164
+
165
+ export { NavItemsOverflow };
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ import type { NavHeaderItem } from '../NavHeader.types';
3
+ export interface NavMegaMenuProps {
4
+ activeItem: NavHeaderItem | null;
5
+ onMouseEnter?: () => void;
6
+ onMouseLeave?: () => void;
7
+ onClose?: () => void;
8
+ shouldAnimate?: boolean;
9
+ motionClass?: string;
10
+ }
11
+ export declare const NavMegaMenu: React.FC<NavMegaMenuProps>;
12
+ //# sourceMappingURL=NavMegaMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavMegaMenu.d.ts","sourceRoot":"","sources":["../../../../src/components/NavHeader/subcomponents/NavMegaMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,aAAa,GAAG,IAAI,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAqElD,CAAC"}
@@ -0,0 +1,73 @@
1
+ "use client";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { AnimatePresence, motion, fadeSlideDownVariants, getTransition } from '@timmbr/motion';
5
+ import { cn } from '@timmbr/utils';
6
+ import { megaMenuCardVariants } from '../NavHeader.styles.js';
7
+
8
+ const NavMegaMenu = ({
9
+ activeItem,
10
+ onMouseEnter,
11
+ onMouseLeave,
12
+ onClose,
13
+ shouldAnimate = true,
14
+ motionClass
15
+ }) => {
16
+ const hasContent = Boolean(activeItem?.content);
17
+ React.useEffect(() => {
18
+ if (!hasContent) return;
19
+ const handleKeyDown = (e) => {
20
+ if (e.key === "Escape") {
21
+ onClose?.();
22
+ }
23
+ };
24
+ window.addEventListener("keydown", handleKeyDown);
25
+ return () => window.removeEventListener("keydown", handleKeyDown);
26
+ }, [hasContent, onClose]);
27
+ const widthStyle = React.useMemo(() => {
28
+ if (typeof activeItem?.popoverWidth === "number") {
29
+ return { maxWidth: `${activeItem.popoverWidth}px` };
30
+ }
31
+ return {};
32
+ }, [activeItem?.popoverWidth]);
33
+ const isFullBleed = activeItem?.popoverWidth === "full";
34
+ return /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: hasContent && activeItem && /* @__PURE__ */ jsx(
35
+ "div",
36
+ {
37
+ "data-slot": "nav-megamenu-wrapper",
38
+ className: "absolute top-full left-0 right-0 z-50 pt-1 pointer-events-none",
39
+ children: /* @__PURE__ */ jsx(
40
+ "div",
41
+ {
42
+ className: "w-full flex justify-center pointer-events-auto",
43
+ onMouseEnter,
44
+ onMouseLeave,
45
+ children: /* @__PURE__ */ jsx(
46
+ motion.div,
47
+ {
48
+ role: "region",
49
+ "aria-label": `${typeof activeItem.label === "string" ? activeItem.label : "Navigation"} Submenu`,
50
+ initial: shouldAnimate ? "hidden" : false,
51
+ animate: shouldAnimate ? "visible" : void 0,
52
+ exit: shouldAnimate ? "exit" : void 0,
53
+ variants: shouldAnimate ? fadeSlideDownVariants : void 0,
54
+ transition: getTransition("fast"),
55
+ style: widthStyle,
56
+ "data-slot": "nav-megamenu-card",
57
+ className: cn(
58
+ megaMenuCardVariants(),
59
+ isFullBleed && "max-w-none rounded-none border-x-0",
60
+ !shouldAnimate && "transition-none",
61
+ motionClass
62
+ ),
63
+ children: activeItem.content
64
+ },
65
+ activeItem.id
66
+ )
67
+ }
68
+ )
69
+ }
70
+ ) });
71
+ };
72
+
73
+ export { NavMegaMenu };
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ import type { NavSearchConfig } from '../NavHeader.types';
3
+ export interface NavMobileSearchDrawerProps {
4
+ isOpen: boolean;
5
+ onClose: () => void;
6
+ value: string;
7
+ onChange: (value: string) => void;
8
+ onSubmit: (value: string) => void;
9
+ config?: NavSearchConfig | boolean;
10
+ shouldAnimate?: boolean;
11
+ motionClass?: string;
12
+ linkComponent?: React.ComponentType<any>;
13
+ }
14
+ export declare const NavMobileSearchDrawer: React.FC<NavMobileSearchDrawerProps>;
15
+ export default NavMobileSearchDrawer;
16
+ //# sourceMappingURL=NavMobileSearchDrawer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavMobileSearchDrawer.d.ts","sourceRoot":"","sources":["../../../../src/components/NavHeader/subcomponents/NavMobileSearchDrawer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAE1D,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC;IACnC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAoKtE,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -0,0 +1,149 @@
1
+ "use client";
2
+ import { jsx, jsxs } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { AnimatePresence, motion } from '@timmbr/motion';
6
+ import { ArrowLeft, Search, X } from '@timmbr/icons';
7
+ import { cn } from '@timmbr/utils';
8
+ import { mobileSearchDrawerVariants } from '../NavHeader.styles.js';
9
+
10
+ const NavMobileSearchDrawer = ({
11
+ isOpen,
12
+ onClose,
13
+ value,
14
+ onChange,
15
+ onSubmit,
16
+ config = {},
17
+ shouldAnimate = true,
18
+ motionClass
19
+ }) => {
20
+ const [mounted, setMounted] = React.useState(false);
21
+ const searchConfig = typeof config === "object" ? config : {};
22
+ const inputRef = React.useRef(null);
23
+ React.useEffect(() => {
24
+ setMounted(true);
25
+ }, []);
26
+ React.useEffect(() => {
27
+ if (!isOpen) return;
28
+ const originalOverflow = document.body.style.overflow;
29
+ document.body.style.overflow = "hidden";
30
+ return () => {
31
+ document.body.style.overflow = originalOverflow;
32
+ };
33
+ }, [isOpen]);
34
+ React.useEffect(() => {
35
+ if (isOpen) {
36
+ const timer = setTimeout(() => {
37
+ inputRef.current?.focus();
38
+ }, 100);
39
+ return () => clearTimeout(timer);
40
+ }
41
+ }, [isOpen]);
42
+ React.useEffect(() => {
43
+ if (!isOpen) return;
44
+ const handleKeyDown = (e) => {
45
+ if (e.key === "Escape") {
46
+ onClose();
47
+ }
48
+ };
49
+ document.addEventListener("keydown", handleKeyDown);
50
+ return () => document.removeEventListener("keydown", handleKeyDown);
51
+ }, [isOpen, onClose]);
52
+ const handleClear = () => {
53
+ onChange("");
54
+ inputRef.current?.focus();
55
+ };
56
+ const handleFormSubmit = (e) => {
57
+ e.preventDefault();
58
+ if (value.trim()) {
59
+ onSubmit(value);
60
+ onClose();
61
+ }
62
+ };
63
+ const handlePopularSearchClick = (tagLabel) => {
64
+ onChange(tagLabel);
65
+ onSubmit(tagLabel);
66
+ onClose();
67
+ };
68
+ const drawerContent = /* @__PURE__ */ jsx(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsxs(
69
+ motion.div,
70
+ {
71
+ role: "dialog",
72
+ "aria-modal": "true",
73
+ "aria-label": "Search drawer",
74
+ initial: shouldAnimate ? { opacity: 0, y: "100%" } : false,
75
+ animate: shouldAnimate ? { opacity: 1, y: "0%" } : void 0,
76
+ exit: shouldAnimate ? { opacity: 0, y: "100%" } : void 0,
77
+ transition: { duration: 0.28, ease: [0.32, 0.72, 0, 1] },
78
+ className: cn(mobileSearchDrawerVariants(), motionClass),
79
+ "data-slot": "nav-mobile-search-drawer",
80
+ children: [
81
+ /* @__PURE__ */ jsxs("div", { className: "w-full bg-[var(--color-bg-2,#F7F1E6)] border-b border-[#E7DFD3] px-3 py-3 flex items-center gap-3 shrink-0 shadow-xs", children: [
82
+ /* @__PURE__ */ jsx(
83
+ "button",
84
+ {
85
+ type: "button",
86
+ onClick: onClose,
87
+ "aria-label": "Close search and go back",
88
+ className: "w-10 h-10 rounded-full flex items-center justify-center text-grey-800 hover:text-[#C0643A] hover:bg-black/5 transition-colors cursor-pointer shrink-0",
89
+ "data-slot": "nav-mobile-search-back-btn",
90
+ children: /* @__PURE__ */ jsx(ArrowLeft, { size: 22, className: "stroke-[2]" })
91
+ }
92
+ ),
93
+ /* @__PURE__ */ jsx("form", { onSubmit: handleFormSubmit, className: "flex-1 relative flex items-center", children: /* @__PURE__ */ jsxs("div", { className: "relative w-full flex items-center", children: [
94
+ /* @__PURE__ */ jsx(
95
+ Search,
96
+ {
97
+ size: 18,
98
+ className: "absolute left-3.5 text-grey-500 pointer-events-none stroke-[2]"
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsx(
102
+ "input",
103
+ {
104
+ ref: inputRef,
105
+ type: "text",
106
+ role: "searchbox",
107
+ "aria-label": "Search store products",
108
+ value,
109
+ onChange: (e) => onChange(e.target.value),
110
+ placeholder: searchConfig.placeholder ?? "Search for furniture, decor, and more...",
111
+ className: "w-full h-11 pl-10 pr-9 rounded-full bg-white border border-[#E7DFD3] text-sm text-grey-900 placeholder:text-grey-400 focus:outline-none focus:border-[#C0643A] focus:ring-2 focus:ring-[#C0643A]/20 transition-all shadow-xs"
112
+ }
113
+ ),
114
+ value.length > 0 && /* @__PURE__ */ jsx(
115
+ "button",
116
+ {
117
+ type: "button",
118
+ onClick: handleClear,
119
+ "aria-label": "Clear search input",
120
+ className: "absolute right-3 w-6 h-6 rounded-full bg-grey-200 hover:bg-grey-300 text-grey-600 flex items-center justify-center transition-colors cursor-pointer",
121
+ children: /* @__PURE__ */ jsx(X, { size: 14, className: "stroke-[2.5]" })
122
+ }
123
+ )
124
+ ] }) })
125
+ ] }),
126
+ /* @__PURE__ */ jsxs("div", { className: "flex-1 overflow-y-auto p-4 sm:p-6 space-y-6 bg-[var(--color-bg-2,#F7F1E6)]", children: [
127
+ searchConfig.popularSearches && searchConfig.popularSearches.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
128
+ /* @__PURE__ */ jsx("h4", { className: "font-title text-xs font-semibold text-grey-500 uppercase tracking-wider", children: "POPULAR SEARCHES" }),
129
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: searchConfig.popularSearches.map((item) => /* @__PURE__ */ jsx(
130
+ "button",
131
+ {
132
+ type: "button",
133
+ onClick: () => handlePopularSearchClick(item.label),
134
+ className: "px-3.5 py-1.5 rounded-full bg-white border border-[#E7DFD3] text-xs font-semibold text-grey-700 hover:border-[#C0643A] hover:text-[#C0643A] hover:bg-white transition-colors cursor-pointer shadow-xs",
135
+ children: item.label
136
+ },
137
+ item.id
138
+ )) })
139
+ ] }),
140
+ searchConfig.suggestions && /* @__PURE__ */ jsx("div", { className: "pt-2", children: searchConfig.suggestions })
141
+ ] })
142
+ ]
143
+ }
144
+ ) });
145
+ if (!mounted) return null;
146
+ return createPortal(drawerContent, document.body);
147
+ };
148
+
149
+ export { NavMobileSearchDrawer };
@@ -0,0 +1,11 @@
1
+ import * as React from 'react';
2
+ import type { NavHeaderItem } from '../NavHeader.types';
3
+ export interface NavMobileTabContentProps {
4
+ activeItem: NavHeaderItem | null;
5
+ onClose?: () => void;
6
+ shouldAnimate?: boolean;
7
+ motionClass?: string;
8
+ }
9
+ export declare const NavMobileTabContent: React.FC<NavMobileTabContentProps>;
10
+ export default NavMobileTabContent;
11
+ //# sourceMappingURL=NavMobileTabContent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavMobileTabContent.d.ts","sourceRoot":"","sources":["../../../../src/components/NavHeader/subcomponents/NavMobileTabContent.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,aAAa,GAAG,IAAI,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,mBAAmB,EAAE,KAAK,CAAC,EAAE,CAAC,wBAAwB,CAuClE,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { jsx } from 'react/jsx-runtime';
3
+ import * as React from 'react';
4
+ import { AnimatePresence, motion } from '@timmbr/motion';
5
+ import { cn } from '@timmbr/utils';
6
+ import { mobileTabContentPanelVariants } from '../NavHeader.styles.js';
7
+
8
+ const NavMobileTabContent = ({
9
+ activeItem,
10
+ onClose,
11
+ shouldAnimate = true,
12
+ motionClass
13
+ }) => {
14
+ React.useEffect(() => {
15
+ if (!activeItem || !onClose) return;
16
+ const handleKeyDown = (e) => {
17
+ if (e.key === "Escape") {
18
+ onClose();
19
+ }
20
+ };
21
+ document.addEventListener("keydown", handleKeyDown);
22
+ return () => document.removeEventListener("keydown", handleKeyDown);
23
+ }, [activeItem, onClose]);
24
+ const hasContent = Boolean(activeItem && activeItem.content);
25
+ return /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: hasContent && /* @__PURE__ */ jsx(
26
+ motion.div,
27
+ {
28
+ initial: shouldAnimate ? { height: 0, opacity: 0 } : false,
29
+ animate: shouldAnimate ? { height: "auto", opacity: 1 } : void 0,
30
+ exit: shouldAnimate ? { height: 0, opacity: 0 } : void 0,
31
+ transition: { duration: 0.22, ease: [0.25, 1, 0.5, 1] },
32
+ className: cn(mobileTabContentPanelVariants(), motionClass),
33
+ "data-slot": "nav-mobile-tab-content-panel",
34
+ children: /* @__PURE__ */ jsx("div", { className: "w-full", children: activeItem.content })
35
+ },
36
+ activeItem.id
37
+ ) });
38
+ };
39
+
40
+ export { NavMobileTabContent };
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import type { NavHeaderItem } from '../NavHeader.types';
3
+ export interface NavMobileTabSliderProps {
4
+ items: NavHeaderItem[];
5
+ activeItemId: string | null;
6
+ onItemClick: (id: string, item: NavHeaderItem) => void;
7
+ shouldAnimate?: boolean;
8
+ motionClass?: string;
9
+ linkComponent?: React.ComponentType<any>;
10
+ }
11
+ export declare const NavMobileTabSlider: React.FC<NavMobileTabSliderProps>;
12
+ export default NavMobileTabSlider;
13
+ //# sourceMappingURL=NavMobileTabSlider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NavMobileTabSlider.d.ts","sourceRoot":"","sources":["../../../../src/components/NavHeader/subcomponents/NavMobileTabSlider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGxD,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,aAAa,KAAK,IAAI,CAAC;IACvD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;CAC1C;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAuGhE,CAAC;AAEF,eAAe,kBAAkB,CAAC"}