diff --git a/apps/wr/docs/작업계획/작업계획_20260623.md b/apps/wr/docs/작업계획/작업계획_20260623.md index e0b106b9d..cad73ed99 100644 --- a/apps/wr/docs/작업계획/작업계획_20260623.md +++ b/apps/wr/docs/작업계획/작업계획_20260623.md @@ -3,13 +3,17 @@ 작성일: 2026-06-23 대상 앱: `D:\iipbs\frontend\apps\wr` +수정불가 component : `packages/common/components`와 `packages/common/assets` +수정가능 파일 : `packages/common/business_components/*`, `apps/wr/*` + ## 1. 분석 요약 - 현재 테마 구조는 이미 `data-theme` 기반으로 잡혀 있다. - 실제 테마 키는 `default + black + blue + green + red` 기준으로 운영되고 있다. - `apps/wr`는 `src/app/index.tsx`에서 `site-theme-key` 쿠키값을 읽어 ``에 주입한다. - `packages/common/components`와 `packages/common/assets`는 CSS 변수와 컴포넌트별 `.css` 파일을 통해 테마를 적용하는 구조다. -- 반면 `packages/common/business_components`는 다수의 UI가 TSX 내부 Tailwind 클래스에 직접 의존하고 있어 테마 일관성이 약하다. +- 이번 작업의 전제는 `business_components`가 1차로 `assets`를 참조하고, 2차로 관련 `components`의 CSS 변경을 따라가야 한다는 점이다. +- 반면 현재 `packages/common/business_components`는 다수의 UI가 TSX 내부 Tailwind 클래스에 직접 의존하고 있어 위 전제를 충분히 만족하지 못한다. - `apps/wr` 내부에서 `@common/business_components`를 참조하는 파일 수가 많아 영향 범위가 크다. ## 2. 확인된 현재 구조 @@ -42,7 +46,7 @@ ### 3.1 CSS 전환 가능 여부 - 가능하다. -- 다만 "모든 Tailwind를 제거"가 아니라 "테마 의존 스타일을 CSS로 추출"하는 방향이 맞다. +- 다만 "모든 Tailwind를 제거"가 아니라 "테마 의존 스타일을 CSS로 추출하고, `assets` 및 `components` 참조 구조에 편입"하는 방향이 맞다. - 즉 배경색, 글자색, 보더색, hover/active 색상, 선택 상태, 강조색은 CSS 변수 기반 클래스로 이동한다. - 반대로 `flex`, `grid`, `gap`, `w-*`, `h-*`, `p-*` 같은 구조/간격 유틸리티까지 전부 CSS로 옮기는 것은 효율이 낮다. @@ -50,9 +54,19 @@ - 기존 `components`가 이미 같은 패턴을 사용한다. - `assets/base.css`의 토큰(`--background`, `--foreground`, `--primary`, `--border`, `--label`, `--container`, `--title` 등)을 재사용할 수 있다. +- `business_components`는 `주 component가 바뀌면 따라 바뀌어야 한다`는 전제가 있으므로, 자체 색상 하드코딩보다 상위 스타일 자산 참조가 우선되어야 한다. +- 따라서 `business_components` 스타일은 독립 규격이 아니라 `assets -> components -> business_components` 흐름 안에서 정리되어야 한다. - `apps/wr`는 `business_components` 사용 범위가 넓어, 테마 속성만 먼저 추출해도 화면 일관성이 크게 개선된다. - 전부 TSX 내부 유틸리티로 유지하면 5개 테마 차이를 한 번에 통제하기 어렵다. +### 3.3 참조 원칙 + +- 1차 기준: `@common/assets/styles/*` +- 2차 기준: 관련 `@common/components` 하위 CSS +- 3차 보완: 위 두 곳으로 흡수되지 않는 business 전용 표현만 `business_components` 내부 CSS에 작성 + +즉 `business_components`는 가능한 한 직접 색상을 소유하지 않고, 상위 레이어가 정의한 테마 결과를 따라가는 구조로 맞춘다. + ## 4. 우선 전환 대상 ### 4.1 1순위: 색상 하드코딩이 직접 보이는 공통 UI @@ -99,8 +113,12 @@ ### 단계 1. 테마 기준 정리 +- `business_components`의 참조 순서를 먼저 고정한다. + - 1차: `assets` 토큰/공통 스타일 참조 + - 2차: 관련 `components` CSS 참조 + - 3차: business 전용 CSS 최소 추가 - `components`에서 이미 쓰는 토큰과 클래스 패턴을 기준 문법으로 고정한다. -- `business_components`에서 새로 쓰는 색상 클래스는 직접 색상값을 쓰지 말고 CSS 변수 기반으로 통일한다. +- `business_components`에서 새로 쓰는 색상 클래스는 직접 색상값을 쓰지 말고, 우선 상위 참조 자산으로 해결 가능한지 확인한 뒤 CSS 변수 기반으로 통일한다. - 예시 기준 - 텍스트: `text-foreground`, `text-label`, `text-title` - 배경: `bg-background`, `bg-card`, `bg-container`, `bg-form` @@ -109,7 +127,14 @@ ### 단계 2. 컴포넌트별 CSS 파일 추가 -- `components`와 동일하게 각 컴포넌트 폴더에 `.css`를 둔다. +- 먼저 관련 `components`에 이미 존재하는 CSS가 있는지 확인한다. +- 같은 성격의 UI라면 `business_components`가 그 CSS 결과를 따라가도록 구조를 맞춘다. +- 상위 참조만으로 해결되지 않는 경우에만 `components`와 동일하게 각 컴포넌트 폴더에 `.css`를 둔다. +- CSS 파일명은 `packages/common/components`와 동일한 규칙으로 작성한다. +- 즉 CSS 파일명은 컴포넌트 폴더/컴포넌트명 기준의 kebab-case를 유지한다. +- CSS 클래스명은 너무 길게 만들지 않는다. +- 네이밍은 `__` 대신 `_`를 사용한다. +- 컴포넌트명도 필요 이상으로 길게 쓰지 않고 축약형을 허용한다. - 예시 - `file-upload/file-upload.css` - `tab/tab.css` @@ -117,17 +142,19 @@ - `select/select.css` - `search_address/search-address.css` - TSX에서는 색상/상태 관련 className을 의미 있는 클래스명으로 치환한다. +- 이때 business 전용 CSS는 상위 CSS와 충돌하지 않도록 override 범위를 최소화한다. 예시 방향 - 변경 전: `text-gray-500`, `bg-white`, `hover:bg-gray-50`, `data-[state=active]:text-blue-600` -- 변경 후: `file-upload__hint`, `tab-trigger`, `search-address__suggestion`, `is-active` +- 변경 후: `fileup_hint`, `tab_trigger`, `searchaddr_suggestion`, `is_active` ### 단계 3. 테마별 분기 작성 - 기본값은 `:root` 또는 기본 클래스에서 처리한다. - 테마 차이가 필요한 경우만 `[data-theme="black"]`, `[data-theme="blue"]`, `[data-theme="green"]`, `[data-theme="red"]` 블록을 추가한다. - 동일한 값은 중복 정의하지 않는다. +- 가능하면 테마 값은 `assets` 또는 `components`에서 정의하고, `business_components`는 해당 결과를 소비하는 쪽으로 유지한다. ### 단계 4. apps/wr 화면 검증 @@ -141,15 +168,17 @@ ## 6. 실제 작업 순서 1. `business_components` 내 테마 하드코딩 파일 목록 확정 -2. 공통 토큰 매핑표 작성 -3. 1순위 UI에 CSS 파일 추가 및 TSX class 정리 -4. `apps/wr` 주요 화면에서 5개 테마 수동 점검 -5. 2순위 테이블/검색/모달 영역 확장 -6. 차트 계열 색상 토큰화 -7. 지도 legacy 영역은 별도 보수 계획으로 분리 +2. 각 대상 UI별로 `assets` 참조와 `components` 참조 가능 여부를 먼저 판정 +3. 공통 토큰 매핑표 작성 +4. 1순위 UI에 상위 참조 우선 적용 후 부족한 부분만 business 전용 CSS 추가 +5. `apps/wr` 주요 화면에서 5개 테마 수동 점검 +6. 2순위 테이블/검색/모달 영역 확장 +7. 차트 계열 색상 토큰화 +8. 지도 legacy 영역은 별도 보수 계획으로 분리하며 배경지도는 수정하지 않는다. ## 7. 검증 기준 +- `assets` , `components`가 수정되면 안된다. - 같은 화면에서 5개 테마 전환 시 텍스트, 보더, 배경, hover, 선택상태가 모두 읽혀야 한다. - black 테마에서 회색/흰색 하드코딩이 남아 흐릿하거나 과도하게 튀는 영역이 없어야 한다. - blue/green/red 테마에서 활성 탭, 버튼, 선택상태가 `primary` 계열로 일관되게 바뀌어야 한다. @@ -157,6 +186,6 @@ ## 8. 결론 -- `business_components`의 Tailwind 스타일을 `components`와 같은 방식의 CSS 중심 구조로 전환하는 것은 가능하다. -- 다만 전체 유틸리티를 전부 CSS로 옮기기보다, 테마에 영향을 주는 색상/상태 표현만 우선 추출하는 방식이 가장 현실적이다. +- `business_components`의 Tailwind 스타일을 `assets` 1차 참조, `components` 2차 참조 구조에 맞춰 재편하는 것은 가능하다. +- 다만 전체 유틸리티를 전부 CSS로 옮기기보다, 테마에 영향을 주는 색상/상태 표현을 우선 상위 자산 참조 구조로 흡수하는 방식이 가장 현실적이다. - 우선순위는 `공통 입력 UI -> 테이블/검색/모달 -> 차트 -> 지도 legacy` 순으로 잡는 것이 좋다. diff --git a/packages/common/business_components/src/ui/file-upload/file-upload.css b/packages/common/business_components/src/ui/file-upload/file-upload.css new file mode 100644 index 000000000..9a8737063 --- /dev/null +++ b/packages/common/business_components/src/ui/file-upload/file-upload.css @@ -0,0 +1,67 @@ +@tailwind components; + +@layer components { + .fileup_root { + @apply relative rounded-lg bg-background p-2; + } + + .fileup_dropzone { + @apply border border-border outline-dashed outline-1 outline-border; + } + + .fileup_panel { + @apply flex w-full flex-col items-center justify-center p-1; + } + + .fileup_icon { + @apply h-4 w-5 text-label; + } + + .fileup_hint { + @apply mb-1 text-sm text-label; + } + + .fileup_hint_strong { + @apply font-semibold text-foreground; + } + + .fileup_meta { + @apply text-xs text-label; + } + + .fileup_item { + @apply text-foreground; + } + + .fileup_item_active { + @apply bg-muted; + } + + .fileup_item_name { + @apply flex h-full w-4/5 items-center gap-1.5 overflow-hidden font-medium leading-normal tracking-tight; + } + + .fileup_item_remove { + @apply text-label; + } + + .fileup_item_remove_icon { + @apply h-4 w-4 transition-colors duration-200 ease-in-out hover:stroke-destructive; + } + + .fileup_input { + @apply border; + } + + .fileup_input_idle { + @apply border-border; + } + + .fileup_input_accept { + @apply border-primary; + } + + .fileup_input_reject { + @apply border-destructive; + } +} diff --git a/packages/common/business_components/src/ui/file-upload/index.tsx b/packages/common/business_components/src/ui/file-upload/index.tsx index 6836691e2..ae6e8cc55 100644 --- a/packages/common/business_components/src/ui/file-upload/index.tsx +++ b/packages/common/business_components/src/ui/file-upload/index.tsx @@ -1,4 +1,6 @@ "use client"; +import "./file-upload.css"; + import { CloudUpload, Paperclip } from "lucide-react"; import { FileInput, FileUploader, FileUploaderContent, FileUploaderItem } from "./lib/file_upload"; @@ -36,19 +38,19 @@ export const FileUpload = onValueChange={setFiles} dropzoneOptions={dropZoneConfig} acceptName={fileTypes} - className="relative bg-background rounded-lg p-2" + className="fileup_root" > -
- -

- 클릭 후 업로드 +

+ +

+ 클릭 후 업로드   혹은 파일을 드래그 해주세요

-

+

{!fileTypes?"SVG, PNG, JPG, GIF":fileTypes}

diff --git a/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx b/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx index 9e47d296e..f0071748e 100644 --- a/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx +++ b/packages/common/business_components/src/ui/file-upload/lib/file_upload.tsx @@ -344,14 +344,14 @@ export const FileUploaderItem = forwardRef< ref={ref} className={cn( buttonVariants({ variant: "ghost" }), - "h-9 justify-between cursor-pointer relative w-full", + "fileup_item h-9 justify-between cursor-pointer relative w-full", className, - isSelected ? "bg-muted" : "", + isSelected ? "fileup_item_active" : "", )} {...props} > -
{onFileClick?.(index) }}> {children} @@ -359,7 +359,7 @@ export const FileUploaderItem = forwardRef<
); @@ -396,14 +396,12 @@ export const FileInput = forwardRef< >
{ label?: string; @@ -29,17 +31,19 @@ const FormUi = React.forwardRef( }, [onCheckboxChange]); return ( - {label && {label}} + {label && {label}} {label === BZNM_TOT_NO && ( <> -
-
- +
+ - +
{children} @@ -48,14 +52,14 @@ const FormUi = React.forwardRef( {label === CANDIDATE && ( {children} - + )} {label !== BZNM_TOT_NO && label !== CANDIDATE && ( {React.isValidElement(children) && isDisabled - ? React.cloneElement(children, { + ? React.cloneElement(children, { disabled: isDisabled, - ...(children.props as FormElementProps) + ...(children.props as FormElementProps), }) : children} @@ -66,5 +70,5 @@ const FormUi = React.forwardRef( } ); -FormUi.displayName = 'FormUi'; -export { FormUi }; \ No newline at end of file +FormUi.displayName = "FormUi"; +export { FormUi }; diff --git a/packages/common/business_components/src/ui/search_icon/index.tsx b/packages/common/business_components/src/ui/search_icon/index.tsx index 0ef0e17e3..5c608354f 100644 --- a/packages/common/business_components/src/ui/search_icon/index.tsx +++ b/packages/common/business_components/src/ui/search_icon/index.tsx @@ -1,3 +1,5 @@ +import "./search-icon.css"; + export default function SearchIcon() { return ( ); - } \ No newline at end of file + } diff --git a/packages/common/business_components/src/ui/search_icon/search-icon.css b/packages/common/business_components/src/ui/search_icon/search-icon.css new file mode 100644 index 000000000..b0ebb1f0a --- /dev/null +++ b/packages/common/business_components/src/ui/search_icon/search-icon.css @@ -0,0 +1,7 @@ +@tailwind components; + +@layer components { + .searchicon_svg { + @apply h-6 w-6 text-label; + } +} diff --git a/packages/common/business_components/src/ui/search_input/index.tsx b/packages/common/business_components/src/ui/search_input/index.tsx index 503322559..e7be9f75d 100644 --- a/packages/common/business_components/src/ui/search_input/index.tsx +++ b/packages/common/business_components/src/ui/search_input/index.tsx @@ -1,5 +1,8 @@ +import "./search-input.css"; + import { Input, Label } from "@common/components"; import React from "react"; +import { cn } from "../../lib/utils"; const SearchInput = React.forwardRefvoid}>(({className, label, textValue, setText}, ref) => { const changeText=(e:React.ChangeEvent)=>{ @@ -20,9 +23,9 @@ const SearchInput = React.forwardRef - {label && } - +
+ {label && } +
) }); diff --git a/packages/common/business_components/src/ui/search_input/search-input.css b/packages/common/business_components/src/ui/search_input/search-input.css new file mode 100644 index 000000000..f272abc7e --- /dev/null +++ b/packages/common/business_components/src/ui/search_input/search-input.css @@ -0,0 +1,15 @@ +@tailwind components; + +@layer components { + .searchinput_root { + @apply flex items-center; + } + + .searchinput_label { + @apply w-20 text-xl; + } + + .searchinput_field { + @apply h-14 w-3/4 flex-1 rounded-md border border-input text-lg; + } +} diff --git a/packages/common/business_components/src/ui/search_input_select/index.tsx b/packages/common/business_components/src/ui/search_input_select/index.tsx index bf8787356..8632eac78 100644 --- a/packages/common/business_components/src/ui/search_input_select/index.tsx +++ b/packages/common/business_components/src/ui/search_input_select/index.tsx @@ -1,6 +1,9 @@ +import "./search-input-select.css"; + import { Input, Label } from "@common/components"; import React from "react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select/lib/selectBox"; +import { cn } from "../../lib/utils"; export type SelectDataType = { text: string; val: string; @@ -23,12 +26,12 @@ const SearchInputSelect = React.forwardRef - {label && } -
- +
+ {label && } +
+