0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added Breadcrumbs toolbar option to PreviewModal in AdminX (#19016)

no issues

- currently there are urls and tabs as a toolbar option for the preview
modal
- as part of adding offers to AdminX, we needed a breadcrumbs option for
navigating between list and create/edit screens
- previewToolbarBreadcrumbs is used for passing an array with the type
BreadcrumbItem
- onBreadcrumbsBack is used for passing a function to be called when the
back button of the breadcrumbs is clicked
This commit is contained in:
Sodbileg Gansukh 2023-11-16 19:26:25 +08:00 committed by GitHub
parent 17f8844134
commit 8377bd8410
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 72 additions and 3 deletions

View file

@ -96,3 +96,11 @@ export const FullBleed: Story = {
size: 'bleed' size: 'bleed'
} }
}; };
export const BreadcrumbsToolbar: Story = {
args: {
...Default.args,
previewToolbarTabs: undefined,
previewToolbarBreadcrumbs: [{label: 'Previous', onClick: () => {}}, {label: 'Current'}]
}
};

View file

@ -4,6 +4,7 @@ import React, {useEffect, useState} from 'react';
import useGlobalDirtyState from '../../hooks/useGlobalDirtyState'; import useGlobalDirtyState from '../../hooks/useGlobalDirtyState';
import {confirmIfDirty} from '../../utils/modals'; import {confirmIfDirty} from '../../utils/modals';
import {ButtonColor, ButtonProps} from '../Button'; import {ButtonColor, ButtonProps} from '../Button';
import Breadcrumbs, {BreadcrumbItem} from '../Breadcrumbs';
import ButtonGroup from '../ButtonGroup'; import ButtonGroup from '../ButtonGroup';
import Heading, {HeadingLevel} from '../Heading'; import Heading, {HeadingLevel} from '../Heading';
import Icon from '../Icon'; import Icon from '../Icon';
@ -33,6 +34,7 @@ export interface PreviewModalProps {
deviceSelector?: boolean; deviceSelector?: boolean;
siteLink?: string; siteLink?: string;
previewToolbarURLs?: SelectOption[]; previewToolbarURLs?: SelectOption[];
previewToolbarBreadcrumbs?: BreadcrumbItem[];
previewBgColor?: 'grey' | 'white' | 'greygradient'; previewBgColor?: 'grey' | 'white' | 'greygradient';
selectedURL?: string; selectedURL?: string;
previewToolbarTabs?: Tab[]; previewToolbarTabs?: Tab[];
@ -49,6 +51,7 @@ export interface PreviewModalProps {
onSelectURL?: (url: string) => void; onSelectURL?: (url: string) => void;
onSelectDesktopView?: () => void; onSelectDesktopView?: () => void;
onSelectMobileView?: () => void; onSelectMobileView?: () => void;
onBreadcrumbsBack?: () => void;
} }
export const PreviewModalContent: React.FC<PreviewModalProps> = ({ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
@ -73,6 +76,7 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
previewBgColor = 'grey', previewBgColor = 'grey',
selectedURL, selectedURL,
previewToolbarTabs, previewToolbarTabs,
previewToolbarBreadcrumbs,
buttonsDisabled, buttonsDisabled,
sidebarButtons, sidebarButtons,
sidebarHeader, sidebarHeader,
@ -85,7 +89,8 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
afterClose, afterClose,
onSelectURL, onSelectURL,
onSelectDesktopView, onSelectDesktopView,
onSelectMobileView onSelectMobileView,
onBreadcrumbsBack
}) => { }) => {
const modal = useModal(); const modal = useModal();
const {setGlobalDirtyState} = useGlobalDirtyState(); const {setGlobalDirtyState} = useGlobalDirtyState();
@ -145,6 +150,16 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
width='wide' width='wide'
onTabChange={onSelectURL!} onTabChange={onSelectURL!}
/>; />;
} else if (previewToolbarBreadcrumbs) {
toolbarLeft = <Breadcrumbs
activeItemClassName='hidden md:!block md:!visible'
containerClassName='whitespace-nowrap'
itemClassName='hidden md:!block md:!visible'
items={previewToolbarBreadcrumbs}
separatorClassName='hidden md:!block md:!visible'
backIcon
onBack={onBreadcrumbsBack}
/>;
} }
const selectedIconColorClass = 'text-black dark:text-green'; const selectedIconColorClass = 'text-black dark:text-green';

View file

@ -433,9 +433,15 @@ const AddOfferModal = () => {
okColor={okProps.color} okColor={okProps.color}
okLabel='Publish' okLabel='Publish'
preview={iframe} preview={iframe}
previewToolbarBreadcrumbs={[{label: 'Offers', onClick: () => {
updateRoute('offers/edit');
}}, {label: 'New offer'}]}
sidebar={sidebar} sidebar={sidebar}
size='lg' size='lg'
title='Offer' title='Offer'
onBreadcrumbsBack={() => {
updateRoute('offers/edit');
}}
onCancel={cancelAddOffer} onCancel={cancelAddOffer}
onOk={async () => { onOk={async () => {
if (!(await handleSave({fakeWhenUnchanged: true}))) { if (!(await handleSave({fakeWhenUnchanged: true}))) {
@ -444,7 +450,8 @@ const AddOfferModal = () => {
message: 'Can\'t save offer, please double check that you\'ve filled all mandatory fields.' message: 'Can\'t save offer, please double check that you\'ve filled all mandatory fields.'
}); });
} }
}} />; }}
/>;
}; };
export default AddOfferModal; export default AddOfferModal;

View file

@ -269,10 +269,16 @@ const EditOfferModal: React.FC<{id: string}> = ({id}) => {
okColor={okProps.color} okColor={okProps.color}
okLabel={okProps.label || 'Save'} okLabel={okProps.label || 'Save'}
preview={iframe} preview={iframe}
previewToolbarBreadcrumbs={[{label: 'Offers', onClick: () => {
updateRoute('offers/edit');
}}, {label: offerById[0]?.name || ''}]}
sidebar={sidebar} sidebar={sidebar}
size='lg' size='lg'
testId='offer-update-modal' testId='offer-update-modal'
title='Offer' title='Offer'
onBreadcrumbsBack={() => {
updateRoute('offers/edit');
}}
onCancel={() => { onCancel={() => {
updateRoute('offers/edit'); updateRoute('offers/edit');
}} }}

View file

@ -1,8 +1,11 @@
import {Breadcrumbs} from '@tryghost/admin-x-design-system';
import {Button} from '@tryghost/admin-x-design-system'; import {Button} from '@tryghost/admin-x-design-system';
import {Icon} from '@tryghost/admin-x-design-system'; import {Icon} from '@tryghost/admin-x-design-system';
import {Modal} from '@tryghost/admin-x-design-system'; import {Modal} from '@tryghost/admin-x-design-system';
import {Offer, useBrowseOffersById} from '@tryghost/admin-x-framework/api/offers'; import {Offer, useBrowseOffersById} from '@tryghost/admin-x-framework/api/offers';
import {currencyToDecimal} from '../../../../utils/currency';
import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site'; import {getHomepageUrl} from '@tryghost/admin-x-framework/api/site';
import {numberWithCommas} from '../../../../utils/helpers';
import {useEffect, useState} from 'react'; import {useEffect, useState} from 'react';
import {useGlobalData} from '../../../providers/GlobalDataProvider'; import {useGlobalData} from '../../../providers/GlobalDataProvider';
import {useRouting} from '@tryghost/admin-x-framework/routing'; import {useRouting} from '@tryghost/admin-x-framework/routing';
@ -39,7 +42,22 @@ const OfferSuccess: React.FC<{id: string}> = ({id}) => {
}; };
const handleTwitter = () => { const handleTwitter = () => {
window.open(`https://twitter.com/intent/tweet?url=${encodeURI(offerLink)}&text=${encodeURIComponent(offer?.name || '')}`, '_blank'); let tweetText = '';
switch (offer?.type) {
case 'percent':
tweetText = offer?.amount + '% discount';
break;
case 'fixed':
tweetText = numberWithCommas(currencyToDecimal(offer?.amount)) + ' ' + offer?.currency + ' discount';
break;
case 'trial':
tweetText = offer?.amount + ' days free trial';
break;
default:
break;
};
window.open(`https://twitter.com/intent/tweet?url=${encodeURI(offerLink)}&text=${encodeURIComponent(offer?.name || '')} — Check out ${encodeURIComponent(tweetText)} on:`, '_blank');
}; };
const handleFacebook = () => { const handleFacebook = () => {
@ -61,6 +79,21 @@ const OfferSuccess: React.FC<{id: string}> = ({id}) => {
topRightContent='close' topRightContent='close'
> >
<div className='-mt-6 flex h-full flex-col items-center justify-center text-center'> <div className='-mt-6 flex h-full flex-col items-center justify-center text-center'>
<div className='absolute left-6 top-5'>
<Breadcrumbs
activeItemClassName='hidden md:!block md:!visible'
containerClassName='whitespace-nowrap'
itemClassName='hidden md:!block md:!visible'
items={[{label: 'Offers', onClick: () => {
updateRoute('offers/edit');
}}, {label: offer?.name || ''}]}
separatorClassName='hidden md:!block md:!visible'
backIcon
onBack={() => {
updateRoute('offers/edit');
}}
/>
</div>
<Icon name='tags-check' size='xl' /> <Icon name='tags-check' size='xl' />
<h1 className='mt-6 text-4xl'>Your new offer is live!</h1> <h1 className='mt-6 text-4xl'>Your new offer is live!</h1>
<p className='mt-3 max-w-[510px] text-[1.6rem]'>You can share the link anywhere. In your newsletter, social media, a podcast, or in-person. It all just works.</p> <p className='mt-3 max-w-[510px] text-[1.6rem]'>You can share the link anywhere. In your newsletter, social media, a podcast, or in-person. It all just works.</p>