@rakeyshgidwani/roger-ui-bank-theme-stan-design 0.1.4 → 0.1.6

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 (164) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/dist/index.d.ts +131 -131
  3. package/dist/index.esm.js +148 -148
  4. package/dist/index.js +148 -148
  5. package/dist/styles.css +1 -1
  6. package/package.json +1 -1
  7. package/src/components/ui/accessibility-demo.tsx +271 -0
  8. package/src/components/ui/advanced-component-architecture-demo.tsx +916 -0
  9. package/src/components/ui/advanced-transition-system-demo.tsx +670 -0
  10. package/src/components/ui/advanced-transition-system.tsx +395 -0
  11. package/src/components/ui/animation/animated-container.tsx +166 -0
  12. package/src/components/ui/animation/index.ts +19 -0
  13. package/src/components/ui/animation/staggered-container.tsx +68 -0
  14. package/src/components/ui/animation-demo.tsx +250 -0
  15. package/src/components/ui/badge.tsx +33 -0
  16. package/src/components/ui/battery-conscious-animation-demo.tsx +568 -0
  17. package/src/components/ui/border-radius-shadow-demo.tsx +187 -0
  18. package/src/components/ui/button.tsx +36 -0
  19. package/src/components/ui/card.tsx +207 -0
  20. package/src/components/ui/checkbox.tsx +30 -0
  21. package/src/components/ui/color-preview.tsx +411 -0
  22. package/src/components/ui/data-display/chart.tsx +653 -0
  23. package/src/components/ui/data-display/data-grid-simple.tsx +76 -0
  24. package/src/components/ui/data-display/data-grid.tsx +680 -0
  25. package/src/components/ui/data-display/list.tsx +456 -0
  26. package/src/components/ui/data-display/table.tsx +482 -0
  27. package/src/components/ui/data-display/timeline.tsx +441 -0
  28. package/src/components/ui/data-display/tree.tsx +602 -0
  29. package/src/components/ui/data-display/types.ts +536 -0
  30. package/src/components/ui/enterprise-mobile-experience-demo.tsx +749 -0
  31. package/src/components/ui/enterprise-mobile-experience.tsx +464 -0
  32. package/src/components/ui/feedback/alert.tsx +157 -0
  33. package/src/components/ui/feedback/progress.tsx +292 -0
  34. package/src/components/ui/feedback/skeleton.tsx +185 -0
  35. package/src/components/ui/feedback/toast.tsx +280 -0
  36. package/src/components/ui/feedback/types.ts +125 -0
  37. package/src/components/ui/font-preview.tsx +288 -0
  38. package/src/components/ui/form-demo.tsx +553 -0
  39. package/src/components/ui/hardware-acceleration-demo.tsx +547 -0
  40. package/src/components/ui/input.tsx +35 -0
  41. package/src/components/ui/label.tsx +16 -0
  42. package/src/components/ui/layout-demo.tsx +367 -0
  43. package/src/components/ui/layouts/adaptive-layout.tsx +139 -0
  44. package/src/components/ui/layouts/desktop-layout.tsx +224 -0
  45. package/src/components/ui/layouts/index.ts +10 -0
  46. package/src/components/ui/layouts/mobile-layout.tsx +162 -0
  47. package/src/components/ui/layouts/tablet-layout.tsx +197 -0
  48. package/src/components/ui/mobile-form-validation.tsx +451 -0
  49. package/src/components/ui/mobile-input-demo.tsx +201 -0
  50. package/src/components/ui/mobile-input.tsx +281 -0
  51. package/src/components/ui/mobile-skeleton-loading-demo.tsx +638 -0
  52. package/src/components/ui/navigation/breadcrumb.tsx +158 -0
  53. package/src/components/ui/navigation/index.ts +36 -0
  54. package/src/components/ui/navigation/menu.tsx +374 -0
  55. package/src/components/ui/navigation/navigation-demo.tsx +324 -0
  56. package/src/components/ui/navigation/pagination.tsx +272 -0
  57. package/src/components/ui/navigation/sidebar.tsx +383 -0
  58. package/src/components/ui/navigation/stepper.tsx +303 -0
  59. package/src/components/ui/navigation/tabs.tsx +205 -0
  60. package/src/components/ui/navigation/types.ts +299 -0
  61. package/src/components/ui/overlay/backdrop.tsx +81 -0
  62. package/src/components/ui/overlay/focus-manager.tsx +143 -0
  63. package/src/components/ui/overlay/index.ts +36 -0
  64. package/src/components/ui/overlay/modal.tsx +270 -0
  65. package/src/components/ui/overlay/overlay-manager.tsx +110 -0
  66. package/src/components/ui/overlay/popover.tsx +462 -0
  67. package/src/components/ui/overlay/portal.tsx +79 -0
  68. package/src/components/ui/overlay/tooltip.tsx +303 -0
  69. package/src/components/ui/overlay/types.ts +196 -0
  70. package/src/components/ui/performance-demo.tsx +596 -0
  71. package/src/components/ui/semantic-input-system-demo.tsx +502 -0
  72. package/src/components/ui/semantic-input-system-demo.tsx.disabled +873 -0
  73. package/src/components/ui/tablet-layout.tsx +192 -0
  74. package/src/components/ui/theme-customizer.tsx +386 -0
  75. package/src/components/ui/theme-preview.tsx +310 -0
  76. package/src/components/ui/theme-switcher.tsx +264 -0
  77. package/src/components/ui/theme-toggle.tsx +38 -0
  78. package/src/components/ui/token-demo.tsx +195 -0
  79. package/src/components/ui/touch-demo.tsx +462 -0
  80. package/src/components/ui/touch-friendly-interface-demo.tsx +519 -0
  81. package/src/components/ui/touch-friendly-interface.tsx +296 -0
  82. package/src/hooks/index.ts +190 -0
  83. package/src/hooks/use-accessibility-support.ts +518 -0
  84. package/src/hooks/use-adaptive-layout.ts +289 -0
  85. package/src/hooks/use-advanced-patterns.ts +294 -0
  86. package/src/hooks/use-advanced-transition-system.ts +393 -0
  87. package/src/hooks/use-animation-profile.ts +288 -0
  88. package/src/hooks/use-battery-animations.ts +384 -0
  89. package/src/hooks/use-battery-conscious-loading.ts +475 -0
  90. package/src/hooks/use-battery-optimization.ts +330 -0
  91. package/src/hooks/use-battery-status.ts +299 -0
  92. package/src/hooks/use-component-performance.ts +344 -0
  93. package/src/hooks/use-device-loading-states.ts +459 -0
  94. package/src/hooks/use-device.tsx +110 -0
  95. package/src/hooks/use-enterprise-mobile-experience.ts +488 -0
  96. package/src/hooks/use-form-feedback.ts +403 -0
  97. package/src/hooks/use-form-performance.ts +513 -0
  98. package/src/hooks/use-frame-rate.ts +251 -0
  99. package/src/hooks/use-gestures.ts +338 -0
  100. package/src/hooks/use-hardware-acceleration.ts +341 -0
  101. package/src/hooks/use-input-accessibility.ts +455 -0
  102. package/src/hooks/use-input-performance.ts +506 -0
  103. package/src/hooks/use-layout-performance.ts +319 -0
  104. package/src/hooks/use-loading-accessibility.ts +535 -0
  105. package/src/hooks/use-loading-performance.ts +473 -0
  106. package/src/hooks/use-memory-usage.ts +287 -0
  107. package/src/hooks/use-mobile-form-layout.ts +464 -0
  108. package/src/hooks/use-mobile-form-validation.ts +518 -0
  109. package/src/hooks/use-mobile-keyboard-optimization.ts +472 -0
  110. package/src/hooks/use-mobile-layout.ts +302 -0
  111. package/src/hooks/use-mobile-optimization.ts +406 -0
  112. package/src/hooks/use-mobile-skeleton.ts +402 -0
  113. package/src/hooks/use-mobile-touch.ts +414 -0
  114. package/src/hooks/use-performance-throttling.ts +348 -0
  115. package/src/hooks/use-performance.ts +316 -0
  116. package/src/hooks/use-reusable-architecture.ts +414 -0
  117. package/src/hooks/use-semantic-input-types.ts +357 -0
  118. package/src/hooks/use-semantic-input.ts +565 -0
  119. package/src/hooks/use-tablet-layout.ts +384 -0
  120. package/src/hooks/use-touch-friendly-input.ts +524 -0
  121. package/src/hooks/use-touch-friendly-interface.ts +331 -0
  122. package/src/hooks/use-touch-optimization.ts +375 -0
  123. package/src/index.ts +279 -279
  124. package/src/lib/utils.ts +6 -0
  125. package/src/themes/README.md +272 -0
  126. package/src/themes/ThemeContext.tsx +31 -0
  127. package/src/themes/ThemeProvider.tsx +232 -0
  128. package/src/themes/accessibility/index.ts +27 -0
  129. package/src/themes/accessibility.ts +259 -0
  130. package/src/themes/aria-patterns.ts +420 -0
  131. package/src/themes/base-themes.ts +55 -0
  132. package/src/themes/colorManager.ts +380 -0
  133. package/src/themes/examples/dark-theme.ts +154 -0
  134. package/src/themes/examples/minimal-theme.ts +108 -0
  135. package/src/themes/focus-management.ts +701 -0
  136. package/src/themes/fontLoader.ts +201 -0
  137. package/src/themes/high-contrast.ts +621 -0
  138. package/src/themes/index.ts +19 -0
  139. package/src/themes/inheritance.ts +227 -0
  140. package/src/themes/keyboard-navigation.ts +550 -0
  141. package/src/themes/motion-reduction.ts +662 -0
  142. package/src/themes/navigation.ts +238 -0
  143. package/src/themes/screen-reader.ts +645 -0
  144. package/src/themes/systemThemeDetector.ts +182 -0
  145. package/src/themes/themeCSSUpdater.ts +262 -0
  146. package/src/themes/themePersistence.ts +238 -0
  147. package/src/themes/themes/default.ts +586 -0
  148. package/src/themes/themes/harvey.ts +554 -0
  149. package/src/themes/themes/stan-design.ts +683 -0
  150. package/src/themes/types.ts +460 -0
  151. package/src/themes/useSystemTheme.ts +48 -0
  152. package/src/themes/useTheme.ts +87 -0
  153. package/src/themes/validation.ts +462 -0
  154. package/src/tokens/index.ts +34 -0
  155. package/src/tokens/tokenExporter.ts +397 -0
  156. package/src/tokens/tokenGenerator.ts +276 -0
  157. package/src/tokens/tokenManager.ts +248 -0
  158. package/src/tokens/tokenValidator.ts +543 -0
  159. package/src/tokens/types.ts +78 -0
  160. package/src/utils/bundle-analyzer.ts +260 -0
  161. package/src/utils/bundle-splitting.ts +483 -0
  162. package/src/utils/lazy-loading.ts +441 -0
  163. package/src/utils/performance-monitor.ts +513 -0
  164. package/src/utils/tree-shaking.ts +274 -0
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Breadcrumb Component
3
+ * Provides navigation breadcrumbs with theme support and responsive design
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import { useState, useMemo } from 'react';
8
+ import { ChevronRightIcon, HomeIcon } from '@heroicons/react/24/outline';
9
+ import { BreadcrumbProps, BreadcrumbItem } from './types';
10
+
11
+ export const Breadcrumb: React.FC<BreadcrumbProps> = ({
12
+ items,
13
+ separator = <ChevronRightIcon className="w-4 h-4" />,
14
+ maxItems = 5,
15
+ showHome = true,
16
+ homeIcon = <HomeIcon className="w-4 h-4" />,
17
+ onItemClick,
18
+ truncate = true,
19
+ responsive = true,
20
+ className = '',
21
+ 'data-testid': testId = 'breadcrumb',
22
+ }) => {
23
+ const [isExpanded, setIsExpanded] = useState(false);
24
+
25
+ // Memoize processed items to avoid unnecessary recalculations
26
+ const processedItems = useMemo(() => {
27
+ if (!truncate || items.length <= maxItems) {
28
+ return items;
29
+ }
30
+
31
+ if (isExpanded) {
32
+ return items;
33
+ }
34
+
35
+ // Show first, last, and ellipsis in the middle
36
+ const firstItems = items.slice(0, 2);
37
+ const lastItems = items.slice(-2);
38
+
39
+ return [
40
+ ...firstItems,
41
+ { id: 'ellipsis', label: '...', disabled: true },
42
+ ...lastItems,
43
+ ];
44
+ }, [items, maxItems, truncate, isExpanded]);
45
+
46
+ const handleItemClick = (item: BreadcrumbItem) => {
47
+ if (item.disabled || item.id === 'ellipsis') return;
48
+
49
+ if (onItemClick) {
50
+ onItemClick(item);
51
+ }
52
+ };
53
+
54
+ const handleExpandToggle = () => {
55
+ setIsExpanded(!isExpanded);
56
+ };
57
+
58
+ const getThemeClasses = () => {
59
+ return 'breadcrumb breadcrumb--stan-design';
60
+ };
61
+
62
+ const getItemClasses = (item: BreadcrumbItem, isLast: boolean) => {
63
+ const baseClasses = 'breadcrumb__item-button';
64
+
65
+ if (item.disabled) {
66
+ return `${baseClasses} breadcrumb__item-button--disabled`;
67
+ }
68
+
69
+ if (isLast) {
70
+ return `${baseClasses} breadcrumb__item-button--active`;
71
+ }
72
+
73
+ return baseClasses;
74
+ };
75
+
76
+ const getSeparatorClasses = () => {
77
+ return 'breadcrumb__separator breadcrumb__separator--semantic';
78
+ };
79
+
80
+ return (
81
+ <nav
82
+ className={`${getThemeClasses()} ${className}`}
83
+ aria-label="Breadcrumb"
84
+ data-testid={testId}
85
+ >
86
+ {showHome && (
87
+ <>
88
+ <button
89
+ onClick={() => handleItemClick({ id: 'home', label: 'Home', href: '/' })}
90
+ className={`breadcrumb__home-button ${getItemClasses({ id: 'home', label: 'Home' } as BreadcrumbItem, false)}`}
91
+ aria-label="Go to home page"
92
+ >
93
+ <span className="breadcrumb__icon">{homeIcon}</span>
94
+ </button>
95
+ {separator && (
96
+ <span className={getSeparatorClasses()} aria-hidden="true">
97
+ {separator}
98
+ </span>
99
+ )}
100
+ </>
101
+ )}
102
+
103
+ <ol className="breadcrumb__list">
104
+ {processedItems.map((item, index) => {
105
+ const isLast = index === processedItems.length - 1;
106
+ const isEllipsis = item.id === 'ellipsis';
107
+
108
+ return (
109
+ <li key={item.id} className="breadcrumb__item">
110
+ {index > 0 && separator && (
111
+ <span className={getSeparatorClasses()} aria-hidden="true">
112
+ {separator}
113
+ </span>
114
+ )}
115
+
116
+ {isEllipsis ? (
117
+ <button
118
+ onClick={handleExpandToggle}
119
+ className="breadcrumb__ellipsis"
120
+ aria-label={isExpanded ? 'Collapse breadcrumbs' : 'Expand breadcrumbs'}
121
+ >
122
+ {item.label}
123
+ </button>
124
+ ) : (
125
+ <button
126
+ onClick={() => handleItemClick(item)}
127
+ className={getItemClasses(item, isLast)}
128
+ disabled={item.disabled}
129
+ aria-current={isLast ? 'page' : undefined}
130
+ aria-label={item.href ? `Navigate to ${item.label}` : item.label}
131
+ >
132
+ {item.icon && <span className="breadcrumb__icon">{item.icon}</span>}
133
+ {responsive && window.innerWidth < 640 ? (
134
+ <span className="breadcrumb__item-label">{item.label}</span>
135
+ ) : (
136
+ <span className="breadcrumb__item-label">{item.label}</span>
137
+ )}
138
+ </button>
139
+ )}
140
+ </li>
141
+ );
142
+ })}
143
+ </ol>
144
+
145
+ {truncate && items.length > maxItems && (
146
+ <button
147
+ onClick={handleExpandToggle}
148
+ className={`breadcrumb__expand-toggle breadcrumb__expand-toggle--semantic`}
149
+ aria-label={isExpanded ? 'Show fewer breadcrumbs' : 'Show all breadcrumbs'}
150
+ >
151
+ {isExpanded ? 'Show Less' : 'Show All'}
152
+ </button>
153
+ )}
154
+ </nav>
155
+ );
156
+ };
157
+
158
+ export default Breadcrumb;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Navigation Components Index
3
+ * Exports all navigation components for easy importing
4
+ */
5
+
6
+ export { Breadcrumb } from './breadcrumb';
7
+ export { Pagination } from './pagination';
8
+ export { Tabs } from './tabs';
9
+ export { Stepper } from './stepper';
10
+ export { Menu } from './menu';
11
+ export { Sidebar } from './sidebar';
12
+
13
+ // Export types
14
+ export type {
15
+ NavigationBaseProps,
16
+ NavigationItem,
17
+ NavigationGroup,
18
+ BreadcrumbProps,
19
+ BreadcrumbItem,
20
+ PaginationProps,
21
+ PaginationItem,
22
+ TabsProps,
23
+ TabItem,
24
+ StepperProps,
25
+ StepItem,
26
+ StepAction,
27
+ MenuProps,
28
+ SidebarProps,
29
+ NavigationState,
30
+ NavigationContextValue,
31
+ NavigationAction,
32
+ NavigationBreakpoint,
33
+ NavigationSpacing,
34
+ NavigationAnimation,
35
+ NavigationAccessibility,
36
+ } from './types';
@@ -0,0 +1,374 @@
1
+ /**
2
+ * Menu Component
3
+ * Provides comprehensive menu functionality with theme support and multiple variants
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import { useState, useCallback, useMemo, useRef, useEffect } from 'react';
8
+ import { MenuProps, NavigationItem, NavigationGroup } from './types';
9
+
10
+ // Simple icon components
11
+ const ChevronDownIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
12
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
13
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
14
+ </svg>
15
+ );
16
+
17
+ const ChevronRightIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
18
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
19
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
20
+ </svg>
21
+ );
22
+
23
+ const MagnifyingGlassIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
24
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
25
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
26
+ </svg>
27
+ );
28
+
29
+ const FunnelIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
30
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
31
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 01-.659 1.591l-5.432 5.432a2.25 2.25 0 00-.659 1.591v2.927a2.25 2.25 0 01-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 00-.659-1.591L3.659 7.409A2.25 2.25 0 013 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0112 3z" />
32
+ </svg>
33
+ );
34
+
35
+ export const Menu: React.FC<MenuProps> = ({
36
+ items = [],
37
+ groups,
38
+ variant = 'dropdown',
39
+ size = 'md',
40
+ // TODO: Implement orientation, trigger, showArrow, and fullWidth in future version
41
+ // orientation = 'horizontal',
42
+ // trigger = 'click',
43
+ placement = 'bottom',
44
+ offset = 8,
45
+ // showArrow = false,
46
+ // fullWidth = false,
47
+ disabled = false,
48
+ loading = false,
49
+ searchable = false,
50
+ onSearch,
51
+ filterable = false,
52
+ onFilter,
53
+ selectable = false,
54
+ multiSelect = false,
55
+ selectedItems = [],
56
+ onSelectionChange,
57
+ className = '',
58
+ 'data-testid': testId = 'menu',
59
+ }) => {
60
+ const [isOpen, setIsOpen] = useState(false);
61
+ const [searchQuery, setSearchQuery] = useState('');
62
+ const [filterValue, setFilterValue] = useState('');
63
+ const [internalSelectedItems, setInternalSelectedItems] = useState<string[]>(selectedItems || []);
64
+ // TODO: Implement group expansion functionality in future version
65
+ // const [expandedGroups, setExpandedGroups] = useState<string[]>([]);
66
+
67
+ const menuRef = useRef<HTMLDivElement>(null);
68
+ const triggerRef = useRef<HTMLButtonElement>(null);
69
+
70
+ // Use controlled or uncontrolled selected items
71
+ const currentSelectedItems = selectedItems !== undefined ? selectedItems : internalSelectedItems;
72
+
73
+ // Handle click outside to close menu
74
+ useEffect(() => {
75
+ const handleClickOutside = (event: MouseEvent) => {
76
+ if (menuRef.current && !menuRef.current.contains(event.target as Node) &&
77
+ triggerRef.current && !triggerRef.current.contains(event.target as Node)) {
78
+ setIsOpen(false);
79
+ }
80
+ };
81
+
82
+ if (isOpen) {
83
+ document.addEventListener('mousedown', handleClickOutside);
84
+ }
85
+
86
+ return () => {
87
+ document.removeEventListener('mousedown', handleClickOutside);
88
+ };
89
+ }, [isOpen]);
90
+
91
+ // Handle escape key to close menu
92
+ useEffect(() => {
93
+ const handleEscape = (event: KeyboardEvent) => {
94
+ if (event.key === 'Escape') {
95
+ setIsOpen(false);
96
+ }
97
+ };
98
+
99
+ if (isOpen) {
100
+ document.addEventListener('keydown', handleEscape);
101
+ }
102
+
103
+ return () => {
104
+ document.removeEventListener('keydown', handleEscape);
105
+ };
106
+ }, [isOpen]);
107
+
108
+ const handleToggle = useCallback(() => {
109
+ if (disabled || loading) return;
110
+ setIsOpen(!isOpen);
111
+ }, [disabled, loading, isOpen]);
112
+
113
+ const handleItemClick = useCallback((item: NavigationItem) => {
114
+ if (item.disabled) return;
115
+
116
+ if (selectable) {
117
+ let newSelectedItems: string[];
118
+
119
+ if (multiSelect) {
120
+ if (currentSelectedItems.includes(item.id)) {
121
+ newSelectedItems = currentSelectedItems.filter(id => id !== item.id);
122
+ } else {
123
+ newSelectedItems = [...currentSelectedItems, item.id];
124
+ }
125
+ } else {
126
+ newSelectedItems = [item.id];
127
+ }
128
+
129
+ if (selectedItems === undefined) {
130
+ setInternalSelectedItems(newSelectedItems);
131
+ }
132
+
133
+ if (onSelectionChange) {
134
+ onSelectionChange(newSelectedItems);
135
+ }
136
+ }
137
+
138
+ if (item.onClick) {
139
+ item.onClick();
140
+ }
141
+
142
+ // Close menu for single select or non-selectable menus
143
+ if (!multiSelect || !selectable) {
144
+ setIsOpen(false);
145
+ }
146
+ }, [selectable, multiSelect, currentSelectedItems, selectedItems, onSelectionChange]);
147
+
148
+ // TODO: Implement group toggle functionality in future version
149
+ // const handleGroupToggle = useCallback((groupId: string) => {
150
+ // setExpandedGroups(prev =>
151
+ // prev.includes(groupId)
152
+ // ? prev.filter(id => id !== groupId)
153
+ // : [...prev, groupId]
154
+ // );
155
+ // }, []);
156
+
157
+ const handleSearch = useCallback((query: string) => {
158
+ setSearchQuery(query);
159
+ if (onSearch) {
160
+ onSearch(query);
161
+ }
162
+ }, [onSearch]);
163
+
164
+ const handleFilter = useCallback((filter: string) => {
165
+ setFilterValue(filter);
166
+ if (onFilter) {
167
+ onFilter(filter);
168
+ }
169
+ }, [onFilter]);
170
+
171
+ // Filter items based on search and filter
172
+ const filteredItems = useMemo(() => {
173
+ let filtered = items;
174
+
175
+ if (searchQuery) {
176
+ filtered = filtered.filter(item =>
177
+ item.label.toLowerCase().includes(searchQuery.toLowerCase()) ||
178
+ item.description?.toLowerCase().includes(searchQuery.toLowerCase())
179
+ );
180
+ }
181
+
182
+ if (filterValue) {
183
+ filtered = filtered.filter(item =>
184
+ item.label.toLowerCase().includes(filterValue.toLowerCase())
185
+ );
186
+ }
187
+
188
+ return filtered;
189
+ }, [items, searchQuery, filterValue]);
190
+
191
+ const getSizeClasses = () => {
192
+ switch (size) {
193
+ case 'sm':
194
+ return 'menu__trigger--sm';
195
+ case 'lg':
196
+ return 'menu__trigger--lg';
197
+ default: // md
198
+ return 'menu__trigger--md';
199
+ }
200
+ };
201
+
202
+ const getVariantClasses = () => {
203
+ switch (variant) {
204
+ case 'dropdown':
205
+ return 'menu__content--dropdown';
206
+ case 'context':
207
+ return 'menu__content--context';
208
+ case 'command':
209
+ return 'menu__content--command';
210
+ default: // default
211
+ return 'menu__content--default';
212
+ }
213
+ };
214
+
215
+ const getThemeClasses = () => {
216
+ return 'menu menu--stan-design';
217
+ };
218
+
219
+ const getItemClasses = (item: NavigationItem) => {
220
+ let classes = `menu__item ${getSizeClasses()}`;
221
+
222
+ if (item.disabled) {
223
+ classes += ' menu__item--disabled';
224
+ } else if (selectable && currentSelectedItems.includes(item.id)) {
225
+ classes += ' menu__item--selected';
226
+ } else {
227
+ classes += ' menu__item--default';
228
+ }
229
+
230
+ return classes;
231
+ };
232
+
233
+ const getPlacementClasses = () => {
234
+ switch (placement) {
235
+ case 'top':
236
+ return 'menu__content--placement-top';
237
+ case 'bottom':
238
+ return 'menu__content--placement-bottom';
239
+ case 'left':
240
+ return 'menu__content--placement-left';
241
+ case 'right':
242
+ return 'menu__content--placement-right';
243
+ default:
244
+ return 'menu__content--placement-bottom';
245
+ }
246
+ };
247
+
248
+ const renderMenuItem = (item: NavigationItem) => (
249
+ <button
250
+ key={item.id}
251
+ onClick={() => handleItemClick(item)}
252
+ disabled={item.disabled}
253
+ className={getItemClasses(item)}
254
+ >
255
+ <div className="menu__item-content">
256
+ {item.icon && <span className="menu__item-icon">{item.icon}</span>}
257
+ <div className="menu__item-text">
258
+ <div className="menu__item-label">{item.label}</div>
259
+ {item.description && (
260
+ <div className="menu__item-description">{item.description}</div>
261
+ )}
262
+ </div>
263
+ </div>
264
+ {item.badge && <span className="menu__item-badge">{item.badge}</span>}
265
+ {item.children && item.children.length > 0 && (
266
+ <ChevronRightIcon className="menu__item-arrow" />
267
+ )}
268
+ </button>
269
+ );
270
+
271
+ const renderMenuGroup = (group: NavigationGroup) => (
272
+ <div key={group.id} className="menu__group">
273
+ {group.title && (
274
+ <div className="menu__group-title">
275
+ {group.title}
276
+ </div>
277
+ )}
278
+ {group.items.map(renderMenuItem)}
279
+ </div>
280
+ );
281
+
282
+ return (
283
+ <div className={`menu__container ${getThemeClasses()}`} data-testid={testId}>
284
+ {/* Trigger Button */}
285
+ <button
286
+ ref={triggerRef}
287
+ onClick={handleToggle}
288
+ disabled={disabled || loading}
289
+ className={`menu__trigger ${getSizeClasses()} ${className}`}
290
+ aria-expanded={isOpen}
291
+ aria-haspopup="true"
292
+ >
293
+ Menu
294
+ <ChevronDownIcon className="menu__trigger-icon" />
295
+ </button>
296
+
297
+ {/* Menu Dropdown */}
298
+ {isOpen && (
299
+ <div
300
+ ref={menuRef}
301
+ role="menu"
302
+ className={`menu__content ${getVariantClasses()} ${getPlacementClasses()}`}
303
+ style={{ marginTop: placement === 'bottom' ? offset : undefined }}
304
+ >
305
+ {/* Search Bar */}
306
+ {searchable && (
307
+ <div className="menu__search">
308
+ <div className="menu__search-container">
309
+ <MagnifyingGlassIcon className="menu__search-icon" />
310
+ <input
311
+ type="text"
312
+ placeholder="Search..."
313
+ value={searchQuery}
314
+ onChange={(e) => handleSearch(e.target.value)}
315
+ className="menu__search-input"
316
+ />
317
+ </div>
318
+ </div>
319
+ )}
320
+
321
+ {/* Filter Bar */}
322
+ {filterable && (
323
+ <div className="menu__filter">
324
+ <div className="menu__filter-container">
325
+ <FunnelIcon className="menu__filter-icon" />
326
+ <select
327
+ value={filterValue}
328
+ onChange={(e) => handleFilter(e.target.value)}
329
+ className="menu__filter-select"
330
+ >
331
+ <option value="">All</option>
332
+ <option value="featured">Featured</option>
333
+ <option value="recent">Recent</option>
334
+ </select>
335
+ </div>
336
+ </div>
337
+ )}
338
+
339
+ {/* Menu Items */}
340
+ <div className="menu__items">
341
+ {groups ? groups.map(renderMenuGroup) : filteredItems.map(renderMenuItem)}
342
+ </div>
343
+
344
+ {/* Selection Actions */}
345
+ {selectable && multiSelect && currentSelectedItems.length > 0 && (
346
+ <div className="menu__selection-actions">
347
+ <div className="menu__selection-info">
348
+ <span className="menu__selection-count">
349
+ {currentSelectedItems.length} selected
350
+ </span>
351
+ <button
352
+ onClick={() => {
353
+ const newSelectedItems: string[] = [];
354
+ if (selectedItems === undefined) {
355
+ setInternalSelectedItems(newSelectedItems);
356
+ }
357
+ if (onSelectionChange) {
358
+ onSelectionChange(newSelectedItems);
359
+ }
360
+ }}
361
+ className="menu__selection-clear"
362
+ >
363
+ Clear
364
+ </button>
365
+ </div>
366
+ </div>
367
+ )}
368
+ </div>
369
+ )}
370
+ </div>
371
+ );
372
+ };
373
+
374
+ export default Menu;