Updated theme preview in AdminX

refs. https://github.com/TryGhost/Team/issues/3432
This commit is contained in:
Peter Zimon 2023-06-14 10:56:58 +02:00
parent e680ddf14a
commit 010be7fd3d
5 changed files with 155 additions and 108 deletions

View File

@ -28,7 +28,7 @@ const PageHeader: React.FC<PageHeaderProps> = ({
children
}) => {
const containerClasses = clsx(
'h-[74px] p-5 px-7',
'z-50 h-[74px] p-5 px-7',
!children && 'flex items-center justify-between gap-3',
sticky && 'sticky top-0',
containerClassName

View File

@ -29,6 +29,7 @@ export interface ModalProps {
backDrop?: boolean;
backDropClick?: boolean;
stickyFooter?: boolean;
scrolling?: boolean;
}
const Modal: React.FC<ModalProps> = ({
@ -46,7 +47,8 @@ const Modal: React.FC<ModalProps> = ({
children,
backDrop = true,
backDropClick = true,
stickyFooter = false
stickyFooter = false,
scrolling = true
}) => {
const modal = useModal();
@ -75,10 +77,10 @@ const Modal: React.FC<ModalProps> = ({
}
let modalClasses = clsx(
'relative z-50 mx-auto flex max-h-[100%] w-full flex-col justify-between overflow-y-auto overflow-x-hidden rounded bg-white shadow-xl'
// !stickyFooter && ' overflow-hidden'
'relative z-50 mx-auto flex max-h-[100%] w-full flex-col justify-between overflow-x-hidden rounded bg-white shadow-xl',
scrolling ? 'overflow-y-auto' : 'overflow-y-hidden'
);
let backdropClasses = clsx('fixed inset-0 z-40 h-[100vh] w-[100vw] overflow-y-scroll ');
let backdropClasses = clsx('fixed inset-0 z-40 h-[100vh] w-[100vw]');
let padding = '';

View File

@ -1,15 +1,13 @@
import AdvancedThemeSettings from './theme/AdvancedThemeSettings';
import Breadcrumbs from '../../../admin-x-ds/global/Breadcrumbs';
import Button from '../../../admin-x-ds/global/Button';
import ButtonGroup from '../../../admin-x-ds/global/ButtonGroup';
import FileUpload from '../../../admin-x-ds/global/form/FileUpload';
import Modal from '../../../admin-x-ds/global/modal/Modal';
import NewThemePreview from './theme/ThemePreview';
import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react';
import OfficialThemes from './theme/OfficialThemes';
import PageHeader from '../../../admin-x-ds/global/layout/PageHeader';
import React, {useState} from 'react';
import TabView from '../../../admin-x-ds/global/TabView';
import ThemePreview from './theme/ThemePreview';
import {OfficialTheme} from '../../../models/themes';
import {Theme} from '../../../types/api';
import {showToast} from '../../../admin-x-ds/global/Toast';
@ -23,10 +21,11 @@ interface ThemeToolbarProps {
modal: NiceModalHandler<Record<string, unknown>>;
themes: Theme[];
setThemes: (themes: Theme[]) => void;
setPreviewMode: (mode: string) => void;
previewMode: string;
}
interface ThemeModalContentProps {
selectedTheme: OfficialTheme|null;
onSelectTheme: (theme: OfficialTheme|null) => void;
currentTab: string;
themes: Theme[];
@ -34,61 +33,13 @@ interface ThemeModalContentProps {
}
const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
selectedTheme,
setCurrentTab,
setSelectedTheme,
modal,
themes,
setThemes
}) => {
const api = useApi();
let left, right;
if (selectedTheme) {
const installedTheme = themes.find(theme => theme.name.toLowerCase() === selectedTheme.name.toLowerCase());
left =
<div className='flex items-center gap-2'>
<Breadcrumbs
items={[
{label: 'Official themes', onClick: () => {
setCurrentTab('official');
setSelectedTheme(null);
}},
{label: selectedTheme.name}
]}
/>
</div>;
right =
<div className='flex justify-end gap-8'>
<ButtonGroup
buttons={[
{icon: 'laptop', link: true, size: 'sm'},
{icon: 'mobile', iconColorClass: 'text-grey-500', link: true, size: 'sm'}
]}
/>
<Button
color='green'
disabled={Boolean(installedTheme)}
label={installedTheme?.active ? 'Activated' : (installedTheme ? 'Installed' : `Install ${selectedTheme?.name}`)}
onClick={async () => {
const data = await api.themes.install(selectedTheme.ref);
const newlyInstalledTheme = data.themes[0];
setThemes([
...themes.map(theme => ({...theme, active: false})),
newlyInstalledTheme
]);
showToast({
message: `Theme installed - ${newlyInstalledTheme.name}`
});
setCurrentTab('installed');
}}
/>
</div>;
} else {
left =
const left =
<TabView
border={false}
tabs={[
@ -99,7 +50,7 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
setCurrentTab(id);
}} />;
right =
const right =
<div className='flex items-center gap-3'>
<FileUpload id='theme-uplaod' onUpload={async (file: File) => {
const data = await api.themes.upload({file});
@ -117,29 +68,21 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
modal.remove();
}} />
</div>;
}
return <PageHeader containerClassName={selectedTheme! && 'bg-grey-50'} left={left} right={right} />;
return <PageHeader containerClassName='bg-white' left={left} right={right} />;
};
const ThemeModalContent: React.FC<ThemeModalContentProps> = ({
currentTab,
selectedTheme,
onSelectTheme,
themes,
setThemes
}) => {
switch (currentTab) {
case 'official':
if (selectedTheme) {
return (
<NewThemePreview selectedTheme={selectedTheme} />
);
} else {
return (
<OfficialThemes onSelectTheme={onSelectTheme} />
);
}
case 'installed':
return (
<AdvancedThemeSettings
@ -154,35 +97,69 @@ const ThemeModalContent: React.FC<ThemeModalContentProps> = ({
const ChangeThemeModal = NiceModal.create(() => {
const [currentTab, setCurrentTab] = useState('official');
const [selectedTheme, setSelectedTheme] = useState<OfficialTheme|null>(null);
const [previewMode, setPreviewMode] = useState('desktop');
const modal = useModal();
const {themes, setThemes} = useThemes();
const api = useApi();
const onSelectTheme = (theme: OfficialTheme|null) => {
setSelectedTheme(theme);
};
let installedTheme;
let onInstall;
if (selectedTheme) {
installedTheme = themes.find(theme => theme.name.toLowerCase() === selectedTheme!.name.toLowerCase());
onInstall = async () => {
const data = await api.themes.install(selectedTheme.ref);
const newlyInstalledTheme = data.themes[0];
setThemes([
...themes.map(theme => ({...theme, active: false})),
newlyInstalledTheme
]);
showToast({
message: `Theme installed - ${newlyInstalledTheme.name}`
});
setCurrentTab('installed');
};
}
return (
<Modal
cancelLabel=''
footer={false}
noPadding={true}
scrolling={currentTab === 'official' ? false : true}
size='full'
title=''
>
<div className='flex h-full justify-between'>
<div className='grow'>
{selectedTheme &&
<ThemePreview
installButtonLabel={
installedTheme?.active ? 'Activated' : (installedTheme ? 'Installed' : `Install ${selectedTheme?.name}`)
}
selectedTheme={selectedTheme}
themeInstalled={Boolean(installedTheme)}
onBack={() => {
setSelectedTheme(null);
}}
onInstall={onInstall} />
}
<ThemeToolbar
modal={modal}
previewMode={previewMode}
selectedTheme={selectedTheme}
setCurrentTab={setCurrentTab}
setPreviewMode={setPreviewMode}
setSelectedTheme={setSelectedTheme}
setThemes={setThemes}
themes={themes}
/>
<ThemeModalContent
currentTab={currentTab}
selectedTheme={selectedTheme}
setThemes={setThemes}
themes={themes}
onSelectTheme={onSelectTheme}

View File

@ -130,7 +130,7 @@ const OfficialThemes: React.FC<{
}];
return (
<div className='p-[8vmin] pt-5'>
<div className='h-[calc(100vh-74px-40px)] overflow-y-auto overflow-x-hidden p-[8vmin] pt-5'>
<Heading>Themes</Heading>
<div className='mt-[6vmin] grid grid-cols-1 gap-[6vmin] sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4'>
{officialThemes.map((theme) => {
@ -139,10 +139,10 @@ const OfficialThemes: React.FC<{
onSelectTheme?.(theme);
}}>
{/* <img alt={theme.name} src={`${assetRoot}/${theme.image}`}/> */}
<div className='h-[420px] w-full bg-grey-100 shadow-md transition-all duration-500 hover:scale-[1.05]'>
<div className='w-full bg-grey-100 shadow-md transition-all duration-500 hover:scale-[1.05]'>
<img
alt="Headline Theme"
className='w-full object-contain'
className='h-full w-full object-contain'
src={`${assetRoot}/${theme.image}`}
/>
</div>

View File

@ -1,20 +1,88 @@
import React from 'react';
import Breadcrumbs from '../../../../admin-x-ds/global/Breadcrumbs';
import Button from '../../../../admin-x-ds/global/Button';
import ButtonGroup from '../../../../admin-x-ds/global/ButtonGroup';
import MobileChrome from '../../../../admin-x-ds/global/chrome/MobileChrome';
import PageHeader from '../../../../admin-x-ds/global/layout/PageHeader';
import React, {useState} from 'react';
import {OfficialTheme} from '../../../../models/themes';
const NewThemePreview: React.FC<{
const ThemePreview: React.FC<{
selectedTheme?: OfficialTheme;
onBack: () => void;
themeInstalled?: boolean;
installButtonLabel?: string;
onInstall?: () => void;
}> = ({
selectedTheme
selectedTheme,
onBack,
themeInstalled,
installButtonLabel,
onInstall
}) => {
const [previewMode, setPreviewMode] = useState('desktop');
if (!selectedTheme) {
return null;
}
const left =
<div className='flex items-center gap-2'>
<Breadcrumbs
items={[
{label: 'Official themes', onClick: onBack},
{label: selectedTheme.name}
]}
/>
</div>;
const right =
<div className='flex justify-end gap-8'>
<ButtonGroup
buttons={[
{
icon: 'laptop',
iconColorClass: (previewMode === 'desktop' ? 'text-black' : 'text-grey-500'),
link: true,
size: 'sm',
onClick: () => {
setPreviewMode('desktop');
}
},
{
icon: 'mobile',
iconColorClass: (previewMode === 'mobile' ? 'text-black' : 'text-grey-500'),
link: true,
size: 'sm',
onClick: () => {
setPreviewMode('mobile');
}
}
]}
/>
<Button
color='green'
disabled={themeInstalled}
label={installButtonLabel}
onClick={onInstall}
/>
</div>;
return (
<div className='flex h-full grow flex-col'>
<div className='absolute inset-0 z-[100]'>
<PageHeader containerClassName='bg-grey-50 z-[100]' left={left} right={right} sticky={false} />
<div className='flex h-[calc(100%-74px)] grow flex-col items-center justify-center bg-grey-50'>
{previewMode === 'desktop' ?
<iframe className='h-full w-full'
src={selectedTheme?.previewUrl} title='Theme preview' />
:
<MobileChrome>
<iframe className='h-full w-full'
src={selectedTheme?.previewUrl} title='Theme preview' />
</MobileChrome>
}
</div>
</div>
);
};
export default NewThemePreview;
export default ThemePreview;