Admin X demo app content (#19079)
refs. https://github.com/TryGhost/Product/issues/4169 - Added demo content for POC AdminX demo app
This commit is contained in:
parent
8d0b9cd269
commit
6b46c828e7
@ -1,12 +1,244 @@
|
||||
import {Button} from '@tryghost/admin-x-design-system';
|
||||
import {Avatar, Button, ButtonGroup, DynamicTable, DynamicTableColumn, DynamicTableRow, Heading, Hint, Page, SortMenu, ViewContainer} from '@tryghost/admin-x-design-system';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
import {useState} from 'react';
|
||||
|
||||
const MainContent = () => {
|
||||
const {updateRoute} = useRouting();
|
||||
const [view, setView] = useState<string>('list');
|
||||
|
||||
return <div>
|
||||
<Button label='Open modal' onClick={() => updateRoute('demo-modal')} />
|
||||
</div>;
|
||||
const dummyActions = [
|
||||
<Button label='Filter' onClick={() => {
|
||||
alert('Clicked filter');
|
||||
}} />,
|
||||
<SortMenu
|
||||
direction='desc'
|
||||
items={[
|
||||
{
|
||||
id: 'date-added',
|
||||
label: 'Date added',
|
||||
selected: true
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
label: 'Name'
|
||||
},
|
||||
{
|
||||
id: 'redemptions',
|
||||
label: 'Open Rate'
|
||||
}
|
||||
]}
|
||||
position="left"
|
||||
onDirectionChange={() => {}}
|
||||
onSortChange={() => {}}
|
||||
/>,
|
||||
<Button icon='magnifying-glass' size='sm' onClick={() => {
|
||||
alert('Clicked search');
|
||||
}} />,
|
||||
<ButtonGroup buttons={[
|
||||
{
|
||||
icon: 'listview',
|
||||
size: 'sm',
|
||||
iconColorClass: (view === 'list' ? 'text-black' : 'text-grey-500'),
|
||||
onClick: () => {
|
||||
setView('list');
|
||||
}
|
||||
},
|
||||
{
|
||||
icon: 'cardview',
|
||||
size: 'sm',
|
||||
iconColorClass: (view === 'card' ? 'text-black' : 'text-grey-500'),
|
||||
onClick: () => {
|
||||
setView('card');
|
||||
}
|
||||
}
|
||||
]} clearBg={false} link />
|
||||
];
|
||||
|
||||
const testColumns: DynamicTableColumn[] = [
|
||||
{
|
||||
title: 'Member',
|
||||
noWrap: true,
|
||||
minWidth: '1%',
|
||||
maxWidth: '1%'
|
||||
},
|
||||
{
|
||||
title: 'Status'
|
||||
},
|
||||
{
|
||||
title: 'Open rate'
|
||||
},
|
||||
{
|
||||
title: 'Location',
|
||||
noWrap: true
|
||||
},
|
||||
{
|
||||
title: 'Created',
|
||||
noWrap: true
|
||||
},
|
||||
{
|
||||
title: 'Signed up on post',
|
||||
noWrap: true,
|
||||
maxWidth: '150px'
|
||||
},
|
||||
{
|
||||
title: 'Newsletter'
|
||||
},
|
||||
{
|
||||
title: 'Billing period'
|
||||
},
|
||||
{
|
||||
title: 'Email sent'
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
hidden: true,
|
||||
disableRowClick: true
|
||||
}
|
||||
];
|
||||
|
||||
const testRows = (noOfRows: number) => {
|
||||
const data: DynamicTableRow[] = [];
|
||||
for (let i = 0; i < noOfRows; i++) {
|
||||
data.push(
|
||||
{
|
||||
onClick: () => {
|
||||
alert('Clicked on row: ' + i);
|
||||
},
|
||||
cells: [
|
||||
(<div className='flex items-center gap-3 whitespace-nowrap pr-10'>
|
||||
<Avatar image={`https://i.pravatar.cc/150?img=${i}`} />
|
||||
<div>
|
||||
{i % 3 === 0 && <div className='whitespace-nowrap text-md'>Jamie Larson</div>}
|
||||
{i % 3 === 1 && <div className='whitespace-nowrap text-md'>Giana Septimus</div>}
|
||||
{i % 3 === 2 && <div className='whitespace-nowrap text-md'>Zaire Bator</div>}
|
||||
<div className='text-grey-700'>jamie@larson.com</div>
|
||||
</div>
|
||||
</div>),
|
||||
'Free',
|
||||
'40%',
|
||||
'London, UK',
|
||||
<div>
|
||||
<div>22 June 2023</div>
|
||||
<div className='text-grey-500'>5 months ago</div>
|
||||
</div>,
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit',
|
||||
'Subscribed',
|
||||
'Monthly',
|
||||
'1,303',
|
||||
<Button color='green' label='Edit' link onClick={() => {
|
||||
alert('Clicked Edit in row:' + i);
|
||||
}} />
|
||||
]
|
||||
}
|
||||
);
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
||||
const dummyCards = (noOfCards: number) => {
|
||||
const cards = [];
|
||||
|
||||
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');
|
||||
}}>
|
||||
<Avatar image={`https://i.pravatar.cc/150?img=${i}`} size='xl' />
|
||||
<div className='flex flex-col items-center'>
|
||||
<Heading level={5}>
|
||||
{i % 3 === 0 && 'Jamie Larson'}
|
||||
{i % 3 === 1 && 'Giana Septimus'}
|
||||
{i % 3 === 2 && 'Zaire Bator'}
|
||||
</Heading>
|
||||
<div className='mt-1 text-sm text-grey-700'>
|
||||
{i % 3 === 0 && 'jamie@larson.com'}
|
||||
{i % 3 === 1 && 'giana@septimus.com'}
|
||||
{i % 3 === 2 && 'zaire@bator.com'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex w-full flex-col gap-4 border-t border-grey-300 pt-5'>
|
||||
{i % 3 === 0 && (<>
|
||||
<div className='flex gap-4'>
|
||||
<div className='basis-1/2 text-center'>
|
||||
<Heading level={6}>Open rate</Heading>
|
||||
<div className='text-lg'>83%</div>
|
||||
</div>
|
||||
<div className='basis-1/2 text-center'>
|
||||
<Heading level={6}>Click rate</Heading>
|
||||
<div className='text-lg'>19%</div>
|
||||
</div>
|
||||
</div>
|
||||
</>)}
|
||||
{i % 3 === 1 && (<>
|
||||
<div className='flex gap-4'>
|
||||
<div className='basis-1/2 text-center'>
|
||||
<Heading level={6}>Open rate</Heading>
|
||||
<div className='text-lg'>68%</div>
|
||||
</div>
|
||||
<div className='basis-1/2 text-center'>
|
||||
<Heading level={6}>Click rate</Heading>
|
||||
<div className='text-lg'>21%</div>
|
||||
</div>
|
||||
</div>
|
||||
</>)}
|
||||
{i % 3 === 2 && (<>
|
||||
<div className='flex gap-4'>
|
||||
<div className='basis-1/2 text-center'>
|
||||
<Heading level={6}>Open rate</Heading>
|
||||
<div className='text-lg'>89%</div>
|
||||
</div>
|
||||
<div className='basis-1/2 text-center'>
|
||||
<Heading level={6}>Click rate</Heading>
|
||||
<div className='text-lg'>34%</div>
|
||||
</div>
|
||||
</div>
|
||||
</>)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return cards;
|
||||
};
|
||||
|
||||
let contents = <></>;
|
||||
switch (view) {
|
||||
case 'list':
|
||||
contents = <DynamicTable
|
||||
cellClassName='text-sm'
|
||||
columns={testColumns}
|
||||
footer={
|
||||
<Hint>30 members</Hint>
|
||||
}
|
||||
rows={testRows(30)}
|
||||
stickyFooter
|
||||
stickyHeader
|
||||
/>;
|
||||
break;
|
||||
case 'card':
|
||||
contents = <div className='grid grid-cols-4 gap-8 py-8'>{dummyCards(30)}</div>;
|
||||
break;
|
||||
}
|
||||
|
||||
const demoPage = (
|
||||
<Page>
|
||||
<ViewContainer
|
||||
actions={dummyActions}
|
||||
primaryAction={{
|
||||
title: 'About',
|
||||
onClick: () => {
|
||||
updateRoute('demo-modal');
|
||||
}
|
||||
}}
|
||||
title='AdminX Demo App'
|
||||
toolbarBorder={view === 'card'}
|
||||
type='page'
|
||||
>
|
||||
{contents}
|
||||
</ViewContainer>
|
||||
</Page>
|
||||
);
|
||||
|
||||
return demoPage;
|
||||
};
|
||||
|
||||
export default MainContent;
|
||||
|
@ -1,18 +1,30 @@
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import {Modal} from '@tryghost/admin-x-design-system';
|
||||
import {Heading, Modal} from '@tryghost/admin-x-design-system';
|
||||
import {useRouting} from '@tryghost/admin-x-framework/routing';
|
||||
|
||||
const DemoModal = NiceModal.create(() => {
|
||||
const {updateRoute} = useRouting();
|
||||
const modal = NiceModal.useModal();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
afterClose={() => {
|
||||
updateRoute('');
|
||||
}}
|
||||
title='Demo modal'
|
||||
cancelLabel=''
|
||||
okLabel='Close'
|
||||
title='About'
|
||||
onOk={() => {
|
||||
updateRoute('');
|
||||
modal.remove();
|
||||
}}
|
||||
>
|
||||
Demo modal
|
||||
<div className='mt-3 flex flex-col gap-4'>
|
||||
<p>{`You're looking at a React app inside Ghost Admin. It uses common AdminX framework and Design System packages, and works seamlessly with the current Admin's routing.`}</p>
|
||||
<p>{`At the moment the look and feel follows the current Admin's style to blend in with existing pages. However the system is built in a very flexible way to allow easy updates in the future.`}</p>
|
||||
<Heading className='-mb-2 mt-4' level={5}>Contents</Heading>
|
||||
<p>{`The demo uses a mocked list of members — it's `}<strong>not</strong> {`the actual or future design of members in Ghost Admin. Instead, the pages showcase common design patterns like a list and detail, navigation, modals and toasts.`}</p>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
@ -241,3 +241,7 @@ html, body, #root {
|
||||
grid-template-columns: auto 240px;
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
.sbdocs-a {
|
||||
color: #30CF43 !important;
|
||||
}
|
@ -150,6 +150,19 @@
|
||||
text-indent: 0; /* 1 */
|
||||
border-color: inherit; /* 2 */
|
||||
border-collapse: collapse; /* 3 */
|
||||
margin: 0;
|
||||
width: auto;
|
||||
max-width: auto;
|
||||
}
|
||||
|
||||
table td, table th {
|
||||
padding: unset;
|
||||
vertical-align: middle;
|
||||
text-align: left;
|
||||
line-height: auto;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -193,7 +206,7 @@
|
||||
*/
|
||||
|
||||
button,
|
||||
[type='button'],
|
||||
/* [type='button'], */
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button; /* 1 */
|
||||
@ -201,6 +214,8 @@
|
||||
background-image: none; /* 2 */
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Use the modern Firefox focus style for all focusable elements.
|
||||
*/
|
||||
|
@ -32,7 +32,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||
label = '',
|
||||
hideLabel = false,
|
||||
icon = '',
|
||||
iconColorClass = 'text-black',
|
||||
iconColorClass,
|
||||
color = 'clear',
|
||||
fullWidth,
|
||||
link,
|
||||
@ -67,6 +67,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||
className
|
||||
);
|
||||
loadingIndicatorColor = 'light';
|
||||
iconColorClass = iconColorClass || 'text-white';
|
||||
break;
|
||||
case 'grey':
|
||||
className = clsx(
|
||||
@ -81,6 +82,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||
className
|
||||
);
|
||||
loadingIndicatorColor = 'light';
|
||||
iconColorClass = iconColorClass || 'text-white';
|
||||
break;
|
||||
case 'red':
|
||||
className = clsx(
|
||||
@ -88,6 +90,7 @@ const Button: React.FC<ButtonProps> = ({
|
||||
className
|
||||
);
|
||||
loadingIndicatorColor = 'light';
|
||||
iconColorClass = iconColorClass || 'text-white';
|
||||
break;
|
||||
case 'white':
|
||||
className = clsx(
|
||||
|
@ -6,7 +6,8 @@ import {ButtonProps} from './Button';
|
||||
const ButtonGroupMeta = {
|
||||
title: 'Global / Button Group',
|
||||
component: ButtonGroup,
|
||||
tags: ['autodocs']
|
||||
tags: ['autodocs'],
|
||||
decorators: [(_story: () => React.ReactNode) => (<div className='inline-block'>{_story()}</div>)]
|
||||
} satisfies Meta<typeof ButtonGroup>;
|
||||
|
||||
export default ButtonGroupMeta;
|
||||
@ -49,4 +50,21 @@ export const LinkButtons: Story = {
|
||||
buttons: linkButtons,
|
||||
link: true
|
||||
}
|
||||
};
|
||||
|
||||
export const WithBackground: Story = {
|
||||
args: {
|
||||
buttons: linkButtons,
|
||||
link: true,
|
||||
clearBg: false
|
||||
}
|
||||
};
|
||||
|
||||
export const SmallWithBackground: Story = {
|
||||
args: {
|
||||
buttons: linkButtons,
|
||||
link: true,
|
||||
clearBg: false,
|
||||
size: 'sm'
|
||||
}
|
||||
};
|
@ -1,18 +1,35 @@
|
||||
import React from 'react';
|
||||
import Button from './Button';
|
||||
import Button, {ButtonSize} from './Button';
|
||||
|
||||
import {ButtonProps} from './Button';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export interface ButtonGroupProps {
|
||||
size?: ButtonSize;
|
||||
buttons: Array<ButtonProps>;
|
||||
link?: boolean;
|
||||
linkWithPadding?: boolean;
|
||||
clearBg?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const ButtonGroup: React.FC<ButtonGroupProps> = ({buttons, link, linkWithPadding, className}) => {
|
||||
const ButtonGroup: React.FC<ButtonGroupProps> = ({size = 'md', buttons, link, linkWithPadding, clearBg = true, className}) => {
|
||||
let groupColorClasses = clsx(
|
||||
'flex items-center justify-start rounded',
|
||||
link ? 'gap-4' : 'gap-5',
|
||||
className
|
||||
);
|
||||
|
||||
if (link && !clearBg) {
|
||||
groupColorClasses = clsx(
|
||||
'transition-all hover:bg-grey-200 dark:hover:bg-grey-900',
|
||||
size === 'sm' ? 'h-7 px-3' : 'h-[34px] px-4',
|
||||
groupColorClasses
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`flex items-center ${link ? 'gap-5' : 'gap-3'} ${className}`}>
|
||||
<div className={groupColorClasses}>
|
||||
{buttons.map(({key, ...props}) => (
|
||||
<Button key={key} link={link} linkWithPadding={linkWithPadding} {...props} />
|
||||
))}
|
||||
|
@ -6,7 +6,12 @@ import Tooltip from './Tooltip';
|
||||
const meta = {
|
||||
title: 'Global / Tooltip',
|
||||
component: Tooltip,
|
||||
tags: ['autodocs']
|
||||
tags: ['autodocs'],
|
||||
decorators: [(_story: () => React.ReactNode) => (
|
||||
<div className='p-10'>
|
||||
{_story()}
|
||||
</div>
|
||||
)]
|
||||
} satisfies Meta<typeof Tooltip>;
|
||||
|
||||
export default meta;
|
||||
|
@ -20,6 +20,9 @@ const meta = {
|
||||
title: 'Global / Layout / Page',
|
||||
component: Page,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
render: function Component(args) {
|
||||
const [, updateArgs] = useArgs();
|
||||
|
||||
@ -35,7 +38,7 @@ const meta = {
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Page>;
|
||||
|
||||
const dummyContent = <div className='m-auto max-w-[800px] p-5 text-center'>Placeholder content</div>;
|
||||
const dummyContent = <div className='w-full bg-grey-100 p-5 text-center'>Placeholder content</div>;
|
||||
|
||||
const customGlobalActions: CustomGlobalAction[] = [
|
||||
{
|
||||
@ -58,52 +61,66 @@ const pageTabs: Tab[] = [
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
children: dummyContent
|
||||
}
|
||||
};
|
||||
|
||||
export const WithHamburger: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
export const LimitToolbarWidth: Story = {
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
children: dummyContent,
|
||||
fullBleedToolbar: false
|
||||
}
|
||||
};
|
||||
|
||||
export const WithHamburger: Story = {
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showAppMenu: true,
|
||||
children: dummyContent
|
||||
}
|
||||
};
|
||||
|
||||
export const WithGlobalActions: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: dummyContent
|
||||
}
|
||||
};
|
||||
|
||||
export const CustomGlobalActions: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: dummyContent,
|
||||
customGlobalActions: customGlobalActions
|
||||
}
|
||||
};
|
||||
|
||||
const currentAdminExample = <ViewContainer
|
||||
title='Members'
|
||||
type='page'
|
||||
>
|
||||
<DynamicTable
|
||||
columns={testColumns}
|
||||
rows={testRows(100)}
|
||||
/>
|
||||
</ViewContainer>;
|
||||
|
||||
export const ExampleCurrentAdminList: Story = {
|
||||
name: 'Example: List in Current Admin',
|
||||
args: {
|
||||
children: currentAdminExample
|
||||
}
|
||||
};
|
||||
|
||||
const simpleList = <ViewContainer
|
||||
firstOnPage={false}
|
||||
title='Members'
|
||||
type='page'
|
||||
>
|
||||
@ -115,19 +132,17 @@ const simpleList = <ViewContainer
|
||||
</ViewContainer>;
|
||||
|
||||
export const ExampleSimpleList: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
name: 'Example: Simple List',
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: simpleList
|
||||
}
|
||||
};
|
||||
|
||||
const stickyList = <ViewContainer
|
||||
firstOnPage={false}
|
||||
title='Members'
|
||||
type='page'
|
||||
>
|
||||
@ -141,19 +156,17 @@ const stickyList = <ViewContainer
|
||||
</ViewContainer>;
|
||||
|
||||
export const ExampleStickyList: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
name: 'Example: Sticky Header/Footer List',
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: stickyList
|
||||
}
|
||||
};
|
||||
|
||||
const examplePrimaryAction = <ViewContainer
|
||||
firstOnPage={false}
|
||||
primaryAction={{
|
||||
title: 'Add member',
|
||||
color: 'black',
|
||||
@ -174,13 +187,10 @@ const examplePrimaryAction = <ViewContainer
|
||||
</ViewContainer>;
|
||||
|
||||
export const ExamplePrimaryAction: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
name: 'Example: Primary Action',
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: examplePrimaryAction
|
||||
}
|
||||
@ -188,6 +198,7 @@ export const ExamplePrimaryAction: Story = {
|
||||
|
||||
const exampleActionsContent = <ViewContainer
|
||||
actions={exampleActionButtons}
|
||||
firstOnPage={false}
|
||||
primaryAction={{
|
||||
title: 'Add member',
|
||||
icon: 'add',
|
||||
@ -209,13 +220,10 @@ const exampleActionsContent = <ViewContainer
|
||||
</ViewContainer>;
|
||||
|
||||
export const ExampleActions: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
name: 'Example: Custom Actions',
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: exampleActionsContent
|
||||
}
|
||||
@ -246,6 +254,7 @@ const mockIdeaCards = () => {
|
||||
const exampleCardViewContent = (
|
||||
<ViewContainer
|
||||
actions={exampleActionButtons}
|
||||
firstOnPage={false}
|
||||
primaryAction={{
|
||||
title: 'New idea',
|
||||
icon: 'add'
|
||||
@ -260,13 +269,10 @@ const exampleCardViewContent = (
|
||||
);
|
||||
|
||||
export const ExampleCardView: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
name: 'Example: Card View',
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: exampleCardViewContent
|
||||
}
|
||||
@ -315,6 +321,7 @@ const mockPosts = () => {
|
||||
const examplePostsContent = (
|
||||
<ViewContainer
|
||||
actions={exampleActionButtons}
|
||||
firstOnPage={false}
|
||||
primaryAction={{
|
||||
title: 'New post',
|
||||
icon: 'add'
|
||||
@ -329,25 +336,19 @@ const examplePostsContent = (
|
||||
);
|
||||
|
||||
export const ExampleAlternativeList: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
name: 'Example: Alternative List',
|
||||
args: {
|
||||
pageTabs: pageTabs,
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
showGlobalActions: true,
|
||||
children: examplePostsContent
|
||||
}
|
||||
};
|
||||
|
||||
export const ExampleDetailScreen: Story = {
|
||||
parameters: {
|
||||
layout: 'fullscreen'
|
||||
},
|
||||
name: 'Example: Detail Page',
|
||||
args: {
|
||||
showPageMenu: true,
|
||||
showAppMenu: true,
|
||||
breadCrumbs: <Breadcrumbs
|
||||
items={[
|
||||
{
|
||||
@ -362,19 +363,23 @@ export const ExampleDetailScreen: Story = {
|
||||
showGlobalActions: true,
|
||||
children: <>
|
||||
<ViewContainer
|
||||
toolbarBorder={false}
|
||||
type='page'>
|
||||
<div className='flex items-end justify-between gap-5 border-b border-grey-200 py-2'>
|
||||
firstOnPage={false}
|
||||
headerContent={
|
||||
<div>
|
||||
<Avatar bgColor='#A5D5F7' label='EV' labelColor='white' size='xl' />
|
||||
<Heading className='mt-2' level={1}>Emerson Vaccaro</Heading>
|
||||
<div className=''>Colombus, OH</div>
|
||||
</div>
|
||||
<div className='pb-2'>
|
||||
<Button color='outline' icon='ellipsis' />
|
||||
</div>
|
||||
</div>
|
||||
<div className='grid grid-cols-4 border-b border-grey-200 py-5'>
|
||||
}
|
||||
primaryAction={
|
||||
{
|
||||
icon: 'ellipsis',
|
||||
color: 'outline'
|
||||
}
|
||||
}
|
||||
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'>
|
||||
<span>Last seen on <strong>22 June 2023</strong></span>
|
||||
<span className='mt-2'>Created on <strong>27 Jan 2021</strong></span>
|
||||
|
@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
import {TabList} from '../TabView';
|
||||
import clsx from 'clsx';
|
||||
import PageMenu from './PageMenu';
|
||||
import AppMenu from './AppMenu';
|
||||
import GlobalActions from './GlobalActions';
|
||||
import Button from '../Button';
|
||||
import {BreadcrumbsProps} from '../Breadcrumbs';
|
||||
import PageHeader from './PageHeader';
|
||||
|
||||
export interface PageTab {
|
||||
id: string;
|
||||
@ -16,21 +17,69 @@ export interface CustomGlobalAction {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
interface PageToolbarProps {
|
||||
interface PageProps {
|
||||
mainContainerClassName?: string;
|
||||
mainClassName?: string;
|
||||
showPageMenu?: boolean;
|
||||
fullBleedPage?: boolean;
|
||||
|
||||
/**
|
||||
* The pageToolbar is a WIP part of this component, it's unused ATM in Ghost Admin.
|
||||
*/
|
||||
pageToolbarClassName?: string;
|
||||
fullBleedToolbar?: boolean;
|
||||
|
||||
/**
|
||||
* TK. Part of the Page Toolbar
|
||||
*/
|
||||
showAppMenu?: boolean;
|
||||
|
||||
/**
|
||||
* Show
|
||||
*/
|
||||
showGlobalActions?: boolean;
|
||||
|
||||
/**
|
||||
* TK. Part of the Page Toolbar
|
||||
*/
|
||||
customGlobalActions?: CustomGlobalAction[];
|
||||
breadCrumbs?: React.ReactElement<BreadcrumbsProps>;
|
||||
|
||||
/**
|
||||
* TK. Part of the Page Toolbar
|
||||
*/
|
||||
pageTabs?: PageTab[],
|
||||
|
||||
/**
|
||||
* TK. Part of the Page Toolbar
|
||||
*/
|
||||
selectedTab?: string;
|
||||
|
||||
/**
|
||||
* TK. Part of the Page Toolbar
|
||||
*/
|
||||
onTabChange?: (id: string) => void;
|
||||
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const PageToolbar: React.FC<PageToolbarProps> = ({
|
||||
/**
|
||||
* The page component is the main container in Ghost Admin. It consists of a
|
||||
* page level toolbar (`pageToolbar` — unused ATM, it's for page level views and
|
||||
* navigation in the future), and the main content area.
|
||||
*
|
||||
* ### Examples
|
||||
* You can find several examples in the sidebar. If you're building a page for the
|
||||
* current Admin you can use the ["List in Current Admin"](/story/global-layout-page--example-current-admin-list)
|
||||
* example as a starting point. The rest of the examples are showing a potential direction for a
|
||||
* future structure.
|
||||
*/
|
||||
const Page: React.FC<PageProps> = ({
|
||||
fullBleedPage = true,
|
||||
mainContainerClassName,
|
||||
mainClassName,
|
||||
showPageMenu = false,
|
||||
pageToolbarClassName,
|
||||
fullBleedToolbar = true,
|
||||
showAppMenu = false,
|
||||
showGlobalActions = false,
|
||||
customGlobalActions,
|
||||
breadCrumbs,
|
||||
@ -48,30 +97,31 @@ const PageToolbar: React.FC<PageToolbarProps> = ({
|
||||
selectedTab = pageTabs[0].id;
|
||||
}
|
||||
|
||||
const left: React.ReactNode = <div className='flex items-center gap-10'>
|
||||
{showPageMenu && (
|
||||
<PageMenu />
|
||||
)}
|
||||
{breadCrumbs}
|
||||
{pageTabs?.length && (
|
||||
<TabList
|
||||
border={false}
|
||||
buttonBorder={false}
|
||||
handleTabChange={handleTabChange}
|
||||
selectedTab={selectedTab}
|
||||
tabs={pageTabs!}
|
||||
width='normal'
|
||||
/>
|
||||
)}
|
||||
|
||||
</div>;
|
||||
const left: React.ReactNode = (
|
||||
(showAppMenu || breadCrumbs || pageTabs?.length) && <div className='flex items-center gap-10'>
|
||||
{showAppMenu && (
|
||||
<AppMenu />
|
||||
)}
|
||||
{breadCrumbs}
|
||||
{pageTabs?.length && (
|
||||
<TabList
|
||||
border={false}
|
||||
buttonBorder={false}
|
||||
handleTabChange={handleTabChange}
|
||||
selectedTab={selectedTab}
|
||||
tabs={pageTabs!}
|
||||
width='normal'
|
||||
/>
|
||||
)}
|
||||
</div>);
|
||||
|
||||
mainClassName = clsx(
|
||||
'flex h-[calc(100%-72px)] w-[100vw] flex-auto flex-col',
|
||||
'flex w-full flex-auto flex-col',
|
||||
mainClassName
|
||||
);
|
||||
|
||||
const globalActions = (
|
||||
(customGlobalActions?.length || showGlobalActions) &&
|
||||
<div className='sticky flex items-center gap-7'>
|
||||
{(customGlobalActions?.map((action) => {
|
||||
return (
|
||||
@ -79,22 +129,34 @@ const PageToolbar: React.FC<PageToolbarProps> = ({
|
||||
);
|
||||
}))}
|
||||
{showGlobalActions && <GlobalActions />}
|
||||
</div>
|
||||
</div>);
|
||||
|
||||
mainContainerClassName = clsx(
|
||||
'flex h-[100vh] w-full flex-col overflow-y-auto overflow-x-hidden',
|
||||
!fullBleedPage && 'mx-auto max-w-7xl',
|
||||
mainContainerClassName
|
||||
);
|
||||
|
||||
pageToolbarClassName = clsx(
|
||||
'sticky top-0 z-50 flex h-18 w-full items-center justify-between gap-5 bg-white p-6',
|
||||
!fullBleedToolbar && 'mx-auto max-w-7xl',
|
||||
pageToolbarClassName
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='w-100 h-[100vh] overflow-y-auto overflow-x-hidden'>
|
||||
<header className='sticky top-0 z-50 flex h-18 items-center justify-between gap-5 bg-white p-6'>
|
||||
<nav>{left}</nav>
|
||||
<div>{globalActions}</div>
|
||||
</header>
|
||||
<div className={mainContainerClassName}>
|
||||
{(left || globalActions) &&
|
||||
<PageHeader
|
||||
containerClassName={pageToolbarClassName}
|
||||
left={left}
|
||||
right={globalActions}
|
||||
/>
|
||||
}
|
||||
<main className={mainClassName}>
|
||||
<section className='mx-auto flex h-full w-full flex-col'>
|
||||
{children}
|
||||
</section>
|
||||
{children}
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PageToolbar;
|
||||
export default Page;
|
@ -28,7 +28,7 @@ const PageHeader: React.FC<PageHeaderProps> = ({
|
||||
children
|
||||
}) => {
|
||||
const containerClasses = clsx(
|
||||
'z-50 h-[74px] p-5 px-7',
|
||||
'z-50 h-[72px] p-5 px-7',
|
||||
!children && 'flex items-center justify-between gap-3',
|
||||
sticky && 'sticky top-0',
|
||||
containerClassName
|
||||
|
@ -18,33 +18,34 @@ const meta = {
|
||||
}}
|
||||
/>;
|
||||
},
|
||||
tags: ['autodocs']
|
||||
argTypes: {
|
||||
children: {
|
||||
control: {
|
||||
type: 'text'
|
||||
}
|
||||
}
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
excludeStories: ['exampleActions']
|
||||
} satisfies Meta<typeof ViewContainer>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ViewContainer>;
|
||||
|
||||
const ContentContainer: React.FC<{children: React.ReactNode}> = ({
|
||||
children
|
||||
}) => {
|
||||
return <div className='m-auto max-w-[800px] p-5 text-center'>{children}</div>;
|
||||
};
|
||||
|
||||
export const exampleActions = [
|
||||
<Button label='Filter' link onClick={() => {
|
||||
<Button label='Filter' onClick={() => {
|
||||
alert('Clicked filter');
|
||||
}} />,
|
||||
<Button label='Sort' link onClick={() => {
|
||||
<Button label='Sort' onClick={() => {
|
||||
alert('Clicked sort');
|
||||
}} />,
|
||||
<Button icon='magnifying-glass' size='sm' link onClick={() => {
|
||||
<Button icon='magnifying-glass' size='sm' onClick={() => {
|
||||
alert('Clicked search');
|
||||
}} />,
|
||||
<ButtonGroup buttons={[
|
||||
{
|
||||
icon: 'listview',
|
||||
size: 'sm',
|
||||
link: true,
|
||||
iconColorClass: 'text-black',
|
||||
onClick: () => {
|
||||
alert('Clicked list view');
|
||||
@ -53,13 +54,12 @@ export const exampleActions = [
|
||||
{
|
||||
icon: 'cardview',
|
||||
size: 'sm',
|
||||
link: true,
|
||||
iconColorClass: 'text-grey-500',
|
||||
onClick: () => {
|
||||
alert('Clicked card view');
|
||||
}
|
||||
}
|
||||
]} />
|
||||
]} clearBg={false} link />
|
||||
];
|
||||
|
||||
const primaryAction: PrimaryActionProps = {
|
||||
@ -74,12 +74,12 @@ const tabs: ViewTab[] = [
|
||||
{
|
||||
id: 'steph',
|
||||
title: 'Steph Curry',
|
||||
contents: <ContentContainer>The tabs component lets you add various datasets. It uses the <code>`TabList`</code> component to stay consistent with the simple TabView.</ContentContainer>
|
||||
contents: 'The tabs component lets you add various datasets. It uses the <code>`TabList`</code> component to stay consistent with the simple TabView.'
|
||||
},
|
||||
{
|
||||
id: 'klay',
|
||||
title: 'Klay Thompson',
|
||||
contents: <ContentContainer>Splash brother #11.</ContentContainer>
|
||||
contents: 'Splash brother #11.'
|
||||
}
|
||||
];
|
||||
|
||||
@ -87,7 +87,7 @@ export const Default: Story = {
|
||||
args: {
|
||||
type: 'page',
|
||||
toolbarBorder: false,
|
||||
children: <ContentContainer>The view container component is the main container of pages and/or sections on a page. Select one of the stories on the right to browse use cases.</ContentContainer>
|
||||
children: 'The view container component is the main container of pages and/or sections on a page. Select one of the stories on the right to browse use cases.'
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,7 +96,7 @@ export const PageType: Story = {
|
||||
args: {
|
||||
type: 'page',
|
||||
title: 'Page title',
|
||||
children: <ContentContainer>In its simplest form you can use this component as the main container of pages.</ContentContainer>
|
||||
children: 'In its simplest form you can use this component as the main container of pages.'
|
||||
}
|
||||
};
|
||||
|
||||
@ -105,7 +105,7 @@ export const SectionType: Story = {
|
||||
args: {
|
||||
type: 'section',
|
||||
title: 'Section title',
|
||||
children: <ContentContainer>This example shows how to use it for sections on a page.</ContentContainer>
|
||||
children: 'This example shows how to use it for sections on a page.'
|
||||
}
|
||||
};
|
||||
|
||||
@ -113,7 +113,8 @@ export const PrimaryActionOnPage: Story = {
|
||||
args: {
|
||||
type: 'page',
|
||||
title: 'Page title',
|
||||
primaryAction: primaryAction
|
||||
primaryAction: primaryAction,
|
||||
children: 'View contents'
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -21,12 +21,50 @@ export interface PrimaryActionProps {
|
||||
title?: string;
|
||||
icon?: string;
|
||||
color?: ButtonColor;
|
||||
className?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
interface ViewContainerProps {
|
||||
/**
|
||||
* Use `page` if the `ViewContainer` is your main component on the page. Use
|
||||
* `section` for individual sections on the page (e.g. blocks on a dashboard).
|
||||
*/
|
||||
type: 'page' | 'section';
|
||||
|
||||
/**
|
||||
* The title of the ViewContainer. `page` type containers will use a large
|
||||
* size that matches the rest of the page titles in the Admin.
|
||||
*/
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
* Use this if there's no toolbar on the page and you use the `ViewContainer`
|
||||
* as the main container on a page. Technically it sticks the header to
|
||||
* the top of the page with the actions aligned properly to match other
|
||||
* pages in the Admin.
|
||||
*/
|
||||
firstOnPage?:boolean;
|
||||
|
||||
/**
|
||||
* Use this for custom content in the header.
|
||||
*/
|
||||
headerContent?: React.ReactNode;
|
||||
|
||||
/**
|
||||
* Sticks the header so it's always visible. The `top` value depends on the
|
||||
* value of `firstOnPage`:
|
||||
*
|
||||
* ```
|
||||
* firstOnPage = true -> top: 0px;
|
||||
* firstOnPage = false -> top: 3vmin;
|
||||
* ```
|
||||
*/
|
||||
stickyHeader?: boolean;
|
||||
|
||||
/**
|
||||
* Use this to break down the view to multiple tabs.
|
||||
*/
|
||||
tabs?: ViewTab[];
|
||||
selectedTab?: string;
|
||||
selectedView?: string;
|
||||
@ -36,18 +74,40 @@ interface ViewContainerProps {
|
||||
toolbarContainerClassName?: string;
|
||||
toolbarLeftClassName?: string;
|
||||
toolbarBorder?: boolean;
|
||||
|
||||
/**
|
||||
* The primary action appears in the view container's top right usually as a solid
|
||||
* button.
|
||||
*/
|
||||
primaryAction?: PrimaryActionProps;
|
||||
actions?: (React.ReactElement<ButtonProps> | React.ReactElement<ButtonGroupProps>)[];
|
||||
|
||||
/**
|
||||
* Adds more actions by the primary action, primarily buttons and button groups.
|
||||
*/
|
||||
actions?: (React.ReactElement<ButtonProps> | React.ReactElement<ButtonGroupProps> | React.ReactNode)[];
|
||||
actionsClassName?: string;
|
||||
actionsHidden?: boolean;
|
||||
contentWrapperClassName?: string;
|
||||
|
||||
/**
|
||||
* Sets the width of the view container full bleed
|
||||
*/
|
||||
contentFullBleed?: boolean;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* The `ViewContainer` component is a generic container for either the complete
|
||||
* contents of a page (`type = 'page'`) or for individual sections on a
|
||||
* page, like blocks on a dashboard (`type = 'section'`). It has a bunch of
|
||||
* parameters to customise its look & feel.
|
||||
*/
|
||||
const ViewContainer: React.FC<ViewContainerProps> = ({
|
||||
type,
|
||||
title,
|
||||
firstOnPage = true,
|
||||
headerContent,
|
||||
stickyHeader = true,
|
||||
tabs,
|
||||
selectedTab,
|
||||
onTabChange,
|
||||
@ -112,12 +172,14 @@ const ViewContainer: React.FC<ViewContainerProps> = ({
|
||||
|
||||
toolbarWrapperClassName = clsx(
|
||||
'z-50',
|
||||
type === 'page' && 'sticky top-18 mx-auto w-full max-w-7xl bg-white px-12 pt-[3vmin]',
|
||||
type === 'page' && 'mx-auto w-full max-w-7xl bg-white px-12',
|
||||
(type === 'page' && stickyHeader) && (firstOnPage ? 'sticky top-0 pt-8' : 'sticky top-18 pt-[3vmin]'),
|
||||
toolbarContainerClassName
|
||||
);
|
||||
|
||||
toolbarContainerClassName = clsx(
|
||||
'flex justify-between',
|
||||
'flex items-end justify-between',
|
||||
(firstOnPage && type === 'page') ? 'pb-8' : (tabs?.length ? '' : 'pb-2'),
|
||||
toolbarBorder && 'border-b border-grey-200',
|
||||
toolbarContainerClassName
|
||||
);
|
||||
@ -128,27 +190,29 @@ const ViewContainer: React.FC<ViewContainerProps> = ({
|
||||
);
|
||||
|
||||
actionsClassName = clsx(
|
||||
'flex items-center gap-10 transition-all',
|
||||
'flex items-center gap-5 transition-all',
|
||||
actionsHidden && 'opacity-0 group-hover/view-container:opacity-100',
|
||||
tabs?.length ? 'pb-2' : 'pb-3',
|
||||
tabs?.length ? 'pb-2' : (type === 'page' ? 'pb-1' : ''),
|
||||
actionsClassName
|
||||
);
|
||||
|
||||
if (primaryAction) {
|
||||
primaryAction!.color = 'black';
|
||||
}
|
||||
|
||||
const primaryActionContents = <>
|
||||
{primaryAction?.title && (
|
||||
<Button color={primaryAction.color} icon={primaryAction.icon} iconColorClass='text-white' label={primaryAction.title} size={type === 'page' ? 'md' : 'sm'} onClick={primaryAction.onClick} />
|
||||
{(primaryAction?.title || primaryAction?.icon) && (
|
||||
<Button className={primaryAction.className} color={primaryAction.color || 'black'} icon={primaryAction.icon} label={primaryAction.title} size={type === 'page' ? 'md' : 'sm'} onClick={primaryAction.onClick} />
|
||||
)}
|
||||
</>;
|
||||
|
||||
const headingClassName = clsx(
|
||||
tabs?.length && 'pb-3',
|
||||
type === 'page' && '-mt-2'
|
||||
);
|
||||
|
||||
toolbar = (
|
||||
<div className={toolbarWrapperClassName}>
|
||||
<div className={toolbarContainerClassName}>
|
||||
<div className={toolbarLeftClassName}>
|
||||
{title && <Heading className={tabs?.length ? 'pb-3' : 'pb-2'} level={type === 'page' ? 1 : 4}>{title}</Heading>}
|
||||
{headerContent}
|
||||
{title && <Heading className={headingClassName} level={type === 'page' ? 1 : 4}>{title}</Heading>}
|
||||
{tabs?.length && (
|
||||
<TabList
|
||||
border={false}
|
||||
@ -179,14 +243,14 @@ const ViewContainer: React.FC<ViewContainerProps> = ({
|
||||
|
||||
contentWrapperClassName = clsx(
|
||||
'relative mx-auto w-full flex-auto',
|
||||
!contentFullBleed && 'max-w-7xl px-12',
|
||||
(!contentFullBleed && type === 'page') && 'max-w-7xl px-12',
|
||||
contentWrapperClassName,
|
||||
(!title && !actions) && 'pt-[3vmin]'
|
||||
);
|
||||
|
||||
return (
|
||||
<section className={mainContainerClassName}>
|
||||
{(title || actions) && toolbar}
|
||||
{(title || actions || headerContent) && toolbar}
|
||||
<div className={contentWrapperClassName}>
|
||||
{mainContent}
|
||||
</div>
|
||||
|
@ -9,7 +9,8 @@ import Button from '../Button';
|
||||
const meta = {
|
||||
title: 'Global / Table / Dynamic Table',
|
||||
component: DynamicTable,
|
||||
tags: ['autodocs']
|
||||
tags: ['autodocs'],
|
||||
excludeStories: ['testColumns', 'testRows']
|
||||
} satisfies Meta<typeof DynamicTable>;
|
||||
|
||||
export default meta;
|
||||
|
@ -81,7 +81,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%-1280px)/2+48px)]',
|
||||
(singlePageTable && (stickyHeader || stickyFooter || absolute)) && 'px-12 xl:px-[calc((100%-1320px)/2+48px)]',
|
||||
tableContainerClassName
|
||||
);
|
||||
|
||||
@ -91,7 +91,7 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
|
||||
);
|
||||
|
||||
thClassName = clsx(
|
||||
'bg-white py-3 pr-3 text-left',
|
||||
'last-child:pr-5 bg-white py-3 text-left [&:not(:first-child)]:pl-5',
|
||||
thClassName
|
||||
);
|
||||
|
||||
@ -102,7 +102,7 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
|
||||
);
|
||||
|
||||
cellClassName = clsx(
|
||||
'flex h-full py-3 pr-3',
|
||||
'flex h-full py-4',
|
||||
cellClassName
|
||||
);
|
||||
|
||||
@ -114,8 +114,8 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
|
||||
|
||||
footerClassName = clsx(
|
||||
'bg-white',
|
||||
(singlePageTable && stickyFooter) && 'mx-12 xl:mx-[calc((100%-1280px)/2+48px)]',
|
||||
footer && 'py-3',
|
||||
(singlePageTable && stickyFooter) && 'mx-12 xl:mx-[calc((100%-1320px)/2+48px)]',
|
||||
footer && 'py-4',
|
||||
stickyFooter && 'sticky inset-x-0 bottom-0',
|
||||
footerBorder && 'border-t border-grey-200',
|
||||
footerClassName
|
||||
@ -166,7 +166,7 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
|
||||
let customTdClasses = tdClassName;
|
||||
customTdClasses = clsx(
|
||||
customTdClasses,
|
||||
currentColumn.noWrap ? 'truncate' : '',
|
||||
// currentColumn.noWrap ? 'truncate' : '',
|
||||
currentColumn.align === 'center' && 'text-center',
|
||||
currentColumn.align === 'right' && 'text-right'
|
||||
);
|
||||
@ -188,6 +188,9 @@ const DynamicTable: React.FC<DynamicTableProps> = ({
|
||||
let customCellClasses = cellClassName;
|
||||
customCellClasses = clsx(
|
||||
customCellClasses,
|
||||
colID !== 0 && 'pl-5',
|
||||
(colID === columns.length - 1) && 'pr-5',
|
||||
currentColumn.noWrap ? 'truncate' : '',
|
||||
currentColumn.valign === 'middle' || !currentColumn.valign && 'items-center',
|
||||
currentColumn.valign === 'top' && 'items-start',
|
||||
currentColumn.valign === 'bottom' && 'items-end'
|
||||
|
@ -128,6 +128,17 @@ export {default as Tooltip} from './global/Tooltip';
|
||||
export type {TooltipProps} from './global/Tooltip';
|
||||
export {default as PageHeader} from './global/layout/PageHeader';
|
||||
export type {PageHeaderProps} from './global/layout/PageHeader';
|
||||
export {default as Page} from './global/layout/Page';
|
||||
export type {PageTab} from './global/layout/Page';
|
||||
export type {CustomGlobalAction} from './global/layout/Page';
|
||||
export {default as ViewContainer} from './global/layout/ViewContainer';
|
||||
export type {View} from './global/layout/ViewContainer';
|
||||
export type {ViewTab} from './global/layout/ViewContainer';
|
||||
export type {PrimaryActionProps} from './global/layout/ViewContainer';
|
||||
export {default as DynamicTable} from './global/table/DynamicTable';
|
||||
export type {DynamicTableProps} from './global/table/DynamicTable';
|
||||
export type {DynamicTableColumn} from './global/table/DynamicTable';
|
||||
export type {DynamicTableRow} from './global/table/DynamicTable';
|
||||
|
||||
export {default as SettingGroup} from './settings/SettingGroup';
|
||||
export type {SettingGroupProps} from './settings/SettingGroup';
|
||||
@ -158,5 +169,4 @@ export {confirmIfDirty} from './utils/modals';
|
||||
|
||||
export {default as DesignSystemApp} from './DesignSystemApp';
|
||||
export type {DesignSystemAppProps} from './DesignSystemApp';
|
||||
export {useFocusContext} from './providers/DesignSystemProvider';
|
||||
|
||||
export {useFocusContext} from './providers/DesignSystemProvider';
|
@ -12,7 +12,7 @@ module.exports = {
|
||||
sm: '480px',
|
||||
md: '640px',
|
||||
lg: '1024px',
|
||||
xl: '1280px',
|
||||
xl: '1320px',
|
||||
tablet: '860px'
|
||||
},
|
||||
colors: {
|
||||
@ -247,7 +247,7 @@ module.exports = {
|
||||
'4xl': '89.6rem',
|
||||
'5xl': '102.4rem',
|
||||
'6xl': '115.2rem',
|
||||
'7xl': '128rem',
|
||||
'7xl': '132rem',
|
||||
'8xl': '140rem',
|
||||
'9xl': '156rem',
|
||||
full: '100%',
|
||||
|
@ -1444,11 +1444,11 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
margin: -3px 0 0 0;
|
||||
margin: -8px 0 0 0;
|
||||
padding: 0;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 3.2rem;
|
||||
font-size: 3.6rem;
|
||||
line-height: 1.3em;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.021em;
|
||||
|
Loading…
Reference in New Issue
Block a user