0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Admin X design system updates (#19102)

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

- some of the new components were not prepared for mobile sizes and dark
mode
- Storybook settings had to be updated to include mobile sizes that
reflect the actual system
This commit is contained in:
Peter Zimon 2023-11-22 16:53:23 +01:00 committed by GitHub
parent 9848469568
commit 82a775086c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 172 additions and 53 deletions

View file

@ -7,6 +7,46 @@ import type { Preview } from "@storybook/react";
import DesignSystemProvider from '../src/providers/DesignSystemProvider';
import adminxTheme from './adminx-theme';
// import { MINIMAL_VIEWPORTS } from '@storybook/addon-viewport';
const customViewports = {
sm: {
name: 'sm',
styles: {
width: '480px',
height: '801px',
},
},
md: {
name: 'md',
styles: {
width: '640px',
height: '801px',
},
},
lg: {
name: 'lg',
styles: {
width: '1024px',
height: '801px',
},
},
xl: {
name: 'xl',
styles: {
width: '1320px',
height: '801px',
},
},
tablet: {
name: 'tablet',
styles: {
width: '860px',
height: '801px',
},
},
};
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
@ -25,6 +65,11 @@ const preview: Preview = {
docs: {
theme: adminxTheme,
},
viewport: {
viewports: {
...customViewports,
},
},
},
decorators: [
(Story, context) => {

View file

@ -1,4 +1,4 @@
import Icon from './Icon';
import Icon, {IconSize} from './Icon';
import React, {HTMLProps} from 'react';
import clsx from 'clsx';
import {LoadingIndicator, LoadingIndicatorColor, LoadingIndicatorSize} from './LoadingIndicator';
@ -11,6 +11,7 @@ export interface ButtonProps extends Omit<HTMLProps<HTMLButtonElement>, 'label'
label?: React.ReactNode;
hideLabel?: boolean;
icon?: string;
iconSize?: IconSize;
iconColorClass?: string;
key?: string;
color?: ButtonColor;
@ -24,6 +25,7 @@ export interface ButtonProps extends Omit<HTMLProps<HTMLButtonElement>, 'label'
loading?: boolean;
loadingIndicatorSize?: LoadingIndicatorSize;
loadingIndicatorColor?: LoadingIndicatorColor;
outlineOnMobile?: boolean;
onClick?: (e?:React.MouseEvent<HTMLElement>) => void;
}
@ -32,6 +34,7 @@ const Button: React.FC<ButtonProps> = ({
label = '',
hideLabel = false,
icon = '',
iconSize,
iconColorClass,
color = 'clear',
fullWidth,
@ -43,6 +46,7 @@ const Button: React.FC<ButtonProps> = ({
tag = 'button',
loading = false,
loadingIndicatorColor,
outlineOnMobile = false,
onClick,
...props
}) => {
@ -108,7 +112,8 @@ const Button: React.FC<ButtonProps> = ({
break;
default:
className = clsx(
link ? ' text-black hover:text-grey-800 dark:text-white' : ` text-black dark:text-white dark:hover:bg-grey-900 ${!disabled && 'hover:bg-grey-200'}`,
link ? ' text-black hover:text-grey-800 dark:text-white' : `text-black dark:text-white dark:hover:bg-grey-900 ${!disabled && 'hover:bg-grey-200'}`,
(outlineOnMobile && !link) && 'border border-grey-300 hover:border-transparent md:border-transparent',
className
);
loadingIndicatorColor = 'dark';
@ -128,8 +133,10 @@ const Button: React.FC<ButtonProps> = ({
labelClasses += (label && hideLabel) ? 'sr-only' : '';
labelClasses += loading ? 'invisible' : '';
iconSize = iconSize || ((size === 'sm') || (label && icon) ? 'sm' : 'md');
const buttonChildren = <>
{icon && <Icon className={iconClasses} colorClass={iconColorClass} name={icon} size={size === 'sm' || (label && icon) ? 'sm' : 'md'} />}
{icon && <Icon className={iconClasses} colorClass={iconColorClass} name={icon} size={iconSize} />}
<span className={labelClasses}>{label}</span>
{loading && <div className='absolute flex'><LoadingIndicator color={loadingIndicatorColor} size={size}/><span className='sr-only'>Loading...</span></div>}
</>;

View file

@ -10,10 +10,11 @@ export interface ButtonGroupProps {
link?: boolean;
linkWithPadding?: boolean;
clearBg?: boolean;
outlineOnMobile?: boolean;
className?: string;
}
const ButtonGroup: React.FC<ButtonGroupProps> = ({size = 'md', buttons, link, linkWithPadding, clearBg = true, className}) => {
const ButtonGroup: React.FC<ButtonGroupProps> = ({size = 'md', buttons, link, linkWithPadding, clearBg = true, outlineOnMobile, className}) => {
let groupColorClasses = clsx(
'flex items-center justify-start rounded',
link ? 'gap-4' : 'gap-5',
@ -24,6 +25,7 @@ const ButtonGroup: React.FC<ButtonGroupProps> = ({size = 'md', buttons, link, li
groupColorClasses = clsx(
'transition-all hover:bg-grey-200 dark:hover:bg-grey-900',
size === 'sm' ? 'h-7 px-3' : 'h-[34px] px-4',
outlineOnMobile && 'border border-grey-300 hover:border-transparent md:border-transparent',
groupColorClasses
);
}

View file

@ -3,7 +3,7 @@ import Button from '../Button';
const PageMenu: React.FC = () => {
return (
<Button icon='hamburger' iconColorClass='text-black' size='sm' link onClick={() => {
<Button icon='hamburger' iconColorClass='text-black dark:text-white' size='sm' link onClick={() => {
alert('Clicked on hamburger');
}} />
);

View file

@ -3,7 +3,7 @@ import Button from '../Button';
const GlobalActions: React.FC = () => {
return (
<Button icon='magnifying-glass' iconColorClass='text-black' size='sm' link onClick={() => {}} />
<Button icon='magnifying-glass' iconColorClass='dark:text-white text-black' size='sm' link onClick={() => {}} />
);
};

View file

@ -108,6 +108,7 @@ const currentAdminExample = <ViewContainer
>
<DynamicTable
columns={testColumns}
pageHasSidebar={false}
rows={testRows(100)}
/>
</ViewContainer>;
@ -127,6 +128,7 @@ const simpleList = <ViewContainer
<DynamicTable
columns={testColumns}
footer={<Hint>Just a regular table footer</Hint>}
pageHasSidebar={false}
rows={testRows(100)}
/>
</ViewContainer>;
@ -149,6 +151,7 @@ const stickyList = <ViewContainer
<DynamicTable
columns={testColumns}
footer={<Hint>Sticky footer</Hint>}
pageHasSidebar={false}
rows={testRows(40)}
stickyFooter
stickyHeader
@ -180,6 +183,7 @@ const examplePrimaryAction = <ViewContainer
<DynamicTable
columns={testColumns}
footer={<Hint>Sticky footer</Hint>}
pageHasSidebar={false}
rows={testRows(40)}
stickyFooter
stickyHeader
@ -213,6 +217,7 @@ const exampleActionsContent = <ViewContainer
<DynamicTable
columns={testColumns}
footer={<Hint>Sticky footer</Hint>}
pageHasSidebar={false}
rows={testRows(40)}
stickyFooter
stickyHeader
@ -262,7 +267,7 @@ const exampleCardViewContent = (
title='Ideas'
type='page'
>
<div className='grid grid-cols-4 gap-7 py-7'>
<div className='grid grid-cols-2 gap-7 py-7 tablet:grid-cols-4'>
{mockIdeaCards()}
</div>
</ViewContainer>
@ -352,7 +357,10 @@ export const ExampleDetailScreen: Story = {
breadCrumbs: <Breadcrumbs
items={[
{
label: 'Members'
label: 'Members',
onClick: () => {
alert('Clicked back');
}
},
{
label: 'Emerson Vaccaro'
@ -379,25 +387,25 @@ export const ExampleDetailScreen: Story = {
}
type='page'
>
<div className='grid grid-cols-4 border-b border-grey-200 pb-5'>
<div className='-ml-5 flex h-full flex-col px-5'>
<div className='grid grid-cols-3 border-b border-grey-200 pb-5 tablet:grid-cols-4'>
<div className='col-span-3 -ml-5 mb-5 hidden h-full gap-4 px-5 tablet:col-span-1 tablet:mb-0 tablet:!flex tablet:flex-col tablet:gap-0'>
<span>Last seen on <strong>22 June 2023</strong></span>
<span className='mt-2'>Created on <strong>27 Jan 2021</strong></span>
<span className='tablet:mt-2'>Created on <strong>27 Jan 2021</strong></span>
</div>
<div className='flex h-full flex-col px-5'>
<div className='flex h-full flex-col tablet:px-5'>
<Heading level={6}>Emails received</Heading>
<span className='mt-1 text-4xl font-bold leading-none'>181</span>
</div>
<div className='flex h-full flex-col px-5'>
<div className='flex h-full flex-col tablet:px-5'>
<Heading level={6}>Emails opened</Heading>
<span className='mt-1 text-4xl font-bold leading-none'>104</span>
</div>
<div className='-mr-5 flex h-full flex-col px-5'>
<div className='-mr-5 flex h-full flex-col tablet:px-5'>
<Heading level={6}>Average open rate</Heading>
<span className='mt-1 text-4xl font-bold leading-none'>57%</span>
</div>
</div>
<div className='grid grid-cols-4 items-baseline py-5'>
<div className='grid grid-cols-2 items-baseline border-b border-grey-200 py-5 tablet:grid-cols-4'>
<div className='-ml-5 flex h-full flex-col gap-6 border-r border-grey-200 px-5'>
<div className='flex justify-between'>
<Heading level={5}>Member data</Heading>
@ -413,10 +421,17 @@ export const ExampleDetailScreen: Story = {
</div>
<div>
<Heading level={6}>Labels</Heading>
<div className='inline-block rounded-sm bg-grey-300 px-1 text-xs font-medium'>VIP</div>
<div className='mt-2 flex gap-1'>
<div className='inline-block rounded-sm bg-grey-200 px-1.5 text-xs font-medium'>VIP</div>
<div className='inline-block rounded-sm bg-grey-200 px-1.5 text-xs font-medium'>Inner Circle</div>
</div>
</div>
<div>
<Heading level={6}>Notes</Heading>
<div className='text-grey-500'>No notes.</div>
</div>
</div>
<div className='flex h-full flex-col gap-6 border-r border-grey-200 px-5'>
<div className='flex h-full flex-col gap-6 border-grey-200 px-5 tablet:border-r'>
<Heading level={5}>Newsletters</Heading>
<div className='flex flex-col gap-3'>
<div className='flex items-center gap-2'>
@ -427,24 +442,44 @@ export const ExampleDetailScreen: Story = {
<Toggle />
<span>Weekly roundup</span>
</div>
<div className='flex items-center gap-2'>
<Toggle checked />
<span>The Inner Circle</span>
</div>
<div className='mt-5 rounded border border-red p-4 text-sm text-red'>
This member cannot receive emails due to permanent failure (bounce).
</div>
</div>
</div>
<div className='flex h-full flex-col gap-6 border-r border-grey-200 px-5'>
<div className='-ml-5 flex h-full flex-col gap-6 border-r border-grey-200 px-5 pt-10 tablet:ml-0 tablet:pt-0'>
<Heading level={5}>Subscriptions</Heading>
<div className='flex flex-col'>
<span className='font-semibold'>Gold &mdash; $12/month</span>
<span className='text-sm text-grey-500'>Renews 21 Jan 2024</span>
<div className='flex items-center gap-3'>
<div className='flex h-16 w-16 flex-col items-center justify-center rounded-md bg-grey-200'>
<Heading level={5}>$5</Heading>
<span className='text-xs text-grey-700'>Yearly</span>
</div>
<div className='flex flex-col'>
<span className='font-semibold'>Gold</span>
<span className='text-sm text-grey-500'>Renews 21 Jan 2024</span>
</div>
</div>
</div>
<div className='-mr-5 flex h-full flex-col gap-6 px-5'>
<Heading level={5}>Activity</Heading>
<div className='flex flex-col'>
<span className='font-semibold'>Logged in</span>
<span className='text-sm text-grey-500'>Renews 21 Jan 2024</span>
<div className='-mr-5 flex h-full flex-col gap-6 px-5 pt-10 tablet:pt-0'>
<div className='flex justify-between'>
<Heading level={5}>Activity</Heading>
<Button color='green' label='View all' link />
</div>
<div className='flex flex-col'>
<div className='flex flex-col text-sm'>
<span className='font-semibold'>Logged in</span>
<span className='text-sm text-grey-500'>13 days ago</span>
</div>
<div className='flex flex-col text-sm'>
<span className='font-semibold'>Subscribed to Daily News</span>
<span className='text-sm text-grey-500'>Renews 21 Jan 2024</span>
<span className='text-sm text-grey-500'>17 days ago</span>
</div>
<div className='flex flex-col text-sm'>
<span className='font-semibold'>Logged in</span>
<span className='text-sm text-grey-500'>21 days ago</span>
</div>
</div>
</div>

View file

@ -125,7 +125,7 @@ const Page: React.FC<PageProps> = ({
<div className='sticky flex items-center gap-7'>
{(customGlobalActions?.map((action) => {
return (
<Button icon={action.iconName} iconColorClass='text-black' size='sm' link onClick={action.onClick} />
<Button icon={action.iconName} iconColorClass='text-black dark:text-white' size='sm' link onClick={action.onClick} />
);
}))}
{showGlobalActions && <GlobalActions />}
@ -138,7 +138,7 @@ const Page: React.FC<PageProps> = ({
);
pageToolbarClassName = clsx(
'sticky top-0 z-50 flex h-18 w-full items-center justify-between gap-5 bg-white p-6',
'sticky top-0 z-50 flex h-18 w-full items-center justify-between gap-5 bg-white p-6 dark:bg-black',
!fullBleedToolbar && 'mx-auto max-w-7xl',
pageToolbarClassName
);

View file

@ -39,7 +39,7 @@ const PageHeader: React.FC<PageHeaderProps> = ({
const leftClasses = clsx(
'flex flex-auto items-center',
(right && center) && 'basis-1/3',
((right && !center) || (!right && center)) && 'basis-1/2'
((!right && center)) && 'basis-1/2'
);
left = <div className={leftClasses}>{left}</div>;
}
@ -55,7 +55,7 @@ const PageHeader: React.FC<PageHeaderProps> = ({
const rightClasses = clsx(
'flex flex-auto items-center justify-end',
(left && center) && 'basis-1/3',
((left && !center) || (!left && center)) && 'basis-1/2'
((!left && center)) && 'basis-1/2'
);
right = <div className={rightClasses}>{right}</div>;
}

View file

@ -8,6 +8,9 @@ import ButtonGroup from '../ButtonGroup';
const meta = {
title: 'Global / Layout / View Container',
component: ViewContainer,
parameters: {
layout: 'fullscreen'
},
render: function Component(args) {
const [, updateArgs] = useArgs();
@ -33,13 +36,13 @@ export default meta;
type Story = StoryObj<typeof ViewContainer>;
export const exampleActions = [
<Button label='Filter' onClick={() => {
<Button label='Filter' outlineOnMobile onClick={() => {
alert('Clicked filter');
}} />,
<Button label='Sort' onClick={() => {
<Button label='Sort' outlineOnMobile onClick={() => {
alert('Clicked sort');
}} />,
<Button icon='magnifying-glass' size='sm' onClick={() => {
<Button icon='magnifying-glass' iconSize='sm' outlineOnMobile onClick={() => {
alert('Clicked search');
}} />,
<ButtonGroup buttons={[
@ -59,7 +62,7 @@ export const exampleActions = [
alert('Clicked card view');
}
}
]} clearBg={false} link />
]} clearBg={false} link outlineOnMobile />
];
const primaryAction: PrimaryActionProps = {
@ -152,13 +155,37 @@ export const TabsWithPrimaryAction: Story = {
}
};
const sectionActions = [
<Button label='Filter' size='sm' onClick={() => {
alert('Clicked filter');
}} />,
<ButtonGroup buttons={[
{
icon: 'listview',
size: 'sm',
iconColorClass: 'text-black',
onClick: () => {
alert('Clicked list view');
}
},
{
icon: 'cardview',
size: 'sm',
iconColorClass: 'text-grey-500',
onClick: () => {
alert('Clicked card view');
}
}
]} clearBg={false} size='sm' link />
];
export const TabsWithActions: Story = {
args: {
type: 'section',
title: 'Section title',
tabs: tabs,
primaryAction: primaryAction,
actions: exampleActions
actions: sectionActions
}
};
@ -167,7 +194,7 @@ export const HiddenActions: Story = {
type: 'section',
title: 'Hover to show actions',
tabs: tabs,
actions: exampleActions,
actions: sectionActions,
actionsHidden: true
}
};

View file

@ -172,15 +172,16 @@ const ViewContainer: React.FC<ViewContainerProps> = ({
toolbarWrapperClassName = clsx(
'z-50',
type === 'page' && 'mx-auto w-full max-w-7xl bg-white px-12',
type === 'page' && 'mx-auto w-full max-w-7xl bg-white px-[4vw] dark:bg-black tablet:px-12',
(type === 'page' && stickyHeader) && (firstOnPage ? 'sticky top-0 pt-8' : 'sticky top-18 pt-[3vmin]'),
toolbarContainerClassName
);
toolbarContainerClassName = clsx(
'flex items-end justify-between',
(firstOnPage && type === 'page') ? 'pb-8' : (tabs?.length ? '' : 'pb-2'),
toolbarBorder && 'border-b border-grey-200',
'flex justify-between gap-5',
(type === 'page' && actions?.length) ? 'flex-col md:flex-row md:items-end' : 'items-end',
(firstOnPage && type === 'page') ? 'pb-3 tablet:pb-8' : (tabs?.length ? '' : 'pb-2'),
toolbarBorder && 'border-b border-grey-200 dark:border-grey-900',
toolbarContainerClassName
);
@ -190,9 +191,9 @@ const ViewContainer: React.FC<ViewContainerProps> = ({
);
actionsClassName = clsx(
'flex items-center gap-5 transition-all',
'flex items-center justify-between gap-3 transition-all tablet:justify-start tablet:gap-5',
actionsHidden && 'opacity-0 group-hover/view-container:opacity-100',
tabs?.length ? 'pb-2' : (type === 'page' ? 'pb-1' : ''),
tabs?.length ? 'pb-1' : (type === 'page' ? 'pb-1' : ''),
actionsClassName
);
@ -243,7 +244,7 @@ const ViewContainer: React.FC<ViewContainerProps> = ({
contentWrapperClassName = clsx(
'relative mx-auto w-full flex-auto',
(!contentFullBleed && type === 'page') && 'max-w-7xl px-12',
(!contentFullBleed && type === 'page') && 'max-w-7xl px-[4vw] tablet:px-12',
contentWrapperClassName,
(!title && !actions) && 'pt-[3vmin]'
);

View file

@ -32,6 +32,7 @@ export interface DynamicTableProps {
* Set this parameter if the table is the main content in a viewcontainer or on a page
*/
singlePageTable?: boolean;
pageHasSidebar?: boolean;
border?: boolean;
footerBorder?:boolean;
@ -60,6 +61,7 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
footerBorder = true,
stickyFooter = false,
singlePageTable = false,
pageHasSidebar = true,
containerClassName,
tableContainerClassName,
tableClassName,
@ -81,7 +83,7 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
tableContainerClassName = clsx(
'flex-auto overflow-x-auto',
!horizontalScrolling && 'w-full max-w-full',
(singlePageTable && (stickyHeader || stickyFooter || absolute)) && 'px-12 xl:px-[calc((100%-1320px)/2+48px)]',
(singlePageTable && (stickyHeader || stickyFooter || absolute)) && `px-[4vw] tablet:px-12 ${pageHasSidebar ? 'min-[1640px]:px-[calc((100%-1320px)/2+48px)]' : 'xl:px-[calc((100%-1320px)/2+48px)]'}`,
tableContainerClassName
);
@ -91,13 +93,13 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
);
thClassName = clsx(
'last-child:pr-5 bg-white py-3 text-left [&:not(:first-child)]:pl-5',
'last-child:pr-5 bg-white py-3 text-left dark:bg-black [&:not(:first-child)]:pl-5',
thClassName
);
tdClassName = clsx(
'w-full border-b group-hover:border-grey-200',
border ? 'border-grey-200' : 'border-transparent',
'w-full border-b group-hover:border-grey-200 dark:group-hover:border-grey-900',
border ? 'border-grey-200 dark:border-grey-900' : 'border-transparent',
tdClassName
);
@ -113,11 +115,11 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
);
footerClassName = clsx(
'bg-white',
(singlePageTable && stickyFooter) && 'mx-12 xl:mx-[calc((100%-1320px)/2+48px)]',
'bg-white dark:bg-black',
(singlePageTable && stickyFooter) && `mx-[4vw] tablet:mx-12 ${pageHasSidebar ? 'min-[1640px]:mx-[calc((100%-1320px)/2+48px)]' : 'xl:mx-[calc((100%-1320px)/2+48px)]'}`,
footer && 'py-4',
stickyFooter && 'sticky inset-x-0 bottom-0',
footerBorder && 'border-t border-grey-200',
footerBorder && 'border-t border-grey-200 dark:border-grey-900',
footerClassName
);
@ -150,7 +152,7 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
</tr>
{headerBorder && (
<tr>
<th className='h-px bg-grey-200 p-0' colSpan={columns.length}></th>
<th className='h-px bg-grey-200 p-0 dark:bg-grey-900' colSpan={columns.length}></th>
</tr>
)}
</thead>