Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -1 +1,7 @@
httpRetry: 5

httpTimeout: 600000

nodeLinker: node-modules

npmRegistryServer: "https://registry.npmjs.org"
40 changes: 40 additions & 0 deletions cleanup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const fs = require('fs');
const path = require('path');

function deleteFolderRecursive(pathToDelete) {
if (fs.existsSync(pathToDelete)) {
fs.readdirSync(pathToDelete).forEach((file) => {
const curPath = path.join(pathToDelete, file);
if (fs.lstatSync(curPath).isDirectory()) {
deleteFolderRecursive(curPath);
} else {
try {
fs.unlinkSync(curPath);
} catch (err) {
// console.error(`Error deleting file ${curPath}:`, err);
}
}
});
try {
fs.rmdirSync(pathToDelete);
} catch (err) {
// console.error(`Error deleting directory ${pathToDelete}:`, err);
}
}
}

['node_modules', '.yarn'].forEach((dir) => {
// console.log(`Removing ${dir}...`);
deleteFolderRecursive(dir);
});

['.pnp.cjs', '.pnp.loader.mjs'].forEach((file) => {
if (fs.existsSync(file)) {
try {
fs.unlinkSync(file);
// console.log(`Removed ${file}`);
} catch (err) {
// console.error(`Error removing ${file}:`, err);
}
}
});
48 changes: 45 additions & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
import { fixupPluginRules } from '@eslint/compat';
import js from '@eslint/js';
import patternflyReact from 'eslint-plugin-patternfly-react';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
// Try to import the workspace plugin by package name first; if Node's ESM resolver
// can't find it in this environment (Yarn v4 workspace on Windows) fall back to
// the local package build. Using top-level await is valid in an ESM config.
let patternflyReact;
try {
const mod = await import('eslint-plugin-patternfly-react');
patternflyReact = mod && mod.default ? mod.default : mod;
} catch (e) {
// Fallback to the local lib build path. This keeps CI behavior unchanged while
// allowing local linting during development.
const local = await import('./packages/eslint-plugin-patternfly-react/lib/index.js');
patternflyReact = local && local.default ? local.default : local;
}
import prettier from 'eslint-plugin-prettier';
import reactCompiler from 'eslint-plugin-react-compiler';
import reactHooks from 'eslint-plugin-react-hooks';
import react from 'eslint-plugin-react';
import testingLibrary from 'eslint-plugin-testing-library';
import globals from 'globals';
import tseslint from 'typescript-eslint';

// The `recommended` export from some plugins (notably eslint-plugin-prettier)
// may include a legacy `extends` key which is invalid in flat config. To
// remain compatible we extract only the parts we can safely include here.
const prettierRecommended = (() => {
try {
const cfg = prettier && prettier.configs && prettier.configs.recommended;
if (cfg && typeof cfg === 'object') {
return {
rules: cfg.rules || {},
settings: cfg.settings || {},
languageOptions: cfg.languageOptions || {}
};
}
} catch (e) {
// swallow — we'll just not include it
}
return null;
})();

export default [
{
ignores: [
Expand All @@ -27,10 +58,20 @@ export default [
...tseslint.configs.recommended,
react.configs.flat.recommended,
react.configs.flat['jsx-runtime'],
eslintPluginPrettierRecommended,
// include only the safe parts of Prettier's recommended config (avoid legacy 'extends')
...(prettierRecommended
? [
{
languageOptions: prettierRecommended.languageOptions,
settings: prettierRecommended.settings,
rules: prettierRecommended.rules
}
]
: []),
{
plugins: {
'patternfly-react': fixupPluginRules(patternflyReact),
prettier: fixupPluginRules(prettier),
'react-hooks': fixupPluginRules(reactHooks),
'react-compiler': reactCompiler
},
Expand Down Expand Up @@ -68,6 +109,7 @@ export default [
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/no-misused-new': 'error',
'@typescript-eslint/no-namespace': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-patternfly-react": "workspace:^",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-react-compiler": "19.0.0-beta-ebf51a3-20250411",
Expand Down
18 changes: 18 additions & 0 deletions packages/react-core/src/components/Toolbar/ToolbarGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ export interface ToolbarGroupProps extends Omit<React.HTMLProps<HTMLDivElement>,
xl?: 'wrap' | 'nowrap';
'2xl'?: 'wrap' | 'nowrap';
};
/** Flex grow modifier at various breakpoints */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Flex grow modifier at various breakpoints */
/** Indicates whether a flex grow modifier of 1 is applied at various breakpoints */

Small nit to the description, just to make it clear that it's currently a hardcoded flex grow modifier rather than something more customizable.

flexGrow?: {
default?: 'flexGrow';
sm?: 'flexGrow';
md?: 'flexGrow';
lg?: 'flexGrow';
xl?: 'flexGrow';
'2xl'?: 'flexGrow';
};
/** Content to be rendered inside the data toolbar group */
children?: React.ReactNode;
/** Flag that modifies the toolbar group to hide overflow and respond to available space. Used for horizontal navigation. */
Expand All @@ -185,6 +194,7 @@ class ToolbarGroupWithRef extends Component<ToolbarGroupProps> {
columnGap,
rowGap,
rowWrap,
flexGrow,
className,
variant,
children,
Expand Down Expand Up @@ -221,6 +231,14 @@ class ToolbarGroupWithRef extends Component<ToolbarGroupProps> {
alignSelf === 'center' && styles.modifiers.alignSelfCenter,
alignSelf === 'baseline' && styles.modifiers.alignSelfBaseline,
isOverflowContainer && styles.modifiers.overflowContainer,
flexGrow &&
Object.entries(flexGrow).reduce(
(acc, [bp]) => ({
...acc,
[`pf-m-flex-grow${bp !== 'default' ? `-on-${bp}` : ''}`]: true
}),
{}
),
Comment on lines +234 to +241
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than this, we can just use our formateBreakpointMods util function

className
)}
{...props}
Expand Down
51 changes: 44 additions & 7 deletions packages/react-core/src/components/Toolbar/ToolbarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ export interface ToolbarItemProps extends React.HTMLProps<HTMLDivElement> {
className?: string;
/** A type modifier which modifies spacing specifically depending on the type of item */
variant?: ToolbarItemVariant | 'pagination' | 'label' | 'label-group' | 'separator' | 'expand-all';
/** Width modifier at various breakpoints */
widths?: {
default?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
sm?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
md?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
lg?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
xl?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
'2xl'?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl';
};
/** Flex grow modifier at various breakpoints */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar nit here for prop description as above

flexGrow?: {
default?: 'flexGrow';
sm?: 'flexGrow';
md?: 'flexGrow';
lg?: 'flexGrow';
xl?: 'flexGrow';
'2xl'?: 'flexGrow';
};
/** Visibility at various breakpoints. */
visibility?: {
default?: 'hidden' | 'visible';
Expand Down Expand Up @@ -185,6 +203,8 @@ export const ToolbarItem: React.FunctionComponent<ToolbarItemProps> = ({
children,
isAllExpanded,
isOverflowContainer,
widths,
flexGrow,
role,
...props
}: ToolbarItemProps) => {
Expand All @@ -210,23 +230,40 @@ export const ToolbarItem: React.FunctionComponent<ToolbarItemProps> = ({
variant === ToolbarItemVariant['label-group'] && styles.modifiers.labelGroup,
isAllExpanded && styles.modifiers.expanded,
isOverflowContainer && styles.modifiers.overflowContainer,
formatBreakpointMods(visibility, styles, '', getBreakpoint(width)),
formatBreakpointMods(align, styles, '', getBreakpoint(width)),
formatBreakpointMods(gap, styles, '', getBreakpoint(width)),
formatBreakpointMods(columnGap, styles, '', getBreakpoint(width)),
formatBreakpointMods(rowGap, styles, '', getBreakpoint(width)),
formatBreakpointMods(rowWrap, styles, '', getBreakpoint(width)),
alignItems === 'start' && styles.modifiers.alignItemsStart,
alignItems === 'center' && styles.modifiers.alignItemsCenter,
alignItems === 'baseline' && styles.modifiers.alignItemsBaseline,
alignSelf === 'start' && styles.modifiers.alignSelfStart,
alignSelf === 'center' && styles.modifiers.alignSelfCenter,
alignSelf === 'baseline' && styles.modifiers.alignSelfBaseline,
className
formatBreakpointMods(visibility, styles, '', getBreakpoint(width)),
formatBreakpointMods(align, styles, '', getBreakpoint(width)),
formatBreakpointMods(gap, styles, '', getBreakpoint(width)),
formatBreakpointMods(columnGap, styles, '', getBreakpoint(width)),
formatBreakpointMods(rowGap, styles, '', getBreakpoint(width)),
formatBreakpointMods(rowWrap, styles, '', getBreakpoint(width)),
className,
widths &&
Object.entries(widths).reduce(
(acc, [bp, size]) => ({
...acc,
[`pf-m-w-${size}${bp !== 'default' ? `-on-${bp}` : ''}`]: true
}),
{}
),
flexGrow &&
Object.entries(flexGrow).reduce(
(acc, [bp]) => ({
...acc,
[`pf-m-flex-grow${bp !== 'default' ? `-on-${bp}` : ''}`]: true
}),
{}
)
Comment on lines +246 to +261
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to above, we should be able to just use our formatBreakpointMods util function instead

Copy link
Contributor

@thatblindgeye thatblindgeye Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just an addendum to this, we should use the formatBreakpointMods function for the flexGrow here, but for handling applying the widths prop we should basically just add back in what was removed in this React PR https://github.com/patternfly/patternfly-react/pull/10022/files#diff-7beeea05e52252eb360f045d661fab89fe634b00becfd8a3dbaa48338c6032c0 (we want to apply a styles object rather than a class)

)}
{...(variant === 'label' && { 'aria-hidden': true })}
id={id}
role={role}
data-testid="toolbaritem"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about hardcoding a testid in, since there could be many items on a page, and a testid should be able to be passed with the spread props line below this.

{...props}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,22 @@ describe('ToolbarGroup', () => {
expect(screen.getByTestId('toolbargroup')).toHaveClass('pf-m-overflow-container');
});

describe('ToolbarGroup flexGrow', () => {
const bps = ['default', 'sm', 'md', 'lg', 'xl', '2xl'];

describe.each(bps)('flexGrow at various breakpoints', (bp) => {
it(`should render with pf-m-flex-grow when flexGrow is set at ${bp}`, () => {
Comment on lines +14 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We typically use the describe block a bit more rarely in our codebase, in this case I'm not sure we should use it. Instead what we could do is put the breakpoints variable as a global var or within the first describe('ToolbarGroup' ... block, then:

  • Move the flexGrow and widths tests inside that block
  • remove the nested describe blocks being used here
  • instead, just run a forEach over the breakpoints array and call the tests in this forEach block

We do something similar in some of our unit test files, such as our Button tests:

Object.values(ButtonVariant).forEach((variant) => {
if (variant !== 'primary') {
test(`Does not render with class ${styles.modifiers[variant]} by default`, () => {
render(<Button>Not {variant} Button</Button>);
expect(screen.getByRole('button')).not.toHaveClass(styles.modifiers[variant]);
});
}
test(`Renders with class ${styles.modifiers[variant]} when variant=${variant}`, () => {
render(<Button variant={variant}>{variant} Button</Button>);
expect(screen.getByRole('button')).toHaveClass(styles.modifiers[variant]);
});
});

render(
<ToolbarGroup data-testid="toolbargroup" flexGrow={{ [bp]: 'flexGrow' }}>
Test
</ToolbarGroup>
);
const bpFlexGrowClass = bp === 'default' ? 'pf-m-flex-grow' : `pf-m-flex-grow-on-${bp}`;
expect(screen.getByTestId('toolbargroup')).toHaveClass(bpFlexGrowClass);
});
});
});

describe('ToobarGroup rowWrap', () => {
const bps = ['default', 'sm', 'md', 'lg', 'xl', '2xl'];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,37 @@ describe('ToolbarItem', () => {
});
});
});

describe('ToolbarItem widths', () => {
const bps = ['default', 'sm', 'md', 'lg', 'xl', '2xl'];
const sizes = ['sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl'];

describe.each(bps)('widths at various breakpoints', (bp) => {
it.each(sizes)(`should render with pf-m-w-%s when widths is set to %s at ${bp}`, (size) => {
render(
<ToolbarItem data-testid="toolbaritem" widths={{ [bp]: size }}>
Test
</ToolbarItem>
);
const bpWidthClass = bp === 'default' ? `pf-m-w-${size}` : `pf-m-w-${size}-on-${bp}`;
expect(screen.getByTestId('toolbaritem')).toHaveClass(bpWidthClass);
});
});
});

describe('ToolbarItem flexGrow', () => {
const bps = ['default', 'sm', 'md', 'lg', 'xl', '2xl'];

describe.each(bps)('flexGrow at various breakpoints', (bp) => {
it(`should render with pf-m-flex-grow when flexGrow is set at ${bp}`, () => {
render(
<ToolbarItem data-testid="toolbaritem" flexGrow={{ [bp]: 'flexGrow' }}>
Test
</ToolbarItem>
);
const bpFlexGrowClass = bp === 'default' ? 'pf-m-flex-grow' : `pf-m-flex-grow-on-${bp}`;
expect(screen.getByTestId('toolbaritem')).toHaveClass(bpFlexGrowClass);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ exports[`Toolbar should render inset 1`] = `
>
<div
class="pf-v6-c-toolbar__item"
data-testid="toolbaritem"
>
Test
</div>
<div
class="pf-v6-c-toolbar__item"
data-testid="toolbaritem"
>
Test 2
</div>
Expand All @@ -30,6 +32,7 @@ exports[`Toolbar should render inset 1`] = `
/>
<div
class="pf-v6-c-toolbar__item"
data-testid="toolbaritem"
>
Test 3
</div>
Expand Down Expand Up @@ -87,6 +90,7 @@ exports[`Toolbar should render with custom label content 1`] = `
>
<div
class="pf-v6-c-toolbar__item"
data-testid="toolbaritem"
>
test content
</div>
Expand All @@ -104,6 +108,7 @@ exports[`Toolbar should render with custom label content 1`] = `
>
<div
class="pf-v6-c-toolbar__item pf-m-label-group pf-m-label-group"
data-testid="toolbaritem"
>
<div
class="pf-v6-c-label-group pf-m-category"
Expand Down Expand Up @@ -259,11 +264,13 @@ exports[`Toolbar should render with custom label content 1`] = `
>
<div
class="pf-v6-c-toolbar__item"
data-testid="toolbaritem"
>
2 filters applied
</div>
<div
class="pf-v6-c-toolbar__item"
data-testid="toolbaritem"
>
<button
class="pf-v6-c-button pf-m-link pf-m-inline"
Expand All @@ -281,6 +288,7 @@ exports[`Toolbar should render with custom label content 1`] = `
</div>
<div
class="pf-v6-c-toolbar__item"
data-testid="toolbaritem"
>
<button
class="pf-v6-c-button pf-m-link pf-m-inline"
Expand Down
Loading
Loading