Skip to content

Commit b039ce1

Browse files
committed
feat: add tour
1 parent 6b5a053 commit b039ce1

30 files changed

+748
-0
lines changed

bun.lockb

155 KB
Binary file not shown.

packages/react/.storybook/main.css

+1
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@
3333
@import url("./styles/toast.css");
3434
@import url("./styles/toggle-group.css");
3535
@import url("./styles/tooltip.css");
36+
@import url("./styles/tour.css");
3637
@import url("./styles/tree-view.css");
+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
[data-scope="tour"][data-part="positioner"][data-type="floating"] {
2+
position: absolute;
3+
}
4+
5+
[data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="bottom"] {
6+
bottom: 24px;
7+
}
8+
9+
[data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="top"] {
10+
top: 24px;
11+
}
12+
13+
[data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="end"] {
14+
inset-inline-end: 24px;
15+
}
16+
17+
[data-scope="tour"][data-part="positioner"][data-type="floating"][data-placement*="start"] {
18+
inset-inline-start: 24px;
19+
}
20+
21+
[data-scope="tour"][data-part="positioner"][data-type="dialog"] {
22+
width: 100%;
23+
position: fixed;
24+
inset: 0;
25+
margin: auto;
26+
display: flex;
27+
align-items: center;
28+
justify-content: center;
29+
}
30+
31+
[data-scope="tour"][data-part="content"] {
32+
--arrow-background: white;
33+
--arrow-size: 10px;
34+
background: white;
35+
padding: 24px;
36+
position: relative;
37+
border-radius: 4px;
38+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
39+
width: 300px;
40+
}
41+
42+
[data-scope="tour"][data-part="content"][data-type="dialog"] {
43+
width: 500px;
44+
background: lightblue;
45+
}
46+
47+
[data-scope="tour"][data-part="content"][data-type="floating"] {
48+
width: 500px;
49+
background: rgb(15, 39, 136);
50+
color: white;
51+
}
52+
53+
[data-scope="tour"][data-part="arrow"] {
54+
--arrow-background: white;
55+
--arrow-shadow-color: #ebebeb;
56+
box-shadow: var(--box-shadow);
57+
}
58+
59+
[data-scope="tour"][data-part="title"] {
60+
font-weight: 600;
61+
}
62+
63+
[data-scope="tour"][data-part="description"] {
64+
margin-bottom: 20px;
65+
}
66+
67+
[data-scope="tour"][data-part="progress-text"] {
68+
margin-bottom: 20px;
69+
opacity: 0.72;
70+
}
71+
72+
[data-scope="tour"][data-part="backdrop"] {
73+
background-color: rgba(0, 0, 0, 0.5);
74+
}
75+
76+
[data-scope="tour"][data-part="spotlight"] {
77+
border: 3px solid pink;
78+
}
79+
80+
[data-scope="tour"][data-part="close-trigger"] {
81+
font-family: inherit;
82+
height: 25px;
83+
width: 25px;
84+
display: inline-flex;
85+
align-items: center;
86+
justify-content: center;
87+
position: absolute;
88+
top: 10px;
89+
right: 10px;
90+
}
91+
92+
.tour.button__group {
93+
display: flex;
94+
align-items: flex-end;
95+
gap: 10px;
96+
}
97+
98+
.tour .steps__container {
99+
display: flex;
100+
flex-direction: column;
101+
align-items: flex-start;
102+
gap: 50vh;
103+
}
104+
105+
.tour .overflow__container {
106+
width: 500px;
107+
height: 400px;
108+
max-height: 200px;
109+
overflow: auto;
110+
border: 2px solid teal;
111+
position: relative;
112+
}
113+
114+
.tour .overflow__container::before {
115+
content: "Overflow";
116+
display: block;
117+
position: sticky;
118+
background-color: teal;
119+
color: white;
120+
padding: 2px 4px 3px;
121+
top: 0px;
122+
}
123+
124+
.tour .overflow__container .h-200px {
125+
height: 200px;
126+
}
127+
128+
.tour .overflow__container .h-100px {
129+
height: 100px;
130+
}

packages/react/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@
213213
"@zag-js/toast": "0.79.1",
214214
"@zag-js/toggle-group": "0.79.1",
215215
"@zag-js/tooltip": "0.79.1",
216+
"@zag-js/tour": "0.79.1",
216217
"@zag-js/tree-view": "0.79.1",
217218
"@zag-js/types": "0.79.1"
218219
},

packages/react/src/components/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ export * from './toast'
4444
export * from './toggle'
4545
export * from './toggle-group'
4646
export * from './tooltip'
47+
export * from './tour'
4748
export * from './tree-view'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Frame } from '@ark-ui/react/frame'
2+
import { DemoTour } from './tour'
3+
4+
export const Basic = () => {
5+
return (
6+
<main>
7+
<DemoTour />
8+
<div className="tour">
9+
<div className="steps__container">
10+
<h3 id="step-1">Step 1</h3>
11+
<div className="overflow__container">
12+
<div className="h-200px" />
13+
<h3 id="step-2">Step 2</h3>
14+
<div className="h-100px" />
15+
</div>
16+
<Frame>
17+
<h1 id="step-2a">Iframe Content</h1>
18+
<p>
19+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor
20+
incididunt ut labore et dolore magna aliqua.
21+
</p>
22+
</Frame>
23+
<h3 id="step-3">Step 3</h3>
24+
<h3 id="step-4">Step 4</h3>
25+
</div>
26+
</div>
27+
</main>
28+
)
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import type { TourStepDetails } from '@ark-ui/react/tour'
2+
3+
export const steps: TourStepDetails[] = [
4+
{
5+
type: 'dialog',
6+
id: 'step-0',
7+
title: 'Centered tour (no target)',
8+
description: 'This is the center of the world. Ready to start the tour?',
9+
actions: [{ label: 'Next', action: 'next' }],
10+
},
11+
{
12+
type: 'tooltip',
13+
id: 'step-1',
14+
title: 'Step 1. Welcome',
15+
description: 'To the new world',
16+
target: () => document.querySelector<HTMLElement>('#step-1'),
17+
actions: [
18+
{ label: 'Prev', action: 'prev' },
19+
{ label: 'Next', action: 'next' },
20+
],
21+
effect({ show, update }) {
22+
const abort = new AbortController()
23+
24+
fetch('https://api.github.com/users/octocat', { signal: abort.signal })
25+
.then((res) => res.json())
26+
.then((data) => {
27+
update({ title: data.name })
28+
show()
29+
})
30+
31+
return () => {
32+
abort.abort()
33+
}
34+
},
35+
},
36+
{
37+
type: 'tooltip',
38+
id: 'step-2',
39+
title: 'Step 2. Inside a scrollable container',
40+
description: 'Using scrollIntoView(...) rocks!',
41+
target: () => document.querySelector<HTMLElement>('#step-2'),
42+
actions: [
43+
{ label: 'Prev', action: 'prev' },
44+
{ label: 'Next', action: 'next' },
45+
],
46+
},
47+
{
48+
type: 'tooltip',
49+
id: 'step-2a',
50+
title: 'Step 2a. Inside an Iframe container',
51+
description: 'It calculates the offset rect correctly. Thanks to floating UI!',
52+
target: () => {
53+
const [frameEl] = Array.from(frames)
54+
return frameEl?.document.querySelector<HTMLElement>('#step-2a')
55+
},
56+
actions: [
57+
{ label: 'Prev', action: 'prev' },
58+
{ label: 'Next', action: 'next' },
59+
],
60+
},
61+
{
62+
type: 'tooltip',
63+
id: 'step-3',
64+
title: 'Step 3. Normal scrolling',
65+
description: 'The new world is a great place',
66+
target: () => document.querySelector<HTMLElement>('#step-3'),
67+
actions: [
68+
{ label: 'Prev', action: 'prev' },
69+
{ label: 'Next', action: 'next' },
70+
],
71+
},
72+
{
73+
type: 'tooltip',
74+
id: 'step-4',
75+
title: 'Step 4. Close to bottom',
76+
description: 'So nice to see the scrolling works!',
77+
target: () => document.querySelector<HTMLElement>('#step-4'),
78+
actions: [
79+
{ label: 'Prev', action: 'prev' },
80+
{ label: 'Next', action: 'next' },
81+
],
82+
},
83+
{
84+
type: 'dialog',
85+
id: 'step-5',
86+
title: "You're all sorted! (no target)",
87+
description: 'Thanks for trying out the tour. Enjoy the app!',
88+
actions: [{ label: 'Finish', action: 'dismiss' }],
89+
},
90+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { Tour, useTour } from '@ark-ui/react/tour'
2+
import { XIcon } from 'lucide-react'
3+
import { useEffect } from 'react'
4+
import { steps } from './steps'
5+
6+
export const DemoTour = () => {
7+
const tour = useTour({ steps })
8+
9+
// Start the tour when the component mounts
10+
useEffect(() => {
11+
tour.start()
12+
}, [tour])
13+
14+
return (
15+
<Tour.Root tour={tour}>
16+
<Tour.Backdrop />
17+
<Tour.Spotlight />
18+
<Tour.Positioner>
19+
<Tour.Content>
20+
<Tour.Arrow>
21+
<Tour.ArrowTip />
22+
</Tour.Arrow>
23+
<Tour.Title />
24+
<Tour.Description />
25+
<Tour.ProgressText />
26+
<Tour.CloseTrigger>
27+
<XIcon />
28+
</Tour.CloseTrigger>
29+
<Tour.Actions>
30+
{(actions) =>
31+
actions.map((action) => <Tour.ActionTrigger key={action.label} action={action} />)
32+
}
33+
</Tour.Actions>
34+
</Tour.Content>
35+
</Tour.Positioner>
36+
</Tour.Root>
37+
)
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
export type { StepDetails as TourStepDetails } from '@zag-js/tour'
2+
export {
3+
TourActionTrigger,
4+
type TourActionTriggerBaseProps,
5+
type TourActionTriggerProps,
6+
} from './tour-action-trigger'
7+
export {
8+
TourActions,
9+
type TourActionsProps,
10+
} from './tour-actions'
11+
export { TourArrow, type TourArrowBaseProps, type TourArrowProps } from './tour-arrow'
12+
export {
13+
TourArrowTip,
14+
type TourArrowTipBaseProps,
15+
type TourArrowTipProps,
16+
} from './tour-arrow-tip'
17+
export { TourBackdrop, type TourBackdropBaseProps, type TourBackdropProps } from './tour-backdrop'
18+
export {
19+
TourCloseTrigger,
20+
type TourCloseTriggerBaseProps,
21+
type TourCloseTriggerProps,
22+
} from './tour-close-trigger'
23+
export { TourContent, type TourContentBaseProps, type TourContentProps } from './tour-content'
24+
export { TourContext, type TourContextProps } from './tour-context'
25+
export {
26+
TourDescription,
27+
type TourDescriptionBaseProps,
28+
type TourDescriptionProps,
29+
} from './tour-description'
30+
export {
31+
TourPositioner,
32+
type TourPositionerBaseProps,
33+
type TourPositionerProps,
34+
} from './tour-positioner'
35+
export {
36+
TourProgressText,
37+
type TourProgressTextBaseProps,
38+
type TourProgressTextProps,
39+
} from './tour-progress-text'
40+
export { TourRoot, type TourRootBaseProps, type TourRootProps } from './tour-root'
41+
export {
42+
TourSpotlight,
43+
type TourSpotlightBaseProps,
44+
type TourSpotlightProps,
45+
} from './tour-spotlight'
46+
export { TourTitle, type TourTitleBaseProps, type TourTitleProps } from './tour-title'
47+
export { tourAnatomy } from './tour.anatomy'
48+
export { useTour, type UseTourProps, type UseTourReturn } from './use-tour'
49+
export { useTourContext, type UseTourContext } from './use-tour-context'
50+
51+
export * as Tour from './tour'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { mergeProps } from '@zag-js/react'
2+
import type { StepActionTriggerProps } from '@zag-js/tour'
3+
import { forwardRef } from 'react'
4+
import { createSplitProps } from '../../utils/create-split-props'
5+
import { type HTMLProps, type PolymorphicProps, ark } from '../factory'
6+
import { useTourContext } from './use-tour-context'
7+
8+
export interface TourActionTriggerBaseProps extends PolymorphicProps, StepActionTriggerProps {}
9+
export interface TourActionTriggerProps extends HTMLProps<'button'>, TourActionTriggerBaseProps {}
10+
11+
export const TourActionTrigger = forwardRef<HTMLButtonElement, TourActionTriggerProps>(
12+
(props, ref) => {
13+
const [actionTriggerProps, localProps] = createSplitProps<StepActionTriggerProps>()(props, [
14+
'action',
15+
])
16+
const tour = useTourContext()
17+
const mergedProps = mergeProps(tour.getActionTriggerProps(actionTriggerProps), localProps)
18+
19+
return (
20+
<ark.button {...mergedProps} ref={ref}>
21+
{actionTriggerProps.action.label}
22+
</ark.button>
23+
)
24+
},
25+
)
26+
27+
TourActionTrigger.displayName = 'TourActionTrigger'

0 commit comments

Comments
 (0)