1
0
mirror of https://github.com/lensapp/lens.git synced 2024-09-11 09:25:26 +03:00

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 <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-05-09 15:45:37 -04:00
parent f13a145a27
commit 9d51ef2aa6
84 changed files with 246 additions and 208 deletions

13
package-lock.json generated
View File

@ -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"
}

View File

@ -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<SafeReactNode>;
}
interface Dependencies {

View File

@ -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<SafeReactNode>;
}
export const KeyboardShortcutScope = ({ id, children }: KeyboardShortcutScopeProps) => (

View File

@ -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;
}

View File

@ -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<any> {
onAdd?: () => void;
onRemove?: () => void;
addTooltip?: React.ReactNode;
removeTooltip?: React.ReactNode;
addTooltip?: SafeReactNode;
removeTooltip?: SafeReactNode;
}
export class AddRemoveButtons extends React.PureComponent<AddRemoveButtonsProps> {

View File

@ -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<SafeReactNode>;
}
interface Dependencies {

View File

@ -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<HTMLImageElement>;
disabled?: boolean;
children?: SingleOrMany<React.ReactNode>;
children?: SingleOrMany<SafeReactNode>;
className?: string;
id?: string;
onClick?: MouseEventHandler<HTMLDivElement>;

View File

@ -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<HTMLDivElement> {
small?: boolean;
flat?: boolean;
label?: React.ReactNode;
label?: SafeReactNode;
expandable?: boolean;
disabled?: boolean;
scrollable?: boolean;
children?: SingleOrMany<SafeReactNode>;
}
// Common handler for all Badge instances

View File

@ -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(() => [
{

View File

@ -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) {

View File

@ -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[];

View File

@ -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<Hotbar | undefined>;
@ -19,8 +19,8 @@ interface Dependencies {
interface HotbarToggleMenuItemProps {
entity: CatalogEntity;
addContent: ReactNode;
removeContent: ReactNode;
addContent: SafeReactNode;
removeContent: SafeReactNode;
}
function NonInjectedHotbarToggleMenuItem({

View File

@ -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<HTMLInputElement>): void;
children?: SingleOrMany<React.ReactChild | React.ReactFragment>;
children?: SingleOrMany<SafeReactNode>;
}
export function Checkbox({ label, inline, className, value, children, onChange = noop, disabled, ...inputProps }: CheckboxProps) {

View File

@ -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<string>;
@ -93,7 +94,7 @@ class NonInjectedClusterView extends React.Component<Dependencies> {
]);
}
renderStatus(): React.ReactNode {
renderStatus(): SafeReactNode {
const { cluster, isReady } = this;
if (cluster && !isReady) {

View File

@ -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 = {

View File

@ -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<ButtonProps>;
cancelButtonProps?: Partial<ButtonProps>;
}

View File

@ -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<KubeO
crd?: CustomResourceDefinition;
}
function convertSpecValue(value: unknown): React.ReactNode {
function convertSpecValue(value: unknown): SafeReactNode {
if (Array.isArray(value)) {
return (
<ul>

View File

@ -106,7 +106,11 @@ class NonInjectedCustomResources extends React.Component<Dependencies> {
isNamespaced && (
<NamespaceSelectBadge namespace={customResource.getNs() as string} />
),
...extraColumns.map((column) => safeJSONPathValue(customResource, column.jsonPath)),
...(
extraColumns
.map((column) => safeJSONPathValue(customResource, column.jsonPath))
.map(formatJSONValue)
),
<KubeObjectAge key="age" object={customResource} />,
]}
failedToLoadMessage={(

View File

@ -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<SafeReactNode>;
}
interface DialogState {

View File

@ -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<DockTabModel> {
moreActions?: React.ReactNode;
moreActions?: SafeReactNode;
}
interface Dependencies {

View File

@ -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;

View File

@ -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<HTMLDivElement> {
name: React.ReactNode;
name: SafeReactNode;
title?: string;
labelsOnly?: boolean;
hidden?: boolean;

View File

@ -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<SafeReactNode>;
}
interface State {

View File

@ -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

View File

@ -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<React.ReactNode>;
toolbar?: SafeReactNode;
children?: SingleOrMany<SafeReactNode>;
"data-testid"?: string;
testIdForClose?: string;
}

View File

@ -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<HTMLDivElement> {
contentForToggle: React.ReactNode;
contentForToggle: SafeReactNode;
children?: SingleOrMany<SafeReactNode>;
}
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)}
</Menu>
</div>
);

View File

@ -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);

View File

@ -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<T> {
@ -23,7 +23,7 @@ export interface EditableListProps<T> {
// 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"];
}

View File

@ -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<FilePickerProps & typeof defau
);
}
getIconRight(): React.ReactNode {
getIconRight(): SafeReactNode {
switch (this.status) {
case FileInputStatus.PROCESSING:
return <Spinner />;

View File

@ -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<HTMLDivElement> {
children?: ReactNode;
export interface HotbarCellProps extends React.HTMLAttributes<HTMLDivElement> {
children?: SingleOrMany<SafeReactNode>;
index: number;
innerRef?: React.Ref<HTMLDivElement>;
}

View File

@ -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) =
)}
</Draggable>
)}
{provided.placeholder}
{provided.placeholder as SafeReactNode}
</HotbarCell>
)}
</Droppable>

View File

@ -88,7 +88,7 @@ const NonInjectedHotbarSelector = observer(({
targetId="hotbarIndex"
preferredPositions={[TooltipPosition.TOP, TooltipPosition.TOP_LEFT]}
>
{hotbar?.name}
{hotbar?.name.get()}
</Tooltip>
</div>
<Icon

View File

@ -5,7 +5,6 @@
import "./icon.scss";
import type { ReactNode } from "react";
import React, { createRef } from "react";
import { NavLink } from "react-router-dom";
import type { LocationDescriptor } from "history";
@ -37,6 +36,7 @@ import Workloads from "./workloads.svg";
import type { Logger } from "@k8slens/logger";
import { withInjectables } from "@ogre-tools/injectable-react";
import { loggerInjectionToken } from "@k8slens/logger";
import type { SingleOrMany, SafeReactNode } from "@k8slens/utilities";
const hrefValidation = /https?:\/\//;
@ -158,7 +158,9 @@ export interface BaseIconProps {
"data-testid"?: string;
}
export interface IconProps extends React.HTMLAttributes<any>, BaseIconProps {}
export interface IconProps extends React.HTMLAttributes<any>, BaseIconProps {
children?: SingleOrMany<SafeReactNode>;
}
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<IconProps> = {
className: cssNames("Icon", className,
{ svg, material, interactive: isInteractive, disabled, sticker, active, focusable },

View File

@ -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<InputElementProps, "onChange" | "onSubmit"> & {
theme?: "round-black" | "round";
@ -74,7 +74,7 @@ export type InputProps = Omit<InputElementProps, "onChange" | "onSubmit"> & {
showErrorsAsTooltip?: boolean | Omit<TooltipProps, "targetId">; // 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<InputValidator>;
blurOnEnter?: boolean;
onChange?(value: string, evt: React.ChangeEvent<InputElement>): 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<InputProps, State> {
async validate() {
const value = this.getValue();
let validationId = (this.validationId = ""); // reset every time for async validators
const asyncValidators: Promise<React.ReactNode>[] = [];
const errors: React.ReactNode[] = [];
const asyncValidators: Promise<SafeReactNode>[] = [];
const errors: SafeReactNode[] = [];
// run validators
for (const validator of this.validators) {
@ -192,6 +192,8 @@ export class Input extends React.Component<InputProps, State> {
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<InputProps, State> {
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<InputProps, State> {
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 : {};

View File

@ -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 boolean> =
IsAsync extends true
@ -16,7 +16,7 @@ export type InputValidationResult<IsAsync extends boolean> =
export type InputValidation<IsAsync extends boolean> = (value: string, props?: InputProps) => InputValidationResult<IsAsync>;
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.

View File

@ -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<Item extends ItemObject, PreLoadStor
sortingCallbacks?: TableSortCallbacks<Item>;
tableProps?: Partial<TableProps<Item>>; // low-level table configuration
renderTableHeader?: (TableCellProps | undefined | null)[];
renderTableContents: (item: Item) => (ReactNode | TableCellProps)[];
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => ReactNode;
renderTableContents: (item: Item) => (SafeReactNode | TableCellProps)[];
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => SafeReactNode;
customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>;
addRemoveButtons?: Partial<AddRemoveButtonsProps>;
virtual?: boolean;
@ -71,7 +70,7 @@ export interface ItemListLayoutContentProps<Item extends ItemObject, PreLoadStor
*
* @default "Failed to load items"
*/
failedToLoadMessage?: React.ReactNode;
failedToLoadMessage?: SafeReactNode;
}
interface Dependencies {

View File

@ -5,10 +5,9 @@
import "./item-list-layout.scss";
import type { ReactNode } from "react";
import React from "react";
import { observer } from "mobx-react";
import type { IClassName } from "@k8slens/utilities";
import type { IClassName, SafeReactNode } from "@k8slens/utilities";
import { cssNames, isDefined } from "@k8slens/utilities";
import type { ItemObject } from "@k8slens/list-layout";
import type { Filter } from "./page-filters/store";
@ -27,8 +26,8 @@ export interface ItemListLayoutHeaderProps<I extends ItemObject, PreLoadStores e
showHeader?: boolean;
headerClassName?: IClassName;
renderHeaderTitle?:
| ReactNode
| (() => ReactNode);
| SafeReactNode
| (() => SafeReactNode);
customizeHeader?: HeaderCustomizer | HeaderCustomizer[];
}

View File

@ -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<I extends ItemObject> = (items: I[]) => I[];
export type ItemsFilters<I extends ItemObject> = Record<string, ItemsFilter<I>>;
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<I extends ItemObject, PreLoadStores extends boolean> =
export type RenderHeaderTitle<
Item extends ItemObject,
PreLoadStores extends boolean,
> = ReactNode | ((parent: NonInjectedItemListLayout<Item, PreLoadStores>) => ReactNode);
> = SafeReactNode | ((parent: NonInjectedItemListLayout<Item, PreLoadStores>) => SafeReactNode);
export type HeaderCustomizer = (placeholders: HeaderPlaceholders) => HeaderPlaceholders;
export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends boolean = boolean> = {
@ -108,8 +107,8 @@ export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends b
sortingCallbacks?: TableSortCallbacks<Item>;
tableProps?: Partial<TableProps<Item>>; // low-level table configuration
renderTableHeader?: (TableCellProps | undefined | null)[];
renderTableContents: (item: Item) => (ReactNode | TableCellProps)[];
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => ReactNode;
renderTableContents: (item: Item) => (SafeReactNode | TableCellProps)[];
renderItemMenu?: (item: Item, store: ItemListStore<Item, PreLoadStores>) => SafeReactNode;
customizeTableRowProps?: (item: Item) => Partial<TableRowProps<Item>>;
addRemoveButtons?: Partial<AddRemoveButtonsProps>;
virtual?: boolean;
@ -121,7 +120,7 @@ export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends b
// other
customizeRemoveDialog?: (selectedItems: Item[]) => Partial<ConfirmDialogParams>;
renderFooter?: (parent: NonInjectedItemListLayout<Item, PreLoadStores>) => React.ReactNode;
renderFooter?: (parent: NonInjectedItemListLayout<Item, PreLoadStores>) => SafeReactNode;
spinnerTestId?: string;
@ -130,7 +129,7 @@ export type ItemListLayoutProps<Item extends ItemObject, PreLoadStores extends b
*
* @default "Failed to load items"
*/
failedToLoadMessage?: React.ReactNode;
failedToLoadMessage?: SafeReactNode;
filterCallbacks?: ItemsFilters<Item>;
"data-testid"?: string;

View File

@ -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;
}

View File

@ -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<string>;
}
@ -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);
}
})();
};

View File

@ -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<boolean>;
orderNumber?: number;

View File

@ -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<SafeReactNode>;
}
/**

View File

@ -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<any> {
contentClass?: IClassName;
provideBackButtonNavigation?: boolean;
contentGaps?: boolean;
navigation?: React.ReactNode;
navigation?: SafeReactNode;
back?: (evt: React.MouseEvent | KeyboardEvent) => void;
closeButtonProps?: { "data-testid"?: string };
}

View File

@ -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;
}

View File

@ -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<boolean>;
isVisible?: IComputedValue<boolean>;
orderNumber: number;

View File

@ -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<React.ReactNode>;
children: SingleOrMany<SafeReactNode>;
}
export class SubHeader extends React.Component<SubHeaderProps> {

View File

@ -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<React.ReactNode>;
children?: SingleOrMany<SafeReactNode>;
}
export class SubTitle extends React.Component<SubTitleProps> {

View File

@ -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;
}

View File

@ -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<any>;
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

View File

@ -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<any> {
className?: IClassName;
header?: React.ReactNode;
header?: SafeReactNode;
headerClass?: IClassName;
contentClass?: IClassName;
infoPanelClass?: IClassName;
infoPanel?: React.ReactNode;
infoPanel?: SafeReactNode;
centered?: boolean; // Centering content horizontally
}

View File

@ -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<HTMLDivElement> {
max?: number;
className?: any;
precise?: number;
children?: SingleOrMany<SafeReactNode>;
}
function valuePercent({ value, min, max, precise }: Required<Pick<LineProgressProps, "value" | "min" | "max" | "precise">>) {

View File

@ -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<T> = (item: T) => string | number;
export interface ListProps<T> extends UseTableOptions<any> {
items: T[];
filters: SearchFilter<T>[];
title?: React.ReactNode;
title?: SafeReactNode;
}
export function List<T>({ columns, data, title, items, filters }: ListProps<T>) {

View File

@ -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<MenuProps> {
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 `<MenuItem>` as part of the `children` passed to this component
*/
removeConfirmationMessage?: React.ReactNode | (() => React.ReactNode);
removeConfirmationMessage?: SafeReactNode | (() => SafeReactNode);
/**
* @deprecated Provide your own update `<MenuItem>` as part of the `children` passed to this component
*/

View File

@ -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;

View File

@ -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<string, string>, annotations?: Record<string, string>): Namespace {

View File

@ -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) {

View File

@ -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<PodSecurityPolicy> {
}
@ -33,7 +34,7 @@ interface Dependencies {
@observer
class NonInjectedPodSecurityPolicyDetails extends React.Component<PodSecurityPolicyDetailsProps & Dependencies> {
renderRuleGroup(title: React.ReactNode, group: RuleGroup | undefined) {
renderRuleGroup(title: SafeReactNode, group: RuleGroup | undefined) {
if (!group) return null;
const { rule, ranges } = group;

View File

@ -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<T> {
@ -50,7 +50,7 @@ export function RadioGroup<T>({
export interface RadioProps<T> {
className?: string;
label: React.ReactNode;
label: SafeReactNode;
value: T;
disabled?: boolean;
}

View File

@ -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<React.ReactNode>;
placeholder?: SafeReactNode;
children: SingleOrMany<SafeReactNode>;
}
interface Dependencies {

View File

@ -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: Value;
label: React.ReactNode;
label: SafeReactNode;
isDisabled?: boolean;
isSelected?: boolean;
id?: string;

View File

@ -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;

View File

@ -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<HTMLDivElement> {
children?: SingleOrMany<SafeReactNode>;
}
export const StatusBrick = withTooltip(({ className, ...elemProps }: StatusBrickProps) => (

View File

@ -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<any, any>;
label: React.ReactNode;
label: SafeReactNode;
}
/**
* @deprecated Use <Switch/> 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: <span>{props.label}</span>,
});

View File

@ -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<HTMLInputElement>, checked: boolean) => void;
name?: string;

View File

@ -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<HTMLDivElement> {
/**
* The actual value of the cell
*/
title?: ReactNode;
title?: SafeReactNode;
/**
* content inside could be scrolled

View File

@ -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<Item> extends React.DOMAttributes<HTMLDivElement> {
/**
* This is shown when {@link TableProps.items} is empty
*/
noItems?: React.ReactNode;
noItems?: SafeReactNode;
/**
* Allows to scroll list to selected item
*/

View File

@ -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<D> extends DOMAttributes<HTMLElement> {
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;
}

View File

@ -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) {

View File

@ -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<ServiceAccountsSecretProps, State> {

View File

@ -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<D> extends WizardCommonProps<D> {
className?: string;
step?: number;
title?: string;
header?: React.ReactNode;
header?: SafeReactNode;
onChange?: (step: number) => void;
children?: React.ReactElement<WizardStepProps<D>>[] | React.ReactElement<WizardStepProps<D>>;
}
@ -107,28 +108,28 @@ export interface WizardStepProps<D> extends WizardCommonProps<D> {
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<any>; // 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;
}

View File

@ -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"

View File

@ -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"
},

View File

@ -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<K extends KubeObject> {
@ -13,7 +13,7 @@ export interface BaseKubeObjectListLayoutColumn<K extends KubeObject> {
sortingCallBack?: TableSortCallback<K>;
searchFilter?: SearchFilter<K>;
header: TableCellProps | undefined | null;
content: (item: K) => ReactNode | TableCellProps;
content: (item: K) => SafeReactNode | TableCellProps;
}
export interface GeneralKubeObjectListLayoutColumn extends BaseKubeObjectListLayoutColumn<KubeObject> {

View File

@ -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<HTMLDivElement> {
/**
* The actual value of the cell
*/
title?: ReactNode;
title?: SafeReactNode;
/**
* content inside could be scrolled

View File

@ -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 =

View File

@ -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<any> {
label?: React.ReactNode;
label?: SafeReactNode;
waiting?: boolean;
primary?: boolean;
accent?: boolean;
@ -23,6 +23,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<any> {
round?: boolean;
href?: string; // render as hyperlink
target?: "_blank"; // in case of using @href
children?: SingleOrMany<SafeReactNode>;
}
export const Button = withTooltip((props: ButtonProps) => {

View File

@ -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<React.ReactNode>;
children?: SingleOrMany<SafeReactNode>;
}
interface State {

View File

@ -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;
}

View File

@ -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<React.ReactNode>;
children?: SingleOrMany<SafeReactNode>;
"data-testid"?: string;
};

View File

@ -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<TooltipProps, "targetId">;
tooltip?: SafeReactNode | Omit<TooltipProps, "targetId">;
/**
* 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<React.ReactNode>;
children?: SingleOrMany<SafeReactNode>;
}
export function withTooltip<TargetProps>(

View File

@ -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";

View File

@ -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);
}

View File

@ -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<SafeReactNode>;
export function toSafeReactChildrenArray(children: SingleOrMany<SafeReactNode>) {
return React.Children.toArray(children) as (Exclude<SafeReactNode, boolean | null | undefined>)[];
}
export function isReactNode(node: unknown): node is SafeReactNode {
return (isObject(node) && React.isValidElement(node))
|| Array.isArray(node) && node.every(isReactNode)
|| node == null

View File

@ -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 {