npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pulley-hub/design-system

v1.0.0

Published

Pulley React design system and design tokens

Readme

@pulley-hub/design-system

Pulley 서비스에서 사용하는 React 컴포넌트, 아이콘, 디자인 토큰 패키지입니다.

  • React 18 이상
  • Tailwind CSS 4 디자인 토큰
  • Radix UI 기반 접근성 컴포넌트
  • ESM 및 TypeScript 타입 제공

AI 에이전트용 사용 계약

AI는 이 패키지를 사용할 때 아래 규칙을 우선 적용합니다.

  1. 이미 제공되는 컴포넌트를 HTML과 Tailwind로 다시 만들지 않습니다.
  2. 모든 컴포넌트와 아이콘은 @pulley-hub/design-system 루트에서 named import 합니다.
  3. 앱의 전역 진입점에서 스타일을 정확히 한 번 import 합니다.
  4. 색상, 간격, 타이포그래피는 raw 값보다 semantic-* 디자인 토큰을 사용합니다.
  5. 폼 요소에는 연결된 label 또는 aria-label을 제공합니다.
  6. 아이콘만 있는 버튼에는 반드시 aria-label을 제공합니다.
  7. Dialog, AlertDialog, DropdownMenu, Popover, Tabs는 아래의 compound 구조를 유지합니다.
  8. InputSearchInputonChange는 DOM event가 아니라 변경된 문자열을 받습니다.
  9. 로딩 중인 액션에는 직접 spinner를 조합하기보다 Button loading을 우선 사용합니다.
  10. 앱 루트에는 Sonner를 한 번만 렌더링하고, 알림은 useToast로 표시합니다.

AI에게 제공할 최소 프롬프트:

@pulley-hub/design-system을 우선 사용해 화면을 구현해.
README의 AI 에이전트용 사용 계약과 컴포넌트 API를 지켜.
기존 컴포넌트를 Tailwind로 다시 만들지 말고 semantic 디자인 토큰만 사용해.

설치

pnpm add @pulley-hub/design-system

React와 React DOM은 peer dependency입니다.

pnpm add react react-dom

스타일 설정

Tailwind CSS 4 앱

앱의 전역 CSS에 다음 순서로 추가합니다.

@import 'tailwindcss';
@import '@pulley-hub/design-system/theme.css';
@import '@pulley-hub/design-system/styles.css';
  • theme.css: 앱 코드에서도 bg-semantic-fill-primary 같은 Pulley 토큰 유틸리티를 생성합니다.
  • styles.css: 컴포넌트에 필요한 CSS, 폰트, 토큰을 미리 컴파일한 파일입니다.
  • 전역 CSS는 Next.js의 root layout.tsx 등에서 한 번만 불러옵니다.

Tailwind를 사용하지 않는 React 앱

앱의 최상위 진입점에서 컴파일된 스타일만 import합니다.

import '@pulley-hub/design-system/styles.css'

빠른 시작

'use client'

import { Button, Field, FieldLabel, FieldTitle, Input, Sonner, Text } from '@pulley-hub/design-system'
import { useState } from 'react'

export function ProfileForm() {
  const [name, setName] = useState('')

  return (
    <>
      <div className='flex flex-col gap-16'>
        <Text as='h1' variant='title-2-bold'>
          프로필
        </Text>
        <Field>
          <FieldLabel htmlFor='name'>
            <FieldTitle>이름</FieldTitle>
          </FieldLabel>
          <Input id='name' value={name} onChange={setName} />
        </Field>
        <Button type='submit'>저장</Button>
      </div>
      <Sonner />
    </>
  )
}

선택 가이드

  • 일반 액션: Button
  • 링크처럼 약한 액션: TextButton
  • 상태 또는 짧은 분류: Badge
  • 짧은 텍스트 입력: Input
  • 검색 입력: SearchInput
  • 비밀번호 입력: PasswordInput
  • 긴 텍스트 입력: Textarea
  • 고정 목록 단일 선택: Select
  • 검색 가능한 목록 또는 서버 페이지네이션 선택: Combobox
  • 참/거짓 설정: ToggleSwitch
  • 복수 선택: Checkbox
  • 단일 선택 그룹: RadioGroup
  • 짧은 확인 모달: AlertDialog
  • 폼이나 복합 콘텐츠 모달: Dialog
  • 일시적인 액션 목록: DropdownMenu
  • 보조 콘텐츠: Popover
  • 짧은 설명: TailTooltip
  • 상호작용 가능한 상세 도움말: DialogTooltip
  • 비동기 알림: useToast + Sonner

컴포넌트 API

Typography

Text

as로 HTML 요소를 바꾸는 polymorphic 타이포그래피 컴포넌트입니다.

  • as: span, p, h1
  • variant: 아래 타이포 variant
  • className: semantic 색상 등 추가 스타일

Variant:

  • Display: display-1-medium, display-1-bold, display-2-medium, display-2-bold, display-3-medium, display-3-bold
  • Title: title-1-medium, title-1-bold, title-2-medium, title-2-bold, title-3-medium, title-3-bold
  • Heading: heading-1-medium, heading-1-semibold, heading-2-medium, heading-2-semibold, heading-3-medium, heading-3-semibold
  • Body: body-1-regular, body-1-medium, body-2-regular, body-2-medium, body-2-semibold, body-3-regular, body-3-medium, body-3-semibold
  • Label: label-1-regular, label-1-semibold, label-2-medium, label-2-semibold
  • Caption: caption-1-medium, caption-1-semibold, caption-2-medium, caption-2-semibold
<Text as='h2' variant='heading-1-semibold'>
  강의 정보
</Text>

Actions

Button

  • variant: primary | secondary | primary-weak | secondary-weak | secondary-weak-line | danger | danger-weak | text-secondary-line | text-weak-line | text-weak
  • size: xxlarge | xlarge | large | medium | small | xsmall
  • loading: 로딩 표시 및 클릭 비활성화
  • leftIcon, rightIcon: 텍스트 양옆 아이콘
  • onlyIcon: 정사각형 아이콘 버튼
  • radiusMax: pill 형태
  • asChild: 자식 요소에 버튼 스타일 적용
<Button
  variant='primary'
  leftIcon={<IconPlusLine aria-hidden />}
  loading={isSubmitting}
  type='submit'
>
  강의 추가
</Button>

<Button variant='secondary-weak' onlyIcon aria-label='닫기'>
  <IconClosedLine aria-hidden />
</Button>

xsmall은 텍스트 없는 onlyIcon 버튼에만 사용합니다.

TextButton

  • variant: text-secondary-line | text-weak-line | text-weak
  • size: xxlarge | xlarge | large | medium | small
  • loading, leftIcon, rightIcon, asChild 지원

Filter

필터 trigger 버튼입니다. Dropdown 또는 Popover trigger와 조합합니다.

  • label: 표시할 값
  • size: medium | small
  • hasValue: 선택된 필터가 있는지 여부
  • thumbnail: 선택 값 앞의 썸네일
  • hideIcon: 왼쪽 filter 아이콘 숨김

Form controls

Input

  • value: controlled value
  • onChange(text: string): 변경 문자열 콜백
  • size: small | medium | large
  • inverse: 흰 배경 사용
  • isError: 오류 스타일과 aria-invalid 적용
  • 나머지는 기본 input 속성 지원

value가 있으면 clear 버튼이 자동으로 표시됩니다.

SearchInput

Input과 같은 API를 사용하며 search 아이콘과 clear 버튼을 포함합니다.

<SearchInput
  aria-label='강의 검색'
  placeholder='강의명을 입력하세요'
  value={query}
  onChange={setQuery}
/>

PasswordInput

  • size: small | medium | large
  • inverse, isError 지원
  • 비밀번호 표시/숨김 버튼을 내장합니다.

Textarea

  • size: small | medium | large
  • inverse: 흰 배경 사용
  • 기본 textarea 속성 지원

InputGroup

커스텀 입력 UI가 필요할 때만 사용합니다.

  • InputGroup: 외곽 컨테이너, size, inverse 지원
  • InputGroupInput: 내부 input
  • InputGroupAddon: align='inline-start' | 'inline-end'

일반 텍스트 입력에는 Input 또는 SearchInput을 우선 사용합니다.

Select

검색이 필요 없는 단일 선택입니다.

const options = [
  { value: 'all', label: '전체' },
  { value: 'active', label: '진행 중' },
] as const

<Select
  aria-label='강의 상태'
  options={options}
  value={status}
  onValueChange={setStatus}
  placeholder='상태 선택'
/>
  • options: { value, label, textValue?, disabled? }[]
  • value, onValueChange
  • size: small | medium | large
  • menuSize: small | medium | large
  • align: start | center | end
  • inverse, disabled, required, name, contentClassName

Combobox

검색, 서버 필터링, 무한 스크롤이 필요한 선택입니다.

  • options: { value: string, label, textValue?, disabled? }[]
  • selectedOption, value, onValueChange(value, label)
  • searchValue, onSearchValueChange
  • shouldFilterOptions: 클라이언트 필터링 여부
  • isLoading, loadingMessage, emptyMessage
  • hasNextPage, onLoadMore, loadMoreThreshold
  • size, menuSize: small | medium | large
  • align, disabled, required, name, contentClassName

서버 검색에서는 shouldFilterOptions={false}를 사용합니다.

Checkbox

  • size: large | medium
  • label: 체크박스 옆 콘텐츠
  • Radix Checkbox의 checked, onCheckedChange, disabled 지원

RadioGroup, RadioGroupItem

<RadioGroup value={role} onValueChange={setRole}>
  <RadioGroupItem value='student' label='학생' />
  <RadioGroupItem value='professor' label='교수자' />
</RadioGroup>
  • RadioGroupItem.size: large | medium
  • RadioGroupItem.label: 라디오 옆 콘텐츠

ToggleSwitch

  • checked, onCheckedChange
  • label
  • labelPosition: left | right
  • disabled

Slider

  • Radix Slider의 value, onValueChange, min, max, step, disabled
  • leftIcon, RightIcon
  • thumbAriaLabel

Field family

폼 label, 설명, 오류, 카운터를 일관된 구조로 조합합니다.

  • Field: 최상위 컨테이너, inverse, disabled
  • FieldLabel, FieldTitle
  • FieldLabelRequired, FieldLabelOptional
  • FieldDescription, FieldError
  • FieldMessage: variant='information' | 'success' | 'danger'
  • FieldGroup, FieldFooter
  • FieldCounter: current, max, error
<Field>
  <FieldLabel htmlFor='title'>
    <FieldTitle>제목</FieldTitle>
    <FieldLabelRequired />
  </FieldLabel>
  <Input id='title' value={title} onChange={setTitle} isError={hasError} />
  <FieldFooter>
    {hasError && <FieldError>제목을 입력하세요.</FieldError>}
    <FieldCounter current={title.length} max={50} error={hasError} />
  </FieldFooter>
</Field>

Feedback and status

Badge

  • size: large | medium | small
  • color: primary | secondary | gradient | accent | positive | negative | warning
  • type: filled | weak
  • shape: square | pill

Spinner

  • size: xxsmall | xsmall | small | medium | large | xlarge | xxlarge
  • type: light | dark

Progress

  • value: 0~100, 범위를 벗어나면 자동 보정
  • label, valueLabel
  • type: primary | secondary | muted
  • shimmer: 진행 중 shimmer 표시

Sonner, useToast

앱 root에 <Sonner />를 한 번 렌더링합니다.

const toast = useToast()

toast.success('저장했습니다.')
toast.danger('저장하지 못했습니다.')
toast.loading('자료를 생성하고 있습니다.')
toast.info('확인이 필요합니다.', {
  action: {
    label: '보기',
    onClick: openDetail,
  },
})

메서드: info, success, warning, danger, loading, close.

Overlay

Dialog family

<Dialog open={isOpen} onOpenChange={setIsOpen}>
  <DialogTrigger asChild>
    <Button>열기</Button>
  </DialogTrigger>
  <DialogContent size='medium' closeOnOverlayClick={false}>
    <DialogHeader>
      <DialogTitle>강의 편집</DialogTitle>
    </DialogHeader>
    <DialogDescription>강의 정보를 수정합니다.</DialogDescription>
    <DialogBody>{/* content */}</DialogBody>
    <DialogFooter>{/* actions */}</DialogFooter>
  </DialogContent>
</Dialog>
  • DialogContent.size: large | medium | small
  • DialogContent.closeOnOverlayClick: 기본 true
  • DialogHeader.hideCloseButton
  • 공개 primitive: DialogTrigger, DialogPortal, DialogOverlay, DialogClose

AlertDialog family

되돌리기 어려운 확인 액션에 사용합니다.

  • AlertDialog, AlertDialogTrigger, AlertDialogContent
  • AlertDialogHeader, AlertDialogTitle, AlertDialogDescription
  • AlertDialogBody, AlertDialogFooter
  • AlertDialogAction, AlertDialogCancel
  • AlertDialogPortal, AlertDialogOverlay

DropdownMenu family

  • DropdownMenu, DropdownMenuTrigger, DropdownMenuContent
  • DropdownMenuLabel, DropdownMenuItem
  • DropdownMenuCheckboxItem, DropdownMenuSeparator
  • content/item size: large | medium | small

Popover family

  • Popover, PopoverTrigger, PopoverContent
  • PopoverAnchor, PopoverClose

Tooltip family

  • 단순 도움말: TailTooltip, TailTooltipTrigger, TailTooltipContent, TailTooltipProvider
  • 클릭 가능한 상세 도움말: DialogTooltip, DialogTooltipTrigger, DialogTooltipContent
  • low-level primitive: TooltipRoot, TooltipTrigger, TooltipContent, TooltipArrow, TooltipPortal, TooltipProvider

Navigation and data display

Tabs

<Tabs defaultValue='overview'>
  <TabsList variant='line'>
    <TabsTrigger value='overview'>개요</TabsTrigger>
    <TabsTrigger value='students'>학생</TabsTrigger>
  </TabsList>
  <TabsContent value='overview'>...</TabsContent>
  <TabsContent value='students'>...</TabsContent>
</Tabs>
  • TabsList.variant: line | item
  • line 크기: lineSize='medium' | 'large'
  • item 크기: itemSize='small' | 'medium'

Breadcrumb family

  • Breadcrumb, BreadcrumbList, BreadcrumbItem
  • BreadcrumbLink, BreadcrumbPage
  • BreadcrumbSeparator, BreadcrumbEllipsis

Pagination family

  • Pagination, PaginationList, PaginationItem
  • PaginationLink, PaginationPrevious, PaginationNext
  • PaginationEllipsis

현재 페이지에는 PaginationLink isActive를 지정하고 접근 가능한 label을 제공합니다.

Table family

  • Table: variant='default' | 'inverse'
  • TableHeader, TableBody, TableFooter, TableRow
  • TableHead: required 지원
  • TableCell, TableCaption
  • TableEmpty: 빈 상태 행

ScrollArea

  • orientation: vertical | horizontal | both | none
  • variant: light | dark
  • ScrollBar를 직접 조합할 수도 있습니다.

Avatar, AvatarGroup

  • Avatar.label: 표시할 이름 또는 이니셜
  • Avatar.type: border | filled
  • Avatar.color: primary | muted
  • AvatarGroup.data: AvatarProps[], 앞 3개와 나머지 개수를 표시

아이콘

모든 아이콘은 React SVG 컴포넌트이며 className, aria-hidden 등 SVG props를 받습니다.

import { IconPlusLine } from '@pulley-hub/design-system'

;<IconPlusLine className='size-20' aria-hidden />

사용 가능한 아이콘:

IconAccountFilled, IconAiFilled, IconAsteriskLine, IconBoldLine, IconBookLine, IconBottomLine, IconCalendarLine, IconCheckBold, IconCheckLine, IconClosedBold, IconClosedLine, IconCompleteFilled, IconCompleteLine, IconComposeFilled, IconCourseFilled, IconCoursesFilled, IconDeleteLine, IconDownLine, IconDownloadLine, IconEditLine, IconEllipsisFilled, IconEmptydataLine, IconExternalLine, IconFileLine, IconFileUseFilled, IconFilterdownFilled, IconFilterLine, IconFilterupFilled, IconFindConceptLine, IconFindLine, IconFindTestLine, IconFindTypeLine, IconFolderLine, IconGripFilled, IconGroupLine, IconHeadingLine, IconHideFilled, IconHighlighterLine, IconHistoryLine, IconImageLine, IconIndeterminateBold, IconIndeterminateLine, IconInfoFilled, IconInfoLine, IconItalicLine, IconLanguageLine, IconLeftLine, IconListLine, IconListNumberLine, IconLockFilled, IconMuteLine, IconNegativeFilled, IconNofileLine, IconPanelRightLine, IconPlannerLine, IconPlusBold, IconPlusLine, IconQuestionLine, IconRadicalLine, IconReloadLine, IconRightLine, IconSearchcheckLine, IconSearchLine, IconSeparatorVertical, IconSettingLine, IconSortdownLine, IconSortupLine, IconStarFilled, IconSubmitLine, IconSwitchLine, IconTopLine, IconTreeExpand, IconTypeLine, IconUnderlineLine, IconUnmuteLine, IconUserLine, IconViewFilled, IconWarningFilled.

디자인 토큰

Tailwind CSS 4 앱에서는 theme.css를 import한 뒤 semantic token을 사용할 수 있습니다.

<section className='bg-semantic-background-card text-semantic-text-normal p-24'>...</section>

우선 사용해야 하는 토큰 접두어:

  • 텍스트: text-semantic-text-*
  • 아이콘: text-semantic-icon-*
  • 배경/채움: bg-semantic-fill-*, bg-semantic-background-*
  • 선: border-semantic-line-*
  • 타이포그래피: text-body-*, text-heading-*, text-label-*
  • 간격: gap-primitive-*, p-primitive-*
  • radius: rounded-semantic-element-size-*

#ffffff, text-gray-500, p-[13px]처럼 디자인 시스템 밖의 raw 값은 디자인 요구가 명확할 때만 사용합니다.

개발 및 배포

# Storybook
pnpm dev:ds

# 패키지 빌드
pnpm --filter @pulley-hub/design-system build

# 변경사항 기록
pnpm changeset

배포는 main 브랜치의 Changesets GitHub Actions가 담당합니다. CI는 npm Trusted Publishing(OIDC)을 쓰므로 NPM_TOKEN은 필요 없습니다. npm 패키지 설정에서 Trusted Publisher로 GitHub Actions 워크플로 release-design-system.yml을 등록해야 합니다.