@shohojdhara/atomix 0.4.1 → 0.4.3
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 +9351 -9259
- 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 -358
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +21 -24
- package/dist/core.js +435 -265
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +11 -18
- package/dist/forms.js +411 -257
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -254
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +38 -41
- package/dist/index.esm.js +731 -487
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +733 -492
- 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 +1 -1
- package/scripts/atomix-cli.js +34 -1
- 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 +90 -77
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/Button.tsx +2 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +2 -2
- package/src/components/Callout/README.md +2 -2
- package/src/components/Card/Card.tsx +31 -11
- 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 +276 -273
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/Footer/FooterLink.tsx +2 -2
- 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 +1 -1
- package/src/components/Form/Select.tsx +1 -1
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +2 -2
- package/src/components/Hero/Hero.tsx +2 -2
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +1 -1
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- package/src/components/Navigation/Nav/NavItem.tsx +6 -3
- 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 +2 -2
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- 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 -4
- package/src/lib/composables/useAtomixGlass.ts +331 -522
- 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 +1 -1
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +18 -21
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
- package/src/styles/02-tools/_tools.utility-api.scss +6 -6
- 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
- package/src/styles/99-utilities/_utilities.text.scss +1 -0
|
@@ -45,6 +45,16 @@ export function useSlider(options: UseSliderOptions): UseSliderReturn {
|
|
|
45
45
|
const autoplayRef = useRef<NodeJS.Timeout | null>(null);
|
|
46
46
|
const [autoplayRunning, setAutoplayRunning] = useState(false);
|
|
47
47
|
|
|
48
|
+
// Create a ref to store the latest state for the autoplay interval
|
|
49
|
+
const sliderStateRef = useRef({
|
|
50
|
+
isTransitioning: false,
|
|
51
|
+
loop,
|
|
52
|
+
slides,
|
|
53
|
+
slidesToShow,
|
|
54
|
+
speed,
|
|
55
|
+
onSlideChange,
|
|
56
|
+
});
|
|
57
|
+
|
|
48
58
|
const [realIndex, setRealIndex] = useState(initialSlide);
|
|
49
59
|
const [internalIndex, setInternalIndex] = useState(0);
|
|
50
60
|
const [isTransitioning, setIsTransitioning] = useState(false);
|
|
@@ -76,6 +86,18 @@ export function useSlider(options: UseSliderOptions): UseSliderReturn {
|
|
|
76
86
|
return -(internalIndex * slideWidth) + dragOffset;
|
|
77
87
|
}, [slideWidth, internalIndex, dragOffset]);
|
|
78
88
|
|
|
89
|
+
// Update the ref whenever the relevant state/props change
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
sliderStateRef.current = {
|
|
92
|
+
isTransitioning,
|
|
93
|
+
loop,
|
|
94
|
+
slides,
|
|
95
|
+
slidesToShow,
|
|
96
|
+
speed,
|
|
97
|
+
onSlideChange,
|
|
98
|
+
};
|
|
99
|
+
}, [isTransitioning, loop, slides, slidesToShow, speed, onSlideChange]);
|
|
100
|
+
|
|
79
101
|
// Autoplay effect
|
|
80
102
|
useEffect(() => {
|
|
81
103
|
if (!autoplay) {
|
|
@@ -102,9 +124,19 @@ export function useSlider(options: UseSliderOptions): UseSliderReturn {
|
|
|
102
124
|
|
|
103
125
|
// Create new interval
|
|
104
126
|
autoplayRef.current = setInterval(() => {
|
|
127
|
+
// Use ref to get the latest state without resetting the interval
|
|
128
|
+
const {
|
|
129
|
+
isTransitioning: currentIsTransitioning,
|
|
130
|
+
loop: currentLoop,
|
|
131
|
+
slides: currentSlides,
|
|
132
|
+
slidesToShow: currentSlidesToShow,
|
|
133
|
+
speed: currentSpeed,
|
|
134
|
+
onSlideChange: currentOnSlideChange,
|
|
135
|
+
} = sliderStateRef.current;
|
|
136
|
+
|
|
105
137
|
// We need to use a functional update to get the latest values
|
|
106
138
|
setRealIndex(prevRealIndex => {
|
|
107
|
-
if (
|
|
139
|
+
if (currentIsTransitioning) return prevRealIndex;
|
|
108
140
|
|
|
109
141
|
// Stop autoplay on interaction if disableOnInteraction is true
|
|
110
142
|
if (disableOnInteraction && autoplayRef.current) {
|
|
@@ -114,49 +146,49 @@ export function useSlider(options: UseSliderOptions): UseSliderReturn {
|
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
let nextIndex;
|
|
117
|
-
if (
|
|
118
|
-
nextIndex = (prevRealIndex + 1) %
|
|
149
|
+
if (currentLoop) {
|
|
150
|
+
nextIndex = (prevRealIndex + 1) % currentSlides.length;
|
|
119
151
|
} else {
|
|
120
|
-
nextIndex = Math.min(prevRealIndex + 1,
|
|
152
|
+
nextIndex = Math.min(prevRealIndex + 1, currentSlides.length - currentSlidesToShow);
|
|
121
153
|
}
|
|
122
154
|
|
|
123
155
|
// Trigger the slide change
|
|
124
156
|
if (reverseDirection) {
|
|
125
157
|
// For reverse direction, we would go to previous slide
|
|
126
|
-
const prevIndex =
|
|
158
|
+
const prevIndex = currentLoop
|
|
127
159
|
? prevRealIndex === 0
|
|
128
|
-
?
|
|
160
|
+
? currentSlides.length - 1
|
|
129
161
|
: prevRealIndex - 1
|
|
130
162
|
: Math.max(prevRealIndex - 1, 0);
|
|
131
|
-
setInternalIndex(
|
|
163
|
+
setInternalIndex(currentLoop ? currentSlides.length + prevIndex : prevIndex);
|
|
132
164
|
setIsTransitioning(true);
|
|
133
165
|
setDragOffset(0);
|
|
134
166
|
|
|
135
167
|
setTimeout(() => {
|
|
136
168
|
setIsTransitioning(false);
|
|
137
|
-
|
|
138
|
-
},
|
|
169
|
+
currentOnSlideChange?.(prevIndex);
|
|
170
|
+
}, currentSpeed);
|
|
139
171
|
|
|
140
172
|
return prevIndex;
|
|
141
173
|
} else {
|
|
142
174
|
// Normal direction
|
|
143
|
-
setInternalIndex(
|
|
175
|
+
setInternalIndex(currentLoop ? currentSlides.length + nextIndex : nextIndex);
|
|
144
176
|
setIsTransitioning(true);
|
|
145
177
|
setDragOffset(0);
|
|
146
178
|
|
|
147
179
|
setTimeout(() => {
|
|
148
180
|
setIsTransitioning(false);
|
|
149
|
-
|
|
181
|
+
currentOnSlideChange?.(nextIndex);
|
|
150
182
|
|
|
151
183
|
// Reposition after transition ends for looped sliders
|
|
152
|
-
if (
|
|
184
|
+
if (currentLoop && nextIndex >= currentSlides.length * 2) {
|
|
153
185
|
repositioningRef.current = true;
|
|
154
|
-
setInternalIndex(
|
|
186
|
+
setInternalIndex(currentSlides.length + nextIndex);
|
|
155
187
|
setTimeout(() => {
|
|
156
188
|
repositioningRef.current = false;
|
|
157
189
|
}, 0);
|
|
158
190
|
}
|
|
159
|
-
},
|
|
191
|
+
}, currentSpeed);
|
|
160
192
|
|
|
161
193
|
return nextIndex;
|
|
162
194
|
}
|
|
@@ -182,35 +214,44 @@ export function useSlider(options: UseSliderOptions): UseSliderReturn {
|
|
|
182
214
|
}
|
|
183
215
|
|
|
184
216
|
autoplayRef.current = setInterval(() => {
|
|
217
|
+
const {
|
|
218
|
+
isTransitioning: currentIsTransitioning,
|
|
219
|
+
loop: currentLoop,
|
|
220
|
+
slides: currentSlides,
|
|
221
|
+
slidesToShow: currentSlidesToShow,
|
|
222
|
+
speed: currentSpeed,
|
|
223
|
+
onSlideChange: currentOnSlideChange,
|
|
224
|
+
} = sliderStateRef.current;
|
|
225
|
+
|
|
185
226
|
setRealIndex(prevRealIndex => {
|
|
186
|
-
if (
|
|
227
|
+
if (currentIsTransitioning) return prevRealIndex;
|
|
187
228
|
|
|
188
229
|
let nextIndex;
|
|
189
|
-
if (
|
|
190
|
-
nextIndex = (prevRealIndex + 1) %
|
|
230
|
+
if (currentLoop) {
|
|
231
|
+
nextIndex = (prevRealIndex + 1) % currentSlides.length;
|
|
191
232
|
} else {
|
|
192
|
-
nextIndex = Math.min(prevRealIndex + 1,
|
|
233
|
+
nextIndex = Math.min(prevRealIndex + 1, currentSlides.length - currentSlidesToShow);
|
|
193
234
|
}
|
|
194
235
|
|
|
195
|
-
setInternalIndex(
|
|
236
|
+
setInternalIndex(currentLoop ? currentSlides.length + nextIndex : nextIndex);
|
|
196
237
|
setIsTransitioning(true);
|
|
197
238
|
setDragOffset(0);
|
|
198
239
|
|
|
199
240
|
setTimeout(() => {
|
|
200
241
|
setIsTransitioning(false);
|
|
201
|
-
|
|
242
|
+
currentOnSlideChange?.(nextIndex);
|
|
202
243
|
|
|
203
|
-
if (
|
|
244
|
+
if (currentLoop) {
|
|
204
245
|
// Reposition after transition ends
|
|
205
|
-
if (nextIndex >=
|
|
246
|
+
if (nextIndex >= currentSlides.length * 2) {
|
|
206
247
|
repositioningRef.current = true;
|
|
207
|
-
setInternalIndex(
|
|
248
|
+
setInternalIndex(currentSlides.length + nextIndex);
|
|
208
249
|
setTimeout(() => {
|
|
209
250
|
repositioningRef.current = false;
|
|
210
251
|
}, 0);
|
|
211
252
|
}
|
|
212
253
|
}
|
|
213
|
-
},
|
|
254
|
+
}, currentSpeed);
|
|
214
255
|
|
|
215
256
|
return nextIndex;
|
|
216
257
|
});
|
|
@@ -237,16 +278,7 @@ export function useSlider(options: UseSliderOptions): UseSliderReturn {
|
|
|
237
278
|
}
|
|
238
279
|
setAutoplayRunning(false);
|
|
239
280
|
};
|
|
240
|
-
}, [
|
|
241
|
-
autoplay,
|
|
242
|
-
slides.length,
|
|
243
|
-
loop,
|
|
244
|
-
slidesToShow,
|
|
245
|
-
isTransitioning,
|
|
246
|
-
speed,
|
|
247
|
-
onSlideChange,
|
|
248
|
-
repositioningRef,
|
|
249
|
-
]);
|
|
281
|
+
}, [autoplay, repositioningRef]);
|
|
250
282
|
|
|
251
283
|
// Initialize
|
|
252
284
|
useEffect(() => {
|
|
@@ -11,7 +11,7 @@ import { Command } from 'commander';
|
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import * as fs from 'fs';
|
|
13
13
|
import * as path from 'path';
|
|
14
|
-
import { ThemeValidator } from './ThemeValidator';
|
|
14
|
+
import { ThemeValidator } from './ThemeValidator.js';
|
|
15
15
|
import boxen from 'boxen';
|
|
16
16
|
|
|
17
17
|
const program = new Command();
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
hexToRgb,
|
|
4
|
+
rgbToHex,
|
|
5
|
+
getLuminance,
|
|
6
|
+
getContrastRatio,
|
|
7
|
+
getContrastText,
|
|
8
|
+
lighten,
|
|
9
|
+
darken,
|
|
10
|
+
alpha,
|
|
11
|
+
emphasize,
|
|
12
|
+
createSpacing,
|
|
13
|
+
} from '../themeUtils';
|
|
14
|
+
|
|
15
|
+
describe('Theme Utils', () => {
|
|
16
|
+
describe('hexToRgb', () => {
|
|
17
|
+
it('should convert valid 6-digit hex codes', () => {
|
|
18
|
+
expect(hexToRgb('#ffffff')).toEqual({ r: 255, g: 255, b: 255 });
|
|
19
|
+
expect(hexToRgb('#000000')).toEqual({ r: 0, g: 0, b: 0 });
|
|
20
|
+
expect(hexToRgb('#ff0000')).toEqual({ r: 255, g: 0, b: 0 });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should handle hex codes without hash', () => {
|
|
24
|
+
expect(hexToRgb('ffffff')).toEqual({ r: 255, g: 255, b: 255 });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return null for invalid hex codes', () => {
|
|
28
|
+
expect(hexToRgb('invalid')).toBeNull();
|
|
29
|
+
expect(hexToRgb('#xyz')).toBeNull();
|
|
30
|
+
// Current implementation does NOT support 3-digit hex
|
|
31
|
+
expect(hexToRgb('#fff')).toBeNull();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('rgbToHex', () => {
|
|
36
|
+
it('should convert RGB values to hex string', () => {
|
|
37
|
+
expect(rgbToHex(255, 255, 255)).toBe('#ffffff');
|
|
38
|
+
expect(rgbToHex(0, 0, 0)).toBe('#000000');
|
|
39
|
+
expect(rgbToHex(255, 0, 0)).toBe('#ff0000');
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should clamp values to 0-255 range', () => {
|
|
43
|
+
expect(rgbToHex(300, -50, 256)).toBe('#ff00ff');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should handle null/undefined values as 0', () => {
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
expect(rgbToHex(null, undefined, 255)).toBe('#0000ff');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('getLuminance', () => {
|
|
53
|
+
it('should calculate luminance for standard colors', () => {
|
|
54
|
+
expect(getLuminance('#000000')).toBe(0);
|
|
55
|
+
expect(getLuminance('#ffffff')).toBe(1);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return 0 for invalid colors', () => {
|
|
59
|
+
expect(getLuminance('invalid')).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('getContrastRatio', () => {
|
|
64
|
+
it('should calculate contrast ratio between black and white', () => {
|
|
65
|
+
const ratio = getContrastRatio('#000000', '#ffffff');
|
|
66
|
+
expect(ratio).toBeCloseTo(21, 1);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should be commutative', () => {
|
|
70
|
+
const r1 = getContrastRatio('#ffffff', '#000000');
|
|
71
|
+
const r2 = getContrastRatio('#000000', '#ffffff');
|
|
72
|
+
expect(r1).toBe(r2);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should return 1 for same colors', () => {
|
|
76
|
+
expect(getContrastRatio('#ffffff', '#ffffff')).toBe(1);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('getContrastText', () => {
|
|
81
|
+
it('should return white for dark backgrounds', () => {
|
|
82
|
+
expect(getContrastText('#000000')).toBe('#FFFFFF');
|
|
83
|
+
expect(getContrastText('#333333')).toBe('#FFFFFF');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should return black for light backgrounds', () => {
|
|
87
|
+
expect(getContrastText('#ffffff')).toBe('#000000');
|
|
88
|
+
expect(getContrastText('#eeeeee')).toBe('#000000');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should respect custom threshold', () => {
|
|
92
|
+
// Very strict threshold might force one or the other
|
|
93
|
+
// But standard usage is tested above
|
|
94
|
+
// Testing logic fallback:
|
|
95
|
+
// If contrast with white < threshold AND contrast with black < threshold
|
|
96
|
+
// it returns whichever has higher contrast.
|
|
97
|
+
|
|
98
|
+
// Let's test a middle grey.
|
|
99
|
+
// #808080.
|
|
100
|
+
// Contrast with white: ~3.95
|
|
101
|
+
// Contrast with black: ~5.32
|
|
102
|
+
// Default threshold is 3.
|
|
103
|
+
// 3.95 >= 3 -> returns white (checked first).
|
|
104
|
+
// Wait, let's check code order:
|
|
105
|
+
// if (contrastWithWhite >= threshold) return '#FFFFFF';
|
|
106
|
+
|
|
107
|
+
expect(getContrastText('#808080', 3)).toBe('#FFFFFF');
|
|
108
|
+
|
|
109
|
+
// If we raise threshold to 4.
|
|
110
|
+
// Contrast white (3.95) < 4.
|
|
111
|
+
// Contrast black (5.32) >= 4.
|
|
112
|
+
// Returns black.
|
|
113
|
+
expect(getContrastText('#808080', 4)).toBe('#000000');
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('lighten', () => {
|
|
118
|
+
it('should lighten a color', () => {
|
|
119
|
+
const result = lighten('#000000', 0.5); // Lighten black by 50%
|
|
120
|
+
// black is 0, 0, 0.
|
|
121
|
+
// lightenValue = val + (255 - val) * amount
|
|
122
|
+
// 0 + 255 * 0.5 = 127.5 -> 128 (0x80)
|
|
123
|
+
expect(result).toBe('#808080');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should return original color if invalid', () => {
|
|
127
|
+
expect(lighten('invalid')).toBe('invalid');
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('darken', () => {
|
|
132
|
+
it('should darken a color', () => {
|
|
133
|
+
const result = darken('#ffffff', 0.5); // Darken white by 50%
|
|
134
|
+
// white is 255.
|
|
135
|
+
// darkenValue = val * (1 - amount)
|
|
136
|
+
// 255 * 0.5 = 127.5 -> 128 (0x80)
|
|
137
|
+
expect(result).toBe('#808080');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should return original color if invalid', () => {
|
|
141
|
+
expect(darken('invalid')).toBe('invalid');
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('alpha', () => {
|
|
146
|
+
it('should add alpha to hex color', () => {
|
|
147
|
+
expect(alpha('#ffffff', 0.5)).toBe('rgba(255, 255, 255, 0.5)');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should clamp opacity', () => {
|
|
151
|
+
expect(alpha('#ffffff', 1.5)).toBe('rgba(255, 255, 255, 1)');
|
|
152
|
+
expect(alpha('#ffffff', -0.5)).toBe('rgba(255, 255, 255, 0)');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should return original color if invalid', () => {
|
|
156
|
+
expect(alpha('invalid', 0.5)).toBe('invalid');
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
describe('emphasize', () => {
|
|
161
|
+
it('should darken light colors', () => {
|
|
162
|
+
// #ffffff luminance is 1 (> 0.5) -> darken
|
|
163
|
+
const result = emphasize('#ffffff', 0.2);
|
|
164
|
+
// darken #ffffff by 0.2
|
|
165
|
+
// 255 * 0.8 = 204 (0xCC)
|
|
166
|
+
expect(result).toBe('#cccccc');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it('should lighten dark colors', () => {
|
|
170
|
+
// #000000 luminance is 0 (<= 0.5) -> lighten
|
|
171
|
+
const result = emphasize('#000000', 0.2);
|
|
172
|
+
// lighten #000000 by 0.2
|
|
173
|
+
// 0 + 255 * 0.2 = 51 (0x33)
|
|
174
|
+
expect(result).toBe('#333333');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('createSpacing', () => {
|
|
179
|
+
it('should use default spacing (4px)', () => {
|
|
180
|
+
const spacing = createSpacing();
|
|
181
|
+
expect(spacing(1)).toBe('4px');
|
|
182
|
+
expect(spacing(2)).toBe('8px');
|
|
183
|
+
expect(spacing(1, 2)).toBe('4px 8px');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('should use numeric spacing multiplier', () => {
|
|
187
|
+
const spacing = createSpacing(8);
|
|
188
|
+
expect(spacing(1)).toBe('8px');
|
|
189
|
+
expect(spacing(2)).toBe('16px');
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it('should use array spacing', () => {
|
|
193
|
+
const scale = [0, 4, 8, 16, 32];
|
|
194
|
+
const spacing = createSpacing(scale);
|
|
195
|
+
expect(spacing(1)).toBe('4px');
|
|
196
|
+
expect(spacing(3)).toBe('16px');
|
|
197
|
+
// fallback to value if index out of bounds?
|
|
198
|
+
// Code: spacingInput[value] || value
|
|
199
|
+
expect(spacing(10)).toBe('10px');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should use function spacing', () => {
|
|
203
|
+
const customFn = (...values: number[]) => values.map(v => `${v}rem`).join(' ');
|
|
204
|
+
const spacing = createSpacing(customFn);
|
|
205
|
+
expect(spacing(1, 2)).toBe('1rem 2rem');
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('should handle no arguments', () => {
|
|
209
|
+
const spacing = createSpacing();
|
|
210
|
+
expect(spacing()).toBe('0px');
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
});
|
|
@@ -215,20 +215,20 @@ export interface OverLightObjectConfig {
|
|
|
215
215
|
/**
|
|
216
216
|
* AtomixGlass component props interface
|
|
217
217
|
*/
|
|
218
|
-
export interface AtomixGlassProps {
|
|
218
|
+
export interface AtomixGlassProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
219
219
|
children?: React.ReactNode;
|
|
220
220
|
displacementScale?: number;
|
|
221
221
|
blurAmount?: number;
|
|
222
222
|
saturation?: number;
|
|
223
223
|
aberrationIntensity?: number;
|
|
224
224
|
elasticity?: number;
|
|
225
|
-
|
|
225
|
+
borderRadius?: number;
|
|
226
226
|
globalMousePosition?: MousePosition;
|
|
227
227
|
mouseOffset?: MousePosition;
|
|
228
228
|
mouseContainer?: React.RefObject<HTMLElement | null> | null;
|
|
229
|
-
className?: string;
|
|
230
229
|
padding?: string;
|
|
231
|
-
|
|
230
|
+
height?: string | number;
|
|
231
|
+
width?: string | number;
|
|
232
232
|
overLight?: OverLightConfig;
|
|
233
233
|
mode?: DisplacementMode;
|
|
234
234
|
onClick?: () => void;
|
|
@@ -245,33 +245,25 @@ export interface AtomixGlassProps {
|
|
|
245
245
|
| 'waves'
|
|
246
246
|
| 'noise';
|
|
247
247
|
|
|
248
|
-
/**
|
|
249
|
-
* Accessibility props
|
|
250
|
-
*/
|
|
251
|
-
'aria-label'?: string;
|
|
252
|
-
'aria-describedby'?: string;
|
|
253
|
-
role?: string;
|
|
254
|
-
tabIndex?: number;
|
|
255
|
-
|
|
256
248
|
/**
|
|
257
249
|
* Performance and accessibility options
|
|
258
250
|
*/
|
|
259
251
|
reducedMotion?: boolean;
|
|
260
252
|
highContrast?: boolean;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
253
|
+
withoutEffects?: boolean;
|
|
254
|
+
withLiquidBlur?: boolean;
|
|
255
|
+
withBorder?: boolean;
|
|
256
|
+
withOverLightLayers?: boolean;
|
|
265
257
|
|
|
266
258
|
/**
|
|
267
259
|
* Performance monitoring
|
|
268
260
|
*/
|
|
269
|
-
|
|
261
|
+
debugPerformance?: boolean;
|
|
270
262
|
|
|
271
263
|
/**
|
|
272
|
-
* Debug mode for
|
|
264
|
+
* Debug mode for borderRadius extraction
|
|
273
265
|
*/
|
|
274
|
-
|
|
266
|
+
debugBorderRadius?: boolean;
|
|
275
267
|
|
|
276
268
|
/**
|
|
277
269
|
* Debug mode for overLight detection and configuration
|
|
@@ -3791,7 +3783,7 @@ export interface VideoPlayerProps extends BaseComponentProps {
|
|
|
3791
3783
|
saturation?: number;
|
|
3792
3784
|
aberrationIntensity?: number;
|
|
3793
3785
|
elasticity?: number;
|
|
3794
|
-
|
|
3786
|
+
borderRadius?: number;
|
|
3795
3787
|
mode?: 'standard' | 'polar' | 'prominent' | 'shader';
|
|
3796
3788
|
overLight?: boolean;
|
|
3797
3789
|
};
|
|
@@ -4049,6 +4041,11 @@ export interface CardProps extends BaseComponentProps {
|
|
|
4049
4041
|
*/
|
|
4050
4042
|
className?: string;
|
|
4051
4043
|
|
|
4044
|
+
/**
|
|
4045
|
+
* Optional custom link component (e.g., Next.js Link, React Router Link)
|
|
4046
|
+
*/
|
|
4047
|
+
LinkComponent?: React.ElementType;
|
|
4048
|
+
|
|
4052
4049
|
/**
|
|
4053
4050
|
* Optional click handler
|
|
4054
4051
|
*/
|
|
@@ -6642,7 +6639,7 @@ export interface GlassContainerProps extends BaseComponentProps {
|
|
|
6642
6639
|
* Border radius of the glass container
|
|
6643
6640
|
* @default 999
|
|
6644
6641
|
*/
|
|
6645
|
-
|
|
6642
|
+
borderRadius?: number;
|
|
6646
6643
|
|
|
6647
6644
|
/**
|
|
6648
6645
|
* Padding inside the glass container
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { hasClass, addClass, removeClass, toggleClass } from '../dom';
|
|
3
|
+
|
|
4
|
+
describe('dom utils', () => {
|
|
5
|
+
let element: HTMLElement;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
element = document.createElement('div');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('hasClass', () => {
|
|
12
|
+
it('should return true if element has class', () => {
|
|
13
|
+
element.className = 'test-class';
|
|
14
|
+
expect(hasClass(element, 'test-class')).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should return false if element does not have class', () => {
|
|
18
|
+
element.className = 'other-class';
|
|
19
|
+
expect(hasClass(element, 'test-class')).toBe(false);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return false if element has no classes', () => {
|
|
23
|
+
expect(hasClass(element, 'test-class')).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('addClass', () => {
|
|
28
|
+
it('should add class if not present', () => {
|
|
29
|
+
addClass(element, 'new-class');
|
|
30
|
+
expect(element.classList.contains('new-class')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should not add duplicate class', () => {
|
|
34
|
+
element.className = 'existing-class';
|
|
35
|
+
addClass(element, 'existing-class');
|
|
36
|
+
expect(element.className).toBe('existing-class');
|
|
37
|
+
expect(element.classList.length).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should preserve existing classes', () => {
|
|
41
|
+
element.className = 'existing-class';
|
|
42
|
+
addClass(element, 'new-class');
|
|
43
|
+
expect(element.classList.contains('existing-class')).toBe(true);
|
|
44
|
+
expect(element.classList.contains('new-class')).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('removeClass', () => {
|
|
49
|
+
it('should remove existing class', () => {
|
|
50
|
+
element.className = 'test-class';
|
|
51
|
+
removeClass(element, 'test-class');
|
|
52
|
+
expect(element.classList.contains('test-class')).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should do nothing if class does not exist', () => {
|
|
56
|
+
element.className = 'other-class';
|
|
57
|
+
removeClass(element, 'test-class');
|
|
58
|
+
expect(element.className).toBe('other-class');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should preserve other classes', () => {
|
|
62
|
+
element.className = 'keep-me remove-me';
|
|
63
|
+
removeClass(element, 'remove-me');
|
|
64
|
+
expect(element.classList.contains('keep-me')).toBe(true);
|
|
65
|
+
expect(element.classList.contains('remove-me')).toBe(false);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('toggleClass', () => {
|
|
70
|
+
it('should add class if not present', () => {
|
|
71
|
+
toggleClass(element, 'test-class');
|
|
72
|
+
expect(element.classList.contains('test-class')).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should remove class if present', () => {
|
|
76
|
+
element.className = 'test-class';
|
|
77
|
+
toggleClass(element, 'test-class');
|
|
78
|
+
expect(element.classList.contains('test-class')).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should add class if force is true', () => {
|
|
82
|
+
toggleClass(element, 'test-class', true);
|
|
83
|
+
expect(element.classList.contains('test-class')).toBe(true);
|
|
84
|
+
|
|
85
|
+
// Should stay added if already present
|
|
86
|
+
toggleClass(element, 'test-class', true);
|
|
87
|
+
expect(element.classList.contains('test-class')).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should remove class if force is false', () => {
|
|
91
|
+
element.className = 'test-class';
|
|
92
|
+
toggleClass(element, 'test-class', false);
|
|
93
|
+
expect(element.classList.contains('test-class')).toBe(false);
|
|
94
|
+
|
|
95
|
+
// Should stay removed if not present
|
|
96
|
+
toggleClass(element, 'test-class', false);
|
|
97
|
+
expect(element.classList.contains('test-class')).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|