@purpurds/popover 0.0.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/LICENSE.txt +905 -0
- package/dist/metadata.js +8 -0
- package/dist/popover-back.d.ts +9 -0
- package/dist/popover-back.d.ts.map +1 -0
- package/dist/popover-button.d.ts +37 -0
- package/dist/popover-button.d.ts.map +1 -0
- package/dist/popover-content.d.ts +93 -0
- package/dist/popover-content.d.ts.map +1 -0
- package/dist/popover-flow.d.ts +65 -0
- package/dist/popover-flow.d.ts.map +1 -0
- package/dist/popover-footer.d.ts +16 -0
- package/dist/popover-footer.d.ts.map +1 -0
- package/dist/popover-header.d.ts +7 -0
- package/dist/popover-header.d.ts.map +1 -0
- package/dist/popover-internal-context.d.ts +15 -0
- package/dist/popover-internal-context.d.ts.map +1 -0
- package/dist/popover-next.d.ts +9 -0
- package/dist/popover-next.d.ts.map +1 -0
- package/dist/popover-standalone.d.ts +12 -0
- package/dist/popover-standalone.d.ts.map +1 -0
- package/dist/popover-steps.d.ts +6 -0
- package/dist/popover-steps.d.ts.map +1 -0
- package/dist/popover-trigger.d.ts +27 -0
- package/dist/popover-trigger.d.ts.map +1 -0
- package/dist/popover-walkthrough.d.ts +13 -0
- package/dist/popover-walkthrough.d.ts.map +1 -0
- package/dist/popover.cjs.js +42 -0
- package/dist/popover.cjs.js.map +1 -0
- package/dist/popover.d.ts +36 -0
- package/dist/popover.d.ts.map +1 -0
- package/dist/popover.es.js +3849 -0
- package/dist/popover.es.js.map +1 -0
- package/dist/styles.css +1 -0
- package/dist/use-screen-size.hook.d.ts +7 -0
- package/dist/use-screen-size.hook.d.ts.map +1 -0
- package/dist/use-smooth-scroll.d.ts +5 -0
- package/dist/use-smooth-scroll.d.ts.map +1 -0
- package/dist/usePopoverTrigger.d.ts +5 -0
- package/dist/usePopoverTrigger.d.ts.map +1 -0
- package/dist/usePopoverWalkthrough.d.ts +7 -0
- package/dist/usePopoverWalkthrough.d.ts.map +1 -0
- package/eslint.config.mjs +2 -0
- package/package.json +82 -0
- package/src/global.d.ts +4 -0
- package/src/popover-back.test.tsx +63 -0
- package/src/popover-back.tsx +40 -0
- package/src/popover-button.test.tsx +51 -0
- package/src/popover-button.tsx +84 -0
- package/src/popover-content.test.tsx +1122 -0
- package/src/popover-content.tsx +277 -0
- package/src/popover-flow.tsx +170 -0
- package/src/popover-footer.test.tsx +21 -0
- package/src/popover-footer.tsx +32 -0
- package/src/popover-header.test.tsx +22 -0
- package/src/popover-header.tsx +32 -0
- package/src/popover-internal-context.tsx +28 -0
- package/src/popover-next.test.tsx +61 -0
- package/src/popover-next.tsx +40 -0
- package/src/popover-standalone.tsx +48 -0
- package/src/popover-steps.tsx +32 -0
- package/src/popover-trigger.tsx +71 -0
- package/src/popover-walkthrough.test.tsx +346 -0
- package/src/popover-walkthrough.tsx +45 -0
- package/src/popover.module.scss +315 -0
- package/src/popover.stories.tsx +1157 -0
- package/src/popover.test.tsx +642 -0
- package/src/popover.tsx +76 -0
- package/src/use-screen-size.hook.ts +39 -0
- package/src/use-smooth-scroll.ts +62 -0
- package/src/usePopoverTrigger.ts +59 -0
- package/src/usePopoverWalkthrough.ts +85 -0
- package/vitest.setup.ts +30 -0
|
@@ -0,0 +1,1157 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button } from "@purpurds/button";
|
|
3
|
+
import { Heading } from "@purpurds/heading";
|
|
4
|
+
import { IconHeart } from "@purpurds/icon/heart";
|
|
5
|
+
import { IconInfo } from "@purpurds/icon/info";
|
|
6
|
+
import { Link } from "@purpurds/link";
|
|
7
|
+
import { Paragraph } from "@purpurds/paragraph";
|
|
8
|
+
import { type Meta, type StoryObj } from "@storybook/react";
|
|
9
|
+
|
|
10
|
+
import "@purpurds/link/styles";
|
|
11
|
+
import "@purpurds/icon/styles";
|
|
12
|
+
import "@purpurds/button/styles";
|
|
13
|
+
import "@purpurds/heading/styles";
|
|
14
|
+
import "@purpurds/paragraph/styles";
|
|
15
|
+
import { Popover, type PopoverAction } from "./popover";
|
|
16
|
+
|
|
17
|
+
const meta: Meta<typeof Popover> = {
|
|
18
|
+
title: "Dialogs and Overlays/Popover",
|
|
19
|
+
component: Popover,
|
|
20
|
+
subcomponents: {
|
|
21
|
+
"Popover.Trigger": Popover.Trigger,
|
|
22
|
+
"Popover.Content": Popover.Content,
|
|
23
|
+
"Popover.Footer": Popover.Footer,
|
|
24
|
+
"Popover.Button": Popover.Button,
|
|
25
|
+
"Popover.Flow": Popover.Flow,
|
|
26
|
+
},
|
|
27
|
+
parameters: {
|
|
28
|
+
layout: "centered",
|
|
29
|
+
docs: {
|
|
30
|
+
description: {
|
|
31
|
+
component: `
|
|
32
|
+
## Popover
|
|
33
|
+
|
|
34
|
+
Popovers help users discover new features and important changes in the UI, enhancing the user experience and driving adoption of high-value features.
|
|
35
|
+
|
|
36
|
+
**Built on [Radix Popover](https://www.radix-ui.com/primitives/docs/components/popover) for accessibility and positioning.**
|
|
37
|
+
|
|
38
|
+
**Usage guidelines:**
|
|
39
|
+
- Prefer single-step popovers for clarity. If a walkthrough is needed, keep it to 3–5 steps maximum and follow the user's natural workflow order.
|
|
40
|
+
- Use popovers for features with high business value or those that create new value for users, not just minor improvements.
|
|
41
|
+
- Consider user segmentation and avoid conflicting or redundant messages. Bundle related highlights when possible.
|
|
42
|
+
|
|
43
|
+
[See Figma design](https://www.figma.com/design/XEaIIFskrrxIBHMZDkIuIg/Purpur-DS---Components-and-guidelines?node-id=43484-6510&p=f&m=dev)
|
|
44
|
+
`,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
design: [
|
|
48
|
+
{
|
|
49
|
+
name: "Popover",
|
|
50
|
+
type: "figma",
|
|
51
|
+
url: "https://www.figma.com/design/XEaIIFskrrxIBHMZDkIuIg/Purpur-DS---Components-and-guidelines?node-id=43484-6510&p=f&m=dev",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
argTypes: {
|
|
56
|
+
open: {
|
|
57
|
+
control: "boolean",
|
|
58
|
+
description: "Controls whether the popover is open",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default meta;
|
|
64
|
+
|
|
65
|
+
interface StandaloneArgs {
|
|
66
|
+
negative: boolean;
|
|
67
|
+
beakPosition: "up" | "right" | "down" | "left" | "none";
|
|
68
|
+
align: "start" | "center" | "end";
|
|
69
|
+
closeIconAriaLabel: string;
|
|
70
|
+
headerTitle: string;
|
|
71
|
+
headerIcon: boolean;
|
|
72
|
+
body: string;
|
|
73
|
+
showFooter: boolean;
|
|
74
|
+
button: boolean;
|
|
75
|
+
buttonText: string;
|
|
76
|
+
highlight: boolean;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface WalkthroughArgs {
|
|
80
|
+
separatorText: string;
|
|
81
|
+
stepText: string;
|
|
82
|
+
backLabel: string;
|
|
83
|
+
nextLabel: string;
|
|
84
|
+
finishLabel: string;
|
|
85
|
+
closeIconAriaLabel: string;
|
|
86
|
+
step1BeakPosition: "up" | "right" | "down" | "left" | "none";
|
|
87
|
+
step2BeakPosition: "up" | "right" | "down" | "left" | "none";
|
|
88
|
+
step3BeakPosition: "up" | "right" | "down" | "left" | "none";
|
|
89
|
+
step4BeakPosition: "up" | "right" | "down" | "left" | "none";
|
|
90
|
+
avoidCollisions: boolean;
|
|
91
|
+
step1Body: string;
|
|
92
|
+
step2Body: string;
|
|
93
|
+
step3Body: string;
|
|
94
|
+
step4Body: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Standalone
|
|
99
|
+
*
|
|
100
|
+
* A popover highlights new features or significant changes in existing ones. Content may link to help articles or telia.se/foretag pages. Triggered by user action or appearing on a new or updated page, it's a single callout with no subsequent steps, designed for onboarding users to new or improved features/flows.
|
|
101
|
+
*
|
|
102
|
+
* Use cases: Introducing a feature, highlighting significant changes, prompting user action.
|
|
103
|
+
*
|
|
104
|
+
* Props:
|
|
105
|
+
* - Built on Radix Popover for accessibility and positioning.
|
|
106
|
+
* - `title` (required): string — header title text.
|
|
107
|
+
* - `icon` (optional): ReactNode — optional header icon.
|
|
108
|
+
* - `body` (required): string — main content.
|
|
109
|
+
* - `beakPosition`, `align`, `showArrow` (optional): Placement and appearance.
|
|
110
|
+
* - `negative`, `highlight` (optional): Visual variants.
|
|
111
|
+
* - `closeIconAriaLabel` (required for accessibility): Label for close icon.
|
|
112
|
+
* - All other [Radix Popover props](https://www.radix-ui.com/primitives/docs/components/popover#root) are supported.
|
|
113
|
+
*
|
|
114
|
+
* Note: Story args like `headerTitle`, `headerIcon`, `buttonText`, etc. are for Storybook controls only and are mapped to the actual component props in the render function.
|
|
115
|
+
*/
|
|
116
|
+
export const Standalone: StoryObj = {
|
|
117
|
+
args: {
|
|
118
|
+
negative: false,
|
|
119
|
+
beakPosition: "down",
|
|
120
|
+
align: "center",
|
|
121
|
+
closeIconAriaLabel: "Close",
|
|
122
|
+
headerTitle: "This is a title",
|
|
123
|
+
headerIcon: true,
|
|
124
|
+
body: "Body text lorem ipsum dolor sit amet, consectetur adipiscing elit.",
|
|
125
|
+
showFooter: true,
|
|
126
|
+
button: false,
|
|
127
|
+
buttonText: "Got it",
|
|
128
|
+
highlight: false,
|
|
129
|
+
},
|
|
130
|
+
argTypes: {
|
|
131
|
+
negative: {
|
|
132
|
+
control: "boolean",
|
|
133
|
+
description: "Use negative (light) variant",
|
|
134
|
+
},
|
|
135
|
+
beakPosition: {
|
|
136
|
+
control: "select",
|
|
137
|
+
options: ["up", "right", "down", "left", "none"],
|
|
138
|
+
description:
|
|
139
|
+
"Position of the popover beak/arrow (up, right, down, left). Use none to hide the arrow.",
|
|
140
|
+
},
|
|
141
|
+
align: {
|
|
142
|
+
control: "select",
|
|
143
|
+
options: ["start", "center", "end"],
|
|
144
|
+
description: "Alignment of the popover",
|
|
145
|
+
},
|
|
146
|
+
closeIconAriaLabel: {
|
|
147
|
+
control: "text",
|
|
148
|
+
description: "Accessible label for close icon",
|
|
149
|
+
},
|
|
150
|
+
headerTitle: {
|
|
151
|
+
control: "text",
|
|
152
|
+
description: "Title text (required prop in component, mapped from this control)",
|
|
153
|
+
},
|
|
154
|
+
headerIcon: {
|
|
155
|
+
control: "boolean",
|
|
156
|
+
description: "Show header icon (optional icon prop in component)",
|
|
157
|
+
},
|
|
158
|
+
body: {
|
|
159
|
+
control: "text",
|
|
160
|
+
description: "Body text content",
|
|
161
|
+
},
|
|
162
|
+
showFooter: {
|
|
163
|
+
control: "boolean",
|
|
164
|
+
description: "Show footer with button",
|
|
165
|
+
},
|
|
166
|
+
button: {
|
|
167
|
+
control: "boolean",
|
|
168
|
+
description: "Use button as trigger",
|
|
169
|
+
},
|
|
170
|
+
buttonText: {
|
|
171
|
+
control: "text",
|
|
172
|
+
description: "Footer button text",
|
|
173
|
+
},
|
|
174
|
+
highlight: {
|
|
175
|
+
control: "boolean",
|
|
176
|
+
description: "Highlight the trigger",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
render: (args) => {
|
|
180
|
+
const typedArgs = args as unknown as StandaloneArgs;
|
|
181
|
+
const backgroundColor = typedArgs.negative
|
|
182
|
+
? "var(--purpur-color-background-tone-on-tone-primary)"
|
|
183
|
+
: "transparent";
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div
|
|
187
|
+
style={{
|
|
188
|
+
padding: "50px",
|
|
189
|
+
backgroundColor,
|
|
190
|
+
minHeight: "450px",
|
|
191
|
+
minWidth: "450px",
|
|
192
|
+
display: "flex",
|
|
193
|
+
placeContent: "center",
|
|
194
|
+
alignItems: "center",
|
|
195
|
+
}}
|
|
196
|
+
>
|
|
197
|
+
<Popover>
|
|
198
|
+
<Popover.Trigger highlight={typedArgs.highlight}>
|
|
199
|
+
{typedArgs.button ? (
|
|
200
|
+
<Button variant="primary">{typedArgs.buttonText}</Button>
|
|
201
|
+
) : (
|
|
202
|
+
<Button variant="primary" aria-label={typedArgs.buttonText} iconOnly>
|
|
203
|
+
<IconInfo />
|
|
204
|
+
</Button>
|
|
205
|
+
)}
|
|
206
|
+
</Popover.Trigger>
|
|
207
|
+
<Popover.Content
|
|
208
|
+
negative={typedArgs.negative}
|
|
209
|
+
beakPosition={typedArgs.beakPosition}
|
|
210
|
+
align={typedArgs.align}
|
|
211
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
212
|
+
title={typedArgs.headerTitle || "Default Title"}
|
|
213
|
+
{...(typedArgs.headerIcon && { icon: <IconHeart size="sm" /> })}
|
|
214
|
+
body={typedArgs.body}
|
|
215
|
+
onAction={(action: PopoverAction) => console.log("Popover action:", action)}
|
|
216
|
+
>
|
|
217
|
+
{typedArgs.showFooter && (
|
|
218
|
+
<Popover.Footer>
|
|
219
|
+
<Popover.Button onClick={() => console.log("Popover dismissed")}>
|
|
220
|
+
{typedArgs.buttonText}
|
|
221
|
+
</Popover.Button>
|
|
222
|
+
</Popover.Footer>
|
|
223
|
+
)}
|
|
224
|
+
</Popover.Content>
|
|
225
|
+
</Popover>
|
|
226
|
+
</div>
|
|
227
|
+
);
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* User Initiated Walkthrough
|
|
233
|
+
*
|
|
234
|
+
* A walkthrough is an interactive tour guiding users through a series of hands-on steps. It effectively introduces new features within a single page, a sequence of steps across multiple pages, or highlights a feature's immediate business value.
|
|
235
|
+
*
|
|
236
|
+
* All popovers in a walkthrough must be wrapped under a single `PopoverFlow` component to ensure correct step management and navigation.
|
|
237
|
+
*
|
|
238
|
+
* Use cases: Onboarding users to a new workflow, providing a tour of new features, or offering contextual assistance during MyBusiness configuration.
|
|
239
|
+
*
|
|
240
|
+
* Props (for the Popover component):
|
|
241
|
+
* - Built on Radix Popover for accessibility and positioning.
|
|
242
|
+
* - `multistep` (required): true
|
|
243
|
+
* - `step` (required): number — the step index for this popover in the walkthrough. Steps must be sequential (e.g., 1, 2, 3, ...).
|
|
244
|
+
* - `initialStep` (optional): number — sets which step is open when the walkthrough mounts. If set to 0, the walkthrough starts closed and no step is shown initially.
|
|
245
|
+
* - `title` (required): string — header title text.
|
|
246
|
+
* - `icon` (optional): ReactNode — optional header icon.
|
|
247
|
+
* - `body` (required): string — main content.
|
|
248
|
+
* - `side`, `align`, `showArrow` (optional): Placement and appearance.
|
|
249
|
+
* - `negative`, `highlight` (optional): Visual variants.
|
|
250
|
+
* - `closeIconAriaLabel` (required for accessibility): Label for close icon.
|
|
251
|
+
* - All other [Radix Popover props](https://www.radix-ui.com/primitives/docs/components/popover#root) are supported.
|
|
252
|
+
*
|
|
253
|
+
* Note: Story args like `separatorText`, `stepText`, `backLabel`, `nextLabel`, `finishLabel`, etc. are for Storybook controls only and are mapped to the actual component props in the render function or via the PopoverFlow provider.
|
|
254
|
+
*/
|
|
255
|
+
export const UserInitiatedWalkthrough: StoryObj = {
|
|
256
|
+
args: {
|
|
257
|
+
separatorText: "of",
|
|
258
|
+
stepText: "Step",
|
|
259
|
+
backLabel: "Back",
|
|
260
|
+
nextLabel: "Next",
|
|
261
|
+
finishLabel: "Finish",
|
|
262
|
+
closeIconAriaLabel: "Exit tour",
|
|
263
|
+
openDelay: 0,
|
|
264
|
+
},
|
|
265
|
+
argTypes: {
|
|
266
|
+
separatorText: {
|
|
267
|
+
control: "text",
|
|
268
|
+
description: "Separator text between step numbers",
|
|
269
|
+
},
|
|
270
|
+
stepText: {
|
|
271
|
+
control: "text",
|
|
272
|
+
description: "Label for 'Step'",
|
|
273
|
+
},
|
|
274
|
+
backLabel: {
|
|
275
|
+
control: "text",
|
|
276
|
+
description: "Label for back button",
|
|
277
|
+
},
|
|
278
|
+
nextLabel: {
|
|
279
|
+
control: "text",
|
|
280
|
+
description: "Label for next button",
|
|
281
|
+
},
|
|
282
|
+
finishLabel: {
|
|
283
|
+
control: "text",
|
|
284
|
+
description: "Label for finish button",
|
|
285
|
+
},
|
|
286
|
+
closeIconAriaLabel: {
|
|
287
|
+
control: "text",
|
|
288
|
+
description: "Accessible label for close icon",
|
|
289
|
+
},
|
|
290
|
+
openDelay: {
|
|
291
|
+
control: "number",
|
|
292
|
+
description: "Delay in milliseconds before opening the popover",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
render: function UserInitiatedWalkthroughStory(args) {
|
|
296
|
+
const typedArgs = args as unknown as {
|
|
297
|
+
separatorText: string;
|
|
298
|
+
stepText: string;
|
|
299
|
+
backLabel: string;
|
|
300
|
+
nextLabel: string;
|
|
301
|
+
finishLabel: string;
|
|
302
|
+
closeIconAriaLabel: string;
|
|
303
|
+
openDelay: number;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const [walkthroughStarted, setWalkthroughStarted] = React.useState(false);
|
|
307
|
+
const [walkthroughCompleted, setWalkthroughCompleted] = React.useState(false);
|
|
308
|
+
const startButtonRef = React.useRef<HTMLButtonElement>(null);
|
|
309
|
+
|
|
310
|
+
const handleStartWalkthrough = () => {
|
|
311
|
+
setWalkthroughStarted(true);
|
|
312
|
+
setWalkthroughCompleted(false);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const handleAction = (action: PopoverAction) => {
|
|
316
|
+
console.log("Walkthrough action:", action);
|
|
317
|
+
|
|
318
|
+
if (action.type === "finish" || action.type === "dismiss") {
|
|
319
|
+
setWalkthroughCompleted(true);
|
|
320
|
+
setWalkthroughStarted(false);
|
|
321
|
+
|
|
322
|
+
// Return focus to the start button
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
startButtonRef.current?.focus();
|
|
325
|
+
console.log("Focus returned to start button");
|
|
326
|
+
}, 100);
|
|
327
|
+
|
|
328
|
+
if (action.type === "finish") {
|
|
329
|
+
console.log("Walkthrough completed successfully");
|
|
330
|
+
} else {
|
|
331
|
+
console.log("Walkthrough dismissed");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return (
|
|
337
|
+
<div
|
|
338
|
+
style={{
|
|
339
|
+
minHeight: "600px",
|
|
340
|
+
position: "relative",
|
|
341
|
+
padding: "40px",
|
|
342
|
+
}}
|
|
343
|
+
>
|
|
344
|
+
{/* Header with Start Button */}
|
|
345
|
+
<div
|
|
346
|
+
style={{
|
|
347
|
+
display: "flex",
|
|
348
|
+
flexDirection: "column",
|
|
349
|
+
alignItems: "center",
|
|
350
|
+
gap: "16px",
|
|
351
|
+
marginBottom: "40px",
|
|
352
|
+
paddingBottom: "20px",
|
|
353
|
+
borderBottom: "1px solid var(--purpur-color-border-secondary)",
|
|
354
|
+
}}
|
|
355
|
+
>
|
|
356
|
+
<Heading tag="h2">User-Initiated Walkthrough Demo</Heading>
|
|
357
|
+
<p style={{ textAlign: "center", maxWidth: "500px" }}>
|
|
358
|
+
This demonstrates a walkthrough that starts when the user clicks the button below. After
|
|
359
|
+
completion or dismissal, focus returns to the start button.
|
|
360
|
+
</p>
|
|
361
|
+
<Button
|
|
362
|
+
ref={startButtonRef}
|
|
363
|
+
variant="primary"
|
|
364
|
+
onClick={handleStartWalkthrough}
|
|
365
|
+
disabled={walkthroughStarted}
|
|
366
|
+
>
|
|
367
|
+
{walkthroughStarted ? "Tour in Progress..." : "Start Product Tour"}
|
|
368
|
+
</Button>
|
|
369
|
+
{walkthroughCompleted && (
|
|
370
|
+
<div
|
|
371
|
+
style={{
|
|
372
|
+
backgroundColor: "var(--purpur-color-background-tone-success)",
|
|
373
|
+
color: "var(--purpur-color-text-on-color)",
|
|
374
|
+
padding: "12px 24px",
|
|
375
|
+
borderRadius: "4px",
|
|
376
|
+
fontSize: "14px",
|
|
377
|
+
}}
|
|
378
|
+
>
|
|
379
|
+
✅ Tour completed! Focus has returned to the start button.
|
|
380
|
+
</div>
|
|
381
|
+
)}
|
|
382
|
+
</div>
|
|
383
|
+
|
|
384
|
+
{/* Main Content Area - Always Visible */}
|
|
385
|
+
<div
|
|
386
|
+
style={{
|
|
387
|
+
position: "relative",
|
|
388
|
+
minHeight: "400px",
|
|
389
|
+
backgroundColor: "var(--purpur-color-background-surface)",
|
|
390
|
+
borderRadius: "8px",
|
|
391
|
+
padding: "20px",
|
|
392
|
+
}}
|
|
393
|
+
>
|
|
394
|
+
{walkthroughStarted && (
|
|
395
|
+
<Popover.Flow
|
|
396
|
+
separatorText={typedArgs.separatorText}
|
|
397
|
+
stepText={typedArgs.stepText}
|
|
398
|
+
backLabel={typedArgs.backLabel}
|
|
399
|
+
nextLabel={typedArgs.nextLabel}
|
|
400
|
+
finishLabel={typedArgs.finishLabel}
|
|
401
|
+
openDelay={typedArgs.openDelay}
|
|
402
|
+
>
|
|
403
|
+
{/* Step 1 - Top Left */}
|
|
404
|
+
<div style={{ position: "absolute", top: "40px", left: "40px" }}>
|
|
405
|
+
<Popover multistep step={1}>
|
|
406
|
+
<Popover.Trigger>
|
|
407
|
+
<Button variant="primary" iconOnly>
|
|
408
|
+
<IconHeart />
|
|
409
|
+
</Button>
|
|
410
|
+
</Popover.Trigger>
|
|
411
|
+
<Popover.Content
|
|
412
|
+
beakPosition="down"
|
|
413
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
414
|
+
title="Welcome to the Tour"
|
|
415
|
+
icon={<IconHeart size="sm" />}
|
|
416
|
+
body="Let's explore the main features of this application. Click Next to continue."
|
|
417
|
+
onAction={handleAction}
|
|
418
|
+
/>
|
|
419
|
+
</Popover>
|
|
420
|
+
</div>
|
|
421
|
+
|
|
422
|
+
{/* Step 2 - Top Right */}
|
|
423
|
+
<div style={{ position: "absolute", top: "40px", right: "40px" }}>
|
|
424
|
+
<Popover multistep step={2}>
|
|
425
|
+
<Popover.Trigger>
|
|
426
|
+
<Button variant="primary">Settings</Button>
|
|
427
|
+
</Popover.Trigger>
|
|
428
|
+
<Popover.Content
|
|
429
|
+
beakPosition="down"
|
|
430
|
+
align="end"
|
|
431
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
432
|
+
title="Customize Your Experience"
|
|
433
|
+
body="Access your settings and preferences here. You can customize themes, notifications, and more."
|
|
434
|
+
onAction={handleAction}
|
|
435
|
+
/>
|
|
436
|
+
</Popover>
|
|
437
|
+
</div>
|
|
438
|
+
|
|
439
|
+
{/* Step 3 - Center */}
|
|
440
|
+
<div
|
|
441
|
+
style={{
|
|
442
|
+
position: "absolute",
|
|
443
|
+
top: "50%",
|
|
444
|
+
left: "50%",
|
|
445
|
+
transform: "translate(-50%, -50%)",
|
|
446
|
+
}}
|
|
447
|
+
>
|
|
448
|
+
<div
|
|
449
|
+
style={{
|
|
450
|
+
padding: "40px",
|
|
451
|
+
backgroundColor: "var(--purpur-color-background-base)",
|
|
452
|
+
borderRadius: "8px",
|
|
453
|
+
textAlign: "center",
|
|
454
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
|
455
|
+
}}
|
|
456
|
+
>
|
|
457
|
+
<Heading tag="h3" style={{ marginBottom: "16px" }}>
|
|
458
|
+
Main Content Area
|
|
459
|
+
</Heading>
|
|
460
|
+
<Popover multistep step={3}>
|
|
461
|
+
<Popover.Trigger>
|
|
462
|
+
<Button variant="primary">Get Started</Button>
|
|
463
|
+
</Popover.Trigger>
|
|
464
|
+
<Popover.Content
|
|
465
|
+
beakPosition="up"
|
|
466
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
467
|
+
title="Ready to Begin!"
|
|
468
|
+
body="This is where you'll spend most of your time. Click Complete to finish the tour and return focus to the start button."
|
|
469
|
+
onAction={handleAction}
|
|
470
|
+
/>
|
|
471
|
+
</Popover>
|
|
472
|
+
</div>
|
|
473
|
+
</div>
|
|
474
|
+
</Popover.Flow>
|
|
475
|
+
)}
|
|
476
|
+
|
|
477
|
+
{/* Static elements when walkthrough is not active */}
|
|
478
|
+
{!walkthroughStarted && (
|
|
479
|
+
<>
|
|
480
|
+
<div style={{ position: "absolute", top: "40px", left: "40px" }}>
|
|
481
|
+
<Button variant="primary" iconOnly disabled>
|
|
482
|
+
<IconHeart />
|
|
483
|
+
</Button>
|
|
484
|
+
</div>
|
|
485
|
+
<div style={{ position: "absolute", top: "40px", right: "40px" }}>
|
|
486
|
+
<Button variant="primary" disabled>
|
|
487
|
+
Settings
|
|
488
|
+
</Button>
|
|
489
|
+
</div>
|
|
490
|
+
<div
|
|
491
|
+
style={{
|
|
492
|
+
position: "absolute",
|
|
493
|
+
top: "50%",
|
|
494
|
+
left: "50%",
|
|
495
|
+
transform: "translate(-50%, -50%)",
|
|
496
|
+
padding: "40px",
|
|
497
|
+
backgroundColor: "var(--purpur-color-background-base)",
|
|
498
|
+
borderRadius: "8px",
|
|
499
|
+
textAlign: "center",
|
|
500
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
|
501
|
+
}}
|
|
502
|
+
>
|
|
503
|
+
<Heading tag="h3" style={{ marginBottom: "16px" }}>
|
|
504
|
+
Main Content Area
|
|
505
|
+
</Heading>
|
|
506
|
+
<Button variant="primary" disabled>
|
|
507
|
+
Get Started
|
|
508
|
+
</Button>
|
|
509
|
+
</div>
|
|
510
|
+
</>
|
|
511
|
+
)}
|
|
512
|
+
</div>
|
|
513
|
+
</div>
|
|
514
|
+
);
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
export const Walkthrough: StoryObj = {
|
|
519
|
+
parameters: {
|
|
520
|
+
docs: { disable: true },
|
|
521
|
+
},
|
|
522
|
+
args: {
|
|
523
|
+
separatorText: "of",
|
|
524
|
+
stepText: "Step",
|
|
525
|
+
backLabel: "Back",
|
|
526
|
+
nextLabel: "Next",
|
|
527
|
+
finishLabel: "Finish",
|
|
528
|
+
closeIconAriaLabel: "Close",
|
|
529
|
+
step1BeakPosition: "down",
|
|
530
|
+
step2BeakPosition: "down",
|
|
531
|
+
step3BeakPosition: "up",
|
|
532
|
+
step4BeakPosition: "down",
|
|
533
|
+
avoidCollisions: false,
|
|
534
|
+
step1Body: "Start here in the top left corner. This is the first step.",
|
|
535
|
+
step2Body: "Continue here in the top right corner. This is the second step.",
|
|
536
|
+
step3Body: "Now we're at the bottom of the page. This is the third step.",
|
|
537
|
+
step4Body: "Finish here in the middle. You are done!",
|
|
538
|
+
},
|
|
539
|
+
argTypes: {
|
|
540
|
+
separatorText: {
|
|
541
|
+
control: "text",
|
|
542
|
+
description: "Separator text between step numbers",
|
|
543
|
+
},
|
|
544
|
+
stepText: {
|
|
545
|
+
control: "text",
|
|
546
|
+
description: "Label for 'Step'",
|
|
547
|
+
},
|
|
548
|
+
backLabel: {
|
|
549
|
+
control: "text",
|
|
550
|
+
description: "Label for back button",
|
|
551
|
+
},
|
|
552
|
+
nextLabel: {
|
|
553
|
+
control: "text",
|
|
554
|
+
description: "Label for next button",
|
|
555
|
+
},
|
|
556
|
+
finishLabel: {
|
|
557
|
+
control: "text",
|
|
558
|
+
description: "Label for finish button",
|
|
559
|
+
},
|
|
560
|
+
closeIconAriaLabel: {
|
|
561
|
+
control: "text",
|
|
562
|
+
description: "Accessible label for close icon",
|
|
563
|
+
},
|
|
564
|
+
step1BeakPosition: {
|
|
565
|
+
control: "select",
|
|
566
|
+
options: ["up", "right", "down", "left", "none"],
|
|
567
|
+
description: "Beak position for step 1",
|
|
568
|
+
},
|
|
569
|
+
step2BeakPosition: {
|
|
570
|
+
control: "select",
|
|
571
|
+
options: ["up", "right", "down", "left", "none"],
|
|
572
|
+
description: "Beak position for step 2",
|
|
573
|
+
},
|
|
574
|
+
step3BeakPosition: {
|
|
575
|
+
control: "select",
|
|
576
|
+
options: ["up", "right", "down", "left", "none"],
|
|
577
|
+
description: "Beak position for step 3",
|
|
578
|
+
},
|
|
579
|
+
step4BeakPosition: {
|
|
580
|
+
control: "select",
|
|
581
|
+
options: ["up", "right", "down", "left", "none"],
|
|
582
|
+
description: "Beak position for step 4",
|
|
583
|
+
},
|
|
584
|
+
avoidCollisions: {
|
|
585
|
+
control: "boolean",
|
|
586
|
+
description: "Avoid collisions with viewport edges",
|
|
587
|
+
},
|
|
588
|
+
step1Body: {
|
|
589
|
+
control: "text",
|
|
590
|
+
description: "Body text for step 1",
|
|
591
|
+
},
|
|
592
|
+
step2Body: {
|
|
593
|
+
control: "text",
|
|
594
|
+
description: "Body text for step 2",
|
|
595
|
+
},
|
|
596
|
+
step3Body: {
|
|
597
|
+
control: "text",
|
|
598
|
+
description: "Body text for step 3",
|
|
599
|
+
},
|
|
600
|
+
step4Body: {
|
|
601
|
+
control: "text",
|
|
602
|
+
description: "Body text for step 4",
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
render: function WalkthroughStory(args) {
|
|
606
|
+
const typedArgs = args as unknown as WalkthroughArgs;
|
|
607
|
+
const [key, setKey] = React.useState(0);
|
|
608
|
+
const [showAutoStartMessage, setShowAutoStartMessage] = React.useState(true);
|
|
609
|
+
const [walkthroughCompleted, setWalkthroughCompleted] = React.useState(false);
|
|
610
|
+
|
|
611
|
+
const handleAction = (action: PopoverAction) => {
|
|
612
|
+
console.log("Popover action:", action);
|
|
613
|
+
|
|
614
|
+
// Handle different completion scenarios
|
|
615
|
+
if (action.type === "finish") {
|
|
616
|
+
console.log("Walkthrough completed successfully!");
|
|
617
|
+
setShowAutoStartMessage(false);
|
|
618
|
+
setWalkthroughCompleted(true);
|
|
619
|
+
|
|
620
|
+
// Example: Consumer can decide what to do here
|
|
621
|
+
// - Redirect to a specific page
|
|
622
|
+
// - Focus on a specific element
|
|
623
|
+
// - Save user preference to not show again
|
|
624
|
+
// - Enable certain features
|
|
625
|
+
// window.location.href = '/dashboard';
|
|
626
|
+
// document.querySelector('#main-feature')?.focus();
|
|
627
|
+
} else if (action.type === "dismiss") {
|
|
628
|
+
console.log("Walkthrough dismissed by user");
|
|
629
|
+
setShowAutoStartMessage(false);
|
|
630
|
+
|
|
631
|
+
// Example: Different behavior for dismiss
|
|
632
|
+
// - Maybe show a "resume tour" option
|
|
633
|
+
// - Track analytics that user skipped
|
|
634
|
+
// - Show a less intrusive hint later
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
return (
|
|
639
|
+
<div
|
|
640
|
+
style={{
|
|
641
|
+
width: "100vw",
|
|
642
|
+
height: "250vh",
|
|
643
|
+
position: "relative",
|
|
644
|
+
}}
|
|
645
|
+
>
|
|
646
|
+
{showAutoStartMessage && (
|
|
647
|
+
<div
|
|
648
|
+
style={{
|
|
649
|
+
position: "fixed",
|
|
650
|
+
top: "20px",
|
|
651
|
+
left: "50%",
|
|
652
|
+
transform: "translateX(-50%)",
|
|
653
|
+
backgroundColor: "var(--purpur-color-background-tone-info)",
|
|
654
|
+
color: "var(--purpur-color-text-on-color)",
|
|
655
|
+
padding: "12px 24px",
|
|
656
|
+
borderRadius: "4px",
|
|
657
|
+
zIndex: 1000,
|
|
658
|
+
fontSize: "14px",
|
|
659
|
+
}}
|
|
660
|
+
>
|
|
661
|
+
🎉 Welcome! We've added new features. Let's take a quick tour.
|
|
662
|
+
</div>
|
|
663
|
+
)}
|
|
664
|
+
|
|
665
|
+
{walkthroughCompleted && (
|
|
666
|
+
<div
|
|
667
|
+
style={{
|
|
668
|
+
position: "fixed",
|
|
669
|
+
top: "20px",
|
|
670
|
+
left: "50%",
|
|
671
|
+
transform: "translateX(-50%)",
|
|
672
|
+
backgroundColor: "var(--purpur-color-background-tone-success)",
|
|
673
|
+
color: "var(--purpur-color-text-on-color)",
|
|
674
|
+
padding: "12px 24px",
|
|
675
|
+
borderRadius: "4px",
|
|
676
|
+
zIndex: 1000,
|
|
677
|
+
fontSize: "14px",
|
|
678
|
+
}}
|
|
679
|
+
>
|
|
680
|
+
✅ Tour completed! You can now explore the new features.
|
|
681
|
+
</div>
|
|
682
|
+
)}
|
|
683
|
+
|
|
684
|
+
<div
|
|
685
|
+
style={{
|
|
686
|
+
paddingTop: "60px",
|
|
687
|
+
display: "flex",
|
|
688
|
+
justifyContent: "center",
|
|
689
|
+
}}
|
|
690
|
+
>
|
|
691
|
+
<Button
|
|
692
|
+
variant="primary"
|
|
693
|
+
onClick={() => {
|
|
694
|
+
setKey((prev) => prev + 1);
|
|
695
|
+
setShowAutoStartMessage(true);
|
|
696
|
+
setWalkthroughCompleted(false);
|
|
697
|
+
}}
|
|
698
|
+
>
|
|
699
|
+
Restart Walkthrough
|
|
700
|
+
</Button>
|
|
701
|
+
</div>
|
|
702
|
+
|
|
703
|
+
<Popover.Flow
|
|
704
|
+
key={key}
|
|
705
|
+
separatorText={typedArgs.separatorText}
|
|
706
|
+
stepText={typedArgs.stepText}
|
|
707
|
+
backLabel={typedArgs.backLabel}
|
|
708
|
+
nextLabel={typedArgs.nextLabel}
|
|
709
|
+
finishLabel={typedArgs.finishLabel}
|
|
710
|
+
>
|
|
711
|
+
{/* Step 1 - Top Left - New Dashboard Widget */}
|
|
712
|
+
<div style={{ position: "absolute", top: "150px", left: "100px" }}>
|
|
713
|
+
<div
|
|
714
|
+
style={{
|
|
715
|
+
border: "2px dashed var(--purpur-color-border-primary)",
|
|
716
|
+
padding: "20px",
|
|
717
|
+
borderRadius: "8px",
|
|
718
|
+
background: "var(--purpur-color-background-surface)",
|
|
719
|
+
}}
|
|
720
|
+
>
|
|
721
|
+
<Heading tag="h3" style={{ marginBottom: "8px" }}>
|
|
722
|
+
📊 Analytics Widget
|
|
723
|
+
</Heading>
|
|
724
|
+
<p style={{ color: "var(--purpur-color-text-secondary)" }}>New feature!</p>
|
|
725
|
+
<Popover multistep step={1}>
|
|
726
|
+
<Popover.Trigger>
|
|
727
|
+
<Button variant="primary" size="sm">
|
|
728
|
+
View Analytics
|
|
729
|
+
</Button>
|
|
730
|
+
</Popover.Trigger>
|
|
731
|
+
<Popover.Content
|
|
732
|
+
beakPosition={typedArgs.step1BeakPosition}
|
|
733
|
+
avoidCollisions={typedArgs.avoidCollisions}
|
|
734
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
735
|
+
title="New: Analytics Dashboard"
|
|
736
|
+
body="Track your performance with our new analytics widget. View real-time data and insights."
|
|
737
|
+
onAction={handleAction}
|
|
738
|
+
/>
|
|
739
|
+
</Popover>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
|
|
743
|
+
{/* Step 2 - Top Right - Quick Actions */}
|
|
744
|
+
<div style={{ position: "absolute", top: "150px", right: "100px" }}>
|
|
745
|
+
<div
|
|
746
|
+
style={{
|
|
747
|
+
border: "2px dashed var(--purpur-color-border-primary)",
|
|
748
|
+
padding: "20px",
|
|
749
|
+
borderRadius: "8px",
|
|
750
|
+
background: "var(--purpur-color-background-surface)",
|
|
751
|
+
}}
|
|
752
|
+
>
|
|
753
|
+
<Heading tag="h3" style={{ marginBottom: "8px" }}>
|
|
754
|
+
⚡ Quick Actions
|
|
755
|
+
</Heading>
|
|
756
|
+
<p style={{ color: "var(--purpur-color-text-secondary)" }}>New feature!</p>
|
|
757
|
+
<Popover multistep step={2}>
|
|
758
|
+
<Popover.Trigger>
|
|
759
|
+
<Button variant="primary" size="sm">
|
|
760
|
+
Quick Actions
|
|
761
|
+
</Button>
|
|
762
|
+
</Popover.Trigger>
|
|
763
|
+
<Popover.Content
|
|
764
|
+
beakPosition={typedArgs.step2BeakPosition}
|
|
765
|
+
align="end"
|
|
766
|
+
avoidCollisions={typedArgs.avoidCollisions}
|
|
767
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
768
|
+
title="New: Quick Actions Menu"
|
|
769
|
+
body="Access frequently used actions with one click. Customize your shortcuts here."
|
|
770
|
+
onAction={handleAction}
|
|
771
|
+
/>
|
|
772
|
+
</Popover>
|
|
773
|
+
</div>
|
|
774
|
+
</div>
|
|
775
|
+
|
|
776
|
+
{/* Step 3 - Bottom - Export Feature */}
|
|
777
|
+
<div
|
|
778
|
+
style={{
|
|
779
|
+
position: "absolute",
|
|
780
|
+
bottom: "100px",
|
|
781
|
+
left: "50%",
|
|
782
|
+
transform: "translateX(-50%)",
|
|
783
|
+
}}
|
|
784
|
+
>
|
|
785
|
+
<div
|
|
786
|
+
style={{
|
|
787
|
+
border: "2px dashed var(--purpur-color-border-primary)",
|
|
788
|
+
padding: "20px",
|
|
789
|
+
borderRadius: "8px",
|
|
790
|
+
background: "var(--purpur-color-background-surface)",
|
|
791
|
+
}}
|
|
792
|
+
>
|
|
793
|
+
<Heading tag="h3" style={{ marginBottom: "8px" }}>
|
|
794
|
+
💾 Export Center
|
|
795
|
+
</Heading>
|
|
796
|
+
<p style={{ color: "var(--purpur-color-text-secondary)" }}>New feature!</p>
|
|
797
|
+
<Popover multistep step={3}>
|
|
798
|
+
<Popover.Trigger>
|
|
799
|
+
<Button variant="primary" size="sm">
|
|
800
|
+
Export Data
|
|
801
|
+
</Button>
|
|
802
|
+
</Popover.Trigger>
|
|
803
|
+
<Popover.Content
|
|
804
|
+
beakPosition={typedArgs.step3BeakPosition}
|
|
805
|
+
avoidCollisions={typedArgs.avoidCollisions}
|
|
806
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
807
|
+
title="New: Advanced Export Options"
|
|
808
|
+
body="Export your data in multiple formats. Schedule automatic exports and more."
|
|
809
|
+
onAction={handleAction}
|
|
810
|
+
/>
|
|
811
|
+
</Popover>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
|
|
815
|
+
{/* Step 4 - Middle - Main CTA */}
|
|
816
|
+
<div
|
|
817
|
+
style={{
|
|
818
|
+
position: "absolute",
|
|
819
|
+
top: "50%",
|
|
820
|
+
left: "50%",
|
|
821
|
+
transform: "translate(-50%, -50%)",
|
|
822
|
+
}}
|
|
823
|
+
>
|
|
824
|
+
<div
|
|
825
|
+
style={{
|
|
826
|
+
backgroundColor: "var(--purpur-color-background-tone-on-tone-primary)",
|
|
827
|
+
padding: "80px 120px",
|
|
828
|
+
borderRadius: "8px",
|
|
829
|
+
display: "flex",
|
|
830
|
+
flexDirection: "column",
|
|
831
|
+
justifyContent: "center",
|
|
832
|
+
alignItems: "center",
|
|
833
|
+
width: "500px",
|
|
834
|
+
height: "250px",
|
|
835
|
+
gap: "20px",
|
|
836
|
+
}}
|
|
837
|
+
>
|
|
838
|
+
<Heading tag="h2" negative style={{ textAlign: "center" }}>
|
|
839
|
+
Ready to explore?
|
|
840
|
+
</Heading>
|
|
841
|
+
<Popover multistep step={4}>
|
|
842
|
+
<Popover.Trigger>
|
|
843
|
+
<Link href="#" variant="standalone" negative>
|
|
844
|
+
Start Using New Features
|
|
845
|
+
</Link>
|
|
846
|
+
</Popover.Trigger>
|
|
847
|
+
<Popover.Content
|
|
848
|
+
negative
|
|
849
|
+
beakPosition={typedArgs.step4BeakPosition}
|
|
850
|
+
avoidCollisions={typedArgs.avoidCollisions}
|
|
851
|
+
closeIconAriaLabel={typedArgs.closeIconAriaLabel}
|
|
852
|
+
title="You're All Set!"
|
|
853
|
+
body="Click here to start using the new features. We'll save your preferences and won't show this tour again."
|
|
854
|
+
onAction={handleAction}
|
|
855
|
+
/>
|
|
856
|
+
</Popover>
|
|
857
|
+
<Paragraph negative>
|
|
858
|
+
After completing the tour, focus remains here by default. Consumers can handle the
|
|
859
|
+
'finish' or 'dismiss' events to control what happens next.
|
|
860
|
+
</Paragraph>
|
|
861
|
+
</div>
|
|
862
|
+
</div>
|
|
863
|
+
</Popover.Flow>
|
|
864
|
+
</div>
|
|
865
|
+
);
|
|
866
|
+
},
|
|
867
|
+
};
|
|
868
|
+
|
|
869
|
+
export const Variants: StoryObj = {
|
|
870
|
+
argTypes: {
|
|
871
|
+
negative: {
|
|
872
|
+
control: "boolean",
|
|
873
|
+
description: "Use negative (light) variant",
|
|
874
|
+
},
|
|
875
|
+
beakPosition: {
|
|
876
|
+
control: "select",
|
|
877
|
+
options: ["Up", "Right", "Down", "Left", "None"],
|
|
878
|
+
description:
|
|
879
|
+
"Position of the popover beak/arrow (Up, Right, Down, Left). Use None to hide the arrow.",
|
|
880
|
+
},
|
|
881
|
+
align: {
|
|
882
|
+
control: "select",
|
|
883
|
+
options: ["start", "center", "end"],
|
|
884
|
+
description: "Alignment of the popover",
|
|
885
|
+
},
|
|
886
|
+
closeIconAriaLabel: {
|
|
887
|
+
control: "text",
|
|
888
|
+
description: "Accessible label for close icon",
|
|
889
|
+
},
|
|
890
|
+
headerTitle: {
|
|
891
|
+
control: "text",
|
|
892
|
+
description: "Title text (required prop in component, mapped from this control)",
|
|
893
|
+
},
|
|
894
|
+
headerIcon: {
|
|
895
|
+
control: "boolean",
|
|
896
|
+
description: "Show header icon (optional icon prop in component)",
|
|
897
|
+
},
|
|
898
|
+
body: {
|
|
899
|
+
control: "text",
|
|
900
|
+
description: "Body text content",
|
|
901
|
+
},
|
|
902
|
+
showFooter: {
|
|
903
|
+
control: "boolean",
|
|
904
|
+
description: "Show footer with button",
|
|
905
|
+
},
|
|
906
|
+
button: {
|
|
907
|
+
control: "boolean",
|
|
908
|
+
description: "Use button as trigger",
|
|
909
|
+
},
|
|
910
|
+
buttonText: {
|
|
911
|
+
control: "text",
|
|
912
|
+
description: "Footer button text",
|
|
913
|
+
},
|
|
914
|
+
highlight: {
|
|
915
|
+
control: "boolean",
|
|
916
|
+
description: "Highlight the trigger",
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
render: () => {
|
|
920
|
+
return (
|
|
921
|
+
<div>
|
|
922
|
+
<div
|
|
923
|
+
style={{
|
|
924
|
+
display: "flex",
|
|
925
|
+
width: "800px",
|
|
926
|
+
height: "500px",
|
|
927
|
+
gap: "200px",
|
|
928
|
+
}}
|
|
929
|
+
>
|
|
930
|
+
<div>
|
|
931
|
+
<Popover>
|
|
932
|
+
<Popover.Trigger>
|
|
933
|
+
<Button variant="primary">Default</Button>
|
|
934
|
+
</Popover.Trigger>
|
|
935
|
+
<Popover.Content
|
|
936
|
+
beakPosition="down"
|
|
937
|
+
closeIconAriaLabel="Close"
|
|
938
|
+
title="Default variant"
|
|
939
|
+
body="This has the default dark background."
|
|
940
|
+
>
|
|
941
|
+
<Popover.Footer>
|
|
942
|
+
<Popover.Button>Got it</Popover.Button>
|
|
943
|
+
</Popover.Footer>
|
|
944
|
+
</Popover.Content>
|
|
945
|
+
</Popover>
|
|
946
|
+
</div>
|
|
947
|
+
|
|
948
|
+
<div
|
|
949
|
+
style={{
|
|
950
|
+
backgroundColor: "var(--purpur-color-background-tone-on-tone-primary)",
|
|
951
|
+
padding: "20px",
|
|
952
|
+
borderRadius: "8px",
|
|
953
|
+
height: "500px",
|
|
954
|
+
width: "500px",
|
|
955
|
+
}}
|
|
956
|
+
>
|
|
957
|
+
<div
|
|
958
|
+
style={{
|
|
959
|
+
width: "fit-content",
|
|
960
|
+
}}
|
|
961
|
+
>
|
|
962
|
+
<Popover>
|
|
963
|
+
<Popover.Trigger negative>
|
|
964
|
+
<Button variant="primary" negative>
|
|
965
|
+
Negative
|
|
966
|
+
</Button>
|
|
967
|
+
</Popover.Trigger>
|
|
968
|
+
<Popover.Content
|
|
969
|
+
negative
|
|
970
|
+
closeIconAriaLabel="Close"
|
|
971
|
+
title="Negative variant"
|
|
972
|
+
body="This has a light background with dark text."
|
|
973
|
+
align="start"
|
|
974
|
+
beakPosition="up"
|
|
975
|
+
>
|
|
976
|
+
<Popover.Footer>
|
|
977
|
+
<Popover.Button>Got it</Popover.Button>
|
|
978
|
+
</Popover.Footer>
|
|
979
|
+
</Popover.Content>
|
|
980
|
+
</Popover>
|
|
981
|
+
</div>
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
</div>
|
|
985
|
+
);
|
|
986
|
+
},
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
export const BeakPlacements: StoryObj = {
|
|
990
|
+
render: () => {
|
|
991
|
+
return (
|
|
992
|
+
<div
|
|
993
|
+
style={{
|
|
994
|
+
padding: "100px",
|
|
995
|
+
display: "flex",
|
|
996
|
+
flexDirection: "column",
|
|
997
|
+
gap: "60px",
|
|
998
|
+
}}
|
|
999
|
+
>
|
|
1000
|
+
{/* Basic Positions */}
|
|
1001
|
+
<div>
|
|
1002
|
+
<Heading tag="h3" style={{ marginBottom: "20px", fontSize: "18px", fontWeight: "600" }}>
|
|
1003
|
+
Basic Positions
|
|
1004
|
+
</Heading>
|
|
1005
|
+
<div
|
|
1006
|
+
style={{
|
|
1007
|
+
display: "flex",
|
|
1008
|
+
gap: "32px",
|
|
1009
|
+
flexWrap: "wrap",
|
|
1010
|
+
}}
|
|
1011
|
+
>
|
|
1012
|
+
{(["up", "down", "left", "right"] as const).map((position) => (
|
|
1013
|
+
<Popover key={position}>
|
|
1014
|
+
<Popover.Trigger>
|
|
1015
|
+
<Button variant="primary">Beak {position}</Button>
|
|
1016
|
+
</Popover.Trigger>
|
|
1017
|
+
<Popover.Content
|
|
1018
|
+
beakPosition={position}
|
|
1019
|
+
closeIconAriaLabel="Close"
|
|
1020
|
+
title={`Beak ${position}`}
|
|
1021
|
+
body={`The popover beak points ${position.toLowerCase()}.`}
|
|
1022
|
+
>
|
|
1023
|
+
<Popover.Footer>
|
|
1024
|
+
<Popover.Button>Got it</Popover.Button>
|
|
1025
|
+
</Popover.Footer>
|
|
1026
|
+
</Popover.Content>
|
|
1027
|
+
</Popover>
|
|
1028
|
+
))}
|
|
1029
|
+
<Popover>
|
|
1030
|
+
<Popover.Trigger>
|
|
1031
|
+
<Button variant="primary">No Arrow</Button>
|
|
1032
|
+
</Popover.Trigger>
|
|
1033
|
+
<Popover.Content
|
|
1034
|
+
beakPosition="none"
|
|
1035
|
+
closeIconAriaLabel="Close"
|
|
1036
|
+
title="No Arrow"
|
|
1037
|
+
body="The popover has no arrow."
|
|
1038
|
+
>
|
|
1039
|
+
<Popover.Footer>
|
|
1040
|
+
<Popover.Button>Got it</Popover.Button>
|
|
1041
|
+
</Popover.Footer>
|
|
1042
|
+
</Popover.Content>
|
|
1043
|
+
</Popover>
|
|
1044
|
+
</div>
|
|
1045
|
+
</div>
|
|
1046
|
+
|
|
1047
|
+
{/* Custom Alignment */}
|
|
1048
|
+
<div>
|
|
1049
|
+
<Heading tag="h3" style={{ marginBottom: "20px", fontSize: "18px", fontWeight: "600" }}>
|
|
1050
|
+
Custom Alignment (Bottom Position)
|
|
1051
|
+
</Heading>
|
|
1052
|
+
<div
|
|
1053
|
+
style={{
|
|
1054
|
+
display: "flex",
|
|
1055
|
+
gap: "32px",
|
|
1056
|
+
flexWrap: "wrap",
|
|
1057
|
+
}}
|
|
1058
|
+
>
|
|
1059
|
+
{(["start", "center", "end"] as const).map((align) => (
|
|
1060
|
+
<Popover key={align}>
|
|
1061
|
+
<Popover.Trigger>
|
|
1062
|
+
<Button variant="primary">Align {align}</Button>
|
|
1063
|
+
</Popover.Trigger>
|
|
1064
|
+
<Popover.Content
|
|
1065
|
+
beakPosition="down"
|
|
1066
|
+
align={align}
|
|
1067
|
+
closeIconAriaLabel="Close"
|
|
1068
|
+
title={`Aligned ${align}`}
|
|
1069
|
+
body={`The popover is aligned to the ${align} of the content.`}
|
|
1070
|
+
>
|
|
1071
|
+
<Popover.Footer>
|
|
1072
|
+
<Popover.Button>Got it</Popover.Button>
|
|
1073
|
+
</Popover.Footer>
|
|
1074
|
+
</Popover.Content>
|
|
1075
|
+
</Popover>
|
|
1076
|
+
))}
|
|
1077
|
+
</div>
|
|
1078
|
+
</div>
|
|
1079
|
+
|
|
1080
|
+
{/* Custom Offset */}
|
|
1081
|
+
<div>
|
|
1082
|
+
<Heading tag="h3" style={{ marginBottom: "20px", fontSize: "18px", fontWeight: "600" }}>
|
|
1083
|
+
Custom Offset
|
|
1084
|
+
</Heading>
|
|
1085
|
+
<div
|
|
1086
|
+
style={{
|
|
1087
|
+
display: "flex",
|
|
1088
|
+
flexDirection: "column",
|
|
1089
|
+
gap: "40px",
|
|
1090
|
+
}}
|
|
1091
|
+
>
|
|
1092
|
+
{/* Start aligned with offsets */}
|
|
1093
|
+
<div>
|
|
1094
|
+
<Heading
|
|
1095
|
+
tag="h4"
|
|
1096
|
+
style={{ marginBottom: "12px", fontSize: "14px", fontWeight: "500" }}
|
|
1097
|
+
>
|
|
1098
|
+
Start Aligned (Bottom Position)
|
|
1099
|
+
</Heading>
|
|
1100
|
+
<div style={{ display: "flex", gap: "32px", flexWrap: "wrap" }}>
|
|
1101
|
+
{([0, 35, -35] as const).map((offset) => (
|
|
1102
|
+
<Popover key={`start-${offset}`}>
|
|
1103
|
+
<Popover.Trigger>
|
|
1104
|
+
<Button variant="primary">Offset {offset}px</Button>
|
|
1105
|
+
</Popover.Trigger>
|
|
1106
|
+
<Popover.Content
|
|
1107
|
+
beakPosition="down"
|
|
1108
|
+
align="start"
|
|
1109
|
+
alignOffset={offset}
|
|
1110
|
+
closeIconAriaLabel="Close"
|
|
1111
|
+
title={`Start ${offset}px`}
|
|
1112
|
+
body={`Start aligned with ${offset}px offset from the start.`}
|
|
1113
|
+
>
|
|
1114
|
+
<Popover.Footer>
|
|
1115
|
+
<Popover.Button>Got it</Popover.Button>
|
|
1116
|
+
</Popover.Footer>
|
|
1117
|
+
</Popover.Content>
|
|
1118
|
+
</Popover>
|
|
1119
|
+
))}
|
|
1120
|
+
</div>
|
|
1121
|
+
</div>
|
|
1122
|
+
|
|
1123
|
+
<div>
|
|
1124
|
+
<Heading
|
|
1125
|
+
tag="h4"
|
|
1126
|
+
style={{ marginBottom: "12px", fontSize: "14px", fontWeight: "500" }}
|
|
1127
|
+
>
|
|
1128
|
+
Start Aligned (Left Position)
|
|
1129
|
+
</Heading>
|
|
1130
|
+
<div style={{ display: "flex", gap: "32px", flexWrap: "wrap" }}>
|
|
1131
|
+
{([0, 15, -25] as const).map((offset) => (
|
|
1132
|
+
<Popover key={`start-${offset}`}>
|
|
1133
|
+
<Popover.Trigger>
|
|
1134
|
+
<Button variant="primary">Offset {offset}px</Button>
|
|
1135
|
+
</Popover.Trigger>
|
|
1136
|
+
<Popover.Content
|
|
1137
|
+
beakPosition="left"
|
|
1138
|
+
align="start"
|
|
1139
|
+
alignOffset={offset}
|
|
1140
|
+
closeIconAriaLabel="Close"
|
|
1141
|
+
title={`Start ${offset}px`}
|
|
1142
|
+
body={`Start aligned with ${offset}px offset from the start.`}
|
|
1143
|
+
>
|
|
1144
|
+
<Popover.Footer>
|
|
1145
|
+
<Popover.Button>Got it</Popover.Button>
|
|
1146
|
+
</Popover.Footer>
|
|
1147
|
+
</Popover.Content>
|
|
1148
|
+
</Popover>
|
|
1149
|
+
))}
|
|
1150
|
+
</div>
|
|
1151
|
+
</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
</div>
|
|
1154
|
+
</div>
|
|
1155
|
+
);
|
|
1156
|
+
},
|
|
1157
|
+
};
|