@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
@@ -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
+ });
@@ -230,18 +230,13 @@ const DemoPanelContent = ({
230
230
  );
231
231
 
232
232
  export const BasicUsage: Story = {
233
- render: (args) => {
233
+ render: args => {
234
234
  const [isOpen, setIsOpen] = useState(false);
235
235
 
236
236
  return (
237
237
  <>
238
238
  <Button onClick={() => setIsOpen(true)}>Open Edge Panel</Button>
239
- <EdgePanel
240
- {...args}
241
- isOpen={isOpen}
242
- onOpenChange={setIsOpen}
243
- title="Basic Edge Panel"
244
- >
239
+ <EdgePanel {...args} isOpen={isOpen} onOpenChange={setIsOpen} title="Basic Edge Panel">
245
240
  <DemoPanelContent {...args} />
246
241
  </EdgePanel>
247
242
  </>
@@ -271,7 +266,7 @@ export const AllPositions: Story = {
271
266
  return (
272
267
  <>
273
268
  <div className="u-flex u-gap-2 u-flex-wrap">
274
- {(['start', 'end', 'top', 'bottom'] as const).map((pos) => (
269
+ {(['start', 'end', 'top', 'bottom'] as const).map(pos => (
275
270
  <Button
276
271
  key={pos}
277
272
  variant={position === pos ? 'primary' : 'secondary'}
@@ -306,18 +301,13 @@ export const AllPositions: Story = {
306
301
  };
307
302
 
308
303
  export const WithSlideMode: Story = {
309
- render: (args) => {
304
+ render: args => {
310
305
  const [isOpen, setIsOpen] = useState(false);
311
306
 
312
307
  return (
313
308
  <>
314
309
  <Button onClick={() => setIsOpen(true)}>Open Slide Mode Panel</Button>
315
- <EdgePanel
316
- {...args}
317
- isOpen={isOpen}
318
- onOpenChange={setIsOpen}
319
- title="Slide Mode Panel"
320
- >
310
+ <EdgePanel {...args} isOpen={isOpen} onOpenChange={setIsOpen} title="Slide Mode Panel">
321
311
  <DemoPanelContent {...args} />
322
312
  </EdgePanel>
323
313
  </>
@@ -834,10 +824,7 @@ export const GlassShowcase: Story = {
834
824
  <p className="u-mb-4">
835
825
  Enhanced polar distortion creates unique radial patterns from center.
836
826
  </p>
837
- <Card
838
- title="Radial Distortion"
839
- text="Creates circular displacement patterns."
840
- />
827
+ <Card title="Radial Distortion" text="Creates circular displacement patterns." />
841
828
  </div>
842
829
  </EdgePanel>
843
830
 
@@ -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,131 +64,138 @@ import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
21
64
  * <p>Panel content</p>
22
65
  * </EdgePanel>
23
66
  *
24
- * // With glass effect
25
- * <EdgePanel
26
- * title="Glass Panel"
27
- * isOpen={isOpen}
28
- * onOpenChange={setIsOpen}
29
- * position="end"
30
- * glass={true}
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
- export const EdgePanel: React.FC<EdgePanelProps> = ({
54
- title,
55
- children,
56
- position = 'start',
57
- mode = 'slide',
58
- isOpen = false,
59
- onOpenChange,
60
- backdrop = true,
61
- closeOnBackdropClick = true,
62
- closeOnEscape = true,
63
- className = '',
64
- style,
65
- glass,
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
- // Moved useRef outside of conditional rendering to fix hook order issue
86
- const glassContentRef = useRef<HTMLDivElement>(null);
118
+ // Moved useRef outside of conditional rendering to fix hook order issue
119
+ const glassContentRef = useRef<HTMLDivElement>(null);
87
120
 
88
- const panelClass = generateEdgePanelClass({
89
- position,
90
- isOpen,
91
- className: glass ? `${className} c-edge-panel--glass` : className,
92
- });
121
+ const panelClass = generateEdgePanelClass({
122
+ position,
123
+ isOpen,
124
+ className: glass ? `${className} c-edge-panel--glass` : className,
125
+ });
93
126
 
94
- // If not open and not controlled by parent, don't render
95
- if (!isOpenState && isOpen === false) {
96
- return null;
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
- const defaultGlassProps = {
100
- elasticity: 0,
101
- };
102
-
103
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
104
-
105
- const panelContent = (
106
- <>
107
- <div className="c-edge-panel__header">
108
- <h4>{title}</h4>
109
- <button
110
- className="c-edge-panel__close c-btn c-btn--icon"
111
- onClick={() => closePanel()}
112
- aria-label="Close panel"
113
- >
114
- <Icon name="X" />
115
- </button>
116
- </div>
117
- <div className="c-edge-panel__body">{children}</div>
118
- </>
119
- );
120
-
121
- return (
122
- <div className={panelClass} data-position={position} data-mode={mode} style={style}>
123
- {backdrop && (
124
- <div ref={backdropRef} className="c-edge-panel__backdrop" onClick={handleBackdropClick} />
125
- )}
126
- <div ref={containerRef} className="c-edge-panel__container">
127
- {glass ? (
128
- <AtomixGlass
129
- {...glassProps}
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"
130
160
  >
131
- <div
132
- ref={glassContentRef}
133
- className="c-edge-panel__glass-content"
134
- style={{ borderRadius: containerRef.current?.style.borderRadius }}
135
- >
136
- {panelContent}
137
- </div>
138
- </AtomixGlass>
139
- ) : (
140
- panelContent
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} />
141
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>
142
188
  </div>
143
- </div>
144
- );
145
- };
146
-
147
- export type { EdgePanelProps };
189
+ );
190
+ }
191
+ ) as unknown as EdgePanelComponent;
148
192
 
149
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 };
150
200
 
151
201
  export default EdgePanel;
@@ -0,0 +1,53 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { EdgePanel } from './EdgePanel';
4
+ import React from 'react';
5
+
6
+ describe('EdgePanel Component', () => {
7
+ it('renders correctly with legacy props', () => {
8
+ render(
9
+ <EdgePanel isOpen={true} title="Legacy Title">
10
+ Legacy Content
11
+ </EdgePanel>
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
+ <EdgePanel isOpen={true}>
21
+ <EdgePanel.Header>
22
+ <h4>Compound Title</h4>
23
+ </EdgePanel.Header>
24
+ <EdgePanel.Body>
25
+ Compound Content
26
+ </EdgePanel.Body>
27
+ <EdgePanel.Footer>
28
+ Footer
29
+ </EdgePanel.Footer>
30
+ </EdgePanel>
31
+ );
32
+
33
+ expect(screen.getByText('Compound Title')).toBeInTheDocument();
34
+ expect(screen.getByText('Compound Content')).toBeInTheDocument();
35
+ expect(screen.getByText('Footer')).toBeInTheDocument();
36
+ });
37
+
38
+ it('uses close button in compound mode', () => {
39
+ const onClose = vi.fn();
40
+ render(
41
+ <EdgePanel isOpen={true} onOpenChange={onClose}>
42
+ <EdgePanel.Header>
43
+ <EdgePanel.CloseButton onClick={() => onClose(false)} />
44
+ </EdgePanel.Header>
45
+ <EdgePanel.Body>Content</EdgePanel.Body>
46
+ </EdgePanel>
47
+ );
48
+
49
+ const closeBtn = screen.getByLabelText('Close panel');
50
+ fireEvent.click(closeBtn);
51
+ expect(onClose).toHaveBeenCalledWith(false);
52
+ });
53
+ });
@@ -97,7 +97,10 @@ Footer provides a comprehensive footer section for websites with multiple layout
97
97
  options: ['columns', 'centered', 'minimal', 'stacked', 'flexible', 'sidebar', 'wide'],
98
98
  description: 'Footer layout variant',
99
99
  table: {
100
- type: { summary: '"columns" | "centered" | "minimal" | "stacked" | "flexible" | "sidebar" | "wide"' },
100
+ type: {
101
+ summary:
102
+ '"columns" | "centered" | "minimal" | "stacked" | "flexible" | "sidebar" | "wide"',
103
+ },
101
104
  defaultValue: { summary: 'columns' },
102
105
  },
103
106
  },
@@ -289,7 +292,7 @@ export const BasicUsage: Story = {
289
292
  copyright: '© 2024 Atomix. All rights reserved.',
290
293
  socialLinks: sampleSocialLinks,
291
294
  },
292
- render: (args) => (
295
+ render: args => (
293
296
  <Footer {...args}>
294
297
  <SampleFooterContent />
295
298
  </Footer>
@@ -316,7 +319,7 @@ export const WithNewsletter: Story = {
316
319
  onNewsletterSubmit: fn(),
317
320
  socialLinks: sampleSocialLinks,
318
321
  },
319
- render: (args) => (
322
+ render: args => (
320
323
  <Footer {...args}>
321
324
  <SampleFooterContent />
322
325
  </Footer>
@@ -340,7 +343,7 @@ export const WithBackToTop: Story = {
340
343
  onBackToTop: fn(),
341
344
  socialLinks: sampleSocialLinks,
342
345
  },
343
- render: (args) => (
346
+ render: args => (
344
347
  <Footer {...args}>
345
348
  <SampleFooterContent />
346
349
  </Footer>
@@ -362,7 +365,7 @@ export const CenteredLayout: Story = {
362
365
  copyright: '© 2024 Atomix. All rights reserved.',
363
366
  socialLinks: sampleSocialLinks,
364
367
  },
365
- render: (args) => (
368
+ render: args => (
366
369
  <Footer {...args}>
367
370
  <SampleFooterContent />
368
371
  </Footer>
@@ -384,7 +387,7 @@ export const MinimalLayout: Story = {
384
387
  copyright: '© 2024 Atomix. All rights reserved.',
385
388
  socialLinks: sampleSocialLinks,
386
389
  },
387
- render: (args) => (
390
+ render: args => (
388
391
  <Footer {...args}>
389
392
  <SampleFooterContent />
390
393
  </Footer>
@@ -406,7 +409,7 @@ export const StackedLayout: Story = {
406
409
  copyright: '© 2024 Atomix. All rights reserved.',
407
410
  socialLinks: sampleSocialLinks,
408
411
  },
409
- render: (args) => (
412
+ render: args => (
410
413
  <Footer {...args}>
411
414
  <SampleFooterContent />
412
415
  </Footer>
@@ -428,7 +431,7 @@ export const FlexibleLayout: Story = {
428
431
  copyright: '© 2024 Atomix. All rights reserved.',
429
432
  socialLinks: sampleSocialLinks,
430
433
  },
431
- render: (args) => (
434
+ render: args => (
432
435
  <Footer {...args}>
433
436
  <SampleFooterContent />
434
437
  </Footer>
@@ -450,7 +453,7 @@ export const SidebarLayout: Story = {
450
453
  copyright: '© 2024 Atomix. All rights reserved.',
451
454
  socialLinks: sampleSocialLinks,
452
455
  },
453
- render: (args) => (
456
+ render: args => (
454
457
  <Footer {...args}>
455
458
  <SampleFooterContent />
456
459
  </Footer>
@@ -472,7 +475,7 @@ export const WideLayout: Story = {
472
475
  copyright: '© 2024 Atomix. All rights reserved.',
473
476
  socialLinks: sampleSocialLinks,
474
477
  },
475
- render: (args) => (
478
+ render: args => (
476
479
  <Footer {...args}>
477
480
  <SampleFooterContent />
478
481
  </Footer>
@@ -494,7 +497,7 @@ export const DarkVariant: Story = {
494
497
  copyright: '© 2024 Atomix. All rights reserved.',
495
498
  socialLinks: sampleSocialLinks,
496
499
  },
497
- render: (args) => (
500
+ render: args => (
498
501
  <Footer {...args}>
499
502
  <SampleFooterContent />
500
503
  </Footer>
@@ -516,7 +519,7 @@ export const LargeSize: Story = {
516
519
  copyright: '© 2024 Atomix. All rights reserved.',
517
520
  socialLinks: sampleSocialLinks,
518
521
  },
519
- render: (args) => (
522
+ render: args => (
520
523
  <Footer {...args}>
521
524
  <SampleFooterContent />
522
525
  </Footer>
@@ -538,7 +541,7 @@ export const WithGlassEffect: Story = {
538
541
  socialLinks: sampleSocialLinks,
539
542
  glass: true,
540
543
  },
541
- render: (args) => (
544
+ render: args => (
542
545
  <Footer {...args}>
543
546
  <SampleFooterContent />
544
547
  </Footer>
@@ -560,9 +563,11 @@ export const StickyFooter: Story = {
560
563
  copyright: '© 2024 Atomix. All rights reserved.',
561
564
  socialLinks: sampleSocialLinks,
562
565
  },
563
- render: (args) => (
566
+ render: args => (
564
567
  <div style={{ minHeight: '200vh' }}>
565
- <div style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
568
+ <div
569
+ style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
570
+ >
566
571
  <p>Scroll down to see the sticky footer</p>
567
572
  </div>
568
573
  <Footer {...args}>
@@ -577,4 +582,4 @@ export const StickyFooter: Story = {
577
582
  },
578
583
  },
579
584
  },
580
- };
585
+ };