refactor: extract configurable home layout (#561)

This commit is contained in:
shenlan 2025-10-17 18:08:12 +08:00 committed by GitHub
parent de5a81c606
commit 04132e93b6
5 changed files with 152 additions and 36 deletions

View File

@ -0,0 +1,28 @@
import type { CommonHomeLayoutConfig } from '@templates/layouts/commonHome'
export const defaultHomeLayoutConfig: CommonHomeLayoutConfig = {
rootClassName: 'bg-slate-950',
hero: {
sectionClassName: 'relative isolate overflow-hidden pb-24 pt-24 text-slate-100',
overlays: [
'absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(56,189,248,0.18),_transparent_60%)]',
'absolute inset-0 bg-[linear-gradient(130deg,_rgba(12,18,32,0.92),_rgba(8,47,73,0.45))]',
],
containerClassName: 'relative px-4',
contentClassName: 'mx-auto max-w-6xl',
slot: {
key: 'ProductMatrix',
},
},
content: {
sectionClassName: 'relative bg-slate-50 pb-16 pt-20',
overlays: ['absolute inset-x-0 top-0 h-24 bg-gradient-to-b from-slate-950 via-slate-900/0'],
containerClassName: 'relative px-4',
contentClassName: 'mx-auto max-w-6xl',
gridClassName: 'grid gap-8 lg:grid-cols-[minmax(0,2.5fr)_minmax(0,1fr)] lg:items-start',
slots: [
{ key: 'ArticleFeed' },
{ key: 'Sidebar' },
],
},
}

View File

@ -1 +1,2 @@
export { default } from '../../../src/templates/default'
export { defaultHomeLayoutConfig } from './config'

View File

@ -2,44 +2,16 @@ import ArticleFeed from '@components/home/ArticleFeed'
import ProductMatrix from '@components/home/ProductMatrix'
import Sidebar from '@components/home/Sidebar'
import type { HomePageTemplateProps, TemplateDefinition } from '../types'
import { defaultHomeLayoutConfig } from '@cms/templates/default/config'
function DefaultHomePageTemplate({ slots }: HomePageTemplateProps) {
const ProductMatrixComponent = slots.ProductMatrix ?? ProductMatrix
const ArticleFeedComponent = slots.ArticleFeed ?? ArticleFeed
const SidebarComponent = slots.Sidebar ?? Sidebar
import { createCommonHomeTemplate } from '../layouts/commonHome'
import type { TemplateDefinition } from '../types'
return (
<main className="bg-slate-950">
<section className="relative isolate overflow-hidden pb-24 pt-24 text-slate-100">
<div
className="absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(56,189,248,0.18),_transparent_60%)]"
aria-hidden
/>
<div
className="absolute inset-0 bg-[linear-gradient(130deg,_rgba(12,18,32,0.92),_rgba(8,47,73,0.45))]"
aria-hidden
/>
<div className="relative px-4">
<div className="mx-auto max-w-6xl">
<ProductMatrixComponent />
</div>
</div>
</section>
<section className="relative bg-slate-50 pb-16 pt-20">
<div className="absolute inset-x-0 top-0 h-24 bg-gradient-to-b from-slate-950 via-slate-900/0" aria-hidden />
<div className="relative px-4">
<div className="mx-auto max-w-6xl">
<div className="grid gap-8 lg:grid-cols-[minmax(0,2.5fr)_minmax(0,1fr)] lg:items-start">
<ArticleFeedComponent />
<SidebarComponent />
</div>
</div>
</div>
</section>
</main>
)
}
const DefaultHomePageTemplate = createCommonHomeTemplate(defaultHomeLayoutConfig, {
ProductMatrix,
ArticleFeed,
Sidebar,
})
const defaultTemplate: TemplateDefinition = {
name: 'default',

View File

@ -0,0 +1,113 @@
import { Fragment, type ComponentType } from 'react'
import clsx from 'clsx'
import type { HomePageSlotKey, HomePageTemplateSlots, TemplateComponent } from '../types'
type SlotProps = Record<string, unknown>
type HeroSlotConfig = {
key: HomePageSlotKey
wrapperClassName?: string
props?: SlotProps
}
type ContentSlotConfig = {
key: HomePageSlotKey
wrapperClassName?: string
props?: SlotProps
}
type SectionBaseConfig = {
sectionClassName?: string
containerClassName?: string
contentClassName?: string
overlays?: string[]
}
type HeroSectionConfig = SectionBaseConfig & {
slot: HeroSlotConfig
}
type ContentSectionConfig = SectionBaseConfig & {
gridClassName?: string
slots: ContentSlotConfig[]
}
export interface CommonHomeLayoutConfig {
rootClassName?: string
hero: HeroSectionConfig
content: ContentSectionConfig
}
type FallbackMap = Partial<Record<HomePageSlotKey, ComponentType<any>>>
function renderOverlays(overlays?: string[]) {
return overlays?.map((className, index) => (
<div key={index} className={className} aria-hidden />
))
}
export function createCommonHomeTemplate(
config: CommonHomeLayoutConfig,
fallbacks: FallbackMap = {},
): TemplateComponent<HomePageTemplateSlots> {
const Template: TemplateComponent<HomePageTemplateSlots> = ({ slots }) => {
const heroSlotConfig = config.hero.slot
const HeroComponent = (slots[heroSlotConfig.key] ?? fallbacks[heroSlotConfig.key]) as
| ComponentType<any>
| undefined
const heroContent = HeroComponent ? <HeroComponent {...(heroSlotConfig.props ?? {})} /> : null
return (
<main className={clsx(config.rootClassName)}>
<section className={clsx(config.hero.sectionClassName)}>
{renderOverlays(config.hero.overlays)}
<div className={clsx(config.hero.containerClassName)}>
<div className={clsx(config.hero.contentClassName)}>
{heroSlotConfig.wrapperClassName ? (
<div className={clsx(heroSlotConfig.wrapperClassName)}>{heroContent}</div>
) : (
heroContent
)}
</div>
</div>
</section>
<section className={clsx(config.content.sectionClassName)}>
{renderOverlays(config.content.overlays)}
<div className={clsx(config.content.containerClassName)}>
<div className={clsx(config.content.contentClassName)}>
<div className={clsx(config.content.gridClassName)}>
{config.content.slots.map((slotConfig) => {
const SlotComponent = (slots[slotConfig.key] ?? fallbacks[slotConfig.key]) as
| ComponentType<any>
| undefined
if (!SlotComponent) {
return null
}
const slotElement = <SlotComponent {...(slotConfig.props ?? {})} />
if (slotConfig.wrapperClassName) {
return (
<div key={slotConfig.key} className={clsx(slotConfig.wrapperClassName)}>
{slotElement}
</div>
)
}
return <Fragment key={slotConfig.key}>{slotElement}</Fragment>
})}
</div>
</div>
</div>
</section>
</main>
)
}
return Template
}
export type { HeroSectionConfig, ContentSectionConfig }

View File

@ -17,6 +17,8 @@ export interface HomePageTemplateSlots extends TemplateSlots {
Sidebar: ComponentType
}
export type HomePageSlotKey = 'ProductMatrix' | 'ArticleFeed' | 'Sidebar'
export type HomePageTemplateProps = TemplateRenderProps<HomePageTemplateSlots>
export interface TemplateDefinition {