orn-ui 0.2.6 → 0.2.8

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 (146) hide show
  1. package/AGENTS.md +372 -0
  2. package/README.md +46 -15
  3. package/lib/commonjs/atoms/Gradient.js +111 -0
  4. package/lib/commonjs/atoms/Gradient.js.map +1 -0
  5. package/lib/commonjs/atoms/index.js +7 -0
  6. package/lib/commonjs/atoms/index.js.map +1 -1
  7. package/lib/commonjs/organisms/Slides.js +406 -0
  8. package/lib/commonjs/organisms/Slides.js.map +1 -0
  9. package/lib/commonjs/organisms/index.js +7 -0
  10. package/lib/commonjs/organisms/index.js.map +1 -1
  11. package/lib/commonjs/organisms/useSlides.js +186 -0
  12. package/lib/commonjs/organisms/useSlides.js.map +1 -0
  13. package/lib/module/atoms/Gradient.js +106 -0
  14. package/lib/module/atoms/Gradient.js.map +1 -0
  15. package/lib/module/atoms/index.js +1 -0
  16. package/lib/module/atoms/index.js.map +1 -1
  17. package/lib/module/organisms/Slides.js +401 -0
  18. package/lib/module/organisms/Slides.js.map +1 -0
  19. package/lib/module/organisms/index.js +1 -0
  20. package/lib/module/organisms/index.js.map +1 -1
  21. package/lib/module/organisms/useSlides.js +182 -0
  22. package/lib/module/organisms/useSlides.js.map +1 -0
  23. package/lib/typescript/atoms/Badge.d.ts +1 -1
  24. package/lib/typescript/atoms/Badge.d.ts.map +1 -1
  25. package/lib/typescript/atoms/Gradient.d.ts +37 -0
  26. package/lib/typescript/atoms/Gradient.d.ts.map +1 -0
  27. package/lib/typescript/atoms/Image.d.ts +6 -6
  28. package/lib/typescript/atoms/Image.d.ts.map +1 -1
  29. package/lib/typescript/atoms/Input.d.ts +1 -1
  30. package/lib/typescript/atoms/Input.d.ts.map +1 -1
  31. package/lib/typescript/atoms/PressableScale.d.ts +1 -1
  32. package/lib/typescript/atoms/PressableScale.d.ts.map +1 -1
  33. package/lib/typescript/atoms/Skeleton.d.ts +5 -5
  34. package/lib/typescript/atoms/Skeleton.d.ts.map +1 -1
  35. package/lib/typescript/atoms/Spinner.d.ts +4 -4
  36. package/lib/typescript/atoms/Spinner.d.ts.map +1 -1
  37. package/lib/typescript/atoms/Transition.d.ts +10 -10
  38. package/lib/typescript/atoms/Transition.d.ts.map +1 -1
  39. package/lib/typescript/atoms/index.d.ts +2 -0
  40. package/lib/typescript/atoms/index.d.ts.map +1 -1
  41. package/lib/typescript/molecules/AvatarHeader.d.ts +2 -2
  42. package/lib/typescript/molecules/AvatarHeader.d.ts.map +1 -1
  43. package/lib/typescript/molecules/FormActions.d.ts +1 -1
  44. package/lib/typescript/molecules/FormActions.d.ts.map +1 -1
  45. package/lib/typescript/molecules/OptionCard.d.ts +2 -2
  46. package/lib/typescript/molecules/OptionCard.d.ts.map +1 -1
  47. package/lib/typescript/molecules/Stepper.d.ts +8 -8
  48. package/lib/typescript/molecules/Stepper.d.ts.map +1 -1
  49. package/lib/typescript/molecules/Steps.d.ts +3 -3
  50. package/lib/typescript/molecules/Steps.d.ts.map +1 -1
  51. package/lib/typescript/molecules/SymmetricGrid.d.ts +4 -4
  52. package/lib/typescript/molecules/SymmetricGrid.d.ts.map +1 -1
  53. package/lib/typescript/organisms/BottomSheet.d.ts +8 -8
  54. package/lib/typescript/organisms/BottomSheet.d.ts.map +1 -1
  55. package/lib/typescript/organisms/DateField.d.ts +10 -10
  56. package/lib/typescript/organisms/DateField.d.ts.map +1 -1
  57. package/lib/typescript/organisms/DatePicker.d.ts +10 -10
  58. package/lib/typescript/organisms/DatePicker.d.ts.map +1 -1
  59. package/lib/typescript/organisms/List.d.ts +6 -6
  60. package/lib/typescript/organisms/List.d.ts.map +1 -1
  61. package/lib/typescript/organisms/NavigationBar.d.ts +2 -2
  62. package/lib/typescript/organisms/NavigationBar.d.ts.map +1 -1
  63. package/lib/typescript/organisms/ReorderableList.d.ts +7 -7
  64. package/lib/typescript/organisms/ReorderableList.d.ts.map +1 -1
  65. package/lib/typescript/organisms/Screen.d.ts +6 -6
  66. package/lib/typescript/organisms/SearchList.d.ts +8 -8
  67. package/lib/typescript/organisms/SearchList.d.ts.map +1 -1
  68. package/lib/typescript/organisms/Select.d.ts +2 -2
  69. package/lib/typescript/organisms/Select.d.ts.map +1 -1
  70. package/lib/typescript/organisms/Slides.d.ts +69 -0
  71. package/lib/typescript/organisms/Slides.d.ts.map +1 -0
  72. package/lib/typescript/organisms/Toast.d.ts +2 -2
  73. package/lib/typescript/organisms/Toast.d.ts.map +1 -1
  74. package/lib/typescript/organisms/ToastProvider.d.ts +2 -2
  75. package/lib/typescript/organisms/ToastProvider.d.ts.map +1 -1
  76. package/lib/typescript/organisms/Wizard.d.ts +7 -7
  77. package/lib/typescript/organisms/Wizard.d.ts.map +1 -1
  78. package/lib/typescript/organisms/index.d.ts +2 -0
  79. package/lib/typescript/organisms/index.d.ts.map +1 -1
  80. package/lib/typescript/organisms/useSlides.d.ts +52 -0
  81. package/lib/typescript/organisms/useSlides.d.ts.map +1 -0
  82. package/lib/typescript/theme/UIProvider.d.ts +9 -9
  83. package/lib/typescript/theme/UIProvider.d.ts.map +1 -1
  84. package/llms.txt +34 -0
  85. package/package.json +34 -6
  86. package/registry/avatar-header.json +1 -1
  87. package/registry/badge.json +1 -1
  88. package/registry/bottom-sheet.json +1 -1
  89. package/registry/core.json +1 -1
  90. package/registry/date-field.json +1 -1
  91. package/registry/date-picker.json +1 -1
  92. package/registry/form-actions.json +1 -1
  93. package/registry/gradient.json +18 -0
  94. package/registry/image.json +1 -1
  95. package/registry/index.json +52 -26
  96. package/registry/input.json +1 -1
  97. package/registry/list.json +1 -1
  98. package/registry/manifest.json +14 -0
  99. package/registry/navigation-bar.json +1 -1
  100. package/registry/option-card.json +1 -1
  101. package/registry/pressable-scale.json +1 -1
  102. package/registry/reorderable-list.json +1 -1
  103. package/registry/screen.json +1 -1
  104. package/registry/search-list.json +1 -1
  105. package/registry/select.json +1 -1
  106. package/registry/skeleton.json +1 -1
  107. package/registry/slides.json +24 -0
  108. package/registry/spinner.json +1 -1
  109. package/registry/stepper.json +1 -1
  110. package/registry/steps.json +1 -1
  111. package/registry/symmetric-grid.json +1 -1
  112. package/registry/toast-provider.json +1 -1
  113. package/registry/toast.json +1 -1
  114. package/registry/transition.json +1 -1
  115. package/registry/wizard.json +1 -1
  116. package/src/atoms/Badge.tsx +1 -1
  117. package/src/atoms/Gradient.tsx +99 -0
  118. package/src/atoms/Image.tsx +6 -6
  119. package/src/atoms/Input.tsx +1 -1
  120. package/src/atoms/PressableScale.tsx +1 -1
  121. package/src/atoms/Skeleton.tsx +5 -5
  122. package/src/atoms/Spinner.tsx +4 -4
  123. package/src/atoms/Transition.tsx +10 -10
  124. package/src/atoms/index.ts +2 -0
  125. package/src/molecules/AvatarHeader.tsx +2 -2
  126. package/src/molecules/FormActions.tsx +1 -1
  127. package/src/molecules/OptionCard.tsx +2 -2
  128. package/src/molecules/Stepper.tsx +8 -8
  129. package/src/molecules/Steps.tsx +3 -3
  130. package/src/molecules/SymmetricGrid.tsx +4 -4
  131. package/src/organisms/BottomSheet.tsx +8 -8
  132. package/src/organisms/DateField.tsx +10 -10
  133. package/src/organisms/DatePicker.tsx +10 -10
  134. package/src/organisms/List.tsx +6 -6
  135. package/src/organisms/NavigationBar.tsx +2 -2
  136. package/src/organisms/ReorderableList.tsx +7 -7
  137. package/src/organisms/Screen.tsx +6 -6
  138. package/src/organisms/SearchList.tsx +8 -8
  139. package/src/organisms/Select.tsx +2 -2
  140. package/src/organisms/Slides.tsx +413 -0
  141. package/src/organisms/Toast.tsx +2 -2
  142. package/src/organisms/ToastProvider.tsx +2 -2
  143. package/src/organisms/Wizard.tsx +7 -7
  144. package/src/organisms/index.ts +8 -0
  145. package/src/organisms/useSlides.ts +220 -0
  146. package/src/theme/UIProvider.tsx +9 -9
@@ -19,7 +19,7 @@ import { IconButton } from '../atoms/IconButton';
19
19
  import type { IconName } from '../icons/types';
20
20
 
21
21
  export interface SearchListProps<T> {
22
- /** Chrome opcional arriba del buscador (ej. back button + título). */
22
+ /** Optional chrome above the search bar (e.g. back button + title). */
23
23
  header?: React.ReactNode;
24
24
 
25
25
  searchValue: string;
@@ -34,23 +34,23 @@ export interface SearchListProps<T> {
34
34
  keyExtractor: (item: T, index: number) => string;
35
35
  renderItem: FlatListProps<T>['renderItem'];
36
36
 
37
- /** Carga inicial: mientras sea true y no haya datos, se muestran placeholders. */
37
+ /** Initial load: while true and there's no data, placeholders are shown. */
38
38
  isLoading?: boolean;
39
- /** 'replace' cambia toda la lista por un spinner/skeleton; 'overlay' lo muestra encima manteniendo el layout. */
39
+ /** 'replace' swaps the whole list for a spinner/skeleton; 'overlay' shows it on top while keeping the layout. */
40
40
  loadingMode?: 'replace' | 'overlay';
41
- /** Recarga con datos ya en pantalla (pull-to-refresh). @default false */
41
+ /** Reload with data already on screen (pull-to-refresh). @default false */
42
42
  isRefreshing?: boolean;
43
43
  isLoadingMore?: boolean;
44
- /** Controlado por el consumidor: mientras es false, se muestra un spinner de pantalla completa. @default true */
44
+ /** Controlled by the consumer: while false, a fullscreen spinner is shown. @default true */
45
45
  isReady?: boolean;
46
46
  loadingText?: string;
47
47
  loadingMoreText?: string;
48
48
  hasMore?: boolean;
49
49
  noMoreText?: string;
50
50
 
51
- /** Filas fantasma de la carga inicial. @default 6 */
51
+ /** Ghost rows for the initial load. @default 6 */
52
52
  skeletonCount?: number;
53
- /** Placeholder propio, para que calce con la forma real del ítem. */
53
+ /** Custom placeholder, to match the real shape of the item. */
54
54
  renderSkeletonItem?: () => React.ReactElement;
55
55
 
56
56
  onLoadMore?: () => void;
@@ -68,7 +68,7 @@ export interface SearchListProps<T> {
68
68
  contentContainerStyle?: StyleProp<ViewStyle>;
69
69
  containerStyle?: ViewStyle;
70
70
 
71
- /** Componente de lista a usar (FlashList, etc). @default FlatList */
71
+ /** List component to use (FlashList, etc). @default FlatList */
72
72
  ListComponent?: React.ComponentType<any>;
73
73
  listProps?: Record<string, unknown>;
74
74
  }
@@ -38,10 +38,10 @@ export interface SelectProps<T> {
38
38
  error?: string;
39
39
  isLoading?: boolean;
40
40
  disabled?: boolean;
41
- /** Agrega un buscador dentro del desplegable. @default false */
41
+ /** Adds a search box inside the dropdown. @default false */
42
42
  searchable?: boolean;
43
43
  searchPlaceholder?: string;
44
- /** Alto máximo del desplegable, si el espacio disponible lo permite. @default 280 */
44
+ /** Maximum height of the dropdown, if the available space allows it. @default 280 */
45
45
  maxHeight?: number;
46
46
  onOpenChange?: (open: boolean) => void;
47
47
  containerStyle?: StyleProp<ViewStyle>;
@@ -0,0 +1,413 @@
1
+ import React, { memo, useEffect, useRef } from 'react';
2
+ import {
3
+ Animated,
4
+ Easing,
5
+ ScrollView,
6
+ StyleSheet,
7
+ Text,
8
+ TouchableOpacity,
9
+ View,
10
+ type StyleProp,
11
+ type ViewStyle,
12
+ } from 'react-native';
13
+ import { createStyles } from '../theme/createStyles';
14
+ import { useAllowFontScaling, useColors, useTheme } from '../theme/UIProvider';
15
+ import { useReduceMotion } from '../atoms/Transition';
16
+ import { Gradient, type GradientDirection } from '../atoms/Gradient';
17
+ import { useSlides, type SlidesOrientation } from './useSlides';
18
+
19
+ export type { SlidesOrientation } from './useSlides';
20
+ export type SlidesIndicators = 'dots' | 'numbers' | 'none';
21
+ export type SlidesIndicatorPlacement = 'overlay' | 'outside';
22
+
23
+ /** Un color sólido, o dos o más para un degradado (ver `Gradient`). */
24
+ export type SlideBackground = string | string[];
25
+
26
+ export interface SlidesProps<T> {
27
+ data: T[];
28
+ keyExtractor: (item: T, index: number) => string;
29
+ /** Contenido de la slide: imágenes, botones, lo que sea. Se dibuja sobre `background`. */
30
+ renderItem: (item: T, index: number) => React.ReactNode;
31
+ /** Fondo por slide: `'#101014'` pinta plano, `['#004cef', '#00cae1']` degrada. */
32
+ background?: (item: T, index: number) => SlideBackground | undefined;
33
+ /** Dirección del degradado cuando `background` devuelve varios colores. @default 'vertical' */
34
+ gradientDirection?: GradientDirection;
35
+ /** @default 'horizontal' */
36
+ orientation?: SlidesOrientation;
37
+ /** Círculos, círculos numerados, o ninguno. @default 'dots' */
38
+ indicators?: SlidesIndicators;
39
+ /**
40
+ * 'overlay' los apoya sobre la slide, en blanco; 'outside' los saca fuera
41
+ * del área deslizable, con los colores del tema.
42
+ * @default 'overlay'
43
+ */
44
+ indicatorPlacement?: SlidesIndicatorPlacement;
45
+ /** Después de la última vuelve a la primera (y al revés). @default false */
46
+ loop?: boolean;
47
+ /** Avanza solo. Se pausa mientras el dedo arrastra. @default false */
48
+ autoPlay?: boolean;
49
+ /** ms entre avances de `autoPlay`. @default 4000 */
50
+ interval?: number;
51
+ /**
52
+ * Botón para frenar el avance automático. Sólo aparece con `autoPlay`, y va
53
+ * en `true` porque contenido que se mueve solo necesita una forma de
54
+ * pausarlo (WCAG 2.2.2).
55
+ * @default true
56
+ */
57
+ showAutoPlayToggle?: boolean;
58
+ /** Hueco entre slides, en px. En 0 quedan pegadas. @default 0 */
59
+ spacing?: number;
60
+ /** Índice controlado. Sin él, el componente maneja el suyo desde `defaultIndex`. */
61
+ index?: number;
62
+ /** @default 0 */
63
+ defaultIndex?: number;
64
+ onIndexChange?: (index: number) => void;
65
+ /** Alto del área deslizable. @default 240 */
66
+ height?: number;
67
+ /** @default true */
68
+ swipeEnabled?: boolean;
69
+ /** Estilos de la caja visible de cada slide — padding, alineación, radio. */
70
+ slideStyle?: StyleProp<ViewStyle>;
71
+ style?: StyleProp<ViewStyle>;
72
+ testID?: string;
73
+ }
74
+
75
+ const DOT = 8;
76
+ const DOT_ACTIVE = 20;
77
+ const NUMBER_CIRCLE = 24;
78
+ const TOGGLE = 32;
79
+ const DEFAULT_HEIGHT = 240;
80
+ const DEFAULT_INTERVAL = 4000;
81
+
82
+ const useStyles = createStyles((theme) => ({
83
+ // Deja redondear el bloque por `style` sin que el degradado se salga.
84
+ root: { overflow: 'hidden' },
85
+ viewport: { flex: 1 },
86
+ // La slide visible es la caja de adentro: con `spacing` la página es más
87
+ // ancha que ella, y el fondo tiene que recortarse acá y no en la página, o
88
+ // el degradado pinta también el hueco.
89
+ slide: { flex: 1, overflow: 'hidden' },
90
+ // Con indicadores fuera y orientación vertical el contenedor es una fila:
91
+ // sin flex la ventana no tiene ancho propio y colapsa a 0.
92
+ viewportInRow: { flex: 1 },
93
+ slideContent: { flex: 1 },
94
+
95
+ indicatorsRow: { flexDirection: 'row', alignItems: 'center', justifyContent: 'center' },
96
+ indicatorsColumn: { flexDirection: 'column', alignItems: 'center', justifyContent: 'center' },
97
+ indicatorsOverlayHorizontal: { position: 'absolute', left: 0, right: 0, bottom: theme.tokens.spacing.md },
98
+ indicatorsOverlayVertical: { position: 'absolute', top: 0, bottom: 0, right: theme.tokens.spacing.md },
99
+ indicatorsOutsideHorizontal: { paddingTop: theme.tokens.spacing.md },
100
+ indicatorsOutsideVertical: { paddingLeft: theme.tokens.spacing.md },
101
+ indicatorSlot: { padding: theme.tokens.spacing.xs },
102
+
103
+ dot: { width: DOT, height: DOT, borderRadius: DOT / 2 },
104
+
105
+ autoPlayToggle: {
106
+ position: 'absolute',
107
+ top: theme.tokens.spacing.md,
108
+ right: theme.tokens.spacing.md,
109
+ width: TOGGLE,
110
+ height: TOGGLE,
111
+ borderRadius: TOGGLE / 2,
112
+ alignItems: 'center',
113
+ justifyContent: 'center',
114
+ flexDirection: 'row',
115
+ gap: 3,
116
+ },
117
+ pauseBar: { width: 3, height: 12, borderRadius: 1 },
118
+ // Triángulo con bordes: un "play" sin SVG ni set de íconos.
119
+ playTriangle: {
120
+ width: 0,
121
+ height: 0,
122
+ marginLeft: 2,
123
+ borderTopWidth: 6,
124
+ borderBottomWidth: 6,
125
+ borderLeftWidth: 10,
126
+ borderTopColor: 'transparent',
127
+ borderBottomColor: 'transparent',
128
+ },
129
+
130
+ numberCircle: {
131
+ minWidth: NUMBER_CIRCLE,
132
+ height: NUMBER_CIRCLE,
133
+ borderRadius: NUMBER_CIRCLE / 2,
134
+ borderWidth: 1,
135
+ paddingHorizontal: theme.tokens.spacing.xs,
136
+ alignItems: 'center',
137
+ justifyContent: 'center',
138
+ },
139
+ numberText: { fontSize: theme.tokens.fontSize.xs, fontWeight: theme.tokens.fontWeight.semibold },
140
+
141
+ outsideHorizontal: { flexDirection: 'column' },
142
+ outsideVertical: { flexDirection: 'row' },
143
+ }));
144
+
145
+ interface DotProps {
146
+ selected: boolean;
147
+ isHorizontal: boolean;
148
+ /** En overlay el contraste lo da la opacidad; afuera, el color. */
149
+ overlay: boolean;
150
+ activeColor: string;
151
+ inactiveColor: string;
152
+ testID?: string;
153
+ }
154
+
155
+ /**
156
+ * El punto activo se estira en vez de saltar: el cambio de golpe se lee como
157
+ * "apareció otro punto", no como "el mismo se movió". Ancho y color son props
158
+ * de layout/pintura, así que van por el driver de JS.
159
+ */
160
+ const Dot = memo(({ selected, isHorizontal, overlay, activeColor, inactiveColor, testID }: DotProps) => {
161
+ const styles = useStyles();
162
+ const theme = useTheme();
163
+ const reduceMotion = useReduceMotion();
164
+ const progress = useRef(new Animated.Value(selected ? 1 : 0)).current;
165
+
166
+ useEffect(() => {
167
+ Animated.timing(progress, {
168
+ toValue: selected ? 1 : 0,
169
+ duration: reduceMotion ? 0 : theme.tokens.duration.base,
170
+ easing: Easing.out(Easing.quad),
171
+ useNativeDriver: false,
172
+ }).start();
173
+ }, [progress, reduceMotion, selected, theme]);
174
+
175
+ const length = progress.interpolate({ inputRange: [0, 1], outputRange: [DOT, DOT_ACTIVE] });
176
+ const backgroundColor = progress.interpolate({ inputRange: [0, 1], outputRange: [inactiveColor, activeColor] });
177
+
178
+ return (
179
+ <Animated.View
180
+ testID={testID}
181
+ style={[
182
+ styles.dot,
183
+ isHorizontal ? { width: length } : { height: length },
184
+ { backgroundColor },
185
+ overlay && { opacity: progress.interpolate({ inputRange: [0, 1], outputRange: [0.45, 1] }) },
186
+ ]}
187
+ />
188
+ );
189
+ });
190
+ Dot.displayName = 'SlidesDot';
191
+
192
+ /**
193
+ * Slides
194
+ * Carrusel con fondo propio (color plano o degradado) y contenido libre:
195
+ * `renderItem` decide si adentro va una imagen, texto o botones. El estado
196
+ * vive en `useSlides`; acá sólo está el árbol.
197
+ *
198
+ * El deslizamiento es un `ScrollView` con `pagingEnabled` en vez de gestos a
199
+ * mano: es lo único que da snap nativo en iOS y Android sin
200
+ * `react-native-gesture-handler`, en todo el rango soportado (Expo SDK 54–57).
201
+ */
202
+ export function Slides<T>({
203
+ data,
204
+ keyExtractor,
205
+ renderItem,
206
+ background,
207
+ gradientDirection = 'vertical',
208
+ orientation = 'horizontal',
209
+ indicators = 'dots',
210
+ indicatorPlacement = 'overlay',
211
+ loop = false,
212
+ autoPlay = false,
213
+ interval = DEFAULT_INTERVAL,
214
+ showAutoPlayToggle = true,
215
+ spacing = 0,
216
+ index,
217
+ defaultIndex = 0,
218
+ onIndexChange,
219
+ height = DEFAULT_HEIGHT,
220
+ swipeEnabled = true,
221
+ slideStyle,
222
+ style,
223
+ testID,
224
+ }: SlidesProps<T>) {
225
+ const colors = useColors();
226
+ const styles = useStyles();
227
+ const allowFontScaling = useAllowFontScaling();
228
+
229
+ const total = data.length;
230
+ const {
231
+ active,
232
+ pages,
233
+ isHorizontal,
234
+ size,
235
+ pageSize,
236
+ scrollRef,
237
+ autoPlayPaused,
238
+ motionReduced,
239
+ goTo,
240
+ toggleAutoPlay,
241
+ onLayout,
242
+ onMomentumScrollEnd,
243
+ onScrollBeginDrag,
244
+ onScrollEndDrag,
245
+ } = useSlides({ total, orientation, loop, autoPlay, interval, index, defaultIndex, onIndexChange });
246
+
247
+ const isOverlay = indicatorPlacement === 'overlay';
248
+
249
+ const renderBackground = (item: T, real: number) => {
250
+ const value = background?.(item, real);
251
+ if (!value) return null;
252
+ if (Array.isArray(value)) {
253
+ return <Gradient colors={value} direction={gradientDirection} style={StyleSheet.absoluteFill} />;
254
+ }
255
+ return <View style={[StyleSheet.absoluteFill, { backgroundColor: value }]} pointerEvents="none" />;
256
+ };
257
+
258
+ const renderIndicator = (position: number) => {
259
+ const selected = position === active;
260
+
261
+ if (indicators === 'numbers') {
262
+ const numberColors = isOverlay
263
+ ? {
264
+ background: selected ? colors.white : 'transparent',
265
+ border: colors.white,
266
+ text: selected ? colors.black : colors.white,
267
+ }
268
+ : {
269
+ background: selected ? colors.primary : 'transparent',
270
+ border: selected ? colors.primary : colors.border,
271
+ text: selected ? colors.onPrimary : colors.textLight,
272
+ };
273
+ return (
274
+ <View
275
+ style={[
276
+ styles.numberCircle,
277
+ { backgroundColor: numberColors.background, borderColor: numberColors.border },
278
+ ]}
279
+ >
280
+ <Text allowFontScaling={allowFontScaling} style={[styles.numberText, { color: numberColors.text }]}>
281
+ {position + 1}
282
+ </Text>
283
+ </View>
284
+ );
285
+ }
286
+
287
+ return (
288
+ <Dot
289
+ testID={`${testID ?? 'slides'}-dot-${position}`}
290
+ selected={selected}
291
+ isHorizontal={isHorizontal}
292
+ overlay={isOverlay}
293
+ activeColor={isOverlay ? colors.white : colors.primary}
294
+ inactiveColor={isOverlay ? colors.white : colors.border}
295
+ />
296
+ );
297
+ };
298
+
299
+ // Con reduce-motion el autoPlay ya está apagado: no hay nada que pausar.
300
+ const renderAutoPlayToggle = () => {
301
+ if (!autoPlay || !showAutoPlayToggle || motionReduced || total < 2) return null;
302
+ return (
303
+ <TouchableOpacity
304
+ testID={`${testID ?? 'slides'}-autoplay-toggle`}
305
+ style={[styles.autoPlayToggle, { backgroundColor: colors.black, opacity: 0.55 }]}
306
+ activeOpacity={0.7}
307
+ onPress={toggleAutoPlay}
308
+ accessibilityRole="button"
309
+ accessibilityLabel={autoPlayPaused ? 'Resume automatic slides' : 'Pause automatic slides'}
310
+ accessibilityState={{ selected: !autoPlayPaused }}
311
+ >
312
+ {autoPlayPaused ? (
313
+ <View style={[styles.playTriangle, { borderLeftColor: colors.white }]} />
314
+ ) : (
315
+ <>
316
+ <View style={[styles.pauseBar, { backgroundColor: colors.white }]} />
317
+ <View style={[styles.pauseBar, { backgroundColor: colors.white }]} />
318
+ </>
319
+ )}
320
+ </TouchableOpacity>
321
+ );
322
+ };
323
+
324
+ const renderIndicators = () => {
325
+ if (indicators === 'none' || total < 2) return null;
326
+ return (
327
+ <View
328
+ testID={`${testID ?? 'slides'}-indicators`}
329
+ style={[
330
+ isHorizontal ? styles.indicatorsRow : styles.indicatorsColumn,
331
+ isOverlay
332
+ ? isHorizontal
333
+ ? styles.indicatorsOverlayHorizontal
334
+ : styles.indicatorsOverlayVertical
335
+ : isHorizontal
336
+ ? styles.indicatorsOutsideHorizontal
337
+ : styles.indicatorsOutsideVertical,
338
+ ]}
339
+ pointerEvents="box-none"
340
+ >
341
+ {data.map((item, position) => (
342
+ <TouchableOpacity
343
+ key={`indicator-${keyExtractor(item, position)}`}
344
+ testID={`${testID ?? 'slides'}-indicator-${position}`}
345
+ style={styles.indicatorSlot}
346
+ activeOpacity={0.7}
347
+ onPress={() => goTo(position)}
348
+ accessibilityRole="button"
349
+ accessibilityLabel={`Slide ${position + 1} of ${total}`}
350
+ accessibilityState={{ selected: position === active }}
351
+ >
352
+ {renderIndicator(position)}
353
+ </TouchableOpacity>
354
+ ))}
355
+ </View>
356
+ );
357
+ };
358
+
359
+ const viewport = (
360
+ <View
361
+ testID={testID}
362
+ style={[styles.root, { height }, !isOverlay && !isHorizontal && styles.viewportInRow, isOverlay && style]}
363
+ onLayout={onLayout}
364
+ >
365
+ <ScrollView
366
+ ref={scrollRef}
367
+ testID={`${testID ?? 'slides'}-scroll`}
368
+ style={styles.viewport}
369
+ horizontal={isHorizontal}
370
+ pagingEnabled
371
+ scrollEnabled={swipeEnabled && total > 1}
372
+ showsHorizontalScrollIndicator={false}
373
+ showsVerticalScrollIndicator={false}
374
+ onMomentumScrollEnd={onMomentumScrollEnd}
375
+ onScrollBeginDrag={onScrollBeginDrag}
376
+ onScrollEndDrag={onScrollEndDrag}
377
+ >
378
+ {pageSize > 0 &&
379
+ pages.map(({ real, clone }) => {
380
+ const item = data[real] as T;
381
+ return (
382
+ // El hueco se reparte a los dos lados de cada página: así la
383
+ // separación entre dos slides es `spacing` entero y la página
384
+ // sigue midiendo exactamente lo que pagina el ScrollView.
385
+ <View
386
+ key={clone ? `slides-clone-${clone}` : keyExtractor(item, real)}
387
+ style={[
388
+ { width: size.width, height: size.height },
389
+ isHorizontal ? { paddingHorizontal: spacing / 2 } : { paddingVertical: spacing / 2 },
390
+ ]}
391
+ >
392
+ <View style={[styles.slide, slideStyle]}>
393
+ {renderBackground(item, real)}
394
+ <View style={styles.slideContent}>{renderItem(item, real)}</View>
395
+ </View>
396
+ </View>
397
+ );
398
+ })}
399
+ </ScrollView>
400
+ {renderAutoPlayToggle()}
401
+ {isOverlay && renderIndicators()}
402
+ </View>
403
+ );
404
+
405
+ if (isOverlay) return viewport;
406
+
407
+ return (
408
+ <View style={[isHorizontal ? styles.outsideHorizontal : styles.outsideVertical, style]}>
409
+ {viewport}
410
+ {renderIndicators()}
411
+ </View>
412
+ );
413
+ }
@@ -12,10 +12,10 @@ export interface ToastProps {
12
12
  title: string;
13
13
  message?: string;
14
14
  variant?: ToastVariant;
15
- /** Se dispara al tocar el toast (además de cerrarlo). */
15
+ /** Fires when the toast is tapped (in addition to closing it). */
16
16
  onPress?: () => void;
17
17
  onDismiss?: () => void;
18
- /** Oculta la X de cerrar. El toast igual se auto-descarta por tiempo. */
18
+ /** Hides the close X. The toast still auto-dismisses by timeout. */
19
19
  hideCloseButton?: boolean;
20
20
  style?: StyleProp<ViewStyle>;
21
21
  testID?: string;
@@ -54,9 +54,9 @@ export interface ToastProviderProps {
54
54
  children: React.ReactNode;
55
55
  /** @default 'top' */
56
56
  position?: 'top' | 'bottom';
57
- /** Máximo de toasts simultáneos; los más viejos se descartan. @default 3 */
57
+ /** Maximum simultaneous toasts; the oldest ones are dropped. @default 3 */
58
58
  maxVisible?: number;
59
- /** Duración por defecto en ms. @default 4000 */
59
+ /** Default duration in ms. @default 4000 */
60
60
  defaultDuration?: number;
61
61
  }
62
62
 
@@ -13,28 +13,28 @@ export interface WizardStep extends StepItem {
13
13
 
14
14
  export interface WizardProps {
15
15
  steps: WizardStep[];
16
- /** Paso actual (controlado). Si se omite, el Wizard maneja su propio estado. */
16
+ /** Current step (controlled). If omitted, the Wizard manages its own state. */
17
17
  current?: number;
18
18
  onStepChange?: (index: number) => void;
19
19
  onFinish?: () => void;
20
20
  /** @default 'horizontal' */
21
21
  orientation?: 'horizontal' | 'vertical';
22
22
  completedIndicator?: 'check' | 'number';
23
- /** Permite volver a un paso ya completado tocándolo en el indicador. @default true */
23
+ /** Allows returning to an already-completed step by tapping it in the indicator. @default true */
24
24
  allowStepNavigation?: boolean;
25
25
  backLabel?: string;
26
26
  nextLabel?: string;
27
27
  finishLabel?: string;
28
28
  /**
29
- * El contenido del paso scrollea cuando no entra. Ponelo en false sólo si
30
- * un paso renderiza su propia lista virtualizada (anidar una
31
- * VirtualizedList dentro de un ScrollView rompe la virtualización).
29
+ * The step content scrolls when it doesn't fit. Set to false only if
30
+ * a step renders its own virtualized list (nesting a
31
+ * VirtualizedList inside a ScrollView breaks virtualization).
32
32
  * @default true
33
33
  */
34
34
  scrollableContent?: boolean;
35
35
  /**
36
- * El contenido del paso entra deslizándose en el sentido de la navegación.
37
- * Sólo opacity y transform, así que la animación corre en el hilo nativo.
36
+ * The step content slides in following the navigation direction.
37
+ * Only opacity and transform, so the animation runs on the native thread.
38
38
  * @default true
39
39
  */
40
40
  animated?: boolean;
@@ -30,3 +30,11 @@ export { NavigationBar } from './NavigationBar';
30
30
  export type { NavigationBarProps, NavigationBarItem } from './NavigationBar';
31
31
  export { ReorderableList } from './ReorderableList';
32
32
  export type { ReorderableListProps } from './ReorderableList';
33
+ export { Slides } from './Slides';
34
+ export type {
35
+ SlidesProps,
36
+ SlidesOrientation,
37
+ SlidesIndicators,
38
+ SlidesIndicatorPlacement,
39
+ SlideBackground,
40
+ } from './Slides';