1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/icon/icon.tsx

261 lines
5.8 KiB
TypeScript

/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import "./icon.scss";
import type { ReactNode } from "react";
import React, { createRef } from "react";
import { NavLink } from "react-router-dom";
import type { LocationDescriptor } from "history";
import { cssNames } from "../../utils";
import { withTooltip } from "../tooltip";
import isNumber from "lodash/isNumber";
import Configuration from "./configuration.svg";
import Crane from "./crane.svg";
import Group from "./group.svg";
import Helm from "./helm.svg";
import Install from "./install.svg";
import Kube from "./kube.svg";
import LensLogo from "./lens-logo.svg";
import License from "./license.svg";
import LogoLens from "./logo-lens.svg";
import Logout from "./logout.svg";
import Nodes from "./nodes.svg";
import PushOff from "./push_off.svg";
import PushPin from "./push_pin.svg";
import Spinner from "./spinner.svg";
import Ssh from "./ssh.svg";
import Storage from "./storage.svg";
import Terminal from "./terminal.svg";
import Notice from "./notice.svg";
import User from "./user.svg";
import Users from "./users.svg";
import Wheel from "./wheel.svg";
import Workloads from "./workloads.svg";
/**
* Mapping between the local file names and the svgs
*
* Because we only really want a fixed list of bundled icons, this is safer so that consumers of
* `<Icon>` cannot pass in a `../some/path`.
*/
const localSvgIcons = new Map([
["configuration", Configuration],
["crane", Crane],
["group", Group],
["helm", Helm],
["install", Install],
["kube", Kube],
["lens-logo", LensLogo],
["license", License],
["logo-lens", LogoLens],
["logout", Logout],
["nodes", Nodes],
["push_off", PushOff],
["push_pin", PushPin],
["spinner", Spinner],
["ssh", Ssh],
["storage", Storage],
["terminal", Terminal],
["notice", Notice],
["user", User],
["users", Users],
["wheel", Wheel],
["workloads", Workloads],
]);
export interface BaseIconProps {
/**
* One of the names from https://material.io/icons/
*/
material?: string;
/**
* Either an SVG XML or one of the following names
* - configuration
* - crane
* - group
* - helm
* - install
* - kube
* - lens-logo
* - license
* - logo-lens
* - logout
* - nodes
* - push_off
* - push_pin
* - spinner
* - ssh
* - storage
* - terminal
* - user
* - users
* - wheel
* - workloads
*/
svg?: string;
/**
* render icon as NavLink from react-router-dom
*/
link?: LocationDescriptor;
/**
* render icon as hyperlink
*/
href?: string;
/**
* The icon size (css units)
*/
size?: string | number;
/**
* A pre-defined icon-size
*/
small?: boolean;
/**
* A pre-defined icon-size
*/
smallest?: boolean;
/**
* A pre-defined icon-size
*/
big?: boolean;
/**
* apply active-state styles
*/
active?: boolean;
/**
* indicates that icon is interactive and highlight it on focus/hover
*/
interactive?: boolean;
/**
* Allow focus to the icon to show `.active` styles. Only applicable if {@link IconProps.interactive} is `true`.
*
* @default true
*/
focusable?: boolean;
sticker?: boolean;
disabled?: boolean;
}
export interface IconProps extends React.HTMLAttributes<any>, BaseIconProps {}
export function isSvg(content: string): boolean {
// source code of the asset
return String(content).includes("<svg");
}
const RawIcon = withTooltip((props: IconProps) => {
const ref = createRef<HTMLAnchorElement>();
const {
// skip passing props to icon's html element
className, href, link, material, svg, size, smallest, small, big,
disabled, sticker, active,
focusable = true,
children,
interactive, onClick, onKeyDown,
...elemProps
} = props;
const isInteractive = interactive ?? !!(onClick || href || link);
const boundOnClick = (event: React.MouseEvent) => {
if (!disabled) {
onClick?.(event);
}
};
const boundOnKeyDown = (event: React.KeyboardEvent<any>) => {
switch (event.nativeEvent.code) {
case "Space":
// fallthrough
case "Enter": {
ref.current?.click();
event.preventDefault();
break;
}
}
onKeyDown?.(event);
};
let iconContent: ReactNode;
const iconProps: Partial<IconProps> = {
className: cssNames("Icon", className,
{ svg, material, interactive: isInteractive, disabled, sticker, active, focusable },
!size ? { smallest, small, big } : {},
),
onClick: isInteractive ? boundOnClick : undefined,
onKeyDown: isInteractive ? boundOnKeyDown : undefined,
tabIndex: isInteractive && focusable && !disabled ? 0 : undefined,
style: size ? { "--size": size + (isNumber(size) ? "px" : "") } as React.CSSProperties : undefined,
...elemProps,
};
// render as inline svg-icon
if (typeof svg === "string") {
const svgIconText = isSvg(svg)
? svg
: localSvgIcons.get(svg) ?? "";
iconContent = (
<span
className="icon"
dangerouslySetInnerHTML={{ __html: svgIconText }}
/>
);
}
// render as material-icon
if (typeof material === "string") {
iconContent = <span className="icon" data-icon-name={material}>{material}</span>;
}
// wrap icon's content passed from decorator
iconProps.children = (
<>
{iconContent}
{children}
</>
);
// render icon type
if (link) {
const { className, children } = iconProps;
return (
<NavLink
className={className}
to={link}
ref={ref}
>
{children}
</NavLink>
);
}
if (href) {
return (
<a
{...iconProps}
href={href}
ref={ref}
/>
);
}
return <i {...iconProps} ref={ref} />;
});
export const Icon = Object.assign(RawIcon, { isSvg });