From a4b8dffdbc9ce8ad17691a5ba1002ad6ca0f5a69 Mon Sep 17 00:00:00 2001 From: gitdallas <5322142+gitdallas@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:08:42 -0500 Subject: [PATCH 1/2] feat(851): PageToggleButton BarsIcon to isHamburgerButton prop Signed-off-by: gitdallas <5322142+gitdallas@users.noreply.github.com> mend --- ...replace-barsIcon-with-isHamburgerButton.md | 17 ++++ ...ce-barsIcon-with-isHamburgerButton.test.ts | 74 ++++++++++++++++++ ...replace-barsIcon-with-isHamburgerButton.ts | 78 +++++++++++++++++++ ...laceBarsIconWithIsHamburgerButtonInput.tsx | 8 ++ ...aceBarsIconWithIsHamburgerButtonOutput.tsx | 5 ++ packages/pf-codemods/README.md | 29 +++++++ test/test.tsx | 3 +- 7 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.md create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonInput.tsx create mode 100644 packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonOutput.tsx diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.md new file mode 100644 index 000000000..1e47a0bac --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.md @@ -0,0 +1,17 @@ +### pageToggleButton-replace-barsIcon-with-isHamburgerButton [(#11861)](https://github.com/patternfly/patternfly-react/pull/11861) + +The `BarsIcon` child component should be replaced with the `isHamburgerButton` prop on PageToggleButton. This simplifies the API and provides a more semantic way to indicate that the toggle button should render as a hamburger menu button. It also allows for the button to use animation. + +#### Examples + +In: + +```jsx +%inputExample% +``` + +Out: + +```jsx +%outputExample% +``` \ No newline at end of file diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts new file mode 100644 index 000000000..e75e4ef8a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts @@ -0,0 +1,74 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./pageToggleButton-replace-barsIcon-with-isHamburgerButton"; + +ruleTester.run("pageToggleButton-replace-barsIcon-with-isHamburgerButton", rule, { + valid: [ + { + code: ``, + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core'; `, + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core'; `, + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core'; Some text content`, + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core'; `, + }, + ], + invalid: [ + { + code: `import { PageToggleButton } from '@patternfly/react-core'; `, + output: `import { PageToggleButton } from '@patternfly/react-core'; `, + errors: [ + { + message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core/dist/esm/components/PageToggleButton/index.js'; `, + output: `import { PageToggleButton } from '@patternfly/react-core/dist/esm/components/PageToggleButton/index.js'; `, + errors: [ + { + message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core/dist/js/components/PageToggleButton/index.js'; `, + output: `import { PageToggleButton } from '@patternfly/react-core/dist/js/components/PageToggleButton/index.js'; `, + errors: [ + { + message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core/dist/dynamic/components/PageToggleButton/index.js'; `, + output: `import { PageToggleButton } from '@patternfly/react-core/dist/dynamic/components/PageToggleButton/index.js'; `, + errors: [ + { + message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageToggleButton } from '@patternfly/react-core'; `, + output: `import { PageToggleButton } from '@patternfly/react-core'; `, + errors: [ + { + message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, + type: "JSXElement", + }, + ], + }, + ], +}); \ No newline at end of file diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts new file mode 100644 index 000000000..bc9de8f9f --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts @@ -0,0 +1,78 @@ +import { Rule } from "eslint"; +import { JSXElement } from "estree-jsx"; +import { + getFromPackage, + getChildJSXElementByName, + isReactIcon, + getAttribute, + removeElement, + makeJSXElementSelfClosing, +} from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/11861 +module.exports = { + meta: { fixable: "code" }, + create: function (context: Rule.RuleContext) { + const source = context.getSourceCode(); + const { imports } = getFromPackage(context, "@patternfly/react-core"); + + const pageToggleButtonImport = imports.find( + (specifier) => specifier.imported.name === "PageToggleButton" + ); + + return !pageToggleButtonImport + ? {} + : { + JSXElement(node: JSXElement) { + if ( + node.openingElement.name.type === "JSXIdentifier" && + pageToggleButtonImport.local.name === node.openingElement.name.name + ) { + // Check if isHamburgerButton prop already exists + const isHamburgerButtonProp = getAttribute(node, "isHamburgerButton"); + + if (isHamburgerButtonProp) { + return; // Already has the prop, skip + } + + // Check if BarsIcon is a direct child + const barsIconChild = getChildJSXElementByName(node, "BarsIcon"); + + if (!barsIconChild) { + return; // No BarsIcon child found + } + + context.report({ + node, + message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, + fix(fixer) { + // Check if we need to make the element self-closing + const otherChildren = node.children.filter(child => child !== barsIconChild); + const shouldBeSelfClosing = otherChildren.length === 0 && node.closingElement; + + // Always add the isHamburgerButton prop + const addPropFix = fixer.insertTextAfter( + node.openingElement.name, + " isHamburgerButton" + ); + + if (shouldBeSelfClosing) { + // For self-closing case: add prop and make self-closing (which removes children) + return [ + addPropFix, + ...makeJSXElementSelfClosing(node, context, fixer, true) + ]; + } else { + // For non-self-closing case: add prop and remove the specific child + return [ + addPropFix, + ...removeElement(fixer, barsIconChild) + ]; + } + }, + }); + } + }, + }; + }, +}; \ No newline at end of file diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonInput.tsx new file mode 100644 index 000000000..39b4e8801 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonInput.tsx @@ -0,0 +1,8 @@ +import { PageToggleButton } from "@patternfly/react-core"; +import { BarsIcon } from "@patternfly/react-icons"; + +export const PageToggleButtonReplaceBarsIconWithIsHamburgerButtonInput = () => ( + + + +); \ No newline at end of file diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonOutput.tsx new file mode 100644 index 000000000..263db99b3 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButtonReplaceBarsIconWithIsHamburgerButtonOutput.tsx @@ -0,0 +1,5 @@ +import { PageToggleButton } from "@patternfly/react-core"; + +export const PageToggleButtonReplaceBarsIconWithIsHamburgerButtonOutput = () => ( + +); \ No newline at end of file diff --git a/packages/pf-codemods/README.md b/packages/pf-codemods/README.md index 8f1763ae8..d78018254 100644 --- a/packages/pf-codemods/README.md +++ b/packages/pf-codemods/README.md @@ -2206,6 +2206,35 @@ export const PageSidebarRemoveThemePropInput = () => ( The markup for Page has changed. When either the `horizontalSubnav` or `breadcrumb` props are passed, a PageBody component will always wrap the contents. +### pageToggleButton-replace-barsIcon-with-isHamburgerButton [(#11861)](https://github.com/patternfly/patternfly-react/pull/11861) + +The `BarsIcon` child component should be replaced with the `isHamburgerButton` prop on PageToggleButton. This simplifies the API and provides a more semantic way to indicate that the toggle button should render as a hamburger menu button. It also allows for the button to use animation. + +#### Examples + +In: + +```jsx +import { PageToggleButton } from "@patternfly/react-core"; +import { BarsIcon } from "@patternfly/react-icons"; + +export const PageToggleButtonReplaceBarsIconWithIsHamburgerButtonInput = () => ( + + + +); +``` + +Out: + +```jsx +import { PageToggleButton } from "@patternfly/react-core"; + +export const PageToggleButtonReplaceBarsIconWithIsHamburgerButtonOutput = () => ( + +); +``` + ### pagination-warn-markup-changed [(#10662)](https://github.com/patternfly/patternfly-react/pull/10662) The markup for Pagination has changed. There is now a wrapper element rendered around the PaginationOptionsMenu toggle. This rule does not provide a fixer, but will throw a warning. diff --git a/test/test.tsx b/test/test.tsx index 01c74b462..b65dcfe73 100644 --- a/test/test.tsx +++ b/test/test.tsx @@ -9,7 +9,7 @@ import { ChartThemeVariant, } from "@patternfly/react-charts"; import { CodeEditor } from "@patternfly/react-code-editor"; -import { FrogIcon } from "@patternfly/react-icons"; +import { BarsIcon, FrogIcon } from "@patternfly/react-icons"; import { AboutModal, @@ -193,6 +193,7 @@ const themeClassName = 'pf-theme-dark'; direction={DropdownDirection.up} /> + Date: Mon, 4 Aug 2025 12:37:42 -0500 Subject: [PATCH 2/2] check that BarsIcon in pageToggleButton is from pf icons Signed-off-by: gitdallas <5322142+gitdallas@users.noreply.github.com> --- ...ce-barsIcon-with-isHamburgerButton.test.ts | 32 +++++++++++++------ ...replace-barsIcon-with-isHamburgerButton.ts | 5 +++ 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts index e75e4ef8a..92a5299a6 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts @@ -18,11 +18,23 @@ ruleTester.run("pageToggleButton-replace-barsIcon-with-isHamburgerButton", rule, { code: `import { PageToggleButton } from '@patternfly/react-core'; `, }, + // PageToggleButton not imported from PatternFly, but BarsIcon is + { + code: `import { BarsIcon } from '@patternfly/react-icons'; `, + }, + // Neither PageToggleButton nor BarsIcon imported from PatternFly + { + code: `import { BarsIcon } from 'some-other-package'; `, + }, + // PageToggleButton imported from PatternFly, but BarsIcon not imported from PatternFly + { + code: `import { PageToggleButton } from '@patternfly/react-core'; import { BarsIcon } from 'some-other-package'; `, + }, ], invalid: [ { - code: `import { PageToggleButton } from '@patternfly/react-core'; `, - output: `import { PageToggleButton } from '@patternfly/react-core'; `, + code: `import { PageToggleButton } from '@patternfly/react-core'; import { BarsIcon } from '@patternfly/react-icons'; `, + output: `import { PageToggleButton } from '@patternfly/react-core'; import { BarsIcon } from '@patternfly/react-icons'; `, errors: [ { message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, @@ -31,8 +43,8 @@ ruleTester.run("pageToggleButton-replace-barsIcon-with-isHamburgerButton", rule, ], }, { - code: `import { PageToggleButton } from '@patternfly/react-core/dist/esm/components/PageToggleButton/index.js'; `, - output: `import { PageToggleButton } from '@patternfly/react-core/dist/esm/components/PageToggleButton/index.js'; `, + code: `import { PageToggleButton } from '@patternfly/react-core/dist/esm/components/PageToggleButton/index.js'; import { BarsIcon } from '@patternfly/react-icons'; `, + output: `import { PageToggleButton } from '@patternfly/react-core/dist/esm/components/PageToggleButton/index.js'; import { BarsIcon } from '@patternfly/react-icons'; `, errors: [ { message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, @@ -41,8 +53,8 @@ ruleTester.run("pageToggleButton-replace-barsIcon-with-isHamburgerButton", rule, ], }, { - code: `import { PageToggleButton } from '@patternfly/react-core/dist/js/components/PageToggleButton/index.js'; `, - output: `import { PageToggleButton } from '@patternfly/react-core/dist/js/components/PageToggleButton/index.js'; `, + code: `import { PageToggleButton } from '@patternfly/react-core/dist/js/components/PageToggleButton/index.js'; import { BarsIcon } from '@patternfly/react-icons'; `, + output: `import { PageToggleButton } from '@patternfly/react-core/dist/js/components/PageToggleButton/index.js'; import { BarsIcon } from '@patternfly/react-icons'; `, errors: [ { message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, @@ -51,8 +63,8 @@ ruleTester.run("pageToggleButton-replace-barsIcon-with-isHamburgerButton", rule, ], }, { - code: `import { PageToggleButton } from '@patternfly/react-core/dist/dynamic/components/PageToggleButton/index.js'; `, - output: `import { PageToggleButton } from '@patternfly/react-core/dist/dynamic/components/PageToggleButton/index.js'; `, + code: `import { PageToggleButton } from '@patternfly/react-core/dist/dynamic/components/PageToggleButton/index.js'; import { BarsIcon } from '@patternfly/react-icons'; `, + output: `import { PageToggleButton } from '@patternfly/react-core/dist/dynamic/components/PageToggleButton/index.js'; import { BarsIcon } from '@patternfly/react-icons'; `, errors: [ { message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, @@ -61,8 +73,8 @@ ruleTester.run("pageToggleButton-replace-barsIcon-with-isHamburgerButton", rule, ], }, { - code: `import { PageToggleButton } from '@patternfly/react-core'; `, - output: `import { PageToggleButton } from '@patternfly/react-core'; `, + code: `import { PageToggleButton } from '@patternfly/react-core'; import { BarsIcon } from '@patternfly/react-icons'; `, + output: `import { PageToggleButton } from '@patternfly/react-core'; import { BarsIcon } from '@patternfly/react-icons'; `, errors: [ { message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`, diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts index bc9de8f9f..5a9c85de5 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts @@ -42,6 +42,11 @@ module.exports = { return; // No BarsIcon child found } + // Check if BarsIcon is imported from PatternFly + if (!isReactIcon(context, barsIconChild)) { + return; // BarsIcon is not from PatternFly, skip + } + context.report({ node, message: `The BarsIcon child component should be replaced with the \`isHamburgerButton\` prop on PageToggleButton.`,