Skip to content

Commit d7a3b29

Browse files
authored
feat(Button): consume Penta tokens and update examples (#9934)
* feat(Button): consume Penta tokens and update examples * fix (Button docs): add aria-label to pass a11y tests * fix(Button): rename noPadding to hasNoPadding * docs(Button): update fragments
1 parent dba392d commit d7a3b29

15 files changed

+359
-256
lines changed

packages/react-core/src/components/Button/Button.tsx

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export enum ButtonVariant {
1313
warning = 'warning',
1414
link = 'link',
1515
plain = 'plain',
16-
control = 'control'
16+
control = 'control',
17+
stateful = 'stateful'
1718
}
1819

1920
export enum ButtonType {
@@ -28,6 +29,12 @@ export enum ButtonSize {
2829
lg = 'lg'
2930
}
3031

32+
export enum ButtonState {
33+
read = 'read',
34+
unread = 'unread',
35+
attention = 'attention'
36+
}
37+
3138
export interface BadgeCountObject {
3239
/** Adds styling to the badge to indicate it has been read */
3340
isRead?: boolean;
@@ -44,8 +51,8 @@ export interface ButtonProps extends Omit<React.HTMLProps<HTMLButtonElement>, 'r
4451
className?: string;
4552
/** Sets the base component to render. defaults to button */
4653
component?: React.ElementType<any> | React.ComponentType<any>;
47-
/** Adds active styling to button. */
48-
isActive?: boolean;
54+
/** Adds clicked styling to button. */
55+
isClicked?: boolean;
4956
/** Adds block styling to button */
5057
isBlock?: boolean;
5158
/** Adds disabled styling and disables the button using the disabled html attribute */
@@ -69,7 +76,11 @@ export interface ButtonProps extends Omit<React.HTMLProps<HTMLButtonElement>, 'r
6976
/** Sets button type */
7077
type?: 'button' | 'submit' | 'reset';
7178
/** Adds button variant styles */
72-
variant?: 'primary' | 'secondary' | 'tertiary' | 'danger' | 'warning' | 'link' | 'plain' | 'control';
79+
variant?: 'primary' | 'secondary' | 'tertiary' | 'danger' | 'warning' | 'link' | 'plain' | 'control' | 'stateful';
80+
/** Sets state of the stateful button variant. Default is "unread" */
81+
state?: 'read' | 'unread' | 'attention';
82+
/** Applies no padding on a plain button variant. Use when plain button is placed inline with text */
83+
hasNoPadding?: boolean;
7384
/** Sets position of the icon. Note: "left" and "right" are deprecated. Use "start" and "end" instead */
7485
iconPosition?: 'start' | 'end' | 'left' | 'right';
7586
/** Adds accessible text to the button. */
@@ -94,9 +105,7 @@ const ButtonBase: React.FunctionComponent<ButtonProps> = ({
94105
children = null,
95106
className = '',
96107
component = 'button',
97-
// TODO: Update eslint ignore when issue #9907 is resolved
98-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
99-
isActive = false,
108+
isClicked = false,
100109
isBlock = false,
101110
isDisabled = false,
102111
isAriaDisabled = false,
@@ -110,6 +119,8 @@ const ButtonBase: React.FunctionComponent<ButtonProps> = ({
110119
isInline = false,
111120
type = ButtonType.button,
112121
variant = ButtonVariant.primary,
122+
state = ButtonState.unread,
123+
hasNoPadding = false,
113124
iconPosition = 'start',
114125
'aria-label': ariaLabel = null,
115126
icon = null,
@@ -157,12 +168,13 @@ const ButtonBase: React.FunctionComponent<ButtonProps> = ({
157168
isBlock && styles.modifiers.block,
158169
isDisabled && styles.modifiers.disabled,
159170
isAriaDisabled && styles.modifiers.ariaDisabled,
160-
// TODO: Update when issue #9907 is resolved
161-
// isActive && styles.modifiers.active,
171+
isClicked && styles.modifiers.clicked,
162172
isInline && variant === ButtonVariant.link && styles.modifiers.inline,
163173
isDanger && (variant === ButtonVariant.secondary || variant === ButtonVariant.link) && styles.modifiers.danger,
164174
isLoading !== null && variant !== ButtonVariant.plain && styles.modifiers.progress,
165175
isLoading && styles.modifiers.inProgress,
176+
hasNoPadding && variant === ButtonVariant.plain && styles.modifiers.noPadding,
177+
variant === ButtonVariant.stateful && styles.modifiers[state],
166178
size === ButtonSize.sm && styles.modifiers.small,
167179
size === ButtonSize.lg && styles.modifiers.displayLg,
168180
className

packages/react-core/src/components/Button/__tests__/Button.test.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from 'react';
22

33
import { render, screen } from '@testing-library/react';
44

5-
import { Button, ButtonVariant } from '../Button';
5+
import { Button, ButtonState, ButtonVariant } from '../Button';
66

77
Object.values(ButtonVariant).forEach((variant) => {
88
if (variant !== 'primary') {
@@ -55,10 +55,9 @@ test('Renders with class pf-m-block when isBlock = true', () => {
5555
expect(screen.getByRole('button')).toHaveClass('pf-m-block');
5656
});
5757

58-
// TODO: Reenable or remove with issue #9907
59-
xtest('Renders with class pf-m-active when isActive = true', () => {
60-
render(<Button isActive>Active Button</Button>);
61-
expect(screen.getByRole('button')).toHaveClass('pf-m-active');
58+
xtest('Renders with class pf-m-clicked when isClicked = true', () => {
59+
render(<Button isClicked>Clicked Button</Button>);
60+
expect(screen.getByRole('button')).toHaveClass('pf-m-clicked');
6261
});
6362

6463
test('Renders with class pf-m-disabled when isDisabled = true', () => {
@@ -80,6 +79,22 @@ test('Does not disable button when isDisabled = true and component = a', () => {
8079
expect(screen.getByText('Disabled yet focusable button')).not.toHaveProperty('disabled');
8180
});
8281

82+
test('Renders with class pf-m-unread by default when variant = stateful', () => {
83+
render(<Button variant="stateful">Stateful Button</Button>);
84+
expect(screen.getByRole('button')).toHaveClass('pf-m-stateful', 'pf-m-unread');
85+
});
86+
87+
Object.values(ButtonState).forEach((state) => {
88+
test(`Renders with class pf-m-${state} when state = ${state} and variant = stateful`, () => {
89+
render(
90+
<Button variant="stateful" state={state}>
91+
Stateful Button - {state}
92+
</Button>
93+
);
94+
expect(screen.getByRole('button')).toHaveClass('pf-m-stateful', `pf-m-${state}`);
95+
});
96+
});
97+
8398
test('Renders with class pf-m-danger when isDanger = true and variant = secondary', () => {
8499
render(
85100
<Button variant="secondary" isDanger>

packages/react-core/src/components/Button/__tests__/__snapshots__/Button.test.tsx.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ exports[`Renders basic button 1`] = `
66
aria-disabled="false"
77
aria-label="basic button"
88
class="pf-v5-c-button pf-m-primary"
9-
data-ouia-component-id="OUIA-Generated-Button-primary-26"
9+
data-ouia-component-id="OUIA-Generated-Button-primary-27"
1010
data-ouia-component-type="PF5/Button"
1111
data-ouia-safe="true"
1212
type="button"

packages/react-core/src/components/Button/examples/Button.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import ExternalLinkSquareAltIcon from '@patternfly/react-icons/dist/esm/icons/ex
1212
import CopyIcon from '@patternfly/react-icons/dist/esm/icons/copy-icon';
1313
import ArrowRightIcon from '@patternfly/react-icons/dist/esm/icons/arrow-right-icon';
1414
import UploadIcon from '@patternfly/react-icons/dist/esm/icons/upload-icon';
15+
import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon';
16+
import QuestionCircleIcon from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
1517
import { Link } from '@reach/router';
1618

1719
## Examples
@@ -33,6 +35,7 @@ PatternFly supports several button styling variants to be used in different scen
3335
| Link | Links are labeled, but have no background or border. Use for actions that require less emphasis, actions that navigate users to another page within the same window, and/or actions that navigate to external pages in a new window. Links may be placed inline with text using the `isInline` property.|
3436
| Plain | Plain buttons have no styling and are intended to be labeled with icons. |
3537
| Control | Control buttons can be labeled with text or icons. Primarily intended to be paired with other controls in an [input group](/components/input-group). |
38+
| Stateful | Stateful buttons are ideal for displaying the state of notifications. They have 3 states: `read`, `unread` and `attention`.
3639

3740
### Disabled buttons
3841

@@ -107,3 +110,17 @@ Buttons can display a `count` in the form of a badge to indicate some value or n
107110

108111
```ts file="./ButtonWithCount.tsx" isBeta
109112
```
113+
114+
### Plain with no padding
115+
116+
To display a plain/icon button inline with text, use `noPadding` prop in addition to `variant="plain"`.
117+
118+
```ts file="./ButtonPlainHasNoPadding.tsx"
119+
```
120+
121+
### Stateful
122+
123+
Stateful buttons are ideal for displaying the state of notifications. Use `variant="stateful"` alongside with the `state` property, which can have these 3 values: `read`, `unread` (used as default) and `attention` (which means unread and requires attention).
124+
125+
```ts file="./ButtonStateful.tsx"
126+
```
Lines changed: 21 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,28 @@
11
import React from 'react';
2-
import { Button, Tooltip } from '@patternfly/react-core';
3-
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
2+
import { Button, Flex, Tooltip } from '@patternfly/react-core';
43
import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
54

65
export const ButtonAriaDisabled: React.FunctionComponent = () => (
7-
<React.Fragment>
8-
<Button isAriaDisabled>Primary aria disabled</Button>
9-
<Button isAriaDisabled>Secondary aria disabled</Button>{' '}
10-
<Button variant="secondary" isDanger isAriaDisabled>
11-
Danger secondary aria disabled
12-
</Button>{' '}
13-
<Button isAriaDisabled variant="tertiary">
14-
Tertiary aria disabled
15-
</Button>{' '}
16-
<Button isAriaDisabled variant="danger">
17-
Danger disabled
18-
</Button>{' '}
19-
<Button isAriaDisabled variant="warning">
20-
Warning disabled
21-
</Button>
22-
<br />
23-
<br />
24-
<Button isAriaDisabled variant="link" icon={<PlusCircleIcon />}>
25-
Link aria disabled
26-
</Button>{' '}
27-
<Button isAriaDisabled variant="link" isInline>
28-
Inline link aria disabled
29-
</Button>{' '}
30-
<Button variant="link" isDanger isAriaDisabled>
31-
Danger link disabled
32-
</Button>{' '}
33-
<Button isAriaDisabled variant="plain" aria-label="Action">
34-
<TimesIcon />
35-
</Button>{' '}
36-
<Button isAriaDisabled variant="control">
37-
Control aria disabled
38-
</Button>
39-
<br />
40-
<br />
41-
<Tooltip content="Aria-disabled buttons are like disabled buttons, but focusable. Allows for tooltip support.">
42-
<Button isAriaDisabled variant="secondary">
43-
Secondary button to core docs
6+
<>
7+
<Flex columnGap={{ default: 'columnGapSm' }}>
8+
<Button isAriaDisabled>Primary aria disabled</Button>
9+
<Button isAriaDisabled variant="link" icon={<PlusCircleIcon />}>
10+
Link aria disabled
4411
</Button>
45-
</Tooltip>{' '}
46-
<Tooltip content="Aria-disabled link as button with tooltip">
47-
<Button component="a" isAriaDisabled href="https://pf4.patternfly.org/" target="_blank" variant="tertiary">
48-
Tertiary link as button to core docs
12+
<Button isAriaDisabled variant="link" isInline>
13+
Inline link aria disabled
4914
</Button>
50-
</Tooltip>
51-
</React.Fragment>
15+
</Flex>
16+
<br />
17+
<Flex columnGap={{ default: 'columnGapSm' }}>
18+
<Tooltip content="Aria-disabled buttons are like disabled buttons, but focusable. Allows for tooltip support.">
19+
<Button isAriaDisabled>Primary button with tooltip</Button>
20+
</Tooltip>
21+
<Tooltip content="Aria-disabled link as button with tooltip">
22+
<Button component="a" isAriaDisabled href="https://www.patternfly.org/" target="_blank" variant="secondary">
23+
Secondary link as button to PatternFly home
24+
</Button>
25+
</Tooltip>
26+
</Flex>
27+
</>
5228
);
Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
import React from 'react';
2-
import { Button } from '@patternfly/react-core';
2+
import { Button, Flex } from '@patternfly/react-core';
33
import ArrowRightIcon from '@patternfly/react-icons/dist/esm/icons/arrow-right-icon';
44

55
export const ButtonCallToAction: React.FunctionComponent = () => (
6-
<React.Fragment>
6+
<Flex columnGap={{ default: 'columnGapSm' }}>
77
<Button variant="primary" size="lg">
88
Call to action
9-
</Button>{' '}
9+
</Button>
1010
<Button variant="secondary" size="lg">
1111
Call to action
12-
</Button>{' '}
12+
</Button>
1313
<Button variant="tertiary" size="lg">
1414
Call to action
15-
</Button>{' '}
16-
<Button variant="link" size="lg">
17-
Call to action <ArrowRightIcon />
1815
</Button>
19-
<br />
20-
<br />
21-
</React.Fragment>
16+
<Button variant="link" size="lg" icon={<ArrowRightIcon />} iconPosition="end">
17+
Call to action
18+
</Button>
19+
</Flex>
2220
);
Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,52 @@
11
import React from 'react';
2-
import { Button } from '@patternfly/react-core';
2+
import { Button, Flex } from '@patternfly/react-core';
33
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
44
import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
5+
import CopyIcon from '@patternfly/react-icons/dist/esm/icons/copy-icon';
56

67
export const ButtonDisabled: React.FunctionComponent = () => (
7-
<React.Fragment>
8-
<Button isDisabled>Primary disabled</Button> <Button isDisabled>Secondary disabled</Button>{' '}
9-
<Button variant="secondary" isDanger isDisabled>
10-
Danger secondary disabled
11-
</Button>{' '}
12-
<Button isDisabled variant="tertiary">
13-
Tertiary disabled
14-
</Button>{' '}
15-
<Button isDisabled variant="danger">
16-
Danger disabled
17-
</Button>{' '}
18-
<Button isDisabled variant="warning">
19-
Warning disabled
20-
</Button>
8+
<>
9+
<Flex columnGap={{ default: 'columnGapSm' }}>
10+
<Button isDisabled>Primary</Button>
11+
<Button variant="secondary" isDisabled>
12+
Secondary
13+
</Button>
14+
<Button variant="secondary" isDanger isDisabled>
15+
Danger secondary
16+
</Button>
17+
<Button isDisabled variant="tertiary">
18+
Tertiary
19+
</Button>
20+
<Button isDisabled variant="danger">
21+
Danger
22+
</Button>
23+
<Button isDisabled variant="warning">
24+
Warning
25+
</Button>
26+
</Flex>
2127
<br />
28+
<Flex columnGap={{ default: 'columnGapSm' }}>
29+
<Button isDisabled variant="link" icon={<PlusCircleIcon />}>
30+
Link
31+
</Button>
32+
<Button isDisabled variant="link" isInline>
33+
Inline link
34+
</Button>
35+
<Button variant="link" isDanger isDisabled>
36+
Danger link
37+
</Button>
38+
<Button isDisabled variant="plain" aria-label="Action">
39+
<TimesIcon />
40+
</Button>
41+
</Flex>
2242
<br />
23-
<Button isDisabled variant="link" icon={<PlusCircleIcon />}>
24-
Link disabled
25-
</Button>{' '}
26-
<Button isDisabled variant="link" isInline>
27-
Inline link disabled
28-
</Button>{' '}
29-
<Button variant="link" isDanger isDisabled>
30-
Danger link disabled
31-
</Button>{' '}
32-
<Button isDisabled variant="plain" aria-label="Action">
33-
<TimesIcon />
34-
</Button>{' '}
35-
<Button isDisabled variant="control">
36-
Control disabled
37-
</Button>
38-
</React.Fragment>
43+
<Flex columnGap={{ default: 'columnGapSm' }}>
44+
<Button isDisabled variant="control">
45+
Control
46+
</Button>
47+
<Button isDisabled variant="control" aria-label="Copy">
48+
<CopyIcon />
49+
</Button>
50+
</Flex>
51+
</>
3952
);
Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
11
import React from 'react';
2-
import { Button } from '@patternfly/react-core';
2+
import { Button, Flex } from '@patternfly/react-core';
33

44
export const ButtonLinks: React.FunctionComponent = () => (
5-
<React.Fragment>
6-
<Button component="a" href="https://pf4.patternfly.org/" target="_blank" variant="primary">
7-
Link to core docs
8-
</Button>{' '}
9-
<Button component="a" href="https://pf4.patternfly.org/" target="_blank" variant="secondary">
10-
Secondary link to core docs
11-
</Button>{' '}
12-
<Button isDisabled component="a" href="https://pf4.patternfly.org/" target="_blank" variant="tertiary">
13-
Tertiary link to core docs
14-
</Button>{' '}
15-
<Button component="a" href="https://pf4.patternfly.org/contribution/#modifiers" variant="link">
16-
Jump to modifiers in contribution guidelines
5+
<Flex>
6+
<Button component="a" href="https://www.patternfly.org/" target="_blank" variant="primary">
7+
Link to PatternFly home
178
</Button>
18-
</React.Fragment>
9+
<Button component="a" href="https://www.patternfly.org/" target="_blank" variant="secondary">
10+
Secondary link to PatternFly home
11+
</Button>
12+
<Button isDisabled component="a" href="https://www.patternfly.org/" target="_blank" variant="tertiary">
13+
Tertiary link to PatternFly home
14+
</Button>
15+
<Button component="a" href="https://www.patternfly.org/" variant="link">
16+
Jump to PatternFly home
17+
</Button>
18+
</Flex>
1919
);

0 commit comments

Comments
 (0)