@rakeyshgidwani/roger-ui-bank-theme-harvey 0.2.52 → 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.
- package/CHANGELOG.md +1 -1
- package/dist/components/ui/button.d.ts +3 -1
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/button.esm.js +3 -2
- package/dist/components/ui/button.js +3 -2
- package/dist/components/ui/layout/container.d.ts +57 -0
- package/dist/components/ui/layout/container.d.ts.map +1 -0
- package/dist/components/ui/layout/container.esm.js +173 -0
- package/dist/components/ui/layout/container.js +173 -0
- package/dist/components/ui/layout/index.d.ts +9 -0
- package/dist/components/ui/layout/index.d.ts.map +1 -0
- package/dist/components/ui/layout/index.esm.js +6 -0
- package/dist/components/ui/layout/index.js +6 -0
- package/dist/components/ui/layout/responsive-grid.d.ts +93 -0
- package/dist/components/ui/layout/responsive-grid.d.ts.map +1 -0
- package/dist/components/ui/layout/responsive-grid.esm.js +124 -0
- package/dist/components/ui/layout/responsive-grid.js +124 -0
- package/dist/components/ui/navigation/index.d.ts +2 -1
- package/dist/components/ui/navigation/index.d.ts.map +1 -1
- package/dist/components/ui/navigation/index.esm.js +1 -0
- package/dist/components/ui/navigation/index.js +1 -0
- package/dist/components/ui/navigation/progressive-navigation.d.ts +37 -0
- package/dist/components/ui/navigation/progressive-navigation.d.ts.map +1 -0
- package/dist/components/ui/navigation/progressive-navigation.esm.js +145 -0
- package/dist/components/ui/navigation/progressive-navigation.js +145 -0
- package/dist/components/ui/navigation/types.d.ts +21 -0
- package/dist/components/ui/navigation/types.d.ts.map +1 -1
- package/dist/hooks/use-adaptive-layout.d.ts +2 -1
- package/dist/hooks/use-adaptive-layout.d.ts.map +1 -1
- package/dist/hooks/use-adaptive-layout.esm.js +13 -8
- package/dist/hooks/use-adaptive-layout.js +13 -8
- package/dist/hooks/use-device.d.ts +3 -1
- package/dist/hooks/use-device.d.ts.map +1 -1
- package/dist/hooks/use-device.esm.js +14 -7
- package/dist/hooks/use-device.js +14 -7
- package/dist/index.d.ts +19 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +9 -4
- package/dist/index.js +9 -4
- package/dist/plugins/css-purge-optimizer.d.ts +25 -0
- package/dist/plugins/css-purge-optimizer.d.ts.map +1 -0
- package/dist/plugins/css-purge-optimizer.esm.js +414 -0
- package/dist/plugins/css-purge-optimizer.js +414 -0
- package/dist/plugins/performance-monitor.d.ts +29 -0
- package/dist/plugins/performance-monitor.d.ts.map +1 -0
- package/dist/plugins/performance-monitor.esm.js +221 -0
- package/dist/plugins/performance-monitor.js +221 -0
- package/dist/plugins/progressive-css-loader.d.ts +21 -0
- package/dist/plugins/progressive-css-loader.d.ts.map +1 -0
- package/dist/plugins/progressive-css-loader.esm.js +227 -0
- package/dist/plugins/progressive-css-loader.js +227 -0
- package/dist/plugins/theme-css-generator.d.ts.map +1 -1
- package/dist/plugins/theme-css-generator.esm.js +19 -6
- package/dist/plugins/theme-css-generator.js +19 -6
- package/dist/styles.css +1025 -110
- package/dist/theme.d.ts.map +1 -1
- package/dist/theme.esm.js +4 -1
- package/dist/theme.js +4 -1
- package/dist/themes/phase1-constants.d.ts +23 -0
- package/dist/themes/phase1-constants.d.ts.map +1 -0
- package/dist/themes/phase1-constants.esm.js +180 -0
- package/dist/themes/phase1-constants.js +180 -0
- package/dist/themes/themes/default.d.ts.map +1 -1
- package/dist/themes/themes/default.esm.js +4 -1
- package/dist/themes/themes/default.js +4 -1
- package/dist/themes/themes/harvey.d.ts.map +1 -1
- package/dist/themes/themes/harvey.esm.js +4 -1
- package/dist/themes/themes/harvey.js +4 -1
- package/dist/themes/types.d.ts +62 -0
- package/dist/themes/types.d.ts.map +1 -1
- package/dist/themes/validation.d.ts +17 -0
- package/dist/themes/validation.d.ts.map +1 -1
- package/dist/themes/validation.esm.js +218 -0
- package/dist/themes/validation.js +218 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/progressive-css-injector.d.ts +80 -0
- package/dist/utils/progressive-css-injector.d.ts.map +1 -0
- package/dist/utils/progressive-css-injector.esm.js +217 -0
- package/dist/utils/progressive-css-injector.js +217 -0
- package/package.json +1 -1
- package/src/components/ui/button.tsx +9 -6
- package/src/components/ui/layout/container.tsx +312 -0
- package/src/components/ui/layout/index.ts +10 -0
- package/src/components/ui/layout/responsive-grid.tsx +286 -0
- package/src/components/ui/navigation/index.ts +2 -0
- package/src/components/ui/navigation/progressive-navigation.tsx +453 -0
- package/src/components/ui/navigation/types.ts +41 -0
- package/src/hooks/use-adaptive-layout.ts +13 -9
- package/src/hooks/use-device.tsx +17 -10
- package/src/index.ts +19 -4
- package/src/plugins/css-purge-optimizer.ts +491 -0
- package/src/plugins/performance-monitor.ts +292 -0
- package/src/plugins/progressive-css-loader.ts +269 -0
- package/src/plugins/theme-css-generator.ts +22 -6
- package/src/styles/components/base/badge.css +2 -2
- package/src/styles/components/base/button.css +238 -35
- package/src/styles/components/base/card.css +2 -2
- package/src/styles/components/base/checkbox.css +3 -3
- package/src/styles/components/base/label.css +3 -3
- package/src/styles/components/feedback/skeleton.css +1 -1
- package/src/styles/components/feedback/toast.css +1 -1
- package/src/styles/components/index.css +3 -0
- package/src/styles/components/layout/container.css +466 -0
- package/src/styles/components/layout/index.css +5 -0
- package/src/styles/components/layout/responsive-grid.css +422 -0
- package/src/styles/components/navigation/breadcrumb.css +1 -1
- package/src/styles/components/navigation/index.css +1 -0
- package/src/styles/components/navigation/menu.css +2 -2
- package/src/styles/components/navigation/pagination.css +4 -4
- package/src/styles/components/navigation/progressive-navigation.css +633 -0
- package/src/styles/components/navigation/sidebar.css +4 -4
- package/src/styles/components/navigation/stepper.css +2 -2
- package/src/styles/components/navigation/tabs.css +1 -1
- package/src/styles/progressive.css +17 -0
- package/src/styles/themes/harvey.css +103 -19
- package/src/styles/utilities/semantic-input-system.css +7 -13
- package/src/theme.ts +5 -1
- package/src/themes/phase1-constants.ts +189 -0
- package/src/themes/themes/default.ts +5 -1
- package/src/themes/themes/harvey.ts +5 -1
- package/src/themes/types.ts +77 -1
- package/src/themes/validation.ts +249 -0
- package/src/types.ts +77 -1
- package/src/utils/progressive-css-injector.ts +254 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progressive Navigation Component
|
|
3
|
+
* Implements Phase 2 progressive navigation patterns as defined in the implementation plan
|
|
4
|
+
*
|
|
5
|
+
* Navigation Patterns:
|
|
6
|
+
* - mobile (xs-sm): hamburger menu pattern with 56px height
|
|
7
|
+
* - tablet (md): horizontal-compact pattern with 64px height
|
|
8
|
+
* - desktop (lg+): horizontal-full pattern with 72px height
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
'use client'
|
|
12
|
+
|
|
13
|
+
import * as React from 'react';
|
|
14
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
15
|
+
import { NavigationItem, NavigationGroup } from './types.js';
|
|
16
|
+
|
|
17
|
+
// Simple icon components for navigation
|
|
18
|
+
const HamburgerIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
19
|
+
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
20
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
|
|
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
|
+
const ChevronDownIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
31
|
+
<svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
32
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
33
|
+
</svg>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export interface ProgressiveNavigationProps {
|
|
37
|
+
// Navigation content
|
|
38
|
+
items?: NavigationItem[];
|
|
39
|
+
groups?: NavigationGroup[];
|
|
40
|
+
|
|
41
|
+
// Logo configuration
|
|
42
|
+
logo?: React.ReactNode;
|
|
43
|
+
logoHref?: string;
|
|
44
|
+
|
|
45
|
+
// Mobile hamburger configuration
|
|
46
|
+
hamburgerHeight?: string; // Default: 56px
|
|
47
|
+
mobileLogoSize?: 'sm' | 'md' | 'lg'; // Default: sm
|
|
48
|
+
mobileTouchTarget?: string; // Default: 48px
|
|
49
|
+
|
|
50
|
+
// Tablet horizontal-compact configuration
|
|
51
|
+
tabletHeight?: string; // Default: 64px
|
|
52
|
+
tabletLogoSize?: 'sm' | 'md' | 'lg'; // Default: md
|
|
53
|
+
|
|
54
|
+
// Desktop horizontal-full configuration
|
|
55
|
+
desktopHeight?: string; // Default: 72px
|
|
56
|
+
desktopLogoSize?: 'sm' | 'md' | 'lg'; // Default: lg
|
|
57
|
+
showSecondaryActions?: boolean; // Default: true
|
|
58
|
+
secondaryActions?: React.ReactNode[];
|
|
59
|
+
|
|
60
|
+
// Responsive behavior
|
|
61
|
+
mobileBreakpoint?: string; // Default: 640px (sm) - Controls mobile/tablet cutoff
|
|
62
|
+
tabletBreakpoint?: string; // Default: 768px (md) - Reserved for future tablet sub-patterns
|
|
63
|
+
desktopBreakpoint?: string; // Default: 1024px (lg) - Controls tablet/desktop cutoff
|
|
64
|
+
|
|
65
|
+
// Event handlers
|
|
66
|
+
onItemClick?: (item: NavigationItem) => void;
|
|
67
|
+
onLogoClick?: () => void;
|
|
68
|
+
|
|
69
|
+
// Accessibility
|
|
70
|
+
'aria-label'?: string;
|
|
71
|
+
'data-testid'?: string;
|
|
72
|
+
|
|
73
|
+
// Styling
|
|
74
|
+
className?: string;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export const ProgressiveNavigation: React.FC<ProgressiveNavigationProps> = ({
|
|
78
|
+
items = [],
|
|
79
|
+
groups = [],
|
|
80
|
+
logo,
|
|
81
|
+
logoHref,
|
|
82
|
+
hamburgerHeight = '56px',
|
|
83
|
+
mobileLogoSize = 'sm',
|
|
84
|
+
mobileTouchTarget = '48px',
|
|
85
|
+
tabletHeight = '64px',
|
|
86
|
+
tabletLogoSize = 'md',
|
|
87
|
+
desktopHeight = '72px',
|
|
88
|
+
desktopLogoSize = 'lg',
|
|
89
|
+
showSecondaryActions = true,
|
|
90
|
+
secondaryActions = [],
|
|
91
|
+
mobileBreakpoint = '640px',
|
|
92
|
+
tabletBreakpoint = '768px', // Currently unused - reserved for future tablet sub-patterns
|
|
93
|
+
desktopBreakpoint = '1024px',
|
|
94
|
+
onItemClick,
|
|
95
|
+
onLogoClick,
|
|
96
|
+
'aria-label': ariaLabel = 'Main navigation',
|
|
97
|
+
'data-testid': testId = 'progressive-navigation',
|
|
98
|
+
className = '',
|
|
99
|
+
}) => {
|
|
100
|
+
// Suppress unused variable warning for tabletBreakpoint (reserved for future use)
|
|
101
|
+
void tabletBreakpoint;
|
|
102
|
+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
103
|
+
const [activeDropdown, setActiveDropdown] = useState<string | null>(null);
|
|
104
|
+
const [screenSize, setScreenSize] = useState<'mobile' | 'tablet' | 'desktop'>('desktop');
|
|
105
|
+
const navRef = useRef<HTMLElement>(null);
|
|
106
|
+
|
|
107
|
+
// Determine current screen size based on configurable breakpoints
|
|
108
|
+
// Default: mobile < 640px, tablet 640-1023px, desktop >= 1024px
|
|
109
|
+
// This ensures sm range (640-767px) uses tablet-compact pattern, not hamburger
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
const handleResize = () => {
|
|
112
|
+
const width = window.innerWidth;
|
|
113
|
+
if (width < parseInt(mobileBreakpoint)) {
|
|
114
|
+
setScreenSize('mobile'); // < 640px: hamburger menu
|
|
115
|
+
} else if (width < parseInt(desktopBreakpoint)) {
|
|
116
|
+
setScreenSize('tablet'); // 640-1023px: horizontal-compact
|
|
117
|
+
} else {
|
|
118
|
+
setScreenSize('desktop'); // >= 1024px: horizontal-full
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
handleResize();
|
|
123
|
+
window.addEventListener('resize', handleResize);
|
|
124
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
125
|
+
}, [mobileBreakpoint, desktopBreakpoint]);
|
|
126
|
+
|
|
127
|
+
// Close mobile menu on escape key
|
|
128
|
+
useEffect(() => {
|
|
129
|
+
const handleEscape = (event: KeyboardEvent) => {
|
|
130
|
+
if (event.key === 'Escape' && isMobileMenuOpen) {
|
|
131
|
+
setIsMobileMenuOpen(false);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (isMobileMenuOpen) {
|
|
136
|
+
document.addEventListener('keydown', handleEscape);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return () => {
|
|
140
|
+
document.removeEventListener('keydown', handleEscape);
|
|
141
|
+
};
|
|
142
|
+
}, [isMobileMenuOpen]);
|
|
143
|
+
|
|
144
|
+
const handleItemClick = useCallback((item: NavigationItem) => {
|
|
145
|
+
if (item.disabled) return;
|
|
146
|
+
|
|
147
|
+
if (onItemClick) {
|
|
148
|
+
onItemClick(item);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (item.onClick) {
|
|
152
|
+
item.onClick();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Close mobile menu after item click
|
|
156
|
+
if (screenSize === 'mobile') {
|
|
157
|
+
setIsMobileMenuOpen(false);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Close dropdown if open
|
|
161
|
+
setActiveDropdown(null);
|
|
162
|
+
}, [onItemClick, screenSize]);
|
|
163
|
+
|
|
164
|
+
const handleDropdownToggle = useCallback((groupId: string) => {
|
|
165
|
+
setActiveDropdown(activeDropdown === groupId ? null : groupId);
|
|
166
|
+
}, [activeDropdown]);
|
|
167
|
+
|
|
168
|
+
const handleLogoClick = useCallback(() => {
|
|
169
|
+
if (onLogoClick) {
|
|
170
|
+
onLogoClick();
|
|
171
|
+
}
|
|
172
|
+
}, [onLogoClick]);
|
|
173
|
+
|
|
174
|
+
const getNavigationHeight = () => {
|
|
175
|
+
switch (screenSize) {
|
|
176
|
+
case 'mobile':
|
|
177
|
+
return hamburgerHeight;
|
|
178
|
+
case 'tablet':
|
|
179
|
+
return tabletHeight;
|
|
180
|
+
case 'desktop':
|
|
181
|
+
return desktopHeight;
|
|
182
|
+
default:
|
|
183
|
+
return desktopHeight;
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const getLogoSize = () => {
|
|
188
|
+
switch (screenSize) {
|
|
189
|
+
case 'mobile':
|
|
190
|
+
return mobileLogoSize;
|
|
191
|
+
case 'tablet':
|
|
192
|
+
return tabletLogoSize;
|
|
193
|
+
case 'desktop':
|
|
194
|
+
return desktopLogoSize;
|
|
195
|
+
default:
|
|
196
|
+
return desktopLogoSize;
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const renderLogo = () => {
|
|
201
|
+
const logoContent = (
|
|
202
|
+
<div className={`progressive-nav__logo progressive-nav__logo--${getLogoSize()}`}>
|
|
203
|
+
{logo}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (logoHref) {
|
|
208
|
+
return (
|
|
209
|
+
<a
|
|
210
|
+
href={logoHref}
|
|
211
|
+
className="progressive-nav__logo-link"
|
|
212
|
+
onClick={handleLogoClick}
|
|
213
|
+
>
|
|
214
|
+
{logoContent}
|
|
215
|
+
</a>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return logoContent;
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const renderNavigationItems = () => {
|
|
223
|
+
const allItems = groups.length > 0
|
|
224
|
+
? groups.flatMap(group => group.items)
|
|
225
|
+
: items;
|
|
226
|
+
|
|
227
|
+
return allItems.map((item) => (
|
|
228
|
+
<button
|
|
229
|
+
key={item.id}
|
|
230
|
+
onClick={() => handleItemClick(item)}
|
|
231
|
+
disabled={item.disabled}
|
|
232
|
+
className={`progressive-nav__item ${
|
|
233
|
+
item.active ? 'progressive-nav__item--active' : ''
|
|
234
|
+
} ${item.disabled ? 'progressive-nav__item--disabled' : ''}`}
|
|
235
|
+
>
|
|
236
|
+
{item.icon && (
|
|
237
|
+
<span className="progressive-nav__item-icon">
|
|
238
|
+
{item.icon}
|
|
239
|
+
</span>
|
|
240
|
+
)}
|
|
241
|
+
<span className="progressive-nav__item-label">{item.label}</span>
|
|
242
|
+
{item.badge && (
|
|
243
|
+
<span className="progressive-nav__item-badge">{item.badge}</span>
|
|
244
|
+
)}
|
|
245
|
+
</button>
|
|
246
|
+
));
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const renderDropdownItems = (groupItems: NavigationItem[]) => {
|
|
250
|
+
return groupItems.map((item) => (
|
|
251
|
+
<button
|
|
252
|
+
key={item.id}
|
|
253
|
+
onClick={() => handleItemClick(item)}
|
|
254
|
+
disabled={item.disabled}
|
|
255
|
+
className={`progressive-nav__dropdown-item ${
|
|
256
|
+
item.active ? 'progressive-nav__dropdown-item--active' : ''
|
|
257
|
+
} ${item.disabled ? 'progressive-nav__dropdown-item--disabled' : ''}`}
|
|
258
|
+
>
|
|
259
|
+
{item.icon && (
|
|
260
|
+
<span className="progressive-nav__dropdown-item-icon">
|
|
261
|
+
{item.icon}
|
|
262
|
+
</span>
|
|
263
|
+
)}
|
|
264
|
+
<span className="progressive-nav__dropdown-item-label">{item.label}</span>
|
|
265
|
+
{item.badge && (
|
|
266
|
+
<span className="progressive-nav__dropdown-item-badge">{item.badge}</span>
|
|
267
|
+
)}
|
|
268
|
+
</button>
|
|
269
|
+
));
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const renderNavigationGroups = () => {
|
|
273
|
+
return groups.map((group) => (
|
|
274
|
+
<div key={group.id} className="progressive-nav__group">
|
|
275
|
+
<button
|
|
276
|
+
onClick={() => handleDropdownToggle(group.id)}
|
|
277
|
+
className={`progressive-nav__group-trigger ${
|
|
278
|
+
activeDropdown === group.id ? 'progressive-nav__group-trigger--active' : ''
|
|
279
|
+
}`}
|
|
280
|
+
>
|
|
281
|
+
<span className="progressive-nav__group-title">{group.title}</span>
|
|
282
|
+
<ChevronDownIcon
|
|
283
|
+
className={`progressive-nav__group-arrow ${
|
|
284
|
+
activeDropdown === group.id ? 'progressive-nav__group-arrow--expanded' : ''
|
|
285
|
+
}`}
|
|
286
|
+
/>
|
|
287
|
+
</button>
|
|
288
|
+
|
|
289
|
+
{activeDropdown === group.id && (
|
|
290
|
+
<div className="progressive-nav__dropdown">
|
|
291
|
+
{renderDropdownItems(group.items)}
|
|
292
|
+
</div>
|
|
293
|
+
)}
|
|
294
|
+
</div>
|
|
295
|
+
));
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const renderSecondaryActions = () => {
|
|
299
|
+
if (!showSecondaryActions || !secondaryActions.length) return null;
|
|
300
|
+
|
|
301
|
+
return (
|
|
302
|
+
<div className="progressive-nav__secondary-actions">
|
|
303
|
+
{secondaryActions.map((action, index) => (
|
|
304
|
+
<div key={index} className="progressive-nav__secondary-action">
|
|
305
|
+
{action}
|
|
306
|
+
</div>
|
|
307
|
+
))}
|
|
308
|
+
</div>
|
|
309
|
+
);
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// Mobile hamburger menu pattern
|
|
313
|
+
if (screenSize === 'mobile') {
|
|
314
|
+
return (
|
|
315
|
+
<nav
|
|
316
|
+
ref={navRef}
|
|
317
|
+
className={`progressive-nav progressive-nav--mobile ${className}`}
|
|
318
|
+
style={{ height: getNavigationHeight() }}
|
|
319
|
+
aria-label={ariaLabel}
|
|
320
|
+
data-testid={testId}
|
|
321
|
+
>
|
|
322
|
+
<div className="progressive-nav__mobile-container">
|
|
323
|
+
{/* Mobile header */}
|
|
324
|
+
<div className="progressive-nav__mobile-header">
|
|
325
|
+
{renderLogo()}
|
|
326
|
+
|
|
327
|
+
<button
|
|
328
|
+
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
|
|
329
|
+
className="progressive-nav__mobile-toggle"
|
|
330
|
+
style={{
|
|
331
|
+
minHeight: mobileTouchTarget,
|
|
332
|
+
minWidth: mobileTouchTarget
|
|
333
|
+
}}
|
|
334
|
+
aria-label={isMobileMenuOpen ? 'Close menu' : 'Open menu'}
|
|
335
|
+
aria-expanded={isMobileMenuOpen}
|
|
336
|
+
>
|
|
337
|
+
{isMobileMenuOpen ? (
|
|
338
|
+
<XMarkIcon className="progressive-nav__mobile-toggle-icon" />
|
|
339
|
+
) : (
|
|
340
|
+
<HamburgerIcon className="progressive-nav__mobile-toggle-icon" />
|
|
341
|
+
)}
|
|
342
|
+
</button>
|
|
343
|
+
</div>
|
|
344
|
+
|
|
345
|
+
{/* Mobile menu overlay */}
|
|
346
|
+
{isMobileMenuOpen && (
|
|
347
|
+
<>
|
|
348
|
+
<div
|
|
349
|
+
className="progressive-nav__mobile-overlay"
|
|
350
|
+
onClick={() => setIsMobileMenuOpen(false)}
|
|
351
|
+
/>
|
|
352
|
+
<div className="progressive-nav__mobile-menu">
|
|
353
|
+
<div className="progressive-nav__mobile-content">
|
|
354
|
+
{groups.length > 0 ? (
|
|
355
|
+
<div className="progressive-nav__mobile-groups">
|
|
356
|
+
{groups.map((group) => (
|
|
357
|
+
<div key={group.id} className="progressive-nav__mobile-group">
|
|
358
|
+
{group.title && (
|
|
359
|
+
<div className="progressive-nav__mobile-group-title">
|
|
360
|
+
{group.title}
|
|
361
|
+
</div>
|
|
362
|
+
)}
|
|
363
|
+
<div className="progressive-nav__mobile-group-items">
|
|
364
|
+
{group.items.map((item) => (
|
|
365
|
+
<button
|
|
366
|
+
key={item.id}
|
|
367
|
+
onClick={() => handleItemClick(item)}
|
|
368
|
+
disabled={item.disabled}
|
|
369
|
+
className={`progressive-nav__mobile-item ${
|
|
370
|
+
item.active ? 'progressive-nav__mobile-item--active' : ''
|
|
371
|
+
} ${item.disabled ? 'progressive-nav__mobile-item--disabled' : ''}`}
|
|
372
|
+
>
|
|
373
|
+
{item.icon && (
|
|
374
|
+
<span className="progressive-nav__mobile-item-icon">
|
|
375
|
+
{item.icon}
|
|
376
|
+
</span>
|
|
377
|
+
)}
|
|
378
|
+
<span className="progressive-nav__mobile-item-label">{item.label}</span>
|
|
379
|
+
{item.badge && (
|
|
380
|
+
<span className="progressive-nav__mobile-item-badge">{item.badge}</span>
|
|
381
|
+
)}
|
|
382
|
+
</button>
|
|
383
|
+
))}
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
))}
|
|
387
|
+
</div>
|
|
388
|
+
) : (
|
|
389
|
+
<div className="progressive-nav__mobile-items">
|
|
390
|
+
{renderNavigationItems()}
|
|
391
|
+
</div>
|
|
392
|
+
)}
|
|
393
|
+
</div>
|
|
394
|
+
</div>
|
|
395
|
+
</>
|
|
396
|
+
)}
|
|
397
|
+
</div>
|
|
398
|
+
</nav>
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Tablet horizontal-compact pattern
|
|
403
|
+
if (screenSize === 'tablet') {
|
|
404
|
+
return (
|
|
405
|
+
<nav
|
|
406
|
+
ref={navRef}
|
|
407
|
+
className={`progressive-nav progressive-nav--tablet ${className}`}
|
|
408
|
+
style={{ height: getNavigationHeight() }}
|
|
409
|
+
aria-label={ariaLabel}
|
|
410
|
+
data-testid={testId}
|
|
411
|
+
>
|
|
412
|
+
<div className="progressive-nav__tablet-container">
|
|
413
|
+
{renderLogo()}
|
|
414
|
+
|
|
415
|
+
<div className="progressive-nav__tablet-content">
|
|
416
|
+
{groups.length > 0 ? renderNavigationGroups() : (
|
|
417
|
+
<div className="progressive-nav__tablet-items">
|
|
418
|
+
{renderNavigationItems()}
|
|
419
|
+
</div>
|
|
420
|
+
)}
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
</nav>
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Desktop horizontal-full pattern
|
|
428
|
+
return (
|
|
429
|
+
<nav
|
|
430
|
+
ref={navRef}
|
|
431
|
+
className={`progressive-nav progressive-nav--desktop ${className}`}
|
|
432
|
+
style={{ height: getNavigationHeight() }}
|
|
433
|
+
aria-label={ariaLabel}
|
|
434
|
+
data-testid={testId}
|
|
435
|
+
>
|
|
436
|
+
<div className="progressive-nav__desktop-container">
|
|
437
|
+
{renderLogo()}
|
|
438
|
+
|
|
439
|
+
<div className="progressive-nav__desktop-content">
|
|
440
|
+
{groups.length > 0 ? renderNavigationGroups() : (
|
|
441
|
+
<div className="progressive-nav__desktop-items">
|
|
442
|
+
{renderNavigationItems()}
|
|
443
|
+
</div>
|
|
444
|
+
)}
|
|
445
|
+
</div>
|
|
446
|
+
|
|
447
|
+
{renderSecondaryActions()}
|
|
448
|
+
</div>
|
|
449
|
+
</nav>
|
|
450
|
+
);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
export default ProgressiveNavigation;
|
|
@@ -215,6 +215,47 @@ export interface MenuProps extends NavigationBaseProps {
|
|
|
215
215
|
onSelectionChange?: (selectedItems: string[]) => void;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// PROGRESSIVE NAVIGATION COMPONENT TYPES (Phase 2 Enhancement)
|
|
220
|
+
// ============================================================================
|
|
221
|
+
|
|
222
|
+
export interface ProgressiveNavigationProps extends NavigationBaseProps {
|
|
223
|
+
// Navigation content
|
|
224
|
+
items?: NavigationItem[];
|
|
225
|
+
groups?: NavigationGroup[];
|
|
226
|
+
|
|
227
|
+
// Logo configuration
|
|
228
|
+
logo?: React.ReactNode;
|
|
229
|
+
logoHref?: string;
|
|
230
|
+
|
|
231
|
+
// Mobile hamburger configuration
|
|
232
|
+
hamburgerHeight?: string; // Default: 56px
|
|
233
|
+
mobileLogoSize?: 'sm' | 'md' | 'lg'; // Default: sm
|
|
234
|
+
mobileTouchTarget?: string; // Default: 48px
|
|
235
|
+
|
|
236
|
+
// Tablet horizontal-compact configuration
|
|
237
|
+
tabletHeight?: string; // Default: 64px
|
|
238
|
+
tabletLogoSize?: 'sm' | 'md' | 'lg'; // Default: md
|
|
239
|
+
|
|
240
|
+
// Desktop horizontal-full configuration
|
|
241
|
+
desktopHeight?: string; // Default: 72px
|
|
242
|
+
desktopLogoSize?: 'sm' | 'md' | 'lg'; // Default: lg
|
|
243
|
+
showSecondaryActions?: boolean; // Default: true
|
|
244
|
+
secondaryActions?: React.ReactNode[];
|
|
245
|
+
|
|
246
|
+
// Responsive behavior
|
|
247
|
+
mobileBreakpoint?: string; // Default: 640px (sm)
|
|
248
|
+
tabletBreakpoint?: string; // Default: 768px (md)
|
|
249
|
+
desktopBreakpoint?: string; // Default: 1024px (lg)
|
|
250
|
+
|
|
251
|
+
// Event handlers
|
|
252
|
+
onItemClick?: (item: NavigationItem) => void;
|
|
253
|
+
onLogoClick?: () => void;
|
|
254
|
+
|
|
255
|
+
// Accessibility
|
|
256
|
+
'aria-label'?: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
218
259
|
// ============================================================================
|
|
219
260
|
// SIDEBAR COMPONENT TYPES
|
|
220
261
|
// ============================================================================
|
|
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, useMemo } from 'react'
|
|
|
2
2
|
|
|
3
3
|
export type DeviceType = 'mobile' | 'tablet' | 'desktop'
|
|
4
4
|
export type Orientation = 'portrait' | 'landscape'
|
|
5
|
-
export type ScreenSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
5
|
+
export type ScreenSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl'
|
|
6
6
|
|
|
7
7
|
export interface AdaptiveLayoutState {
|
|
8
8
|
deviceType: DeviceType
|
|
@@ -28,6 +28,7 @@ export interface AdaptiveLayoutConfig {
|
|
|
28
28
|
lg: number
|
|
29
29
|
xl: number
|
|
30
30
|
'2xl': number
|
|
31
|
+
'3xl': number
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -49,12 +50,13 @@ export const useAdaptiveLayout = (
|
|
|
49
50
|
enableBatteryOptimization = true,
|
|
50
51
|
enableMemoryOptimization = true,
|
|
51
52
|
breakpoints = {
|
|
52
|
-
xs:
|
|
53
|
-
sm: 640,
|
|
54
|
-
md: 768,
|
|
55
|
-
lg: 1024,
|
|
56
|
-
xl: 1280,
|
|
57
|
-
'2xl': 1536
|
|
53
|
+
xs: 475, // Phase 1: Large phones (iPhone 14 Plus, etc.)
|
|
54
|
+
sm: 640, // Small tablets, large phones landscape
|
|
55
|
+
md: 768, // Tablets (iPad Mini, etc.)
|
|
56
|
+
lg: 1024, // Desktop, large tablets
|
|
57
|
+
xl: 1280, // Large desktop (MacBook Pro 13")
|
|
58
|
+
'2xl': 1536, // Ultra-wide (MacBook Pro 16", external monitors)
|
|
59
|
+
'3xl': 1920 // Large external monitors, TV displays
|
|
58
60
|
}
|
|
59
61
|
} = config
|
|
60
62
|
|
|
@@ -79,12 +81,14 @@ export const useAdaptiveLayout = (
|
|
|
79
81
|
|
|
80
82
|
// Screen size detection
|
|
81
83
|
const detectScreenSize = useCallback((width: number): ScreenSize => {
|
|
82
|
-
if (width < breakpoints.
|
|
84
|
+
if (width < breakpoints.xs) return 'xs' // Small phones (< 475px)
|
|
85
|
+
if (width < breakpoints.sm) return 'xs' // Still xs for 475-640px range
|
|
83
86
|
if (width < breakpoints.md) return 'sm'
|
|
84
87
|
if (width < breakpoints.lg) return 'md'
|
|
85
88
|
if (width < breakpoints.xl) return 'lg'
|
|
86
89
|
if (width < breakpoints['2xl']) return 'xl'
|
|
87
|
-
return '2xl'
|
|
90
|
+
if (width < breakpoints['3xl']) return '2xl'
|
|
91
|
+
return '3xl'
|
|
88
92
|
}, [breakpoints])
|
|
89
93
|
|
|
90
94
|
// Touch device detection
|
package/src/hooks/use-device.tsx
CHANGED
|
@@ -6,16 +6,18 @@ export interface DeviceInfo {
|
|
|
6
6
|
isMobile: boolean
|
|
7
7
|
isTablet: boolean
|
|
8
8
|
isDesktop: boolean
|
|
9
|
-
|
|
9
|
+
isLargeDisplay: boolean
|
|
10
|
+
screenSize: 'mobile' | 'tablet' | 'desktop' | 'largeDisplay'
|
|
10
11
|
orientation: 'portrait' | 'landscape'
|
|
11
12
|
touchDevice: boolean
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
+
// Updated breakpoints to align with Phase 1 Enhanced Breakpoint System
|
|
16
|
+
// xs: 475px, sm: 640px, md: 768px, lg: 1024px, xl: 1280px, 2xl: 1536px, 3xl: 1920px
|
|
15
17
|
const BREAKPOINTS = {
|
|
16
|
-
mobile: 768,
|
|
17
|
-
tablet: 1024,
|
|
18
|
-
desktop:
|
|
18
|
+
mobile: 768, // xs-sm range (475-768px)
|
|
19
|
+
tablet: 1024, // md range (768-1024px)
|
|
20
|
+
desktop: 1536 // lg-xl range (1024-1536px), largeDisplay starts at 1536px+ (2xl-3xl)
|
|
19
21
|
} as const
|
|
20
22
|
|
|
21
23
|
/**
|
|
@@ -27,6 +29,7 @@ export const useDevice = (): DeviceInfo => {
|
|
|
27
29
|
isMobile: false,
|
|
28
30
|
isTablet: false,
|
|
29
31
|
isDesktop: false,
|
|
32
|
+
isLargeDisplay: false,
|
|
30
33
|
screenSize: 'desktop',
|
|
31
34
|
orientation: 'landscape',
|
|
32
35
|
touchDevice: false
|
|
@@ -37,16 +40,18 @@ export const useDevice = (): DeviceInfo => {
|
|
|
37
40
|
const width = window.innerWidth
|
|
38
41
|
const height = window.innerHeight
|
|
39
42
|
|
|
40
|
-
// Determine device type based on width
|
|
43
|
+
// Determine device type based on width (aligned with new breakpoint system)
|
|
41
44
|
const isMobile = width < BREAKPOINTS.mobile
|
|
42
45
|
const isTablet = width >= BREAKPOINTS.mobile && width < BREAKPOINTS.tablet
|
|
43
|
-
const isDesktop = width >= BREAKPOINTS.tablet
|
|
44
|
-
|
|
46
|
+
const isDesktop = width >= BREAKPOINTS.tablet && width < BREAKPOINTS.desktop
|
|
47
|
+
const isLargeDisplay = width >= BREAKPOINTS.desktop
|
|
48
|
+
|
|
45
49
|
// Determine screen size category
|
|
46
50
|
let screenSize: DeviceInfo['screenSize']
|
|
47
51
|
if (isMobile) screenSize = 'mobile'
|
|
48
52
|
else if (isTablet) screenSize = 'tablet'
|
|
49
|
-
else screenSize = 'desktop'
|
|
53
|
+
else if (isDesktop) screenSize = 'desktop'
|
|
54
|
+
else screenSize = 'largeDisplay'
|
|
50
55
|
|
|
51
56
|
// Determine orientation
|
|
52
57
|
const orientation: DeviceInfo['orientation'] = width > height ? 'landscape' : 'portrait'
|
|
@@ -61,6 +66,7 @@ export const useDevice = (): DeviceInfo => {
|
|
|
61
66
|
isMobile,
|
|
62
67
|
isTablet,
|
|
63
68
|
isDesktop,
|
|
69
|
+
isLargeDisplay,
|
|
64
70
|
screenSize,
|
|
65
71
|
orientation,
|
|
66
72
|
touchDevice
|
|
@@ -92,8 +98,9 @@ export const useDevice = (): DeviceInfo => {
|
|
|
92
98
|
|
|
93
99
|
// Convenience hooks for specific use cases
|
|
94
100
|
export const useIsMobile = () => useDevice().isMobile
|
|
95
|
-
export const useIsTablet = () => useDevice().isTablet
|
|
101
|
+
export const useIsTablet = () => useDevice().isTablet
|
|
96
102
|
export const useIsDesktop = () => useDevice().isDesktop
|
|
103
|
+
export const useIsLargeDisplay = () => useDevice().isLargeDisplay
|
|
97
104
|
export const useOrientation = () => useDevice().orientation
|
|
98
105
|
export const useIsTouchDevice = () => useDevice().touchDevice
|
|
99
106
|
|
package/src/index.ts
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
* Production-ready design system package with harvey theme
|
|
4
4
|
*
|
|
5
5
|
* Auto-generated exports for:
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
6
|
+
* - 90 UI components
|
|
7
|
+
* - 68 custom hooks
|
|
8
8
|
* - 6 utility functions
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
9
|
+
* - 32 theme system components
|
|
10
|
+
* - 165 TypeScript types
|
|
11
11
|
* - 4 design tokens
|
|
12
12
|
* - 0 build plugins
|
|
13
13
|
* - Complete CSS system (61+ files)
|
|
@@ -57,6 +57,7 @@ export { TouchFriendlySlider } from './components/ui/touch-friendly-interface.js
|
|
|
57
57
|
export { Breadcrumb } from './components/ui/navigation/breadcrumb.js';
|
|
58
58
|
export { Menu } from './components/ui/navigation/menu.js';
|
|
59
59
|
export { Pagination } from './components/ui/navigation/pagination.js';
|
|
60
|
+
export { ProgressiveNavigation } from './components/ui/navigation/progressive-navigation.js';
|
|
60
61
|
export { Sidebar } from './components/ui/navigation/sidebar.js';
|
|
61
62
|
export { Stepper } from './components/ui/navigation/stepper.js';
|
|
62
63
|
export { SubscriptionBadge } from './components/ui/navigation/subscription-badge.js';
|
|
@@ -127,6 +128,7 @@ export { useDevice } from './hooks/use-device.js';
|
|
|
127
128
|
export { useIsMobile } from './hooks/use-device.js';
|
|
128
129
|
export { useIsTablet } from './hooks/use-device.js';
|
|
129
130
|
export { useIsDesktop } from './hooks/use-device.js';
|
|
131
|
+
export { useIsLargeDisplay } from './hooks/use-device.js';
|
|
130
132
|
export { useOrientation } from './hooks/use-device.js';
|
|
131
133
|
export { useIsTouchDevice } from './hooks/use-device.js';
|
|
132
134
|
export { useEnterpriseMobileExperience } from './hooks/use-enterprise-mobile-experience.js';
|
|
@@ -208,6 +210,9 @@ export { KeyboardFocusManager } from './themes/keyboard-navigation.js';
|
|
|
208
210
|
export { MotionReductionManager } from './themes/motion-reduction.js';
|
|
209
211
|
export { MotionReductionUtils } from './themes/motion-reduction.js';
|
|
210
212
|
export { MotionReductionHooks } from './themes/motion-reduction.js';
|
|
213
|
+
export { PHASE1_BREAKPOINTS } from './themes/phase1-constants.js';
|
|
214
|
+
export { PHASE1_CONTENT_DENSITY } from './themes/phase1-constants.js';
|
|
215
|
+
export { PHASE1_RESPONSIVE_TYPOGRAPHY } from './themes/phase1-constants.js';
|
|
211
216
|
export { ScreenReaderOptimizer } from './themes/screen-reader.js';
|
|
212
217
|
export { ScreenReaderNavigation } from './themes/screen-reader.js';
|
|
213
218
|
export { SystemThemeDetector } from './themes/systemThemeDetector.js';
|
|
@@ -238,6 +243,7 @@ export type { StepperProps } from './components/ui/navigation/types.js';
|
|
|
238
243
|
export type { StepItem } from './components/ui/navigation/types.js';
|
|
239
244
|
export type { StepAction } from './components/ui/navigation/types.js';
|
|
240
245
|
export type { MenuProps } from './components/ui/navigation/types.js';
|
|
246
|
+
export type { ProgressiveNavigationProps } from './components/ui/navigation/types.js';
|
|
241
247
|
export type { SidebarProps } from './components/ui/navigation/types.js';
|
|
242
248
|
export type { NavigationState } from './components/ui/navigation/types.js';
|
|
243
249
|
export type { NavigationContextValue } from './components/ui/navigation/types.js';
|
|
@@ -370,6 +376,15 @@ export type { ThemeValidationError } from './themes/types.js';
|
|
|
370
376
|
export type { ThemeValidationWarning } from './themes/types.js';
|
|
371
377
|
export type { ThemeInheritance } from './themes/types.js';
|
|
372
378
|
export type { CompleteThemeConfig } from './themes/types.js';
|
|
379
|
+
export type { BreakpointThemeConfig } from './themes/types.js';
|
|
380
|
+
export type { ContentDensityThemeConfig } from './themes/types.js';
|
|
381
|
+
export type { DensityLevel } from './themes/types.js';
|
|
382
|
+
export type { DensitySpacing } from './themes/types.js';
|
|
383
|
+
export type { DensityTypography } from './themes/types.js';
|
|
384
|
+
export type { DensityTouchTargets } from './themes/types.js';
|
|
385
|
+
export type { DensityInteractions } from './themes/types.js';
|
|
386
|
+
export type { ResponsiveTypographyThemeConfig } from './themes/types.js';
|
|
387
|
+
export type { ResponsiveTypographyScale } from './themes/types.js';
|
|
373
388
|
export type { DesignToken } from './tokens/types.js';
|
|
374
389
|
export type { TokenType } from './tokens/types.js';
|
|
375
390
|
export type { ColorToken } from './tokens/types.js';
|