@shohojdhara/atomix 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/atomix.css +0 -14
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.d.ts +12 -19
  6. package/dist/charts.js +555 -359
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.d.ts +98 -28
  9. package/dist/core.js +1082 -733
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.d.ts +26 -21
  12. package/dist/forms.js +937 -350
  13. package/dist/forms.js.map +1 -1
  14. package/dist/heavy.d.ts +14 -21
  15. package/dist/heavy.js +409 -256
  16. package/dist/heavy.js.map +1 -1
  17. package/dist/index.d.ts +518 -284
  18. package/dist/index.esm.js +1993 -1237
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +1994 -1237
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.min.js +1 -1
  23. package/dist/index.min.js.map +1 -1
  24. package/package.json +2 -2
  25. package/scripts/atomix-cli.js +43 -1
  26. package/scripts/cli/__tests__/utils.test.js +6 -2
  27. package/scripts/cli/migration-tools.js +2 -2
  28. package/scripts/cli/theme-bridge.js +7 -9
  29. package/scripts/cli/utils.js +2 -1
  30. package/src/components/Accordion/Accordion.stories.tsx +40 -0
  31. package/src/components/Accordion/Accordion.tsx +174 -56
  32. package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
  33. package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
  34. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
  35. package/src/components/AtomixGlass/README.md +5 -5
  36. package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
  37. package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
  38. package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
  39. package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
  40. package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
  41. package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
  42. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
  43. package/src/components/Badge/Badge.stories.tsx +1 -1
  44. package/src/components/Badge/Badge.tsx +1 -1
  45. package/src/components/Breadcrumb/Breadcrumb.tsx +185 -65
  46. package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
  47. package/src/components/Breadcrumb/index.ts +2 -2
  48. package/src/components/Button/Button.stories.tsx +1 -1
  49. package/src/components/Button/README.md +2 -2
  50. package/src/components/Callout/Callout.stories.tsx +166 -1011
  51. package/src/components/Callout/Callout.test.tsx +3 -3
  52. package/src/components/Callout/Callout.tsx +196 -84
  53. package/src/components/Callout/CalloutCompound.test.tsx +72 -0
  54. package/src/components/Callout/README.md +2 -2
  55. package/src/components/Chart/Chart.stories.tsx +1 -1
  56. package/src/components/Chart/Chart.tsx +5 -5
  57. package/src/components/Chart/TreemapChart.tsx +37 -29
  58. package/src/components/DatePicker/readme.md +3 -3
  59. package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
  60. package/src/components/Dropdown/Dropdown.tsx +133 -20
  61. package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
  62. package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
  63. package/src/components/EdgePanel/EdgePanel.tsx +164 -112
  64. package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
  65. package/src/components/Form/Checkbox.stories.tsx +1 -1
  66. package/src/components/Form/Checkbox.tsx +1 -1
  67. package/src/components/Form/Input.stories.tsx +1 -1
  68. package/src/components/Form/Input.tsx +1 -1
  69. package/src/components/Form/Radio.stories.tsx +1 -1
  70. package/src/components/Form/Radio.tsx +1 -1
  71. package/src/components/Form/Select.stories.tsx +24 -1
  72. package/src/components/Form/Select.test.tsx +99 -0
  73. package/src/components/Form/Select.tsx +145 -94
  74. package/src/components/Form/SelectOption.tsx +88 -0
  75. package/src/components/Form/Textarea.stories.tsx +1 -1
  76. package/src/components/Form/Textarea.tsx +1 -1
  77. package/src/components/Hero/Hero.stories.tsx +39 -2
  78. package/src/components/Hero/Hero.test.tsx +142 -0
  79. package/src/components/Hero/Hero.tsx +143 -4
  80. package/src/components/List/List.test.tsx +62 -0
  81. package/src/components/List/List.tsx +16 -5
  82. package/src/components/List/ListItem.tsx +20 -0
  83. package/src/components/Messages/Messages.stories.tsx +1 -1
  84. package/src/components/Messages/Messages.tsx +2 -2
  85. package/src/components/Modal/Modal.stories.tsx +66 -2
  86. package/src/components/Modal/Modal.tsx +115 -35
  87. package/src/components/Modal/ModalCompound.test.tsx +94 -0
  88. package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
  89. package/src/components/Navigation/Nav/Nav.tsx +1 -1
  90. package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
  91. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  92. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
  93. package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
  94. package/src/components/Pagination/Pagination.stories.tsx +1 -1
  95. package/src/components/Pagination/Pagination.tsx +1 -1
  96. package/src/components/Popover/Popover.stories.tsx +1 -1
  97. package/src/components/Popover/Popover.tsx +1 -1
  98. package/src/components/Progress/Progress.tsx +1 -1
  99. package/src/components/Rating/Rating.stories.tsx +1 -1
  100. package/src/components/Rating/Rating.test.tsx +73 -0
  101. package/src/components/Rating/Rating.tsx +25 -37
  102. package/src/components/Spinner/Spinner.tsx +1 -1
  103. package/src/components/Steps/Steps.stories.tsx +1 -1
  104. package/src/components/Steps/Steps.tsx +125 -22
  105. package/src/components/Steps/StepsCompound.test.tsx +81 -0
  106. package/src/components/Tabs/Tabs.stories.tsx +1 -1
  107. package/src/components/Tabs/Tabs.tsx +198 -45
  108. package/src/components/Tabs/TabsCompound.test.tsx +64 -0
  109. package/src/components/Todo/Todo.tsx +0 -1
  110. package/src/components/Toggle/Toggle.stories.tsx +1 -1
  111. package/src/components/Toggle/Toggle.tsx +1 -1
  112. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  113. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
  114. package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
  115. package/src/lib/composables/__tests__/useChart.test.ts +50 -0
  116. package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
  117. package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
  118. package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
  119. package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
  120. package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
  121. package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
  122. package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
  123. package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
  124. package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
  125. package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
  126. package/src/lib/composables/glass-styles.ts +302 -0
  127. package/src/lib/composables/index.ts +0 -8
  128. package/src/lib/composables/useAtomixGlass.ts +331 -537
  129. package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
  130. package/src/lib/composables/useBarChart.ts +1 -1
  131. package/src/lib/composables/useBreadcrumb.ts +6 -6
  132. package/src/lib/composables/useChart.ts +104 -21
  133. package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
  134. package/src/lib/composables/useSlider.ts +66 -34
  135. package/src/lib/theme/devtools/CLI.ts +2 -10
  136. package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
  137. package/src/lib/types/components.ts +21 -23
  138. package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
  139. package/src/lib/utils/__tests__/dom.test.ts +100 -0
  140. package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
  141. package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
  142. package/src/lib/utils/themeNaming.ts +1 -1
  143. package/src/styles/06-components/_components.accordion.scss +0 -2
  144. package/src/styles/06-components/_components.chart.scss +0 -1
  145. package/src/styles/06-components/_components.dropdown.scss +0 -1
  146. package/src/styles/06-components/_components.edge-panel.scss +0 -2
  147. package/src/styles/06-components/_components.photoviewer.scss +0 -1
  148. package/src/styles/06-components/_components.river.scss +0 -1
  149. package/src/styles/06-components/_components.slider.scss +0 -3
  150. package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
@@ -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
- export const Modal: React.FC<ModalProps> = memo(
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
- {(title || closeButton) && (
150
- <div className="c-modal__header">
151
- <div className="c-modal__header-content">
152
- {title && <h3 className="c-modal__title">{title}</h3>}
153
- {subtitle && <p className="c-modal__sub">{subtitle}</p>}
154
- </div>
155
- {closeButton && (
156
- <button
157
- type="button"
158
- className="c-modal__close c-btn js-modal-close"
159
- onClick={close}
160
- aria-label="Close modal"
161
- >
162
- <svg
163
- width="20"
164
- height="20"
165
- viewBox="0 0 20 20"
166
- fill="none"
167
- xmlns="http://www.w3.org/2000/svg"
168
- >
169
- <path
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
- </div>
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
- Modal.displayName = 'Modal';
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
- cornerRadius: 20,
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
- cornerRadius: 16,
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
- cornerRadius: 8,
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
- cornerRadius: 0,
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 (cornerRadius: 0) create a modern, edge-to-edge aesthetic.',
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
- cornerRadius: 0,
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
- cornerRadius: 0,
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
- cornerRadius: 12,
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
- cornerRadius: 16,
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
- cornerRadius: 12,
311
+ borderRadius: 12,
312
312
  mode: 'shader' as const,
313
313
  };
314
314
  const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
@@ -285,7 +285,7 @@ export const GlassCustom: Story = {
285
285
  blurAmount: 2,
286
286
  saturation: 200,
287
287
  aberrationIntensity: 0.8,
288
- cornerRadius: 12,
288
+ borderRadius: 12,
289
289
  },
290
290
  },
291
291
  render: (args: any) => {
@@ -244,7 +244,7 @@ export const Pagination: React.FC<PaginationProps> = memo(
244
244
  blurAmount: 1,
245
245
  saturation: 160,
246
246
  aberrationIntensity: 0.5,
247
- cornerRadius: 8,
247
+ borderRadius: 8,
248
248
  mode: 'shader' as const,
249
249
  };
250
250
 
@@ -423,7 +423,7 @@ export const GlassPopoverCustom: Story = {
423
423
  blurAmount: 2,
424
424
  saturation: 200,
425
425
  aberrationIntensity: 1,
426
- cornerRadius: 16,
426
+ borderRadius: 16,
427
427
  mode: 'polar',
428
428
  } as any,
429
429
  },
@@ -86,7 +86,7 @@ export const Popover: React.FC<PopoverProps> = ({
86
86
  blurAmount: 1,
87
87
  saturation: 160,
88
88
  aberrationIntensity: 0.5,
89
- cornerRadius: 8,
89
+ borderRadius: 8,
90
90
  mode: 'shader' as const,
91
91
  };
92
92
 
@@ -46,7 +46,7 @@ export const Progress = memo(
46
46
  const defaultGlassProps = {
47
47
  displacementScale: 30,
48
48
  blurAmount: 0.5,
49
- cornerRadius: 8,
49
+ borderRadius: 8,
50
50
  mode: 'shader' as const,
51
51
  };
52
52
  const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
@@ -212,7 +212,7 @@ export const GlassCustom: Story = {
212
212
  blurAmount: 2,
213
213
  saturation: 200,
214
214
  aberrationIntensity: 0.8,
215
- cornerRadius: 12,
215
+ borderRadius: 12,
216
216
  },
217
217
  },
218
218
  render: (args: any) => (
@@ -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
- cornerRadius: 8,
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
- cornerRadius: 999,
41
+ borderRadius: 999,
42
42
  mode: 'shader' as const,
43
43
  };
44
44
  const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
@@ -404,7 +404,7 @@ export const GlassCustom: Story = {
404
404
  blurAmount: 2,
405
405
  saturation: 200,
406
406
  aberrationIntensity: 0.8,
407
- cornerRadius: 12,
407
+ borderRadius: 12,
408
408
  },
409
409
  },
410
410
  render: args => (