You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
portainer/app/react/components/Icon.tsx

83 lines
1.9 KiB

import clsx from 'clsx';
import { ComponentType, ReactNode } from 'react';
import * as lucideIcons from 'lucide-react';
import { isValidElementType } from 'react-is';
import Svg, { SvgIcons } from './Svg';
export interface IconProps {
icon: ReactNode | ComponentType<unknown>;
}
export type IconMode =
| 'alt'
| 'primary'
| 'primary-alt'
| 'secondary'
| 'secondary-alt'
| 'warning'
| 'warning-alt'
| 'danger'
| 'danger-alt'
| 'success'
| 'success-alt';
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
interface Props {
icon: ReactNode | ComponentType<{ size?: string | number }>;
className?: string;
size?: IconSize;
mode?: IconMode;
spin?: boolean;
}
export function Icon({ icon, className, mode, size, spin }: Props) {
const classes = clsx(className, 'icon inline-flex', {
[`icon-${mode}`]: mode,
[`icon-${size}`]: size,
'animate-spin-slow': spin,
});
if (typeof icon !== 'string') {
const Icon = isValidElementType(icon) ? icon : null;
if (Icon) {
return <Icon className={classes} aria-hidden="true" role="img" />;
}
return (
<span className={classes} aria-hidden="true" role="img">
{icon}
</span>
);
}
if (icon.indexOf('svg-') === 0) {
const svgIcon = icon.replace('svg-', '');
return (
<Svg
icon={svgIcon as keyof typeof SvgIcons}
className={classes}
aria-hidden="true"
/>
);
}
const iconName = icon
.split('-')
.map((s) => s.slice(0, 1).toUpperCase() + s.slice(1))
.join('') as keyof typeof lucideIcons;
const IconComponent = lucideIcons[iconName] as React.FC<
React.SVGProps<SVGSVGElement>
>;
if (!IconComponent) {
// console error so that the error is logged but no functionality is broken
// eslint-disable-next-line no-console
console.error(`Icon not found: '${icon}'`);
return null;
}
return <IconComponent className={classes} aria-hidden="true" />;
}