diff --git a/packages/core/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts b/packages/core/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts index 38eb14aad8..bc579b1c8e 100644 --- a/packages/core/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts +++ b/packages/core/src/features/preferences/renderer/preference-items/preference-item-injection-token.ts @@ -9,7 +9,7 @@ import type { Discriminable } from "../../../../common/utils/composable-responsi import type { Labelable } from "../../../../common/utils/composable-responsibilities/labelable/labelable"; import type { MaybeShowable } from "../../../../common/utils/composable-responsibilities/showable/showable"; import type { Orderable } from "../../../../common/utils/composable-responsibilities/orderable/orderable"; -import type { GetSeparator } from "../../../../common/utils/add-separator/add-separator"; +import type { GetSeparator } from "@k8slens/utilities"; import type { Composite } from "../../../../common/utils/composite/get-composite/get-composite"; export type ChildrenAreSeparated = diff --git a/packages/core/src/renderer/components/map/map.tsx b/packages/core/src/renderer/components/map/map.tsx index f3b660d041..82a55bd0bc 100644 --- a/packages/core/src/renderer/components/map/map.tsx +++ b/packages/core/src/renderer/components/map/map.tsx @@ -5,8 +5,8 @@ import { pipeline } from "@ogre-tools/fp"; import { identity, map } from "lodash/fp"; import React from "react"; -import type { GetSeparator } from "../../../common/utils/add-separator/add-separator"; -import { addSeparator } from "../../../common/utils/add-separator/add-separator"; +import type { GetSeparator } from "@k8slens/utilities"; +import { addSeparator } from "@k8slens/utilities"; interface RequiredPropertiesForItem { id: string; diff --git a/packages/technical-features/ui-components/index.ts b/packages/technical-features/ui-components/index.ts index b60fb0485a..a70b921258 100644 --- a/packages/technical-features/ui-components/index.ts +++ b/packages/technical-features/ui-components/index.ts @@ -1,3 +1,5 @@ export { uiComponentsFeature } from "./src/feature"; export * from "./src/element/elements"; +export * from "./src/map/map"; +export * from "./src/gutter/gutter"; diff --git a/packages/technical-features/ui-components/package.json b/packages/technical-features/ui-components/package.json index 079163c475..4e6be480d9 100644 --- a/packages/technical-features/ui-components/package.json +++ b/packages/technical-features/ui-components/package.json @@ -35,15 +35,17 @@ }, "peerDependencies": { "@k8slens/feature-core": "^6.5.0-alpha.0", + "@k8slens/utilities": "^1.0.0-alpha.1", + "@ogre-tools/fp": "^15.1.2", "@ogre-tools/injectable": "^15.1.2", "@ogre-tools/injectable-extension-for-auto-registration": "^15.1.2", - "@ogre-tools/fp": "^15.1.2", "lodash": "^4.17.21", "react": "^17" }, "devDependencies": { "@async-fn/jest": "^1.6.4", "@k8slens/eslint-config": "6.5.0-alpha.1", - "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0" + "@k8slens/react-testing-library-discovery": "^1.0.0-alpha.0", + "@testing-library/react": "^12.1.5" } } diff --git a/packages/technical-features/ui-components/src/gutter/__snapshots__/gutter.test.tsx.snap b/packages/technical-features/ui-components/src/gutter/__snapshots__/gutter.test.tsx.snap new file mode 100644 index 0000000000..2458cd90a3 --- /dev/null +++ b/packages/technical-features/ui-components/src/gutter/__snapshots__/gutter.test.tsx.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Gutter "when size is md", renders 1`] = ` + +
+
+
+ +`; + +exports[`Gutter "when size is not set", renders 1`] = ` + +
+
+
+ +`; + +exports[`Gutter "when size is sm", renders 1`] = ` + +
+
+
+ +`; + +exports[`Gutter "when size is xl", renders 1`] = ` + +
+
+
+ +`; diff --git a/packages/technical-features/ui-components/src/gutter/gutter.test.tsx b/packages/technical-features/ui-components/src/gutter/gutter.test.tsx new file mode 100644 index 0000000000..7526d66004 --- /dev/null +++ b/packages/technical-features/ui-components/src/gutter/gutter.test.tsx @@ -0,0 +1,26 @@ +import React from "react"; + +import { Gutter, GutterProps } from "./Gutter"; +import { render } from "@testing-library/react"; + +interface Scenario { + name: string; + props: GutterProps; +} + +describe("Gutter", () => { + ( + [ + { name: "when size is not set", props: {} }, + { name: "when size is sm", props: { size: "sm" } }, + { name: "when size is md", props: { size: "md" } }, + { name: "when size is xl", props: { size: "xl" } }, + ] as Scenario[] + ).forEach((scenario) => { + it(`"${scenario.name}", renders`, () => { + const rendered = render(); + + expect(rendered.baseElement).toMatchSnapshot(); + }); + }); +}); diff --git a/packages/technical-features/ui-components/src/gutter/gutter.tsx b/packages/technical-features/ui-components/src/gutter/gutter.tsx new file mode 100644 index 0000000000..51f817953b --- /dev/null +++ b/packages/technical-features/ui-components/src/gutter/gutter.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import type { ShirtSize } from "../shirt-size"; + +export interface GutterProps { + size?: ShirtSize; +} + +const classNamesByShirtSize: Record = { + sm: "w-4 h-4", + md: "w-6 h-6", + xl: "w-10 h-10", +}; + +export const Gutter = ({ size = "md" }: GutterProps) => ( +
+); diff --git a/packages/technical-features/ui-components/src/map/__snapshots__/map.test.tsx.snap b/packages/technical-features/ui-components/src/map/__snapshots__/map.test.tsx.snap new file mode 100644 index 0000000000..0cd7134778 --- /dev/null +++ b/packages/technical-features/ui-components/src/map/__snapshots__/map.test.tsx.snap @@ -0,0 +1,90 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Map given items and placeholder but no separator renders 1`] = ` + +
+
+
+
+ +`; + +exports[`Map given more than one item and separator renders 1`] = ` + +
+
+
+ Some separator +
+
+
+ Some separator +
+
+
+ +`; + +exports[`Map given more than one item and separator using left and right renders 1`] = ` + +
+
+
+ Some separator between + some-item-id + and + some-other-item-id +
+
+
+ Some separator between + some-other-item-id + and + some-another-item-id +
+
+
+ +`; + +exports[`Map given no items and placeholder renders 1`] = ` + +
+
+ Some placeholder +
+
+ +`; + +exports[`Map given no items but no placeholder renders 1`] = ` + +
+ +`; diff --git a/packages/technical-features/ui-components/src/map/map.test.tsx b/packages/technical-features/ui-components/src/map/map.test.tsx new file mode 100644 index 0000000000..976b2e672b --- /dev/null +++ b/packages/technical-features/ui-components/src/map/map.test.tsx @@ -0,0 +1,147 @@ +import type { RenderResult } from "@testing-library/react"; +import { render } from "@testing-library/react"; +import React from "react"; +import { Map } from "./map"; + +describe("Map", () => { + describe("given no items and placeholder", () => { + let rendered: RenderResult; + + beforeEach(() => { + rendered = render( +
Some placeholder
} + > + {() =>
Irrelevant
} +
, + ); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("renders placeholder", () => { + expect(rendered.getByTestId("some-placeholder")).toBeInTheDocument(); + }); + + it("does not render rows", () => { + expect(rendered.queryByTestId("some-row")).not.toBeInTheDocument(); + }); + }); + + describe("given no items but no placeholder", () => { + let rendered: RenderResult; + + beforeEach(() => { + rendered = render({() =>
Irrelevant
}
); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("does not render rows", () => { + expect(rendered.queryByTestId("some-row")).not.toBeInTheDocument(); + }); + }); + + describe("given items and placeholder but no separator", () => { + let rendered: RenderResult; + + beforeEach(() => { + rendered = render( +
Some placeholder
} + > + {(item) =>
} + , + ); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("does not render placeholder", () => { + expect(rendered.queryByTestId("some-placeholder")).not.toBeInTheDocument(); + }); + + it("renders items", () => { + expect(rendered.getByTestId("some-item-id")).toBeInTheDocument(); + }); + }); + + describe("given more than one item and separator", () => { + let rendered: RenderResult; + + beforeEach(() => { + rendered = render( +
Some separator
} + > + {(item) =>
} + , + ); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("renders items", () => { + expect(rendered.getByTestId("some-item-id")).toBeInTheDocument(); + }); + + it("renders separator", () => { + expect(rendered.getAllByTestId("separator")).toHaveLength(2); + }); + }); + + describe("given more than one item and separator using left and right", () => { + let rendered: RenderResult; + + beforeEach(() => { + rendered = render( + ( +
+ Some separator between + {left.id} + and + {right.id} +
+ )} + > + {(item) =>
} + , + ); + }); + + it("renders", () => { + expect(rendered.baseElement).toMatchSnapshot(); + }); + + it("renders items", () => { + expect(rendered.getByTestId("some-item-id")).toBeInTheDocument(); + }); + + it("renders separator", () => { + expect( + rendered.getByTestId("separator-between-some-item-id-and-some-other-item-id"), + ).toBeInTheDocument(); + }); + }); +}); diff --git a/packages/technical-features/ui-components/src/map/map.tsx b/packages/technical-features/ui-components/src/map/map.tsx new file mode 100644 index 0000000000..765a70c7f0 --- /dev/null +++ b/packages/technical-features/ui-components/src/map/map.tsx @@ -0,0 +1,50 @@ +import { pipeline } from "@ogre-tools/fp"; +import { identity, map } from "lodash/fp"; +import React from "react"; +import { addSeparator, GetSeparator } from "@k8slens/utilities"; + +interface RequiredPropertiesForItem { + id: string; +} + +export interface MapProps { + items: Item[]; + children: (item: Item) => React.ReactElement; + getPlaceholder?: () => React.ReactElement; + getSeparator?: GetSeparator; +} + +export const Map = (props: MapProps) => { + const { items, getPlaceholder, getSeparator, children } = props; + + if (items.length === 0) { + return getPlaceholder?.() || null; + } + + return ( + <> + {pipeline( + items, + + map((item) => ({ item, render: () => children(item) })), + + getSeparator + ? (items) => + addSeparator( + (left, right) => ({ + item: { + id: `separator-between-${left.item.id}-and-${right.item.id}`, + }, + + render: () => getSeparator(left.item, right.item), + }), + + items, + ) + : identity, + + map(({ render, item }) => {render()}), + )} + + ); +}; diff --git a/packages/technical-features/ui-components/src/shirt-size.ts b/packages/technical-features/ui-components/src/shirt-size.ts new file mode 100644 index 0000000000..4b5b150eaa --- /dev/null +++ b/packages/technical-features/ui-components/src/shirt-size.ts @@ -0,0 +1 @@ +export type ShirtSize = "sm" | "md" | "xl"; diff --git a/packages/utility-features/utilities/index.ts b/packages/utility-features/utilities/index.ts index dc8eacdfdd..3e1797fb16 100644 --- a/packages/utility-features/utilities/index.ts +++ b/packages/utility-features/utilities/index.ts @@ -43,3 +43,4 @@ export * from "./src/types"; export * from "./src/union-env-path"; export * from "./src/wait"; export * from "./src/with-concurrency-limit"; +export * from "./src/add-separator/add-separator"; diff --git a/packages/core/src/common/utils/add-separator/add-separator.test.ts b/packages/utility-features/utilities/src/add-separator/add-separator.test.ts similarity index 100% rename from packages/core/src/common/utils/add-separator/add-separator.test.ts rename to packages/utility-features/utilities/src/add-separator/add-separator.test.ts diff --git a/packages/core/src/common/utils/add-separator/add-separator.ts b/packages/utility-features/utilities/src/add-separator/add-separator.ts similarity index 100% rename from packages/core/src/common/utils/add-separator/add-separator.ts rename to packages/utility-features/utilities/src/add-separator/add-separator.ts