@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.
- package/dist/atomix.css +0 -14
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +12 -19
- package/dist/charts.js +555 -359
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +98 -28
- package/dist/core.js +1082 -733
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +26 -21
- package/dist/forms.js +937 -350
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -256
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +518 -284
- package/dist/index.esm.js +1993 -1237
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1994 -1237
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +2 -2
- package/scripts/atomix-cli.js +43 -1
- package/scripts/cli/__tests__/utils.test.js +6 -2
- package/scripts/cli/migration-tools.js +2 -2
- package/scripts/cli/theme-bridge.js +7 -9
- package/scripts/cli/utils.js +2 -1
- package/src/components/Accordion/Accordion.stories.tsx +40 -0
- package/src/components/Accordion/Accordion.tsx +174 -56
- package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
- package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
- package/src/components/AtomixGlass/README.md +5 -5
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
- package/src/components/Badge/Badge.stories.tsx +1 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +185 -65
- package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.stories.tsx +166 -1011
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +196 -84
- package/src/components/Callout/CalloutCompound.test.tsx +72 -0
- package/src/components/Callout/README.md +2 -2
- package/src/components/Chart/Chart.stories.tsx +1 -1
- package/src/components/Chart/Chart.tsx +5 -5
- package/src/components/Chart/TreemapChart.tsx +37 -29
- package/src/components/DatePicker/readme.md +3 -3
- package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +133 -20
- package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/EdgePanel/EdgePanel.tsx +164 -112
- package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
- package/src/components/Form/Checkbox.stories.tsx +1 -1
- package/src/components/Form/Checkbox.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +1 -1
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/Radio.stories.tsx +1 -1
- package/src/components/Form/Radio.tsx +1 -1
- package/src/components/Form/Select.stories.tsx +24 -1
- package/src/components/Form/Select.test.tsx +99 -0
- package/src/components/Form/Select.tsx +145 -94
- package/src/components/Form/SelectOption.tsx +88 -0
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +39 -2
- package/src/components/Hero/Hero.test.tsx +142 -0
- package/src/components/Hero/Hero.tsx +143 -4
- package/src/components/List/List.test.tsx +62 -0
- package/src/components/List/List.tsx +16 -5
- package/src/components/List/ListItem.tsx +20 -0
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +66 -2
- package/src/components/Modal/Modal.tsx +115 -35
- package/src/components/Modal/ModalCompound.test.tsx +94 -0
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
- package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/Rating/Rating.stories.tsx +1 -1
- package/src/components/Rating/Rating.test.tsx +73 -0
- package/src/components/Rating/Rating.tsx +25 -37
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Steps/Steps.stories.tsx +1 -1
- package/src/components/Steps/Steps.tsx +125 -22
- package/src/components/Steps/StepsCompound.test.tsx +81 -0
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +198 -45
- package/src/components/Tabs/TabsCompound.test.tsx +64 -0
- package/src/components/Todo/Todo.tsx +0 -1
- package/src/components/Toggle/Toggle.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
- package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
- package/src/lib/composables/__tests__/useChart.test.ts +50 -0
- package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
- package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
- package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
- package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
- package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
- package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
- package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
- package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
- package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
- package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
- package/src/lib/composables/glass-styles.ts +302 -0
- package/src/lib/composables/index.ts +0 -8
- package/src/lib/composables/useAtomixGlass.ts +331 -537
- package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
- package/src/lib/composables/useBarChart.ts +1 -1
- package/src/lib/composables/useBreadcrumb.ts +6 -6
- package/src/lib/composables/useChart.ts +104 -21
- package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
- package/src/lib/composables/useSlider.ts +66 -34
- package/src/lib/theme/devtools/CLI.ts +2 -10
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +21 -23
- package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
- package/src/lib/utils/themeNaming.ts +1 -1
- package/src/styles/06-components/_components.accordion.scss +0 -2
- package/src/styles/06-components/_components.chart.scss +0 -1
- package/src/styles/06-components/_components.dropdown.scss +0 -1
- package/src/styles/06-components/_components.edge-panel.scss +0 -2
- package/src/styles/06-components/_components.photoviewer.scss +0 -1
- package/src/styles/06-components/_components.river.scss +0 -1
- package/src/styles/06-components/_components.slider.scss +0 -3
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
6
|
-
|
|
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;
|
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
592
|
+
borderRadius: 20,
|
|
529
593
|
mode: 'polar',
|
|
530
594
|
} as GlassProps
|
|
531
595
|
}
|