mirror of
https://github.com/portainer/portainer.git
synced 2025-07-25 08:19:40 +02:00
refactor(icons): replace fa icons [EE-4459] (#7907)
refactor(icons): remove fontawesome EE-4459 refactor(icon) replace feather with lucide EE-4472
This commit is contained in:
parent
9dfac98a26
commit
d78b762f7b
498 changed files with 2102 additions and 2817 deletions
|
@ -1,11 +1,13 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Briefcase } from 'react-feather';
|
||||
import { Briefcase } from 'lucide-react';
|
||||
|
||||
import './BEFeatureIndicator.css';
|
||||
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import { getFeatureDetails } from './utils';
|
||||
|
||||
export interface Props {
|
||||
|
@ -33,8 +35,8 @@ export function BEFeatureIndicator({
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
{children}
|
||||
{showIcon && <Briefcase className="icon icon-sm vertical-center" />}
|
||||
<span className="be-indicator-label break-words space-left">
|
||||
{showIcon && <Icon icon={Briefcase} className="mr-1 be-indicator-icon" />}
|
||||
<span className="be-indicator-label break-words">
|
||||
Business Edition Feature
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
@ -29,7 +29,7 @@ function Template({
|
|||
size?: BadgeSize;
|
||||
icon: string;
|
||||
}) {
|
||||
return <BadgeIcon icon={icon} size={size} featherIcon />;
|
||||
return <BadgeIcon icon={icon} size={size} />;
|
||||
}
|
||||
|
||||
export const Example = Template.bind({});
|
|
@ -8,7 +8,7 @@ export interface Props extends IconProps {
|
|||
size?: BadgeSize;
|
||||
}
|
||||
|
||||
export function BadgeIcon({ icon, featherIcon, size = '3xl' }: Props) {
|
||||
export function BadgeIcon({ icon, size = '3xl' }: Props) {
|
||||
const sizeClasses = iconSizeToClasses(size);
|
||||
return (
|
||||
<div
|
||||
|
@ -22,7 +22,7 @@ export function BadgeIcon({ icon, featherIcon, size = '3xl' }: Props) {
|
|||
`
|
||||
)}
|
||||
>
|
||||
<Icon icon={icon} feather={featherIcon} className="feather !flex" />
|
||||
<Icon icon={icon} className="!flex" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Meta } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
import { User } from 'lucide-react';
|
||||
|
||||
import { init as initFeatureService } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
import { Edition, FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
@ -21,14 +22,14 @@ function Example() {
|
|||
const options: BoxSelectorOption<number>[] = [
|
||||
{
|
||||
description: 'description 1',
|
||||
icon: 'fa fa-rocket',
|
||||
icon: User,
|
||||
id: '1',
|
||||
value: 3,
|
||||
label: 'option 1',
|
||||
},
|
||||
{
|
||||
description: 'description 2',
|
||||
icon: 'fa fa-rocket',
|
||||
icon: User,
|
||||
id: '2',
|
||||
value: 4,
|
||||
label: 'option 2',
|
||||
|
@ -53,14 +54,14 @@ function LimitedFeature() {
|
|||
const options: BoxSelectorOption<number>[] = [
|
||||
{
|
||||
description: 'description 1',
|
||||
icon: 'fa fa-rocket',
|
||||
icon: User,
|
||||
id: '1',
|
||||
value: 3,
|
||||
label: 'option 1',
|
||||
},
|
||||
{
|
||||
description: 'description 2',
|
||||
icon: 'fa fa-rocket',
|
||||
icon: User,
|
||||
id: '2',
|
||||
value: 4,
|
||||
label: 'option 2',
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { Rocket } from 'lucide-react';
|
||||
|
||||
import { render, fireEvent } from '@/react-tools/test-utils';
|
||||
|
||||
import { BoxSelector, Props } from './BoxSelector';
|
||||
|
@ -23,14 +25,14 @@ test('should render with the initial value selected and call onChange when click
|
|||
const options: BoxSelectorOption<number>[] = [
|
||||
{
|
||||
description: 'description 1',
|
||||
icon: 'fa fa-rocket',
|
||||
icon: Rocket,
|
||||
id: '1',
|
||||
value: 3,
|
||||
label: 'option 1',
|
||||
},
|
||||
{
|
||||
description: 'description 2',
|
||||
icon: 'fa fa-rocket',
|
||||
icon: Rocket,
|
||||
id: '2',
|
||||
value: 4,
|
||||
label: 'option 2',
|
||||
|
|
|
@ -13,11 +13,6 @@
|
|||
color: var(--text-boxselector-header);
|
||||
}
|
||||
|
||||
.boxselector_header .fa,
|
||||
.fab {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.boxselector_wrapper input[type='radio'],
|
||||
.box-selector-item input[type='radio'] {
|
||||
display: none;
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { Meta } from '@storybook/react';
|
||||
import { User } from 'lucide-react';
|
||||
|
||||
import { init as initFeatureService } from '@/react/portainer/feature-flags/feature-flags.service';
|
||||
import { Edition, FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
import { IconProps } from '@@/Icon';
|
||||
|
||||
import { BoxSelectorItem } from './BoxSelectorItem';
|
||||
import { BoxSelectorOption } from './types';
|
||||
|
||||
|
@ -11,7 +14,7 @@ const meta: Meta = {
|
|||
args: {
|
||||
selected: false,
|
||||
description: 'description',
|
||||
icon: 'fa-rocket',
|
||||
icon: User,
|
||||
label: 'label',
|
||||
},
|
||||
};
|
||||
|
@ -21,7 +24,7 @@ export default meta;
|
|||
interface ExampleProps {
|
||||
selected?: boolean;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
icon?: IconProps['icon'];
|
||||
label?: string;
|
||||
feature?: FeatureId;
|
||||
}
|
||||
|
@ -35,7 +38,7 @@ function Template({
|
|||
}: ExampleProps) {
|
||||
const option: BoxSelectorOption<number> = {
|
||||
description,
|
||||
icon: `fa ${icon}`,
|
||||
icon,
|
||||
id: 'id',
|
||||
label,
|
||||
value: 1,
|
||||
|
|
|
@ -54,7 +54,6 @@ export function BoxSelectorItem<T extends number | string>({
|
|||
{!!option.icon && (
|
||||
<Icon
|
||||
icon={option.icon}
|
||||
feather={option.featherIcon}
|
||||
className="boxselector_icon !flex items-center"
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ReactTooltip from 'react-tooltip';
|
||||
import { HelpCircle } from 'react-feather';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
|
||||
import { FeatureId } from '@/react/portainer/feature-flags/enums';
|
||||
|
||||
|
@ -21,7 +21,7 @@ export function LimitedToBeIndicator({ tooltipId, featureId }: Props) {
|
|||
<span className="text-warning-9">Pro Feature</span>
|
||||
</a>
|
||||
<HelpCircle
|
||||
className="feather !text-warning-7"
|
||||
className="lucide !text-warning-7"
|
||||
data-tip
|
||||
data-for={tooltipId}
|
||||
tooltip-append-to-body="true"
|
||||
|
|
|
@ -2,15 +2,15 @@ import { Icon, IconProps } from '@@/Icon';
|
|||
|
||||
type Props = IconProps;
|
||||
|
||||
export function LogoIcon({ icon, featherIcon }: Props) {
|
||||
export function LogoIcon({ icon }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
text-6xl h-14 w-14
|
||||
inline-flex items-center justify-center
|
||||
inline-flex items-center justify-center
|
||||
`}
|
||||
>
|
||||
<Icon icon={icon} feather={featherIcon} className="feather !flex" />
|
||||
<Icon icon={icon} className="!flex" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Edit, FileText, Globe, Upload } from 'react-feather';
|
||||
import { Edit, FileText, Globe, Upload } from 'lucide-react';
|
||||
|
||||
import GitIcon from '@/assets/ico/git.svg?c';
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import clsx from 'clsx';
|
||||
import { Check, Copy } from 'lucide-react';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
import { useCopy } from '@@/buttons/CopyButton/useCopy';
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import styles from './Code.module.css';
|
||||
|
||||
|
@ -19,12 +20,10 @@ export function Code({ children, showCopyButton }: Props) {
|
|||
|
||||
{showCopyButton && (
|
||||
<Button color="link" className={styles.copyButton} onClick={handleCopy}>
|
||||
<i
|
||||
className={clsx(
|
||||
'fa',
|
||||
copiedSuccessfully ? 'fa-check green-icon' : 'fa-copy '
|
||||
)}
|
||||
aria-hidden="true"
|
||||
<Icon
|
||||
icon={copiedSuccessfully ? Check : Copy}
|
||||
className="!ml-1"
|
||||
mode={copiedSuccessfully ? 'success' : undefined}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { Meta, Story } from '@storybook/react';
|
||||
import { List } from 'lucide-react';
|
||||
|
||||
import { Link } from '@@/Link';
|
||||
import { IconProps } from '@@/Icon';
|
||||
|
||||
import { DashboardItem } from './DashboardItem';
|
||||
|
||||
|
@ -12,7 +14,7 @@ export default meta;
|
|||
|
||||
interface StoryProps {
|
||||
value: number;
|
||||
icon: string;
|
||||
icon: IconProps['icon'];
|
||||
type: string;
|
||||
}
|
||||
|
||||
|
@ -23,21 +25,21 @@ function Template({ value, icon, type }: StoryProps) {
|
|||
export const Primary: Story<StoryProps> = Template.bind({});
|
||||
Primary.args = {
|
||||
value: 1,
|
||||
icon: 'fa fa-th-list',
|
||||
icon: List,
|
||||
type: 'Example resource',
|
||||
};
|
||||
|
||||
export function WithLink() {
|
||||
return (
|
||||
<Link to="example.page">
|
||||
<DashboardItem value={1} icon="fa fa-th-list" type="Example resource" />
|
||||
<DashboardItem value={1} icon={List} type="Example resource" />
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export function WithChildren() {
|
||||
return (
|
||||
<DashboardItem value={1} icon="fa fa-th-list" type="Example resource">
|
||||
<DashboardItem value={1} icon={List} type="Example resource">
|
||||
<div>Children</div>
|
||||
</DashboardItem>
|
||||
);
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { User } from 'lucide-react';
|
||||
|
||||
import { render } from '@/react-tools/test-utils';
|
||||
|
||||
import { DashboardItem } from './DashboardItem';
|
||||
|
@ -11,7 +13,7 @@ test('should show provided resource value', async () => {
|
|||
});
|
||||
|
||||
test('should show provided resource type', async () => {
|
||||
const { getByLabelText } = renderComponent(0, '', 'Test');
|
||||
const { getByLabelText } = renderComponent(0, User, 'Test');
|
||||
const title = getByLabelText('resourceType');
|
||||
|
||||
expect(title).toBeVisible();
|
||||
|
@ -19,11 +21,11 @@ test('should show provided resource type', async () => {
|
|||
});
|
||||
|
||||
test('should have accessibility label created from the provided resource type', async () => {
|
||||
const { getByLabelText } = renderComponent(0, '', 'testLabel');
|
||||
const { getByLabelText } = renderComponent(0, User, 'testLabel');
|
||||
|
||||
expect(getByLabelText('testLabel')).toBeTruthy();
|
||||
});
|
||||
|
||||
function renderComponent(value = 0, icon = '', type = '') {
|
||||
function renderComponent(value = 0, icon = User, type = '') {
|
||||
return render(<DashboardItem value={value} icon={icon} type={type} />);
|
||||
}
|
||||
|
|
|
@ -10,13 +10,7 @@ interface Props extends IconProps {
|
|||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function DashboardItem({
|
||||
value,
|
||||
icon,
|
||||
type,
|
||||
children,
|
||||
featherIcon,
|
||||
}: Props) {
|
||||
export function DashboardItem({ value, icon, type, children }: Props) {
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
|
@ -35,7 +29,7 @@ export function DashboardItem({
|
|||
'th-highcontrast:bg-blue-3 th-highcontrast:text-blue-8'
|
||||
)}
|
||||
>
|
||||
<Icon icon={icon} feather={featherIcon} className="feather" />
|
||||
<Icon icon={icon} />
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col justify-around">
|
||||
|
|
|
@ -9,8 +9,6 @@ interface Props {
|
|||
alt?: string;
|
||||
size?: BadgeSize;
|
||||
className?: string;
|
||||
// additional fallback badge props
|
||||
feather?: boolean;
|
||||
}
|
||||
|
||||
export function FallbackImage({
|
||||
|
@ -19,7 +17,6 @@ export function FallbackImage({
|
|||
alt,
|
||||
size,
|
||||
className,
|
||||
feather,
|
||||
}: Props) {
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
|
@ -39,5 +36,5 @@ export function FallbackImage({
|
|||
}
|
||||
|
||||
// fallback icon if there is an error loading the image
|
||||
return <BadgeIcon icon={fallbackIcon} featherIcon={feather} size={size} />;
|
||||
return <BadgeIcon icon={fallbackIcon} size={size} />;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
import clsx from 'clsx';
|
||||
import { ComponentType, ReactNode } from 'react';
|
||||
import * as featherIcons from 'react-feather';
|
||||
import * as lucideIcons from 'lucide-react';
|
||||
import { isValidElementType } from 'react-is';
|
||||
|
||||
import Svg, { SvgIcons } from './Svg';
|
||||
|
||||
export interface IconProps {
|
||||
icon: ReactNode | ComponentType<unknown>;
|
||||
featherIcon?: boolean;
|
||||
}
|
||||
|
||||
export type IconMode =
|
||||
|
@ -27,16 +26,15 @@ export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|||
|
||||
interface Props {
|
||||
icon: ReactNode | ComponentType<{ size?: string | number }>;
|
||||
feather?: boolean;
|
||||
className?: string;
|
||||
size?: IconSize;
|
||||
mode?: IconMode;
|
||||
}
|
||||
|
||||
export function Icon({ icon, feather, className, mode, size }: Props) {
|
||||
export function Icon({ icon, className, mode, size }: Props) {
|
||||
const classes = clsx(
|
||||
className,
|
||||
'icon',
|
||||
'icon inline-flex',
|
||||
{ [`icon-${mode}`]: mode },
|
||||
{ [`icon-${size}`]: size }
|
||||
);
|
||||
|
@ -44,9 +42,13 @@ export function Icon({ icon, feather, className, mode, size }: Props) {
|
|||
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 == null ? <>{icon}</> : <Icon size="1em" />}
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
@ -56,28 +58,25 @@ export function Icon({ icon, feather, className, mode, size }: Props) {
|
|||
return (
|
||||
<Svg
|
||||
icon={svgIcon as keyof typeof SvgIcons}
|
||||
className={clsx(classes, '!flex')}
|
||||
className={classes}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (feather) {
|
||||
const iconName = icon
|
||||
.split('-')
|
||||
.map((s) => s.slice(0, 1).toUpperCase() + s.slice(1))
|
||||
.join('') as keyof typeof featherIcons;
|
||||
const IconComponent = featherIcons[iconName];
|
||||
if (!IconComponent) {
|
||||
throw new Error(`Feather icon not found: ${iconName}`);
|
||||
}
|
||||
return <IconComponent className={classes} />;
|
||||
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 (
|
||||
<i
|
||||
className={clsx(icon.startsWith('fa-') ? `fa ${icon}` : icon, classes)}
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
/>
|
||||
);
|
||||
return <IconComponent className={classes} aria-hidden="true" />;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { Widget, WidgetBody } from './Widget';
|
||||
import { Button } from './buttons';
|
||||
|
@ -32,8 +33,8 @@ export function InformationPanel({
|
|||
style={{ float: 'right' }}
|
||||
ng-if="dismissAction"
|
||||
>
|
||||
<Button color="link" onClick={() => onDismiss()}>
|
||||
<i className="fa fa-times" /> dismiss
|
||||
<Button color="link" icon={X} onClick={() => onDismiss()}>
|
||||
dismiss
|
||||
</Button>
|
||||
</span>
|
||||
)}
|
||||
|
|
|
@ -4,7 +4,7 @@ import { ReactNode } from 'react';
|
|||
import styles from './NavTabs.module.css';
|
||||
|
||||
export interface Option<T extends string | number = string> {
|
||||
label: string | ReactNode;
|
||||
label: ReactNode;
|
||||
children?: ReactNode;
|
||||
id: T;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { HelpCircle } from 'react-feather';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { getDocURL } from '@@/PageHeader/ContextHelp/docURLs';
|
||||
|
@ -24,7 +24,7 @@ export function ContextHelp() {
|
|||
)}
|
||||
title="Help"
|
||||
>
|
||||
<HelpCircle className="feather" onClick={onHelpClick} />
|
||||
<HelpCircle className="lucide" onClick={onHelpClick} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -9,6 +9,7 @@ import { UISrefProps, useSref } from '@uirouter/react';
|
|||
import Moment from 'moment';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useStore } from 'zustand';
|
||||
import { AlertCircle, Bell, CheckCircle, Trash2 } from 'lucide-react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
import { useUser } from '@/react/hooks/useUser';
|
||||
|
@ -69,7 +70,7 @@ export function NotificationsMenu() {
|
|||
'th-dark:text-gray-warm-7'
|
||||
)}
|
||||
>
|
||||
<Icon icon="bell" feather />
|
||||
<Icon icon={Bell} />
|
||||
<span className={badge ? notificationStyles.badge : ''} />
|
||||
</div>
|
||||
</MenuButton>
|
||||
|
@ -126,7 +127,7 @@ export function NotificationsMenu() {
|
|||
</>
|
||||
) : (
|
||||
<div className="flex flex-col items-center">
|
||||
<Icon icon="bell" feather size="xl" />
|
||||
<Icon icon={Bell} size="xl" />
|
||||
<p className="my-5">You have no notifications yet.</p>
|
||||
</div>
|
||||
)}
|
||||
|
@ -160,9 +161,9 @@ function MenuLink({ to, params, notification, onDelete }: MenuLinkProps) {
|
|||
<div className={notificationStyles.container}>
|
||||
<div className={notificationStyles.notificationIcon}>
|
||||
{notification.type === 'success' ? (
|
||||
<Icon icon="check-circle" feather size="lg" mode="success" />
|
||||
<Icon icon={CheckCircle} size="lg" mode="success" />
|
||||
) : (
|
||||
<Icon icon="alert-circle" feather size="lg" mode="danger" />
|
||||
<Icon icon={AlertCircle} size="lg" mode="danger" />
|
||||
)}
|
||||
</div>
|
||||
<div className={notificationStyles.notificationBody}>
|
||||
|
@ -186,9 +187,8 @@ function MenuLink({ to, params, notification, onDelete }: MenuLinkProps) {
|
|||
}}
|
||||
data-cy="notification-deleteButton"
|
||||
size="large"
|
||||
>
|
||||
<Icon icon="trash-2" feather />
|
||||
</Button>
|
||||
icon={Trash2}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ReachMenuLink>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useRouter } from '@uirouter/react';
|
||||
import { RefreshCw } from 'react-feather';
|
||||
import { RefreshCw } from 'lucide-react';
|
||||
|
||||
import { Button } from '../buttons';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
} from '@reach/menu-button';
|
||||
import { UISrefProps, useSref } from '@uirouter/react';
|
||||
import clsx from 'clsx';
|
||||
import { User, ChevronDown } from 'react-feather';
|
||||
import { User, ChevronDown } from 'lucide-react';
|
||||
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
import { useUser } from '@/react/hooks/useUser';
|
||||
|
@ -34,7 +34,7 @@ export function UserMenu() {
|
|||
'th-dark:text-gray-warm-7'
|
||||
)}
|
||||
>
|
||||
<User className="feather" />
|
||||
<User className="lucide" />
|
||||
</div>
|
||||
{user && <span>{user.Username}</span>}
|
||||
<ChevronDown className={styles.arrowDown} />
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { AlertTriangle, Check } from 'lucide-react';
|
||||
|
||||
import { usePublicSettings } from '@/react/portainer/settings/queries';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
@ -17,12 +19,12 @@ export function PasswordCheckHint({
|
|||
return (
|
||||
<div>
|
||||
<p className="text-warning vertical-center">
|
||||
<Icon icon="alert-triangle" className="icon-warning" feather />
|
||||
<Icon icon={AlertTriangle} className="icon-warning" />
|
||||
{forceChangePassword &&
|
||||
'An administrator has changed your password requirements, '}
|
||||
The password must be at least {minPasswordLength} characters long.
|
||||
{passwordValid && (
|
||||
<i className="fa fa-check green-icon space-left" aria-hidden="true" />
|
||||
<Icon icon={Check} className="!ml-1" mode="success" />
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -3,57 +3,19 @@ import automode from '@/assets/ico/theme/auto.svg?c';
|
|||
import darkmode from '@/assets/ico/theme/darkmode.svg?c';
|
||||
import lightmode from '@/assets/ico/theme/lightmode.svg?c';
|
||||
import highcontrastmode from '@/assets/ico/theme/highcontrastmode.svg?c';
|
||||
// wizard icons
|
||||
import agent from '@/assets/ico/wizard/agent.svg?c';
|
||||
import api from '@/assets/ico/wizard/api.svg?c';
|
||||
import edgeagent from '@/assets/ico/wizard/edge-agent.svg?c';
|
||||
import cloudimport from '@/assets/ico/wizard/import.svg?c';
|
||||
import socket from '@/assets/ico/wizard/socket.svg?c';
|
||||
// general icons
|
||||
import arrowsupdown from '@/assets/ico/arrows-updown.svg?c';
|
||||
import arrowright from '@/assets/ico/arrow-right-long.svg?c';
|
||||
import bomb from '@/assets/ico/bomb.svg?c';
|
||||
import checked from '@/assets/ico/checked.svg?c';
|
||||
import circlenotch from '@/assets/ico/circle-notch.svg?c';
|
||||
import clockrewind from '@/assets/ico/clock-rewind.svg?c';
|
||||
import compress from '@/assets/ico/compress.svg?c';
|
||||
import cubes from '@/assets/ico/cubes.svg?c';
|
||||
import custom from '@/assets/ico/custom.svg?c';
|
||||
import dataflow from '@/assets/ico/dataflow-1.svg?c';
|
||||
import dataflow2 from '@/assets/ico/dataflow-2.svg?c';
|
||||
import expand from '@/assets/ico/expand.svg?c';
|
||||
import filecode from '@/assets/ico/file-code.svg?c';
|
||||
import filesignature from '@/assets/ico/file-signature.svg?c';
|
||||
import fileupload from '@/assets/ico/file-upload.svg?c';
|
||||
import flask from '@/assets/ico/flask.svg?c';
|
||||
import git from '@/assets/ico/git.svg?c';
|
||||
import hacker from '@/assets/ico/hacker.svg?c';
|
||||
import heartbeat from '@/assets/ico/heartbeat.svg?c';
|
||||
import laptop from '@/assets/ico/laptop.svg?c';
|
||||
import kube from '@/assets/ico/kube.svg?c';
|
||||
import laptopcode from '@/assets/ico/laptop-code.svg?c';
|
||||
import ldap from '@/assets/ico/ldap.svg?c';
|
||||
import magic from '@/assets/ico/magic.svg?c';
|
||||
import magicwand from '@/assets/ico/magic-wand.svg?c';
|
||||
import linux from '@/assets/ico/linux.svg?c';
|
||||
import memory from '@/assets/ico/memory.svg?c';
|
||||
import objectgroup from '@/assets/ico/object-group.svg?c';
|
||||
import palette from '@/assets/ico/palette.svg?c';
|
||||
import plug from '@/assets/ico/plug.svg?c';
|
||||
import restore from '@/assets/ico/restore.svg?c';
|
||||
import restorewindow from '@/assets/ico/restore-window.svg?c';
|
||||
import rocket from '@/assets/ico/rocket.svg?c';
|
||||
import route from '@/assets/ico/route.svg?c';
|
||||
import share from '@/assets/ico/share.svg?c';
|
||||
import sort from '@/assets/ico/sort.svg?c';
|
||||
import tachometer from '@/assets/ico/tachometer.svg?c';
|
||||
import template from '@/assets/ico/template.svg?c';
|
||||
import tag from '@/assets/ico/tag-2.svg?c';
|
||||
import tag2 from '@/assets/ico/tags.svg?c';
|
||||
import tools from '@/assets/ico/tools.svg?c';
|
||||
import upload from '@/assets/ico/upload.svg?c';
|
||||
import url from '@/assets/ico/url.svg?c';
|
||||
import usercircle from '@/assets/ico/user-circle.svg?c';
|
||||
import userlock from '@/assets/ico/user-lock.svg?c';
|
||||
import kube from '@/assets/ico/kube.svg?c';
|
||||
import subscription from '@/assets/ico/subscription.svg?c';
|
||||
import Placeholder from '@/assets/ico/placeholder.svg?c'; // Placeholder is used when an icon name cant be matched
|
||||
// vendor icons
|
||||
import aws from '@/assets/ico/vendor/aws.svg?c';
|
||||
|
@ -61,6 +23,7 @@ import azure from '@/assets/ico/vendor/azure.svg?c';
|
|||
import civo from '@/assets/ico/vendor/civo.svg?c';
|
||||
import digitalocean from '@/assets/ico/vendor/digitalocean.svg?c';
|
||||
import docker from '@/assets/ico/vendor/docker.svg?c';
|
||||
import dockericon from '@/assets/ico/vendor/docker-icon.svg?c';
|
||||
import dockercompose from '@/assets/ico/vendor/docker-compose.svg?c';
|
||||
import ecr from '@/assets/ico/vendor/ecr.svg?c';
|
||||
import github from '@/assets/ico/vendor/github.svg?c';
|
||||
|
@ -71,68 +34,33 @@ import kubernetes from '@/assets/ico/vendor/kubernetes.svg?c';
|
|||
import helm from '@/assets/ico/vendor/helm.svg?c';
|
||||
import linode from '@/assets/ico/vendor/linode.svg?c';
|
||||
import microsoft from '@/assets/ico/vendor/microsoft.svg?c';
|
||||
import microsofticon from '@/assets/ico/vendor/microsoft-icon.svg?c';
|
||||
import nomad from '@/assets/ico/vendor/nomad.svg?c';
|
||||
import nomadicon from '@/assets/ico/vendor/nomad-icon.svg?c';
|
||||
import openldap from '@/assets/ico/vendor/openldap.svg?c';
|
||||
import proget from '@/assets/ico/vendor/proget.svg?c';
|
||||
import quay from '@/assets/ico/vendor/quay.svg?c';
|
||||
import internal from '@/assets/ico/vendor/internal.svg?c';
|
||||
|
||||
const placeholder = Placeholder;
|
||||
|
||||
export const SvgIcons = {
|
||||
agent,
|
||||
api,
|
||||
edgeagent,
|
||||
cloudimport,
|
||||
socket,
|
||||
automode,
|
||||
darkmode,
|
||||
lightmode,
|
||||
highcontrastmode,
|
||||
dataflow,
|
||||
dataflow2,
|
||||
arrowsupdown,
|
||||
arrowright,
|
||||
bomb,
|
||||
checked,
|
||||
circlenotch,
|
||||
clockrewind,
|
||||
compress,
|
||||
cubes,
|
||||
custom,
|
||||
expand,
|
||||
filecode,
|
||||
filesignature,
|
||||
fileupload,
|
||||
flask,
|
||||
dockericon,
|
||||
git,
|
||||
hacker,
|
||||
heartbeat,
|
||||
laptop,
|
||||
laptopcode,
|
||||
ldap,
|
||||
magic,
|
||||
magicwand,
|
||||
linux,
|
||||
memory,
|
||||
objectgroup,
|
||||
palette,
|
||||
placeholder,
|
||||
plug,
|
||||
restore,
|
||||
restorewindow,
|
||||
rocket,
|
||||
route,
|
||||
share,
|
||||
sort,
|
||||
tachometer,
|
||||
template,
|
||||
tag,
|
||||
tag2,
|
||||
tools,
|
||||
upload,
|
||||
url,
|
||||
usercircle,
|
||||
userlock,
|
||||
subscription,
|
||||
aws,
|
||||
azure,
|
||||
civo,
|
||||
|
@ -148,11 +76,12 @@ export const SvgIcons = {
|
|||
helm,
|
||||
linode,
|
||||
microsoft,
|
||||
microsofticon,
|
||||
nomad,
|
||||
nomadicon,
|
||||
openldap,
|
||||
proget,
|
||||
quay,
|
||||
internal,
|
||||
kube,
|
||||
};
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import clsx from 'clsx';
|
||||
import _ from 'lodash';
|
||||
import { Trash2 } from 'lucide-react';
|
||||
|
||||
import { TagId } from '@/portainer/tags/types';
|
||||
import { Icon } from '@/react/components/Icon';
|
||||
|
@ -74,7 +75,7 @@ export function TagSelector({ value, allowCreate = false, onChange }: Props) {
|
|||
key={tag.value}
|
||||
>
|
||||
{tag.label}
|
||||
<Icon icon="trash-2" feather />
|
||||
<Icon icon={Trash2} />
|
||||
</button>
|
||||
))}
|
||||
</FormControl>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import clsx from 'clsx';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { AlertCircle } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
|
@ -30,8 +31,7 @@ export function TextTip({
|
|||
<p className="small vertical-center">
|
||||
<i className="icon-container">
|
||||
<Icon
|
||||
icon="alert-circle"
|
||||
feather
|
||||
icon={AlertCircle}
|
||||
className={clsx(`${iconClass}`, 'space-right')}
|
||||
/>
|
||||
</i>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import ReactTooltip from 'react-tooltip';
|
||||
import { HelpCircle } from 'react-feather';
|
||||
import { HelpCircle } from 'lucide-react';
|
||||
import clsx from 'clsx';
|
||||
import _ from 'lodash';
|
||||
|
||||
|
@ -22,7 +22,7 @@ export function Tooltip({ message, position = 'bottom', className }: Props) {
|
|||
data-for={id}
|
||||
className={clsx(styles.icon, 'inline-flex text-base')}
|
||||
>
|
||||
<HelpCircle className="feather" aria-hidden="true" />
|
||||
<HelpCircle className="lucide" aria-hidden="true" />
|
||||
<ReactTooltip
|
||||
id={id}
|
||||
multiline
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { Settings } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import styles from './ViewLoading.module.css';
|
||||
|
||||
|
@ -18,7 +21,7 @@ export function ViewLoading({ message }: Props) {
|
|||
{message && (
|
||||
<span className={styles.message}>
|
||||
{message}
|
||||
<i className="fa fa-cog fa-spin space-left" />
|
||||
<Icon icon={Settings} className="animate-spin-slow !ml-1" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { ReactNode } from 'react';
|
||||
import type { Meta } from '@storybook/react';
|
||||
import { User } from 'lucide-react';
|
||||
|
||||
import { Widget } from './Widget';
|
||||
import { WidgetBody } from './WidgetBody';
|
||||
|
@ -9,7 +11,7 @@ import { WidgetTaskbar } from './WidgetTaskbar';
|
|||
interface WidgetProps {
|
||||
loading: boolean;
|
||||
title: string;
|
||||
icon: string;
|
||||
icon: ReactNode;
|
||||
bodyText: string;
|
||||
footerText: string;
|
||||
}
|
||||
|
@ -20,7 +22,7 @@ const meta: Meta<WidgetProps> = {
|
|||
args: {
|
||||
loading: false,
|
||||
title: 'Title',
|
||||
icon: 'fa-rocket',
|
||||
icon: User,
|
||||
bodyText: 'Body',
|
||||
footerText: 'Footer',
|
||||
},
|
||||
|
@ -52,11 +54,15 @@ function WidgetWithCustomImage({
|
|||
<WidgetTitle
|
||||
title={title}
|
||||
icon={
|
||||
<img
|
||||
className="custom-header-ico space-right"
|
||||
src={icon}
|
||||
alt="header-icon"
|
||||
/>
|
||||
typeof icon === 'string' ? (
|
||||
<img
|
||||
className="custom-header-ico space-right"
|
||||
src={icon}
|
||||
alt="header-icon"
|
||||
/>
|
||||
) : (
|
||||
icon
|
||||
)
|
||||
}
|
||||
/>
|
||||
<WidgetBody loading={loading}>{bodyText}</WidgetBody>
|
||||
|
|
|
@ -8,7 +8,6 @@ import { useWidgetContext } from './Widget';
|
|||
interface Props {
|
||||
title: ReactNode;
|
||||
icon: ReactNode;
|
||||
featherIcon?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
|
@ -17,7 +16,6 @@ export function WidgetTitle({
|
|||
icon,
|
||||
className,
|
||||
children,
|
||||
featherIcon,
|
||||
}: PropsWithChildren<Props>) {
|
||||
useWidgetContext();
|
||||
|
||||
|
@ -26,11 +24,7 @@ export function WidgetTitle({
|
|||
<div className="row">
|
||||
<span className={clsx('pull-left vertical-center', className)}>
|
||||
<div className="widget-icon">
|
||||
<Icon
|
||||
icon={icon}
|
||||
feather={featherIcon}
|
||||
className="space-right feather"
|
||||
/>
|
||||
<Icon icon={icon} className="space-right" />
|
||||
</div>
|
||||
<span>{title}</span>
|
||||
</span>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import clsx from 'clsx';
|
||||
import { PlusCircle } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@/react/components/Icon';
|
||||
|
||||
|
@ -27,7 +28,7 @@ export function AddButton({ label, onClick, className, disabled }: Props) {
|
|||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Icon icon="plus-circle" feather />
|
||||
<Icon icon={PlusCircle} />
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Meta, Story } from '@storybook/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Download } from 'react-feather';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
import { Button, Props } from './Button';
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@ type Size = 'xsmall' | 'small' | 'medium' | 'large';
|
|||
|
||||
export interface Props extends AriaAttributes, AutomationTestingProps {
|
||||
icon?: ReactNode | ComponentType<unknown>;
|
||||
featherIcon?: boolean;
|
||||
|
||||
color?: Color;
|
||||
size?: Size;
|
||||
|
@ -47,7 +46,6 @@ export function Button({
|
|||
onClick,
|
||||
title,
|
||||
icon,
|
||||
featherIcon,
|
||||
children,
|
||||
|
||||
...ariaProps
|
||||
|
@ -64,12 +62,7 @@ export function Button({
|
|||
{...ariaProps}
|
||||
>
|
||||
{icon && (
|
||||
<Icon
|
||||
icon={icon}
|
||||
size={getIconSize(size)}
|
||||
className="inline-flex"
|
||||
feather={featherIcon}
|
||||
/>
|
||||
<Icon icon={icon} size={getIconSize(size)} className="inline-flex" />
|
||||
)}
|
||||
{children}
|
||||
</button>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Meta, Story } from '@storybook/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { Play, RefreshCw, Square, Trash2 } from 'lucide-react';
|
||||
|
||||
import { Button } from './Button';
|
||||
import { ButtonGroup, Props } from './ButtonGroup';
|
||||
|
@ -14,28 +15,19 @@ function Template({
|
|||
}: JSX.IntrinsicAttributes & PropsWithChildren<Props>) {
|
||||
return (
|
||||
<ButtonGroup size={size}>
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
Start
|
||||
</Button>
|
||||
<Button color="danger" onClick={() => {}}>
|
||||
<i className="fa fa-stop space-right" aria-hidden="true" />
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
Stop
|
||||
</Button>
|
||||
<Button color="danger" onClick={() => {}}>
|
||||
<i className="fa fa-bomb space-right" aria-hidden="true" />
|
||||
Kill
|
||||
</Button>
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-sync space-right" aria-hidden="true" />
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
Restart
|
||||
</Button>
|
||||
<Button color="primary" disabled onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="primary" disabled onClick={() => {}}>
|
||||
Resume
|
||||
</Button>
|
||||
<Button color="danger" onClick={() => {}}>
|
||||
<i className="fa fa-trash-alt space-right" aria-hidden="true" />
|
||||
<Button icon={Trash2} color="danger" onClick={() => {}}>
|
||||
Remove
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -50,20 +42,16 @@ Primary.args = {
|
|||
export function Xsmall() {
|
||||
return (
|
||||
<ButtonGroup size="xsmall">
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
Start
|
||||
</Button>
|
||||
<Button color="danger" onClick={() => {}}>
|
||||
<i className="fa fa-stop space-right" aria-hidden="true" />
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
Stop
|
||||
</Button>
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
Start
|
||||
</Button>
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-sync space-right" aria-hidden="true" />
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
Restart
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -73,20 +61,16 @@ export function Xsmall() {
|
|||
export function Small() {
|
||||
return (
|
||||
<ButtonGroup size="small">
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
Start
|
||||
</Button>
|
||||
<Button color="danger" onClick={() => {}}>
|
||||
<i className="fa fa-stop space-right" aria-hidden="true" />
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
Stop
|
||||
</Button>
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
Start
|
||||
</Button>
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-sync space-right" aria-hidden="true" />
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
Restart
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
@ -96,20 +80,16 @@ export function Small() {
|
|||
export function Large() {
|
||||
return (
|
||||
<ButtonGroup size="large">
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="primary" onClick={() => {}}>
|
||||
Start
|
||||
</Button>
|
||||
<Button color="danger" onClick={() => {}}>
|
||||
<i className="fa fa-stop space-right" aria-hidden="true" />
|
||||
<Button icon={Square} color="danger" onClick={() => {}}>
|
||||
Stop
|
||||
</Button>
|
||||
<Button color="light" onClick={() => {}}>
|
||||
<i className="fa fa-play space-right" aria-hidden="true" />
|
||||
<Button icon={Play} color="light" onClick={() => {}}>
|
||||
Start
|
||||
</Button>
|
||||
<Button color="primary" onClick={() => {}}>
|
||||
<i className="fa fa-sync space-right" aria-hidden="true" />
|
||||
<Button icon={RefreshCw} color="primary" onClick={() => {}}>
|
||||
Restart
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Check, Copy } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
|
@ -33,7 +34,7 @@ export function CopyButton({
|
|||
title="Copy Value"
|
||||
type="button"
|
||||
>
|
||||
<Icon icon="copy" feather />
|
||||
<Icon icon={Copy} />
|
||||
{children}
|
||||
</Button>
|
||||
|
||||
|
@ -45,7 +46,7 @@ export function CopyButton({
|
|||
'vertical-center'
|
||||
)}
|
||||
>
|
||||
<Icon icon="check" feather />
|
||||
<Icon icon={Check} />
|
||||
{displayText && <span className="space-left">{displayText}</span>}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Meta } from '@storybook/react';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
import { LoadingButton } from './LoadingButton';
|
||||
|
||||
|
@ -14,8 +15,12 @@ interface Args {
|
|||
|
||||
function Template({ loadingText, isLoading }: Args) {
|
||||
return (
|
||||
<LoadingButton loadingText={loadingText} isLoading={isLoading}>
|
||||
<i className="fa fa-download" aria-hidden="true" /> Download
|
||||
<LoadingButton
|
||||
loadingText={loadingText}
|
||||
isLoading={isLoading}
|
||||
icon={Download}
|
||||
>
|
||||
Download
|
||||
</LoadingButton>
|
||||
);
|
||||
}
|
||||
|
@ -29,8 +34,8 @@ export const Example = Template.bind({});
|
|||
|
||||
export function IsLoading() {
|
||||
return (
|
||||
<LoadingButton loadingText="loading" isLoading>
|
||||
<i className="fa fa-download" aria-hidden="true" /> Download
|
||||
<LoadingButton loadingText="loading" isLoading icon={Download}>
|
||||
Download
|
||||
</LoadingButton>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,18 +6,18 @@ test('when isLoading is true should show spinner and loading text', async () =>
|
|||
const loadingText = 'loading';
|
||||
const children = 'not visible';
|
||||
|
||||
const { findByLabelText, queryByText, findByText } = render(
|
||||
const { queryByText, findByText, container } = render(
|
||||
<LoadingButton loadingText={loadingText} isLoading>
|
||||
{children}
|
||||
</LoadingButton>
|
||||
);
|
||||
|
||||
const spinner = container.querySelector('svg');
|
||||
expect(spinner).toBeVisible();
|
||||
|
||||
const buttonLabel = queryByText(children);
|
||||
expect(buttonLabel).toBeNull();
|
||||
|
||||
const spinner = await findByLabelText('loading');
|
||||
expect(spinner).toBeVisible();
|
||||
|
||||
const loadingTextElem = await findByText(loadingText);
|
||||
expect(loadingTextElem).toBeVisible();
|
||||
});
|
||||
|
@ -26,7 +26,7 @@ test('should show children when false', async () => {
|
|||
const loadingText = 'loading';
|
||||
const children = 'visible';
|
||||
|
||||
const { queryByLabelText, queryByText } = render(
|
||||
const { queryByText, container } = render(
|
||||
<LoadingButton loadingText={loadingText} isLoading={false}>
|
||||
{children}
|
||||
</LoadingButton>
|
||||
|
@ -35,7 +35,7 @@ test('should show children when false', async () => {
|
|||
const buttonLabel = queryByText(children);
|
||||
expect(buttonLabel).toBeVisible();
|
||||
|
||||
const spinner = queryByLabelText('loading');
|
||||
const spinner = container.querySelector('svg');
|
||||
expect(spinner).toBeNull();
|
||||
|
||||
const loadingTextElem = queryByText(loadingText);
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import { Loader2 } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import { type Props as ButtonProps, Button } from './Button';
|
||||
|
||||
|
@ -13,6 +16,7 @@ export function LoadingButton({
|
|||
disabled,
|
||||
type = 'submit',
|
||||
children,
|
||||
icon,
|
||||
...buttonProps
|
||||
}: PropsWithChildren<Props>) {
|
||||
return (
|
||||
|
@ -21,19 +25,22 @@ export function LoadingButton({
|
|||
{...buttonProps}
|
||||
type={type}
|
||||
disabled={disabled || isLoading}
|
||||
icon={loadingButtonIcon(isLoading, icon)}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<i
|
||||
className="fa fa-circle-notch fa-spin space-right"
|
||||
aria-label="loading"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{loadingText}
|
||||
</>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
{isLoading ? loadingText : children}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
function loadingButtonIcon(isLoading: boolean, defaultIcon: ReactNode) {
|
||||
if (!isLoading) {
|
||||
return defaultIcon;
|
||||
}
|
||||
return (
|
||||
<Icon
|
||||
icon={Loader2}
|
||||
className="animate-spin-slow ml-1"
|
||||
aria-label="loading"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { ReactNode } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { Menu, MenuList, MenuButton } from '@reach/menu-button';
|
||||
import { MoreVertical } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import styles from './ActionsMenu.module.css';
|
||||
|
||||
|
@ -19,7 +22,7 @@ export function ActionsMenu({ children }: Props) {
|
|||
isExpanded && styles.actionsActive
|
||||
)}
|
||||
>
|
||||
<i className="fa fa-ellipsis-v" aria-hidden="true" />
|
||||
<Icon icon={MoreVertical} />
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
<div className={styles.tableActionsMenuList}>{children}</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { Menu, MenuButton, MenuList } from '@reach/menu-button';
|
||||
import { ColumnInstance } from 'react-table';
|
||||
import { Columns } from 'react-feather';
|
||||
import { Columns } from 'lucide-react';
|
||||
|
||||
import { Checkbox } from '@@/form-components/Checkbox';
|
||||
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import { Row } from 'react-table';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import styles from './ExpandingCell.module.css';
|
||||
|
||||
|
@ -15,22 +18,15 @@ export function ExpandingCell<
|
|||
<>
|
||||
{showExpandArrow && (
|
||||
<button type="button" className={styles.expandButton}>
|
||||
<i
|
||||
<Icon
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...row.getToggleRowExpandedProps()}
|
||||
className={`fas ${arrowClass(row.isExpanded)} space-right`}
|
||||
aria-hidden="true"
|
||||
icon={row.isExpanded ? ChevronDown : ChevronRight}
|
||||
className="mr-1"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
||||
function arrowClass(isExpanded: boolean) {
|
||||
if (isExpanded) {
|
||||
return 'fa-angle-down';
|
||||
}
|
||||
return 'fa-angle-right';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@ import clsx from 'clsx';
|
|||
import { useMemo } from 'react';
|
||||
import { Menu, MenuButton, MenuPopover } from '@reach/menu-button';
|
||||
import { ColumnInstance } from 'react-table';
|
||||
import { Check, Filter } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
export const DefaultFilter = filterHOC('Filter by state');
|
||||
|
||||
|
@ -25,17 +28,12 @@ export function MultipleSelectionFilter({
|
|||
<div>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
className={clsx('table-filter', { 'filter-active': enabled })}
|
||||
className={clsx('table-filter flex items-center', {
|
||||
'filter-active': enabled,
|
||||
})}
|
||||
>
|
||||
Filter
|
||||
<i
|
||||
className={clsx(
|
||||
'fa',
|
||||
'space-left',
|
||||
enabled ? 'fa-check' : 'fa-filter'
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<Icon icon={enabled ? Check : Filter} className="!ml-1" />
|
||||
</MenuButton>
|
||||
<MenuPopover className="dropdown-menu">
|
||||
<div className="tableMenu">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Search } from 'react-feather';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
import { useLocalStorage } from '@/react/hooks/useLocalStorage';
|
||||
|
||||
|
@ -15,7 +15,7 @@ export function FilterSearchBar({
|
|||
}: Props) {
|
||||
return (
|
||||
<div className="searchBar items-center flex h-[34px]">
|
||||
<Search className="searchIcon feather" />
|
||||
<Search className="searchIcon lucide" />
|
||||
<input
|
||||
type="text"
|
||||
className="searchInput"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Search } from 'react-feather';
|
||||
import { Search } from 'lucide-react';
|
||||
|
||||
import { useLocalStorage } from '@/react/hooks/useLocalStorage';
|
||||
import { AutomationTestingProps } from '@/types';
|
||||
|
@ -20,7 +20,7 @@ export function SearchBar({
|
|||
|
||||
return (
|
||||
<div className="searchBar items-center flex min-w-[90px]">
|
||||
<Search className="searchIcon feather shrink-0" />
|
||||
<Search className="searchIcon lucide shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
className="searchInput"
|
||||
|
|
|
@ -76,6 +76,7 @@ function SortWrapper({
|
|||
<TableHeaderSortIcons
|
||||
sorted={isSorted}
|
||||
descending={isSorted && !!isSortedDesc}
|
||||
className="ml-1"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
|
|
|
@ -7,14 +7,15 @@ import styles from './TableHeaderSortIcons.module.css';
|
|||
interface Props {
|
||||
sorted: boolean;
|
||||
descending: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function TableHeaderSortIcons({ sorted, descending }: Props) {
|
||||
export function TableHeaderSortIcons({ sorted, descending, className }: Props) {
|
||||
return (
|
||||
<div className="flex flex-row no-wrap w-min-max">
|
||||
<div className="flex flex-row no-wrap w-min-max align-middle">
|
||||
<SortDownIcon
|
||||
className={clsx(
|
||||
'space-left',
|
||||
className,
|
||||
sorted && !descending && styles.activeSortIcon,
|
||||
styles.sortIcon
|
||||
)}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import clsx from 'clsx';
|
||||
import { Menu, MenuButton, MenuList } from '@reach/menu-button';
|
||||
import { PropsWithChildren, ReactNode } from 'react';
|
||||
import { MoreVertical } from 'react-feather';
|
||||
import { MoreVertical } from 'lucide-react';
|
||||
|
||||
interface Props {
|
||||
quickActions?: ReactNode;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
import { Checkbox } from '@@/form-components/Checkbox';
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
import styles from './TableSettingsMenuAutoRefresh.module.css';
|
||||
|
||||
|
@ -46,12 +48,7 @@ export function TableSettingsMenuAutoRefresh({ onChange, value }: Props) {
|
|||
)}
|
||||
onTransitionEnd={() => setIsCheckVisible(false)}
|
||||
>
|
||||
<i
|
||||
id="refreshRateChange"
|
||||
className="fa fa-check green-icon"
|
||||
aria-hidden="true"
|
||||
style={{ marginTop: '7px' }}
|
||||
/>
|
||||
<Icon icon={Check} className="!ml-1" mode="success" />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -4,14 +4,12 @@ import { Icon } from '@@/Icon';
|
|||
|
||||
interface Props {
|
||||
icon?: ReactNode | ComponentType<unknown>;
|
||||
featherIcon?: boolean;
|
||||
label: string;
|
||||
description?: ReactNode;
|
||||
}
|
||||
|
||||
export function TableTitle({
|
||||
icon,
|
||||
featherIcon,
|
||||
label,
|
||||
children,
|
||||
description,
|
||||
|
@ -22,11 +20,7 @@ export function TableTitle({
|
|||
<div className="toolBarTitle">
|
||||
{icon && (
|
||||
<div className="widget-icon">
|
||||
<Icon
|
||||
icon={icon}
|
||||
feather={featherIcon}
|
||||
className="space-right feather"
|
||||
/>
|
||||
<Icon icon={icon} className="space-right" />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ChevronDown, ChevronUp } from 'react-feather';
|
||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||
import { CellProps, Column, HeaderProps } from 'react-table';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ChangeEvent, createRef } from 'react';
|
||||
import { XCircle } from 'lucide-react';
|
||||
|
||||
import { Button } from '@@/buttons';
|
||||
import { Icon } from '@@/Icon';
|
||||
|
@ -46,7 +47,7 @@ export function FileUploadField({
|
|||
</Button>
|
||||
|
||||
<span className="vertical-center">
|
||||
{value ? value.name : <Icon icon="x-circle" feather mode="danger" />}
|
||||
{value ? value.name : <Icon icon={XCircle} mode="danger" />}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -11,11 +11,11 @@ export type Size = 'xsmall' | 'small' | 'medium' | 'large';
|
|||
|
||||
export interface Props {
|
||||
inputId?: string;
|
||||
label: string | ReactNode;
|
||||
label: ReactNode;
|
||||
size?: Size;
|
||||
tooltip?: string;
|
||||
children: ReactNode;
|
||||
errors?: string | ReactNode;
|
||||
errors?: ReactNode;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { PropsWithChildren } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { AlertTriangle } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
|
@ -10,7 +11,7 @@ interface Props {
|
|||
export function FormError({ children, className }: PropsWithChildren<Props>) {
|
||||
return (
|
||||
<p className={clsx(`text-muted small vertical-center`, className)}>
|
||||
<Icon icon="alert-triangle" className="icon-warning" feather />
|
||||
<Icon icon={AlertTriangle} className="icon-warning" />
|
||||
<span className="text-warning">{children}</span>
|
||||
</p>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { PropsWithChildren, useState } from 'react';
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react';
|
||||
|
||||
import { Icon } from '@@/Icon';
|
||||
|
||||
|
@ -27,9 +28,8 @@ export function FormSection({
|
|||
className="border-0 mx-2 !ml-0 bg-transparent inline-flex justify-center items-center w-2"
|
||||
>
|
||||
<Icon
|
||||
icon={isExpanded ? 'chevron-down' : 'chevron-right'}
|
||||
icon={isExpanded ? ChevronDown : ChevronRight}
|
||||
className="shrink-0"
|
||||
feather
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { ComponentType } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { FormikErrors } from 'formik';
|
||||
import { ArrowDown, ArrowUp, Trash2 } from 'lucide-react';
|
||||
|
||||
import { AddButton, Button } from '@@/buttons';
|
||||
import { Icon } from '@@/Icon';
|
||||
import { Tooltip } from '@@/Tip/Tooltip';
|
||||
import { TextTip } from '@@/Tip/TextTip';
|
||||
|
||||
|
@ -136,18 +136,16 @@ export function InputList<T = DefaultType>({
|
|||
disabled={disabled || index === 0}
|
||||
onClick={() => handleMoveUp(index)}
|
||||
className="vertical-center btn-only-icon"
|
||||
>
|
||||
<Icon icon="arrow-up" feather />
|
||||
</Button>
|
||||
icon={ArrowUp}
|
||||
/>
|
||||
<Button
|
||||
size="medium"
|
||||
type="button"
|
||||
disabled={disabled || index === value.length - 1}
|
||||
onClick={() => handleMoveDown(index)}
|
||||
className="vertical-center btn-only-icon"
|
||||
>
|
||||
<Icon icon="arrow-down" feather />
|
||||
</Button>
|
||||
icon={ArrowDown}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!readOnly && (
|
||||
|
@ -156,9 +154,8 @@ export function InputList<T = DefaultType>({
|
|||
size="medium"
|
||||
onClick={() => handleRemoveItem(key, item)}
|
||||
className="vertical-center btn-only-icon"
|
||||
>
|
||||
<Icon icon="trash-2" feather size="md" />
|
||||
</Button>
|
||||
icon={Trash2}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -32,6 +32,10 @@
|
|||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.portainer-selector-root .portainer-selector__clear-indicator {
|
||||
padding: 6px;
|
||||
}
|
||||
|
||||
.portainer-selector-root .portainer-selector__multi-value__label {
|
||||
@apply text-black;
|
||||
@apply th-dark:text-white;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue