@pablo2410/shared-ui 0.19.0 → 0.20.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/dist/{chunk-5RY3AJKI.js → chunk-57T6QK3M.js} +40 -4
- package/dist/chunk-57T6QK3M.js.map +1 -0
- package/dist/{chunk-NLQ45OYS.js → chunk-LR5TUYFD.js} +73 -11
- package/dist/{chunk-NLQ45OYS.js.map → chunk-LR5TUYFD.js.map} +1 -1
- package/dist/layout/index.js +1 -1
- package/dist/services/index.d.ts +25 -1
- package/dist/services/index.js +4 -2
- package/dist/shell/index.js +2 -2
- package/dist/shell/oplytics-shell.css +1 -1
- package/dist/tour/index.d.ts +102 -0
- package/dist/tour/index.js +282 -0
- package/dist/tour/index.js.map +1 -0
- package/package.json +5 -1
- package/dist/chunk-5RY3AJKI.js.map +0 -1
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
// src/tour/TourShell.tsx
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
import {
|
|
4
|
+
X,
|
|
5
|
+
ChevronLeft,
|
|
6
|
+
ChevronRight,
|
|
7
|
+
ArrowRight,
|
|
8
|
+
ExternalLink
|
|
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
|
+
export {
|
|
279
|
+
TourShell,
|
|
280
|
+
useTour
|
|
281
|
+
};
|
|
282
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tour/TourShell.tsx","../../src/tour/useTour.ts","../../src/tour/tourMachine.ts"],"sourcesContent":["/**\n * TourShell — the Opi onboarding tour overlay chrome.\n *\n * Deliberately shell-only, same split as OpiInsightsShell: this owns the\n * overlay, orb, narration area, step dots, and Back/Next/Skip controls.\n * The consumer (Portal) owns the step list, narration fetching (falling\n * back to the step's scripted text), and persistence. Hand-rolled overlay\n * (no Radix Dialog) to stay dependency-free, matching OpiInsightsShell's\n * own precedent rather than pulling in /primitives.\n */\nimport { useEffect, useRef, type ReactNode } from \"react\";\nimport {\n X,\n ChevronLeft,\n ChevronRight,\n ArrowRight,\n ExternalLink,\n} from \"lucide-react\";\nimport type { TourStep } from \"./types\";\n\nexport interface TourShellSkin {\n accent: string;\n overlayBg: string;\n panelBg: string;\n panelBorder: string;\n mutedText: string;\n bodyText: string;\n softText: string;\n}\n\nconst defaultSkin: TourShellSkin = {\n accent: \"#8C34E9\",\n overlayBg: \"rgba(10,14,26,0.72)\",\n panelBg: \"#0e1624\",\n panelBorder: \"1px solid #1e2738\",\n mutedText: \"#596475\",\n bodyText: \"#c0c6d0\",\n softText: \"#8890a0\",\n};\n\nexport interface TourShellProps {\n step: TourStep;\n index: number;\n total: number;\n isFirst: boolean;\n isLast: boolean;\n onNext: () => void;\n onBack: () => void;\n onDismiss: () => void;\n /** Narration to render — pass the live AI reply once resolved; while\n * pending or on failure, pass `step.fallbackText` so the tour never\n * shows a blank state. */\n narration: string;\n /** True while an AI narration pass is in flight — shows a subtle pulse, never blocks Next. */\n narrationPending?: boolean;\n /** Rendered below the narration/highlights — e.g. a docked OpiAskBar. */\n children?: ReactNode;\n skin?: TourShellSkin;\n /** Orb / avatar mark shown in the header — pass OpiOrb from the consumer. */\n orb?: ReactNode;\n}\n\n/**\n * Full-viewport dismissible overlay with a centered panel. Esc and the\n * backdrop both dismiss; focus is trapped to the panel while open.\n */\nexport function TourShell({\n step,\n index,\n total,\n isFirst,\n isLast,\n onNext,\n onBack,\n onDismiss,\n narration,\n narrationPending,\n children,\n skin = defaultSkin,\n orb,\n}: TourShellProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const onKeyDown = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onDismiss();\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [onDismiss]);\n\n useEffect(() => {\n panelRef.current?.focus();\n }, [step.id]);\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center p-4\"\n style={{ background: skin.overlayBg }}\n onClick={e => {\n if (e.target === e.currentTarget) onDismiss();\n }}\n >\n <div\n ref={panelRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={step.label}\n className=\"w-full max-w-md rounded-xl overflow-hidden flex flex-col max-h-[85vh] outline-none\"\n style={{ background: skin.panelBg, border: skin.panelBorder }}\n >\n {/* Header */}\n <div\n className=\"flex items-center justify-between gap-3 px-5 py-4 shrink-0\"\n style={{\n background: \"linear-gradient(135deg, #2a1450 0%, #1a0e33 100%)\",\n }}\n >\n <div className=\"flex items-center gap-2.5 min-w-0\">\n {orb}\n <span className=\"text-sm font-bold text-white truncate\">\n {step.label}\n </span>\n </div>\n <button\n onClick={onDismiss}\n className=\"p-1 rounded hover:bg-white/10 transition-colors shrink-0\"\n title=\"Skip tour\"\n >\n <X size={16} className=\"text-white/70\" />\n </button>\n </div>\n\n {/* Step dots */}\n <div\n className=\"flex items-center gap-1.5 px-5 py-3 shrink-0\"\n style={{ borderBottom: skin.panelBorder }}\n >\n {Array.from({ length: total }, (_, i) => (\n <span\n key={i}\n className=\"h-1.5 rounded-full transition-all\"\n style={{\n width: i === index ? 20 : 8,\n background: i === index ? skin.accent : skin.mutedText,\n opacity: i === index ? 1 : 0.4,\n }}\n />\n ))}\n </div>\n\n {/* Body */}\n <div className=\"overflow-y-auto px-5 py-4 space-y-4\">\n <p\n className=\"text-[13px] leading-relaxed transition-opacity\"\n style={{\n color: skin.bodyText,\n opacity: narrationPending ? 0.7 : 1,\n }}\n >\n {narration}\n </p>\n\n {step.highlights && step.highlights.length > 0 && (\n <ul className=\"space-y-1.5\">\n {step.highlights.map((h, i) => (\n <li\n key={i}\n className=\"text-[12.5px] leading-relaxed pl-3 relative\"\n style={{ color: skin.softText }}\n >\n <span\n className=\"absolute left-0\"\n style={{ color: skin.mutedText }}\n >\n •\n </span>\n {h}\n </li>\n ))}\n </ul>\n )}\n\n {step.action && (\n <a\n href={step.action.href}\n target={step.action.external ? \"_blank\" : undefined}\n rel={step.action.external ? \"noopener noreferrer\" : undefined}\n className=\"inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-[13px] font-semibold transition-opacity hover:opacity-90\"\n style={{ background: `${skin.accent}20`, color: skin.accent }}\n >\n {step.action.label}\n {step.action.external ? (\n <ExternalLink size={13} />\n ) : (\n <ArrowRight size={13} />\n )}\n </a>\n )}\n\n {children}\n </div>\n\n {/* Footer controls */}\n <div\n className=\"flex items-center justify-between gap-2 px-5 py-3.5 shrink-0\"\n style={{ borderTop: skin.panelBorder }}\n >\n <button\n onClick={onDismiss}\n className=\"text-[12px] font-medium transition-colors hover:underline\"\n style={{ color: skin.mutedText }}\n >\n Skip tour\n </button>\n <div className=\"flex items-center gap-2\">\n {!isFirst && (\n <button\n onClick={onBack}\n className=\"flex items-center gap-1 px-3 py-1.5 rounded-md text-[12.5px] font-medium transition-colors\"\n style={{ color: skin.bodyText, border: skin.panelBorder }}\n >\n <ChevronLeft size={13} />\n Back\n </button>\n )}\n <button\n onClick={onNext}\n 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\"\n style={{\n background: \"linear-gradient(135deg, #8C34E9 0%, #5B1FA6 100%)\",\n }}\n >\n {isLast ? \"Done\" : \"Next\"}\n {!isLast && <ChevronRight size={13} />}\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n}\n","/**\n * useTour — pure step-index state machine for the onboarding tour.\n *\n * Deliberately dumb: it doesn't fetch steps, doesn't call any AI endpoint,\n * doesn't persist anything itself. The consumer (Portal) owns all of that\n * and is told about every state change via the on* callbacks so it can\n * write to user_settings however it likes. See OpiInsightsShell for the\n * same shell-owns-chrome/consumer-owns-data split.\n *\n * The actual transitions live in tourMachine.ts as plain functions (no\n * React import) so they're testable without a DOM renderer — this package\n * has no jsdom/@testing-library/react set up (see src/hooks/hooks.test.ts).\n * This file is just the useState wrapper around them.\n */\nimport { useCallback, useState } from \"react\";\nimport type { TourStep } from \"./types\";\nimport {\n backIndex,\n gotoIndex,\n nextIndex,\n resolveInitialIndex,\n} from \"./tourMachine\";\n\nexport interface UseTourOptions {\n /** Called whenever the current step changes (not on initial mount). */\n onStepChange?: (step: TourStep, index: number) => void;\n /** Called once when the user reaches the last step and advances past it. */\n onComplete?: () => void;\n /** Called when the user dismisses the tour before completion. */\n onDismiss?: () => void;\n /** Step id to resume on, if the tour was already in progress. */\n initialStepId?: string | null;\n}\n\nexport interface UseTourResult {\n step: TourStep;\n index: number;\n total: number;\n isFirst: boolean;\n isLast: boolean;\n next: () => void;\n back: () => void;\n goto: (stepId: string) => void;\n dismiss: () => void;\n}\n\nexport function useTour(\n steps: TourStep[],\n opts: UseTourOptions = {}\n): UseTourResult {\n if (steps.length === 0) {\n throw new Error(\"useTour requires at least one step\");\n }\n\n const [index, setIndex] = useState(() =>\n resolveInitialIndex(steps, opts.initialStepId)\n );\n\n const next = useCallback(() => {\n const result = nextIndex(steps, index);\n if (result.completed) {\n opts.onComplete?.();\n return;\n }\n setIndex(result.index);\n opts.onStepChange?.(steps[result.index], result.index);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- opts callbacks read fresh via closure; steps identity is expected stable per mount\n }, [index, steps]);\n\n const back = useCallback(() => {\n const target = backIndex(index);\n if (target === index) return;\n setIndex(target);\n opts.onStepChange?.(steps[target], target);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [index, steps]);\n\n const goto = useCallback(\n (stepId: string) => {\n const target = gotoIndex(steps, stepId, index);\n if (target === index) return;\n setIndex(target);\n opts.onStepChange?.(steps[target], target);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n },\n [steps, index]\n );\n\n const dismiss = useCallback(() => {\n opts.onDismiss?.();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n return {\n step: steps[index],\n index,\n total: steps.length,\n isFirst: index === 0,\n isLast: index === steps.length - 1,\n next,\n back,\n goto,\n dismiss,\n };\n}\n","/**\n * Pure step-index transitions behind useTour — factored out so the actual\n * state machine logic is testable in a plain Node environment, without a\n * React renderer (this package has no jsdom/@testing-library/react set up;\n * see src/hooks/hooks.test.ts's note on the same limitation). useTour.ts is\n * the thin useState wrapper; this file has no React import at all.\n */\nimport type { TourStep } from \"./types\";\n\nexport interface TourMachineResult {\n index: number;\n /** True if this call reached the end and should fire onComplete instead of moving. */\n completed: boolean;\n}\n\nexport function resolveInitialIndex(\n steps: TourStep[],\n initialStepId: string | null | undefined\n): number {\n if (!initialStepId) return 0;\n const found = steps.findIndex(s => s.id === initialStepId);\n return found === -1 ? 0 : found;\n}\n\nexport function nextIndex(\n steps: TourStep[],\n current: number\n): TourMachineResult {\n if (current >= steps.length - 1) {\n return { index: current, completed: true };\n }\n return { index: current + 1, completed: false };\n}\n\nexport function backIndex(current: number): number {\n return Math.max(0, current - 1);\n}\n\nexport function gotoIndex(\n steps: TourStep[],\n stepId: string,\n current: number\n): number {\n const target = steps.findIndex(s => s.id === stepId);\n return target === -1 ? current : target;\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pablo2410/shared-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "Shared UI component library for all Oplytics.digital subdomains",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -57,6 +57,10 @@
|
|
|
57
57
|
"./insights": {
|
|
58
58
|
"types": "./dist/insights/index.d.ts",
|
|
59
59
|
"import": "./dist/insights/index.js"
|
|
60
|
+
},
|
|
61
|
+
"./tour": {
|
|
62
|
+
"types": "./dist/tour/index.d.ts",
|
|
63
|
+
"import": "./dist/tour/index.js"
|
|
60
64
|
}
|
|
61
65
|
},
|
|
62
66
|
"files": [
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/services/catalog.ts","../src/services/crossNav.ts"],"sourcesContent":["/**\n * Shared Service Catalog — Single source of truth for all Oplytics.digital services.\n *\n * Consumed by:\n * - oplytics-subdomains (ServiceHub.tsx) — service cards, access gating\n * - oplytics-marketing-site (services.ts) — marketing pages, hero sections\n *\n * Each consumer extends with context-specific fields locally:\n * - Platform adds: route, external URL, React icon component\n * - Marketing adds: heroImage, demoImage, problem, howItWorks, results, aiFeatures\n */\n\nimport { platformUrl } from \"./platformDomain\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type ServiceStatus = \"live\" | \"in-development\" | \"coming-soon\";\nexport type ServiceCategory = \"core\" | \"hub\";\n\nexport interface ServiceDefinition {\n /** Unique identifier used for DB lookups, URL slugs, and cross-referencing */\n slug: string;\n /** Display name shown in cards, headers, and navigation */\n name: string;\n /** Short one-line value proposition */\n tagline: string;\n /** Longer description (1-2 sentences) for cards and summaries */\n description: string;\n /** Lucide icon name (string) — consumers resolve to component locally */\n icon: string;\n /** Hex accent color for cards, badges, and theming */\n accentColor: string;\n /** Current availability status */\n status: ServiceStatus;\n /** Core = standalone product, Hub = extension/add-on module */\n category: ServiceCategory;\n /**\n * Subdomain label on the platform domain (e.g. \"sqdcp\") — null if hosted\n * on portal. Full URLs are built via getServiceUrl(), which resolves the\n * platform domain at runtime (see ./platformDomain).\n */\n subdomainLabel: string | null;\n}\n\n// ---------------------------------------------------------------------------\n// Catalog\n// ---------------------------------------------------------------------------\n\nexport const SERVICE_CATALOG: ServiceDefinition[] = [\n // ─── CORE SERVICES ───\n {\n slug: \"policy-deployment\",\n name: \"Policy Deployment\",\n tagline: \"Align strategy to execution\",\n description:\n \"Hoshin Kanri and X-matrix planning. Cascade strategic objectives through every level of your organisation with live tracking and bowling charts.\",\n icon: \"Target\",\n accentColor: \"#F59E0B\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"policy\",\n },\n {\n slug: \"sqdcp\",\n name: \"SQDCP Dashboard\",\n tagline: \"Daily management boards, digitised\",\n description:\n \"Digital SQDCP boards that drive daily accountability. Safety, Quality, Delivery, Cost, and People metrics at a glance for every team.\",\n icon: \"LayoutGrid\",\n accentColor: \"#1DB8CE\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"sqdcp\",\n },\n {\n slug: \"oee-manager\",\n name: \"OEE Manager\",\n tagline: \"Real-time equipment effectiveness tracking\",\n description:\n \"Monitor Overall Equipment Effectiveness in real-time. Track availability, performance, and quality metrics across your entire production line.\",\n icon: \"Gauge\",\n accentColor: \"#8C34E9\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"oeemanager\",\n },\n {\n slug: \"action-manager\",\n name: \"Action Manager\",\n tagline: \"Track every action to closure\",\n description:\n \"Capture, assign, and track corrective and preventive actions from any source. Ensure nothing falls through the cracks.\",\n icon: \"ClipboardCheck\",\n accentColor: \"#22C55E\",\n status: \"live\",\n category: \"core\",\n subdomainLabel: \"actionmanager\",\n },\n {\n slug: \"connect\",\n name: \"OplyticsConnect\",\n tagline: \"Integrate machines, sensors, and systems\",\n description:\n \"Industrial IoT connectivity layer. Connect PLCs, sensors, and legacy systems to the Oplytics platform with zero-code configuration.\",\n icon: \"Plug\",\n accentColor: \"#1DB8CE\",\n status: \"in-development\",\n category: \"core\",\n subdomainLabel: \"connect\",\n },\n\n // ─── HUB SERVICES ───\n {\n slug: \"safety-manager\",\n name: \"Safety Manager\",\n tagline: \"Proactive safety culture, powered by data\",\n description:\n \"Incident reporting, hazard tracking, safety observations, and compliance management in one unified platform.\",\n icon: \"Shield\",\n accentColor: \"#EF4444\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomainLabel: \"safetymanager\",\n },\n {\n slug: \"quality-manager\",\n name: \"Quality Manager\",\n tagline: \"End-to-end quality assurance\",\n description:\n \"Non-conformance tracking, CAPA management, audit scheduling, and quality metrics dashboards.\",\n icon: \"CheckCircle\",\n accentColor: \"#22C55E\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomainLabel: null,\n },\n {\n slug: \"certification-manager\",\n name: \"Certification Manager\",\n tagline: \"Stay audit-ready, always\",\n description:\n \"Manage ISO, IATF, and other certification requirements. Document control, audit trails, and compliance tracking.\",\n icon: \"Award\",\n accentColor: \"#F97316\",\n status: \"coming-soon\",\n category: \"hub\",\n subdomainLabel: null,\n },\n];\n\n// ---------------------------------------------------------------------------\n// Derived helpers\n// ---------------------------------------------------------------------------\n\nexport const coreServices = SERVICE_CATALOG.filter(s => s.category === \"core\");\nexport const hubServices = SERVICE_CATALOG.filter(s => s.category === \"hub\");\nexport const liveServices = SERVICE_CATALOG.filter(s => s.status === \"live\");\nexport const inDevServices = SERVICE_CATALOG.filter(\n s => s.status === \"in-development\"\n);\nexport const comingSoonServices = SERVICE_CATALOG.filter(\n s => s.status === \"coming-soon\"\n);\n\nexport function getServiceBySlug(slug: string): ServiceDefinition | undefined {\n return SERVICE_CATALOG.find(s => s.slug === slug);\n}\n\nexport function getServiceUrl(service: ServiceDefinition): string | null {\n if (service.subdomainLabel) {\n return platformUrl(service.subdomainLabel);\n }\n return null;\n}\n","/**\n * crossNav — build the sidebar cross-service links (service → service jump\n * without going through the Portal Service Hub).\n *\n * Visibility mirrors the Portal ServiceHub rule: platform_admin and\n * enterprise_admin see every live service; everyone else only sees the\n * services granted in the portal `user_services` table. The current service\n * never links to itself. Each link URL carries the caller's current\n * org-hierarchy selection (?oh=…) so the breadcrumb arrives pre-aligned.\n */\nimport type { OrgHierarchySelection } from \"../hierarchy/orgHierarchy\";\nimport { appendSelectionParam } from \"../hierarchy/selectionParams\";\nimport { SERVICE_CATALOG, getServiceUrl } from \"./catalog\";\n\n/** Roles that see every live service regardless of individual grants. */\nexport const ADMIN_SEE_ALL_ROLES = [\n \"platform_admin\",\n \"enterprise_admin\",\n] as const;\n\n/**\n * Legacy grant slugs → canonical catalog slugs. Auto-provisioned accounts\n * were granted \"sqdcp-logic\" while the catalog slug is \"sqdcp\".\n */\nexport const SLUG_ALIASES: Record<string, string> = {\n \"sqdcp-logic\": \"sqdcp\",\n};\n\nexport function normalizeServiceSlug(slug: string): string {\n return SLUG_ALIASES[slug] ?? slug;\n}\n\n/** Display-ready cross-service link entry. */\nexport interface CrossServiceLink {\n slug: string;\n name: string;\n /** Lucide icon name from the service catalog (consumers resolve locally). */\n icon: string;\n accentColor: string;\n /** Absolute URL including the ?oh= hierarchy selection param. */\n url: string;\n}\n\nexport interface BuildCrossServiceLinksOptions {\n /** Catalog slug of the service doing the rendering (excluded from links). */\n currentSlug: string;\n /** Grant slugs from the portal (raw — aliases are normalized here). */\n grantedSlugs: string[];\n /** Portal role of the current user (drives the admin see-all rule). */\n role?: string | null;\n /** Current org-hierarchy selection to carry across (may be empty/null). */\n selection?: OrgHierarchySelection | null;\n}\n\nexport function buildCrossServiceLinks(\n options: BuildCrossServiceLinksOptions\n): CrossServiceLink[] {\n const { currentSlug, grantedSlugs, role, selection } = options;\n\n const isAdmin =\n !!role && (ADMIN_SEE_ALL_ROLES as readonly string[]).includes(role);\n const granted = new Set(grantedSlugs.map(normalizeServiceSlug));\n const current = normalizeServiceSlug(currentSlug);\n\n return SERVICE_CATALOG.filter(\n svc =>\n svc.status === \"live\" &&\n svc.subdomainLabel !== null &&\n svc.slug !== current &&\n (isAdmin || granted.has(svc.slug))\n ).map(svc => ({\n slug: svc.slug,\n name: svc.name,\n icon: svc.icon,\n accentColor: svc.accentColor,\n url: appendSelectionParam(getServiceUrl(svc)!, selection),\n }));\n}\n"],"mappings":"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","names":[]}
|