Admin X demo detail page (#19105)
refs. https://github.com/TryGhost/Product/issues/4169 - the detail page for the Admin X proto app was empty - the asc/desc selector of the SortMenu component in the design system needed a bit of refinement - page toolbar was not set
This commit is contained in:
parent
876f13c075
commit
7f451b2627
@ -1,10 +1,147 @@
|
||||
import {Button} from '@tryghost/admin-x-design-system';
|
||||
import {Avatar, Breadcrumbs, Button, Heading, Page, Toggle, ViewContainer} from '@tryghost/admin-x-design-system';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
const DetailPage: React.FC = () => {
|
||||
const {updateRoute} = useRouting();
|
||||
|
||||
return <>Detail page <Button label='Back' onClick={() => updateRoute('')} /></>;
|
||||
return (
|
||||
<Page
|
||||
breadCrumbs={
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{
|
||||
label: 'Members',
|
||||
onClick: () => {
|
||||
updateRoute('');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Emerson Vaccaro'
|
||||
}
|
||||
]}
|
||||
onBack={() => {
|
||||
updateRoute('');
|
||||
}}
|
||||
/>
|
||||
}
|
||||
fullBleedPage={false}
|
||||
>
|
||||
<ViewContainer
|
||||
firstOnPage={false}
|
||||
headerContent={
|
||||
<div>
|
||||
<Avatar bgColor='#A5D5F7' image='https://i.pravatar.cc/150' label='EV' labelColor='white' size='2xl' />
|
||||
<Heading className='mt-2' level={1}>Emerson Vaccaro</Heading>
|
||||
<div className=''>Colombus, OH</div>
|
||||
</div>
|
||||
}
|
||||
primaryAction={
|
||||
{
|
||||
icon: 'ellipsis',
|
||||
color: 'outline'
|
||||
}
|
||||
}
|
||||
type='page'
|
||||
>
|
||||
<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:!visible 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='tablet:mt-2'>Created on <strong>27 Jan 2021</strong></span>
|
||||
</div>
|
||||
<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 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 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-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>
|
||||
<Button color='green' label='Edit' link />
|
||||
</div>
|
||||
<div>
|
||||
<Heading level={6}>Name</Heading>
|
||||
<div>Emerson Vaccaro</div>
|
||||
</div>
|
||||
<div>
|
||||
<Heading level={6}>Email</Heading>
|
||||
<div>emerson@vaccaro.com</div>
|
||||
</div>
|
||||
<div>
|
||||
<Heading level={6}>Labels</Heading>
|
||||
<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-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'>
|
||||
<Toggle />
|
||||
<span>Daily news</span>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<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='-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 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 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 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'>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>
|
||||
</ViewContainer>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
export default DetailPage;
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {Avatar, Button, ButtonGroup, DynamicTable, DynamicTableColumn, DynamicTableRow, Heading, Hint, Page, SortMenu, ViewContainer} from '@tryghost/admin-x-design-system';
|
||||
import {Avatar, Button, ButtonGroup, DynamicTable, DynamicTableColumn, DynamicTableRow, Heading, Hint, Page, SortMenu, Tooltip, ViewContainer, showToast} from '@tryghost/admin-x-design-system';
|
||||
import {Toaster} from 'react-hot-toast';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
import {useState} from 'react';
|
||||
|
||||
@ -8,7 +9,7 @@ const ListPage = () => {
|
||||
|
||||
const dummyActions = [
|
||||
<Button label='Filter' onClick={() => {
|
||||
alert('Clicked filter');
|
||||
showToast({message: 'Were you really expecting a filter? 😛'});
|
||||
}} />,
|
||||
<SortMenu
|
||||
direction='desc'
|
||||
@ -31,9 +32,11 @@ const ListPage = () => {
|
||||
onDirectionChange={() => {}}
|
||||
onSortChange={() => {}}
|
||||
/>,
|
||||
<Button icon='magnifying-glass' size='sm' onClick={() => {
|
||||
alert('Clicked search');
|
||||
}} />,
|
||||
<Tooltip content="Search members">
|
||||
<Button icon='magnifying-glass' size='sm' onClick={() => {
|
||||
alert('Clicked search');
|
||||
}} />
|
||||
</Tooltip>,
|
||||
<ButtonGroup buttons={[
|
||||
{
|
||||
icon: 'listview',
|
||||
@ -141,7 +144,7 @@ const ListPage = () => {
|
||||
for (let i = 0; i < noOfCards; i++) {
|
||||
cards.push(
|
||||
<div className='flex min-h-[20vh] cursor-pointer flex-col items-center gap-5 rounded-sm bg-grey-100 p-7 pt-9 transition-all hover:bg-grey-200' onClick={() => {
|
||||
alert('Clicked');
|
||||
updateRoute('detail');
|
||||
}}>
|
||||
<Avatar image={`https://i.pravatar.cc/150?img=${i}`} size='xl' />
|
||||
<div className='flex flex-col items-center'>
|
||||
@ -221,6 +224,7 @@ const ListPage = () => {
|
||||
|
||||
const demoPage = (
|
||||
<Page>
|
||||
<Toaster />
|
||||
<ViewContainer
|
||||
actions={dummyActions}
|
||||
primaryAction={{
|
||||
|
@ -13,6 +13,7 @@ const DemoModal = NiceModal.create(() => {
|
||||
}}
|
||||
cancelLabel=''
|
||||
okLabel='Close'
|
||||
size='sm'
|
||||
title='About'
|
||||
onOk={() => {
|
||||
updateRoute('');
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import {ReactComponent as UserIcon} from '../assets/icons/single-user-fill.svg';
|
||||
|
||||
type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
|
||||
type AvatarSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
||||
|
||||
export interface AvatarProps {
|
||||
image?: string;
|
||||
@ -35,6 +35,10 @@ const Avatar: React.FC<AvatarProps> = ({image, label, labelColor, bgColor, size,
|
||||
avatarSize = ' w-16 h-16 text-2xl ';
|
||||
fallbackPosition = ' -mb-3 ';
|
||||
break;
|
||||
case '2xl':
|
||||
avatarSize = ' w-20 h-20 text-2xl ';
|
||||
fallbackPosition = ' -mb-3 ';
|
||||
break;
|
||||
default:
|
||||
avatarSize = ' w-10 h-10 text-md ';
|
||||
break;
|
||||
|
@ -39,7 +39,7 @@ const List: React.FC<ListProps> = ({
|
||||
}) => {
|
||||
const listClasses = clsx(
|
||||
(borderTop || pageTitle) && 'border-t border-grey-300',
|
||||
pageTitle && 'mt-14',
|
||||
pageTitle && 'mt-5',
|
||||
className
|
||||
);
|
||||
|
||||
|
@ -63,9 +63,9 @@ const SortMenu: React.FC<SortMenuProps> = ({
|
||||
<button key={item.id} className="group relative mx-1 flex grow cursor-pointer items-center rounded-[2.5px] px-8 py-1.5 pr-12 text-left text-sm hover:bg-grey-100 dark:hover:bg-grey-800" type="button" onClick={() => {
|
||||
handleSortChange(item.id);
|
||||
}}>
|
||||
{item.selected ? <Icon className='absolute left-2' name='check' size='sm' /> : null}
|
||||
{item.selected ? <Icon className='absolute left-2' name='check' size='xs' /> : null}
|
||||
{item.label}
|
||||
{item.selected ? <button className='absolute right-2 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full bg-grey-200 opacity-0 group-hover:bg-grey-300 group-hover:opacity-100' title={`${localDirection === 'asc' ? 'Ascending' : 'Descending'}`} type='button' onClick={handleSortDirection}>
|
||||
{item.selected ? <button className='absolute right-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full hover:bg-grey-300' title={`${localDirection === 'asc' ? 'Ascending' : 'Descending'}`} type='button' onClick={handleSortDirection}>
|
||||
{localDirection === 'asc' ? <Icon name='arrow-up' size='xs' /> : <Icon name='arrow-down' size='xs' />}
|
||||
</button> : null}
|
||||
</button>
|
||||
|
@ -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 dark:bg-black',
|
||||
'sticky top-0 z-50 flex h-22 min-h-[92px] w-full items-center justify-between gap-5 bg-white p-8 dark:bg-black',
|
||||
!fullBleedToolbar && 'mx-auto max-w-7xl',
|
||||
pageToolbarClassName
|
||||
);
|
||||
|
@ -28,7 +28,7 @@ const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
children
|
||||
}) => {
|
||||
const containerClasses = clsx(
|
||||
'z-50 h-[72px] p-5 px-7',
|
||||
'z-50 h-22 min-h-[92px] p-8 px-6 tablet:px-12',
|
||||
!children && 'flex items-center justify-between gap-3',
|
||||
sticky && 'sticky top-0',
|
||||
containerClassName
|
||||
|
@ -173,7 +173,7 @@ const ViewContainer: React.FC<ViewContainerProps> = ({
|
||||
toolbarWrapperClassName = clsx(
|
||||
'z-50',
|
||||
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]'),
|
||||
(type === 'page' && stickyHeader) && (firstOnPage ? 'sticky top-0 pt-8' : 'sticky top-22 pt-[3vmin]'),
|
||||
toolbarContainerClassName
|
||||
);
|
||||
|
||||
|
@ -219,6 +219,7 @@ module.exports = {
|
||||
16: '6.4rem',
|
||||
18: '7.2rem',
|
||||
20: '8rem',
|
||||
22: '9.2rem',
|
||||
24: '9.6rem',
|
||||
28: '11.2rem',
|
||||
32: '12.8rem',
|
||||
|
Loading…
Reference in New Issue
Block a user