@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState, useCallback, memo } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState, useCallback, memo, forwardRef, ReactNode } from 'react';
|
|
2
2
|
import { ModalProps } from '../../lib/types/components';
|
|
3
3
|
import { MODAL } from '../../lib/constants/components';
|
|
4
4
|
import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
@@ -73,10 +73,83 @@ function useModal({
|
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// Modal Subcomponents
|
|
77
|
+
|
|
78
|
+
export interface ModalHeaderProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
|
|
79
|
+
title?: ReactNode;
|
|
80
|
+
subtitle?: ReactNode;
|
|
81
|
+
closeButton?: boolean;
|
|
82
|
+
onClose?: () => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const ModalHeader = forwardRef<HTMLDivElement, ModalHeaderProps>(
|
|
86
|
+
({ title, subtitle, closeButton, onClose, children, className = '', ...props }, ref) => {
|
|
87
|
+
return (
|
|
88
|
+
<div ref={ref} className={`c-modal__header ${className}`.trim()} {...props}>
|
|
89
|
+
<div className="c-modal__header-content">
|
|
90
|
+
{title && <h3 className="c-modal__title">{title}</h3>}
|
|
91
|
+
{subtitle && <p className="c-modal__sub">{subtitle}</p>}
|
|
92
|
+
{children}
|
|
93
|
+
</div>
|
|
94
|
+
{closeButton && (
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
className="c-modal__close c-btn js-modal-close"
|
|
98
|
+
onClick={onClose}
|
|
99
|
+
aria-label="Close modal"
|
|
100
|
+
>
|
|
101
|
+
<svg
|
|
102
|
+
width="20"
|
|
103
|
+
height="20"
|
|
104
|
+
viewBox="0 0 20 20"
|
|
105
|
+
fill="none"
|
|
106
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
107
|
+
>
|
|
108
|
+
<path
|
|
109
|
+
d="M16.0672 15.1828C16.1253 15.2409 16.1713 15.3098 16.2028 15.3857C16.2342 15.4615 16.2504 15.5429 16.2504 15.625C16.2504 15.7071 16.2342 15.7884 16.2028 15.8643C16.1713 15.9402 16.1253 16.0091 16.0672 16.0672C16.0091 16.1252 15.9402 16.1713 15.8643 16.2027C15.7885 16.2342 15.7071 16.2503 15.625 16.2503C15.5429 16.2503 15.4616 16.2342 15.3857 16.2027C15.3098 16.1713 15.2409 16.1252 15.1828 16.0672L10 10.8836L4.8172 16.0672C4.69992 16.1844 4.54086 16.2503 4.37501 16.2503C4.20916 16.2503 4.0501 16.1844 3.93282 16.0672C3.81555 15.9499 3.74966 15.7908 3.74966 15.625C3.74966 15.4591 3.81555 15.3001 3.93282 15.1828L9.11642 9.99998L3.93282 4.81717C3.81555 4.69989 3.74966 4.54083 3.74966 4.37498C3.74966 4.20913 3.81555 4.05007 3.93282 3.93279C4.0501 3.81552 4.20916 3.74963 4.37501 3.74963C4.54086 3.74963 4.69992 3.81552 4.8172 3.93279L10 9.11639L15.1828 3.93279C15.3001 3.81552 15.4592 3.74963 15.625 3.74963C15.7909 3.74963 15.9499 3.81552 16.0672 3.93279C16.1845 4.05007 16.2504 4.20913 16.2504 4.37498C16.2504 4.54083 16.1845 4.69989 16.0672 4.81717L10.8836 9.99998L16.0672 15.1828Z"
|
|
110
|
+
fill="#141414"
|
|
111
|
+
/>
|
|
112
|
+
</svg>
|
|
113
|
+
</button>
|
|
114
|
+
)}
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
);
|
|
119
|
+
ModalHeader.displayName = 'ModalHeader';
|
|
120
|
+
|
|
121
|
+
export const ModalBody = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
122
|
+
({ children, className = '', ...props }, ref) => {
|
|
123
|
+
return (
|
|
124
|
+
<div ref={ref} className={`c-modal__body ${className}`.trim()} {...props}>
|
|
125
|
+
{children}
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
ModalBody.displayName = 'ModalBody';
|
|
131
|
+
|
|
132
|
+
export const ModalFooter = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
133
|
+
({ children, className = '', ...props }, ref) => {
|
|
134
|
+
return (
|
|
135
|
+
<div ref={ref} className={`c-modal__footer ${className}`.trim()} {...props}>
|
|
136
|
+
{children}
|
|
137
|
+
</div>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
ModalFooter.displayName = 'ModalFooter';
|
|
142
|
+
|
|
76
143
|
/**
|
|
77
144
|
* Modal component for displaying overlay content
|
|
78
145
|
*/
|
|
79
|
-
|
|
146
|
+
type ModalComponent = React.FC<ModalProps> & {
|
|
147
|
+
Header: typeof ModalHeader;
|
|
148
|
+
Body: typeof ModalBody;
|
|
149
|
+
Footer: typeof ModalFooter;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const ModalImpl = memo(
|
|
80
153
|
({
|
|
81
154
|
children,
|
|
82
155
|
isOpen = false,
|
|
@@ -94,7 +167,7 @@ export const Modal: React.FC<ModalProps> = memo(
|
|
|
94
167
|
footer,
|
|
95
168
|
glass,
|
|
96
169
|
...props
|
|
97
|
-
}) => {
|
|
170
|
+
}: ModalProps) => {
|
|
98
171
|
const modalRef = useRef<HTMLDivElement>(null);
|
|
99
172
|
const dialogRef = useRef<HTMLDivElement>(null);
|
|
100
173
|
const backdropRef = useRef<HTMLDivElement>(null);
|
|
@@ -144,41 +217,40 @@ export const Modal: React.FC<ModalProps> = memo(
|
|
|
144
217
|
.filter(Boolean)
|
|
145
218
|
.join(' ');
|
|
146
219
|
|
|
220
|
+
// Check for compound components usage
|
|
221
|
+
const hasCompoundComponents = React.Children.toArray(children).some((child) =>
|
|
222
|
+
React.isValidElement(child) &&
|
|
223
|
+
['ModalHeader', 'ModalBody', 'ModalFooter'].includes((child.type as any).displayName)
|
|
224
|
+
);
|
|
225
|
+
|
|
147
226
|
const modalContent = (
|
|
148
227
|
<div className="c-modal__content">
|
|
149
|
-
{
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
d="M16.0672 15.1828C16.1253 15.2409 16.1713 15.3098 16.2028 15.3857C16.2342 15.4615 16.2504 15.5429 16.2504 15.625C16.2504 15.7071 16.2342 15.7884 16.2028 15.8643C16.1713 15.9402 16.1253 16.0091 16.0672 16.0672C16.0091 16.1252 15.9402 16.1713 15.8643 16.2027C15.7885 16.2342 15.7071 16.2503 15.625 16.2503C15.5429 16.2503 15.4616 16.2342 15.3857 16.2027C15.3098 16.1713 15.2409 16.1252 15.1828 16.0672L10 10.8836L4.8172 16.0672C4.69992 16.1844 4.54086 16.2503 4.37501 16.2503C4.20916 16.2503 4.0501 16.1844 3.93282 16.0672C3.81555 15.9499 3.74966 15.7908 3.74966 15.625C3.74966 15.4591 3.81555 15.3001 3.93282 15.1828L9.11642 9.99998L3.93282 4.81717C3.81555 4.69989 3.74966 4.54083 3.74966 4.37498C3.74966 4.20913 3.81555 4.05007 3.93282 3.93279C4.0501 3.81552 4.20916 3.74963 4.37501 3.74963C4.54086 3.74963 4.69992 3.81552 4.8172 3.93279L10 9.11639L15.1828 3.93279C15.3001 3.81552 15.4592 3.74963 15.625 3.74963C15.7909 3.74963 15.9499 3.81552 16.0672 3.93279C16.1845 4.05007 16.2504 4.20913 16.2504 4.37498C16.2504 4.54083 16.1845 4.69989 16.0672 4.81717L10.8836 9.99998L16.0672 15.1828Z"
|
|
171
|
-
fill="#141414"
|
|
172
|
-
/>
|
|
173
|
-
</svg>
|
|
174
|
-
</button>
|
|
228
|
+
{hasCompoundComponents ? (
|
|
229
|
+
React.Children.map(children, child => {
|
|
230
|
+
if (
|
|
231
|
+
React.isValidElement(child) &&
|
|
232
|
+
(child.type as any).displayName === 'ModalHeader'
|
|
233
|
+
) {
|
|
234
|
+
return React.cloneElement(child, {
|
|
235
|
+
onClose: (child.props as any).onClose || close,
|
|
236
|
+
} as any);
|
|
237
|
+
}
|
|
238
|
+
return child;
|
|
239
|
+
})
|
|
240
|
+
) : (
|
|
241
|
+
<>
|
|
242
|
+
{(title || closeButton) && (
|
|
243
|
+
<ModalHeader
|
|
244
|
+
title={title}
|
|
245
|
+
subtitle={subtitle}
|
|
246
|
+
closeButton={closeButton}
|
|
247
|
+
onClose={close}
|
|
248
|
+
/>
|
|
175
249
|
)}
|
|
176
|
-
|
|
250
|
+
<ModalBody>{children}</ModalBody>
|
|
251
|
+
{footer && <ModalFooter>{footer}</ModalFooter>}
|
|
252
|
+
</>
|
|
177
253
|
)}
|
|
178
|
-
|
|
179
|
-
<div className="c-modal__body">{children}</div>
|
|
180
|
-
|
|
181
|
-
{footer && <div className="c-modal__footer">{footer}</div>}
|
|
182
254
|
</div>
|
|
183
255
|
);
|
|
184
256
|
|
|
@@ -218,7 +290,15 @@ export const Modal: React.FC<ModalProps> = memo(
|
|
|
218
290
|
}
|
|
219
291
|
);
|
|
220
292
|
|
|
221
|
-
|
|
293
|
+
ModalImpl.displayName = 'Modal';
|
|
294
|
+
|
|
295
|
+
// Attach subcomponents
|
|
296
|
+
const ModalWithSubcomponents = ModalImpl as unknown as ModalComponent;
|
|
297
|
+
ModalWithSubcomponents.Header = ModalHeader;
|
|
298
|
+
ModalWithSubcomponents.Body = ModalBody;
|
|
299
|
+
ModalWithSubcomponents.Footer = ModalFooter;
|
|
300
|
+
|
|
301
|
+
export const Modal = ModalWithSubcomponents;
|
|
222
302
|
|
|
223
303
|
export type { ModalProps };
|
|
224
304
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
+
import { Modal } from './Modal';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
|
|
6
|
+
describe('Modal Component', () => {
|
|
7
|
+
it('renders correctly with legacy props', () => {
|
|
8
|
+
render(
|
|
9
|
+
<Modal isOpen={true} title="Legacy Title" footer="Legacy Footer">
|
|
10
|
+
Legacy Content
|
|
11
|
+
</Modal>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
expect(screen.getByText('Legacy Title')).toBeInTheDocument();
|
|
15
|
+
expect(screen.getByText('Legacy Content')).toBeInTheDocument();
|
|
16
|
+
expect(screen.getByText('Legacy Footer')).toBeInTheDocument();
|
|
17
|
+
|
|
18
|
+
// Check structure classes
|
|
19
|
+
expect(document.querySelector('.c-modal__header')).toBeInTheDocument();
|
|
20
|
+
expect(document.querySelector('.c-modal__body')).toBeInTheDocument();
|
|
21
|
+
expect(document.querySelector('.c-modal__footer')).toBeInTheDocument();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('renders correctly with compound components', () => {
|
|
25
|
+
render(
|
|
26
|
+
<Modal isOpen={true}>
|
|
27
|
+
<Modal.Header title="Compound Header" />
|
|
28
|
+
<Modal.Body>Compound Body</Modal.Body>
|
|
29
|
+
<Modal.Footer>Compound Footer</Modal.Footer>
|
|
30
|
+
</Modal>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
expect(screen.getByText('Compound Header')).toBeInTheDocument();
|
|
34
|
+
expect(screen.getByText('Compound Body')).toBeInTheDocument();
|
|
35
|
+
expect(screen.getByText('Compound Footer')).toBeInTheDocument();
|
|
36
|
+
|
|
37
|
+
// Verify no double wrapping
|
|
38
|
+
// If double wrapping occurred, we might see nested .c-modal__body or similar issues,
|
|
39
|
+
// or the header inside the body if logic failed.
|
|
40
|
+
|
|
41
|
+
const header = document.querySelector('.c-modal__header');
|
|
42
|
+
const body = document.querySelector('.c-modal__body');
|
|
43
|
+
const footer = document.querySelector('.c-modal__footer');
|
|
44
|
+
|
|
45
|
+
// Header should be a direct child of .c-modal__content (or close to it)
|
|
46
|
+
expect(header?.parentElement).toHaveClass('c-modal__content');
|
|
47
|
+
expect(body?.parentElement).toHaveClass('c-modal__content');
|
|
48
|
+
expect(footer?.parentElement).toHaveClass('c-modal__content');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('injects onClose into Modal.Header when used in compound mode', () => {
|
|
52
|
+
const onClose = vi.fn();
|
|
53
|
+
render(
|
|
54
|
+
<Modal isOpen={true} onClose={onClose}>
|
|
55
|
+
<Modal.Header closeButton data-testid="header" />
|
|
56
|
+
<Modal.Body>Content</Modal.Body>
|
|
57
|
+
</Modal>
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const closeBtn = screen.getByLabelText('Close modal');
|
|
61
|
+
fireEvent.click(closeBtn);
|
|
62
|
+
expect(onClose).toHaveBeenCalled();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('allows custom onClose in Modal.Header', () => {
|
|
66
|
+
const modalOnClose = vi.fn();
|
|
67
|
+
const headerOnClose = vi.fn();
|
|
68
|
+
|
|
69
|
+
render(
|
|
70
|
+
<Modal isOpen={true} onClose={modalOnClose}>
|
|
71
|
+
<Modal.Header closeButton onClose={headerOnClose} />
|
|
72
|
+
<Modal.Body>Content</Modal.Body>
|
|
73
|
+
</Modal>
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const closeBtn = screen.getByLabelText('Close modal');
|
|
77
|
+
fireEvent.click(closeBtn);
|
|
78
|
+
|
|
79
|
+
expect(headerOnClose).toHaveBeenCalled();
|
|
80
|
+
expect(modalOnClose).not.toHaveBeenCalled();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('prioritizes compound components over legacy props', () => {
|
|
84
|
+
render(
|
|
85
|
+
<Modal isOpen={true} title="Legacy Title">
|
|
86
|
+
<Modal.Header title="Compound Header" />
|
|
87
|
+
<Modal.Body>Compound Body</Modal.Body>
|
|
88
|
+
</Modal>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(screen.getByText('Compound Header')).toBeInTheDocument();
|
|
92
|
+
expect(screen.queryByText('Legacy Title')).not.toBeInTheDocument();
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -645,7 +645,7 @@ export const GlassCustom: Story = {
|
|
|
645
645
|
glass={{
|
|
646
646
|
displacementScale: 80,
|
|
647
647
|
blurAmount: 2.5,
|
|
648
|
-
|
|
648
|
+
borderRadius: 20,
|
|
649
649
|
mode: 'shader',
|
|
650
650
|
}}
|
|
651
651
|
>
|
|
@@ -799,7 +799,7 @@ export const GlassThemeShowcase: Story = {
|
|
|
799
799
|
glass={{
|
|
800
800
|
displacementScale: 60,
|
|
801
801
|
blurAmount: 2,
|
|
802
|
-
|
|
802
|
+
borderRadius: 16,
|
|
803
803
|
mode: 'shader',
|
|
804
804
|
}}
|
|
805
805
|
>
|
|
@@ -51,7 +51,7 @@ export const Nav = forwardRef<HTMLUListElement, NavProps>(
|
|
|
51
51
|
const defaultGlassProps = {
|
|
52
52
|
displacementScale: 60,
|
|
53
53
|
blurAmount: 1.5,
|
|
54
|
-
|
|
54
|
+
borderRadius: 8,
|
|
55
55
|
mode: 'shader' as const,
|
|
56
56
|
};
|
|
57
57
|
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
@@ -715,7 +715,7 @@ export const GlassCustom: Story = {
|
|
|
715
715
|
glass={{
|
|
716
716
|
displacementScale: 100,
|
|
717
717
|
blurAmount: 2.5,
|
|
718
|
-
|
|
718
|
+
borderRadius: 0,
|
|
719
719
|
mode: 'shader',
|
|
720
720
|
}}
|
|
721
721
|
>
|
|
@@ -773,7 +773,7 @@ export const GlassCustom: Story = {
|
|
|
773
773
|
docs: {
|
|
774
774
|
description: {
|
|
775
775
|
story:
|
|
776
|
-
'Customized glass effect with increased displacement and blur for a more pronounced visual impact. The sharp corners (
|
|
776
|
+
'Customized glass effect with increased displacement and blur for a more pronounced visual impact. The sharp corners (borderRadius: 0) create a modern, edge-to-edge aesthetic.',
|
|
777
777
|
},
|
|
778
778
|
},
|
|
779
779
|
},
|
|
@@ -942,7 +942,7 @@ export const GlassThemeShowcase: Story = {
|
|
|
942
942
|
glass={{
|
|
943
943
|
displacementScale: 60,
|
|
944
944
|
blurAmount: 2,
|
|
945
|
-
|
|
945
|
+
borderRadius: 0,
|
|
946
946
|
mode: 'shader',
|
|
947
947
|
}}
|
|
948
948
|
>
|
|
@@ -146,7 +146,7 @@ export const Navbar = forwardRef<HTMLElement, NavbarProps>(
|
|
|
146
146
|
const defaultGlassProps = {
|
|
147
147
|
displacementScale: 30,
|
|
148
148
|
blurAmount: 2,
|
|
149
|
-
|
|
149
|
+
borderRadius: 0,
|
|
150
150
|
elasticity: 0,
|
|
151
151
|
mode: 'shader' as const,
|
|
152
152
|
shaderVariant: 'premiumGlass' as const,
|
|
@@ -1140,7 +1140,7 @@ export const GlassCustom: Story = {
|
|
|
1140
1140
|
glass={{
|
|
1141
1141
|
displacementScale: 70,
|
|
1142
1142
|
blurAmount: 2,
|
|
1143
|
-
|
|
1143
|
+
borderRadius: 12,
|
|
1144
1144
|
mode: 'shader',
|
|
1145
1145
|
}}
|
|
1146
1146
|
>
|
|
@@ -1293,7 +1293,7 @@ export const GlassThemeShowcase: Story = {
|
|
|
1293
1293
|
glass={{
|
|
1294
1294
|
displacementScale: 60,
|
|
1295
1295
|
blurAmount: 1.8,
|
|
1296
|
-
|
|
1296
|
+
borderRadius: 16,
|
|
1297
1297
|
mode: 'shader',
|
|
1298
1298
|
}}
|
|
1299
1299
|
>
|
|
@@ -308,7 +308,7 @@ export const SideMenu = forwardRef<HTMLDivElement, SideMenuProps>(
|
|
|
308
308
|
const defaultGlassProps = {
|
|
309
309
|
displacementScale: 70,
|
|
310
310
|
blurAmount: 2,
|
|
311
|
-
|
|
311
|
+
borderRadius: 12,
|
|
312
312
|
mode: 'shader' as const,
|
|
313
313
|
};
|
|
314
314
|
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
@@ -46,7 +46,7 @@ export const Progress = memo(
|
|
|
46
46
|
const defaultGlassProps = {
|
|
47
47
|
displacementScale: 30,
|
|
48
48
|
blurAmount: 0.5,
|
|
49
|
-
|
|
49
|
+
borderRadius: 8,
|
|
50
50
|
mode: 'shader' as const,
|
|
51
51
|
};
|
|
52
52
|
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import Rating from './Rating';
|
|
5
|
+
|
|
6
|
+
// Mock AtomixGlass since it's used in Rating
|
|
7
|
+
vi.mock('../AtomixGlass/AtomixGlass', () => ({
|
|
8
|
+
AtomixGlass: ({ children }: any) => <div data-testid="glass-wrapper">{children}</div>,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe('Rating', () => {
|
|
12
|
+
it('renders correctly', () => {
|
|
13
|
+
render(<Rating value={3} />);
|
|
14
|
+
const stars = screen.getAllByRole('button');
|
|
15
|
+
expect(stars).toHaveLength(5);
|
|
16
|
+
// 3 full stars, 2 empty
|
|
17
|
+
expect(stars[0]).toHaveAttribute('aria-checked', 'true');
|
|
18
|
+
expect(stars[1]).toHaveAttribute('aria-checked', 'true');
|
|
19
|
+
expect(stars[2]).toHaveAttribute('aria-checked', 'true');
|
|
20
|
+
expect(stars[3]).toHaveAttribute('aria-checked', 'false');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('calculates half star on hover when allowHalf is true', () => {
|
|
24
|
+
render(<Rating allowHalf />);
|
|
25
|
+
const stars = screen.getAllByRole('button');
|
|
26
|
+
const firstStar = stars[0];
|
|
27
|
+
|
|
28
|
+
// Mock getBoundingClientRect
|
|
29
|
+
vi.spyOn(firstStar, 'getBoundingClientRect').mockReturnValue({
|
|
30
|
+
left: 100,
|
|
31
|
+
width: 20,
|
|
32
|
+
top: 0,
|
|
33
|
+
bottom: 20,
|
|
34
|
+
right: 120,
|
|
35
|
+
height: 20,
|
|
36
|
+
x: 100,
|
|
37
|
+
y: 0,
|
|
38
|
+
toJSON: () => {},
|
|
39
|
+
} as DOMRect);
|
|
40
|
+
|
|
41
|
+
// Hover on left half (105 is < 100 + 10 = 110)
|
|
42
|
+
fireEvent.mouseEnter(firstStar, { clientX: 105 });
|
|
43
|
+
fireEvent.mouseMove(firstStar, { clientX: 105 });
|
|
44
|
+
|
|
45
|
+
// Check if the star has the half class
|
|
46
|
+
// RATING.CLASSES.HALF is 'c-rating__star--half'
|
|
47
|
+
expect(firstStar).toHaveClass('c-rating__star--half');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('calculates full star on hover right half when allowHalf is true', () => {
|
|
51
|
+
render(<Rating allowHalf />);
|
|
52
|
+
const stars = screen.getAllByRole('button');
|
|
53
|
+
const firstStar = stars[0];
|
|
54
|
+
|
|
55
|
+
vi.spyOn(firstStar, 'getBoundingClientRect').mockReturnValue({
|
|
56
|
+
left: 100,
|
|
57
|
+
width: 20,
|
|
58
|
+
top: 0,
|
|
59
|
+
bottom: 20,
|
|
60
|
+
right: 120,
|
|
61
|
+
height: 20,
|
|
62
|
+
x: 100,
|
|
63
|
+
y: 0,
|
|
64
|
+
toJSON: () => {},
|
|
65
|
+
} as DOMRect);
|
|
66
|
+
|
|
67
|
+
// Hover on right half (115 is > 100 + 10 = 110)
|
|
68
|
+
fireEvent.mouseEnter(firstStar, { clientX: 115 });
|
|
69
|
+
fireEvent.mouseMove(firstStar, { clientX: 115 });
|
|
70
|
+
|
|
71
|
+
expect(firstStar).toHaveClass('c-rating__star--full');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -5,6 +5,27 @@ import type { RatingProps } from '../../lib/types/components';
|
|
|
5
5
|
import useForkRef from '../../lib/utils/useForkRef';
|
|
6
6
|
import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
|
|
7
7
|
|
|
8
|
+
// Helper function to calculate star value based on mouse position
|
|
9
|
+
const calculateStarValue = (
|
|
10
|
+
e: React.MouseEvent,
|
|
11
|
+
starValue: number,
|
|
12
|
+
allowHalf: boolean
|
|
13
|
+
): number => {
|
|
14
|
+
if (!allowHalf) {
|
|
15
|
+
return starValue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Get the star element's bounding rectangle
|
|
19
|
+
const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
20
|
+
// Calculate the x position within the star
|
|
21
|
+
const starCenterX = starRect.left + starRect.width / 2;
|
|
22
|
+
// If mouse is on the left half of the star, use half value
|
|
23
|
+
const isHalfStar = e.clientX < starCenterX;
|
|
24
|
+
const adjustedValue = isHalfStar ? starValue - 0.5 : starValue;
|
|
25
|
+
|
|
26
|
+
return Math.max(0.5, adjustedValue); // Ensure minimum of 0.5
|
|
27
|
+
};
|
|
28
|
+
|
|
8
29
|
/**
|
|
9
30
|
* Rating component for displaying and collecting star ratings
|
|
10
31
|
*
|
|
@@ -61,19 +82,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
|
|
|
61
82
|
const handleMouseEnter = useCallback(
|
|
62
83
|
(e: React.MouseEvent, starValue: number) => {
|
|
63
84
|
if (readOnly) return;
|
|
64
|
-
|
|
65
|
-
if (allowHalf) {
|
|
66
|
-
// Get the star element's bounding rectangle
|
|
67
|
-
const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
68
|
-
// Calculate the x position within the star
|
|
69
|
-
const starCenterX = starRect.left + starRect.width / 2;
|
|
70
|
-
// If mouse is on the left half of the star, use half value
|
|
71
|
-
const isHalfStar = e.clientX < starCenterX;
|
|
72
|
-
const adjustedValue = isHalfStar ? starValue - 0.5 : starValue;
|
|
73
|
-
setHoverValue(Math.max(0.5, adjustedValue)); // Ensure minimum of 0.5
|
|
74
|
-
} else {
|
|
75
|
-
setHoverValue(starValue);
|
|
76
|
-
}
|
|
85
|
+
setHoverValue(calculateStarValue(e, starValue, !!allowHalf));
|
|
77
86
|
},
|
|
78
87
|
[readOnly, allowHalf, setHoverValue]
|
|
79
88
|
);
|
|
@@ -82,15 +91,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
|
|
|
82
91
|
const handleMouseMove = useCallback(
|
|
83
92
|
(e: React.MouseEvent, starValue: number) => {
|
|
84
93
|
if (readOnly || !allowHalf) return;
|
|
85
|
-
|
|
86
|
-
// Get the star element's bounding rectangle
|
|
87
|
-
const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
88
|
-
// Calculate the x position within the star
|
|
89
|
-
const starCenterX = starRect.left + starRect.width / 2;
|
|
90
|
-
// If mouse is on the left half of the star, use half value
|
|
91
|
-
const isHalfStar = e.clientX < starCenterX;
|
|
92
|
-
const adjustedValue = isHalfStar ? starValue - 0.5 : starValue;
|
|
93
|
-
setHoverValue(Math.max(0.5, adjustedValue)); // Ensure minimum of 0.5
|
|
94
|
+
setHoverValue(calculateStarValue(e, starValue, !!allowHalf));
|
|
94
95
|
},
|
|
95
96
|
[readOnly, allowHalf, setHoverValue]
|
|
96
97
|
);
|
|
@@ -105,20 +106,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
|
|
|
105
106
|
const handleClick = useCallback(
|
|
106
107
|
(e: React.MouseEvent, starValue: number) => {
|
|
107
108
|
if (readOnly) return;
|
|
108
|
-
|
|
109
|
-
let newValue = starValue;
|
|
110
|
-
|
|
111
|
-
if (allowHalf) {
|
|
112
|
-
// Get the star element's bounding rectangle
|
|
113
|
-
const starRect = (e.currentTarget as HTMLElement).getBoundingClientRect();
|
|
114
|
-
// Calculate the x position within the star
|
|
115
|
-
const starCenterX = starRect.left + starRect.width / 2;
|
|
116
|
-
// If click is on the left half of the star, use half value
|
|
117
|
-
const isHalfStar = e.clientX < starCenterX;
|
|
118
|
-
newValue = isHalfStar ? starValue - 0.5 : starValue;
|
|
119
|
-
newValue = Math.max(0.5, newValue); // Ensure minimum of 0.5
|
|
120
|
-
}
|
|
121
|
-
|
|
109
|
+
const newValue = calculateStarValue(e, starValue, !!allowHalf);
|
|
122
110
|
onChange?.(newValue);
|
|
123
111
|
},
|
|
124
112
|
[readOnly, onChange, allowHalf]
|
|
@@ -291,7 +279,7 @@ export const Rating = forwardRef<HTMLDivElement, RatingProps>(
|
|
|
291
279
|
blurAmount: 1,
|
|
292
280
|
saturation: 160,
|
|
293
281
|
aberrationIntensity: 0.5,
|
|
294
|
-
|
|
282
|
+
borderRadius: 8,
|
|
295
283
|
mode: 'shader' as const,
|
|
296
284
|
};
|
|
297
285
|
|
|
@@ -38,7 +38,7 @@ export const Spinner: React.FC<SpinnerProps> = memo(
|
|
|
38
38
|
const defaultGlassProps = {
|
|
39
39
|
displacementScale: 20,
|
|
40
40
|
blurAmount: 1,
|
|
41
|
-
|
|
41
|
+
borderRadius: 999,
|
|
42
42
|
mode: 'shader' as const,
|
|
43
43
|
};
|
|
44
44
|
const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
|