@shohojdhara/atomix 0.5.0 → 0.5.2
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/atomix.config.ts +12 -0
- package/build-tools/webpack-loader.js +5 -4
- package/dist/atomix.css +230 -83
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/webpack-loader.js +5 -4
- package/dist/charts.d.ts +24 -23
- package/dist/charts.js +271 -369
- package/dist/charts.js.map +1 -1
- package/dist/config.d.ts +624 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/core.d.ts +3 -2
- package/dist/core.js +342 -382
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +4 -6
- package/dist/forms.js +233 -334
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +11 -2
- package/dist/heavy.js +406 -445
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +109 -65
- package/dist/index.esm.js +654 -748
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +621 -717
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/layout.js +59 -60
- package/dist/layout.js.map +1 -1
- package/dist/theme.js +4 -4
- package/dist/theme.js.map +1 -1
- package/package.json +24 -9
- package/scripts/atomix-cli.js +15 -1
- package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
- package/scripts/cli/__tests__/detector.test.js +50 -0
- package/scripts/cli/__tests__/template-engine.test.js +23 -0
- package/scripts/cli/__tests__/test-setup.js +1 -133
- package/scripts/cli/commands/doctor.js +15 -3
- package/scripts/cli/commands/generate.js +113 -51
- package/scripts/cli/internal/ai-engine.js +30 -10
- package/scripts/cli/internal/complexity-utils.js +60 -0
- package/scripts/cli/internal/component-validator.js +49 -16
- package/scripts/cli/internal/generator.js +89 -36
- package/scripts/cli/internal/hook-generator.js +5 -2
- package/scripts/cli/internal/itcss-generator.js +16 -12
- package/scripts/cli/templates/next-templates.js +81 -30
- package/scripts/cli/templates/storybook-templates.js +12 -2
- package/scripts/cli/utils/detector.js +45 -7
- package/scripts/cli/utils/diagnostics.js +78 -0
- package/scripts/cli/utils/telemetry.js +13 -0
- package/src/components/Accordion/Accordion.stories.tsx +4 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +188 -128
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +63 -91
- package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +9 -6
- package/src/components/AtomixGlass/glass-utils.ts +51 -1
- package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +52 -46
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
- package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
- package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
- package/src/components/AtomixGlass/stories/types.ts +3 -3
- package/src/components/Button/Button.tsx +114 -57
- package/src/components/Callout/Callout.tsx +4 -4
- package/src/components/Chart/ChartRenderer.tsx +1 -1
- package/src/components/Chart/DonutChart.tsx +11 -8
- package/src/components/EdgePanel/EdgePanel.tsx +119 -115
- package/src/components/Form/Select.tsx +4 -4
- package/src/components/List/List.tsx +4 -4
- package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
- package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
- package/src/components/ProductReview/ProductReview.tsx +4 -2
- package/src/components/Rating/Rating.tsx +4 -2
- package/src/components/SectionIntro/SectionIntro.tsx +4 -2
- package/src/components/Steps/Steps.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +5 -5
- package/src/components/Testimonial/Testimonial.tsx +4 -2
- package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
- package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
- package/src/layouts/CssGrid/CssGrid.tsx +215 -0
- package/src/layouts/CssGrid/index.ts +8 -0
- package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
- package/src/layouts/CssGrid/scripts/index.js +43 -0
- package/src/layouts/Grid/scripts/Container.js +139 -0
- package/src/layouts/Grid/scripts/Grid.js +184 -0
- package/src/layouts/Grid/scripts/GridCol.js +273 -0
- package/src/layouts/Grid/scripts/Row.js +154 -0
- package/src/layouts/Grid/scripts/index.js +48 -0
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
- package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
- package/src/lib/composables/useAccordion.ts +5 -5
- package/src/lib/composables/useAtomixGlass.ts +111 -74
- package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
- package/src/lib/composables/useBarChart.ts +2 -2
- package/src/lib/composables/useChart.ts +3 -2
- package/src/lib/composables/useChartToolbar.ts +48 -66
- package/src/lib/composables/useDataTable.ts +1 -1
- package/src/lib/composables/useDatePicker.ts +2 -2
- package/src/lib/composables/useEdgePanel.ts +45 -54
- package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
- package/src/lib/composables/usePhotoViewer.ts +2 -3
- package/src/lib/composables/usePieChart.ts +1 -1
- package/src/lib/composables/usePopover.ts +151 -139
- package/src/lib/composables/useSideMenu.ts +28 -41
- package/src/lib/composables/useSlider.ts +2 -6
- package/src/lib/composables/useTooltip.ts +2 -2
- package/src/lib/config/index.ts +39 -0
- package/src/lib/constants/components.ts +1 -0
- package/src/lib/theme/devtools/Comparator.tsx +1 -1
- package/src/lib/theme/devtools/Inspector.tsx +1 -1
- package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
- package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
- package/src/lib/types/components.ts +1 -0
- package/src/styles/01-settings/_index.scss +1 -0
- package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
- package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
- package/src/styles/02-tools/_tools.glass.scss +6 -0
- package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
- package/src/styles/06-components/_components.atomix-glass.scss +160 -99
- package/scripts/cli/__tests__/README.md +0 -81
- package/scripts/cli/__tests__/basic.test.js +0 -116
- package/scripts/cli/__tests__/clean.test.js +0 -278
- package/scripts/cli/__tests__/component-generator.test.js +0 -332
- package/scripts/cli/__tests__/component-validator.test.js +0 -433
- package/scripts/cli/__tests__/generator.test.js +0 -613
- package/scripts/cli/__tests__/glass-motion.test.js +0 -256
- package/scripts/cli/__tests__/integration.test.js +0 -938
- package/scripts/cli/__tests__/migrate.test.js +0 -74
- package/scripts/cli/__tests__/security.test.js +0 -206
- package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
- package/scripts/cli/__tests__/token-manager.test.js +0 -251
- package/scripts/cli/__tests__/token-provider.test.js +0 -361
- package/scripts/cli/__tests__/utils.test.js +0 -165
- package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
- package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
- package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
- package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
- package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
- package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
- package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
- package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
- package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
- package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
- package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
- package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
- package/src/components/TypedButton/TypedButton.tsx +0 -39
- package/src/components/TypedButton/index.ts +0 -2
- package/src/lib/composables/useBreadcrumb.ts +0 -81
- package/src/lib/composables/useChartInteractions.ts +0 -123
- package/src/lib/composables/useChartPerformance.ts +0 -347
- package/src/lib/composables/useDropdown.ts +0 -338
- package/src/lib/composables/useModal.ts +0 -110
- package/src/lib/composables/useTypedButton.ts +0 -66
- package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
- package/src/lib/utils/displacement-generator.ts +0 -92
- package/src/lib/utils/memoryMonitor.ts +0 -191
- package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
- package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
- package/src/styles/06-components/_components.testbutton.scss +0 -212
- package/src/styles/06-components/_components.testtypecheck.scss +0 -212
- package/src/styles/06-components/_components.typedbutton.scss +0 -212
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
2
|
-
import { DROPDOWN } from '../constants/components';
|
|
3
|
-
import type { DropdownPlacement, DropdownTrigger } from '../types/components';
|
|
4
|
-
|
|
5
|
-
interface UseDropdownProps {
|
|
6
|
-
placement?: DropdownPlacement;
|
|
7
|
-
trigger?: DropdownTrigger;
|
|
8
|
-
offset?: number;
|
|
9
|
-
defaultOpen?: boolean;
|
|
10
|
-
isOpen?: boolean;
|
|
11
|
-
onOpenChange?: (isOpen: boolean) => void;
|
|
12
|
-
closeOnClickOutside?: boolean;
|
|
13
|
-
closeOnEscape?: boolean;
|
|
14
|
-
id?: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
interface UseDropdownReturn {
|
|
18
|
-
isOpen: boolean;
|
|
19
|
-
setIsOpen: (isOpen: boolean) => void;
|
|
20
|
-
triggerRef: React.RefObject<HTMLElement | null>;
|
|
21
|
-
menuRef: React.RefObject<HTMLElement | null>;
|
|
22
|
-
dropdownId: string;
|
|
23
|
-
currentPlacement: DropdownPlacement;
|
|
24
|
-
updatePosition: () => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Hook for managing dropdown state and position
|
|
29
|
-
*/
|
|
30
|
-
export const useDropdown = ({
|
|
31
|
-
placement = DROPDOWN.DEFAULTS.PLACEMENT as DropdownPlacement,
|
|
32
|
-
trigger = DROPDOWN.DEFAULTS.TRIGGER as DropdownTrigger,
|
|
33
|
-
offset = DROPDOWN.DEFAULTS.OFFSET,
|
|
34
|
-
defaultOpen = false,
|
|
35
|
-
isOpen: controlledIsOpen,
|
|
36
|
-
onOpenChange,
|
|
37
|
-
closeOnClickOutside = true,
|
|
38
|
-
closeOnEscape = true,
|
|
39
|
-
id,
|
|
40
|
-
}: UseDropdownProps): UseDropdownReturn => {
|
|
41
|
-
// Generate unique ID for the dropdown menu
|
|
42
|
-
const uniqueId = useRef(`dropdown-${id || Math.random().toString(36).substring(2, 9)}`);
|
|
43
|
-
|
|
44
|
-
// Setup controlled vs uncontrolled state
|
|
45
|
-
const [uncontrolledIsOpen, setUncontrolledIsOpen] = useState(defaultOpen);
|
|
46
|
-
|
|
47
|
-
// Use either controlled or uncontrolled state
|
|
48
|
-
const isOpen = controlledIsOpen !== undefined ? controlledIsOpen : uncontrolledIsOpen;
|
|
49
|
-
|
|
50
|
-
// Callback to update open state with notification to parent
|
|
51
|
-
const setIsOpen = useCallback(
|
|
52
|
-
(nextIsOpen: boolean) => {
|
|
53
|
-
if (controlledIsOpen === undefined) {
|
|
54
|
-
setUncontrolledIsOpen(nextIsOpen);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (onOpenChange) {
|
|
58
|
-
onOpenChange(nextIsOpen);
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
[controlledIsOpen, onOpenChange]
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// Refs for trigger and dropdown menu elements
|
|
65
|
-
const triggerRef = useRef<HTMLElement>(null);
|
|
66
|
-
const menuRef = useRef<HTMLElement>(null);
|
|
67
|
-
|
|
68
|
-
// Current placement state
|
|
69
|
-
const [currentPlacement, setCurrentPlacement] = useState(placement);
|
|
70
|
-
|
|
71
|
-
// Handle click outside
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
if (!isOpen || !closeOnClickOutside) return undefined;
|
|
74
|
-
|
|
75
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
76
|
-
if (
|
|
77
|
-
menuRef.current &&
|
|
78
|
-
triggerRef.current &&
|
|
79
|
-
!menuRef.current.contains(event.target as Node) &&
|
|
80
|
-
!triggerRef.current.contains(event.target as Node)
|
|
81
|
-
) {
|
|
82
|
-
setIsOpen(false);
|
|
83
|
-
}
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
document.addEventListener('mousedown', handleClickOutside);
|
|
87
|
-
|
|
88
|
-
return () => {
|
|
89
|
-
document.removeEventListener('mousedown', handleClickOutside);
|
|
90
|
-
};
|
|
91
|
-
}, [isOpen, closeOnClickOutside, setIsOpen]);
|
|
92
|
-
|
|
93
|
-
// Handle escape key
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (!isOpen || !closeOnEscape) return undefined;
|
|
96
|
-
|
|
97
|
-
const handleEscapeKey = (event: KeyboardEvent) => {
|
|
98
|
-
if (event.key === 'Escape') {
|
|
99
|
-
setIsOpen(false);
|
|
100
|
-
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
document.addEventListener('keydown', handleEscapeKey);
|
|
104
|
-
|
|
105
|
-
return () => {
|
|
106
|
-
document.removeEventListener('keydown', handleEscapeKey);
|
|
107
|
-
};
|
|
108
|
-
}, [isOpen, closeOnEscape, setIsOpen]);
|
|
109
|
-
|
|
110
|
-
// Handle arrow key navigation
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (!isOpen || !menuRef.current) return undefined;
|
|
113
|
-
|
|
114
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
115
|
-
const menu = menuRef.current;
|
|
116
|
-
if (!menu) return;
|
|
117
|
-
|
|
118
|
-
const items = Array.from(
|
|
119
|
-
menu.querySelectorAll<HTMLElement>('[role="menuitem"]:not([disabled])')
|
|
120
|
-
);
|
|
121
|
-
if (items.length === 0) return;
|
|
122
|
-
|
|
123
|
-
const currentIndex = items.indexOf(document.activeElement as HTMLElement);
|
|
124
|
-
|
|
125
|
-
switch (event.key) {
|
|
126
|
-
case 'ArrowDown':
|
|
127
|
-
event.preventDefault();
|
|
128
|
-
const nextIndex = (currentIndex + 1) % items.length;
|
|
129
|
-
const nextItem = items[nextIndex];
|
|
130
|
-
if (nextItem) nextItem.focus();
|
|
131
|
-
break;
|
|
132
|
-
case 'ArrowUp':
|
|
133
|
-
event.preventDefault();
|
|
134
|
-
const prevIndex = (currentIndex - 1 + items.length) % items.length;
|
|
135
|
-
const prevItem = items[prevIndex];
|
|
136
|
-
if (prevItem) prevItem.focus();
|
|
137
|
-
break;
|
|
138
|
-
case 'Home':
|
|
139
|
-
event.preventDefault();
|
|
140
|
-
const firstItem = items[0];
|
|
141
|
-
if (firstItem) firstItem.focus();
|
|
142
|
-
break;
|
|
143
|
-
case 'End':
|
|
144
|
-
event.preventDefault();
|
|
145
|
-
const lastItem = items[items.length - 1];
|
|
146
|
-
if (lastItem) lastItem.focus();
|
|
147
|
-
break;
|
|
148
|
-
case 'Tab':
|
|
149
|
-
// Close dropdown on tab
|
|
150
|
-
setIsOpen(false);
|
|
151
|
-
break;
|
|
152
|
-
default:
|
|
153
|
-
break;
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
document.addEventListener('keydown', handleKeyDown);
|
|
158
|
-
|
|
159
|
-
return () => {
|
|
160
|
-
document.removeEventListener('keydown', handleKeyDown);
|
|
161
|
-
};
|
|
162
|
-
}, [isOpen, setIsOpen]);
|
|
163
|
-
|
|
164
|
-
// Focus management when dropdown opens
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
if (isOpen && menuRef.current) {
|
|
167
|
-
const firstItem = menuRef.current.querySelector<HTMLElement>(
|
|
168
|
-
'[role="menuitem"]:not([disabled])'
|
|
169
|
-
);
|
|
170
|
-
if (firstItem) {
|
|
171
|
-
setTimeout(() => firstItem.focus(), 0);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}, [isOpen]);
|
|
175
|
-
|
|
176
|
-
// Helper function to get the flipped placement if needed
|
|
177
|
-
const getFlippedPlacement = useCallback(
|
|
178
|
-
(
|
|
179
|
-
placement: DropdownPlacement,
|
|
180
|
-
triggerRect: DOMRect,
|
|
181
|
-
menuRect: DOMRect,
|
|
182
|
-
offset: number
|
|
183
|
-
): DropdownPlacement => {
|
|
184
|
-
const viewportWidth = window.innerWidth;
|
|
185
|
-
const viewportHeight = window.innerHeight;
|
|
186
|
-
|
|
187
|
-
// Start with the requested placement
|
|
188
|
-
let newPlacement = placement;
|
|
189
|
-
|
|
190
|
-
// Flip vertical placement if needed
|
|
191
|
-
if (
|
|
192
|
-
placement.startsWith('bottom') &&
|
|
193
|
-
triggerRect.bottom + menuRect.height + offset > viewportHeight
|
|
194
|
-
) {
|
|
195
|
-
newPlacement = placement.replace('bottom', 'top') as DropdownPlacement;
|
|
196
|
-
} else if (placement.startsWith('top') && triggerRect.top - menuRect.height - offset < 0) {
|
|
197
|
-
newPlacement = placement.replace('top', 'bottom') as DropdownPlacement;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Flip horizontal placement if needed
|
|
201
|
-
if (placement.startsWith('left') && triggerRect.left - menuRect.width - offset < 0) {
|
|
202
|
-
newPlacement = placement.replace('left', 'right') as DropdownPlacement;
|
|
203
|
-
} else if (
|
|
204
|
-
placement.startsWith('right') &&
|
|
205
|
-
triggerRect.right + menuRect.width + offset > viewportWidth
|
|
206
|
-
) {
|
|
207
|
-
newPlacement = placement.replace('right', 'left') as DropdownPlacement;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Adjust alignment for top/bottom placements
|
|
211
|
-
if (newPlacement.startsWith('top') || newPlacement.startsWith('bottom')) {
|
|
212
|
-
if (newPlacement.endsWith('start') && triggerRect.left + menuRect.width > viewportWidth) {
|
|
213
|
-
newPlacement = newPlacement.replace('start', 'end') as DropdownPlacement;
|
|
214
|
-
} else if (newPlacement.endsWith('end') && triggerRect.right - menuRect.width < 0) {
|
|
215
|
-
newPlacement = newPlacement.replace('end', 'start') as DropdownPlacement;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
return newPlacement;
|
|
220
|
-
},
|
|
221
|
-
[]
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// Helper function to calculate position based on placement
|
|
225
|
-
const calculatePosition = useCallback(
|
|
226
|
-
(
|
|
227
|
-
placement: DropdownPlacement,
|
|
228
|
-
triggerRect: DOMRect,
|
|
229
|
-
menuRect: DOMRect,
|
|
230
|
-
offset: number
|
|
231
|
-
): { top: number; left: number } => {
|
|
232
|
-
let top = 0;
|
|
233
|
-
let left = 0;
|
|
234
|
-
|
|
235
|
-
// Vertical positioning
|
|
236
|
-
if (placement.startsWith('bottom')) {
|
|
237
|
-
top = triggerRect.height + offset;
|
|
238
|
-
} else if (placement.startsWith('top')) {
|
|
239
|
-
top = -menuRect.height - offset;
|
|
240
|
-
} else if (placement.startsWith('left') || placement.startsWith('right')) {
|
|
241
|
-
top = triggerRect.height / 2 - menuRect.height / 2;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// Horizontal positioning
|
|
245
|
-
if (placement.startsWith('left')) {
|
|
246
|
-
left = -menuRect.width - offset;
|
|
247
|
-
} else if (placement.startsWith('right')) {
|
|
248
|
-
left = triggerRect.width + offset;
|
|
249
|
-
} else if (placement.endsWith('start')) {
|
|
250
|
-
left = 0;
|
|
251
|
-
} else if (placement.endsWith('end')) {
|
|
252
|
-
left = triggerRect.width - menuRect.width;
|
|
253
|
-
} else {
|
|
254
|
-
left = triggerRect.width / 2 - menuRect.width / 2;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
return { top, left };
|
|
258
|
-
},
|
|
259
|
-
[]
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
// Calculate and update dropdown position
|
|
263
|
-
const updatePosition = useCallback(() => {
|
|
264
|
-
if (!isOpen || !triggerRef.current || !menuRef.current) return;
|
|
265
|
-
|
|
266
|
-
const triggerRect = triggerRef.current.getBoundingClientRect();
|
|
267
|
-
const menuRect = menuRef.current.getBoundingClientRect();
|
|
268
|
-
|
|
269
|
-
// Get the optimal placement
|
|
270
|
-
const newPlacement = getFlippedPlacement(placement, triggerRect, menuRect, offset);
|
|
271
|
-
|
|
272
|
-
// Calculate position based on the new placement
|
|
273
|
-
const { top, left } = calculatePosition(newPlacement, triggerRect, menuRect, offset);
|
|
274
|
-
|
|
275
|
-
// Apply position
|
|
276
|
-
menuRef.current.style.position = 'absolute';
|
|
277
|
-
menuRef.current.style.top = `${top}px`;
|
|
278
|
-
menuRef.current.style.left = `${left}px`;
|
|
279
|
-
|
|
280
|
-
// Update placement state if it changed
|
|
281
|
-
if (newPlacement !== currentPlacement) {
|
|
282
|
-
setCurrentPlacement(newPlacement);
|
|
283
|
-
}
|
|
284
|
-
}, [isOpen, offset, placement, currentPlacement, getFlippedPlacement, calculatePosition]);
|
|
285
|
-
|
|
286
|
-
// Update position when menu is opened
|
|
287
|
-
useEffect(() => {
|
|
288
|
-
if (!isOpen) return undefined;
|
|
289
|
-
|
|
290
|
-
// Initial position update
|
|
291
|
-
updatePosition();
|
|
292
|
-
|
|
293
|
-
// Use ResizeObserver to detect size changes in the menu
|
|
294
|
-
let resizeObserver: ResizeObserver | null = null;
|
|
295
|
-
if (menuRef.current && typeof ResizeObserver !== 'undefined') {
|
|
296
|
-
resizeObserver = new ResizeObserver(() => {
|
|
297
|
-
requestAnimationFrame(updatePosition);
|
|
298
|
-
});
|
|
299
|
-
resizeObserver.observe(menuRef.current);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// Update position on resize/scroll
|
|
303
|
-
const handleResize = () => {
|
|
304
|
-
requestAnimationFrame(updatePosition);
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
const handleScroll = () => {
|
|
308
|
-
requestAnimationFrame(updatePosition);
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
window.addEventListener('resize', handleResize);
|
|
312
|
-
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
313
|
-
|
|
314
|
-
// Fallback for browsers without ResizeObserver or for dynamic content changes
|
|
315
|
-
// Use a less frequent interval (500ms instead of 200ms)
|
|
316
|
-
const intervalId = window.setInterval(updatePosition, 500);
|
|
317
|
-
|
|
318
|
-
return () => {
|
|
319
|
-
if (resizeObserver && menuRef.current) {
|
|
320
|
-
resizeObserver.unobserve(menuRef.current);
|
|
321
|
-
resizeObserver.disconnect();
|
|
322
|
-
}
|
|
323
|
-
window.removeEventListener('resize', handleResize);
|
|
324
|
-
window.removeEventListener('scroll', handleScroll);
|
|
325
|
-
window.clearInterval(intervalId);
|
|
326
|
-
};
|
|
327
|
-
}, [isOpen, updatePosition]);
|
|
328
|
-
|
|
329
|
-
return {
|
|
330
|
-
isOpen,
|
|
331
|
-
setIsOpen,
|
|
332
|
-
triggerRef,
|
|
333
|
-
menuRef,
|
|
334
|
-
dropdownId: uniqueId.current,
|
|
335
|
-
currentPlacement,
|
|
336
|
-
updatePosition,
|
|
337
|
-
};
|
|
338
|
-
};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
-
|
|
3
|
-
export interface UseModalProps {
|
|
4
|
-
/**
|
|
5
|
-
* Whether the modal is open
|
|
6
|
-
*/
|
|
7
|
-
isOpen?: boolean;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Callback when modal state changes
|
|
11
|
-
*/
|
|
12
|
-
onOpenChange?: (isOpen: boolean) => void;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Callback when modal opens
|
|
16
|
-
*/
|
|
17
|
-
onOpen?: () => void;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Callback when modal closes
|
|
21
|
-
*/
|
|
22
|
-
onClose?: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface UseModalReturn {
|
|
26
|
-
/**
|
|
27
|
-
* Current open state
|
|
28
|
-
*/
|
|
29
|
-
isOpen: boolean;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Function to open the modal
|
|
33
|
-
*/
|
|
34
|
-
open: () => void;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Function to close the modal
|
|
38
|
-
*/
|
|
39
|
-
close: () => void;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Function to toggle the modal
|
|
43
|
-
*/
|
|
44
|
-
toggle: () => void;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Hook for managing modal state
|
|
49
|
-
*/
|
|
50
|
-
export function useModal({
|
|
51
|
-
isOpen: isOpenProp,
|
|
52
|
-
onOpenChange,
|
|
53
|
-
onOpen,
|
|
54
|
-
onClose,
|
|
55
|
-
}: UseModalProps = {}): UseModalReturn {
|
|
56
|
-
// For uncontrolled usage
|
|
57
|
-
const [isOpenState, setIsOpenState] = useState(false);
|
|
58
|
-
|
|
59
|
-
// Determine if we're in controlled or uncontrolled mode
|
|
60
|
-
const isControlled = isOpenProp !== undefined;
|
|
61
|
-
const isOpen = isControlled ? !!isOpenProp : isOpenState;
|
|
62
|
-
|
|
63
|
-
// Update internal state when prop changes (for controlled mode)
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
if (isControlled) {
|
|
66
|
-
setIsOpenState(!!isOpenProp);
|
|
67
|
-
}
|
|
68
|
-
}, [isOpenProp, isControlled]);
|
|
69
|
-
|
|
70
|
-
const updateOpen = useCallback(
|
|
71
|
-
(nextIsOpen: boolean) => {
|
|
72
|
-
// For uncontrolled mode, update internal state
|
|
73
|
-
if (!isControlled) {
|
|
74
|
-
setIsOpenState(nextIsOpen);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Call the change handler in either mode
|
|
78
|
-
if (onOpenChange) {
|
|
79
|
-
onOpenChange(nextIsOpen);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Call the specific handler
|
|
83
|
-
if (nextIsOpen && onOpen) {
|
|
84
|
-
onOpen();
|
|
85
|
-
} else if (!nextIsOpen && onClose) {
|
|
86
|
-
onClose();
|
|
87
|
-
}
|
|
88
|
-
},
|
|
89
|
-
[isControlled, onOpenChange, onOpen, onClose]
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
const open = useCallback(() => {
|
|
93
|
-
updateOpen(true);
|
|
94
|
-
}, [updateOpen]);
|
|
95
|
-
|
|
96
|
-
const close = useCallback(() => {
|
|
97
|
-
updateOpen(false);
|
|
98
|
-
}, [updateOpen]);
|
|
99
|
-
|
|
100
|
-
const toggle = useCallback(() => {
|
|
101
|
-
updateOpen(!isOpen);
|
|
102
|
-
}, [isOpen, updateOpen]);
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
isOpen,
|
|
106
|
-
open,
|
|
107
|
-
close,
|
|
108
|
-
toggle,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { TypedButtonProps } from '../types/components';
|
|
2
|
-
import { TYPEDBUTTON } from '../constants/components';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* TypedButton state and functionality
|
|
6
|
-
* @param initialProps - Initial typedbutton properties
|
|
7
|
-
* @returns TypedButton state and methods
|
|
8
|
-
*/
|
|
9
|
-
export function useTypedButton(initialProps?: Partial<TypedButtonProps>) {
|
|
10
|
-
// Default typedbutton properties
|
|
11
|
-
const defaultProps: Partial<TypedButtonProps> = {
|
|
12
|
-
variant: 'primary',
|
|
13
|
-
size: 'md',
|
|
14
|
-
disabled: false,
|
|
15
|
-
...initialProps,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Generate typedbutton class based on properties
|
|
20
|
-
* @param props - TypedButton properties
|
|
21
|
-
* @returns Class string
|
|
22
|
-
*/
|
|
23
|
-
const generateClassNames = (props: Partial<TypedButtonProps> = {}): string => {
|
|
24
|
-
const {
|
|
25
|
-
variant = defaultProps.variant,
|
|
26
|
-
size = defaultProps.size,
|
|
27
|
-
disabled = defaultProps.disabled,
|
|
28
|
-
glass = defaultProps.glass,
|
|
29
|
-
className = '',
|
|
30
|
-
} = props;
|
|
31
|
-
|
|
32
|
-
const sizeClass = size === 'md' ? '' : `c-typedbutton--${size}`;
|
|
33
|
-
const disabledClass = disabled ? 'c-typedbutton--disabled' : '';
|
|
34
|
-
const glassClass = glass ? 'c-typedbutton--glass' : '';
|
|
35
|
-
|
|
36
|
-
return [
|
|
37
|
-
TYPEDBUTTON.BASE_CLASS,
|
|
38
|
-
`c-typedbutton--${variant}`,
|
|
39
|
-
sizeClass,
|
|
40
|
-
disabledClass,
|
|
41
|
-
glassClass,
|
|
42
|
-
className,
|
|
43
|
-
]
|
|
44
|
-
.filter(Boolean)
|
|
45
|
-
.join(' ');
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Handle typedbutton click with disabled check
|
|
50
|
-
* @param handler - Click handler function
|
|
51
|
-
* @returns Function that respects disabled state
|
|
52
|
-
*/
|
|
53
|
-
const handleClick = (handler?: (event: React.MouseEvent<HTMLDivElement>) => void) => {
|
|
54
|
-
return (event: React.MouseEvent<HTMLDivElement>) => {
|
|
55
|
-
if (!defaultProps.disabled && handler) {
|
|
56
|
-
handler(event);
|
|
57
|
-
}
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
defaultProps,
|
|
63
|
-
generateClassNames,
|
|
64
|
-
handleClick,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Performance Monitoring Hook
|
|
3
|
-
*
|
|
4
|
-
* Tracks component render times and re-render counts
|
|
5
|
-
* for performance analysis and optimization
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useEffect, useRef } from 'react';
|
|
9
|
-
|
|
10
|
-
export interface PerformanceMetrics {
|
|
11
|
-
/**
|
|
12
|
-
* Component name
|
|
13
|
-
*/
|
|
14
|
-
componentName: string;
|
|
15
|
-
/**
|
|
16
|
-
* Number of renders
|
|
17
|
-
*/
|
|
18
|
-
renderCount: number;
|
|
19
|
-
/**
|
|
20
|
-
* Average render time in milliseconds
|
|
21
|
-
*/
|
|
22
|
-
averageRenderTime: number;
|
|
23
|
-
/**
|
|
24
|
-
* Total render time in milliseconds
|
|
25
|
-
*/
|
|
26
|
-
totalRenderTime: number;
|
|
27
|
-
/**
|
|
28
|
-
* Maximum render time in milliseconds
|
|
29
|
-
*/
|
|
30
|
-
maxRenderTime: number;
|
|
31
|
-
/**
|
|
32
|
-
* Minimum render time in milliseconds
|
|
33
|
-
*/
|
|
34
|
-
minRenderTime: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Options for performance monitoring
|
|
39
|
-
*/
|
|
40
|
-
export interface UsePerformanceMonitorOptions {
|
|
41
|
-
/**
|
|
42
|
-
* Component name to track
|
|
43
|
-
*/
|
|
44
|
-
componentName: string;
|
|
45
|
-
/**
|
|
46
|
-
* Whether to log metrics to console (development only)
|
|
47
|
-
*/
|
|
48
|
-
logToConsole?: boolean;
|
|
49
|
-
/**
|
|
50
|
-
* Threshold in milliseconds to warn about slow renders
|
|
51
|
-
*/
|
|
52
|
-
warnThreshold?: number;
|
|
53
|
-
/**
|
|
54
|
-
* Callback to report metrics (e.g., to analytics)
|
|
55
|
-
*/
|
|
56
|
-
onMetrics?: (metrics: PerformanceMetrics) => void;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Hook to monitor component performance
|
|
61
|
-
*
|
|
62
|
-
* @param options - Performance monitoring options
|
|
63
|
-
* @returns Performance metrics
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* ```tsx
|
|
67
|
-
* function MyComponent() {
|
|
68
|
-
* usePerformanceMonitor({
|
|
69
|
-
* componentName: 'MyComponent',
|
|
70
|
-
* warnThreshold: 16, // Warn if render takes > 16ms (1 frame)
|
|
71
|
-
* });
|
|
72
|
-
*
|
|
73
|
-
* return <div>Content</div>;
|
|
74
|
-
* }
|
|
75
|
-
* ```
|
|
76
|
-
*/
|
|
77
|
-
export function usePerformanceMonitor(options: UsePerformanceMonitorOptions) {
|
|
78
|
-
const {
|
|
79
|
-
componentName,
|
|
80
|
-
logToConsole = typeof process === 'undefined' || process.env?.NODE_ENV === 'development',
|
|
81
|
-
warnThreshold = 16,
|
|
82
|
-
onMetrics,
|
|
83
|
-
} = options;
|
|
84
|
-
|
|
85
|
-
const metricsRef = useRef<PerformanceMetrics>({
|
|
86
|
-
componentName,
|
|
87
|
-
renderCount: 0,
|
|
88
|
-
averageRenderTime: 0,
|
|
89
|
-
totalRenderTime: 0,
|
|
90
|
-
maxRenderTime: 0,
|
|
91
|
-
minRenderTime: Infinity,
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
const renderStartRef = useRef<number>(0);
|
|
95
|
-
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
// Start timing the render
|
|
98
|
-
renderStartRef.current = performance.now();
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
// Calculate render time
|
|
103
|
-
const renderTime = performance.now() - renderStartRef.current;
|
|
104
|
-
const metrics = metricsRef.current;
|
|
105
|
-
|
|
106
|
-
// Update metrics
|
|
107
|
-
metrics.renderCount += 1;
|
|
108
|
-
metrics.totalRenderTime += renderTime;
|
|
109
|
-
metrics.averageRenderTime = metrics.totalRenderTime / metrics.renderCount;
|
|
110
|
-
metrics.maxRenderTime = Math.max(metrics.maxRenderTime, renderTime);
|
|
111
|
-
metrics.minRenderTime = Math.min(metrics.minRenderTime, renderTime);
|
|
112
|
-
|
|
113
|
-
// Warn if render is slow
|
|
114
|
-
if (renderTime > warnThreshold && logToConsole) {
|
|
115
|
-
console.warn(
|
|
116
|
-
`[Performance] ${componentName} render took ${renderTime.toFixed(2)}ms ` +
|
|
117
|
-
`(threshold: ${warnThreshold}ms)`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Log metrics in development
|
|
122
|
-
if (logToConsole && metrics.renderCount % 10 === 0) {
|
|
123
|
-
console.log(`[Performance] ${componentName} metrics:`, {
|
|
124
|
-
renderCount: metrics.renderCount,
|
|
125
|
-
averageRenderTime: metrics.averageRenderTime.toFixed(2) + 'ms',
|
|
126
|
-
maxRenderTime: metrics.maxRenderTime.toFixed(2) + 'ms',
|
|
127
|
-
minRenderTime: metrics.minRenderTime.toFixed(2) + 'ms',
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Report metrics via callback
|
|
132
|
-
if (onMetrics) {
|
|
133
|
-
onMetrics({ ...metrics });
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
return metricsRef.current;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Get all performance metrics for all monitored components
|
|
142
|
-
* (useful for debugging and analytics)
|
|
143
|
-
*/
|
|
144
|
-
export function getPerformanceMetrics(): PerformanceMetrics[] {
|
|
145
|
-
// This would need to be implemented with a global store
|
|
146
|
-
// For now, this is a placeholder
|
|
147
|
-
return [];
|
|
148
|
-
}
|