Files
sam-design/src/DESIGN_SYSTEM_STANDARDIZATION_GUIDE.md
정재웅 060b9ce2ef 프로젝트 초기 설정 및 구조 추가
- Vite + React 프로젝트 구조 설정
- 불필요한 PDF 파일 삭제

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 13:01:43 +09:00

13 KiB

디자인 시스템 표준화 가이드

목적

SAM MES 시스템의 모든 페이지가 일관된 디자인을 유지하도록 표준화합니다.


1. 필수 컴포넌트 사용

1.1 PageLayout

모든 페이지는 반드시 PageLayout으로 감싸야 합니다.

import { PageLayout } from "./organisms/PageLayout";

export function YourPage() {
  return (
    <PageLayout maxWidth="full">
      {/* 페이지 내용 */}
    </PageLayout>
  );
}

maxWidth 옵션:

  • full: 대시보드, 목록 페이지 (기본값)
  • 2xl: 넓은 폼 페이지
  • xl: 일반 폼 페이지
  • lg: 좁은 폼 페이지

1.2 PageHeader

모든 페이지는 반드시 PageHeader를 사용해야 합니다.

import { PageHeader } from "./organisms/PageHeader";
import { Package } from "lucide-react";
import { Button } from "./ui/button";

<PageHeader
  title="페이지 제목"
  description="페이지에 대한 간단한 설명"
  icon={Package}
  actions={
    <>
      <Button variant="outline">취소</Button>
      <Button>등록</Button>
    </>
  }
/>

금지 사항:

// ❌ 직접 헤더 작성 금지
<div className="flex justify-between">
  <h1>제목</h1>
  <Button>등록</Button>
</div>

// ✅ PageHeader 사용
<PageHeader title="제목" actions={<Button>등록</Button>} />

2. 표준 페이지 구조

2.1 기본 목록 페이지 구조

import { PageLayout } from "./organisms/PageLayout";
import { PageHeader } from "./organisms/PageHeader";
import { StatCards } from "./organisms/StatCards";
import { SearchFilter } from "./organisms/SearchFilter";
import { DataTable } from "./organisms/DataTable";

export function ListPage() {
  return (
    <PageLayout>
      {/* 1. 헤더 (제목 + 서브제목 + 아이콘 + 액션 버튼) */}
      <PageHeader
        title="페이지 제목"
        description="페이지 설명"
        icon={Icon}
        actions={<Button>등록</Button>}
      />

      {/* 2. 통계 카드 (4개) */}
      <StatCards
        stats={[
          { title: "통계1", value: "100", icon: Icon, color: "blue" },
          { title: "통계2", value: "200", icon: Icon, color: "green" },
          { title: "통계3", value: "300", icon: Icon, color: "yellow" },
          { title: "통계4", value: "400", icon: Icon, color: "purple" },
        ]}
      />

      {/* 3. 검색/필터 */}
      <SearchFilter
        searchValue={searchValue}
        onSearchChange={setSearchValue}
        filters={filterConfig}
      />

      {/* 4. 콘텐츠 (테이블) */}
      <DataTable
        columns={columns}
        data={filteredData}
      />
    </PageLayout>
  );
}

2.2 대시보드 페이지 구조

export function DashboardPage() {
  return (
    <PageLayout>
      <PageHeader
        title="대시보드"
        description="실시간 현황을 확인하세요"
        icon={LayoutDashboard}
      />

      <StatCards stats={stats} />

      {/* 차트 및 위젯 */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
        <Widget1 />
        <Widget2 />
        <Widget3 />
      </div>
    </PageLayout>
  );
}

2.3 폼 페이지 구조

export function FormPage() {
  return (
    <PageLayout maxWidth="xl">
      <PageHeader
        title="신규 등록"
        description="필수 항목을 입력하세요"
        icon={Plus}
        actions={
          <>
            <Button variant="outline" onClick={handleCancel}>
              취소
            </Button>
            <Button onClick={handleSubmit}>
              저장
            </Button>
          </>
        }
      />

      <FormSection title="기본 정보">
        {/* 폼 필드들 */}
      </FormSection>

      <FormSection title="추가 정보">
        {/* 폼 필드들 */}
      </FormSection>
    </PageLayout>
  );
}

3. 스타일 가이드

3.1 금지된 패턴

// ❌ 직접 패딩/여백 정의 금지
<div className="p-6 space-y-6">

// ❌ 배경 그라데이션 금지 (특별한 경우 제외)
<div className="bg-gradient-to-br from-slate-50 to-blue-50/30">

// ❌ 불필요한 min-h-screen 금지
<div className="min-h-screen">

// ❌ 인라인 헤더 스타일 금지
<h1 className="text-3xl font-bold text-foreground">

// ❌ 커스텀 카드 스타일 금지
<div className="bg-card border border-border/20 rounded-xl p-6">

3.2 권장 패턴

// ✅ PageLayout 사용
<PageLayout>

// ✅ PageHeader 사용
<PageHeader title="제목" />

// ✅ Card 컴포넌트 사용
import { Card, CardHeader, CardTitle, CardContent } from "./ui/card";
<Card>
  <CardHeader>
    <CardTitle>제목</CardTitle>
  </CardHeader>
  <CardContent>
    {/* 내용 */}
  </CardContent>
</Card>

// ✅ 그리드 레이아웃
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">

4. 타이포그래피 표준

4.1 절대 사용하지 말 것

globals.css에 타이포그래피가 정의되어 있으므로 Tailwind 클래스를 사용하지 마세요.

// ❌ 절대 금지!
className="text-2xl"
className="text-xl" 
className="text-lg"
className="font-bold"
className="font-semibold"
className="leading-tight"

4.2 올바른 사용법

// ✅ HTML 태그 사용 (자동 스타일 적용)
<h1>대제목</h1>
<h2>중제목</h2>
<h3>소제목</h3>
<p>본문</p>
<label>레이블</label>

예외: 사용자 요청이 있을 때만 타이포그래피 클래스 사용


5. 색상 시스템

5.1 디자인 토큰 사용

// ✅ CSS 변수 사용
className="bg-primary text-primary-foreground"
className="bg-secondary text-secondary-foreground"
className="bg-muted text-muted-foreground"
className="bg-destructive text-destructive-foreground"
className="border-border"
className="bg-background text-foreground"
className="bg-card text-card-foreground"

5.2 아이콘 배경 색상

// ✅ 표준 아이콘 배경
<div className="p-2 bg-primary/10 rounded-lg">
  <Icon className="w-6 h-6 text-primary" />
</div>

// ✅ 다른 색상 옵션
bg-blue-100 text-blue-600
bg-green-100 text-green-600
bg-yellow-100 text-yellow-600
bg-purple-100 text-purple-600
bg-orange-100 text-orange-600
bg-red-100 text-red-600

6. 반응형 디자인

6.1 브레이크포인트

// 모바일 우선
className="grid-cols-1"         // < 768px
className="md:grid-cols-2"      // 768px ~ 1023px (태블릿)
className="lg:grid-cols-4"      // 1024px+ (데스크톱)

6.2 통계 카드 레이아웃

// ✅ 표준 반응형 그리드
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
  <StatCard />
  <StatCard />
  <StatCard />
  <StatCard />
</div>

6.3 폼 레이아웃

// ✅ 반응형 폼
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  <FormField />
  <FormField />
  <FormField />
</div>

7. 컴포넌트 사용 우선순위

7.1 Organisms (가장 높은 우선순위)

  1. PageLayout - 모든 페이지 필수
  2. PageHeader - 모든 페이지 필수
  3. StatCards - 통계 카드가 있는 페이지
  4. SearchFilter - 검색/필터가 있는 페이지
  5. DataTable - 테이블이 있는 페이지
  6. FormSection - 폼이 있는 페이지
  7. EmptyState - 데이터가 없을 때

7.2 Molecules

  1. StatCard - 개별 통계 카드
  2. FormField - 개별 폼 필드
  3. SearchBar - 검색 입력
  4. StatusBadge - 상태 표시
  5. TableActions - 테이블 액션 버튼

7.3 ShadCN UI Components

  1. Card - 카드 레이아웃
  2. Button - 버튼
  3. Input - 입력 필드
  4. Select - 드롭다운
  5. Dialog - 다이얼로그
  6. Table - 테이블 (DataTable 내부에서 사용)

8. 페이지별 체크리스트

목록 페이지 체크리스트

  • PageLayout으로 감싸져 있는가?
  • PageHeader를 사용하고 있는가?
  • 아이콘이 PageHeader에 전달되었는가?
  • 통계 카드가 4개인가?
  • StatCards 컴포넌트를 사용하는가?
  • SearchFilter 컴포넌트를 사용하는가?
  • DataTable 컴포넌트를 사용하는가?
  • 데이터가 없을 때 EmptyState를 보여주는가?
  • 반응형 레이아웃인가? (grid-cols-1 md:grid-cols-2 lg:grid-cols-4)

대시보드 페이지 체크리스트

  • PageLayout으로 감싸져 있는가?
  • PageHeader를 사용하고 있는가?
  • StatCards를 사용하는가?
  • 위젯이 반응형 그리드로 배치되어 있는가?
  • 타이포그래피 클래스를 사용하지 않았는가?

폼 페이지 체크리스트

  • PageLayout (maxWidth="xl")으로 감싸져 있는가?
  • PageHeader를 사용하고 있는가?
  • 액션 버튼이 PageHeader에 전달되었는가?
  • FormSection으로 섹션을 구분했는가?
  • FormField를 사용하는가?
  • 반응형 폼 레이아웃인가?

9. 마이그레이션 예시

Before ()

export function OldPage() {
  return (
    <div className="p-6 space-y-6 bg-gradient-to-br from-slate-50 to-blue-50/30 min-h-screen">
      <div className="flex justify-between items-center">
        <div>
          <h1 className="text-3xl font-bold text-foreground">페이지 제목</h1>
          <p className="text-sm text-muted-foreground mt-1">설명</p>
        </div>
        <Button>등록</Button>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
        <Card>
          <CardHeader>
            <CardTitle>통계 1</CardTitle>
          </CardHeader>
          <CardContent>
            <p className="text-2xl font-bold">100</p>
          </CardContent>
        </Card>
        {/* ... */}
      </div>

      <div className="bg-card border rounded-xl p-4">
        <Input placeholder="검색..." />
      </div>

      <Card>
        <Table>
          {/* ... */}
        </Table>
      </Card>
    </div>
  );
}

After ()

import { PageLayout } from "./organisms/PageLayout";
import { PageHeader } from "./organisms/PageHeader";
import { StatCards } from "./organisms/StatCards";
import { SearchFilter } from "./organisms/SearchFilter";
import { DataTable } from "./organisms/DataTable";
import { Package } from "lucide-react";

export function NewPage() {
  return (
    <PageLayout>
      <PageHeader
        title="페이지 제목"
        description="설명"
        icon={Package}
        actions={<Button>등록</Button>}
      />

      <StatCards
        stats={[
          { title: "통계 1", value: "100", icon: Package, color: "blue" },
          { title: "통계 2", value: "200", icon: Package, color: "green" },
          { title: "통계 3", value: "300", icon: Package, color: "yellow" },
          { title: "통계 4", value: "400", icon: Package, color: "purple" },
        ]}
      />

      <SearchFilter
        searchValue={searchValue}
        onSearchChange={setSearchValue}
      />

      <DataTable
        columns={columns}
        data={data}
      />
    </PageLayout>
  );
}

10. 자주 발생하는 실수

실수 1: 직접 헤더 작성

<div className="flex justify-between">
  <h1>제목</h1>
  <Button>등록</Button>
</div>

해결

<PageHeader
  title="제목"
  actions={<Button>등록</Button>}
/>

실수 2: 커스텀 카드 스타일

<div className="bg-card border border-border/20 rounded-xl p-6">
  <h3 className="text-lg font-semibold">제목</h3>
  <p>내용</p>
</div>

해결

<Card>
  <CardHeader>
    <CardTitle>제목</CardTitle>
  </CardHeader>
  <CardContent>
    <p>내용</p>
  </CardContent>
</Card>

실수 3: 타이포그래피 클래스 사용

<h1 className="text-3xl font-bold">제목</h1>

해결

<h1>제목</h1>  {/* globals.css에 정의된 스타일 자동 적용 */}

11. 개발 워크플로우

새 페이지 생성 시

  1. PageLayout으로 시작
  2. PageHeader 추가
  3. 필요한 Organisms 컴포넌트 추가 (StatCards, SearchFilter, DataTable 등)
  4. ShadCN UI 컴포넌트로 나머지 구현
  5. 타이포그래피 클래스 사용하지 않기
  6. 반응형 확인 (모바일, 태블릿, 데스크톱)
  7. 다크모드 확인

기존 페이지 수정 시

  1. 현재 PageLayout 사용 여부 확인
  2. PageHeader 사용 여부 확인
  3. 커스텀 스타일 제거
  4. Organisms 컴포넌트로 교체
  5. 타이포그래피 클래스 제거

12. 참고 자료

  • 디자인 토큰: /styles/globals.css
  • 공통 컴포넌트: /components/organisms/, /components/molecules/
  • ShadCN UI: /components/ui/
  • 디자인 시스템 관리: "기준정보 > 디자인시스템" 메뉴

요약

핵심 원칙 3가지

  1. PageLayout + PageHeader는 필수
  2. 타이포그래피 클래스 사용 금지
  3. Organisms 컴포넌트 최대한 활용

이 가이드를 따르면 모든 페이지가 일관된 디자인을 유지할 수 있습니다.