@pablo2410/shared-ui 0.21.0 → 0.22.1
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/{chunk-57T6QK3M.js → chunk-4ERWOH67.js} +43 -1
- package/dist/chunk-4ERWOH67.js.map +1 -0
- package/dist/{chunk-K6FHU4HN.js → chunk-JEHQIJS2.js} +20 -3
- package/dist/{chunk-K6FHU4HN.js.map → chunk-JEHQIJS2.js.map} +1 -1
- package/dist/{chunk-VJEXXX2P.js → chunk-RSFGV7CG.js} +2 -2
- package/dist/{chunk-LR5TUYFD.js → chunk-S4UQSTZN.js} +3 -3
- package/dist/hierarchy/index.js +2 -2
- package/dist/layout/index.js +3 -3
- package/dist/primitives/index.js +1 -1
- package/dist/reporting/index.js +1 -1
- package/dist/services/index.d.ts +19 -2
- package/dist/services/index.js +1 -1
- package/dist/shell/index.js +4 -4
- package/dist/shell/oplytics-shell.css +1 -1
- package/dist/tour/index.d.ts +16 -3
- package/dist/tour/index.js +440 -274
- package/dist/tour/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-57T6QK3M.js.map +0 -1
- /package/dist/{chunk-VJEXXX2P.js.map → chunk-RSFGV7CG.js.map} +0 -0
- /package/dist/{chunk-LR5TUYFD.js.map → chunk-S4UQSTZN.js.map} +0 -0
package/dist/tour/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/tour/TourShell.tsx
|
|
2
|
-
import { useEffect, useRef } from "react";
|
|
2
|
+
import { useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
3
3
|
import {
|
|
4
4
|
X,
|
|
5
5
|
ChevronLeft,
|
|
@@ -7,283 +7,15 @@ import {
|
|
|
7
7
|
ArrowRight,
|
|
8
8
|
ExternalLink
|
|
9
9
|
} from "lucide-react";
|
|
10
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
var defaultSkin = {
|
|
12
|
-
accent: "#8C34E9",
|
|
13
|
-
overlayBg: "rgba(10,14,26,0.72)",
|
|
14
|
-
panelBg: "#0e1624",
|
|
15
|
-
panelBorder: "1px solid #1e2738",
|
|
16
|
-
mutedText: "#596475",
|
|
17
|
-
bodyText: "#c0c6d0",
|
|
18
|
-
softText: "#8890a0"
|
|
19
|
-
};
|
|
20
|
-
function TourShell({
|
|
21
|
-
step,
|
|
22
|
-
index,
|
|
23
|
-
total,
|
|
24
|
-
isFirst,
|
|
25
|
-
isLast,
|
|
26
|
-
onNext,
|
|
27
|
-
onBack,
|
|
28
|
-
onDismiss,
|
|
29
|
-
narration,
|
|
30
|
-
narrationPending,
|
|
31
|
-
children,
|
|
32
|
-
skin = defaultSkin,
|
|
33
|
-
orb
|
|
34
|
-
}) {
|
|
35
|
-
const panelRef = useRef(null);
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const onKeyDown = (e) => {
|
|
38
|
-
if (e.key === "Escape") onDismiss();
|
|
39
|
-
};
|
|
40
|
-
document.addEventListener("keydown", onKeyDown);
|
|
41
|
-
return () => document.removeEventListener("keydown", onKeyDown);
|
|
42
|
-
}, [onDismiss]);
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
panelRef.current?.focus();
|
|
45
|
-
}, [step.id]);
|
|
46
|
-
return /* @__PURE__ */ jsx(
|
|
47
|
-
"div",
|
|
48
|
-
{
|
|
49
|
-
className: "fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
50
|
-
style: { background: skin.overlayBg },
|
|
51
|
-
onClick: (e) => {
|
|
52
|
-
if (e.target === e.currentTarget) onDismiss();
|
|
53
|
-
},
|
|
54
|
-
children: /* @__PURE__ */ jsxs(
|
|
55
|
-
"div",
|
|
56
|
-
{
|
|
57
|
-
ref: panelRef,
|
|
58
|
-
tabIndex: -1,
|
|
59
|
-
role: "dialog",
|
|
60
|
-
"aria-modal": "true",
|
|
61
|
-
"aria-label": step.label,
|
|
62
|
-
className: "w-full max-w-md rounded-xl overflow-hidden flex flex-col max-h-[85vh] outline-none",
|
|
63
|
-
style: { background: skin.panelBg, border: skin.panelBorder },
|
|
64
|
-
children: [
|
|
65
|
-
/* @__PURE__ */ jsxs(
|
|
66
|
-
"div",
|
|
67
|
-
{
|
|
68
|
-
className: "flex items-center justify-between gap-3 px-5 py-4 shrink-0",
|
|
69
|
-
style: {
|
|
70
|
-
background: "linear-gradient(135deg, #2a1450 0%, #1a0e33 100%)"
|
|
71
|
-
},
|
|
72
|
-
children: [
|
|
73
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
|
|
74
|
-
orb,
|
|
75
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-white truncate", children: step.label })
|
|
76
|
-
] }),
|
|
77
|
-
/* @__PURE__ */ jsx(
|
|
78
|
-
"button",
|
|
79
|
-
{
|
|
80
|
-
onClick: onDismiss,
|
|
81
|
-
className: "p-1 rounded hover:bg-white/10 transition-colors shrink-0",
|
|
82
|
-
title: "Skip tour",
|
|
83
|
-
children: /* @__PURE__ */ jsx(X, { size: 16, className: "text-white/70" })
|
|
84
|
-
}
|
|
85
|
-
)
|
|
86
|
-
]
|
|
87
|
-
}
|
|
88
|
-
),
|
|
89
|
-
/* @__PURE__ */ jsx(
|
|
90
|
-
"div",
|
|
91
|
-
{
|
|
92
|
-
className: "flex items-center gap-1.5 px-5 py-3 shrink-0",
|
|
93
|
-
style: { borderBottom: skin.panelBorder },
|
|
94
|
-
children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx(
|
|
95
|
-
"span",
|
|
96
|
-
{
|
|
97
|
-
className: "h-1.5 rounded-full transition-all",
|
|
98
|
-
style: {
|
|
99
|
-
width: i === index ? 20 : 8,
|
|
100
|
-
background: i === index ? skin.accent : skin.mutedText,
|
|
101
|
-
opacity: i === index ? 1 : 0.4
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
i
|
|
105
|
-
))
|
|
106
|
-
}
|
|
107
|
-
),
|
|
108
|
-
/* @__PURE__ */ jsxs("div", { className: "overflow-y-auto px-5 py-4 space-y-4", children: [
|
|
109
|
-
/* @__PURE__ */ jsx(
|
|
110
|
-
"p",
|
|
111
|
-
{
|
|
112
|
-
className: "text-[13px] leading-relaxed transition-opacity",
|
|
113
|
-
style: {
|
|
114
|
-
color: skin.bodyText,
|
|
115
|
-
opacity: narrationPending ? 0.7 : 1
|
|
116
|
-
},
|
|
117
|
-
children: narration
|
|
118
|
-
}
|
|
119
|
-
),
|
|
120
|
-
step.highlights && step.highlights.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-1.5", children: step.highlights.map((h, i) => /* @__PURE__ */ jsxs(
|
|
121
|
-
"li",
|
|
122
|
-
{
|
|
123
|
-
className: "text-[12.5px] leading-relaxed pl-3 relative",
|
|
124
|
-
style: { color: skin.softText },
|
|
125
|
-
children: [
|
|
126
|
-
/* @__PURE__ */ jsx(
|
|
127
|
-
"span",
|
|
128
|
-
{
|
|
129
|
-
className: "absolute left-0",
|
|
130
|
-
style: { color: skin.mutedText },
|
|
131
|
-
children: "\u2022"
|
|
132
|
-
}
|
|
133
|
-
),
|
|
134
|
-
h
|
|
135
|
-
]
|
|
136
|
-
},
|
|
137
|
-
i
|
|
138
|
-
)) }),
|
|
139
|
-
step.action && /* @__PURE__ */ jsxs(
|
|
140
|
-
"a",
|
|
141
|
-
{
|
|
142
|
-
href: step.action.href,
|
|
143
|
-
target: step.action.external ? "_blank" : void 0,
|
|
144
|
-
rel: step.action.external ? "noopener noreferrer" : void 0,
|
|
145
|
-
className: "inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-[13px] font-semibold transition-opacity hover:opacity-90",
|
|
146
|
-
style: { background: `${skin.accent}20`, color: skin.accent },
|
|
147
|
-
children: [
|
|
148
|
-
step.action.label,
|
|
149
|
-
step.action.external ? /* @__PURE__ */ jsx(ExternalLink, { size: 13 }) : /* @__PURE__ */ jsx(ArrowRight, { size: 13 })
|
|
150
|
-
]
|
|
151
|
-
}
|
|
152
|
-
),
|
|
153
|
-
children
|
|
154
|
-
] }),
|
|
155
|
-
/* @__PURE__ */ jsxs(
|
|
156
|
-
"div",
|
|
157
|
-
{
|
|
158
|
-
className: "flex items-center justify-between gap-2 px-5 py-3.5 shrink-0",
|
|
159
|
-
style: { borderTop: skin.panelBorder },
|
|
160
|
-
children: [
|
|
161
|
-
/* @__PURE__ */ jsx(
|
|
162
|
-
"button",
|
|
163
|
-
{
|
|
164
|
-
onClick: onDismiss,
|
|
165
|
-
className: "text-[12px] font-medium transition-colors hover:underline",
|
|
166
|
-
style: { color: skin.mutedText },
|
|
167
|
-
children: "Skip tour"
|
|
168
|
-
}
|
|
169
|
-
),
|
|
170
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
171
|
-
!isFirst && /* @__PURE__ */ jsxs(
|
|
172
|
-
"button",
|
|
173
|
-
{
|
|
174
|
-
onClick: onBack,
|
|
175
|
-
className: "flex items-center gap-1 px-3 py-1.5 rounded-md text-[12.5px] font-medium transition-colors",
|
|
176
|
-
style: { color: skin.bodyText, border: skin.panelBorder },
|
|
177
|
-
children: [
|
|
178
|
-
/* @__PURE__ */ jsx(ChevronLeft, { size: 13 }),
|
|
179
|
-
"Back"
|
|
180
|
-
]
|
|
181
|
-
}
|
|
182
|
-
),
|
|
183
|
-
/* @__PURE__ */ jsxs(
|
|
184
|
-
"button",
|
|
185
|
-
{
|
|
186
|
-
onClick: onNext,
|
|
187
|
-
className: "flex items-center gap-1 px-3.5 py-1.5 rounded-md text-[12.5px] font-bold text-white transition-opacity hover:opacity-90",
|
|
188
|
-
style: {
|
|
189
|
-
background: "linear-gradient(135deg, #8C34E9 0%, #5B1FA6 100%)"
|
|
190
|
-
},
|
|
191
|
-
children: [
|
|
192
|
-
isLast ? "Done" : "Next",
|
|
193
|
-
!isLast && /* @__PURE__ */ jsx(ChevronRight, { size: 13 })
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
)
|
|
197
|
-
] })
|
|
198
|
-
]
|
|
199
|
-
}
|
|
200
|
-
)
|
|
201
|
-
]
|
|
202
|
-
}
|
|
203
|
-
)
|
|
204
|
-
}
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// src/tour/useTour.ts
|
|
209
|
-
import { useCallback, useState } from "react";
|
|
210
|
-
|
|
211
|
-
// src/tour/tourMachine.ts
|
|
212
|
-
function resolveInitialIndex(steps, initialStepId) {
|
|
213
|
-
if (!initialStepId) return 0;
|
|
214
|
-
const found = steps.findIndex((s) => s.id === initialStepId);
|
|
215
|
-
return found === -1 ? 0 : found;
|
|
216
|
-
}
|
|
217
|
-
function nextIndex(steps, current) {
|
|
218
|
-
if (current >= steps.length - 1) {
|
|
219
|
-
return { index: current, completed: true };
|
|
220
|
-
}
|
|
221
|
-
return { index: current + 1, completed: false };
|
|
222
|
-
}
|
|
223
|
-
function backIndex(current) {
|
|
224
|
-
return Math.max(0, current - 1);
|
|
225
|
-
}
|
|
226
|
-
function gotoIndex(steps, stepId, current) {
|
|
227
|
-
const target = steps.findIndex((s) => s.id === stepId);
|
|
228
|
-
return target === -1 ? current : target;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// src/tour/useTour.ts
|
|
232
|
-
function useTour(steps, opts = {}) {
|
|
233
|
-
if (steps.length === 0) {
|
|
234
|
-
throw new Error("useTour requires at least one step");
|
|
235
|
-
}
|
|
236
|
-
const [index, setIndex] = useState(
|
|
237
|
-
() => resolveInitialIndex(steps, opts.initialStepId)
|
|
238
|
-
);
|
|
239
|
-
const next = useCallback(() => {
|
|
240
|
-
const result = nextIndex(steps, index);
|
|
241
|
-
if (result.completed) {
|
|
242
|
-
opts.onComplete?.();
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
setIndex(result.index);
|
|
246
|
-
opts.onStepChange?.(steps[result.index], result.index);
|
|
247
|
-
}, [index, steps]);
|
|
248
|
-
const back = useCallback(() => {
|
|
249
|
-
const target = backIndex(index);
|
|
250
|
-
if (target === index) return;
|
|
251
|
-
setIndex(target);
|
|
252
|
-
opts.onStepChange?.(steps[target], target);
|
|
253
|
-
}, [index, steps]);
|
|
254
|
-
const goto = useCallback(
|
|
255
|
-
(stepId) => {
|
|
256
|
-
const target = gotoIndex(steps, stepId, index);
|
|
257
|
-
if (target === index) return;
|
|
258
|
-
setIndex(target);
|
|
259
|
-
opts.onStepChange?.(steps[target], target);
|
|
260
|
-
},
|
|
261
|
-
[steps, index]
|
|
262
|
-
);
|
|
263
|
-
const dismiss = useCallback(() => {
|
|
264
|
-
opts.onDismiss?.();
|
|
265
|
-
}, []);
|
|
266
|
-
return {
|
|
267
|
-
step: steps[index],
|
|
268
|
-
index,
|
|
269
|
-
total: steps.length,
|
|
270
|
-
isFirst: index === 0,
|
|
271
|
-
isLast: index === steps.length - 1,
|
|
272
|
-
next,
|
|
273
|
-
back,
|
|
274
|
-
goto,
|
|
275
|
-
dismiss
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
10
|
|
|
279
11
|
// src/tour/useSpotlight.ts
|
|
280
|
-
import { useEffect
|
|
12
|
+
import { useEffect, useRef, useState } from "react";
|
|
281
13
|
var RESIZE_DEBOUNCE_MS = 100;
|
|
282
14
|
function useSpotlight(selector) {
|
|
283
|
-
const [rect, setRect] =
|
|
284
|
-
const [found, setFound] =
|
|
285
|
-
const scrolledForSelector =
|
|
286
|
-
|
|
15
|
+
const [rect, setRect] = useState(null);
|
|
16
|
+
const [found, setFound] = useState(false);
|
|
17
|
+
const scrolledForSelector = useRef(null);
|
|
18
|
+
useEffect(() => {
|
|
287
19
|
if (!selector) {
|
|
288
20
|
setRect(null);
|
|
289
21
|
setFound(false);
|
|
@@ -433,6 +165,440 @@ function fits(pos, panelSize, viewport) {
|
|
|
433
165
|
function clamp(value, min, max) {
|
|
434
166
|
return Math.min(Math.max(value, min), max);
|
|
435
167
|
}
|
|
168
|
+
|
|
169
|
+
// src/tour/TourShell.tsx
|
|
170
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
171
|
+
var defaultSkin = {
|
|
172
|
+
accent: "#8C34E9",
|
|
173
|
+
overlayBg: "rgba(10,14,26,0.72)",
|
|
174
|
+
panelBg: "#0e1624",
|
|
175
|
+
panelBorder: "1px solid #1e2738",
|
|
176
|
+
mutedText: "#596475",
|
|
177
|
+
bodyText: "#c0c6d0",
|
|
178
|
+
softText: "#8890a0"
|
|
179
|
+
};
|
|
180
|
+
var SPOTLIGHT_PANEL_SIZE = { width: 320, height: 280 };
|
|
181
|
+
function TourShell({
|
|
182
|
+
step,
|
|
183
|
+
index,
|
|
184
|
+
total,
|
|
185
|
+
isFirst,
|
|
186
|
+
isLast,
|
|
187
|
+
onNext,
|
|
188
|
+
onBack,
|
|
189
|
+
onDismiss,
|
|
190
|
+
narration,
|
|
191
|
+
narrationPending,
|
|
192
|
+
children,
|
|
193
|
+
skin = defaultSkin,
|
|
194
|
+
orb,
|
|
195
|
+
onSpotlightTargetActivated
|
|
196
|
+
}) {
|
|
197
|
+
const panelRef = useRef2(null);
|
|
198
|
+
const { rect, found } = useSpotlight(step.spotlight?.selector ?? null);
|
|
199
|
+
const isSpotlight = Boolean(step.spotlight) && found && rect !== null;
|
|
200
|
+
useEffect2(() => {
|
|
201
|
+
const onKeyDown = (e) => {
|
|
202
|
+
if (e.key === "Escape") onDismiss();
|
|
203
|
+
};
|
|
204
|
+
document.addEventListener("keydown", onKeyDown);
|
|
205
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
206
|
+
}, [onDismiss]);
|
|
207
|
+
useEffect2(() => {
|
|
208
|
+
panelRef.current?.focus();
|
|
209
|
+
}, [step.id]);
|
|
210
|
+
useEffect2(() => {
|
|
211
|
+
if (!isSpotlight || !onSpotlightTargetActivated || !step.spotlight) return;
|
|
212
|
+
const selector = step.spotlight.selector;
|
|
213
|
+
const onClickCapture = (e) => {
|
|
214
|
+
const target = e.target;
|
|
215
|
+
if (target?.closest(`[data-tour-target="${selector}"]`)) {
|
|
216
|
+
onSpotlightTargetActivated();
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
document.addEventListener("click", onClickCapture, { capture: true });
|
|
220
|
+
return () => document.removeEventListener("click", onClickCapture, {
|
|
221
|
+
capture: true
|
|
222
|
+
});
|
|
223
|
+
}, [isSpotlight, onSpotlightTargetActivated, step.spotlight]);
|
|
224
|
+
if (isSpotlight && rect) {
|
|
225
|
+
const viewport = { width: window.innerWidth, height: window.innerHeight };
|
|
226
|
+
const frame = computeSpotlightFrame(rect, viewport);
|
|
227
|
+
const panelPos = computePanelPosition(
|
|
228
|
+
rect,
|
|
229
|
+
SPOTLIGHT_PANEL_SIZE,
|
|
230
|
+
viewport,
|
|
231
|
+
step.spotlight?.placement ?? "auto"
|
|
232
|
+
);
|
|
233
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
234
|
+
["top", "bottom", "left", "right"].map((side) => {
|
|
235
|
+
const segment = frame[side];
|
|
236
|
+
if (segment.width <= 0 || segment.height <= 0) return null;
|
|
237
|
+
return /* @__PURE__ */ jsx(
|
|
238
|
+
"div",
|
|
239
|
+
{
|
|
240
|
+
className: "fixed z-50",
|
|
241
|
+
style: {
|
|
242
|
+
top: segment.top,
|
|
243
|
+
left: segment.left,
|
|
244
|
+
width: segment.width,
|
|
245
|
+
height: segment.height,
|
|
246
|
+
background: skin.overlayBg
|
|
247
|
+
},
|
|
248
|
+
onClick: onDismiss
|
|
249
|
+
},
|
|
250
|
+
side
|
|
251
|
+
);
|
|
252
|
+
}),
|
|
253
|
+
/* @__PURE__ */ jsx(
|
|
254
|
+
"div",
|
|
255
|
+
{
|
|
256
|
+
className: "fixed z-50 rounded-xl pointer-events-none transition-all",
|
|
257
|
+
style: {
|
|
258
|
+
top: rect.top - 4,
|
|
259
|
+
left: rect.left - 4,
|
|
260
|
+
width: rect.width + 8,
|
|
261
|
+
height: rect.height + 8,
|
|
262
|
+
boxShadow: `0 0 0 2px ${skin.accent}, 0 0 24px 4px ${skin.accent}66`
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
),
|
|
266
|
+
/* @__PURE__ */ jsxs(
|
|
267
|
+
"div",
|
|
268
|
+
{
|
|
269
|
+
ref: panelRef,
|
|
270
|
+
tabIndex: -1,
|
|
271
|
+
role: "dialog",
|
|
272
|
+
"aria-modal": "true",
|
|
273
|
+
"aria-label": step.label,
|
|
274
|
+
className: "fixed z-50 rounded-xl overflow-hidden flex flex-col outline-none",
|
|
275
|
+
style: {
|
|
276
|
+
top: panelPos.top,
|
|
277
|
+
left: panelPos.left,
|
|
278
|
+
width: SPOTLIGHT_PANEL_SIZE.width,
|
|
279
|
+
maxHeight: SPOTLIGHT_PANEL_SIZE.height,
|
|
280
|
+
background: skin.panelBg,
|
|
281
|
+
border: skin.panelBorder
|
|
282
|
+
},
|
|
283
|
+
children: [
|
|
284
|
+
/* @__PURE__ */ jsx(TourShellHeader, { step, orb, onDismiss }),
|
|
285
|
+
/* @__PURE__ */ jsx(TourShellStepDots, { total, index, skin }),
|
|
286
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-y-auto px-4 py-3 space-y-3", children: /* @__PURE__ */ jsx(
|
|
287
|
+
TourShellBody,
|
|
288
|
+
{
|
|
289
|
+
step,
|
|
290
|
+
narration,
|
|
291
|
+
narrationPending,
|
|
292
|
+
skin,
|
|
293
|
+
children
|
|
294
|
+
}
|
|
295
|
+
) }),
|
|
296
|
+
/* @__PURE__ */ jsx(
|
|
297
|
+
TourShellFooter,
|
|
298
|
+
{
|
|
299
|
+
isFirst,
|
|
300
|
+
isLast,
|
|
301
|
+
onNext,
|
|
302
|
+
onBack,
|
|
303
|
+
onDismiss,
|
|
304
|
+
skin
|
|
305
|
+
}
|
|
306
|
+
)
|
|
307
|
+
]
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
] });
|
|
311
|
+
}
|
|
312
|
+
return /* @__PURE__ */ jsx(
|
|
313
|
+
"div",
|
|
314
|
+
{
|
|
315
|
+
className: "fixed inset-0 z-50 flex items-center justify-center p-4",
|
|
316
|
+
style: { background: skin.overlayBg },
|
|
317
|
+
onClick: (e) => {
|
|
318
|
+
if (e.target === e.currentTarget) onDismiss();
|
|
319
|
+
},
|
|
320
|
+
children: /* @__PURE__ */ jsxs(
|
|
321
|
+
"div",
|
|
322
|
+
{
|
|
323
|
+
ref: panelRef,
|
|
324
|
+
tabIndex: -1,
|
|
325
|
+
role: "dialog",
|
|
326
|
+
"aria-modal": "true",
|
|
327
|
+
"aria-label": step.label,
|
|
328
|
+
className: "w-full max-w-md rounded-xl overflow-hidden flex flex-col max-h-[85vh] outline-none",
|
|
329
|
+
style: { background: skin.panelBg, border: skin.panelBorder },
|
|
330
|
+
children: [
|
|
331
|
+
/* @__PURE__ */ jsx(TourShellHeader, { step, orb, onDismiss }),
|
|
332
|
+
/* @__PURE__ */ jsx(TourShellStepDots, { total, index, skin }),
|
|
333
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-y-auto px-5 py-4 space-y-4", children: /* @__PURE__ */ jsx(
|
|
334
|
+
TourShellBody,
|
|
335
|
+
{
|
|
336
|
+
step,
|
|
337
|
+
narration,
|
|
338
|
+
narrationPending,
|
|
339
|
+
skin,
|
|
340
|
+
children
|
|
341
|
+
}
|
|
342
|
+
) }),
|
|
343
|
+
/* @__PURE__ */ jsx(
|
|
344
|
+
TourShellFooter,
|
|
345
|
+
{
|
|
346
|
+
isFirst,
|
|
347
|
+
isLast,
|
|
348
|
+
onNext,
|
|
349
|
+
onBack,
|
|
350
|
+
onDismiss,
|
|
351
|
+
skin
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
]
|
|
355
|
+
}
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
function TourShellHeader({
|
|
361
|
+
step,
|
|
362
|
+
orb,
|
|
363
|
+
onDismiss
|
|
364
|
+
}) {
|
|
365
|
+
return /* @__PURE__ */ jsxs(
|
|
366
|
+
"div",
|
|
367
|
+
{
|
|
368
|
+
className: "flex items-center justify-between gap-3 px-5 py-4 shrink-0",
|
|
369
|
+
style: {
|
|
370
|
+
background: "linear-gradient(135deg, #2a1450 0%, #1a0e33 100%)"
|
|
371
|
+
},
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 min-w-0", children: [
|
|
374
|
+
orb,
|
|
375
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-white truncate", children: step.label })
|
|
376
|
+
] }),
|
|
377
|
+
/* @__PURE__ */ jsx(
|
|
378
|
+
"button",
|
|
379
|
+
{
|
|
380
|
+
onClick: onDismiss,
|
|
381
|
+
className: "p-1 rounded hover:bg-white/10 transition-colors shrink-0",
|
|
382
|
+
title: "Skip tour",
|
|
383
|
+
children: /* @__PURE__ */ jsx(X, { size: 16, className: "text-white/70" })
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
function TourShellStepDots({
|
|
391
|
+
total,
|
|
392
|
+
index,
|
|
393
|
+
skin
|
|
394
|
+
}) {
|
|
395
|
+
return /* @__PURE__ */ jsx(
|
|
396
|
+
"div",
|
|
397
|
+
{
|
|
398
|
+
className: "flex items-center gap-1.5 px-5 py-3 shrink-0",
|
|
399
|
+
style: { borderBottom: skin.panelBorder },
|
|
400
|
+
children: Array.from({ length: total }, (_, i) => /* @__PURE__ */ jsx(
|
|
401
|
+
"span",
|
|
402
|
+
{
|
|
403
|
+
className: "h-1.5 rounded-full transition-all",
|
|
404
|
+
style: {
|
|
405
|
+
width: i === index ? 20 : 8,
|
|
406
|
+
background: i === index ? skin.accent : skin.mutedText,
|
|
407
|
+
opacity: i === index ? 1 : 0.4
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
i
|
|
411
|
+
))
|
|
412
|
+
}
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
function TourShellBody({
|
|
416
|
+
step,
|
|
417
|
+
narration,
|
|
418
|
+
narrationPending,
|
|
419
|
+
skin,
|
|
420
|
+
children
|
|
421
|
+
}) {
|
|
422
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
423
|
+
/* @__PURE__ */ jsx(
|
|
424
|
+
"p",
|
|
425
|
+
{
|
|
426
|
+
className: "text-[13px] leading-relaxed transition-opacity",
|
|
427
|
+
style: {
|
|
428
|
+
color: skin.bodyText,
|
|
429
|
+
opacity: narrationPending ? 0.7 : 1
|
|
430
|
+
},
|
|
431
|
+
children: narration
|
|
432
|
+
}
|
|
433
|
+
),
|
|
434
|
+
step.features && step.features.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: step.features.map((f, i) => /* @__PURE__ */ jsxs("li", { className: "text-[12.5px] leading-relaxed", children: [
|
|
435
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold", style: { color: skin.accent }, children: f.name }),
|
|
436
|
+
/* @__PURE__ */ jsxs("span", { style: { color: skin.softText }, children: [
|
|
437
|
+
" \u2014 ",
|
|
438
|
+
f.detail
|
|
439
|
+
] })
|
|
440
|
+
] }, i)) }) : step.highlights && step.highlights.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-1.5", children: step.highlights.map((h, i) => /* @__PURE__ */ jsxs(
|
|
441
|
+
"li",
|
|
442
|
+
{
|
|
443
|
+
className: "text-[12.5px] leading-relaxed pl-3 relative",
|
|
444
|
+
style: { color: skin.softText },
|
|
445
|
+
children: [
|
|
446
|
+
/* @__PURE__ */ jsx(
|
|
447
|
+
"span",
|
|
448
|
+
{
|
|
449
|
+
className: "absolute left-0",
|
|
450
|
+
style: { color: skin.mutedText },
|
|
451
|
+
children: "\u2022"
|
|
452
|
+
}
|
|
453
|
+
),
|
|
454
|
+
h
|
|
455
|
+
]
|
|
456
|
+
},
|
|
457
|
+
i
|
|
458
|
+
)) }),
|
|
459
|
+
step.action && /* @__PURE__ */ jsxs(
|
|
460
|
+
"a",
|
|
461
|
+
{
|
|
462
|
+
href: step.action.href,
|
|
463
|
+
target: step.action.external ? "_blank" : void 0,
|
|
464
|
+
rel: step.action.external ? "noopener noreferrer" : void 0,
|
|
465
|
+
className: "inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-[13px] font-semibold transition-opacity hover:opacity-90",
|
|
466
|
+
style: { background: `${skin.accent}20`, color: skin.accent },
|
|
467
|
+
children: [
|
|
468
|
+
step.action.label,
|
|
469
|
+
step.action.external ? /* @__PURE__ */ jsx(ExternalLink, { size: 13 }) : /* @__PURE__ */ jsx(ArrowRight, { size: 13 })
|
|
470
|
+
]
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
children
|
|
474
|
+
] });
|
|
475
|
+
}
|
|
476
|
+
function TourShellFooter({
|
|
477
|
+
isFirst,
|
|
478
|
+
isLast,
|
|
479
|
+
onNext,
|
|
480
|
+
onBack,
|
|
481
|
+
onDismiss,
|
|
482
|
+
skin
|
|
483
|
+
}) {
|
|
484
|
+
return /* @__PURE__ */ jsxs(
|
|
485
|
+
"div",
|
|
486
|
+
{
|
|
487
|
+
className: "flex items-center justify-between gap-2 px-5 py-3.5 shrink-0",
|
|
488
|
+
style: { borderTop: skin.panelBorder },
|
|
489
|
+
children: [
|
|
490
|
+
/* @__PURE__ */ jsx(
|
|
491
|
+
"button",
|
|
492
|
+
{
|
|
493
|
+
onClick: onDismiss,
|
|
494
|
+
className: "text-[12px] font-medium transition-colors hover:underline",
|
|
495
|
+
style: { color: skin.mutedText },
|
|
496
|
+
children: "Skip tour"
|
|
497
|
+
}
|
|
498
|
+
),
|
|
499
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
500
|
+
!isFirst && /* @__PURE__ */ jsxs(
|
|
501
|
+
"button",
|
|
502
|
+
{
|
|
503
|
+
onClick: onBack,
|
|
504
|
+
className: "flex items-center gap-1 px-3 py-1.5 rounded-md text-[12.5px] font-medium transition-colors",
|
|
505
|
+
style: { color: skin.bodyText, border: skin.panelBorder },
|
|
506
|
+
children: [
|
|
507
|
+
/* @__PURE__ */ jsx(ChevronLeft, { size: 13 }),
|
|
508
|
+
"Back"
|
|
509
|
+
]
|
|
510
|
+
}
|
|
511
|
+
),
|
|
512
|
+
/* @__PURE__ */ jsxs(
|
|
513
|
+
"button",
|
|
514
|
+
{
|
|
515
|
+
onClick: onNext,
|
|
516
|
+
className: "flex items-center gap-1 px-3.5 py-1.5 rounded-md text-[12.5px] font-bold text-white transition-opacity hover:opacity-90",
|
|
517
|
+
style: {
|
|
518
|
+
background: "linear-gradient(135deg, #8C34E9 0%, #5B1FA6 100%)"
|
|
519
|
+
},
|
|
520
|
+
children: [
|
|
521
|
+
isLast ? "Done" : "Next",
|
|
522
|
+
!isLast && /* @__PURE__ */ jsx(ChevronRight, { size: 13 })
|
|
523
|
+
]
|
|
524
|
+
}
|
|
525
|
+
)
|
|
526
|
+
] })
|
|
527
|
+
]
|
|
528
|
+
}
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// src/tour/useTour.ts
|
|
533
|
+
import { useCallback, useState as useState2 } from "react";
|
|
534
|
+
|
|
535
|
+
// src/tour/tourMachine.ts
|
|
536
|
+
function resolveInitialIndex(steps, initialStepId) {
|
|
537
|
+
if (!initialStepId) return 0;
|
|
538
|
+
const found = steps.findIndex((s) => s.id === initialStepId);
|
|
539
|
+
return found === -1 ? 0 : found;
|
|
540
|
+
}
|
|
541
|
+
function nextIndex(steps, current) {
|
|
542
|
+
if (current >= steps.length - 1) {
|
|
543
|
+
return { index: current, completed: true };
|
|
544
|
+
}
|
|
545
|
+
return { index: current + 1, completed: false };
|
|
546
|
+
}
|
|
547
|
+
function backIndex(current) {
|
|
548
|
+
return Math.max(0, current - 1);
|
|
549
|
+
}
|
|
550
|
+
function gotoIndex(steps, stepId, current) {
|
|
551
|
+
const target = steps.findIndex((s) => s.id === stepId);
|
|
552
|
+
return target === -1 ? current : target;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// src/tour/useTour.ts
|
|
556
|
+
function useTour(steps, opts = {}) {
|
|
557
|
+
if (steps.length === 0) {
|
|
558
|
+
throw new Error("useTour requires at least one step");
|
|
559
|
+
}
|
|
560
|
+
const [index, setIndex] = useState2(
|
|
561
|
+
() => resolveInitialIndex(steps, opts.initialStepId)
|
|
562
|
+
);
|
|
563
|
+
const next = useCallback(() => {
|
|
564
|
+
const result = nextIndex(steps, index);
|
|
565
|
+
if (result.completed) {
|
|
566
|
+
opts.onComplete?.();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
setIndex(result.index);
|
|
570
|
+
opts.onStepChange?.(steps[result.index], result.index);
|
|
571
|
+
}, [index, steps]);
|
|
572
|
+
const back = useCallback(() => {
|
|
573
|
+
const target = backIndex(index);
|
|
574
|
+
if (target === index) return;
|
|
575
|
+
setIndex(target);
|
|
576
|
+
opts.onStepChange?.(steps[target], target);
|
|
577
|
+
}, [index, steps]);
|
|
578
|
+
const goto = useCallback(
|
|
579
|
+
(stepId) => {
|
|
580
|
+
const target = gotoIndex(steps, stepId, index);
|
|
581
|
+
if (target === index) return;
|
|
582
|
+
setIndex(target);
|
|
583
|
+
opts.onStepChange?.(steps[target], target);
|
|
584
|
+
},
|
|
585
|
+
[steps, index]
|
|
586
|
+
);
|
|
587
|
+
const dismiss = useCallback(() => {
|
|
588
|
+
opts.onDismiss?.();
|
|
589
|
+
}, []);
|
|
590
|
+
return {
|
|
591
|
+
step: steps[index],
|
|
592
|
+
index,
|
|
593
|
+
total: steps.length,
|
|
594
|
+
isFirst: index === 0,
|
|
595
|
+
isLast: index === steps.length - 1,
|
|
596
|
+
next,
|
|
597
|
+
back,
|
|
598
|
+
goto,
|
|
599
|
+
dismiss
|
|
600
|
+
};
|
|
601
|
+
}
|
|
436
602
|
export {
|
|
437
603
|
TourShell,
|
|
438
604
|
computePanelPosition,
|