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..92a5299a6 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.test.ts @@ -0,0 +1,86 @@ +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'; `, + }, + // 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'; 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.`, + type: "JSXElement", + }, + ], + }, + { + 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.`, + type: "JSXElement", + }, + ], + }, + { + 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.`, + type: "JSXElement", + }, + ], + }, + { + 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.`, + type: "JSXElement", + }, + ], + }, + { + 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.`, + 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..5a9c85de5 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageToggleButtonReplaceBarsIconWithIsHamburgerButton/pageToggleButton-replace-barsIcon-with-isHamburgerButton.ts @@ -0,0 +1,83 @@ +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 + } + + // 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.`, + 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} /> +