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:
parent
e68db848dc
commit
bc3ad1a798
4 changed files with 84 additions and 50 deletions
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue