From 9d51ef2aa69c6c9df0fd57281b25131a10efd27a Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 9 May 2023 15:45:37 -0400 Subject: [PATCH] fix: Don't crash when hovering hotbar menu index - Remove all uses of React.ReactNode as it is unsafe, replace them with usages of SafeReactNode which doesn't contain the '{}' type Signed-off-by: Sebastian Malton --- package-lock.json | 13 +++++++++ .../src/keyboard-shortcut-listener.tsx | 3 ++- .../src/keyboard-shortcut-scope.tsx | 3 ++- .../core/src/common/catalog/catalog-entity.ts | 4 +-- .../add-remove-buttons/add-remove-buttons.tsx | 5 ++-- .../renderer/components/animate/animate.tsx | 3 ++- .../src/renderer/components/avatar/avatar.tsx | 4 +-- .../src/renderer/components/badge/badge.tsx | 4 ++- .../catalog/__tests__/custom-views.test.ts | 10 +++---- .../catalog/columns/get.injectable.ts | 3 ++- .../catalog/custom-category-columns.ts | 6 ++--- .../catalog/hotbar-toggle-menu-item.tsx | 6 ++--- .../renderer/components/checkbox/checkbox.tsx | 6 ++--- .../cluster-manager/cluster-view.tsx | 3 ++- .../details.test.tsx | 3 ++- .../confirm-dialog/confirm-dialog.tsx | 10 +++---- .../custom-resources/crd-resource-details.tsx | 3 ++- .../custom-resources/crd-resources.tsx | 6 ++++- .../src/renderer/components/dialog/dialog.tsx | 3 ++- .../src/renderer/components/dock/dock-tab.tsx | 3 ++- .../renderer/components/dock/info-panel.tsx | 8 +++--- .../components/drawer/drawer-item.tsx | 3 ++- .../drawer/drawer-param-toggler.tsx | 3 ++- .../components/drawer/drawer-title.tsx | 5 ++-- .../src/renderer/components/drawer/drawer.tsx | 8 +++--- .../renderer/components/dropdown/dropdown.tsx | 9 ++++--- .../components/duration/reactive-duration.tsx | 2 +- .../editable-list/editable-list.tsx | 4 +-- .../components/file-picker/file-picker.tsx | 5 ++-- .../components/hotbar/hotbar-cell.tsx | 7 +++-- .../components/hotbar/hotbar-menu.tsx | 4 +-- .../components/hotbar/hotbar-selector.tsx | 2 +- .../src/renderer/components/icon/icon.tsx | 8 +++--- .../src/renderer/components/input/input.tsx | 20 +++++++------- .../components/input/input_validators.ts | 4 +-- .../components/item-object-list/content.tsx | 9 +++---- .../components/item-object-list/header.tsx | 7 +++-- .../item-object-list/list-layout.tsx | 19 +++++++------ .../kubeconfig-dialog/kubeconfig-dialog.tsx | 3 ++- .../kubeconfig-dialog/open.injectable.ts | 8 +++--- .../components/layout/cluster-page-menu.ts | 3 ++- .../components/layout/main-layout.tsx | 7 ++--- .../components/layout/setting-layout.tsx | 4 +-- .../layout/siblings-in-tab-layout.tsx | 3 ++- .../layout/sidebar-items.injectable.ts | 5 ++-- .../renderer/components/layout/sub-header.tsx | 4 +-- .../renderer/components/layout/sub-title.tsx | 6 ++--- .../components/layout/tab-layout-2.tsx | 3 ++- .../renderer/components/layout/tab-layout.tsx | 7 +++-- .../components/layout/wizard-layout.tsx | 6 ++--- .../line-progress/line-progress.tsx | 2 ++ .../src/renderer/components/list/list.tsx | 3 ++- .../renderer/components/menu/menu-actions.tsx | 5 ++-- .../src/renderer/components/menu/menu.tsx | 5 ++-- .../namespaces/namespace-tree-view.test.tsx | 3 ++- .../renderer/components/no-items/no-items.tsx | 4 +-- .../pod-security-policy-details.tsx | 3 ++- .../src/renderer/components/radio/radio.tsx | 4 +-- .../components/render-delay/render-delay.tsx | 6 ++--- .../src/renderer/components/select/select.tsx | 3 ++- .../status-bar/status-bar-registration.ts | 5 ++-- .../components/status-brick/status-brick.tsx | 2 ++ .../components/switch/form-switcher.tsx | 5 ++-- .../renderer/components/switch/switcher.tsx | 3 ++- .../renderer/components/table/table-cell.tsx | 4 +-- .../src/renderer/components/table/table.tsx | 3 ++- .../src/renderer/components/tabs/tabs.tsx | 5 ++-- .../components/tree-view/tree-view.tsx | 3 ++- .../service-accounts/secret.tsx | 3 ++- .../src/renderer/components/wizard/wizard.tsx | 17 ++++++------ packages/infrastructure/webpack/package.json | 1 + packages/list-layout/package.json | 1 + .../src/kube-list-layout-column.ts | 4 +-- .../list-layout/src/list-layout-column.ts | 5 ++-- ...-higher-order-component-injection-token.ts | 3 ++- packages/ui-components/button/src/button.tsx | 5 ++-- .../error-boundary/src/error-boundary.tsx | 4 +-- .../ui-components/tooltip/src/tooltip.tsx | 4 +-- .../tooltip/src/withTooltip.test.tsx | 4 +-- .../ui-components/tooltip/src/withTooltip.tsx | 7 +++-- packages/utility-features/utilities/index.ts | 1 - .../utilities/src/is-node-falsy.ts | 27 ------------------- .../utilities/src/isReactNode.ts | 11 +++++--- .../utilities/src/jsonPath.ts | 2 +- 84 files changed, 246 insertions(+), 208 deletions(-) delete mode 100644 packages/utility-features/utilities/src/is-node-falsy.ts diff --git a/package-lock.json b/package-lock.json index 532ff2cdcd..d1389da35b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36625,6 +36625,7 @@ "devDependencies": { "@async-fn/jest": "^1.6.4", "@k8slens/typescript": "^6.5.0-alpha.2", + "@ogre-tools/injectable-extension-for-auto-registration": "^15.9.0", "@ogre-tools/test-utils": "^15.8.1", "ts-node": "^10.9.1", "webpack-node-externals": "^3.0.0" @@ -36635,6 +36636,17 @@ "lodash": "^4.17.21" } }, + "packages/infrastructure/webpack/node_modules/@ogre-tools/injectable-extension-for-auto-registration": { + "version": "15.9.0", + "resolved": "https://registry.npmjs.org/@ogre-tools/injectable-extension-for-auto-registration/-/injectable-extension-for-auto-registration-15.9.0.tgz", + "integrity": "sha512-5Ik43ZLAOhBODrhF/MbTkC3SItFMNxibufBoYFqCERfHSHyZE6pUa5yMbjrh+YFkntIrDH2IScW7BqZkraHvTA==", + "dev": true, + "peerDependencies": { + "@ogre-tools/fp": "*", + "@ogre-tools/injectable": "*", + "lodash": "^4.17.21" + } + }, "packages/infrastructure/webpack/node_modules/sass-loader": { "version": "13.2.2", "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.2.2.tgz", @@ -36869,6 +36881,7 @@ }, "peerDependencies": { "@k8slens/kube-object": "^1.0.0-alpha.1", + "@k8slens/utilities": "^1.0.0-alpha.3", "@ogre-tools/injectable": "^15.8.1", "react": "^17.0.2" } diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx index 5dad663129..71f1886ffb 100644 --- a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-listener.tsx @@ -1,10 +1,11 @@ +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { withInjectables } from "@ogre-tools/injectable-react"; import React, { useEffect } from "react"; import invokeShortcutInjectable, { InvokeShortcut } from "./invoke-shortcut.injectable"; export interface KeyboardShortcutListenerProps { - children: React.ReactNode; + children: SingleOrMany; } interface Dependencies { diff --git a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx index 0e725cc4d4..a06d6d4be5 100644 --- a/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx +++ b/packages/business-features/keyboard-shortcuts/src/keyboard-shortcut-scope.tsx @@ -1,8 +1,9 @@ +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import React from "react"; export interface KeyboardShortcutScopeProps { id: string; - children: React.ReactNode; + children: SingleOrMany; } export const KeyboardShortcutScope = ({ id, children }: KeyboardShortcutScopeProps) => ( diff --git a/packages/core/src/common/catalog/catalog-entity.ts b/packages/core/src/common/catalog/catalog-entity.ts index 4ddfaf5a11..bd4805bc48 100644 --- a/packages/core/src/common/catalog/catalog-entity.ts +++ b/packages/core/src/common/catalog/catalog-entity.ts @@ -7,7 +7,7 @@ import EventEmitter from "events"; import type TypedEmitter from "typed-emitter"; import { observable, makeObservable } from "mobx"; import { once } from "lodash"; -import type { Disposer } from "@k8slens/utilities"; +import type { Disposer, SafeReactNode } from "@k8slens/utilities"; import { iter } from "@k8slens/utilities"; import type { CategoryColumnRegistration, TitleCellProps } from "../../renderer/components/catalog/custom-category-columns"; @@ -201,7 +201,7 @@ export abstract class CatalogCategory extends (EventEmitter as new () => TypedEm * Defaults to no badge. * The badge is displayed next to the Category name in the Catalog Category menu */ - public getBadge(): React.ReactNode { + public getBadge(): SafeReactNode { return null; } diff --git a/packages/core/src/renderer/components/add-remove-buttons/add-remove-buttons.tsx b/packages/core/src/renderer/components/add-remove-buttons/add-remove-buttons.tsx index 99d3f64b8d..48ad484641 100644 --- a/packages/core/src/renderer/components/add-remove-buttons/add-remove-buttons.tsx +++ b/packages/core/src/renderer/components/add-remove-buttons/add-remove-buttons.tsx @@ -6,6 +6,7 @@ import "./add-remove-buttons.scss"; import React from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Button } from "@k8slens/button"; import { Icon } from "../icon"; @@ -13,8 +14,8 @@ import { Icon } from "../icon"; export interface AddRemoveButtonsProps extends React.HTMLAttributes { onAdd?: () => void; onRemove?: () => void; - addTooltip?: React.ReactNode; - removeTooltip?: React.ReactNode; + addTooltip?: SafeReactNode; + removeTooltip?: SafeReactNode; } export class AddRemoveButtons extends React.PureComponent { diff --git a/packages/core/src/renderer/components/animate/animate.tsx b/packages/core/src/renderer/components/animate/animate.tsx index 6a7870e336..c1381cbc6e 100644 --- a/packages/core/src/renderer/components/animate/animate.tsx +++ b/packages/core/src/renderer/components/animate/animate.tsx @@ -5,6 +5,7 @@ import "./animate.scss"; import React, { useEffect, useState } from "react"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities"; import { withInjectables } from "@ogre-tools/injectable-react"; import type { RequestAnimationFrame } from "./request-animation-frame.injectable"; @@ -21,7 +22,7 @@ export interface AnimateProps { onLeave?: () => void; enterDuration?: number; leaveDuration?: number; - children?: React.ReactNode; + children?: SingleOrMany; } interface Dependencies { diff --git a/packages/core/src/renderer/components/avatar/avatar.tsx b/packages/core/src/renderer/components/avatar/avatar.tsx index 991b7e761b..91f0ad7043 100644 --- a/packages/core/src/renderer/components/avatar/avatar.tsx +++ b/packages/core/src/renderer/components/avatar/avatar.tsx @@ -8,7 +8,7 @@ import styles from "./avatar.module.scss"; import type { ImgHTMLAttributes, MouseEventHandler } from "react"; import React from "react"; import randomColor from "randomcolor"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { computeDefaultShortName } from "../../../common/catalog/helpers"; @@ -21,7 +21,7 @@ export interface AvatarProps { variant?: "circle" | "rounded" | "square"; imgProps?: ImgHTMLAttributes; disabled?: boolean; - children?: SingleOrMany; + children?: SingleOrMany; className?: string; id?: string; onClick?: MouseEventHandler; diff --git a/packages/core/src/renderer/components/badge/badge.tsx b/packages/core/src/renderer/components/badge/badge.tsx index 2e217c2de7..d2957c9076 100644 --- a/packages/core/src/renderer/components/badge/badge.tsx +++ b/packages/core/src/renderer/components/badge/badge.tsx @@ -8,16 +8,18 @@ import styles from "./badge.module.scss"; import React, { useEffect, useRef, useState } from "react"; import { action, observable } from "mobx"; import { observer } from "mobx-react"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { withTooltip } from "@k8slens/tooltip"; export interface BadgeProps extends React.HTMLAttributes { small?: boolean; flat?: boolean; - label?: React.ReactNode; + label?: SafeReactNode; expandable?: boolean; disabled?: boolean; scrollable?: boolean; + children?: SingleOrMany; } // Common handler for all Badge instances diff --git a/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts b/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts index 8bd996505e..1c9fdd090e 100644 --- a/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts +++ b/packages/core/src/renderer/components/catalog/__tests__/custom-views.test.ts @@ -3,9 +3,9 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { SafeReactNode } from "@k8slens/utilities"; import type { DiContainer } from "@ogre-tools/injectable"; import { computed } from "mobx"; -import type React from "react"; import type { LensRendererExtension } from "../../../../extensions/lens-renderer-extension"; import rendererExtensionsInjectable from "../../../../extensions/renderer-extensions.injectable"; import { getDiForUnitTesting } from "../../../getDiForUnitTesting"; @@ -20,8 +20,8 @@ describe("Custom Category Views", () => { }); it("should order items correctly over all extensions", () => { - const component1 = (): React.ReactNode => null; - const component2 = (): React.ReactNode => null; + const component1 = (): SafeReactNode => null; + const component2 = (): SafeReactNode => null; di.override(rendererExtensionsInjectable, () => computed(() => [ { @@ -58,8 +58,8 @@ describe("Custom Category Views", () => { }); it("should put put priority < 50 items in before", () => { - const component1 = (): React.ReactNode => null; - const component2 = (): React.ReactNode => null; + const component1 = (): SafeReactNode => null; + const component2 = (): SafeReactNode => null; di.override(rendererExtensionsInjectable, () => computed(() => [ { diff --git a/packages/core/src/renderer/components/catalog/columns/get.injectable.ts b/packages/core/src/renderer/components/catalog/columns/get.injectable.ts index 91a92473fd..5feb7bcb97 100644 --- a/packages/core/src/renderer/components/catalog/columns/get.injectable.ts +++ b/packages/core/src/renderer/components/catalog/columns/get.injectable.ts @@ -2,6 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { SafeReactNode } from "@k8slens/utilities"; import { getInjectable } from "@ogre-tools/injectable"; import { orderBy } from "lodash"; import type { CatalogCategory, CatalogEntity } from "../../../../common/catalog"; @@ -35,7 +36,7 @@ const getCategoryColumnsInjectable = getInjectable({ const sortingCallbacks: CategoryColumns["sortingCallbacks"] = {}; const searchFilters: CategoryColumns["searchFilters"] = []; const renderTableHeader: CategoryColumns["renderTableHeader"] = []; - const tableRowRenderers: ((entity: CatalogEntity) => React.ReactNode)[] = []; + const tableRowRenderers: ((entity: CatalogEntity) => SafeReactNode)[] = []; for (const registration of allRegistrations) { if (registration.sortCallback) { diff --git a/packages/core/src/renderer/components/catalog/custom-category-columns.ts b/packages/core/src/renderer/components/catalog/custom-category-columns.ts index 801d68832e..c10489f523 100644 --- a/packages/core/src/renderer/components/catalog/custom-category-columns.ts +++ b/packages/core/src/renderer/components/catalog/custom-category-columns.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type React from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import type { CatalogEntity } from "../../../common/catalog"; import type { TableCellProps } from "../table"; @@ -32,7 +32,7 @@ export interface CategoryColumnRegistration { /** * This function will be called to generate the cells (on demand) for the column */ - renderCell: (entity: CatalogEntity) => React.ReactNode; + renderCell: (entity: CatalogEntity) => SafeReactNode; /** * This function will be used to generate the columns title cell. @@ -79,7 +79,7 @@ export interface AdditionalCategoryColumnRegistration extends CategoryColumnRegi export interface RegisteredAdditionalCategoryColumn { id: string; priority: number; - renderCell: (entity: CatalogEntity) => React.ReactNode; + renderCell: (entity: CatalogEntity) => SafeReactNode; titleProps: TableCellProps; sortCallback?: (entity: CatalogEntity) => string | number | (string | number)[]; searchFilter?: (entity: CatalogEntity) => string | string[]; diff --git a/packages/core/src/renderer/components/catalog/hotbar-toggle-menu-item.tsx b/packages/core/src/renderer/components/catalog/hotbar-toggle-menu-item.tsx index bab9b79eb5..1080c50b2a 100644 --- a/packages/core/src/renderer/components/catalog/hotbar-toggle-menu-item.tsx +++ b/packages/core/src/renderer/components/catalog/hotbar-toggle-menu-item.tsx @@ -2,7 +2,6 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ReactNode } from "react"; import React, { useState } from "react"; import { MenuItem } from "../menu"; @@ -12,6 +11,7 @@ import { withInjectables } from "@ogre-tools/injectable-react"; import type { IComputedValue } from "mobx"; import type { Hotbar } from "../../../features/hotbar/storage/common/hotbar"; import activeHotbarInjectable from "../../../features/hotbar/storage/common/active.injectable"; +import type { SafeReactNode } from "@k8slens/utilities"; interface Dependencies { activeHotbar: IComputedValue; @@ -19,8 +19,8 @@ interface Dependencies { interface HotbarToggleMenuItemProps { entity: CatalogEntity; - addContent: ReactNode; - removeContent: ReactNode; + addContent: SafeReactNode; + removeContent: SafeReactNode; } function NonInjectedHotbarToggleMenuItem({ diff --git a/packages/core/src/renderer/components/checkbox/checkbox.tsx b/packages/core/src/renderer/components/checkbox/checkbox.tsx index 4193f1d283..2b7d35fe51 100644 --- a/packages/core/src/renderer/components/checkbox/checkbox.tsx +++ b/packages/core/src/renderer/components/checkbox/checkbox.tsx @@ -5,17 +5,17 @@ import "./checkbox.scss"; import React from "react"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities"; export interface CheckboxProps { className?: string; - label?: React.ReactNode; + label?: SafeReactNode; inline?: boolean; disabled?: boolean; value?: boolean; onChange?(value: boolean, evt: React.ChangeEvent): void; - children?: SingleOrMany; + children?: SingleOrMany; } export function Checkbox({ label, inline, className, value, children, onChange = noop, disabled, ...inputProps }: CheckboxProps) { diff --git a/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx b/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx index 1e5062b313..43728037d9 100644 --- a/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx +++ b/packages/core/src/renderer/components/cluster-manager/cluster-view.tsx @@ -22,6 +22,7 @@ import type { RequestClusterActivation } from "../../../features/cluster/activat import requestClusterActivationInjectable from "../../../features/cluster/activation/renderer/request-activation.injectable"; import type { GetClusterById } from "../../../features/cluster/storage/common/get-by-id.injectable"; import getClusterByIdInjectable from "../../../features/cluster/storage/common/get-by-id.injectable"; +import type { SafeReactNode } from "@k8slens/utilities"; interface Dependencies { clusterId: IComputedValue; @@ -93,7 +94,7 @@ class NonInjectedClusterView extends React.Component { ]); } - renderStatus(): React.ReactNode { + renderStatus(): SafeReactNode { const { cluster, isReady } = this; if (cluster && !isReady) { diff --git a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx index be08c90352..b82aba0bc9 100644 --- a/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx +++ b/packages/core/src/renderer/components/config-horizontal-pod-autoscalers/details.test.tsx @@ -14,9 +14,10 @@ import storesAndApisCanBeCreatedInjectable from "../../stores-apis-can-be-create import type { DiRender } from "../test-utils/renderFor"; import { renderFor } from "../test-utils/renderFor"; import { HorizontalPodAutoscalerDetails } from "./details"; +import type { SafeReactNode } from "@k8slens/utilities"; jest.mock("react-router-dom", () => ({ - Link: ({ children }: { children: React.ReactNode }) => children, + Link: ({ children }: { children: SafeReactNode }) => children, })); const hpaV2 = { diff --git a/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx b/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx index 1ebecfe321..140a427af4 100644 --- a/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx +++ b/packages/core/src/renderer/components/confirm-dialog/confirm-dialog.tsx @@ -5,11 +5,11 @@ import "./confirm-dialog.scss"; -import type { ReactNode } from "react"; import React from "react"; import type { IObservableValue } from "mobx"; import { observable, makeObservable, computed } from "mobx"; import { observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames, noop, prevDefault } from "@k8slens/utilities"; import type { ButtonProps } from "@k8slens/button"; import { Button } from "@k8slens/button"; @@ -30,10 +30,10 @@ export interface ConfirmDialogParams extends ConfirmDialogBooleanParams { } export interface ConfirmDialogBooleanParams { - labelOk?: ReactNode; - labelCancel?: ReactNode; - message: ReactNode; - icon?: ReactNode; + labelOk?: SafeReactNode; + labelCancel?: SafeReactNode; + message: SafeReactNode; + icon?: SafeReactNode; okButtonProps?: Partial; cancelButtonProps?: Partial; } diff --git a/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx b/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx index f56091f395..b2b44721b1 100644 --- a/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx +++ b/packages/core/src/renderer/components/custom-resources/crd-resource-details.tsx @@ -7,6 +7,7 @@ import "./crd-resource-details.scss"; import React from "react"; import { observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames, safeJSONPathValue } from "@k8slens/utilities"; import { Badge } from "../badge"; import { DrawerItem } from "../drawer"; @@ -22,7 +23,7 @@ export interface CustomResourceDetailsProps extends KubeObjectDetailsProps diff --git a/packages/core/src/renderer/components/custom-resources/crd-resources.tsx b/packages/core/src/renderer/components/custom-resources/crd-resources.tsx index 5ac87621db..812a6c8a9d 100644 --- a/packages/core/src/renderer/components/custom-resources/crd-resources.tsx +++ b/packages/core/src/renderer/components/custom-resources/crd-resources.tsx @@ -106,7 +106,11 @@ class NonInjectedCustomResources extends React.Component { isNamespaced && ( ), - ...extraColumns.map((column) => safeJSONPathValue(customResource, column.jsonPath)), + ...( + extraColumns + .map((column) => safeJSONPathValue(customResource, column.jsonPath)) + .map(formatJSONValue) + ), , ]} failedToLoadMessage={( diff --git a/packages/core/src/renderer/components/dialog/dialog.tsx b/packages/core/src/renderer/components/dialog/dialog.tsx index f1c4b42342..28f2a367cc 100644 --- a/packages/core/src/renderer/components/dialog/dialog.tsx +++ b/packages/core/src/renderer/components/dialog/dialog.tsx @@ -10,6 +10,7 @@ import { createPortal } from "react-dom"; import { disposeOnUnmount, observer } from "mobx-react"; import { reaction } from "mobx"; import { Animate } from "../animate"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames, noop, stopPropagation } from "@k8slens/utilities"; import type { ObservableHistory } from "mobx-observable-history"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -29,7 +30,7 @@ export interface DialogProps { pinned?: boolean; animated?: boolean; "data-testid"?: string; - children?: React.ReactNode | React.ReactNode[]; + children?: SingleOrMany; } interface DialogState { diff --git a/packages/core/src/renderer/components/dock/dock-tab.tsx b/packages/core/src/renderer/components/dock/dock-tab.tsx index d33eb682dc..c75364302e 100644 --- a/packages/core/src/renderer/components/dock/dock-tab.tsx +++ b/packages/core/src/renderer/components/dock/dock-tab.tsx @@ -7,6 +7,7 @@ import styles from "./dock-tab.module.scss"; import React from "react"; import { observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames, prevDefault, isMiddleClick } from "@k8slens/utilities"; import type { DockStore, DockTab as DockTabModel } from "./dock/store"; import type { TabProps } from "../tabs"; @@ -21,7 +22,7 @@ import isMacInjectable from "../../../common/vars/is-mac.injectable"; import autoBindReact from "auto-bind/react"; export interface DockTabProps extends TabProps { - moreActions?: React.ReactNode; + moreActions?: SafeReactNode; } interface Dependencies { diff --git a/packages/core/src/renderer/components/dock/info-panel.tsx b/packages/core/src/renderer/components/dock/info-panel.tsx index 632ba7d99a..c79803303f 100644 --- a/packages/core/src/renderer/components/dock/info-panel.tsx +++ b/packages/core/src/renderer/components/dock/info-panel.tsx @@ -5,10 +5,10 @@ import "./info-panel.scss"; -import type { ReactNode } from "react"; import React, { Component } from "react"; import { computed, observable, reaction, makeObservable } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Button } from "@k8slens/button"; import { Icon } from "../icon"; @@ -29,9 +29,9 @@ export interface InfoPanelProps extends OptionalProps { export interface OptionalProps { className?: string; error?: string; - controls?: ReactNode; - submitLabel?: ReactNode; - submittingMessage?: ReactNode; + controls?: SafeReactNode; + submitLabel?: SafeReactNode; + submittingMessage?: SafeReactNode; disableSubmit?: boolean; showButtons?: boolean; showSubmitClose?: boolean; diff --git a/packages/core/src/renderer/components/drawer/drawer-item.tsx b/packages/core/src/renderer/components/drawer/drawer-item.tsx index 8c17d1ae91..981f717c5f 100644 --- a/packages/core/src/renderer/components/drawer/drawer-item.tsx +++ b/packages/core/src/renderer/components/drawer/drawer-item.tsx @@ -5,10 +5,11 @@ import "./drawer-item.scss"; import React from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; export interface DrawerItemProps extends React.HTMLAttributes { - name: React.ReactNode; + name: SafeReactNode; title?: string; labelsOnly?: boolean; hidden?: boolean; diff --git a/packages/core/src/renderer/components/drawer/drawer-param-toggler.tsx b/packages/core/src/renderer/components/drawer/drawer-param-toggler.tsx index 2c4702fcde..b3ac2f8c6c 100644 --- a/packages/core/src/renderer/components/drawer/drawer-param-toggler.tsx +++ b/packages/core/src/renderer/components/drawer/drawer-param-toggler.tsx @@ -6,11 +6,12 @@ import "./drawer-param-toggler.scss"; import React from "react"; import { Icon } from "../icon"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; export interface DrawerParamTogglerProps { label: string | number; - children: React.ReactNode | React.ReactNode[]; + children: SingleOrMany; } interface State { diff --git a/packages/core/src/renderer/components/drawer/drawer-title.tsx b/packages/core/src/renderer/components/drawer/drawer-title.tsx index 1b3b6d3d0f..57e50cb5e5 100644 --- a/packages/core/src/renderer/components/drawer/drawer-title.tsx +++ b/packages/core/src/renderer/components/drawer/drawer-title.tsx @@ -5,16 +5,17 @@ import styles from "./drawer-title.module.css"; import React from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; export interface DrawerTitleProps { className?: string; - children?: React.ReactNode; + children?: SafeReactNode; /** * @deprecated Prefer passing the value as `children` */ - title?: React.ReactNode; + title?: SafeReactNode; /** * Specifies how large this title is diff --git a/packages/core/src/renderer/components/drawer/drawer.tsx b/packages/core/src/renderer/components/drawer/drawer.tsx index 59e6983b83..44ccccdf4c 100644 --- a/packages/core/src/renderer/components/drawer/drawer.tsx +++ b/packages/core/src/renderer/components/drawer/drawer.tsx @@ -8,7 +8,7 @@ import "./drawer.scss"; import React from "react"; import { clipboard } from "electron"; import { createPortal } from "react-dom"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities"; import { Icon } from "../icon"; import type { AnimateName } from "../animate"; @@ -24,7 +24,7 @@ export type DrawerPosition = "top" | "left" | "right" | "bottom"; export interface DrawerProps { open: boolean; - title: React.ReactNode; + title: SafeReactNode; /** * The width or heigh (depending on `position`) of the Drawer. @@ -38,8 +38,8 @@ export interface DrawerProps { position?: DrawerPosition; animation?: AnimateName; onClose?: () => void; - toolbar?: React.ReactNode; - children?: SingleOrMany; + toolbar?: SafeReactNode; + children?: SingleOrMany; "data-testid"?: string; testIdForClose?: string; } diff --git a/packages/core/src/renderer/components/dropdown/dropdown.tsx b/packages/core/src/renderer/components/dropdown/dropdown.tsx index 40efd877c6..3fd6727fa6 100644 --- a/packages/core/src/renderer/components/dropdown/dropdown.tsx +++ b/packages/core/src/renderer/components/dropdown/dropdown.tsx @@ -3,18 +3,21 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; +import { toSafeReactChildrenArray } from "@k8slens/utilities"; import type { HTMLAttributes } from "react"; import React, { useState } from "react"; import { Menu } from "../menu"; interface DropdownProps extends HTMLAttributes { - contentForToggle: React.ReactNode; + contentForToggle: SafeReactNode; + children?: SingleOrMany; } export function Dropdown(props: DropdownProps) { const { id, contentForToggle, children, ...rest } = props; const [opened, setOpened] = useState(false); - + const toggle = () => { setOpened(!opened); }; @@ -31,7 +34,7 @@ export function Dropdown(props: DropdownProps) { close={toggle} open={toggle} > - {React.Children.toArray(children)} + {toSafeReactChildrenArray(children)} ); diff --git a/packages/core/src/renderer/components/duration/reactive-duration.tsx b/packages/core/src/renderer/components/duration/reactive-duration.tsx index 57eed992f7..32a2bc1bc9 100644 --- a/packages/core/src/renderer/components/duration/reactive-duration.tsx +++ b/packages/core/src/renderer/components/duration/reactive-duration.tsx @@ -22,7 +22,7 @@ const everySecond = 1000; const everyMinute = 60 * 1000; /** - * This function computes a resonable update interval, matching `formatDuration`'s rules on when to display seconds + * This function computes a reasonable update interval, matching `formatDuration`'s rules on when to display seconds */ function computeUpdateInterval(creationTimestampEpoch: number, compact: boolean): number { const seconds = Math.floor((Date.now() - creationTimestampEpoch) / 1000); diff --git a/packages/core/src/renderer/components/editable-list/editable-list.tsx b/packages/core/src/renderer/components/editable-list/editable-list.tsx index 7a11df7749..59164e607d 100644 --- a/packages/core/src/renderer/components/editable-list/editable-list.tsx +++ b/packages/core/src/renderer/components/editable-list/editable-list.tsx @@ -11,7 +11,7 @@ import React from "react"; import { Icon } from "../icon"; import type { InputProps, InputValidator } from "../input"; import { Input } from "../input"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import autoBindReact from "auto-bind/react"; export interface EditableListProps { @@ -23,7 +23,7 @@ export interface EditableListProps { // An optional prop used to convert T to a displayable string // defaults to `String` - renderItem?: (item: T, index: number) => React.ReactNode; + renderItem?: (item: T, index: number) => SafeReactNode; inputTheme?: InputProps["theme"]; } diff --git a/packages/core/src/renderer/components/file-picker/file-picker.tsx b/packages/core/src/renderer/components/file-picker/file-picker.tsx index df5ea38a0d..64c3acabf2 100644 --- a/packages/core/src/renderer/components/file-picker/file-picker.tsx +++ b/packages/core/src/renderer/components/file-picker/file-picker.tsx @@ -13,6 +13,7 @@ import { Spinner } from "../spinner"; import { observable, makeObservable } from "mobx"; import { observer } from "mobx-react"; import _ from "lodash"; +import type { SafeReactNode } from "@k8slens/utilities"; export interface FileUploadProps { uploadDir: string; @@ -48,7 +49,7 @@ export enum OverTotalSizeLimitStyle { export interface BaseProps { accept?: string; - label: React.ReactNode; + label: SafeReactNode; multiple?: boolean; // limit is the optional maximum number of files to upload @@ -220,7 +221,7 @@ class DefaultedFilePicker extends React.Component; diff --git a/packages/core/src/renderer/components/hotbar/hotbar-cell.tsx b/packages/core/src/renderer/components/hotbar/hotbar-cell.tsx index 29466a9de5..44c1f4c7e0 100644 --- a/packages/core/src/renderer/components/hotbar/hotbar-cell.tsx +++ b/packages/core/src/renderer/components/hotbar/hotbar-cell.tsx @@ -4,13 +4,12 @@ */ import "./hotbar-menu.scss"; -import type { HTMLAttributes, ReactNode } from "react"; import React, { useState } from "react"; - +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; -export interface HotbarCellProps extends HTMLAttributes { - children?: ReactNode; +export interface HotbarCellProps extends React.HTMLAttributes { + children?: SingleOrMany; index: number; innerRef?: React.Ref; } diff --git a/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx b/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx index b9ba43b711..166b7da18f 100644 --- a/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx +++ b/packages/core/src/renderer/components/hotbar/hotbar-menu.tsx @@ -8,7 +8,7 @@ import "./hotbar-menu.scss"; import React, { useState } from "react"; import { observer } from "mobx-react"; import { HotbarEntityIcon } from "./hotbar-entity-icon"; -import type { IClassName } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import type { CatalogEntityRegistry } from "../../api/catalog/entity/registry"; import type { CatalogEntity } from "../../api/catalog-entity"; @@ -147,7 +147,7 @@ const NonInjectedHotbarMenu = observer((props: Dependencies & HotbarMenuProps) = )} )} - {provided.placeholder} + {provided.placeholder as SafeReactNode} )} diff --git a/packages/core/src/renderer/components/hotbar/hotbar-selector.tsx b/packages/core/src/renderer/components/hotbar/hotbar-selector.tsx index c0ef3c54c7..a72428be07 100644 --- a/packages/core/src/renderer/components/hotbar/hotbar-selector.tsx +++ b/packages/core/src/renderer/components/hotbar/hotbar-selector.tsx @@ -88,7 +88,7 @@ const NonInjectedHotbarSelector = observer(({ targetId="hotbarIndex" preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]} > - {hotbar?.name} + {hotbar?.name.get()} , BaseIconProps {} +export interface IconProps extends React.HTMLAttributes, BaseIconProps { + children?: SingleOrMany; +} export function isSvg(content: string): boolean { // source code of the asset @@ -204,7 +206,7 @@ const RawIcon = (props: IconProps & Dependencies) => { onKeyDown?.(event); }; - let iconContent: ReactNode; + let iconContent: SafeReactNode; const iconProps: Partial = { className: cssNames("Icon", className, { svg, material, interactive: isInteractive, disabled, sticker, active, focusable }, diff --git a/packages/core/src/renderer/components/input/input.tsx b/packages/core/src/renderer/components/input/input.tsx index 5b0f10da5f..85e28d1396 100644 --- a/packages/core/src/renderer/components/input/input.tsx +++ b/packages/core/src/renderer/components/input/input.tsx @@ -7,7 +7,7 @@ import "./input.scss"; import type { DOMAttributes, InputHTMLAttributes, TextareaHTMLAttributes } from "react"; import React from "react"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { debouncePromise, isPromiseSettledFulfilled, cssNames } from "@k8slens/utilities"; import { Icon } from "../icon"; import type { TooltipProps } from "@k8slens/tooltip"; @@ -53,12 +53,12 @@ export interface IconDataFnArg { } /** - * One of the folloing: + * One of the following: * - A material icon name * - A react node * - Or a function that produces a react node */ -export type IconData = string | React.ReactNode | ((opt: IconDataFnArg) => React.ReactNode); +export type IconData = string | SafeReactNode | ((opt: IconDataFnArg) => SafeReactNode); export type InputProps = Omit & { theme?: "round-black" | "round"; @@ -74,7 +74,7 @@ export type InputProps = Omit & { showErrorsAsTooltip?: boolean | Omit; // show validation errors as a tooltip :hover (instead of block below) iconLeft?: IconData; iconRight?: IconData; - contentRight?: string | React.ReactNode; // Any component of string goes after iconRight + contentRight?: string | SafeReactNode; // Any component of string goes after iconRight validators?: SingleOrMany; blurOnEnter?: boolean; onChange?(value: string, evt: React.ChangeEvent): void; @@ -86,7 +86,7 @@ interface State { dirty: boolean; valid: boolean; validating: boolean; - errors: React.ReactNode[]; + errors: SafeReactNode[]; submitted: boolean; } @@ -171,8 +171,8 @@ export class Input extends React.Component { async validate() { const value = this.getValue(); let validationId = (this.validationId = ""); // reset every time for async validators - const asyncValidators: Promise[] = []; - const errors: React.ReactNode[] = []; + const asyncValidators: Promise[] = []; + const errors: SafeReactNode[] = []; // run validators for (const validator of this.validators) { @@ -192,6 +192,8 @@ export class Input extends React.Component { asyncValidators.push((async () => { try { await validator.validate(value, this.props); + + return undefined; } catch (error) { return this.getValidatorError(value, validator) || (error instanceof Error ? error.message : String(error)); } @@ -220,7 +222,7 @@ export class Input extends React.Component { this.input?.setCustomValidity(errors[0]?.toString() ?? ""); } - setValidation(errors: React.ReactNode[]) { + setValidation(errors: SafeReactNode[]) { this.setState({ validating: false, valid: !errors.length, @@ -432,7 +434,7 @@ export class Input extends React.Component { const componentId = id || showErrorsAsTooltip ? `input_tooltip_id_${uuid.v4()}` : undefined; - let tooltipError: React.ReactNode; + let tooltipError: SafeReactNode; if (showErrorsAsTooltip && showErrors) { const tooltipProps = typeof showErrorsAsTooltip === "object" ? showErrorsAsTooltip : {}; diff --git a/packages/core/src/renderer/components/input/input_validators.ts b/packages/core/src/renderer/components/input/input_validators.ts index 065b60db3f..31b6d8bff7 100644 --- a/packages/core/src/renderer/components/input/input_validators.ts +++ b/packages/core/src/renderer/components/input/input_validators.ts @@ -4,10 +4,10 @@ */ import type { InputProps } from "./input"; -import type React from "react"; import fse from "fs-extra"; import { TypedRegEx } from "typed-regex"; import type { SetRequired } from "type-fest"; +import type { SafeReactNode } from "@k8slens/utilities"; export type InputValidationResult = IsAsync extends true @@ -16,7 +16,7 @@ export type InputValidationResult = export type InputValidation = (value: string, props?: InputProps) => InputValidationResult; -export type SyncValidationMessage = React.ReactNode | ((value: string, props?: InputProps) => React.ReactNode); +export type SyncValidationMessage = SafeReactNode | ((value: string, props?: InputProps) => SafeReactNode); /** * @deprecated This type is not as type safe as it is possible to specify an async input validator without specifying a `debounce` time. diff --git a/packages/core/src/renderer/components/item-object-list/content.tsx b/packages/core/src/renderer/components/item-object-list/content.tsx index 279f3f6d4b..888585878d 100644 --- a/packages/core/src/renderer/components/item-object-list/content.tsx +++ b/packages/core/src/renderer/components/item-object-list/content.tsx @@ -5,7 +5,6 @@ import "./item-list-layout.scss"; -import type { ReactNode } from "react"; import React from "react"; import type { IComputedValue } from "mobx"; import { computed, makeObservable } from "mobx"; @@ -13,7 +12,7 @@ import { Observer, observer } from "mobx-react"; import type { ConfirmDialogParams } from "../confirm-dialog"; import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table"; import { Table, TableCell, TableHead, TableRow } from "../table"; -import type { IClassName } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode } from "@k8slens/utilities"; import { cssNames, isDefined, isReactNode, noop, prevDefault, stopPropagation } from "@k8slens/utilities"; import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import { AddRemoveButtons } from "../add-remove-buttons"; @@ -50,8 +49,8 @@ export interface ItemListLayoutContentProps; tableProps?: Partial>; // low-level table configuration renderTableHeader?: (TableCellProps | undefined | null)[]; - renderTableContents: (item: Item) => (ReactNode | TableCellProps)[]; - renderItemMenu?: (item: Item, store: ItemListStore) => ReactNode; + renderTableContents: (item: Item) => (SafeReactNode | TableCellProps)[]; + renderItemMenu?: (item: Item, store: ItemListStore) => SafeReactNode; customizeTableRowProps?: (item: Item) => Partial>; addRemoveButtons?: Partial; virtual?: boolean; @@ -71,7 +70,7 @@ export interface ItemListLayoutContentProps ReactNode); + | SafeReactNode + | (() => SafeReactNode); customizeHeader?: HeaderCustomizer | HeaderCustomizer[]; } diff --git a/packages/core/src/renderer/components/item-object-list/list-layout.tsx b/packages/core/src/renderer/components/item-object-list/list-layout.tsx index da6ac127e0..f612cbac06 100644 --- a/packages/core/src/renderer/components/item-object-list/list-layout.tsx +++ b/packages/core/src/renderer/components/item-object-list/list-layout.tsx @@ -5,13 +5,12 @@ import "./item-list-layout.scss"; -import type { ReactNode } from "react"; import React from "react"; import type { IComputedValue } from "mobx"; import { computed, makeObservable, untracked } from "mobx"; import type { ConfirmDialogParams } from "../confirm-dialog"; import type { TableCellProps, TableProps, TableRowProps, TableSortCallbacks } from "../table"; -import type { IClassName, SingleOrMany } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities"; import type { AddRemoveButtonsProps } from "../add-remove-buttons"; import type { ItemObject } from "@k8slens/list-layout"; @@ -39,10 +38,10 @@ export type ItemsFilter = (items: I[]) => I[]; export type ItemsFilters = Record>; export interface HeaderPlaceholders { - title?: ReactNode; + title?: SafeReactNode; searchProps?: SearchInputUrlProps; - filters?: ReactNode; - info?: ReactNode; + filters?: SafeReactNode; + info?: SafeReactNode; } function normalizeText(value: Primitive) { @@ -79,7 +78,7 @@ export type ItemListStore = export type RenderHeaderTitle< Item extends ItemObject, PreLoadStores extends boolean, -> = ReactNode | ((parent: NonInjectedItemListLayout) => ReactNode); +> = SafeReactNode | ((parent: NonInjectedItemListLayout) => SafeReactNode); export type HeaderCustomizer = (placeholders: HeaderPlaceholders) => HeaderPlaceholders; export type ItemListLayoutProps = { @@ -108,8 +107,8 @@ export type ItemListLayoutProps; tableProps?: Partial>; // low-level table configuration renderTableHeader?: (TableCellProps | undefined | null)[]; - renderTableContents: (item: Item) => (ReactNode | TableCellProps)[]; - renderItemMenu?: (item: Item, store: ItemListStore) => ReactNode; + renderTableContents: (item: Item) => (SafeReactNode | TableCellProps)[]; + renderItemMenu?: (item: Item, store: ItemListStore) => SafeReactNode; customizeTableRowProps?: (item: Item) => Partial>; addRemoveButtons?: Partial; virtual?: boolean; @@ -121,7 +120,7 @@ export type ItemListLayoutProps Partial; - renderFooter?: (parent: NonInjectedItemListLayout) => React.ReactNode; + renderFooter?: (parent: NonInjectedItemListLayout) => SafeReactNode; spinnerTestId?: string; @@ -130,7 +129,7 @@ export type ItemListLayoutProps; "data-testid"?: string; diff --git a/packages/core/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx b/packages/core/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx index e562900f64..adc086c78e 100644 --- a/packages/core/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx +++ b/packages/core/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx @@ -7,6 +7,7 @@ import styles from "./kubeconfig-dialog.module.scss"; import React from "react"; import type { IObservableValue } from "mobx"; import { observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Button } from "@k8slens/button"; import type { DialogProps } from "../dialog"; @@ -22,7 +23,7 @@ import kubeconfigDialogStateInjectable from "./state.injectable"; import { saveFileDialog } from "../../utils/saveFile"; export interface KubeconfigDialogData { - title?: React.ReactNode; + title?: SafeReactNode; config: string; } diff --git a/packages/core/src/renderer/components/kubeconfig-dialog/open.injectable.ts b/packages/core/src/renderer/components/kubeconfig-dialog/open.injectable.ts index 0d7321369b..050d124f1d 100644 --- a/packages/core/src/renderer/components/kubeconfig-dialog/open.injectable.ts +++ b/packages/core/src/renderer/components/kubeconfig-dialog/open.injectable.ts @@ -3,13 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import { getInjectable } from "@ogre-tools/injectable"; -import type React from "react"; import { loggerInjectionToken } from "@k8slens/logger"; import showCheckedErrorNotificationInjectable from "../notifications/show-checked-error.injectable"; import kubeconfigDialogStateInjectable from "./state.injectable"; +import type { SafeReactNode } from "@k8slens/utilities"; export interface OpenKubeconfigDialogArgs { - title?: React.ReactNode; + title?: SafeReactNode; loader: () => Promise; } @@ -29,8 +29,8 @@ const openKubeconfigDialogInjectable = getInjectable({ state.set({ title, config }); } catch (error) { - showCheckedErrorNotification(error, "Failed to retrive config for dialog"); - logger.warn("[KUBEOCONFIG-DIALOG]: failed to retrived config for dialog", error); + showCheckedErrorNotification(error, "Failed to retrieve config for dialog"); + logger.warn("[KUBECONFIG-DIALOG]: failed to retrieve config for dialog", error); } })(); }; diff --git a/packages/core/src/renderer/components/layout/cluster-page-menu.ts b/packages/core/src/renderer/components/layout/cluster-page-menu.ts index 1e38289ad0..bcd23c717b 100644 --- a/packages/core/src/renderer/components/layout/cluster-page-menu.ts +++ b/packages/core/src/renderer/components/layout/cluster-page-menu.ts @@ -7,12 +7,13 @@ import type { IconProps } from "../icon"; import type React from "react"; import type { PageTarget } from "../../routes/page-registration"; import type { IComputedValue } from "mobx"; +import type { SafeReactNode } from "@k8slens/utilities"; export interface ClusterPageMenuRegistration { id?: string; parentId?: string; target?: PageTarget; - title: React.ReactNode; + title: SafeReactNode; components: ClusterPageMenuComponents; visible?: IComputedValue; orderNumber?: number; diff --git a/packages/core/src/renderer/components/layout/main-layout.tsx b/packages/core/src/renderer/components/layout/main-layout.tsx index eeccb79a8f..ab83451c86 100755 --- a/packages/core/src/renderer/components/layout/main-layout.tsx +++ b/packages/core/src/renderer/components/layout/main-layout.tsx @@ -7,6 +7,7 @@ import styles from "./main-layout.module.scss"; import React from "react"; import { observer } from "mobx-react"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { ErrorBoundary } from "@k8slens/error-boundary"; import { ResizeDirection, ResizeGrowthDirection, ResizeSide, ResizingAnchor } from "@k8slens/resizing-anchor"; @@ -16,10 +17,10 @@ import sidebarStorageInjectable, { defaultSidebarWidth } from "./sidebar-storage import type { StorageLayer } from "../../utils/storage-helper"; export interface MainLayoutProps { - sidebar: React.ReactNode; + sidebar: SafeReactNode; className?: string; - footer?: React.ReactNode; - children?: React.ReactNode | React.ReactNode[]; + footer?: SafeReactNode; + children?: SingleOrMany; } /** diff --git a/packages/core/src/renderer/components/layout/setting-layout.tsx b/packages/core/src/renderer/components/layout/setting-layout.tsx index f2c4715aa2..3ce871c90e 100644 --- a/packages/core/src/renderer/components/layout/setting-layout.tsx +++ b/packages/core/src/renderer/components/layout/setting-layout.tsx @@ -7,7 +7,7 @@ import "./setting-layout.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { IClassName } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { CloseButton } from "./close-button"; import { getLegacyGlobalDiForExtensionApi } from "@k8slens/legacy-global-di"; @@ -19,7 +19,7 @@ export interface SettingLayoutProps extends React.DOMAttributes { contentClass?: IClassName; provideBackButtonNavigation?: boolean; contentGaps?: boolean; - navigation?: React.ReactNode; + navigation?: SafeReactNode; back?: (evt: React.MouseEvent | KeyboardEvent) => void; closeButtonProps?: { "data-testid"?: string }; } diff --git a/packages/core/src/renderer/components/layout/siblings-in-tab-layout.tsx b/packages/core/src/renderer/components/layout/siblings-in-tab-layout.tsx index 06e57227e9..891ebd4737 100644 --- a/packages/core/src/renderer/components/layout/siblings-in-tab-layout.tsx +++ b/packages/core/src/renderer/components/layout/siblings-in-tab-layout.tsx @@ -9,9 +9,10 @@ import React from "react"; import siblingTabsInjectable from "../../routes/sibling-tabs.injectable"; import { TabLayout } from "./tab-layout-2"; import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; +import type { SafeReactNode } from "@k8slens/utilities"; interface SiblingTabLayoutProps { - children: React.ReactNode; + children: SafeReactNode; scrollable?: boolean; } diff --git a/packages/core/src/renderer/components/layout/sidebar-items.injectable.ts b/packages/core/src/renderer/components/layout/sidebar-items.injectable.ts index e44b70528c..d31bf5f7c3 100644 --- a/packages/core/src/renderer/components/layout/sidebar-items.injectable.ts +++ b/packages/core/src/renderer/components/layout/sidebar-items.injectable.ts @@ -8,13 +8,14 @@ import { computed } from "mobx"; import { computedInjectManyInjectable } from "@ogre-tools/injectable-extension-for-mobx"; import { byOrderNumber } from "../../../common/utils/composable-responsibilities/orderable/orderable"; import type { SetRequired } from "type-fest"; +import type { SafeReactNode } from "@k8slens/utilities"; export interface SidebarItemRegistration { id: string; parentId: string | null; - title: React.ReactNode; + title: SafeReactNode; onClick: () => void; - getIcon?: () => React.ReactNode; + getIcon?: () => SafeReactNode; isActive?: IComputedValue; isVisible?: IComputedValue; orderNumber: number; diff --git a/packages/core/src/renderer/components/layout/sub-header.tsx b/packages/core/src/renderer/components/layout/sub-header.tsx index 7a782d1123..9fa1cd4939 100644 --- a/packages/core/src/renderer/components/layout/sub-header.tsx +++ b/packages/core/src/renderer/components/layout/sub-header.tsx @@ -5,14 +5,14 @@ import "./sub-header.scss"; import React from "react"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; export interface SubHeaderProps { className?: string; withLine?: boolean; // add bottom line compact?: boolean; // no extra padding around content - children: SingleOrMany; + children: SingleOrMany; } export class SubHeader extends React.Component { diff --git a/packages/core/src/renderer/components/layout/sub-title.tsx b/packages/core/src/renderer/components/layout/sub-title.tsx index 90ab8dc3e7..6489c42483 100644 --- a/packages/core/src/renderer/components/layout/sub-title.tsx +++ b/packages/core/src/renderer/components/layout/sub-title.tsx @@ -5,15 +5,15 @@ import "./sub-title.scss"; import React from "react"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; export interface SubTitleProps { className?: string; - title: React.ReactNode; + title: SafeReactNode; compact?: boolean; // no bottom padding id?: string; - children?: SingleOrMany; + children?: SingleOrMany; } export class SubTitle extends React.Component { diff --git a/packages/core/src/renderer/components/layout/tab-layout-2.tsx b/packages/core/src/renderer/components/layout/tab-layout-2.tsx index 62a545c22b..2247dccf4d 100644 --- a/packages/core/src/renderer/components/layout/tab-layout-2.tsx +++ b/packages/core/src/renderer/components/layout/tab-layout-2.tsx @@ -7,6 +7,7 @@ import "./tab-layout.scss"; import React from "react"; import { observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Tab, Tabs } from "../tabs"; import { ErrorBoundary } from "@k8slens/error-boundary"; @@ -14,7 +15,7 @@ import type { HierarchicalSidebarItem } from "./sidebar-items.injectable"; export interface TabLayoutProps { tabs?: HierarchicalSidebarItem[]; - children?: React.ReactNode; + children?: SafeReactNode; scrollable?: boolean; } diff --git a/packages/core/src/renderer/components/layout/tab-layout.tsx b/packages/core/src/renderer/components/layout/tab-layout.tsx index 50e692c7c1..c3b04fe944 100644 --- a/packages/core/src/renderer/components/layout/tab-layout.tsx +++ b/packages/core/src/renderer/components/layout/tab-layout.tsx @@ -5,11 +5,10 @@ import "./tab-layout.scss"; -import type { ReactNode } from "react"; import React from "react"; import { matchPath, Redirect, Route, Switch } from "react-router"; import { observer } from "mobx-react"; -import type { IClassName } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Tab, Tabs } from "../tabs"; import { ErrorBoundary } from "@k8slens/error-boundary"; @@ -23,13 +22,13 @@ export interface TabLayoutProps { className?: IClassName; contentClass?: IClassName; tabs?: TabLayoutRoute[]; - children?: ReactNode; + children?: SafeReactNode; scrollable?: boolean; } export interface TabLayoutRoute { routePath: string; - title: React.ReactNode; + title: SafeReactNode; component: React.ComponentType; url?: string; // page-url, if not provided `routePath` is used (doesn't work when path has some :placeholder(s)) exact?: boolean; // route-path matching rule diff --git a/packages/core/src/renderer/components/layout/wizard-layout.tsx b/packages/core/src/renderer/components/layout/wizard-layout.tsx index bcdb47f9c5..b76c1f57be 100644 --- a/packages/core/src/renderer/components/layout/wizard-layout.tsx +++ b/packages/core/src/renderer/components/layout/wizard-layout.tsx @@ -6,16 +6,16 @@ import "./wizard-layout.scss"; import React from "react"; import { observer } from "mobx-react"; -import type { IClassName } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; export interface WizardLayoutProps extends React.DOMAttributes { className?: IClassName; - header?: React.ReactNode; + header?: SafeReactNode; headerClass?: IClassName; contentClass?: IClassName; infoPanelClass?: IClassName; - infoPanel?: React.ReactNode; + infoPanel?: SafeReactNode; centered?: boolean; // Centering content horizontally } diff --git a/packages/core/src/renderer/components/line-progress/line-progress.tsx b/packages/core/src/renderer/components/line-progress/line-progress.tsx index 7ce47c10ca..d651a52cc3 100644 --- a/packages/core/src/renderer/components/line-progress/line-progress.tsx +++ b/packages/core/src/renderer/components/line-progress/line-progress.tsx @@ -5,6 +5,7 @@ import "./line-progress.scss"; import React from "react"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { withTooltip } from "@k8slens/tooltip"; @@ -14,6 +15,7 @@ export interface LineProgressProps extends React.HTMLProps { max?: number; className?: any; precise?: number; + children?: SingleOrMany; } function valuePercent({ value, min, max, precise }: Required>) { diff --git a/packages/core/src/renderer/components/list/list.tsx b/packages/core/src/renderer/components/list/list.tsx index dfdc042de2..572888ca68 100644 --- a/packages/core/src/renderer/components/list/list.tsx +++ b/packages/core/src/renderer/components/list/list.tsx @@ -9,13 +9,14 @@ import { SearchInput } from "../input"; import type { UseTableOptions } from "react-table"; import { ReactTable } from "../table/react-table"; +import type { SafeReactNode } from "@k8slens/utilities"; export type SearchFilter = (item: T) => string | number; export interface ListProps extends UseTableOptions { items: T[]; filters: SearchFilter[]; - title?: React.ReactNode; + title?: SafeReactNode; } export function List({ columns, data, title, items, filters }: ListProps) { diff --git a/packages/core/src/renderer/components/menu/menu-actions.tsx b/packages/core/src/renderer/components/menu/menu-actions.tsx index b482ec9ea6..1fbbde86ea 100644 --- a/packages/core/src/renderer/components/menu/menu-actions.tsx +++ b/packages/core/src/renderer/components/menu/menu-actions.tsx @@ -8,6 +8,7 @@ import "./menu-actions.scss"; import React, { isValidElement } from "react"; import { observable, makeObservable, reaction } from "mobx"; import { disposeOnUnmount, observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import type { IconProps } from "../icon"; import { Icon } from "../icon"; @@ -25,11 +26,11 @@ export interface MenuActionsProps extends Partial { className?: string; toolbar?: boolean; // display menu as toolbar with icons autoCloseOnSelect?: boolean; - triggerIcon?: string | (IconProps & TooltipDecoratorProps) | React.ReactNode; + triggerIcon?: string | (IconProps & TooltipDecoratorProps) | SafeReactNode; /** * @deprecated Provide your own remove `` as part of the `children` passed to this component */ - removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode); + removeConfirmationMessage?: SafeReactNode | (() => SafeReactNode); /** * @deprecated Provide your own update `` as part of the `children` passed to this component */ diff --git a/packages/core/src/renderer/components/menu/menu.tsx b/packages/core/src/renderer/components/menu/menu.tsx index 16e7c518a6..1fcca78925 100644 --- a/packages/core/src/renderer/components/menu/menu.tsx +++ b/packages/core/src/renderer/components/menu/menu.tsx @@ -5,9 +5,10 @@ import "./menu.scss"; -import type { ReactElement, ReactNode } from "react"; +import type { ReactElement } from "react"; import React, { Fragment } from "react"; import { createPortal } from "react-dom"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities"; import { Animate } from "../animate"; import type { IconProps } from "../icon"; @@ -48,7 +49,7 @@ export interface MenuProps { closeOnClickOutside?: boolean; // use false value for sub-menus closeOnScroll?: boolean; // applicable when usePortal={true} position?: MenuPosition; // applicable when usePortal={false} - children?: ReactNode; + children?: SafeReactNode; animated?: boolean; toggleEvent?: "click" | "contextmenu"; "data-testid"?: string; diff --git a/packages/core/src/renderer/components/namespaces/namespace-tree-view.test.tsx b/packages/core/src/renderer/components/namespaces/namespace-tree-view.test.tsx index 1882101605..1d081ec276 100644 --- a/packages/core/src/renderer/components/namespaces/namespace-tree-view.test.tsx +++ b/packages/core/src/renderer/components/namespaces/namespace-tree-view.test.tsx @@ -12,9 +12,10 @@ import { renderFor } from "../test-utils/renderFor"; import hierarchicalNamespacesInjectable from "./hierarchical-namespaces.injectable"; import { NamespaceTreeView } from "./namespace-tree-view"; import type { NamespaceTree } from "./store"; +import type { SafeReactNode } from "@k8slens/utilities"; jest.mock("react-router-dom", () => ({ - Link: ({ children }: { children: React.ReactNode }) => children, + Link: ({ children }: { children: SafeReactNode }) => children, })); function createNamespace(name: string, labels?: Record, annotations?: Record): Namespace { diff --git a/packages/core/src/renderer/components/no-items/no-items.tsx b/packages/core/src/renderer/components/no-items/no-items.tsx index 4c50fdfe57..c4bf7eccb5 100644 --- a/packages/core/src/renderer/components/no-items/no-items.tsx +++ b/packages/core/src/renderer/components/no-items/no-items.tsx @@ -6,12 +6,12 @@ import "./no-items.scss"; import React from "react"; -import type { IClassName } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; export interface NoItemsProps { className?: IClassName; - children?: React.ReactNode; + children?: SafeReactNode; } export function NoItems(props: NoItemsProps) { diff --git a/packages/core/src/renderer/components/pod-security-policies/pod-security-policy-details.tsx b/packages/core/src/renderer/components/pod-security-policies/pod-security-policy-details.tsx index 20ca217f50..a8b55ae0c3 100644 --- a/packages/core/src/renderer/components/pod-security-policies/pod-security-policy-details.tsx +++ b/packages/core/src/renderer/components/pod-security-policies/pod-security-policy-details.tsx @@ -15,6 +15,7 @@ import { Table, TableCell, TableHead, TableRow } from "../table"; import type { Logger } from "@k8slens/logger"; import { withInjectables } from "@ogre-tools/injectable-react"; import { loggerInjectionToken } from "@k8slens/logger"; +import type { SafeReactNode } from "@k8slens/utilities"; export interface PodSecurityPolicyDetailsProps extends KubeObjectDetailsProps { } @@ -33,7 +34,7 @@ interface Dependencies { @observer class NonInjectedPodSecurityPolicyDetails extends React.Component { - renderRuleGroup(title: React.ReactNode, group: RuleGroup | undefined) { + renderRuleGroup(title: SafeReactNode, group: RuleGroup | undefined) { if (!group) return null; const { rule, ranges } = group; diff --git a/packages/core/src/renderer/components/radio/radio.tsx b/packages/core/src/renderer/components/radio/radio.tsx index 3857d6ecbe..33ad29b659 100644 --- a/packages/core/src/renderer/components/radio/radio.tsx +++ b/packages/core/src/renderer/components/radio/radio.tsx @@ -5,7 +5,7 @@ import "./radio.scss"; import React, { useContext, useRef } from "react"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames, noop } from "@k8slens/utilities"; export interface RadioGroupProps { @@ -50,7 +50,7 @@ export function RadioGroup({ export interface RadioProps { className?: string; - label: React.ReactNode; + label: SafeReactNode; value: T; disabled?: boolean; } diff --git a/packages/core/src/renderer/components/render-delay/render-delay.tsx b/packages/core/src/renderer/components/render-delay/render-delay.tsx index 7d65b68071..eeadf20a1f 100644 --- a/packages/core/src/renderer/components/render-delay/render-delay.tsx +++ b/packages/core/src/renderer/components/render-delay/render-delay.tsx @@ -4,7 +4,7 @@ */ import React, { useEffect, useState } from "react"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import type { RequestIdleCallback } from "./request-idle-callback.injectable"; import type { CancelIdleCallback } from "./cancel-idle-callback.injectable"; import { withInjectables } from "@ogre-tools/injectable-react"; @@ -13,8 +13,8 @@ import requestIdleCallbackInjectable from "./request-idle-callback.injectable"; import idleCallbackTimeoutInjectable from "./idle-callback-timeout.injectable"; export interface RenderDelayProps { - placeholder?: React.ReactNode; - children: SingleOrMany; + placeholder?: SafeReactNode; + children: SingleOrMany; } interface Dependencies { diff --git a/packages/core/src/renderer/components/select/select.tsx b/packages/core/src/renderer/components/select/select.tsx index 891fa59aa4..0ed1e6be7a 100644 --- a/packages/core/src/renderer/components/select/select.tsx +++ b/packages/core/src/renderer/components/select/select.tsx @@ -14,6 +14,7 @@ import { observer } from "mobx-react"; import ReactSelect, { components, createFilter } from "react-select"; import type { Props as ReactSelectProps, GroupBase, MultiValue, OptionsOrGroups, PropsValue, SingleValue } from "react-select"; import type { LensTheme } from "../../themes/lens-theme"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { withInjectables } from "@ogre-tools/injectable-react"; import activeThemeInjectable from "../../themes/active.injectable"; @@ -23,7 +24,7 @@ const { Menu } = components; export interface SelectOption { value: Value; - label: React.ReactNode; + label: SafeReactNode; isDisabled?: boolean; isSelected?: boolean; id?: string; diff --git a/packages/core/src/renderer/components/status-bar/status-bar-registration.ts b/packages/core/src/renderer/components/status-bar/status-bar-registration.ts index 32e7c9c88e..8083df2f3f 100644 --- a/packages/core/src/renderer/components/status-bar/status-bar-registration.ts +++ b/packages/core/src/renderer/components/status-bar/status-bar-registration.ts @@ -3,6 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { SafeReactNode } from "@k8slens/utilities"; import type { IComputedValue } from "mobx"; /** @@ -34,10 +35,10 @@ export interface StatusBarRegistration { /** * @deprecated use {@link StatusBarRegistration.components} instead */ - item?: React.ReactNode | (() => React.ReactNode); + item?: SafeReactNode | (() => SafeReactNode); /** - * The newer API, allows for registering a component instead of a ReactNode + * The newer API, allows for registering a component instead of a SafeReactNode */ components?: StatusBarComponents; diff --git a/packages/core/src/renderer/components/status-brick/status-brick.tsx b/packages/core/src/renderer/components/status-brick/status-brick.tsx index 94e4e4e0c0..a329747334 100644 --- a/packages/core/src/renderer/components/status-brick/status-brick.tsx +++ b/packages/core/src/renderer/components/status-brick/status-brick.tsx @@ -6,10 +6,12 @@ import "./status-brick.scss"; import React from "react"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { withTooltip } from "@k8slens/tooltip"; export interface StatusBrickProps extends React.HTMLAttributes { + children?: SingleOrMany; } export const StatusBrick = withTooltip(({ className, ...elemProps }: StatusBrickProps) => ( diff --git a/packages/core/src/renderer/components/switch/form-switcher.tsx b/packages/core/src/renderer/components/switch/form-switcher.tsx index d2a069a771..24325380fd 100644 --- a/packages/core/src/renderer/components/switch/form-switcher.tsx +++ b/packages/core/src/renderer/components/switch/form-switcher.tsx @@ -3,17 +3,18 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { SafeReactNode } from "@k8slens/utilities"; import React from "react"; interface FormControlLabelProps { control: React.ReactElement; - label: React.ReactNode; + label: SafeReactNode; } /** * @deprecated Use instead from "../switch.tsx". */ -export function FormSwitch(props: FormControlLabelProps & { children?: React.ReactNode }) { +export function FormSwitch(props: FormControlLabelProps & { children?: SafeReactNode }) { const ClonedElement = React.cloneElement(props.control, { children: {props.label}, }); diff --git a/packages/core/src/renderer/components/switch/switcher.tsx b/packages/core/src/renderer/components/switch/switcher.tsx index e30e63ccf5..cf0c8ff786 100644 --- a/packages/core/src/renderer/components/switch/switcher.tsx +++ b/packages/core/src/renderer/components/switch/switcher.tsx @@ -3,12 +3,13 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { SafeReactNode } from "@k8slens/utilities"; import React from "react"; import { Switch } from "./switch"; export interface SwitcherProps { disabled?: boolean; - children?: React.ReactNode; + children?: SafeReactNode; checked?: boolean; onChange?: (event: React.ChangeEvent, checked: boolean) => void; name?: string; diff --git a/packages/core/src/renderer/components/table/table-cell.tsx b/packages/core/src/renderer/components/table/table-cell.tsx index 29f68f457a..0e2a8345d2 100644 --- a/packages/core/src/renderer/components/table/table-cell.tsx +++ b/packages/core/src/renderer/components/table/table-cell.tsx @@ -6,8 +6,8 @@ import "./table-cell.scss"; import type { TableSortBy, TableSortParams } from "./table"; -import type { ReactNode } from "react"; import React from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Icon } from "../icon"; import { Checkbox } from "../checkbox"; @@ -29,7 +29,7 @@ export interface TableCellProps extends React.DOMAttributes { /** * The actual value of the cell */ - title?: ReactNode; + title?: SafeReactNode; /** * content inside could be scrolled diff --git a/packages/core/src/renderer/components/table/table.tsx b/packages/core/src/renderer/components/table/table.tsx index 66eb21d524..8038542907 100644 --- a/packages/core/src/renderer/components/table/table.tsx +++ b/packages/core/src/renderer/components/table/table.tsx @@ -7,6 +7,7 @@ import "./table.scss"; import React from "react"; import { observer } from "mobx-react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames, isDefined } from "@k8slens/utilities"; import type { TableRowElem, TableRowProps } from "./table-row"; import { TableRow } from "./table-row"; @@ -86,7 +87,7 @@ export interface TableProps extends React.DOMAttributes { /** * This is shown when {@link TableProps.items} is empty */ - noItems?: React.ReactNode; + noItems?: SafeReactNode; /** * Allows to scroll list to selected item */ diff --git a/packages/core/src/renderer/components/tabs/tabs.tsx b/packages/core/src/renderer/components/tabs/tabs.tsx index d077304d88..f11401bb69 100644 --- a/packages/core/src/renderer/components/tabs/tabs.tsx +++ b/packages/core/src/renderer/components/tabs/tabs.tsx @@ -6,6 +6,7 @@ import "./tabs.scss"; import type { DOMAttributes } from "react"; import React from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Icon } from "../icon"; import autoBindReact from "auto-bind/react"; @@ -55,8 +56,8 @@ export interface TabProps extends DOMAttributes { className?: string; active?: boolean; disabled?: boolean; - icon?: React.ReactNode | string; // material-io name or custom icon - label?: React.ReactNode; + icon?: SafeReactNode | string; // material-io name or custom icon + label?: SafeReactNode; value: D; } diff --git a/packages/core/src/renderer/components/tree-view/tree-view.tsx b/packages/core/src/renderer/components/tree-view/tree-view.tsx index 9cd839a49a..a17ec8d826 100644 --- a/packages/core/src/renderer/components/tree-view/tree-view.tsx +++ b/packages/core/src/renderer/components/tree-view/tree-view.tsx @@ -6,6 +6,7 @@ import styles from "./tree-view.module.scss"; import type { MouseEventHandler } from "react"; import React, { useState } from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { Icon } from "../icon"; @@ -15,7 +16,7 @@ export interface TreeViewClasses { export interface TreeViewProps { classes?: TreeViewClasses; - children: React.ReactNode; + children: SafeReactNode; } export function TreeView(props: TreeViewProps) { diff --git a/packages/core/src/renderer/components/user-management/service-accounts/secret.tsx b/packages/core/src/renderer/components/user-management/service-accounts/secret.tsx index fdd993d5ca..b88ca0d93a 100644 --- a/packages/core/src/renderer/components/user-management/service-accounts/secret.tsx +++ b/packages/core/src/renderer/components/user-management/service-accounts/secret.tsx @@ -9,6 +9,7 @@ import moment from "moment"; import React from "react"; import type { Secret } from "@k8slens/kube-object"; +import type { SafeReactNode } from "@k8slens/utilities"; import { prevDefault } from "@k8slens/utilities"; import { Icon } from "../../icon"; @@ -22,7 +23,7 @@ interface State { interface RenderRowArgs { name: string; - value: React.ReactNode; + value: SafeReactNode; } export class ServiceAccountsSecret extends React.Component { diff --git a/packages/core/src/renderer/components/wizard/wizard.tsx b/packages/core/src/renderer/components/wizard/wizard.tsx index bd03921f88..2f74dd5c23 100755 --- a/packages/core/src/renderer/components/wizard/wizard.tsx +++ b/packages/core/src/renderer/components/wizard/wizard.tsx @@ -5,6 +5,7 @@ import "./wizard.scss"; import React from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import { cssNames, prevDefault } from "@k8slens/utilities"; import { Button } from "@k8slens/button"; import { Stepper } from "../stepper"; @@ -24,7 +25,7 @@ export interface WizardProps extends WizardCommonProps { className?: string; step?: number; title?: string; - header?: React.ReactNode; + header?: SafeReactNode; onChange?: (step: number) => void; children?: React.ReactElement>[] | React.ReactElement>; } @@ -107,28 +108,28 @@ export interface WizardStepProps extends WizardCommonProps { title?: string; className?: string | object; contentClass?: string | object; - customButtons?: React.ReactNode; // render custom buttons block in footer - moreButtons?: React.ReactNode; // add more buttons to section in the footer + customButtons?: SafeReactNode; // render custom buttons block in footer + moreButtons?: SafeReactNode; // add more buttons to section in the footer loading?: boolean; // indicator of loading content for the step waiting?: boolean; // indicator of waiting response before going to next step disabledNext?: boolean; // disable next button flag, e.g when filling step is not finished hideNextBtn?: boolean; hideBackBtn?: boolean; step?: number; - prevLabel?: React.ReactNode; // custom label for prev button - nextLabel?: React.ReactNode; // custom label for next button + prevLabel?: SafeReactNode; // custom label for prev button + nextLabel?: SafeReactNode; // custom label for next button next?: () => void | boolean | Promise; // custom action for next button prev?: () => void; // custom action for prev button first?: () => void; last?: () => void; isFirst?: () => boolean; isLast?: () => boolean; - beforeContent?: React.ReactNode; - afterContent?: React.ReactNode; + beforeContent?: SafeReactNode; + afterContent?: SafeReactNode; noValidate?: boolean; // no validate form attribute skip?: boolean; // don't render the step scrollable?: boolean; - children?: React.ReactNode | React.ReactNode[]; + children?: SafeReactNode | SafeReactNode[]; testIdForNext?: string; testIdForPrev?: string; } diff --git a/packages/infrastructure/webpack/package.json b/packages/infrastructure/webpack/package.json index 32ca3567ae..512106e610 100644 --- a/packages/infrastructure/webpack/package.json +++ b/packages/infrastructure/webpack/package.json @@ -47,6 +47,7 @@ "devDependencies": { "@async-fn/jest": "^1.6.4", "@k8slens/typescript": "^6.5.0-alpha.2", + "@ogre-tools/injectable-extension-for-auto-registration": "^15.9.0", "@ogre-tools/test-utils": "^15.8.1", "ts-node": "^10.9.1", "webpack-node-externals": "^3.0.0" diff --git a/packages/list-layout/package.json b/packages/list-layout/package.json index a931efebca..fa5d7ba2f7 100644 --- a/packages/list-layout/package.json +++ b/packages/list-layout/package.json @@ -33,6 +33,7 @@ }, "peerDependencies": { "@k8slens/kube-object": "^1.0.0-alpha.1", + "@k8slens/utilities": "^1.0.0-alpha.3", "@ogre-tools/injectable": "^15.8.1", "react": "^17.0.2" }, diff --git a/packages/list-layout/src/kube-list-layout-column.ts b/packages/list-layout/src/kube-list-layout-column.ts index 3816fa8d8b..cac41ccf3e 100644 --- a/packages/list-layout/src/kube-list-layout-column.ts +++ b/packages/list-layout/src/kube-list-layout-column.ts @@ -4,7 +4,7 @@ */ import type { KubeObject } from "@k8slens/kube-object"; -import type { ReactNode } from "react"; +import type { SafeReactNode } from "@k8slens/utilities"; import type { TableSortCallback, SearchFilter, TableCellProps } from "./list-layout-column"; export interface BaseKubeObjectListLayoutColumn { @@ -13,7 +13,7 @@ export interface BaseKubeObjectListLayoutColumn { sortingCallBack?: TableSortCallback; searchFilter?: SearchFilter; header: TableCellProps | undefined | null; - content: (item: K) => ReactNode | TableCellProps; + content: (item: K) => SafeReactNode | TableCellProps; } export interface GeneralKubeObjectListLayoutColumn extends BaseKubeObjectListLayoutColumn { diff --git a/packages/list-layout/src/list-layout-column.ts b/packages/list-layout/src/list-layout-column.ts index 7b614cc839..b5d581bf0c 100644 --- a/packages/list-layout/src/list-layout-column.ts +++ b/packages/list-layout/src/list-layout-column.ts @@ -3,8 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ReactNode } from "react"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; export interface ItemObject { getId: () => string; @@ -37,7 +36,7 @@ export interface TableCellProps extends React.DOMAttributes { /** * The actual value of the cell */ - title?: ReactNode; + title?: SafeReactNode; /** * content inside could be scrolled diff --git a/packages/technical-features/react-application/src/react-application/react-application-higher-order-component-injection-token.ts b/packages/technical-features/react-application/src/react-application/react-application-higher-order-component-injection-token.ts index cae1a6b468..8627c11580 100644 --- a/packages/technical-features/react-application/src/react-application/react-application-higher-order-component-injection-token.ts +++ b/packages/technical-features/react-application/src/react-application/react-application-higher-order-component-injection-token.ts @@ -1,8 +1,9 @@ +import type { SafeReactNode } from "@k8slens/utilities"; import { getInjectionToken } from "@ogre-tools/injectable"; import type React from "react"; export type ReactApplicationHigherOrderComponent = React.ComponentType<{ - children: React.ReactNode; + children: SafeReactNode; }>; export const reactApplicationHigherOrderComponentInjectionToken = diff --git a/packages/ui-components/button/src/button.tsx b/packages/ui-components/button/src/button.tsx index bf56033f93..7e186e20f1 100644 --- a/packages/ui-components/button/src/button.tsx +++ b/packages/ui-components/button/src/button.tsx @@ -6,11 +6,11 @@ import "./button.scss"; import type { ButtonHTMLAttributes } from "react"; import React from "react"; -import { cssNames } from "@k8slens/utilities"; +import { cssNames, SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { withTooltip } from "@k8slens/tooltip"; export interface ButtonProps extends ButtonHTMLAttributes { - label?: React.ReactNode; + label?: SafeReactNode; waiting?: boolean; primary?: boolean; accent?: boolean; @@ -23,6 +23,7 @@ export interface ButtonProps extends ButtonHTMLAttributes { round?: boolean; href?: string; // render as hyperlink target?: "_blank"; // in case of using @href + children?: SingleOrMany; } export const Button = withTooltip((props: ButtonProps) => { diff --git a/packages/ui-components/error-boundary/src/error-boundary.tsx b/packages/ui-components/error-boundary/src/error-boundary.tsx index a5f7c41beb..8b6f58f32f 100644 --- a/packages/ui-components/error-boundary/src/error-boundary.tsx +++ b/packages/ui-components/error-boundary/src/error-boundary.tsx @@ -9,7 +9,7 @@ import type { ErrorInfo } from "react"; import React from "react"; import { observer } from "mobx-react"; import { Button } from "@k8slens/button"; -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import type { ObservableHistory } from "mobx-observable-history"; import { withInjectables } from "@ogre-tools/injectable-react"; import { observableHistoryInjectionToken } from "@k8slens/routing"; @@ -18,7 +18,7 @@ const issuesTrackerUrl = "https://github.com/lensapp/lens/issues"; const forumsUrl = "https://forums.k8slens.dev"; export interface ErrorBoundaryProps { - children?: SingleOrMany; + children?: SingleOrMany; } interface State { diff --git a/packages/ui-components/tooltip/src/tooltip.tsx b/packages/ui-components/tooltip/src/tooltip.tsx index c2f80b4d05..686a391130 100644 --- a/packages/ui-components/tooltip/src/tooltip.tsx +++ b/packages/ui-components/tooltip/src/tooltip.tsx @@ -8,7 +8,7 @@ import "./tooltip.scss"; import React from "react"; import { createPortal } from "react-dom"; import { observer } from "mobx-react"; -import type { IClassName } from "@k8slens/utilities"; +import type { IClassName, SafeReactNode } from "@k8slens/utilities"; import { cssNames } from "@k8slens/utilities"; import { observable, makeObservable, action, runInAction } from "mobx"; import autoBindReact from "auto-bind/react"; @@ -35,7 +35,7 @@ export interface TooltipProps { className?: IClassName; formatters?: TooltipContentFormatters; style?: React.CSSProperties; - children?: React.ReactNode; + children?: SafeReactNode; "data-testid"?: string; } diff --git a/packages/ui-components/tooltip/src/withTooltip.test.tsx b/packages/ui-components/tooltip/src/withTooltip.test.tsx index 3f50c0a986..28a68bf81e 100644 --- a/packages/ui-components/tooltip/src/withTooltip.test.tsx +++ b/packages/ui-components/tooltip/src/withTooltip.test.tsx @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { SingleOrMany } from "@k8slens/utilities"; +import type { SafeReactNode, SingleOrMany } from "@k8slens/utilities"; import { render, RenderResult } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import React from "react"; @@ -12,7 +12,7 @@ import { withTooltip } from "./withTooltip"; type MyComponentProps = { text: string; id?: string; - children?: SingleOrMany; + children?: SingleOrMany; "data-testid"?: string; }; diff --git a/packages/ui-components/tooltip/src/withTooltip.tsx b/packages/ui-components/tooltip/src/withTooltip.tsx index 4b0d828b88..14617c9dec 100644 --- a/packages/ui-components/tooltip/src/withTooltip.tsx +++ b/packages/ui-components/tooltip/src/withTooltip.tsx @@ -3,23 +3,22 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -import type { ReactNode } from "react"; import React, { useState } from "react"; import type { TooltipProps } from "./tooltip"; import { Tooltip } from "./tooltip"; -import { isReactNode } from "@k8slens/utilities"; +import { isReactNode, SafeReactNode } from "@k8slens/utilities"; import uniqueId from "lodash/uniqueId"; import type { SingleOrMany } from "@k8slens/utilities"; export interface TooltipDecoratorProps { - tooltip?: ReactNode | Omit; + tooltip?: SafeReactNode | Omit; /** * forces tooltip to detect the target's parent for mouse events. This is * useful for displaying tooltips even when the target is "disabled" */ tooltipOverrideDisabled?: boolean; id?: string; - children?: SingleOrMany; + children?: SingleOrMany; } export function withTooltip( diff --git a/packages/utility-features/utilities/index.ts b/packages/utility-features/utilities/index.ts index dc8eacdfdd..59f90d3da5 100644 --- a/packages/utility-features/utilities/index.ts +++ b/packages/utility-features/utilities/index.ts @@ -17,7 +17,6 @@ export * from "./src/disposer"; export * from "./src/formatDuration"; export * from "./src/hash-set"; export * from "./src/interval"; -export * from "./src/is-node-falsy"; export * from "./src/isMiddleClick"; export * from "./src/isReactNode"; export * from "./src/iter"; diff --git a/packages/utility-features/utilities/src/is-node-falsy.ts b/packages/utility-features/utilities/src/is-node-falsy.ts deleted file mode 100644 index 0bc6477285..0000000000 --- a/packages/utility-features/utilities/src/is-node-falsy.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type React from "react"; - -/** - * Returns `true` if `node` is a falsy value - */ -export function isNodeFalsy(node: React.ReactNode): boolean { - return !isNodeRenderable(node); -} - -/** - * Return `true` if React would render this - */ -export function isNodeRenderable(node: React.ReactNode): boolean { - return Boolean(node); -} - -/** - * Returns the first react node provided that is would be rendered by react - */ -export function foldNodes(...nodes: React.ReactNode[]): React.ReactNode { - return nodes.find(isNodeRenderable); -} diff --git a/packages/utility-features/utilities/src/isReactNode.ts b/packages/utility-features/utilities/src/isReactNode.ts index a2c97c707a..2bf72462b8 100755 --- a/packages/utility-features/utilities/src/isReactNode.ts +++ b/packages/utility-features/utilities/src/isReactNode.ts @@ -3,12 +3,17 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ -// Type guard for checking valid react node to use in render -import type { ReactNode } from "react"; import React from "react"; import { isObject } from "./type-narrowing"; +import type { SingleOrMany } from "./types"; -export function isReactNode(node: unknown): node is ReactNode { +export type SafeReactNode = React.ReactElement | React.ReactText | boolean | null | undefined | Iterable; + +export function toSafeReactChildrenArray(children: SingleOrMany) { + return React.Children.toArray(children) as (Exclude)[]; +} + +export function isReactNode(node: unknown): node is SafeReactNode { return (isObject(node) && React.isValidElement(node)) || Array.isArray(node) && node.every(isReactNode) || node == null diff --git a/packages/utility-features/utilities/src/jsonPath.ts b/packages/utility-features/utilities/src/jsonPath.ts index 57fffbaea9..7bcf538952 100644 --- a/packages/utility-features/utilities/src/jsonPath.ts +++ b/packages/utility-features/utilities/src/jsonPath.ts @@ -95,7 +95,7 @@ export function formatJSONValue(value: unknown): string { /** * This function is a safer version of `JSONPath.value(obj, path)` with untrusted jsonpath strings * - * This function will also stringify the value retreived from the object + * This function will also stringify the value retrieved from the object */ export function safeJSONPathValue(obj: object, path: string): unknown { try {