@shohojdhara/atomix 0.2.8 → 0.3.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +40 -1
  3. package/dist/atomix.css +96 -39
  4. package/dist/atomix.min.css +2 -2
  5. package/dist/index.d.ts +632 -2
  6. package/dist/index.esm.js +1306 -95
  7. package/dist/index.esm.js.map +1 -1
  8. package/dist/index.js +1330 -94
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/themes/applemix.css +96 -39
  13. package/dist/themes/applemix.min.css +2 -2
  14. package/dist/themes/boomdevs.css +96 -39
  15. package/dist/themes/boomdevs.min.css +2 -2
  16. package/dist/themes/esrar.css +96 -39
  17. package/dist/themes/esrar.min.css +2 -2
  18. package/dist/themes/flashtrade.css +97 -40
  19. package/dist/themes/flashtrade.min.css +2 -2
  20. package/dist/themes/mashroom.css +96 -39
  21. package/dist/themes/mashroom.min.css +3 -3
  22. package/dist/themes/shaj-default.css +96 -39
  23. package/dist/themes/shaj-default.min.css +2 -2
  24. package/package.json +13 -2
  25. package/src/components/Breadcrumb/Breadcrumb.tsx +8 -3
  26. package/src/components/Card/Card.tsx +9 -4
  27. package/src/components/Footer/Footer.stories.tsx +1 -2
  28. package/src/components/Footer/Footer.tsx +0 -5
  29. package/src/components/Footer/FooterLink.tsx +3 -2
  30. package/src/components/Footer/FooterSection.tsx +0 -7
  31. package/src/components/Navigation/Nav/NavItem.tsx +8 -3
  32. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +301 -13
  33. package/src/components/Navigation/SideMenu/SideMenu.tsx +236 -9
  34. package/src/components/Navigation/SideMenu/SideMenuItem.tsx +9 -4
  35. package/src/lib/composables/useSideMenu.ts +89 -30
  36. package/src/lib/index.ts +5 -0
  37. package/src/lib/theme/ThemeContext.tsx +17 -0
  38. package/src/lib/theme/ThemeManager.stories.tsx +472 -0
  39. package/src/lib/theme/ThemeManager.test.ts +186 -0
  40. package/src/lib/theme/ThemeManager.ts +501 -0
  41. package/src/lib/theme/ThemeProvider.tsx +227 -0
  42. package/src/lib/theme/index.ts +56 -0
  43. package/src/lib/theme/types.ts +247 -0
  44. package/src/lib/theme/useTheme.test.tsx +66 -0
  45. package/src/lib/theme/useTheme.ts +80 -0
  46. package/src/lib/theme/utils.test.ts +140 -0
  47. package/src/lib/theme/utils.ts +398 -0
  48. package/src/lib/types/components.ts +32 -0
  49. package/src/styles/06-components/_components.card.scss +39 -24
  50. package/src/styles/06-components/_components.side-menu.scss +79 -18
@@ -1,10 +1,10 @@
1
1
  import React, { useState, useEffect, useRef, forwardRef } from 'react';
2
2
  import { SideMenuProps } from '../../../lib/types/components';
3
3
  import { useSideMenu } from '../../../lib/composables/useSideMenu';
4
- import { SIDE_MENU } from '../../../lib/constants/components';
5
4
  import { Icon } from '../../Icon';
6
5
  import { AtomixGlass } from '../../AtomixGlass/AtomixGlass';
7
- import { log } from 'console';
6
+ import SideMenuList from './SideMenuList';
7
+ import SideMenuItem from './SideMenuItem';
8
8
 
9
9
  /**
10
10
  * SideMenu component provides a collapsible navigation menu with title and menu items.
@@ -26,9 +26,12 @@ export const SideMenu = forwardRef<HTMLDivElement, SideMenuProps>(
26
26
  {
27
27
  title,
28
28
  children,
29
+ menuItems = [],
29
30
  isOpen,
30
31
  onToggle,
31
32
  collapsible = true,
33
+ collapsibleDesktop = false,
34
+ defaultCollapsedDesktop = false,
32
35
  className = '',
33
36
  style,
34
37
  disabled = false,
@@ -42,25 +45,162 @@ export const SideMenu = forwardRef<HTMLDivElement, SideMenuProps>(
42
45
  isOpenState,
43
46
  wrapperRef,
44
47
  innerRef,
48
+ sideMenuRef,
45
49
  generateSideMenuClass,
46
50
  generateWrapperClass,
47
51
  handleToggle,
52
+ handleDesktopCollapse,
48
53
  } = useSideMenu({
49
54
  isOpen,
50
55
  onToggle,
51
56
  collapsible,
57
+ collapsibleDesktop,
58
+ defaultCollapsedDesktop,
52
59
  disabled,
53
60
  });
54
61
 
55
- const sideMenuClass = generateSideMenuClass({ className, isOpen: isOpenState });
62
+ const MOBILE_BREAKPOINT = 768;
63
+
64
+ // Track mobile state
65
+ const [isMobileState, setIsMobileState] = useState(() => {
66
+ if (typeof window === 'undefined') return false;
67
+ return window.innerWidth < MOBILE_BREAKPOINT;
68
+ });
69
+
70
+ // Track open state for nested menu items
71
+ const [nestedItemStates, setNestedItemStates] = useState<Record<number, boolean>>(() => {
72
+ const initialState: Record<number, boolean> = {};
73
+ menuItems?.forEach((_, index) => {
74
+ initialState[index] = true; // Default to open
75
+ });
76
+ return initialState;
77
+ });
78
+
79
+ // Refs for nested menu item wrappers
80
+ const nestedWrapperRefs = useRef<Record<number, HTMLDivElement | null>>({});
81
+ const nestedInnerRefs = useRef<Record<number, HTMLDivElement | null>>({});
82
+ const menuItemsLengthRef = useRef<number>(menuItems?.length ?? 0);
83
+
84
+ useEffect(() => {
85
+ const handleResize = () => {
86
+ setIsMobileState(window.innerWidth < MOBILE_BREAKPOINT);
87
+ };
88
+
89
+ window.addEventListener('resize', handleResize);
90
+ return () => window.removeEventListener('resize', handleResize);
91
+ }, []);
92
+
93
+ // Update nested item states when menuItems change
94
+ useEffect(() => {
95
+ const currentLength = menuItems?.length ?? 0;
96
+ // Only update if the length actually changed to prevent infinite loops
97
+ if (menuItemsLengthRef.current === currentLength) return;
98
+
99
+ menuItemsLengthRef.current = currentLength;
100
+
101
+ setNestedItemStates(prevStates => {
102
+ const newStates: Record<number, boolean> = {};
103
+ menuItems?.forEach((_, index) => {
104
+ newStates[index] = prevStates[index] ?? true;
105
+ });
106
+ return newStates;
107
+ });
108
+
109
+ // Clean up refs for removed items
110
+ Object.keys(nestedWrapperRefs.current).forEach(key => {
111
+ const index = Number(key);
112
+ if (index >= currentLength) {
113
+ delete nestedWrapperRefs.current[index];
114
+ delete nestedInnerRefs.current[index];
115
+ }
116
+ });
117
+ }, [menuItems?.length]);
118
+
119
+ // Set initial heights for nested wrappers on mount and when menuItems change
120
+ useEffect(() => {
121
+ if (!menuItems?.length) return;
122
+
123
+ const timeoutId = setTimeout(() => {
124
+ menuItems.forEach((_, index) => {
125
+ const wrapper = nestedWrapperRefs.current[index];
126
+ const inner = nestedInnerRefs.current[index];
127
+ const isOpen = nestedItemStates[index] ?? true;
128
+
129
+ if (wrapper && inner) {
130
+ if (isOpen) {
131
+ wrapper.style.height = `${inner.scrollHeight}px`;
132
+ } else {
133
+ wrapper.style.height = '0px';
134
+ }
135
+ }
136
+ });
137
+ }, 0);
138
+
139
+ return () => clearTimeout(timeoutId);
140
+ // Only run when menuItems change, nestedItemStates is read but not in deps to avoid loops
141
+ // eslint-disable-next-line react-hooks/exhaustive-deps
142
+ }, [menuItems?.length]);
143
+
144
+ // Update nested wrapper heights when state changes
145
+ useEffect(() => {
146
+ if (!menuItems?.length) return;
147
+
148
+ const frameIds: number[] = [];
149
+
150
+ Object.keys(nestedItemStates).forEach(key => {
151
+ const index = Number(key);
152
+ const wrapper = nestedWrapperRefs.current[index];
153
+ const inner = nestedInnerRefs.current[index];
154
+ const isOpen = nestedItemStates[index];
155
+
156
+ if (wrapper && inner) {
157
+ const frameId = requestAnimationFrame(() => {
158
+ if (wrapper && inner) {
159
+ if (isOpen) {
160
+ wrapper.style.height = `${inner.scrollHeight}px`;
161
+ } else {
162
+ wrapper.style.height = '0px';
163
+ }
164
+ }
165
+ });
166
+ frameIds.push(frameId);
167
+ }
168
+ });
169
+
170
+ return () => {
171
+ frameIds.forEach(id => cancelAnimationFrame(id));
172
+ };
173
+ }, [nestedItemStates, menuItems?.length]);
174
+
175
+ // Combine refs
176
+ const combinedRef = (node: HTMLDivElement | null) => {
177
+ (sideMenuRef as React.MutableRefObject<HTMLDivElement | null>).current = node;
178
+ if (typeof ref === 'function') {
179
+ ref(node);
180
+ } else if (ref) {
181
+ (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
182
+ }
183
+ };
184
+
185
+ const sideMenuClass = generateSideMenuClass({
186
+ className,
187
+ isOpen: isOpenState,
188
+ });
56
189
  const wrapperClass = generateWrapperClass();
57
190
 
58
191
  // Default toggle icon using Atomix Icon component
59
192
  const defaultToggleIcon = <Icon name="CaretRight" size="xs" />;
60
193
 
194
+ // Determine if we should show toggler (mobile or desktop with collapsibleDesktop)
195
+ const shouldShowToggler =
196
+ (isMobileState && collapsible) || (!isMobileState && collapsibleDesktop);
197
+ // Only show separate title if toggler is NOT shown (toggler already contains the title)
198
+ const shouldShowTitle = title && !shouldShowToggler;
199
+
61
200
  const sideMenuContent = (
62
201
  <>
63
- {title && collapsible && (
202
+ {/* Toggler (works for both mobile and desktop) */}
203
+ {title && shouldShowToggler && (
64
204
  <div
65
205
  className="c-side-menu__toggler"
66
206
  onClick={handleToggle}
@@ -81,16 +221,98 @@ export const SideMenu = forwardRef<HTMLDivElement, SideMenuProps>(
81
221
  </div>
82
222
  )}
83
223
 
84
- {title && !collapsible && <h3 className="c-side-menu__title">{title}</h3>}
224
+ {/* Title (non-collapsible) */}
225
+ {shouldShowTitle && <h3 className="c-side-menu__title">{title}</h3>}
85
226
 
86
227
  <div
87
228
  ref={wrapperRef}
88
229
  className={wrapperClass}
89
230
  id={id ? `${id}-content` : undefined}
90
- aria-hidden={collapsible ? !isOpenState : false}
231
+ aria-hidden={shouldShowToggler ? !isOpenState : false}
91
232
  >
92
233
  <div ref={innerRef} className="c-side-menu__inner">
93
- {children}
234
+ {children && children}
235
+ {menuItems?.map((item, index) => {
236
+ const isNestedItemOpen = nestedItemStates[index] ?? true;
237
+ const hasItems = item.items && item.items.length > 0;
238
+ const canToggle = hasItems && !disabled;
239
+
240
+ const handleNestedToggle = () => {
241
+ if (!canToggle) return;
242
+ setNestedItemStates(prev => ({
243
+ ...prev,
244
+ [index]: !prev[index],
245
+ }));
246
+ };
247
+
248
+ return (
249
+ <div key={index} className="c-side-menu__item">
250
+ {item.title && (
251
+ <div
252
+ className={[
253
+ 'c-side-menu__toggler',
254
+ canToggle && 'c-side-menu__toggler--nested',
255
+ isNestedItemOpen && 'is-open',
256
+ ]
257
+ .filter(Boolean)
258
+ .join(' ')}
259
+ onClick={canToggle ? handleNestedToggle : undefined}
260
+ role={canToggle ? 'button' : undefined}
261
+ tabIndex={canToggle && !disabled ? 0 : undefined}
262
+ aria-expanded={canToggle ? isNestedItemOpen : undefined}
263
+ aria-disabled={disabled}
264
+ onKeyDown={
265
+ canToggle
266
+ ? e => {
267
+ if ((e.key === 'Enter' || e.key === ' ') && !disabled) {
268
+ e.preventDefault();
269
+ handleNestedToggle();
270
+ }
271
+ }
272
+ : undefined
273
+ }
274
+ >
275
+ <span className="c-side-menu__title">{item.title}</span>
276
+ {canToggle && (
277
+ <span className="c-side-menu__toggler-icon">
278
+ {item.toggleIcon || <Icon name="CaretRight" size="xs" />}
279
+ </span>
280
+ )}
281
+ </div>
282
+ )}
283
+ {hasItems && (
284
+ <div
285
+ ref={node => {
286
+ nestedWrapperRefs.current[index] = node;
287
+ }}
288
+ className="c-side-menu__nested-wrapper"
289
+ >
290
+ <div
291
+ ref={node => {
292
+ nestedInnerRefs.current[index] = node;
293
+ }}
294
+ className="c-side-menu__nested-inner"
295
+ >
296
+ <SideMenuList>
297
+ {item.items?.map((subItem, subIndex) => (
298
+ <SideMenuItem
299
+ key={subIndex}
300
+ href={subItem.href}
301
+ onClick={subItem.onClick}
302
+ active={subItem.active}
303
+ disabled={subItem.disabled}
304
+ icon={subItem.icon}
305
+ >
306
+ {subItem.title}
307
+ </SideMenuItem>
308
+ ))}
309
+ </SideMenuList>
310
+ </div>
311
+ </div>
312
+ )}
313
+ </div>
314
+ );
315
+ })}
94
316
  </div>
95
317
  </div>
96
318
  </>
@@ -106,7 +328,12 @@ export const SideMenu = forwardRef<HTMLDivElement, SideMenuProps>(
106
328
  const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
107
329
  return (
108
330
  <AtomixGlass {...glassProps}>
109
- <div ref={ref} className={sideMenuClass + ' c-side-menu--glass'} id={id} style={style}>
331
+ <div
332
+ ref={combinedRef}
333
+ className={sideMenuClass + ' c-side-menu--glass'}
334
+ id={id}
335
+ style={style}
336
+ >
110
337
  {sideMenuContent}
111
338
  </div>
112
339
  </AtomixGlass>
@@ -114,7 +341,7 @@ export const SideMenu = forwardRef<HTMLDivElement, SideMenuProps>(
114
341
  }
115
342
 
116
343
  return (
117
- <div ref={ref} className={sideMenuClass} id={id} style={style}>
344
+ <div ref={combinedRef} className={sideMenuClass} id={id} style={style}>
118
345
  {sideMenuContent}
119
346
  </div>
120
347
  );
@@ -66,10 +66,15 @@ export const SideMenuItem = forwardRef<HTMLAnchorElement | HTMLButtonElement, Si
66
66
  // Render as link if href is provided
67
67
  if (href) {
68
68
  return LinkComponent ? (
69
- <LinkComponent {...linkProps}>
70
- {icon && <span className="c-side-menu__link-icon">{icon}</span>}
71
- <span className="c-side-menu__link-text">{children}</span>
72
- </LinkComponent>
69
+ (() => {
70
+ const Component = LinkComponent as React.ComponentType<any>;
71
+ return (
72
+ <Component {...linkProps}>
73
+ {icon && <span className="c-side-menu__link-icon">{icon}</span>}
74
+ <span className="c-side-menu__link-text">{children}</span>
75
+ </Component>
76
+ );
77
+ })()
73
78
  ) : (
74
79
  <a {...linkProps}>
75
80
  {icon && <span className="c-side-menu__link-icon">{icon}</span>}
@@ -11,76 +11,126 @@ export function useSideMenu(initialProps?: Partial<SideMenuProps>) {
11
11
  // Default side menu properties
12
12
  const defaultProps: Partial<SideMenuProps> = {
13
13
  collapsible: true,
14
+ collapsibleDesktop: false,
15
+ defaultCollapsedDesktop: false,
14
16
  isOpen: false,
15
17
  ...initialProps,
16
18
  };
17
19
 
18
20
  // Local open state for when not controlled externally
19
- const [isOpenState, setIsOpenState] = useState(defaultProps.isOpen || false);
21
+ const [isOpenState, setIsOpenState] = useState(
22
+ defaultProps.defaultCollapsedDesktop !== undefined
23
+ ? !defaultProps.defaultCollapsedDesktop
24
+ : (defaultProps.isOpen || false)
25
+ );
20
26
 
21
27
  // Refs for managing responsive behavior
22
28
  const wrapperRef = useRef<HTMLDivElement>(null);
23
29
  const innerRef = useRef<HTMLDivElement>(null);
30
+ const sideMenuRef = useRef<HTMLDivElement>(null);
24
31
 
25
32
  // Update local state when external state changes
26
33
  useEffect(() => {
27
34
  if (typeof defaultProps.isOpen !== 'undefined') {
28
35
  setIsOpenState(defaultProps.isOpen);
36
+ } else if (defaultProps.defaultCollapsedDesktop !== undefined) {
37
+ setIsOpenState(!defaultProps.defaultCollapsedDesktop);
29
38
  }
30
- }, [defaultProps.isOpen]);
39
+ }, [defaultProps.isOpen, defaultProps.defaultCollapsedDesktop]);
31
40
 
32
- // Handle responsive behavior - auto-open on desktop, controlled on mobile
41
+ // Set initial height on mount
33
42
  useEffect(() => {
34
- const handleResize = () => {
35
- const isMobile = window.innerWidth < 768; // MD breakpoint
43
+ const isMobile = window.innerWidth < 768;
44
+ const shouldCollapse = isMobile ? defaultProps.collapsible : defaultProps.collapsibleDesktop;
45
+ const currentOpen = typeof defaultProps.isOpen !== 'undefined' ? defaultProps.isOpen : isOpenState;
36
46
 
37
- if (!isMobile && defaultProps.collapsible) {
38
- // Auto-open on desktop
39
- if (typeof defaultProps.onToggle === 'function') {
40
- defaultProps.onToggle(true);
41
- } else {
42
- setIsOpenState(true);
47
+ if (shouldCollapse && wrapperRef.current && innerRef.current) {
48
+ // Use setTimeout to ensure DOM is fully rendered
49
+ const timeoutId = setTimeout(() => {
50
+ if (wrapperRef.current && innerRef.current) {
51
+ if (currentOpen) {
52
+ wrapperRef.current.style.height = `${innerRef.current.scrollHeight}px`;
53
+ } else {
54
+ wrapperRef.current.style.height = '0px';
55
+ }
43
56
  }
57
+ }, 0);
58
+
59
+ return () => clearTimeout(timeoutId);
60
+ } else if (!shouldCollapse && wrapperRef.current) {
61
+ wrapperRef.current.style.height = 'auto';
62
+ }
63
+ }, []); // Only run on mount
64
+
65
+ // Handle responsive behavior - vertical collapse for both mobile and desktop
66
+ useEffect(() => {
67
+ const handleResize = () => {
68
+ const isMobile = window.innerWidth < 768; // MD breakpoint
69
+ const shouldCollapse = isMobile ? defaultProps.collapsible : defaultProps.collapsibleDesktop;
44
70
 
45
- // Reset wrapper height on desktop
71
+ if (!shouldCollapse) {
72
+ // Not collapsible - always show content
46
73
  if (wrapperRef.current) {
47
74
  wrapperRef.current.style.height = 'auto';
48
75
  }
49
- } else if (isMobile && wrapperRef.current && innerRef.current) {
50
- // Set proper height for mobile animation
76
+ } else if (wrapperRef.current && innerRef.current) {
77
+ // Set proper height for vertical animation (both mobile and desktop)
51
78
  const currentOpen =
52
79
  typeof defaultProps.isOpen !== 'undefined' ? defaultProps.isOpen : isOpenState;
53
- if (currentOpen) {
54
- wrapperRef.current.style.height = `${innerRef.current.scrollHeight}px`;
55
- } else {
56
- wrapperRef.current.style.height = '0px';
57
- }
80
+
81
+ // Use requestAnimationFrame to ensure DOM is ready
82
+ requestAnimationFrame(() => {
83
+ if (wrapperRef.current && innerRef.current) {
84
+ if (currentOpen) {
85
+ wrapperRef.current.style.height = `${innerRef.current.scrollHeight}px`;
86
+ } else {
87
+ wrapperRef.current.style.height = '0px';
88
+ }
89
+ }
90
+ });
58
91
  }
59
92
  };
60
93
 
61
- handleResize(); // Initial call
94
+ // Initial call with a small delay to ensure DOM is ready
95
+ const timeoutId = setTimeout(handleResize, 0);
62
96
  window.addEventListener('resize', handleResize);
63
97
 
64
98
  return () => {
99
+ clearTimeout(timeoutId);
65
100
  window.removeEventListener('resize', handleResize);
66
101
  };
67
- }, [defaultProps.collapsible, defaultProps.isOpen, defaultProps.onToggle, isOpenState]);
102
+ }, [
103
+ defaultProps.collapsible,
104
+ defaultProps.collapsibleDesktop,
105
+ defaultProps.isOpen,
106
+ defaultProps.onToggle,
107
+ isOpenState,
108
+ ]);
68
109
 
69
- // Update wrapper height when open state changes on mobile
110
+ // Update wrapper height when open state changes (both mobile and desktop)
70
111
  useEffect(() => {
71
112
  const isMobile = window.innerWidth < 768;
113
+ const shouldCollapse = isMobile ? defaultProps.collapsible : defaultProps.collapsibleDesktop;
72
114
 
73
- if (isMobile && wrapperRef.current && innerRef.current && defaultProps.collapsible) {
115
+ if (shouldCollapse && wrapperRef.current && innerRef.current) {
74
116
  const currentOpen =
75
117
  typeof defaultProps.isOpen !== 'undefined' ? defaultProps.isOpen : isOpenState;
76
118
 
77
- if (currentOpen) {
78
- wrapperRef.current.style.height = `${innerRef.current.scrollHeight}px`;
79
- } else {
80
- wrapperRef.current.style.height = '0px';
81
- }
119
+ // Use requestAnimationFrame to ensure DOM is ready
120
+ requestAnimationFrame(() => {
121
+ if (wrapperRef.current && innerRef.current) {
122
+ if (currentOpen) {
123
+ wrapperRef.current.style.height = `${innerRef.current.scrollHeight}px`;
124
+ } else {
125
+ wrapperRef.current.style.height = '0px';
126
+ }
127
+ }
128
+ });
129
+ } else if (!shouldCollapse && wrapperRef.current) {
130
+ // Not collapsible - always show content
131
+ wrapperRef.current.style.height = 'auto';
82
132
  }
83
- }, [defaultProps.isOpen, isOpenState, defaultProps.collapsible]);
133
+ }, [defaultProps.isOpen, isOpenState, defaultProps.collapsible, defaultProps.collapsibleDesktop]);
84
134
 
85
135
  /**
86
136
  * Generate side menu class based on properties
@@ -104,7 +154,7 @@ export function useSideMenu(initialProps?: Partial<SideMenuProps>) {
104
154
  };
105
155
 
106
156
  /**
107
- * Handle toggle click
157
+ * Handle toggle click (mobile)
108
158
  */
109
159
  const handleToggle = () => {
110
160
  if (defaultProps.disabled) return;
@@ -121,6 +171,13 @@ export function useSideMenu(initialProps?: Partial<SideMenuProps>) {
121
171
  }
122
172
  };
123
173
 
174
+ /**
175
+ * Handle desktop collapse toggle (uses same toggle as mobile)
176
+ */
177
+ const handleDesktopCollapse = () => {
178
+ handleToggle();
179
+ };
180
+
124
181
  /**
125
182
  * Get current open state
126
183
  * @returns Current open state
@@ -134,9 +191,11 @@ export function useSideMenu(initialProps?: Partial<SideMenuProps>) {
134
191
  isOpenState: getCurrentOpenState(),
135
192
  wrapperRef,
136
193
  innerRef,
194
+ sideMenuRef,
137
195
  generateSideMenuClass,
138
196
  generateWrapperClass,
139
197
  handleToggle,
198
+ handleDesktopCollapse,
140
199
  getCurrentOpenState,
141
200
  };
142
201
  }
package/src/lib/index.ts CHANGED
@@ -3,9 +3,14 @@ import * as composablesImport from './composables';
3
3
  import * as utilsImport from './utils';
4
4
  import * as typesImport from './types';
5
5
  import * as constantsImport from './constants';
6
+ import * as themeImport from './theme';
6
7
 
7
8
  // Export as namespaces with explicit typing
8
9
  export const composables: typeof composablesImport = composablesImport;
9
10
  export const utils: typeof utilsImport = utilsImport;
10
11
  export const types: typeof typesImport = typesImport;
11
12
  export const constants: typeof constantsImport = constantsImport;
13
+ export const theme: typeof themeImport = themeImport;
14
+
15
+ // Also export theme utilities directly for convenience
16
+ export * from './theme';
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Theme Context
3
+ *
4
+ * React context for theme management
5
+ */
6
+
7
+ import { createContext } from 'react';
8
+ import type { ThemeContextValue } from './types';
9
+
10
+ /**
11
+ * Theme context with default values
12
+ */
13
+ export const ThemeContext = createContext<ThemeContextValue | null>(null);
14
+
15
+ ThemeContext.displayName = 'ThemeContext';
16
+
17
+ export default ThemeContext;