mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Introduce Element pattern to reduce duplication in UI code
Co-authored-by: Mikko Aspiala <mikko.aspiala@gmail.com> Signed-off-by: Janne Savolainen <janne.savolainen@live.fi>
This commit is contained in:
parent
3e150adabc
commit
99b0c8639f
@ -1 +1,3 @@
|
|||||||
export { uiComponentsFeature } from "./src/feature";
|
export { uiComponentsFeature } from "./src/feature";
|
||||||
|
|
||||||
|
export * from "./src/element/elements";
|
||||||
|
|||||||
@ -30,12 +30,16 @@
|
|||||||
"lint": "lens-lint",
|
"lint": "lens-lint",
|
||||||
"lint:fix": "lens-lint --fix"
|
"lint:fix": "lens-lint --fix"
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"classnames": "^2.3.2"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@k8slens/feature-core": "^6.5.0-alpha.0",
|
"@k8slens/feature-core": "^6.5.0-alpha.0",
|
||||||
"@ogre-tools/injectable": "^15.1.2",
|
"@ogre-tools/injectable": "^15.1.2",
|
||||||
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
"@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2",
|
||||||
"@ogre-tools/fp": "^15.1.2",
|
"@ogre-tools/fp": "^15.1.2",
|
||||||
"lodash": "^4.17.21"
|
"lodash": "^4.17.21",
|
||||||
|
"react": "^17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@async-fn/jest": "^1.6.4",
|
"@async-fn/jest": "^1.6.4",
|
||||||
|
|||||||
@ -0,0 +1,9 @@
|
|||||||
|
import type React from "react";
|
||||||
|
|
||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface ElementProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import React, { HTMLAttributes } from "react";
|
||||||
|
import { pipeline } from "@ogre-tools/fp";
|
||||||
|
import { flexParentModification } from "./prop-modifications/flex/flex-parent";
|
||||||
|
import { classNameModification } from "./prop-modifications/class-names/class-names";
|
||||||
|
import { vanillaClassNameAdapterModification } from "./prop-modifications/class-names/vanilla-class-name-adapter";
|
||||||
|
|
||||||
|
export const ElementFor =
|
||||||
|
<T extends HTMLElement, Y extends HTMLAttributes<T>>(TagName: React.ElementType) =>
|
||||||
|
(props: React.DetailedHTMLProps<Y, T> & ElementProps) => {
|
||||||
|
const modifiedProps = pipeline(
|
||||||
|
props,
|
||||||
|
vanillaClassNameAdapterModification,
|
||||||
|
flexParentModification,
|
||||||
|
classNameModification,
|
||||||
|
);
|
||||||
|
|
||||||
|
return <TagName {...modifiedProps} />;
|
||||||
|
};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { ElementFor } from "./element";
|
||||||
|
import type React from "react";
|
||||||
|
|
||||||
|
export const Div = ElementFor<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>("div");
|
||||||
|
export const Span = ElementFor<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>("span");
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import { Div } from "../../elements";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import { discoverFor } from "@k8slens/react-testing-library-discovery";
|
||||||
|
|
||||||
|
describe("class-names", () => {
|
||||||
|
it("given complex class names, renders with class name", () => {
|
||||||
|
const rendered = render(
|
||||||
|
<Div
|
||||||
|
_className={[
|
||||||
|
"some-class-name",
|
||||||
|
|
||||||
|
{
|
||||||
|
"some-not-present-class-name": false,
|
||||||
|
"some-present-class-name": true,
|
||||||
|
},
|
||||||
|
|
||||||
|
["first-class-name-in-array", "second-class-name-in-array"],
|
||||||
|
undefined,
|
||||||
|
false,
|
||||||
|
]}
|
||||||
|
data-some-element-test
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe(
|
||||||
|
"some-class-name some-present-class-name first-class-name-in-array second-class-name-in-array",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given minimal class name, renders with class name", () => {
|
||||||
|
const rendered = render(<Div _className="some-class-name" data-some-element-test />);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("some-class-name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given complex class names leading to class name not being present, renders without class name", () => {
|
||||||
|
const rendered = render(
|
||||||
|
<Div _className={[{ some: false }, [undefined, false]]} data-some-element-test />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered).not.toHaveAttribute('class');
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import classnames from "classnames";
|
||||||
|
|
||||||
|
export type ClassName = classnames.Argument;
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface ElementProps {
|
||||||
|
_className?: ClassName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const classNameModification = <T extends ElementProps>({ _className, ...props }: T) => {
|
||||||
|
const classNameString = classnames(_className);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
...(classNameString ? { className: classNameString } : {}),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,42 @@
|
|||||||
|
import { Div } from "../../elements";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import { discoverFor } from "@k8slens/react-testing-library-discovery";
|
||||||
|
|
||||||
|
describe("vanilla-class-name-adapter", () => {
|
||||||
|
it("given vanilla class name, has class name", () => {
|
||||||
|
const rendered = render(<Div className="some-class-name" data-some-element-test />);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("some-class-name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given custom class name, has class name", () => {
|
||||||
|
const rendered = render(<Div _className="some-class-name" data-some-element-test />);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("some-class-name");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given both vanilla and custom class names, has class names", () => {
|
||||||
|
const rendered = render(
|
||||||
|
<Div
|
||||||
|
className="some-vanilla-class-name"
|
||||||
|
_className="some-class-name"
|
||||||
|
data-some-element-test
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("some-vanilla-class-name some-class-name");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import type { ClassName } from "./class-names";
|
||||||
|
|
||||||
|
export const vanillaClassNameAdapterModification = <T extends ElementProps>({
|
||||||
|
className,
|
||||||
|
_className,
|
||||||
|
...props
|
||||||
|
}: T & { className?: string; _className?: ClassName }) => ({
|
||||||
|
...props,
|
||||||
|
_className: [className, _className],
|
||||||
|
});
|
||||||
@ -0,0 +1,50 @@
|
|||||||
|
import { Div } from "../../elements";
|
||||||
|
import { render } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import { discoverFor } from "@k8slens/react-testing-library-discovery";
|
||||||
|
|
||||||
|
describe("flexParent", () => {
|
||||||
|
it("given flex parent without specific configuration, renders", () => {
|
||||||
|
const rendered = render(<Div _flexParent data-some-element-test />);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("flex");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given flex parent prop with false, renders", () => {
|
||||||
|
const rendered = render(<Div _flexParent={false} data-some-element-test />);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given flex parent with aligning child vertically center, renders", () => {
|
||||||
|
const rendered = render(
|
||||||
|
<Div _flexParent={{ centeredVertically: true }} data-some-element-test />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("flex align-center");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("given flex parent with explicitly not aligning child vertically center, renders", () => {
|
||||||
|
const rendered = render(
|
||||||
|
<Div _flexParent={{ centeredVertically: false }} data-some-element-test />,
|
||||||
|
);
|
||||||
|
|
||||||
|
const discover = discoverFor(() => rendered);
|
||||||
|
|
||||||
|
const { discovered } = discover.getSingleElement("some-element");
|
||||||
|
|
||||||
|
expect(discovered.className).toBe("flex");
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import type { ClassName } from "../class-names/class-names";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface ElementProps {
|
||||||
|
_className?: ClassName;
|
||||||
|
_flexParent?: { centeredVertically: boolean } | boolean;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const flexParentModification = <T extends ElementProps>({
|
||||||
|
_flexParent,
|
||||||
|
_className,
|
||||||
|
...props
|
||||||
|
}: T) => {
|
||||||
|
if (!_flexParent) {
|
||||||
|
return { _className, ...props };
|
||||||
|
}
|
||||||
|
|
||||||
|
const centeredVertically =
|
||||||
|
typeof _flexParent === "boolean" ? false : _flexParent.centeredVertically;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...props,
|
||||||
|
|
||||||
|
_className: [
|
||||||
|
"flex",
|
||||||
|
|
||||||
|
{
|
||||||
|
"align-center": centeredVertically,
|
||||||
|
},
|
||||||
|
|
||||||
|
_className,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user