feat: Improve V2Select multi-select dropdown item display
- Enhanced the display of selected items in the V2Select component to show labels in a comma-separated format, improving user visibility without needing to open the dropdown. - Implemented tooltip functionality that activates only when the text is truncated, allowing users to see all selected items at a glance. - Updated the DropdownSelect component to ensure consistent behavior across all screens using this component. - Added necessary imports and state management for tooltip visibility and text truncation detection. Made-with: Cursor
This commit is contained in:
parent
cfd49020a0
commit
2b324d083b
|
|
@ -0,0 +1,146 @@
|
|||
# [계획서] V2Select 다중 선택 드롭다운 - 선택 항목 표시 개선
|
||||
|
||||
> 관련 문서: [맥락노트](./MST[맥락노트]-v2select-multiselect-tooltip.md) | [체크리스트](./MST[체크리스트]-v2select-multiselect-tooltip.md)
|
||||
|
||||
## 개요
|
||||
|
||||
모든 화면에서 다중 선택 가능한 드롭다운(`V2Select` - `DropdownSelect`)의 선택 항목 표시 방식을 개선합니다.
|
||||
|
||||
---
|
||||
|
||||
## 현재 동작
|
||||
|
||||
- 다중 선택 시 `"3개 선택됨"` 같은 텍스트만 표시
|
||||
- 어떤 항목이 선택되었는지 드롭다운을 열어야만 확인 가능
|
||||
|
||||
### 현재 코드 (V2Select.tsx - DropdownSelect, 174~178행)
|
||||
|
||||
```tsx
|
||||
{selectedLabels.length > 0
|
||||
? multiple
|
||||
? `${selectedLabels.length}개 선택됨`
|
||||
: selectedLabels[0]
|
||||
: placeholder}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 변경 후 동작
|
||||
|
||||
### 1. 선택된 항목 라벨을 쉼표로 연결하여 한 줄로 표시
|
||||
|
||||
- 예: `"구매품, 판매품, 재고품"`
|
||||
- `truncate` (text-overflow: ellipsis)로 필드 너비를 넘으면 말줄임(`...`) 처리
|
||||
- 무조건 한 줄 표시, 넘치면 `...`으로 숨김
|
||||
|
||||
### 2. 텍스트가 말줄임(`...`) 처리될 때만 호버 툴팁 표시
|
||||
|
||||
- 필드 너비를 넘어서 `...`으로 잘릴 때만 툴팁 활성화
|
||||
- 필드 내에 전부 보이면 툴팁 불필요
|
||||
- 툴팁 내용은 세로 나열로 각 항목을 한눈에 확인 가능
|
||||
- 툴팁은 딜레이 없이 즉시 표시
|
||||
|
||||
---
|
||||
|
||||
## 시각적 동작 예시
|
||||
|
||||
| 상태 | 필드 내 표시 | 호버 시 툴팁 |
|
||||
|------|-------------|-------------|
|
||||
| 미선택 | `선택` (placeholder) | 없음 |
|
||||
| 1개 선택 | `구매품` | 없음 |
|
||||
| 3개 선택 (필드 내 수용) | `구매품, 판매품, 재고품` | 없음 (잘리지 않으므로) |
|
||||
| 5개 선택 (필드 넘침) | `구매품, 판매품, 재고품, 외...` | 구매품 / 판매품 / 재고품 / 외주품 / 반제품 (세로 나열) |
|
||||
|
||||
---
|
||||
|
||||
## 변경 대상
|
||||
|
||||
- **파일**: `frontend/components/v2/V2Select.tsx`
|
||||
- **컴포넌트**: `DropdownSelect` 내부 표시 텍스트 부분 (170~178행)
|
||||
- **적용 범위**: `DropdownSelect`를 사용하는 모든 화면 (품목정보, 기타 모든 모달 포함)
|
||||
- **변경 규모**: 약 30줄 내외 소규모 변경
|
||||
|
||||
---
|
||||
|
||||
## 코드 설계
|
||||
|
||||
### 추가 import
|
||||
|
||||
```tsx
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
```
|
||||
|
||||
### 말줄임 감지 로직
|
||||
|
||||
```tsx
|
||||
// 텍스트가 잘리는지(truncated) 감지
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
const [isTruncated, setIsTruncated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const el = textRef.current;
|
||||
if (el) {
|
||||
setIsTruncated(el.scrollWidth > el.clientWidth);
|
||||
}
|
||||
}, [selectedLabels]);
|
||||
```
|
||||
|
||||
### 수정 코드 (DropdownSelect 내부, 170~178행 대체)
|
||||
|
||||
```tsx
|
||||
const displayText = selectedLabels.length > 0
|
||||
? (multiple ? selectedLabels.join(", ") : selectedLabels[0])
|
||||
: placeholder;
|
||||
|
||||
const isPlaceholder = selectedLabels.length === 0;
|
||||
|
||||
// 렌더링 부분
|
||||
{isTruncated && multiple ? (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
ref={textRef}
|
||||
className={cn("truncate flex-1 text-left", isPlaceholder && "text-muted-foreground")}
|
||||
{...(isPlaceholder ? { "data-placeholder": placeholder } : {})}
|
||||
>
|
||||
{displayText}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="top" className="max-w-[300px]">
|
||||
<div className="space-y-0.5 text-xs">
|
||||
{selectedLabels.map((label, i) => (
|
||||
<div key={i}>{label}</div>
|
||||
))}
|
||||
</div>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
) : (
|
||||
<span
|
||||
ref={textRef}
|
||||
className={cn("truncate flex-1 text-left", isPlaceholder && "text-muted-foreground")}
|
||||
{...(isPlaceholder ? { "data-placeholder": placeholder } : {})}
|
||||
>
|
||||
{displayText}
|
||||
</span>
|
||||
)}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 설계 원칙
|
||||
|
||||
- 기존 단일 선택 동작은 변경하지 않음
|
||||
- `DropdownSelect` 공통 컴포넌트 수정이므로 모든 화면에 자동 적용
|
||||
- 무조건 한 줄 표시, 넘치면 `...`으로 말줄임
|
||||
- 툴팁은 텍스트가 실제로 잘릴 때(`scrollWidth > clientWidth`)만 표시
|
||||
- 툴팁 내용은 세로 나열로 각 항목 확인 용이
|
||||
- 툴팁 딜레이 없음 (`delayDuration={0}`)
|
||||
- shadcn 표준 Tooltip 컴포넌트 사용으로 프로젝트 일관성 유지
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# [맥락노트] V2Select 다중 선택 드롭다운 - 선택 항목 표시 개선
|
||||
|
||||
> 관련 문서: [계획서](./MST[계획서]-v2select-multiselect-tooltip.md) | [체크리스트](./MST[체크리스트]-v2select-multiselect-tooltip.md)
|
||||
|
||||
---
|
||||
|
||||
## 왜 이 작업을 하는가
|
||||
|
||||
- 사용자가 수정 모달에서 다중 선택 드롭다운을 사용할 때 `"3개 선택됨"` 만 보임
|
||||
- 드롭다운을 다시 열어봐야만 무엇이 선택됐는지 확인 가능 → UX 불편
|
||||
- 선택 항목을 직접 보여주고, 넘치면 툴팁으로 확인할 수 있게 개선
|
||||
|
||||
---
|
||||
|
||||
## 핵심 결정 사항과 근거
|
||||
|
||||
### 1. "n개 선택됨" → 라벨 쉼표 나열
|
||||
|
||||
- **결정**: `"구매품, 판매품, 재고품"` 형태로 표시
|
||||
- **근거**: 사용자가 드롭다운을 열지 않아도 선택 내용을 바로 확인 가능
|
||||
|
||||
### 2. 무조건 한 줄, 넘치면 말줄임(`...`)
|
||||
|
||||
- **결정**: 여러 줄 줄바꿈 없이 한 줄 고정, `truncate`로 오버플로우 처리
|
||||
- **근거**: 드롭다운 필드 높이가 고정되어 있어 여러 줄 표시 시 레이아웃이 깨짐
|
||||
|
||||
### 3. 텍스트가 잘릴 때만 툴팁 표시
|
||||
|
||||
- **결정**: `scrollWidth > clientWidth` 비교로 실제 잘림 여부 감지 후 툴팁 활성화
|
||||
- **근거**: 전부 보이는데 툴팁이 뜨면 오히려 방해. 필요할 때만 보여야 함
|
||||
- **대안 검토**: "2개 이상이면 항상 툴팁" → 기각 (불필요한 툴팁 발생)
|
||||
|
||||
### 4. 툴팁 내용은 세로 나열
|
||||
|
||||
- **결정**: 툴팁 안에서 항목을 줄바꿈으로 세로 나열
|
||||
- **근거**: 가로 나열 시 툴팁도 길어져서 읽기 어려움. 세로가 한눈에 파악하기 좋음
|
||||
|
||||
### 5. 툴팁 딜레이 0ms
|
||||
|
||||
- **결정**: `delayDuration={0}` 즉시 표시
|
||||
- **근거**: 사용자가 "무엇을 선택했는지" 확인하려는 의도적 행동이므로 즉시 응답해야 함
|
||||
|
||||
### 6. Radix Tooltip 대신 커스텀 호버 툴팁 사용
|
||||
|
||||
- **결정**: Radix Tooltip을 사용하지 않고 `onMouseEnter`/`onMouseLeave`로 직접 제어
|
||||
- **근거**: Radix Tooltip + Popover 조합은 이벤트 충돌 발생. 내부 배치든 외부 래핑이든 Popover가 호버를 가로챔
|
||||
- **시도 1**: Tooltip을 Button 안에 배치 → Popover가 이벤트 가로챔 (실패)
|
||||
- **시도 2**: Radix 공식 패턴 (TooltipTrigger > PopoverTrigger > Button 체이닝) → 여전히 동작 안 함 (실패)
|
||||
- **최종**: wrapper div에 마우스 이벤트 + 절대 위치 div로 툴팁 렌더링 (성공)
|
||||
- **추가**: Popover 열릴 때 `setHoverTooltip(false)`로 툴팁 자동 숨김
|
||||
|
||||
### 7. DropdownSelect 공통 컴포넌트 수정
|
||||
|
||||
- **결정**: 특정 화면이 아닌 `DropdownSelect` 자체를 수정
|
||||
- **근거**: 품목정보뿐 아니라 모든 화면에서 동일한 문제가 있으므로 공통 해결
|
||||
|
||||
---
|
||||
|
||||
## 관련 파일 위치
|
||||
|
||||
| 구분 | 파일 경로 | 설명 |
|
||||
|------|----------|------|
|
||||
| 수정 대상 | `frontend/components/v2/V2Select.tsx` | DropdownSelect 컴포넌트 (170~178행) |
|
||||
| 타입 정의 | `frontend/types/v2-components.ts` | V2SelectProps, SelectOption 타입 |
|
||||
| UI 컴포넌트 | `frontend/components/ui/tooltip.tsx` | shadcn Tooltip 컴포넌트 |
|
||||
| 렌더러 | `frontend/lib/registry/components/v2-select/V2SelectRenderer.tsx` | V2Select를 레지스트리에 연결 |
|
||||
| 수정 모달 | `frontend/components/screen/EditModal.tsx` | 공통 편집 모달 |
|
||||
|
||||
---
|
||||
|
||||
## 기술 참고
|
||||
|
||||
### truncate 감지 방식
|
||||
|
||||
```
|
||||
scrollWidth: 텍스트의 실제 전체 너비 (보이지 않는 부분 포함)
|
||||
clientWidth: 요소의 보이는 너비
|
||||
|
||||
scrollWidth > clientWidth → 텍스트가 잘리고 있음 (... 표시 중)
|
||||
```
|
||||
|
||||
### selectedLabels 계산 흐름
|
||||
|
||||
```
|
||||
value (string[]) → selectedValues → safeOptions에서 label 매칭 → selectedLabels (string[])
|
||||
```
|
||||
|
||||
- `selectedLabels`는 이미 `DropdownSelect` 내부에서 `useMemo`로 계산됨 (126~130행)
|
||||
- 추가 데이터 fetching 불필요, 기존 값을 `.join(", ")`로 결합하면 됨
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# [체크리스트] V2Select 다중 선택 드롭다운 - 선택 항목 표시 개선
|
||||
|
||||
> 관련 문서: [계획서](./MST[계획서]-v2select-multiselect-tooltip.md) | [맥락노트](./MST[맥락노트]-v2select-multiselect-tooltip.md)
|
||||
|
||||
---
|
||||
|
||||
## 공정 상태
|
||||
|
||||
- 전체 진행률: **100%** (완료)
|
||||
- 현재 단계: 전체 완료
|
||||
|
||||
---
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
### 1단계: 코드 수정
|
||||
|
||||
- [x] `V2Select.tsx`에 Tooltip 관련 import 추가
|
||||
- [x] `DropdownSelect` 내부에 `textRef`, `isTruncated` 상태 추가
|
||||
- [x] `useEffect`로 `scrollWidth > clientWidth` 감지 로직 추가
|
||||
- [x] 표시 텍스트를 `selectedLabels.join(", ")`로 변경
|
||||
- [x] `isTruncated && multiple` 조건으로 Tooltip 래핑
|
||||
- [x] 툴팁 내용을 세로 나열 (`space-y-0.5`)로 구성
|
||||
- [x] `delayDuration={0}` 설정
|
||||
- [x] Radix Tooltip → 커스텀 호버 툴팁으로 변경 (onMouseEnter/onMouseLeave + 절대 위치 div)
|
||||
|
||||
### 2단계: 검증
|
||||
|
||||
- [x] 단일 선택 모드: 기존 동작 변화 없음 확인
|
||||
- [x] 다중 선택 1개: 라벨 정상 표시, 툴팁 없음
|
||||
- [x] 다중 선택 3개 (필드 내 수용): 쉼표 나열 표시, 툴팁 없음
|
||||
- [x] 다중 선택 5개+ (필드 넘침): 말줄임 표시, 호버 시 툴팁 세로 나열
|
||||
- [x] 품목정보 수정 모달에서 동작 확인
|
||||
- [x] 다른 화면의 다중 선택 드롭다운에서도 동작 확인
|
||||
|
||||
### 3단계: 정리
|
||||
|
||||
- [x] 린트 에러 없음 확인
|
||||
- [x] 이 체크리스트 완료 표시 업데이트
|
||||
|
||||
---
|
||||
|
||||
## 변경 이력
|
||||
|
||||
| 날짜 | 내용 |
|
||||
|------|------|
|
||||
| 2026-03-04 | 설계 문서 작성 완료 |
|
||||
| 2026-03-04 | 맥락노트, 체크리스트 작성 완료 |
|
||||
| 2026-03-04 | 파일명 MST 접두사 적용 |
|
||||
| 2026-03-04 | 1단계 코드 수정 완료 (V2Select.tsx) |
|
||||
| 2026-03-04 | Radix Tooltip이 Popover와 충돌 → 커스텀 호버 툴팁으로 변경 |
|
||||
| 2026-03-04 | 사용자 검증 완료, 전체 작업 완료 |
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Tooltip as TooltipPrimitive } from "radix-ui"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function TooltipProvider({
|
||||
delayDuration = 0,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
|
||||
return (
|
||||
<TooltipPrimitive.Provider
|
||||
data-slot="tooltip-provider"
|
||||
delayDuration={delayDuration}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function Tooltip({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
||||
return <TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
||||
}
|
||||
|
||||
function TooltipTrigger({
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
|
||||
}
|
||||
|
||||
function TooltipContent({
|
||||
className,
|
||||
sideOffset = 0,
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
||||
return (
|
||||
<TooltipPrimitive.Portal>
|
||||
<TooltipPrimitive.Content
|
||||
data-slot="tooltip-content"
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-fit origin-(--radix-tooltip-content-transform-origin) animate-in rounded-md bg-foreground px-3 py-1.5 text-xs text-balance text-background fade-in-0 zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<TooltipPrimitive.Arrow className="z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground" />
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
|
||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
* - swap: 스왑 선택 (좌우 이동)
|
||||
*/
|
||||
|
||||
import React, { forwardRef, useCallback, useContext, useEffect, useMemo, useState } from "react";
|
||||
import React, { forwardRef, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
|
|
@ -57,6 +57,9 @@ const DropdownSelect = forwardRef<HTMLButtonElement, {
|
|||
style,
|
||||
}, ref) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const textRef = useRef<HTMLSpanElement>(null);
|
||||
const [isTruncated, setIsTruncated] = useState(false);
|
||||
const [hoverTooltip, setHoverTooltip] = useState(false);
|
||||
|
||||
// 현재 선택된 값 존재 여부
|
||||
const hasValue = useMemo(() => {
|
||||
|
|
@ -129,6 +132,13 @@ const DropdownSelect = forwardRef<HTMLButtonElement, {
|
|||
.filter(Boolean) as string[];
|
||||
}, [selectedValues, safeOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = textRef.current;
|
||||
if (el) {
|
||||
setIsTruncated(el.scrollWidth > el.clientWidth);
|
||||
}
|
||||
}, [selectedLabels]);
|
||||
|
||||
const handleSelect = useCallback((selectedValue: string) => {
|
||||
if (multiple) {
|
||||
const newValues = selectedValues.includes(selectedValue)
|
||||
|
|
@ -148,91 +158,109 @@ const DropdownSelect = forwardRef<HTMLButtonElement, {
|
|||
onChange?.(multiple ? [] : "");
|
||||
}, [multiple, onChange]);
|
||||
|
||||
const displayText = selectedLabels.length > 0
|
||||
? (multiple ? selectedLabels.join(", ") : selectedLabels[0])
|
||||
: placeholder;
|
||||
const isPlaceholder = selectedLabels.length === 0;
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
{/* Button에 style로 직접 height 전달 (Popover도 DOM 체인 끊김) */}
|
||||
<Button
|
||||
ref={ref}
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"w-full justify-between font-normal",
|
||||
"bg-transparent hover:bg-transparent", // 표준 Select와 동일한 투명 배경
|
||||
"border-input shadow-xs", // 표준 Select와 동일한 테두리
|
||||
"h-6 px-2 py-0 text-sm", // 표준 Select xs와 동일한 높이
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
<span
|
||||
className="truncate flex-1 text-left"
|
||||
{...(selectedLabels.length === 0 ? { "data-placeholder": placeholder } : {})}
|
||||
>
|
||||
{selectedLabels.length > 0
|
||||
? multiple
|
||||
? `${selectedLabels.length}개 선택됨`
|
||||
: selectedLabels[0]
|
||||
: placeholder}
|
||||
</span>
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
{allowClear && selectedValues.length > 0 && (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleClear}
|
||||
onPointerDown={(e) => {
|
||||
// Radix Popover가 onPointerDown으로 팝오버를 여는 것을 방지
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4 opacity-50 hover:opacity-100" />
|
||||
</span>
|
||||
<div
|
||||
className="relative w-full"
|
||||
onMouseEnter={() => { if (isTruncated && multiple) setHoverTooltip(true); }}
|
||||
onMouseLeave={() => setHoverTooltip(false)}
|
||||
>
|
||||
<Popover open={open} onOpenChange={(isOpen) => {
|
||||
setOpen(isOpen);
|
||||
if (isOpen) setHoverTooltip(false);
|
||||
}}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
"w-full justify-between font-normal",
|
||||
"bg-transparent hover:bg-transparent",
|
||||
"border-input shadow-xs",
|
||||
"h-6 px-2 py-0 text-sm",
|
||||
className,
|
||||
)}
|
||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
style={style}
|
||||
>
|
||||
<span
|
||||
ref={textRef}
|
||||
className="truncate flex-1 text-left"
|
||||
{...(isPlaceholder ? { "data-placeholder": placeholder } : {})}
|
||||
>
|
||||
{displayText}
|
||||
</span>
|
||||
<div className="flex items-center gap-1 ml-2">
|
||||
{allowClear && selectedValues.length > 0 && (
|
||||
<span
|
||||
role="button"
|
||||
tabIndex={-1}
|
||||
onClick={handleClear}
|
||||
onPointerDown={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<X className="h-4 w-4 opacity-50 hover:opacity-100" />
|
||||
</span>
|
||||
)}
|
||||
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
||||
<Command
|
||||
filter={(itemValue, search) => {
|
||||
if (!search) return 1;
|
||||
const option = safeOptions.find((o) => o.value === itemValue);
|
||||
const label = (option?.label || option?.value || "").toLowerCase();
|
||||
if (label.includes(search.toLowerCase())) return 1;
|
||||
return 0;
|
||||
}}
|
||||
>
|
||||
{searchable && <CommandInput placeholder="검색..." className="h-9" />}
|
||||
<CommandList>
|
||||
<CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{safeOptions.map((option) => {
|
||||
const displayLabel = option.label || option.value || "(빈 값)";
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
onSelect={() => handleSelect(option.value)}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
selectedValues.includes(option.value) ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{displayLabel}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
{hoverTooltip && !open && (
|
||||
<div className="absolute bottom-full left-0 z-50 mb-1 rounded-md border bg-popover px-3 py-1.5 shadow-md animate-in fade-in-0 zoom-in-95">
|
||||
<div className="space-y-0.5 text-xs">
|
||||
{selectedLabels.map((label, i) => (
|
||||
<div key={i}>{label}</div>
|
||||
))}
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="p-0" style={{ width: "var(--radix-popover-trigger-width)" }} align="start">
|
||||
<Command
|
||||
filter={(itemValue, search) => {
|
||||
if (!search) return 1;
|
||||
const option = safeOptions.find((o) => o.value === itemValue);
|
||||
const label = (option?.label || option?.value || "").toLowerCase();
|
||||
if (label.includes(search.toLowerCase())) return 1;
|
||||
return 0;
|
||||
}}
|
||||
>
|
||||
{searchable && <CommandInput placeholder="검색..." className="h-9" />}
|
||||
<CommandList>
|
||||
<CommandEmpty>검색 결과가 없습니다.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{safeOptions.map((option) => {
|
||||
const displayLabel = option.label || option.value || "(빈 값)";
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
onSelect={() => handleSelect(option.value)}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"mr-2 h-4 w-4",
|
||||
selectedValues.includes(option.value) ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
{displayLabel}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
DropdownSelect.displayName = "DropdownSelect";
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
"mammoth": "^1.11.0",
|
||||
"next": "^15.4.8",
|
||||
"qrcode": "^1.5.4",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.1.0",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
|
|
@ -1492,6 +1493,29 @@
|
|||
"integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-accessible-icon": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz",
|
||||
"integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-accordion": {
|
||||
"version": "1.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz",
|
||||
|
|
@ -1574,6 +1598,29 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-aspect-ratio": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.7.tgz",
|
||||
"integrity": "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-primitive": "2.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-avatar": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.10.tgz",
|
||||
|
|
@ -1892,6 +1939,65 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-form": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz",
|
||||
"integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-label": "2.1.7",
|
||||
"@radix-ui/react-primitive": "2.1.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-hover-card": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz",
|
||||
"integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||
"@radix-ui/react-popper": "1.2.8",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
|
||||
|
|
@ -1973,6 +2079,38 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-menubar": {
|
||||
"version": "1.1.16",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz",
|
||||
"integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-menu": "2.1.16",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-roving-focus": "1.1.11",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-navigation-menu": {
|
||||
"version": "1.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz",
|
||||
|
|
@ -2009,6 +2147,70 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-one-time-password-field": {
|
||||
"version": "0.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz",
|
||||
"integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.1",
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-roving-focus": "1.1.11",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||
"@radix-ui/react-use-is-hydrated": "0.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-password-toggle-field": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz",
|
||||
"integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||
"@radix-ui/react-use-is-hydrated": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover": {
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
|
||||
|
|
@ -2333,6 +2535,39 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slider": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz",
|
||||
"integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/number": "1.1.1",
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1",
|
||||
"@radix-ui/react-use-previous": "1.1.1",
|
||||
"@radix-ui/react-use-size": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||
|
|
@ -2410,6 +2645,157 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toast": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz",
|
||||
"integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1",
|
||||
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle": {
|
||||
"version": "1.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz",
|
||||
"integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle-group": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz",
|
||||
"integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-roving-focus": "1.1.11",
|
||||
"@radix-ui/react-toggle": "1.1.10",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toolbar": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz",
|
||||
"integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-roving-focus": "1.1.11",
|
||||
"@radix-ui/react-separator": "1.1.7",
|
||||
"@radix-ui/react-toggle-group": "1.1.11"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tooltip": {
|
||||
"version": "1.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
|
||||
"integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||
"@radix-ui/react-id": "1.1.1",
|
||||
"@radix-ui/react-popper": "1.2.8",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
|
||||
|
|
@ -13127,6 +13513,83 @@
|
|||
"integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/radix-ui": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz",
|
||||
"integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.3",
|
||||
"@radix-ui/react-accessible-icon": "1.1.7",
|
||||
"@radix-ui/react-accordion": "1.2.12",
|
||||
"@radix-ui/react-alert-dialog": "1.1.15",
|
||||
"@radix-ui/react-arrow": "1.1.7",
|
||||
"@radix-ui/react-aspect-ratio": "1.1.7",
|
||||
"@radix-ui/react-avatar": "1.1.10",
|
||||
"@radix-ui/react-checkbox": "1.3.3",
|
||||
"@radix-ui/react-collapsible": "1.1.12",
|
||||
"@radix-ui/react-collection": "1.1.7",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-context-menu": "2.2.16",
|
||||
"@radix-ui/react-dialog": "1.1.15",
|
||||
"@radix-ui/react-direction": "1.1.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.1.11",
|
||||
"@radix-ui/react-dropdown-menu": "2.1.16",
|
||||
"@radix-ui/react-focus-guards": "1.1.3",
|
||||
"@radix-ui/react-focus-scope": "1.1.7",
|
||||
"@radix-ui/react-form": "0.1.8",
|
||||
"@radix-ui/react-hover-card": "1.1.15",
|
||||
"@radix-ui/react-label": "2.1.7",
|
||||
"@radix-ui/react-menu": "2.1.16",
|
||||
"@radix-ui/react-menubar": "1.1.16",
|
||||
"@radix-ui/react-navigation-menu": "1.2.14",
|
||||
"@radix-ui/react-one-time-password-field": "0.1.8",
|
||||
"@radix-ui/react-password-toggle-field": "0.1.3",
|
||||
"@radix-ui/react-popover": "1.1.15",
|
||||
"@radix-ui/react-popper": "1.2.8",
|
||||
"@radix-ui/react-portal": "1.1.9",
|
||||
"@radix-ui/react-presence": "1.1.5",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-progress": "1.1.7",
|
||||
"@radix-ui/react-radio-group": "1.3.8",
|
||||
"@radix-ui/react-roving-focus": "1.1.11",
|
||||
"@radix-ui/react-scroll-area": "1.2.10",
|
||||
"@radix-ui/react-select": "2.2.6",
|
||||
"@radix-ui/react-separator": "1.1.7",
|
||||
"@radix-ui/react-slider": "1.3.6",
|
||||
"@radix-ui/react-slot": "1.2.3",
|
||||
"@radix-ui/react-switch": "1.2.6",
|
||||
"@radix-ui/react-tabs": "1.1.13",
|
||||
"@radix-ui/react-toast": "1.2.15",
|
||||
"@radix-ui/react-toggle": "1.1.10",
|
||||
"@radix-ui/react-toggle-group": "1.1.11",
|
||||
"@radix-ui/react-toolbar": "1.1.11",
|
||||
"@radix-ui/react-tooltip": "1.2.8",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-effect-event": "0.0.2",
|
||||
"@radix-ui/react-use-escape-keydown": "1.1.1",
|
||||
"@radix-ui/react-use-is-hydrated": "0.1.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.1",
|
||||
"@radix-ui/react-use-size": "1.1.1",
|
||||
"@radix-ui/react-visually-hidden": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/raf": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
"mammoth": "^1.11.0",
|
||||
"next": "^15.4.8",
|
||||
"qrcode": "^1.5.4",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "19.1.0",
|
||||
"react-day-picker": "^9.11.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
|
|
|
|||
Loading…
Reference in New Issue