SectionRenderer.tsx93 lines · main
| 1 | import { cn } from 'ui' |
| 2 | |
| 3 | import type { GoSection } from '../schemas' |
| 4 | import CodeBlockSection from './CodeBlockSection' |
| 5 | import FaqSection from './FaqSection' |
| 6 | import FeatureGridSection from './FeatureGridSection' |
| 7 | import FormSection from './FormSection' |
| 8 | import HubSpotMeetingSection from './HubSpotMeetingSection' |
| 9 | import MetricsSection from './MetricsSection' |
| 10 | import QuoteSection from './QuoteSection' |
| 11 | import SingleColumnSection from './SingleColumnSection' |
| 12 | import StepsSection from './StepsSection' |
| 13 | import ThreeColumnSection from './ThreeColumnSection' |
| 14 | import TwoColumnSection from './TwoColumnSection' |
| 15 | |
| 16 | export type CustomSectionRenderers = { |
| 17 | [K in GoSection['type']]?: React.ComponentType<{ |
| 18 | section: Extract<GoSection, { type: K }> |
| 19 | }> |
| 20 | } |
| 21 | |
| 22 | interface SectionRendererProps { |
| 23 | section: GoSection |
| 24 | customRenderers?: CustomSectionRenderers |
| 25 | } |
| 26 | |
| 27 | export default function SectionRenderer({ section, customRenderers }: SectionRendererProps) { |
| 28 | // Check for a custom renderer first |
| 29 | const CustomRenderer = customRenderers?.[section.type] as |
| 30 | | React.ComponentType<{ section: typeof section }> |
| 31 | | undefined |
| 32 | |
| 33 | let content: React.ReactNode = null |
| 34 | |
| 35 | if (CustomRenderer) { |
| 36 | content = <CustomRenderer section={section} /> |
| 37 | } else { |
| 38 | switch (section.type) { |
| 39 | case 'single-column': |
| 40 | content = <SingleColumnSection section={section} /> |
| 41 | break |
| 42 | case 'two-column': |
| 43 | content = <TwoColumnSection section={section} /> |
| 44 | break |
| 45 | case 'three-column': |
| 46 | content = <ThreeColumnSection section={section} /> |
| 47 | break |
| 48 | case 'form': |
| 49 | content = <FormSection section={section} /> |
| 50 | break |
| 51 | case 'feature-grid': |
| 52 | content = <FeatureGridSection section={section} /> |
| 53 | break |
| 54 | case 'metrics': |
| 55 | content = <MetricsSection section={section} /> |
| 56 | break |
| 57 | case 'tweets': |
| 58 | // Tweets requires app-specific dependencies — must be provided via customRenderers |
| 59 | break |
| 60 | case 'faq': |
| 61 | content = <FaqSection section={section} /> |
| 62 | break |
| 63 | case 'code-block': |
| 64 | content = <CodeBlockSection section={section} /> |
| 65 | break |
| 66 | case 'steps': |
| 67 | content = <StepsSection section={section} /> |
| 68 | break |
| 69 | case 'quote': |
| 70 | content = <QuoteSection section={section} /> |
| 71 | break |
| 72 | case 'hubspot-meeting': |
| 73 | content = <HubSpotMeetingSection section={section} /> |
| 74 | break |
| 75 | default: { |
| 76 | const _exhaustive: never = section |
| 77 | break |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | if (!content) return null |
| 83 | |
| 84 | if (section.id || section.className) { |
| 85 | return ( |
| 86 | <div id={section.id} className={cn(section.className)}> |
| 87 | {content} |
| 88 | </div> |
| 89 | ) |
| 90 | } |
| 91 | |
| 92 | return content |
| 93 | } |