0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Refactored theme demo functionality (#18525)

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

Refactored the theme demo functionality to remove the hard-coded
reference to the source theme and enable the functionality for any
theme. This also ensures the demo images are correctly referenced in
pro.
This commit is contained in:
Michael Barrett 2023-10-09 09:05:43 +01:00 committed by GitHub
parent e68db848dc
commit bc3ad1a798
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 84 additions and 50 deletions

View file

@ -3,6 +3,11 @@ import useSearchService, {SearchService} from '../../utils/search';
import {DefaultHeaderTypes} from '../../utils/unsplash/UnsplashTypes';
import {ZapierTemplate} from '../settings/advanced/integrations/ZapierModal';
export type ThemeVariant = {
image: string;
category: string;
};
export type OfficialTheme = {
name: string;
category: string;
@ -10,6 +15,7 @@ export type OfficialTheme = {
ref: string;
image: string;
url?: string;
variants?: ThemeVariant[]
};
export type FetchKoenigLexical = () => Promise<any>

View file

@ -2,14 +2,26 @@ import Heading from '../../../../admin-x-ds/global/Heading';
import MarketplaceBgImage from '../../../../assets/images/footer-marketplace-bg.png';
import ModalPage from '../../../../admin-x-ds/global/modal/ModalPage';
import React, {useEffect, useState} from 'react';
import {OfficialTheme, useOfficialThemes} from '../../../providers/ServiceProvider';
import clsx from 'clsx';
import {OfficialTheme, ThemeVariant, useOfficialThemes} from '../../../providers/ServiceProvider';
import {getGhostPaths, resolveAsset} from '../../../../utils/helpers';
const sourceDemos = [
{image: 'Source.png', category: 'News'},
{image: 'Source-Magazine.png', category: 'Magazine'},
{image: 'Source-Newsletter.png', category: 'Newsletter'}
];
const VARIANT_LOOP_INTERVAL = 3000;
const hasVariants = (theme: OfficialTheme) => theme.variants && theme.variants.length > 0;
const getAllVariants = (theme: OfficialTheme) : ThemeVariant[] => {
const variants = [{
image: theme.image,
category: theme.category
}];
if (theme.variants && theme.variants.length > 0) {
variants.push(...theme.variants);
}
return variants;
};
const OfficialThemes: React.FC<{
onSelectTheme?: (theme: OfficialTheme) => void;
@ -18,68 +30,76 @@ const OfficialThemes: React.FC<{
}) => {
const {adminRoot} = getGhostPaths();
const officialThemes = useOfficialThemes();
const [currentSourceDemoIndex, setCurrentSourceDemoIndex] = useState(0);
const [isHovered, setIsHovered] = useState(false);
const [variantLoopTheme, setVariantLoopTheme] = useState<OfficialTheme | null>(null);
const [visibleVariantIdx, setVisibleVariantIdx] = useState(0);
const setupVariantLoop = (theme: OfficialTheme | null) => {
setVariantLoopTheme(theme);
setVisibleVariantIdx(
(theme !== null && hasVariants(theme) && getAllVariants(theme).length > 1) ? 1 : 0
);
};
useEffect(() => {
const switchSourceDemos = () => {
if (isHovered) {
setCurrentSourceDemoIndex(prevIndex => (prevIndex + 1) % sourceDemos.length);
}
};
switchSourceDemos();
const interval = setInterval(() => {
switchSourceDemos();
}, 3000);
return () => {
clearInterval(interval);
};
}, [isHovered]);
useEffect(() => {
if (!isHovered) {
setCurrentSourceDemoIndex(0);
if (variantLoopTheme === null) {
return;
}
}, [isHovered]);
const loopInterval = setInterval(() => {
setVisibleVariantIdx((visibleVariantIdx + 1) % (getAllVariants(variantLoopTheme).length || 0));
}, VARIANT_LOOP_INTERVAL);
return () => clearInterval(loopInterval);
}, [variantLoopTheme, visibleVariantIdx]);
return (
<ModalPage heading='Themes'>
<div className='mt-[6vmin] grid grid-cols-1 gap-[6vmin] sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4'>
{officialThemes.map((theme) => {
const showVariants = hasVariants(theme);
const variants = getAllVariants(theme);
return (
<button key={theme.name} className='flex cursor-pointer flex-col gap-3 text-left' type='button' onClick={() => {
onSelectTheme?.(theme);
}}>
{/* <img alt={theme.name} src={`${assetRoot}/${theme.image}`}/> */}
<div className='relative w-full bg-grey-100 shadow-md transition-all duration-500 hover:scale-[1.05]' onMouseEnter={() => theme.name === 'Source' && setIsHovered(true)} onMouseLeave={() => theme.name === 'Source' && setIsHovered(false)}>
{theme.name !== 'Source' ?
<div className='relative w-full bg-grey-100 shadow-md transition-all duration-500 hover:scale-[1.05]' onMouseEnter={() => setupVariantLoop(theme)} onMouseLeave={() => setupVariantLoop(null)}>
{showVariants ?
<>
{variants.map((variant, idx) => (
<img
key={`theme-variant-${variant.category.toLowerCase()}`}
alt={`${theme.name} Theme - ${variant.category}`}
className={clsx('h-full w-full object-contain transition-opacity duration-500', {
'opacity-100': idx === visibleVariantIdx,
'opacity-0': idx !== visibleVariantIdx,
relative: idx === visibleVariantIdx,
absolute: idx !== visibleVariantIdx,
'left-0': idx !== visibleVariantIdx,
'top-0': idx !== visibleVariantIdx
})}
src={resolveAsset(variant.image, adminRoot)}
/>
))}
</> :
<img
alt={`${theme.name} Theme`}
className='h-full w-full object-contain'
src={resolveAsset(theme.image, adminRoot)}
/> :
<>
{sourceDemos.map((demo, index) => (
<img
key={`source-theme-${demo.category}`}
alt={`${theme.name} Theme - ${demo.category}`}
className={`${index === 0 ? 'relative' : 'absolute'} left-0 top-0 h-full w-full object-contain transition-opacity duration-500 ${index === currentSourceDemoIndex ? 'opacity-100' : 'opacity-0'}`}
src={resolveAsset(`assets/img/themes/${demo.image}`, adminRoot)}
/>
))}
</>
/>
}
</div>
<div className='relative mt-3'>
<Heading level={4}>{theme.name}</Heading>
{theme.name !== 'Source' ?
<span className='text-sm text-grey-700'>{theme.category}</span> :
sourceDemos.map((demo, index) => (
<span className={`${index === 0 ? 'absolute' : 'absolute'} left-0 translate-y-px text-sm text-grey-700 ${index === currentSourceDemoIndex ? 'opacity-100' : 'opacity-0'}`}>{demo.category}</span>
))
{showVariants ?
variants.map((variant, idx) => (
<span className={clsx('absolute left-0 translate-y-px text-sm text-grey-700', {
'opacity-100': idx === visibleVariantIdx,
'opacity-0': idx !== visibleVariantIdx
})}>{variant.category}</span>
)) :
<span className='text-sm text-grey-700'>{theme.category}</span>
}
</div>
</button>

View file

@ -17,7 +17,11 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
category: 'News',
previewUrl: 'https://source.ghost.io/',
ref: 'default',
image: 'assets/img/themes/Source.png'
image: 'assets/img/themes/Source.png',
variants: [
{image: 'assets/img/themes/Source-Magazine.png', category: 'Magazine'},
{image: 'assets/img/themes/Source-Newsletter.png', category: 'Newsletter'}
]
}, {
name: 'Casper',
category: 'Blog',

View file

@ -16,7 +16,11 @@ const officialThemes = [{
category: 'News',
previewUrl: 'https://source.ghost.io/',
ref: 'default',
image: 'assets/img/themes/Source.png'
image: 'assets/img/themes/Source.png',
variants: [
{image: 'assets/img/themes/Source-Magazine.png', category: 'Magazine'},
{image: 'assets/img/themes/Source-Newsletter.png', category: 'Newsletter'}
]
}, {
name: 'Casper',
category: 'Blog',