Автоматическая генерация компонентов в 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;
'