@shohojdhara/atomix 0.4.0 → 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 (66) hide show
  1. package/dist/atomix.css +9231 -9337
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +2 -2
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.js +4 -5
  6. package/dist/charts.js.map +1 -1
  7. package/dist/core.d.ts +87 -10
  8. package/dist/core.js +673 -480
  9. package/dist/core.js.map +1 -1
  10. package/dist/forms.d.ts +15 -3
  11. package/dist/forms.js +530 -97
  12. package/dist/forms.js.map +1 -1
  13. package/dist/heavy.js +5 -6
  14. package/dist/heavy.js.map +1 -1
  15. package/dist/index.d.ts +495 -254
  16. package/dist/index.esm.js +1269 -723
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.js +1273 -723
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.min.js +1 -1
  21. package/dist/index.min.js.map +1 -1
  22. package/package.json +2 -2
  23. package/scripts/atomix-cli.js +10 -1
  24. package/scripts/cli/__tests__/utils.test.js +6 -2
  25. package/scripts/cli/migration-tools.js +2 -2
  26. package/scripts/cli/theme-bridge.js +7 -9
  27. package/scripts/cli/utils.js +2 -1
  28. package/src/components/Accordion/Accordion.stories.tsx +40 -0
  29. package/src/components/Accordion/Accordion.tsx +174 -56
  30. package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
  31. package/src/components/Breadcrumb/Breadcrumb.tsx +156 -50
  32. package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
  33. package/src/components/Callout/Callout.stories.tsx +166 -1011
  34. package/src/components/Callout/Callout.tsx +196 -84
  35. package/src/components/Callout/CalloutCompound.test.tsx +72 -0
  36. package/src/components/Dropdown/Dropdown.tsx +133 -20
  37. package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
  38. package/src/components/EdgePanel/EdgePanel.tsx +164 -112
  39. package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
  40. package/src/components/Form/Select.stories.tsx +23 -0
  41. package/src/components/Form/Select.test.tsx +99 -0
  42. package/src/components/Form/Select.tsx +144 -93
  43. package/src/components/Form/SelectOption.tsx +88 -0
  44. package/src/components/Hero/Hero.stories.tsx +37 -0
  45. package/src/components/Hero/Hero.test.tsx +142 -0
  46. package/src/components/Hero/Hero.tsx +142 -3
  47. package/src/components/List/List.test.tsx +62 -0
  48. package/src/components/List/List.tsx +16 -5
  49. package/src/components/List/ListItem.tsx +20 -0
  50. package/src/components/Modal/Modal.stories.tsx +65 -1
  51. package/src/components/Modal/Modal.tsx +115 -35
  52. package/src/components/Modal/ModalCompound.test.tsx +94 -0
  53. package/src/components/Steps/Steps.tsx +124 -21
  54. package/src/components/Steps/StepsCompound.test.tsx +81 -0
  55. package/src/components/Tabs/Tabs.tsx +197 -44
  56. package/src/components/Tabs/TabsCompound.test.tsx +64 -0
  57. package/src/lib/composables/index.ts +0 -4
  58. package/src/lib/composables/useAtomixGlass.ts +0 -15
  59. package/src/lib/theme/devtools/CLI.ts +2 -10
  60. package/src/lib/types/components.ts +8 -2
  61. package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
  62. package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
  63. package/src/lib/utils/themeNaming.ts +1 -1
  64. package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
  65. package/src/styles/02-tools/_tools.utility-api.scss +6 -6
  66. package/src/styles/99-utilities/_utilities.text.scss +0 -1
@@ -1,10 +1,53 @@
1
- import React, { useRef, useEffect } from 'react';
1
+ import React, { useRef, useEffect, memo, forwardRef } from 'react';
2
2
  import { EdgePanelProps } from '../../lib/types/components';
3
3
  import { useEdgePanel } from '../../lib/composables/useEdgePanel';
4
4
  import { EDGE_PANEL } from '../../lib/constants/components';
5
5
  import { Icon } from '../Icon/Icon';
6
6
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
7
7
 
8
+ // Subcomponents
9
+ export const EdgePanelHeader = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
10
+ ({ children, className = '', ...props }, ref) => (
11
+ <div ref={ref} className={`c-edge-panel__header ${className}`.trim()} {...props}>
12
+ {children}
13
+ </div>
14
+ )
15
+ );
16
+ EdgePanelHeader.displayName = 'EdgePanelHeader';
17
+
18
+ export const EdgePanelBody = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
19
+ ({ children, className = '', ...props }, ref) => (
20
+ <div ref={ref} className={`c-edge-panel__body ${className}`.trim()} {...props}>
21
+ {children}
22
+ </div>
23
+ )
24
+ );
25
+ EdgePanelBody.displayName = 'EdgePanelBody';
26
+
27
+ export const EdgePanelFooter = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
28
+ ({ children, className = '', ...props }, ref) => (
29
+ <div ref={ref} className={`c-edge-panel__footer ${className}`.trim()} {...props}>
30
+ {children}
31
+ </div>
32
+ )
33
+ );
34
+ EdgePanelFooter.displayName = 'EdgePanelFooter';
35
+
36
+ export const EdgePanelCloseButton = forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
37
+ ({ className = '', onClick, ...props }, ref) => (
38
+ <button
39
+ ref={ref}
40
+ className={`c-edge-panel__close c-btn c-btn--icon ${className}`.trim()}
41
+ onClick={onClick}
42
+ aria-label="Close panel"
43
+ {...props}
44
+ >
45
+ <Icon name="X" />
46
+ </button>
47
+ )
48
+ );
49
+ EdgePanelCloseButton.displayName = 'EdgePanelCloseButton';
50
+
8
51
  /**
9
52
  * EdgePanel - A sliding panel component that appears from any screen edge
10
53
  *
@@ -21,129 +64,138 @@ import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
21
64
  * <p>Panel content</p>
22
65
  * </EdgePanel>
23
66
  *
24
- * // With glass effect
25
- * <EdgePanel
26
- * title="Glass Panel"
27
- * isOpen={isOpen}
28
- * onOpenChange={setIsOpen}
29
- * position="end"
30
- * glass={true}
31
- * >
32
- * <p>Panel with glass morphism</p>
33
- * </EdgePanel>
34
- *
35
- * // With custom glass configuration
36
- * <EdgePanel
37
- * title="Custom Glass"
38
- * isOpen={isOpen}
39
- * onOpenChange={setIsOpen}
40
- * position="start"
41
- * glass={{
42
- * mode: 'shader',
43
- * shaderVariant: 'liquidGlass',
44
- * displacementScale: 70,
45
- * blurAmount: 1.8,
46
- * saturation: 170,
47
- * }}
48
- * >
49
- * <p>Panel with custom glass effect</p>
67
+ * // Compound Usage
68
+ * <EdgePanel isOpen={isOpen} onOpenChange={setIsOpen}>
69
+ * <EdgePanel.Header>
70
+ * <h4>Title</h4>
71
+ * <EdgePanel.CloseButton onClick={() => setIsOpen(false)} />
72
+ * </EdgePanel.Header>
73
+ * <EdgePanel.Body>Content</EdgePanel.Body>
74
+ * <EdgePanel.Footer>Footer</EdgePanel.Footer>
50
75
  * </EdgePanel>
51
76
  * ```
52
77
  */
53
- export const EdgePanel: React.FC<EdgePanelProps> = ({
54
- title,
55
- children,
56
- position = 'start',
57
- mode = 'slide',
58
- isOpen = false,
59
- onOpenChange,
60
- backdrop = true,
61
- closeOnBackdropClick = true,
62
- closeOnEscape = true,
63
- className = '',
64
- style,
65
- glass,
66
- }) => {
67
- const {
68
- isOpen: isOpenState,
69
- containerRef,
70
- backdropRef,
71
- generateEdgePanelClass,
72
- closePanel,
73
- handleBackdropClick,
74
- } = useEdgePanel({
75
- position,
76
- mode,
77
- isOpen,
78
+ type EdgePanelComponent = React.FC<EdgePanelProps> & {
79
+ Header: typeof EdgePanelHeader;
80
+ Body: typeof EdgePanelBody;
81
+ Footer: typeof EdgePanelFooter;
82
+ CloseButton: typeof EdgePanelCloseButton;
83
+ };
84
+
85
+ export const EdgePanel: EdgePanelComponent = memo(
86
+ ({
87
+ title,
88
+ children,
89
+ position = 'start',
90
+ mode = 'slide',
91
+ isOpen = false,
78
92
  onOpenChange,
79
- backdrop,
80
- closeOnBackdropClick,
81
- closeOnEscape,
93
+ backdrop = true,
94
+ closeOnBackdropClick = true,
95
+ closeOnEscape = true,
96
+ className = '',
97
+ style,
82
98
  glass,
83
- });
99
+ }: EdgePanelProps) => {
100
+ const {
101
+ isOpen: isOpenState,
102
+ containerRef,
103
+ backdropRef,
104
+ generateEdgePanelClass,
105
+ closePanel,
106
+ handleBackdropClick,
107
+ } = useEdgePanel({
108
+ position,
109
+ mode,
110
+ isOpen,
111
+ onOpenChange,
112
+ backdrop,
113
+ closeOnBackdropClick,
114
+ closeOnEscape,
115
+ glass,
116
+ });
84
117
 
85
- // Moved useRef outside of conditional rendering to fix hook order issue
86
- const glassContentRef = useRef<HTMLDivElement>(null);
118
+ // Moved useRef outside of conditional rendering to fix hook order issue
119
+ const glassContentRef = useRef<HTMLDivElement>(null);
87
120
 
88
- const panelClass = generateEdgePanelClass({
89
- position,
90
- isOpen,
91
- className: glass ? `${className} c-edge-panel--glass` : className,
92
- });
121
+ const panelClass = generateEdgePanelClass({
122
+ position,
123
+ isOpen,
124
+ className: glass ? `${className} c-edge-panel--glass` : className,
125
+ });
93
126
 
94
- // If not open and not controlled by parent, don't render
95
- if (!isOpenState && isOpen === false) {
96
- return null;
97
- }
127
+ // If not open and not controlled by parent, don't render
128
+ // Note: useEdgePanel manages internal state if onOpenChange is not provided?
129
+ // Looking at useEdgePanel (implied): it seems to return isOpenState.
130
+ // If we return null here, animations might be cut off.
131
+ // Usually EdgePanel/Drawer should stay mounted but hidden or conditionally mounted.
132
+ // The original code returned null if !isOpenState && isOpen === false.
133
+ // Let's keep that logic.
134
+ if (!isOpenState && isOpen === false) {
135
+ return null;
136
+ }
98
137
 
99
- const defaultGlassProps = {
100
- elasticity: 0,
101
- };
102
-
103
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
104
-
105
- const panelContent = (
106
- <>
107
- <div className="c-edge-panel__header">
108
- <h4>{title}</h4>
109
- <button
110
- className="c-edge-panel__close c-btn c-btn--icon"
111
- onClick={() => closePanel()}
112
- aria-label="Close panel"
113
- >
114
- <Icon name="X" />
115
- </button>
116
- </div>
117
- <div className="c-edge-panel__body">{children}</div>
118
- </>
119
- );
120
-
121
- return (
122
- <div className={panelClass} data-position={position} data-mode={mode} style={style}>
123
- {backdrop && (
124
- <div ref={backdropRef} className="c-edge-panel__backdrop" onClick={handleBackdropClick} />
125
- )}
126
- <div ref={containerRef} className="c-edge-panel__container">
127
- {glass ? (
128
- <AtomixGlass {...glassProps}>
129
- <div
130
- ref={glassContentRef}
131
- className="c-edge-panel__glass-content"
132
- style={{ borderRadius: containerRef.current?.style.borderRadius }}
133
- >
134
- {panelContent}
135
- </div>
136
- </AtomixGlass>
137
- ) : (
138
- panelContent
138
+ const defaultGlassProps = {
139
+ elasticity: 0,
140
+ };
141
+
142
+ const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
143
+
144
+ // Check for compound components
145
+ const hasCompoundComponents = React.Children.toArray(children).some((child) =>
146
+ React.isValidElement(child) &&
147
+ ['EdgePanelHeader', 'EdgePanelBody', 'EdgePanelFooter'].includes((child.type as any).displayName)
148
+ );
149
+
150
+ const panelContent = hasCompoundComponents ? (
151
+ children
152
+ ) : (
153
+ <>
154
+ <div className="c-edge-panel__header">
155
+ <h4>{title}</h4>
156
+ <button
157
+ className="c-edge-panel__close c-btn c-btn--icon"
158
+ onClick={() => closePanel()}
159
+ aria-label="Close panel"
160
+ >
161
+ <Icon name="X" />
162
+ </button>
163
+ </div>
164
+ <div className="c-edge-panel__body">{children}</div>
165
+ </>
166
+ );
167
+
168
+ return (
169
+ <div className={panelClass} data-position={position} data-mode={mode} style={style}>
170
+ {backdrop && (
171
+ <div ref={backdropRef} className="c-edge-panel__backdrop" onClick={handleBackdropClick} />
139
172
  )}
173
+ <div ref={containerRef} className="c-edge-panel__container">
174
+ {glass ? (
175
+ <AtomixGlass {...glassProps}>
176
+ <div
177
+ ref={glassContentRef}
178
+ className="c-edge-panel__glass-content"
179
+ style={{ borderRadius: containerRef.current?.style.borderRadius }}
180
+ >
181
+ {panelContent}
182
+ </div>
183
+ </AtomixGlass>
184
+ ) : (
185
+ panelContent
186
+ )}
187
+ </div>
140
188
  </div>
141
- </div>
142
- );
143
- };
144
-
145
- export type { EdgePanelProps };
189
+ );
190
+ }
191
+ ) as unknown as EdgePanelComponent;
146
192
 
147
193
  EdgePanel.displayName = 'EdgePanel';
194
+ EdgePanel.Header = EdgePanelHeader;
195
+ EdgePanel.Body = EdgePanelBody;
196
+ EdgePanel.Footer = EdgePanelFooter;
197
+ EdgePanel.CloseButton = EdgePanelCloseButton;
198
+
199
+ export type { EdgePanelProps };
148
200
 
149
201
  export default EdgePanel;
@@ -0,0 +1,53 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { EdgePanel } from './EdgePanel';
4
+ import React from 'react';
5
+
6
+ describe('EdgePanel Component', () => {
7
+ it('renders correctly with legacy props', () => {
8
+ render(
9
+ <EdgePanel isOpen={true} title="Legacy Title">
10
+ Legacy Content
11
+ </EdgePanel>
12
+ );
13
+
14
+ expect(screen.getByText('Legacy Title')).toBeInTheDocument();
15
+ expect(screen.getByText('Legacy Content')).toBeInTheDocument();
16
+ });
17
+
18
+ it('renders correctly with compound components', () => {
19
+ render(
20
+ <EdgePanel isOpen={true}>
21
+ <EdgePanel.Header>
22
+ <h4>Compound Title</h4>
23
+ </EdgePanel.Header>
24
+ <EdgePanel.Body>
25
+ Compound Content
26
+ </EdgePanel.Body>
27
+ <EdgePanel.Footer>
28
+ Footer
29
+ </EdgePanel.Footer>
30
+ </EdgePanel>
31
+ );
32
+
33
+ expect(screen.getByText('Compound Title')).toBeInTheDocument();
34
+ expect(screen.getByText('Compound Content')).toBeInTheDocument();
35
+ expect(screen.getByText('Footer')).toBeInTheDocument();
36
+ });
37
+
38
+ it('uses close button in compound mode', () => {
39
+ const onClose = vi.fn();
40
+ render(
41
+ <EdgePanel isOpen={true} onOpenChange={onClose}>
42
+ <EdgePanel.Header>
43
+ <EdgePanel.CloseButton onClick={() => onClose(false)} />
44
+ </EdgePanel.Header>
45
+ <EdgePanel.Body>Content</EdgePanel.Body>
46
+ </EdgePanel>
47
+ );
48
+
49
+ const closeBtn = screen.getByLabelText('Close panel');
50
+ fireEvent.click(closeBtn);
51
+ expect(onClose).toHaveBeenCalledWith(false);
52
+ });
53
+ });
@@ -256,6 +256,29 @@ export const Sizes: Story = {
256
256
  ),
257
257
  };
258
258
 
259
+ // Compound usage
260
+ export const Compound: Story = {
261
+ render: () => (
262
+ <div style={{ width: '300px' }}>
263
+ <Select placeholder="Select a framework">
264
+ <Select.Option value="react">React</Select.Option>
265
+ <Select.Option value="vue">Vue</Select.Option>
266
+ <Select.Option value="angular">Angular</Select.Option>
267
+ <Select.Option value="svelte" disabled>
268
+ Svelte (Disabled)
269
+ </Select.Option>
270
+ </Select>
271
+ </div>
272
+ ),
273
+ parameters: {
274
+ docs: {
275
+ description: {
276
+ story: 'Select component using Compound Component Pattern.',
277
+ },
278
+ },
279
+ },
280
+ };
281
+
259
282
  // Select states
260
283
  export const States: Story = {
261
284
  args: {
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import { Select } from './Select';
5
+
6
+ describe('Select Component', () => {
7
+ it('renders legacy options correctly', () => {
8
+ const options = [
9
+ { value: '1', label: 'Option 1' },
10
+ { value: '2', label: 'Option 2' },
11
+ ];
12
+ render(<Select options={options} value="" onChange={() => {}} />);
13
+
14
+ // Check custom UI items
15
+ const items = document.querySelectorAll('.c-select__item');
16
+ expect(items).toHaveLength(2);
17
+ expect(items[0]).toHaveTextContent('Option 1');
18
+ expect(items[1]).toHaveTextContent('Option 2');
19
+
20
+ // Check native select options
21
+ const select = document.querySelector('select');
22
+ expect(select).not.toBeNull();
23
+ expect(select?.options).toHaveLength(3); // Placeholder + 2
24
+ expect(select?.options[1].value).toBe('1');
25
+ expect(select?.options[2].value).toBe('2');
26
+ });
27
+
28
+ it('renders compound options correctly', async () => {
29
+ render(
30
+ <Select value="" onChange={() => {}}>
31
+ <Select.Option value="1">Compound Option 1</Select.Option>
32
+ <Select.Option value="2">Compound Option 2</Select.Option>
33
+ </Select>
34
+ );
35
+
36
+ // Check custom UI items
37
+ const items = document.querySelectorAll('.c-select__item');
38
+ expect(items).toHaveLength(2);
39
+ expect(items[0]).toHaveTextContent('Compound Option 1');
40
+ expect(items[1]).toHaveTextContent('Compound Option 2');
41
+
42
+ // Check native select options
43
+ await waitFor(() => {
44
+ const select = document.querySelector('select');
45
+ expect(select).not.toBeNull();
46
+ expect(select?.options).toHaveLength(3); // Placeholder + 2
47
+ expect(select?.options[1].value).toBe('1');
48
+ expect(select?.options[2].value).toBe('2');
49
+ });
50
+ });
51
+
52
+ it('handles selection in legacy mode', () => {
53
+ const handleChange = vi.fn();
54
+ const options = [
55
+ { value: '1', label: 'Option 1' },
56
+ { value: '2', label: 'Option 2' },
57
+ ];
58
+ render(<Select options={options} value="" onChange={handleChange} />);
59
+
60
+ // Open dropdown
61
+ const trigger = document.querySelector('.c-select__selected');
62
+ fireEvent.click(trigger!);
63
+
64
+ // Click item
65
+ const item = document.querySelector('.c-select__item[data-value="1"]');
66
+ fireEvent.click(item!);
67
+
68
+ expect(handleChange).toHaveBeenCalled();
69
+ // Check event value
70
+ expect(handleChange.mock.calls[0][0].target.value).toBe('1');
71
+ });
72
+
73
+ it('handles selection in compound mode', async () => {
74
+ const handleChange = vi.fn();
75
+ render(
76
+ <Select value="" onChange={handleChange}>
77
+ <Select.Option value="1">Option 1</Select.Option>
78
+ <Select.Option value="2">Option 2</Select.Option>
79
+ </Select>
80
+ );
81
+
82
+ // Wait for options to be registered
83
+ await waitFor(() => {
84
+ const select = document.querySelector('select');
85
+ expect(select?.options).toHaveLength(3);
86
+ });
87
+
88
+ // Open dropdown
89
+ const trigger = document.querySelector('.c-select__selected');
90
+ fireEvent.click(trigger!);
91
+
92
+ // Click item
93
+ const item = document.querySelector('.c-select__item[data-value="2"]');
94
+ fireEvent.click(item!);
95
+
96
+ expect(handleChange).toHaveBeenCalled();
97
+ expect(handleChange.mock.calls[0][0].target.value).toBe('2');
98
+ });
99
+ });