@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,383 @@
1
+ /**
2
+ * Sidebar Component
3
+ * Provides comprehensive sidebar functionality with theme support and multiple variants
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import { useState, useCallback, useRef, useEffect } from 'react';
8
+ import { SidebarProps, NavigationItem, NavigationGroup } from './types';
9
+
10
+ // Simple icon components
11
+ const ChevronLeftIcon: 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="M15 19l-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
+ // TODO: Implement close icon functionality in future version
24
+ // const XMarkIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
25
+ // <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
26
+ // <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
27
+ // </svg>
28
+ // );
29
+
30
+ export const Sidebar: React.FC<SidebarProps> = ({
31
+ items = [],
32
+ groups = [],
33
+ variant = 'default',
34
+ size = 'md',
35
+ position = 'left',
36
+ collapsed = false,
37
+ defaultCollapsed = false,
38
+ onCollapseChange,
39
+ collapsible = true,
40
+ showToggle = true,
41
+ toggleIcon,
42
+ header,
43
+ footer,
44
+ overlay = false,
45
+ overlayCloseOnClick = true,
46
+ overlayCloseOnEscape = true,
47
+ overlayCloseOnOutsideClick = true,
48
+ responsive = true,
49
+ breakpoint = 'lg',
50
+ onItemClick,
51
+ onGroupToggle,
52
+ expandedGroups = [],
53
+ defaultExpandedGroups = [],
54
+ // TODO: Implement group icons in future version
55
+ // showGroupIcons = true,
56
+ showItemIcons = true,
57
+ showItemBadges = true,
58
+ showItemDescriptions = true,
59
+ sticky = false,
60
+ zIndex = 1000,
61
+ className = '',
62
+ 'data-testid': testId = 'sidebar',
63
+ }) => {
64
+ const [internalCollapsed, setInternalCollapsed] = useState<boolean>(
65
+ collapsed !== undefined ? collapsed : defaultCollapsed
66
+ );
67
+ const [internalExpandedGroups, setInternalExpandedGroups] = useState<string[]>(
68
+ expandedGroups !== undefined ? expandedGroups : defaultExpandedGroups
69
+ );
70
+ const [isOverlayOpen, setIsOverlayOpen] = useState(false);
71
+ const [isMobile, setIsMobile] = useState(false);
72
+
73
+ const sidebarRef = useRef<HTMLDivElement>(null);
74
+
75
+ // Use controlled or uncontrolled collapsed state
76
+ const currentCollapsed = collapsed !== undefined ? collapsed : internalCollapsed;
77
+ const currentExpandedGroups = expandedGroups !== undefined ? expandedGroups : internalExpandedGroups;
78
+
79
+ // Handle responsive behavior
80
+ useEffect(() => {
81
+ const handleResize = () => {
82
+ const breakpointWidth = breakpoint === 'sm' ? 640 : breakpoint === 'md' ? 768 : breakpoint === 'lg' ? 1024 : 1280;
83
+ setIsMobile(window.innerWidth < breakpointWidth);
84
+ };
85
+
86
+ if (responsive) {
87
+ handleResize();
88
+ window.addEventListener('resize', handleResize);
89
+ }
90
+
91
+ return () => {
92
+ if (responsive) {
93
+ window.removeEventListener('resize', handleResize);
94
+ }
95
+ };
96
+ }, [responsive, breakpoint]);
97
+
98
+ // Handle escape key for overlay
99
+ useEffect(() => {
100
+ const handleEscape = (event: KeyboardEvent) => {
101
+ if (event.key === 'Escape' && isOverlayOpen && overlayCloseOnEscape) {
102
+ setIsOverlayOpen(false);
103
+ }
104
+ };
105
+
106
+ if (isOverlayOpen) {
107
+ document.addEventListener('keydown', handleEscape);
108
+ }
109
+
110
+ return () => {
111
+ document.removeEventListener('keydown', handleEscape);
112
+ };
113
+ }, [isOverlayOpen, overlayCloseOnEscape]);
114
+
115
+ const handleCollapseToggle = useCallback(() => {
116
+ if (!collapsible) return;
117
+
118
+ const newCollapsed = !currentCollapsed;
119
+
120
+ if (collapsed === undefined) {
121
+ setInternalCollapsed(newCollapsed);
122
+ }
123
+
124
+ if (onCollapseChange) {
125
+ onCollapseChange(newCollapsed);
126
+ }
127
+ }, [collapsed, currentCollapsed, collapsible, onCollapseChange]);
128
+
129
+ const handleGroupToggle = useCallback((groupId: string) => {
130
+ const newExpandedGroups = currentExpandedGroups.includes(groupId)
131
+ ? currentExpandedGroups.filter(id => id !== groupId)
132
+ : [...currentExpandedGroups, groupId];
133
+
134
+ if (expandedGroups === undefined) {
135
+ setInternalExpandedGroups(newExpandedGroups);
136
+ }
137
+
138
+ if (onGroupToggle) {
139
+ onGroupToggle(groupId, newExpandedGroups.includes(groupId));
140
+ }
141
+ }, [currentExpandedGroups, expandedGroups, onGroupToggle]);
142
+
143
+ const handleItemClick = useCallback((item: NavigationItem) => {
144
+ if (item.disabled) return;
145
+
146
+ if (onItemClick) {
147
+ onItemClick(item);
148
+ }
149
+
150
+ if (item.onClick) {
151
+ item.onClick();
152
+ }
153
+
154
+ // Close overlay on mobile after item click
155
+ if (isMobile && overlay && overlayCloseOnClick) {
156
+ setIsOverlayOpen(false);
157
+ }
158
+ }, [onItemClick, isMobile, overlay, overlayCloseOnClick]);
159
+
160
+ const handleOverlayClose = useCallback(() => {
161
+ setIsOverlayOpen(false);
162
+ }, []);
163
+
164
+ const getSizeClasses = () => {
165
+ if (currentCollapsed) return 'sidebar--collapsed';
166
+
167
+ switch (size) {
168
+ case 'sm':
169
+ return 'sidebar--size-sm';
170
+ case 'lg':
171
+ return 'sidebar--size-lg';
172
+ case 'xl':
173
+ return 'sidebar--size-xl';
174
+ default: // md
175
+ return 'sidebar--size-md';
176
+ }
177
+ };
178
+
179
+ const getVariantClasses = () => {
180
+ switch (variant) {
181
+ case 'collapsible':
182
+ return 'sidebar--collapsible';
183
+ case 'overlay':
184
+ return 'sidebar--overlay';
185
+ case 'drawer':
186
+ return 'sidebar--drawer';
187
+ default: // default
188
+ return 'sidebar--default';
189
+ }
190
+ };
191
+
192
+ const getThemeClasses = () => {
193
+ return 'sidebar sidebar--stan-design';
194
+ };
195
+
196
+ const getPositionClasses = () => {
197
+ if (variant === 'overlay' || variant === 'drawer') {
198
+ return position === 'right' ? 'sidebar--position-right' : 'sidebar--position-left';
199
+ }
200
+ return '';
201
+ };
202
+
203
+ const getTransformClasses = () => {
204
+ if (variant === 'overlay' || variant === 'drawer' || (isMobile && overlay)) {
205
+ if (position === 'right') {
206
+ return isOverlayOpen ? 'sidebar--open' : 'sidebar--closed-right';
207
+ }
208
+ return isOverlayOpen ? 'sidebar--open' : 'sidebar--closed-left';
209
+ }
210
+ return '';
211
+ };
212
+
213
+ const getItemClasses = (item: NavigationItem) => {
214
+ let classes = `sidebar__item ${
215
+ currentCollapsed ? 'sidebar__item--collapsed' : 'sidebar__item--expanded'
216
+ }`;
217
+
218
+ if (item.disabled) {
219
+ classes += ' sidebar__item--disabled';
220
+ } else if (item.active) {
221
+ classes += ' sidebar__item--active';
222
+ } else {
223
+ classes += ' sidebar__item--default';
224
+ }
225
+
226
+ return classes;
227
+ };
228
+
229
+ const getGroupClasses = (groupId: string) => {
230
+ const isExpanded = currentExpandedGroups.includes(groupId);
231
+ let classes = 'sidebar__group';
232
+
233
+ if (isExpanded) {
234
+ classes += ' sidebar__group--expanded';
235
+ } else {
236
+ classes += ' sidebar__group--collapsed';
237
+ }
238
+
239
+ return classes;
240
+ };
241
+
242
+ const renderMenuItem = (item: NavigationItem) => (
243
+ <button
244
+ key={item.id}
245
+ onClick={() => handleItemClick(item)}
246
+ disabled={item.disabled}
247
+ className={getItemClasses(item)}
248
+ title={currentCollapsed ? item.label : undefined}
249
+ >
250
+ {showItemIcons && item.icon && (
251
+ <span className={`sidebar__item-icon ${currentCollapsed ? 'sidebar__item-icon--collapsed' : 'sidebar__item-icon--expanded'}`}>
252
+ {item.icon}
253
+ </span>
254
+ )}
255
+
256
+ {!currentCollapsed && (
257
+ <div className="sidebar__item-content">
258
+ <div className="sidebar__item-label">{item.label}</div>
259
+ {showItemDescriptions && item.description && (
260
+ <div className="sidebar__item-description">{item.description}</div>
261
+ )}
262
+ </div>
263
+ )}
264
+
265
+ {!currentCollapsed && showItemBadges && item.badge && (
266
+ <span className="sidebar__item-badge">{item.badge}</span>
267
+ )}
268
+ </button>
269
+ );
270
+
271
+ const renderMenuGroup = (group: NavigationGroup) => (
272
+ <div key={group.id}>
273
+ <button
274
+ onClick={() => handleGroupToggle(group.id)}
275
+ className={getGroupClasses(group.id)}
276
+ title={currentCollapsed ? group.title : undefined}
277
+ >
278
+ {/* TODO: Add icon support to NavigationGroup in future version */}
279
+ {/* {showGroupIcons && group.icon && (
280
+ <span className={currentCollapsed ? '' : 'mr-3'}>
281
+ {group.icon}
282
+ </span>
283
+ )} */}
284
+
285
+ {!currentCollapsed && (
286
+ <>
287
+ <div className="sidebar__group-title">{group.title}</div>
288
+ <ChevronRightIcon
289
+ className={`sidebar__group-arrow ${
290
+ currentExpandedGroups.includes(group.id) ? 'sidebar__group-arrow--expanded' : ''
291
+ }`}
292
+ />
293
+ </>
294
+ )}
295
+ </button>
296
+
297
+ {!currentCollapsed && currentExpandedGroups.includes(group.id) && (
298
+ <div className="sidebar__group-items">
299
+ {group.items.map(renderMenuItem)}
300
+ </div>
301
+ )}
302
+ </div>
303
+ );
304
+
305
+ const sidebarContent = (
306
+ <div
307
+ ref={sidebarRef}
308
+ className={`${getSizeClasses()} ${getVariantClasses()} ${getThemeClasses()} ${getPositionClasses()} ${getTransformClasses()} ${className}`}
309
+ data-testid={testId}
310
+ style={{ zIndex }}
311
+ >
312
+ {/* Header */}
313
+ {header && (
314
+ <div className="sidebar__header">
315
+ {header}
316
+ </div>
317
+ )}
318
+
319
+ {/* Toggle Button */}
320
+ {showToggle && collapsible && (
321
+ <div className="sidebar__toggle-container">
322
+ <button
323
+ onClick={handleCollapseToggle}
324
+ className="sidebar__toggle-button"
325
+ aria-label={currentCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
326
+ >
327
+ {toggleIcon || (currentCollapsed ? <ChevronRightIcon className="sidebar__toggle-icon" /> : <ChevronLeftIcon className="sidebar__toggle-icon" />)}
328
+ </button>
329
+ </div>
330
+ )}
331
+
332
+ {/* Navigation Items */}
333
+ <nav className="sidebar__navigation">
334
+ {groups && groups.length > 0 ? groups.map(renderMenuGroup) : items.map(renderMenuItem)}
335
+ </nav>
336
+
337
+ {/* Footer */}
338
+ {footer && (
339
+ <div className="sidebar__footer">
340
+ {footer}
341
+ </div>
342
+ )}
343
+ </div>
344
+ );
345
+
346
+ // Mobile overlay
347
+ if (isMobile && overlay) {
348
+ return (
349
+ <>
350
+ {/* Overlay Backdrop */}
351
+ {isOverlayOpen && (
352
+ <div
353
+ className="sidebar__overlay-backdrop"
354
+ onClick={overlayCloseOnOutsideClick ? handleOverlayClose : undefined}
355
+ />
356
+ )}
357
+
358
+ {/* Sidebar */}
359
+ {sidebarContent}
360
+
361
+ {/* Mobile Toggle Button */}
362
+ <button
363
+ onClick={() => setIsOverlayOpen(!isOverlayOpen)}
364
+ className="sidebar__mobile-toggle"
365
+ aria-label="Toggle sidebar"
366
+ >
367
+ <svg className="sidebar__mobile-toggle-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
368
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M4 6h16M4 12h16M4 18h16" />
369
+ </svg>
370
+ </button>
371
+ </>
372
+ );
373
+ }
374
+
375
+ // Desktop sidebar
376
+ return (
377
+ <div className={`sidebar__wrapper ${sticky ? 'sidebar__wrapper--sticky' : ''}`}>
378
+ {sidebarContent}
379
+ </div>
380
+ );
381
+ };
382
+
383
+ export default Sidebar;
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Stepper Component
3
+ * Provides comprehensive stepper functionality with theme support and multiple variants
4
+ */
5
+
6
+ import * as React from 'react';
7
+ import { useState, useCallback, useMemo } from 'react';
8
+ import { CheckIcon, ExclamationTriangleIcon } from '@heroicons/react/24/outline';
9
+ import { StepperProps, StepItem } from './types';
10
+
11
+ // Simple icon components
12
+ const ExclamationCircleIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
13
+ <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
14
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
15
+ </svg>
16
+ );
17
+
18
+ export const Stepper: React.FC<StepperProps> = ({
19
+ steps,
20
+ currentStep = 0,
21
+ defaultCurrentStep = 0,
22
+ onStepChange,
23
+ variant = 'default',
24
+ size = 'md',
25
+ showStepNumbers = true,
26
+ showStepLabels = true,
27
+ showStepDescriptions = true,
28
+ showStepIcons = true,
29
+ allowStepClick = true,
30
+ allowStepNavigation = true,
31
+ showProgress = true,
32
+ // TODO: Implement step status display in future version
33
+ // showStepStatus = true,
34
+ onStepComplete,
35
+ onStepError,
36
+ onFinish,
37
+ onCancel,
38
+ className = '',
39
+ 'data-testid': testId = 'stepper',
40
+ }) => {
41
+ const [internalCurrentStep, setInternalCurrentStep] = useState<number>(
42
+ currentStep !== undefined ? currentStep : defaultCurrentStep
43
+ );
44
+
45
+ // Use controlled or uncontrolled current step
46
+ const currentStepIndex = currentStep !== undefined ? currentStep : internalCurrentStep;
47
+
48
+ const handleStepClick = useCallback((stepIndex: number) => {
49
+ if (!allowStepClick || !allowStepNavigation) return;
50
+
51
+ const step = steps[stepIndex];
52
+ if (step.disabled) return;
53
+
54
+ if (currentStep === undefined) {
55
+ setInternalCurrentStep(stepIndex);
56
+ }
57
+
58
+ if (onStepChange) {
59
+ onStepChange(stepIndex);
60
+ }
61
+ }, [currentStep, onStepChange, allowStepClick, allowStepNavigation, steps]);
62
+
63
+ const handleStepComplete = useCallback(async (stepIndex: number) => {
64
+ const step = steps[stepIndex];
65
+ if (!step.validation) return;
66
+
67
+ try {
68
+ const isValid = await step.validation();
69
+ if (isValid) {
70
+ if (onStepComplete) {
71
+ onStepComplete(stepIndex);
72
+ }
73
+ // Move to next step if available
74
+ if (stepIndex < steps.length - 1) {
75
+ handleStepClick(stepIndex + 1);
76
+ } else if (onFinish) {
77
+ onFinish();
78
+ }
79
+ }
80
+ } catch (error) {
81
+ if (onStepError) {
82
+ onStepError(stepIndex);
83
+ }
84
+ }
85
+ }, [steps, onStepComplete, onStepError, onFinish, handleStepClick]);
86
+
87
+ const handleFinish = useCallback(() => {
88
+ if (onFinish) {
89
+ onFinish();
90
+ }
91
+ }, [onFinish]);
92
+
93
+ const handleCancel = useCallback(() => {
94
+ if (onCancel) {
95
+ onCancel();
96
+ }
97
+ }, [onCancel]);
98
+
99
+ // Calculate progress percentage
100
+ const progressPercentage = useMemo(() => {
101
+ return ((currentStepIndex + 1) / steps.length) * 100;
102
+ }, [currentStepIndex, steps.length]);
103
+
104
+ const getSizeClasses = () => {
105
+ switch (size) {
106
+ case 'sm':
107
+ return 'stepper--size-sm';
108
+ case 'lg':
109
+ return 'stepper--size-lg';
110
+ default: // md
111
+ return 'stepper--size-md';
112
+ }
113
+ };
114
+
115
+ const getVariantClasses = () => {
116
+ switch (variant) {
117
+ case 'vertical':
118
+ return 'stepper--vertical';
119
+ case 'horizontal':
120
+ return 'stepper--horizontal';
121
+ case 'wizard':
122
+ return 'stepper--wizard';
123
+ default:
124
+ return 'stepper--default';
125
+ }
126
+ };
127
+
128
+ const getThemeClasses = () => {
129
+ return 'stepper stepper--stan-design';
130
+ };
131
+
132
+ const getStepStatusIcon = (step: StepItem, stepIndex: number) => {
133
+ if (!showStepIcons) return null;
134
+
135
+ if (step.status === 'completed') {
136
+ return <CheckIcon className="stepper__step-icon stepper__step-icon--completed" />;
137
+ }
138
+
139
+ if (step.status === 'error') {
140
+ return <ExclamationCircleIcon className="stepper__step-icon stepper__step-icon--error" />;
141
+ }
142
+
143
+ if (step.status === 'warning') {
144
+ return <ExclamationTriangleIcon className="stepper__step-icon stepper__step-icon--warning" />;
145
+ }
146
+
147
+ if (stepIndex === currentStepIndex) {
148
+ return <div className="stepper__step-number stepper__step-number--current">
149
+ {stepIndex + 1}
150
+ </div>;
151
+ }
152
+
153
+ if (showStepNumbers) {
154
+ return <div className="stepper__step-number stepper__step-number--default">
155
+ {stepIndex + 1}
156
+ </div>;
157
+ }
158
+
159
+ return null;
160
+ };
161
+
162
+ const getStepClasses = (step: StepItem, stepIndex: number) => {
163
+ let classes = 'stepper__step';
164
+
165
+ if (step.disabled) {
166
+ classes += ' stepper__step--disabled';
167
+ } else if (stepIndex === currentStepIndex) {
168
+ classes += ' stepper__step--current';
169
+ } else if (step.status === 'completed') {
170
+ classes += ' stepper__step--completed';
171
+ } else if (step.status === 'error') {
172
+ classes += ' stepper__step--error';
173
+ } else if (step.status === 'warning') {
174
+ classes += ' stepper__step--warning';
175
+ } else {
176
+ classes += ' stepper__step--default';
177
+ }
178
+
179
+ return classes;
180
+ };
181
+
182
+ if (steps.length === 0) {
183
+ return null;
184
+ }
185
+
186
+ return (
187
+ <div className={`stepper__container ${getThemeClasses()} ${getSizeClasses()} ${className}`} data-testid={testId}>
188
+ {/* Progress Bar */}
189
+ {showProgress && (
190
+ <div className="stepper__progress">
191
+ <div className="stepper__progress-header">
192
+ <span className="stepper__progress-label">Progress</span>
193
+ <span className="stepper__progress-percentage">{Math.round(progressPercentage)}%</span>
194
+ </div>
195
+ <div className="stepper__progress-track">
196
+ <div
197
+ className="stepper__progress-bar"
198
+ style={{
199
+ width: `${progressPercentage}%`
200
+ }}
201
+ />
202
+ </div>
203
+ </div>
204
+ )}
205
+
206
+ {/* Steps */}
207
+ <div className={`stepper__steps ${getVariantClasses()}`}>
208
+ {steps.map((step, stepIndex) => (
209
+ <div
210
+ key={step.id}
211
+ className={`stepper__step-container ${variant === 'vertical' ? 'stepper__step-container--vertical' : ''}`}
212
+ >
213
+ <div
214
+ className={`${getStepClasses(step, stepIndex)} ${variant === 'vertical' ? 'stepper__step--vertical' : ''}`}
215
+ onClick={() => handleStepClick(stepIndex)}
216
+ >
217
+ {getStepStatusIcon(step, stepIndex)}
218
+
219
+ <div className={`stepper__step-content ${variant === 'vertical' ? 'stepper__step-content--vertical' : ''}`}>
220
+ {showStepLabels && (
221
+ <div className="stepper__step-label">
222
+ {step.label}
223
+ {step.required && <span className="stepper__step-required">*</span>}
224
+ </div>
225
+ )}
226
+
227
+ {showStepDescriptions && step.description && (
228
+ <div className="stepper__step-description">
229
+ {step.description}
230
+ </div>
231
+ )}
232
+ </div>
233
+ </div>
234
+
235
+ {/* Step Content for Wizard variant */}
236
+ {variant === 'wizard' && stepIndex === currentStepIndex && step.content && (
237
+ <div className="stepper__step-panel">
238
+ {step.content}
239
+
240
+ {/* Step Actions */}
241
+ {step.actions && (
242
+ <div className="stepper__step-actions">
243
+ {step.actions.map((action, actionIndex) => (
244
+ <button
245
+ key={actionIndex}
246
+ onClick={action.onClick}
247
+ disabled={action.disabled}
248
+ className={`stepper__action stepper__action--${
249
+ action.variant || 'default'
250
+ }`}
251
+ >
252
+ {action.loading ? 'Loading...' : action.label}
253
+ </button>
254
+ ))}
255
+ </div>
256
+ )}
257
+ </div>
258
+ )}
259
+ </div>
260
+ ))}
261
+ </div>
262
+
263
+ {/* Global Actions */}
264
+ <div className="stepper__navigation">
265
+ <button
266
+ onClick={handleCancel}
267
+ className="stepper__nav-button stepper__nav-button--cancel"
268
+ >
269
+ Cancel
270
+ </button>
271
+
272
+ <div className="stepper__nav-actions">
273
+ {currentStepIndex > 0 && (
274
+ <button
275
+ onClick={() => handleStepClick(currentStepIndex - 1)}
276
+ className="stepper__nav-button stepper__nav-button--previous"
277
+ >
278
+ Previous
279
+ </button>
280
+ )}
281
+
282
+ {currentStepIndex < steps.length - 1 && !steps.slice(currentStepIndex + 1).every(step => step.disabled) ? (
283
+ <button
284
+ onClick={() => handleStepComplete(currentStepIndex)}
285
+ className="stepper__nav-button stepper__nav-button--next"
286
+ >
287
+ Next
288
+ </button>
289
+ ) : (
290
+ <button
291
+ onClick={handleFinish}
292
+ className="stepper__nav-button stepper__nav-button--finish"
293
+ >
294
+ Finish
295
+ </button>
296
+ )}
297
+ </div>
298
+ </div>
299
+ </div>
300
+ );
301
+ };
302
+
303
+ export default Stepper;