Skip to content

Repository files navigation

@jiin.seok/formkit-react

🚀 Compound Component Pattern과 내장 검증, TypeScript 지원을 갖춘 강력한 React 폼 라이브러리

npm version License: MIT

English Documentation

✨ 주요 기능

  • 🎯 Compound Component Pattern - 깔끔하고 조합 가능한 API
  • 🔄 React Hook Form 통합 - 성능 최적화
  • 🛡️ Zod 스키마 지원 - 타입 안전 검증
  • 접근성 우선 - ARIA 준수
  • 🎨 켜고 끄는 기본 테마 - 스타일시트를 불러오면 바로 쓸 만한 모양, 안 불러오면 순수 HTML
  • 📝 TypeScript - 완전한 타입 안정성
  • 🔒 비밀번호 토글 - 내장 가시성 토글
  • 🎛️ Select 컴포넌트 - Radix UI 기반

📦 설치

npm install @jiin.seok/formkit-react
# 또는
yarn add @jiin.seok/formkit-react
# 또는
pnpm add @jiin.seok/formkit-react

Peer Dependencies

npm install react react-dom

react-hook-form, zod, @hookform/resolvers 등은 패키지 의존성으로 자동 설치됩니다.

스타일 불러오기

기본 테마를 쓰려면 앱 진입점(예: main.tsx, layout.tsx)에서 한 번 불러옵니다.

import '@jiin.seok/formkit-react/styles.css'

이 줄을 넣지 않으면 컴포넌트는 아무 스타일도 얹지 않은 순수 HTML로 그려집니다. 처음부터 직접 그리고 싶으면 불러오지 않으면 됩니다. 스타일시트는 Tailwind를 요구하지 않으므로 어떤 앱에서도 동작합니다.

🚀 빠른 시작

기본 예제

import FormKit from '@jiin.seok/formkit-react'

function LoginForm() {
  const handleSubmit = (data) => {
    console.log('폼 데이터:', data)
  }

  return (
    <FormKit.Root formId="login" onSubmit={handleSubmit}>
      <FormKit.Title>로그인</FormKit.Title>
      
      <FormKit.Field>
        <FormKit.Label>이메일</FormKit.Label>
        <FormKit.Input name="email" type="email" required />
      </FormKit.Field>
      
      <FormKit.Field>
        <FormKit.Label>비밀번호</FormKit.Label>
        <FormKit.Input name="password" type="password" required />
      </FormKit.Field>
      
      <FormKit.SubmitButton>로그인</FormKit.SubmitButton>
    </FormKit.Root>
  )
}

Zod 검증과 함께 사용

import FormKit from '@jiin.seok/formkit-react'
import { z } from 'zod'

const loginSchema = z.object({
  email: z.string().email('유효하지 않은 이메일 주소입니다'),
  password: z.string().min(8, '비밀번호는 최소 8자 이상이어야 합니다')
})

function LoginForm() {
  const handleSubmit = (data) => {
    console.log('검증된 데이터:', data)
  }

  return (
    <FormKit.Root 
      formId="login" 
      schema={loginSchema} 
      onSubmit={handleSubmit}
    >
      <FormKit.Field>
        <FormKit.Label>이메일</FormKit.Label>
        <FormKit.Input name="email" type="email" />
      </FormKit.Field>
      
      <FormKit.Field>
        <FormKit.Label>비밀번호</FormKit.Label>
        <FormKit.Input name="password" type="password" />
      </FormKit.Field>
      
      <FormKit.SubmitButton>로그인</FormKit.SubmitButton>
    </FormKit.Root>
  )
}

Select를 포함한 고급 폼

import FormKit from '@jiin.seok/formkit-react'

function RegistrationForm() {
  const countries = [
    { value: 'kr', label: '대한민국' },
    { value: 'us', label: '미국' },
    { value: 'jp', label: '일본' },
  ]

  return (
    <FormKit.Root formId="registration" onSubmit={handleSubmit}>
      <FormKit.Fieldset>
        <FormKit.Legend required>개인 정보</FormKit.Legend>
        
        <FormKit.Field>
          <FormKit.Label>이름</FormKit.Label>
          <FormKit.Input name="fullName" required />
        </FormKit.Field>
        
        <FormKit.Field>
          <FormKit.Label>국가</FormKit.Label>
          <FormKit.Select 
            name="country" 
            options={countries}
            placeholder="국가를 선택하세요"
            required
          />
        </FormKit.Field>
        
        <FormKit.Field>
          <FormKit.Label>자기소개</FormKit.Label>
          <FormKit.Textarea 
            name="bio" 
            placeholder="간단한 자기소개를 작성해주세요"
            maxLength={500}
          />
        </FormKit.Field>
      </FormKit.Fieldset>
      
      <FormKit.SubmitButton>가입하기</FormKit.SubmitButton>
    </FormKit.Root>
  )
}

📦 사용 가능한 컴포넌트

FormKit은 포괄적인 폼 컴포넌트 세트를 제공합니다:

📝 핵심 컴포넌트

  • FormKit.Root - 검증 컨텍스트를 포함한 메인 폼 컨테이너
  • FormKit.Field - 레이블-입력 연결이 있는 필드 래퍼
  • FormKit.Fieldset - 관련된 필드 그룹화
  • FormKit.Legend - 필수 표시가 선택적으로 포함된 필드셋 제목

🎨 입력 컴포넌트

  • FormKit.Input - 비밀번호 토글, 이메일, 숫자 등을 지원하는 텍스트 입력
  • FormKit.Textarea - 여러 줄 텍스트 입력
  • FormKit.Select - 검색 기능이 있는 드롭다운 선택 (Radix UI 기반)

🏷️ 표시 컴포넌트

  • FormKit.Label - 필드 레이블
  • FormKit.Description - 입력 예시·형식 안내
  • FormKit.Title - 폼 제목
  • FormKit.Wrapper - 커스텀 레이아웃용 컨테이너
  • FormKit.Unit - 단위 표시 (예: "원", "kg")
  • FormKit.Error - 오류 메시지 표시

🎯 액션 컴포넌트

  • FormKit.SubmitButton - 로딩 상태가 있는 제출 버튼
  • FormKit.ResetButton - 폼을 초기값으로 재설정

📚 API 레퍼런스

FormKit.Root

모든 자식 컴포넌트에 컨텍스트를 제공하는 메인 폼 컨테이너입니다.

Prop Type 필수 설명
formId string 폼의 고유 식별자
onSubmit (data) => void 폼 제출 핸들러
schema ZodSchema 검증을 위한 Zod 스키마
defaultValues object 기본 폼 값
locale 'ko' | 'en' 자동 노출 문구(Select placeholder·비밀번호 aria-label)의 기본 언어. 기본값 'en'
messages Partial<FormMessages> 개별 문구만 덮어쓰기. locale 기본값 위에 얕게 병합됨
// 폼 전체의 기본 문구를 한국어로
<FormKit.Root formId="signup" locale="ko" onSubmit={onSubmit}>
  {/* Select 기본 placeholder가 '옵션을 선택하세요'로 나온다 */}
  <FormKit.Select name="genre" options={options} />
</FormKit.Root>

// 특정 문구만 커스텀
<FormKit.Root
  formId="signup"
  locale="ko"
  messages={{ selectPlaceholder: '장르를 골라주세요' }}
  onSubmit={onSubmit}
/>

FormKit.Field

자동 레이블-입력 연결이 있는 폼 입력용 컨테이너입니다.

Prop Type 기본값 설명
isInline boolean false 레이블과 입력을 가로로 표시
hidden boolean false 필드 숨기기
htmlFor string auto 레이블-입력 연결을 위한 커스텀 ID

FormKit.Input

내장 기능이 있는 향상된 입력 컴포넌트입니다.

Prop Type 필수 설명
name string 필드 이름
type string 입력 타입 (text, email, password 등)
required boolean 필드를 필수로 표시
minLength number 최소 문자 길이
maxLength number 최대 문자 길이

기능:

  • 🔒 type="password"에 대한 자동 비밀번호 가시성 토글
  • ✅ 확인 필드에 대한 자동 검증 (예: confirmPassword)
  • 🎯 완전한 TypeScript 지원

FormKit.Select

Radix UI를 사용한 드롭다운 선택 컴포넌트입니다.

Prop Type 필수 설명
name string 필드 이름
options Array<{value, label}> 선택 옵션
placeholder string 플레이스홀더 텍스트. 넘기지 않으면 FormKit.Rootlocale에 맞는 기본값 사용
required boolean 필드를 필수로 표시

FormKit.Textarea

여러 줄 텍스트 입력 컴포넌트입니다.

Prop Type 필수 설명
name string 필드 이름
required boolean 필드를 필수로 표시
minLength number 최소 문자 길이
maxLength number 최대 문자 길이
rows number 표시되는 텍스트 줄 수 (기본값: 4)

FormKit.Fieldset

관련된 폼 필드를 함께 그룹화합니다.

Prop Type 필수 설명
className string 커스텀 CSS 클래스
children ReactNode 자식 컴포넌트

FormKit.Legend

필수 표시가 선택적으로 있는 필드셋 제목입니다.

Prop Type 필수 설명
required boolean 빨간색 별표(*) 표시
className string 커스텀 CSS 클래스
children ReactNode 범례 텍스트

FormKit.Label

폼 입력을 위한 접근 가능한 레이블입니다.

Prop Type 필수 설명
className string 커스텀 CSS 클래스
children ReactNode 레이블 텍스트

FormKit.Description

입력 예시나 형식을 안내하는 설명입니다. 같은 Field 안의 입력이 aria-describedby로 이 설명을 가리키므로, 스크린리더는 칸에 들어갔을 때 설명을 함께 읽습니다. 오류가 함께 있으면 설명과 오류를 모두 가리킵니다.

설명은 칸 이름에는 섞이지 않습니다. Field가 label이라 그냥 두면 이름이 "예상 인원 수업에 참여하실 어르신 수"처럼 길어져, 칸 목록을 훑을 때 방해가 되기 때문입니다.

<FormKit.Field>
  <FormKit.Legend required>예상 인원</FormKit.Legend>
  <FormKit.Description>수업에 참여하실 어르신 수</FormKit.Description>
  <FormKit.Input name="headcount" type="number" required />
</FormKit.Field>
Prop Type 필수 설명
className string 커스텀 CSS 클래스
children ReactNode 설명 텍스트

FormKit.Title

폼 제목/헤더 컴포넌트입니다.

Prop Type 필수 설명
className string 커스텀 CSS 클래스
children ReactNode 제목 텍스트

FormKit.SubmitButton

내장 로딩 상태가 있는 제출 버튼입니다.

Prop Type 필수 설명
variant string 버튼 스타일 변형
disabled boolean 버튼 비활성화
className string 커스텀 CSS 클래스
children ReactNode 버튼 텍스트

FormKit.ResetButton

폼을 초기값으로 재설정합니다.

Prop Type 필수 설명
onClick function 추가 클릭 핸들러
className string 커스텀 CSS 클래스
children ReactNode 버튼 텍스트

FormKit.Wrapper

커스텀 레이아웃용 컨테이너 컴포넌트입니다.

Prop Type 필수 설명
className string 커스텀 CSS 클래스
children ReactNode 자식 컴포넌트

FormKit.Unit

입력 필드 옆에 단위를 표시합니다.

Prop Type 필수 설명
unit string 단위 텍스트 (예: "원", "kg", "%")

FormKit.Error

검증 오류 메시지를 표시합니다.

Prop Type 필수 설명
error FieldError react-hook-form의 오류 객체

🎨 스타일링

색 바꾸기

기본 테마는 호스트 앱의 CSS 변수를 먼저 읽고, 없을 때만 자체 기본값으로 떨어집니다. :root에 아래 변수 중 있는 것만 정의하면 그대로 반영됩니다. 값은 완전한 색상값(oklch(...)·hsl(...)· #hex)으로 씁니다.

:root {
  --primary: oklch(0.53 0.15 215); /* 강조색: 기본 버튼·초점 테두리·필수 표시 */
  --primary-foreground: oklch(0.99 0 0); /* 강조색 위 글자 */
  --background: oklch(1 0 0); /* 초점이 온 입력칸 배경 */
  --foreground: oklch(0.24 0.015 270); /* 본문 글자 */
  --muted-foreground: oklch(0.52 0.012 270); /* 안내문·플레이스홀더 */
  --input: oklch(0.9 0.006 270); /* 테두리 */
  --destructive: oklch(0.55 0.2 25); /* 오류 */
}

곡률·컨트롤 높이는 --fk- 변수로 바꿉니다.

:root {
  --fk-radius: 0.625rem;
  --fk-control-height: 2.75rem;
}

클래스로 덮어쓰기

컴포넌트는 className을 그대로 흘려보내고, 기본 모양은 @layer formkit 안에서만 그립니다. 레이어에 든 규칙은 레이어 밖 규칙과 프레임워크 유틸리티에 항상 지므로, 넘긴 클래스는 이름이 무엇이든 특이성이 얼마든 기본값을 이깁니다.

<FormKit.Input name="email" className="rounded-none border-2 border-black" />

Tailwind를 쓰지 않는 앱이면 평범한 CSS 클래스를 넘겨도 같습니다.

🧪 테스팅

import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import FormKit from '@jiin.seok/formkit-react'

test('폼 데이터 제출', async () => {
  const handleSubmit = jest.fn()
  
  render(
    <FormKit.Root formId="test" onSubmit={handleSubmit}>
      <FormKit.Field>
        <FormKit.Input name="username" />
      </FormKit.Field>
      <FormKit.SubmitButton>제출</FormKit.SubmitButton>
    </FormKit.Root>
  )
  
  await userEvent.type(screen.getByRole('textbox'), 'john')
  await userEvent.click(screen.getByRole('button'))
  
  expect(handleSubmit).toHaveBeenCalledWith({ username: 'john' })
})

📄 라이선스

MIT © [Jiin Seok]

🔗 링크

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages