Lookup
기존에 존재하는 내부 또는 외부 데이터를 선택하기 위해 사용하는 입력 요소
개요
Lookup은 팝업을 통해 데이터를 조회하고 선택할 수 있는 입력 컴포넌트입니다. Foundation Lookup을 FormItem으로 감싸 레이블, 설명, 에러 메시지를 통합 제공합니다.
주요 특징
- ✅ 기본 상태: 데이터 선택을 위한 입력 요소 제공
- ✅ 필수값 표시: Label 좌측에
*표시 - ✅ 포커스 상태: 포커스 시 시각적으로 구분되는 스타일
- ✅ 읽기 전용 상태: 읽기 전용(readOnly) 시각적 구분
- ✅ 오류 상태 표시: error prop으로 에러 메시지와 스타일 자동 적용
- ✅ 레이블 제공: label, labelWidth, orientation으로 레이블 배치
- ✅ 디자인 토큰: 테마 커스터마이징 지원
Sizes
Preview
사용 예시
기본 상태
기존에 존재하는 내부 또는 외부 데이터를 선택하기 위해 사용하는 입력 요소를 제공합니다.
Preview
필수값 표시
필수 입력 항목은 Label 좌측에 *(Asterisk)를 표시합니다.
Preview
포커스 상태
포커스 시 시각적으로 구분되는 스타일을 제공합니다.
Preview
읽기 전용 상태
읽기 전용(readOnly) 상태를 시각적으로 구분합니다. 클릭해도 팝업이 열리지 않습니다.
Preview
오류 상태 표시
오류 발생 시 error prop으로 에러 메시지를 표시하고, 오류 스타일이 자동 적용됩니다.
Preview
레이블 제공
레이블(Label)을 제공하며, orientation으로 배치 방향을 설정합니다.
Preview
데이터 조회 팝업에서 사용자를 선택합니다
프로젝트팀 구현 항목: 아래 기능은 프로젝트 요구사항에 맞춰 직접 구현합니다.
- 팝업 호출: Lookup 버튼 클릭 시 데이터 조회 팝업 화면 표시
- 선택 결과 반영: 팝업에서 데이터 선택 시 팝업 닫힘 및 선택 데이터 반영
- Select/Cancel/Clear 버튼: 팝업 내 선택, 취소, 초기화 버튼 제공
- 단일 행 선택: 데이터(Row) 클릭 시 단일 항목만 선택
- 입력 방식 제한: 팝업에서만 값 선택 가능
- 데이터 소스 연동: 내부/외부 API 데이터 조회
- 입력 가능 제어: 읽기 전용 상태에서 선택 및 변경 제한
API Reference
Props
| Prop | Type | Default | Description |
|---|---|---|---|
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | 크기 |
value | T | null | - | 선택된 값 |
displayValue | string | - | 표시할 텍스트 |
placeholder | string | "선택하세요" | 플레이스홀더 텍스트 |
disabled | boolean | false | 비활성화 여부 |
readOnly | boolean | false | 읽기 전용 여부 |
width | string | number | - | 너비 |
modalOptions | LookupModalOptions | - | 팝업 모달 옵션 |
label | string | - | 필드 레이블 |
labelWidth | number | - | 레이블 너비 |
labelAlign | "start" | "center" | "end" | - | 레이블 가로 정렬 |
labelVerticalAlign | "start" | "center" | "end" | - | 레이블 세로 정렬 |
required | boolean | false | 필수 여부 (Label에 * 표시) |
orientation | "horizontal" | "vertical" | "horizontal" | 레이블-필드 배치 방향 |
description | string | - | 설명 텍스트 |
error | string | - | 에러 메시지 |
className | string | - | 래퍼에 적용할 추가 CSS 클래스 |
renderContent | (props: LookupRenderProps<T>) => ReactNode | 필수 | 팝업 콘텐츠 렌더 함수 |
Events
| Event | Type | Description |
|---|---|---|
onValueChange | (value: T | null) => void | 값 변경 시 호출 |
LookupModalOptions
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | - | 팝업 타이틀 |
description | ReactNode | - | 팝업 설명 |
className | string | - | 팝업 추가 CSS 클래스 |
type | "center" | "bottomsheet" | "fullscreen" | "sidepanel" | - | 팝업 유형 |
dim | boolean | - | 배경 딤 처리 여부 |
allowInteraction | boolean | - | 딤 상태에서 뒤쪽 조작 허용 |
resizable | boolean | - | 팝업 리사이즈 가능 여부 |
showCloseButton | boolean | - | 닫기 버튼 표시 여부 |
draggable | boolean | - | 팝업 드래그 가능 여부 |
footerAlign | "left" | "center" | "right" | - | 푸터 정렬 |
dedupeKey | string | - | 동일 lookup 중복 방지용 식별자 (자세한 내용은 아래 참고) |
미지정 시
DialogProvider의 전역 config 기본값이 적용됩니다.
LookupRenderProps
| Prop | Type | Description |
|---|---|---|
select | (value: T) => void | 값 선택 (팝업 닫힘) |
cancel | () => void | 선택 취소 (팝업 닫힘) |
clear | () => void | 값 초기화 (팝업 닫힘) |
기본 사용법
import { Lookup } from "@vortex/ui-icignal"
<Lookup
label="사용자"
labelWidth={100}
required
value={selectedUser}
displayValue={selectedUser?.name}
onValueChange={setSelectedUser}
placeholder="사용자 선택"
modalOptions={{ title: "사용자 조회" }}
renderContent={({ select, cancel, clear }) => (
<div>
{users.map((user) => (
<div key={user.id} onClick={() => select(user)}>{user.name}</div>
))}
</div>
)}
/>모달 옵션 활용
<Lookup
label="사용자"
labelWidth={100}
value={selectedUser}
displayValue={selectedUser?.name}
onValueChange={setSelectedUser}
placeholder="사용자 선택"
modalOptions={{
title: "사용자 조회",
dim: false,
allowInteraction: true,
resizable: true,
type: "center",
}}
renderContent={({ select, cancel, clear }) => (
<div>
{users.map((user) => (
<div key={user.id} onClick={() => select(user)}>{user.name}</div>
))}
</div>
)}
/>중복 모달 방지 (dedupeKey)
Lookup 컴포넌트는 내부에서 인스턴스별 자동 dedupeKey 를 부여하여 같은 인스턴스에서 빠른 더블클릭으로 모달이 두 번 열리지 않도록 자동 차단합니다. (사용자가 별도 설정을 하지 않아도 동작합니다.)
다음과 같은 경우에는 호출자가 명시적으로 modalOptions.dedupeKey 를 지정해야 cross-instance 중복까지 차단할 수 있습니다.
- 여러 트리거(다른 인스턴스 / 외부 버튼 / 단축키 등)에서 의미상 같은 lookup을 호출하는 경우
useDialog().lookup(...)을 직접 호출하는 경우dim: false,allowInteraction: true로 배경 조작이 허용된 상태
modalOptions.dedupeKey (또는 useDialog().lookup({ dedupeKey })) 를 지정하면 동일 key 의 lookup이 이미 열려있을 때 새 모달을 띄우지 않고 즉시 Promise<null> 로 resolve 합니다. 사용자가 명시한 dedupeKey 가 있으면 자동 인스턴스 키 대신 명시값이 사용됩니다. 이미 열린 lookup은 그대로 유지되며, 자체 select/cancel 로만 닫힙니다.
<Lookup
label="사용자"
labelWidth={100}
value={selectedUser}
displayValue={selectedUser?.name}
onValueChange={setSelectedUser}
placeholder="사용자 선택"
modalOptions={{
title: "사용자 조회",
dim: false,
allowInteraction: true,
dedupeKey: "user-pick",
}}
renderContent={({ select, cancel }) => (...)}
/>또는 store 를 직접 호출하는 경우:
const user = await useDialog.getState().lookup<User>({
dedupeKey: "user-pick",
title: "사용자 조회",
content: ({ select, cancel }) => (...),
})
// user === null 인 경우: cancel 되었거나, 이미 동일 dedupeKey 의 lookup 이 열려있어 dedupe 로 차단된 호출
dedupeKey미지정 호출은 기존과 동일하게 매번 별개 모달이 열립니다.
반환값 동작
| 케이스 | store Promise | Lookup 컴포넌트의 onValueChange |
|---|---|---|
select(value) | value | onValueChange(value) |
clear() (Lookup만) | 내부 CLEAR_SYMBOL 처리 | onValueChange(null) |
cancel() | null | (호출 안 됨) |
dedupeKey 로 차단됨 | null | (호출 안 됨) |
주의:
dedupeKey로 차단된 호출은cancel과 동일하게null로 resolve 됩니다. 호출자는 두 경우를 구분할 수 없습니다. 구분이 필요하면 호출 전에 외부 플래그/ref로 in-flight 여부를 직접 확인하세요.clear는 store 의LookupHandlers에는 존재하지 않으며,Lookup컴포넌트가CLEAR_SYMBOL을select하는 wrapper 트릭으로 제공합니다.
접근성
키보드 네비게이션
- Tab: Lookup 필드에 포커스 이동
- Enter/Click: 조회 팝업 열기
권장 사항
- ✅
label을 제공하여 입력 필드 목적 전달 - ✅ 에러 시
errorprop으로 구체적 안내 - ✅
displayValue로 선택된 값을 명확히 표시 - ❌ readOnly 상태에서 팝업 호출 방지
- ❌ placeholder만으로 레이블 대체 지양
관련 컴포넌트
- Select: 드롭다운 기반 선택
- Combobox: 검색 가능한 선택
- Input: 텍스트 직접 입력
- InputNumber: 숫자 입력