Lookup
기존에 존재하는 내부 또는 외부 데이터를 선택하기 위해 사용하는 입력 요소
개요
Lookup은 팝업을 통해 데이터를 조회하고 선택할 수 있는 입력 컴포넌트입니다. 사용자가 직접 값을 입력하지 않고, 조회 팝업에서 데이터를 선택하는 방식으로 동작합니다.
주요 특징
- ✅ 기본 상태: 데이터 선택을 위한 입력 요소 제공
- ✅ 포커스 상태: 포커스 시 시각적으로 구분되는 스타일
- ✅ 읽기 전용 상태: 읽기 전용(readOnly) 시각적 구분
- ✅ 오류 상태 표시: ariaInvalid 기반 에러 스타일
- ✅ 디자인 토큰: 테마 커스터마이징 지원
Sizes
Lookup은 5가지 크기를 지원합니다. 기본값은 md입니다.
Preview
사용 예시
기본 상태
기존에 존재하는 내부 또는 외부 데이터를 선택하기 위해 사용하는 입력 요소를 제공합니다.
Preview
포커스 상태
포커스 시 시각적으로 구분되는 스타일을 제공합니다. Tab 키로 포커스하면 border와 ring 스타일이 적용됩니다.
Preview
읽기 전용 상태
읽기 전용(readOnly) 상태를 시각적으로 구분합니다. 클릭해도 팝업이 열리지 않습니다.
Preview
오류 상태 표시
ariaInvalid prop으로 오류 상태를 시각적으로 표현합니다.
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 | - | 팝업 모달 옵션 |
ariaInvalid | boolean | - | 오류 상태 |
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-foundation"
<Lookup
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
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
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: 조회 팝업 열기
권장 사항
- ✅
displayValue로 선택된 값을 명확히 표시 - ✅
placeholder로 입력 필드 목적 전달 - ✅ 에러 상태 시
ariaInvalid속성 사용 - ❌ readOnly 상태에서 팝업 호출 방지
관련 컴포넌트
- Select: 드롭다운 기반 선택
- Combobox: 검색 가능한 선택
- Input: 텍스트 직접 입력
- InputGroup: 입력 필드 그룹