AdminX Announcement Bar (#17950)

refs https://github.com/TryGhost/Product/issues/3807

- Created Announcement Bar components in Admin X.
This commit is contained in:
Peter Zimon 2023-09-05 05:27:20 +03:00 committed by GitHub
parent 187f369720
commit fcb2636d59
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 178 additions and 21 deletions

View File

@ -1,7 +1,7 @@
import React from 'react';
import Separator from './Separator';
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
interface HeadingBaseProps {
level?: HeadingLevel;

View File

@ -1,13 +1,17 @@
import Heading from '../Heading';
import clsx from 'clsx';
import {Fragment, MouseEvent, useRef} from 'react';
type SwatchSizes = 'md' | 'lg';
const ColorSwatch: React.FC<{
hex: string;
value?: string | null;
title: string;
size?: SwatchSizes;
isSelected: boolean;
onSelect: (value: string | null) => void;
}> = ({hex, value, title, isSelected, onSelect}) => {
}> = ({hex, value, title, size = 'md', isSelected, onSelect}) => {
const ref = useRef(null);
const onSelectHandler = (e: MouseEvent) => {
@ -22,11 +26,19 @@ const ColorSwatch: React.FC<{
const isTransparent = (hex.length === 4 && hex[3] === '0') || (hex.length === 8 && hex.slice(6) === '00');
let sizeClass = 'h-5 w-5';
switch (size) {
case 'lg':
sizeClass = 'w-6 h-6';
break;
}
return (
<button
ref={ref}
className={clsx(
`relative flex h-5 w-5 shrink-0 cursor-pointer items-center rounded-full border border-grey-200 dark:border-grey-800`,
`relative flex shrink-0 cursor-pointer items-center rounded-full border border-grey-200 dark:border-grey-800`,
sizeClass,
isSelected && 'outline outline-2 outline-green'
)}
style={{backgroundColor: hex}}
@ -48,33 +60,47 @@ export type SwatchOption = {
/** Should usually be used via [ColorPickerField](?path=/docs/global-form-color-picker-field--docs) */
const ColorIndicator: React.FC<{
title?: string;
value?: string | null;
swatches: SwatchOption[];
swatchSize?: SwatchSizes;
onSwatchChange: (newValue: string | null) => void;
onTogglePicker: () => void;
isExpanded: boolean;
}> = ({value, swatches, onSwatchChange, onTogglePicker, isExpanded}) => {
picker?: boolean;
containerClassName?: string;
}> = ({title, value, swatches, swatchSize = 'md',onSwatchChange, onTogglePicker, isExpanded, picker = true, containerClassName}) => {
let selectedSwatch = swatches.find(swatch => swatch.value === value || swatch.hex === value);
if (isExpanded) {
selectedSwatch = undefined;
}
containerClassName = clsx(
'flex flex-col gap-3'
);
return (
<div className='flex gap-1'>
<div className={`flex items-center gap-1`}>
{swatches.map(({customContent, ...swatch}) => (
customContent ? <Fragment key={swatch.title}>{customContent}</Fragment> : <ColorSwatch key={swatch.title} isSelected={selectedSwatch?.title === swatch.title} onSelect={onSwatchChange} {...swatch} />
))}
<div className={containerClassName}>
{title && <Heading useLabelTag>{title}</Heading>}
<div className='flex gap-1'>
<div className={`flex items-center gap-1`}>
{swatches.map(({customContent, ...swatch}) => (
customContent ? <Fragment key={swatch.title}>{customContent}</Fragment> : <ColorSwatch key={swatch.title} isSelected={selectedSwatch?.title === swatch.title} size={swatchSize} onSelect={onSwatchChange} {...swatch} />
))}
</div>
{picker &&
<button aria-label="Pick color" className="relative h-6 w-6 cursor-pointer rounded-full border border-grey-200 dark:border-grey-800" type="button" onClick={onTogglePicker}>
<div className='absolute inset-0 rounded-full bg-[conic-gradient(hsl(360,100%,50%),hsl(315,100%,50%),hsl(270,100%,50%),hsl(225,100%,50%),hsl(180,100%,50%),hsl(135,100%,50%),hsl(90,100%,50%),hsl(45,100%,50%),hsl(0,100%,50%))]' />
{value && !selectedSwatch && (
<div className="dark:border-grey-950 absolute inset-[3px] overflow-hidden rounded-full border border-white" style={{backgroundColor: value}}>
{value === 'transparent' && <div className="absolute left-[3px] top-[3px] z-10 w-[136%] origin-left rotate-45 border-b border-b-red" />}
</div>
)}
</button>
}
</div>
<button aria-label="Pick color" className="relative h-6 w-6 cursor-pointer rounded-full border border-grey-200 dark:border-grey-800" type="button" onClick={onTogglePicker}>
<div className='absolute inset-0 rounded-full bg-[conic-gradient(hsl(360,100%,50%),hsl(315,100%,50%),hsl(270,100%,50%),hsl(225,100%,50%),hsl(180,100%,50%),hsl(135,100%,50%),hsl(90,100%,50%),hsl(45,100%,50%),hsl(0,100%,50%))]' />
{value && !selectedSwatch && (
<div className="dark:border-grey-950 absolute inset-[3px] overflow-hidden rounded-full border border-white" style={{backgroundColor: value}}>
{value === 'transparent' && <div className="absolute left-[3px] top-[3px] z-10 w-[136%] origin-left rotate-45 border-b border-b-red" />}
</div>
)}
</button>
</div>
);
};

View File

@ -1,6 +1,6 @@
import ButtonGroup from '../ButtonGroup';
import DesktopChrome from '../chrome/DesktopChrome';
import Heading from '../Heading';
import Heading, {HeadingLevel} from '../Heading';
import MobileChrome from '../chrome/MobileChrome';
import Modal, {ModalSize} from './Modal';
import NiceModal, {useModal} from '@ebay/nice-modal-react';
@ -15,6 +15,7 @@ import {confirmIfDirty} from '../../../utils/modals';
export interface PreviewModalProps {
testId?: string;
title?: string;
titleHeadingLevel?: HeadingLevel;
size?: ModalSize;
sidebar?: boolean | React.ReactNode;
preview?: React.ReactNode;
@ -48,6 +49,7 @@ export interface PreviewModalProps {
export const PreviewModalContent: React.FC<PreviewModalProps> = ({
testId,
title,
titleHeadingLevel = 4,
size = 'full',
sidebar = '',
preview,
@ -216,7 +218,7 @@ export const PreviewModalContent: React.FC<PreviewModalProps> = ({
<div className='relative flex h-full basis-[400px] flex-col border-l border-grey-100'>
{sidebarHeader ? sidebarHeader : (
<div className='flex max-h-[74px] items-start justify-between gap-3 px-7 py-5'>
<Heading className='mt-1' level={4}>{title}</Heading>
<Heading className='mt-1' level={titleHeadingLevel}>{title}</Heading>
{sidebarButtons ? sidebarButtons : <ButtonGroup buttons={buttons} /> }
</div>
)}

View File

@ -46,6 +46,7 @@ const Sidebar: React.FC = () => {
{/* <SettingNavItem navid='theme' title="Theme" onClick={handleSectionClick} /> */}
<SettingNavItem navid='design' title="Branding and design" onClick={handleSectionClick} />
<SettingNavItem navid='navigation' title="Navigation" onClick={handleSectionClick} />
<SettingNavItem navid='announcement-bar' title="Announcement bar" onClick={handleSectionClick} />
</SettingNavSection>
<SettingNavSection title="Membership">

View File

@ -2,6 +2,7 @@ import AddIntegrationModal from '../settings/advanced/integrations/AddIntegratio
import AddNewsletterModal from '../settings/email/newsletters/AddNewsletterModal';
import AddRecommendationModal from '../settings/site/recommendations/AddRecommendationModal';
import AmpModal from '../settings/advanced/integrations/AmpModal';
import AnnouncementBarModal from '../settings/site/AnnouncementBarModal';
import ChangeThemeModal from '../settings/site/ThemeModal';
import CustomIntegrationModal from '../settings/advanced/integrations/CustomIntegrationModal';
import DesignModal from '../settings/site/DesignModal';
@ -77,7 +78,8 @@ const modalPaths: {[key: string]: React.FC<NiceModalHocProps & RoutingModalProps
'integrations/add': AddIntegrationModal,
'integrations/show/:id': CustomIntegrationModal,
'recommendations/add': AddRecommendationModal,
'recommendations/:id': EditRecommendationModal
'recommendations/:id': EditRecommendationModal,
'announcement-bar/edit': AnnouncementBarModal
};
function getHashPath(urlPath: string | undefined) {

View File

@ -0,0 +1,24 @@
import Button from '../../../admin-x-ds/global/Button';
import React from 'react';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
import useRouting from '../../../hooks/useRouting';
const AnnouncementBar: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {updateRoute} = useRouting();
const openModal = () => {
updateRoute('announcement-bar/edit');
};
return (
<SettingGroup
customButtons={<Button color='green' label='Customize' link onClick={openModal}/>}
description="Highlight important updates or offers"
keywords={keywords}
navid='announcement-bar'
testId='announcement-bar'
title="Announcement bar"
/>
);
};
export default AnnouncementBar;

View File

@ -0,0 +1,92 @@
import AnnouncementBarPreview from './announcementBar/AnnouncementBarPreview';
import CheckboxGroup from '../../../admin-x-ds/global/form/CheckboxGroup';
import ColorIndicator from '../../../admin-x-ds/global/form/ColorIndicator';
import Form from '../../../admin-x-ds/global/form/Form';
import HtmlField from '../../../admin-x-ds/global/form/HtmlField';
import NiceModal from '@ebay/nice-modal-react';
import React from 'react';
import useRouting from '../../../hooks/useRouting';
import {PreviewModalContent} from '../../../admin-x-ds/global/modal/PreviewModal';
import {useGlobalData} from '../../providers/GlobalDataProvider';
const Sidebar: React.FC = () => {
const {config} = useGlobalData();
return (
<Form>
<HtmlField
config={config}
nodes='MINIMAL_NODES'
placeholder='Highlight breaking news, offers or updates'
title='Announcement'
/>
<ColorIndicator
isExpanded={false}
picker={false}
swatches={[
{
hex: '#08090c',
title: 'Dark'
},
{
hex: '#ffffff',
title: 'Light'
},
{
hex: '#ffdd00',
title: 'Accent'
}
]}
swatchSize='lg'
title='Background color'
onSwatchChange={() => {}}
onTogglePicker={() => {}}
/>
<CheckboxGroup
checkboxes={[
{
label: 'Logged out visitors',
onChange: () => {},
value: ''
},
{
label: 'Free members',
onChange: () => {},
value: ''
},
{
label: 'Paid members',
onChange: () => {},
value: ''
}
]}
title='Visibility'
/>
</Form>
);
};
const AnnouncementBarModal: React.FC = () => {
// const modal = useModal();
const {updateRoute} = useRouting();
const sidebar = <Sidebar />;
return <PreviewModalContent
afterClose={() => {
updateRoute('announcement-bar');
}}
cancelLabel='Close'
deviceSelector={false}
dirty={false}
okLabel='Save'
preview={<AnnouncementBarPreview />}
previewBgColor='greygradient'
sidebar={sidebar}
testId='announcement-bar-modal'
title='Announcement bar'
titleHeadingLevel={5}
onOk={() => {}}
/>;
};
export default NiceModal.create(AnnouncementBarModal);

View File

@ -1,3 +1,4 @@
import AnnouncementBar from './AnnouncementBar';
import DesignSetting from './DesignSetting';
import Navigation from './Navigation';
import React from 'react';
@ -7,7 +8,8 @@ import SettingSection from '../../../admin-x-ds/settings/SettingSection';
const searchKeywords = {
theme: ['themes', 'design', 'appearance', 'style'],
design: ['design', 'branding', 'logo', 'cover', 'colors', 'fonts', 'background'],
navigation: ['navigation', 'menus', 'primary', 'secondary', 'links']
navigation: ['navigation', 'menus', 'primary', 'secondary', 'links'],
announcementBar: ['announcement', 'bar', 'important', 'banner']
};
const SiteSettings: React.FC = () => {
@ -17,6 +19,7 @@ const SiteSettings: React.FC = () => {
{/* <Theme keywords={searchKeywords.theme} /> */}
<DesignSetting keywords={searchKeywords.design} />
<Navigation keywords={searchKeywords.navigation} />
<AnnouncementBar keywords={searchKeywords.announcementBar} />
</SettingSection>
</>
);

View File

@ -0,0 +1,7 @@
import React from 'react';
const AnnouncementBarPreview: React.FC = () => {
return (<>Announcement bar preview</>);
};
export default AnnouncementBarPreview;