0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Added preview selection toolbar in AdminX

refs. https://github.com/TryGhost/Team/issues/3354
This commit is contained in:
Peter Zimon 2023-06-07 22:48:02 +02:00
parent a173a31736
commit 1bfade4891
29 changed files with 282 additions and 74 deletions

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-down</title><line x1="12" y1="0.75" x2="12" y2="23.25" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></line><polyline points="1.5 12.75 12 23.25 22.5 12.75" fill-rule="evenodd" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></polyline></svg>

After

Width:  |  Height:  |  Size: 451 B

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-left</title><line x1="23.25" y1="12" x2="0.75" y2="12" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></line><polyline points="11.25 1.5 0.75 12 11.25 22.5" fill-rule="evenodd" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></polyline></svg>

After

Width:  |  Height:  |  Size: 450 B

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-right</title><line x1="0.75" y1="12" x2="23.25" y2="12" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></line><polyline points="12.75 22.5 23.25 12 12.75 1.5" fill-rule="evenodd" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></polyline></svg>

After

Width:  |  Height:  |  Size: 452 B

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-up</title><line x1="12" y1="23.25" x2="12" y2="0.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></line><polyline points="22.5 11.25 12 0.75 1.5 11.25" fill-rule="evenodd" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></polyline></svg>

After

Width:  |  Height:  |  Size: 448 B

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-down-1</title><path d="M23.25,7.311,12.53,18.03a.749.749,0,0,1-1.06,0L.75,7.311" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px" fill-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 315 B

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-left-1</title><path d="M16.25,23.25,5.53,12.53a.749.749,0,0,1,0-1.06L16.25.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px" fill-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 313 B

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-right-1</title><path d="M5.5.75,16.22,11.47a.749.749,0,0,1,0,1.06L5.5,23.25" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px" fill-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 311 B

View file

@ -0,0 +1 @@
<svg id="Regular" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>arrow-up-1</title><path d="M.75,17.189,11.47,6.47a.749.749,0,0,1,1.06,0L23.25,17.189" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px" fill-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 314 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><defs></defs><title>laptop</title><path d="M21,14.25V4.5A1.5,1.5,0,0,0,19.5,3H4.5A1.5,1.5,0,0,0,3,4.5v9.75Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></path><path d="M23.121,18.891A1.5,1.5,0,0,1,21.75,21H2.25A1.5,1.5,0,0,1,.879,18.891L3,14.25H21Z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></path><line x1="10.5" y1="18" x2="13.5" y2="18" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></line></svg>

After

Width:  |  Height:  |  Size: 635 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g><rect x="5.25" y="0.75" width="13.5" height="22.5" rx="3" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></rect><line x1="5.25" y1="17.75" x2="18.75" y2="17.75" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5px"></line></g></svg>

After

Width:  |  Height:  |  Size: 398 B

View file

@ -62,7 +62,8 @@ export const LinkButton: Story = {
export const Icon: Story = {
args: {
icon: 'menu-horizontal',
color: 'green'
color: 'green',
iconColorClass: 'text-white'
}
};
@ -70,6 +71,7 @@ export const IconSmall: Story = {
args: {
size: 'sm',
icon: 'menu-horizontal',
color: 'green'
color: 'green',
iconColorClass: 'text-white'
}
};

View file

@ -8,6 +8,7 @@ export interface IButton {
size?: ButtonSize;
label?: string;
icon?: string;
iconColorClass?: string;
key?: string;
color?: string;
fullWidth?: boolean;
@ -21,6 +22,7 @@ const Button: React.FC<IButton> = ({
size = 'md',
label = '',
icon = '',
iconColorClass = 'text-black',
color = 'clear',
fullWidth,
link,
@ -72,7 +74,7 @@ const Button: React.FC<IButton> = ({
onClick={onClick}
{...props}
>
{icon && <Icon color={(color === 'clear' || color === 'grey' || color === 'white' ? 'black' : 'white')} name={icon} size={size === 'sm' ? 'sm' : 'md'} />}
{icon && <Icon colorClass={iconColorClass} name={icon} size={size === 'sm' ? 'sm' : 'md'} />}
{label}
</button>
);

View file

@ -1,6 +1,10 @@
import type {Meta, StoryObj} from '@storybook/react';
import Button from './Button';
import ButtonGroup from './ButtonGroup';
import DesktopChrome from './DesktopChrome';
import URLSelect from './URLSelect';
import {SelectOption} from './Select';
const meta = {
title: 'Global / Chrome / Desktop',
@ -43,28 +47,52 @@ export const WithBorder: Story = {
}
};
export const NoTrafficLights: Story = {
export const Empty: Story = {
args: {
children: (
<div className='flex items-center justify-center p-10 text-sm text-grey-500'>
Window contents
</div>
),
trafficLights: false
toolbarLeft: <></>
}
};
export const WithHeaderContents: Story = {
export const WithTitle: Story = {
args: {
children: (
<div className='flex items-center justify-center p-10 text-sm text-grey-500'>
Window contents
</div>
),
header: (
<div className='flex grow justify-center text-sm font-semibold text-grey-900'>
Window header
toolbarCenter: 'Hello title'
}
};
const selectOptions: SelectOption[] = [
{value: 'homepage', label: 'Homepage'},
{value: 'post', label: 'Post'},
{value: 'page', label: 'Page'},
{value: 'tag-archive', label: 'Tag archive'},
{value: 'author-archive', label: 'Author archive'}
];
export const CustomToolbar: Story = {
args: {
children: (
<div className='flex items-center justify-center p-10 text-sm text-grey-500'>
Window contents
</div>
)
),
toolbarLeft: <Button icon='arrow-left' link={true} size='sm' />,
toolbarCenter: <URLSelect options={selectOptions} onSelect={(value: string) => {
alert(value);
}} />,
toolbarRight: <ButtonGroup
buttons={[
{icon: 'laptop', link: true, size: 'sm'},
{icon: 'mobile', link: true, size: 'sm', iconColorClass: 'text-grey-500'}
]}
/>
}
};

View file

@ -4,48 +4,65 @@ export type DesktopChromeSize = 'sm' | 'md';
interface DesktopChromeProps {
size?: DesktopChromeSize;
trafficLights?: boolean;
toolbarLeft?: React.ReactNode;
toolbarCenter?: React.ReactNode;
toolbarRight?: React.ReactNode;
children?: React.ReactNode;
chromeClasses?: string;
headerClasses?: string;
toolbarClasses?: string;
contentClasses?: string;
header?: React.ReactNode;
headerCenter?: boolean;
border?: boolean;
}
const DesktopChrome: React.FC<DesktopChromeProps> = ({
size = 'md',
trafficLights = true,
toolbarLeft = '',
toolbarCenter = '',
toolbarRight = '',
children,
chromeClasses = '',
headerClasses = '',
toolbarClasses = '',
contentClasses = '',
header,
headerCenter = true,
border = false
}) => {
let containerSize = size === 'sm' ? 'h-6 p-2' : 'h-10 p-3';
let containerSize = size === 'sm' ? 'min-h-[32px] p-2' : 'min-h-[48px] p-3';
const trafficLightSize = size === 'sm' ? 'w-[6px] h-[6px]' : 'w-[10px] h-[10px]';
const trafficLightContainerStyle = size === 'sm' ? 'gap-[5px] w-[36px] ' : 'gap-2 w-[56px] ';
const trafficLightWidth = size === 'sm' ? 36 : 56;
let trafficLightContainerStyle = size === 'sm' ? 'gap-[5px] ' : 'gap-2 ';
trafficLightContainerStyle += `w-[${trafficLightWidth}px]`;
contentClasses += ' grow';
if (headerCenter) {
containerSize += size === 'sm' ? ' pr-[48px]' : ' pr-[68px]';
}
const trafficLights = (
<div className={`absolute left-4 flex h-full items-center ${trafficLightContainerStyle}`}>
<div className={`rounded-full bg-grey-500 ${trafficLightSize}`}></div>
<div className={`rounded-full bg-grey-500 ${trafficLightSize}`}></div>
<div className={`rounded-full bg-grey-500 ${trafficLightSize}`}></div>
</div>
);
return (
<div className={`flex h-full grow-0 flex-col ${border ? 'rounded-sm border border-grey-100' : ''} ${chromeClasses}`}>
<header className={`flex items-center justify-between bg-grey-50 ${containerSize} ${headerClasses}`}>
{trafficLights &&
<div className={`flex items-center ${trafficLightContainerStyle}`}>
<div className={`rounded-full bg-grey-500 ${trafficLightSize}`}></div>
<div className={`rounded-full bg-grey-500 ${trafficLightSize}`}></div>
<div className={`rounded-full bg-grey-500 ${trafficLightSize}`}></div>
<header className={`relative flex items-center justify-center bg-grey-50 ${containerSize} ${toolbarClasses}`}>
{toolbarLeft ?
<div className='absolute left-4 flex h-full items-center'>
{toolbarLeft}
</div>
:
trafficLights
}
<div className='flex grow justify-center'>
{(typeof toolbarCenter === 'string') ?
(<span className='text-sm font-bold'>{toolbarCenter}</span>)
:
(<>{toolbarCenter}</>)
}
</div>
{toolbarRight &&
<div className='absolute right-4 flex h-full items-center'>
{toolbarRight}
</div>
}
{header && header}
</header>
<section className={contentClasses}>
{children}

View file

@ -1,4 +1,5 @@
import React, {useEffect, useState} from 'react';
import clsx from 'clsx';
interface UseDynamicSVGImportOptions {
onCompleted?: (
@ -52,7 +53,7 @@ interface IconProps {
/**
* Accepts all colors available in the actual TailwindCSS theme, e.g. `black`, `green-100`
*/
color?: string;
colorClass?: string;
styles?: string;
className?: string;
}
@ -63,7 +64,7 @@ interface IconProps {
* - all icons must have all it's children color value set `currentColor`
* - all strokes must be paths and _NOT_ outlined objects. Stroke width should be set to 1.5px
*/
const Icon: React.FC<IconProps> = ({name, size = 'md', color = 'black', className = ''}) => {
const Icon: React.FC<IconProps> = ({name, size = 'md', colorClass = 'text-black', className = ''}) => {
const {SvgComponent} = useDynamicSVGImport(name);
let styles = '';
@ -89,9 +90,10 @@ const Icon: React.FC<IconProps> = ({name, size = 'md', color = 'black', classNam
}
}
if (color) {
styles += ` text-${color}`;
}
styles = clsx(
styles,
colorClass
);
if (SvgComponent) {
return (

View file

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

View file

@ -35,7 +35,7 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
imageClassName = imageClassName || 'group relative bg-cover';
fileUploadClassName = fileUploadClassName || 'flex cursor-pointer items-center justify-center rounded border border-grey-100 bg-grey-75 p-3 text-sm font-semibold text-grey-800 hover:text-black';
deleteButtonClassName = deleteButtonClassName || 'invisible absolute right-4 top-4 flex h-8 w-8 cursor-pointer items-center justify-center rounded bg-[rgba(0,0,0,0.75)] text-white hover:bg-black group-hover:!visible';
deleteButtonContent = deleteButtonContent || <Icon color='white' name='trash' size='sm' />;
deleteButtonContent = deleteButtonContent || <Icon colorClass='text-white' name='trash' size='sm' />;
}
if (imageURL) {

View file

@ -73,59 +73,51 @@ const Modal: React.FC<ModalProps> = ({
}
let modalClasses = clsx(
'relative z-50 mx-auto flex w-full flex-col justify-between rounded bg-white shadow-xl'
'relative z-50 mx-auto flex max-h-[100%] w-full flex-col justify-between overflow-y-auto overflow-x-hidden rounded bg-white shadow-xl'
// !stickyFooter && ' overflow-hidden'
);
let backdropClasses = clsx('fixed inset-0 z-40 h-[100vh] w-[100vw] overflow-y-scroll ');
let padding = '';
let footerContainerBottom = '';
switch (size) {
case 'sm':
modalClasses += ' max-w-[480px] ';
backdropClasses += ' p-[8vmin]';
padding = 'p-8';
footerContainerBottom = '-8vmin';
break;
case 'md':
modalClasses += ' max-w-[720px] ';
backdropClasses += ' p-[8vmin]';
padding = 'p-8';
footerContainerBottom = '-8vmin';
break;
case 'lg':
modalClasses += ' max-w-[1020px] ';
backdropClasses += ' p-[4vmin]';
padding = 'p-12';
footerContainerBottom = '-4vmin';
padding = 'p-8';
break;
case 'xl':
modalClasses += ' max-w-[1240px] ';
backdropClasses += ' p-[3vmin]';
padding = 'p-12';
footerContainerBottom = '-3vmin';
padding = 'p-10';
break;
case 'full':
modalClasses += ' h-full ';
backdropClasses += ' p-[2vmin]';
padding = 'p-12';
footerContainerBottom = '-2vmin';
padding = 'p-10';
break;
case 'bleed':
modalClasses += ' h-full ';
padding = 'p-12';
padding = 'p-10';
break;
default:
backdropClasses += ' p-[8vmin]';
footerContainerBottom = '-8vmin';
padding = 'p-8';
break;
}
@ -139,7 +131,10 @@ const Modal: React.FC<ModalProps> = ({
'flex w-full items-center justify-between'
);
let contentClasses = `${padding} h-full`;
let contentClasses = clsx(
padding,
size === 'full' && 'h-full'
);
if (!customFooter) {
contentClasses += ' pb-0 ';
@ -169,7 +164,7 @@ const Modal: React.FC<ModalProps> = ({
);
const footer = (stickyFooter ?
<StickyFooter shiftY={footerContainerBottom}>
<StickyFooter height={84}>
{footerContent}
</StickyFooter>
:

View file

@ -12,7 +12,7 @@ interface NoValueLabelProps {
const NoValueLabel: React.FC<NoValueLabelProps> = ({icon, children}) => {
return (
<div className='my-10 flex flex-col items-center gap-1 text-sm text-grey-600'>
{icon && <Icon className='stroke-[1px]' color='grey-500' name={icon} size='lg' />}
{icon && <Icon className='stroke-[1px]' colorClass='text-grey-500' name={icon} size='lg' />}
{children}
</div>
);

View file

@ -66,7 +66,7 @@ const PreviewModal: React.FC<PreviewModalProps> = ({
</div>
<div className='flex h-full basis-[400px] flex-col gap-3 border-l border-grey-100'>
{customHeader ? customHeader : (
<div className='flex justify-between gap-3 px-7 pt-7'>
<div className='flex justify-between gap-3 px-7 pt-5'>
<>
<Heading className='mt-1' level={4}>{title}</Heading>
{customButtons ? customButtons : <ButtonGroup buttons={buttons} /> }

View file

@ -61,7 +61,7 @@ export const WithHint: Story = {
}
};
export const WithDefaultSelectedOption: Story = {
export const WithDefaultSelected: Story = {
args: {
title: 'Title',
options: selectOptions,
@ -70,6 +70,15 @@ export const WithDefaultSelectedOption: Story = {
}
};
export const WithCallback: Story = {
args: {
options: selectOptions,
onSelect: (value: string) => {
alert(value);
}
}
};
export const Error: Story = {
args: {
title: 'Title',
@ -77,4 +86,11 @@ export const Error: Story = {
hint: 'Invalid value',
error: true
}
};
export const Unstyled: Story = {
args: {
options: selectOptions,
unstyled: true
}
};

View file

@ -2,6 +2,7 @@ import React, {useEffect, useId, useState} from 'react';
import Heading from './Heading';
import Hint from './Hint';
import clsx from 'clsx';
export interface SelectOption {
value: string;
@ -17,9 +18,26 @@ interface SelectProps {
hint?: React.ReactNode;
defaultSelectedOption?: string;
clearBg?: boolean;
containerClassName?: string;
selectClassName?: string;
optionClassName?: string;
unstyled?: boolean;
}
const Select: React.FC<SelectProps> = ({title, prompt, options, onSelect, error, hint, defaultSelectedOption, clearBg = false}) => {
const Select: React.FC<SelectProps> = ({
title,
prompt,
options,
onSelect,
error,
hint,
defaultSelectedOption,
clearBg = false,
containerClassName,
selectClassName,
optionClassName,
unstyled
}) => {
const id = useId();
const [selectedOption, setSelectedOption] = useState(defaultSelectedOption);
@ -36,15 +54,46 @@ const Select: React.FC<SelectProps> = ({title, prompt, options, onSelect, error,
onSelect(selectedValue);
};
let containerClasses = '';
if (!unstyled) {
containerClasses = clsx(
'relative w-full after:pointer-events-none',
`after:absolute after:block after:h-2 after:w-2 after:rotate-45 after:border-[1px] after:border-l-0 after:border-t-0 after:border-grey-900 after:content-['']`,
title ? 'after:top-[22px]' : 'after:top-[14px]',
clearBg ? 'after:right-0' : 'after:right-4'
);
}
containerClasses = clsx(
containerClasses,
containerClassName
);
let selectClasses = '';
if (!unstyled) {
selectClasses = clsx(
'w-full cursor-pointer appearance-none border-b py-2 outline-none',
!clearBg && 'bg-grey-75 px-[10px]',
error ? 'border-red' : 'border-grey-500 hover:border-grey-700 focus:border-black',
title && 'mt-2'
);
}
selectClasses = clsx(
selectClasses,
selectClassName
);
const optionClasses = optionClassName;
return (
<div className='flex flex-col'>
<>
{title && <Heading htmlFor={id} useLabelTag={true}>{title}</Heading>}
<div className={`relative w-full after:pointer-events-none after:absolute ${clearBg ? 'after:right-0' : 'after:right-4'} after:block after:h-2 after:w-2 after:rotate-45 after:border-[1px] after:border-l-0 after:border-t-0 after:border-grey-900 after:content-[''] ${title ? 'after:top-[22px]' : 'after:top-[14px]'}`}>
<select className={`w-full cursor-pointer appearance-none border-b ${!clearBg && 'bg-grey-75 px-[10px]'} py-2 outline-none ${error ? `border-red` : `border-grey-500 hover:border-grey-700 focus:border-black`} ${title && `mt-2`}`} id={id} value={selectedOption} onChange={handleOptionChange}>
{prompt && <option value="">{prompt}</option>}
<div className={containerClasses}>
<select className={selectClasses} id={id} value={selectedOption} onChange={handleOptionChange}>
{prompt && <option className={optionClasses} value="">{prompt}</option>}
{options.map(option => (
<option
key={option.value}
className={optionClasses}
value={option.value}
>
{option.label}
@ -53,7 +102,7 @@ const Select: React.FC<SelectProps> = ({title, prompt, options, onSelect, error,
</select>
</div>
{hint && <Hint color={error ? 'red' : ''}>{hint}</Hint>}
</div>
</>
);
};

View file

@ -52,14 +52,14 @@ const Toast: React.FC<ToastProps> = ({
<div className={classNames}>
<div className='flex items-start gap-3'>
{props?.icon && (typeof props.icon === 'string' ?
<div className='mt-0.5'><Icon className='grow' color={props.type === 'success' ? 'green' : 'white'} name={props.icon} size='sm' /></div> : props.icon)}
<div className='mt-0.5'><Icon className='grow' colorClass={props.type === 'success' ? 'text-green' : 'text-white'} name={props.icon} size='sm' /></div> : props.icon)}
{children}
</div>
<button className='cursor-pointer' type='button' onClick={() => {
toast.dismiss(t.id);
}}>
<div className='mt-1'>
<Icon color='white' name='close' size='xs' />
<Icon colorClass='text-white' name='close' size='xs' />
</div>
</button>
</div>

View file

@ -0,0 +1,28 @@
import type {Meta, StoryObj} from '@storybook/react';
import URLSelect from './URLSelect';
import {SelectOption} from './Select';
const meta = {
title: 'Global / Select / URL Select',
component: URLSelect,
tags: ['autodocs']
} satisfies Meta<typeof URLSelect>;
export default meta;
type Story = StoryObj<typeof URLSelect>;
const selectOptions: SelectOption[] = [
{value: 'homepage', label: 'Homepage'},
{value: 'post', label: 'Post'},
{value: 'page', label: 'Page'},
{value: 'tag-archive', label: 'Tag archive'},
{value: 'author-archive', label: 'Author archive'}
];
export const Default: Story = {
args: {
options: selectOptions,
onSelect: () => {}
}
};

View file

@ -0,0 +1,31 @@
import React from 'react';
import Select, {SelectOption} from './Select';
import clsx from 'clsx';
interface URLSelectProps {
options: SelectOption[];
onSelect: (value: string) => void;
}
const URLSelect: React.FC<URLSelectProps> = ({options, onSelect}) => {
const selectClasses = clsx(
`!h-[unset] w-full appearance-none rounded-full border border-grey-100 bg-white px-3 py-1 text-sm`
);
const containerClasses = clsx(
'relative w-full max-w-[320px] self-center after:pointer-events-none',
`after:absolute after:right-4 after:top-[9px] after:block after:h-2 after:w-2 after:rotate-45 after:border-[1px] after:border-l-0 after:border-t-0 after:border-grey-900 after:content-['']`
);
return (
<Select
containerClassName={containerClasses}
options={options}
selectClassName={selectClasses}
unstyled={true}
onSelect={onSelect}
/>
);
};
export default URLSelect;

View file

@ -33,7 +33,7 @@ const MailGun: React.FC = () => {
{
key: 'status',
value: (
<IconLabel icon='check-circle' iconColor='green'>
<IconLabel icon='check-circle' iconColorClass='text-green'>
Mailgun is set up
</IconLabel>
)

View file

@ -35,12 +35,12 @@ const LockSite: React.FC = () => {
key: 'private',
value: passwordEnabled ? (
<div className='flex items-center gap-1'>
<Icon color='yellow' name='lock-locked' size='sm' />
<Icon colorClass='text-yellow' name='lock-locked' size='sm' />
<span>Your site is password protected</span>
</div>
) : (
<div className='flex items-center gap-1 text-grey-900'>
<Icon color='black' name='lock-unlocked' size='sm' />
<Icon colorClass='text-black' name='lock-unlocked' size='sm' />
<span>Your site is not password protected</span>
</div>
)

View file

@ -47,7 +47,7 @@ const RoleSelector: React.FC<UserDetailProps> = ({user, setUserData}) => {
<>
<Heading level={6}>Role</Heading>
<div className='flex h-[295px] flex-col items-center justify-center gap-3 bg-grey-75 px-10 py-20 text-center text-sm text-grey-800'>
<Icon color='grey-800' name='crown' size='lg' />
<Icon colorClass='text-grey-800' name='crown' size='lg' />
This user is the owner of the site. To change their role, you need to transfer the ownership first.
</div>
</>
@ -401,7 +401,7 @@ interface UserDetailModalProps {
const UserMenuTrigger = () => (
<div className='flex h-8 cursor-pointer items-center justify-center rounded bg-[rgba(0,0,0,0.75)] px-3 opacity-80 hover:opacity-100'>
<Icon color='white' name='menu-horizontal' size='sm' />
<Icon colorClass='text-white' name='menu-horizontal' size='sm' />
</div>
);
@ -643,7 +643,7 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
<div className='relative flex items-center gap-4 px-12 pb-12 pt-60'>
<ImageUpload
deleteButtonClassName='invisible absolute -right-2 -top-2 flex h-8 w-8 cursor-pointer items-center justify-center rounded-full bg-[rgba(0,0,0,0.75)] text-white hover:bg-black group-hover:!visible'
deleteButtonContent={<Icon color='white' name='trash' size='sm' />}
deleteButtonContent={<Icon colorClass='text-white' name='trash' size='sm' />}
fileUploadClassName='rounded-full bg-black flex items-center justify-center opacity-80 transition hover:opacity-100 -ml-2 cursor-pointer h-[80px] w-[80px]'
id='avatar'
imageClassName='relative rounded-full group bg-cover bg-center -ml-2 h-[80px] w-[80px]'
@ -657,7 +657,7 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
handleImageUpload('profile_image', file);
}}
>
<Icon color='white' name='user-add' size='lg' />
<Icon colorClass='text-white' name='user-add' size='lg' />
</ImageUpload>
<div>
<Heading styles='text-white'>{user.name}{suspendedText}</Heading>

View file

@ -1,12 +1,40 @@
import ButtonGroup from '../../../../admin-x-ds/global/ButtonGroup';
import DesktopChrome from '../../../../admin-x-ds/global/DesktopChrome';
import React from 'react';
import URLSelect from '../../../../admin-x-ds/global/URLSelect';
import {SelectOption} from '../../../../admin-x-ds/global/Select';
const ThemePreview: React.FC = () => {
const urlOptions: SelectOption[] = [
{value: 'homepage', label: 'Homepage'},
{value: 'post', label: 'Post'}
];
const toolbarCenter = (
<URLSelect options={urlOptions} onSelect={(value: string) => {
alert(value);
}} />
);
const toolbarRight = (
<ButtonGroup
buttons={[
{icon: 'laptop', link: true, size: 'sm'},
{icon: 'mobile', link: true, size: 'sm', iconColorClass: 'text-grey-500'}
]}
/>
);
return (
<>
<DesktopChrome>
<DesktopChrome
chromeClasses='bg-grey-50'
toolbarCenter={toolbarCenter}
toolbarClasses='m-2'
toolbarRight={toolbarRight}
>
<div className='flex h-full items-center justify-center bg-grey-50 text-sm text-grey-400'>
Preview iframe
Preview iframe
</div>
</DesktopChrome>
</>