---
description: 관리자 페이지 표준 스타일 가이드 - shadcn/ui 기반 일관된 디자인 시스템
globs: **/app/(main)/admin/**/*.tsx,**/components/admin/**/*.tsx
---

# 관리자 페이지 표준 스타일 가이드

이 가이드는 관리자 페이지의 일관된 UI/UX를 위한 표준 스타일 규칙입니다.
모든 관리자 페이지는 이 가이드를 따라야 합니다.

## 1. 페이지 레이아웃 구조

### 기본 페이지 템플릿

```tsx
export default function AdminPage() {
  return (
    <div className="flex min-h-screen flex-col bg-background">
      <div className="space-y-6 p-6">
        {/* 페이지 헤더 */}
        <div className="space-y-2 border-b pb-4">
          <h1 className="text-3xl font-bold tracking-tight">페이지 제목</h1>
          <p className="text-sm text-muted-foreground">페이지 설명</p>
        </div>

        {/* 메인 컨텐츠 */}
        <MainComponent />
      </div>

      {/* Scroll to Top 버튼 (모바일/태블릿 전용) */}
      <ScrollToTop />
    </div>
  );
}
```

**필수 적용 사항:**

- 최상위: `flex min-h-screen flex-col bg-background`
- 컨텐츠 영역: `space-y-6 p-6` (24px 좌우 여백, 24px 간격)
- 헤더 구분선: `border-b pb-4` (테두리 박스 사용 금지)
- Scroll to Top: 모든 관리자 페이지에 포함

## 2. Color System (색상 시스템)

### CSS Variables 사용 (하드코딩 금지)

```tsx
// ❌ 잘못된 예시
<div className="bg-gray-50 text-gray-900 border-gray-200">

// ✅ 올바른 예시
<div className="bg-background text-foreground border-border">
<div className="bg-card text-card-foreground">
<div className="bg-muted text-muted-foreground">
```

**표준 색상 토큰:**

- `bg-background` / `text-foreground`: 기본 배경/텍스트
- `bg-card` / `text-card-foreground`: 카드 배경/텍스트
- `bg-muted` / `text-muted-foreground`: 보조 배경/텍스트
- `bg-primary` / `text-primary`: 메인 액션
- `bg-destructive` / `text-destructive`: 삭제/에러
- `border-border`: 테두리
- `ring-ring`: 포커스 링

## 3. Typography (타이포그래피)

### 표준 텍스트 크기와 가중치

```tsx
// 페이지 제목
<h1 className="text-3xl font-bold tracking-tight">

// 섹션 제목
<h2 className="text-xl font-semibold">
<h3 className="text-lg font-semibold">
<h4 className="text-sm font-semibold">

// 본문 텍스트
<p className="text-sm">

// 보조 텍스트
<p className="text-sm text-muted-foreground">
<p className="text-xs text-muted-foreground">

// 라벨
<label className="text-sm font-medium">
```

## 4. Spacing System (간격)

### 일관된 간격 (4px 기준)

```tsx
// 페이지 레벨 간격
<div className="space-y-6">        // 24px

// 섹션 레벨 간격
<div className="space-y-4">        // 16px

// 필드 레벨 간격
<div className="space-y-2">        // 8px

// 패딩
<div className="p-6">              // 24px (카드)
<div className="p-4">              // 16px (내부 섹션)

// 갭
<div className="gap-4">            // 16px (flex/grid)
<div className="gap-2">            // 8px (버튼 그룹)
```

## 5. 검색 툴바 (Toolbar)

### 패턴 A: 통합 검색 영역 (권장)

```tsx
<div className="space-y-4">
  {/* 검색 및 액션 영역 */}
  <div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
    {/* 검색 영역 */}
    <div className="flex flex-col gap-4 sm:flex-row sm:items-center">
      {/* 통합 검색 */}
      <div className="w-full sm:w-[400px]">
        <div className="relative">
          <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
          <Input placeholder="통합 검색..." className="h-10 pl-10 text-sm" />
        </div>
      </div>

      {/* 고급 검색 토글 */}
      <Button variant="outline" className="h-10 gap-2 text-sm font-medium">
        고급 검색
      </Button>
    </div>

    {/* 액션 버튼 영역 */}
    <div className="flex items-center gap-4">
      <div className="text-sm text-muted-foreground">
        총{" "}
        <span className="font-semibold text-foreground">
          {count.toLocaleString()}
        </span>{" "}
        건
      </div>
      <Button className="h-10 gap-2 text-sm font-medium">
        <Plus className="h-4 w-4" />
        등록
      </Button>
    </div>
  </div>

  {/* 고급 검색 옵션 */}
  {showAdvanced && (
    <div className="space-y-4">
      <div className="space-y-1">
        <h4 className="text-sm font-semibold">고급 검색 옵션</h4>
        <p className="text-xs text-muted-foreground">설명</p>
      </div>
      <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
        <Input placeholder="필드 검색" className="h-10 text-sm" />
      </div>
    </div>
  )}
</div>
```

### 패턴 B: 제목 + 검색 + 버튼 한 줄 (공간 효율적)

```tsx
{
  /* 상단 헤더: 제목 + 검색 + 버튼 */
}
<div className="relative flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
  {/* 왼쪽: 제목 */}
  <h2 className="text-xl font-semibold">페이지 제목</h2>

  {/* 오른쪽: 검색 + 버튼 */}
  <div className="flex flex-col gap-3 sm:flex-row sm:items-center">
    {/* 필터 선택 */}
    <div className="w-full sm:w-[160px]">
      <Select>
        <SelectTrigger className="h-10">
          <SelectValue placeholder="필터" />
        </SelectTrigger>
      </Select>
    </div>

    {/* 검색 입력 */}
    <div className="w-full sm:w-[240px]">
      <Input placeholder="검색..." className="h-10 text-sm" />
    </div>

    {/* 초기화 버튼 */}
    <Button variant="outline" className="h-10 text-sm font-medium">
      초기화
    </Button>

    {/* 주요 액션 버튼 */}
    <Button variant="outline" className="h-10 gap-2 text-sm font-medium">
      <Plus className="h-4 w-4" />
      등록
    </Button>

    {/* 조건부 버튼 (선택 시) */}
    {selectedCount > 0 && (
      <Button variant="destructive" className="h-10 gap-2 text-sm font-medium">
        삭제 ({selectedCount})
      </Button>
    )}
  </div>
</div>;
```

**필수 적용 사항:**

- ❌ 검색 영역에 박스/테두리 사용 금지
- ✅ 검색창 권장 너비: `w-full sm:w-[240px]` ~ `sm:w-[400px]`
- ✅ 필터/Select 권장 너비: `w-full sm:w-[160px]` ~ `sm:w-[200px]`
- ✅ 고급 검색 필드: placeholder만 사용 (라벨 제거)
- ✅ 검색 아이콘: `Search` (lucide-react)
- ✅ Input/Select 높이: `h-10` (40px)
- ✅ 상단 헤더에 `relative` 추가 (드롭다운 표시용)

## 6. Button (버튼)

### 표준 버튼 variants와 크기

```tsx
// Primary 액션
<Button variant="default" size="default" className="h-10 gap-2 text-sm font-medium">
  <Plus className="h-4 w-4" />
  등록
</Button>

// Secondary 액션
<Button variant="outline" size="default" className="h-10 gap-2 text-sm font-medium">
  취소
</Button>

// Ghost 버튼 (아이콘 전용)
<Button variant="ghost" size="icon" className="h-8 w-8">
  <Icon className="h-4 w-4" />
</Button>

// Destructive
<Button variant="destructive" size="default" className="h-10 gap-2 text-sm font-medium">
  삭제
</Button>
```

**표준 크기:**

- `h-10`: 기본 버튼 (40px)
- `h-9`: 작은 버튼 (36px)
- `h-8`: 아이콘 버튼 (32px)

**아이콘 크기:**

- `h-4 w-4`: 버튼 내 아이콘 (16px)

## 7. Input (입력 필드)

### 표준 Input 스타일

```tsx
// 기본
<Input placeholder="입력..." className="h-10 text-sm" />

// 검색 (아이콘 포함)
<div className="relative">
  <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
  <Input placeholder="검색..." className="h-10 pl-10 text-sm" />
</div>

// 로딩/액티브
<Input className="h-10 text-sm border-primary ring-2 ring-primary/20" />

// 비활성화
<Input disabled className="h-10 text-sm cursor-not-allowed bg-muted text-muted-foreground" />
```

**필수 적용 사항:**

- 높이: `h-10` (40px)
- 텍스트: `text-sm`
- 포커스: 자동 적용 (`ring-2 ring-ring`)

## 8. Table & Card (테이블과 카드)

### 반응형 테이블/카드 구조

```tsx
// 실제 데이터 렌더링
return (
  <>
    {/* 데스크톱 테이블 뷰 (lg 이상) */}
    <div className="hidden rounded-lg border bg-card shadow-sm lg:block">
      <Table>
        <TableHeader>
          <TableRow className="border-b bg-muted/50 hover:bg-muted/50">
            <TableHead className="h-12 text-sm font-semibold">컬럼</TableHead>
          </TableRow>
        </TableHeader>
        <TableBody>
          <TableRow className="border-b transition-colors hover:bg-muted/50">
            <TableCell className="h-16 text-sm">데이터</TableCell>
          </TableRow>
        </TableBody>
      </Table>
    </div>

    {/* 모바일/태블릿 카드 뷰 (lg 미만) */}
    <div className="grid gap-4 sm:grid-cols-2 lg:hidden">
      {items.map((item) => (
        <div
          key={item.id}
          className="rounded-lg border bg-card p-4 shadow-sm transition-colors hover:bg-muted/50"
        >
          {/* 헤더 */}
          <div className="mb-4 flex items-start justify-between">
            <div className="flex-1">
              <h3 className="text-base font-semibold">{item.name}</h3>
              <p className="mt-1 text-sm text-muted-foreground">{item.id}</p>
            </div>
            <Switch checked={item.active} />
          </div>

          {/* 정보 */}
          <div className="space-y-2 border-t pt-4">
            <div className="flex justify-between text-sm">
              <span className="text-muted-foreground">필드</span>
              <span className="font-medium">{item.value}</span>
            </div>
          </div>

          {/* 액션 */}
          <div className="mt-4 flex gap-2 border-t pt-4">
            <Button
              variant="outline"
              size="sm"
              className="h-9 flex-1 gap-2 text-sm"
            >
              <Icon className="h-4 w-4" />
              액션
            </Button>
          </div>
        </div>
      ))}
    </div>
  </>
);
```

**테이블 표준:**

- 헤더: `h-12` (48px), `bg-muted/50`, `font-semibold`
- 데이터 행: `h-16` (64px), `hover:bg-muted/50`
- 텍스트: `text-sm`

**카드 표준:**

- 컨테이너: `rounded-lg border bg-card p-4 shadow-sm`
- 헤더 제목: `text-base font-semibold`
- 부제목: `text-sm text-muted-foreground`
- 정보 라벨: `text-sm text-muted-foreground`
- 정보 값: `text-sm font-medium`
- 버튼: `h-9 flex-1 gap-2 text-sm`

## 9. Loading States (로딩 상태)

### Skeleton UI 패턴

```tsx
// 테이블 스켈레톤 (데스크톱)
<div className="hidden rounded-lg border bg-card shadow-sm lg:block">
  <Table>
    <TableHeader>...</TableHeader>
    <TableBody>
      {Array.from({ length: 10 }).map((_, index) => (
        <TableRow key={index} className="border-b">
          <TableCell className="h-16">
            <div className="h-4 animate-pulse rounded bg-muted"></div>
          </TableCell>
        </TableRow>
      ))}
    </TableBody>
  </Table>
</div>

// 카드 스켈레톤 (모바일/태블릿)
<div className="grid gap-4 sm:grid-cols-2 lg:hidden">
  {Array.from({ length: 6 }).map((_, index) => (
    <div key={index} className="rounded-lg border bg-card p-4 shadow-sm">
      <div className="mb-4 flex items-start justify-between">
        <div className="flex-1 space-y-2">
          <div className="h-5 w-32 animate-pulse rounded bg-muted"></div>
          <div className="h-4 w-24 animate-pulse rounded bg-muted"></div>
        </div>
        <div className="h-6 w-11 animate-pulse rounded-full bg-muted"></div>
      </div>
      <div className="space-y-2 border-t pt-4">
        {Array.from({ length: 5 }).map((_, i) => (
          <div key={i} className="flex justify-between">
            <div className="h-4 w-16 animate-pulse rounded bg-muted"></div>
            <div className="h-4 w-32 animate-pulse rounded bg-muted"></div>
          </div>
        ))}
      </div>
    </div>
  ))}
</div>
```

## 10. Empty States (빈 상태)

### 표준 Empty State

```tsx
<div className="flex h-64 flex-col items-center justify-center rounded-lg border bg-card shadow-sm">
  <div className="flex flex-col items-center gap-2 text-center">
    <p className="text-sm text-muted-foreground">데이터가 없습니다.</p>
  </div>
</div>
```

## 11. Error States (에러 상태)

### 표준 에러 메시지

```tsx
<div className="rounded-lg border border-destructive/50 bg-destructive/10 p-4">
  <div className="flex items-center justify-between">
    <p className="text-sm font-semibold text-destructive">
      오류가 발생했습니다
    </p>
    <button
      onClick={clearError}
      className="text-destructive transition-colors hover:text-destructive/80"
      aria-label="에러 메시지 닫기"
    >
      ✕
    </button>
  </div>
  <p className="mt-1.5 text-sm text-destructive/80">{errorMessage}</p>
</div>
```

## 12. Responsive Design (반응형)

### Breakpoints

- `sm`: 640px (모바일 가로/태블릿)
- `md`: 768px (태블릿)
- `lg`: 1024px (노트북)
- `xl`: 1280px (데스크톱)

### 모바일 우선 패턴

```tsx
// 레이아웃
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">

// 그리드
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">

// 검색창
<div className="w-full sm:w-[400px]">

// 테이블/카드 전환
<div className="hidden lg:block">    {/* 데스크톱 테이블 */}
<div className="lg:hidden">          {/* 모바일 카드 */}

// 간격
<div className="p-4 sm:p-6">
<div className="gap-2 sm:gap-4">
```

## 13. 좌우 레이아웃 (Side-by-Side Layout)

### 사이드바 + 메인 영역 구조

```tsx
<div className="flex h-full gap-6">
  {/* 좌측 사이드바 (20-30%) */}
  <div className="w-[20%] border-r pr-6">
    <div className="space-y-4">
      <h3 className="text-lg font-semibold">사이드바 제목</h3>

      {/* 사이드바 컨텐츠 */}
      <div className="space-y-3">
        <div className="cursor-pointer rounded-lg border bg-card p-4 shadow-sm transition-all hover:shadow-md">
          <h4 className="text-sm font-semibold">항목</h4>
          <p className="mt-1 text-xs text-muted-foreground">설명</p>
        </div>
      </div>
    </div>
  </div>

  {/* 우측 메인 영역 (70-80%) */}
  <div className="w-[80%] pl-0">
    <div className="flex h-full flex-col space-y-4">
      <h2 className="text-xl font-semibold">메인 제목</h2>

      {/* 메인 컨텐츠 */}
      <div className="flex-1 overflow-hidden">{/* 컨텐츠 */}</div>
    </div>
  </div>
</div>
```

**필수 적용 사항:**

- ✅ 좌우 구분: `border-r` 사용 (세로 구분선)
- ✅ 간격: `gap-6` (24px)
- ✅ 사이드바 패딩: `pr-6` (오른쪽 24px)
- ✅ 메인 영역 패딩: `pl-0` (gap으로 간격 확보)
- ✅ 비율: 20:80 또는 30:70
- ❌ 과도한 구분선 사용 금지 (세로 구분선 1개만)
- ❌ 사이드바와 메인 영역 각각에 추가 border 금지

## 14. Custom Dropdown (커스텀 드롭다운)

### 커스텀 Select/Dropdown 구조

```tsx
{
  /* 드롭다운 컨테이너 */
}
<div className="w-full sm:w-[160px]">
  <div className="company-dropdown relative">
    {/* 트리거 버튼 */}
    <button
      type="button"
      onClick={() => setIsOpen(!isOpen)}
      className="flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
    >
      <span className={!value ? "text-muted-foreground" : ""}>
        {value || "선택하세요"}
      </span>
      <svg
        className={`h-4 w-4 transition-transform ${isOpen ? "rotate-180" : ""}`}
        fill="none"
        stroke="currentColor"
        viewBox="0 0 24 24"
      >
        <path
          strokeLinecap="round"
          strokeLinejoin="round"
          strokeWidth={2}
          d="M19 9l-7 7-7-7"
        />
      </svg>
    </button>

    {/* 드롭다운 메뉴 */}
    {isOpen && (
      <div className="absolute top-full left-0 z-[100] mt-1 w-full min-w-[200px] rounded-md border bg-popover text-popover-foreground shadow-lg">
        {/* 검색 (선택사항) */}
        <div className="border-b p-2">
          <Input
            placeholder="검색..."
            value={searchText}
            onChange={(e) => setSearchText(e.target.value)}
            className="h-8 text-sm"
            onClick={(e) => e.stopPropagation()}
          />
        </div>

        {/* 옵션 목록 */}
        <div className="max-h-48 overflow-y-auto">
          {options.map((option) => (
            <div
              key={option.value}
              className="flex cursor-pointer items-center px-2 py-1.5 text-sm hover:bg-accent hover:text-accent-foreground"
              onClick={() => {
                setValue(option.value);
                setIsOpen(false);
              }}
            >
              {option.label}
            </div>
          ))}
        </div>
      </div>
    )}
  </div>
</div>;
```

**필수 적용 사항:**

- ✅ z-index: `z-[100]` (다른 요소 위에 표시)
- ✅ 그림자: `shadow-lg` (명확한 레이어 구분)
- ✅ 최소 너비: `min-w-[200px]` (내용이 잘리지 않도록)
- ✅ 최대 높이: `max-h-48` (스크롤 가능)
- ✅ 애니메이션: 화살표 아이콘 회전 (`rotate-180`)
- ✅ 부모 요소: `relative` 클래스 필요
- ⚠️ 부모에 `overflow-hidden` 사용 시 드롭다운 잘림 주의

**드롭다운이 잘릴 때 해결방법:**

```tsx
// 부모 요소의 overflow 제거
<div className="w-[80%] pl-0">  // overflow-hidden 제거

// 또는 상단 헤더에 relative 추가
<div className="relative flex ...">  // 드롭다운 포지셔닝 기준점
```

## 15. Scroll to Top Button

### 모바일/태블릿 전용 버튼

```tsx
import { ScrollToTop } from "@/components/common/ScrollToTop";

// 페이지에 추가
<ScrollToTop />;
```

**특징:**

- 데스크톱에서 숨김 (`lg:hidden`)
- 스크롤 200px 이상 시 나타남
- 부드러운 페이드 인/아웃 애니메이션
- 오른쪽 하단 고정 위치
- 원형 디자인 (`rounded-full`)

## 14. Accessibility (접근성)

### 필수 적용 사항

```tsx
// Label과 Input 연결
<label htmlFor="field-id" className="text-sm font-medium">
  라벨
</label>
<Input id="field-id" />

// 버튼에 aria-label
<Button aria-label="설명">
  <Icon />
</Button>

// Switch에 aria-label
<Switch
  checked={isActive}
  onCheckedChange={handleChange}
  aria-label="상태 토글"
/>

// 포커스 표시 (자동 적용)
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring
```

## 15. Class 순서 (일관성)

### 표준 클래스 작성 순서

1. Layout: `flex`, `grid`, `block`
2. Position: `fixed`, `absolute`, `relative`
3. Sizing: `w-full`, `h-10`
4. Spacing: `p-4`, `m-2`, `gap-4`
5. Typography: `text-sm`, `font-medium`
6. Colors: `bg-primary`, `text-white`
7. Border: `border`, `rounded-md`
8. Effects: `shadow-sm`, `opacity-50`
9. States: `hover:`, `focus:`, `disabled:`
10. Responsive: `sm:`, `md:`, `lg:`

## 16. 금지 사항

### ❌ 절대 사용하지 말 것

1. 하드코딩된 색상 (`bg-gray-50`, `text-blue-500` 등)
2. 인라인 스타일로 색상 지정 (`style={{ color: '#3b82f6' }}`)
3. 포커스 스타일 제거 (`outline-none`만 단독 사용)
4. 중첩된 박스 (Card 안에 Card, Border 안에 Border)
5. 검색 영역에 불필요한 박스/테두리
6. 검색 필드에 라벨 (placeholder만 사용)
7. 반응형 무시 (데스크톱 전용 스타일)
8. **이모지 사용** (사용자가 명시적으로 요청하지 않는 한 절대 사용 금지)
9. 과도한 구분선 사용 (최소한으로 유지)
10. 드롭다운 부모에 `overflow-hidden` (잘림 발생)

## 17. 체크리스트

새로운 관리자 페이지 작성 시 다음을 확인하세요:

### 페이지 레벨

- [ ] `bg-background` 사용 (하드코딩 금지)
- [ ] `space-y-6 p-6` 구조
- [ ] 페이지 헤더에 `border-b pb-4`
- [ ] `ScrollToTop` 컴포넌트 포함

### 검색 툴바

- [ ] 박스/테두리 없음
- [ ] 검색창 최대 너비 `sm:w-[400px]`
- [ ] 고급 검색 필드에 라벨 없음 (placeholder만)
- [ ] 반응형 레이아웃 적용

### 테이블/카드

- [ ] 데스크톱: 테이블 (`hidden lg:block`)
- [ ] 모바일: 카드 (`lg:hidden`)
- [ ] 표준 높이와 간격 적용
- [ ] 로딩/Empty 상태 구현

### 버튼

- [ ] 표준 variants 사용
- [ ] 표준 높이: `h-10`, `h-9`, `h-8`
- [ ] 아이콘 크기: `h-4 w-4`
- [ ] `gap-2`로 아이콘과 텍스트 간격

### 반응형

- [ ] 모바일 우선 디자인
- [ ] Breakpoints 적용 (`sm:`, `lg:`)
- [ ] 테이블/카드 전환
- [ ] Scroll to Top 버튼

### 접근성

- [ ] Label `htmlFor` / Input `id` 연결
- [ ] 버튼 `aria-label`
- [ ] Switch `aria-label`
- [ ] 포커스 표시 유지

## 참고 파일

완성된 예시:

### 기본 패턴

- [사용자 관리 페이지](<mdc:frontend/app/(main)/admin/userMng/page.tsx>) - 기본 페이지 구조
- [검색 툴바](mdc:frontend/components/admin/UserToolbar.tsx) - 패턴 A (통합 검색)
- [테이블/카드](mdc:frontend/components/admin/UserTable.tsx) - 반응형 테이블/카드
- [Scroll to Top](mdc:frontend/components/common/ScrollToTop.tsx) - 스크롤 버튼

### 고급 패턴

- [메뉴 관리 페이지](<mdc:frontend/app/(main)/admin/menu/page.tsx>) - 좌우 레이아웃 + 패턴 B (제목+검색+버튼)
- [메뉴 관리 컴포넌트](mdc:frontend/components/admin/MenuManagement.tsx) - 커스텀 드롭다운 + 좌우 레이아웃
