@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,9 +1,10 @@
1
- import React, { useEffect, useState, ReactNode } from 'react';
1
+ import React, { useEffect, useState, ReactNode, forwardRef, Children, cloneElement, isValidElement } from 'react';
2
2
  import { STEPS } from '../../lib/constants/components';
3
3
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
4
4
  import { AtomixGlassProps } from '../../lib/types/components';
5
5
 
6
- export interface StepItem {
6
+ // Legacy Item Interface
7
+ export interface StepItemData {
7
8
  /**
8
9
  * The number for the step
9
10
  */
@@ -20,11 +21,71 @@ export interface StepItem {
20
21
  content?: React.ReactNode;
21
22
  }
22
23
 
24
+ export type { StepItemData as StepItem };
25
+
26
+ // Compound Component Props
27
+ export interface StepsItemProps extends React.HTMLAttributes<HTMLDivElement> {
28
+ /**
29
+ * The number or icon for the step
30
+ */
31
+ number?: number | string | ReactNode;
32
+
33
+ /**
34
+ * The text label/title for the step
35
+ */
36
+ title?: ReactNode;
37
+
38
+ /**
39
+ * Whether the step is active
40
+ */
41
+ active?: boolean;
42
+
43
+ /**
44
+ * Whether the step is completed
45
+ */
46
+ completed?: boolean;
47
+
48
+ /**
49
+ * Index of the step (injected by parent)
50
+ */
51
+ index?: number;
52
+ }
53
+
54
+ export const StepsItem = forwardRef<HTMLDivElement, StepsItemProps>(
55
+ ({ children, className = '', number, title, active, completed, index, ...props }, ref) => {
56
+ const itemClasses = [
57
+ 'c-steps__item',
58
+ active ? STEPS.CLASSES.ACTIVE : '',
59
+ completed ? STEPS.CLASSES.COMPLETED : '',
60
+ className
61
+ ].filter(Boolean).join(' ');
62
+
63
+ return (
64
+ <div
65
+ ref={ref}
66
+ className={itemClasses}
67
+ aria-current={active ? 'step' : undefined}
68
+ data-index={index}
69
+ {...props}
70
+ >
71
+ <div className="c-steps__line"></div>
72
+ <div className="c-steps__content">
73
+ {(number !== undefined && number !== null) && <div className="c-steps__number">{number}</div>}
74
+ {title && <div className="c-steps__text">{title}</div>}
75
+ {children && <div className="c-steps__custom-content">{children}</div>}
76
+ </div>
77
+ </div>
78
+ );
79
+ }
80
+ );
81
+
82
+ StepsItem.displayName = 'StepsItem';
83
+
23
84
  export interface StepsProps {
24
85
  /**
25
- * Array of step items
86
+ * Array of step items (Legacy)
26
87
  */
27
- items: StepItem[];
88
+ items?: StepItemData[];
28
89
 
29
90
  /**
30
91
  * Current active step index (0-based)
@@ -56,12 +117,22 @@ export interface StepsProps {
56
117
  * Can be a boolean to enable with default settings, or an object with AtomixGlassProps to customize the effect
57
118
  */
58
119
  glass?: AtomixGlassProps | boolean;
120
+
121
+ /**
122
+ * Children (Compound)
123
+ */
124
+ children?: ReactNode;
59
125
  }
60
126
 
127
+ type StepsComponent = React.FC<StepsProps> & {
128
+ Item: typeof StepsItem;
129
+ Step: typeof StepsItem; // Alias for convenience
130
+ };
131
+
61
132
  /**
62
133
  * Steps component for displaying a sequence of steps
63
134
  */
64
- export const Steps: React.FC<StepsProps> = ({
135
+ const StepsComp: React.FC<StepsProps> = ({
65
136
  items,
66
137
  activeIndex = 0,
67
138
  vertical = false,
@@ -69,6 +140,7 @@ export const Steps: React.FC<StepsProps> = ({
69
140
  className = '',
70
141
  style,
71
142
  glass,
143
+ children,
72
144
  }) => {
73
145
  const [currentStep, setCurrentStep] = useState(activeIndex);
74
146
 
@@ -79,10 +151,11 @@ export const Steps: React.FC<StepsProps> = ({
79
151
  }
80
152
  }, [activeIndex]);
81
153
 
82
- // Method to go to next step
154
+ // Method to go to next step (Internal helper)
83
155
  const goToNextStep = () => {
84
156
  const nextIndex = currentStep + 1;
85
- if (nextIndex < items.length) {
157
+ const maxIndex = items ? items.length : Children.count(children);
158
+ if (nextIndex < maxIndex) {
86
159
  setCurrentStep(nextIndex);
87
160
  if (onStepChange) {
88
161
  onStepChange(nextIndex);
@@ -101,6 +174,45 @@ export const Steps: React.FC<StepsProps> = ({
101
174
  }
102
175
  };
103
176
 
177
+ let content: ReactNode;
178
+
179
+ if (items && items.length > 0) {
180
+ // Legacy rendering
181
+ content = items.map((item, index) => (
182
+ <StepsItem
183
+ key={`step-${index}`}
184
+ index={index}
185
+ number={item.number}
186
+ title={item.text}
187
+ active={index <= currentStep}
188
+ completed={index < currentStep}
189
+ >
190
+ {item.content}
191
+ </StepsItem>
192
+ ));
193
+ } else {
194
+ // Compound rendering
195
+ content = Children.map(children, (child, index) => {
196
+ if (isValidElement(child)) {
197
+ const childProps = child.props as any;
198
+ // Inject active/completed based on index if not explicitly provided
199
+ const isActive = childProps.active ?? index <= currentStep;
200
+ const isCompleted = childProps.completed ?? index < currentStep;
201
+
202
+ // If number is not provided, default to index + 1
203
+ const number = childProps.number ?? (index + 1);
204
+
205
+ return cloneElement(child, {
206
+ index,
207
+ active: isActive,
208
+ completed: isCompleted,
209
+ number,
210
+ } as any);
211
+ }
212
+ return child;
213
+ });
214
+ }
215
+
104
216
  const stepsContent = (
105
217
  <div
106
218
  className={`c-steps ${vertical ? STEPS.CLASSES.VERTICAL : ''} ${className}`}
@@ -108,20 +220,7 @@ export const Steps: React.FC<StepsProps> = ({
108
220
  role="navigation"
109
221
  aria-label="Steps"
110
222
  >
111
- {items.map((item, index) => (
112
- <div
113
- key={`step-${index}`}
114
- className={`c-steps__item ${index <= currentStep ? STEPS.CLASSES.ACTIVE : ''} ${index < currentStep ? STEPS.CLASSES.COMPLETED : ''}`}
115
- aria-current={index === currentStep ? 'step' : undefined}
116
- >
117
- <div className="c-steps__line"></div>
118
- <div className="c-steps__content">
119
- <div className="c-steps__number">{item.number}</div>
120
- <div className="c-steps__text">{item.text}</div>
121
- {item.content && <div className="c-steps__custom-content">{item.content}</div>}
122
- </div>
123
- </div>
124
- ))}
223
+ {content}
125
224
  </div>
126
225
  );
127
226
 
@@ -144,6 +243,10 @@ export const Steps: React.FC<StepsProps> = ({
144
243
  return stepsContent;
145
244
  };
146
245
 
246
+ export const Steps = StepsComp as StepsComponent;
247
+
147
248
  Steps.displayName = 'Steps';
249
+ Steps.Item = StepsItem;
250
+ Steps.Step = StepsItem;
148
251
 
149
252
  export default Steps;
@@ -0,0 +1,81 @@
1
+ import { render, screen } from '@testing-library/react';
2
+ import { describe, it, expect } from 'vitest';
3
+ import { Steps } from './Steps';
4
+ import React from 'react';
5
+
6
+ describe('Steps Component', () => {
7
+ it('renders correctly with legacy items prop', () => {
8
+ const items = [
9
+ { number: 1, text: 'Step 1' },
10
+ { number: 2, text: 'Step 2' },
11
+ { number: 3, text: 'Step 3' },
12
+ ];
13
+ render(<Steps items={items} activeIndex={1} />);
14
+
15
+ // Step 1: active (<= 1) and completed (< 1)
16
+ const step1 = screen.getByText('Step 1').closest('.c-steps__item');
17
+ expect(step1).toHaveClass('is-active');
18
+ expect(step1).toHaveClass('is-completed');
19
+
20
+ // Step 2: active (<= 1) and NOT completed (>= 1)
21
+ const step2 = screen.getByText('Step 2').closest('.c-steps__item');
22
+ expect(step2).toHaveClass('is-active');
23
+ expect(step2).not.toHaveClass('is-completed');
24
+
25
+ // Step 3: NOT active (> 1)
26
+ const step3 = screen.getByText('Step 3').closest('.c-steps__item');
27
+ expect(step3).not.toHaveClass('is-active');
28
+ });
29
+
30
+ it('renders correctly with compound components', () => {
31
+ render(
32
+ <Steps activeIndex={1}>
33
+ <Steps.Item title="Step 1">Content 1</Steps.Item>
34
+ <Steps.Item title="Step 2">Content 2</Steps.Item>
35
+ <Steps.Item title="Step 3">Content 3</Steps.Item>
36
+ </Steps>
37
+ );
38
+
39
+ // Verify titles
40
+ expect(screen.getByText('Step 1')).toBeInTheDocument();
41
+ expect(screen.getByText('Step 2')).toBeInTheDocument();
42
+ expect(screen.getByText('Step 3')).toBeInTheDocument();
43
+
44
+ // Verify content
45
+ expect(screen.getByText('Content 1')).toBeInTheDocument();
46
+
47
+ // Step 1: active and completed (inferred from activeIndex 1)
48
+ const step1 = screen.getByText('Step 1').closest('.c-steps__item');
49
+ expect(step1).toHaveClass('is-active');
50
+ expect(step1).toHaveClass('is-completed');
51
+
52
+ // Step 2: active and NOT completed
53
+ const step2 = screen.getByText('Step 2').closest('.c-steps__item');
54
+ expect(step2).toHaveClass('is-active');
55
+ expect(step2).not.toHaveClass('is-completed');
56
+
57
+ // Step 3: NOT active
58
+ const step3 = screen.getByText('Step 3').closest('.c-steps__item');
59
+ expect(step3).not.toHaveClass('is-active');
60
+
61
+ // Verify automatic numbering
62
+ expect(step1?.querySelector('.c-steps__number')).toHaveTextContent('1');
63
+ expect(step2?.querySelector('.c-steps__number')).toHaveTextContent('2');
64
+ expect(step3?.querySelector('.c-steps__number')).toHaveTextContent('3');
65
+ });
66
+
67
+ it('supports explicit props on Steps.Item', () => {
68
+ render(
69
+ <Steps>
70
+ <Steps.Item title="Custom Step" number="A" active completed>
71
+ Custom Content
72
+ </Steps.Item>
73
+ </Steps>
74
+ );
75
+
76
+ const step = screen.getByText('Custom Step').closest('.c-steps__item');
77
+ expect(step).toHaveClass('is-active');
78
+ expect(step).toHaveClass('is-completed');
79
+ expect(step?.querySelector('.c-steps__number')).toHaveTextContent('A');
80
+ });
81
+ });
@@ -180,13 +180,15 @@ export const WithDifferentActiveTab: Story = {
180
180
 
181
181
  export const WithGlassEffect: Story = {
182
182
  render: args => (
183
- <div style={{
184
- maxWidth: '600px',
185
- margin: '0 auto',
186
- padding: '30px',
187
- background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
188
- minHeight: '300px'
189
- }}>
183
+ <div
184
+ style={{
185
+ maxWidth: '600px',
186
+ margin: '0 auto',
187
+ padding: '30px',
188
+ background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
189
+ minHeight: '300px',
190
+ }}
191
+ >
190
192
  <Tabs {...args} />
191
193
  </div>
192
194
  ),
@@ -224,7 +226,8 @@ export const WithRichContent: Story = {
224
226
  parameters: {
225
227
  docs: {
226
228
  description: {
227
- story: 'Demonstrates tabs containing rich HTML content including headings, lists, and tables, showing the flexibility of the tabs component.',
229
+ story:
230
+ 'Demonstrates tabs containing rich HTML content including headings, lists, and tables, showing the flexibility of the tabs component.',
228
231
  },
229
232
  },
230
233
  },
@@ -392,4 +395,4 @@ export const GlassCustom: Story = {
392
395
  </div>
393
396
  </div>
394
397
  ),
395
- };
398
+ };
@@ -1,4 +1,4 @@
1
- import React, { useState, ReactNode, memo } from 'react';
1
+ import React, { useState, ReactNode, memo, createContext, useContext, forwardRef } from 'react';
2
2
  import { TAB } from '../../lib/constants/components';
3
3
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
4
4
  import { AtomixGlassProps } from '../../lib/types/components';
@@ -27,9 +27,9 @@ export interface TabsItemProps {
27
27
 
28
28
  export interface TabsProps {
29
29
  /**
30
- * Array of tab items
30
+ * Array of tab items (Legacy mode)
31
31
  */
32
- items: TabsItemProps[];
32
+ items?: TabsItemProps[];
33
33
 
34
34
  /**
35
35
  * Initial active tab index
@@ -56,89 +56,244 @@ export interface TabsProps {
56
56
  * Can be a boolean to enable with default settings, or an object with AtomixGlassProps to customize the effect
57
57
  */
58
58
  glass?: AtomixGlassProps | boolean;
59
+
60
+ /**
61
+ * Children (Compound mode)
62
+ */
63
+ children?: ReactNode;
64
+ }
65
+
66
+ // Context for compound usage
67
+ const TabsContext = createContext<{
68
+ currentTab: number;
69
+ handleTabClick: (index: number) => void;
70
+ }>({
71
+ currentTab: 0,
72
+ handleTabClick: () => {},
73
+ });
74
+
75
+ // Compound components
76
+ export const TabsList = forwardRef<HTMLUListElement, React.HTMLAttributes<HTMLUListElement>>(
77
+ ({ children, className = '', ...props }, ref) => {
78
+ return (
79
+ <ul ref={ref} className={`c-tabs__nav ${className}`.trim()} {...props}>
80
+ {React.Children.map(children, (child, index) => {
81
+ if (React.isValidElement(child)) {
82
+ // Inject index into TabsTrigger
83
+ return React.cloneElement(child, { index } as any);
84
+ }
85
+ return child;
86
+ })}
87
+ </ul>
88
+ );
89
+ }
90
+ );
91
+ TabsList.displayName = 'TabsList';
92
+
93
+ export interface TabsTriggerProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
94
+ index?: number; // Injected by TabsList or passed explicitly
95
+ }
96
+
97
+ export const TabsTrigger = forwardRef<HTMLButtonElement, TabsTriggerProps>(
98
+ ({ children, className = '', index, onClick, ...props }, ref) => {
99
+ const { currentTab, handleTabClick } = useContext(TabsContext);
100
+
101
+ // Safety check if used outside context or without index
102
+ if (index === undefined) {
103
+ console.warn('TabsTrigger requires an index prop or must be a direct child of TabsList');
104
+ }
105
+
106
+ const isActive = index !== undefined && currentTab === index;
107
+
108
+ return (
109
+ <li className="c-tabs__nav-item">
110
+ <button
111
+ ref={ref}
112
+ className={`c-tabs__nav-btn ${isActive ? TAB.CLASSES.ACTIVE : ''} ${className}`.trim()}
113
+ onClick={(e) => {
114
+ if (index !== undefined) handleTabClick(index);
115
+ onClick?.(e);
116
+ }}
117
+ data-tabindex={index}
118
+ role="tab"
119
+ aria-selected={isActive}
120
+ aria-controls={`tab-panel-${index}`}
121
+ type="button"
122
+ {...props}
123
+ >
124
+ {children}
125
+ </button>
126
+ </li>
127
+ );
128
+ }
129
+ );
130
+ TabsTrigger.displayName = 'TabsTrigger';
131
+
132
+ export const TabsPanels = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
133
+ ({ children, className = '', ...props }, ref) => {
134
+ return (
135
+ <div ref={ref} className={`c-tabs__panels ${className}`.trim()} {...props}>
136
+ {React.Children.map(children, (child, index) => {
137
+ if (React.isValidElement(child)) {
138
+ return React.cloneElement(child, { index } as any);
139
+ }
140
+ return child;
141
+ })}
142
+ </div>
143
+ );
144
+ }
145
+ );
146
+ TabsPanels.displayName = 'TabsPanels';
147
+
148
+ export interface TabsPanelProps extends React.HTMLAttributes<HTMLDivElement> {
149
+ index?: number;
59
150
  }
60
151
 
152
+ export const TabsPanel = forwardRef<HTMLDivElement, TabsPanelProps>(
153
+ ({ children, className = '', index, style, ...props }, ref) => {
154
+ const { currentTab } = useContext(TabsContext);
155
+ const isActive = index !== undefined && currentTab === index;
156
+
157
+ return (
158
+ <div
159
+ ref={ref}
160
+ className={`c-tabs__panel ${isActive ? TAB.CLASSES.ACTIVE : ''} ${className}`.trim()}
161
+ data-tabindex={index}
162
+ id={`tab-panel-${index}`}
163
+ role="tabpanel"
164
+ aria-labelledby={`tab-nav-${index}`}
165
+ style={{
166
+ height: isActive ? 'auto' : '0px',
167
+ opacity: isActive ? 1 : 0,
168
+ overflow: 'hidden',
169
+ transition: 'height 0.3s ease, opacity 0.3s ease',
170
+ ...style,
171
+ }}
172
+ {...props}
173
+ >
174
+ <div className="c-tabs__panel-body">{children}</div>
175
+ </div>
176
+ );
177
+ }
178
+ );
179
+ TabsPanel.displayName = 'TabsPanel';
180
+
181
+
61
182
  /**
62
183
  * Tabs component for switching between different content panels
63
184
  */
64
- export const Tabs: React.FC<TabsProps> = memo(({
65
- items,
66
- activeIndex = TAB.DEFAULTS.ACTIVE_INDEX,
67
- onTabChange,
68
- className = '',
69
- style,
70
- glass,
71
- }) => {
72
- const [currentTab, setCurrentTab] = useState(activeIndex);
73
-
74
- // Handle tab change
75
- const handleTabClick = (index: number) => {
76
- setCurrentTab(index);
77
- if (onTabChange) {
78
- onTabChange(index);
79
- }
80
- };
81
-
82
- const tabContent = (
83
- <div className={`c-tabs js-atomix-tab ${className}`} style={style}>
84
- <ul className="c-tabs__nav">
85
- {items.map((item, index) => (
86
- <li className="c-tabs__nav-item" key={`tab-nav-${index}`}>
87
- <button
88
- className={`c-tabs__nav-btn ${index === currentTab ? TAB.CLASSES.ACTIVE : ''}`}
89
- onClick={() => handleTabClick(index)}
90
- data-tabindex={index}
91
- role="tab"
92
- aria-selected={index === currentTab}
93
- aria-controls={`tab-panel-${index}`}
94
- >
95
- {item.label}
96
- </button>
97
- </li>
98
- ))}
99
- </ul>
100
- <div className="c-tabs__panels">
101
- {items.map((item, index) => (
102
- <div
103
- className={`c-tabs__panel ${index === currentTab ? TAB.CLASSES.ACTIVE : ''}`}
104
- key={`tab-panel-${index}`}
105
- data-tabindex={index}
106
- id={`tab-panel-${index}`}
107
- role="tabpanel"
108
- aria-labelledby={`tab-nav-${index}`}
109
- style={{
110
- height: index === currentTab ? 'auto' : '0px',
111
- opacity: index === currentTab ? 1 : 0,
112
- overflow: 'hidden',
113
- transition: 'height 0.3s ease, opacity 0.3s ease',
114
- }}
115
- >
116
- <div className="c-tabs__panel-body">{item.content}</div>
185
+ type TabsComponent = React.FC<TabsProps> & {
186
+ List: typeof TabsList;
187
+ Trigger: typeof TabsTrigger;
188
+ Panels: typeof TabsPanels;
189
+ Panel: typeof TabsPanel;
190
+ };
191
+
192
+ export const Tabs: TabsComponent = memo(
193
+ ({
194
+ items,
195
+ activeIndex = TAB.DEFAULTS.ACTIVE_INDEX,
196
+ onTabChange,
197
+ className = '',
198
+ style,
199
+ glass,
200
+ children,
201
+ }: TabsProps) => {
202
+ const [currentTab, setCurrentTab] = useState(activeIndex);
203
+
204
+ // Handle tab change
205
+ const handleTabClick = (index: number) => {
206
+ setCurrentTab(index);
207
+ if (onTabChange) {
208
+ onTabChange(index);
209
+ }
210
+ };
211
+
212
+ // Determine content based on mode (legacy items vs compound children)
213
+ let content: ReactNode;
214
+
215
+ // Use items prop if provided
216
+ if (items && items.length > 0) {
217
+ // Legacy mode
218
+ content = (
219
+ <>
220
+ <ul className="c-tabs__nav">
221
+ {items.map((item, index) => (
222
+ <li className="c-tabs__nav-item" key={`tab-nav-${index}`}>
223
+ <button
224
+ className={`c-tabs__nav-btn ${index === currentTab ? TAB.CLASSES.ACTIVE : ''}`}
225
+ onClick={() => handleTabClick(index)}
226
+ data-tabindex={index}
227
+ role="tab"
228
+ aria-selected={index === currentTab}
229
+ aria-controls={`tab-panel-${index}`}
230
+ >
231
+ {item.label}
232
+ </button>
233
+ </li>
234
+ ))}
235
+ </ul>
236
+ <div className="c-tabs__panels">
237
+ {items.map((item, index) => (
238
+ <div
239
+ className={`c-tabs__panel ${index === currentTab ? TAB.CLASSES.ACTIVE : ''}`}
240
+ key={`tab-panel-${index}`}
241
+ data-tabindex={index}
242
+ id={`tab-panel-${index}`}
243
+ role="tabpanel"
244
+ aria-labelledby={`tab-nav-${index}`}
245
+ style={{
246
+ height: index === currentTab ? 'auto' : '0px',
247
+ opacity: index === currentTab ? 1 : 0,
248
+ overflow: 'hidden',
249
+ transition: 'height 0.3s ease, opacity 0.3s ease',
250
+ }}
251
+ >
252
+ <div className="c-tabs__panel-body">{item.content}</div>
253
+ </div>
254
+ ))}
117
255
  </div>
118
- ))}
256
+ </>
257
+ );
258
+ } else {
259
+ // Compound mode
260
+ content = (
261
+ <TabsContext.Provider value={{ currentTab, handleTabClick }}>
262
+ {children}
263
+ </TabsContext.Provider>
264
+ );
265
+ }
266
+
267
+ const wrapper = (
268
+ <div className={`c-tabs js-atomix-tab ${className}`} style={style}>
269
+ {content}
119
270
  </div>
120
- </div>
121
- );
122
-
123
- if (glass) {
124
- // Default glass settings for tabs
125
- const defaultGlassProps = {
126
- displacementScale: 60,
127
- blurAmount: 1,
128
- saturation: 160,
129
- aberrationIntensity: 0.5,
130
- cornerRadius: 8,
131
- mode: 'shader' as const,
132
- };
271
+ );
133
272
 
134
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
273
+ if (glass) {
274
+ // Default glass settings for tabs
275
+ const defaultGlassProps = {
276
+ displacementScale: 60,
277
+ blurAmount: 1,
278
+ saturation: 160,
279
+ aberrationIntensity: 0.5,
280
+ cornerRadius: 8,
281
+ mode: 'shader' as const,
282
+ };
135
283
 
136
- return <AtomixGlass {...glassProps}>{tabContent}</AtomixGlass>;
137
- }
284
+ const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
138
285
 
139
- return tabContent;
140
- });
286
+ return <AtomixGlass {...glassProps}>{wrapper}</AtomixGlass>;
287
+ }
288
+
289
+ return wrapper;
290
+ }
291
+ ) as unknown as TabsComponent;
141
292
 
142
293
  Tabs.displayName = 'Tabs';
294
+ Tabs.List = TabsList;
295
+ Tabs.Trigger = TabsTrigger;
296
+ Tabs.Panels = TabsPanels;
297
+ Tabs.Panel = TabsPanel;
143
298
 
144
299
  export default Tabs;