Skip to content

Commit 3467d6b

Browse files
authored
Add deck component (#827)
* feat(Deck): add new Deck component * feat(Deck): add modalDeck and demo * fix linting errors * fix linting errors
1 parent 110a760 commit 3467d6b

File tree

14 files changed

+999
-0
lines changed

14 files changed

+999
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
# Sidenav top-level section
3+
# should be the same for all markdown files
4+
section: Component groups
5+
subsection: Content containers
6+
# Sidenav secondary level section
7+
# should be the same for all markdown files
8+
id: Deck
9+
# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility)
10+
source: react
11+
# If you use typescript, the name of the interface to display props for
12+
# These are found through the sourceProps function provided in patternfly-docs.source.js
13+
propComponents: ['Deck', 'DeckPage', 'DeckButton', 'ModalDeck']
14+
sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/Deck/Deck.md
15+
---
16+
17+
import Deck from '@patternfly/react-component-groups/dist/dynamic/Deck';
18+
import { ModalDeck } from '@patternfly/react-component-groups/dist/dynamic/ModalDeck';
19+
import { FunctionComponent, useState } from 'react';
20+
21+
The **deck** component is a compact, sequential container for presenting a suite of static announcements or an informational walkthrough. It is not intended for task completion or form-filling workflows.
22+
23+
## Examples
24+
25+
### Basic deck
26+
27+
This example demonstrates the basic deck with automatic navigation. Buttons can use the `navigation` prop to automatically handle page changes:
28+
- `navigation: 'next'` - Advances to the next page
29+
- `navigation: 'previous'` - Goes back to the previous page
30+
- `navigation: 'close'` - Triggers the onClose callback
31+
32+
You can also add custom `onClick` handlers for analytics, validation, or other logic. The custom `onClick` will be called **before** the automatic navigation occurs.
33+
34+
```ts file="./DeckExample.tsx"
35+
36+
```
37+
38+
### Modal deck
39+
40+
Display the deck in a modal dialog. The `ModalDeck` component wraps the Deck in a PatternFly Modal without a close button or extra padding, ideal for guided walkthroughs that require user interaction.
41+
42+
```ts file="./ModalDeckExample.tsx"
43+
44+
```
45+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
# Sidenav top-level section
3+
# should be the same for all markdown files
4+
section: Component groups
5+
subsection: Content containers
6+
# Sidenav secondary level section
7+
# should be the same for all markdown files
8+
id: Deck
9+
# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility)
10+
source: react-demos
11+
sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/Deck/DeckDemos.md
12+
---
13+
import { FunctionComponent, useState } from 'react';
14+
15+
import Deck from '@patternfly/react-component-groups/dist/dynamic/Deck';
16+
import { ModalDeck } from '@patternfly/react-component-groups/dist/dynamic/ModalDeck';
17+
18+
## Demos
19+
20+
### Onboarding modal deck
21+
22+
A complete onboarding experience using ModalDeck to guide users through key product features. This demo demonstrates a multi-step walkthrough with custom styling, labels, and content.
23+
24+
```ts file="./OnboardingModalDeckDemo.tsx"
25+
26+
```
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/* eslint-disable no-console */
2+
import { FunctionComponent, useState } from 'react';
3+
import Deck, { DeckButton } from '@patternfly/react-component-groups/dist/dynamic/Deck';
4+
import { ButtonVariant } from '@patternfly/react-core';
5+
6+
export const BasicExample: FunctionComponent = () => {
7+
const [ deckKey, setDeckKey ] = useState(0);
8+
9+
// Simulated analytics function
10+
const trackEvent = (eventName, data) => {
11+
console.log('Analytics:', eventName, data);
12+
};
13+
14+
const restartDeck = () => {
15+
setDeckKey(prev => prev + 1);
16+
trackEvent('deck_restarted', {});
17+
};
18+
19+
const pages = [
20+
{
21+
content: (
22+
<div>
23+
<p>This is the first page of your informational walkthrough.</p>
24+
</div>
25+
),
26+
buttons: [
27+
{
28+
children: 'Next',
29+
variant: ButtonVariant.primary,
30+
navigation: 'next',
31+
// Custom onClick for analytics - called before navigation
32+
onClick: () => trackEvent('deck_next_clicked', { fromPage: 1 })
33+
}
34+
] as DeckButton[]
35+
},
36+
{
37+
content: (
38+
<div>
39+
<p>Continue through your walkthrough.</p>
40+
</div>
41+
),
42+
buttons: [
43+
{
44+
children: 'Next',
45+
variant: ButtonVariant.primary,
46+
navigation: 'next',
47+
onClick: () => trackEvent('deck_next_clicked', { fromPage: 2 })
48+
}
49+
] as DeckButton[]
50+
},
51+
{
52+
content: (
53+
<div>
54+
<p>You've reached the end of the deck.</p>
55+
</div>
56+
),
57+
buttons: [
58+
{
59+
children: 'Restart',
60+
variant: ButtonVariant.primary,
61+
// Restart the deck for demo purposes
62+
onClick: () => {
63+
trackEvent('deck_completed', { totalPages: 3 });
64+
console.log('Deck completed! Restarting...');
65+
restartDeck();
66+
}
67+
}
68+
]
69+
}
70+
];
71+
72+
return (
73+
<Deck
74+
key={deckKey}
75+
pages={pages}
76+
onPageChange={(index) => {
77+
console.log('Current page:', index);
78+
trackEvent('deck_page_changed', { page: index + 1 });
79+
}}
80+
onClose={() => {
81+
trackEvent('deck_closed', {});
82+
console.log('Deck closed');
83+
}}
84+
/>
85+
);
86+
};
87+
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/* eslint-disable no-console */
2+
import { FunctionComponent, useState } from 'react';
3+
import Deck, { DeckButton } from '@patternfly/react-component-groups/dist/dynamic/Deck';
4+
import { ModalDeck } from '@patternfly/react-component-groups/dist/dynamic/ModalDeck';
5+
import { Button, ButtonVariant } from '@patternfly/react-core';
6+
7+
export const ModalDeckExample: FunctionComponent = () => {
8+
const [ isModalOpen, setIsModalOpen ] = useState(false);
9+
10+
const handleClose = () => {
11+
setIsModalOpen(false);
12+
console.log('Modal deck closed');
13+
};
14+
15+
const pages = [
16+
{
17+
content: (
18+
<div>
19+
<h2>Welcome to the Modal Deck</h2>
20+
<p>This deck is displayed in a modal dialog.</p>
21+
</div>
22+
),
23+
buttons: [
24+
{
25+
children: 'Next',
26+
variant: ButtonVariant.primary,
27+
navigation: 'next'
28+
}
29+
] as DeckButton[]
30+
},
31+
{
32+
content: (
33+
<div>
34+
<h2>Page 2</h2>
35+
<p>Navigate through the walkthrough.</p>
36+
</div>
37+
),
38+
buttons: [
39+
{
40+
children: 'Next',
41+
variant: ButtonVariant.primary,
42+
navigation: 'next'
43+
}
44+
] as DeckButton[]
45+
},
46+
{
47+
content: (
48+
<div>
49+
<h2>Final Page</h2>
50+
<p>Click Close to finish.</p>
51+
</div>
52+
),
53+
buttons: [
54+
{
55+
children: 'Close',
56+
variant: ButtonVariant.primary,
57+
navigation: 'close'
58+
}
59+
] as DeckButton[]
60+
}
61+
];
62+
63+
return (
64+
<>
65+
<Button onClick={() => setIsModalOpen(true)}>
66+
Launch modal deck
67+
</Button>
68+
<ModalDeck isOpen={isModalOpen} onClose={handleClose}>
69+
<Deck
70+
pages={pages}
71+
onClose={handleClose}
72+
onPageChange={(index) => console.log('Page changed to:', index + 1)}
73+
/>
74+
</ModalDeck>
75+
</>
76+
);
77+
};
78+
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/* eslint-disable no-console */
2+
import { FunctionComponent, useState } from 'react';
3+
import Deck, { DeckButton } from '@patternfly/react-component-groups/dist/dynamic/Deck';
4+
import { ModalDeck } from '@patternfly/react-component-groups/dist/dynamic/ModalDeck';
5+
import { Button, ButtonVariant, Label, Title, Stack, StackItem, Content } from '@patternfly/react-core';
6+
7+
export const OnboardingModalDeckDemo: FunctionComponent = () => {
8+
const [ isModalOpen, setIsModalOpen ] = useState(false);
9+
10+
const handleClose = () => {
11+
setIsModalOpen(false);
12+
console.log('Onboarding completed or skipped');
13+
};
14+
15+
// Placeholder for illustration - in a real app, replace with actual images
16+
const placeholderImage = (
17+
<div
18+
style={{
19+
width: '256px',
20+
height: '256px',
21+
borderRadius: '8px',
22+
background: 'repeating-linear-gradient(45deg, #f3f4f6, #f3f4f6 10px, #e5e7eb 10px, #e5e7eb 20px)',
23+
opacity: 0.25,
24+
margin: 'auto'
25+
}}
26+
/>
27+
);
28+
29+
const pages = [
30+
{
31+
content: (
32+
<Stack hasGutter>
33+
<StackItem>{placeholderImage}</StackItem>
34+
<StackItem>
35+
<Title headingLevel="h2" size="2xl">Welcome to [Product Name]</Title>
36+
</StackItem>
37+
<StackItem>
38+
<Content component="p">
39+
Harness the full potential of the hybrid cloud, simply by asking.
40+
</Content>
41+
</StackItem>
42+
</Stack>
43+
),
44+
buttons: [
45+
{
46+
children: 'Continue',
47+
variant: ButtonVariant.primary,
48+
navigation: 'next'
49+
}
50+
] as DeckButton[]
51+
},
52+
{
53+
content: (
54+
<Stack hasGutter>
55+
<StackItem>{placeholderImage}</StackItem>
56+
<StackItem><Label color="grey">AI Command Center</Label></StackItem>
57+
<StackItem><Title headingLevel="h2" size="2xl">Intelligence at your command</Title></StackItem>
58+
<StackItem><Content component="p">Ask anything. Get answers. Troubleshoot, analyze, and understand your entire fleet just by asking. It's the power of your data, in plain language.</Content></StackItem>
59+
</Stack>
60+
),
61+
buttons: [
62+
{
63+
children: 'Continue',
64+
variant: ButtonVariant.primary,
65+
navigation: 'next'
66+
}
67+
] as DeckButton[]
68+
},
69+
{
70+
content: (
71+
<Stack hasGutter>
72+
<StackItem>{placeholderImage}</StackItem>
73+
<StackItem><Label color="grey">Canvas Mode</Label></StackItem>
74+
<StackItem><Title headingLevel="h2" size="2xl">Go from conversation to clarity.</Title></StackItem>
75+
<StackItem><Content component="p">Transform answers into custom dashboards. In Canvas Mode, you can effortlessly arrange, customize, and build the precise view you need to monitor what matters most.</Content></StackItem>
76+
</Stack>
77+
),
78+
buttons: [
79+
{
80+
children: 'Continue',
81+
variant: ButtonVariant.primary,
82+
navigation: 'next'
83+
}
84+
] as DeckButton[]
85+
},
86+
{
87+
content: (
88+
<Stack hasGutter>
89+
<StackItem>{placeholderImage}</StackItem>
90+
<StackItem><Label color="grey">Sharing</Label></StackItem>
91+
<StackItem><Title headingLevel="h2" size="2xl">Share your vision. Instantly.</Title></StackItem>
92+
<StackItem><Content component="p">An insight is only powerful when it’s shared. Save any view to your library and share it with your team in a single click. Drive decisions, together.</Content></StackItem>
93+
</Stack>
94+
),
95+
buttons: [
96+
{
97+
children: 'Get started',
98+
variant: ButtonVariant.primary,
99+
navigation: 'close'
100+
}
101+
] as DeckButton[]
102+
}
103+
];
104+
105+
return (
106+
<>
107+
<Button onClick={() => setIsModalOpen(true)}>
108+
Launch onboarding
109+
</Button>
110+
<ModalDeck
111+
isOpen={isModalOpen}
112+
modalProps={{
113+
'aria-label': 'Product onboarding walkthrough'
114+
}}
115+
>
116+
<Deck
117+
pages={pages}
118+
onClose={handleClose}
119+
onPageChange={(index) => console.log('Onboarding page:', index + 1)}
120+
ariaLabel="Product onboarding"
121+
ariaRoleDescription="onboarding walkthrough"
122+
contentFlexProps={{
123+
spaceItems: { default: 'spaceItemsXl' }
124+
}}
125+
/>
126+
</ModalDeck>
127+
</>
128+
);
129+
};
130+

0 commit comments

Comments
 (0)