Skip to content

Commit f9fe659

Browse files
feat(Toolbar): add flexGrow and widths props to ToolbarItem and ToolbarGroup
- Add flexGrow prop to ToolbarItem with breakpoint support (default, sm, md, lg, xl, 2xl) - Add flexGrow prop to ToolbarGroup with breakpoint support - Add widths prop to ToolbarItem for setting custom widths at various breakpoints - Add comprehensive tests for new props - Add example documentation showing flexGrow and widths usage Fixes #11910 Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2acbb5f commit f9fe659

File tree

6 files changed

+210
-1
lines changed

6 files changed

+210
-1
lines changed

packages/react-core/src/components/Toolbar/ToolbarGroup.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,15 @@ export interface ToolbarGroupProps extends Omit<React.HTMLProps<HTMLDivElement>,
166166
xl?: 'wrap' | 'nowrap';
167167
'2xl'?: 'wrap' | 'nowrap';
168168
};
169+
/** Sets flex-grow at various breakpoints to allow the group to consume available main-axis space */
170+
flexGrow?: {
171+
default?: 'flexGrow';
172+
sm?: 'flexGrow';
173+
md?: 'flexGrow';
174+
lg?: 'flexGrow';
175+
xl?: 'flexGrow';
176+
'2xl'?: 'flexGrow';
177+
};
169178
/** Content to be rendered inside the data toolbar group */
170179
children?: React.ReactNode;
171180
/** Flag that modifies the toolbar group to hide overflow and respond to available space. Used for horizontal navigation. */
@@ -185,6 +194,7 @@ class ToolbarGroupWithRef extends Component<ToolbarGroupProps> {
185194
columnGap,
186195
rowGap,
187196
rowWrap,
197+
flexGrow,
188198
className,
189199
variant,
190200
children,
@@ -214,6 +224,7 @@ class ToolbarGroupWithRef extends Component<ToolbarGroupProps> {
214224
formatBreakpointMods(columnGap, styles, '', getBreakpoint(width)),
215225
formatBreakpointMods(rowGap, styles, '', getBreakpoint(width)),
216226
formatBreakpointMods(rowWrap, styles, '', getBreakpoint(width)),
227+
formatBreakpointMods(flexGrow, styles, '', getBreakpoint(width)),
217228
alignItems === 'start' && styles.modifiers.alignItemsStart,
218229
alignItems === 'center' && styles.modifiers.alignItemsCenter,
219230
alignItems === 'baseline' && styles.modifiers.alignItemsBaseline,

packages/react-core/src/components/Toolbar/ToolbarItem.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import styles from '@patternfly/react-styles/css/components/Toolbar/toolbar';
22
import { css } from '@patternfly/react-styles';
3-
import { formatBreakpointMods, toCamel } from '../../helpers/util';
3+
import { formatBreakpointMods, setBreakpointCssVars, toCamel } from '../../helpers/util';
44
import { Divider } from '../Divider';
55
import { PageContext } from '../Page/PageContext';
6+
import cssToolbarItemWidth from '@patternfly/react-tokens/dist/esm/c_toolbar__item_Width';
67

78
export enum ToolbarItemVariant {
89
separator = 'separator',
@@ -160,6 +161,24 @@ export interface ToolbarItemProps extends React.HTMLProps<HTMLDivElement> {
160161
xl?: 'wrap' | 'nowrap';
161162
'2xl'?: 'wrap' | 'nowrap';
162163
};
164+
/** Sets flex-grow at various breakpoints to allow the item to consume available main-axis space */
165+
flexGrow?: {
166+
default?: 'flexGrow';
167+
sm?: 'flexGrow';
168+
md?: 'flexGrow';
169+
lg?: 'flexGrow';
170+
xl?: 'flexGrow';
171+
'2xl'?: 'flexGrow';
172+
};
173+
/** Width at various breakpoints. */
174+
widths?: {
175+
default?: string;
176+
sm?: string;
177+
md?: string;
178+
lg?: string;
179+
xl?: string;
180+
'2xl'?: string;
181+
};
163182
/** id for this data toolbar item */
164183
id?: string;
165184
/** Flag indicating if the expand-all variant is expanded or not */
@@ -178,6 +197,8 @@ export const ToolbarItem: React.FunctionComponent<ToolbarItemProps> = ({
178197
columnGap,
179198
rowGap,
180199
rowWrap,
200+
flexGrow,
201+
widths,
181202
align,
182203
alignSelf,
183204
alignItems,
@@ -186,6 +207,7 @@ export const ToolbarItem: React.FunctionComponent<ToolbarItemProps> = ({
186207
isAllExpanded,
187208
isOverflowContainer,
188209
role,
210+
style,
189211
...props
190212
}: ToolbarItemProps) => {
191213
if (variant === ToolbarItemVariant.separator) {
@@ -200,6 +222,8 @@ export const ToolbarItem: React.FunctionComponent<ToolbarItemProps> = ({
200222
);
201223
}
202224

225+
const responsiveWidths = widths ? setBreakpointCssVars(widths, cssToolbarItemWidth.name) : {};
226+
203227
return (
204228
<PageContext.Consumer>
205229
{({ width, getBreakpoint }) => (
@@ -216,6 +240,7 @@ export const ToolbarItem: React.FunctionComponent<ToolbarItemProps> = ({
216240
formatBreakpointMods(columnGap, styles, '', getBreakpoint(width)),
217241
formatBreakpointMods(rowGap, styles, '', getBreakpoint(width)),
218242
formatBreakpointMods(rowWrap, styles, '', getBreakpoint(width)),
243+
formatBreakpointMods(flexGrow, styles, '', getBreakpoint(width)),
219244
alignItems === 'start' && styles.modifiers.alignItemsStart,
220245
alignItems === 'center' && styles.modifiers.alignItemsCenter,
221246
alignItems === 'baseline' && styles.modifiers.alignItemsBaseline,
@@ -227,6 +252,7 @@ export const ToolbarItem: React.FunctionComponent<ToolbarItemProps> = ({
227252
{...(variant === 'label' && { 'aria-hidden': true })}
228253
id={id}
229254
role={role}
255+
style={{ ...style, ...responsiveWidths }}
230256
{...props}
231257
>
232258
{children}

packages/react-core/src/components/Toolbar/__tests__/ToolbarGroup.test.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,20 @@ describe('ToolbarGroup', () => {
3737
});
3838
});
3939
});
40+
41+
describe('ToolbarGroup flexGrow', () => {
42+
const bps = ['default', 'sm', 'md', 'lg', 'xl', '2xl'];
43+
44+
describe.each(bps)(`flexGrow at various breakpoints`, (bp) => {
45+
it(`should render with pf-m-flex-grow when flexGrow is set at ${bp}`, () => {
46+
render(
47+
<ToolbarGroup data-testid="toolbargroup" flexGrow={{ [bp]: 'flexGrow' }}>
48+
Test
49+
</ToolbarGroup>
50+
);
51+
const bpFlexGrowClass = bp === 'default' ? 'pf-m-flex-grow' : `pf-m-flex-grow-on-${bp}`;
52+
expect(screen.getByTestId('toolbargroup')).toHaveClass(bpFlexGrowClass);
53+
});
54+
});
55+
});
4056
});

packages/react-core/src/components/Toolbar/__tests__/ToolbarItem.test.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,46 @@ describe('ToolbarItem', () => {
3737
});
3838
});
3939
});
40+
41+
describe('ToolbarItem flexGrow', () => {
42+
const bps = ['default', 'sm', 'md', 'lg', 'xl', '2xl'];
43+
44+
describe.each(bps)(`flexGrow at various breakpoints`, (bp) => {
45+
it(`should render with pf-m-flex-grow when flexGrow is set at ${bp}`, () => {
46+
render(
47+
<ToolbarItem data-testid="toolbaritem" flexGrow={{ [bp]: 'flexGrow' }}>
48+
Test
49+
</ToolbarItem>
50+
);
51+
const bpFlexGrowClass = bp === 'default' ? 'pf-m-flex-grow' : `pf-m-flex-grow-on-${bp}`;
52+
expect(screen.getByTestId('toolbaritem')).toHaveClass(bpFlexGrowClass);
53+
});
54+
});
55+
});
56+
57+
describe('ToolbarItem widths', () => {
58+
it('should apply width CSS variable when widths prop is set', () => {
59+
render(
60+
<ToolbarItem data-testid="toolbaritem" widths={{ default: '200px' }}>
61+
Test
62+
</ToolbarItem>
63+
);
64+
const item = screen.getByTestId('toolbaritem');
65+
expect(item).toHaveStyle('--pf-v6-c-toolbar__item--Width: 200px');
66+
});
67+
68+
it('should apply responsive width CSS variables when widths prop has breakpoint values', () => {
69+
render(
70+
<ToolbarItem data-testid="toolbaritem" widths={{ default: '100px', md: '200px', xl: '300px' }}>
71+
Test
72+
</ToolbarItem>
73+
);
74+
const item = screen.getByTestId('toolbaritem');
75+
expect(item).toHaveStyle({
76+
'--pf-v6-c-toolbar__item--Width': '100px',
77+
'--pf-v6-c-toolbar__item--Width-on-md': '200px',
78+
'--pf-v6-c-toolbar__item--Width-on-xl': '300px'
79+
});
80+
});
81+
});
4082
});

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,3 +136,11 @@ The toolbar content section will wrap by default, but you can set the `rowRap` p
136136
```ts file="./ToolbarItemSpacers.tsx"
137137

138138
```
139+
140+
### FlexGrow and widths
141+
142+
You can use the `flexGrow` prop to make toolbar items or groups expand to fill available space. The `widths` prop allows you to set custom widths at various breakpoints.
143+
144+
```ts file="./ToolbarFlexGrowAndWidths.tsx"
145+
146+
```
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { Fragment } from 'react';
2+
import { Toolbar, ToolbarItem, ToolbarGroup, ToolbarContent } from '@patternfly/react-core';
3+
import { Button } from '@patternfly/react-core';
4+
import SearchInput from '@patternfly/react-core/dist/esm/components/SearchInput/SearchInput';
5+
6+
export const ToolbarFlexGrowAndWidths: React.FunctionComponent = () => {
7+
const flexGrowItems = (
8+
<Fragment>
9+
<ToolbarItem>
10+
<Button variant="secondary">Item 1</Button>
11+
</ToolbarItem>
12+
<ToolbarItem flexGrow={{ default: 'flexGrow' }}>
13+
<SearchInput aria-label="Flex grow search input" />
14+
</ToolbarItem>
15+
<ToolbarItem>
16+
<Button variant="secondary">Item 2</Button>
17+
</ToolbarItem>
18+
</Fragment>
19+
);
20+
21+
const flexGrowGroupItems = (
22+
<Fragment>
23+
<ToolbarGroup>
24+
<ToolbarItem>
25+
<Button variant="secondary">Item 1</Button>
26+
</ToolbarItem>
27+
<ToolbarItem>
28+
<Button variant="secondary">Item 2</Button>
29+
</ToolbarItem>
30+
</ToolbarGroup>
31+
<ToolbarGroup flexGrow={{ default: 'flexGrow' }}>
32+
<ToolbarItem>
33+
<Button variant="secondary">Flex grow group item 1</Button>
34+
</ToolbarItem>
35+
<ToolbarItem>
36+
<Button variant="secondary">Flex grow group item 2</Button>
37+
</ToolbarItem>
38+
</ToolbarGroup>
39+
<ToolbarGroup>
40+
<ToolbarItem>
41+
<Button variant="secondary">Item 3</Button>
42+
</ToolbarItem>
43+
</ToolbarGroup>
44+
</Fragment>
45+
);
46+
47+
const widthItems = (
48+
<Fragment>
49+
<ToolbarItem widths={{ default: '200px' }}>
50+
<SearchInput aria-label="Search input with fixed width" />
51+
</ToolbarItem>
52+
<ToolbarItem>
53+
<Button variant="secondary">Regular item</Button>
54+
</ToolbarItem>
55+
<ToolbarItem widths={{ default: '300px' }}>
56+
<SearchInput aria-label="Search input with wider fixed width" />
57+
</ToolbarItem>
58+
</Fragment>
59+
);
60+
61+
const responsiveWidthItems = (
62+
<Fragment>
63+
<ToolbarItem widths={{ default: '100px', md: '200px', xl: '300px' }}>
64+
<SearchInput aria-label="Search input with responsive width" />
65+
</ToolbarItem>
66+
<ToolbarItem>
67+
<Button variant="secondary">Regular item</Button>
68+
</ToolbarItem>
69+
</Fragment>
70+
);
71+
72+
return (
73+
<>
74+
Using flexGrow on ToolbarItem
75+
<br />
76+
<br />
77+
<Toolbar id="toolbar-flex-grow-item">
78+
<ToolbarContent>{flexGrowItems}</ToolbarContent>
79+
</Toolbar>
80+
<br />
81+
<br />
82+
Using flexGrow on ToolbarGroup
83+
<br />
84+
<br />
85+
<Toolbar id="toolbar-flex-grow-group">
86+
<ToolbarContent>{flexGrowGroupItems}</ToolbarContent>
87+
</Toolbar>
88+
<br />
89+
<br />
90+
Using widths on ToolbarItem
91+
<br />
92+
<br />
93+
<Toolbar id="toolbar-widths">
94+
<ToolbarContent>{widthItems}</ToolbarContent>
95+
</Toolbar>
96+
<br />
97+
<br />
98+
Using responsive widths on ToolbarItem
99+
<br />
100+
<br />
101+
<Toolbar id="toolbar-responsive-widths">
102+
<ToolbarContent>{responsiveWidthItems}</ToolbarContent>
103+
</Toolbar>
104+
</>
105+
);
106+
};

0 commit comments

Comments
 (0)