Added sticky footer option to modals in AdminX

refs. https://github.com/TryGhost/Team/issues/3351
This commit is contained in:
Peter Zimon 2023-06-06 14:58:30 +02:00
parent f5415a25ad
commit 19dfd28946
4 changed files with 116 additions and 37 deletions

View File

@ -31,7 +31,7 @@ const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
const [taskState, setTaskState] = useState<'running' | ''>('');
return (
<Modal
backDrop={false}
backDropClick={false}
cancelLabel={cancelLabel}
customFooter={customFooter}
okColor={okColor}
@ -45,7 +45,7 @@ const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
setTaskState('');
}}
>
<div className='py-4'>
<div className='py-4 leading-9'>
{prompt}
</div>
</Modal>

View File

@ -13,7 +13,7 @@ const meta = {
<ModalContainer {...context.args} />
</NiceModal.Provider>
)]
} satisfies Meta<typeof Modal>;
export default meta;
@ -108,4 +108,31 @@ export const CustomButtons: Story = {
title: 'Custom buttons',
children: modalContent
}
};
const longContent = (
<>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure. Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure. Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p className='mb-6'>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
<p>Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure. Esse ex officia ipsum et magna reprehenderit ullamco dolore cillum cupidatat ullamco culpa. In et irure irure est id cillum officia pariatur et proident. Nulla nulla dolore qui excepteur magna eu adipisicing mollit. Eiusmod eu irure cupidatat consequat consectetur irure.</p>
</>
);
export const StickyFooter: Story = {
args: {
size: 'md',
stickyFooter: true,
onOk: () => {
alert('Clicked OK!');
},
title: 'Sticky footer',
children: longContent
}
};

View File

@ -26,6 +26,7 @@ export interface ModalProps {
children?: React.ReactNode;
backDrop?: boolean;
backDropClick?: boolean;
stickyFooter?: boolean;
}
const Modal: React.FC<ModalProps> = ({
@ -41,69 +42,113 @@ const Modal: React.FC<ModalProps> = ({
onCancel,
children,
backDrop = true,
backDropClick = true
backDropClick = true,
stickyFooter = false
}) => {
const modal = useModal();
let buttons: IButton[] = [];
if (!customFooter) {
buttons.push({
key: 'cancel-modal',
label: cancelLabel,
onClick: (onCancel ? onCancel : () => {
modal.remove();
})
});
if (cancelLabel) {
buttons.push({
key: 'cancel-modal',
label: cancelLabel,
onClick: (onCancel ? onCancel : () => {
modal.remove();
})
});
}
buttons.push({
key: 'ok-modal',
label: okLabel,
color: okColor,
className: 'min-w-[80px]',
onClick: onOk
});
if (okLabel) {
buttons.push({
key: 'ok-modal',
label: okLabel,
color: okColor,
className: 'min-w-[80px]',
onClick: onOk
});
}
}
let modalClasses = 'relative rounded overflow-hidden z-50 mx-auto flex flex-col justify-between bg-white shadow-xl w-full';
let backdropClasses = 'fixed inset-0 h-[100vh] w-[100vw] overflow-y-scroll z-40 ';
//bg-[linear-gradient(0deg,rgba(255,255,255,1)_85%,rgba(255,255,255,0)_100%)]
let modalClasses = clsx('relative z-50 mx-auto flex w-full flex-col justify-between rounded bg-white shadow-xl');
let backdropClasses = clsx('fixed inset-0 z-40 h-[100vh] w-[100vw] overflow-y-scroll ');
let padding;
let footerContainerBottom = '';
switch (size) {
case 'sm':
modalClasses += ` max-w-[480px] ${!noPadding && 'p-8'}`;
modalClasses += ' max-w-[480px] ';
backdropClasses += ' p-[8vmin]';
padding = 8;
footerContainerBottom = 'calc(-1 * (8vmin + 48px)';
break;
case 'md':
modalClasses += ` max-w-[720px] ${!noPadding && 'p-8'}`;
modalClasses += ' max-w-[720px] ';
backdropClasses += ' p-[8vmin]';
padding = 8;
footerContainerBottom = 'calc(-1 * (8vmin + 48px)';
break;
case 'lg':
modalClasses += ` max-w-[1020px] ${!noPadding && 'p-12'}`;
modalClasses += ' max-w-[1020px] ';
backdropClasses += ' p-[4vmin]';
padding = 12;
footerContainerBottom = 'calc(-1 * (4vmin + 68px)';
break;
case 'xl':
modalClasses += ` max-w-[1240px] ${!noPadding && 'p-14'}`;
modalClasses += ' max-w-[1240px] ';
backdropClasses += ' p-[3vmin]';
padding = 14;
footerContainerBottom = 'calc(-1 * (3vmin + 68px)';
break;
case 'full':
modalClasses += ` h-full ${!noPadding && 'p-12'}`;
modalClasses += ' h-full ';
backdropClasses += ' p-[2vmin]';
padding = 12;
footerContainerBottom = 'calc(-1 * (2vmin + 68px)';
break;
case 'bleed':
modalClasses += ` h-full ${!noPadding && 'p-12'}`;
modalClasses += ' h-full ';
padding = 12;
break;
default:
modalClasses += ` ${!noPadding && 'p-8'}`;
backdropClasses += ' p-[8vmin]';
footerContainerBottom = 'calc(-1 * (8vmin + 48px)';
padding = 8;
break;
}
if (noPadding) {
padding = 0;
}
let footerContainerClasses = clsx(
'w-100',
stickyFooter && 'sticky z-[100] mb-[-24px]',
`${stickyFooter ? 'before:sticky' : 'before:hidden'} before:bottom-0 before:z-[100] before:block before:h-[24px] before:bg-white before:content-['']`,
`${stickyFooter ? 'after:sticky' : 'after:hidden'} after:bottom-[72px] after:block after:h-[24px] after:shadow-[0_0_0_1px_rgba(0,0,0,.04),0_-8px_16px_-3px_rgba(0,0,0,.15)] after:content-['']`
);
let footerClasses = clsx(
`px-${padding} pb-${padding} ${stickyFooter && `pt-8`} z-[101] flex items-center justify-between`,
stickyFooter && `sticky bottom-[-48px] rounded-b bg-white`
);
let contentClasses = `p-${padding}`;
if (!customFooter) {
contentClasses += ' pb-0 ';
}
const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget && backDropClick) {
modal.remove();
@ -121,19 +166,25 @@ const Modal: React.FC<ModalProps> = ({
backDrop && 'bg-[rgba(98,109,121,0.15)] backdrop-blur-[3px]'
)}></div>
<section className={modalClasses} style={modalStyles}>
<div className='h-full'>
{title && <Heading level={4}>{title}</Heading>}
{children}
<div className={contentClasses}>
<div className='h-full'>
{title && <Heading level={4}>{title}</Heading>}
{children}
</div>
</div>
{customFooter ? customFooter :
<div className='w-100 flex items-center justify-between gap-6'>
<div>
{leftButtonLabel &&
<div className={footerContainerClasses} style={{
bottom: `${stickyFooter && footerContainerBottom}`
}}>
<div className={footerClasses}>
<div>
{leftButtonLabel &&
<Button label={leftButtonLabel} link={true} />
}
</div>
<div className='flex gap-3'>
<ButtonGroup buttons={buttons}/>
}
</div>
<div className='flex gap-3'>
<ButtonGroup buttons={buttons}/>
</div>
</div>
</div>
}

View File

@ -593,6 +593,7 @@ const UserDetailModal:React.FC<UserDetailModalProps> = ({user, updateUser}) => {
okColor='green'
okLabel={okLabel}
size='lg'
stickyFooter={true}
onOk={async () => {
setSaveState('saving');
if (!validator.isEmail(userData.email)) {