@shohojdhara/atomix 0.3.15 → 0.4.1
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/build-tools/index.d.ts +31 -30
- package/build-tools/package.json +4 -21
- package/dist/atomix.css +20234 -2027
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +76 -2
- package/dist/atomix.min.css.map +1 -1
- package/dist/build-tools/index.d.ts +31 -30
- package/dist/build-tools/package.json +4 -21
- package/dist/charts.js +4 -5
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +87 -10
- package/dist/core.js +673 -480
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +15 -3
- package/dist/forms.js +530 -97
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +5 -6
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +644 -277
- package/dist/index.esm.js +1948 -1347
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +3333 -2728
- 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.map +1 -1
- package/dist/theme.d.ts +9 -9
- package/dist/theme.js.map +1 -1
- package/package.json +2 -2
- package/scripts/atomix-cli.js +10 -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 +72 -23
- package/src/components/Accordion/Accordion.test.tsx +70 -50
- package/src/components/Accordion/Accordion.tsx +219 -96
- package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +1 -1
- package/src/components/AtomixGlass/GlassFilter.tsx +9 -16
- package/src/components/AtomixGlass/glass-utils.ts +4 -3
- package/src/components/AtomixGlass/shader-utils.ts +128 -52
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +1 -1
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +1 -1
- package/src/components/Avatar/Avatar.stories.tsx +45 -62
- package/src/components/Avatar/Avatar.tsx +58 -56
- package/src/components/Badge/Badge.stories.tsx +20 -9
- package/src/components/Badge/Badge.test.tsx +41 -41
- package/src/components/Badge/Badge.tsx +64 -62
- package/src/components/Block/Block.stories.tsx +14 -4
- package/src/components/Breadcrumb/Breadcrumb.stories.tsx +9 -8
- package/src/components/Breadcrumb/Breadcrumb.tsx +173 -65
- package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
- package/src/components/Button/Button.stories.tsx +13 -22
- package/src/components/Button/Button.test.tsx +97 -81
- package/src/components/Button/Button.tsx +46 -14
- package/src/components/Button/ButtonGroup.stories.tsx +37 -32
- package/src/components/Button/ButtonGroup.tsx +4 -15
- package/src/components/Callout/Callout.stories.tsx +166 -918
- package/src/components/Callout/Callout.tsx +196 -84
- package/src/components/Callout/CalloutCompound.test.tsx +72 -0
- package/src/components/Card/Card.stories.tsx +67 -36
- package/src/components/Card/Card.tsx +30 -14
- package/src/components/Chart/AreaChart.tsx +1 -1
- package/src/components/Chart/CandlestickChart.tsx +23 -16
- package/src/components/Chart/Chart.stories.tsx +4 -9
- package/src/components/Chart/Chart.tsx +40 -44
- package/src/components/Chart/ChartRenderer.tsx +39 -12
- package/src/components/Chart/ChartToolbar.tsx +21 -5
- package/src/components/Chart/DonutChart.tsx +1 -1
- package/src/components/Chart/FunnelChart.tsx +4 -1
- package/src/components/Chart/GaugeChart.tsx +3 -1
- package/src/components/Chart/HeatmapChart.tsx +50 -37
- package/src/components/Chart/LineChart.tsx +3 -2
- package/src/components/Chart/MultiAxisChart.tsx +24 -16
- package/src/components/Chart/RadarChart.tsx +19 -17
- package/src/components/Chart/ScatterChart.tsx +29 -21
- package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +6 -2
- package/src/components/ColorModeToggle/ColorModeToggle.tsx +15 -3
- package/src/components/Countdown/Countdown.stories.tsx +7 -7
- package/src/components/DataTable/DataTable.stories.tsx +43 -38
- package/src/components/DataTable/DataTable.test.tsx +26 -148
- package/src/components/DataTable/DataTable.tsx +485 -456
- package/src/components/DatePicker/DatePicker.stories.tsx +32 -47
- package/src/components/DatePicker/DatePicker.tsx +31 -26
- package/src/components/Dropdown/Dropdown.stories.tsx +2 -5
- package/src/components/Dropdown/Dropdown.tsx +425 -298
- package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
- package/src/components/EdgePanel/EdgePanel.stories.tsx +6 -19
- package/src/components/EdgePanel/EdgePanel.tsx +163 -113
- package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
- package/src/components/Footer/Footer.stories.tsx +21 -16
- package/src/components/Footer/Footer.tsx +130 -128
- package/src/components/Footer/FooterLink.tsx +2 -2
- package/src/components/Form/Checkbox.test.tsx +49 -49
- package/src/components/Form/Checkbox.tsx +108 -100
- package/src/components/Form/Form.stories.tsx +2 -10
- package/src/components/Form/Input.stories.tsx +22 -39
- package/src/components/Form/Input.test.tsx +38 -44
- package/src/components/Form/Radio.stories.tsx +6 -12
- package/src/components/Form/Radio.tsx +68 -66
- package/src/components/Form/Select.stories.tsx +23 -0
- package/src/components/Form/Select.test.tsx +99 -0
- package/src/components/Form/Select.tsx +239 -186
- package/src/components/Form/SelectOption.tsx +88 -0
- package/src/components/Form/Textarea.test.tsx +27 -32
- package/src/components/Hero/Hero.stories.tsx +93 -23
- package/src/components/Hero/Hero.test.tsx +142 -0
- package/src/components/Hero/Hero.tsx +343 -58
- package/src/components/Icon/index.ts +7 -1
- package/src/components/List/List.test.tsx +62 -0
- package/src/components/List/List.tsx +32 -25
- package/src/components/List/ListItem.tsx +20 -0
- package/src/components/Modal/Modal.stories.tsx +67 -2
- package/src/components/Modal/Modal.tsx +208 -125
- package/src/components/Modal/ModalCompound.test.tsx +94 -0
- package/src/components/Navigation/Menu/MegaMenu.tsx +70 -70
- package/src/components/Navigation/Nav/NavDropdown.tsx +1 -5
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +128 -28
- package/src/components/Navigation/SideMenu/SideMenu.tsx +5 -7
- package/src/components/Navigation/SideMenu/SideMenuItem.tsx +4 -5
- package/src/components/Pagination/Pagination.stories.tsx +7 -4
- package/src/components/Pagination/Pagination.tsx +199 -202
- package/src/components/PhotoViewer/PhotoViewer.tsx +4 -1
- package/src/components/Popover/Popover.stories.tsx +99 -192
- package/src/components/Popover/Popover.tsx +41 -37
- package/src/components/Progress/Progress.stories.tsx +35 -44
- package/src/components/River/River.stories.tsx +2 -1
- package/src/components/SectionIntro/SectionIntro.stories.tsx +71 -71
- package/src/components/Slider/Slider.stories.tsx +12 -4
- package/src/components/Spinner/Spinner.stories.tsx +3 -1
- package/src/components/Spinner/Spinner.test.tsx +23 -23
- package/src/components/Spinner/Spinner.tsx +43 -46
- package/src/components/Steps/Steps.stories.tsx +8 -6
- package/src/components/Steps/Steps.tsx +124 -21
- package/src/components/Steps/StepsCompound.test.tsx +81 -0
- package/src/components/Tabs/Tabs.stories.tsx +12 -9
- package/src/components/Tabs/Tabs.tsx +230 -75
- package/src/components/Tabs/TabsCompound.test.tsx +64 -0
- package/src/components/Toggle/Toggle.stories.tsx +27 -13
- package/src/components/Toggle/Toggle.test.tsx +65 -70
- package/src/components/Toggle/Toggle.tsx +4 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +24 -20
- package/src/components/Tooltip/Tooltip.tsx +104 -106
- package/src/components/Upload/Upload.stories.tsx +129 -127
- package/src/components/Upload/Upload.tsx +287 -283
- package/src/components/VideoPlayer/VideoPlayer.tsx +6 -1
- package/src/components/index.ts +13 -2
- package/src/layouts/Grid/Grid.stories.tsx +9 -3
- package/src/layouts/MasonryGrid/MasonryGrid.tsx +5 -1
- package/src/lib/__tests__/theme-tools.test.ts +32 -6
- package/src/lib/composables/index.ts +0 -4
- package/src/lib/composables/shared-mouse-tracker.ts +13 -14
- package/src/lib/composables/useAtomixGlass.ts +102 -60
- package/src/lib/composables/useChartExport.ts +1 -1
- package/src/lib/composables/useDataTable.ts +29 -17
- package/src/lib/composables/useHero.ts +58 -14
- package/src/lib/composables/useHeroBackgroundSlider.ts +2 -9
- package/src/lib/composables/useInput.ts +10 -8
- package/src/lib/composables/useSideMenu.ts +6 -5
- package/src/lib/composables/useTooltip.ts +1 -2
- package/src/lib/composables/useVideoPlayer.ts +44 -35
- package/src/lib/config/index.ts +154 -154
- package/src/lib/constants/cssVariables.ts +29 -29
- package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +2 -6
- package/src/lib/hooks/index.ts +1 -1
- package/src/lib/hooks/useComponentCustomization.ts +11 -17
- package/src/lib/hooks/usePerformanceMonitor.ts +6 -7
- package/src/lib/patterns/__tests__/slots.test.ts +1 -1
- package/src/lib/patterns/index.ts +1 -1
- package/src/lib/patterns/slots.tsx +8 -13
- package/src/lib/storybook/InteractiveDemo.tsx +13 -18
- package/src/lib/storybook/PreviewContainer.tsx +1 -1
- package/src/lib/storybook/VariantsGrid.tsx +3 -7
- package/src/lib/storybook/index.ts +1 -1
- package/src/lib/theme/adapters/cssVariableMapper.ts +47 -74
- package/src/lib/theme/adapters/index.ts +3 -9
- package/src/lib/theme/adapters/themeAdapter.ts +41 -26
- package/src/lib/theme/config/index.ts +1 -1
- package/src/lib/theme/config/types.ts +2 -2
- package/src/lib/theme/config/validator.ts +10 -5
- package/src/lib/theme/constants/constants.ts +2 -2
- package/src/lib/theme/constants/index.ts +1 -2
- package/src/lib/theme/core/__tests__/createTheme.test.ts +20 -22
- package/src/lib/theme/core/composeTheme.ts +32 -26
- package/src/lib/theme/core/createTheme.ts +1 -1
- package/src/lib/theme/core/createThemeObject.ts +308 -301
- package/src/lib/theme/core/index.ts +3 -3
- package/src/lib/theme/devtools/CLI.ts +105 -111
- package/src/lib/theme/devtools/Comparator.tsx +50 -32
- package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +50 -48
- package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +257 -63
- package/src/lib/theme/devtools/Inspector.tsx +75 -60
- package/src/lib/theme/devtools/LiveEditor.tsx +97 -76
- package/src/lib/theme/devtools/Preview.tsx +150 -106
- package/src/lib/theme/devtools/ThemeValidator.ts +29 -21
- package/src/lib/theme/devtools/index.ts +3 -9
- package/src/lib/theme/devtools/useHistory.ts +23 -21
- package/src/lib/theme/errors/errors.ts +12 -11
- package/src/lib/theme/errors/index.ts +2 -7
- package/src/lib/theme/generators/generateCSS.ts +9 -13
- package/src/lib/theme/generators/generateCSSNested.ts +1 -6
- package/src/lib/theme/generators/generateCSSVariables.ts +673 -630
- package/src/lib/theme/generators/index.ts +1 -4
- package/src/lib/theme/i18n/index.ts +1 -1
- package/src/lib/theme/i18n/rtl.ts +13 -13
- package/src/lib/theme/index.ts +7 -16
- package/src/lib/theme/runtime/ThemeApplicator.ts +4 -4
- package/src/lib/theme/runtime/ThemeContext.tsx +1 -1
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +19 -23
- package/src/lib/theme/runtime/ThemeProvider.tsx +230 -239
- package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +1 -1
- package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +24 -29
- package/src/lib/theme/runtime/index.ts +2 -5
- package/src/lib/theme/runtime/useTheme.ts +18 -18
- package/src/lib/theme/runtime/useThemeTokens.ts +22 -22
- package/src/lib/theme/test/testTheme.ts +15 -16
- package/src/lib/theme/tokens/index.ts +2 -7
- package/src/lib/theme/tokens/tokens.ts +25 -24
- package/src/lib/theme/types.ts +428 -411
- package/src/lib/theme/utils/__tests__/themeValidation.test.ts +3 -3
- package/src/lib/theme/utils/componentTheming.ts +18 -18
- package/src/lib/theme/utils/domUtils.ts +277 -289
- package/src/lib/theme/utils/index.ts +1 -2
- package/src/lib/theme/utils/injectCSS.ts +10 -14
- package/src/lib/theme/utils/naming.ts +20 -16
- package/src/lib/theme/utils/themeHelpers.ts +10 -12
- package/src/lib/theme/utils/themeUtils.ts +85 -86
- package/src/lib/theme/utils/themeValidation.ts +82 -33
- package/src/lib/theme-tools.ts +8 -6
- package/src/lib/types/components.ts +180 -73
- package/src/lib/types/partProps.ts +1 -1
- package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
- package/src/lib/utils/__tests__/csv.test.ts +1 -1
- package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
- package/src/lib/utils/componentUtils.ts +8 -12
- package/src/lib/utils/csv.ts +3 -1
- package/src/lib/utils/dataTableExport.ts +1 -5
- package/src/lib/utils/fontPreloader.ts +10 -19
- package/src/lib/utils/icons.ts +4 -1
- package/src/lib/utils/index.ts +2 -6
- package/src/lib/utils/memoryMonitor.ts +10 -8
- package/src/lib/utils/themeNaming.ts +3 -3
- package/src/styles/01-settings/_index.scss +0 -1
- package/src/styles/01-settings/_settings.colors.scss +8 -8
- package/src/styles/01-settings/_settings.design-tokens.scss +61 -50
- package/src/styles/01-settings/_settings.navbar.scss +1 -1
- package/src/styles/01-settings/_settings.spacing.scss +3 -4
- package/src/styles/01-settings/_settings.tooltip.scss +1 -1
- package/src/styles/01-settings/_settings.typography.scss +1 -1
- package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
- package/src/styles/02-tools/_tools.button.scss +51 -21
- package/src/styles/02-tools/_tools.utility-api.scss +36 -24
- package/src/styles/03-generic/_generic.root.scss +4 -3
- package/src/styles/06-components/_components.atomix-glass.scss +13 -9
- package/src/styles/06-components/_components.button.scss +16 -4
- package/src/styles/06-components/_components.callout.scss +27 -21
- package/src/styles/06-components/_components.card.scss +5 -14
- package/src/styles/06-components/_components.chart.scss +22 -19
- package/src/styles/06-components/_components.checkbox.scss +3 -1
- package/src/styles/06-components/_components.color-mode-toggle.scss +3 -1
- package/src/styles/06-components/_components.edge-panel.scss +9 -2
- package/src/styles/06-components/_components.footer.scss +1 -1
- package/src/styles/06-components/_components.side-menu.scss +5 -5
- package/src/styles/06-components/_components.toggle.scss +18 -0
- package/src/styles/06-components/_index.scss +1 -1
- package/src/styles/06-components/old.chart.styles.scss +0 -2
- package/src/styles/99-utilities/_utilities.border.scss +69 -27
- package/src/styles/99-utilities/_utilities.display.scss +1 -1
- package/src/styles/99-utilities/_utilities.opacity.scss +10 -0
- package/src/styles/99-utilities/_utilities.position.scss +16 -9
- package/src/styles/99-utilities/_utilities.scss +1 -1
- package/src/styles/99-utilities/_utilities.sizes.scss +47 -18
- package/src/styles/99-utilities/_utilities.spacing.scss +118 -66
- package/src/styles/99-utilities/_utilities.text-gradient.scss +30 -30
- package/src/styles/99-utilities/_utilities.text.scss +67 -47
|
@@ -1,123 +1,246 @@
|
|
|
1
|
-
import React, { ReactNode, useId, memo } from 'react';
|
|
1
|
+
import React, { ReactNode, useId, memo, forwardRef } from 'react';
|
|
2
2
|
import { ACCORDION } from '../../lib/constants/components';
|
|
3
3
|
import { useAccordion } from '../../lib/composables/useAccordion';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
AccordionProps as AccordionPropsType,
|
|
6
|
+
AtomixGlassProps,
|
|
7
|
+
} from '../../lib/types/components';
|
|
5
8
|
import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
6
9
|
|
|
7
10
|
export type AccordionProps = AccordionPropsType;
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// Generate unique IDs for accessibility
|
|
25
|
-
const instanceId = useId();
|
|
26
|
-
const buttonId = `accordion-header-${instanceId}`;
|
|
27
|
-
const panelId = `accordion-panel-${instanceId}`;
|
|
28
|
-
|
|
29
|
-
// Use composable hook for logic/state
|
|
30
|
-
const {
|
|
31
|
-
state,
|
|
32
|
-
toggle,
|
|
33
|
-
updatePanelHeight,
|
|
34
|
-
panelRef,
|
|
35
|
-
contentRef,
|
|
36
|
-
generateClassNames,
|
|
37
|
-
generateHeaderClassNames,
|
|
38
|
-
} = useAccordion({
|
|
39
|
-
defaultOpen,
|
|
40
|
-
disabled,
|
|
41
|
-
iconPosition,
|
|
42
|
-
isOpen: controlledOpen,
|
|
43
|
-
onOpenChange,
|
|
44
|
-
onOpen,
|
|
45
|
-
onClose,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
// Default icon
|
|
49
|
-
const defaultIcon = (
|
|
50
|
-
<i className="c-accordion__icon" aria-hidden="true">
|
|
51
|
-
<svg
|
|
52
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
-
width="24"
|
|
54
|
-
height="24"
|
|
55
|
-
viewBox="0 0 24 24"
|
|
56
|
-
fill="none"
|
|
57
|
-
stroke="currentColor"
|
|
58
|
-
strokeWidth="2"
|
|
59
|
-
strokeLinecap="round"
|
|
60
|
-
strokeLinejoin="round"
|
|
61
|
-
aria-hidden="true"
|
|
62
|
-
focusable="false"
|
|
63
|
-
>
|
|
64
|
-
<polyline points="6 9 12 15 18 9"></polyline>
|
|
65
|
-
</svg>
|
|
66
|
-
</i>
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
const accordionContent = (
|
|
70
|
-
<div
|
|
71
|
-
className={generateClassNames(className) + (glass ? ' c-accordion--glass' : '')}
|
|
72
|
-
style={style}
|
|
12
|
+
// Default icon
|
|
13
|
+
const DefaultIcon = () => (
|
|
14
|
+
<i className="c-accordion__icon" aria-hidden="true">
|
|
15
|
+
<svg
|
|
16
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
17
|
+
width="24"
|
|
18
|
+
height="24"
|
|
19
|
+
viewBox="0 0 24 24"
|
|
20
|
+
fill="none"
|
|
21
|
+
stroke="currentColor"
|
|
22
|
+
strokeWidth="2"
|
|
23
|
+
strokeLinecap="round"
|
|
24
|
+
strokeLinejoin="round"
|
|
25
|
+
aria-hidden="true"
|
|
26
|
+
focusable="false"
|
|
73
27
|
>
|
|
28
|
+
<polyline points="6 9 12 15 18 9"></polyline>
|
|
29
|
+
</svg>
|
|
30
|
+
</i>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface AccordionHeaderProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'title'> {
|
|
34
|
+
title?: ReactNode;
|
|
35
|
+
icon?: ReactNode;
|
|
36
|
+
iconPosition?: 'left' | 'right';
|
|
37
|
+
isOpen?: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const AccordionHeader = forwardRef<HTMLButtonElement, AccordionHeaderProps>(
|
|
41
|
+
(
|
|
42
|
+
{ title, icon, iconPosition = 'right', isOpen, children, className = '', ...props },
|
|
43
|
+
ref
|
|
44
|
+
) => {
|
|
45
|
+
// Determine icon to render. Explicit check for undefined to allow null/false to hide icon.
|
|
46
|
+
const iconElement = icon === undefined ? <DefaultIcon /> : icon;
|
|
47
|
+
|
|
48
|
+
return (
|
|
74
49
|
<button
|
|
75
|
-
|
|
76
|
-
className={generateHeaderClassNames()}
|
|
77
|
-
onClick={toggle}
|
|
78
|
-
aria-expanded={state.isOpen}
|
|
79
|
-
aria-controls={panelId}
|
|
80
|
-
aria-disabled={disabled}
|
|
81
|
-
disabled={disabled}
|
|
50
|
+
ref={ref}
|
|
82
51
|
type="button"
|
|
52
|
+
className={className} // Parent injects the class names
|
|
53
|
+
{...props}
|
|
83
54
|
>
|
|
84
|
-
<span className="c-accordion__title">{title}</span>
|
|
85
|
-
{
|
|
55
|
+
{title && <span className="c-accordion__title">{title}</span>}
|
|
56
|
+
{children}
|
|
57
|
+
{iconElement}
|
|
86
58
|
</button>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
AccordionHeader.displayName = 'AccordionHeader';
|
|
63
|
+
|
|
64
|
+
export interface AccordionBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
65
|
+
panelRef?: React.RefObject<HTMLDivElement>;
|
|
66
|
+
contentRef?: React.RefObject<HTMLDivElement>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Helper to merge refs
|
|
70
|
+
function mergeRefs<T = any>(...refs: (React.MutableRefObject<T> | React.LegacyRef<T> | undefined | null)[]) {
|
|
71
|
+
return (node: T) => {
|
|
72
|
+
refs.forEach((ref) => {
|
|
73
|
+
if (typeof ref === 'function') {
|
|
74
|
+
ref(node);
|
|
75
|
+
} else if (ref != null) {
|
|
76
|
+
(ref as React.MutableRefObject<T | null>).current = node;
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const AccordionBody = forwardRef<HTMLDivElement, AccordionBodyProps>(
|
|
83
|
+
({ children, className = '', panelRef, contentRef, ...props }, ref) => {
|
|
84
|
+
const mergedPanelRef = React.useMemo(() => mergeRefs(ref, panelRef), [ref, panelRef]);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
87
|
<div
|
|
88
|
-
|
|
89
|
-
className={
|
|
90
|
-
ref={panelRef as React.RefObject<HTMLDivElement>}
|
|
88
|
+
ref={mergedPanelRef}
|
|
89
|
+
className={className} // Parent injects class names
|
|
91
90
|
role="region"
|
|
92
|
-
|
|
91
|
+
{...props}
|
|
93
92
|
>
|
|
94
93
|
<div
|
|
95
94
|
className={ACCORDION.SELECTORS.BODY.replace('.', '')}
|
|
96
|
-
ref={contentRef
|
|
95
|
+
ref={contentRef}
|
|
97
96
|
>
|
|
98
97
|
{children}
|
|
99
98
|
</div>
|
|
100
99
|
</div>
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
AccordionBody.displayName = 'AccordionBody';
|
|
104
|
+
|
|
105
|
+
type AccordionComponent = React.FC<AccordionProps> & {
|
|
106
|
+
Header: typeof AccordionHeader;
|
|
107
|
+
Body: typeof AccordionBody;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const AccordionImpl = memo(
|
|
111
|
+
({
|
|
112
|
+
title,
|
|
113
|
+
children,
|
|
114
|
+
defaultOpen = false,
|
|
115
|
+
isOpen: controlledOpen,
|
|
116
|
+
onOpenChange,
|
|
117
|
+
onOpen,
|
|
118
|
+
onClose,
|
|
119
|
+
disabled = false,
|
|
120
|
+
iconPosition = 'right',
|
|
121
|
+
icon,
|
|
122
|
+
className = '',
|
|
123
|
+
style,
|
|
124
|
+
glass,
|
|
125
|
+
}: AccordionProps) => {
|
|
126
|
+
// Generate unique IDs for accessibility
|
|
127
|
+
const instanceId = useId();
|
|
128
|
+
const buttonId = `accordion-header-${instanceId}`;
|
|
129
|
+
const panelId = `accordion-panel-${instanceId}`;
|
|
130
|
+
|
|
131
|
+
// Use composable hook for logic/state
|
|
132
|
+
const {
|
|
133
|
+
state,
|
|
134
|
+
toggle,
|
|
135
|
+
updatePanelHeight,
|
|
136
|
+
panelRef,
|
|
137
|
+
contentRef,
|
|
138
|
+
generateClassNames,
|
|
139
|
+
generateHeaderClassNames,
|
|
140
|
+
} = useAccordion({
|
|
141
|
+
defaultOpen,
|
|
142
|
+
disabled,
|
|
143
|
+
iconPosition,
|
|
144
|
+
isOpen: controlledOpen,
|
|
145
|
+
onOpenChange,
|
|
146
|
+
onOpen,
|
|
147
|
+
onClose,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const headerClassNames = generateHeaderClassNames();
|
|
151
|
+
const panelClassNames = ACCORDION.SELECTORS.PANEL.replace('.', '');
|
|
152
|
+
|
|
153
|
+
// Check for compound usage
|
|
154
|
+
const hasCompoundComponents = React.Children.toArray(children).some((child) =>
|
|
155
|
+
React.isValidElement(child) &&
|
|
156
|
+
['AccordionHeader', 'AccordionBody'].includes((child.type as any).displayName)
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const content = (
|
|
160
|
+
<div
|
|
161
|
+
className={generateClassNames(className) + (glass ? ' c-accordion--glass' : '')}
|
|
162
|
+
style={style}
|
|
163
|
+
>
|
|
164
|
+
{hasCompoundComponents ? (
|
|
165
|
+
React.Children.map(children, child => {
|
|
166
|
+
if (React.isValidElement(child)) {
|
|
167
|
+
if ((child.type as any).displayName === 'AccordionHeader') {
|
|
168
|
+
return React.cloneElement(child, {
|
|
169
|
+
id: buttonId,
|
|
170
|
+
className: `${headerClassNames} ${(child.props as any).className || ''}`.trim(),
|
|
171
|
+
onClick: (e: React.MouseEvent) => {
|
|
172
|
+
toggle();
|
|
173
|
+
(child.props as any).onClick?.(e);
|
|
174
|
+
},
|
|
175
|
+
'aria-expanded': state.isOpen,
|
|
176
|
+
'aria-controls': panelId,
|
|
177
|
+
'aria-disabled': disabled,
|
|
178
|
+
disabled: disabled,
|
|
179
|
+
iconPosition: (child.props as any).iconPosition || iconPosition,
|
|
180
|
+
} as any);
|
|
181
|
+
}
|
|
182
|
+
if ((child.type as any).displayName === 'AccordionBody') {
|
|
183
|
+
return React.cloneElement(child, {
|
|
184
|
+
id: panelId,
|
|
185
|
+
className: `${panelClassNames} ${(child.props as any).className || ''}`.trim(),
|
|
186
|
+
'aria-labelledby': buttonId,
|
|
187
|
+
panelRef: panelRef,
|
|
188
|
+
contentRef: contentRef,
|
|
189
|
+
} as any);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return child;
|
|
193
|
+
})
|
|
194
|
+
) : (
|
|
195
|
+
<>
|
|
196
|
+
<AccordionHeader
|
|
197
|
+
id={buttonId}
|
|
198
|
+
className={headerClassNames}
|
|
199
|
+
onClick={toggle}
|
|
200
|
+
aria-expanded={state.isOpen}
|
|
201
|
+
aria-controls={panelId}
|
|
202
|
+
aria-disabled={disabled}
|
|
203
|
+
disabled={disabled}
|
|
204
|
+
title={title}
|
|
205
|
+
icon={icon}
|
|
206
|
+
iconPosition={iconPosition}
|
|
207
|
+
/>
|
|
208
|
+
<AccordionBody
|
|
209
|
+
id={panelId}
|
|
210
|
+
className={panelClassNames}
|
|
211
|
+
aria-labelledby={buttonId}
|
|
212
|
+
panelRef={panelRef as React.RefObject<HTMLDivElement>}
|
|
213
|
+
contentRef={contentRef as React.RefObject<HTMLDivElement>}
|
|
214
|
+
>
|
|
215
|
+
{children}
|
|
216
|
+
</AccordionBody>
|
|
217
|
+
</>
|
|
218
|
+
)}
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (glass) {
|
|
223
|
+
// Default glass settings for accordions
|
|
224
|
+
const defaultGlassProps = {
|
|
225
|
+
displacementScale: 20,
|
|
226
|
+
elasticity: 0,
|
|
227
|
+
};
|
|
103
228
|
|
|
104
|
-
|
|
105
|
-
// Default glass settings for accordions
|
|
106
|
-
const defaultGlassProps = {
|
|
107
|
-
displacementScale: 20,
|
|
108
|
-
elasticity: 0,
|
|
109
|
-
};
|
|
229
|
+
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
110
230
|
|
|
111
|
-
|
|
231
|
+
return <AtomixGlass {...glassProps}>{content}</AtomixGlass>;
|
|
232
|
+
}
|
|
112
233
|
|
|
113
|
-
return
|
|
234
|
+
return content;
|
|
114
235
|
}
|
|
236
|
+
);
|
|
115
237
|
|
|
116
|
-
|
|
117
|
-
});
|
|
238
|
+
AccordionImpl.displayName = 'Accordion';
|
|
118
239
|
|
|
119
|
-
//
|
|
120
|
-
|
|
240
|
+
// Attach subcomponents
|
|
241
|
+
const AccordionWithSubcomponents = AccordionImpl as unknown as AccordionComponent;
|
|
242
|
+
AccordionWithSubcomponents.Header = AccordionHeader;
|
|
243
|
+
AccordionWithSubcomponents.Body = AccordionBody;
|
|
121
244
|
|
|
122
|
-
|
|
123
|
-
export default Accordion;
|
|
245
|
+
export const Accordion = AccordionWithSubcomponents;
|
|
246
|
+
export default Accordion as unknown as AccordionComponent;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { Accordion } from './Accordion';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
describe('Accordion Component', () => {
|
|
7
|
+
it('renders correctly with legacy props', () => {
|
|
8
|
+
render(
|
|
9
|
+
<Accordion title="Legacy Title" defaultOpen>
|
|
10
|
+
Legacy Content
|
|
11
|
+
</Accordion>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(screen.getByText('Legacy Title')).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByText('Legacy Content')).toBeInTheDocument();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('renders correctly with compound components', () => {
|
|
19
|
+
render(
|
|
20
|
+
<Accordion defaultOpen>
|
|
21
|
+
<Accordion.Header>
|
|
22
|
+
<span>Compound Header</span>
|
|
23
|
+
</Accordion.Header>
|
|
24
|
+
<Accordion.Body>
|
|
25
|
+
<p>Compound Body</p>
|
|
26
|
+
</Accordion.Body>
|
|
27
|
+
</Accordion>
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
expect(screen.getByText('Compound Header')).toBeInTheDocument();
|
|
31
|
+
expect(screen.getByText('Compound Body')).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('toggles visibility in compound mode', () => {
|
|
35
|
+
render(
|
|
36
|
+
<Accordion>
|
|
37
|
+
<Accordion.Header>Header</Accordion.Header>
|
|
38
|
+
<Accordion.Body>Body</Accordion.Body>
|
|
39
|
+
</Accordion>
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const button = screen.getByRole('button');
|
|
43
|
+
expect(button).toHaveAttribute('aria-expanded', 'false');
|
|
44
|
+
|
|
45
|
+
fireEvent.click(button);
|
|
46
|
+
expect(button).toHaveAttribute('aria-expanded', 'true');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('injects props into compound components', () => {
|
|
50
|
+
// We want to verify that aria-controls and aria-labelledby are correctly linked
|
|
51
|
+
// even in compound mode (since the parent injects IDs)
|
|
52
|
+
render(
|
|
53
|
+
<Accordion>
|
|
54
|
+
<Accordion.Header>Header</Accordion.Header>
|
|
55
|
+
<Accordion.Body>Body</Accordion.Body>
|
|
56
|
+
</Accordion>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const button = screen.getByRole('button');
|
|
60
|
+
const region = screen.getByRole('region');
|
|
61
|
+
|
|
62
|
+
const controlsId = button.getAttribute('aria-controls');
|
|
63
|
+
const labelledById = region.getAttribute('aria-labelledby');
|
|
64
|
+
const buttonId = button.getAttribute('id');
|
|
65
|
+
const regionId = region.getAttribute('id');
|
|
66
|
+
|
|
67
|
+
expect(controlsId).toBe(regionId);
|
|
68
|
+
expect(labelledById).toBe(buttonId);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -35,12 +35,7 @@ const GlassFilterComponent: React.FC<GlassFilterProps> = ({
|
|
|
35
35
|
aria-hidden="true"
|
|
36
36
|
>
|
|
37
37
|
<defs>
|
|
38
|
-
<radialGradient
|
|
39
|
-
id={`${id}-edge-mask`}
|
|
40
|
-
cx="50%"
|
|
41
|
-
cy="50%"
|
|
42
|
-
r="50%"
|
|
43
|
-
>
|
|
38
|
+
<radialGradient id={`${id}-edge-mask`} cx="50%" cy="50%" r="50%">
|
|
44
39
|
<stop offset="0%" stopColor="black" stopOpacity="0" />
|
|
45
40
|
<stop
|
|
46
41
|
offset={`${Math.max(30, 80 - aberrationIntensity * 2)}%`}
|
|
@@ -49,14 +44,7 @@ const GlassFilterComponent: React.FC<GlassFilterProps> = ({
|
|
|
49
44
|
/>
|
|
50
45
|
<stop offset="100%" stopColor="white" stopOpacity="1" />
|
|
51
46
|
</radialGradient>
|
|
52
|
-
<filter
|
|
53
|
-
id={id}
|
|
54
|
-
x="-35%"
|
|
55
|
-
y="-35%"
|
|
56
|
-
width="170%"
|
|
57
|
-
height="170%"
|
|
58
|
-
colorInterpolationFilters="sRGB"
|
|
59
|
-
>
|
|
47
|
+
<filter id={id} x="-35%" y="-35%" width="170%" height="170%" colorInterpolationFilters="sRGB">
|
|
60
48
|
<feImage
|
|
61
49
|
id={`${id}-image`}
|
|
62
50
|
x="0"
|
|
@@ -64,7 +52,13 @@ const GlassFilterComponent: React.FC<GlassFilterProps> = ({
|
|
|
64
52
|
width="100%"
|
|
65
53
|
height="100%"
|
|
66
54
|
result="DISPLACEMENT_MAP"
|
|
67
|
-
href={getDisplacementMap(
|
|
55
|
+
href={getDisplacementMap(
|
|
56
|
+
mode,
|
|
57
|
+
displacementMap,
|
|
58
|
+
polarDisplacementMap,
|
|
59
|
+
prominentDisplacementMap,
|
|
60
|
+
shaderMapUrl
|
|
61
|
+
)}
|
|
68
62
|
preserveAspectRatio="xMidYMid slice"
|
|
69
63
|
/>
|
|
70
64
|
|
|
@@ -178,4 +172,3 @@ export const GlassFilter = memo(GlassFilterComponent, (prevProps, nextProps) =>
|
|
|
178
172
|
prevProps.blurAmount === nextProps.blurAmount
|
|
179
173
|
);
|
|
180
174
|
});
|
|
181
|
-
|
|
@@ -45,7 +45,9 @@ export const calculateMouseInfluence = (mouseOffset: MousePosition): number => {
|
|
|
45
45
|
return 0;
|
|
46
46
|
}
|
|
47
47
|
// Bounded calculation — keeps the glass effect subtle and stable
|
|
48
|
-
const influence =
|
|
48
|
+
const influence =
|
|
49
|
+
Math.sqrt(mouseOffset.x * mouseOffset.x + mouseOffset.y * mouseOffset.y) /
|
|
50
|
+
CONSTANTS.MOUSE_INFLUENCE_DIVISOR;
|
|
49
51
|
return Math.min(0.8, influence); // Tighter cap to prevent blur/filter blow-out
|
|
50
52
|
};
|
|
51
53
|
|
|
@@ -59,7 +61,7 @@ export const calculateOverLightIntensity = (
|
|
|
59
61
|
if (!mouseOffset || typeof mouseOffset.x !== 'number' || typeof mouseOffset.y !== 'number') {
|
|
60
62
|
return baseIntensity;
|
|
61
63
|
}
|
|
62
|
-
|
|
64
|
+
|
|
63
65
|
// Calculate additional intensity based on mouse position
|
|
64
66
|
const mouseInfluence = calculateMouseInfluence(mouseOffset);
|
|
65
67
|
return Math.min(1.0, baseIntensity * (1 + mouseInfluence * 0.3));
|
|
@@ -260,4 +262,3 @@ export const getDisplacementMap = (
|
|
|
260
262
|
return displacementMap;
|
|
261
263
|
}
|
|
262
264
|
};
|
|
263
|
-
|