@glowhop/react-tour 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +22 -0
- package/README.md +25 -0
- package/components/default-tour.d.ts +16 -0
- package/components/tour-components.d.ts +125 -0
- package/config.d.ts +37 -0
- package/config.js +17 -0
- package/glow-tour.d.ts +20 -0
- package/index.d.ts +6 -0
- package/index.js +328 -0
- package/package.json +49 -0
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Glowhop
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# @glowhop/react-tour
|
|
2
|
+
|
|
3
|
+
ESM-only React 18/19 adapter with `useSyncExternalStore`-backed state. Content is React content. See the [Core guide](https://github.com/Glowhop/GlowTour.js/tree/main/packages/core) for workflow options and actions.
|
|
4
|
+
|
|
5
|
+
Compatibility: React 18 and 19 (`^18.0.0 || ^19.0.0`). SSR: `DefaultTour` renders through `react-dom/server` and package imports are DOM-free; hydration is verified at package level and in a production Next.js app. Full contract: [compatibility](https://github.com/Glowhop/GlowTour.js/blob/main/docs/compatibility.md).
|
|
6
|
+
|
|
7
|
+
<!-- glow-tour:snippet react-quick-start -->
|
|
8
|
+
```tsx
|
|
9
|
+
import { createRoot } from "react-dom/client";
|
|
10
|
+
import "@glowhop/styles-tour/default.css";
|
|
11
|
+
import { DefaultTour, createGlowTour } from "@glowhop/react-tour";
|
|
12
|
+
|
|
13
|
+
const tour = createGlowTour();
|
|
14
|
+
const workflow = tour.create("intro").step({ id: "welcome", target: "#welcome", title: "Welcome", content: "Hello." }).build();
|
|
15
|
+
createRoot(document.getElementById("app")!).render(<><button id="welcome">Welcome</button><button type="button" onClick={() => void tour.run(workflow)}>Start tour</button><DefaultTour tour={tour} /></>);
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
<!-- glow-tour:snippet react-advanced -->
|
|
19
|
+
```tsx
|
|
20
|
+
import { GlowTour, createGlowTour } from "@glowhop/react-tour";
|
|
21
|
+
const tour = createGlowTour(); const workflow = tour.create("custom").step({ id: "welcome", target: "#welcome", title: "Welcome", content: "Hello." }).build();
|
|
22
|
+
export function CustomTour() { return <><button id="welcome">Target</button><button type="button" onClick={() => void tour.run(workflow)}>Start</button><GlowTour.Root tour={tour}><GlowTour.Overlay /><GlowTour.Pointer /><GlowTour.Popover><GlowTour.Header /><GlowTour.Content /><GlowTour.Footer><GlowTour.CancelTrigger /><GlowTour.AdvanceTrigger /></GlowTour.Footer></GlowTour.Popover></GlowTour.Root></>; }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Compose `GlowTour.Root`, `Overlay`, `Pointer`, `Popover`, `Header`, `Content`, `Footer`, and trigger primitives. `DefaultTour` is the complete default composition; `useTour()` exposes native reactive state. Static/dynamic targets, placement, interaction, scroll, callbacks, actions/events, cancellation, and cleanup follow Core.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { GlowTour as CoreGlowTour } from "@glowhop/core-tour";
|
|
2
|
+
import type { ReactTourContent } from "../glow-tour";
|
|
3
|
+
/** Props for the DefaultTour component. */
|
|
4
|
+
export interface DefaultTourProps {
|
|
5
|
+
/** Optional prefix for internal element IDs. */
|
|
6
|
+
readonly idPrefix?: string;
|
|
7
|
+
/** The tour controller instance. */
|
|
8
|
+
readonly tour: CoreGlowTour<ReactTourContent>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* A default tour UI component that includes overlay, pointer, popover with header, content, and footer.
|
|
12
|
+
* Provides all standard tour controls (previous, advance, cancel buttons).
|
|
13
|
+
* @param props The component props.
|
|
14
|
+
* @returns The rendered tour UI.
|
|
15
|
+
*/
|
|
16
|
+
export declare function DefaultTour({ idPrefix, tour }: DefaultTourProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { GlowTour as CoreGlowTour, TourState } from "@glowhop/core-tour";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import type { ReactTourContent } from "../glow-tour";
|
|
4
|
+
import { DefaultTour } from "./default-tour";
|
|
5
|
+
type Tour = CoreGlowTour<ReactTourContent>;
|
|
6
|
+
type RootProps = Omit<React.HTMLAttributes<HTMLDivElement>, "children" | "id" | "ref"> & {
|
|
7
|
+
children?: React.ReactNode;
|
|
8
|
+
idPrefix?: string;
|
|
9
|
+
tour: Tour;
|
|
10
|
+
};
|
|
11
|
+
type ElementProps = Omit<React.HTMLAttributes<HTMLElement>, "id" | "ref"> & {
|
|
12
|
+
as?: React.ElementType;
|
|
13
|
+
};
|
|
14
|
+
type ContentProps = Omit<React.HTMLAttributes<HTMLElement>, "children" | "id">;
|
|
15
|
+
type OverlayProps = Omit<React.SVGAttributes<SVGSVGElement>, "ref">;
|
|
16
|
+
/** Content displayed in the pointer indicator for each direction. */
|
|
17
|
+
export interface PointerDirectionContent {
|
|
18
|
+
readonly top?: React.ReactNode;
|
|
19
|
+
readonly bottom?: React.ReactNode;
|
|
20
|
+
readonly left?: React.ReactNode;
|
|
21
|
+
readonly right?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
type PointerProps = Omit<React.HTMLAttributes<HTMLElement>, "aria-hidden" | "children" | "ref"> & {
|
|
24
|
+
as?: React.ElementType;
|
|
25
|
+
directionContent?: PointerDirectionContent;
|
|
26
|
+
};
|
|
27
|
+
type ButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children" | "type"> & {
|
|
28
|
+
children?: React.ReactElement<React.ButtonHTMLAttributes<HTMLButtonElement>> | ((props: React.ButtonHTMLAttributes<HTMLButtonElement>) => React.ReactElement);
|
|
29
|
+
};
|
|
30
|
+
type BackTriggerProps = ButtonProps & {
|
|
31
|
+
backLabel?: string;
|
|
32
|
+
};
|
|
33
|
+
type AdvanceTriggerProps = ButtonProps & {
|
|
34
|
+
finishLabel?: string;
|
|
35
|
+
advanceLabel?: string;
|
|
36
|
+
};
|
|
37
|
+
type CancelTriggerProps = ButtonProps;
|
|
38
|
+
/**
|
|
39
|
+
* Root component that must wrap all other tour components.
|
|
40
|
+
*
|
|
41
|
+
* Manages tour initialization and connects the tour instance to the DOM.
|
|
42
|
+
* All other tour components (Overlay, Pointer, Popover, etc.) must be rendered inside this root.
|
|
43
|
+
* @param props Component props including the tour instance and optional ID prefix.
|
|
44
|
+
* @returns The root provider component.
|
|
45
|
+
*/
|
|
46
|
+
export declare function Root({ children, idPrefix, tour, ...props }: RootProps): React.JSX.Element;
|
|
47
|
+
/**
|
|
48
|
+
* The popover container that displays step content.
|
|
49
|
+
*
|
|
50
|
+
* Renders as a `<section>` by default, but can be customized via the `as` prop.
|
|
51
|
+
* Should contain Header, Content, and Footer components.
|
|
52
|
+
* @param props HTML attributes and the `as` prop for customizing the container element.
|
|
53
|
+
* @returns The popover container.
|
|
54
|
+
*/
|
|
55
|
+
export declare function Popover({ as: Component, style, ...props }: ElementProps): React.JSX.Element;
|
|
56
|
+
/**
|
|
57
|
+
* Displays the title of the current step.
|
|
58
|
+
* @param props HTML attributes.
|
|
59
|
+
* @returns The step title header.
|
|
60
|
+
*/
|
|
61
|
+
export declare function Header(props: ContentProps): React.JSX.Element;
|
|
62
|
+
/**
|
|
63
|
+
* Displays the body content of the current step.
|
|
64
|
+
* @param props HTML attributes.
|
|
65
|
+
* @returns The step content area.
|
|
66
|
+
*/
|
|
67
|
+
export declare function Content(props: ContentProps): React.JSX.Element;
|
|
68
|
+
/**
|
|
69
|
+
* The footer section of the popover, typically containing navigation buttons.
|
|
70
|
+
* Automatically hidden if configured via `popover.hideFooter`.
|
|
71
|
+
* @param props HTML attributes and children.
|
|
72
|
+
* @returns The footer container, or null if hidden.
|
|
73
|
+
*/
|
|
74
|
+
export declare function Footer({ children, ...props }: ElementProps): React.JSX.Element | null;
|
|
75
|
+
export declare function Overlay({ children, style, viewBox, ...props }: OverlayProps): React.JSX.Element;
|
|
76
|
+
/**
|
|
77
|
+
* A pointer/indicator that visually highlights the target element.
|
|
78
|
+
* Displays directional content (emoji or custom content) based on pointer position.
|
|
79
|
+
* Renders as a `<div>` by default, but can be customized via the `as` prop.
|
|
80
|
+
* @param props HTML attributes, the `as` prop for customizing the container, and `directionContent`.
|
|
81
|
+
* @returns The pointer indicator element.
|
|
82
|
+
*/
|
|
83
|
+
export declare function Pointer({ as: Component, directionContent, style, ...props }: PointerProps): React.JSX.Element;
|
|
84
|
+
/**
|
|
85
|
+
* Button that navigates to the previous step.
|
|
86
|
+
* Automatically hidden or disabled based on tour state.
|
|
87
|
+
* @param props Button props and an optional `backLabel` for the button text.
|
|
88
|
+
* @returns The back button, or null if hidden.
|
|
89
|
+
*/
|
|
90
|
+
export declare function BackTrigger({ backLabel, ...props }: BackTriggerProps): React.JSX.Element | null;
|
|
91
|
+
/**
|
|
92
|
+
* Button that navigates to the next step, or finishes the tour on the last step.
|
|
93
|
+
* Automatically hidden or disabled based on tour state.
|
|
94
|
+
* @param props Button props, an optional `advanceLabel` for non-final steps, and `finishLabel` for the final step.
|
|
95
|
+
* @returns The advance button, or null if hidden.
|
|
96
|
+
*/
|
|
97
|
+
export declare function AdvanceTrigger({ finishLabel, advanceLabel, ...props }: AdvanceTriggerProps): React.JSX.Element | null;
|
|
98
|
+
/**
|
|
99
|
+
* Button that cancels the tour.
|
|
100
|
+
* Automatically hidden if the tour is not cancellable.
|
|
101
|
+
* @param props Button props.
|
|
102
|
+
* @returns The cancel button, or null if the tour cannot be cancelled.
|
|
103
|
+
*/
|
|
104
|
+
export declare function CancelTrigger(props: CancelTriggerProps): React.JSX.Element | null;
|
|
105
|
+
/**
|
|
106
|
+
* React hook that returns the current tour state.
|
|
107
|
+
*
|
|
108
|
+
* Must be called inside a component rendered within `<GlowTour.Root>`.
|
|
109
|
+
* @returns The current tour state.
|
|
110
|
+
*/
|
|
111
|
+
export declare function useTour(): TourState<ReactTourContent>;
|
|
112
|
+
export declare const GlowTour: {
|
|
113
|
+
Default: typeof DefaultTour;
|
|
114
|
+
Root: typeof Root;
|
|
115
|
+
Popover: typeof Popover;
|
|
116
|
+
Header: typeof Header;
|
|
117
|
+
Content: typeof Content;
|
|
118
|
+
Footer: typeof Footer;
|
|
119
|
+
Overlay: typeof Overlay;
|
|
120
|
+
Pointer: typeof Pointer;
|
|
121
|
+
BackTrigger: typeof BackTrigger;
|
|
122
|
+
AdvanceTrigger: typeof AdvanceTrigger;
|
|
123
|
+
CancelTrigger: typeof CancelTrigger;
|
|
124
|
+
};
|
|
125
|
+
export {};
|
package/config.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BuiltinAction, ConfigValidationIssue, EventHandlerConfig as CoreEventHandlerConfig, LifecycleActionRef as CoreLifecycleActionRef, StepActionRef as CoreStepActionRef, StepConfig as CoreStepConfig, TransitionActionRef as CoreTransitionActionRef, WorkflowConfig as CoreWorkflowConfig, WorkflowDefinitionFromConfig as CoreWorkflowDefinitionFromConfig, CreateWorkflowFromConfigOptions, ValidateWorkflowConfigOptions } from "@glowhop/core-tour/config";
|
|
2
|
+
import { ConfigValidationError } from "@glowhop/core-tour/config";
|
|
3
|
+
import type { ReactTourContent } from "./glow-tour";
|
|
4
|
+
export type { BuiltinAction, ConfigValidationIssue, CreateWorkflowFromConfigOptions, ValidateWorkflowConfigOptions, };
|
|
5
|
+
export { ConfigValidationError };
|
|
6
|
+
/** React workflow config: `WorkflowConfig` pre-bound to `ReactTourContent`. */
|
|
7
|
+
export type WorkflowConfig = CoreWorkflowConfig<ReactTourContent>;
|
|
8
|
+
/** React step config: `StepConfig` pre-bound to `ReactTourContent`. */
|
|
9
|
+
export type StepConfig = CoreStepConfig<ReactTourContent>;
|
|
10
|
+
/** React event handler config: `EventHandlerConfig` pre-bound to `ReactTourContent`. */
|
|
11
|
+
export type EventHandlerConfig = CoreEventHandlerConfig<ReactTourContent>;
|
|
12
|
+
/** React step action reference: `StepActionRef` pre-bound to `ReactTourContent`. */
|
|
13
|
+
export type StepActionRef = CoreStepActionRef<ReactTourContent>;
|
|
14
|
+
/** React transition action reference: `TransitionActionRef` pre-bound to `ReactTourContent`. */
|
|
15
|
+
export type TransitionActionRef = CoreTransitionActionRef<ReactTourContent>;
|
|
16
|
+
/** React lifecycle action reference: `LifecycleActionRef` pre-bound to `ReactTourContent`. */
|
|
17
|
+
export type LifecycleActionRef = CoreLifecycleActionRef<ReactTourContent>;
|
|
18
|
+
/** React workflow definition produced from config, pre-bound to `ReactTourContent`. */
|
|
19
|
+
export type WorkflowDefinitionFromConfig = CoreWorkflowDefinitionFromConfig<ReactTourContent>;
|
|
20
|
+
/**
|
|
21
|
+
* Builds a React `WorkflowDefinition` from a JSON-serializable config, pre-bound to
|
|
22
|
+
* `ReactTourContent` so the result is accepted by `createGlowTour().run(...)` with no generic
|
|
23
|
+
* and no cast.
|
|
24
|
+
* @param config The parsed JSON (or equivalent plain object) to build from.
|
|
25
|
+
* @param options Options; see `CreateWorkflowFromConfigOptions`.
|
|
26
|
+
* @throws {ConfigValidationError} When the config is invalid.
|
|
27
|
+
* @returns A frozen `WorkflowDefinition` with `.source` attached.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createWorkflowFromConfig(config: unknown, options?: CreateWorkflowFromConfigOptions): WorkflowDefinitionFromConfig;
|
|
30
|
+
/**
|
|
31
|
+
* Validates and narrows an unknown value to a React `WorkflowConfig`.
|
|
32
|
+
* @param config The parsed JSON (or equivalent plain object) to validate.
|
|
33
|
+
* @param options Validation options; see `ValidateWorkflowConfigOptions`.
|
|
34
|
+
* @throws {ConfigValidationError} When one or more issues are found.
|
|
35
|
+
* @returns The same value, narrowed to `WorkflowConfig`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateWorkflowConfig(config: unknown, options?: ValidateWorkflowConfigOptions): WorkflowConfig;
|
package/config.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// packages/react/src/config.ts
|
|
2
|
+
import {
|
|
3
|
+
ConfigValidationError,
|
|
4
|
+
createWorkflowFromConfig as createCoreWorkflowFromConfig,
|
|
5
|
+
validateWorkflowConfig as validateCoreWorkflowConfig
|
|
6
|
+
} from "@glowhop/core-tour/config";
|
|
7
|
+
function createWorkflowFromConfig(config, options = {}) {
|
|
8
|
+
return createCoreWorkflowFromConfig(config, options);
|
|
9
|
+
}
|
|
10
|
+
function validateWorkflowConfig(config, options = {}) {
|
|
11
|
+
return validateCoreWorkflowConfig(config, options);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
validateWorkflowConfig,
|
|
15
|
+
createWorkflowFromConfig,
|
|
16
|
+
ConfigValidationError
|
|
17
|
+
};
|
package/glow-tour.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GlowTour as CoreGlowTour, StartOptions as CoreStartOptions, StepPropsStore as CoreStepPropsStore, TourState as CoreTourState, WorkflowDefinition as CoreWorkflowDefinition, GlowTourOptions } from "@glowhop/core-tour";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
/** React content type: any valid React node. */
|
|
4
|
+
export type ReactTourContent = ReactNode;
|
|
5
|
+
/** React tour controller instance. */
|
|
6
|
+
export type Tour = CoreGlowTour<ReactTourContent>;
|
|
7
|
+
/** React tour state snapshot. */
|
|
8
|
+
export type TourState = CoreTourState<ReactTourContent>;
|
|
9
|
+
/** React step properties store. */
|
|
10
|
+
export type StepPropsStore = CoreStepPropsStore<ReactTourContent>;
|
|
11
|
+
/** React workflow definition. */
|
|
12
|
+
export type WorkflowDefinition = CoreWorkflowDefinition<ReactTourContent>;
|
|
13
|
+
/** React tour start options. */
|
|
14
|
+
export type StartOptions = CoreStartOptions<ReactTourContent>;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new GlowTour.js instance for React.
|
|
17
|
+
* @param options Tour options for error handling.
|
|
18
|
+
* @returns A tour controller ready to run React workflows.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createGlowTour(options?: GlowTourOptions): Tour;
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { GlowTourOptions } from "@glowhop/core-tour";
|
|
2
|
+
export { DefaultTour, type DefaultTourProps } from "./components/default-tour";
|
|
3
|
+
export type { PointerDirectionContent } from "./components/tour-components";
|
|
4
|
+
export { AdvanceTrigger, BackTrigger, CancelTrigger, Content, Footer, GlowTour, Header, Overlay, Pointer, Popover, Root, useTour, } from "./components/tour-components";
|
|
5
|
+
export type { ReactTourContent, StartOptions, StepPropsStore, Tour, TourState, WorkflowDefinition, } from "./glow-tour";
|
|
6
|
+
export { createGlowTour } from "./glow-tour";
|
package/index.js
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// packages/react/src/components/tour-components.tsx
|
|
2
|
+
import {
|
|
3
|
+
connectGlowTourRoot,
|
|
4
|
+
OVERLAY_IDLE_ATTRIBUTES,
|
|
5
|
+
OVERLAY_IDLE_STYLE,
|
|
6
|
+
OVERLAY_PATH_IDLE_ATTRIBUTES,
|
|
7
|
+
POINTER_IDLE_STYLE,
|
|
8
|
+
POPOVER_IDLE_ATTRIBUTES,
|
|
9
|
+
POPOVER_IDLE_STYLE,
|
|
10
|
+
styleRecordToCamelCase
|
|
11
|
+
} from "@glowhop/core-tour/adapter";
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
var DEFAULT_POINTER_DIRECTION_CONTENT = {
|
|
15
|
+
bottom: "\uD83D\uDC47",
|
|
16
|
+
left: "\uD83D\uDC48",
|
|
17
|
+
right: "\uD83D\uDC49",
|
|
18
|
+
top: "\uD83D\uDC46"
|
|
19
|
+
};
|
|
20
|
+
var OVERLAY_IDLE_STYLE_REACT = styleRecordToCamelCase(OVERLAY_IDLE_STYLE);
|
|
21
|
+
var POINTER_IDLE_STYLE_REACT = styleRecordToCamelCase(POINTER_IDLE_STYLE);
|
|
22
|
+
var POPOVER_IDLE_STYLE_REACT = styleRecordToCamelCase(POPOVER_IDLE_STYLE);
|
|
23
|
+
var TourContext = React.createContext(null);
|
|
24
|
+
function useTourContext() {
|
|
25
|
+
const context = React.useContext(TourContext);
|
|
26
|
+
if (!context) {
|
|
27
|
+
throw new Error("GlowTour components must be rendered inside <GlowTour.Root tour={...}>.");
|
|
28
|
+
}
|
|
29
|
+
return context;
|
|
30
|
+
}
|
|
31
|
+
function useTourSnapshot(tour) {
|
|
32
|
+
return React.useSyncExternalStore(tour.state.subscribe, tour.state.get, tour.state.get);
|
|
33
|
+
}
|
|
34
|
+
var useIsomorphicLayoutEffect = typeof window !== "undefined" ? React.useLayoutEffect : React.useEffect;
|
|
35
|
+
function useEffectEvent(fn) {
|
|
36
|
+
const ref = React.useRef(fn);
|
|
37
|
+
useIsomorphicLayoutEffect(() => {
|
|
38
|
+
ref.current = fn;
|
|
39
|
+
});
|
|
40
|
+
return React.useCallback((...args) => ref.current(...args), []);
|
|
41
|
+
}
|
|
42
|
+
function useBoundElement(bind) {
|
|
43
|
+
const { binding } = useTourContext();
|
|
44
|
+
const [element, setElement] = React.useState(null);
|
|
45
|
+
const binder = useEffectEvent(bind);
|
|
46
|
+
React.useEffect(() => {
|
|
47
|
+
if (!binding || !element)
|
|
48
|
+
return;
|
|
49
|
+
return binder(binding, element);
|
|
50
|
+
}, [binding, element, binder]);
|
|
51
|
+
return setElement;
|
|
52
|
+
}
|
|
53
|
+
function useStep(snapshot) {
|
|
54
|
+
return snapshot.currentStep?.currentProps;
|
|
55
|
+
}
|
|
56
|
+
function Root({ children, idPrefix, tour, ...props }) {
|
|
57
|
+
const mounted = React.useRef(null);
|
|
58
|
+
const [binding, setBinding] = React.useState(null);
|
|
59
|
+
const release = React.useCallback(() => {
|
|
60
|
+
const current = mounted.current;
|
|
61
|
+
if (!current)
|
|
62
|
+
return;
|
|
63
|
+
mounted.current = null;
|
|
64
|
+
current.binding.release();
|
|
65
|
+
setBinding((active) => active === current.binding ? null : active);
|
|
66
|
+
}, []);
|
|
67
|
+
const connect = React.useCallback((element) => {
|
|
68
|
+
release();
|
|
69
|
+
if (!element)
|
|
70
|
+
return;
|
|
71
|
+
const nextBinding = connectGlowTourRoot(tour, {
|
|
72
|
+
idPrefix,
|
|
73
|
+
root: element
|
|
74
|
+
});
|
|
75
|
+
mounted.current = { binding: nextBinding, element };
|
|
76
|
+
setBinding(nextBinding);
|
|
77
|
+
}, [idPrefix, release, tour]);
|
|
78
|
+
const context = React.useMemo(() => ({ binding, tour }), [binding, tour]);
|
|
79
|
+
return jsx(TourContext.Provider, {
|
|
80
|
+
value: context,
|
|
81
|
+
children: jsx("div", {
|
|
82
|
+
...props,
|
|
83
|
+
"data-glow-tour-root": true,
|
|
84
|
+
ref: connect,
|
|
85
|
+
children
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function Popover({ as: Component = "section", style, ...props }) {
|
|
90
|
+
const { binding } = useTourContext();
|
|
91
|
+
const ref = useBoundElement((activeBinding, element) => activeBinding.bindPopover(element));
|
|
92
|
+
return jsx(Component, {
|
|
93
|
+
...props,
|
|
94
|
+
"aria-describedby": binding?.ids.description,
|
|
95
|
+
"aria-hidden": POPOVER_IDLE_ATTRIBUTES["aria-hidden"],
|
|
96
|
+
"aria-labelledby": binding?.ids.title,
|
|
97
|
+
"data-glow-tour-popover": true,
|
|
98
|
+
id: binding?.ids.popover,
|
|
99
|
+
inert: POPOVER_IDLE_ATTRIBUTES.inert === "true",
|
|
100
|
+
ref,
|
|
101
|
+
role: "dialog",
|
|
102
|
+
style: style ?? POPOVER_IDLE_STYLE_REACT,
|
|
103
|
+
tabIndex: -1
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function Header(props) {
|
|
107
|
+
const { binding, tour } = useTourContext();
|
|
108
|
+
const step = useStep(useTourSnapshot(tour));
|
|
109
|
+
return jsx("header", {
|
|
110
|
+
...props,
|
|
111
|
+
"data-glow-tour-header": true,
|
|
112
|
+
id: binding?.ids.title,
|
|
113
|
+
children: step?.title ?? null
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
function Content(props) {
|
|
117
|
+
const { binding, tour } = useTourContext();
|
|
118
|
+
const step = useStep(useTourSnapshot(tour));
|
|
119
|
+
return jsx("div", {
|
|
120
|
+
...props,
|
|
121
|
+
"aria-live": "polite",
|
|
122
|
+
"data-glow-tour-content": true,
|
|
123
|
+
id: binding?.ids.description,
|
|
124
|
+
children: step?.content ?? null
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function Footer({ children, ...props }) {
|
|
128
|
+
const { tour } = useTourContext();
|
|
129
|
+
const step = useStep(useTourSnapshot(tour));
|
|
130
|
+
if (step?.popover?.hideFooter)
|
|
131
|
+
return null;
|
|
132
|
+
return jsx("footer", {
|
|
133
|
+
...props,
|
|
134
|
+
"data-glow-tour-footer": true,
|
|
135
|
+
children
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
var OVERLAY_INERT_PROP = {
|
|
139
|
+
inert: OVERLAY_IDLE_ATTRIBUTES.inert === "true"
|
|
140
|
+
};
|
|
141
|
+
function Overlay({ children, style, viewBox = "0 0 0 0", ...props }) {
|
|
142
|
+
const ref = useBoundElement((binding, element) => binding.bindOverlay(element));
|
|
143
|
+
return jsxs("svg", {
|
|
144
|
+
...props,
|
|
145
|
+
...OVERLAY_INERT_PROP,
|
|
146
|
+
"aria-hidden": OVERLAY_IDLE_ATTRIBUTES["aria-hidden"],
|
|
147
|
+
"data-glow-tour-allow-interaction": OVERLAY_IDLE_ATTRIBUTES["data-glow-tour-allow-interaction"],
|
|
148
|
+
"data-glow-tour-overlay": true,
|
|
149
|
+
focusable: "false",
|
|
150
|
+
ref,
|
|
151
|
+
role: "presentation",
|
|
152
|
+
style: style ?? OVERLAY_IDLE_STYLE_REACT,
|
|
153
|
+
viewBox,
|
|
154
|
+
children: [
|
|
155
|
+
jsx("path", {
|
|
156
|
+
cursor: OVERLAY_PATH_IDLE_ATTRIBUTES.cursor,
|
|
157
|
+
"data-glow-tour-overlay-path": true,
|
|
158
|
+
fillRule: "evenodd",
|
|
159
|
+
opacity: OVERLAY_PATH_IDLE_ATTRIBUTES.opacity,
|
|
160
|
+
pointerEvents: OVERLAY_PATH_IDLE_ATTRIBUTES["pointer-events"]
|
|
161
|
+
}),
|
|
162
|
+
children
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function Pointer({
|
|
167
|
+
as: Component = "div",
|
|
168
|
+
directionContent,
|
|
169
|
+
style,
|
|
170
|
+
...props
|
|
171
|
+
}) {
|
|
172
|
+
const ref = useBoundElement((binding, element) => binding.bindPointer(element));
|
|
173
|
+
const content = { ...DEFAULT_POINTER_DIRECTION_CONTENT, ...directionContent };
|
|
174
|
+
return jsx(Component, {
|
|
175
|
+
...props,
|
|
176
|
+
"aria-hidden": "true",
|
|
177
|
+
"data-glow-tour-pointer": true,
|
|
178
|
+
ref,
|
|
179
|
+
style: style ?? POINTER_IDLE_STYLE_REACT,
|
|
180
|
+
children: Object.keys(DEFAULT_POINTER_DIRECTION_CONTENT).map((direction) => jsx("div", {
|
|
181
|
+
"data-glow-tour-pointer-direction": direction,
|
|
182
|
+
children: content[direction]
|
|
183
|
+
}, direction))
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function Trigger({
|
|
187
|
+
children,
|
|
188
|
+
capabilityDisabled,
|
|
189
|
+
label,
|
|
190
|
+
marker,
|
|
191
|
+
onClick,
|
|
192
|
+
disabled: userDisabled,
|
|
193
|
+
...props
|
|
194
|
+
}) {
|
|
195
|
+
const { binding } = useTourContext();
|
|
196
|
+
const child = typeof children === "function" ? null : children;
|
|
197
|
+
const childProps = child?.props ?? {};
|
|
198
|
+
const consumerDisabled = userDisabled === true || childProps.disabled === true;
|
|
199
|
+
const disabled = capabilityDisabled || consumerDisabled;
|
|
200
|
+
const buttonProps = {
|
|
201
|
+
...props,
|
|
202
|
+
"aria-controls": binding?.ids.popover,
|
|
203
|
+
"aria-label": props["aria-label"] || label,
|
|
204
|
+
"aria-disabled": disabled ? "true" : "false",
|
|
205
|
+
"data-glow-tour-cancel-trigger": marker === "cancel" || undefined,
|
|
206
|
+
"data-glow-tour-consumer-disabled": consumerDisabled ? "true" : undefined,
|
|
207
|
+
"data-glow-tour-advance-trigger": marker === "advance" || undefined,
|
|
208
|
+
"data-glow-tour-previous-trigger": marker === "previous" || undefined,
|
|
209
|
+
disabled,
|
|
210
|
+
onClick: (event) => {
|
|
211
|
+
childProps.onClick?.(event);
|
|
212
|
+
onClick?.(event);
|
|
213
|
+
},
|
|
214
|
+
type: "button"
|
|
215
|
+
};
|
|
216
|
+
if (typeof children === "function")
|
|
217
|
+
return children(buttonProps);
|
|
218
|
+
if (child)
|
|
219
|
+
return React.cloneElement(child, buttonProps);
|
|
220
|
+
return jsx("button", {
|
|
221
|
+
...buttonProps,
|
|
222
|
+
children: label
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
function BackTrigger({ backLabel, ...props }) {
|
|
226
|
+
const { tour } = useTourContext();
|
|
227
|
+
const snapshot = useTourSnapshot(tour);
|
|
228
|
+
const step = useStep(snapshot);
|
|
229
|
+
if (step?.popover?.hidePreviousButton)
|
|
230
|
+
return null;
|
|
231
|
+
const label = backLabel ?? "Back step";
|
|
232
|
+
return jsx(Trigger, {
|
|
233
|
+
...props,
|
|
234
|
+
capabilityDisabled: !snapshot.canPrevious || step?.popover?.disablePreviousButton === true,
|
|
235
|
+
label,
|
|
236
|
+
marker: "previous"
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function AdvanceTrigger({ finishLabel, advanceLabel, ...props }) {
|
|
240
|
+
const { tour } = useTourContext();
|
|
241
|
+
const snapshot = useTourSnapshot(tour);
|
|
242
|
+
const step = useStep(snapshot);
|
|
243
|
+
if (step?.popover?.hideAdvanceButton)
|
|
244
|
+
return null;
|
|
245
|
+
const label = snapshot.isLastStep ? finishLabel ?? "Finish tour" : advanceLabel ?? "Advance step";
|
|
246
|
+
return jsx(Trigger, {
|
|
247
|
+
...props,
|
|
248
|
+
capabilityDisabled: !snapshot.canAdvance || step?.popover?.disableAdvanceButton === true,
|
|
249
|
+
label,
|
|
250
|
+
marker: "advance"
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function CancelTrigger(props) {
|
|
254
|
+
const { tour } = useTourContext();
|
|
255
|
+
const snapshot = useTourSnapshot(tour);
|
|
256
|
+
if (!snapshot.canCancel)
|
|
257
|
+
return null;
|
|
258
|
+
return jsx(Trigger, {
|
|
259
|
+
...props,
|
|
260
|
+
capabilityDisabled: !snapshot.canCancel,
|
|
261
|
+
label: "Skip",
|
|
262
|
+
marker: "cancel"
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
function useTour() {
|
|
266
|
+
const { tour } = useTourContext();
|
|
267
|
+
return useTourSnapshot(tour);
|
|
268
|
+
}
|
|
269
|
+
var GlowTour = {
|
|
270
|
+
Default: DefaultTour,
|
|
271
|
+
Root,
|
|
272
|
+
Popover,
|
|
273
|
+
Header,
|
|
274
|
+
Content,
|
|
275
|
+
Footer,
|
|
276
|
+
Overlay,
|
|
277
|
+
Pointer,
|
|
278
|
+
BackTrigger,
|
|
279
|
+
AdvanceTrigger,
|
|
280
|
+
CancelTrigger
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// packages/react/src/components/default-tour.tsx
|
|
284
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
285
|
+
function DefaultTour({ idPrefix, tour }) {
|
|
286
|
+
return jsxs2(Root, {
|
|
287
|
+
idPrefix,
|
|
288
|
+
tour,
|
|
289
|
+
children: [
|
|
290
|
+
jsx2(Overlay, {}),
|
|
291
|
+
jsx2(Pointer, {}),
|
|
292
|
+
jsxs2(Popover, {
|
|
293
|
+
children: [
|
|
294
|
+
jsx2(Header, {}),
|
|
295
|
+
jsx2(Content, {}),
|
|
296
|
+
jsxs2(Footer, {
|
|
297
|
+
children: [
|
|
298
|
+
jsx2(CancelTrigger, {}),
|
|
299
|
+
jsx2(BackTrigger, {}),
|
|
300
|
+
jsx2(AdvanceTrigger, {})
|
|
301
|
+
]
|
|
302
|
+
})
|
|
303
|
+
]
|
|
304
|
+
})
|
|
305
|
+
]
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
// packages/react/src/glow-tour.ts
|
|
309
|
+
import { createGlowTour as createCoreGlowTour } from "@glowhop/core-tour";
|
|
310
|
+
function createGlowTour(options = {}) {
|
|
311
|
+
return createCoreGlowTour(options);
|
|
312
|
+
}
|
|
313
|
+
export {
|
|
314
|
+
useTour,
|
|
315
|
+
createGlowTour,
|
|
316
|
+
Root,
|
|
317
|
+
Popover,
|
|
318
|
+
Pointer,
|
|
319
|
+
Overlay,
|
|
320
|
+
Header,
|
|
321
|
+
GlowTour,
|
|
322
|
+
Footer,
|
|
323
|
+
DefaultTour,
|
|
324
|
+
Content,
|
|
325
|
+
CancelTrigger,
|
|
326
|
+
BackTrigger,
|
|
327
|
+
AdvanceTrigger
|
|
328
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glowhop/react-tour",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React adapter for GlowTour.js.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://github.com/Glowhop/GlowTour.js#readme",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/Glowhop/GlowTour.js/issues"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"tour",
|
|
12
|
+
"guided-tour",
|
|
13
|
+
"react",
|
|
14
|
+
"onboarding"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18.19.1"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"**/*"
|
|
21
|
+
],
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/Glowhop/GlowTour.js.git",
|
|
28
|
+
"directory": "packages/react"
|
|
29
|
+
},
|
|
30
|
+
"type": "module",
|
|
31
|
+
"types": "./index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./index.d.ts",
|
|
35
|
+
"import": "./index.js"
|
|
36
|
+
},
|
|
37
|
+
"./config": {
|
|
38
|
+
"types": "./config.d.ts",
|
|
39
|
+
"import": "./config.js"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@glowhop/core-tour": "1.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
47
|
+
},
|
|
48
|
+
"sideEffects": false
|
|
49
|
+
}
|