src/components/heading.tsx
import React from 'react'
import { clsx } from 'clsx'
import type { HTMLAttributes } from 'react'
import { generateIdFromChildren } from '@/tools/generate-id-from-children'
type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
children?: React.ReactNode
className?: string
id?: string
as?: HeadingLevel
styleAs?: HeadingLevel
fontSize?: string
fontWeight?: string
letterSpacing?: string
lineHeight?: string
textWrap?: string
fontFamily?: string
color?: string | undefined
margin?: string
textAlign?: string
disableId?: boolean
}
export function Heading({
children = '',
className,
id,
as: Element = 'h1',
styleAs,
fontSize = '',
fontWeight = '',
letterSpacing = '',
lineHeight = '',
textWrap = '',
fontFamily = '',
color = '',
margin = '',
textAlign = '',
disableId = false,
...props
}: HeadingProps) {
const getHeadingDefaults = (level: HeadingLevel) => {
switch (level) {
case 'h1':
return {
fontSize: 'text-5xl/[1.2] sm:text-5xl/[1.2] md:text-7xl/[1.2]',
fontWeight: 'font-medium',
letterSpacing: 'tracking-tight',
lineHeight: 'leading-[1.2]',
textWrap: 'text-balance',
fontFamily: 'font-heading',
color: 'text-accent',
margin: 'mb-8',
}
case 'h2':
return {
fontSize: 'text-4xl sm:text-5xl md:text-6xl',
fontWeight: 'font-bold',
letterSpacing: 'tracking-tight',
lineHeight: 'leading-tight sm:leading-tight md:leading-tight',
textWrap: '',
fontFamily: 'font-heading',
color: 'text-contrast',
margin: 'mb-8',
}
case 'h3':
return {
fontSize: 'text-3xl sm:text-4xl',
fontWeight: 'font-semibold',
letterSpacing: 'tracking-tight',
lineHeight: 'leading-normal',
textWrap: '',
fontFamily: 'font-heading',
color: 'text-contrast',
margin: 'mb-8',
}
case 'h4':
return {
fontSize: 'text-xl md:text-xl',
fontWeight: 'font-bold',
letterSpacing: 'tracking-normal',
lineHeight: 'leading-tight',
textWrap: '',
fontFamily: 'font-heading',
color: 'text-accent',
margin: 'mb-8',
}
case 'h5':
return {
fontSize: 'text-xl sm:text-2xl',
fontWeight: 'font-semibold',
letterSpacing: 'tracking-tight',
lineHeight: 'leading-tight',
textWrap: '',
fontFamily: 'font-heading',
color: 'text-contrast',
margin: 'mb-8',
}
case 'h6':
return {
fontSize: 'text-lg sm:text-xl',
fontWeight: 'font-semibold',
letterSpacing: 'tracking-tight',
lineHeight: 'leading-tight',
textWrap: '',
fontFamily: 'font-heading',
color: 'text-contrast',
margin: 'mb-8',
}
default:
return {
fontSize: 'text-4xl sm:text-5xl md:text-6xl',
fontWeight: 'font-bold',
letterSpacing: 'tracking-tight',
lineHeight: 'leading-tight sm:leading-tight md:leading-tight',
textWrap: '',
fontFamily: 'font-heading',
color: 'text-contrast',
margin: 'mb-8',
}
}
}
const styleLevel = styleAs || Element
const defaults = getHeadingDefaults(styleLevel)
const finalFontSize = fontSize || defaults.fontSize
const finalFontWeight = fontWeight || defaults.fontWeight
const finalLetterSpacing = letterSpacing || defaults.letterSpacing
const finalLineHeight = lineHeight || defaults.lineHeight
const finalTextWrap = textWrap || defaults.textWrap
const finalFontFamily = fontFamily || defaults.fontFamily
const finalColor = color || defaults.color
const finalMargin = margin || defaults.margin
const finalTextAlign = textAlign
const headingId = disableId
? undefined
: id || generateIdFromChildren(children)
return (
<Element
id={headingId}
className={clsx(
'content-wrapper',
finalFontSize,
finalFontWeight,
finalLetterSpacing,
finalLineHeight,
finalTextWrap,
finalFontFamily,
finalColor,
finalMargin,
finalTextAlign,
className
)}
{...props}
>
{children}
</Element>
)
}