Skip to content

Commit

Permalink
feat(EmptyState): support icons passed directly to EmptyStateHeader (#…
Browse files Browse the repository at this point in the history
…612)

* feat(helpers): add getDefaultImports helper

* feat(pfPackageMatches helper): handle imports from react-icons package

* feat(EmptyStateHeader): handle icons passed without EmptyStateIcon wrapper

* refactor(EmptyState icon)

* fix(EmptyState icon): correct undefined check
  • Loading branch information
adamviktora authored Mar 27, 2024
1 parent 8ab72b1 commit b5d2340
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Statement,
Directive,
ExportNamedDeclaration,
ImportDefaultSpecifier,
ImportSpecifier,
ExportSpecifier,
} from "estree-jsx";
Expand Down Expand Up @@ -85,3 +86,23 @@ export function getFromPackage(

return { imports: specifiedImports, exports: specifiedExports };
}

export function getDefaultImportsFromPackage(
context: Rule.RuleContext,
packageName: string
): ImportDefaultSpecifier[] {
const astBody = context.getSourceCode().ast.body;

const importDeclarations = astBody.filter(
(node) => node?.type === "ImportDeclaration"
) as ImportDeclaration[];

const importDeclarationsFromPackage = filterByPackageName(
importDeclarations,
packageName
) as ImportDeclaration[];

return importDeclarationsFromPackage
.filter((imp) => imp.specifiers[0]?.type === "ImportDefaultSpecifier")
.map((imp) => imp.specifiers[0]) as ImportDefaultSpecifier[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export function pfPackageMatches(
parts[1] +
"(/dist/(esm|js))?" +
(parts[2] ? "/" + parts[2] : "") +
"(/(components|helpers)/.*)?$"
`(/(components|helpers${
parts[1] === "react-icons" ? "|icons" : ""
})/.*)?$`
);
return regex.test(nodeSrc);
}
Original file line number Diff line number Diff line change
Expand Up @@ -378,5 +378,79 @@ ruleTester.run("emptyStateHeader-move-into-emptyState", rule, {
},
],
},
{
// with icon prop value not being wrapped in EmptyStateIcon (default import)
code: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";
import CubesIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon';
export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState>
<EmptyStateHeader
icon={<CubesIcon />}
titleText="Empty state"
/>
</EmptyState>
);
`,
output: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";
import CubesIcon from '@patternfly/react-icons/dist/esm/icons/cubes-icon';
export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState icon={CubesIcon} titleText="Empty state">
</EmptyState>
);
`,
errors: [
{
message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
type: "JSXElement",
},
],
},
{
// with icon prop value not being wrapped in EmptyStateIcon (classic import)
code: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";
import { CubesIcon } from '@patternfly/react-icons';
export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState>
<EmptyStateHeader
icon={<CubesIcon />}
titleText="Empty state"
/>
</EmptyState>
);
`,
output: `import {
EmptyState,
EmptyStateHeader,
} from "@patternfly/react-core";
import { CubesIcon } from '@patternfly/react-icons';
export const EmptyStateHeaderMoveIntoEmptyStateInput = () => (
<EmptyState icon={CubesIcon} titleText="Empty state">
</EmptyState>
);
`,
errors: [
{
message: `EmptyStateHeader has been moved inside of the EmptyState component and is now only customizable using props, and the EmptyStateIcon component now wraps content passed to the icon prop automatically. Additionally, the titleText prop is now required on EmptyState.`,
type: "JSXElement",
},
],
},
],
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
getAttributeText,
getAttributeValueText,
getChildElementByName,
getDefaultImportsFromPackage,
getExpression,
getFromPackage,
includesImport,
Expand Down Expand Up @@ -98,8 +99,52 @@ module.exports = {

const iconPropValue = getExpression(headerIconAttribute?.value);

const emptyStateIconComponent =
iconPropValue?.type === "JSXElement" ? iconPropValue : undefined;
const iconElementIdentifier =
iconPropValue?.type === "JSXElement" &&
iconPropValue.openingElement.name.type === "JSXIdentifier"
? iconPropValue.openingElement.name
: undefined;

const iconPropIsEmptyStateIconComponent = () => {
const emptyStateIconImport = imports.find(
(specifier) => specifier.imported.name === "EmptyStateIcon"
);

if (!emptyStateIconImport) {
return false;
}

return (
iconElementIdentifier?.name === emptyStateIconImport.local.name
);
};

const iconPropIsIconElement = () => {
const pfIconsPackage = "@patternfly/react-icons";
const { imports: iconSpecifiers } = getFromPackage(
context,
pfIconsPackage
);
const iconDefaultSpecifiers = getDefaultImportsFromPackage(
context,
pfIconsPackage
);
const allIconSpecifiers = [
...iconSpecifiers,
...iconDefaultSpecifiers,
];

return (
iconElementIdentifier !== undefined &&
allIconSpecifiers.some(
(spec) => spec.local.name === iconElementIdentifier.name
)
);
};

const emptyStateIconComponent = iconPropIsEmptyStateIconComponent()
? (iconPropValue as JSXElement)
: undefined;

const emptyStateIconComponentIconAttribute =
emptyStateIconComponent &&
Expand All @@ -108,23 +153,29 @@ module.exports = {
const emptyStateIconComponentColorAttribute =
emptyStateIconComponent &&
getAttribute(emptyStateIconComponent, "color");
const emptyStateIconComponentColor = getAttributeText(
context,
emptyStateIconComponentColorAttribute
);

if (emptyStateIconComponentColor) {
if (emptyStateIconComponentColorAttribute) {
context.report({
node,
message: `The color prop on EmptyStateIcon has been removed. We suggest using the new status prop on EmptyState to apply colors to the icon.`,
});
}

const icon = emptyStateIconComponentIconAttribute
? context
const getIconPropText = () => {
if (emptyStateIconComponentIconAttribute) {
return context
.getSourceCode()
.getText(emptyStateIconComponentIconAttribute)
: "";
.getText(emptyStateIconComponentIconAttribute);
}

if (iconPropIsIconElement()) {
return `icon={${iconElementIdentifier!.name}}`;
}

return "";
};

const icon = getIconPropText();

context.report({
node,
Expand Down

0 comments on commit b5d2340

Please sign in to comment.