@elogroup-sereduc/portal-aluno-tour 1.1.1 → 1.1.2
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/dist/components/Tour.d.ts +6 -0
- package/dist/components/Tour.d.ts.map +1 -0
- package/dist/components/Tour.js +468 -0
- package/{src/index.ts → dist/index.d.ts} +3 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/tour.css +566 -0
- package/dist/types/index.d.ts +96 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +4 -0
- package/package.json +5 -1
- package/package-lock.json +0 -2411
- package/postcss.config.js +0 -6
- package/src/components/Tour.tsx +0 -638
- package/src/styles/tour.css +0 -3
- package/src/types/index.ts +0 -114
- package/tailwind.config.js +0 -11
- package/tsconfig.json +0 -23
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TourProps } from "../types";
|
|
2
|
+
/**
|
|
3
|
+
* Componente de tour guiado customizado usando HeroUI
|
|
4
|
+
*/
|
|
5
|
+
export declare function Tour({ enabled, steps, initialStep, options, onExit, onComplete, }: TourProps): import("react/jsx-runtime").JSX.Element | null;
|
|
6
|
+
//# sourceMappingURL=Tour.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Tour.d.ts","sourceRoot":"","sources":["../../src/components/Tour.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAY,MAAM,UAAU,CAAC;AAG/C;;GAEG;AACH,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,KAAK,EACL,WAAe,EACf,OAAY,EACZ,MAAM,EACN,UAAU,GACX,EAAE,SAAS,kDA8mBX"}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState, useCallback, useRef } from "react";
|
|
3
|
+
import { Button } from "@heroui/button";
|
|
4
|
+
import { HiXMark, HiChevronLeft, HiChevronRight } from "react-icons/hi2";
|
|
5
|
+
/**
|
|
6
|
+
* Componente de tour guiado customizado usando HeroUI
|
|
7
|
+
*/
|
|
8
|
+
export function Tour({ enabled, steps, initialStep = 0, options = {}, onExit, onComplete, }) {
|
|
9
|
+
const [currentStep, setCurrentStep] = useState(initialStep);
|
|
10
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
11
|
+
const [highlightedElement, setHighlightedElement] = useState(null);
|
|
12
|
+
const [tooltipPosition, setTooltipPosition] = useState(null);
|
|
13
|
+
const overlayRef = useRef(null);
|
|
14
|
+
const tooltipRef = useRef(null);
|
|
15
|
+
const isConfiguredRef = useRef(false);
|
|
16
|
+
const { nextLabel = "Próximo", prevLabel = "Anterior", skipLabel = "Pular", doneLabel = "Concluir", showProgress = true, showBullets = true, exitOnOverlayClick = false, exitOnEsc = true, } = options;
|
|
17
|
+
// Calcula a posição da tooltip baseado no elemento destacado
|
|
18
|
+
// Usa getBoundingClientRect() que retorna posições relativas à viewport
|
|
19
|
+
// Isso mantém a tooltip sempre visível mesmo durante scroll
|
|
20
|
+
const calculateTooltipPosition = useCallback((element, position = "auto") => {
|
|
21
|
+
const rect = element.getBoundingClientRect();
|
|
22
|
+
const viewportWidth = window.innerWidth;
|
|
23
|
+
const viewportHeight = window.innerHeight;
|
|
24
|
+
// Tamanho estimado da tooltip
|
|
25
|
+
const tooltipWidth = 384; // max-w-sm = 384px
|
|
26
|
+
const tooltipHeight = 250; // altura estimada
|
|
27
|
+
const spacing = 8; // espaçamento próximo ao elemento
|
|
28
|
+
let top = 0;
|
|
29
|
+
let left = 0;
|
|
30
|
+
let finalPosition = position;
|
|
31
|
+
// Verifica se o elemento está visível na viewport
|
|
32
|
+
const isElementVisible = rect.top < viewportHeight &&
|
|
33
|
+
rect.bottom > 0 &&
|
|
34
|
+
rect.left < viewportWidth &&
|
|
35
|
+
rect.right > 0;
|
|
36
|
+
// Se o elemento não estiver visível, tenta posicionar próximo à borda mais próxima
|
|
37
|
+
if (!isElementVisible) {
|
|
38
|
+
// Elemento está fora da viewport - posiciona próximo à borda mais próxima
|
|
39
|
+
if (rect.bottom < 0) {
|
|
40
|
+
// Elemento está acima da viewport
|
|
41
|
+
top = 8;
|
|
42
|
+
left = Math.max(tooltipWidth / 2 + 8, Math.min(viewportWidth - tooltipWidth / 2 - 8, rect.left + rect.width / 2));
|
|
43
|
+
finalPosition = "top";
|
|
44
|
+
}
|
|
45
|
+
else if (rect.top > viewportHeight) {
|
|
46
|
+
// Elemento está abaixo da viewport
|
|
47
|
+
top = viewportHeight - tooltipHeight - 8;
|
|
48
|
+
left = Math.max(tooltipWidth / 2 + 8, Math.min(viewportWidth - tooltipWidth / 2 - 8, rect.left + rect.width / 2));
|
|
49
|
+
finalPosition = "bottom";
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
// Elemento está à esquerda ou direita
|
|
53
|
+
top = Math.max(tooltipHeight / 2 + 8, Math.min(viewportHeight - tooltipHeight / 2 - 8, rect.top + rect.height / 2));
|
|
54
|
+
if (rect.right < 0) {
|
|
55
|
+
left = 8;
|
|
56
|
+
finalPosition = "left";
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
left = viewportWidth - tooltipWidth - 8;
|
|
60
|
+
finalPosition = "right";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { top, left, position: finalPosition };
|
|
64
|
+
}
|
|
65
|
+
// Calcula espaço disponível em todas as direções
|
|
66
|
+
const spaceAbove = rect.top;
|
|
67
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
68
|
+
const spaceLeft = rect.left;
|
|
69
|
+
const spaceRight = viewportWidth - rect.right;
|
|
70
|
+
// Determina a melhor posição baseado no espaço disponível
|
|
71
|
+
let bestPosition;
|
|
72
|
+
// Se position for "auto" ou não especificado, calcula automaticamente a melhor posição
|
|
73
|
+
if (!position || position === "auto") {
|
|
74
|
+
// Escolhe automaticamente a direção com mais espaço disponível
|
|
75
|
+
const maxSpace = Math.max(spaceAbove, spaceBelow, spaceLeft, spaceRight);
|
|
76
|
+
if (maxSpace === spaceAbove)
|
|
77
|
+
bestPosition = "top";
|
|
78
|
+
else if (maxSpace === spaceBelow)
|
|
79
|
+
bestPosition = "bottom";
|
|
80
|
+
else if (maxSpace === spaceLeft)
|
|
81
|
+
bestPosition = "left";
|
|
82
|
+
else
|
|
83
|
+
bestPosition = "right";
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Posição específica foi fornecida - tenta usar ela primeiro
|
|
87
|
+
// Verifica se a posição especificada tem espaço suficiente
|
|
88
|
+
const hasEnoughSpace = (position === "top" && spaceAbove >= tooltipHeight + spacing + 8) ||
|
|
89
|
+
(position === "bottom" && spaceBelow >= tooltipHeight + spacing + 8) ||
|
|
90
|
+
(position === "left" && spaceLeft >= tooltipWidth + spacing + 8) ||
|
|
91
|
+
(position === "right" && spaceRight >= tooltipWidth + spacing + 8);
|
|
92
|
+
if (hasEnoughSpace) {
|
|
93
|
+
// Tem espaço suficiente, usa a posição especificada
|
|
94
|
+
bestPosition = position;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// Não tem espaço suficiente na posição especificada, escolhe a melhor alternativa
|
|
98
|
+
// Para posições verticais (top/bottom), compara entre elas
|
|
99
|
+
if (position === "top" || position === "bottom") {
|
|
100
|
+
bestPosition = spaceAbove > spaceBelow ? "top" : "bottom";
|
|
101
|
+
}
|
|
102
|
+
// Para posições horizontais (left/right), compara entre elas
|
|
103
|
+
else if (position === "left" || position === "right") {
|
|
104
|
+
bestPosition = spaceLeft > spaceRight ? "left" : "right";
|
|
105
|
+
}
|
|
106
|
+
// Fallback: escolhe a direção com mais espaço
|
|
107
|
+
else {
|
|
108
|
+
const maxSpace = Math.max(spaceAbove, spaceBelow, spaceLeft, spaceRight);
|
|
109
|
+
if (maxSpace === spaceAbove)
|
|
110
|
+
bestPosition = "top";
|
|
111
|
+
else if (maxSpace === spaceBelow)
|
|
112
|
+
bestPosition = "bottom";
|
|
113
|
+
else if (maxSpace === spaceLeft)
|
|
114
|
+
bestPosition = "left";
|
|
115
|
+
else
|
|
116
|
+
bestPosition = "right";
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Calcula posição base na melhor direção escolhida
|
|
121
|
+
switch (bestPosition) {
|
|
122
|
+
case "top":
|
|
123
|
+
top = rect.top - spacing;
|
|
124
|
+
left = rect.left + rect.width / 2;
|
|
125
|
+
break;
|
|
126
|
+
case "bottom":
|
|
127
|
+
top = rect.bottom + spacing;
|
|
128
|
+
left = rect.left + rect.width / 2;
|
|
129
|
+
break;
|
|
130
|
+
case "left":
|
|
131
|
+
top = rect.top + rect.height / 2;
|
|
132
|
+
// Para left, a tooltip fica à esquerda do elemento
|
|
133
|
+
// left é onde a tooltip termina (borda direita), então subtrai a largura
|
|
134
|
+
left = rect.left - tooltipWidth - spacing;
|
|
135
|
+
break;
|
|
136
|
+
case "right":
|
|
137
|
+
top = rect.top + rect.height / 2;
|
|
138
|
+
left = rect.right + spacing;
|
|
139
|
+
break;
|
|
140
|
+
default:
|
|
141
|
+
top = rect.bottom + spacing;
|
|
142
|
+
left = rect.left + rect.width / 2;
|
|
143
|
+
bestPosition = "bottom";
|
|
144
|
+
}
|
|
145
|
+
finalPosition = bestPosition;
|
|
146
|
+
// Ajusta horizontalmente para tooltips top/bottom - mantém próximo ao elemento
|
|
147
|
+
if (finalPosition === "bottom" || finalPosition === "top") {
|
|
148
|
+
// Centraliza no elemento, mas ajusta apenas se necessário para não sair da tela
|
|
149
|
+
const elementCenterX = rect.left + rect.width / 2;
|
|
150
|
+
const minLeft = tooltipWidth / 2 + 8;
|
|
151
|
+
const maxLeft = viewportWidth - tooltipWidth / 2 - 8;
|
|
152
|
+
// Mantém o mais próximo possível do centro do elemento
|
|
153
|
+
if (elementCenterX < minLeft) {
|
|
154
|
+
left = minLeft;
|
|
155
|
+
}
|
|
156
|
+
else if (elementCenterX > maxLeft) {
|
|
157
|
+
left = maxLeft;
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
left = elementCenterX; // Mantém centralizado no elemento
|
|
161
|
+
}
|
|
162
|
+
// Verifica espaço vertical e ajusta posição se necessário
|
|
163
|
+
if (finalPosition === "bottom") {
|
|
164
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
165
|
+
if (spaceBelow < tooltipHeight + spacing + 8) {
|
|
166
|
+
const spaceAbove = rect.top;
|
|
167
|
+
if (spaceAbove > spaceBelow && spaceAbove > tooltipHeight + spacing + 8) {
|
|
168
|
+
top = rect.top - spacing;
|
|
169
|
+
finalPosition = "top";
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// Mantém próximo ao elemento mesmo se não couber perfeitamente
|
|
173
|
+
top = rect.bottom + spacing;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (finalPosition === "top") {
|
|
178
|
+
const spaceAbove = rect.top;
|
|
179
|
+
if (spaceAbove < tooltipHeight + spacing + 8) {
|
|
180
|
+
const spaceBelow = viewportHeight - rect.bottom;
|
|
181
|
+
if (spaceBelow > spaceAbove && spaceBelow > tooltipHeight + spacing + 8) {
|
|
182
|
+
top = rect.bottom + spacing;
|
|
183
|
+
finalPosition = "bottom";
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Mantém próximo ao elemento mesmo se não couber perfeitamente
|
|
187
|
+
top = rect.top - spacing;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Ajusta verticalmente para tooltips left/right - mantém próximo ao elemento
|
|
193
|
+
else if (finalPosition === "left" || finalPosition === "right") {
|
|
194
|
+
// Centraliza no elemento verticalmente
|
|
195
|
+
const elementCenterY = rect.top + rect.height / 2;
|
|
196
|
+
const minTop = tooltipHeight / 2 + 8;
|
|
197
|
+
const maxTop = viewportHeight - tooltipHeight / 2 - 8;
|
|
198
|
+
// Mantém o mais próximo possível do centro do elemento
|
|
199
|
+
if (elementCenterY < minTop) {
|
|
200
|
+
top = minTop;
|
|
201
|
+
}
|
|
202
|
+
else if (elementCenterY > maxTop) {
|
|
203
|
+
top = maxTop;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
top = elementCenterY; // Mantém centralizado no elemento
|
|
207
|
+
}
|
|
208
|
+
// Verifica espaço horizontal e ajusta posição se necessário
|
|
209
|
+
if (finalPosition === "left") {
|
|
210
|
+
const spaceLeft = rect.left;
|
|
211
|
+
if (spaceLeft < tooltipWidth + spacing + 8) {
|
|
212
|
+
const spaceRight = viewportWidth - rect.right;
|
|
213
|
+
if (spaceRight > spaceLeft && spaceRight > tooltipWidth + spacing + 8) {
|
|
214
|
+
// Muda para direita se houver mais espaço
|
|
215
|
+
left = rect.right + spacing;
|
|
216
|
+
finalPosition = "right";
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// Mantém à esquerda, mas ajusta para ficar visível
|
|
220
|
+
left = Math.max(8, rect.left - tooltipWidth - spacing);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Se tem espaço suficiente, o left já foi calculado corretamente no switch acima
|
|
224
|
+
}
|
|
225
|
+
else if (finalPosition === "right") {
|
|
226
|
+
const spaceRight = viewportWidth - rect.right;
|
|
227
|
+
if (spaceRight < tooltipWidth + spacing + 8) {
|
|
228
|
+
const spaceLeft = rect.left;
|
|
229
|
+
if (spaceLeft > spaceRight && spaceLeft > tooltipWidth + spacing + 8) {
|
|
230
|
+
// Muda para esquerda se houver mais espaço
|
|
231
|
+
left = rect.left - tooltipWidth - spacing;
|
|
232
|
+
finalPosition = "left";
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// Mantém à direita, mas ajusta para ficar visível
|
|
236
|
+
left = Math.min(viewportWidth - tooltipWidth - 8, rect.right + spacing);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Se tem espaço suficiente, o left já foi calculado corretamente no switch acima
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return { top, left, position: finalPosition };
|
|
243
|
+
}, []);
|
|
244
|
+
// Configura o tour quando steps ou options mudam
|
|
245
|
+
const configureTour = useCallback(() => {
|
|
246
|
+
isConfiguredRef.current = true;
|
|
247
|
+
}, []);
|
|
248
|
+
// Renderiza os steps (mostra/esconde o tour)
|
|
249
|
+
const renderSteps = useCallback(() => {
|
|
250
|
+
if (enabled && steps.length > 0 && !isVisible) {
|
|
251
|
+
setIsVisible(true);
|
|
252
|
+
setCurrentStep(initialStep);
|
|
253
|
+
}
|
|
254
|
+
else if (!enabled && isVisible) {
|
|
255
|
+
setIsVisible(false);
|
|
256
|
+
setCurrentStep(initialStep);
|
|
257
|
+
setHighlightedElement(null);
|
|
258
|
+
setTooltipPosition(null);
|
|
259
|
+
}
|
|
260
|
+
}, [enabled, steps.length, isVisible, initialStep]);
|
|
261
|
+
// Função auxiliar para buscar elemento por seletor CSS ou data-onboard
|
|
262
|
+
const findElement = useCallback((selector) => {
|
|
263
|
+
// Se o seletor começa com caracteres de seletor CSS (. # [ etc), usa diretamente
|
|
264
|
+
if (/^[.#\[:>+\s~]/.test(selector)) {
|
|
265
|
+
return document.querySelector(selector);
|
|
266
|
+
}
|
|
267
|
+
// Caso contrário, assume que é um valor de data-onboard
|
|
268
|
+
return document.querySelector(`[data-onboard="${selector}"]`);
|
|
269
|
+
}, []);
|
|
270
|
+
// Destaca o elemento atual
|
|
271
|
+
useEffect(() => {
|
|
272
|
+
if (!isVisible || currentStep < 0 || currentStep >= steps.length) {
|
|
273
|
+
setHighlightedElement(null);
|
|
274
|
+
setTooltipPosition(null);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const step = steps[currentStep];
|
|
278
|
+
const element = findElement(step.element);
|
|
279
|
+
if (!element) {
|
|
280
|
+
console.warn(`Elemento não encontrado: ${step.element}. Tentando buscar [data-onboard="${step.element}"]`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
setHighlightedElement(element);
|
|
284
|
+
// Scroll para o elemento
|
|
285
|
+
element.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
286
|
+
// Função para calcular e atualizar posição
|
|
287
|
+
const updatePosition = () => {
|
|
288
|
+
const updatedRect = element.getBoundingClientRect();
|
|
289
|
+
if (updatedRect.width > 0 && updatedRect.height > 0) {
|
|
290
|
+
// Usa "auto" como default se position não for especificado
|
|
291
|
+
const position = calculateTooltipPosition(element, step.position || "auto");
|
|
292
|
+
setTooltipPosition(position);
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
// Calcula posição inicial após um delay para garantir que o scroll terminou
|
|
296
|
+
setTimeout(updatePosition, 400);
|
|
297
|
+
// Adiciona listeners para recalcular posição em tempo real
|
|
298
|
+
const handleScroll = () => {
|
|
299
|
+
// Usa requestAnimationFrame para suavizar a atualização durante scroll
|
|
300
|
+
requestAnimationFrame(updatePosition);
|
|
301
|
+
};
|
|
302
|
+
const handleResize = () => {
|
|
303
|
+
updatePosition();
|
|
304
|
+
};
|
|
305
|
+
// Adiciona listeners em window e document para capturar todos os tipos de scroll
|
|
306
|
+
window.addEventListener("scroll", handleScroll, { passive: true, capture: true });
|
|
307
|
+
window.addEventListener("resize", handleResize, { passive: true });
|
|
308
|
+
document.addEventListener("scroll", handleScroll, { passive: true, capture: true });
|
|
309
|
+
return () => {
|
|
310
|
+
window.removeEventListener("scroll", handleScroll, true);
|
|
311
|
+
window.removeEventListener("resize", handleResize);
|
|
312
|
+
document.removeEventListener("scroll", handleScroll, true);
|
|
313
|
+
};
|
|
314
|
+
}, [isVisible, currentStep, steps, calculateTooltipPosition, findElement]);
|
|
315
|
+
// Adiciona overlay e highlight ao elemento
|
|
316
|
+
useEffect(() => {
|
|
317
|
+
if (!highlightedElement) {
|
|
318
|
+
// Remove highlights anteriores
|
|
319
|
+
document.querySelectorAll(".tour-highlight").forEach((el) => {
|
|
320
|
+
el.classList.remove("tour-highlight");
|
|
321
|
+
el.style.zIndex = "";
|
|
322
|
+
});
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
// Adiciona classe de highlight e z-index alto
|
|
326
|
+
highlightedElement.classList.add("tour-highlight");
|
|
327
|
+
const originalZIndex = highlightedElement.style.zIndex;
|
|
328
|
+
highlightedElement.style.zIndex = "10000";
|
|
329
|
+
return () => {
|
|
330
|
+
highlightedElement.classList.remove("tour-highlight");
|
|
331
|
+
highlightedElement.style.zIndex = originalZIndex;
|
|
332
|
+
};
|
|
333
|
+
}, [highlightedElement]);
|
|
334
|
+
// Configura o tour quando necessário
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (!isConfiguredRef.current || steps.length > 0) {
|
|
337
|
+
configureTour();
|
|
338
|
+
renderSteps();
|
|
339
|
+
}
|
|
340
|
+
}, [steps, configureTour, renderSteps]);
|
|
341
|
+
// Atualiza quando enabled muda
|
|
342
|
+
useEffect(() => {
|
|
343
|
+
renderSteps();
|
|
344
|
+
}, [enabled, renderSteps]);
|
|
345
|
+
// Handler de teclado (ESC e setas)
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
if (!isVisible || !exitOnEsc)
|
|
348
|
+
return;
|
|
349
|
+
const handleKeyDown = (e) => {
|
|
350
|
+
if (e.key === "Escape") {
|
|
351
|
+
handleExit();
|
|
352
|
+
}
|
|
353
|
+
else if (e.key === "ArrowRight") {
|
|
354
|
+
handleNext();
|
|
355
|
+
}
|
|
356
|
+
else if (e.key === "ArrowLeft") {
|
|
357
|
+
handlePrev();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
361
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
362
|
+
}, [isVisible, exitOnEsc, currentStep, steps.length]);
|
|
363
|
+
// Controla overflow do body
|
|
364
|
+
useEffect(() => {
|
|
365
|
+
if (isVisible) {
|
|
366
|
+
document.body.style.overflow = "hidden";
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
document.body.style.overflow = "";
|
|
370
|
+
}
|
|
371
|
+
return () => {
|
|
372
|
+
document.body.style.overflow = "";
|
|
373
|
+
};
|
|
374
|
+
}, [isVisible]);
|
|
375
|
+
const handleNext = useCallback(() => {
|
|
376
|
+
if (currentStep < steps.length - 1) {
|
|
377
|
+
setCurrentStep(currentStep + 1);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
handleComplete();
|
|
381
|
+
}
|
|
382
|
+
}, [currentStep, steps.length]);
|
|
383
|
+
const handlePrev = useCallback(() => {
|
|
384
|
+
if (currentStep > 0) {
|
|
385
|
+
setCurrentStep(currentStep - 1);
|
|
386
|
+
}
|
|
387
|
+
}, [currentStep]);
|
|
388
|
+
const handleSkip = useCallback(() => {
|
|
389
|
+
handleExit();
|
|
390
|
+
}, []);
|
|
391
|
+
const handleComplete = useCallback(() => {
|
|
392
|
+
onComplete?.();
|
|
393
|
+
handleExit();
|
|
394
|
+
}, [onComplete]);
|
|
395
|
+
const handleExit = useCallback(() => {
|
|
396
|
+
setIsVisible(false);
|
|
397
|
+
setCurrentStep(initialStep);
|
|
398
|
+
setHighlightedElement(null);
|
|
399
|
+
setTooltipPosition(null);
|
|
400
|
+
onExit?.();
|
|
401
|
+
}, [initialStep, onExit]);
|
|
402
|
+
const handleOverlayClick = useCallback((e) => {
|
|
403
|
+
if (exitOnOverlayClick && e.target === overlayRef.current) {
|
|
404
|
+
handleExit();
|
|
405
|
+
}
|
|
406
|
+
}, [exitOnOverlayClick, handleExit]);
|
|
407
|
+
if (!isVisible) {
|
|
408
|
+
return null;
|
|
409
|
+
}
|
|
410
|
+
const currentStepData = steps[currentStep];
|
|
411
|
+
const isFirstStep = currentStep === 0;
|
|
412
|
+
const isLastStep = currentStep === steps.length - 1;
|
|
413
|
+
const progress = ((currentStep + 1) / steps.length) * 100;
|
|
414
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: overlayRef, onClick: handleOverlayClick, style: {
|
|
415
|
+
position: "fixed",
|
|
416
|
+
top: 0,
|
|
417
|
+
left: 0,
|
|
418
|
+
right: 0,
|
|
419
|
+
bottom: 0,
|
|
420
|
+
backgroundColor: "rgba(0, 0, 0, 0.6)",
|
|
421
|
+
zIndex: 9998,
|
|
422
|
+
pointerEvents: exitOnOverlayClick ? "auto" : "none",
|
|
423
|
+
} }), tooltipPosition && currentStepData && highlightedElement && (_jsxs("div", { ref: tooltipRef, className: "tour:max-w-sm", style: {
|
|
424
|
+
position: "fixed",
|
|
425
|
+
top: tooltipPosition.position === "bottom" || tooltipPosition.position === "left" || tooltipPosition.position === "right"
|
|
426
|
+
? `${tooltipPosition.top}px`
|
|
427
|
+
: undefined,
|
|
428
|
+
bottom: tooltipPosition.position === "top" ? `${window.innerHeight - tooltipPosition.top}px` : undefined,
|
|
429
|
+
left: tooltipPosition.position === "left" || tooltipPosition.position === "right"
|
|
430
|
+
? `${tooltipPosition.left}px`
|
|
431
|
+
: `${tooltipPosition.left}px`,
|
|
432
|
+
transform: tooltipPosition.position === "left" || tooltipPosition.position === "right"
|
|
433
|
+
? "translate(0, -50%)"
|
|
434
|
+
: "translate(-50%, 0)",
|
|
435
|
+
zIndex: 10001,
|
|
436
|
+
}, children: [_jsxs("div", { className: "tour:bg-white tour:rounded-lg tour:shadow-xl tour:p-6 tour:relative", children: [_jsx("button", { onClick: handleExit, className: "tour:absolute tour:top-2 tour:right-2 tour:p-1 tour:rounded-full tour:hover:bg-gray-100 tour:transition-colors tour:z-10", "aria-label": "Fechar tour", children: _jsx(HiXMark, { className: "tour:w-5 tour:h-5 tour:text-gray-500" }) }), currentStepData.title && (_jsx("h3", { className: "tour:text-lg tour:font-semibold tour:text-gray-900 tour:mb-2 tour:pr-6", children: currentStepData.title })), _jsx("p", { className: "tour:text-gray-700 tour:mb-4", children: currentStepData.intro }), showProgress && (_jsxs("div", { className: "tour:mb-4", children: [_jsx("div", { className: "tour:w-full tour:bg-gray-200 tour:rounded-full tour:h-2", children: _jsx("div", { className: "tour:bg-brand-primary tour:h-2 tour:rounded-full tour:transition-all tour:duration-300", style: { width: `${progress}%` } }) }), _jsxs("p", { className: "tour:text-xs tour:text-gray-500 tour:mt-1 tour:text-center", children: [currentStep + 1, " de ", steps.length] })] })), showBullets && (_jsx("div", { className: "tour:flex tour:justify-center tour:gap-1 tour:mb-4", children: steps.map((_, index) => (_jsx("button", { onClick: () => setCurrentStep(index), className: `tour:w-2 tour:h-2 tour:rounded-full tour:transition-all ${index === currentStep
|
|
437
|
+
? "tour:bg-brand-primary tour:w-6"
|
|
438
|
+
: "tour:bg-gray-300 tour:hover:bg-gray-400"}`, "aria-label": `Ir para passo ${index + 1}` }, index))) })), _jsxs("div", { className: "tour:flex tour:justify-between tour:items-center tour:gap-2", children: [_jsx("div", { className: "tour:flex tour:gap-2", children: !isFirstStep && (_jsx(Button, { variant: "bordered", onPress: handlePrev, startContent: _jsx(HiChevronLeft, { className: "tour:w-4 tour:h-4" }), children: prevLabel })) }), _jsxs("div", { className: "tour:flex tour:gap-2", children: [_jsx(Button, { variant: "light", onPress: handleSkip, children: skipLabel }), _jsx(Button, { color: "primary", onPress: isLastStep ? handleComplete : handleNext, endContent: !isLastStep ? _jsx(HiChevronRight, { className: "tour:w-4 tour:h-4" }) : undefined, children: isLastStep ? doneLabel : nextLabel })] })] })] }), tooltipPosition.position === "bottom" && (_jsx("div", { className: "tour:absolute tour:w-0 tour:h-0 tour:border-8 tour:border-transparent", style: {
|
|
439
|
+
top: "-16px",
|
|
440
|
+
left: "50%",
|
|
441
|
+
transform: "translateX(-50%)",
|
|
442
|
+
borderColor: "transparent transparent white transparent",
|
|
443
|
+
} })), tooltipPosition.position === "top" && (_jsx("div", { className: "tour:absolute tour:w-0 tour:h-0 tour:border-8 tour:border-transparent", style: {
|
|
444
|
+
bottom: "-16px",
|
|
445
|
+
left: "50%",
|
|
446
|
+
transform: "translateX(-50%)",
|
|
447
|
+
borderColor: "white transparent transparent transparent",
|
|
448
|
+
} })), tooltipPosition.position === "right" && (_jsx("div", { className: "tour:absolute tour:w-0 tour:h-0 tour:border-8 tour:border-transparent", style: {
|
|
449
|
+
left: "-16px",
|
|
450
|
+
top: "50%",
|
|
451
|
+
transform: "translateY(-50%)",
|
|
452
|
+
borderColor: "transparent white transparent transparent",
|
|
453
|
+
} })), tooltipPosition.position === "left" && (_jsx("div", { className: "tour:absolute tour:w-0 tour:h-0 tour:border-8 tour:border-transparent", style: {
|
|
454
|
+
right: "-16px",
|
|
455
|
+
top: "50%",
|
|
456
|
+
transform: "translateY(-50%)",
|
|
457
|
+
borderColor: "transparent transparent transparent white",
|
|
458
|
+
} }))] })), _jsx("style", { children: `
|
|
459
|
+
.tour-highlight {
|
|
460
|
+
position: relative !important;
|
|
461
|
+
z-index: 10000 !important;
|
|
462
|
+
outline: 3px solid var(--brand-primary, #0056b0) !important;
|
|
463
|
+
outline-offset: 2px;
|
|
464
|
+
border-radius: 4px;
|
|
465
|
+
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.6) !important;
|
|
466
|
+
}
|
|
467
|
+
` })] }));
|
|
468
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { Tour } from "./components/Tour";
|
|
2
|
-
export type { TourProps, TourStep, TourOptions } from "./types";
|
|
3
|
-
|
|
1
|
+
export { Tour } from "./components/Tour";
|
|
2
|
+
export type { TourProps, TourStep, TourOptions } from "./types";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC"}
|