From 9e96793c41e220bb3f9c943b4475a79affced12e Mon Sep 17 00:00:00 2001 From: nelee Date: Tue, 12 May 2026 17:00:23 +0900 Subject: [PATCH] =?UTF-8?q?fix(compat):=20=ED=81=AC=EB=A1=9C=EC=8A=A4=20?= =?UTF-8?q?=EB=B8=8C=EB=9D=BC=EC=9A=B0=EC=A0=80=20=ED=98=B8=ED=99=98?= =?UTF-8?q?=EC=84=B1=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Modal: createPortal + z-200/201로 헤더(z-50) 위에 올바르게 표시 - Modal: fixed 중첩 제거로 Mac/Windows stacking context 차이 해결 - globals.css: scrollbar-gutter:stable 추가로 OS별 레이아웃 일치 - globals.css: -webkit-overflow-scrolling:touch deprecated 제거 - globals.css: oklch() 색상에 hex 폴백 추가 (Safari < 15.4) - Textarea: field-sizing:content 제거 → scrollHeight 기반 자동 높이 - ProjectCreationModal/MemoView: supports-backdrop-filter 분기 처리 Co-Authored-By: Claude Sonnet 4.6 --- src/app/globals.css | 48 +++++- src/app/layout.tsx | 2 +- .../features/kanban/KanbanBoard.tsx | 6 +- src/components/features/kanban/MemoView.tsx | 4 +- src/components/ui/Modal.tsx | 153 +++++++++--------- src/components/ui/shadcn/Textarea.tsx | 22 ++- .../project/ui/ProjectCreationModal.tsx | 2 +- 7 files changed, 146 insertions(+), 91 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index d96e7b4..0bbabcb 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -93,10 +93,15 @@ --border: #BDE3EC; --input: #FFFFFF; --ring: #5BBECE; + --chart-1: #c2622d; --chart-1: oklch(0.646 0.222 41.116); + --chart-2: #3a9e94; --chart-2: oklch(0.6 0.118 184.704); + --chart-3: #2e5e8e; --chart-3: oklch(0.398 0.07 227.392); + --chart-4: #d4b84a; --chart-4: oklch(0.828 0.189 84.429); + --chart-5: #c8a030; --chart-5: oklch(0.769 0.188 70.08); --sidebar: #EEF7F9; --sidebar-foreground: #1A4A5A; @@ -111,6 +116,30 @@ --dark-body: #1A4A5A; --dark-description: #3A8A9A; --dark-border: #BDE3EC; + + /* 유체 타이포그래피: clamp(최솟값, 뷰포트 기반 선호값, 최댓값) */ + --fs-xs: clamp(0.6875rem, 0.0625vw + 0.675rem, 0.75rem); + --fs-sm: clamp(0.8125rem, 0.0625vw + 0.8rem, 0.875rem); + --fs-base: clamp(0.875rem, 0.125vw + 0.85rem, 1rem); + --fs-lg: clamp(1rem, 0.125vw + 0.975rem, 1.125rem); + --fs-xl: clamp(1.125rem, 0.125vw + 1.1rem, 1.25rem); + --fs-2xl: clamp(1.25rem, 0.25vw + 1.2rem, 1.5rem); + + /* 유체 간격: 동일 공식으로 저해상도↔고해상도 자연스럽게 전환 */ + --sp-2: clamp(0.375rem, 0.125vw + 0.35rem, 0.5rem); + --sp-3: clamp(0.625rem, 0.125vw + 0.6rem, 0.75rem); + --sp-4: clamp(0.875rem, 0.125vw + 0.85rem, 1rem); + --sp-6: clamp(1.25rem, 0.25vw + 1.2rem, 1.5rem); + --sp-8: clamp(1.5rem, 0.5vw + 1.4rem, 2rem); + + /* 컴포넌트별 유체 값 */ + --fluid-gap-kanban: clamp(0.75rem, 0.75vw + 0.6rem, 1.5rem); + --fluid-px-outer: clamp(0.5rem, 1.25vw, 1rem); + --fluid-py-outer: clamp(0.5rem, 0.625vw + 0.25rem, 0.75rem); + --fluid-modal-py: clamp(1.5rem, 1.25vw + 1.25rem, 2.25rem); + --fluid-modal-px: clamp(1.25rem, 0.85vw + 1.05rem, 1.75rem); + --fluid-modal-min-h: clamp(12rem, 40vh, 17rem); + --fluid-textarea-p: clamp(0.75rem, 0.4vw + 0.675rem, 1rem); } .dark { @@ -133,10 +162,15 @@ --border: #2A6070; --input: #1A4A5A; --ring: #6DD5E8; + --chart-1: #3a6fd8; --chart-1: oklch(0.488 0.243 264.376); + --chart-2: #4db89a; --chart-2: oklch(0.696 0.17 162.48); + --chart-3: #c8a030; --chart-3: oklch(0.769 0.188 70.08); + --chart-4: #9b4fd8; --chart-4: oklch(0.627 0.265 303.9); + --chart-5: #d84a3a; --chart-5: oklch(0.645 0.246 16.439); --sidebar: #0D2530; --sidebar-foreground: #E8F5F7; @@ -154,6 +188,14 @@ } @layer base { + html { + /* Windows 고정 스크롤바(15-17px)로 인한 레이아웃 밀림 방지 */ + scrollbar-gutter: stable; + } + *, *::before, *::after { + /* OS별 box model 차이 통일 */ + box-sizing: border-box; + } * { @apply border-border outline-ring/50; } @@ -162,6 +204,11 @@ } } +/* 스크롤바 공간 예약 — Mac/Windows 레이아웃 일치 */ +.scrollbar-gutter-stable { + scrollbar-gutter: stable; +} + /* ========================================================================== React Calendar (react-calendar) ========================================================================== */ @@ -633,7 +680,6 @@ overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; - -webkit-overflow-scrolling: touch; scrollbar-width: thin; scrollbar-color: var(--color-gray-300) transparent; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d89a363..e8627fe 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -38,7 +38,7 @@ export default function RootLayout({
-
{children}
+
{children}
diff --git a/src/components/features/kanban/KanbanBoard.tsx b/src/components/features/kanban/KanbanBoard.tsx index fd272a2..f38260f 100644 --- a/src/components/features/kanban/KanbanBoard.tsx +++ b/src/components/features/kanban/KanbanBoard.tsx @@ -454,10 +454,10 @@ function ColumnGrid({ isDragging: boolean; }) { return ( -
+
-
-
+
+
{KANBAN_COLUMNS.map((column) => ( { placeholder="메모를 입력하세요..." disabled={loadingMemos} rows={3} - className="w-full p-3 sm:p-4 pr-8 sm:pr-10 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900/50 rounded-xl resize-none text-sm input-focus-style transition-all placeholder:text-gray-400 dark:placeholder:text-gray-500 text-gray-900 dark:text-gray-100 disabled:opacity-50" + className="w-full pt-(--fluid-textarea-p) pb-(--fluid-textarea-p) pl-(--fluid-textarea-p) pr-8 sm:pr-10 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900/50 rounded-xl resize-none text-sm input-focus-style transition-all placeholder:text-gray-400 dark:placeholder:text-gray-500 text-gray-900 dark:text-gray-100 disabled:opacity-50" /> {newMemo && ( - - {/* 텍스트 또는 커스텀 컨텐츠 */} -
- {children ? ( - children - ) : ( - <> -

- {finalTitle} -

-

- {finalDescription} -

+ {/* modal */} +
+
+ {config?.icon && ( +
+ +
+ )} + - {finalWarning && ( -

- {finalWarning} +

+ {children ? ( + children + ) : ( + <> +

+ {finalTitle} +

+

+ {finalDescription}

- )} - {config?.info && ( -

+ {finalWarning} +

+ )} + + {config?.info && ( +

+ {config.info} +

+ )} + + )} +
+ + {(config?.buttonCancelText || config?.buttonConfirmText) && ( +
+ {config?.buttonCancelText && ( + + )} + {config?.buttonConfirmText && ( + )} - +
)}
- - {/* 버튼 */} - {(config?.buttonCancelText || config?.buttonConfirmText) && ( -
- {config?.buttonCancelText && ( - - )} - {config?.buttonConfirmText && ( - - )} -
- )}
-
+ , + document.body ); } diff --git a/src/components/ui/shadcn/Textarea.tsx b/src/components/ui/shadcn/Textarea.tsx index 8c1c2df..20e4374 100644 --- a/src/components/ui/shadcn/Textarea.tsx +++ b/src/components/ui/shadcn/Textarea.tsx @@ -2,14 +2,32 @@ import * as React from "react"; import { cn } from "@/lib/utils/utils"; -function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { +function Textarea({ className, onChange, ...props }: React.ComponentProps<"textarea">) { + const ref = React.useRef(null); + + const autoResize = (el: HTMLTextAreaElement) => { + el.style.height = "auto"; + el.style.height = `${el.scrollHeight}px`; + }; + + React.useEffect(() => { + if (ref.current) autoResize(ref.current); + }, [props.value, props.defaultValue]); + + const handleChange = (e: React.ChangeEvent) => { + autoResize(e.target); + onChange?.(e); + }; + return (