Автоматическая генерация компонентов в styled

Йо-йо! Представьте вам нужно создать компонент для uikit’а, который может быть с разным тэгом. Допустим это будет компонент, который может возвращать h2, h3, p или span тэг, при этом у них могут быть разные свойства, ну и плюс вы не хотите чтобы были разные варнинги)) Писать я буду на typescript

Описание типа

type TTypography = {
  tag?: 'p' | 'h2' | 'h3' | 'span' | 'h1' |
  children?: React.ReactNode
  fontSize?: string,
  lineHeight?: string,
  primary?: boolean,
  secondary?: boolean,
  white?: boolean,
  link?: boolean,
  bold?: boolean
} & React.HTMLAttributes<HTMLElement>

Кроме свойства tag нам обязательно нужно только children, собственно это будет наш текст.

Импорт

Для автоматической генерации нам нужно импортировать:

import React from 'react'
import styled from 'styled-components'

Генерация

И так самое основное. Styled может работать как функция и этим мы воспользуемся (но медленно по шагам):

export const Typography = styled((data: TTypography) => ..... )` /* стили для элемента*/`

Из этой функции мы должны вернуть элемент реакта.

export const Typography = styled(({tag, children, fontSize, lineHeight, primary, secondary, white, link, bold,  ...other}: TTypography) => 
React.createElement(tag = 'p', other, children) )``

Давайте разберём, что здесь происходит. Мы деструктуризировали объект со свойствами, которые к нам придут и которые мы явно объявили в нашем типе. Свойства, которые унаследованы от React.HTMLAttributes<HTMLElement>, мы передали в React.createElement как пропсы, которые передадутся нашему компоненту. Так мы сможем передавать, например, синтетические события типа onClick

Стилизация

Далее у нас осталось только добавить стилей в зависимости от наших пропсов.

export const Typography = styled(({tag, children, fontSize, lineHeight, primary, secondary, white, link, bold,  ...other}: TTypography) => React.createElement(tag = 'p', other, children) )`
  font-family: 'ALS_sans';
  font-weight: ${({ bold }) => bold ? 'bold' : 'normal'};
  margin-top: 0;
  margin-bottom: 0;

  ${props => props.primary && css`
    color: ${({ theme }) => theme.colors.black};
  `}

  ${props => props.secondary && css`
    color: ${({ theme }) => theme.colors.grayBlue};
  `}

  ${props => props.white && css`
    color: ${({ theme }) => theme.colors.white};
  `}

  ${props => props.link && css`
    color: ${({ theme }) => theme.colors.greenDark};
    font-weight: bold;
    cursor: pointer;
  `}

  ${props => props.tag === 'h1' && css`
    font-size: 24px;
    line-height: 28px;
    font-weight: bold;
  `}

  ${props => props.tag === 'h2' && css`
    font-size: 20px;
    line-height: 28px;
    font-weight: bold;
  `}

  ${props => props.tag === 'h3' && css`
    font-size: 16px;
    line-height: 20px;
    font-weight: bold;
  `}
  
  ${props => props.fontSize && css`
    font-size: ${props.fontSize};
  `}

  ${props => props.lineHeight && css`
    line-height: ${props.lineHeight};
  `}
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
`'