@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.
- package/dist/atomix.css +9231 -9337
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +2 -2
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.js +4 -5
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +87 -10
- package/dist/core.js +673 -480
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +15 -3
- package/dist/forms.js +530 -97
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +5 -6
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +495 -254
- package/dist/index.esm.js +1269 -723
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1273 -723
- 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 +10 -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/Breadcrumb/Breadcrumb.tsx +156 -50
- package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
- package/src/components/Callout/Callout.stories.tsx +166 -1011
- package/src/components/Callout/Callout.tsx +196 -84
- package/src/components/Callout/CalloutCompound.test.tsx +72 -0
- package/src/components/Dropdown/Dropdown.tsx +133 -20
- package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
- package/src/components/EdgePanel/EdgePanel.tsx +164 -112
- package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
- package/src/components/Form/Select.stories.tsx +23 -0
- package/src/components/Form/Select.test.tsx +99 -0
- package/src/components/Form/Select.tsx +144 -93
- package/src/components/Form/SelectOption.tsx +88 -0
- package/src/components/Hero/Hero.stories.tsx +37 -0
- package/src/components/Hero/Hero.test.tsx +142 -0
- package/src/components/Hero/Hero.tsx +142 -3
- 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/Modal/Modal.stories.tsx +65 -1
- package/src/components/Modal/Modal.tsx +115 -35
- package/src/components/Modal/ModalCompound.test.tsx +94 -0
- package/src/components/Steps/Steps.tsx +124 -21
- package/src/components/Steps/StepsCompound.test.tsx +81 -0
- package/src/components/Tabs/Tabs.tsx +197 -44
- package/src/components/Tabs/TabsCompound.test.tsx +64 -0
- package/src/lib/composables/index.ts +0 -4
- package/src/lib/composables/useAtomixGlass.ts +0 -15
- package/src/lib/theme/devtools/CLI.ts +2 -10
- package/src/lib/types/components.ts +8 -2
- package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
- package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
- package/src/lib/utils/themeNaming.ts +1 -1
- package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
- package/src/styles/02-tools/_tools.utility-api.scss +6 -6
- package/src/styles/99-utilities/_utilities.text.scss +0 -1
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import React, { useRef, useEffect, useState, memo } from 'react';
|
|
2
|
-
import { SelectProps } from '../../lib/types/components';
|
|
1
|
+
import React, { useRef, useEffect, useState, memo, useCallback } from 'react';
|
|
2
|
+
import { SelectProps, SelectOption as SelectOptionType } from '../../lib/types/components';
|
|
3
3
|
import { useSelect } from '../../lib/composables';
|
|
4
4
|
import { SELECT } from '../../lib/constants/components';
|
|
5
5
|
import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
6
|
+
import { SelectContext, SelectOption } from './SelectOption';
|
|
7
|
+
|
|
8
|
+
export type SelectComponent = React.FC<SelectProps> & {
|
|
9
|
+
Option: typeof SelectOption;
|
|
10
|
+
};
|
|
6
11
|
|
|
7
12
|
/**
|
|
8
13
|
* Select - A component for dropdown selection
|
|
9
14
|
*/
|
|
10
|
-
export const Select:
|
|
15
|
+
export const Select: SelectComponent = memo(
|
|
11
16
|
({
|
|
12
|
-
options
|
|
17
|
+
options,
|
|
13
18
|
value,
|
|
14
19
|
onChange,
|
|
15
20
|
onBlur,
|
|
@@ -28,7 +33,8 @@ export const Select: React.FC<SelectProps> = memo(
|
|
|
28
33
|
'aria-label': ariaLabel,
|
|
29
34
|
'aria-describedby': ariaDescribedBy,
|
|
30
35
|
glass,
|
|
31
|
-
|
|
36
|
+
children,
|
|
37
|
+
}: SelectProps) => {
|
|
32
38
|
const { generateSelectClass } = useSelect({
|
|
33
39
|
size,
|
|
34
40
|
disabled,
|
|
@@ -51,17 +57,35 @@ export const Select: React.FC<SelectProps> = memo(
|
|
|
51
57
|
const bodyRef = useRef<HTMLDivElement>(null);
|
|
52
58
|
const nativeSelectRef = useRef<HTMLSelectElement>(null);
|
|
53
59
|
|
|
60
|
+
// State for registered options (Compound mode)
|
|
61
|
+
const [registeredOptions, setRegisteredOptions] = useState<SelectOptionType[]>([]);
|
|
62
|
+
|
|
63
|
+
const registerOption = useCallback((option: SelectOptionType) => {
|
|
64
|
+
setRegisteredOptions((prev) => {
|
|
65
|
+
if (prev.some(o => o.value === option.value)) return prev;
|
|
66
|
+
return [...prev, option];
|
|
67
|
+
});
|
|
68
|
+
}, []);
|
|
69
|
+
|
|
70
|
+
const unregisterOption = useCallback((value: string) => {
|
|
71
|
+
setRegisteredOptions((prev) => prev.filter(o => o.value !== value));
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
// Determine active options
|
|
75
|
+
const hasOptionsProp = options && options.length > 0;
|
|
76
|
+
const activeOptions = hasOptionsProp ? options : registeredOptions;
|
|
77
|
+
|
|
54
78
|
// Update selected label when value changes
|
|
55
79
|
useEffect(() => {
|
|
56
80
|
if (value) {
|
|
57
|
-
const selectedOption =
|
|
81
|
+
const selectedOption = activeOptions.find(opt => opt.value === value);
|
|
58
82
|
if (selectedOption) {
|
|
59
83
|
setSelectedLabel(selectedOption.label);
|
|
60
84
|
}
|
|
61
85
|
} else {
|
|
62
86
|
setSelectedLabel(placeholder);
|
|
63
87
|
}
|
|
64
|
-
}, [value,
|
|
88
|
+
}, [value, activeOptions, placeholder]);
|
|
65
89
|
|
|
66
90
|
// Handle click outside to close dropdown
|
|
67
91
|
useEffect(() => {
|
|
@@ -93,99 +117,125 @@ export const Select: React.FC<SelectProps> = memo(
|
|
|
93
117
|
};
|
|
94
118
|
|
|
95
119
|
// Handle item selection
|
|
96
|
-
const handleItemClick = (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
bodyRef.current
|
|
101
|
-
|
|
120
|
+
const handleItemClick = useCallback(
|
|
121
|
+
(option: { value: string; label: string }) => {
|
|
122
|
+
setSelectedLabel(option.label);
|
|
123
|
+
setIsOpen(false);
|
|
124
|
+
if (bodyRef.current) {
|
|
125
|
+
bodyRef.current.style.height = '0px';
|
|
126
|
+
}
|
|
102
127
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
128
|
+
if (nativeSelectRef.current) {
|
|
129
|
+
nativeSelectRef.current.value = option.value;
|
|
130
|
+
}
|
|
106
131
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
132
|
+
if (onChange) {
|
|
133
|
+
// Create a synthetic event
|
|
134
|
+
const event = {
|
|
135
|
+
target: {
|
|
136
|
+
name,
|
|
137
|
+
value: option.value,
|
|
138
|
+
},
|
|
139
|
+
} as React.ChangeEvent<HTMLSelectElement>;
|
|
140
|
+
onChange(event);
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
[onChange, name]
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const onSelect = useCallback(
|
|
147
|
+
(val: string, label: string) => {
|
|
148
|
+
handleItemClick({ value: val, label });
|
|
149
|
+
},
|
|
150
|
+
[handleItemClick]
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const contextValue = React.useMemo(
|
|
154
|
+
() => ({
|
|
155
|
+
registerOption,
|
|
156
|
+
unregisterOption,
|
|
157
|
+
selectedValue: value,
|
|
158
|
+
onSelect,
|
|
159
|
+
}),
|
|
160
|
+
[registerOption, unregisterOption, value, onSelect]
|
|
161
|
+
);
|
|
118
162
|
|
|
119
163
|
const selectContent = (
|
|
120
|
-
<
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{/* Native select for accessibility and form submission */}
|
|
127
|
-
<select
|
|
128
|
-
ref={nativeSelectRef}
|
|
129
|
-
value={value}
|
|
130
|
-
onChange={onChange}
|
|
131
|
-
onBlur={onBlur}
|
|
132
|
-
onFocus={onFocus}
|
|
133
|
-
disabled={disabled}
|
|
134
|
-
required={required}
|
|
135
|
-
id={id}
|
|
136
|
-
name={name}
|
|
137
|
-
multiple={multiple}
|
|
138
|
-
aria-label={ariaLabel}
|
|
139
|
-
aria-describedby={ariaDescribedBy}
|
|
140
|
-
aria-invalid={invalid}
|
|
141
|
-
style={{ display: 'none' }}
|
|
164
|
+
<SelectContext.Provider value={contextValue}>
|
|
165
|
+
<div
|
|
166
|
+
className={`${selectClass} ${isOpen ? SELECT.CLASSES.IS_OPEN : ''}`}
|
|
167
|
+
ref={dropdownRef}
|
|
168
|
+
style={style}
|
|
169
|
+
aria-expanded={isOpen}
|
|
142
170
|
>
|
|
143
|
-
{
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
{/* Native select for accessibility and form submission */}
|
|
172
|
+
<select
|
|
173
|
+
ref={nativeSelectRef}
|
|
174
|
+
value={value}
|
|
175
|
+
onChange={onChange}
|
|
176
|
+
onBlur={onBlur}
|
|
177
|
+
onFocus={onFocus}
|
|
178
|
+
disabled={disabled}
|
|
179
|
+
required={required}
|
|
180
|
+
id={id}
|
|
181
|
+
name={name}
|
|
182
|
+
multiple={multiple}
|
|
183
|
+
aria-label={ariaLabel}
|
|
184
|
+
aria-describedby={ariaDescribedBy}
|
|
185
|
+
aria-invalid={invalid}
|
|
186
|
+
style={{ display: 'none' }}
|
|
187
|
+
>
|
|
188
|
+
{placeholder && (
|
|
189
|
+
<option value="" disabled>
|
|
190
|
+
{placeholder}
|
|
191
|
+
</option>
|
|
192
|
+
)}
|
|
193
|
+
{activeOptions.map(option => (
|
|
194
|
+
<option key={option.value} value={option.value} disabled={option.disabled}>
|
|
195
|
+
{option.label}
|
|
196
|
+
</option>
|
|
197
|
+
))}
|
|
198
|
+
</select>
|
|
199
|
+
|
|
200
|
+
{/* Custom Select UI */}
|
|
201
|
+
<div className={SELECT.CLASSES.SELECTED} onClick={handleToggle} aria-disabled={disabled}>
|
|
202
|
+
{selectedLabel}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<i className={`${SELECT.CLASSES.ICON_CARET} ${SELECT.CLASSES.TOGGLE_ICON}`} />
|
|
159
206
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
207
|
+
<div className={SELECT.CLASSES.SELECT_BODY} ref={bodyRef} style={{ height: 0 }}>
|
|
208
|
+
<div className={SELECT.CLASSES.SELECT_PANEL} ref={panelRef}>
|
|
209
|
+
<ul className={SELECT.CLASSES.SELECT_ITEMS}>
|
|
210
|
+
{hasOptionsProp ? (
|
|
211
|
+
options.map((option, index) => (
|
|
212
|
+
<li
|
|
213
|
+
key={option.value}
|
|
214
|
+
className={SELECT.CLASSES.SELECT_ITEM}
|
|
215
|
+
data-value={option.value}
|
|
216
|
+
onClick={() => !option.disabled && handleItemClick(option)}
|
|
217
|
+
>
|
|
218
|
+
<label htmlFor={`SelectItem${index}`} className="c-checkbox">
|
|
219
|
+
<input
|
|
220
|
+
type="checkbox"
|
|
221
|
+
id={`SelectItem${index}`}
|
|
222
|
+
className="c-checkbox__input c-select__item-input"
|
|
223
|
+
checked={value === option.value}
|
|
224
|
+
readOnly
|
|
225
|
+
disabled={option.disabled}
|
|
226
|
+
/>
|
|
227
|
+
<div className="c-select__item-label">{option.label}</div>
|
|
228
|
+
</label>
|
|
229
|
+
</li>
|
|
230
|
+
))
|
|
231
|
+
) : (
|
|
232
|
+
children
|
|
233
|
+
)}
|
|
234
|
+
</ul>
|
|
235
|
+
</div>
|
|
186
236
|
</div>
|
|
187
237
|
</div>
|
|
188
|
-
</
|
|
238
|
+
</SelectContext.Provider>
|
|
189
239
|
);
|
|
190
240
|
|
|
191
241
|
if (glass) {
|
|
@@ -206,10 +256,11 @@ export const Select: React.FC<SelectProps> = memo(
|
|
|
206
256
|
|
|
207
257
|
return selectContent;
|
|
208
258
|
}
|
|
209
|
-
);
|
|
259
|
+
) as unknown as SelectComponent;
|
|
210
260
|
|
|
211
261
|
export type { SelectProps };
|
|
212
262
|
|
|
213
263
|
Select.displayName = 'Select';
|
|
264
|
+
Select.Option = SelectOption;
|
|
214
265
|
|
|
215
266
|
export default Select;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React, { createContext, useContext, useEffect, memo, ReactNode } from 'react';
|
|
2
|
+
import { SelectOption as SelectOptionType } from '../../lib/types/components';
|
|
3
|
+
import { SELECT } from '../../lib/constants/components';
|
|
4
|
+
|
|
5
|
+
// Context for managing options registration and selection
|
|
6
|
+
export interface SelectContextType {
|
|
7
|
+
registerOption: (option: SelectOptionType) => void;
|
|
8
|
+
unregisterOption: (value: string) => void;
|
|
9
|
+
selectedValue?: string | string[];
|
|
10
|
+
onSelect: (value: string, label: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const SelectContext = createContext<SelectContextType | null>(null);
|
|
14
|
+
|
|
15
|
+
export interface SelectOptionProps {
|
|
16
|
+
value: string;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
className?: string;
|
|
20
|
+
style?: React.CSSProperties;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const SelectOption: React.FC<SelectOptionProps> = memo(
|
|
24
|
+
({ value, children, disabled = false, className = '', style }) => {
|
|
25
|
+
const context = useContext(SelectContext);
|
|
26
|
+
|
|
27
|
+
// We assume children is the label if it's a string, or we need a way to get label.
|
|
28
|
+
// For simplicity, we use children as label for registration if it's a string.
|
|
29
|
+
const label = typeof children === 'string' ? children : value;
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (context) {
|
|
33
|
+
context.registerOption({ value, label, disabled });
|
|
34
|
+
return () => {
|
|
35
|
+
context.unregisterOption(value);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return undefined;
|
|
39
|
+
}, [context, value, label, disabled]);
|
|
40
|
+
|
|
41
|
+
if (!context) {
|
|
42
|
+
console.warn('SelectOption must be used within a Select component');
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const { selectedValue, onSelect } = context;
|
|
47
|
+
|
|
48
|
+
const isSelected = Array.isArray(selectedValue)
|
|
49
|
+
? selectedValue.includes(value)
|
|
50
|
+
: selectedValue === value;
|
|
51
|
+
|
|
52
|
+
const handleClick = (e: React.MouseEvent) => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
if (!disabled) {
|
|
56
|
+
onSelect(value, label);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<li
|
|
62
|
+
className={`${SELECT.CLASSES.SELECT_ITEM} ${className}`.trim()}
|
|
63
|
+
data-value={value}
|
|
64
|
+
onClick={handleClick}
|
|
65
|
+
style={style}
|
|
66
|
+
role="option"
|
|
67
|
+
aria-selected={isSelected}
|
|
68
|
+
aria-disabled={disabled}
|
|
69
|
+
>
|
|
70
|
+
<label className="c-checkbox" style={{ pointerEvents: 'none' }}>
|
|
71
|
+
<input
|
|
72
|
+
type="checkbox"
|
|
73
|
+
className="c-checkbox__input c-select__item-input"
|
|
74
|
+
checked={isSelected}
|
|
75
|
+
readOnly
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
tabIndex={-1}
|
|
78
|
+
/>
|
|
79
|
+
<div className="c-select__item-label">{children}</div>
|
|
80
|
+
</label>
|
|
81
|
+
</li>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
SelectOption.displayName = 'SelectOption';
|
|
87
|
+
|
|
88
|
+
export default SelectOption;
|
|
@@ -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',
|
|
@@ -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
|
+
});
|