Skip to content

Commit 59f6a1b

Browse files
authored
docs: Add story for card with top-level action (#32215)
1 parent 6f53f97 commit 59f6a1b

File tree

2 files changed

+170
-0
lines changed

2 files changed

+170
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import * as React from 'react';
2+
import {
3+
Body1,
4+
Button,
5+
Caption1,
6+
Card,
7+
CardHeader,
8+
CardFooter,
9+
CardPreview,
10+
Link,
11+
makeStyles,
12+
Subtitle1,
13+
tokens,
14+
} from '@fluentui/react-components';
15+
import { MoreHorizontal20Regular, Open16Regular } from '@fluentui/react-icons';
16+
17+
const resolveAsset = (asset: string) => {
18+
const ASSET_URL =
19+
'https://raw.githubusercontent.com/microsoft/fluentui/master/packages/react-components/react-card/stories/src/assets/';
20+
21+
return `${ASSET_URL}${asset}`;
22+
};
23+
24+
const useStyles = makeStyles({
25+
main: {
26+
display: 'flex',
27+
flexWrap: 'wrap',
28+
flexDirection: 'column',
29+
columnGap: '16px',
30+
rowGap: '36px',
31+
},
32+
33+
title: { margin: '0 0 12px' },
34+
35+
description: { margin: '0 0 12px' },
36+
37+
card: {
38+
width: '400px',
39+
maxWidth: '100%',
40+
height: 'fit-content',
41+
},
42+
43+
link: {
44+
color: tokens.colorNeutralForeground1,
45+
46+
':hover': {
47+
color: tokens.colorNeutralForeground1,
48+
textDecoration: 'none',
49+
},
50+
},
51+
52+
text: { margin: '0' },
53+
});
54+
55+
const Header = ({ title, description }: Record<string, string>) => {
56+
const styles = useStyles();
57+
58+
return (
59+
<>
60+
{title ? (
61+
<Subtitle1 as="h4" block className={styles.title}>
62+
{title}
63+
</Subtitle1>
64+
) : null}
65+
66+
{description ? (
67+
<Body1 as="p" block className={styles.description}>
68+
{description}
69+
</Body1>
70+
) : null}
71+
</>
72+
);
73+
};
74+
75+
export const WithAction = () => {
76+
const styles = useStyles();
77+
const linkRef = React.useRef<HTMLAnchorElement>(null);
78+
79+
const onActionCardKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>) => {
80+
if (ev.key === 'Enter') {
81+
onActionCardClick();
82+
}
83+
};
84+
85+
const onActionCardClick = () => {
86+
alert('Opened Classroom Collaboration app');
87+
};
88+
89+
const onLinkedCardClick = () => {
90+
linkRef.current?.click();
91+
};
92+
93+
const onLinkedCardKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>) => {
94+
if (ev.key === 'Enter') {
95+
onLinkedCardClick();
96+
}
97+
};
98+
99+
return (
100+
<div className={styles.main}>
101+
<section>
102+
<Header
103+
title="Card with click event"
104+
description="This card has both a root click event and an Open button that performs the same action. Adding enter key handling to the card root is optional since the Open button also provides keyboard access."
105+
/>
106+
<Card className={styles.card} onClick={onActionCardClick} onKeyDown={onActionCardKeyDown} focusMode="off">
107+
<CardPreview>
108+
<img src={resolveAsset('office2.png')} alt="Sales Presentation Preview" />
109+
</CardPreview>
110+
111+
<CardHeader
112+
image={<img src={resolveAsset('pptx.png')} width="32px" height="32px" alt="Microsoft PowerPoint logo" />}
113+
header={
114+
<Body1>
115+
<b>App Name</b>
116+
</Body1>
117+
}
118+
description={<Caption1>Developer</Caption1>}
119+
action={<Button appearance="transparent" icon={<MoreHorizontal20Regular />} aria-label="More options" />}
120+
/>
121+
122+
<p className={styles.text}>
123+
Donut chocolate bar oat cake. Dragée tiramisu lollipop bear claw. Marshmallow pastry jujubes toffee sugar
124+
plum.
125+
</p>
126+
127+
<CardFooter>
128+
<Button appearance="primary" icon={<Open16Regular />} onClick={onActionCardClick}>
129+
Open
130+
</Button>
131+
</CardFooter>
132+
</Card>
133+
</section>
134+
135+
<section>
136+
<Header
137+
title="Linked Card"
138+
description="When a card doesn't have a separate button within its contents, it usually makes the most sense for the title text of the card to become the additional interactive element (a link in this example)."
139+
/>
140+
<Card className={styles.card} onClick={onLinkedCardClick} onKeyDown={onLinkedCardKeyDown} focusMode="off">
141+
<CardPreview>
142+
<img src={resolveAsset('office2.png')} alt="Sales Presentation Preview" />
143+
</CardPreview>
144+
145+
<CardHeader
146+
image={<img src={resolveAsset('pptx.png')} width="32px" height="32px" alt="Microsoft PowerPoint logo" />}
147+
header={
148+
<Link href="https://www.microsoft.com/" target="_blank" ref={linkRef} className={styles.link}>
149+
<b>App Name</b>
150+
</Link>
151+
}
152+
description={<Caption1>Developer</Caption1>}
153+
action={<Button appearance="transparent" icon={<MoreHorizontal20Regular />} aria-label="More options" />}
154+
/>
155+
</Card>
156+
</section>
157+
</div>
158+
);
159+
};
160+
161+
WithAction.parameters = {
162+
docs: {
163+
description: {
164+
story:
165+
"When giving a card a top-level click handler, it's important to ensure the same action can be done by a button or link within the Card. " +
166+
'This ensures the action is accesible to screen reader, touch screen reader, keyboard, and voice control users.',
167+
},
168+
},
169+
};

packages/react-components/react-card/stories/src/Card/index.stories.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export { Size } from './CardSize.stories';
88
export { Appearance } from './CardAppearance.stories';
99
export { Selectable } from './CardSelectable.stories';
1010
export { SelectableIndicator } from './CardSelectableIndicator.stories';
11+
export { WithAction } from './CardAction.stories';
1112
export { FocusMode } from './CardFocusMode.stories';
1213
export { Templates } from './CardTemplates.stories';
1314

0 commit comments

Comments
 (0)