Skip to content

Commit 63f79eb

Browse files
feat(Button): added circle variant (#12092)
* feat(Button): added circle variant * Added test * Updated prop to beta * Added prop to menu toggle * Bumped core to pull in styles * Added prop to example * Bumped to core 6.5 prerelease 9 --------- Co-authored-by: Eric Olkowski <git.eric@thatblindgeye.dev>
1 parent 4365bf8 commit 63f79eb

File tree

15 files changed

+155
-22
lines changed

15 files changed

+155
-22
lines changed

packages/react-core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"tslib": "^2.8.1"
5555
},
5656
"devDependencies": {
57-
"@patternfly/patternfly": "6.5.0-prerelease.3",
57+
"@patternfly/patternfly": "6.5.0-prerelease.9",
5858
"case-anything": "^3.1.2",
5959
"css": "^3.0.0",
6060
"fs-extra": "^11.3.0"

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ export interface ButtonProps extends Omit<React.HTMLProps<HTMLButtonElement>, 'r
107107
isHamburger?: boolean;
108108
/** Adjusts and animates the hamburger icon to indicate what will happen upon clicking the button. */
109109
hamburgerVariant?: 'expand' | 'collapse';
110+
/** @beta Flag indicating the button is a circle button. Intended for buttons that only contain an icon.. */
111+
isCircle?: boolean;
110112
/** @hide Forwarded ref */
111113
innerRef?: React.Ref<any>;
112114
/** Adds count number to button */
@@ -131,6 +133,7 @@ const ButtonBase: React.FunctionComponent<ButtonProps> = ({
131133
isSettings,
132134
isHamburger,
133135
hamburgerVariant,
136+
isCircle,
134137
spinnerAriaValueText,
135138
spinnerAriaLabelledBy,
136139
spinnerAriaLabel,
@@ -261,6 +264,7 @@ const ButtonBase: React.FunctionComponent<ButtonProps> = ({
261264
variant === ButtonVariant.stateful && styles.modifiers[state],
262265
size === ButtonSize.sm && styles.modifiers.small,
263266
size === ButtonSize.lg && styles.modifiers.displayLg,
267+
isCircle && styles.modifiers.circle,
264268
className
265269
)}
266270
disabled={isButtonElement ? isDisabled : null}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ test(`Renders without class ${styles.modifiers.progress} when isLoading = false
214214
expect(screen.getByRole('button')).not.toHaveClass(styles.modifiers.progress);
215215
});
216216

217+
test(`Renders with class ${styles.modifiers.circle} when isCircle is true`, () => {
218+
render(<Button isCircle>Circle Button</Button>);
219+
expect(screen.getByRole('button')).toHaveClass(styles.modifiers.circle);
220+
});
221+
217222
test(`Renders custom icon with class ${styles.modifiers.inProgress} when isLoading = true and icon is present`, () => {
218223
render(
219224
<Button variant="plain" isLoading aria-label="Upload" spinnerAriaValueText="Loading" icon={<div>ICON</div>} />

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
@@ -5,7 +5,7 @@ exports[`Renders basic button 1`] = `
55
<button
66
aria-label="basic button"
77
class="pf-v6-c-button pf-m-primary"
8-
data-ouia-component-id="OUIA-Generated-Button-primary-66"
8+
data-ouia-component-id="OUIA-Generated-Button-primary-67"
99
data-ouia-component-type="PF6/Button"
1010
data-ouia-safe="true"
1111
type="button"

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ Stateful buttons are ideal for displaying the state of notifications. Use `varia
138138

139139
```
140140

141+
### Circle buttons
142+
143+
Pass `isCircle` to a button to modify its styling from simply rounded corners to complete circles. Circle buttons are intended for buttons that only contain an icon and adequate accessible naming.
144+
145+
```ts file="./ButtonCircle.tsx"
146+
147+
```
148+
141149
## Animated examples
142150

143151
The following `<Button>` implementations have animations built into them. When using one of the following implementations, the `icon` property will be overridden due to the animations needing specific icons internally.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Button, Flex } from '@patternfly/react-core';
2+
import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
3+
import PlusCircleIcon from '@patternfly/react-icons/dist/esm/icons/plus-circle-icon';
4+
import CopyIcon from '@patternfly/react-icons/dist/esm/icons/copy-icon';
5+
import BellIcon from '@patternfly/react-icons/dist/esm/icons/bell-icon';
6+
import UploadIcon from '@patternfly/react-icons/dist/esm/icons/upload-icon';
7+
8+
interface LoadingPropsType {
9+
spinnerAriaValueText: string;
10+
spinnerAriaLabelledBy?: string;
11+
spinnerAriaLabel?: string;
12+
isLoading: boolean;
13+
}
14+
15+
export const ButtonCircle: React.FunctionComponent = () => {
16+
const [isUploading, setIsUploading] = useState<boolean>(false);
17+
18+
const uploadingProps = {} as LoadingPropsType;
19+
uploadingProps.spinnerAriaValueText = 'Loading circle variant example';
20+
uploadingProps.spinnerAriaLabel = 'Uploading circle variant example data';
21+
uploadingProps.isLoading = isUploading;
22+
23+
return (
24+
<Flex columnGap={{ default: 'columnGapSm' }}>
25+
<Button isCircle icon={<PlusCircleIcon />} aria-label="Add primary circle variant example" />
26+
<Button
27+
variant="secondary"
28+
isCircle
29+
icon={<PlusCircleIcon />}
30+
aria-label="Add secondary circle variant example"
31+
/>
32+
<Button variant="tertiary" isCircle icon={<PlusCircleIcon />} aria-label="Add tertiary circle variant example" />
33+
<Button variant="danger" isCircle icon={<PlusCircleIcon />} aria-label="Add danger circle variant example" />
34+
<Button variant="warning" isCircle icon={<PlusCircleIcon />} aria-label="Add warning circle variant example" />
35+
<Button variant="link" isCircle icon={<PlusCircleIcon />} aria-label="Add link circle variant example" />
36+
<Button variant="control" isCircle icon={<CopyIcon />} aria-label="Copy control circle variant example" />
37+
<Button variant="plain" isCircle icon={<TimesIcon />} aria-label="Remove plain circle variant example" />
38+
<Button variant="stateful" isCircle icon={<BellIcon />} aria-label="Stateful unread circle variant example" />
39+
<Button
40+
variant="stateful"
41+
state="read"
42+
isCircle
43+
icon={<BellIcon />}
44+
aria-label="Stateful read circle variant example"
45+
/>
46+
<Button
47+
variant="stateful"
48+
state="attention"
49+
isCircle
50+
icon={<BellIcon />}
51+
aria-label="Stateful attention circle variant example"
52+
/>
53+
<Button
54+
variant="plain"
55+
isCircle
56+
{...(!isUploading && { 'aria-label': 'Upload circle variant example' })}
57+
onClick={() => setIsUploading(!isUploading)}
58+
icon={<UploadIcon />}
59+
{...uploadingProps}
60+
/>
61+
</Flex>
62+
);
63+
};

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ export interface MenuToggleProps
4545
isFullWidth?: boolean;
4646
/** Flag indicating the toggle contains placeholder text */
4747
isPlaceholder?: boolean;
48+
/** @beta Flag indicating the toggle has circular styling. Can only be applied to plain toggles. */
49+
isCircle?: boolean;
4850
/** Flag indicating whether the toggle is a settings toggle. This will override the icon property */
4951
isSettings?: boolean;
5052
/** Elements to display before the toggle button. When included, renders the menu toggle as a split button. */
@@ -84,6 +86,7 @@ class MenuToggleBase extends Component<MenuToggleProps, MenuToggleState> {
8486
isFullWidth: false,
8587
isFullHeight: false,
8688
isPlaceholder: false,
89+
isCircle: false,
8790
size: 'default',
8891
ouiaSafe: true
8992
};
@@ -103,6 +106,7 @@ class MenuToggleBase extends Component<MenuToggleProps, MenuToggleState> {
103106
isFullHeight,
104107
isFullWidth,
105108
isPlaceholder,
109+
isCircle,
106110
isSettings,
107111
splitButtonItems,
108112
variant,
@@ -225,7 +229,7 @@ class MenuToggleBase extends Component<MenuToggleProps, MenuToggleState> {
225229

226230
return (
227231
<button
228-
className={css(commonStyles)}
232+
className={css(commonStyles, isCircle && isPlain && styles.modifiers.circle)}
229233
type="button"
230234
aria-label={ariaLabel}
231235
aria-expanded={isExpanded}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ describe('Old Snapshot tests - remove when refactoring', () => {
5555
});
5656
});
5757

58+
const toggleVariants = ['default', 'plain', 'primary', 'plainText', 'secondary', 'typeahead'];
59+
5860
test(`Renders with class ${styles.modifiers.small} when size="sm" is passed`, () => {
5961
render(<MenuToggle size="sm">Toggle</MenuToggle>);
6062
expect(screen.getByRole('button')).toHaveClass(styles.modifiers.small);
@@ -110,6 +112,24 @@ test(`Renders with class ${styles.modifiers.settings} when isSettings is passed`
110112
expect(screen.getByRole('button')).toHaveClass(styles.modifiers.settings);
111113
});
112114

115+
test(`Renders with class ${styles.modifiers.circle} when variant="plain" and isCircle is true`, () => {
116+
render(<MenuToggle isCircle variant="plain" aria-label="Toggle"></MenuToggle>);
117+
expect(screen.getByRole('button')).toHaveClass(styles.modifiers.circle);
118+
});
119+
120+
toggleVariants
121+
.filter((variant) => variant !== 'plain')
122+
.forEach((variant) => {
123+
test(`Does not with class ${styles.modifiers.circle} when variant="${variant}" and isCircle is true`, () => {
124+
render(
125+
<MenuToggle isCircle variant={variant as 'default' | 'primary' | 'plainText' | 'secondary' | 'typeahead'}>
126+
Toggle
127+
</MenuToggle>
128+
);
129+
expect(screen.getByRole('button')).not.toHaveClass(styles.modifiers.circle);
130+
});
131+
});
132+
113133
test('Does not render custom icon when icon prop and isSettings are passed', () => {
114134
render(
115135
<MenuToggle isSettings icon={<div>Custom icon</div>}>

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,15 @@ import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
2020
A toggle is collapsed until it is selected by a user.
2121

2222
```ts file="MenuToggleCollapsed.tsx"
23+
2324
```
2425

2526
### Expanded toggle
2627

2728
When a user selects a toggle, it becomes expanded and is styled with a blue underline. To flag expanded toggles, and apply expanded styling, use the `isExpanded` property .
2829

2930
```ts file ="MenuToggleExpanded.tsx"
31+
3032
```
3133

3234
### Small toggle
@@ -42,13 +44,15 @@ You can pass `size="sm"` to a MenuToggle to style it as a small toggle, such as
4244
To disable the selection and expansion of a toggle, use the `isDisabled` property.
4345

4446
```ts file="MenuToggleDisabled.tsx"
47+
4548
```
4649

4750
### With a badge
4851

4952
To display a count of selected items in a toggle, use the `badge` property. You can also pass in `variant="plainText"` for a badge only toggle.
5053

5154
```ts file="MenuToggleBadge.tsx"
55+
5256
```
5357

5458
### Settings toggle
@@ -76,13 +80,15 @@ You can also pass images into the `icon` property. The following example passes
7680
This can be used alongside a text label that provides more context for the image.
7781

7882
```ts file="MenuToggleAvatarText.tsx"
83+
7984
```
8085

8186
### Variant styles
8287

8388
Variant styling can be applied to menu toggles. In the following example, the toggle uses primary styling by passing `variant="primary"` into the `<MenuToggle>` component. Additional variant options include “default”, “plain”, “plainText”, “secondary”, and “typeahead”.
8489

8590
```ts file="MenuToggleVariantStyles.tsx"
91+
8692
```
8793

8894
### Plain toggle with icon
@@ -92,13 +98,23 @@ To apply plain styling to a menu toggle with an icon, pass in `variant="plain"`.
9298
If the toggle does not have any visible text content, use the `aria-label` property to provide an accessible name.
9399

94100
```ts file="MenuTogglePlainIcon.tsx"
101+
102+
```
103+
104+
### Plain circle toggle
105+
106+
You can also pass the `isCircle` property to a plain, icon-only toggle to adjust the styling to a complete circular shape, rather than a rounded square.
107+
108+
```ts file="MenuTogglePlainCircle.tsx"
109+
95110
```
96111

97112
### Plain toggle with text label
98113

99114
To apply plain styling to a menu toggle with a text label, pass in `variant="plainText"`. Unlike the “plain” variant, “plainText” adds a caret pointing down in the toggle.
100115

101116
```ts file="MenuTogglePlainTextLabel.tsx"
117+
102118
```
103119

104120
### Split toggle with checkbox
@@ -150,6 +166,7 @@ A full height toggle fills the height of its parent. To flag a full height toggl
150166
In the following example, the toggle fills the size of the "80px" `<div>` element that it is within.
151167

152168
```ts file="MenuToggleFullHeight.tsx"
169+
153170
```
154171

155172
### Full width toggle
@@ -159,6 +176,7 @@ A full width toggle fills the width of its parent. To flag a full width toggle,
159176
In the following example, the toggle fills the width of its parent as the window size changes.
160177

161178
```ts file="MenuToggleFullWidth.tsx"
179+
162180
```
163181

164182
### Typeahead toggle
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Fragment } from 'react';
2+
import { MenuToggle } from '@patternfly/react-core';
3+
import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
4+
5+
export const MenuTogglePlainCircle: React.FunctionComponent = () => (
6+
<Fragment>
7+
<MenuToggle isCircle icon={<EllipsisVIcon />} variant="plain" aria-label="plain circle kebab" />{' '}
8+
<MenuToggle isCircle icon={<EllipsisVIcon />} variant="plain" isExpanded aria-label="plain circle expanded kebab" />{' '}
9+
<MenuToggle isCircle icon={<EllipsisVIcon />} variant="plain" isDisabled aria-label="disabled plain circle kebab" />
10+
</Fragment>
11+
);

0 commit comments

Comments
 (0)