npm package: @imc0nt0/mirror
Description: 심플한 React 컴포넌트 라이브러리
Author: imc0nt0
Latest version: 0.1.4

# @imc0nt0/mirror

심플한 React 컴포넌트 라이브러리

## 설치

```bash
npm install @imc0nt0/mirror framer-motion bootstrap-icons
```

## Color System

```json
{
  "primary": "#b8e8fd",
  "secondary": "#f4f4fe",
  "error": "#cc0000",
  "success": "#4bd964",
  "warning": "#ff4d00"
}
```

## 컴포넌트

### Button
버튼 컴포넌트

```jsx
import { Button } from '@imc0nt0/mirror';

<Button variant="primary">클릭</Button>
<Button variant="secondary" size="large">큰 버튼</Button>
<Button fullWidth>전체 너비</Button>
```

**Props:**
- `variant`: 'primary' | 'secondary' | 'outline' | 'ghost'
- `size`: 'small' | 'medium' | 'large'
- `disabled`: boolean
- `fullWidth`: boolean

### Card
카드 컨테이너

```jsx
import { Card } from '@imc0nt0/mirror';

<Card>
  <h2>제목</h2>
  <p>내용</p>
</Card>
```

**Props:**
- `hoverable`: boolean - 호버 효과
- `noPadding`: boolean - 패딩 제거
- `onClick`: function - 클릭 이벤트

### TextField
텍스트 입력 필드

```jsx
import { TextField } from '@imc0nt0/mirror';

<TextField 
  label="이름"
  placeholder="이름을 입력하세요"
  fullWidth
/>
```

**Props:**
- `label`: string - 라벨 텍스트
- `placeholder`: string
- `value`: string
- `onChange`: function
- `error`: string - 에러 메시지
- `disabled`: boolean
- `fullWidth`: boolean

**스타일:**
- 호버 시 배경색 변경 (#fafbfc)
- 호버 시 테두리 색상 변경 (#d4f1fd)
- 포커스 시 테두리 강조 (#b8e8fd)
- 모션 효과 없음 (성능 최적화)

### IconButton
아이콘만 있는 버튼

```jsx
import { IconButton } from '@imc0nt0/mirror';

<IconButton icon="bi-heart-fill" variant="primary" />
<IconButton icon="bi-trash-fill" variant="danger" />
<IconButton icon="bi-search" variant="ghost" size="small" />
```

**Props:**
- `icon`: string - Bootstrap Icons 클래스명 (예: 'bi-heart-fill')
- `variant`: 'primary' | 'secondary' | 'ghost' | 'danger' (기본값: 'ghost')
- `size`: 'small' | 'medium' | 'large' (기본값: 'medium')
- `disabled`: boolean
- `rounded`: boolean - 완전히 둥근 모양 (기본값: true)
- `onClick`: function

**Variants:**
- `primary`: #b8e8fd 배경
- `secondary`: #f4f4fe 배경
- `ghost`: 투명 배경, 호버 시 회색
- `danger`: 투명 배경, 빨간색 아이콘

### ListRow
깔끔한 스타일의 리스트 행

```jsx
import { ListRow, ColorBlock, ListSection } from '@imc0nt0/mirror';

// 기본 사용
<ListRow
  left={<ColorBlock color="#ff6b6b" />}
  title="ListRow"
  description="ListRow"
  showArrow
  onClick={() => console.log('클릭')}
/>

// 아이콘과 함께
<ListRow
  left={<i className="bi bi-bell-fill"></i>}
  title="알림 설정"
  right={<Switch />}
/>

// 섹션으로 그룹화
<ListSection title="카드 관리">
  <ListRow title="카드 1" showArrow />
  <ListRow title="카드 2" showArrow />
</ListSection>
```

**ListRow Props:**
- `left`: ReactNode - 왼쪽 콘텐츠 (아이콘, 색상 블록 등)
- `title`: string - 제목 (필수)
- `description`: string - 설명 (옵션)
- `right`: ReactNode - 오른쪽 콘텐츠 (아이콘, 스위치, 텍스트 등)
- `showArrow`: boolean - 오른쪽 화살표 표시 (기본값: false)
- `divider`: boolean - 하단 구분선 표시 (기본값: true)
- `onClick`: function - 클릭 이벤트
- `disabled`: boolean - 비활성화 여부

**ColorBlock Props:**
- `color`: string - 색상 (기본값: '#b8e8fd')
- `size`: number - 크기 (px, 기본값: 44)

**ListSection Props:**
- `title`: string - 섹션 제목
- `children`: ReactNode - ListRow 컴포넌트들

**디자인 개선:**
- 세련된 타이포그래피 (letter-spacing, font-weight)
- 부드러운 hover 애니메이션 (x축 이동)
- 둥근 모서리 카드 스타일
- 은은한 그림자 효과
- 개선된 색상 대비

### Toast
토스트 알림 (Bootstrap Icons + Framer Motion)

#### ToastProvider (권장)
앱 최상위에서 한 번만 선언:

```jsx
import { ToastProvider, useToast } from '@imc0nt0/mirror';

function App() {
  return (
    <ToastProvider>
      <YourApp />
    </ToastProvider>
  );
}

function YourApp() {
  const toast = useToast();

  const handleClick = () => {
    toast.success('성공했습니다!');
    toast.error('에러가 발생했습니다!');
    toast.warning('경고 메시지');
    toast.info('정보 메시지');
  };

  return <button onClick={handleClick}>토스트 띄우기</button>;
}
```

**useToast Hook:**
- `success(message, options)` - 성공 토스트
- `error(message, options)` - 에러 토스트
- `warning(message, options)` - 경고 토스트
- `info(message, options)` - 정보 토스트
- `showToast(options)` - 커스텀 토스트
  ```jsx
  toast.showToast({
    message: '메시지',
    type: 'success',
    position: 'top',
    duration: 5000
  });
  ```

**Options:**
- `position`: 'top' | 'center' | 'bottom' (기본값: 'bottom')
- `duration`: number (ms, 기본값: 3000)

**특징:**
- **한 번에 하나의 토스트만 표시** - 새 토스트가 오면 이전 토스트를 즉시 교체
- **타입별 아이콘 및 색상 자동 적용**
- **애니메이션 재생** - 새 토스트마다 애니메이션 효과

**아이콘:**
- Success: `bi-check-circle-fill` (체크 원형)
- Error: `bi-x-circle-fill` (X 원형)
- Warning: `bi-exclamation-triangle-fill` (경고 삼각형)
- Info: `bi-info-circle-fill` (정보 원형)

**애니메이션:**
- Spring 기반 바운스 효과
- 아이콘 회전 + 스케일 애니메이션
- 텍스트 페이드인 + 슬라이드
- **반복 표시 시마다 애니메이션 재생** (고유 key 사용)

#### Toast Component (직접 사용)
개별적으로 사용할 경우:

```jsx
import { Toast } from '@imc0nt0/mirror';

<Toast
  message="성공했습니다!"
  type="success"
  position="top"
  duration={3000}
  isOpen={true}
  onClose={() => console.log('closed')}
/>
```

**Toast Component Props:**
- `message`: string - 메시지
- `type`: 'success' | 'error' | 'warning' | 'info'
- `position`: 'top' | 'center' | 'bottom' - 위치
- `duration`: number - 표시 시간
- `isOpen`: boolean
- `onClose`: function

### Modal
모달 다이얼로그 (Normal, Confirm)

```jsx
import { Modal, useModal } from '@imc0nt0/mirror';

function App() {
  const { isOpen, openModal, closeModal } = useModal();

  return (
    <div>
      <button onClick={openModal}>모달 열기</button>
      
      {/* Normal Modal */}
      <Modal
        isOpen={isOpen}
        onClose={closeModal}
        title="알림"
        type="normal"
      >
        <p>모달 내용입니다.</p>
      </Modal>

      {/* Confirm Modal */}
      <Modal
        isOpen={isOpen}
        onClose={closeModal}
        title="삭제 확인"
        type="confirm"
        confirmText="삭제"
        cancelText="취소"
        onConfirm={() => console.log('삭제됨')}
      >
        <p>정말로 삭제하시겠습니까?</p>
      </Modal>
    </div>
  );
}
```

**Modal Props:**
- `isOpen`: boolean - 모달 표시 여부
- `onClose`: function - 닫기 콜백
- `title`: string - 모달 제목
- `children`: ReactNode - 모달 내용
- `type`: 'normal' | 'confirm' - 모달 타입 (기본값: 'normal')
- `confirmText`: string - 확인 버튼 텍스트 (기본값: '확인')
- `cancelText`: string - 취소 버튼 텍스트 (기본값: '취소')
- `onConfirm`: function - 확인 버튼 콜백 (confirm 타입)
- `showCloseButton`: boolean - X 닫기 버튼 표시 (기본값: true)

**기능:**
- ESC 키로 닫기
- 외부 클릭으로 닫기
- 모달 열릴 때 스크롤 방지
- Spring 애니메이션

**useModal Hook:**
- `isOpen`: boolean - 모달 열림 상태
- `openModal()`: function - 모달 열기
- `closeModal()`: function - 모달 닫기

## 애니메이션

### 모션 프리셋

미리 정의된 애니메이션을 제공합니다.

```jsx
import { Animate, AnimateOnView, AnimateStagger, motions } from '@imc0nt0/mirror';

// 기본 애니메이션
<Animate motion="fadeIn">
  <div>페이드 인</div>
</Animate>

<Animate motion="slideUp" delay={0.2}>
  <div>슬라이드 업</div>
</Animate>
```

**사용 가능한 모션:**
- `fadeIn`, `fadeOut` - 페이드 효과
- `slideUp`, `slideDown`, `slideLeft`, `slideRight` - 슬라이드 효과
- `scaleIn`, `scaleOut`, `scaleBounce` - 스케일 효과
- `rotateIn` - 회전 효과
- `blurIn` - 블러 효과
- `entrance` - 입장 효과 (슬라이드 + 페이드 + 블러)
- `pop` - 팝 효과 (바운스)

### AnimateOnView

뷰포트에 진입할 때 애니메이션을 실행합니다.

```jsx
<AnimateOnView motion="slideUp" once={true}>
  <div>스크롤 시 나타남</div>
</AnimateOnView>
```

**Props:**
- `motion`: string - 모션 프리셋 이름
- `delay`: number - 지연 시간(초)
- `once`: boolean - 한 번만 실행 (기본값: true)
- `amount`: number - 얼마나 보일 때 실행할지 (0-1, 기본값: 0.3)

### AnimateStagger

자식 요소들을 순차적으로 애니메이션합니다.

```jsx
<AnimateStagger motion="fadeIn" staggerDelay={0.1}>
  {items.map(item => (
    <div key={item.id}>{item.name}</div>
  ))}
</AnimateStagger>
```

**Props:**
- `motion`: string - 모션 프리셋 이름
- `staggerDelay`: number - 자식 간 지연 시간(초, 기본값: 0.1)

### useAnimation Hook

프로그래매틱하게 애니메이션을 제어합니다.

```jsx
import { useAnimation, motions } from '@imc0nt0/mirror';

function MyComponent() {
  const { scope, play, serial, parallel, stagger, timeline } = useAnimation();
  
  const handleClick = async () => {
    // 단일 실행
    await play('.target', motions.fadeIn);
    
    // 순차 실행
    await serial([
      { target: '.item1', motion: motions.fadeIn },
      { target: '.item2', motion: motions.slideUp },
    ]);
    
    // 동시 실행
    await parallel([
      { target: '.item1', motion: motions.fadeIn },
      { target: '.item2', motion: motions.fadeIn },
    ]);
    
    // 스태거 실행
    await stagger([
      { target: '.item1', motion: motions.fadeIn },
      { target: '.item2', motion: motions.fadeIn },
    ], 0.1);
    
    // 타임라인
    await timeline([
      { type: 'parallel', animations: [...] },
      { type: 'wait', duration: 0.5 },
      { type: 'serial', animations: [...] },
    ]);
  };
  
  return (
    <div ref={scope}>
      <div className="target">애니메이션 대상</div>
    </div>
  );
}
```

**메서드:**
- `play(target, motion, options)` - 단일 모션 실행
- `serial(animations)` - 순차 실행
- `parallel(animations)` - 동시 실행
- `stagger(animations, delayBetween)` - 스태거 실행
- `timeline(sequence)` - 복합 타임라인

### 커스텀 모션

```jsx
import { createMotion, easings } from '@imc0nt0/mirror';

const customMotion = createMotion({
  from: { opacity: 0, y: 20 },
  to: { opacity: 1, y: 0 },
  duration: 0.5,
  ease: easings.toss,
});

<Animate motion={customMotion}>
  <div>커스텀 애니메이션</div>
</Animate>
```

**이징 함수:**
- `linear`, `ease`, `easeIn`, `easeOut`, `easeInOut`
- `spring`, `springBouncy`, `springSoft`
- `toss`, `smooth`, `bounce`, `anticipate`

### 모션 결합

```jsx
import { combineMotions, motions } from '@imc0nt0/mirror';

const combined = combineMotions(
  motions.fadeIn,
  motions.slideUp
);
```

## 라이선스

MIT