@udixio/ui-react 2.10.12 → 2.10.14

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 (179) hide show
  1. package/dist/index.cjs +3 -3
  2. package/dist/index.js +2696 -2710
  3. package/dist/lib/effects/ThemeProvider.d.ts.map +1 -1
  4. package/dist/theme.worker.js +6633 -0
  5. package/package.json +4 -1
  6. package/.eslintrc.mjs +0 -22
  7. package/.storybook/main.ts +0 -20
  8. package/.storybook/preview.ts +0 -1
  9. package/CHANGELOG.md +0 -1130
  10. package/dist/scrollDriven-AP2yWhzi.js +0 -121
  11. package/postcss.config.mjs +0 -5
  12. package/src/index.css +0 -4
  13. package/src/index.ts +0 -1
  14. package/src/lib/components/AnchorPositioner.tsx +0 -185
  15. package/src/lib/components/Button.tsx +0 -208
  16. package/src/lib/components/Card.tsx +0 -47
  17. package/src/lib/components/Carousel.tsx +0 -437
  18. package/src/lib/components/CarouselItem.tsx +0 -61
  19. package/src/lib/components/Checkbox.tsx +0 -120
  20. package/src/lib/components/Chip.tsx +0 -341
  21. package/src/lib/components/Chips.tsx +0 -331
  22. package/src/lib/components/ContextMenu.tsx +0 -109
  23. package/src/lib/components/DatePicker.tsx +0 -432
  24. package/src/lib/components/Divider.tsx +0 -20
  25. package/src/lib/components/Fab.tsx +0 -127
  26. package/src/lib/components/FabMenu.tsx +0 -239
  27. package/src/lib/components/IconButton.tsx +0 -146
  28. package/src/lib/components/Menu.tsx +0 -88
  29. package/src/lib/components/MenuGroup.tsx +0 -34
  30. package/src/lib/components/MenuHeadline.tsx +0 -9
  31. package/src/lib/components/MenuItem.tsx +0 -215
  32. package/src/lib/components/NavigationRail.tsx +0 -186
  33. package/src/lib/components/NavigationRailItem.tsx +0 -227
  34. package/src/lib/components/ProgressIndicator.tsx +0 -214
  35. package/src/lib/components/SideSheet.tsx +0 -135
  36. package/src/lib/components/Slider.tsx +0 -374
  37. package/src/lib/components/Snackbar.tsx +0 -77
  38. package/src/lib/components/Switch.tsx +0 -107
  39. package/src/lib/components/Tab.tsx +0 -123
  40. package/src/lib/components/TabGroup.tsx +0 -66
  41. package/src/lib/components/TabGroupContext.tsx +0 -16
  42. package/src/lib/components/TabPanel.tsx +0 -27
  43. package/src/lib/components/TabPanels.tsx +0 -76
  44. package/src/lib/components/Tabs.tsx +0 -105
  45. package/src/lib/components/TextField.tsx +0 -586
  46. package/src/lib/components/Tooltip.tsx +0 -217
  47. package/src/lib/components/index.ts +0 -34
  48. package/src/lib/config/config.interface.ts +0 -9
  49. package/src/lib/config/define-config.ts +0 -16
  50. package/src/lib/config/index.ts +0 -2
  51. package/src/lib/effects/AnimateOnScroll.ts +0 -391
  52. package/src/lib/effects/State.tsx +0 -90
  53. package/src/lib/effects/SyncedFixedWrapper.tsx +0 -62
  54. package/src/lib/effects/ThemeProvider.tsx +0 -172
  55. package/src/lib/effects/block-scroll.effect.tsx +0 -313
  56. package/src/lib/effects/custom-scroll/custom-scroll.effect.tsx +0 -407
  57. package/src/lib/effects/custom-scroll/custom-scroll.interface.ts +0 -29
  58. package/src/lib/effects/custom-scroll/custom-scroll.style.ts +0 -32
  59. package/src/lib/effects/custom-scroll/index.ts +0 -3
  60. package/src/lib/effects/index.ts +0 -7
  61. package/src/lib/effects/ripple/RippleEffect.tsx +0 -116
  62. package/src/lib/effects/ripple/index.tsx +0 -1
  63. package/src/lib/effects/scrollDriven.ts +0 -239
  64. package/src/lib/effects/smooth-scroll.effect.tsx +0 -112
  65. package/src/lib/effects/theme.worker.ts +0 -97
  66. package/src/lib/hooks/index.ts +0 -10
  67. package/src/lib/hooks/useTooltipTrigger.ts +0 -270
  68. package/src/lib/icon/icon.tsx +0 -125
  69. package/src/lib/icon/index.ts +0 -1
  70. package/src/lib/index.ts +0 -8
  71. package/src/lib/interfaces/button.interface.ts +0 -65
  72. package/src/lib/interfaces/card.interface.ts +0 -11
  73. package/src/lib/interfaces/carousel-item.interface.ts +0 -12
  74. package/src/lib/interfaces/carousel.interface.ts +0 -41
  75. package/src/lib/interfaces/checkbox.interface.ts +0 -39
  76. package/src/lib/interfaces/chip.interface.ts +0 -97
  77. package/src/lib/interfaces/chips.interface.ts +0 -37
  78. package/src/lib/interfaces/date-picker.interface.ts +0 -79
  79. package/src/lib/interfaces/divider.interface.ts +0 -7
  80. package/src/lib/interfaces/fab-menu.interface.ts +0 -12
  81. package/src/lib/interfaces/fab.interface.ts +0 -27
  82. package/src/lib/interfaces/icon-button.interface.ts +0 -38
  83. package/src/lib/interfaces/index.ts +0 -26
  84. package/src/lib/interfaces/menu-group.interface.ts +0 -13
  85. package/src/lib/interfaces/menu-item.interface.ts +0 -29
  86. package/src/lib/interfaces/menu.interface.ts +0 -19
  87. package/src/lib/interfaces/navigation-rail-item.interface.ts +0 -39
  88. package/src/lib/interfaces/navigation-rail.interface.ts +0 -39
  89. package/src/lib/interfaces/progress-indicator.interface.ts +0 -41
  90. package/src/lib/interfaces/side-sheet.interface.tsx +0 -28
  91. package/src/lib/interfaces/slider.interface.ts +0 -27
  92. package/src/lib/interfaces/snackbar.interface.ts +0 -13
  93. package/src/lib/interfaces/switch.interface.ts +0 -14
  94. package/src/lib/interfaces/tab-group.interface.ts +0 -13
  95. package/src/lib/interfaces/tab-panels.interface.ts +0 -21
  96. package/src/lib/interfaces/tab.interface.ts +0 -31
  97. package/src/lib/interfaces/tabs.interface.ts +0 -22
  98. package/src/lib/interfaces/text-field.interface.ts +0 -61
  99. package/src/lib/interfaces/tooltip.interface.ts +0 -61
  100. package/src/lib/styles/button.style.ts +0 -136
  101. package/src/lib/styles/card.style.ts +0 -29
  102. package/src/lib/styles/carousel-item.style.ts +0 -24
  103. package/src/lib/styles/carousel.style.ts +0 -22
  104. package/src/lib/styles/checkbox.style.ts +0 -64
  105. package/src/lib/styles/chip.style.ts +0 -62
  106. package/src/lib/styles/chips.style.ts +0 -20
  107. package/src/lib/styles/date-picker.style.ts +0 -43
  108. package/src/lib/styles/divider.style.ts +0 -31
  109. package/src/lib/styles/fab-menu.style.ts +0 -29
  110. package/src/lib/styles/fab.style.ts +0 -49
  111. package/src/lib/styles/icon-button.style.ts +0 -168
  112. package/src/lib/styles/index.ts +0 -25
  113. package/src/lib/styles/menu-group.style.ts +0 -34
  114. package/src/lib/styles/menu-headline.style.ts +0 -20
  115. package/src/lib/styles/menu-item.style.ts +0 -45
  116. package/src/lib/styles/menu.style.ts +0 -32
  117. package/src/lib/styles/navigation-rail-item.style.ts +0 -56
  118. package/src/lib/styles/navigation-rail.style.ts +0 -36
  119. package/src/lib/styles/progress-indicator.style.ts +0 -72
  120. package/src/lib/styles/side-sheet.style.ts +0 -45
  121. package/src/lib/styles/slider.style.ts +0 -41
  122. package/src/lib/styles/snackbar.style.ts +0 -26
  123. package/src/lib/styles/switch.style.ts +0 -67
  124. package/src/lib/styles/tab-panels.style.ts +0 -35
  125. package/src/lib/styles/tab.style.ts +0 -78
  126. package/src/lib/styles/tabs.style.ts +0 -22
  127. package/src/lib/styles/text-field.style.ts +0 -115
  128. package/src/lib/styles/tooltip.style.ts +0 -48
  129. package/src/lib/utils/component-helper.ts +0 -134
  130. package/src/lib/utils/component.ts +0 -34
  131. package/src/lib/utils/index.ts +0 -7
  132. package/src/lib/utils/string.ts +0 -9
  133. package/src/lib/utils/styles/classnames.ts +0 -49
  134. package/src/lib/utils/styles/get-classname.ts +0 -96
  135. package/src/lib/utils/styles/index.ts +0 -4
  136. package/src/lib/utils/styles/use-classnames.ts +0 -25
  137. package/src/stories/action/button.stories.tsx +0 -86
  138. package/src/stories/action/fab.stories.tsx +0 -54
  139. package/src/stories/action/icon-button.stories.tsx +0 -134
  140. package/src/stories/assets/accessibility.png +0 -0
  141. package/src/stories/assets/accessibility.svg +0 -5
  142. package/src/stories/assets/addon-library.png +0 -0
  143. package/src/stories/assets/assets.png +0 -0
  144. package/src/stories/assets/context.png +0 -0
  145. package/src/stories/assets/discord.svg +0 -15
  146. package/src/stories/assets/docs.png +0 -0
  147. package/src/stories/assets/figma-plugin.png +0 -0
  148. package/src/stories/assets/github.svg +0 -3
  149. package/src/stories/assets/share.png +0 -0
  150. package/src/stories/assets/styling.png +0 -0
  151. package/src/stories/assets/testing.png +0 -0
  152. package/src/stories/assets/theming.png +0 -0
  153. package/src/stories/assets/tutorials.svg +0 -12
  154. package/src/stories/assets/youtube.svg +0 -4
  155. package/src/stories/communication/ProgressIndicator.stories.tsx +0 -57
  156. package/src/stories/communication/SnackBar.stories.tsx +0 -32
  157. package/src/stories/communication/tool-tip.stories.tsx +0 -133
  158. package/src/stories/containment/card.stories.tsx +0 -42
  159. package/src/stories/containment/carousel.stories.tsx +0 -65
  160. package/src/stories/containment/divider.stories.tsx +0 -35
  161. package/src/stories/containment/slide-sheet.stories.tsx +0 -45
  162. package/src/stories/effect/smooth-scroll.stories.tsx +0 -54
  163. package/src/stories/navigation/navigation-rail/navigation-rail-item.stories.tsx +0 -65
  164. package/src/stories/navigation/navigation-rail/navigation-rail.stories.tsx +0 -122
  165. package/src/stories/navigation/tabs/tab.stories.tsx +0 -57
  166. package/src/stories/navigation/tabs/tabs.stories.tsx +0 -102
  167. package/src/stories/selection/slider.stories.tsx +0 -85
  168. package/src/stories/selection/switch.stories.tsx +0 -46
  169. package/src/stories/text-inputs/text-field.stories.tsx +0 -135
  170. package/src/tests/Button.spec.tsx +0 -67
  171. package/src/tests/useClassNames.spec.tsx +0 -82
  172. package/src/udixio.css +0 -120
  173. package/theme.config.ts +0 -7
  174. package/tsconfig.json +0 -16
  175. package/tsconfig.lib.json +0 -51
  176. package/tsconfig.spec.json +0 -37
  177. package/tsconfig.storybook.json +0 -38
  178. package/vite.config.ts +0 -82
  179. /package/dist/{scrollDriven-DWAu7CR0.cjs → scrollDriven.js} +0 -0
@@ -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
- };