@shohojdhara/atomix 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/atomix.css +0 -14
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.d.ts +12 -19
  6. package/dist/charts.js +555 -359
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.d.ts +98 -28
  9. package/dist/core.js +1082 -733
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.d.ts +26 -21
  12. package/dist/forms.js +937 -350
  13. package/dist/forms.js.map +1 -1
  14. package/dist/heavy.d.ts +14 -21
  15. package/dist/heavy.js +409 -256
  16. package/dist/heavy.js.map +1 -1
  17. package/dist/index.d.ts +518 -284
  18. package/dist/index.esm.js +1993 -1237
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +1994 -1237
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.min.js +1 -1
  23. package/dist/index.min.js.map +1 -1
  24. package/package.json +2 -2
  25. package/scripts/atomix-cli.js +43 -1
  26. package/scripts/cli/__tests__/utils.test.js +6 -2
  27. package/scripts/cli/migration-tools.js +2 -2
  28. package/scripts/cli/theme-bridge.js +7 -9
  29. package/scripts/cli/utils.js +2 -1
  30. package/src/components/Accordion/Accordion.stories.tsx +40 -0
  31. package/src/components/Accordion/Accordion.tsx +174 -56
  32. package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
  33. package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
  34. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
  35. package/src/components/AtomixGlass/README.md +5 -5
  36. package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
  37. package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
  38. package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
  39. package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
  40. package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
  41. package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
  42. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
  43. package/src/components/Badge/Badge.stories.tsx +1 -1
  44. package/src/components/Badge/Badge.tsx +1 -1
  45. package/src/components/Breadcrumb/Breadcrumb.tsx +185 -65
  46. package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
  47. package/src/components/Breadcrumb/index.ts +2 -2
  48. package/src/components/Button/Button.stories.tsx +1 -1
  49. package/src/components/Button/README.md +2 -2
  50. package/src/components/Callout/Callout.stories.tsx +166 -1011
  51. package/src/components/Callout/Callout.test.tsx +3 -3
  52. package/src/components/Callout/Callout.tsx +196 -84
  53. package/src/components/Callout/CalloutCompound.test.tsx +72 -0
  54. package/src/components/Callout/README.md +2 -2
  55. package/src/components/Chart/Chart.stories.tsx +1 -1
  56. package/src/components/Chart/Chart.tsx +5 -5
  57. package/src/components/Chart/TreemapChart.tsx +37 -29
  58. package/src/components/DatePicker/readme.md +3 -3
  59. package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
  60. package/src/components/Dropdown/Dropdown.tsx +133 -20
  61. package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
  62. package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
  63. package/src/components/EdgePanel/EdgePanel.tsx +164 -112
  64. package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
  65. package/src/components/Form/Checkbox.stories.tsx +1 -1
  66. package/src/components/Form/Checkbox.tsx +1 -1
  67. package/src/components/Form/Input.stories.tsx +1 -1
  68. package/src/components/Form/Input.tsx +1 -1
  69. package/src/components/Form/Radio.stories.tsx +1 -1
  70. package/src/components/Form/Radio.tsx +1 -1
  71. package/src/components/Form/Select.stories.tsx +24 -1
  72. package/src/components/Form/Select.test.tsx +99 -0
  73. package/src/components/Form/Select.tsx +145 -94
  74. package/src/components/Form/SelectOption.tsx +88 -0
  75. package/src/components/Form/Textarea.stories.tsx +1 -1
  76. package/src/components/Form/Textarea.tsx +1 -1
  77. package/src/components/Hero/Hero.stories.tsx +39 -2
  78. package/src/components/Hero/Hero.test.tsx +142 -0
  79. package/src/components/Hero/Hero.tsx +143 -4
  80. package/src/components/List/List.test.tsx +62 -0
  81. package/src/components/List/List.tsx +16 -5
  82. package/src/components/List/ListItem.tsx +20 -0
  83. package/src/components/Messages/Messages.stories.tsx +1 -1
  84. package/src/components/Messages/Messages.tsx +2 -2
  85. package/src/components/Modal/Modal.stories.tsx +66 -2
  86. package/src/components/Modal/Modal.tsx +115 -35
  87. package/src/components/Modal/ModalCompound.test.tsx +94 -0
  88. package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
  89. package/src/components/Navigation/Nav/Nav.tsx +1 -1
  90. package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
  91. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  92. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
  93. package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
  94. package/src/components/Pagination/Pagination.stories.tsx +1 -1
  95. package/src/components/Pagination/Pagination.tsx +1 -1
  96. package/src/components/Popover/Popover.stories.tsx +1 -1
  97. package/src/components/Popover/Popover.tsx +1 -1
  98. package/src/components/Progress/Progress.tsx +1 -1
  99. package/src/components/Rating/Rating.stories.tsx +1 -1
  100. package/src/components/Rating/Rating.test.tsx +73 -0
  101. package/src/components/Rating/Rating.tsx +25 -37
  102. package/src/components/Spinner/Spinner.tsx +1 -1
  103. package/src/components/Steps/Steps.stories.tsx +1 -1
  104. package/src/components/Steps/Steps.tsx +125 -22
  105. package/src/components/Steps/StepsCompound.test.tsx +81 -0
  106. package/src/components/Tabs/Tabs.stories.tsx +1 -1
  107. package/src/components/Tabs/Tabs.tsx +198 -45
  108. package/src/components/Tabs/TabsCompound.test.tsx +64 -0
  109. package/src/components/Todo/Todo.tsx +0 -1
  110. package/src/components/Toggle/Toggle.stories.tsx +1 -1
  111. package/src/components/Toggle/Toggle.tsx +1 -1
  112. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  113. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
  114. package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
  115. package/src/lib/composables/__tests__/useChart.test.ts +50 -0
  116. package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
  117. package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
  118. package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
  119. package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
  120. package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
  121. package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
  122. package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
  123. package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
  124. package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
  125. package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
  126. package/src/lib/composables/glass-styles.ts +302 -0
  127. package/src/lib/composables/index.ts +0 -8
  128. package/src/lib/composables/useAtomixGlass.ts +331 -537
  129. package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
  130. package/src/lib/composables/useBarChart.ts +1 -1
  131. package/src/lib/composables/useBreadcrumb.ts +6 -6
  132. package/src/lib/composables/useChart.ts +104 -21
  133. package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
  134. package/src/lib/composables/useSlider.ts +66 -34
  135. package/src/lib/theme/devtools/CLI.ts +2 -10
  136. package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
  137. package/src/lib/types/components.ts +21 -23
  138. package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
  139. package/src/lib/utils/__tests__/dom.test.ts +100 -0
  140. package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
  141. package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
  142. package/src/lib/utils/themeNaming.ts +1 -1
  143. package/src/styles/06-components/_components.accordion.scss +0 -2
  144. package/src/styles/06-components/_components.chart.scss +0 -1
  145. package/src/styles/06-components/_components.dropdown.scss +0 -1
  146. package/src/styles/06-components/_components.edge-panel.scss +0 -2
  147. package/src/styles/06-components/_components.photoviewer.scss +0 -1
  148. package/src/styles/06-components/_components.river.scss +0 -1
  149. package/src/styles/06-components/_components.slider.scss +0 -3
  150. package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
@@ -279,6 +279,43 @@ export const BasicUsage: Story = {
279
279
  },
280
280
  };
281
281
 
282
+ /**
283
+ * Hero using Compound Component Pattern
284
+ */
285
+ export const CompoundUsage: Story = {
286
+ render: (args) => (
287
+ <Hero {...args}>
288
+ <Hero.Content>
289
+ <Hero.Title level="h1">Compound Component Pattern</Hero.Title>
290
+ <Hero.Subtitle>Fully Customizable Structure</Hero.Subtitle>
291
+ <Hero.Text>
292
+ This example demonstrates the new Compound Component pattern, allowing full control over the internal structure of the Hero component.
293
+ </Hero.Text>
294
+ <Hero.Actions>
295
+ <Button variant="primary" className="u-mr-3">
296
+ Get Started
297
+ </Button>
298
+ <Button variant="outline">Learn More</Button>
299
+ </Hero.Actions>
300
+ </Hero.Content>
301
+ </Hero>
302
+ ),
303
+ args: {
304
+ fullViewportHeight: true,
305
+ alignment: 'center',
306
+ backgroundImageSrc: 'https://picsum.photos/id/1015/1920/1080',
307
+ title: '', // Ignored but kept for types
308
+ showOverlay: true,
309
+ },
310
+ parameters: {
311
+ docs: {
312
+ description: {
313
+ story: 'Using the Compound Component pattern for maximum flexibility.',
314
+ },
315
+ },
316
+ },
317
+ };
318
+
282
319
  export const WithImage: Story = {
283
320
  args: {
284
321
  title: 'Beautiful Interfaces',
@@ -396,7 +433,7 @@ export const WithCustomGlassEffect: Story = {
396
433
  blurAmount: -0.1,
397
434
  saturation: 130,
398
435
  aberrationIntensity: 0,
399
- cornerRadius: 45,
436
+ borderRadius: 45,
400
437
  mode: 'standard',
401
438
  elasticity: 0.2,
402
439
  onClick: () => {
@@ -874,7 +911,7 @@ export const PremiumShowcase: Story = {
874
911
  blurAmount: 2,
875
912
  saturation: 150,
876
913
  aberrationIntensity: 0.5,
877
- cornerRadius: 20,
914
+ borderRadius: 20,
878
915
  overLight: true,
879
916
  mode: 'standard',
880
917
  },
@@ -0,0 +1,142 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import Hero from './Hero';
5
+
6
+ // Mock AtomixGlass component
7
+ vi.mock('../AtomixGlass/AtomixGlass', () => ({
8
+ AtomixGlass: ({ children, className }: any) => (
9
+ <div data-testid="atomix-glass" className={className}>
10
+ {children}
11
+ </div>
12
+ ),
13
+ }));
14
+
15
+ describe('Hero Component', () => {
16
+ describe('Monolithic Usage', () => {
17
+ it('renders title and subtitle correctly', () => {
18
+ render(<Hero title="Test Title" subtitle="Test Subtitle" />);
19
+
20
+ expect(screen.getByText('Test Title')).toBeInTheDocument();
21
+ expect(screen.getByText('Test Subtitle')).toBeInTheDocument();
22
+ });
23
+
24
+ it('renders text content correctly', () => {
25
+ render(<Hero title="Title" text="Test Description" />);
26
+
27
+ expect(screen.getByText('Test Description')).toBeInTheDocument();
28
+ });
29
+
30
+ it('renders background image correctly', () => {
31
+ const bgSrc = 'test-bg.jpg';
32
+ render(<Hero title="Title" backgroundImageSrc={bgSrc} />);
33
+
34
+ const bgImage = screen.getByAltText('Background');
35
+ expect(bgImage).toBeInTheDocument();
36
+ expect(bgImage).toHaveAttribute('src', bgSrc);
37
+ });
38
+
39
+ it('renders foreground image correctly', () => {
40
+ const imgSrc = 'test-img.jpg';
41
+ render(<Hero title="Title" imageSrc={imgSrc} imageAlt="Foreground Image" />);
42
+
43
+ const image = screen.getByAltText('Foreground Image');
44
+ expect(image).toBeInTheDocument();
45
+ expect(image).toHaveAttribute('src', imgSrc);
46
+ });
47
+
48
+ it('renders actions correctly', () => {
49
+ render(
50
+ <Hero
51
+ title="Title"
52
+ actions={<button>Click Me</button>}
53
+ />
54
+ );
55
+
56
+ expect(screen.getByText('Click Me')).toBeInTheDocument();
57
+ });
58
+
59
+ it('renders children correctly', () => {
60
+ render(
61
+ <Hero title="Title">
62
+ <div data-testid="child-content">Child Content</div>
63
+ </Hero>
64
+ );
65
+
66
+ expect(screen.getByTestId('child-content')).toBeInTheDocument();
67
+ });
68
+ });
69
+
70
+ describe('Compound Component Usage', () => {
71
+ it('renders Hero.Title, Hero.Subtitle, Hero.Text correctly', () => {
72
+ render(
73
+ <Hero title="">
74
+ <Hero.Content>
75
+ <Hero.Title>Compound Title</Hero.Title>
76
+ <Hero.Subtitle>Compound Subtitle</Hero.Subtitle>
77
+ <Hero.Text>Compound Text</Hero.Text>
78
+ </Hero.Content>
79
+ </Hero>
80
+ );
81
+
82
+ expect(screen.getByText('Compound Title')).toBeInTheDocument();
83
+ expect(screen.getByText('Compound Title').tagName).toBe('H1');
84
+ expect(screen.getByText('Compound Subtitle')).toBeInTheDocument();
85
+ expect(screen.getByText('Compound Text')).toBeInTheDocument();
86
+ });
87
+
88
+ it('renders Hero.Actions correctly', () => {
89
+ render(
90
+ <Hero title="">
91
+ <Hero.Content>
92
+ <Hero.Actions>
93
+ <button>Action</button>
94
+ </Hero.Actions>
95
+ </Hero.Content>
96
+ </Hero>
97
+ );
98
+
99
+ expect(screen.getByText('Action')).toBeInTheDocument();
100
+ });
101
+
102
+ it('renders Hero.Image correctly', () => {
103
+ render(
104
+ <Hero title="">
105
+ <Hero.Image src="compound-img.jpg" alt="Compound Image" />
106
+ </Hero>
107
+ );
108
+
109
+ const img = screen.getByAltText('Compound Image');
110
+ expect(img).toBeInTheDocument();
111
+ expect(img).toHaveAttribute('src', 'compound-img.jpg');
112
+ });
113
+
114
+ it('renders Hero.Background via backgroundElement prop', () => {
115
+ render(
116
+ <Hero
117
+ title="Title"
118
+ backgroundElement={<Hero.Background src="bg.jpg" data-testid="custom-bg" />}
119
+ />
120
+ );
121
+
122
+ const bg = screen.getByTestId('custom-bg');
123
+ expect(bg).toBeInTheDocument();
124
+ // Verify it renders the image inside
125
+ const img = screen.getByAltText('Background');
126
+ expect(img).toHaveAttribute('src', 'bg.jpg');
127
+ });
128
+
129
+ it('Hero.Content supports glass prop', () => {
130
+ render(
131
+ <Hero title="">
132
+ <Hero.Content glass>
133
+ Glass Content
134
+ </Hero.Content>
135
+ </Hero>
136
+ );
137
+
138
+ expect(screen.getByTestId('atomix-glass')).toBeInTheDocument();
139
+ expect(screen.getByText('Glass Content')).toBeInTheDocument();
140
+ });
141
+ });
142
+ });
@@ -1,10 +1,139 @@
1
- import React, { CSSProperties, useEffect } from 'react';
2
- import { HeroProps, HeroAlignment } from '../../lib/types/components';
1
+ import React, { CSSProperties, useEffect, ReactNode } from 'react';
2
+ import { HeroProps, HeroAlignment, AtomixGlassProps } from '../../lib/types/components';
3
3
  import { useHero } from '../../lib/composables/useHero';
4
4
  import { HERO } from '../../lib/constants/components';
5
5
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
6
6
 
7
- export const Hero: React.FC<HeroProps> = ({
7
+ // Subcomponents
8
+ export interface HeroTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {
9
+ level?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'div';
10
+ }
11
+
12
+ const HeroTitle = ({ children, className, level = 'h1', ...props }: HeroTitleProps) => {
13
+ const Tag = level as any;
14
+ return (
15
+ <Tag className={`${HERO.SELECTORS.TITLE.replace('.', '')} ${className || ''}`.trim()} {...props}>
16
+ {children}
17
+ </Tag>
18
+ );
19
+ };
20
+
21
+ const HeroSubtitle = ({ children, className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => {
22
+ return (
23
+ <p className={`${HERO.SELECTORS.SUBTITLE.replace('.', '')} ${className || ''}`.trim()} {...props}>
24
+ {children}
25
+ </p>
26
+ );
27
+ };
28
+
29
+ const HeroText = ({ children, className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => {
30
+ return (
31
+ <p className={`${HERO.SELECTORS.TEXT.replace('.', '')} ${className || ''}`.trim()} {...props}>
32
+ {children}
33
+ </p>
34
+ );
35
+ };
36
+
37
+ const HeroActions = ({ children, className, ...props }: React.HTMLAttributes<HTMLDivElement>) => {
38
+ return (
39
+ <div className={`${HERO.SELECTORS.ACTIONS.replace('.', '')} ${className || ''}`.trim()} {...props}>
40
+ {children}
41
+ </div>
42
+ );
43
+ };
44
+
45
+ export interface HeroContentProps extends React.HTMLAttributes<HTMLDivElement> {
46
+ glass?: AtomixGlassProps | boolean;
47
+ }
48
+
49
+ const HeroContent = ({ children, className, style, glass, ...props }: HeroContentProps) => {
50
+ const contentClass = `${HERO.SELECTORS.CONTENT.replace('.', '')} ${className || ''}`.trim();
51
+
52
+ if (glass) {
53
+ const glassProps = typeof glass === 'boolean' ? {
54
+ displacementScale: 60,
55
+ blurAmount: 3,
56
+ saturation: 180,
57
+ aberrationIntensity: 0,
58
+ borderRadius: 8,
59
+ overLight: false,
60
+ mode: 'standard' as const,
61
+ } : glass;
62
+
63
+ return (
64
+ <div className={contentClass} style={style} {...props}>
65
+ <AtomixGlass {...glassProps}>
66
+ <div className="u-p-4">
67
+ {children}
68
+ </div>
69
+ </AtomixGlass>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ return (
75
+ <div className={contentClass} style={style} {...props}>
76
+ {children}
77
+ </div>
78
+ );
79
+ };
80
+
81
+ export interface HeroImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {
82
+ wrapperClassName?: string;
83
+ wrapperStyle?: React.CSSProperties;
84
+ }
85
+
86
+ const HeroImage = ({
87
+ src,
88
+ alt = '',
89
+ className,
90
+ wrapperClassName,
91
+ wrapperStyle,
92
+ ...props
93
+ }: HeroImageProps) => {
94
+ return (
95
+ <div
96
+ className={`${HERO.SELECTORS.IMAGE_WRAPPER.replace('.', '')} ${wrapperClassName || ''}`.trim()}
97
+ style={wrapperStyle}
98
+ >
99
+ <img
100
+ src={src}
101
+ alt={alt}
102
+ className={`${HERO.SELECTORS.IMAGE.replace('.', '')} ${className || ''}`.trim()}
103
+ {...props}
104
+ />
105
+ </div>
106
+ );
107
+ };
108
+
109
+ const HeroBackground = ({ className, style, src, children, ...props }: React.HTMLAttributes<HTMLDivElement> & { src?: string }) => {
110
+ return (
111
+ <div
112
+ className={`${HERO.SELECTORS.BG.replace('.', '')} ${className || ''}`.trim()}
113
+ style={style}
114
+ {...props}
115
+ >
116
+ {src && (
117
+ <img
118
+ src={src}
119
+ alt="Background"
120
+ className={HERO.SELECTORS.BG_IMAGE.replace('.', '')}
121
+ />
122
+ )}
123
+ {children}
124
+ </div>
125
+ );
126
+ };
127
+
128
+ export const Hero: React.FC<HeroProps> & {
129
+ Title: typeof HeroTitle;
130
+ Subtitle: typeof HeroSubtitle;
131
+ Text: typeof HeroText;
132
+ Actions: typeof HeroActions;
133
+ Content: typeof HeroContent;
134
+ Image: typeof HeroImage;
135
+ Background: typeof HeroBackground;
136
+ } = ({
8
137
  title,
9
138
  subtitle,
10
139
  text,
@@ -38,6 +167,7 @@ export const Hero: React.FC<HeroProps> = ({
38
167
  headingLevel = 'h1',
39
168
  reverseOnMobile = false,
40
169
  parts,
170
+ backgroundElement,
41
171
  ...rest
42
172
  }: HeroProps) => {
43
173
  // Define dynamic heading tag
@@ -259,7 +389,7 @@ export const Hero: React.FC<HeroProps> = ({
259
389
  blurAmount={3}
260
390
  saturation={180}
261
391
  aberrationIntensity={0}
262
- cornerRadius={8}
392
+ borderRadius={8}
263
393
  overLight={false}
264
394
  mode="standard"
265
395
  >
@@ -421,6 +551,7 @@ export const Hero: React.FC<HeroProps> = ({
421
551
  data-parallax-intensity={parallax ? parallaxIntensity : undefined}
422
552
  {...rest}
423
553
  >
554
+ {backgroundElement}
424
555
  {renderBackground()}
425
556
  <div
426
557
  className={`${HERO.SELECTORS.CONTAINER.replace('.', '')} o-container ${parts?.container?.className || ''}`.trim()}
@@ -451,6 +582,14 @@ export const Hero: React.FC<HeroProps> = ({
451
582
  );
452
583
  };
453
584
 
585
+ Hero.Title = HeroTitle;
586
+ Hero.Subtitle = HeroSubtitle;
587
+ Hero.Text = HeroText;
588
+ Hero.Actions = HeroActions;
589
+ Hero.Content = HeroContent;
590
+ Hero.Image = HeroImage;
591
+ Hero.Background = HeroBackground;
592
+
454
593
  export type { HeroProps };
455
594
 
456
595
  Hero.displayName = 'Hero';
@@ -0,0 +1,62 @@
1
+ import React from 'react';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { describe, it, expect } from 'vitest';
4
+ import { List } from './List';
5
+
6
+ describe('List Component', () => {
7
+ it('renders legacy items wrapped in li', () => {
8
+ render(
9
+ <List>
10
+ <span>Item 1</span>
11
+ <span>Item 2</span>
12
+ </List>
13
+ );
14
+
15
+ const listItems = screen.getAllByRole('listitem');
16
+ expect(listItems).toHaveLength(2);
17
+ expect(listItems[0]).toHaveTextContent('Item 1');
18
+ expect(listItems[0]).toHaveClass('c-list__item');
19
+ });
20
+
21
+ it('renders List.Item components directly', () => {
22
+ render(
23
+ <List>
24
+ <List.Item>Item 1</List.Item>
25
+ <List.Item className="custom-class">Item 2</List.Item>
26
+ </List>
27
+ );
28
+
29
+ const listItems = screen.getAllByRole('listitem');
30
+ expect(listItems).toHaveLength(2);
31
+ expect(listItems[0]).toHaveTextContent('Item 1');
32
+ expect(listItems[0]).toHaveClass('c-list__item');
33
+ expect(listItems[1]).toHaveTextContent('Item 2');
34
+ expect(listItems[1]).toHaveClass('c-list__item');
35
+ expect(listItems[1]).toHaveClass('custom-class');
36
+ });
37
+
38
+ it('renders mixed content correctly', () => {
39
+ render(
40
+ <List>
41
+ <List.Item>Compound Item</List.Item>
42
+ <span>Legacy Item</span>
43
+ </List>
44
+ );
45
+
46
+ const listItems = screen.getAllByRole('listitem');
47
+ expect(listItems).toHaveLength(2);
48
+ expect(listItems[0]).toHaveTextContent('Compound Item');
49
+ expect(listItems[1]).toHaveTextContent('Legacy Item');
50
+ });
51
+
52
+ it('renders ordered list when variant is number', () => {
53
+ render(
54
+ <List variant="number">
55
+ <List.Item>Item 1</List.Item>
56
+ </List>
57
+ );
58
+
59
+ const list = screen.getByRole('list');
60
+ expect(list.tagName).toBe('OL');
61
+ });
62
+ });
@@ -1,9 +1,16 @@
1
1
  import React, { memo } from 'react';
2
2
  import { ListProps } from '../../lib/types/components';
3
3
  import { LIST } from '../../lib/constants/components';
4
+ import { ListItem } from './ListItem';
4
5
 
5
- export const List: React.FC<ListProps> = memo(
6
- ({ children, variant = 'default', className = '', style, ...props }) => {
6
+ export type { ListProps };
7
+
8
+ export type ListComponent = React.FC<ListProps> & {
9
+ Item: typeof ListItem;
10
+ };
11
+
12
+ export const List: ListComponent = memo(
13
+ ({ children, variant = 'default', className = '', style, ...props }: ListProps) => {
7
14
  // Generate CSS classes
8
15
  const listClasses = [LIST.BASE_CLASS, variant !== 'default' && `c-list--${variant}`, className]
9
16
  .filter(Boolean)
@@ -16,6 +23,11 @@ export const List: React.FC<ListProps> = memo(
16
23
  <ListElement className={listClasses} style={style} {...props}>
17
24
  {React.Children.map(children, child => {
18
25
  if (React.isValidElement(child)) {
26
+ // Check if child is a ListItem
27
+ if (child.type === ListItem) {
28
+ return child;
29
+ }
30
+ // Legacy behavior: wrap in li
19
31
  return <li className="c-list__item">{child}</li>;
20
32
  }
21
33
  return <li className="c-list__item">{child}</li>;
@@ -23,10 +35,9 @@ export const List: React.FC<ListProps> = memo(
23
35
  </ListElement>
24
36
  );
25
37
  }
26
- );
27
-
28
- export type { ListProps };
38
+ ) as unknown as ListComponent;
29
39
 
30
40
  List.displayName = 'List';
41
+ List.Item = ListItem;
31
42
 
32
43
  export default List;
@@ -0,0 +1,20 @@
1
+ import React, { forwardRef } from 'react';
2
+ import { LIST } from '../../lib/constants/components';
3
+
4
+ export interface ListItemProps extends React.LiHTMLAttributes<HTMLLIElement> {
5
+ children?: React.ReactNode;
6
+ }
7
+
8
+ export const ListItem = forwardRef<HTMLLIElement, ListItemProps>(
9
+ ({ children, className = '', ...props }, ref) => {
10
+ return (
11
+ <li ref={ref} className={`${LIST.ITEM_CLASS} ${className}`.trim()} {...props}>
12
+ {children}
13
+ </li>
14
+ );
15
+ }
16
+ );
17
+
18
+ ListItem.displayName = 'ListItem';
19
+
20
+ export default ListItem;
@@ -192,7 +192,7 @@ export const GlassCustom: Story = {
192
192
  glass: {
193
193
  displacementScale: 30,
194
194
  blurAmount: 0,
195
- cornerRadius: 16,
195
+ borderRadius: 16,
196
196
  elasticity: 0,
197
197
  },
198
198
  },
@@ -35,7 +35,7 @@ export const Messages: React.FC<MessagesProps> = ({
35
35
  // Default glass settings for messages
36
36
  const defaultGlassProps = {
37
37
  displacementScale: 150,
38
- cornerRadius: 12,
38
+ borderRadius: 12,
39
39
  elasticity: 0,
40
40
  aberrationIntensity: 2,
41
41
  };
@@ -185,7 +185,7 @@ export const Messages: React.FC<MessagesProps> = ({
185
185
  <AtomixGlass {...glassProps}>
186
186
  <div
187
187
  className="c-messages__glass-content"
188
- style={{ borderRadius: glassProps.cornerRadius }}
188
+ style={{ borderRadius: glassProps.borderRadius }}
189
189
  >
190
190
  {messagesContent}
191
191
  </div>
@@ -2,7 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react';
2
2
  import { fn } from '@storybook/test';
3
3
  import { useState } from 'react';
4
4
  import type { AtomixGlassProps } from '../../lib/types/components';
5
- import Modal from './Modal';
5
+ import { Modal } from './Modal';
6
6
 
7
7
  // Helper type for glass props in stories (without children requirement)
8
8
  type GlassProps = boolean | Omit<AtomixGlassProps, 'children'>;
@@ -31,6 +31,7 @@ Modal displays content in a focused overlay dialog. It provides a way to present
31
31
  - Header and footer sections
32
32
  - Accessible design
33
33
  - Responsive behavior
34
+ - **Compound Component Pattern** (new)
34
35
 
35
36
  ## Accessibility
36
37
 
@@ -53,6 +54,20 @@ Modal displays content in a focused overlay dialog. It provides a way to present
53
54
  </Modal>
54
55
  \`\`\`
55
56
 
57
+ ### Compound Component Usage
58
+
59
+ \`\`\`tsx
60
+ <Modal isOpen={isOpen} onOpenChange={setIsOpen}>
61
+ <Modal.Header closeButton title="Custom Header" />
62
+ <Modal.Body>
63
+ <p>Flexible body content</p>
64
+ </Modal.Body>
65
+ <Modal.Footer>
66
+ <button>Action</button>
67
+ </Modal.Footer>
68
+ </Modal>
69
+ \`\`\`
70
+
56
71
  ### With Glass Effect
57
72
 
58
73
  \`\`\`tsx
@@ -284,6 +299,55 @@ export const WithGlassEffect: Story = {
284
299
  },
285
300
  };
286
301
 
302
+ export const CompoundUsage: Story = {
303
+ render: args => {
304
+ const [isOpen, setIsOpen] = useState(false);
305
+
306
+ return (
307
+ <>
308
+ <div
309
+ className="c-btn c-btn--primary"
310
+ onClick={() => setIsOpen(true)}
311
+ style={{ cursor: 'pointer', padding: '8px 16px', display: 'inline-block' }}
312
+ >
313
+ Open Compound Modal
314
+ </div>
315
+
316
+ <Modal
317
+ {...args}
318
+ isOpen={isOpen}
319
+ onOpenChange={setIsOpen}
320
+ >
321
+ <Modal.Header
322
+ title="Compound Component Pattern"
323
+ subtitle="Fully customizable header"
324
+ closeButton
325
+ />
326
+ <Modal.Body>
327
+ <p>
328
+ This modal uses the Compound Component pattern (Modal.Header, Modal.Body, Modal.Footer).
329
+ This allows for greater flexibility in content arrangement.
330
+ </p>
331
+ <div style={{ marginTop: '1rem', padding: '1rem', background: '#f5f5f5', borderRadius: '4px' }}>
332
+ Custom content structure inside Body
333
+ </div>
334
+ </Modal.Body>
335
+ <Modal.Footer>
336
+ <button className="c-btn c-btn--outline-secondary" onClick={() => setIsOpen(false)}>Custom Footer Button</button>
337
+ </Modal.Footer>
338
+ </Modal>
339
+ </>
340
+ );
341
+ },
342
+ parameters: {
343
+ docs: {
344
+ description: {
345
+ story: 'Demonstrates the Compound Component usage pattern.',
346
+ },
347
+ },
348
+ },
349
+ };
350
+
287
351
  /**
288
352
  * Small size modal variant.
289
353
  */
@@ -525,7 +589,7 @@ export const GlassModalCustom: Story = {
525
589
  blurAmount: 3,
526
590
  saturation: 200,
527
591
  aberrationIntensity: 2,
528
- cornerRadius: 20,
592
+ borderRadius: 20,
529
593
  mode: 'polar',
530
594
  } as GlassProps
531
595
  }