From 556354219aa7c86bd5d71acf41c994fc1d222c0c Mon Sep 17 00:00:00 2001 From: kjs Date: Thu, 30 Oct 2025 12:03:50 +0900 Subject: [PATCH] =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/services/dynamicFormService.ts | 26 - docs/shadcn-ui-완전가이드.md | 1215 +++++++++++++++++ .../(main)/admin/ui-components-demo/page.tsx | 837 ++++++++++++ .../app/(main)/screens/[screenId]/page.tsx | 29 - frontend/app/globals.css | 392 ++++-- frontend/app/layout.tsx | 2 +- frontend/components/common/ScreenModal.tsx | 10 +- .../components/examples/ExampleFormDialog.tsx | 421 ++++++ .../components/screen/CreateScreenModal.tsx | 11 + frontend/components/screen/EditModal.tsx | 52 +- .../EnhancedInteractiveScreenViewer.tsx | 10 +- .../screen/InteractiveScreenViewer.tsx | 15 +- frontend/components/screen/ScreenDesigner.tsx | 6 +- .../config-panels/ButtonConfigPanel.tsx | 33 +- .../screen/panels/UnifiedPropertiesPanel.tsx | 10 +- .../components/screen/widgets/FlowWidget.tsx | 15 - .../screen/widgets/types/TextWidget.tsx | 2 +- frontend/components/ui/calendar.tsx | 5 +- frontend/components/ui/custom-calendar.tsx | 263 ++++ frontend/components/ui/input.tsx | 2 +- frontend/components/ui/select.tsx | 2 +- frontend/lib/api/dynamicForm.ts | 12 - .../button-primary/ButtonPrimaryComponent.tsx | 8 - .../registry/components/form-layout/index.ts | 47 + .../text-input/TextInputComponent.tsx | 15 - frontend/lib/utils/buttonActions.ts | 81 +- frontend/package-lock.json | 8 +- frontend/package.json | 2 +- 28 files changed, 3210 insertions(+), 321 deletions(-) create mode 100644 docs/shadcn-ui-완전가이드.md create mode 100644 frontend/app/(main)/admin/ui-components-demo/page.tsx create mode 100644 frontend/components/examples/ExampleFormDialog.tsx create mode 100644 frontend/components/ui/custom-calendar.tsx create mode 100644 frontend/lib/registry/components/form-layout/index.ts diff --git a/backend-node/src/services/dynamicFormService.ts b/backend-node/src/services/dynamicFormService.ts index b36baff5..8a943b96 100644 --- a/backend-node/src/services/dynamicFormService.ts +++ b/backend-node/src/services/dynamicFormService.ts @@ -229,14 +229,6 @@ export class DynamicFormService { ...actualData } = data; - console.log("🔍 [dynamicFormService] 받은 데이터:", { - 전체데이터: data, - writer, - company_code, - created_by, - updated_by, - }); - // 기본 데이터 준비 const dataToInsert: any = { ...actualData }; @@ -259,21 +251,12 @@ export class DynamicFormService { // 작성자 정보 추가 (writer 컬럼 우선, 없으면 created_by/updated_by) if (writer && tableColumns.includes("writer")) { - console.log(`✅ writer 추가: ${writer}`); dataToInsert.writer = writer; - } else { - console.log(`❌ writer 추가 실패:`, { - hasWriter: !!writer, - writerValue: writer, - hasColumn: tableColumns.includes("writer"), - }); } if (created_by && tableColumns.includes("created_by")) { - console.log(`✅ created_by 추가: ${created_by}`); dataToInsert.created_by = created_by; } if (updated_by && tableColumns.includes("updated_by")) { - console.log(`✅ updated_by 추가: ${updated_by}`); dataToInsert.updated_by = updated_by; } if (company_code && tableColumns.includes("company_code")) { @@ -299,18 +282,9 @@ export class DynamicFormService { `⚠️ company_code 길이 제한: 앞의 32자로 자름 -> "${processedCompanyCode}"` ); } - console.log(`✅ company_code 추가: ${processedCompanyCode}`); dataToInsert.company_code = processedCompanyCode; - } else { - console.log(`❌ company_code 추가 실패:`, { - hasCompanyCode: !!company_code, - companyCodeValue: company_code, - hasColumn: tableColumns.includes("company_code"), - }); } - console.log("🔍 [dynamicFormService] 최종 저장 데이터:", dataToInsert); - // 날짜/시간 문자열을 적절한 형태로 변환 Object.keys(dataToInsert).forEach((key) => { const value = dataToInsert[key]; diff --git a/docs/shadcn-ui-완전가이드.md b/docs/shadcn-ui-완전가이드.md new file mode 100644 index 00000000..0c575e76 --- /dev/null +++ b/docs/shadcn-ui-완전가이드.md @@ -0,0 +1,1215 @@ +# shadcn/ui 완전 구현 가이드 + +> 이 문서는 shadcn/ui 공식 문서(https://ui.shadcn.com)를 철저히 분석하여 작성되었습니다. + +## 목차 + +1. [설치 및 초기 설정](#1-설치-및-초기-설정) +2. [CSS 변수 및 테마 설정](#2-css-변수-및-테마-설정) +3. [컴포넌트별 구현 가이드](#3-컴포넌트별-구현-가이드) +4. [문제 해결 (Troubleshooting)](#4-문제-해결-troubleshooting) + +--- + +## 1. 설치 및 초기 설정 + +### 1.1 필수 패키지 설치 + +```bash +npm install tailwindcss@latest +npm install class-variance-authority clsx tailwind-merge +npm install @radix-ui/react-* # 필요한 Radix UI 컴포넌트들 +npm install lucide-react # 아이콘 +``` + +### 1.2 Tailwind CSS 설정 + +**중요**: shadcn/ui는 Tailwind CSS v4를 사용할 수 있지만, 기본적으로 v3 스타일을 따릅니다. + +```js +// tailwind.config.js (v3 스타일) +module.exports = { + darkMode: ["class"], + content: [ + "./pages/**/*.{ts,tsx}", + "./components/**/*.{ts,tsx}", + "./app/**/*.{ts,tsx}", + "./src/**/*.{ts,tsx}", + ], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; +``` + +**Tailwind CSS v4 스타일 (@theme 사용):** + +```css +/* app/globals.css */ +@import "tailwindcss"; + +@theme { + --color-background: hsl(var(--background)); + --color-foreground: hsl(var(--foreground)); + --color-card: hsl(var(--card)); + --color-card-foreground: hsl(var(--card-foreground)); + --color-primary: hsl(var(--primary)); + --color-primary-foreground: hsl(var(--primary-foreground)); + /* ... 나머지 색상 */ +} +``` + +### 1.3 globals.css 필수 설정 + +```css +/* app/globals.css */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --input: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} +``` + +**중요한 점:** + +1. CSS 변수는 **HSL 형식**으로 작성 (예: `222.2 47.4% 11.2%`) +2. `hsl()` 함수는 **사용하지 않음** (Tailwind가 자동으로 추가) +3. 공백으로 구분 (쉼표 없음) + +### 1.4 유틸리티 함수 (lib/utils.ts) + +```typescript +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} +``` + +이 함수는 **모든 shadcn/ui 컴포넌트에서 필수**입니다. + +--- + +## 2. CSS 변수 및 테마 설정 + +### 2.1 색상 시스템 이해 + +shadcn/ui는 **시맨틱 색상 시스템**을 사용합니다: + +| 색상 변수 | 용도 | 예시 | +| ---------------------- | ------------------------- | ---------------------------- | +| `--background` | 페이지 배경 | 흰색 (라이트), 검은색 (다크) | +| `--foreground` | 기본 텍스트 | 검은색 (라이트), 흰색 (다크) | +| `--primary` | 주요 액션 (버튼, 선택 등) | 진한 네이비/검은색 | +| `--primary-foreground` | Primary 위의 텍스트 | 흰색 | +| `--secondary` | 보조 액션 | 연한 회색 | +| `--accent` | 강조, 호버 효과 | 연한 파란색 | +| `--muted` | 비활성/보조 배경 | 밝은 회색 | +| `--muted-foreground` | 보조 텍스트 (설명 등) | 중간 회색 | +| `--destructive` | 삭제/에러 액션 | 빨간색 | +| `--border` | 테두리 | 밝은 회색 | +| `--input` | 입력 필드 테두리 | 밝은 회색 | +| `--ring` | 포커스 링 | 검은색 | + +### 2.2 HSL vs OKLCH 주의사항 + +**공식 shadcn/ui는 HSL 형식을 사용합니다:** + +```css +/* ✅ 올바른 방법 (HSL) */ +--primary: 222.2 47.4% 11.2%; + +/* ❌ 잘못된 방법 (OKLCH) */ +--primary: oklch(0.205 0 0); +``` + +만약 OKLCH를 사용하려면: + +1. 모든 컴포넌트에서 `hsl()` → `oklch()` 변경 필요 +2. Tailwind 설정 수정 필요 +3. 공식 문서와 다른 색상으로 보일 수 있음 + +**권장: 공식 HSL 형식 그대로 사용** + +### 2.3 Border Radius 설정 + +```css +:root { + --radius: 0.5rem; /* 8px, 공식 기본값 */ +} +``` + +컴포넌트에서 사용: + +- `rounded-lg`: `var(--radius)` = 8px +- `rounded-md`: `calc(var(--radius) - 2px)` = 6px +- `rounded-sm`: `calc(var(--radius) - 4px)` = 4px + +--- + +## 3. 컴포넌트별 구현 가이드 + +### 3.1 Button 컴포넌트 + +```tsx +// components/ui/button.tsx +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + } +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; +``` + +**사용법:** + +```tsx +import { Button } from "@/components/ui/button"; + + + + + + + +``` + +### 3.2 Calendar 컴포넌트 (자체 제작 - 완벽 구현) + +#### 3.2.1 문제점 및 해결 방안 + +**react-day-picker v9 문제:** + +- 요일 헤더 간격이 이상하게 보임 (MoTuWeThFrSa) +- 클래스명이 변경되어 스타일 적용이 어려움 +- `captionLayout="dropdown"`이 제대로 작동하지 않음 + +**해결책: 자체 Calendar 컴포넌트 제작** + +- shadcn/ui 스타일을 완벽하게 재현 +- 월/연도 드롭다운 선택 기능 내장 +- 크기 조절 가능 (`sm`, `default`, `lg`) + +#### 3.2.2 패키지 설치 + +```bash +# shadcn/ui 기본 컴포넌트만 필요 +npm install lucide-react +# react-day-picker는 불필요! +``` + +#### 3.2.2 컴포넌트 코드 + +**파일 위치:** `components/ui/custom-calendar.tsx` + +```tsx +"use client"; + +import * as React from "react"; +import { ChevronLeft, ChevronRight } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +interface CustomCalendarProps { + selected?: Date; + onSelect?: (date: Date | undefined) => void; + className?: string; + mode?: "single"; + size?: "sm" | "default" | "lg"; +} + +const DAYS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]; +const MONTHS = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +]; + +export function CustomCalendar({ + selected, + onSelect, + className, + mode = "single", + size = "default", +}: CustomCalendarProps) { + // 크기별 클래스 정의 + const sizeClasses = { + sm: { + cell: "h-7 w-7 text-xs", + header: "text-xs", + day: "text-[0.7rem]", + nav: "h-6 w-6", + }, + default: { + cell: "h-9 w-9 text-sm", + header: "text-[0.8rem]", + day: "text-sm", + nav: "h-7 w-7", + }, + lg: { + cell: "h-11 w-11 text-base", + header: "text-sm", + day: "text-base", + nav: "h-8 w-8", + }, + }; + + const currentSize = sizeClasses[size]; + const [currentDate, setCurrentDate] = React.useState(selected || new Date()); + const [viewYear, setViewYear] = React.useState(currentDate.getFullYear()); + const [viewMonth, setViewMonth] = React.useState(currentDate.getMonth()); + + const getDaysInMonth = (year: number, month: number) => { + return new Date(year, month + 1, 0).getDate(); + }; + + const getFirstDayOfMonth = (year: number, month: number) => { + return new Date(year, month, 1).getDay(); + }; + + const generateCalendarDays = () => { + const daysInMonth = getDaysInMonth(viewYear, viewMonth); + const firstDay = getFirstDayOfMonth(viewYear, viewMonth); + const daysInPrevMonth = getDaysInMonth(viewYear, viewMonth - 1); + + const days: Array<{ + date: number; + month: "prev" | "current" | "next"; + fullDate: Date; + }> = []; + + // Previous month days + for (let i = firstDay - 1; i >= 0; i--) { + const date = daysInPrevMonth - i; + days.push({ + date, + month: "prev", + fullDate: new Date(viewYear, viewMonth - 1, date), + }); + } + + // Current month days + for (let i = 1; i <= daysInMonth; i++) { + days.push({ + date: i, + month: "current", + fullDate: new Date(viewYear, viewMonth, i), + }); + } + + // Next month days + const remainingDays = 42 - days.length; + for (let i = 1; i <= remainingDays; i++) { + days.push({ + date: i, + month: "next", + fullDate: new Date(viewYear, viewMonth + 1, i), + }); + } + + return days; + }; + + const handlePrevMonth = () => { + if (viewMonth === 0) { + setViewMonth(11); + setViewYear(viewYear - 1); + } else { + setViewMonth(viewMonth - 1); + } + }; + + const handleNextMonth = () => { + if (viewMonth === 11) { + setViewMonth(0); + setViewYear(viewYear + 1); + } else { + setViewMonth(viewMonth + 1); + } + }; + + const handleDateClick = (date: Date) => { + if (onSelect) { + onSelect(date); + } + }; + + const isToday = (date: Date) => { + const today = new Date(); + return ( + date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear() + ); + }; + + const isSelected = (date: Date) => { + if (!selected) return false; + return ( + date.getDate() === selected.getDate() && + date.getMonth() === selected.getMonth() && + date.getFullYear() === selected.getFullYear() + ); + }; + + const calendarDays = generateCalendarDays(); + + return ( +
+ {/* Header with Month/Year Dropdowns */} +
+ + +
+ {/* Month Select */} + + + {/* Year Select */} + +
+ + +
+ + {/* Days of week */} +
+ {DAYS.map((day) => ( +
+ {day} +
+ ))} +
+ + {/* Calendar grid */} +
+ {calendarDays.map((day, index) => { + const isOutside = day.month !== "current"; + const isTodayDate = isToday(day.fullDate); + const isSelectedDate = isSelected(day.fullDate); + + return ( + + ); + })} +
+
+ ); +} + +CustomCalendar.displayName = "CustomCalendar"; +``` + +#### 3.2.3 주요 기능 + +**1. 월/연도 드롭다운 선택** + +- 월: January ~ December 전체 선택 가능 +- 연도: 현재 연도 ±50년 범위 (총 100년) +- shadcn/ui Select 컴포넌트 사용 + +**2. 크기 조절 (`size` prop)** + +- `sm`: 28px × 28px 셀 (작은 크기) +- `default`: 36px × 36px 셀 (기본 크기) +- `lg`: 44px × 44px 셀 (큰 크기) + +**3. 완벽한 요일 간격** + +- `grid grid-cols-7`로 정확히 7개 컬럼 +- Su Mo Tu We Th Fr Sa 간격 완벽 + +**4. shadcn/ui 스타일** + +- 선택된 날짜: Primary 배경 (검은색) +- 오늘 날짜: Accent 배경 (연한 파란색) +- 외부 날짜: Muted 색상, 50% 투명도 +- 호버 효과: Ghost 버튼 스타일 + +#### 3.2.4 기본 사용법 + +```tsx +import { CustomCalendar } from "@/components/ui/custom-calendar"; +import { useState } from "react"; + +export function CalendarDemo() { + const [date, setDate] = useState(new Date()); + + return ( + + ); +} +``` + +#### 3.2.5 크기 조절 + +```tsx +{ + /* 작은 크기 */ +} +; + +{ + /* 기본 크기 */ +} +; + +{ + /* 큰 크기 */ +} +; +``` + +#### 3.2.6 Date Picker (Popover 결합) + +```tsx +import { CustomCalendar } from "@/components/ui/custom-calendar"; +import { Button } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { useState } from "react"; + +export function DatePickerDemo() { + const [date, setDate] = useState(); + + return ( + + + + + + + + + ); +} +``` + +**장점:** + +- ✅ 월/연도 드롭다운 선택 가능 +- ✅ 완벽한 요일 간격 +- ✅ shadcn/ui 스타일 완벽 재현 +- ✅ `date-fns` 불필요 (순수 JavaScript) + +### 3.3 Dialog (Modal) 컴포넌트 + +#### 3.3.1 패키지 설치 + +```bash +npm install @radix-ui/react-dialog +``` + +#### 3.3.2 컴포넌트 코드 + +```tsx +// components/ui/dialog.tsx +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; +const DialogTrigger = DialogPrimitive.Trigger; +const DialogPortal = DialogPrimitive.Portal; +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; +``` + +#### 3.3.3 표준 사용 패턴 + +```tsx +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; + +export function DialogDemo() { + return ( + + + + + + + 프로필 수정 + + 프로필 정보를 수정합니다. 완료되면 저장을 클릭하세요. + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ ); +} +``` + +--- + +## 4. 문제 해결 (Troubleshooting) + +### 4.1 Calendar가 이상하게 보이는 경우 + +#### 증상 1: 드롭다운이 두 개로 보임 + +- **원인**: `react-day-picker` v8 스타일과 v9 스타일 혼용 +- **해결**: + ```tsx + // 올바른 v9 스타일 classNames 사용 + classNames={{ + caption: "flex justify-center pt-1 relative items-center", + caption_label: "text-sm font-medium", + // ... v9 클래스명 사용 + }} + ``` + +#### 증상 2: 선택된 날짜가 파란 테두리로 보임 + +- **원인**: `react-day-picker/style.css`를 import함 +- **해결**: + + ```css + /* ❌ 제거해야 함 */ + @import "react-day-picker/style.css"; + + /* ✅ shadcn/ui는 자체 Tailwind 스타일만 사용 */ + ``` + +#### 증상 3: 색상이 공식 문서와 다름 + +- **원인**: CSS 변수가 잘못 설정됨 +- **해결**: + + ```css + /* ✅ 올바른 HSL 형식 */ + --primary: 222.2 47.4% 11.2%; + + /* ❌ 잘못된 OKLCH 형식 */ + --primary: oklch(0.205 0 0); + ``` + +#### 증상 4: 드롭다운 화살표가 없음 + +- **원인**: `captionLayout="dropdown"`을 사용했지만 커스텀 CSS가 없음 +- **해결**: `react-day-picker` v9는 자동으로 네이티브 ` +
+ + {/* Textarea */} +
+ +