# 공통 바텀시트 (CommonBottomSheet)

## 개요
모바일에서는 바텀시트(Sheet), 데스크톱에서는 다이얼로그(Dialog)로 표시되는 반응형 공통 템플릿입니다.
대시보드 위젯관리를 비롯한 모든 모달/바텀시트 UI에 사용할 수 있습니다.

**⭐ 핵심 기능: 모바일에서 CTA 버튼이 하단에 sticky로 고정됩니다!**

## 파일 위치
`/components/templates/CommonBottomSheet.tsx`

## 주요 특징

### 1. 반응형 디자인
- **모바일 (~767px)**: 바텀시트 형태 (하단에서 올라오는 Sheet)
- **데스크톱 (768px+)**: 다이얼로그 형태 (중앙 Dialog)
- 자동으로 화면 크기를 감지하여 최적의 UI 제공

### 2. 모바일 Sticky Footer (NEW!)
- **모바일에서 푸터가 하단에 고정**: 스크롤해도 CTA 버튼이 항상 보임
- 콘텐츠가 많아도 저장/취소 버튼에 바로 접근 가능
- 데스크톱에서는 일반 푸터로 표시

### 3. 일관된 UX
- 모바일 바텀시트에는 핸들바 자동 표시
- 헤더, 콘텐츠, 푸터 영역 명확히 구분
- 스크롤 가능한 콘텐츠 영역
- 반응형 버튼 레이아웃 (모바일 2컬럼, 데스크톱 자동)

### 4. 커스터마이징 가능
- 타이틀, 설명, 아이콘 설정
- 푸터 표시/숨김
- 버튼 텍스트 커스터마이징
- 최대 너비/높이 조절
- 커스텀 푸터 지원
- 핸들바 숨김 옵션

## 기본 사용법

```tsx
import { CommonBottomSheet } from "./templates/CommonBottomSheet";
import { Settings } from "lucide-react";

function MyComponent() {
  const [isOpen, setIsOpen] = useState(false);

  const handleSave = () => {
    // 저장 로직
    setIsOpen(false);
  };

  return (
    <CommonBottomSheet
      isOpen={isOpen}
      onClose={() => setIsOpen(false)}
      title="위젯 관리"
      description="대시보드에 표시할 위젯을 선택하고 순서를 변경할 수 있습니다."
      icon={<Settings className="h-5 w-5" />}
      showFooter={true}
      onSave={handleSave}
    >
      <div>콘텐츠 영역</div>
    </CommonBottomSheet>
  );
}
```

## Props

| Prop | Type | Default | 설명 |
|------|------|---------|------|
| `isOpen` | `boolean` | - | 바텀시트 열림 상태 (필수) |
| `onClose` | `() => void` | - | 바텀시트 닫기 핸들러 (필수) |
| `title` | `string` | - | 바텀시트 타이틀 (필수) |
| `description` | `string` | `undefined` | 바텀시트 설명 (선택) |
| `icon` | `ReactNode` | `undefined` | 타이틀 앞 아이콘 (선택) |
| `children` | `ReactNode` | - | 콘텐츠 영역 (필수) |
| `showFooter` | `boolean` | `false` | 푸터 표시 여부 |
| `saveButtonText` | `string` | `"저장"` | 저장 버튼 텍스트 |
| `cancelButtonText` | `string` | `"취소"` | 취소 버튼 텍스트 |
| `onSave` | `() => void` | `undefined` | 저장 버튼 클릭 핸들러 |
| `onCancel` | `() => void` | `undefined` | 취소 버튼 클릭 핸들러 (기본: onClose) |
| `saveDisabled` | `boolean` | `false` | 저장 버튼 비활성화 여부 |
| `customFooter` | `ReactNode` | `undefined` | 커스텀 푸터 (showFooter=true일 때만) |
| `maxWidth` | `"sm"` \| `"md"` \| `"lg"` \| `"xl"` \| `"2xl"` \| `"3xl"` \| `"4xl"` \| `"5xl"` \| `"6xl"` \| `"7xl"` | `"5xl"` | 최대 너비 |
| `maxHeight` | `string` | `"85vh"` | 최대 높이 |
| `forceBottomSheet` | `boolean` | `false` | 데스크톱에서도 바텀시트 사용 |
| `hideHandle` | `boolean` | `false` | 모바일 핸들바 숨김 |
| `contentClassName` | `string` | `""` | 콘텐츠 영역 커스텀 클래스 |
| `headerClassName` | `string` | `""` | 헤더 영역 커스텀 클래스 |
| `footerClassName` | `string` | `""` | 푸터 영역 커스텀 클래스 |

## 사용 예시

### 1. 기본 사용 (푸터 없음)

```tsx
<CommonBottomSheet
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="알림"
  description="새로운 메시지가 도착했습니다."
>
  <div className="space-y-4">
    <p>콘텐츠 내용</p>
  </div>
</CommonBottomSheet>
```

### 2. 저장/취소 버튼 포함

```tsx
<CommonBottomSheet
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="설정 변경"
  icon={<Settings className="h-5 w-5" />}
  showFooter={true}
  saveButtonText="적용"
  cancelButtonText="닫기"
  onSave={handleSave}
  saveDisabled={!hasChanges}
>
  <div className="space-y-4">
    {/* 설정 폼 */}
  </div>
</CommonBottomSheet>
```

### 3. 커스텀 푸터

```tsx
<CommonBottomSheet
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="항목 선택"
  showFooter={true}
  customFooter={
    <div className="flex gap-2 w-full">
      <Button variant="outline" onClick={onReset} className="flex-1">
        초기화
      </Button>
      <Button onClick={onApply} className="flex-1">
        선택 완료 ({selectedCount})
      </Button>
    </div>
  }
>
  <div>콘텐츠</div>
</CommonBottomSheet>
```

### 4. 큰 콘텐츠 (최대 크기 조절)

```tsx
<CommonBottomSheet
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="데이터 미리보기"
  maxWidth="7xl"
  maxHeight="90vh"
  contentClassName="p-0"
>
  <div className="h-full">
    {/* 큰 테이블이나 그래프 */}
  </div>
</CommonBottomSheet>
```

### 5. 강제 바텀시트 모드

```tsx
<CommonBottomSheet
  isOpen={isOpen}
  onClose={() => setIsOpen(false)}
  title="필터 설정"
  forceBottomSheet={true}
  hideHandle={true}
>
  <div>데스크톱에서도 바텀시트로 표시됩니다</div>
</CommonBottomSheet>
```

## 실제 적용 사례

### 1. 대시보드 위젯 관리
파일: `/components/WidgetManager.tsx`

```tsx
<CommonBottomSheet
  isOpen={isOpen}
  onClose={onClose}
  title="위젯 관리"
  description="대시보드에 표시할 위젯을 선택하고 순서를 변경할 수 있습니다."
  icon={<Settings className="h-5 w-5" />}
  maxWidth="5xl"
  maxHeight="85vh"
  showFooter={true}
  saveButtonText="저장"
  cancelButtonText="취소"
  onSave={handleSave}
  contentClassName="pt-4"
>
  <Tabs>
    {/* 위젯 설정 UI */}
  </Tabs>
</CommonBottomSheet>
```

### 2. BOM 품목 추가 (권장 개선)
파일: `/components/BomCalculationResults.tsx`

기존 코드를 CommonBottomSheet로 교체하면 더 일관된 UX를 제공할 수 있습니다:

```tsx
<CommonBottomSheet
  isOpen={showAddItem}
  onClose={() => setShowAddItem(false)}
  title="품목 추가"
  description="품목마스터에서 선택하여 BOM 목록에 추가합니다"
  maxWidth="6xl"
  showFooter={true}
  saveButtonText={`일괄 추가 (${selectedItems.size})`}
  saveDisabled={selectedItems.size === 0}
  onSave={handleAddNewItem}
>
  {/* 품목 검색 및 선택 UI */}
</CommonBottomSheet>
```

## 디자인 가이드라인

### 1. 타이틀
- 간결하고 명확한 동작 설명
- 2-4단어 권장
- 예: "위젯 관리", "품목 추가", "필터 설정"

### 2. 설명 (Description)
- 사용자가 수행할 작업 안내
- 1-2문장 권장
- 선택 사항이지만 복잡한 기능은 권장

### 3. 콘텐츠 영역
- 충분한 여백 제공 (padding)
- 스크롤 가능하도록 구성
- 복잡한 UI는 Tabs로 분리

### 4. 버튼
- 저장 버튼: 긍정적 액션 (primary)
- 취소 버튼: 중립적 액션 (outline)
- 모바일에서는 2컬럼 그리드로 균등 배치
- 데스크톱에서는 우측 정렬, 자동 너비

## 접근성 (Accessibility)

### 1. ARIA 속성
- `aria-describedby`: 자동으로 description과 연결
- Sheet/Dialog 컴포넌트가 제공하는 기본 접근성 기능 활용

### 2. 키보드 네비게이션
- `ESC`: 바텀시트 닫기
- `Tab`: 포커스 이동
- `Enter`: 저장 버튼 활성화

### 3. 포커스 관리
- 바텀시트 열릴 때 자동 포커스
- 닫힐 때 이전 포커스 복원

## 모바일 UX 최적화

### 1. 핸들바
- 시각적 단서로 드래그 가능함을 표시
- 필요시 `hideHandle={true}`로 숨김 가능

### 2. 버튼 크기
- 터치 친화적인 높이 (최소 44px)
- 모바일에서 전체 너비 버튼

### 3. 스크롤
- 콘텐츠가 많을 때 자동 스크롤
- 헤더/푸터는 고정, 콘텐츠만 스크롤

## 마이그레이션 가이드

기존 Dialog/Sheet를 CommonBottomSheet로 변경:

### Before
```tsx
const [isMobile, setIsMobile] = useState(false);

// 모바일 감지 로직...

if (isMobile) {
  return <Sheet>...</Sheet>;
}
return <Dialog>...</Dialog>;
```

### After
```tsx
<CommonBottomSheet
  isOpen={isOpen}
  onClose={onClose}
  title="제목"
  showFooter={true}
  onSave={handleSave}
>
  {content}
</CommonBottomSheet>
```

## 주의사항

1. **중복 푸터 방지**: `showFooter={true}`일 때 children 내부에 별도 버튼 배치 지양
2. **콘텐츠 높이**: 콘텐츠가 너무 작으면 모바일에서 이상하게 보일 수 있음
3. **maxHeight 조절**: 콘텐츠 양에 따라 적절히 조절
4. **Tabs 사용 시**: `contentClassName`으로 padding 조절 필요

## 버전 히스토리

- **v1.0.0** (2024-12-01): 초기 버전 생성, 대시보드 위젯관리에 적용