uo287827-react-native-tour-component 1.0.0
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/README.md +175 -0
- package/dist/components/tour-highlight.component.d.ts +9 -0
- package/dist/components/tour-highlight.component.d.ts.map +1 -0
- package/dist/components/tour-highlight.component.js +23 -0
- package/dist/components/tour-overlay.component.d.ts +16 -0
- package/dist/components/tour-overlay.component.d.ts.map +1 -0
- package/dist/components/tour-overlay.component.js +51 -0
- package/dist/components/tour-tooltip-position.component.d.ts +7 -0
- package/dist/components/tour-tooltip-position.component.d.ts.map +1 -0
- package/dist/components/tour-tooltip-position.component.js +16 -0
- package/dist/components/tour-tooltip.component.d.ts +19 -0
- package/dist/components/tour-tooltip.component.d.ts.map +1 -0
- package/dist/components/tour-tooltip.component.js +60 -0
- package/dist/components/tour.component.d.ts +22 -0
- package/dist/components/tour.component.d.ts.map +1 -0
- package/dist/components/tour.component.js +32 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/service/tour-theme.context.d.ts +8 -0
- package/dist/service/tour-theme.context.d.ts.map +1 -0
- package/dist/service/tour-theme.context.js +6 -0
- package/dist/service/tour.context.d.ts +8 -0
- package/dist/service/tour.context.d.ts.map +1 -0
- package/dist/service/tour.context.js +8 -0
- package/dist/tour.types.d.ts +58 -0
- package/dist/tour.types.d.ts.map +1 -0
- package/dist/tour.types.js +14 -0
- package/dist/utils/tour-tooltip-position.d.ts +8 -0
- package/dist/utils/tour-tooltip-position.d.ts.map +1 -0
- package/dist/utils/tour-tooltip-position.js +16 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# feature-tour
|
|
2
|
+
|
|
3
|
+
Componente de tour/onboarding para React Native.
|
|
4
|
+
|
|
5
|
+
## Instalación
|
|
6
|
+
|
|
7
|
+
Requiere `react-native-svg`:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx expo install react-native-svg
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Uso básico
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { useRef } from 'react';
|
|
17
|
+
import { View, Text, Button } from 'react-native';
|
|
18
|
+
import { Tour, TourProvider, TourHandle, TourStepConfig } from './feature-tour';
|
|
19
|
+
|
|
20
|
+
export default function MyScreen() {
|
|
21
|
+
// tourRef expone start() y stop() para controlar el tour de forma imperativa
|
|
22
|
+
const tourRef = useRef<TourHandle>(null);
|
|
23
|
+
|
|
24
|
+
// Cada ref se asigna al elemento de UI que el tour resaltará
|
|
25
|
+
const headerRef = useRef<View>(null);
|
|
26
|
+
const buttonRef = useRef<View>(null);
|
|
27
|
+
|
|
28
|
+
// Define la lista ordenada de pasos; cada paso apunta a un ref y contiene los datos del tooltip
|
|
29
|
+
const steps: TourStepConfig[] = [
|
|
30
|
+
{
|
|
31
|
+
id: 'header',
|
|
32
|
+
ref: headerRef, // El elemento a resaltar
|
|
33
|
+
title: 'Cabecera',
|
|
34
|
+
content: 'Aquí está el título principal.',
|
|
35
|
+
tooltipPosition: 'bottom', // El tooltip aparece debajo del elemento resaltado
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'button',
|
|
39
|
+
ref: buttonRef,
|
|
40
|
+
title: 'Acción',
|
|
41
|
+
content: 'Pulsa aquí para continuar.',
|
|
42
|
+
tooltipPosition: 'top', // El tooltip aparece encima del elemento resaltado
|
|
43
|
+
tooltipSize: 'lg', // Usa la variante de tooltip más ancha (320px en lugar de 260px)
|
|
44
|
+
},
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
// TourProvider provee el tema y el contexto compartido a todos los componentes del tour
|
|
49
|
+
<TourProvider>
|
|
50
|
+
{/* <Tour> renderiza el overlay y el tooltip; es invisible hasta que se llama a start() */}
|
|
51
|
+
<Tour ref={tourRef} steps={steps} />
|
|
52
|
+
|
|
53
|
+
{/* Asigna los refs a los elementos que quieres resaltar en cada paso */}
|
|
54
|
+
<Text ref={headerRef}>Mi app</Text>
|
|
55
|
+
|
|
56
|
+
{/* Llamar a tourRef.current.start() inicia el tour en el paso 0 */}
|
|
57
|
+
<Button title="Iniciar tour" onPress={() => tourRef.current?.start()} />
|
|
58
|
+
</TourProvider>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Props de `<Tour>`
|
|
64
|
+
|
|
65
|
+
| Prop | Tipo | Default | Descripción |
|
|
66
|
+
|---|---|---|---|
|
|
67
|
+
| `steps` | `TourStepConfig[]` | — | Pasos del tour |
|
|
68
|
+
| `skipLabel` | `string` | `'Saltar'` | Texto del botón "Saltar" |
|
|
69
|
+
| `nextLabel` | `string` | `'Siguiente'` | Texto del botón "Siguiente" |
|
|
70
|
+
| `finishLabel` | `string` | `'Finalizar'` | Texto del botón del último paso |
|
|
71
|
+
| `showSkipButton` | `boolean` | `true` | Muestra u oculta el botón de omitir |
|
|
72
|
+
|
|
73
|
+
Ejemplo con etiquetas en inglés:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// Sobreescribe las etiquetas predeterminadas
|
|
77
|
+
<Tour ref={tourRef} steps={steps} skipLabel="Skip" nextLabel="Next" finishLabel="Done" />
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Contenido personalizado en un paso
|
|
81
|
+
|
|
82
|
+
`content` acepta tanto un `string` (texto simple) como cualquier `ReactNode`:
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
// --- Opción 1: string simple ---
|
|
86
|
+
// El tooltip renderiza el texto directamente dentro de un componente <Text>.
|
|
87
|
+
{ id: 'step1', ref, title: 'Título', content: 'Descripción sencilla.' }
|
|
88
|
+
|
|
89
|
+
// --- Opción 2: ReactNode personalizado ---
|
|
90
|
+
// Cuando content es un ReactNode, se envuelve en un <View> simple;
|
|
91
|
+
// tienes control total sobre el layout y los estilos.
|
|
92
|
+
{
|
|
93
|
+
id: 'stats',
|
|
94
|
+
ref: statsRef,
|
|
95
|
+
title: 'Estadísticas',
|
|
96
|
+
content: (
|
|
97
|
+
<View style={{ gap: 4 }}>
|
|
98
|
+
{/* Fila de texto descriptivo */}
|
|
99
|
+
<Text style={{ fontSize: 13, color: '#475569' }}>Aquí ves un resumen de tu actividad.</Text>
|
|
100
|
+
|
|
101
|
+
{/* Fila de badges — cada badge es una píldora pequeña con fondo de color */}
|
|
102
|
+
<View style={{ flexDirection: 'row', gap: 8, marginTop: 4 }}>
|
|
103
|
+
<Text style={{ fontSize: 12, backgroundColor: '#e0f2fe', paddingHorizontal: 8,
|
|
104
|
+
paddingVertical: 2, borderRadius: 10 }}>12 proyectos</Text>
|
|
105
|
+
<Text style={{ fontSize: 12, backgroundColor: '#dcfce7', paddingHorizontal: 8,
|
|
106
|
+
paddingVertical: 2, borderRadius: 10 }}>48 tareas</Text>
|
|
107
|
+
</View>
|
|
108
|
+
</View>
|
|
109
|
+
),
|
|
110
|
+
tooltipPosition: 'bottom', // Coloca el tooltip debajo del elemento de estadísticas
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Props de `TourStepConfig`
|
|
115
|
+
|
|
116
|
+
| Prop | Tipo | Default | Descripción |
|
|
117
|
+
|---|---|---|---|
|
|
118
|
+
| `id` | `string` | — | Identificador único del paso |
|
|
119
|
+
| `ref` | `RefObject<View>` | — | Referencia al elemento a destacar |
|
|
120
|
+
| `title` | `string` | — | Título del tooltip |
|
|
121
|
+
| `content` | `string \| ReactNode` | — | Texto simple o componente React personalizado |
|
|
122
|
+
| `tooltipPosition` | `'top' \| 'bottom' \| 'auto'` | `'auto'` | Posición del tooltip |
|
|
123
|
+
| `tooltipSize` | `'sm' \| 'lg'` | `'sm'` | Tamaño del tooltip |
|
|
124
|
+
| `highlightPadding` | `number` | `8` | Espacio extra alrededor del elemento |
|
|
125
|
+
|
|
126
|
+
## Tema
|
|
127
|
+
|
|
128
|
+
Personaliza el aspecto visual pasando un objeto `theme` parcial a `<TourProvider>`:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { Tour, TourProvider, TourTheme } from './feature-tour';
|
|
132
|
+
|
|
133
|
+
// Define el tema fuera del componente para evitar recrearlo en cada render
|
|
134
|
+
// Partial<TourTheme> permite sobreescribir solo los tokens deseados
|
|
135
|
+
const miTema: Partial<TourTheme> = {
|
|
136
|
+
primaryColor: '#7c3aed', // Botón "Siguiente" en morado
|
|
137
|
+
tooltipBackgroundColor: '#1e1e2e', // Fondo oscuro para el tooltip
|
|
138
|
+
tooltipTitleColor: '#ffffff',
|
|
139
|
+
tooltipDescriptionColor: '#a0a0b0',
|
|
140
|
+
overlayColor: 'rgba(0,0,0,0.75)',
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// Pasa el tema al provider; solo los tokens definidos sobreescriben el DEFAULT_TOUR_THEME
|
|
144
|
+
export default function App() {
|
|
145
|
+
return (
|
|
146
|
+
<TourProvider theme={miTema}>
|
|
147
|
+
{/* resto de la app */}
|
|
148
|
+
</TourProvider>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
También puedes pasar los tokens directamente como objeto literal:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
// Proporciona solo los tokens que quieras sobreescribir;
|
|
157
|
+
// el resto tomará los valores de DEFAULT_TOUR_THEME.
|
|
158
|
+
// Aquí cambiamos el color de acción principal y suavizamos las esquinas del highlight.
|
|
159
|
+
<TourProvider theme={{ primaryColor: '#2563eb', highlightBorderRadius: 12 }}>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
| Token | Default | Descripción |
|
|
163
|
+
|---|---|---|
|
|
164
|
+
| `primaryColor` | `'#2563eb'` | Color de fondo del botón "Siguiente" |
|
|
165
|
+
| `primaryTextColor` | `'#ffffff'` | Color del texto del botón "Siguiente" |
|
|
166
|
+
| `skipButtonTextColor` | `'#888888'` | Color del texto del botón "Saltar" |
|
|
167
|
+
| `skipButtonBackgroundColor` | `'transparent'` | Color de fondo del botón "Saltar" |
|
|
168
|
+
| `tooltipBackgroundColor` | `'#ffffff'` | Fondo del tooltip |
|
|
169
|
+
| `tooltipTitleColor` | `'#111111'` | Color del título |
|
|
170
|
+
| `tooltipDescriptionColor` | `'#444444'` | Color de la descripción |
|
|
171
|
+
| `tooltipProgressColor` | `'#888888'` | Color del contador de pasos |
|
|
172
|
+
| `tooltipBorderRadius` | `10` | Radio del tooltip |
|
|
173
|
+
| `overlayColor` | `'rgba(0,0,0,0.6)'` | Color del fondo oscuro |
|
|
174
|
+
| `highlightBorderColor` | `'#ffffff'` | Color del borde del elemento destacado |
|
|
175
|
+
| `highlightBorderRadius` | `10` | Radio del recuadro de highlight |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ElementMeasure } from '../tour.types';
|
|
3
|
+
interface TourHighlightProps {
|
|
4
|
+
measure: ElementMeasure;
|
|
5
|
+
padding: number;
|
|
6
|
+
}
|
|
7
|
+
export declare const TourHighlight: React.FC<TourHighlightProps>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=tour-highlight.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-highlight.component.d.ts","sourceRoot":"","sources":["../../components/tour-highlight.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,UAAU,kBAAkB;IACxB,OAAO,EAAE,cAAc,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;CACnB;AAGD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAgBtD,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StyleSheet, View } from 'react-native';
|
|
3
|
+
import { useTourTheme } from '../service/tour-theme.context';
|
|
4
|
+
// Componente que dibuja un borde resaltado alrededor del elemento destacado.
|
|
5
|
+
export const TourHighlight = ({ measure, padding }) => {
|
|
6
|
+
const theme = useTourTheme();
|
|
7
|
+
const { x, y, width, height } = measure;
|
|
8
|
+
return (_jsx(View, { style: [styles.highlight, {
|
|
9
|
+
top: y - padding,
|
|
10
|
+
left: x - padding,
|
|
11
|
+
width: width + padding * 2,
|
|
12
|
+
height: height + padding * 2,
|
|
13
|
+
borderColor: theme.highlightBorderColor,
|
|
14
|
+
borderRadius: theme.highlightBorderRadius,
|
|
15
|
+
}] }));
|
|
16
|
+
};
|
|
17
|
+
const styles = StyleSheet.create({
|
|
18
|
+
highlight: {
|
|
19
|
+
position: 'absolute',
|
|
20
|
+
borderWidth: 2,
|
|
21
|
+
backgroundColor: 'transparent',
|
|
22
|
+
},
|
|
23
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TourStepConfig } from '../tour.types';
|
|
3
|
+
interface TourOverlayProps {
|
|
4
|
+
step: TourStepConfig;
|
|
5
|
+
stepIndex: number;
|
|
6
|
+
totalSteps: number;
|
|
7
|
+
onNext: () => void;
|
|
8
|
+
onSkip: () => void;
|
|
9
|
+
skipLabel: string;
|
|
10
|
+
nextLabel: string;
|
|
11
|
+
finishLabel: string;
|
|
12
|
+
showSkipButton: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare const TourOverlay: React.FC<TourOverlayProps>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=tour-overlay.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-overlay.component.d.ts","sourceRoot":"","sources":["../../components/tour-overlay.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA8B,MAAM,OAAO,CAAC;AAGnD,OAAO,EAAkB,cAAc,EAAE,MAAM,eAAe,CAAC;AAK/D,UAAU,gBAAgB;IACtB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;CAC3B;AAwBD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CAkElD,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Dimensions, Modal, StyleSheet, View } from 'react-native';
|
|
4
|
+
import { Path, Svg } from 'react-native-svg';
|
|
5
|
+
import { useTourTheme } from '../service/tour-theme.context';
|
|
6
|
+
import { TourHighlight } from './tour-highlight.component';
|
|
7
|
+
import { TourTooltip } from './tour-tooltip.component';
|
|
8
|
+
/** Este SVG sirve para oscurecer toda la pantalla excepto el área del elemento destacado, creando un efecto de "agujero". */
|
|
9
|
+
function buildCutoutPath(sw, sh, x, y, w, h, r) {
|
|
10
|
+
return [
|
|
11
|
+
// rectángulo exterior (pantalla completa)
|
|
12
|
+
`M 0 0 H ${sw} V ${sh} H 0 Z`,
|
|
13
|
+
// rectángulo redondeado interior (el "agujero")
|
|
14
|
+
`M ${x + r} ${y}`,
|
|
15
|
+
`H ${x + w - r}`,
|
|
16
|
+
`A ${r} ${r} 0 0 1 ${x + w} ${y + r}`,
|
|
17
|
+
`V ${y + h - r}`,
|
|
18
|
+
`A ${r} ${r} 0 0 1 ${x + w - r} ${y + h}`,
|
|
19
|
+
`H ${x + r}`,
|
|
20
|
+
`A ${r} ${r} 0 0 1 ${x} ${y + h - r}`,
|
|
21
|
+
`V ${y + r}`,
|
|
22
|
+
`A ${r} ${r} 0 0 1 ${x + r} ${y} Z`,
|
|
23
|
+
].join(' ');
|
|
24
|
+
}
|
|
25
|
+
export const TourOverlay = ({ step, stepIndex, totalSteps, onNext, onSkip, skipLabel, nextLabel, finishLabel, showSkipButton }) => {
|
|
26
|
+
var _a;
|
|
27
|
+
const theme = useTourTheme();
|
|
28
|
+
const [measure, setMeasure] = useState(null);
|
|
29
|
+
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
var _a;
|
|
32
|
+
if (!((_a = step.ref) === null || _a === void 0 ? void 0 : _a.current))
|
|
33
|
+
return;
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
step.ref.current.measureInWindow((x, y, width, height) => {
|
|
36
|
+
// measureInWindow devuelve coordenadas relativas a la ventana visible
|
|
37
|
+
setMeasure({ x, y, width, height });
|
|
38
|
+
});
|
|
39
|
+
}, 100);
|
|
40
|
+
return () => clearTimeout(timer);
|
|
41
|
+
}, [step.id]);
|
|
42
|
+
const pad = (_a = step.highlightPadding) !== null && _a !== void 0 ? _a : 8;
|
|
43
|
+
const hx = measure ? measure.x - pad : 0;
|
|
44
|
+
const hy = measure ? measure.y - pad : 0;
|
|
45
|
+
const hw = measure ? measure.width + pad * 2 : 0;
|
|
46
|
+
const hh = measure ? measure.height + pad * 2 : 0;
|
|
47
|
+
return (
|
|
48
|
+
// Modal que cubre toda la pantalla con un fondo semitransparente, dejando un "agujero" alrededor del elemento destacado.
|
|
49
|
+
_jsx(Modal, { transparent: true, visible: true, animationType: "fade", children: _jsxs(View, { style: StyleSheet.absoluteFill, pointerEvents: "box-none", children: [_jsx(Svg, { style: StyleSheet.absoluteFill, width: screenWidth, height: screenHeight, pointerEvents: "none", children: _jsx(Path, { d: buildCutoutPath(screenWidth, screenHeight, hx, hy, hw, hh, theme.highlightBorderRadius), fill: theme.overlayColor, fillRule: "evenodd" }) }), measure && (_jsxs(_Fragment, { children: [_jsx(TourHighlight, { measure: measure, padding: pad }), _jsx(TourTooltip, { step: step, measure: measure, screenWidth: screenWidth, screenHeight: screenHeight, stepIndex: stepIndex, totalSteps: totalSteps, onNext: onNext, onSkip: onSkip, skipLabel: skipLabel, nextLabel: nextLabel, finishLabel: finishLabel, showSkipButton: showSkipButton })] }))] }) }));
|
|
50
|
+
};
|
|
51
|
+
const styles = StyleSheet.create({});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ElementMeasure, TooltipPosition } from '../tour.types';
|
|
2
|
+
export interface TooltipCoords {
|
|
3
|
+
top: number;
|
|
4
|
+
left: number;
|
|
5
|
+
}
|
|
6
|
+
export declare const calculateTooltipPosition: (measure: ElementMeasure, screenWidth: number, screenHeight: number, preferred?: TooltipPosition) => TooltipCoords;
|
|
7
|
+
//# sourceMappingURL=tour-tooltip-position.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-tooltip-position.component.d.ts","sourceRoot":"","sources":["../../components/tour-tooltip-position.component.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAKhE,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CAChB;AAGD,eAAO,MAAM,wBAAwB,GACjC,SAAS,cAAc,EACvB,aAAa,MAAM,EACnB,cAAc,MAAM,EACpB,YAAW,eAAwB,KACpC,aAgBF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const TOOLTIP_HEIGHT = 120;
|
|
2
|
+
const TOOLTIP_MARGIN = 12;
|
|
3
|
+
// Calcula la posición del tooltip basado en la medida del elemento destacado, el tamaño de la pantalla y la preferencia de posición.
|
|
4
|
+
export const calculateTooltipPosition = (measure, screenWidth, screenHeight, preferred = 'auto') => {
|
|
5
|
+
const spaceBelow = screenHeight - (measure.y + measure.height);
|
|
6
|
+
const spaceAbove = measure.y;
|
|
7
|
+
const position = preferred !== 'auto' ? preferred
|
|
8
|
+
: spaceBelow >= TOOLTIP_HEIGHT + TOOLTIP_MARGIN ? 'bottom'
|
|
9
|
+
: spaceAbove >= TOOLTIP_HEIGHT + TOOLTIP_MARGIN ? 'top'
|
|
10
|
+
: 'bottom'; // valor por defecto
|
|
11
|
+
const left = Math.max(16, Math.min(measure.x, screenWidth - 240));
|
|
12
|
+
if (position === 'bottom') {
|
|
13
|
+
return { top: measure.y + measure.height + TOOLTIP_MARGIN, left };
|
|
14
|
+
}
|
|
15
|
+
return { top: measure.y - TOOLTIP_HEIGHT - TOOLTIP_MARGIN, left };
|
|
16
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ElementMeasure, TourStepConfig } from '../tour.types';
|
|
3
|
+
interface TourTooltipProps {
|
|
4
|
+
step: TourStepConfig;
|
|
5
|
+
measure: ElementMeasure;
|
|
6
|
+
screenWidth: number;
|
|
7
|
+
screenHeight: number;
|
|
8
|
+
stepIndex: number;
|
|
9
|
+
totalSteps: number;
|
|
10
|
+
onNext: () => void;
|
|
11
|
+
onSkip: () => void;
|
|
12
|
+
skipLabel: string;
|
|
13
|
+
nextLabel: string;
|
|
14
|
+
finishLabel: string;
|
|
15
|
+
showSkipButton: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const TourTooltip: React.FC<TourTooltipProps>;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=tour-tooltip.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-tooltip.component.d.ts","sourceRoot":"","sources":["../../components/tour-tooltip.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAI/D,UAAU,gBAAgB;IACtB,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,cAAc,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;CAC3B;AAGD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,gBAAgB,CA4DlD,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native';
|
|
4
|
+
import { useTourTheme } from '../service/tour-theme.context';
|
|
5
|
+
import { calculateTooltipPosition } from '../utils/tour-tooltip-position';
|
|
6
|
+
// Componente que muestra el tooltip con la información de la etapa actual, posicionado cerca del elemento destacado.
|
|
7
|
+
export const TourTooltip = ({ step, measure, screenWidth, screenHeight, stepIndex, totalSteps, onNext, onSkip, skipLabel, nextLabel, finishLabel, showSkipButton, }) => {
|
|
8
|
+
const theme = useTourTheme();
|
|
9
|
+
const [tooltipHeight, setTooltipHeight] = useState(0);
|
|
10
|
+
const isLast = stepIndex === totalSteps - 1;
|
|
11
|
+
const tooltipWidth = step.tooltipSize === 'lg' ? 320 : 260;
|
|
12
|
+
const { top, left } = calculateTooltipPosition(measure, screenWidth, screenHeight, step.tooltipPosition, tooltipHeight);
|
|
13
|
+
const handleLayout = (e) => {
|
|
14
|
+
const h = e.nativeEvent.layout.height;
|
|
15
|
+
if (h !== tooltipHeight)
|
|
16
|
+
setTooltipHeight(h);
|
|
17
|
+
};
|
|
18
|
+
return (_jsxs(View, { style: [styles.container, {
|
|
19
|
+
top, left,
|
|
20
|
+
width: tooltipWidth,
|
|
21
|
+
opacity: tooltipHeight === 0 ? 0 : 1,
|
|
22
|
+
backgroundColor: theme.tooltipBackgroundColor,
|
|
23
|
+
borderRadius: theme.tooltipBorderRadius,
|
|
24
|
+
}], onLayout: handleLayout, children: [_jsxs(View, { style: styles.header, children: [_jsx(Text, { style: [styles.title, { color: theme.tooltipTitleColor }], children: step.title }), _jsxs(Text, { style: [styles.progress, { color: theme.tooltipProgressColor }], children: [stepIndex + 1, " / ", totalSteps] })] }), step.content != null && (typeof step.content === 'string'
|
|
25
|
+
? _jsx(Text, { style: [styles.description, { color: theme.tooltipDescriptionColor }], children: step.content })
|
|
26
|
+
: _jsx(View, { style: styles.customContent, children: step.content })), _jsxs(View, { style: [styles.actions, !showSkipButton && styles.actionsEnd], children: [showSkipButton && (_jsx(TouchableOpacity, { style: [styles.skipBtn, { backgroundColor: theme.skipButtonBackgroundColor, borderRadius: theme.tooltipBorderRadius - 2 }], onPress: onSkip, children: _jsx(Text, { style: [styles.skipBtnText, { color: theme.skipButtonTextColor }], children: skipLabel }) })), _jsx(TouchableOpacity, { style: [styles.nextBtn, { backgroundColor: theme.primaryColor, borderRadius: theme.tooltipBorderRadius - 2 }], onPress: onNext, children: _jsx(Text, { style: [styles.nextBtnText, { color: theme.primaryTextColor }], children: isLast ? finishLabel : nextLabel }) })] })] }));
|
|
27
|
+
};
|
|
28
|
+
// Estilos básicos del tooltip, algunos de ellos se pueden personalizar o sobreescribir con un TourTheme.
|
|
29
|
+
const styles = StyleSheet.create({
|
|
30
|
+
container: {
|
|
31
|
+
position: 'absolute',
|
|
32
|
+
padding: 16,
|
|
33
|
+
shadowColor: '#000',
|
|
34
|
+
shadowOpacity: 0.2,
|
|
35
|
+
shadowRadius: 8,
|
|
36
|
+
elevation: 6,
|
|
37
|
+
},
|
|
38
|
+
header: {
|
|
39
|
+
flexDirection: 'row',
|
|
40
|
+
justifyContent: 'space-between',
|
|
41
|
+
alignItems: 'center',
|
|
42
|
+
marginBottom: 6,
|
|
43
|
+
},
|
|
44
|
+
title: { fontSize: 15, fontWeight: '700' },
|
|
45
|
+
progress: { fontSize: 12 },
|
|
46
|
+
description: { fontSize: 13, lineHeight: 20, marginBottom: 14 },
|
|
47
|
+
customContent: { marginBottom: 14 },
|
|
48
|
+
actions: {
|
|
49
|
+
flexDirection: 'row',
|
|
50
|
+
justifyContent: 'space-between',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
},
|
|
53
|
+
actionsEnd: {
|
|
54
|
+
justifyContent: 'flex-end',
|
|
55
|
+
},
|
|
56
|
+
skipBtn: { paddingVertical: 7, paddingHorizontal: 12 },
|
|
57
|
+
skipBtnText: { fontSize: 13 },
|
|
58
|
+
nextBtn: { paddingVertical: 7, paddingHorizontal: 16 },
|
|
59
|
+
nextBtnText: { fontSize: 13, fontWeight: '600' },
|
|
60
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TourHandle, TourStepConfig } from '../tour.types';
|
|
3
|
+
interface TourProps {
|
|
4
|
+
steps: TourStepConfig[];
|
|
5
|
+
/** Texto del botón "Saltar" (por defecto 'Saltar') */
|
|
6
|
+
skipLabel?: string;
|
|
7
|
+
/** Texto del botón "Siguiente" (por defecto 'Siguiente') */
|
|
8
|
+
nextLabel?: string;
|
|
9
|
+
/** Texto del botón del último paso (por defecto 'Finalizar') */
|
|
10
|
+
finishLabel?: string;
|
|
11
|
+
/** Muestra u oculta el botón de omitir. Por defecto `true`. */
|
|
12
|
+
showSkipButton?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* <Tour ref={tourRef} steps={steps} />
|
|
16
|
+
*
|
|
17
|
+
* Coloca este componente dentro de TourProvider. Se puede iniciar el tour con:
|
|
18
|
+
* tourRef.current?.start()
|
|
19
|
+
*/
|
|
20
|
+
export declare const Tour: React.ForwardRefExoticComponent<TourProps & React.RefAttributes<TourHandle>>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=tour.component.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour.component.d.ts","sourceRoot":"","sources":["../../components/tour.component.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoD,MAAM,OAAO,CAAC;AACzE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG3D,UAAU,SAAS;IACf,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,IAAI,8EAqCf,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useImperativeHandle, useState } from 'react';
|
|
3
|
+
import { TourOverlay } from './tour-overlay.component';
|
|
4
|
+
/**
|
|
5
|
+
* <Tour ref={tourRef} steps={steps} />
|
|
6
|
+
*
|
|
7
|
+
* Coloca este componente dentro de TourProvider. Se puede iniciar el tour con:
|
|
8
|
+
* tourRef.current?.start()
|
|
9
|
+
*/
|
|
10
|
+
export const Tour = forwardRef(({ steps, skipLabel = 'Saltar', nextLabel = 'Siguiente', finishLabel = 'Finalizar', showSkipButton = true }, ref) => {
|
|
11
|
+
const [running, setRunning] = useState(false);
|
|
12
|
+
const [currentIndex, setCurrentIndex] = useState(0);
|
|
13
|
+
useImperativeHandle(ref, () => ({
|
|
14
|
+
start: () => {
|
|
15
|
+
setCurrentIndex(0);
|
|
16
|
+
setRunning(true);
|
|
17
|
+
},
|
|
18
|
+
stop: () => setRunning(false),
|
|
19
|
+
}));
|
|
20
|
+
const handleNext = () => {
|
|
21
|
+
if (currentIndex >= steps.length - 1) {
|
|
22
|
+
setRunning(false);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
setCurrentIndex(prev => prev + 1);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const handleSkip = () => setRunning(false);
|
|
29
|
+
if (!running || steps.length === 0)
|
|
30
|
+
return null;
|
|
31
|
+
return (_jsx(TourOverlay, { step: steps[currentIndex], stepIndex: currentIndex, totalSteps: steps.length, onNext: handleNext, onSkip: handleSkip, skipLabel: skipLabel, nextLabel: nextLabel, finishLabel: finishLabel, showSkipButton: showSkipButton }));
|
|
32
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { TourProvider } from './service/tour.context';
|
|
2
|
+
export { useTourTheme } from './service/tour-theme.context';
|
|
3
|
+
export { Tour } from './components/tour.component';
|
|
4
|
+
export type { TourStepConfig, TourHandle, TooltipPosition, TourTheme, TourStepSize, TourStepContent } from './tour.types';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,IAAI,EAAE,MAAM,6BAA6B,CAAC;AACnD,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TourTheme } from '../tour.types';
|
|
3
|
+
export declare const TourThemeProvider: React.FC<{
|
|
4
|
+
theme: TourTheme;
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
}>;
|
|
7
|
+
export declare const useTourTheme: () => TourTheme;
|
|
8
|
+
//# sourceMappingURL=tour-theme.context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-theme.context.d.ts","sourceRoot":"","sources":["../../service/tour-theme.context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAoC,MAAM,OAAO,CAAC;AACzD,OAAO,EAAsB,SAAS,EAAE,MAAM,eAAe,CAAC;AAI9D,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC;IACrC,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC7B,CAIA,CAAC;AAEF,eAAO,MAAM,YAAY,QAAO,SAAyC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
import { DEFAULT_TOUR_THEME } from '../tour.types';
|
|
4
|
+
const TourThemeContext = createContext(DEFAULT_TOUR_THEME);
|
|
5
|
+
export const TourThemeProvider = ({ theme, children }) => (_jsx(TourThemeContext.Provider, { value: theme, children: children }));
|
|
6
|
+
export const useTourTheme = () => useContext(TourThemeContext);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { TourTheme } from '../tour.types';
|
|
3
|
+
/** Proveedor raíz — colócalo una vez en la raíz de la app para configurar el tema del tour. */
|
|
4
|
+
export declare const TourProvider: React.FC<{
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
theme?: Partial<TourTheme>;
|
|
7
|
+
}>;
|
|
8
|
+
//# sourceMappingURL=tour.context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour.context.d.ts","sourceRoot":"","sources":["../../service/tour.context.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAsB,SAAS,EAAE,MAAM,eAAe,CAAC;AAG9D,+FAA+F;AAC/F,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAA;CAAE,CAO5F,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { DEFAULT_TOUR_THEME } from '../tour.types';
|
|
3
|
+
import { TourThemeProvider } from './tour-theme.context';
|
|
4
|
+
/** Proveedor raíz — colócalo una vez en la raíz de la app para configurar el tema del tour. */
|
|
5
|
+
export const TourProvider = ({ children, theme }) => {
|
|
6
|
+
const resolvedTheme = Object.assign(Object.assign({}, DEFAULT_TOUR_THEME), theme);
|
|
7
|
+
return (_jsx(TourThemeProvider, { theme: resolvedTheme, children: children }));
|
|
8
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { RefObject } from "react";
|
|
2
|
+
import { View } from "react-native";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
export type TourStepContent = string | ReactNode;
|
|
5
|
+
export type TourState = 'IDLE' | 'RUNNING' | 'FINISHED';
|
|
6
|
+
export type TooltipPosition = 'top' | 'bottom' | 'auto';
|
|
7
|
+
/** Controla el tamaño del tooltip. 'sm' = compacto (predeterminado), 'lg' = ancho. */
|
|
8
|
+
export type TourStepSize = 'sm' | 'lg';
|
|
9
|
+
export interface TourTheme {
|
|
10
|
+
/** Color principal: botón "Siguiente", badge de progreso */
|
|
11
|
+
primaryColor: string;
|
|
12
|
+
/** Color del texto sobre el botón primario */
|
|
13
|
+
primaryTextColor: string;
|
|
14
|
+
/** Color de fondo del tooltip */
|
|
15
|
+
tooltipBackgroundColor: string;
|
|
16
|
+
/** Color del título del tooltip */
|
|
17
|
+
tooltipTitleColor: string;
|
|
18
|
+
/** Color del texto de descripción */
|
|
19
|
+
tooltipDescriptionColor: string;
|
|
20
|
+
/** Color del texto de progreso ("1 / 6") */
|
|
21
|
+
tooltipProgressColor: string;
|
|
22
|
+
/** Color del botón "Saltar" */
|
|
23
|
+
skipButtonTextColor: string;
|
|
24
|
+
/** Color de fondo del botón "Saltar" (por defecto transparente) */
|
|
25
|
+
skipButtonBackgroundColor: string;
|
|
26
|
+
/** Border radius del tooltip */
|
|
27
|
+
tooltipBorderRadius: number;
|
|
28
|
+
/** Color del overlay semitransparente */
|
|
29
|
+
overlayColor: string;
|
|
30
|
+
/** Color del borde del highlight */
|
|
31
|
+
highlightBorderColor: string;
|
|
32
|
+
/** Border radius del highlight */
|
|
33
|
+
highlightBorderRadius: number;
|
|
34
|
+
}
|
|
35
|
+
export declare const DEFAULT_TOUR_THEME: TourTheme;
|
|
36
|
+
export interface TourStepConfig {
|
|
37
|
+
id: string;
|
|
38
|
+
ref: RefObject<View | null>;
|
|
39
|
+
title: string;
|
|
40
|
+
/** Contenido del tooltip. Puede ser un string (texto simple) o un ReactNode (contenido personalizado). */
|
|
41
|
+
content?: TourStepContent;
|
|
42
|
+
tooltipPosition?: TooltipPosition;
|
|
43
|
+
highlightPadding?: number;
|
|
44
|
+
/** Controla el tamaño del tooltip. */
|
|
45
|
+
tooltipSize?: TourStepSize;
|
|
46
|
+
}
|
|
47
|
+
/** Handle imperativo expuesto por <Tour> mediante forwardRef */
|
|
48
|
+
export interface TourHandle {
|
|
49
|
+
start: () => void;
|
|
50
|
+
stop: () => void;
|
|
51
|
+
}
|
|
52
|
+
export interface ElementMeasure {
|
|
53
|
+
x: number;
|
|
54
|
+
y: number;
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=tour.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour.types.d.ts","sourceRoot":"","sources":["../tour.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACpC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,CAAC;AAEjD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAExD,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AAExD,sFAAsF;AACtF,MAAM,MAAM,YAAY,GAAG,IAAI,GAAG,IAAI,CAAC;AAEvC,MAAM,WAAW,SAAS;IACtB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,iCAAiC;IACjC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mCAAmC;IACnC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,qCAAqC;IACrC,uBAAuB,EAAE,MAAM,CAAC;IAChC,4CAA4C;IAC5C,oBAAoB,EAAE,MAAM,CAAC;IAC7B,+BAA+B;IAC/B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mEAAmE;IACnE,yBAAyB,EAAE,MAAM,CAAC;IAClC,gCAAgC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,yCAAyC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,oCAAoC;IACpC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kCAAkC;IAClC,qBAAqB,EAAE,MAAM,CAAC;CACjC;AAED,eAAO,MAAM,kBAAkB,EAAE,SAahC,CAAC;AAEF,MAAM,WAAW,cAAc;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,SAAS,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,0GAA0G;IAC1G,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sCAAsC;IACtC,WAAW,CAAC,EAAE,YAAY,CAAC;CAC9B;AAED,gEAAgE;AAChE,MAAM,WAAW,UAAU;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC3B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAClB"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_TOUR_THEME = {
|
|
2
|
+
primaryColor: '#2563eb',
|
|
3
|
+
primaryTextColor: '#ffffff',
|
|
4
|
+
tooltipBackgroundColor: '#ffffff',
|
|
5
|
+
tooltipTitleColor: '#111111',
|
|
6
|
+
tooltipDescriptionColor: '#444444',
|
|
7
|
+
tooltipProgressColor: '#888888',
|
|
8
|
+
skipButtonTextColor: '#888888',
|
|
9
|
+
skipButtonBackgroundColor: 'transparent',
|
|
10
|
+
tooltipBorderRadius: 10,
|
|
11
|
+
overlayColor: 'rgba(0,0,0,0.6)',
|
|
12
|
+
highlightBorderColor: '#ffffff',
|
|
13
|
+
highlightBorderRadius: 10,
|
|
14
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ElementMeasure, TooltipPosition } from '../tour.types';
|
|
2
|
+
export interface TooltipCoords {
|
|
3
|
+
top: number;
|
|
4
|
+
left: number;
|
|
5
|
+
}
|
|
6
|
+
/** Calcula la posición del tooltip basado en las medidas del elemento destacado, el tamaño de la pantalla y la posición preferida. */
|
|
7
|
+
export declare const calculateTooltipPosition: (measure: ElementMeasure, screenWidth: number, screenHeight: number, preferred?: TooltipPosition, tooltipHeight?: number) => TooltipCoords;
|
|
8
|
+
//# sourceMappingURL=tour-tooltip-position.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tour-tooltip-position.d.ts","sourceRoot":"","sources":["../../utils/tour-tooltip-position.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAKhE,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,sIAAsI;AACtI,eAAO,MAAM,wBAAwB,GACjC,SAAS,cAAc,EACvB,aAAa,MAAM,EACnB,cAAc,MAAM,EACpB,YAAW,eAAwB,EACnC,gBAAe,MAAU,KAC1B,aAgBF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const TOOLTIP_WIDTH = 260;
|
|
2
|
+
const TOOLTIP_MARGIN = 10;
|
|
3
|
+
/** Calcula la posición del tooltip basado en las medidas del elemento destacado, el tamaño de la pantalla y la posición preferida. */
|
|
4
|
+
export const calculateTooltipPosition = (measure, screenWidth, screenHeight, preferred = 'auto', tooltipHeight = 0) => {
|
|
5
|
+
const spaceBelow = screenHeight - (measure.y + measure.height);
|
|
6
|
+
const spaceAbove = measure.y;
|
|
7
|
+
const position = preferred !== 'auto' ? preferred
|
|
8
|
+
: spaceBelow >= tooltipHeight + TOOLTIP_MARGIN ? 'bottom'
|
|
9
|
+
: spaceAbove >= tooltipHeight + TOOLTIP_MARGIN ? 'top'
|
|
10
|
+
: 'bottom';
|
|
11
|
+
const clampedLeft = Math.max(16, Math.min(measure.x, screenWidth - TOOLTIP_WIDTH - 16));
|
|
12
|
+
if (position === 'bottom') {
|
|
13
|
+
return { top: measure.y + measure.height + TOOLTIP_MARGIN, left: clampedLeft };
|
|
14
|
+
}
|
|
15
|
+
return { top: measure.y - tooltipHeight - TOOLTIP_MARGIN, left: clampedLeft };
|
|
16
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "uo287827-react-native-tour-component",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Componente de tour/onboarding interactivo para React Native",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"react-native",
|
|
9
|
+
"tour",
|
|
10
|
+
"onboarding",
|
|
11
|
+
"tutorial",
|
|
12
|
+
"guide",
|
|
13
|
+
"walkthrough"
|
|
14
|
+
],
|
|
15
|
+
"author": "Raúl Mera Soto <UO287827@uniovi.es>",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"peerDependencies": {
|
|
18
|
+
"react": ">=18.0.0",
|
|
19
|
+
"react-native": ">=0.70.0",
|
|
20
|
+
"react-native-svg": ">=13.0.0"
|
|
21
|
+
},
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc -p tsconfig.json",
|
|
24
|
+
"prepare": "npm run build"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.3.0",
|
|
32
|
+
"@types/react": "^19.2.14",
|
|
33
|
+
"@types/react-native": "^0.73.0"
|
|
34
|
+
}
|
|
35
|
+
}
|