@shohojdhara/atomix 0.4.0 → 0.4.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/dist/atomix.css +0 -14
- 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 -359
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +98 -28
- package/dist/core.js +1082 -733
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +26 -21
- package/dist/forms.js +937 -350
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -256
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +518 -284
- package/dist/index.esm.js +1993 -1237
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1994 -1237
- 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 +2 -2
- package/scripts/atomix-cli.js +43 -1
- package/scripts/cli/__tests__/utils.test.js +6 -2
- package/scripts/cli/migration-tools.js +2 -2
- package/scripts/cli/theme-bridge.js +7 -9
- package/scripts/cli/utils.js +2 -1
- package/src/components/Accordion/Accordion.stories.tsx +40 -0
- package/src/components/Accordion/Accordion.tsx +174 -56
- package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
- 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 +185 -65
- package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.stories.tsx +166 -1011
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +196 -84
- package/src/components/Callout/CalloutCompound.test.tsx +72 -0
- package/src/components/Callout/README.md +2 -2
- 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 +133 -20
- package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/EdgePanel/EdgePanel.tsx +164 -112
- package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
- 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 +24 -1
- package/src/components/Form/Select.test.tsx +99 -0
- package/src/components/Form/Select.tsx +145 -94
- package/src/components/Form/SelectOption.tsx +88 -0
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +39 -2
- package/src/components/Hero/Hero.test.tsx +142 -0
- package/src/components/Hero/Hero.tsx +143 -4
- package/src/components/List/List.test.tsx +62 -0
- package/src/components/List/List.tsx +16 -5
- package/src/components/List/ListItem.tsx +20 -0
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +66 -2
- package/src/components/Modal/Modal.tsx +115 -35
- package/src/components/Modal/ModalCompound.test.tsx +94 -0
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- 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 +125 -22
- package/src/components/Steps/StepsCompound.test.tsx +81 -0
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +198 -45
- package/src/components/Tabs/TabsCompound.test.tsx +64 -0
- 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 -8
- package/src/lib/composables/useAtomixGlass.ts +331 -537
- 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 +2 -10
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +21 -23
- package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
- package/src/lib/utils/themeNaming.ts +1 -1
- 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
|
@@ -6,6 +6,8 @@ import React, {
|
|
|
6
6
|
useContext,
|
|
7
7
|
useEffect,
|
|
8
8
|
memo,
|
|
9
|
+
forwardRef,
|
|
10
|
+
ReactNode,
|
|
9
11
|
} from 'react';
|
|
10
12
|
import { DROPDOWN } from '../../lib/constants/components';
|
|
11
13
|
import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
@@ -14,6 +16,7 @@ import type {
|
|
|
14
16
|
DropdownItemProps,
|
|
15
17
|
DropdownDividerProps,
|
|
16
18
|
DropdownHeaderProps,
|
|
19
|
+
AtomixGlassProps,
|
|
17
20
|
} from '../../lib/types/components';
|
|
18
21
|
|
|
19
22
|
// Context type definition
|
|
@@ -32,6 +35,54 @@ const DropdownContext = createContext<DropdownContextType>({
|
|
|
32
35
|
trigger: 'click',
|
|
33
36
|
});
|
|
34
37
|
|
|
38
|
+
// Compound Components
|
|
39
|
+
|
|
40
|
+
export const DropdownMenu = forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
|
|
41
|
+
({ children, className = '', ...props }, ref) => {
|
|
42
|
+
const { glass } = useContext(DropdownStyleContext); // We need to access glass prop here?
|
|
43
|
+
// Wait, the original code wrapped <ul> in Context Provider.
|
|
44
|
+
// And applied glass wrapper around <ul>.
|
|
45
|
+
// If we use Compound Component, DropdownMenu should be the list.
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<ul
|
|
49
|
+
ref={ref}
|
|
50
|
+
className={`c-dropdown__menu ${glass ? 'c-dropdown__menu--glass' : ''} ${className}`.trim()}
|
|
51
|
+
{...props}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</ul>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
DropdownMenu.displayName = 'DropdownMenu';
|
|
59
|
+
|
|
60
|
+
export const DropdownTrigger = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
61
|
+
({ children, className = '', onClick, onKeyDown, ...props }, ref) => {
|
|
62
|
+
// We need to inject the trigger logic here.
|
|
63
|
+
// But triggers are usually handled by the parent Dropdown in the original code.
|
|
64
|
+
// The original code wraps children in `c-dropdown__toggle` div.
|
|
65
|
+
|
|
66
|
+
// Ideally, DropdownTrigger allows user to customize the trigger element.
|
|
67
|
+
// For backward compat, Dropdown wraps `children` (legacy) in `c-dropdown__toggle`.
|
|
68
|
+
|
|
69
|
+
// If we use <Dropdown.Trigger><Button/></Dropdown.Trigger>, we want the Button to be the trigger.
|
|
70
|
+
|
|
71
|
+
return (
|
|
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
|
+
);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
DropdownTrigger.displayName = 'DropdownTrigger';
|
|
85
|
+
|
|
35
86
|
/**
|
|
36
87
|
* DropdownItem component for menu items
|
|
37
88
|
*/
|
|
@@ -139,10 +190,21 @@ export const DropdownHeader: React.FC<DropdownHeaderProps> = memo(
|
|
|
139
190
|
}
|
|
140
191
|
);
|
|
141
192
|
|
|
193
|
+
// Helper context to pass glass prop to DropdownMenu
|
|
194
|
+
const DropdownStyleContext = createContext<{ glass?: AtomixGlassProps | boolean }>({});
|
|
195
|
+
|
|
142
196
|
/**
|
|
143
197
|
* Dropdown component for creating dropdown menus
|
|
144
198
|
*/
|
|
145
|
-
|
|
199
|
+
type DropdownComponent = React.FC<DropdownProps> & {
|
|
200
|
+
Trigger: typeof DropdownTrigger;
|
|
201
|
+
Menu: typeof DropdownMenu;
|
|
202
|
+
Item: typeof DropdownItem;
|
|
203
|
+
Divider: typeof DropdownDivider;
|
|
204
|
+
Header: typeof DropdownHeader;
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
export const Dropdown: DropdownComponent = memo(
|
|
146
208
|
({
|
|
147
209
|
children,
|
|
148
210
|
menu,
|
|
@@ -160,7 +222,7 @@ export const Dropdown: React.FC<DropdownProps> = memo(
|
|
|
160
222
|
style,
|
|
161
223
|
glass,
|
|
162
224
|
...props
|
|
163
|
-
}) => {
|
|
225
|
+
}: DropdownProps) => {
|
|
164
226
|
// Set up controlled vs uncontrolled state
|
|
165
227
|
const [uncontrolledIsOpen, setUncontrolledIsOpen] = useState(false);
|
|
166
228
|
const isControlled = controlledIsOpen !== undefined;
|
|
@@ -328,22 +390,46 @@ export const Dropdown: React.FC<DropdownProps> = memo(
|
|
|
328
390
|
menuStyleProps.minWidth = typeof minWidth === 'number' ? `${minWidth}px` : minWidth;
|
|
329
391
|
}
|
|
330
392
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
393
|
+
// Determine content structure
|
|
394
|
+
// Legacy: menu prop + children as trigger
|
|
395
|
+
// Compound: children contains Trigger and Menu
|
|
396
|
+
|
|
397
|
+
const hasCompoundComponents = React.Children.toArray(children).some((child) =>
|
|
398
|
+
React.isValidElement(child) &&
|
|
399
|
+
['DropdownTrigger', 'DropdownMenu'].includes((child.type as any).displayName)
|
|
337
400
|
);
|
|
338
401
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
{
|
|
346
|
-
|
|
402
|
+
let triggerContent: ReactNode;
|
|
403
|
+
let menuContentNode: ReactNode;
|
|
404
|
+
|
|
405
|
+
if (hasCompoundComponents) {
|
|
406
|
+
// Find Trigger and Menu in children
|
|
407
|
+
React.Children.forEach(children, (child) => {
|
|
408
|
+
if (React.isValidElement(child)) {
|
|
409
|
+
if ((child.type as any).displayName === 'DropdownTrigger') {
|
|
410
|
+
triggerContent = React.cloneElement(child, {
|
|
411
|
+
ref: toggleRef,
|
|
412
|
+
onClick: (e: React.MouseEvent) => {
|
|
413
|
+
handleToggleClick(e);
|
|
414
|
+
(child.props as any).onClick?.(e);
|
|
415
|
+
},
|
|
416
|
+
onKeyDown: (e: React.KeyboardEvent) => {
|
|
417
|
+
handleToggleKeyDown(e);
|
|
418
|
+
(child.props as any).onKeyDown?.(e);
|
|
419
|
+
},
|
|
420
|
+
'aria-haspopup': 'menu',
|
|
421
|
+
'aria-expanded': isOpen,
|
|
422
|
+
'aria-controls': dropdownId,
|
|
423
|
+
tabIndex: 0,
|
|
424
|
+
} as any);
|
|
425
|
+
} else if ((child.type as any).displayName === 'DropdownMenu') {
|
|
426
|
+
menuContentNode = child;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
} else {
|
|
431
|
+
// Legacy mode
|
|
432
|
+
triggerContent = (
|
|
347
433
|
<div
|
|
348
434
|
ref={toggleRef}
|
|
349
435
|
className="c-dropdown__toggle"
|
|
@@ -356,6 +442,31 @@ export const Dropdown: React.FC<DropdownProps> = memo(
|
|
|
356
442
|
>
|
|
357
443
|
{children}
|
|
358
444
|
</div>
|
|
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>
|
|
458
|
+
</div>
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
return (
|
|
462
|
+
<div
|
|
463
|
+
ref={dropdownRef}
|
|
464
|
+
className={dropdownClasses}
|
|
465
|
+
style={style}
|
|
466
|
+
onMouseEnter={trigger === 'hover' ? handleHoverOpen : undefined}
|
|
467
|
+
{...props}
|
|
468
|
+
>
|
|
469
|
+
{triggerContent}
|
|
359
470
|
|
|
360
471
|
<div
|
|
361
472
|
ref={menuRef}
|
|
@@ -384,13 +495,15 @@ export const Dropdown: React.FC<DropdownProps> = memo(
|
|
|
384
495
|
</div>
|
|
385
496
|
);
|
|
386
497
|
}
|
|
387
|
-
);
|
|
498
|
+
) as unknown as DropdownComponent;
|
|
388
499
|
|
|
389
500
|
export type { DropdownProps, DropdownItemProps, DropdownDividerProps, DropdownHeaderProps };
|
|
390
501
|
|
|
391
502
|
Dropdown.displayName = 'Dropdown';
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
503
|
+
Dropdown.Trigger = DropdownTrigger;
|
|
504
|
+
Dropdown.Menu = DropdownMenu;
|
|
505
|
+
Dropdown.Item = DropdownItem;
|
|
506
|
+
Dropdown.Divider = DropdownDivider;
|
|
507
|
+
Dropdown.Header = DropdownHeader;
|
|
395
508
|
|
|
396
509
|
export default Dropdown;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { Dropdown } from './Dropdown';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
describe('Dropdown Component', () => {
|
|
7
|
+
it('renders correctly with legacy props', () => {
|
|
8
|
+
// In legacy mode, `menu` prop content is rendered inside the dropdown wrapper.
|
|
9
|
+
// The dropdown wrapper uses CSS visibility/opacity/display to hide the menu when not open.
|
|
10
|
+
// `toBeVisible` checks if the element is visible to the user.
|
|
11
|
+
// However, if the menu is just visually hidden via CSS classes (e.g. opacity: 0),
|
|
12
|
+
// jest-dom might consider it visible if display is not none and visibility is not hidden.
|
|
13
|
+
// Let's check if the wrapper has `is-open` class.
|
|
14
|
+
|
|
15
|
+
const { container } = render(
|
|
16
|
+
<Dropdown menu={<Dropdown.Item>Item 1</Dropdown.Item>}>
|
|
17
|
+
<button>Trigger</button>
|
|
18
|
+
</Dropdown>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
expect(screen.getByText('Trigger')).toBeInTheDocument();
|
|
22
|
+
|
|
23
|
+
// Check if the menu wrapper exists but does not have 'is-open' class
|
|
24
|
+
const menuWrapper = container.querySelector('.c-dropdown__menu-wrapper');
|
|
25
|
+
expect(menuWrapper).not.toHaveClass('is-open');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('renders correctly with compound components', () => {
|
|
29
|
+
render(
|
|
30
|
+
<Dropdown>
|
|
31
|
+
<Dropdown.Trigger>
|
|
32
|
+
<button>Compound Trigger</button>
|
|
33
|
+
</Dropdown.Trigger>
|
|
34
|
+
<Dropdown.Menu>
|
|
35
|
+
<Dropdown.Item>Item 1</Dropdown.Item>
|
|
36
|
+
</Dropdown.Menu>
|
|
37
|
+
</Dropdown>
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
expect(screen.getByText('Compound Trigger')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('toggles menu in compound mode', () => {
|
|
44
|
+
const { container } = render(
|
|
45
|
+
<Dropdown>
|
|
46
|
+
<Dropdown.Trigger>
|
|
47
|
+
<button>Trigger</button>
|
|
48
|
+
</Dropdown.Trigger>
|
|
49
|
+
<Dropdown.Menu>
|
|
50
|
+
<Dropdown.Item>Item 1</Dropdown.Item>
|
|
51
|
+
</Dropdown.Menu>
|
|
52
|
+
</Dropdown>
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
fireEvent.click(screen.getByText('Trigger'));
|
|
56
|
+
|
|
57
|
+
// Check if open class is applied or aria-expanded
|
|
58
|
+
const trigger = screen.getByText('Trigger').closest('.c-dropdown__toggle');
|
|
59
|
+
expect(trigger).toHaveAttribute('aria-expanded', 'true');
|
|
60
|
+
|
|
61
|
+
const menuWrapper = container.querySelector('.c-dropdown__menu-wrapper');
|
|
62
|
+
expect(menuWrapper).toHaveClass('is-open');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
@@ -627,7 +627,7 @@ export const GlassCustom: Story = {
|
|
|
627
627
|
displacementScale: 70,
|
|
628
628
|
blurAmount: 1.8,
|
|
629
629
|
saturation: 170,
|
|
630
|
-
|
|
630
|
+
borderRadius: 0,
|
|
631
631
|
} as any
|
|
632
632
|
}
|
|
633
633
|
>
|
|
@@ -693,7 +693,7 @@ export const GlassPremium: Story = {
|
|
|
693
693
|
displacementScale: 180,
|
|
694
694
|
blurAmount: 1,
|
|
695
695
|
saturation: 60,
|
|
696
|
-
|
|
696
|
+
borderRadius: 0,
|
|
697
697
|
} as any
|
|
698
698
|
}
|
|
699
699
|
>
|
|
@@ -786,7 +786,7 @@ export const GlassShowcase: Story = {
|
|
|
786
786
|
displacementScale: 25,
|
|
787
787
|
blurAmount: 1.2,
|
|
788
788
|
saturation: 140,
|
|
789
|
-
|
|
789
|
+
borderRadius: 0,
|
|
790
790
|
} as any
|
|
791
791
|
}
|
|
792
792
|
>
|
|
@@ -815,7 +815,7 @@ export const GlassShowcase: Story = {
|
|
|
815
815
|
displacementScale: 35,
|
|
816
816
|
blurAmount: 1.5,
|
|
817
817
|
saturation: 160,
|
|
818
|
-
|
|
818
|
+
borderRadius: 0,
|
|
819
819
|
} as any
|
|
820
820
|
}
|
|
821
821
|
>
|
|
@@ -844,7 +844,7 @@ export const GlassShowcase: Story = {
|
|
|
844
844
|
displacementScale: 50,
|
|
845
845
|
blurAmount: 2,
|
|
846
846
|
saturation: 180,
|
|
847
|
-
|
|
847
|
+
borderRadius: 0,
|
|
848
848
|
} as any
|
|
849
849
|
}
|
|
850
850
|
>
|
|
@@ -874,7 +874,7 @@ export const GlassShowcase: Story = {
|
|
|
874
874
|
displacementScale: 70,
|
|
875
875
|
blurAmount: 1.8,
|
|
876
876
|
saturation: 170,
|
|
877
|
-
|
|
877
|
+
borderRadius: 0,
|
|
878
878
|
} as any
|
|
879
879
|
}
|
|
880
880
|
>
|
|
@@ -907,7 +907,7 @@ export const GlassShowcase: Story = {
|
|
|
907
907
|
displacementScale: 180,
|
|
908
908
|
blurAmount: 1,
|
|
909
909
|
saturation: 60,
|
|
910
|
-
|
|
910
|
+
borderRadius: 0,
|
|
911
911
|
} as any
|
|
912
912
|
}
|
|
913
913
|
>
|
|
@@ -1,10 +1,53 @@
|
|
|
1
|
-
import React, { useRef, useEffect } from 'react';
|
|
1
|
+
import React, { useRef, useEffect, memo, forwardRef } from 'react';
|
|
2
2
|
import { EdgePanelProps } from '../../lib/types/components';
|
|
3
3
|
import { useEdgePanel } from '../../lib/composables/useEdgePanel';
|
|
4
4
|
import { EDGE_PANEL } from '../../lib/constants/components';
|
|
5
5
|
import { Icon } from '../Icon/Icon';
|
|
6
6
|
import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
7
7
|
|
|
8
|
+
// Subcomponents
|
|
9
|
+
export const EdgePanelHeader = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
10
|
+
({ children, className = '', ...props }, ref) => (
|
|
11
|
+
<div ref={ref} className={`c-edge-panel__header ${className}`.trim()} {...props}>
|
|
12
|
+
{children}
|
|
13
|
+
</div>
|
|
14
|
+
)
|
|
15
|
+
);
|
|
16
|
+
EdgePanelHeader.displayName = 'EdgePanelHeader';
|
|
17
|
+
|
|
18
|
+
export const EdgePanelBody = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
19
|
+
({ children, className = '', ...props }, ref) => (
|
|
20
|
+
<div ref={ref} className={`c-edge-panel__body ${className}`.trim()} {...props}>
|
|
21
|
+
{children}
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
);
|
|
25
|
+
EdgePanelBody.displayName = 'EdgePanelBody';
|
|
26
|
+
|
|
27
|
+
export const EdgePanelFooter = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
28
|
+
({ children, className = '', ...props }, ref) => (
|
|
29
|
+
<div ref={ref} className={`c-edge-panel__footer ${className}`.trim()} {...props}>
|
|
30
|
+
{children}
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
EdgePanelFooter.displayName = 'EdgePanelFooter';
|
|
35
|
+
|
|
36
|
+
export const EdgePanelCloseButton = forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
|
|
37
|
+
({ className = '', onClick, ...props }, ref) => (
|
|
38
|
+
<button
|
|
39
|
+
ref={ref}
|
|
40
|
+
className={`c-edge-panel__close c-btn c-btn--icon ${className}`.trim()}
|
|
41
|
+
onClick={onClick}
|
|
42
|
+
aria-label="Close panel"
|
|
43
|
+
{...props}
|
|
44
|
+
>
|
|
45
|
+
<Icon name="X" />
|
|
46
|
+
</button>
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
EdgePanelCloseButton.displayName = 'EdgePanelCloseButton';
|
|
50
|
+
|
|
8
51
|
/**
|
|
9
52
|
* EdgePanel - A sliding panel component that appears from any screen edge
|
|
10
53
|
*
|
|
@@ -21,129 +64,138 @@ import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
|
21
64
|
* <p>Panel content</p>
|
|
22
65
|
* </EdgePanel>
|
|
23
66
|
*
|
|
24
|
-
* //
|
|
25
|
-
* <EdgePanel
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* <p>Panel with glass morphism</p>
|
|
33
|
-
* </EdgePanel>
|
|
34
|
-
*
|
|
35
|
-
* // With custom glass configuration
|
|
36
|
-
* <EdgePanel
|
|
37
|
-
* title="Custom Glass"
|
|
38
|
-
* isOpen={isOpen}
|
|
39
|
-
* onOpenChange={setIsOpen}
|
|
40
|
-
* position="start"
|
|
41
|
-
* glass={{
|
|
42
|
-
* mode: 'shader',
|
|
43
|
-
* shaderVariant: 'liquidGlass',
|
|
44
|
-
* displacementScale: 70,
|
|
45
|
-
* blurAmount: 1.8,
|
|
46
|
-
* saturation: 170,
|
|
47
|
-
* }}
|
|
48
|
-
* >
|
|
49
|
-
* <p>Panel with custom glass effect</p>
|
|
67
|
+
* // Compound Usage
|
|
68
|
+
* <EdgePanel isOpen={isOpen} onOpenChange={setIsOpen}>
|
|
69
|
+
* <EdgePanel.Header>
|
|
70
|
+
* <h4>Title</h4>
|
|
71
|
+
* <EdgePanel.CloseButton onClick={() => setIsOpen(false)} />
|
|
72
|
+
* </EdgePanel.Header>
|
|
73
|
+
* <EdgePanel.Body>Content</EdgePanel.Body>
|
|
74
|
+
* <EdgePanel.Footer>Footer</EdgePanel.Footer>
|
|
50
75
|
* </EdgePanel>
|
|
51
76
|
* ```
|
|
52
77
|
*/
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const {
|
|
68
|
-
isOpen: isOpenState,
|
|
69
|
-
containerRef,
|
|
70
|
-
backdropRef,
|
|
71
|
-
generateEdgePanelClass,
|
|
72
|
-
closePanel,
|
|
73
|
-
handleBackdropClick,
|
|
74
|
-
} = useEdgePanel({
|
|
75
|
-
position,
|
|
76
|
-
mode,
|
|
77
|
-
isOpen,
|
|
78
|
+
type EdgePanelComponent = React.FC<EdgePanelProps> & {
|
|
79
|
+
Header: typeof EdgePanelHeader;
|
|
80
|
+
Body: typeof EdgePanelBody;
|
|
81
|
+
Footer: typeof EdgePanelFooter;
|
|
82
|
+
CloseButton: typeof EdgePanelCloseButton;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const EdgePanel: EdgePanelComponent = memo(
|
|
86
|
+
({
|
|
87
|
+
title,
|
|
88
|
+
children,
|
|
89
|
+
position = 'start',
|
|
90
|
+
mode = 'slide',
|
|
91
|
+
isOpen = false,
|
|
78
92
|
onOpenChange,
|
|
79
|
-
backdrop,
|
|
80
|
-
closeOnBackdropClick,
|
|
81
|
-
closeOnEscape,
|
|
93
|
+
backdrop = true,
|
|
94
|
+
closeOnBackdropClick = true,
|
|
95
|
+
closeOnEscape = true,
|
|
96
|
+
className = '',
|
|
97
|
+
style,
|
|
82
98
|
glass,
|
|
83
|
-
})
|
|
99
|
+
}: EdgePanelProps) => {
|
|
100
|
+
const {
|
|
101
|
+
isOpen: isOpenState,
|
|
102
|
+
containerRef,
|
|
103
|
+
backdropRef,
|
|
104
|
+
generateEdgePanelClass,
|
|
105
|
+
closePanel,
|
|
106
|
+
handleBackdropClick,
|
|
107
|
+
} = useEdgePanel({
|
|
108
|
+
position,
|
|
109
|
+
mode,
|
|
110
|
+
isOpen,
|
|
111
|
+
onOpenChange,
|
|
112
|
+
backdrop,
|
|
113
|
+
closeOnBackdropClick,
|
|
114
|
+
closeOnEscape,
|
|
115
|
+
glass,
|
|
116
|
+
});
|
|
84
117
|
|
|
85
|
-
|
|
86
|
-
|
|
118
|
+
// Moved useRef outside of conditional rendering to fix hook order issue
|
|
119
|
+
const glassContentRef = useRef<HTMLDivElement>(null);
|
|
87
120
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
121
|
+
const panelClass = generateEdgePanelClass({
|
|
122
|
+
position,
|
|
123
|
+
isOpen,
|
|
124
|
+
className: glass ? `${className} c-edge-panel--glass` : className,
|
|
125
|
+
});
|
|
93
126
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
return
|
|
97
|
-
|
|
127
|
+
// If not open and not controlled by parent, don't render
|
|
128
|
+
// Note: useEdgePanel manages internal state if onOpenChange is not provided?
|
|
129
|
+
// Looking at useEdgePanel (implied): it seems to return isOpenState.
|
|
130
|
+
// If we return null here, animations might be cut off.
|
|
131
|
+
// Usually EdgePanel/Drawer should stay mounted but hidden or conditionally mounted.
|
|
132
|
+
// The original code returned null if !isOpenState && isOpen === false.
|
|
133
|
+
// Let's keep that logic.
|
|
134
|
+
if (!isOpenState && isOpen === false) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
98
137
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
>
|
|
134
|
-
{panelContent}
|
|
135
|
-
</div>
|
|
136
|
-
</AtomixGlass>
|
|
137
|
-
) : (
|
|
138
|
-
panelContent
|
|
138
|
+
const defaultGlassProps = {
|
|
139
|
+
elasticity: 0,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
143
|
+
|
|
144
|
+
// Check for compound components
|
|
145
|
+
const hasCompoundComponents = React.Children.toArray(children).some((child) =>
|
|
146
|
+
React.isValidElement(child) &&
|
|
147
|
+
['EdgePanelHeader', 'EdgePanelBody', 'EdgePanelFooter'].includes((child.type as any).displayName)
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const panelContent = hasCompoundComponents ? (
|
|
151
|
+
children
|
|
152
|
+
) : (
|
|
153
|
+
<>
|
|
154
|
+
<div className="c-edge-panel__header">
|
|
155
|
+
<h4>{title}</h4>
|
|
156
|
+
<button
|
|
157
|
+
className="c-edge-panel__close c-btn c-btn--icon"
|
|
158
|
+
onClick={() => closePanel()}
|
|
159
|
+
aria-label="Close panel"
|
|
160
|
+
>
|
|
161
|
+
<Icon name="X" />
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
<div className="c-edge-panel__body">{children}</div>
|
|
165
|
+
</>
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<div className={panelClass} data-position={position} data-mode={mode} style={style}>
|
|
170
|
+
{backdrop && (
|
|
171
|
+
<div ref={backdropRef} className="c-edge-panel__backdrop" onClick={handleBackdropClick} />
|
|
139
172
|
)}
|
|
173
|
+
<div ref={containerRef} className="c-edge-panel__container">
|
|
174
|
+
{glass ? (
|
|
175
|
+
<AtomixGlass {...glassProps}>
|
|
176
|
+
<div
|
|
177
|
+
ref={glassContentRef}
|
|
178
|
+
className="c-edge-panel__glass-content"
|
|
179
|
+
style={{ borderRadius: containerRef.current?.style.borderRadius }}
|
|
180
|
+
>
|
|
181
|
+
{panelContent}
|
|
182
|
+
</div>
|
|
183
|
+
</AtomixGlass>
|
|
184
|
+
) : (
|
|
185
|
+
panelContent
|
|
186
|
+
)}
|
|
187
|
+
</div>
|
|
140
188
|
</div>
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
export type { EdgePanelProps };
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
) as unknown as EdgePanelComponent;
|
|
146
192
|
|
|
147
193
|
EdgePanel.displayName = 'EdgePanel';
|
|
194
|
+
EdgePanel.Header = EdgePanelHeader;
|
|
195
|
+
EdgePanel.Body = EdgePanelBody;
|
|
196
|
+
EdgePanel.Footer = EdgePanelFooter;
|
|
197
|
+
EdgePanel.CloseButton = EdgePanelCloseButton;
|
|
198
|
+
|
|
199
|
+
export type { EdgePanelProps };
|
|
148
200
|
|
|
149
201
|
export default EdgePanel;
|