Files
react/components/text/text.tsx
2020-05-06 14:25:10 +08:00

130 lines
2.9 KiB
TypeScript

import React, { ReactNode, useMemo } from 'react'
import withDefaults from '../utils/with-defaults'
import { NormalTypes } from '../utils/prop-types'
import TextChild from './child'
interface Props {
h1?: boolean
h2?: boolean
h3?: boolean
h4?: boolean
h5?: boolean
h6?: boolean
p?: boolean
b?: boolean
small?: boolean
i?: boolean
span?: boolean
del?: boolean
em?: boolean
blockquote?: boolean
className?: string
size?: string | number
type?: NormalTypes
}
const defaultProps = {
h1: false,
h2: false,
h3: false,
h4: false,
h5: false,
h6: false,
p: false,
b: false,
small: false,
i: false,
span: false,
del: false,
em: false,
blockquote: false,
className: '',
type: 'default' as NormalTypes,
}
type ElementMap = { [key in keyof JSX.IntrinsicElements]?: boolean }
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
export type TextProps = Props & typeof defaultProps & NativeAttrs
type TextRenderableElements = Array<keyof JSX.IntrinsicElements>
const getModifierChild = (
tags: TextRenderableElements,
children: ReactNode,
size?: string | number,
) => {
if (!tags.length) return children
const nextTag = tags.slice(1, tags.length)
return (
<TextChild tag={tags[0]} size={size}>
{getModifierChild(nextTag, children, size)}
</TextChild>
)
}
const Text: React.FC<React.PropsWithChildren<TextProps>> = ({
h1,
h2,
h3,
h4,
h5,
h6,
p,
b,
small,
i,
span,
del,
em,
blockquote,
size,
children,
className,
...props
}) => {
const elements: ElementMap = { h1, h2, h3, h4, h5, h6, p, blockquote }
const inlineElements: ElementMap = { span, small, b, em, i, del }
const names = Object.keys(elements).filter(
(name: keyof JSX.IntrinsicElements) => elements[name],
) as TextRenderableElements
const inlineNames = Object.keys(inlineElements).filter(
(name: keyof JSX.IntrinsicElements) => inlineElements[name],
) as TextRenderableElements
/**
* Render element "p" only if no element is found.
* If there is only one modifier, just rendered one modifier element
* e.g.
* <Text /> => <p />
* <Text em /> => <em />
* <Text p em /> => <p><em>children</em></p>
*
*/
const tag = useMemo(() => {
if (names[0]) return names[0]
if (inlineNames[0]) return inlineNames[0]
return 'p' as keyof JSX.IntrinsicElements
}, [names, inlineNames])
const renderableChildElements = inlineNames.filter(
(name: keyof JSX.IntrinsicElements) => name !== tag,
) as TextRenderableElements
const modifers = useMemo(() => {
if (!renderableChildElements.length) return children
return getModifierChild(renderableChildElements, children, size)
}, [renderableChildElements, children, size])
return (
<TextChild className={className} tag={tag} size={size} {...props}>
{modifers}
</TextChild>
)
}
const MemoText = React.memo(Text)
export default withDefaults(MemoText, defaultProps)