orn-ui 0.2.7 → 0.2.9
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/AGENTS.md +25 -3
- package/README.md +9 -8
- package/lib/commonjs/atoms/Gradient.js +111 -0
- package/lib/commonjs/atoms/Gradient.js.map +1 -0
- package/lib/commonjs/atoms/ShinyText.js +188 -0
- package/lib/commonjs/atoms/ShinyText.js.map +1 -0
- package/lib/commonjs/atoms/index.js +14 -0
- package/lib/commonjs/atoms/index.js.map +1 -1
- package/lib/commonjs/organisms/Chart.js +947 -0
- package/lib/commonjs/organisms/Chart.js.map +1 -0
- package/lib/commonjs/organisms/OptionWheel.js +408 -0
- package/lib/commonjs/organisms/OptionWheel.js.map +1 -0
- package/lib/commonjs/organisms/Slides.js +406 -0
- package/lib/commonjs/organisms/Slides.js.map +1 -0
- package/lib/commonjs/organisms/Timeline.js +420 -0
- package/lib/commonjs/organisms/Timeline.js.map +1 -0
- package/lib/commonjs/organisms/chart/frame.js +373 -0
- package/lib/commonjs/organisms/chart/frame.js.map +1 -0
- package/lib/commonjs/organisms/chart/marks.js +316 -0
- package/lib/commonjs/organisms/chart/marks.js.map +1 -0
- package/lib/commonjs/organisms/chart/math.js +468 -0
- package/lib/commonjs/organisms/chart/math.js.map +1 -0
- package/lib/commonjs/organisms/chart/palette.js +120 -0
- package/lib/commonjs/organisms/chart/palette.js.map +1 -0
- package/lib/commonjs/organisms/index.js +28 -0
- package/lib/commonjs/organisms/index.js.map +1 -1
- package/lib/commonjs/organisms/useSlides.js +186 -0
- package/lib/commonjs/organisms/useSlides.js.map +1 -0
- package/lib/module/atoms/Gradient.js +106 -0
- package/lib/module/atoms/Gradient.js.map +1 -0
- package/lib/module/atoms/ShinyText.js +183 -0
- package/lib/module/atoms/ShinyText.js.map +1 -0
- package/lib/module/atoms/index.js +2 -0
- package/lib/module/atoms/index.js.map +1 -1
- package/lib/module/organisms/Chart.js +942 -0
- package/lib/module/organisms/Chart.js.map +1 -0
- package/lib/module/organisms/OptionWheel.js +403 -0
- package/lib/module/organisms/OptionWheel.js.map +1 -0
- package/lib/module/organisms/Slides.js +401 -0
- package/lib/module/organisms/Slides.js.map +1 -0
- package/lib/module/organisms/Timeline.js +415 -0
- package/lib/module/organisms/Timeline.js.map +1 -0
- package/lib/module/organisms/chart/frame.js +367 -0
- package/lib/module/organisms/chart/frame.js.map +1 -0
- package/lib/module/organisms/chart/marks.js +310 -0
- package/lib/module/organisms/chart/marks.js.map +1 -0
- package/lib/module/organisms/chart/math.js +445 -0
- package/lib/module/organisms/chart/math.js.map +1 -0
- package/lib/module/organisms/chart/palette.js +114 -0
- package/lib/module/organisms/chart/palette.js.map +1 -0
- package/lib/module/organisms/index.js +4 -0
- package/lib/module/organisms/index.js.map +1 -1
- package/lib/module/organisms/useSlides.js +182 -0
- package/lib/module/organisms/useSlides.js.map +1 -0
- package/lib/typescript/atoms/Gradient.d.ts +37 -0
- package/lib/typescript/atoms/Gradient.d.ts.map +1 -0
- package/lib/typescript/atoms/ShinyText.d.ts +48 -0
- package/lib/typescript/atoms/ShinyText.d.ts.map +1 -0
- package/lib/typescript/atoms/index.d.ts +4 -0
- package/lib/typescript/atoms/index.d.ts.map +1 -1
- package/lib/typescript/organisms/Chart.d.ts +208 -0
- package/lib/typescript/organisms/Chart.d.ts.map +1 -0
- package/lib/typescript/organisms/OptionWheel.d.ts +78 -0
- package/lib/typescript/organisms/OptionWheel.d.ts.map +1 -0
- package/lib/typescript/organisms/Slides.d.ts +69 -0
- package/lib/typescript/organisms/Slides.d.ts.map +1 -0
- package/lib/typescript/organisms/Timeline.d.ts +61 -0
- package/lib/typescript/organisms/Timeline.d.ts.map +1 -0
- package/lib/typescript/organisms/chart/frame.d.ts +85 -0
- package/lib/typescript/organisms/chart/frame.d.ts.map +1 -0
- package/lib/typescript/organisms/chart/marks.d.ts +88 -0
- package/lib/typescript/organisms/chart/marks.d.ts.map +1 -0
- package/lib/typescript/organisms/chart/math.d.ts +161 -0
- package/lib/typescript/organisms/chart/math.d.ts.map +1 -0
- package/lib/typescript/organisms/chart/palette.d.ts +6 -0
- package/lib/typescript/organisms/chart/palette.d.ts.map +1 -0
- package/lib/typescript/organisms/index.d.ts +8 -0
- package/lib/typescript/organisms/index.d.ts.map +1 -1
- package/lib/typescript/organisms/useSlides.d.ts +52 -0
- package/lib/typescript/organisms/useSlides.d.ts.map +1 -0
- package/llms.txt +6 -5
- package/package.json +33 -2
- package/registry/chart.json +35 -0
- package/registry/gradient.json +18 -0
- package/registry/index.json +79 -0
- package/registry/manifest.json +42 -0
- package/registry/option-wheel.json +19 -0
- package/registry/shiny-text.json +19 -0
- package/registry/slides.json +24 -0
- package/registry/timeline.json +20 -0
- package/src/atoms/Gradient.tsx +99 -0
- package/src/atoms/ShinyText.tsx +226 -0
- package/src/atoms/index.ts +4 -0
- package/src/organisms/Chart.tsx +1223 -0
- package/src/organisms/OptionWheel.tsx +476 -0
- package/src/organisms/Slides.tsx +413 -0
- package/src/organisms/Timeline.tsx +467 -0
- package/src/organisms/chart/frame.tsx +349 -0
- package/src/organisms/chart/marks.tsx +295 -0
- package/src/organisms/chart/math.ts +475 -0
- package/src/organisms/chart/palette.ts +112 -0
- package/src/organisms/index.ts +22 -0
- package/src/organisms/useSlides.ts +220 -0
|
@@ -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
|
+
}
|