Avoiding premature abstraction with Unstyled React Components (buildui.com)

공유할 것이 동작(로딩 UI)뿐이면 그것만 스타일 없는 컴포넌트로 뽑고, 스타일은 호출부에 맡긴다 — 스타일까지 묶은 섣부른 추상화를 피한다1.

import React, { ComponentProps, FC, ReactNode } from 'react'

function Spinner() {
  return (
    <span className="absolute inset-0 flex items-center justify-center">
      `<Spinner />`
    </span>
  )
}

type LabelProps = ComponentProps<'span'>

function Label({ children, ...rest }: LabelProps) {
  return <span {...rest}>{children}</span>
}

type LoadingButtonProps = ComponentProps<'button'> & {
  children: FC | ReactNode
}

/**
 * @description 버튼이 비활성화되었을 때 로딩 스피너를 표시할 수 있는 버튼 컴포넌트입니다. 이 컴포넌트는 children 속성으로 함수나 React 노드를 받을 수 있으며, 버튼이 비활성화되면 스피너가 표시되고, 텍스트는 보이지 않게 됩니다.
 */
function LoadingButton({
  disabled,
  className,
  children,
  ...rest
}: LoadingButtonProps) {
  return (
    <button {...rest} className={`${className} relative`} disabled={disabled}>
      {typeof children === 'function' ? (
        children({})
      ) : (
        <>
          {disabled && <Spinner />}
          <Label className={disabled ? 'invisible' : ''}>{children}</Label>
        </>
      )}
    </button>
  )
}

LoadingButton.Spinner = Spinner
LoadingButton.Label = Label

Footnotes

  1. Avoiding premature abstraction with Unstyled React Components: “This is really what we’re trying to share, and it’s a perfect use case for extracting an Unstyled Component.” … “They’re unstyled, so the caller is responsible for all the styling code.” ↩

`#311`