@pablo2410/shared-ui 0.20.0 → 0.22.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.
@@ -11,6 +11,26 @@ import { ReactNode } from 'react';
11
11
  * and content-authored separately before it gets an engine.
12
12
  */
13
13
  type TourStepKind = "intro" | "service" | "outro";
14
+ /**
15
+ * Where a "service"-kind step should highlight a live element on the page,
16
+ * instead of rendering as a centered modal. Spotlighting is a rendering mode
17
+ * of a service step, not a distinct step kind — intro/outro never spotlight
18
+ * anything, and a service step authored without this still renders as the
19
+ * original centered modal.
20
+ *
21
+ * Deliberately same-origin only: the consumer's own DOM must contain an
22
+ * element carrying `data-tour-target="<selector>"`. A cross-origin tour (a
23
+ * subdomain spotlighting its own features) mints its own selector values —
24
+ * this type doesn't know or care which app is consuming it.
25
+ */
26
+ interface TourSpotlightTarget {
27
+ /** Matches a `data-tour-target` attribute value in the consumer's DOM. */
28
+ selector: string;
29
+ /** Where to place the explanation panel relative to the highlighted
30
+ * element. "auto" (default) picks based on available viewport space,
31
+ * falling back bottom -> top -> right -> left. */
32
+ placement?: "top" | "bottom" | "left" | "right" | "auto";
33
+ }
14
34
  interface TourStep {
15
35
  /** Stable id — used for progress persistence and analytics, not display. */
16
36
  id: string;
@@ -20,8 +40,17 @@ interface TourStep {
20
40
  /** Scripted fallback narration — always renders first; an AI narration
21
41
  * pass (via ai.askOpi) may replace it once it resolves. Never blocks. */
22
42
  fallbackText: string;
23
- /** 2-4 bullets shown under the narration. Empty for intro/outro steps. */
43
+ /** 2-4 bullets shown under the narration. Empty for intro/outro steps.
44
+ * Superseded by `features` when both are present — kept for back-compat
45
+ * with any step that hasn't been upgraded to the richer shape. */
24
46
  highlights?: string[];
47
+ /** Named feature call-outs, richer than a flat bullet — rendered instead
48
+ * of `highlights` when present. Each `name` is also the natural spotlight
49
+ * target label a later in-app tour (inside the service itself) can reuse. */
50
+ features?: {
51
+ name: string;
52
+ detail: string;
53
+ }[];
25
54
  /** The one action this step offers, if any. */
26
55
  action?: {
27
56
  label: string;
@@ -31,6 +60,9 @@ interface TourStep {
31
60
  };
32
61
  /** Which catalog service slug this step is about, for narration context / readiness lookups. Absent for intro/outro. */
33
62
  serviceSlug?: string;
63
+ /** Highlight a live element on the page instead of a centered modal. See
64
+ * TourSpotlightTarget's own doc comment. Absent = today's centered modal. */
65
+ spotlight?: TourSpotlightTarget;
34
66
  }
35
67
  type TourStatus = "unseen" | "in-progress" | "done" | "dismissed";
36
68
  interface TourProgress {
@@ -69,12 +101,25 @@ interface TourShellProps {
69
101
  skin?: TourShellSkin;
70
102
  /** Orb / avatar mark shown in the header — pass OpiOrb from the consumer. */
71
103
  orb?: ReactNode;
104
+ /**
105
+ * Fires when the user activates the spotlighted element itself (a
106
+ * document-level capture-phase listener scoped to the resolved target —
107
+ * see useSpotlight.ts), alongside — never instead of — whatever the
108
+ * consumer's own element does on click. Only meaningful when the current
109
+ * step has `spotlight` and a target was found; ignored otherwise. Opt-in
110
+ * per step via the consumer wiring it up, not a built-in auto-advance —
111
+ * a later in-app spotlight (inside a subdomain) may want "just look
112
+ * here," no navigation expected.
113
+ */
114
+ onSpotlightTargetActivated?: () => void;
72
115
  }
73
116
  /**
74
- * Full-viewport dismissible overlay with a centered panel. Esc and the
75
- * backdrop both dismiss; focus is trapped to the panel while open.
117
+ * Full-viewport dismissible overlay. Esc always dismisses. In centered
118
+ * mode, clicking the backdrop dismisses too; in spotlight mode, each of the
119
+ * four backdrop segments independently dismisses on click, same behavior,
120
+ * just split across four elements instead of one.
76
121
  */
77
- declare function TourShell({ step, index, total, isFirst, isLast, onNext, onBack, onDismiss, narration, narrationPending, children, skin, orb, }: TourShellProps): React.JSX.Element;
122
+ declare function TourShell({ step, index, total, isFirst, isLast, onNext, onBack, onDismiss, narration, narrationPending, children, skin, orb, onSpotlightTargetActivated, }: TourShellProps): React.JSX.Element;
78
123
 
79
124
  interface UseTourOptions {
80
125
  /** Called whenever the current step changes (not on initial mount). */
@@ -99,4 +144,68 @@ interface UseTourResult {
99
144
  }
100
145
  declare function useTour(steps: TourStep[], opts?: UseTourOptions): UseTourResult;
101
146
 
102
- export { type TourProgress, TourShell, type TourShellProps, type TourShellSkin, type TourStatus, type TourStep, type TourStepKind, type UseTourOptions, type UseTourResult, useTour };
147
+ /**
148
+ * Pure geometry behind the spotlight rendering mode — no React, no DOM
149
+ * access, just numbers in and numbers out. Factored out so it's testable
150
+ * without jsdom (this package has none — see tourMachine.ts for the same
151
+ * split/precedent on the step-index state machine).
152
+ *
153
+ * TourShell is the consumer: given a target element's measured rect (from
154
+ * useSpotlight.ts) and the current viewport size, it needs to know (a) the
155
+ * four backdrop-frame rectangles that surround the target without ever
156
+ * covering it, and (b) where to place the explanation panel so it doesn't
157
+ * overflow the viewport, honoring a preferred placement when there's room.
158
+ */
159
+ interface Rect {
160
+ top: number;
161
+ left: number;
162
+ width: number;
163
+ height: number;
164
+ }
165
+ interface Size {
166
+ width: number;
167
+ height: number;
168
+ }
169
+ type Placement = "top" | "bottom" | "left" | "right" | "auto";
170
+ /** The four backdrop segments that frame a target rect without covering it.
171
+ * Each is a plain rect in viewport (fixed-position) coordinates. A segment
172
+ * with zero width or height (e.g. the target touches a viewport edge) is
173
+ * still returned — the caller can skip rendering a degenerate rect, but
174
+ * the geometry itself doesn't special-case it. */
175
+ interface SpotlightFrame {
176
+ top: Rect;
177
+ bottom: Rect;
178
+ left: Rect;
179
+ right: Rect;
180
+ }
181
+ /** Frames `target` within a `viewport` sized 0,0 to width,height. */
182
+ declare function computeSpotlightFrame(target: Rect, viewport: Size): SpotlightFrame;
183
+ type ConcretePlacement = Exclude<Placement, "auto">;
184
+ interface PanelPosition {
185
+ top: number;
186
+ left: number;
187
+ placement: ConcretePlacement;
188
+ }
189
+ /**
190
+ * Where to place a panel of `panelSize` next to `target`, preferring
191
+ * `placement` but falling back through the other three sides (then finally
192
+ * clamping into the viewport) if the preferred side doesn't have room.
193
+ */
194
+ declare function computePanelPosition(target: Rect, panelSize: Size, viewport: Size, placement?: Placement): PanelPosition;
195
+
196
+ interface UseSpotlightResult {
197
+ /** The target's current viewport-relative rect, or null while unresolved. */
198
+ rect: Rect | null;
199
+ /** False if no element in the DOM currently carries this selector —
200
+ * callers must fall back to the non-spotlight rendering path, never
201
+ * throw or render a spotlight around nothing. */
202
+ found: boolean;
203
+ }
204
+ /**
205
+ * @param selector The `data-tour-target` attribute value to look for
206
+ * (e.g. "service-card-policy-deployment"). Pass `null` to disable
207
+ * resolution entirely (e.g. the current step has no spotlight).
208
+ */
209
+ declare function useSpotlight(selector: string | null): UseSpotlightResult;
210
+
211
+ export { type PanelPosition, type Placement, type Rect, type Size, type SpotlightFrame, type TourProgress, TourShell, type TourShellProps, type TourShellSkin, type TourSpotlightTarget, type TourStatus, type TourStep, type TourStepKind, type UseSpotlightResult, type UseTourOptions, type UseTourResult, computePanelPosition, computeSpotlightFrame, useSpotlight, useTour };