0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-04-15 03:01:37 -05:00

AdminX design cleanup (#17489)

refs. https://github.com/TryGhost/Product/issues/3349

- applied outline and fixed spacing for form groups
- small UI refinements for static version of Newsletter settings
- replaced textareas with textfields in site description, twitter and FB descriptions
- unified pattern for "Save & close" and "Cancel" in user detail settings
- refined checked background for logo container in Design settings
- refined spacing in Tier detail modal
- fixed gradient bug in Portal preview
- fixed UI bugs in Portal / Links
- fixed tier dropdown bug in Portal / Links. It was always showing links for the first tier
- unified form input element headings
- refined checkbox and toggle label typography and spacing
This commit is contained in:
Peter Zimon 2023-07-26 12:47:52 +02:00 committed by GitHub
parent 48ccea818a
commit acd84fe25c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 199 additions and 127 deletions

View file

@ -38,12 +38,13 @@ type HeadingLabelProps = {
grey?: boolean } & HeadingBaseProps & React.LabelHTMLAttributes<HTMLLabelElement>
export const Heading6Styles = 'text-2xs font-semibold uppercase tracking-wider';
export const Heading6StylesGrey = 'text-2xs font-semibold uppercase tracking-wider text-grey-800';
const Heading: React.FC<Heading1to5Props | Heading6Props | HeadingLabelProps> = ({
level,
children,
styles = '',
grey,
grey = true,
separator,
useLabelTag,
className = '',
@ -54,7 +55,7 @@ const Heading: React.FC<Heading1to5Props | Heading6Props | HeadingLabelProps> =
}
const newElement = `${useLabelTag ? 'label' : `h${level}`}`;
styles += (level === 6 || useLabelTag) ? (` block text-2xs ${Heading6Styles} ${(grey && 'text-grey-700')}`) : ' ';
styles += (level === 6 || useLabelTag) ? (` block ${grey ? Heading6StylesGrey : Heading6Styles}`) : ' ';
const Element = React.createElement(newElement, {className: styles + ' ' + className, key: 'heading-elem', ...props}, children);

View file

@ -19,7 +19,7 @@ const listItems = (
<>
<ListItem id='list-item-1' {...listItemProps}/>
<ListItem id='list-item-2' {...listItemProps}/>
<ListItem id='list-item-3' {...listItemProps}/>
<ListItem id='list-item-3' separator={false} {...listItemProps}/>
</>
);

View file

@ -1,6 +1,6 @@
import Heading from './Heading';
import Hint from './Hint';
import ListHeading from './ListHeading';
import ListHeading, {ListHeadingSize} from './ListHeading';
import React from 'react';
import Separator from './Separator';
import clsx from 'clsx';
@ -15,6 +15,7 @@ interface ListProps {
* When you use the list in a block and it's not the primary content of the page then you can set a title to the list
*/
title?: React.ReactNode;
titleSize?: ListHeadingSize;
titleSeparator?: boolean;
children?: React.ReactNode;
actions?: React.ReactNode;
@ -24,10 +25,18 @@ interface ListProps {
className?: string;
}
const List: React.FC<ListProps> = ({title, titleSeparator, children, actions, hint, hintSeparator, borderTop, pageTitle, className}) => {
titleSeparator = (titleSeparator === undefined) ? true : titleSeparator;
hintSeparator = (hintSeparator === undefined) ? true : hintSeparator;
const List: React.FC<ListProps> = ({
title,
titleSeparator = true,
titleSize = 'sm',
children,
actions,
hint,
hintSeparator = true,
borderTop,
pageTitle,
className
}) => {
const listClasses = clsx(
(borderTop || pageTitle) && 'border-t border-grey-300',
pageTitle && 'mt-14',
@ -38,15 +47,15 @@ const List: React.FC<ListProps> = ({title, titleSeparator, children, actions, hi
<>
{pageTitle && <Heading>{pageTitle}</Heading>}
<section className={listClasses}>
<ListHeading actions={actions} title={title} titleSeparator={!pageTitle && titleSeparator && borderTop} />
<ListHeading actions={actions} title={title} titleSeparator={!pageTitle && titleSeparator && !borderTop} titleSize={titleSize} />
<div className='flex flex-col'>
{children}
</div>
{hint &&
<>
<div className='-mt-px'>
{hintSeparator && <Separator />}
<Hint>{hint}</Hint>
</>
</div>
}
</section>
</>

View file

@ -2,17 +2,28 @@ import Heading from './Heading';
import React from 'react';
import Separator from './Separator';
export type ListHeadingSize = 'sm' | 'lg';
interface ListHeadingProps {
title?: React.ReactNode;
titleSize?: ListHeadingSize,
actions?: React.ReactNode;
titleSeparator?: boolean;
}
const ListHeading: React.FC<ListHeadingProps> = ({title, actions, titleSeparator}) => {
const ListHeading: React.FC<ListHeadingProps> = ({
title,
titleSize = 'sm',
actions,
titleSeparator
}) => {
let heading;
if (title) {
const headingTitle = <Heading grey={true} level={6}>{title}</Heading>;
const headingTitle = titleSize === 'sm' ?
<Heading grey={true} level={6}>{title}</Heading>
:
<Heading level={5}>{title}</Heading>;
heading = actions ? (
<div className='flex items-end justify-between gap-2'>
{headingTitle}

View file

@ -21,12 +21,24 @@ interface ListItemProps {
children?: React.ReactNode;
}
const ListItem: React.FC<ListItemProps> = ({id, title, detail, action, hideActions, avatar, className, testId, separator, bgOnHover = true, onClick, children}) => {
const ListItem: React.FC<ListItemProps> = ({
id,
title,
detail,
action,
hideActions,
avatar,
className,
testId,
separator = true,
bgOnHover = true,
onClick,
children
}) => {
const handleClick = (e: React.MouseEvent<HTMLDivElement>) => {
onClick?.(e);
};
separator = (separator === undefined) ? true : separator;
const listItemClasses = clsx(
'group flex items-center justify-between',
bgOnHover && 'hover:bg-gradient-to-r hover:from-white hover:to-grey-50',

View file

@ -45,7 +45,7 @@ const Checkbox: React.FC<CheckboxProps> = ({title, label, value, onChange, disab
onChange={handleOnChange}
/>
<div className={`ml-2 flex flex-col ${hint && 'mb-2'}`}>
<span className={`inline-block text-md ${hint && '-mb-1'}`}>{label}</span>
<span className={`inline-block text-[1.425rem] ${hint && '-mb-1'}`}>{label}</span>
{hint && <Hint color={error ? 'red' : ''}>{hint}</Hint>}
</div>
</label>

View file

@ -18,7 +18,7 @@ const CheckboxGroup: React.FC<CheckboxGroupProps> = ({
}) => {
return (
<div>
{title && <Heading level={6}>{title}</Heading>}
{title && <Heading grey={true} level={6}>{title}</Heading>}
<div className='mt-2 flex flex-col gap-1'>
{checkboxes?.map(({key, ...props}) => (
<Checkbox key={key} {...props} />

View file

@ -38,4 +38,19 @@ export const LargeGap: Story = {
children: formElements,
gap: 'lg'
}
};
export const WithTitle: Story = {
args: {
title: 'Form group',
children: formElements
}
};
export const Grouped: Story = {
args: {
title: 'Form group',
children: formElements,
grouped: true
}
};

View file

@ -58,9 +58,19 @@ const Form: React.FC<FormProps> = ({
grouped ? 'mb-2' : 'mb-4'
);
if (grouped || title) {
return (
<div>
{title && <Heading className={titleClasses} level={5}>{title}</Heading>}
<div className={classes}>
{children}
</div>
</div>
);
}
return (
<div className={classes}>
{title && <Heading className={titleClasses} level={5}>{title}</Heading>}
{children}
</div>
);

View file

@ -47,7 +47,7 @@ const HtmlField: React.FC<HtmlFieldProps> = ({
return (
<div className={`flex flex-col ${containerClassName}`}>
{title && <Heading className={hideTitle ? 'sr-only' : ''} grey={value ? true : false} useLabelTag={true}>{title}</Heading>}
{title && <Heading className={hideTitle ? 'sr-only' : ''} grey={true} useLabelTag={true}>{title}</Heading>}
<div className={textFieldClasses}>
<HtmlEditor {...props} value={value} />
</div>

View file

@ -96,8 +96,8 @@ const ImageUpload: React.FC<ImageUploadProps> = ({
);
if (imageBWCheckedBg) {
const dark = '#ddd';
const light = '#f9f9f9';
const dark = '#d9d9d9';
const light = '#f1f1f1';
image = (
<div style={{
backgroundImage: `

View file

@ -25,10 +25,10 @@ export const Default: Story = {
}
};
export const Clear: Story = {
export const WithBackground: Story = {
args: {
options: options,
clearBg: true
clearBg: false
}
};

View file

@ -52,7 +52,7 @@ const Option: React.FC<OptionProps<MultiSelectOption, true>> = ({children, ...op
const MultiSelect: React.FC<MultiSelectProps> = ({
title = '',
clearBg = false,
clearBg = true,
error = false,
placeholder,
color = 'grey',

View file

@ -24,13 +24,6 @@ export const Default: Story = {
}
};
export const ClearBackground: Story = {
args: {
placeholder: 'Enter description',
clearBg: true
}
};
export const WithValue: Story = {
render: function Component(args) {
const [, updateArgs] = useArgs();

View file

@ -2,6 +2,7 @@ import React, {useId} from 'react';
import Heading from '../Heading';
import Hint from '../Hint';
import clsx from 'clsx';
type ResizeOptions = 'both' | 'vertical' | 'horizontal' | 'none';
@ -35,7 +36,12 @@ const TextArea: React.FC<TextAreaProps> = ({
}) => {
const id = useId();
let styles = `border-b ${clearBg ? 'bg-transparent' : 'bg-grey-75 px-[10px]'} py-2 ${error ? `border-red` : `border-grey-500 hover:border-grey-700 focus:border-black`} ${(title && !clearBg) && `mt-2`}`;
let styles = clsx(
'rounded-sm border px-3 py-2',
clearBg ? 'bg-transparent' : 'bg-grey-75',
error ? 'border-red' : 'border-grey-500 hover:border-grey-700 focus:border-grey-800',
title && 'mt-2'
);
switch (resize) {
case 'both':
@ -57,7 +63,7 @@ const TextArea: React.FC<TextAreaProps> = ({
return (
<div className='flex flex-col'>
{title && <Heading grey={value ? true : false} htmlFor={id} useLabelTag={true}>{title}</Heading>}
{title && <Heading grey={true} htmlFor={id} useLabelTag={true}>{title}</Heading>}
<textarea
ref={inputRef}
className={styles}

View file

@ -29,7 +29,7 @@ const TextField: React.FC<TextFieldProps> = ({
type = 'text',
inputRef,
title,
titleColor = 'auto',
titleColor = 'grey',
hideTitle,
value,
error,
@ -50,7 +50,7 @@ const TextField: React.FC<TextFieldProps> = ({
const id = useId();
const textFieldClasses = !unstyled && clsx(
'h-10 border-b py-2',
'h-10 w-full border-b py-2',
clearBg ? 'bg-transparent' : 'bg-grey-75 px-[10px]',
error ? `border-red` : `${disabled ? 'border-grey-300' : 'border-grey-500 hover:border-grey-700 focus:border-black'}`,
(title && !hideTitle && !clearBg) && `mt-2`,
@ -91,15 +91,15 @@ const TextField: React.FC<TextFieldProps> = ({
}
if (title || hint) {
let titleGrey = false;
if (titleColor === 'auto') {
titleGrey = value ? true : false;
} else {
titleGrey = titleColor === 'grey' ? true : false;
}
// let titleGrey = false;
// if (titleColor === 'auto') {
// titleGrey = value ? true : false;
// } else {
// titleGrey = titleColor === 'grey' ? true : false;
// }
return (
<div className={`flex flex-col ${containerClassName}`}>
{title && <Heading className={hideTitle ? 'sr-only' : ''} grey={titleGrey} htmlFor={id} useLabelTag={true}>{title}</Heading>}
{title && <Heading className={hideTitle ? 'sr-only' : ''} grey={true} htmlFor={id} useLabelTag={true}>{title}</Heading>}
{field}
{hint && <Hint className={hintClassName} color={error ? 'red' : ''}>{hint}</Hint>}
</div>

View file

@ -1,7 +1,7 @@
import React, {useId} from 'react';
import Separator from '../Separator';
import clsx from 'clsx';
import {Heading6Styles} from '../Heading';
import {Heading6StylesGrey} from '../Heading';
type ToggleSizes = 'sm' | 'md' | 'lg';
type ToggleDirections = 'ltr' | 'rtl';
@ -14,7 +14,7 @@ interface ToggleProps {
label?: React.ReactNode;
labelStyle?: 'heading' | 'value';
labelClasses?: string;
toggleBg?: 'green' | 'stripetest';
toggleBg?: 'green' | 'black' | 'stripetest';
separator?: boolean;
direction?: ToggleDirections;
hint?: React.ReactNode;
@ -27,7 +27,7 @@ const Toggle: React.FC<ToggleProps> = ({
label,
labelStyle = 'value',
labelClasses,
toggleBg = 'green',
toggleBg = 'black',
hint,
separator,
error,
@ -70,9 +70,13 @@ const Toggle: React.FC<ToggleProps> = ({
toggleBgClass = 'checked:bg-[#EC6803]';
break;
default:
case 'green':
toggleBgClass = 'checked:bg-green';
break;
default:
toggleBgClass = 'checked:bg-black';
break;
}
return (
@ -88,7 +92,7 @@ const Toggle: React.FC<ToggleProps> = ({
<label className={`flex flex-col hover:cursor-pointer ${direction === 'rtl' && 'order-1'} ${labelStyles}`} htmlFor={id}>
{
labelStyle === 'heading' ?
<span className={`${Heading6Styles} mt-1`}>{label}</span>
<span className={`${Heading6StylesGrey} mt-1`}>{label}</span>
:
<span>{label}</span>
}

View file

@ -10,7 +10,7 @@ interface ModalPageProps {
const ModalPage: React.FC<ModalPageProps> = ({heading, children, className}) => {
className = clsx(
'h-full w-full p-[8vmin] pt-5',
'w-full p-[8vmin] pt-5',
className
);
return (

View file

@ -7,6 +7,7 @@ import NiceModal, {useModal} from '@ebay/nice-modal-react';
import React, {useEffect, useState} from 'react';
import Select, {SelectOption} from '../form/Select';
import TabView, {Tab} from '../TabView';
import clsx from 'clsx';
import useGlobalDirtyState from '../../../hooks/useGlobalDirtyState';
import {ButtonProps} from '../Button';
import {confirmIfDirty} from '../../../utils/modals';
@ -27,7 +28,7 @@ export interface PreviewModalProps {
rightToolbar?: boolean;
deviceSelector?: boolean;
previewToolbarURLs?: SelectOption[];
previewBgColor?: 'grey' | 'white';
previewBgColor?: 'grey' | 'white' | 'greygradient';
selectedURL?: string;
previewToolbarTabs?: Tab[];
defaultTab?: string;
@ -144,8 +145,20 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
/>
);
let previewBgClass = '';
if (previewBgColor === 'grey') {
previewBgClass = 'bg-grey-50';
} else if (previewBgColor === 'greygradient') {
previewBgClass = 'bg-gradient-to-tr from-white to-[#f9f9fa]';
}
const containerClasses = clsx(
'min-w-100 absolute inset-y-0 left-0 right-[400px] flex grow flex-col overflow-y-scroll',
previewBgClass
);
preview = (
<div className={`min-w-100 absolute inset-y-0 left-0 right-[400px] flex grow flex-col overflow-y-scroll ${previewBgColor === 'grey' ? 'bg-grey-50' : 'bg-white'}`}>
<div className={containerClasses}>
{previewToolbar && <header className="relative flex h-[74px] shrink-0 items-center justify-center px-3 py-5" data-testid="design-toolbar">
{leftToolbar && <div className='absolute left-5 flex h-full items-center'>
{toolbarLeft}
@ -154,7 +167,7 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
{toolbarRight}
</div>}
</header>}
<div className='flex h-full grow items-center justify-center text-sm text-grey-400'>
<div className='flex grow items-center justify-center text-sm text-grey-400'>
{preview}
</div>
</div>

View file

@ -15,9 +15,6 @@ import TextArea from '../../../../admin-x-ds/global/form/TextArea';
import TextField from '../../../../admin-x-ds/global/form/TextField';
import Toggle from '../../../../admin-x-ds/global/form/Toggle';
import {PreviewModalContent} from '../../../../admin-x-ds/global/modal/PreviewModal';
// import {ServicesContext} from '../../providers/ServiceProvider';
// import {SettingsContext} from '../../providers/SettingsProvider';
// import {getHomepageUrl, getSettingValues} from '../../../utils/helpers';
interface NewsletterDetailModalProps {
@ -29,8 +26,8 @@ interface NewsletterDetailModalProps {
// ];
const selectOptions: SelectOption[] = [
{value: 'option-1', label: 'Option 1'},
{value: 'option-2', label: 'Option 2'}
{value: 'option-1', label: 'Elegant serif'},
{value: 'option-2', label: 'Modern sans-serif'}
];
const Sidebar: React.FC = () => {
@ -57,6 +54,7 @@ const Sidebar: React.FC = () => {
<Heading className="mt-5" level={5}>Member settings</Heading>
<Toggle
direction='rtl'
label='Subscribe new members on signup'
labelStyle='value'
/>
@ -180,7 +178,7 @@ const Sidebar: React.FC = () => {
<StickyFooter height={96}>
<div className='flex w-full items-start px-7'>
<span>
<Icon className='-mt-[1px] mr-2' colorClass='text-red' name='heart'/>
<Icon className='mr-2 mt-[-1px]' colorClass='text-red' name='heart'/>
</span>
<Form marginBottom={false}>
<Toggle

View file

@ -1,4 +1,8 @@
import CoverImage from '../../../../assets/images/user-cover.png';
import Icon from '../../../../admin-x-ds/global/Icon';
import LatestPosts1 from '../../../../assets/images/latest-posts-1.png';
import LatestPosts2 from '../../../../assets/images/latest-posts-2.png';
import LatestPosts3 from '../../../../assets/images/latest-posts-3.png';
import React from 'react';
import {ReactComponent as GhostOrb} from '../../../../admin-x-ds/assets/images/ghost-orb.svg';
@ -26,18 +30,20 @@ const NewsletterPreview: React.FC = () => {
<div className="flex flex-col items-center pb-10 pt-12">
<h2 className="pb-4 text-center text-5xl font-bold leading-supertight text-black">Your email newsletter</h2>
<div className="flex w-full flex-col justify-between text-center text-sm leading-none tracking-[0.1px] text-grey-600">
<p className="pb-2">By Djordje Vlaisavljevic<span className="before:pl-0.5 before:pr-1 before:content-['•']">17 Jul 2023</span><span className="before:pl-0.5 before:pr-1 before:content-['•']"><Icon className="-mt-[2px] inline-block" colorClass="text-grey-600" name="comment" size="sm"/></span></p>
<p className="pb-2">By Djordje Vlaisavljevic<span className="before:pl-0.5 before:pr-1 before:content-['•']">17 Jul 2023</span><span className="before:pl-0.5 before:pr-1 before:content-['•']"><Icon className="mt-[-2px] inline-block" colorClass="text-grey-600" name="comment" size="sm"/></span></p>
<p className="pb-2 underline"><span>View in browser</span></p>
</div>
</div>
{/* Feature image */}
<div className="mb-2 h-[300px] w-full max-w-[600px] bg-grey-300 bg-cover bg-no-repeat"></div>
<div className="w-full max-w-[600px] pb-[30px] text-center text-[1.3rem] text-grey-600">Feature image caption</div>
<div className="h-[300px] w-full max-w-[600px] bg-grey-200 bg-cover bg-no-repeat">
<img alt="Feature" className='min-h-full min-w-full shrink-0' src={CoverImage} />
</div>
<div className="mt-1 w-full max-w-[600px] pb-[30px] text-center text-[1.3rem] text-grey-600">Feature image caption</div>
<div className="max-w-[600px] border-b border-grey-200 py-5 text-[1.6rem] leading-[1.7] text-black">
<p className="mb-5">This is what your content will look like when you send one of your posts as an email newsletter to your subscribers.</p>
<p className="mb-5">Over there on the left youll see some settings that allow you to customize the look and feel of this template to make it perfectly suited to your brand. Email templates are exceptionally finnicky to make, but weve spent a long time optimising this one to make it work beautifully across devices, email clients and content types.</p>
<p className="mb-5">Over there on the left you'll see some settings that allow you to customize the look and feel of this template to make it perfectly suited to your brand. Email templates are exceptionally finnicky to make, but we've spent a long time optimising this one to make it work beautifully across devices, email clients and content types.</p>
<p className="mb-5">So, you can trust that every email you send with Ghost will look great and work well. Just like the rest of your site.</p>
</div>
@ -67,33 +73,39 @@ const NewsletterPreview: React.FC = () => {
{/* Latest posts */}
<div className="border-b border-grey-200 py-6">
<h3 className="mb-4 mt-2 pb-1 text-[1.4rem] font-semibold uppercase">Keep reading</h3>
<h3 className="mb-4 mt-2 pb-1 text-[1.2rem] font-semibold uppercase tracking-wide">Keep reading</h3>
<div className="flex justify-between gap-4 py-2">
<div>
<h4 className="mb-1 mt-0.5 text-[1.9rem]">The three latest posts published on your site</h4>
<p className="m-0 text-base text-grey-600">Posts sent as an email only will never be shown here.</p>
</div>
<div className="aspect-square h-auto w-full max-w-[100px] bg-grey-200 bg-cover bg-no-repeat" style={{backgroundImage: 'url(\'/../../../../admin-x-ds/assets/images/latest-posts-1.png'}}></div>
<div className="aspect-square h-auto w-full max-w-[100px] bg-grey-200 bg-cover bg-no-repeat">
<img alt="Latest post" src={LatestPosts1} />
</div>
</div>
<div className="flex justify-between gap-4 py-2">
<div>
<h4 className="mb-1 mt-0.5 text-[1.9rem]">Displayed at the bottom of each newsletter</h4>
<p className="m-0 text-base text-grey-600">Giving your readers one more place to discover your stories.</p>
</div>
<div className="aspect-square h-auto w-full max-w-[100px] bg-grey-200 bg-cover bg-no-repeat"></div>
<div className="aspect-square h-auto w-full max-w-[100px] bg-grey-200 bg-cover bg-no-repeat">
<img alt="Latest post" src={LatestPosts2} />
</div>
</div>
<div className="flex justify-between gap-4 py-2">
<div>
<h4 className="mb-1 mt-0.5 text-[1.9rem]">To keep your work front and center</h4>
<p className="m-0 text-base text-grey-600">Making sure that your audience stays engaged.</p>
</div>
<div className="aspect-square h-auto w-full max-w-[100px] bg-grey-200 bg-cover bg-no-repeat"></div>
<div className="aspect-square h-auto w-full max-w-[100px] bg-grey-200 bg-cover bg-no-repeat">
<img alt="Latest post" src={LatestPosts3} />
</div>
</div>
</div>
{/* Subscription details */}
<div className="border-b border-grey-200 py-8">
<h4 className="mb-3 text-[1.4rem] uppercase">Subscription details</h4>
<h4 className="mb-3 text-[1.2rem] uppercase tracking-wide">Subscription details</h4>
<p className="m-0 mb-4 text-base">You are receiving this because you are a paid subscriber to The Local Host. Your subscription will renew on 17 Jul 2024.</p>
<div className="flex">
<div className="shrink-0 text-base">
@ -110,12 +122,12 @@ const NewsletterPreview: React.FC = () => {
<div className="text break-words px-8 py-3 text-center text-[1.3rem] leading-base text-grey-600">This is custom email footer text.</div>
<div className="px-8 pb-14 pt-3 text-center text-[1.3rem] text-grey-600">
<span>Ghosty © 2023 </span>
<span>Ghost © 2023 &mdash; </span>
<span className="pointer-events-none cursor-auto underline">Unsubscribe</span>
</div>
<div className="flex flex-col items-center pb-[40px] pt-[10px]">
<a className="pointer-events-none inline-flex cursor-auto items-center px-2 py-1 text-[1.25rem] font-semibold tracking-tight text-grey-900" href="#">
<a className="pointer-events-none inline-flex cursor-auto items-center px-2 py-1 text-[1.25rem] font-semibold tracking-tight text-grey-900" href="https://ghost.org">
<GhostOrb className="mr-[6px] h-4 w-4"/>
<span>Powered by Ghost</span>
</a>

View file

@ -2,7 +2,6 @@ import ImageUpload from '../../../admin-x-ds/global/form/ImageUpload';
import React, {useContext} from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
import TextArea from '../../../admin-x-ds/global/form/TextArea';
import TextField from '../../../admin-x-ds/global/form/TextField';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {ReactComponent as FacebookLogo} from '../../../admin-x-ds/assets/images/facebook-logo.svg';
@ -31,7 +30,7 @@ const Facebook: React.FC<{ keywords: string[] }> = ({keywords}) => {
updateSetting('og_title', e.target.value);
};
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
updateSetting('og_description', e.target.value);
};
@ -82,10 +81,9 @@ const Facebook: React.FC<{ keywords: string[] }> = ({keywords}) => {
value={facebookTitle}
onChange={handleTitleChange}
/>
<TextArea
<TextField
clearBg={true}
placeholder={siteDescription}
rows={2}
title="Facebook description"
value={facebookDescription}
onChange={handleDescriptionChange}

View file

@ -1,7 +1,6 @@
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
import TextArea from '../../../admin-x-ds/global/form/TextArea';
import TextField from '../../../admin-x-ds/global/form/TextField';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {getSettingValues} from '../../../utils/helpers';
@ -24,7 +23,7 @@ const TitleAndDescription: React.FC<{ keywords: string[] }> = ({keywords}) => {
updateSetting('title', e.target.value);
};
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
updateSetting('description', e.target.value);
};
@ -56,7 +55,7 @@ const TitleAndDescription: React.FC<{ keywords: string[] }> = ({keywords}) => {
value={title}
onChange={handleTitleChange}
/>
<TextArea
<TextField
hint="A short description, used in your theme, meta data and search results"
placeholder="Site description"
title="Site description"

View file

@ -2,7 +2,6 @@ import ImageUpload from '../../../admin-x-ds/global/form/ImageUpload';
import React, {useContext} from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
import TextArea from '../../../admin-x-ds/global/form/TextArea';
import TextField from '../../../admin-x-ds/global/form/TextField';
import useSettingGroup from '../../../hooks/useSettingGroup';
import {FileService, ServicesContext} from '../../providers/ServiceProvider';
@ -31,7 +30,7 @@ const Twitter: React.FC<{ keywords: string[] }> = ({keywords}) => {
updateSetting('twitter_title', e.target.value);
};
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const handleDescriptionChange = (e: React.ChangeEvent<HTMLInputElement>) => {
updateSetting('twitter_description', e.target.value);
};
@ -84,10 +83,9 @@ const Twitter: React.FC<{ keywords: string[] }> = ({keywords}) => {
value={twitterTitle}
onChange={handleTitleChange}
/>
<TextArea
<TextField
clearBg={true}
placeholder={siteDescription}
rows={2}
title="Twitter description"
value={twitterDescription}
onChange={handleDescriptionChange}

View file

@ -568,28 +568,22 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
}
]);
let okLabel = saveState === 'saved' ? 'Saved' : 'Save';
let okLabel = saveState === 'saved' ? 'Saved' : 'Save & close';
if (saveState === 'saving') {
okLabel = 'Saving...';
} else if (saveState === 'saved') {
okLabel = 'Saved';
setTimeout(() => {
mainModal.remove();
}, 300);
}
// remove saved state after 2 seconds
useEffect(() => {
if (saveState === 'saved') {
setTimeout(() => {
setSaveState('');
}, 2000);
}
}, [saveState]);
const fileUploadButtonClasses = 'absolute right-[104px] bottom-12 bg-[rgba(0,0,0,0.75)] rounded text-sm text-white flex items-center justify-center px-3 h-8 opacity-80 hover:opacity-100 transition cursor-pointer font-medium z-10';
const suspendedText = userData.status === 'inactive' ? ' (Suspended)' : '';
return (
<Modal
backDropClick={false}
cancelLabel='Close'
okLabel={okLabel}
size='lg'
stickyFooter={true}

View file

@ -48,10 +48,10 @@ const PortalLinks: React.FC = () => {
};
useEffect(() => {
if (tiers?.length) {
if (tiers?.length && !selectedTier) {
setSelectedTier(tiers[0].id);
}
}, [tiers]);
}, [tiers, selectedTier]);
const tierOptions = tiers?.map((tier) => {
return {
@ -66,19 +66,19 @@ const PortalLinks: React.FC = () => {
<ModalPage className='max-w-[920px] text-base text-black' heading='Links'>
<p className='-mt-6 mb-16'>Use these {isDataAttributes ? 'data attributes' : 'links'} in your theme to show pages of Portal.</p>
<List actions={<Button color='green' label={isDataAttributes ? 'Links' : 'Data attributes'} link onClick={toggleIsDataAttributes}/>} title='Generic'>
<List actions={<Button color='green' label={isDataAttributes ? 'Links' : 'Data attributes'} link onClick={toggleIsDataAttributes}/>} title='Generic' titleSize='lg'>
<PortalLink name='Default' value={isDataAttributes ? 'data-portal' : `${homePageURL}#/portal`} />
<PortalLink name='Sign in' value={isDataAttributes ? 'data-portal="signin"' : `${homePageURL}#/portal/signin`} />
<PortalLink name='Sign up' value={isDataAttributes ? 'data-portal="signup"' : `${homePageURL}#/portal/signup`} />
</List>
<List className='mt-14' title='Tiers'>
<List className='mt-14' title='Tiers' titleSize='lg'>
<ListItem
hideActions
separator
>
<div className='flex w-full items-center gap-5 py-2 pr-6'>
<span className='inline-block w-[240px] shrink-0 font-bold'>Tier</span>
<span className='inline-block w-[240px] shrink-0'>Tier</span>
<Select
options={tierOptions}
selectedOption={selectedTier}
@ -93,7 +93,7 @@ const PortalLinks: React.FC = () => {
<PortalLink name='Signup / Free' value={isDataAttributes ? 'data-portal="signup/free"' : `${homePageURL}#/portal/signup/free`} />
</List>
<List className='mt-14' title='Account'>
<List className='mt-14' title='Account' titleSize='lg'>
<PortalLink name='Account' value={isDataAttributes ? 'data-portal="account"' : `${homePageURL}#/portal/account`} />
<PortalLink name='Account / Plans' value={isDataAttributes ? 'data-portal="account/plans"' : `${homePageURL}#/portal/account/plans`} />
<PortalLink name='Account / Profile' value={isDataAttributes ? 'data-portal="account/profile"' : `${homePageURL}#/portal/account/profile`} />

View file

@ -162,7 +162,7 @@ const PortalModal: React.FC = () => {
dirty={saveState === 'unsaved'}
okLabel={okLabel}
preview={preview}
previewBgColor={selectedPreviewTab === 'links' ? 'white' : 'grey'}
previewBgColor={selectedPreviewTab === 'links' ? 'white' : 'greygradient'}
previewToolbarTabs={previewTabs}
selectedURL={selectedPreviewTab}
sidebar={sidebar}

View file

@ -129,13 +129,13 @@ const SignupOptions: React.FC<{
onChange={html => updateSetting('portal_signup_terms_html', html)}
/>
<Toggle
{portalSignupTermsHtml?.toString() && <Toggle
checked={Boolean(portalSignupCheckboxRequired)}
disabled={isDisabled}
label='Require agreement'
labelStyle='heading'
onChange={e => updateSetting('portal_signup_checkbox_required', e.target.checked)}
/>
/>}
</Form>;
};

View file

@ -118,9 +118,9 @@ const TierDetailModal: React.FC<TierDetailModalProps> = ({tier}) => {
handleSave();
}}
>
<div className='mt-8 flex items-start gap-16'>
<div className='flex grow flex-col gap-5'>
<Form title='Basic'>
<div className='-mb-8 mt-8 flex items-start gap-8'>
<div className='flex grow flex-col gap-8'>
<Form marginBottom={false} title='Basic' grouped>
{!isFreeTier && <TextField
autoComplete='off'
error={Boolean(errors.name)}
@ -204,26 +204,29 @@ const TierDetailModal: React.FC<TierDetailModalProps> = ({tier}) => {
</div>}
</Form>
<Form gap='none' title='Benefits'>
<SortableList
items={benefits.items}
itemSeparator={false}
renderItem={({id, item}) => <div className='relative flex w-full items-center gap-5'>
<div className='absolute left-[-32px] top-[7px] flex h-6 w-6 items-center justify-center bg-white group-hover:hidden'><Icon name='check' size='sm' /></div>
<TextField
className='grow border-b border-grey-500 py-2 focus:border-grey-800 group-hover:border-grey-600'
value={item}
unstyled
onChange={e => benefits.updateItem(id, e.target.value)}
/>
<Button className='absolute right-0 top-1' icon='trash' iconColorClass='opacity-0 group-hover:opacity-100' size='sm' onClick={() => benefits.removeItem(id)} />
</div>}
onMove={benefits.moveItem}
/>
<Form gap='none' title='Benefits' grouped>
<div className='-mt-3'>
<SortableList
items={benefits.items}
itemSeparator={false}
renderItem={({id, item}) => <div className='relative flex w-full items-center gap-5'>
<div className='absolute left-[-32px] top-[7px] flex h-6 w-6 items-center justify-center bg-white group-hover:hidden'><Icon name='check' size='sm' /></div>
<TextField
className='grow border-b border-grey-500 py-2 focus:border-grey-800 group-hover:border-grey-600'
value={item}
unstyled
onChange={e => benefits.updateItem(id, e.target.value)}
/>
<Button className='absolute right-0 top-1' icon='trash' iconColorClass='opacity-0 group-hover:opacity-100' size='sm' onClick={() => benefits.removeItem(id)} />
</div>}
onMove={benefits.moveItem}
/>
</div>
<div className="relative mt-0.5 flex items-center gap-3">
<Icon name='check' size='sm' />
<TextField
className='grow'
containerClassName='w-100'
placeholder='Expert analysis'
title='New benefit'
value={benefits.newItem}

View file

@ -90,7 +90,7 @@ const TierDetailPreview: React.FC<TierDetailPreviewProps> = ({tier, isFreeTier})
<div className="mt-1">
<div className="flex items-baseline justify-between">
<Heading className="pb-2" level={6} grey>{isFreeTier ? 'Free membership preview' : 'Tier preview'}</Heading>
{!isFreeTier && <div className="flex">
{!isFreeTier && <div className="flex gap-1">
<Button className={`${showingYearly === true ? 'text-grey-500' : 'text-grey-900'}`} label="Monthly" link onClick={() => setShowingYearly(false)} />
<Button className={`ml-2 ${showingYearly === true ? 'text-grey-900' : 'text-grey-500'}`} label="Yearly" link onClick={() => setShowingYearly(true)} />
</div>}

View file

@ -214,7 +214,7 @@ module.exports = {
full: '9999px'
},
fontSize: {
'2xs': '0.95rem',
'2xs': '1.0rem',
base: '1.5rem',
xs: '1.2rem',
sm: '1.35rem',

View file

@ -40,12 +40,10 @@ test.describe('User profile', async () => {
await modal.getByLabel(/New paid members/).uncheck();
await modal.getByLabel(/Paid member cancellations/).check();
await modal.getByRole('button', {name: 'Save'}).click();
await modal.getByRole('button', {name: 'Save & close'}).click();
await expect(modal.getByRole('button', {name: 'Saved'})).toBeVisible();
await modal.getByRole('button', {name: 'Close'}).click();
await expect(listItem.getByText('New Admin')).toBeVisible();
await expect(listItem.getByText('newadmin@test.com')).toBeVisible();

View file

@ -61,12 +61,10 @@ test.describe('User roles', async () => {
await modal.locator('input[value=editor]').check();
await modal.getByRole('button', {name: 'Save'}).click();
await modal.getByRole('button', {name: 'Save & close'}).click();
await expect(modal.getByRole('button', {name: 'Saved'})).toBeVisible();
await modal.getByRole('button', {name: 'Close'}).click();
await expect(activeTab).toHaveText(/No users found/);
await section.getByRole('tab', {name: 'Editors'}).click();