@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.
- package/CHANGELOG.md +1 -1
- package/dist/index.d.ts +131 -131
- package/dist/index.esm.js +148 -148
- package/dist/index.js +148 -148
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/components/ui/accessibility-demo.tsx +271 -0
- package/src/components/ui/advanced-component-architecture-demo.tsx +916 -0
- package/src/components/ui/advanced-transition-system-demo.tsx +670 -0
- package/src/components/ui/advanced-transition-system.tsx +395 -0
- package/src/components/ui/animation/animated-container.tsx +166 -0
- package/src/components/ui/animation/index.ts +19 -0
- package/src/components/ui/animation/staggered-container.tsx +68 -0
- package/src/components/ui/animation-demo.tsx +250 -0
- package/src/components/ui/badge.tsx +33 -0
- package/src/components/ui/battery-conscious-animation-demo.tsx +568 -0
- package/src/components/ui/border-radius-shadow-demo.tsx +187 -0
- package/src/components/ui/button.tsx +36 -0
- package/src/components/ui/card.tsx +207 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/color-preview.tsx +411 -0
- package/src/components/ui/data-display/chart.tsx +653 -0
- package/src/components/ui/data-display/data-grid-simple.tsx +76 -0
- package/src/components/ui/data-display/data-grid.tsx +680 -0
- package/src/components/ui/data-display/list.tsx +456 -0
- package/src/components/ui/data-display/table.tsx +482 -0
- package/src/components/ui/data-display/timeline.tsx +441 -0
- package/src/components/ui/data-display/tree.tsx +602 -0
- package/src/components/ui/data-display/types.ts +536 -0
- package/src/components/ui/enterprise-mobile-experience-demo.tsx +749 -0
- package/src/components/ui/enterprise-mobile-experience.tsx +464 -0
- package/src/components/ui/feedback/alert.tsx +157 -0
- package/src/components/ui/feedback/progress.tsx +292 -0
- package/src/components/ui/feedback/skeleton.tsx +185 -0
- package/src/components/ui/feedback/toast.tsx +280 -0
- package/src/components/ui/feedback/types.ts +125 -0
- package/src/components/ui/font-preview.tsx +288 -0
- package/src/components/ui/form-demo.tsx +553 -0
- package/src/components/ui/hardware-acceleration-demo.tsx +547 -0
- package/src/components/ui/input.tsx +35 -0
- package/src/components/ui/label.tsx +16 -0
- package/src/components/ui/layout-demo.tsx +367 -0
- package/src/components/ui/layouts/adaptive-layout.tsx +139 -0
- package/src/components/ui/layouts/desktop-layout.tsx +224 -0
- package/src/components/ui/layouts/index.ts +10 -0
- package/src/components/ui/layouts/mobile-layout.tsx +162 -0
- package/src/components/ui/layouts/tablet-layout.tsx +197 -0
- package/src/components/ui/mobile-form-validation.tsx +451 -0
- package/src/components/ui/mobile-input-demo.tsx +201 -0
- package/src/components/ui/mobile-input.tsx +281 -0
- package/src/components/ui/mobile-skeleton-loading-demo.tsx +638 -0
- package/src/components/ui/navigation/breadcrumb.tsx +158 -0
- package/src/components/ui/navigation/index.ts +36 -0
- package/src/components/ui/navigation/menu.tsx +374 -0
- package/src/components/ui/navigation/navigation-demo.tsx +324 -0
- package/src/components/ui/navigation/pagination.tsx +272 -0
- package/src/components/ui/navigation/sidebar.tsx +383 -0
- package/src/components/ui/navigation/stepper.tsx +303 -0
- package/src/components/ui/navigation/tabs.tsx +205 -0
- package/src/components/ui/navigation/types.ts +299 -0
- package/src/components/ui/overlay/backdrop.tsx +81 -0
- package/src/components/ui/overlay/focus-manager.tsx +143 -0
- package/src/components/ui/overlay/index.ts +36 -0
- package/src/components/ui/overlay/modal.tsx +270 -0
- package/src/components/ui/overlay/overlay-manager.tsx +110 -0
- package/src/components/ui/overlay/popover.tsx +462 -0
- package/src/components/ui/overlay/portal.tsx +79 -0
- package/src/components/ui/overlay/tooltip.tsx +303 -0
- package/src/components/ui/overlay/types.ts +196 -0
- package/src/components/ui/performance-demo.tsx +596 -0
- package/src/components/ui/semantic-input-system-demo.tsx +502 -0
- package/src/components/ui/semantic-input-system-demo.tsx.disabled +873 -0
- package/src/components/ui/tablet-layout.tsx +192 -0
- package/src/components/ui/theme-customizer.tsx +386 -0
- package/src/components/ui/theme-preview.tsx +310 -0
- package/src/components/ui/theme-switcher.tsx +264 -0
- package/src/components/ui/theme-toggle.tsx +38 -0
- package/src/components/ui/token-demo.tsx +195 -0
- package/src/components/ui/touch-demo.tsx +462 -0
- package/src/components/ui/touch-friendly-interface-demo.tsx +519 -0
- package/src/components/ui/touch-friendly-interface.tsx +296 -0
- package/src/hooks/index.ts +190 -0
- package/src/hooks/use-accessibility-support.ts +518 -0
- package/src/hooks/use-adaptive-layout.ts +289 -0
- package/src/hooks/use-advanced-patterns.ts +294 -0
- package/src/hooks/use-advanced-transition-system.ts +393 -0
- package/src/hooks/use-animation-profile.ts +288 -0
- package/src/hooks/use-battery-animations.ts +384 -0
- package/src/hooks/use-battery-conscious-loading.ts +475 -0
- package/src/hooks/use-battery-optimization.ts +330 -0
- package/src/hooks/use-battery-status.ts +299 -0
- package/src/hooks/use-component-performance.ts +344 -0
- package/src/hooks/use-device-loading-states.ts +459 -0
- package/src/hooks/use-device.tsx +110 -0
- package/src/hooks/use-enterprise-mobile-experience.ts +488 -0
- package/src/hooks/use-form-feedback.ts +403 -0
- package/src/hooks/use-form-performance.ts +513 -0
- package/src/hooks/use-frame-rate.ts +251 -0
- package/src/hooks/use-gestures.ts +338 -0
- package/src/hooks/use-hardware-acceleration.ts +341 -0
- package/src/hooks/use-input-accessibility.ts +455 -0
- package/src/hooks/use-input-performance.ts +506 -0
- package/src/hooks/use-layout-performance.ts +319 -0
- package/src/hooks/use-loading-accessibility.ts +535 -0
- package/src/hooks/use-loading-performance.ts +473 -0
- package/src/hooks/use-memory-usage.ts +287 -0
- package/src/hooks/use-mobile-form-layout.ts +464 -0
- package/src/hooks/use-mobile-form-validation.ts +518 -0
- package/src/hooks/use-mobile-keyboard-optimization.ts +472 -0
- package/src/hooks/use-mobile-layout.ts +302 -0
- package/src/hooks/use-mobile-optimization.ts +406 -0
- package/src/hooks/use-mobile-skeleton.ts +402 -0
- package/src/hooks/use-mobile-touch.ts +414 -0
- package/src/hooks/use-performance-throttling.ts +348 -0
- package/src/hooks/use-performance.ts +316 -0
- package/src/hooks/use-reusable-architecture.ts +414 -0
- package/src/hooks/use-semantic-input-types.ts +357 -0
- package/src/hooks/use-semantic-input.ts +565 -0
- package/src/hooks/use-tablet-layout.ts +384 -0
- package/src/hooks/use-touch-friendly-input.ts +524 -0
- package/src/hooks/use-touch-friendly-interface.ts +331 -0
- package/src/hooks/use-touch-optimization.ts +375 -0
- package/src/index.ts +279 -279
- package/src/lib/utils.ts +6 -0
- package/src/themes/README.md +272 -0
- package/src/themes/ThemeContext.tsx +31 -0
- package/src/themes/ThemeProvider.tsx +232 -0
- package/src/themes/accessibility/index.ts +27 -0
- package/src/themes/accessibility.ts +259 -0
- package/src/themes/aria-patterns.ts +420 -0
- package/src/themes/base-themes.ts +55 -0
- package/src/themes/colorManager.ts +380 -0
- package/src/themes/examples/dark-theme.ts +154 -0
- package/src/themes/examples/minimal-theme.ts +108 -0
- package/src/themes/focus-management.ts +701 -0
- package/src/themes/fontLoader.ts +201 -0
- package/src/themes/high-contrast.ts +621 -0
- package/src/themes/index.ts +19 -0
- package/src/themes/inheritance.ts +227 -0
- package/src/themes/keyboard-navigation.ts +550 -0
- package/src/themes/motion-reduction.ts +662 -0
- package/src/themes/navigation.ts +238 -0
- package/src/themes/screen-reader.ts +645 -0
- package/src/themes/systemThemeDetector.ts +182 -0
- package/src/themes/themeCSSUpdater.ts +262 -0
- package/src/themes/themePersistence.ts +238 -0
- package/src/themes/themes/default.ts +586 -0
- package/src/themes/themes/harvey.ts +554 -0
- package/src/themes/themes/stan-design.ts +683 -0
- package/src/themes/types.ts +460 -0
- package/src/themes/useSystemTheme.ts +48 -0
- package/src/themes/useTheme.ts +87 -0
- package/src/themes/validation.ts +462 -0
- package/src/tokens/index.ts +34 -0
- package/src/tokens/tokenExporter.ts +397 -0
- package/src/tokens/tokenGenerator.ts +276 -0
- package/src/tokens/tokenManager.ts +248 -0
- package/src/tokens/tokenValidator.ts +543 -0
- package/src/tokens/types.ts +78 -0
- package/src/utils/bundle-analyzer.ts +260 -0
- package/src/utils/bundle-splitting.ts +483 -0
- package/src/utils/lazy-loading.ts +441 -0
- package/src/utils/performance-monitor.ts +513 -0
- package/src/utils/tree-shaking.ts +274 -0
|
@@ -0,0 +1,602 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
|
+
import { useTheme } from '../../../themes/useTheme';
|
|
3
|
+
import {
|
|
4
|
+
TreeProps,
|
|
5
|
+
TreeNode,
|
|
6
|
+
TreeItemProps
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
// Simple icon components (inline SVG)
|
|
10
|
+
const ChevronRightIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
11
|
+
<svg
|
|
12
|
+
className={`tree__icon ${className}`}
|
|
13
|
+
fill="none"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
viewBox="0 0 24 24"
|
|
16
|
+
>
|
|
17
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const ChevronDownIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
22
|
+
<svg
|
|
23
|
+
className={`tree__icon ${className}`}
|
|
24
|
+
fill="none"
|
|
25
|
+
stroke="currentColor"
|
|
26
|
+
viewBox="0 0 24 24"
|
|
27
|
+
>
|
|
28
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
|
|
29
|
+
</svg>
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const SearchIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
33
|
+
<svg
|
|
34
|
+
className={`tree__icon ${className}`}
|
|
35
|
+
fill="none"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
viewBox="0 0 24 24"
|
|
38
|
+
>
|
|
39
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
|
40
|
+
</svg>
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const FilterIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
44
|
+
<svg
|
|
45
|
+
className={`tree__icon ${className}`}
|
|
46
|
+
fill="none"
|
|
47
|
+
stroke="currentColor"
|
|
48
|
+
viewBox="0 0 24 24"
|
|
49
|
+
>
|
|
50
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.414A1 1 0 013 6.707V4z" />
|
|
51
|
+
</svg>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const FolderIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
55
|
+
<svg
|
|
56
|
+
className={`tree__icon ${className}`}
|
|
57
|
+
fill="none"
|
|
58
|
+
stroke="currentColor"
|
|
59
|
+
viewBox="0 0 24 24"
|
|
60
|
+
>
|
|
61
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" />
|
|
62
|
+
</svg>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const DocumentIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
66
|
+
<svg
|
|
67
|
+
className={`tree__icon ${className}`}
|
|
68
|
+
fill="none"
|
|
69
|
+
stroke="currentColor"
|
|
70
|
+
viewBox="0 0 24 24"
|
|
71
|
+
>
|
|
72
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
73
|
+
</svg>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const CheckIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
77
|
+
<svg
|
|
78
|
+
className={`tree__icon ${className}`}
|
|
79
|
+
fill="none"
|
|
80
|
+
stroke="currentColor"
|
|
81
|
+
viewBox="0 0 24 24"
|
|
82
|
+
>
|
|
83
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
|
84
|
+
</svg>
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const MinusIcon: React.FC<{ className?: string }> = ({ className = '' }) => (
|
|
88
|
+
<svg
|
|
89
|
+
className={`tree__icon ${className}`}
|
|
90
|
+
fill="none"
|
|
91
|
+
stroke="currentColor"
|
|
92
|
+
viewBox="0 0 24 24"
|
|
93
|
+
>
|
|
94
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12H4" />
|
|
95
|
+
</svg>
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Default color fallbacks for when theme is not available
|
|
99
|
+
const getDefaultColors = () => ({
|
|
100
|
+
surface: {
|
|
101
|
+
background: '#ffffff',
|
|
102
|
+
surface: '#f3f4f6',
|
|
103
|
+
border: '#d1d5db',
|
|
104
|
+
divider: '#e5e7eb'
|
|
105
|
+
},
|
|
106
|
+
text: {
|
|
107
|
+
primary: '#111827',
|
|
108
|
+
secondary: '#6b7280',
|
|
109
|
+
muted: '#9ca3af',
|
|
110
|
+
inverse: '#ffffff',
|
|
111
|
+
onPrimary: '#ffffff',
|
|
112
|
+
onSecondary: '#ffffff',
|
|
113
|
+
onSurface: '#111827'
|
|
114
|
+
},
|
|
115
|
+
interactive: {
|
|
116
|
+
hover: '#f3f4f6',
|
|
117
|
+
active: '#e5e7eb',
|
|
118
|
+
focus: '#3b82f6',
|
|
119
|
+
disabled: '#d1d5db'
|
|
120
|
+
},
|
|
121
|
+
primary: { 500: '#3b82f6' },
|
|
122
|
+
semantic: {
|
|
123
|
+
success: '#10b981',
|
|
124
|
+
warning: '#f59e0b',
|
|
125
|
+
error: '#ef4444',
|
|
126
|
+
info: '#3b82f6'
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// Tree Search Bar
|
|
131
|
+
const TreeSearchBar: React.FC<{
|
|
132
|
+
searchable: boolean;
|
|
133
|
+
searchValue: string;
|
|
134
|
+
onSearchChange: (value: string) => void;
|
|
135
|
+
filterable: boolean;
|
|
136
|
+
theme: string;
|
|
137
|
+
size: 'sm' | 'md' | 'lg';
|
|
138
|
+
}> = ({
|
|
139
|
+
searchable,
|
|
140
|
+
searchValue,
|
|
141
|
+
onSearchChange,
|
|
142
|
+
filterable,
|
|
143
|
+
theme,
|
|
144
|
+
size
|
|
145
|
+
}) => {
|
|
146
|
+
const { getTheme } = useTheme();
|
|
147
|
+
const themeConfig = getTheme(theme);
|
|
148
|
+
const colors = themeConfig?.colors || getDefaultColors();
|
|
149
|
+
|
|
150
|
+
const sizeClasses = {
|
|
151
|
+
sm: 'h-8 text-sm px-3',
|
|
152
|
+
md: 'h-10 text-base px-4',
|
|
153
|
+
lg: 'h-12 text-lg px-5'
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
if (!searchable && !filterable) return null;
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div className="tree__search-filter">
|
|
160
|
+
{searchable && (
|
|
161
|
+
<div className="tree__search">
|
|
162
|
+
<div className="tree__search-icon">
|
|
163
|
+
<SearchIcon className="tree__icon" />
|
|
164
|
+
</div>
|
|
165
|
+
<input
|
|
166
|
+
type="text"
|
|
167
|
+
value={searchValue}
|
|
168
|
+
onChange={(e) => onSearchChange(e.target.value)}
|
|
169
|
+
placeholder="Search nodes..."
|
|
170
|
+
className={`w-full ${sizeClasses[size]} pl-10 pr-4 rounded-lg border outline-none`}
|
|
171
|
+
style={{
|
|
172
|
+
backgroundColor: colors.surface.background,
|
|
173
|
+
border: `1px solid ${colors.surface.border}`,
|
|
174
|
+
color: colors.text.primary
|
|
175
|
+
}}
|
|
176
|
+
onFocus={(e) => {
|
|
177
|
+
e.target.style.borderColor = colors.primary[500];
|
|
178
|
+
e.target.style.boxShadow = `0 0 0 3px ${colors.primary[500]}20`;
|
|
179
|
+
}}
|
|
180
|
+
onBlur={(e) => {
|
|
181
|
+
e.target.style.borderColor = colors.surface.border;
|
|
182
|
+
e.target.style.boxShadow = 'none';
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{filterable && (
|
|
189
|
+
<button className="tree__filter-button">
|
|
190
|
+
<FilterIcon className="tree__icon" />
|
|
191
|
+
<span>Filter</span>
|
|
192
|
+
</button>
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// Individual Tree Item Component
|
|
199
|
+
const TreeItem: React.FC<TreeItemProps> = ({
|
|
200
|
+
node,
|
|
201
|
+
level = 0,
|
|
202
|
+
expandedKeys,
|
|
203
|
+
selectedKeys,
|
|
204
|
+
selectable,
|
|
205
|
+
expandable,
|
|
206
|
+
onToggleExpand,
|
|
207
|
+
onSelectionChange,
|
|
208
|
+
onClick,
|
|
209
|
+
theme,
|
|
210
|
+
size,
|
|
211
|
+
variant,
|
|
212
|
+
// draggable = false, // TODO: Implement drag and drop
|
|
213
|
+
// onDragStart, // TODO: Implement drag and drop
|
|
214
|
+
// onDragEnd, // TODO: Implement drag and drop
|
|
215
|
+
// onDrop, // TODO: Implement drag and drop
|
|
216
|
+
showLines = true,
|
|
217
|
+
showIcons = true
|
|
218
|
+
}) => {
|
|
219
|
+
// const { getTheme } = useTheme();
|
|
220
|
+
// const themeConfig = getTheme(theme);
|
|
221
|
+
// const colors = themeConfig?.colors || getDefaultColors();
|
|
222
|
+
|
|
223
|
+
const isExpanded = expandedKeys.includes(node.id);
|
|
224
|
+
const isSelected = selectedKeys.includes(node.id);
|
|
225
|
+
const hasChildren = node.children && node.children.length > 0;
|
|
226
|
+
const isClickable = !!onClick;
|
|
227
|
+
|
|
228
|
+
const iconSizes = {
|
|
229
|
+
sm: 'w-3 h-3',
|
|
230
|
+
md: 'w-4 h-4',
|
|
231
|
+
lg: 'w-5 h-5'
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const indentWidth = {
|
|
235
|
+
sm: 16,
|
|
236
|
+
md: 20,
|
|
237
|
+
lg: 24
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleToggleExpand = (e: React.MouseEvent) => {
|
|
241
|
+
e.stopPropagation();
|
|
242
|
+
if (hasChildren && expandable) {
|
|
243
|
+
onToggleExpand?.(node.id, !isExpanded);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const handleSelectionChange = (e: React.MouseEvent) => {
|
|
248
|
+
e.stopPropagation();
|
|
249
|
+
if (selectable) {
|
|
250
|
+
onSelectionChange?.(node.id, !isSelected);
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
255
|
+
if (isClickable) {
|
|
256
|
+
onClick?.(node, e);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const getNodeIcon = () => {
|
|
261
|
+
if (node.icon) {
|
|
262
|
+
return node.icon;
|
|
263
|
+
}
|
|
264
|
+
if (hasChildren) {
|
|
265
|
+
return <FolderIcon className={iconSizes[size]} />;
|
|
266
|
+
}
|
|
267
|
+
return <DocumentIcon className={iconSizes[size]} />;
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const getCheckboxState = () => {
|
|
271
|
+
if (!hasChildren) return isSelected;
|
|
272
|
+
|
|
273
|
+
// For parent nodes, check if all children are selected
|
|
274
|
+
const selectedChildrenCount = node.children?.filter(child =>
|
|
275
|
+
selectedKeys.includes(child.id)
|
|
276
|
+
).length || 0;
|
|
277
|
+
|
|
278
|
+
const totalChildren = node.children?.length || 0;
|
|
279
|
+
|
|
280
|
+
if (selectedChildrenCount === 0) return false;
|
|
281
|
+
if (selectedChildrenCount === totalChildren) return true;
|
|
282
|
+
return 'indeterminate'; // Some children selected
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const checkboxState = getCheckboxState();
|
|
286
|
+
|
|
287
|
+
return (
|
|
288
|
+
<>
|
|
289
|
+
<div
|
|
290
|
+
className={`tree__node tree__node--${size} ${variant !== 'default' ? `tree__node--${variant}` : ''} ${
|
|
291
|
+
isSelected ? 'tree__node--selected' : ''
|
|
292
|
+
} ${isClickable ? 'tree__node--clickable' : ''}`}
|
|
293
|
+
style={{
|
|
294
|
+
paddingLeft: `${level * indentWidth[size] + 12}px`
|
|
295
|
+
}}
|
|
296
|
+
onClick={handleClick}
|
|
297
|
+
>
|
|
298
|
+
{/* Tree Lines */}
|
|
299
|
+
{showLines && level > 0 && (
|
|
300
|
+
<div
|
|
301
|
+
className="tree__line"
|
|
302
|
+
style={{
|
|
303
|
+
left: `${(level - 1) * indentWidth[size] + 12 + indentWidth[size] / 2 - 1}px`
|
|
304
|
+
}}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
307
|
+
|
|
308
|
+
{/* Expand/Collapse Button */}
|
|
309
|
+
<div className="tree__node-toggle-container">
|
|
310
|
+
{hasChildren && expandable ? (
|
|
311
|
+
<button
|
|
312
|
+
onClick={handleToggleExpand}
|
|
313
|
+
className={`tree__node-toggle tree__node-toggle--${size}`}
|
|
314
|
+
>
|
|
315
|
+
<div className={`tree__expand-icon ${isExpanded ? 'tree__expand-icon--expanded' : ''}`}>
|
|
316
|
+
{isExpanded ? (
|
|
317
|
+
<ChevronDownIcon className={`tree__icon tree__icon--${size}`} />
|
|
318
|
+
) : (
|
|
319
|
+
<ChevronRightIcon className={`tree__icon tree__icon--${size}`} />
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
</button>
|
|
323
|
+
) : (
|
|
324
|
+
<div className={`tree__node-spacer tree__node-spacer--${size}`} />
|
|
325
|
+
)}
|
|
326
|
+
</div>
|
|
327
|
+
|
|
328
|
+
{/* Selection Checkbox */}
|
|
329
|
+
{selectable && (
|
|
330
|
+
<div className={`tree__checkbox tree__checkbox--${size}`}>
|
|
331
|
+
<button
|
|
332
|
+
onClick={handleSelectionChange}
|
|
333
|
+
className={`tree__checkbox-input ${
|
|
334
|
+
checkboxState ? 'tree__checkbox-input--checked' : ''
|
|
335
|
+
} ${
|
|
336
|
+
checkboxState === 'indeterminate' ? 'tree__checkbox-input--indeterminate' : ''
|
|
337
|
+
}`}
|
|
338
|
+
>
|
|
339
|
+
{checkboxState === true && <CheckIcon className="tree__checkbox-icon" />}
|
|
340
|
+
{checkboxState === 'indeterminate' && <MinusIcon className="tree__checkbox-icon" />}
|
|
341
|
+
</button>
|
|
342
|
+
</div>
|
|
343
|
+
)}
|
|
344
|
+
|
|
345
|
+
{/* Node Icon */}
|
|
346
|
+
{showIcons && (
|
|
347
|
+
<div className={`tree__node-icon tree__node-icon--${size}`}>
|
|
348
|
+
{getNodeIcon()}
|
|
349
|
+
</div>
|
|
350
|
+
)}
|
|
351
|
+
|
|
352
|
+
{/* Node Content */}
|
|
353
|
+
<div className={`tree__node-content tree__node-content--${size}`}>
|
|
354
|
+
<div className="tree__node-main">
|
|
355
|
+
<div className="tree__node-text">
|
|
356
|
+
<span className={`tree__node-label ${
|
|
357
|
+
isSelected ? 'tree__node-label--selected' : ''
|
|
358
|
+
}`}>
|
|
359
|
+
{node.label}
|
|
360
|
+
</span>
|
|
361
|
+
{node.description && (
|
|
362
|
+
<div className="tree__node-description">
|
|
363
|
+
{node.description}
|
|
364
|
+
</div>
|
|
365
|
+
)}
|
|
366
|
+
</div>
|
|
367
|
+
|
|
368
|
+
{/* Node Metadata */}
|
|
369
|
+
{node.metadata && (
|
|
370
|
+
<div className="tree__node-metadata">
|
|
371
|
+
{node.metadata.count !== undefined && (
|
|
372
|
+
<span className="tree__node-badge tree__node-badge--count">
|
|
373
|
+
{node.metadata.count}
|
|
374
|
+
</span>
|
|
375
|
+
)}
|
|
376
|
+
{node.metadata.status && (
|
|
377
|
+
<span className={`tree__node-badge tree__node-badge--status tree__node-badge--${node.metadata.status}`}>
|
|
378
|
+
{node.metadata.status}
|
|
379
|
+
</span>
|
|
380
|
+
)}
|
|
381
|
+
</div>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
{/* Child Nodes */}
|
|
388
|
+
{hasChildren && isExpanded && (
|
|
389
|
+
<>
|
|
390
|
+
{node.children!.map((childNode) => (
|
|
391
|
+
<TreeItem
|
|
392
|
+
key={childNode.id}
|
|
393
|
+
node={childNode}
|
|
394
|
+
level={level + 1}
|
|
395
|
+
expandedKeys={expandedKeys}
|
|
396
|
+
selectedKeys={selectedKeys}
|
|
397
|
+
selectable={selectable}
|
|
398
|
+
expandable={expandable}
|
|
399
|
+
onToggleExpand={onToggleExpand}
|
|
400
|
+
onSelectionChange={onSelectionChange}
|
|
401
|
+
onClick={onClick}
|
|
402
|
+
theme={theme}
|
|
403
|
+
size={size}
|
|
404
|
+
variant={variant}
|
|
405
|
+
showLines={showLines}
|
|
406
|
+
showIcons={showIcons}
|
|
407
|
+
/>
|
|
408
|
+
))}
|
|
409
|
+
</>
|
|
410
|
+
)}
|
|
411
|
+
</>
|
|
412
|
+
);
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// Main Tree Component
|
|
416
|
+
export const Tree: React.FC<TreeProps> = ({
|
|
417
|
+
data,
|
|
418
|
+
expandedKeys = [],
|
|
419
|
+
selectedKeys = [],
|
|
420
|
+
onExpandedKeysChange,
|
|
421
|
+
onSelectedKeysChange,
|
|
422
|
+
onNodeClick,
|
|
423
|
+
selectable = false,
|
|
424
|
+
expandable = true,
|
|
425
|
+
// draggable = false, // TODO: Implement drag and drop
|
|
426
|
+
// onDragStart, // TODO: Implement drag and drop
|
|
427
|
+
// onDragEnd, // TODO: Implement drag and drop
|
|
428
|
+
// onDrop, // TODO: Implement drag and drop
|
|
429
|
+
searchable = false,
|
|
430
|
+
searchValue = '',
|
|
431
|
+
onSearchChange,
|
|
432
|
+
filterable = false,
|
|
433
|
+
// filters = [], // TODO: Implement filter functionality
|
|
434
|
+
// onFiltersChange, // TODO: Implement filter functionality
|
|
435
|
+
showLines = true,
|
|
436
|
+
showIcons = true,
|
|
437
|
+
expandAll = false,
|
|
438
|
+
collapseAll = false,
|
|
439
|
+
loading = false,
|
|
440
|
+
error = null,
|
|
441
|
+
emptyMessage = 'No data available',
|
|
442
|
+
className = '',
|
|
443
|
+
theme = 'stan-design',
|
|
444
|
+
size = 'md',
|
|
445
|
+
variant = 'default'
|
|
446
|
+
}) => {
|
|
447
|
+
const { getTheme } = useTheme();
|
|
448
|
+
const themeConfig = getTheme(theme);
|
|
449
|
+
const colors = themeConfig?.colors || getDefaultColors();
|
|
450
|
+
|
|
451
|
+
// Filter nodes based on search
|
|
452
|
+
const filteredData = useMemo(() => {
|
|
453
|
+
if (!searchable || !searchValue.trim()) return data;
|
|
454
|
+
|
|
455
|
+
const searchLower = searchValue.toLowerCase();
|
|
456
|
+
|
|
457
|
+
const filterNode = (node: TreeNode): TreeNode | null => {
|
|
458
|
+
const matchesSearch = node.label.toLowerCase().includes(searchLower) ||
|
|
459
|
+
node.description?.toLowerCase().includes(searchLower);
|
|
460
|
+
|
|
461
|
+
let filteredChildren: TreeNode[] = [];
|
|
462
|
+
if (node.children) {
|
|
463
|
+
filteredChildren = node.children
|
|
464
|
+
.map(child => filterNode(child))
|
|
465
|
+
.filter((child): child is TreeNode => child !== null);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Include node if it matches search OR has matching children
|
|
469
|
+
if (matchesSearch || filteredChildren.length > 0) {
|
|
470
|
+
return {
|
|
471
|
+
...node,
|
|
472
|
+
children: filteredChildren.length > 0 ? filteredChildren : node.children
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return null;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
return data
|
|
480
|
+
.map(node => filterNode(node))
|
|
481
|
+
.filter((node): node is TreeNode => node !== null);
|
|
482
|
+
}, [data, searchable, searchValue]);
|
|
483
|
+
|
|
484
|
+
// Auto-expand all nodes when expandAll changes
|
|
485
|
+
React.useEffect(() => {
|
|
486
|
+
if (expandAll) {
|
|
487
|
+
const getAllNodeIds = (nodes: TreeNode[]): string[] => {
|
|
488
|
+
const ids: string[] = [];
|
|
489
|
+
const traverse = (nodeList: TreeNode[]) => {
|
|
490
|
+
nodeList.forEach(node => {
|
|
491
|
+
if (node.children && node.children.length > 0) {
|
|
492
|
+
ids.push(node.id);
|
|
493
|
+
traverse(node.children);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
};
|
|
497
|
+
traverse(nodes);
|
|
498
|
+
return ids;
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const allIds = getAllNodeIds(data);
|
|
502
|
+
onExpandedKeysChange?.(allIds);
|
|
503
|
+
}
|
|
504
|
+
}, [expandAll, data, onExpandedKeysChange]);
|
|
505
|
+
|
|
506
|
+
// Auto-collapse all nodes when collapseAll changes
|
|
507
|
+
React.useEffect(() => {
|
|
508
|
+
if (collapseAll) {
|
|
509
|
+
onExpandedKeysChange?.([]);
|
|
510
|
+
}
|
|
511
|
+
}, [collapseAll, onExpandedKeysChange]);
|
|
512
|
+
|
|
513
|
+
const handleToggleExpand = useCallback((nodeId: string, expanded: boolean) => {
|
|
514
|
+
const newExpandedKeys = expanded
|
|
515
|
+
? [...expandedKeys, nodeId]
|
|
516
|
+
: expandedKeys.filter(key => key !== nodeId);
|
|
517
|
+
|
|
518
|
+
onExpandedKeysChange?.(newExpandedKeys);
|
|
519
|
+
}, [expandedKeys, onExpandedKeysChange]);
|
|
520
|
+
|
|
521
|
+
const handleSelectionChange = useCallback((nodeId: string, selected: boolean) => {
|
|
522
|
+
const newSelectedKeys = selected
|
|
523
|
+
? [...selectedKeys, nodeId]
|
|
524
|
+
: selectedKeys.filter(key => key !== nodeId);
|
|
525
|
+
|
|
526
|
+
onSelectedKeysChange?.(newSelectedKeys);
|
|
527
|
+
}, [selectedKeys, onSelectedKeysChange]);
|
|
528
|
+
|
|
529
|
+
if (loading) {
|
|
530
|
+
return (
|
|
531
|
+
<div
|
|
532
|
+
className={`p-8 text-center ${className}`}
|
|
533
|
+
style={{ color: colors.text.muted }}
|
|
534
|
+
>
|
|
535
|
+
Loading...
|
|
536
|
+
</div>
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (error) {
|
|
541
|
+
return (
|
|
542
|
+
<div
|
|
543
|
+
className={`p-8 text-center ${className}`}
|
|
544
|
+
style={{
|
|
545
|
+
color: colors.semantic.error,
|
|
546
|
+
backgroundColor: colors.semantic.error + '10',
|
|
547
|
+
border: `1px solid ${colors.semantic.error}30`,
|
|
548
|
+
borderRadius: '8px'
|
|
549
|
+
}}
|
|
550
|
+
>
|
|
551
|
+
{error}
|
|
552
|
+
</div>
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return (
|
|
557
|
+
<div className={`tree__container tree__container--${size} ${variant !== 'default' ? `tree__container--${variant}` : ''} ${className}`}>
|
|
558
|
+
{/* Search Bar */}
|
|
559
|
+
<TreeSearchBar
|
|
560
|
+
searchable={searchable}
|
|
561
|
+
searchValue={searchValue}
|
|
562
|
+
onSearchChange={onSearchChange || (() => {})}
|
|
563
|
+
filterable={filterable}
|
|
564
|
+
theme={theme}
|
|
565
|
+
size={size}
|
|
566
|
+
/>
|
|
567
|
+
|
|
568
|
+
{/* Tree */}
|
|
569
|
+
<div className={`tree__tree tree__tree--${size} ${variant !== 'default' ? `tree__tree--${variant}` : ''}`}>
|
|
570
|
+
{filteredData.length === 0 ? (
|
|
571
|
+
<div className="tree__empty">
|
|
572
|
+
{emptyMessage}
|
|
573
|
+
</div>
|
|
574
|
+
) : (
|
|
575
|
+
<div className="tree__nodes">
|
|
576
|
+
{filteredData.map((node) => (
|
|
577
|
+
<TreeItem
|
|
578
|
+
key={node.id}
|
|
579
|
+
node={node}
|
|
580
|
+
level={0}
|
|
581
|
+
expandedKeys={expandedKeys}
|
|
582
|
+
selectedKeys={selectedKeys}
|
|
583
|
+
selectable={selectable}
|
|
584
|
+
expandable={expandable}
|
|
585
|
+
onToggleExpand={handleToggleExpand}
|
|
586
|
+
onSelectionChange={handleSelectionChange}
|
|
587
|
+
onClick={onNodeClick}
|
|
588
|
+
theme={theme}
|
|
589
|
+
size={size}
|
|
590
|
+
variant={variant}
|
|
591
|
+
showLines={showLines}
|
|
592
|
+
showIcons={showIcons}
|
|
593
|
+
/>
|
|
594
|
+
))}
|
|
595
|
+
</div>
|
|
596
|
+
)}
|
|
597
|
+
</div>
|
|
598
|
+
</div>
|
|
599
|
+
);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
export default Tree;
|