0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added dynamic icon component in AdminX

refs. https://github.com/TryGhost/Team/issues/3150
This commit is contained in:
Peter Zimon 2023-05-25 16:57:10 +02:00
parent 5c20aa08de
commit 0a02768ec1
6 changed files with 203 additions and 14 deletions

View file

@ -0,0 +1,60 @@
import type {Meta, StoryObj} from '@storybook/react';
import Icon from './Icon';
const meta = {
title: 'Global / Icon',
component: Icon,
tags: ['autodocs']
} satisfies Meta<typeof Icon>;
export default meta;
type Story = StoryObj<typeof Icon>;
export const Default: Story = {
args: {
name: 'lock-locked'
}
};
export const ExtraSmall: Story = {
args: {
size: 'xs',
name: 'lock-locked'
}
};
export const Small: Story = {
args: {
size: 'sm',
name: 'lock-locked'
}
};
export const Medium: Story = {
args: {
size: 'md',
name: 'lock-locked'
}
};
export const Large: Story = {
args: {
size: 'lg',
name: 'lock-locked'
}
};
export const ExtraLarge: Story = {
args: {
size: 'xl',
name: 'lock-locked'
}
};
export const Color: Story = {
args: {
color: 'green',
name: 'lock-locked'
}
};

View file

@ -0,0 +1,93 @@
import React, {useEffect, useRef, useState} from 'react';
interface UseDynamicSVGImportOptions {
onCompleted?: (
name: string,
SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> | undefined
) => void;
onError?: (err: Error) => void;
}
function useDynamicSVGImport(
name: string,
options: UseDynamicSVGImportOptions = {}
) {
const ImportedIconRef = useRef<React.FC<React.SVGProps<SVGSVGElement>>>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error>();
const {onCompleted, onError} = options;
useEffect(() => {
setLoading(true);
const importIcon = async (): Promise<void> => {
try {
ImportedIconRef.current = (
await import(`../../assets/icons/${name}.svg`)
).ReactComponent;
onCompleted?.(name, ImportedIconRef.current);
} catch (err: any) {
onError?.(err);
setError(err);
} finally {
setLoading(false);
}
};
importIcon();
}, [name, onCompleted, onError]);
return {error, loading, SvgIcon: ImportedIconRef.current};
}
export type IconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | number;
interface IconProps {
name: string;
/**
* Accepts either predefined sizes or number, in which case the size means the pixel width & height
*/
size?: IconSize;
color?: string;
styles?: string;
className?: string;
}
const Icon: React.FC<IconProps> = ({name, size = 'md', color = 'black', className}) => {
const {SvgIcon} = useDynamicSVGImport(name);
let styles = '';
if (!styles) {
switch (size) {
case 'xs':
styles = 'w-3 h-3';
break;
case 'sm':
styles = 'w-4 h-4';
break;
case 'lg':
styles = 'w-8 h-8';
break;
case 'xl':
styles = 'w-10 h-10';
break;
default:
styles = 'w-5 h-5';
break;
}
}
if (color) {
styles += ` text-${color}`;
}
if (SvgIcon) {
return (
<SvgIcon className={`${styles} ${className}`} />
);
}
return null;
};
export default Icon;

View file

@ -0,0 +1,20 @@
import type {Meta, StoryObj} from '@storybook/react';
import IconLabel from './IconLabel';
const meta = {
title: 'Global / Label with icon',
component: IconLabel,
tags: ['autodocs']
} satisfies Meta<typeof IconLabel>;
export default meta;
type Story = StoryObj<typeof IconLabel>;
export const Default: Story = {
args: {
icon: 'check-circle',
iconColor: 'green',
children: 'Here\'s a label with icon'
}
};

View file

@ -0,0 +1,19 @@
import Icon from './Icon';
import React from 'react';
interface IconLabelProps {
icon: string;
iconColor?: string;
children?: React.ReactNode;
}
const IconLabel: React.FC<IconLabelProps> = ({icon, iconColor, children}) => {
return (
<div className='flex items-center gap-2'>
<Icon color={iconColor} name={icon} size='sm' />
{children}
</div>
);
};
export default IconLabel;

View file

@ -1,11 +1,11 @@
import Dropdown from '../../../admin-x-ds/global/Dropdown';
import IconLabel from '../../../admin-x-ds/global/IconLabel';
import Link from '../../../admin-x-ds/global/Link';
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
import TextField from '../../../admin-x-ds/global/TextField';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {ReactComponent as CheckIcon} from '../../../assets/icons/check-circle.svg';
const MAILGUN_REGIONS = [
{label: '🇺🇸 US', value: 'https://api.mailgun.net/v3'},
@ -32,9 +32,9 @@ const MailGun: React.FC = () => {
{
key: 'status',
value: (
<div className='flex items-center'>
<CheckIcon className='mr-2 h-4 w-4 text-green' /> Mailgun is set up
</div>
<IconLabel icon='check-circle' iconColor='green'>
Mailgun is set up
</IconLabel>
)
}
] : [

View file

@ -1,3 +1,4 @@
import IconLabel from '../../../admin-x-ds/global/IconLabel';
import Link from '../../../admin-x-ds/global/Link';
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
@ -5,8 +6,6 @@ import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupConten
import TextField from '../../../admin-x-ds/global/TextField';
import Toggle from '../../../admin-x-ds/global/Toggle';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {ReactComponent as LockedIcon} from '../../../assets/icons/lock-locked.svg';
import {ReactComponent as UnLockedIcon} from '../../../assets/icons/lock-unlocked.svg';
const LockSite: React.FC = () => {
const {
@ -34,15 +33,13 @@ const LockSite: React.FC = () => {
{
key: 'private',
value: passwordEnabled ? (
<div className='flex items-center '>
<LockedIcon className='mr-2 h-4 w-4 text-yellow' />
<span>Your site is password protected</span>
</div>
<IconLabel icon='lock-locked' iconColor='yellow'>
Your site is password protected
</IconLabel>
) : (
<div className='flex items-center text-grey-900 '>
<UnLockedIcon className='mr-2 h-4 w-4' />
<span>Your site is not password protected</span>
</div>
<IconLabel icon='lock-unlocked' iconColor='grey-900'>
Your site is password protected
</IconLabel>
)
}
]}