wr: business_component css 수정1
This commit is contained in:
@@ -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` 쿠키값을 읽어 `<html data-theme=...>`에 주입한다.
|
||||
- `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` 순으로 잡는 것이 좋다.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
>
|
||||
<FileInput
|
||||
id="fileInput"
|
||||
className="outline-dashed outline-1 outline-slate-500"
|
||||
className="fileup_dropzone"
|
||||
>
|
||||
<div className="flex items-center justify-center flex-col p-1 w-full ">
|
||||
<CloudUpload className='text-gray-500 w-5 h-4' />
|
||||
<p className="mb-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span className="font-semibold">클릭 후 업로드 </span>
|
||||
<div className="fileup_panel">
|
||||
<CloudUpload className='fileup_icon' />
|
||||
<p className="fileup_hint">
|
||||
<span className="fileup_hint_strong">클릭 후 업로드 </span>
|
||||
혹은 파일을 드래그 해주세요
|
||||
</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">
|
||||
<p className="fileup_meta">
|
||||
{!fileTypes?"SVG, PNG, JPG, GIF":fileTypes}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
|
||||
>
|
||||
<div className="font-medium leading-normal tracking-tight flex items-center gap-1.5 h-full !w-4/5 overflow-hidden "
|
||||
<div className="fileup_item_name"
|
||||
onClick={() =>{onFileClick?.(index)
|
||||
}}>
|
||||
{children}
|
||||
@@ -359,7 +359,7 @@ export const FileUploaderItem = forwardRef<
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
"absolute",
|
||||
"fileup_item_remove absolute",
|
||||
"!w-1/5 z-50",
|
||||
direction === "rtl"
|
||||
? "left-1 top-1/2 -translate-y-1/2"
|
||||
@@ -372,7 +372,7 @@ export const FileUploaderItem = forwardRef<
|
||||
removeFileFromSet(index)}}
|
||||
>
|
||||
<span className="sr-only">remove item {index}</span>
|
||||
<RemoveIcon className="w-4 h-4 hover:stroke-destructive duration-200 ease-in-out" />
|
||||
<RemoveIcon className="fileup_item_remove_icon" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
@@ -396,14 +396,12 @@ export const FileInput = forwardRef<
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
`w-full rounded-lg duration-300 ease-in-out
|
||||
${
|
||||
dropzoneState.isDragAccept
|
||||
? "border-green-500"
|
||||
: dropzoneState.isDragReject || isFileTooBig
|
||||
? "border-red-500"
|
||||
: "border-gray-300"
|
||||
}`,
|
||||
"fileup_input w-full rounded-lg duration-300 ease-in-out",
|
||||
dropzoneState.isDragAccept && "fileup_input_accept",
|
||||
(dropzoneState.isDragReject || isFileTooBig) && "fileup_input_reject",
|
||||
!dropzoneState.isDragAccept &&
|
||||
!(dropzoneState.isDragReject || isFileTooBig) &&
|
||||
"fileup_input_idle",
|
||||
className,
|
||||
)}
|
||||
{...rootProps}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.formui_label {
|
||||
@apply text-xl;
|
||||
}
|
||||
|
||||
.formui_option_wrap {
|
||||
@apply float-right;
|
||||
}
|
||||
|
||||
.formui_option {
|
||||
@apply flex items-center gap-2;
|
||||
}
|
||||
|
||||
.formui_option_label {
|
||||
@apply w-28 text-xl text-label;
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import "./form-ui.css";
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { FormControl, FormItem, FormLabel, FormMessage } from "../form";
|
||||
import { Button, Checkbox } from "@common/components/ui";
|
||||
import { Checkbox } from "@common/components/ui";
|
||||
|
||||
export const BZNM_TOT_NO = '종사업자등록번호';
|
||||
export const CANDIDATE = '공급처 후보지 선택(필수아님)';
|
||||
export const BZNM_TOT_NO = "종사업자등록번호";
|
||||
export const CANDIDATE = "공급처 후보지 선택(필수아님)";
|
||||
|
||||
interface FormUiProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
label?: string;
|
||||
@@ -29,17 +31,19 @@ const FormUi = React.forwardRef<HTMLDivElement, FormUiProps>(
|
||||
}, [onCheckboxChange]);
|
||||
return (
|
||||
<FormItem ref={ref}>
|
||||
{label && <FormLabel className="text-xl">{label}</FormLabel>}
|
||||
{label && <FormLabel className="formui_label">{label}</FormLabel>}
|
||||
{label === BZNM_TOT_NO && (
|
||||
<>
|
||||
<div className="float-right">
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox
|
||||
id="check"
|
||||
<div className="formui_option_wrap">
|
||||
<div className="formui_option">
|
||||
<Checkbox
|
||||
id="check"
|
||||
checked={isChecked}
|
||||
onCheckedChange={handleCheckboxChange}
|
||||
/>
|
||||
<label className="w-28 text-xl text-gray-600" htmlFor="check">해당없음</label>
|
||||
<label className="formui_option_label" htmlFor="check">
|
||||
해당없음
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
@@ -48,14 +52,14 @@ const FormUi = React.forwardRef<HTMLDivElement, FormUiProps>(
|
||||
{label === CANDIDATE && (
|
||||
<FormControl>
|
||||
{children}
|
||||
</FormControl>
|
||||
</FormControl>
|
||||
)}
|
||||
{label !== BZNM_TOT_NO && label !== CANDIDATE && (
|
||||
<FormControl>
|
||||
{React.isValidElement(children) && isDisabled
|
||||
? React.cloneElement(children, {
|
||||
? React.cloneElement(children, {
|
||||
disabled: isDisabled,
|
||||
...(children.props as FormElementProps)
|
||||
...(children.props as FormElementProps),
|
||||
})
|
||||
: children}
|
||||
</FormControl>
|
||||
@@ -66,5 +70,5 @@ const FormUi = React.forwardRef<HTMLDivElement, FormUiProps>(
|
||||
}
|
||||
);
|
||||
|
||||
FormUi.displayName = 'FormUi';
|
||||
export { FormUi };
|
||||
FormUi.displayName = "FormUi";
|
||||
export { FormUi };
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./search-icon.css";
|
||||
|
||||
export default function SearchIcon() {
|
||||
return (
|
||||
<svg
|
||||
@@ -6,7 +8,7 @@ export default function SearchIcon() {
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className="w-6 h-6 text-gray-700"
|
||||
className="searchicon_svg"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
@@ -15,4 +17,4 @@ export default function SearchIcon() {
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.searchicon_svg {
|
||||
@apply h-6 w-6 text-label;
|
||||
}
|
||||
}
|
||||
@@ -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.forwardRef<HTMLInputElement, {className:string, label:string, textValue:string, setText:(val:string)=>void}>(({className, label, textValue, setText}, ref) => {
|
||||
const changeText=(e:React.ChangeEvent<HTMLInputElement>)=>{
|
||||
@@ -20,9 +23,9 @@ const SearchInput = React.forwardRef<HTMLInputElement, {className:string, label:
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className+" flex items-center"} >
|
||||
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
|
||||
<Input className="!text-lg flex-1 w-3/4 h-13 border rounded" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
|
||||
<div className={cn("searchinput_root", className)} >
|
||||
{label && <Label className="searchinput_label">{label}</Label>}
|
||||
<Input ref={ref} className="searchinput_field" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
|
||||
</div>
|
||||
)
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<HTMLInputElement, {className:string,
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className+" flex items-center"} >
|
||||
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
|
||||
<div className="flex w-full items-center">
|
||||
<Input className="w-2/3 text-xl h-14 p-2 border rounded" value={textValue} onChange={changeText} />
|
||||
<div className={cn("searchinputselect_root", className)} >
|
||||
{label && <Label className="searchinputselect_label">{label}</Label>}
|
||||
<div className="searchinputselect_controls">
|
||||
<Input ref={ref} className="searchinputselect_input" value={textValue} onChange={changeText} />
|
||||
<Select onValueChange={onSelectValue} value={selectValue}>
|
||||
<SelectTrigger className="ml-2 w-1/3" >
|
||||
<SelectTrigger className="searchinputselect_select" >
|
||||
<SelectValue placeholder={selectData?.[0]?.text ?? ""} />
|
||||
</SelectTrigger>
|
||||
{/* <SelectContent ref={refs ? (refs as unknown as RefType)?.selectValue : null}> */}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.searchinputselect_root {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.searchinputselect_label {
|
||||
@apply w-20 text-xl;
|
||||
}
|
||||
|
||||
.searchinputselect_controls {
|
||||
@apply flex w-full items-center;
|
||||
}
|
||||
|
||||
.searchinputselect_input {
|
||||
@apply h-14 w-2/3 rounded-md border border-input p-2 text-xl;
|
||||
}
|
||||
|
||||
.searchinputselect_select {
|
||||
@apply ml-2 w-1/3;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import "./select.css";
|
||||
|
||||
// selectBox
|
||||
|
||||
import { Label } from "@common/components";
|
||||
@@ -10,6 +12,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "./lib/selectBox";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
/** 차트 기본 스타일 */
|
||||
interface SelectBox1 {
|
||||
@@ -43,7 +46,7 @@ const SelectBox = React.forwardRef<
|
||||
value,
|
||||
readOnly = false,
|
||||
selectContentClass,
|
||||
labelClass = "w-20 whitespace-nowrap text-xl text-gray-600",
|
||||
labelClass = "w-20 whitespace-nowrap text-xl",
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
@@ -72,9 +75,9 @@ const SelectBox = React.forwardRef<
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<div className={className + " flex items-center"} ref={ref}>
|
||||
<div className={cn("selectbox_root", className)} ref={ref}>
|
||||
{label && (
|
||||
<Label className={labelClass}>
|
||||
<Label className={cn("selectbox_label", labelClass)}>
|
||||
{label}
|
||||
</Label>
|
||||
)}
|
||||
@@ -92,9 +95,7 @@ const SelectBox = React.forwardRef<
|
||||
value={currentValue}
|
||||
// disabled={readOnly} //물재이용 감리 지적사항으로 일단 주석처리
|
||||
>
|
||||
<SelectTrigger
|
||||
className={selectClass ? selectClass : "flex-1 rounded border p-2"}
|
||||
>
|
||||
<SelectTrigger className={cn("selectbox_trigger", selectClass)}>
|
||||
<SelectValue placeholder={placeHolder} />
|
||||
</SelectTrigger>
|
||||
<SelectContent className={selectContentClass}>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.selectbox_root {
|
||||
@apply flex items-center;
|
||||
}
|
||||
|
||||
.selectbox_label {
|
||||
@apply text-label;
|
||||
}
|
||||
|
||||
.selectbox_trigger {
|
||||
@apply flex-1 rounded-md border border-input bg-form px-3 py-2;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +1,49 @@
|
||||
import { Tabs, TabsList, TabsTrigger } from '@common/components/ui';
|
||||
import React from 'react';
|
||||
import "./tab.css";
|
||||
|
||||
import { Tabs, TabsList, TabsTrigger } from "@common/components/ui";
|
||||
import React from "react";
|
||||
import { cn } from "../../lib/utils";
|
||||
|
||||
interface TabItem {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface UITabProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'dir'> {
|
||||
interface UITabProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "dir"> {
|
||||
defaultValue?: string;
|
||||
tabList: TabItem[];
|
||||
children?: React.ReactNode;
|
||||
onValueChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const UITab = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
UITabProps
|
||||
>(({defaultValue, tabList, children, onValueChange, ...props}, ref) => {
|
||||
return (
|
||||
<Tabs
|
||||
defaultValue={defaultValue || tabList[0]?.value}
|
||||
className={"w-full space-x-1 border-gray-200"}
|
||||
onValueChange={onValueChange}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<TabsList className="flex space-x-1">
|
||||
{tabList.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
className="py-2 px-4 text-sm font-medium border-b-2 focus:outline-none data-[state=active]:border-primary data-[state=active]:text-blue-600 data-[state=inactive]:border-transparent data-[state=inactive]:text-gray-500 hover:text-gray-700 hover:border-gray-300"
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{children}
|
||||
</Tabs>
|
||||
);
|
||||
});
|
||||
const UITab = React.forwardRef<HTMLDivElement, UITabProps>(
|
||||
({ defaultValue, tabList, children, onValueChange, className, ...props }, ref) => {
|
||||
return (
|
||||
<Tabs
|
||||
variant="underline"
|
||||
defaultValue={defaultValue || tabList[0]?.value}
|
||||
className={cn("btab_root", className)}
|
||||
onValueChange={onValueChange}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<TabsList className="btab_list">
|
||||
{tabList.map((tab) => (
|
||||
<TabsTrigger
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
className="btab_trigger"
|
||||
>
|
||||
{tab.label}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{children}
|
||||
</Tabs>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
UITab.displayName = 'UITab';
|
||||
UITab.displayName = "UITab";
|
||||
|
||||
export { UITab };
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@tailwind components;
|
||||
|
||||
@layer components {
|
||||
.btab_root {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
.btab_list {
|
||||
@apply flex space-x-1 border-b border-border bg-transparent p-0;
|
||||
}
|
||||
|
||||
.btab_trigger {
|
||||
@apply rounded-none border-b-2 border-transparent bg-transparent px-4 py-2 text-sm font-medium text-label;
|
||||
}
|
||||
|
||||
.btab_root .btab_trigger[data-state="active"] {
|
||||
@apply border-primary text-primary;
|
||||
}
|
||||
|
||||
.btab_root .btab_trigger[data-state="inactive"] {
|
||||
@apply text-label;
|
||||
}
|
||||
|
||||
.btab_root .btab_trigger:hover {
|
||||
@apply border-border text-foreground;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user