@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.
Files changed (276) hide show
  1. package/build-tools/index.d.ts +31 -30
  2. package/build-tools/package.json +4 -21
  3. package/dist/atomix.css +20234 -2027
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +76 -2
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/build-tools/index.d.ts +31 -30
  8. package/dist/build-tools/package.json +4 -21
  9. package/dist/charts.js +4 -5
  10. package/dist/charts.js.map +1 -1
  11. package/dist/core.d.ts +87 -10
  12. package/dist/core.js +673 -480
  13. package/dist/core.js.map +1 -1
  14. package/dist/forms.d.ts +15 -3
  15. package/dist/forms.js +530 -97
  16. package/dist/forms.js.map +1 -1
  17. package/dist/heavy.js +5 -6
  18. package/dist/heavy.js.map +1 -1
  19. package/dist/index.d.ts +644 -277
  20. package/dist/index.esm.js +1948 -1347
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/index.js +3333 -2728
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.min.js +1 -1
  25. package/dist/index.min.js.map +1 -1
  26. package/dist/layout.js.map +1 -1
  27. package/dist/theme.d.ts +9 -9
  28. package/dist/theme.js.map +1 -1
  29. package/package.json +2 -2
  30. package/scripts/atomix-cli.js +10 -1
  31. package/scripts/cli/__tests__/utils.test.js +6 -2
  32. package/scripts/cli/migration-tools.js +2 -2
  33. package/scripts/cli/theme-bridge.js +7 -9
  34. package/scripts/cli/utils.js +2 -1
  35. package/src/components/Accordion/Accordion.stories.tsx +72 -23
  36. package/src/components/Accordion/Accordion.test.tsx +70 -50
  37. package/src/components/Accordion/Accordion.tsx +219 -96
  38. package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
  39. package/src/components/AtomixGlass/AtomixGlass.test.tsx +1 -1
  40. package/src/components/AtomixGlass/GlassFilter.tsx +9 -16
  41. package/src/components/AtomixGlass/glass-utils.ts +4 -3
  42. package/src/components/AtomixGlass/shader-utils.ts +128 -52
  43. package/src/components/AtomixGlass/stories/Playground.stories.tsx +1 -1
  44. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +1 -1
  45. package/src/components/Avatar/Avatar.stories.tsx +45 -62
  46. package/src/components/Avatar/Avatar.tsx +58 -56
  47. package/src/components/Badge/Badge.stories.tsx +20 -9
  48. package/src/components/Badge/Badge.test.tsx +41 -41
  49. package/src/components/Badge/Badge.tsx +64 -62
  50. package/src/components/Block/Block.stories.tsx +14 -4
  51. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +9 -8
  52. package/src/components/Breadcrumb/Breadcrumb.tsx +173 -65
  53. package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
  54. package/src/components/Button/Button.stories.tsx +13 -22
  55. package/src/components/Button/Button.test.tsx +97 -81
  56. package/src/components/Button/Button.tsx +46 -14
  57. package/src/components/Button/ButtonGroup.stories.tsx +37 -32
  58. package/src/components/Button/ButtonGroup.tsx +4 -15
  59. package/src/components/Callout/Callout.stories.tsx +166 -918
  60. package/src/components/Callout/Callout.tsx +196 -84
  61. package/src/components/Callout/CalloutCompound.test.tsx +72 -0
  62. package/src/components/Card/Card.stories.tsx +67 -36
  63. package/src/components/Card/Card.tsx +30 -14
  64. package/src/components/Chart/AreaChart.tsx +1 -1
  65. package/src/components/Chart/CandlestickChart.tsx +23 -16
  66. package/src/components/Chart/Chart.stories.tsx +4 -9
  67. package/src/components/Chart/Chart.tsx +40 -44
  68. package/src/components/Chart/ChartRenderer.tsx +39 -12
  69. package/src/components/Chart/ChartToolbar.tsx +21 -5
  70. package/src/components/Chart/DonutChart.tsx +1 -1
  71. package/src/components/Chart/FunnelChart.tsx +4 -1
  72. package/src/components/Chart/GaugeChart.tsx +3 -1
  73. package/src/components/Chart/HeatmapChart.tsx +50 -37
  74. package/src/components/Chart/LineChart.tsx +3 -2
  75. package/src/components/Chart/MultiAxisChart.tsx +24 -16
  76. package/src/components/Chart/RadarChart.tsx +19 -17
  77. package/src/components/Chart/ScatterChart.tsx +29 -21
  78. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +6 -2
  79. package/src/components/ColorModeToggle/ColorModeToggle.tsx +15 -3
  80. package/src/components/Countdown/Countdown.stories.tsx +7 -7
  81. package/src/components/DataTable/DataTable.stories.tsx +43 -38
  82. package/src/components/DataTable/DataTable.test.tsx +26 -148
  83. package/src/components/DataTable/DataTable.tsx +485 -456
  84. package/src/components/DatePicker/DatePicker.stories.tsx +32 -47
  85. package/src/components/DatePicker/DatePicker.tsx +31 -26
  86. package/src/components/Dropdown/Dropdown.stories.tsx +2 -5
  87. package/src/components/Dropdown/Dropdown.tsx +425 -298
  88. package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
  89. package/src/components/EdgePanel/EdgePanel.stories.tsx +6 -19
  90. package/src/components/EdgePanel/EdgePanel.tsx +163 -113
  91. package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
  92. package/src/components/Footer/Footer.stories.tsx +21 -16
  93. package/src/components/Footer/Footer.tsx +130 -128
  94. package/src/components/Footer/FooterLink.tsx +2 -2
  95. package/src/components/Form/Checkbox.test.tsx +49 -49
  96. package/src/components/Form/Checkbox.tsx +108 -100
  97. package/src/components/Form/Form.stories.tsx +2 -10
  98. package/src/components/Form/Input.stories.tsx +22 -39
  99. package/src/components/Form/Input.test.tsx +38 -44
  100. package/src/components/Form/Radio.stories.tsx +6 -12
  101. package/src/components/Form/Radio.tsx +68 -66
  102. package/src/components/Form/Select.stories.tsx +23 -0
  103. package/src/components/Form/Select.test.tsx +99 -0
  104. package/src/components/Form/Select.tsx +239 -186
  105. package/src/components/Form/SelectOption.tsx +88 -0
  106. package/src/components/Form/Textarea.test.tsx +27 -32
  107. package/src/components/Hero/Hero.stories.tsx +93 -23
  108. package/src/components/Hero/Hero.test.tsx +142 -0
  109. package/src/components/Hero/Hero.tsx +343 -58
  110. package/src/components/Icon/index.ts +7 -1
  111. package/src/components/List/List.test.tsx +62 -0
  112. package/src/components/List/List.tsx +32 -25
  113. package/src/components/List/ListItem.tsx +20 -0
  114. package/src/components/Modal/Modal.stories.tsx +67 -2
  115. package/src/components/Modal/Modal.tsx +208 -125
  116. package/src/components/Modal/ModalCompound.test.tsx +94 -0
  117. package/src/components/Navigation/Menu/MegaMenu.tsx +70 -70
  118. package/src/components/Navigation/Nav/NavDropdown.tsx +1 -5
  119. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +128 -28
  120. package/src/components/Navigation/SideMenu/SideMenu.tsx +5 -7
  121. package/src/components/Navigation/SideMenu/SideMenuItem.tsx +4 -5
  122. package/src/components/Pagination/Pagination.stories.tsx +7 -4
  123. package/src/components/Pagination/Pagination.tsx +199 -202
  124. package/src/components/PhotoViewer/PhotoViewer.tsx +4 -1
  125. package/src/components/Popover/Popover.stories.tsx +99 -192
  126. package/src/components/Popover/Popover.tsx +41 -37
  127. package/src/components/Progress/Progress.stories.tsx +35 -44
  128. package/src/components/River/River.stories.tsx +2 -1
  129. package/src/components/SectionIntro/SectionIntro.stories.tsx +71 -71
  130. package/src/components/Slider/Slider.stories.tsx +12 -4
  131. package/src/components/Spinner/Spinner.stories.tsx +3 -1
  132. package/src/components/Spinner/Spinner.test.tsx +23 -23
  133. package/src/components/Spinner/Spinner.tsx +43 -46
  134. package/src/components/Steps/Steps.stories.tsx +8 -6
  135. package/src/components/Steps/Steps.tsx +124 -21
  136. package/src/components/Steps/StepsCompound.test.tsx +81 -0
  137. package/src/components/Tabs/Tabs.stories.tsx +12 -9
  138. package/src/components/Tabs/Tabs.tsx +230 -75
  139. package/src/components/Tabs/TabsCompound.test.tsx +64 -0
  140. package/src/components/Toggle/Toggle.stories.tsx +27 -13
  141. package/src/components/Toggle/Toggle.test.tsx +65 -70
  142. package/src/components/Toggle/Toggle.tsx +4 -1
  143. package/src/components/Tooltip/Tooltip.stories.tsx +24 -20
  144. package/src/components/Tooltip/Tooltip.tsx +104 -106
  145. package/src/components/Upload/Upload.stories.tsx +129 -127
  146. package/src/components/Upload/Upload.tsx +287 -283
  147. package/src/components/VideoPlayer/VideoPlayer.tsx +6 -1
  148. package/src/components/index.ts +13 -2
  149. package/src/layouts/Grid/Grid.stories.tsx +9 -3
  150. package/src/layouts/MasonryGrid/MasonryGrid.tsx +5 -1
  151. package/src/lib/__tests__/theme-tools.test.ts +32 -6
  152. package/src/lib/composables/index.ts +0 -4
  153. package/src/lib/composables/shared-mouse-tracker.ts +13 -14
  154. package/src/lib/composables/useAtomixGlass.ts +102 -60
  155. package/src/lib/composables/useChartExport.ts +1 -1
  156. package/src/lib/composables/useDataTable.ts +29 -17
  157. package/src/lib/composables/useHero.ts +58 -14
  158. package/src/lib/composables/useHeroBackgroundSlider.ts +2 -9
  159. package/src/lib/composables/useInput.ts +10 -8
  160. package/src/lib/composables/useSideMenu.ts +6 -5
  161. package/src/lib/composables/useTooltip.ts +1 -2
  162. package/src/lib/composables/useVideoPlayer.ts +44 -35
  163. package/src/lib/config/index.ts +154 -154
  164. package/src/lib/constants/cssVariables.ts +29 -29
  165. package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +2 -6
  166. package/src/lib/hooks/index.ts +1 -1
  167. package/src/lib/hooks/useComponentCustomization.ts +11 -17
  168. package/src/lib/hooks/usePerformanceMonitor.ts +6 -7
  169. package/src/lib/patterns/__tests__/slots.test.ts +1 -1
  170. package/src/lib/patterns/index.ts +1 -1
  171. package/src/lib/patterns/slots.tsx +8 -13
  172. package/src/lib/storybook/InteractiveDemo.tsx +13 -18
  173. package/src/lib/storybook/PreviewContainer.tsx +1 -1
  174. package/src/lib/storybook/VariantsGrid.tsx +3 -7
  175. package/src/lib/storybook/index.ts +1 -1
  176. package/src/lib/theme/adapters/cssVariableMapper.ts +47 -74
  177. package/src/lib/theme/adapters/index.ts +3 -9
  178. package/src/lib/theme/adapters/themeAdapter.ts +41 -26
  179. package/src/lib/theme/config/index.ts +1 -1
  180. package/src/lib/theme/config/types.ts +2 -2
  181. package/src/lib/theme/config/validator.ts +10 -5
  182. package/src/lib/theme/constants/constants.ts +2 -2
  183. package/src/lib/theme/constants/index.ts +1 -2
  184. package/src/lib/theme/core/__tests__/createTheme.test.ts +20 -22
  185. package/src/lib/theme/core/composeTheme.ts +32 -26
  186. package/src/lib/theme/core/createTheme.ts +1 -1
  187. package/src/lib/theme/core/createThemeObject.ts +308 -301
  188. package/src/lib/theme/core/index.ts +3 -3
  189. package/src/lib/theme/devtools/CLI.ts +105 -111
  190. package/src/lib/theme/devtools/Comparator.tsx +50 -32
  191. package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +50 -48
  192. package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +257 -63
  193. package/src/lib/theme/devtools/Inspector.tsx +75 -60
  194. package/src/lib/theme/devtools/LiveEditor.tsx +97 -76
  195. package/src/lib/theme/devtools/Preview.tsx +150 -106
  196. package/src/lib/theme/devtools/ThemeValidator.ts +29 -21
  197. package/src/lib/theme/devtools/index.ts +3 -9
  198. package/src/lib/theme/devtools/useHistory.ts +23 -21
  199. package/src/lib/theme/errors/errors.ts +12 -11
  200. package/src/lib/theme/errors/index.ts +2 -7
  201. package/src/lib/theme/generators/generateCSS.ts +9 -13
  202. package/src/lib/theme/generators/generateCSSNested.ts +1 -6
  203. package/src/lib/theme/generators/generateCSSVariables.ts +673 -630
  204. package/src/lib/theme/generators/index.ts +1 -4
  205. package/src/lib/theme/i18n/index.ts +1 -1
  206. package/src/lib/theme/i18n/rtl.ts +13 -13
  207. package/src/lib/theme/index.ts +7 -16
  208. package/src/lib/theme/runtime/ThemeApplicator.ts +4 -4
  209. package/src/lib/theme/runtime/ThemeContext.tsx +1 -1
  210. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +19 -23
  211. package/src/lib/theme/runtime/ThemeProvider.tsx +230 -239
  212. package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +1 -1
  213. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +24 -29
  214. package/src/lib/theme/runtime/index.ts +2 -5
  215. package/src/lib/theme/runtime/useTheme.ts +18 -18
  216. package/src/lib/theme/runtime/useThemeTokens.ts +22 -22
  217. package/src/lib/theme/test/testTheme.ts +15 -16
  218. package/src/lib/theme/tokens/index.ts +2 -7
  219. package/src/lib/theme/tokens/tokens.ts +25 -24
  220. package/src/lib/theme/types.ts +428 -411
  221. package/src/lib/theme/utils/__tests__/themeValidation.test.ts +3 -3
  222. package/src/lib/theme/utils/componentTheming.ts +18 -18
  223. package/src/lib/theme/utils/domUtils.ts +277 -289
  224. package/src/lib/theme/utils/index.ts +1 -2
  225. package/src/lib/theme/utils/injectCSS.ts +10 -14
  226. package/src/lib/theme/utils/naming.ts +20 -16
  227. package/src/lib/theme/utils/themeHelpers.ts +10 -12
  228. package/src/lib/theme/utils/themeUtils.ts +85 -86
  229. package/src/lib/theme/utils/themeValidation.ts +82 -33
  230. package/src/lib/theme-tools.ts +8 -6
  231. package/src/lib/types/components.ts +180 -73
  232. package/src/lib/types/partProps.ts +1 -1
  233. package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
  234. package/src/lib/utils/__tests__/csv.test.ts +1 -1
  235. package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
  236. package/src/lib/utils/componentUtils.ts +8 -12
  237. package/src/lib/utils/csv.ts +3 -1
  238. package/src/lib/utils/dataTableExport.ts +1 -5
  239. package/src/lib/utils/fontPreloader.ts +10 -19
  240. package/src/lib/utils/icons.ts +4 -1
  241. package/src/lib/utils/index.ts +2 -6
  242. package/src/lib/utils/memoryMonitor.ts +10 -8
  243. package/src/lib/utils/themeNaming.ts +3 -3
  244. package/src/styles/01-settings/_index.scss +0 -1
  245. package/src/styles/01-settings/_settings.colors.scss +8 -8
  246. package/src/styles/01-settings/_settings.design-tokens.scss +61 -50
  247. package/src/styles/01-settings/_settings.navbar.scss +1 -1
  248. package/src/styles/01-settings/_settings.spacing.scss +3 -4
  249. package/src/styles/01-settings/_settings.tooltip.scss +1 -1
  250. package/src/styles/01-settings/_settings.typography.scss +1 -1
  251. package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
  252. package/src/styles/02-tools/_tools.button.scss +51 -21
  253. package/src/styles/02-tools/_tools.utility-api.scss +36 -24
  254. package/src/styles/03-generic/_generic.root.scss +4 -3
  255. package/src/styles/06-components/_components.atomix-glass.scss +13 -9
  256. package/src/styles/06-components/_components.button.scss +16 -4
  257. package/src/styles/06-components/_components.callout.scss +27 -21
  258. package/src/styles/06-components/_components.card.scss +5 -14
  259. package/src/styles/06-components/_components.chart.scss +22 -19
  260. package/src/styles/06-components/_components.checkbox.scss +3 -1
  261. package/src/styles/06-components/_components.color-mode-toggle.scss +3 -1
  262. package/src/styles/06-components/_components.edge-panel.scss +9 -2
  263. package/src/styles/06-components/_components.footer.scss +1 -1
  264. package/src/styles/06-components/_components.side-menu.scss +5 -5
  265. package/src/styles/06-components/_components.toggle.scss +18 -0
  266. package/src/styles/06-components/_index.scss +1 -1
  267. package/src/styles/06-components/old.chart.styles.scss +0 -2
  268. package/src/styles/99-utilities/_utilities.border.scss +69 -27
  269. package/src/styles/99-utilities/_utilities.display.scss +1 -1
  270. package/src/styles/99-utilities/_utilities.opacity.scss +10 -0
  271. package/src/styles/99-utilities/_utilities.position.scss +16 -9
  272. package/src/styles/99-utilities/_utilities.scss +1 -1
  273. package/src/styles/99-utilities/_utilities.sizes.scss +47 -18
  274. package/src/styles/99-utilities/_utilities.spacing.scss +118 -66
  275. package/src/styles/99-utilities/_utilities.text-gradient.scss +30 -30
  276. 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 { AccordionProps as AccordionPropsType, AtomixGlassProps } from '../../lib/types/components';
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
- export const Accordion: React.FC<AccordionProps> = memo(({
10
- title,
11
- children,
12
- defaultOpen = false,
13
- isOpen: controlledOpen,
14
- onOpenChange,
15
- onOpen,
16
- onClose,
17
- disabled = false,
18
- iconPosition = 'right',
19
- icon,
20
- className = '',
21
- style,
22
- glass,
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
- id={buttonId}
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
- {icon || defaultIcon}
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
- id={panelId}
89
- className={ACCORDION.SELECTORS.PANEL.replace('.', '')}
90
- ref={panelRef as React.RefObject<HTMLDivElement>}
88
+ ref={mergedPanelRef}
89
+ className={className} // Parent injects class names
91
90
  role="region"
92
- aria-labelledby={buttonId}
91
+ {...props}
93
92
  >
94
93
  <div
95
94
  className={ACCORDION.SELECTORS.BODY.replace('.', '')}
96
- ref={contentRef as React.RefObject<HTMLDivElement>}
95
+ ref={contentRef}
97
96
  >
98
97
  {children}
99
98
  </div>
100
99
  </div>
101
- </div>
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
- if (glass) {
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
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
231
+ return <AtomixGlass {...glassProps}>{content}</AtomixGlass>;
232
+ }
112
233
 
113
- return <AtomixGlass {...glassProps}>{accordionContent}</AtomixGlass>;
234
+ return content;
114
235
  }
236
+ );
115
237
 
116
- return accordionContent;
117
- });
238
+ AccordionImpl.displayName = 'Accordion';
118
239
 
119
- // Set display name for debugging
120
- Accordion.displayName = 'Accordion';
240
+ // Attach subcomponents
241
+ const AccordionWithSubcomponents = AccordionImpl as unknown as AccordionComponent;
242
+ AccordionWithSubcomponents.Header = AccordionHeader;
243
+ AccordionWithSubcomponents.Body = AccordionBody;
121
244
 
122
- // Export as default
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
+ });
@@ -10,7 +10,7 @@ vi.mock('./shader-utils', () => ({
10
10
  updateShader() {
11
11
  return 'data:image/png;base64,mockBase64String';
12
12
  }
13
- destroy() { }
13
+ destroy() {}
14
14
  },
15
15
  fragmentShaders: {
16
16
  liquidGlass: vi.fn(),
@@ -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(mode, displacementMap, polarDisplacementMap, prominentDisplacementMap, shaderMapUrl)}
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 = Math.sqrt(mouseOffset.x * mouseOffset.x + mouseOffset.y * mouseOffset.y) / CONSTANTS.MOUSE_INFLUENCE_DIVISOR;
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
-