mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Added basic modal to AdminX Design System
refs. https://github.com/TryGhost/Team/issues/3150
This commit is contained in:
parent
09e11c6a29
commit
60154cfa89
9 changed files with 297 additions and 22 deletions
|
@ -41,9 +41,10 @@
|
||||||
"prepublishOnly": "yarn build"
|
"prepublishOnly": "yarn build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ebay/nice-modal-react": "^1.2.10",
|
||||||
|
"@tryghost/timezone-data": "0.3.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0"
|
||||||
"@tryghost/timezone-data": "0.3.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-essentials": "7.0.15",
|
"@storybook/addon-essentials": "7.0.15",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Button from './admin-x-ds/global/Button';
|
import Button from './admin-x-ds/global/Button';
|
||||||
import Heading from './admin-x-ds/global/Heading';
|
import Heading from './admin-x-ds/global/Heading';
|
||||||
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
import Settings from './components/Settings';
|
import Settings from './components/Settings';
|
||||||
import Sidebar from './components/Sidebar';
|
import Sidebar from './components/Sidebar';
|
||||||
import {SettingsProvider} from './components/SettingsProvider';
|
import {SettingsProvider} from './components/SettingsProvider';
|
||||||
|
@ -7,28 +8,30 @@ import {SettingsProvider} from './components/SettingsProvider';
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<div className="admin-x-settings">
|
<div className="admin-x-settings">
|
||||||
<div className='fixed left-6 top-4'>
|
<NiceModal.Provider>
|
||||||
<Button label='← Done' onClick={() => window.history.back()} />
|
<div className='fixed left-6 top-4'>
|
||||||
</div>
|
<Button label='← Done' link={true} onClick={() => window.history.back()} />
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Main container */}
|
{/* Main container */}
|
||||||
<div className="mx-auto flex max-w-[1080px] flex-col px-[5vmin] py-[12vmin] md:flex-row md:items-start md:gap-x-10 md:py-[8vmin]">
|
<div className="mx-auto flex max-w-[1080px] flex-col px-[5vmin] py-[12vmin] md:flex-row md:items-start md:gap-x-10 md:py-[8vmin]">
|
||||||
|
|
||||||
{/* Sidebar */}
|
{/* Sidebar */}
|
||||||
<div className="relative min-w-[240px] grow-0 md:fixed md:top-[8vmin] md:basis-[240px]">
|
<div className="relative min-w-[240px] grow-0 md:fixed md:top-[8vmin] md:basis-[240px]">
|
||||||
<div className='h-[84px]'>
|
<div className='h-[84px]'>
|
||||||
<Heading>Settings</Heading>
|
<Heading>Settings</Heading>
|
||||||
|
</div>
|
||||||
|
<div className="relative mt-[-32px] w-[240px] overflow-x-hidden after:absolute after:inset-x-0 after:top-0 after:block after:h-[40px] after:bg-gradient-to-b after:from-white after:to-transparent after:content-['']">
|
||||||
|
<Sidebar />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative mt-[-32px] w-[240px] overflow-x-hidden after:absolute after:inset-x-0 after:top-0 after:block after:h-[40px] after:bg-gradient-to-b after:from-white after:to-transparent after:content-['']">
|
<div className="flex-auto pt-[3vmin] md:ml-[280px] md:pt-[84px]">
|
||||||
<Sidebar />
|
<SettingsProvider>
|
||||||
|
<Settings />
|
||||||
|
</SettingsProvider>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-auto pt-[3vmin] md:ml-[280px] md:pt-[84px]">
|
</NiceModal.Provider>
|
||||||
<SettingsProvider>
|
|
||||||
<Settings />
|
|
||||||
</SettingsProvider>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ export interface IButton {
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
link?: boolean;
|
link?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
styles?: string;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,13 +20,14 @@ const Button: React.FC<IButton> = ({
|
||||||
link,
|
link,
|
||||||
disabled,
|
disabled,
|
||||||
onClick,
|
onClick,
|
||||||
|
styles,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
if (!color) {
|
if (!color) {
|
||||||
color = 'clear';
|
color = 'clear';
|
||||||
}
|
}
|
||||||
|
|
||||||
let styles = 'transition flex items-center justify-center rounded-sm text-sm';
|
styles += ' transition flex items-center justify-center rounded-sm text-sm';
|
||||||
styles += ((link && color !== 'clear' && color !== 'black') || (!link && color !== 'clear')) ? ' font-bold' : ' font-semibold';
|
styles += ((link && color !== 'clear' && color !== 'black') || (!link && color !== 'clear')) ? ' font-bold' : ' font-semibold';
|
||||||
styles += !link ? ' px-4 h-9' : '';
|
styles += !link ? ' px-4 h-9' : '';
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ interface ButtonGroupProps {
|
||||||
|
|
||||||
const ButtonGroup: React.FC<ButtonGroupProps> = ({buttons, link}) => {
|
const ButtonGroup: React.FC<ButtonGroupProps> = ({buttons, link}) => {
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center ${link ? 'gap-5' : 'gap-2'}`}>
|
<div className={`flex items-center ${link ? 'gap-5' : 'gap-3'}`}>
|
||||||
{buttons.map(({key, ...props}) => (
|
{buttons.map(({key, ...props}) => (
|
||||||
<Button key={key} link={link} {...props} />
|
<Button key={key} link={link} {...props} />
|
||||||
))}
|
))}
|
||||||
|
|
111
ghost/admin-x-settings/src/admin-x-ds/global/Modal.stories.tsx
Normal file
111
ghost/admin-x-settings/src/admin-x-ds/global/Modal.stories.tsx
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
import type {Meta, StoryObj} from '@storybook/react';
|
||||||
|
|
||||||
|
import Modal from './Modal';
|
||||||
|
import ModalContainer from './ModalContainer';
|
||||||
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Global / Modal',
|
||||||
|
component: Modal,
|
||||||
|
tags: ['autodocs'],
|
||||||
|
decorators: [(_story: any, context: any) => (
|
||||||
|
<NiceModal.Provider>
|
||||||
|
<ModalContainer {...context.args} />
|
||||||
|
</NiceModal.Provider>
|
||||||
|
)]
|
||||||
|
|
||||||
|
} satisfies Meta<typeof Modal>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Modal>;
|
||||||
|
|
||||||
|
const modalContent = (<div>Modal content</div>);
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked OK!');
|
||||||
|
},
|
||||||
|
title: 'Modal dialog',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Small: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'sm',
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked OK!');
|
||||||
|
},
|
||||||
|
title: 'Small modal',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Medium: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'md',
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked OK!');
|
||||||
|
},
|
||||||
|
title: 'Medium modal (default size)',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Large: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'lg',
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked OK!');
|
||||||
|
},
|
||||||
|
title: 'Large modal',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ExtraLarge: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'xl',
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked OK!');
|
||||||
|
},
|
||||||
|
title: 'Extra large modal',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const full: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'full',
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked OK!');
|
||||||
|
},
|
||||||
|
title: 'Full modal',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Bleed: Story = {
|
||||||
|
args: {
|
||||||
|
size: 'bleed',
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked OK!');
|
||||||
|
},
|
||||||
|
title: 'Full bleed modal',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomButtons: Story = {
|
||||||
|
args: {
|
||||||
|
leftButtonLabel: 'Extra action',
|
||||||
|
cancelLabel: 'Nope',
|
||||||
|
okLabel: 'Yep',
|
||||||
|
onOk: () => {
|
||||||
|
alert('Clicked Yep!');
|
||||||
|
},
|
||||||
|
title: 'Custom buttons',
|
||||||
|
children: modalContent
|
||||||
|
}
|
||||||
|
};
|
104
ghost/admin-x-settings/src/admin-x-ds/global/Modal.tsx
Normal file
104
ghost/admin-x-settings/src/admin-x-ds/global/Modal.tsx
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
import Button, {IButton} from './Button';
|
||||||
|
import ButtonGroup from './ButtonGroup';
|
||||||
|
import Heading from './Heading';
|
||||||
|
import React from 'react';
|
||||||
|
import {useModal} from '@ebay/nice-modal-react';
|
||||||
|
|
||||||
|
export type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'full' | 'bleed';
|
||||||
|
|
||||||
|
export interface ModalProps {
|
||||||
|
size?: ModalSize;
|
||||||
|
title?: string;
|
||||||
|
okLabel?: string;
|
||||||
|
cancelLabel?: string;
|
||||||
|
leftButtonLabel?: string;
|
||||||
|
customFooter?: React.ReactNode;
|
||||||
|
onOk?: () => void;
|
||||||
|
onCancel?: () => void;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Modal: React.FC<ModalProps> = ({size = 'md', title, okLabel, cancelLabel, customFooter, leftButtonLabel, onOk, onCancel, children}) => {
|
||||||
|
const modal = useModal();
|
||||||
|
|
||||||
|
let buttons: IButton[] = [];
|
||||||
|
|
||||||
|
if (!customFooter) {
|
||||||
|
buttons.push({
|
||||||
|
key: 'cancel-modal',
|
||||||
|
label: cancelLabel ? cancelLabel : 'Cancel',
|
||||||
|
onClick: (onCancel ? onCancel : () => {
|
||||||
|
modal.remove();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
key: 'ok-modal',
|
||||||
|
label: okLabel ? okLabel : 'OK',
|
||||||
|
color: 'black',
|
||||||
|
styles: 'min-w-[80px]',
|
||||||
|
onClick: onOk
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let modalStyles = 'z-50 mx-auto flex flex-col justify-between bg-white p-8 shadow-xl w-full';
|
||||||
|
let backdropStyles = 'fixed inset-0 h-[100vh] w-[100vw] overflow-y-scroll bg-[rgba(0,0,0,0.1)]';
|
||||||
|
|
||||||
|
switch (size) {
|
||||||
|
case 'sm':
|
||||||
|
modalStyles += ' max-w-[480px]';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'md':
|
||||||
|
modalStyles += ' max-w-[720px]';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'lg':
|
||||||
|
modalStyles += ' max-w-[940px]';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'xl':
|
||||||
|
modalStyles += ' max-w-[1180px] ';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'full':
|
||||||
|
case 'bleed':
|
||||||
|
modalStyles += ' h-full';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size !== 'bleed') {
|
||||||
|
modalStyles += ' rounded';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size !== 'bleed' && size !== 'full') {
|
||||||
|
backdropStyles += ' p-[8vmin]';
|
||||||
|
} else if (size === 'full') {
|
||||||
|
backdropStyles += ' p-[2vmin]';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={backdropStyles}>
|
||||||
|
<section className={modalStyles}>
|
||||||
|
<div>
|
||||||
|
{title && <Heading level={4}>{title}</Heading>}
|
||||||
|
<div>{children}</div>
|
||||||
|
</div>
|
||||||
|
{customFooter ? customFooter :
|
||||||
|
<div className='w-100 flex items-center justify-between gap-6'>
|
||||||
|
<div>
|
||||||
|
{leftButtonLabel &&
|
||||||
|
<Button label={leftButtonLabel} link={true} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className='flex gap-3'>
|
||||||
|
<ButtonGroup buttons={buttons}/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Modal;
|
|
@ -0,0 +1,26 @@
|
||||||
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import Button from './Button';
|
||||||
|
import Modal, {ModalProps} from './Modal';
|
||||||
|
|
||||||
|
const ModalContainer: React.FC<ModalProps> = ({children, onCancel, ...props}) => {
|
||||||
|
const modal = NiceModal.create<ModalProps>(() => {
|
||||||
|
return (
|
||||||
|
<Modal {...props}>
|
||||||
|
<div className='py-4'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button color='black' label='Open modal' onClick={() => {
|
||||||
|
NiceModal.show(modal);
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModalContainer;
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Modal from '../../admin-x-ds/global/Modal';
|
||||||
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
|
|
||||||
|
const InviteUserModal = NiceModal.create(() => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size='md'
|
||||||
|
title='Invite users'
|
||||||
|
onOk={() => {
|
||||||
|
alert('Clicked OK');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='py-4'>
|
||||||
|
[TBD: invite user contents]
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default InviteUserModal;
|
|
@ -1,13 +1,21 @@
|
||||||
import Button from '../../../admin-x-ds/global/Button';
|
import Button from '../../../admin-x-ds/global/Button';
|
||||||
|
import InviteUserModal from '../../modals/InviteUserModal';
|
||||||
import List from '../../../admin-x-ds/global/List';
|
import List from '../../../admin-x-ds/global/List';
|
||||||
import ListItem from '../../../admin-x-ds/global/ListItem';
|
import ListItem from '../../../admin-x-ds/global/ListItem';
|
||||||
|
import NiceModal from '@ebay/nice-modal-react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
|
||||||
import TabView from '../../../admin-x-ds/global/TabView';
|
import TabView from '../../../admin-x-ds/global/TabView';
|
||||||
|
|
||||||
const Users: React.FC = () => {
|
const Users: React.FC = () => {
|
||||||
|
const showAddModal = () => {
|
||||||
|
NiceModal.show(InviteUserModal);
|
||||||
|
};
|
||||||
|
|
||||||
const buttons = (
|
const buttons = (
|
||||||
<Button color='green' label='Invite users' link={true} />
|
<Button color='green' label='Invite users' link={true} onClick={() => {
|
||||||
|
showAddModal();
|
||||||
|
}} />
|
||||||
);
|
);
|
||||||
|
|
||||||
const owner = (
|
const owner = (
|
||||||
|
|
Loading…
Add table
Reference in a new issue