@udixio/ui-react 2.10.13 → 2.10.15

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 (173) hide show
  1. package/package.json +4 -2
  2. package/.eslintrc.mjs +0 -22
  3. package/.storybook/main.ts +0 -20
  4. package/.storybook/preview.ts +0 -1
  5. package/CHANGELOG.md +0 -1144
  6. package/postcss.config.mjs +0 -5
  7. package/src/index.css +0 -4
  8. package/src/index.ts +0 -1
  9. package/src/lib/components/AnchorPositioner.tsx +0 -185
  10. package/src/lib/components/Button.tsx +0 -208
  11. package/src/lib/components/Card.tsx +0 -47
  12. package/src/lib/components/Carousel.tsx +0 -437
  13. package/src/lib/components/CarouselItem.tsx +0 -61
  14. package/src/lib/components/Checkbox.tsx +0 -120
  15. package/src/lib/components/Chip.tsx +0 -341
  16. package/src/lib/components/Chips.tsx +0 -331
  17. package/src/lib/components/ContextMenu.tsx +0 -109
  18. package/src/lib/components/DatePicker.tsx +0 -432
  19. package/src/lib/components/Divider.tsx +0 -20
  20. package/src/lib/components/Fab.tsx +0 -127
  21. package/src/lib/components/FabMenu.tsx +0 -239
  22. package/src/lib/components/IconButton.tsx +0 -146
  23. package/src/lib/components/Menu.tsx +0 -88
  24. package/src/lib/components/MenuGroup.tsx +0 -34
  25. package/src/lib/components/MenuHeadline.tsx +0 -9
  26. package/src/lib/components/MenuItem.tsx +0 -215
  27. package/src/lib/components/NavigationRail.tsx +0 -186
  28. package/src/lib/components/NavigationRailItem.tsx +0 -227
  29. package/src/lib/components/ProgressIndicator.tsx +0 -214
  30. package/src/lib/components/SideSheet.tsx +0 -135
  31. package/src/lib/components/Slider.tsx +0 -374
  32. package/src/lib/components/Snackbar.tsx +0 -77
  33. package/src/lib/components/Switch.tsx +0 -107
  34. package/src/lib/components/Tab.tsx +0 -123
  35. package/src/lib/components/TabGroup.tsx +0 -66
  36. package/src/lib/components/TabGroupContext.tsx +0 -16
  37. package/src/lib/components/TabPanel.tsx +0 -27
  38. package/src/lib/components/TabPanels.tsx +0 -76
  39. package/src/lib/components/Tabs.tsx +0 -105
  40. package/src/lib/components/TextField.tsx +0 -586
  41. package/src/lib/components/Tooltip.tsx +0 -217
  42. package/src/lib/components/index.ts +0 -34
  43. package/src/lib/config/config.interface.ts +0 -9
  44. package/src/lib/config/define-config.ts +0 -16
  45. package/src/lib/config/index.ts +0 -2
  46. package/src/lib/effects/AnimateOnScroll.ts +0 -391
  47. package/src/lib/effects/State.tsx +0 -90
  48. package/src/lib/effects/SyncedFixedWrapper.tsx +0 -62
  49. package/src/lib/effects/ThemeProvider.tsx +0 -174
  50. package/src/lib/effects/block-scroll.effect.tsx +0 -313
  51. package/src/lib/effects/custom-scroll/custom-scroll.effect.tsx +0 -407
  52. package/src/lib/effects/custom-scroll/custom-scroll.interface.ts +0 -29
  53. package/src/lib/effects/custom-scroll/custom-scroll.style.ts +0 -32
  54. package/src/lib/effects/custom-scroll/index.ts +0 -3
  55. package/src/lib/effects/index.ts +0 -7
  56. package/src/lib/effects/ripple/RippleEffect.tsx +0 -116
  57. package/src/lib/effects/ripple/index.tsx +0 -1
  58. package/src/lib/effects/scrollDriven.ts +0 -239
  59. package/src/lib/effects/smooth-scroll.effect.tsx +0 -112
  60. package/src/lib/effects/theme.worker.ts +0 -97
  61. package/src/lib/hooks/index.ts +0 -10
  62. package/src/lib/hooks/useTooltipTrigger.ts +0 -270
  63. package/src/lib/icon/icon.tsx +0 -125
  64. package/src/lib/icon/index.ts +0 -1
  65. package/src/lib/index.ts +0 -8
  66. package/src/lib/interfaces/button.interface.ts +0 -65
  67. package/src/lib/interfaces/card.interface.ts +0 -11
  68. package/src/lib/interfaces/carousel-item.interface.ts +0 -12
  69. package/src/lib/interfaces/carousel.interface.ts +0 -41
  70. package/src/lib/interfaces/checkbox.interface.ts +0 -39
  71. package/src/lib/interfaces/chip.interface.ts +0 -97
  72. package/src/lib/interfaces/chips.interface.ts +0 -37
  73. package/src/lib/interfaces/date-picker.interface.ts +0 -79
  74. package/src/lib/interfaces/divider.interface.ts +0 -7
  75. package/src/lib/interfaces/fab-menu.interface.ts +0 -12
  76. package/src/lib/interfaces/fab.interface.ts +0 -27
  77. package/src/lib/interfaces/icon-button.interface.ts +0 -38
  78. package/src/lib/interfaces/index.ts +0 -26
  79. package/src/lib/interfaces/menu-group.interface.ts +0 -13
  80. package/src/lib/interfaces/menu-item.interface.ts +0 -29
  81. package/src/lib/interfaces/menu.interface.ts +0 -19
  82. package/src/lib/interfaces/navigation-rail-item.interface.ts +0 -39
  83. package/src/lib/interfaces/navigation-rail.interface.ts +0 -39
  84. package/src/lib/interfaces/progress-indicator.interface.ts +0 -41
  85. package/src/lib/interfaces/side-sheet.interface.tsx +0 -28
  86. package/src/lib/interfaces/slider.interface.ts +0 -27
  87. package/src/lib/interfaces/snackbar.interface.ts +0 -13
  88. package/src/lib/interfaces/switch.interface.ts +0 -14
  89. package/src/lib/interfaces/tab-group.interface.ts +0 -13
  90. package/src/lib/interfaces/tab-panels.interface.ts +0 -21
  91. package/src/lib/interfaces/tab.interface.ts +0 -31
  92. package/src/lib/interfaces/tabs.interface.ts +0 -22
  93. package/src/lib/interfaces/text-field.interface.ts +0 -61
  94. package/src/lib/interfaces/tooltip.interface.ts +0 -61
  95. package/src/lib/styles/button.style.ts +0 -136
  96. package/src/lib/styles/card.style.ts +0 -29
  97. package/src/lib/styles/carousel-item.style.ts +0 -24
  98. package/src/lib/styles/carousel.style.ts +0 -22
  99. package/src/lib/styles/checkbox.style.ts +0 -64
  100. package/src/lib/styles/chip.style.ts +0 -62
  101. package/src/lib/styles/chips.style.ts +0 -20
  102. package/src/lib/styles/date-picker.style.ts +0 -43
  103. package/src/lib/styles/divider.style.ts +0 -31
  104. package/src/lib/styles/fab-menu.style.ts +0 -29
  105. package/src/lib/styles/fab.style.ts +0 -49
  106. package/src/lib/styles/icon-button.style.ts +0 -168
  107. package/src/lib/styles/index.ts +0 -25
  108. package/src/lib/styles/menu-group.style.ts +0 -34
  109. package/src/lib/styles/menu-headline.style.ts +0 -20
  110. package/src/lib/styles/menu-item.style.ts +0 -45
  111. package/src/lib/styles/menu.style.ts +0 -32
  112. package/src/lib/styles/navigation-rail-item.style.ts +0 -56
  113. package/src/lib/styles/navigation-rail.style.ts +0 -36
  114. package/src/lib/styles/progress-indicator.style.ts +0 -72
  115. package/src/lib/styles/side-sheet.style.ts +0 -45
  116. package/src/lib/styles/slider.style.ts +0 -41
  117. package/src/lib/styles/snackbar.style.ts +0 -26
  118. package/src/lib/styles/switch.style.ts +0 -67
  119. package/src/lib/styles/tab-panels.style.ts +0 -35
  120. package/src/lib/styles/tab.style.ts +0 -78
  121. package/src/lib/styles/tabs.style.ts +0 -22
  122. package/src/lib/styles/text-field.style.ts +0 -115
  123. package/src/lib/styles/tooltip.style.ts +0 -48
  124. package/src/lib/utils/component-helper.ts +0 -134
  125. package/src/lib/utils/component.ts +0 -34
  126. package/src/lib/utils/index.ts +0 -7
  127. package/src/lib/utils/string.ts +0 -9
  128. package/src/lib/utils/styles/classnames.ts +0 -49
  129. package/src/lib/utils/styles/get-classname.ts +0 -96
  130. package/src/lib/utils/styles/index.ts +0 -4
  131. package/src/lib/utils/styles/use-classnames.ts +0 -25
  132. package/src/stories/action/button.stories.tsx +0 -86
  133. package/src/stories/action/fab.stories.tsx +0 -54
  134. package/src/stories/action/icon-button.stories.tsx +0 -134
  135. package/src/stories/assets/accessibility.png +0 -0
  136. package/src/stories/assets/accessibility.svg +0 -5
  137. package/src/stories/assets/addon-library.png +0 -0
  138. package/src/stories/assets/assets.png +0 -0
  139. package/src/stories/assets/context.png +0 -0
  140. package/src/stories/assets/discord.svg +0 -15
  141. package/src/stories/assets/docs.png +0 -0
  142. package/src/stories/assets/figma-plugin.png +0 -0
  143. package/src/stories/assets/github.svg +0 -3
  144. package/src/stories/assets/share.png +0 -0
  145. package/src/stories/assets/styling.png +0 -0
  146. package/src/stories/assets/testing.png +0 -0
  147. package/src/stories/assets/theming.png +0 -0
  148. package/src/stories/assets/tutorials.svg +0 -12
  149. package/src/stories/assets/youtube.svg +0 -4
  150. package/src/stories/communication/ProgressIndicator.stories.tsx +0 -57
  151. package/src/stories/communication/SnackBar.stories.tsx +0 -32
  152. package/src/stories/communication/tool-tip.stories.tsx +0 -133
  153. package/src/stories/containment/card.stories.tsx +0 -42
  154. package/src/stories/containment/carousel.stories.tsx +0 -65
  155. package/src/stories/containment/divider.stories.tsx +0 -35
  156. package/src/stories/containment/slide-sheet.stories.tsx +0 -45
  157. package/src/stories/effect/smooth-scroll.stories.tsx +0 -54
  158. package/src/stories/navigation/navigation-rail/navigation-rail-item.stories.tsx +0 -65
  159. package/src/stories/navigation/navigation-rail/navigation-rail.stories.tsx +0 -122
  160. package/src/stories/navigation/tabs/tab.stories.tsx +0 -57
  161. package/src/stories/navigation/tabs/tabs.stories.tsx +0 -102
  162. package/src/stories/selection/slider.stories.tsx +0 -85
  163. package/src/stories/selection/switch.stories.tsx +0 -46
  164. package/src/stories/text-inputs/text-field.stories.tsx +0 -135
  165. package/src/tests/Button.spec.tsx +0 -67
  166. package/src/tests/useClassNames.spec.tsx +0 -82
  167. package/src/udixio.css +0 -120
  168. package/theme.config.ts +0 -7
  169. package/tsconfig.json +0 -16
  170. package/tsconfig.lib.json +0 -51
  171. package/tsconfig.spec.json +0 -37
  172. package/tsconfig.storybook.json +0 -38
  173. package/vite.config.ts +0 -96
@@ -1,437 +0,0 @@
1
- import React, { useEffect, useLayoutEffect, useRef, useState, useCallback } from 'react';
2
- import { animate } from 'motion/react';
3
- import { CarouselInterface, CarouselItemInterface } from '../interfaces';
4
-
5
- import { useCarouselStyle } from '../styles';
6
- import { CustomScroll } from '../effects';
7
- import { ReactProps } from '../utils';
8
- import { CarouselItem, normalize } from './CarouselItem';
9
-
10
- /**
11
- * Carousels show a collection of items that can be scrolled on and off the screen
12
- *
13
- * @status beta
14
- * @category Layout
15
- * @devx
16
- * - Only `CarouselItem` children are rendered; other children are ignored.
17
- * - Use `index` for controlled positioning; otherwise relies on internal scroll state.
18
- * @limitations
19
- * - Responsive behavior on mobile is not supported.
20
- * - Only the default (hero) variant is supported.
21
- * - No keyboard navigation or focus management.
22
- */
23
- export const Carousel = ({
24
- variant = 'hero',
25
- className,
26
- children,
27
- ref: optionalRef,
28
- marginPourcent = 0,
29
- inputRange = [0.21, 0.65],
30
- outputRange = [42, 300],
31
- gap = 8,
32
- onChange,
33
- onMetricsChange,
34
- index,
35
- scrollSensitivity = 1.25,
36
- ...restProps
37
- }: ReactProps<CarouselInterface>) => {
38
- const defaultRef = useRef<HTMLDivElement>(null);
39
- const ref = optionalRef || defaultRef;
40
-
41
- const styles = useCarouselStyle({
42
- index,
43
- className,
44
- children,
45
- variant,
46
- inputRange,
47
- outputRange,
48
- marginPourcent,
49
- onChange,
50
- gap,
51
- scrollSensitivity,
52
- onMetricsChange,
53
- });
54
-
55
- const items = React.Children.toArray(children).filter(
56
- (child) => React.isValidElement(child) && child.type === CarouselItem,
57
- );
58
-
59
- const trackRef = useRef<HTMLDivElement>(null);
60
-
61
- // OPTIMIZATION: We no longer store width and translate in React state to avoid laggy 60fps re-renders.
62
- // We use refs instead.
63
- const getScrollState = useRef({
64
- scrollProgress: 0,
65
- scrollTotal: 0,
66
- scrollVisible: 0,
67
- scroll: 0,
68
- });
69
-
70
- // Smoothed scroll progress using framer-motion animate()
71
- const smoothedProgressRef = useRef(0);
72
- const scrollAnimationRef = useRef<ReturnType<typeof animate> | null>(null);
73
-
74
- const itemRefs = useRef<React.RefObject<HTMLDivElement | null>[]>([]).current;
75
- const [selectedItem, setSelectedItem] = useState(0);
76
-
77
- if (itemRefs.length !== items.length) {
78
- itemRefs.length = 0; // reset
79
- items.forEach((_, i) => {
80
- itemRefs[i] = React.createRef<HTMLDivElement>();
81
- });
82
- }
83
-
84
- // Pure mathematical logic - kept from original
85
- const updateLayoutFromCalculations = useCallback(() => {
86
- if (!trackRef.current || !ref.current) return;
87
- const currentScrollProgress = smoothedProgressRef.current;
88
-
89
- // Need dimensions
90
- const scrollVisible = getScrollState.current.scrollVisible || (ref.current as any).clientWidth || 0;
91
-
92
- function assignRelativeIndexes(values: number[], progressScroll: number) {
93
- return values.map((value, idx) => {
94
- const relativeIndex = (value - progressScroll) / Math.abs(values[1] - values[0]);
95
- return { itemScrollXCenter: value, relativeIndex, index: idx, width: 0 };
96
- });
97
- }
98
-
99
- const itemsScrollXCenter = items.map((_, idx) => {
100
- // Calculate original center normalized
101
- const itemScrollXCenter = idx / Math.max(1, items.length - 1);
102
- return normalize(itemScrollXCenter, [0, 1], [0, 1]);
103
- });
104
-
105
- const itemValues = assignRelativeIndexes(
106
- itemsScrollXCenter,
107
- currentScrollProgress,
108
- ).sort((a, b) => a.index - b.index);
109
-
110
- let widthLeft = scrollVisible + gap + outputRange[0] + gap;
111
-
112
- let localSelected = selectedItem;
113
- const visibleItemValues = itemValues
114
- .sort((a, b) => Math.abs(a.relativeIndex) - Math.abs(b.relativeIndex))
115
- .map((item, idx) => {
116
- if (widthLeft <= 0) return undefined;
117
- if (idx === 0) localSelected = item.index;
118
-
119
- item.width = normalize(
120
- widthLeft - gap,
121
- [outputRange[0], outputRange[1]],
122
- [outputRange[0], outputRange[1]],
123
- );
124
-
125
- widthLeft -= item.width + gap;
126
-
127
- if (widthLeft !== 0 && widthLeft < (outputRange[0] + gap) * 2) {
128
- const newWidth = item.width - ((outputRange[0] + gap) * 2 - widthLeft);
129
- widthLeft += item.width;
130
- item.width = newWidth;
131
- widthLeft -= item.width;
132
- } else if (widthLeft === 0 && item.width >= outputRange[0] * 2 + gap) {
133
- const newWidth = item.width - (outputRange[0] + gap - widthLeft);
134
- widthLeft += item.width;
135
- item.width = newWidth;
136
- widthLeft -= item.width;
137
- }
138
- return item;
139
- })
140
- .filter(Boolean) as { itemScrollXCenter: number; relativeIndex: number; index: number; width: number; }[];
141
-
142
- const reverseItemsVisible = [...visibleItemValues].reverse();
143
- const itemsVisibleByIndex = [...visibleItemValues].sort((a, b) => Math.abs(a.index) - Math.abs(b.index));
144
-
145
- reverseItemsVisible.forEach((item, idx) => {
146
- const nextItem = reverseItemsVisible[idx + 1];
147
- if (!nextItem) return;
148
-
149
- const test = 1 - (Math.abs(item.relativeIndex) - Math.abs(nextItem.relativeIndex));
150
- const newWidth = normalize(test, [0, 2], [item.width + widthLeft, nextItem.width]);
151
-
152
- widthLeft += item.width;
153
- item.width = newWidth;
154
- widthLeft -= item.width;
155
- });
156
-
157
- const percentMax = visibleItemValues.length / 2;
158
- const percent = normalize(
159
- Math.abs(itemsVisibleByIndex[0].relativeIndex),
160
- [itemsVisibleByIndex[0].index === 0 ? 0 : percentMax - 1, percentMax],
161
- [0, 1],
162
- );
163
-
164
- const translate = normalize(percent, [0, 1], [0, 1]) * -(outputRange[0] + gap);
165
-
166
- // ===================================
167
- // DOM INJECTION OPTIMIZATION
168
- // ===================================
169
-
170
- // Apply width to each visible item using DOM instead of setItemWidths React state
171
- // First, fallback everything to outputRange[0] (or hide them)
172
- itemRefs.forEach((refItem, i) => {
173
- if (refItem.current) {
174
- const match = visibleItemValues.find(v => v.index === i);
175
- if (match) {
176
- refItem.current.style.setProperty('--carousel-item-width', `${match.width}px`);
177
- refItem.current.style.display = 'block';
178
- } else {
179
- refItem.current.style.setProperty('--carousel-item-width', `${outputRange[0]}px`);
180
- refItem.current.style.display = 'none';
181
- }
182
- }
183
- });
184
-
185
- // Apply track translate directly via DOM
186
- trackRef.current.style.transform = `translateX(${translate}px)`;
187
-
188
- if (localSelected !== selectedItem) {
189
- setSelectedItem(localSelected);
190
- }
191
- }, [items.length, outputRange, gap, selectedItem]);
192
-
193
- useLayoutEffect(() => {
194
- updateLayoutFromCalculations();
195
- }, [updateLayoutFromCalculations, items.length]);
196
-
197
-
198
- useEffect(() => {
199
- if (onChange) onChange(selectedItem);
200
- }, [selectedItem, onChange]);
201
-
202
- // accessibility and interaction states
203
- const [focusedIndex, setFocusedIndex] = useState(0);
204
-
205
- useEffect(() => {
206
- setFocusedIndex(selectedItem);
207
- }, [selectedItem]);
208
-
209
-
210
- const centerOnIndex = (idx: number, opts: { animate?: boolean } = {}) => {
211
- if (!items.length) return 0;
212
- const itemRef = itemRefs[idx];
213
- if (!itemRef || !itemRef.current || !trackRef.current) return 0;
214
-
215
- const itemScrollXCenter = normalize(
216
- idx / Math.max(1, items.length - 1),
217
- [0, 1],
218
- [0, 1],
219
- );
220
-
221
- setFocusedIndex(idx);
222
-
223
- const track = trackRef.current as HTMLElement;
224
- track.dispatchEvent(
225
- new CustomEvent('udx:customScroll:set', {
226
- bubbles: true,
227
- detail: {
228
- progress: itemScrollXCenter,
229
- orientation: 'horizontal',
230
- animate: opts.animate !== false,
231
- },
232
- }),
233
- );
234
-
235
- return itemScrollXCenter;
236
- };
237
-
238
- useEffect(() => {
239
- if (typeof index === 'number' && items.length > 0 && index !== selectedItem) {
240
- centerOnIndex(index);
241
- }
242
- }, [index, items.length]);
243
-
244
- const handleScroll = (args: {
245
- scrollProgress: number;
246
- scrollTotal: number;
247
- scrollVisible: number;
248
- scroll: number;
249
- }) => {
250
- getScrollState.current = args;
251
-
252
- if (args.scrollTotal > 0) {
253
- scrollAnimationRef.current?.stop();
254
- const from = smoothedProgressRef.current ?? 0;
255
- const to = args.scrollProgress ?? 0;
256
-
257
- scrollAnimationRef.current = animate(from, to, {
258
- type: 'spring',
259
- stiffness: 260,
260
- damping: 32,
261
- mass: 0.6,
262
- restDelta: 0.0005,
263
- onUpdate: (v) => {
264
- smoothedProgressRef.current = v;
265
- requestAnimationFrame(() => {
266
- updateLayoutFromCalculations(); // Apply DOM updates synchronously to animation
267
- });
268
- },
269
- });
270
- }
271
- };
272
-
273
- // Keep latest onMetricsChange in a ref to avoid effect dependency loops
274
- const onMetricsChangeRef = useRef(onMetricsChange);
275
- useEffect(() => {
276
- onMetricsChangeRef.current = onMetricsChange;
277
- }, [onMetricsChange]);
278
-
279
- const lastMetricsRef = useRef<any>(null);
280
-
281
- useEffect(() => {
282
- const cb = onMetricsChangeRef.current;
283
- if (!cb || !ref?.current || items.length <= 0) return;
284
- const total = items.length;
285
- const viewportWidth = (ref.current as any).clientWidth || 0;
286
- const itemMaxWidth = outputRange[1];
287
- const sProgress = smoothedProgressRef.current;
288
- const visibleApprox = (viewportWidth + gap) / (itemMaxWidth + gap);
289
- const visibleFull = Math.max(1, Math.floor(visibleApprox));
290
- const stepHalf = Math.max(1, Math.round(visibleFull * (2 / 3)));
291
- const selectedIndexSafe = Math.min(Math.max(0, selectedItem), Math.max(0, total - 1));
292
-
293
- const metrics = {
294
- total,
295
- selectedIndex: selectedIndexSafe,
296
- visibleApprox,
297
- visibleFull,
298
- stepHalf,
299
- canPrev: selectedIndexSafe > 0,
300
- canNext: selectedIndexSafe < total - 1,
301
- scrollProgress: sProgress,
302
- viewportWidth,
303
- itemMaxWidth,
304
- gap,
305
- };
306
-
307
- const last = lastMetricsRef.current;
308
- let changed = !last;
309
- if (!changed) {
310
- for (const k in metrics) {
311
- if ((metrics as any)[k] !== last[k]) {
312
- changed = true;
313
- break;
314
- }
315
- }
316
- }
317
-
318
- if (changed) {
319
- lastMetricsRef.current = metrics;
320
- cb(metrics);
321
- }
322
- }, [ref, items.length, selectedItem, gap, outputRange]);
323
-
324
- useEffect(() => {
325
- return () => {
326
- scrollAnimationRef.current?.stop();
327
- };
328
- }, []);
329
-
330
- const [scrollSize, setScrollSize] = useState(0);
331
- useLayoutEffect(() => {
332
- let maxWidth = outputRange[1];
333
- const scrollState = getScrollState.current;
334
- if (scrollState && maxWidth > scrollState.scrollVisible && scrollState.scrollVisible > 0) {
335
- maxWidth = scrollState.scrollVisible;
336
- }
337
- const result = ((maxWidth + gap) * items.length) / scrollSensitivity;
338
- setScrollSize(result || 400); // Fail-safe
339
- }, [ref, items.length, gap, outputRange, scrollSensitivity]);
340
-
341
-
342
- const handleKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
343
- if (!items.length) return;
344
- const idx = focusedIndex ?? selectedItem;
345
- switch (e.key) {
346
- case 'ArrowLeft':
347
- e.preventDefault();
348
- centerOnIndex(Math.max(0, idx - 1));
349
- break;
350
- case 'ArrowRight':
351
- e.preventDefault();
352
- centerOnIndex(Math.min(items.length - 1, idx + 1));
353
- break;
354
- case 'Home':
355
- e.preventDefault();
356
- centerOnIndex(0);
357
- break;
358
- case 'End':
359
- e.preventDefault();
360
- centerOnIndex(items.length - 1);
361
- break;
362
- case 'Enter':
363
- case ' ':
364
- e.preventDefault();
365
- centerOnIndex(idx);
366
- break;
367
- }
368
- };
369
-
370
- useEffect(() => {
371
- const root = ref.current as any;
372
- if (!root) return;
373
- const handler = (ev: Event) => {
374
- const detail = (ev as CustomEvent).detail;
375
- if (detail && typeof detail.index === 'number') {
376
- centerOnIndex(detail.index);
377
- }
378
- };
379
- root.addEventListener('udx:carousel:centerIndex', handler);
380
- return () => {
381
- root.removeEventListener('udx:carousel:centerIndex', handler);
382
- };
383
- }, [ref, items.length]);
384
-
385
- const renderItems = items.map((child, idx) => {
386
- const existingOnClick = (child as any).props?.onClick;
387
- const handleClick = (e: any) => {
388
- existingOnClick?.(e);
389
- // centerOnIndex(idx);
390
- };
391
-
392
- return React.cloneElement(
393
- child as React.ReactElement<ReactProps<CarouselItemInterface>>,
394
- {
395
- outputRange,
396
- ref: itemRefs[idx],
397
- key: idx,
398
- index: idx,
399
- role: 'option',
400
- 'aria-selected': selectedItem === idx,
401
- tabIndex: selectedItem === idx ? 0 : -1,
402
- onClick: handleClick,
403
- onFocus: () => setFocusedIndex(idx),
404
- // NOTE: We REMOVED the 'width' prop from here!
405
- } as any,
406
- );
407
- });
408
-
409
- return (
410
- <div
411
- className={styles.carousel}
412
- ref={ref}
413
- role="listbox"
414
- aria-orientation="horizontal"
415
- onKeyDown={handleKeyDown}
416
- {...restProps}
417
- >
418
- <CustomScroll
419
- draggable
420
- orientation={'horizontal'}
421
- onScroll={handleScroll}
422
- scrollSize={scrollSize}
423
- >
424
- <div
425
- className={styles.track}
426
- ref={trackRef}
427
- style={{
428
- gap: `${gap}px`,
429
- willChange: 'transform',
430
- }}
431
- >
432
- {renderItems}
433
- </div>
434
- </CustomScroll>
435
- </div>
436
- );
437
- };
@@ -1,61 +0,0 @@
1
- import React, { useRef } from 'react';
2
- import { CarouselItemInterface } from '../interfaces';
3
- import { useCarouselItemStyle } from '../styles';
4
- import { MotionProps } from '../utils';
5
-
6
- export const normalize = (
7
- value: number,
8
- inputRange: [number, number],
9
- outputRange: [number, number] = [0, 1],
10
- ): number => {
11
- const [inputMin, inputMax] = inputRange;
12
- const [outputMin, outputMax] = outputRange;
13
-
14
- const clampedValue = Math.max(inputMin, Math.min(value, inputMax));
15
-
16
- const normalizedValue = (clampedValue - inputMin) / (inputMax - inputMin);
17
-
18
- return outputMin + normalizedValue * (outputMax - outputMin);
19
- };
20
-
21
- /**
22
- * @status beta
23
- * @parent Carousel
24
- * @devx
25
- * - Intended for use inside `Carousel`; width and outputRange drive sizing.
26
- * @limitations
27
- * - Requires `outputRange` for min/max sizing; missing values can break layout.
28
- */
29
- export const CarouselItem = ({
30
- className,
31
- children,
32
- width,
33
- index = 0,
34
- outputRange,
35
- ref: optionalRef,
36
- ...restProps
37
- }: MotionProps<CarouselItemInterface>) => {
38
- const defaultRef = useRef(null);
39
- const ref: React.RefObject<null | HTMLDivElement> = optionalRef || defaultRef;
40
-
41
- const styles = useCarouselItemStyle({
42
- className,
43
- index,
44
- children,
45
- });
46
-
47
- return (
48
- <div
49
- ref={ref}
50
- style={{
51
- width: 'var(--carousel-item-width, 100%)',
52
- maxWidth: outputRange ? outputRange[1] + 'px' : undefined,
53
- minWidth: outputRange ? outputRange[0] + 'px' : undefined,
54
- }}
55
- className={styles.carouselItem}
56
- {...restProps}
57
- >
58
- {children}
59
- </div>
60
- );
61
- };
@@ -1,120 +0,0 @@
1
- import React, { useEffect, useId, useState } from 'react';
2
- import { AnimatePresence, motion } from 'motion/react';
3
- import { useCheckboxStyle } from '../styles/checkbox.style';
4
- import { classNames } from '../utils';
5
- import { ReactProps } from '../utils/component';
6
- import { CheckboxInterface } from '../interfaces/checkbox.interface';
7
- import { Icon } from '../icon';
8
- import { faCheck, faMinus } from '@fortawesome/free-solid-svg-icons';
9
- import { State } from '../effects';
10
-
11
- /**
12
- * Checkboxes allow the user to select one or more items from a set.
13
- * @status beta
14
- * @category Selection
15
- * @devx
16
- * - Supports controlled (`checked`) and uncontrolled (`defaultChecked`) modes.
17
- * - Handles `indeterminate` state strictly as visual (updates UI, but underlying input is checked/unchecked based on logic).
18
- * @a11y
19
- * - Uses native input for accessibility.
20
- * - Supports standard keyboard interaction.
21
- */
22
- export const Checkbox = ({
23
- checked: checkedProp,
24
- defaultChecked,
25
- indeterminate = false,
26
- disabled = false,
27
- error = false,
28
- onChange,
29
- id: idProp,
30
- name,
31
- value,
32
- style,
33
- className,
34
- ...restProps
35
- }: ReactProps<CheckboxInterface>) => {
36
- const generatedId = useId();
37
- const id = idProp || generatedId;
38
-
39
- const isControlled = checkedProp !== undefined;
40
- const [internalChecked, setInternalChecked] = useState(
41
- defaultChecked ?? false,
42
- );
43
- const isChecked = isControlled ? checkedProp : internalChecked;
44
-
45
- const [isFocused, setIsFocused] = useState(false);
46
-
47
- const inputRef = React.useRef<HTMLInputElement>(null);
48
-
49
- // Sync indeterminate state to input element for a11y
50
- useEffect(() => {
51
- if (inputRef.current) {
52
- inputRef.current.indeterminate = indeterminate;
53
- }
54
- }, [indeterminate]);
55
-
56
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
57
- if (disabled) return;
58
-
59
- if (!isControlled) {
60
- setInternalChecked(e.target.checked);
61
- }
62
-
63
- if (onChange) {
64
- onChange(e);
65
- }
66
- };
67
-
68
- const styles = useCheckboxStyle({
69
- isChecked: !!isChecked,
70
- isIndeterminate: indeterminate,
71
- isDisabled: disabled,
72
- isError: error,
73
- isFocused,
74
- isHovered: false, // Not used in style logic but requested by strict type if defined in interface? style config keys are flexible usually.
75
- });
76
-
77
- return (
78
- <div
79
- className={classNames(styles.checkbox, className, 'group/checkbox')}
80
- style={style}
81
- >
82
- <State
83
- stateClassName={styles.stateLayer}
84
- colorName={isChecked || indeterminate ? 'primary' : 'on-surface'}
85
- >
86
- <input
87
- ref={inputRef}
88
- type="checkbox"
89
- id={id}
90
- name={name}
91
- value={value}
92
- checked={isChecked}
93
- disabled={disabled}
94
- onChange={handleChange}
95
- onFocus={() => setIsFocused(true)}
96
- onBlur={() => setIsFocused(false)}
97
- className={styles.input}
98
- {...(restProps as any)}
99
- />
100
- <div className={styles.box}></div>
101
- <AnimatePresence>
102
- {(isChecked || indeterminate) && (
103
- <motion.div
104
- initial={{ opacity: 0, scale: 0.5 }}
105
- animate={{ opacity: 1, scale: 1 }}
106
- exit={{ opacity: 0, scale: 0.5 }}
107
- transition={{ duration: 0.15 }}
108
- className={styles.icon}
109
- >
110
- <Icon
111
- icon={indeterminate ? faMinus : faCheck}
112
- className="size-3.5" // ~14px icon
113
- />
114
- </motion.div>
115
- )}
116
- </AnimatePresence>
117
- </State>
118
- </div>
119
- );
120
- };