mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Added preview modal to AdminX Design System
refs. https://github.com/TryGhost/Team/issues/3328
This commit is contained in:
parent
ee274deae6
commit
0e5d66b680
5 changed files with 183 additions and 14 deletions
|
@ -18,9 +18,10 @@ interface IHeading {
|
|||
* Uses <label> tag and standardised styling for form labels
|
||||
*/
|
||||
useLabelTag?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Heading: React.FC<IHeading> = ({level, children, styles, grey, separator, useLabelTag, ...props}) => {
|
||||
const Heading: React.FC<IHeading> = ({level, children, styles, grey, separator, useLabelTag, className, ...props}) => {
|
||||
if (!level) {
|
||||
level = 1;
|
||||
}
|
||||
|
@ -28,7 +29,7 @@ const Heading: React.FC<IHeading> = ({level, children, styles, grey, separator,
|
|||
const newElement = `${useLabelTag ? 'label' : `h${level}`}`;
|
||||
styles += (level === 6 || useLabelTag) ? (` block text-2xs font-semibold uppercase tracking-wide ${(grey && 'text-grey-700')}`) : ' ';
|
||||
|
||||
const Element = React.createElement(newElement, {className: styles, key: 'heading-elem', ...props}, children);
|
||||
const Element = React.createElement(newElement, {className: styles + ' ' + className, key: 'heading-elem', ...props}, children);
|
||||
|
||||
if (separator) {
|
||||
let gap = (!level || level === 1) ? 2 : 1;
|
||||
|
|
|
@ -14,12 +14,25 @@ export interface ModalProps {
|
|||
cancelLabel?: string;
|
||||
leftButtonLabel?: string;
|
||||
customFooter?: React.ReactNode;
|
||||
noPadding?: boolean;
|
||||
onOk?: () => void;
|
||||
onCancel?: () => void;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({size = 'md', title, okLabel, cancelLabel, customFooter, leftButtonLabel, onOk, okColor, onCancel, children}) => {
|
||||
const Modal: React.FC<ModalProps> = ({
|
||||
size = 'md',
|
||||
title,
|
||||
okLabel = 'OK',
|
||||
cancelLabel = 'Cancel',
|
||||
customFooter,
|
||||
leftButtonLabel,
|
||||
noPadding = false,
|
||||
onOk,
|
||||
okColor = 'black',
|
||||
onCancel,
|
||||
children
|
||||
}) => {
|
||||
const modal = useModal();
|
||||
|
||||
let buttons: IButton[] = [];
|
||||
|
@ -27,7 +40,7 @@ const Modal: React.FC<ModalProps> = ({size = 'md', title, okLabel, cancelLabel,
|
|||
if (!customFooter) {
|
||||
buttons.push({
|
||||
key: 'cancel-modal',
|
||||
label: cancelLabel ? cancelLabel : 'Cancel',
|
||||
label: cancelLabel,
|
||||
onClick: (onCancel ? onCancel : () => {
|
||||
modal.remove();
|
||||
})
|
||||
|
@ -35,8 +48,8 @@ const Modal: React.FC<ModalProps> = ({size = 'md', title, okLabel, cancelLabel,
|
|||
|
||||
buttons.push({
|
||||
key: 'ok-modal',
|
||||
label: okLabel ? okLabel : 'OK',
|
||||
color: okColor ? okColor : 'black',
|
||||
label: okLabel,
|
||||
color: okColor,
|
||||
styles: 'min-w-[80px]',
|
||||
onClick: onOk
|
||||
});
|
||||
|
@ -47,32 +60,32 @@ const Modal: React.FC<ModalProps> = ({size = 'md', title, okLabel, cancelLabel,
|
|||
|
||||
switch (size) {
|
||||
case 'sm':
|
||||
modalStyles += ' max-w-[480px] p-8';
|
||||
modalStyles += ` max-w-[480px] ${!noPadding && 'p-8'}`;
|
||||
backdropStyles += ' p-[8vmin]';
|
||||
break;
|
||||
|
||||
case 'md':
|
||||
modalStyles += ' max-w-[720px] p-8';
|
||||
modalStyles += ` max-w-[720px] ${!noPadding && 'p-8'}`;
|
||||
backdropStyles += ' p-[8vmin]';
|
||||
break;
|
||||
|
||||
case 'lg':
|
||||
modalStyles += ' max-w-[1020px] p-12';
|
||||
modalStyles += ` max-w-[1020px] ${!noPadding && 'p-12'}`;
|
||||
backdropStyles += ' p-[4vmin]';
|
||||
break;
|
||||
|
||||
case 'xl':
|
||||
modalStyles += ' max-w-[1240px] p-14';
|
||||
modalStyles += ` max-w-[1240px] ${!noPadding && 'p-14'}`;
|
||||
backdropStyles += ' p-[3vmin]';
|
||||
break;
|
||||
|
||||
case 'full':
|
||||
modalStyles += ' h-full p-12';
|
||||
modalStyles += ` h-full ${!noPadding && 'p-12'}`;
|
||||
backdropStyles += ' p-[2vmin]';
|
||||
break;
|
||||
|
||||
case 'bleed':
|
||||
modalStyles += ' h-full p-12';
|
||||
modalStyles += ` h-full ${!noPadding && 'p-12'}`;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -86,9 +99,9 @@ const Modal: React.FC<ModalProps> = ({size = 'md', title, okLabel, cancelLabel,
|
|||
<div className={backdropStyles} id='modal-backdrop' onClick={handleBackdropClick}>
|
||||
<div className='pointer-events-none fixed inset-0 z-0 bg-[rgba(0,0,0,0.1)]'></div>
|
||||
<section className={modalStyles}>
|
||||
<div>
|
||||
<div className='h-full'>
|
||||
{title && <Heading level={4}>{title}</Heading>}
|
||||
<div>{children}</div>
|
||||
{children}
|
||||
</div>
|
||||
{customFooter ? customFooter :
|
||||
<div className='w-100 flex items-center justify-between gap-6'>
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
import type {Meta, StoryObj} from '@storybook/react';
|
||||
|
||||
import Heading from './Heading';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import PreviewModal from './PreviewModal';
|
||||
import PreviewModalContainer from './PreviewModalContainer';
|
||||
|
||||
const meta = {
|
||||
title: 'Global / Modal / Preview Modal',
|
||||
component: PreviewModal,
|
||||
tags: ['autodocs'],
|
||||
decorators: [(_story: any, context: any) => (
|
||||
<NiceModal.Provider>
|
||||
<PreviewModalContainer {...context.args} />
|
||||
</NiceModal.Provider>
|
||||
)]
|
||||
} satisfies Meta<typeof PreviewModal>;
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof PreviewModal>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: 'Preview modal',
|
||||
preview: (
|
||||
<div className='flex h-full items-center justify-center text-sm text-grey-500'>
|
||||
Preview area
|
||||
</div>
|
||||
),
|
||||
sidebar: (
|
||||
<div className='flex h-full items-center justify-center text-sm text-grey-500'>
|
||||
Sidebar area
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
export const CustomButtons: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
cancelLabel: 'Meh',
|
||||
okLabel: 'Alrite',
|
||||
okColor: 'green'
|
||||
}
|
||||
};
|
||||
|
||||
export const CustomHeader: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
customHeader: (
|
||||
<div className='border-b border-grey-100 bg-black p-10 text-center text-white'>
|
||||
<Heading level={3}>A custom header here</Heading>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
};
|
|
@ -0,0 +1,85 @@
|
|||
import ButtonGroup from './ButtonGroup';
|
||||
import Heading from './Heading';
|
||||
import Modal from './Modal';
|
||||
import NiceModal, {useModal} from '@ebay/nice-modal-react';
|
||||
import React from 'react';
|
||||
import {IButton} from './Button';
|
||||
|
||||
export interface PreviewModalProps {
|
||||
title?: string;
|
||||
sidebar?: React.ReactNode;
|
||||
preview?: React.ReactNode;
|
||||
cancelLabel?: string;
|
||||
okLabel?: string;
|
||||
okColor?: string;
|
||||
onCancel?: () => void;
|
||||
onOk?: () => void;
|
||||
customButtons?: React.ReactNode;
|
||||
customHeader?: React.ReactNode;
|
||||
sidebarPadding?: boolean;
|
||||
}
|
||||
|
||||
const PreviewModal: React.FC<PreviewModalProps> = ({
|
||||
title,
|
||||
sidebar,
|
||||
preview,
|
||||
cancelLabel = 'Cancel',
|
||||
okLabel = 'OK',
|
||||
okColor = 'black',
|
||||
onCancel,
|
||||
onOk,
|
||||
customButtons,
|
||||
customHeader,
|
||||
sidebarPadding = true
|
||||
}) => {
|
||||
const modal = useModal();
|
||||
let buttons: IButton[] = [];
|
||||
|
||||
if (!customButtons) {
|
||||
buttons.push({
|
||||
key: 'cancel-modal',
|
||||
label: cancelLabel,
|
||||
onClick: (onCancel ? onCancel : () => {
|
||||
modal.remove();
|
||||
})
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
key: 'ok-modal',
|
||||
label: okLabel,
|
||||
color: okColor,
|
||||
styles: 'min-w-[80px]',
|
||||
onClick: onOk
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
customFooter={(<></>)}
|
||||
noPadding={true}
|
||||
size='full'
|
||||
title=''
|
||||
>
|
||||
<div className='flex h-full grow'>
|
||||
<div className='grow'>
|
||||
{preview}
|
||||
</div>
|
||||
<div className='flex h-full basis-[400px] flex-col gap-3 border-l border-grey-100'>
|
||||
{customHeader ? customHeader : (
|
||||
<div className='flex justify-between gap-3 px-7 pt-7'>
|
||||
<>
|
||||
<Heading className='mt-1' level={4}>{title}</Heading>
|
||||
{customButtons ? customButtons : <ButtonGroup buttons={buttons} /> }
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
<div className={`grow ${sidebarPadding && 'p-7'}`}>
|
||||
{sidebar}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default NiceModal.create(PreviewModal);
|
|
@ -0,0 +1,14 @@
|
|||
import Button from './Button';
|
||||
import NiceModal from '@ebay/nice-modal-react';
|
||||
import PreviewModal, {PreviewModalProps} from './PreviewModal';
|
||||
import React from 'react';
|
||||
|
||||
const PreviewModalContainer: React.FC<PreviewModalProps> = ({...props}) => {
|
||||
return (
|
||||
<Button color='black' label='Open preview modal' onClick={() => {
|
||||
NiceModal.show(PreviewModal, {...props});
|
||||
}} />
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewModalContainer;
|
Loading…
Add table
Reference in a new issue