@shohojdhara/atomix 0.4.1 → 0.4.3
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/dist/atomix.css +9351 -9259
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +12 -19
- package/dist/charts.js +555 -358
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +21 -24
- package/dist/core.js +435 -265
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +11 -18
- package/dist/forms.js +411 -257
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -254
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +38 -41
- package/dist/index.esm.js +731 -487
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +733 -492
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -1
- package/scripts/atomix-cli.js +34 -1
- package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
- package/src/components/AtomixGlass/README.md +5 -5
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
- package/src/components/Badge/Badge.stories.tsx +1 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +90 -77
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/Button.tsx +2 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +2 -2
- package/src/components/Callout/README.md +2 -2
- package/src/components/Card/Card.tsx +31 -11
- package/src/components/Chart/Chart.stories.tsx +1 -1
- package/src/components/Chart/Chart.tsx +5 -5
- package/src/components/Chart/TreemapChart.tsx +37 -29
- package/src/components/DatePicker/readme.md +3 -3
- package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +276 -273
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/Footer/FooterLink.tsx +2 -2
- package/src/components/Form/Checkbox.stories.tsx +1 -1
- package/src/components/Form/Checkbox.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +1 -1
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/Radio.stories.tsx +1 -1
- package/src/components/Form/Radio.tsx +1 -1
- package/src/components/Form/Select.stories.tsx +1 -1
- package/src/components/Form/Select.tsx +1 -1
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +2 -2
- package/src/components/Hero/Hero.tsx +2 -2
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +1 -1
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- package/src/components/Navigation/Nav/NavItem.tsx +6 -3
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
- package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/Rating/Rating.stories.tsx +1 -1
- package/src/components/Rating/Rating.test.tsx +73 -0
- package/src/components/Rating/Rating.tsx +25 -37
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Steps/Steps.stories.tsx +1 -1
- package/src/components/Steps/Steps.tsx +2 -2
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Todo/Todo.tsx +0 -1
- package/src/components/Toggle/Toggle.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
- package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
- package/src/lib/composables/__tests__/useChart.test.ts +50 -0
- package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
- package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
- package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
- package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
- package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
- package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
- package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
- package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
- package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
- package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
- package/src/lib/composables/glass-styles.ts +302 -0
- package/src/lib/composables/index.ts +0 -4
- package/src/lib/composables/useAtomixGlass.ts +331 -522
- package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
- package/src/lib/composables/useBarChart.ts +1 -1
- package/src/lib/composables/useBreadcrumb.ts +6 -6
- package/src/lib/composables/useChart.ts +104 -21
- package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
- package/src/lib/composables/useSlider.ts +66 -34
- package/src/lib/theme/devtools/CLI.ts +1 -1
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +18 -21
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
- package/src/styles/02-tools/_tools.utility-api.scss +6 -6
- package/src/styles/06-components/_components.accordion.scss +0 -2
- package/src/styles/06-components/_components.chart.scss +0 -1
- package/src/styles/06-components/_components.dropdown.scss +0 -1
- package/src/styles/06-components/_components.edge-panel.scss +0 -2
- package/src/styles/06-components/_components.photoviewer.scss +0 -1
- package/src/styles/06-components/_components.river.scss +0 -1
- package/src/styles/06-components/_components.slider.scss +0 -3
- package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
- package/src/styles/99-utilities/_utilities.text.scss +1 -0
|
@@ -69,15 +69,15 @@ export const DropdownTrigger = forwardRef<HTMLDivElement, React.HTMLAttributes<H
|
|
|
69
69
|
// If we use <Dropdown.Trigger><Button/></Dropdown.Trigger>, we want the Button to be the trigger.
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
<div
|
|
73
|
+
ref={ref}
|
|
74
|
+
className={`c-dropdown__toggle ${className}`.trim()}
|
|
75
|
+
onClick={onClick}
|
|
76
|
+
onKeyDown={onKeyDown}
|
|
77
|
+
{...props}
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</div>
|
|
81
81
|
);
|
|
82
82
|
}
|
|
83
83
|
);
|
|
@@ -125,6 +125,7 @@ export const DropdownItem: React.FC<DropdownItemProps> = memo(
|
|
|
125
125
|
|
|
126
126
|
const linkProps = {
|
|
127
127
|
href,
|
|
128
|
+
to: href,
|
|
128
129
|
className: itemClasses,
|
|
129
130
|
onClick: handleClick,
|
|
130
131
|
role: 'menuitem',
|
|
@@ -173,6 +174,7 @@ export const DropdownItem: React.FC<DropdownItemProps> = memo(
|
|
|
173
174
|
);
|
|
174
175
|
}
|
|
175
176
|
);
|
|
177
|
+
DropdownItem.displayName = 'DropdownItem';
|
|
176
178
|
|
|
177
179
|
/**
|
|
178
180
|
* DropdownDivider component for separating groups of items
|
|
@@ -180,6 +182,7 @@ export const DropdownItem: React.FC<DropdownItemProps> = memo(
|
|
|
180
182
|
export const DropdownDivider: React.FC<DropdownDividerProps> = memo(({ className = '' }) => {
|
|
181
183
|
return <li className={`c-dropdown__divider ${className}`} role="separator" />;
|
|
182
184
|
});
|
|
185
|
+
DropdownDivider.displayName = 'DropdownDivider';
|
|
183
186
|
|
|
184
187
|
/**
|
|
185
188
|
* DropdownHeader component for section headers
|
|
@@ -189,6 +192,7 @@ export const DropdownHeader: React.FC<DropdownHeaderProps> = memo(
|
|
|
189
192
|
return <li className={`c-dropdown__header ${className}`}>{children}</li>;
|
|
190
193
|
}
|
|
191
194
|
);
|
|
195
|
+
DropdownHeader.displayName = 'DropdownHeader';
|
|
192
196
|
|
|
193
197
|
// Helper context to pass glass prop to DropdownMenu
|
|
194
198
|
const DropdownStyleContext = createContext<{ glass?: AtomixGlassProps | boolean }>({});
|
|
@@ -204,298 +208,297 @@ type DropdownComponent = React.FC<DropdownProps> & {
|
|
|
204
208
|
Header: typeof DropdownHeader;
|
|
205
209
|
};
|
|
206
210
|
|
|
207
|
-
export const Dropdown: DropdownComponent = memo(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
};
|
|
211
|
+
export const Dropdown: DropdownComponent = memo(function DropdownBase({
|
|
212
|
+
children,
|
|
213
|
+
menu,
|
|
214
|
+
placement = 'bottom-start',
|
|
215
|
+
trigger = 'click',
|
|
216
|
+
offset = DROPDOWN.DEFAULTS.OFFSET,
|
|
217
|
+
isOpen: controlledIsOpen,
|
|
218
|
+
onOpenChange,
|
|
219
|
+
closeOnClickOutside = true,
|
|
220
|
+
closeOnEscape = true,
|
|
221
|
+
maxHeight,
|
|
222
|
+
minWidth = DROPDOWN.DEFAULTS.MIN_WIDTH,
|
|
223
|
+
variant,
|
|
224
|
+
className = '',
|
|
225
|
+
style,
|
|
226
|
+
glass,
|
|
227
|
+
...props
|
|
228
|
+
}: DropdownProps) {
|
|
229
|
+
// Set up controlled vs uncontrolled state
|
|
230
|
+
const [uncontrolledIsOpen, setUncontrolledIsOpen] = useState(false);
|
|
231
|
+
const isControlled = controlledIsOpen !== undefined;
|
|
232
|
+
const isOpen = isControlled ? controlledIsOpen : uncontrolledIsOpen;
|
|
233
|
+
|
|
234
|
+
// Create refs
|
|
235
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
236
|
+
const toggleRef = useRef<HTMLDivElement>(null);
|
|
237
|
+
const menuRef = useRef<HTMLDivElement>(null);
|
|
238
|
+
|
|
239
|
+
// Generate unique ID
|
|
240
|
+
const dropdownId = useRef(`dropdown-${Math.random().toString(36).substring(2, 9)}`).current;
|
|
241
|
+
|
|
242
|
+
// State change handlers
|
|
243
|
+
const setIsOpen = useCallback(
|
|
244
|
+
(nextIsOpen: boolean) => {
|
|
245
|
+
if (!isControlled) {
|
|
246
|
+
setUncontrolledIsOpen(nextIsOpen);
|
|
247
|
+
}
|
|
248
|
+
if (onOpenChange) {
|
|
249
|
+
onOpenChange(nextIsOpen);
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
[isControlled, onOpenChange]
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
const toggle = useCallback(() => setIsOpen(!isOpen), [isOpen, setIsOpen]);
|
|
256
|
+
|
|
257
|
+
const close = useCallback(() => {
|
|
258
|
+
setIsOpen(false);
|
|
259
|
+
// Return focus to the toggle button after closing
|
|
260
|
+
setTimeout(() => {
|
|
261
|
+
toggleRef.current?.focus();
|
|
262
|
+
}, 0);
|
|
263
|
+
}, [setIsOpen]);
|
|
264
|
+
|
|
265
|
+
// Click outside handler
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
if (!isOpen || !closeOnClickOutside) return undefined;
|
|
268
|
+
|
|
269
|
+
const handleClickOutside = (e: MouseEvent) => {
|
|
270
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
|
271
|
+
close();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
271
274
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
276
|
+
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
277
|
+
}, [isOpen, closeOnClickOutside, close]);
|
|
275
278
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
+
// Escape key handler
|
|
280
|
+
useEffect(() => {
|
|
281
|
+
if (!isOpen || !closeOnEscape) return undefined;
|
|
279
282
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
283
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
284
|
+
if (e.key === 'Escape') {
|
|
285
|
+
close();
|
|
286
|
+
}
|
|
287
|
+
};
|
|
285
288
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
290
|
+
return () => document.removeEventListener('keydown', handleKeyDown);
|
|
291
|
+
}, [isOpen, closeOnEscape, close]);
|
|
289
292
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
// Keyboard navigation
|
|
294
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
295
|
+
if (!menuRef.current) return;
|
|
293
296
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
297
|
+
const focusableItems = menuRef.current.querySelectorAll<HTMLElement>(
|
|
298
|
+
'[role="menuitem"]:not([disabled])'
|
|
299
|
+
);
|
|
300
|
+
if (!focusableItems.length) return;
|
|
298
301
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
+
const currentIndex = Array.from(focusableItems).findIndex(
|
|
303
|
+
item => item === document.activeElement
|
|
304
|
+
);
|
|
302
305
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
focusableItems[0]?.focus();
|
|
310
|
-
}
|
|
311
|
-
break;
|
|
312
|
-
|
|
313
|
-
case 'ArrowUp':
|
|
314
|
-
e.preventDefault();
|
|
315
|
-
if (currentIndex > 0) {
|
|
316
|
-
focusableItems[currentIndex - 1]?.focus();
|
|
317
|
-
} else {
|
|
318
|
-
focusableItems[focusableItems.length - 1]?.focus();
|
|
319
|
-
}
|
|
320
|
-
break;
|
|
321
|
-
|
|
322
|
-
case 'Home':
|
|
323
|
-
e.preventDefault();
|
|
306
|
+
switch (e.key) {
|
|
307
|
+
case 'ArrowDown':
|
|
308
|
+
e.preventDefault();
|
|
309
|
+
if (currentIndex < focusableItems.length - 1) {
|
|
310
|
+
focusableItems[currentIndex + 1]?.focus();
|
|
311
|
+
} else {
|
|
324
312
|
focusableItems[0]?.focus();
|
|
325
|
-
|
|
313
|
+
}
|
|
314
|
+
break;
|
|
326
315
|
|
|
327
|
-
|
|
328
|
-
|
|
316
|
+
case 'ArrowUp':
|
|
317
|
+
e.preventDefault();
|
|
318
|
+
if (currentIndex > 0) {
|
|
319
|
+
focusableItems[currentIndex - 1]?.focus();
|
|
320
|
+
} else {
|
|
329
321
|
focusableItems[focusableItems.length - 1]?.focus();
|
|
330
|
-
break;
|
|
331
|
-
}
|
|
332
|
-
}, []);
|
|
333
|
-
|
|
334
|
-
// Event handlers
|
|
335
|
-
const handleToggleClick = useCallback(
|
|
336
|
-
(e: React.MouseEvent) => {
|
|
337
|
-
if (trigger === 'click') {
|
|
338
|
-
e.preventDefault();
|
|
339
|
-
e.stopPropagation();
|
|
340
|
-
toggle();
|
|
341
322
|
}
|
|
342
|
-
|
|
343
|
-
[trigger, toggle]
|
|
344
|
-
);
|
|
323
|
+
break;
|
|
345
324
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
setIsOpen(true);
|
|
351
|
-
|
|
352
|
-
// Only focus the first menu item when using keyboard navigation
|
|
353
|
-
if (e.key === 'ArrowDown' && menuRef.current) {
|
|
354
|
-
setTimeout(() => {
|
|
355
|
-
const firstItem = menuRef.current?.querySelector<HTMLElement>('[role="menuitem"]');
|
|
356
|
-
firstItem?.focus();
|
|
357
|
-
}, 100);
|
|
358
|
-
}
|
|
359
|
-
} else if (e.key === 'Escape' && isOpen) {
|
|
360
|
-
e.preventDefault();
|
|
361
|
-
close();
|
|
362
|
-
}
|
|
363
|
-
},
|
|
364
|
-
[isOpen, setIsOpen, close]
|
|
365
|
-
);
|
|
325
|
+
case 'Home':
|
|
326
|
+
e.preventDefault();
|
|
327
|
+
focusableItems[0]?.focus();
|
|
328
|
+
break;
|
|
366
329
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
330
|
+
case 'End':
|
|
331
|
+
e.preventDefault();
|
|
332
|
+
focusableItems[focusableItems.length - 1]?.focus();
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
}, []);
|
|
336
|
+
|
|
337
|
+
// Event handlers
|
|
338
|
+
const handleToggleClick = useCallback(
|
|
339
|
+
(e: React.MouseEvent) => {
|
|
340
|
+
if (trigger === 'click') {
|
|
341
|
+
e.preventDefault();
|
|
342
|
+
e.stopPropagation();
|
|
343
|
+
toggle();
|
|
371
344
|
}
|
|
372
|
-
},
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
className,
|
|
382
|
-
]
|
|
383
|
-
.filter(Boolean)
|
|
384
|
-
.join(' ');
|
|
345
|
+
},
|
|
346
|
+
[trigger, toggle]
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const handleToggleKeyDown = useCallback(
|
|
350
|
+
(e: React.KeyboardEvent) => {
|
|
351
|
+
if ((e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') && !isOpen) {
|
|
352
|
+
e.preventDefault();
|
|
353
|
+
setIsOpen(true);
|
|
385
354
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
355
|
+
// Only focus the first menu item when using keyboard navigation
|
|
356
|
+
if (e.key === 'ArrowDown' && menuRef.current) {
|
|
357
|
+
setTimeout(() => {
|
|
358
|
+
const firstItem = menuRef.current?.querySelector<HTMLElement>('[role="menuitem"]');
|
|
359
|
+
firstItem?.focus();
|
|
360
|
+
}, 100);
|
|
361
|
+
}
|
|
362
|
+
} else if (e.key === 'Escape' && isOpen) {
|
|
363
|
+
e.preventDefault();
|
|
364
|
+
close();
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
[isOpen, setIsOpen, close]
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
// Hover handlers for trigger="hover"
|
|
371
|
+
const handleHoverOpen = useCallback(() => {
|
|
372
|
+
if (trigger === 'hover') {
|
|
373
|
+
setIsOpen(true);
|
|
391
374
|
}
|
|
375
|
+
}, [trigger, setIsOpen]);
|
|
376
|
+
|
|
377
|
+
// Build class names
|
|
378
|
+
const dropdownClasses = [
|
|
379
|
+
'c-dropdown',
|
|
380
|
+
trigger === 'click' ? 'c-dropdown--onclick' : '',
|
|
381
|
+
variant ? `c-dropdown--${variant}` : '',
|
|
382
|
+
isOpen ? 'is-open' : '',
|
|
383
|
+
glass ? 'c-dropdown--glass' : '',
|
|
384
|
+
className,
|
|
385
|
+
]
|
|
386
|
+
.filter(Boolean)
|
|
387
|
+
.join(' ');
|
|
388
|
+
|
|
389
|
+
// Menu styles
|
|
390
|
+
const menuStyleProps: React.CSSProperties = {};
|
|
391
|
+
if (maxHeight) menuStyleProps.maxHeight = maxHeight;
|
|
392
|
+
if (minWidth !== undefined) {
|
|
393
|
+
menuStyleProps.minWidth = typeof minWidth === 'number' ? `${minWidth}px` : minWidth;
|
|
394
|
+
}
|
|
392
395
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
+
// Determine content structure
|
|
397
|
+
// Legacy: menu prop + children as trigger
|
|
398
|
+
// Compound: children contains Trigger and Menu
|
|
396
399
|
|
|
397
|
-
|
|
400
|
+
const hasCompoundComponents = React.Children.toArray(children).some(
|
|
401
|
+
child =>
|
|
398
402
|
React.isValidElement(child) &&
|
|
399
403
|
['DropdownTrigger', 'DropdownMenu'].includes((child.type as any).displayName)
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
let triggerContent: ReactNode;
|
|
407
|
+
let menuContentNode: ReactNode;
|
|
408
|
+
|
|
409
|
+
if (hasCompoundComponents) {
|
|
410
|
+
// Find Trigger and Menu in children
|
|
411
|
+
React.Children.forEach(children, child => {
|
|
412
|
+
if (React.isValidElement(child)) {
|
|
413
|
+
if ((child.type as any).displayName === 'DropdownTrigger') {
|
|
414
|
+
triggerContent = React.cloneElement(child, {
|
|
415
|
+
ref: toggleRef,
|
|
416
|
+
onClick: (e: React.MouseEvent) => {
|
|
417
|
+
handleToggleClick(e);
|
|
418
|
+
(child.props as any).onClick?.(e);
|
|
419
|
+
},
|
|
420
|
+
onKeyDown: (e: React.KeyboardEvent) => {
|
|
421
|
+
handleToggleKeyDown(e);
|
|
422
|
+
(child.props as any).onKeyDown?.(e);
|
|
423
|
+
},
|
|
424
|
+
'aria-haspopup': 'menu',
|
|
425
|
+
'aria-expanded': isOpen,
|
|
426
|
+
'aria-controls': dropdownId,
|
|
427
|
+
tabIndex: 0,
|
|
428
|
+
} as any);
|
|
429
|
+
} else if ((child.type as any).displayName === 'DropdownMenu') {
|
|
430
|
+
menuContentNode = child;
|
|
428
431
|
}
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
);
|
|
446
|
-
menuContentNode = (
|
|
447
|
-
<ul className={`c-dropdown__menu ${glass ? 'c-dropdown__menu--glass' : ''}`}>{menu}</ul>
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
const menuContent = (
|
|
452
|
-
<div className="c-dropdown__menu-inner" style={menuStyleProps}>
|
|
453
|
-
<DropdownStyleContext.Provider value={{ glass }}>
|
|
454
|
-
<DropdownContext.Provider value={{ isOpen, close, id: dropdownId, trigger }}>
|
|
455
|
-
{menuContentNode}
|
|
456
|
-
</DropdownContext.Provider>
|
|
457
|
-
</DropdownStyleContext.Provider>
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
} else {
|
|
435
|
+
// Legacy mode
|
|
436
|
+
triggerContent = (
|
|
437
|
+
<div
|
|
438
|
+
ref={toggleRef}
|
|
439
|
+
className="c-dropdown__toggle"
|
|
440
|
+
onClick={handleToggleClick}
|
|
441
|
+
onKeyDown={handleToggleKeyDown}
|
|
442
|
+
aria-haspopup="menu"
|
|
443
|
+
aria-expanded={isOpen}
|
|
444
|
+
aria-controls={dropdownId}
|
|
445
|
+
tabIndex={0}
|
|
446
|
+
>
|
|
447
|
+
{children}
|
|
458
448
|
</div>
|
|
459
449
|
);
|
|
450
|
+
menuContentNode = (
|
|
451
|
+
<ul className={`c-dropdown__menu ${glass ? 'c-dropdown__menu--glass' : ''}`}>{menu}</ul>
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const menuContent = (
|
|
456
|
+
<div className="c-dropdown__menu-inner" style={menuStyleProps}>
|
|
457
|
+
<DropdownStyleContext.Provider value={{ glass }}>
|
|
458
|
+
<DropdownContext.Provider value={{ isOpen, close, id: dropdownId, trigger }}>
|
|
459
|
+
{menuContentNode}
|
|
460
|
+
</DropdownContext.Provider>
|
|
461
|
+
</DropdownStyleContext.Provider>
|
|
462
|
+
</div>
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
<div
|
|
467
|
+
ref={dropdownRef}
|
|
468
|
+
className={dropdownClasses}
|
|
469
|
+
style={style}
|
|
470
|
+
onMouseEnter={trigger === 'hover' ? handleHoverOpen : undefined}
|
|
471
|
+
{...props}
|
|
472
|
+
>
|
|
473
|
+
{triggerContent}
|
|
460
474
|
|
|
461
|
-
return (
|
|
462
475
|
<div
|
|
463
|
-
ref={
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
476
|
+
ref={menuRef}
|
|
477
|
+
id={dropdownId}
|
|
478
|
+
className={`c-dropdown__menu-wrapper c-dropdown__menu-wrapper--${placement} ${isOpen ? 'is-open' : ''} ${glass ? 'is-glass' : ''}`}
|
|
479
|
+
role="menu"
|
|
480
|
+
aria-orientation="vertical"
|
|
481
|
+
aria-hidden={!isOpen}
|
|
482
|
+
onKeyDown={handleKeyDown}
|
|
468
483
|
>
|
|
469
|
-
{
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
const defaultGlassProps = {
|
|
484
|
-
displacementScale: 20,
|
|
485
|
-
elasticity: 0,
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
const glassProps =
|
|
489
|
-
glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
490
|
-
|
|
491
|
-
return <AtomixGlass {...glassProps}>{menuContent}</AtomixGlass>;
|
|
492
|
-
})()
|
|
493
|
-
: menuContent}
|
|
494
|
-
</div>
|
|
484
|
+
{glass
|
|
485
|
+
? // Default glass settings for dropdowns
|
|
486
|
+
(() => {
|
|
487
|
+
const defaultGlassProps = {
|
|
488
|
+
displacementScale: 20,
|
|
489
|
+
elasticity: 0,
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
const glassProps =
|
|
493
|
+
glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
494
|
+
|
|
495
|
+
return <AtomixGlass {...glassProps}>{menuContent}</AtomixGlass>;
|
|
496
|
+
})()
|
|
497
|
+
: menuContent}
|
|
495
498
|
</div>
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
) as unknown as DropdownComponent;
|
|
499
|
+
</div>
|
|
500
|
+
);
|
|
501
|
+
}) as unknown as DropdownComponent;
|
|
499
502
|
|
|
500
503
|
export type { DropdownProps, DropdownItemProps, DropdownDividerProps, DropdownHeaderProps };
|
|
501
504
|
|