Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
"svg-sprite-loader": "^6.0.11",
"tailwindcss": "4.1.14",
"typescript": "^5",
"class-variance-authority": "^0.7.1"
"class-variance-authority": "^0.7.1",
"@radix-ui/react-popover": "^1.1.15",
"date-fns": "^4.1.0",
"react-day-picker": "^9.11.1",
"@radix-ui/react-slot": "^1.2.3",
"lucide-react": "^0.540.0"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

런타임 의존성이 devDependencies에 들어가 있어 배포 시 누락 위험

@radix-ui/react-popover, @radix-ui/react-slot, react-day-picker, date-fns, class-variance-authority, lucide-react는 런타임에 import되는 UI/유틸입니다. prod 설치(예: CI/CD에서 --prod, Docker multi-stage)에서는 devDependencies가 제외되어 앱이 빌드/실행 중 터질 수 있습니다. 또한 반대로 svgo는 빌드 스크립트용이므로 devDependencies가 적절합니다.

권장 수정:

  • 위 6개 패키지를 dependencies로 이동
  • svgo는 devDependencies로 이동
   "dependencies": {
     "axios": "^1.12.2",
     "clsx": "^2.1.1",
     "next": "15.5.4",
     "react": "19.1.0",
     "react-dom": "19.1.0",
     "react-hook-form": "^7.64.0",
-    "svgo": "^4.0.0",
-    "tailwind-merge": "^3.3.1"
+    "tailwind-merge": "^3.3.1",
+    "class-variance-authority": "^0.7.1",
+    "@radix-ui/react-popover": "^1.1.15",
+    "@radix-ui/react-slot": "^1.2.3",
+    "react-day-picker": "^9.11.1",
+    "date-fns": "^4.1.0",
+    "lucide-react": "^0.540.0"
   },
   "devDependencies": {
+    "svgo": "^4.0.0",
     ...
-    "class-variance-authority": "^0.7.1",
-    "@radix-ui/react-popover": "^1.1.15",
-    "date-fns": "^4.1.0",
-    "react-day-picker": "^9.11.1",
-    "@radix-ui/react-slot": "^1.2.3",
-    "lucide-react": "^0.540.0"
   }

의존성 위치를 점검하는 빠른 확인 스크립트입니다(런타임 import되는 패키지가 devDependencies에 남아있는지 검사).


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== 런타임 import 탐지 =="
rg -nP --type=ts --type=tsx -C0 \
  -e "from\s+'@radix-ui/react-popover'" \
  -e "from\s+'@radix-ui/react-slot'" \
  -e "from\s+'react-day-picker'" \
  -e "from\s+'date-fns'" \
  -e "from\s+'class-variance-authority'" \
  -e "from\s+'lucide-react'"

echo
echo "== package.json devDependencies 위치 검사 =="
jq -r '.devDependencies | keys[]?' package.json | \
  rg -n "^(class-variance-authority|@radix-ui/react-popover|@radix-ui/react-slot|react-day-picker|date-fns|lucide-react)$" || true

Length of output: 47



---

🏁 Script executed:

```shell
#!/bin/bash
set -euo pipefail

echo "== package.json 현재 상태 확인 =="
cat package.json | jq '.dependencies, .devDependencies' | head -60

echo
echo "== 런타임 import 탐지 (6개 패키지) =="
rg -nP --type=ts -C0 \
  -e "from\s+'@radix-ui/react-popover'" \
  -e "from\s+'@radix-ui/react-slot'" \
  -e "from\s+'react-day-picker'" \
  -e "from\s+'date-fns'" \
  -e "from\s+'class-variance-authority'" \
  -e "from\s+'lucide-react'" || echo "No matches found"

echo
echo "== svgo 빌드 스크립트 사용 확인 =="
cat package.json | jq '.scripts'

echo
echo "== svgo 코드 사용 확인 =="
rg -nP "svgo" --type=ts -C2 || echo "No svgo usage found in TypeScript files"

Length of output: 72


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json 현재 상태 확인 =="
jq '.dependencies, .devDependencies' package.json 2>/dev/null || echo "package.json not found"

echo
echo "== 런타임 import 탐지 (6개 패키지) =="
echo "Searching for runtime imports..."
rg -nP "from\s+['\"](@radix-ui/react-popover|@radix-ui/react-slot|react-day-picker|date-fns|class-variance-authority|lucide-react)['\"]" \
  --type=ts --type=js || echo "No matches found"

echo
echo "== package.json 스크립트 확인 =="
jq '.scripts' package.json

echo
echo "== svgo 사용 패턴 확인 =="
rg -n "svgo" --type=ts --type=js -C2 || echo "No svgo usage found"

echo
echo "== devDependencies 상세 확인 =="
jq '.devDependencies | keys | map(select(. == "class-variance-authority" or . == "@radix-ui/react-popover" or . == "@radix-ui/react-slot" or . == "react-day-picker" or . == "date-fns" or . == "lucide-react"))' package.json

Length of output: 1810


런타임 의존성이 devDependencies에 있어 프로덕션 배포 시 누락 위험 – 즉시 수정 필요

검증 결과 @radix-ui/react-popover, @radix-ui/react-slot, class-variance-authority, react-day-pickersrc/shared/components/datePicker/ 파일들에서 런타임에 직접 import되고 있습니다. --prod 플래그나 Docker multi-stage 빌드에서는 devDependencies가 제외되어 앱 실행 중 모듈을 찾을 수 없는 오류가 발생합니다.

동시에 svgopackage.json 스크립트에서만 사용되므로(build 시 icons:clean 실행) 현재 dependencies에 있는 것이 잘못되었습니다.

필수 수정:

  • 6개 패키지를 dependencies로 이동
  • svgo를 devDependencies로 이동
   "dependencies": {
     "axios": "^1.12.2",
     "clsx": "^2.1.1",
     "next": "15.5.4",
     "react": "19.1.0",
     "react-dom": "19.1.0",
     "react-hook-form": "^7.64.0",
-    "svgo": "^4.0.0",
-    "tailwind-merge": "^3.3.1"
+    "tailwind-merge": "^3.3.1",
+    "class-variance-authority": "^0.7.1",
+    "@radix-ui/react-popover": "^1.1.15",
+    "@radix-ui/react-slot": "^1.2.3",
+    "react-day-picker": "^9.11.1",
+    "date-fns": "^4.1.0",
+    "lucide-react": "^0.540.0"
   },
   "devDependencies": {
+    "svgo": "^4.0.0",
     "@eslint/eslintrc": "^3",
     ...
-    "class-variance-authority": "^0.7.1",
-    "@radix-ui/react-popover": "^1.1.15",
-    "date-fns": "^4.1.0",
-    "react-day-picker": "^9.11.1",
-    "@radix-ui/react-slot": "^1.2.3",
-    "lucide-react": "^0.540.0"
   }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In package.json around lines 37 to 42, several runtime libraries are incorrectly
listed as devDependencies which will be omitted in production builds and break
imports from src/shared/components/datePicker; move these
packages—class-variance-authority, @radix-ui/react-popover, date-fns,
react-day-picker, @radix-ui/react-slot, lucide-react—into "dependencies", and
move svgo from "dependencies" into "devDependencies"; update package.json
accordingly and run your package manager (npm/yarn/pnpm) to update the lockfile
so production installs include the runtime libs.

}
}
600 changes: 600 additions & 0 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

22 changes: 6 additions & 16 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import { ControlBar, Header, Tag } from '@/shared/components';
import { DatePicker } from '@/shared/components';
import { Icon } from '@/shared/icons';
import LocationBubble from '@/shared/components/container/LocationBubble';
import LocationCard from '@/shared/components/container/LocationCard';
import EventCard from '@/shared/components/container/EventCard';
import AddressCopy from '@/shared/components/button/AddressCopy';
import { useState } from 'react';

export default function Home() {
const [date, setDate] = useState<Date>();

return (
<>
<div className='flex flex-col items-center justify-center min-h-screen py-2 bg-gray-50'>
<main className='flex flex-col items-center justify-center w-full flex-1 px-4 sm:px-20 text-center'>
<Header onClick={() => {}} className='' title={'글다'} />
<ControlBar isLoggedIn={false} onLogin={() => {}} userName={''} />
<ControlBar isLoggedIn={true} userName='홍길동' onLogin={() => {}} />
<DatePicker value={date} onChange={setDate} />

<h1 className='text-4xl sm:text-6xl font-extrabold text-gray-900 mb-4'>
초기 세팅 완료
<Icon name='User' color='mint-600' />
</h1>

<Tag label='리스트로 보기' icon='MapPin_' variant='toggle' />
<Tag label='지도로 보기' icon='FadersHorizontal' variant='toggle' />
<Tag label='#데이트' variant='hash' />
<Icon name='CalendarBlank' size={200} color='gray-900' />
<Icon name='User' size={200} color='blue-400' />
<Icon name='User' size={200} color='red-300' />
<Icon name='User' size={200} color='red-300' />
<p className='mt-3 text-lg sm:text-2xl text-gray-600'>
Next.js(Page Router) + TS + Tailwind + Axios
</p>
Expand Down
23 changes: 23 additions & 0 deletions src/shared/components/datePicker/CalendarDropdownAdapter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { CustomDropdown } from './CustomDropdown';
import type { DropdownProps } from 'react-day-picker';

export function CalendarDropdownAdapter({
value,
onChange,
options,
'aria-label': ariaLabel,
}: DropdownProps) {
return (
<CustomDropdown
value={value as number}
options={options ?? []}
aria-label={ariaLabel}
onChange={(next) => {
const evt = {
target: { value: String(next) },
} as unknown as React.ChangeEvent<HTMLSelectElement>;
onChange?.(evt);
}}
/>
);
Comment on lines +10 to +22
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

타입 안전성 문제: value의 타입 변환

value as number 캐스팅은 DropdownPropsvaluestring | number | undefined일 수 있는데, 타입 검증 없이 number로 가정합니다. react-day-picker가 string 값을 전달하는 경우 런타임 오류나 예기치 않은 동작이 발생할 수 있습니다.

타입 가드를 추가하여 안전하게 처리하세요:

 export function CalendarDropdownAdapter({
   value,
   onChange,
   options,
   'aria-label': ariaLabel,
 }: DropdownProps) {
+  const numericValue = typeof value === 'number' ? value : undefined;
+
   return (
     <CustomDropdown
-      value={value as number}
+      value={numericValue}
       options={options ?? []}
       aria-label={ariaLabel}
       onChange={(next) => {
         const evt = {
           target: { value: String(next) },
         } as unknown as React.ChangeEvent<HTMLSelectElement>;
         onChange?.(evt);
       }}
     />
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (
<CustomDropdown
value={value as number}
options={options ?? []}
aria-label={ariaLabel}
onChange={(next) => {
const evt = {
target: { value: String(next) },
} as unknown as React.ChangeEvent<HTMLSelectElement>;
onChange?.(evt);
}}
/>
);
export function CalendarDropdownAdapter({
value,
onChange,
options,
'aria-label': ariaLabel,
}: DropdownProps) {
const numericValue = typeof value === 'number' ? value : undefined;
return (
<CustomDropdown
value={numericValue}
options={options ?? []}
aria-label={ariaLabel}
onChange={(next) => {
const evt = {
target: { value: String(next) },
} as unknown as React.ChangeEvent<HTMLSelectElement>;
onChange?.(evt);
}}
/>
);
}
🤖 Prompt for AI Agents
In src/shared/components/datePicker/CalendarDropdownAdapter.tsx around lines 10
to 22, the code force-casts value to number which is unsafe because
DropdownProps.value can be string | number | undefined; add a type guard to
detect if value is a string, number, or undefined and normalize it before
passing to CustomDropdown (e.g., parseInt or Number when value is a numeric
string, use a safe fallback like undefined or null when value is invalid, or
pass the original string when options are string-typed), and ensure the onChange
event conversion matches that normalized type so no unchecked casts remain.

}
177 changes: 177 additions & 0 deletions src/shared/components/datePicker/CustomDropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import { cn } from '@/shared/lib';
import { useCallback, useEffect, useRef, useState } from 'react';
import type { DropdownOption } from 'react-day-picker';

export interface CustomDropdownProps {
value?: number;
onChange: (next: number) => void;
options: DropdownOption[];
'aria-label'?: string;
className?: string;
}

export function CustomDropdown({
value,
onChange,
options,
'aria-label': ariaLabel,
className,
}: CustomDropdownProps) {
const [open, setOpen] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
const listRef = useRef<HTMLUListElement>(null);

const calcSelectedIndex = useCallback(() => {
const i = options.findIndex((o) => o.value === value);
return i >= 0 ? i : 0;
}, [options, value]);

const [activeIndex, setActiveIndex] = useState<number>(calcSelectedIndex());

useEffect(() => {
setActiveIndex(calcSelectedIndex());
}, [calcSelectedIndex]);

const label =
options.find((o) => o.value === value)?.label ?? options[0]?.label ?? '';

useEffect(() => {
if (!open) return;

const handler = (e: Event) => {
const t = (e as Event).target as Node | null;
if (!t) return;
if (!buttonRef.current?.contains(t) && !listRef.current?.contains(t)) {
setOpen(false);
}
};

const usePointer =
typeof window !== 'undefined' && 'PointerEvent' in window;

if (usePointer) {
document.addEventListener('pointerdown', handler as EventListener, true);
return () =>
document.removeEventListener(
'pointerdown',
handler as EventListener,
true,
);
} else {
document.addEventListener('mousedown', handler as EventListener, true);
document.addEventListener('touchstart', handler as EventListener, true);
return () => {
document.removeEventListener(
'mousedown',
handler as EventListener,
true,
);
document.removeEventListener(
'touchstart',
handler as EventListener,
true,
);
};
}
}, [open]);

const onKeyDown = (e: React.KeyboardEvent) => {
if (
!open &&
(e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === ' ')
) {
e.preventDefault();
setOpen(true);
return;
}
if (!open) return;

if (e.key === 'Escape') {
setOpen(false);
return;
}

if (e.key === 'ArrowDown') {
e.preventDefault();
let i = activeIndex + 1;
while (i < options.length && options[i].disabled) i++;
setActiveIndex(Math.min(i, options.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
let i = activeIndex - 1;
while (i >= 0 && options[i].disabled) i--;
setActiveIndex(Math.max(i, 0));
Comment on lines +94 to +103
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

키보드 내비게이션 버그: 비활성 옵션에 멈출 수 있음

ArrowDown/ArrowUp 로직에서 모든 남은 옵션이 disabled인 경우, while 루프가 배열 끝까지 진행한 뒤 Math.min/Math.max로 인해 비활성 옵션에 activeIndex가 설정될 수 있습니다.

재현 시나리오:

  • activeIndex=0, options=[{disabled:false}, {disabled:true}, {disabled:true}]
  • ArrowDown 입력 → i=1 (disabled) → i=2 (disabled) → i=3 (종료) → setActiveIndex(2)
  • 결과: options[2]는 disabled인데 활성화됨

비활성 옵션을 건너뛸 수 있도록 수정하세요:

 if (e.key === 'ArrowDown') {
   e.preventDefault();
   let i = activeIndex + 1;
   while (i < options.length && options[i].disabled) i++;
-  setActiveIndex(Math.min(i, options.length - 1));
+  if (i < options.length) {
+    setActiveIndex(i);
+  }
 } else if (e.key === 'ArrowUp') {
   e.preventDefault();
   let i = activeIndex - 1;
   while (i >= 0 && options[i].disabled) i--;
-  setActiveIndex(Math.max(i, 0));
+  if (i >= 0) {
+    setActiveIndex(i);
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (e.key === 'ArrowDown') {
e.preventDefault();
let i = activeIndex + 1;
while (i < options.length && options[i].disabled) i++;
setActiveIndex(Math.min(i, options.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
let i = activeIndex - 1;
while (i >= 0 && options[i].disabled) i--;
setActiveIndex(Math.max(i, 0));
if (e.key === 'ArrowDown') {
e.preventDefault();
let i = activeIndex + 1;
while (i < options.length && options[i].disabled) i++;
if (i < options.length) {
setActiveIndex(i);
}
} else if (e.key === 'ArrowUp') {
e.preventDefault();
let i = activeIndex - 1;
while (i >= 0 && options[i].disabled) i--;
if (i >= 0) {
setActiveIndex(i);
}
}
🤖 Prompt for AI Agents
In src/shared/components/datePicker/CustomDropdown.tsx around lines 94 to 103,
the ArrowDown/ArrowUp handlers can set activeIndex to a disabled option when the
while loop runs past the array bounds; change the logic so you scan for the next
non-disabled index and only call setActiveIndex if a valid index was found
within bounds, otherwise leave activeIndex unchanged (i.e., compute nextIdx by
advancing/retreating while skipping disabled, then if nextIdx is within [0,
options.length-1] and options[nextIdx] is not disabled call
setActiveIndex(nextIdx); do the same mirrored behavior for ArrowUp).

} else if (e.key === 'Enter') {
e.preventDefault();
const opt = options[activeIndex];
if (opt && !opt.disabled) {
onChange(opt.value);
setOpen(false);
}
}
};

return (
<div className={cn('relative inline-block', className)}>
<button
ref={buttonRef}
type='button'
aria-haspopup='listbox'
aria-expanded={open}
aria-label={ariaLabel}
onClick={() => setOpen((v) => !v)}
onKeyDown={onKeyDown}
className={cn(
'h-[32px] rounded-[12px] bg-pink-50 border border-pink-100',
'px-3 pr-8 text-label-lg text-pink-800 inline-flex items-center gap-1',
'focus:outline-none focus:ring-2 focus:ring-pink-200',
)}
>
{label}
</button>

{open && (
<ul
ref={listRef}
role='listbox'
tabIndex={-1}
aria-label={ariaLabel}
className={cn(
'absolute z-50 mt-2 w-[14rem] max-h-[20rem] overflow-auto',
'rounded-[12px] border border-pink-200 bg-white p-1 shadow-xl',
)}
>
{options.map((opt, idx) => {
const selected = opt.value === value;
const highlighted = idx === activeIndex;

return (
<li
key={opt.value}
role='option'
aria-selected={selected}
aria-disabled={opt.disabled || undefined}
onMouseEnter={() => setActiveIndex(idx)}
onPointerDown={(e) => e.preventDefault()}
onClick={() => {
if (!opt.disabled) {
onChange(opt.value);
setOpen(false);
}
}}
className={cn(
'relative cursor-pointer select-none rounded-[8px] px-3 py-2 text-body-md',
opt.disabled && 'opacity-40 pointer-events-none',
highlighted && 'bg-pink-50',
selected && 'text-pink-800 font-medium',
)}
>
{opt.label}
</li>
);
})}
</ul>
)}
</div>
);
}
88 changes: 88 additions & 0 deletions src/shared/components/datePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
'use client';
import { Calendar } from './calendar';
import { Button } from './button';
import { Popover, PopoverContent, PopoverTrigger } from './popover';
import { Icon } from '@/shared/icons';
import { cn } from '@/shared/lib';
import { useEffect, useState } from 'react';

interface DatePickerProps {
value?: Date;
onChange?: (date: Date) => void;
defaultValue?: Date;
className?: string;
}
const toFirstOfMonth = (d: Date) => new Date(d.getFullYear(), d.getMonth(), 1);
export function DatePicker({
value,
onChange,
defaultValue,
className,
...calendarProps
}: DatePickerProps) {
const [open, setOpen] = useState(false);
const [innerDate, setInnerDate] = useState<Date | undefined>(defaultValue);

const [displayMonth, setDisplayMonth] = useState<Date>(
toFirstOfMonth(value ?? innerDate ?? new Date()),
);

useEffect(() => {
if (value !== undefined) {
setInnerDate(value);
setDisplayMonth(toFirstOfMonth(value));
}
}, [value]);

useEffect(() => {
if (open) {
const base = value ?? innerDate ?? new Date();
setDisplayMonth(toFirstOfMonth(base));
}
}, [open, value, innerDate]);

const selected = value ?? innerDate;

const handleSelect = (d?: Date) => {
if (!d) return;
onChange ? onChange(d) : setInnerDate(d);
setDisplayMonth(toFirstOfMonth(d));
setOpen(false);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 value랑 deafultvalue 있어서 물어봐요..
sync 타이밍 안꼬이고 잘 호출하나요..? ineerDate잘 업데이트 되는지 궁금합니다

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇.. 네 따로 문제는 없었어요 한번 더 체크해볼게요


const today = new Date();
const startOfToday = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate(),
);

return (
<div className={cn('flex flex-col gap-3', className)}>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button className='w-[16rem] h-[4rem] justify-between rounded-[500px] text-label-lg text-pink-300 bg-pink-50 border border-pink-100'>
{selected ? selected.toLocaleDateString() : 'Select date'}
<Icon name='CalendarBlank' color='pink-400' size={14} />
</Button>
</PopoverTrigger>

<PopoverContent className='w-[24rem] p-0' align='start'>
<Calendar
mode='single'
className='w-[24rem] h-auto p-3 [--cell-size:2.8rem]'
selected={selected}
onSelect={handleSelect}
month={displayMonth}
onMonthChange={setDisplayMonth}
captionLayout='dropdown'
disabled={{ before: startOfToday }}
fromDate={startOfToday}
toYear={new Date(new Date().getFullYear() + 5, 0, 1).getFullYear()}
{...calendarProps}
/>
</PopoverContent>
</Popover>
</div>
);
}
32 changes: 32 additions & 0 deletions src/shared/components/datePicker/PopoverContent.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use client';
import * as PopoverPrimitive from '@radix-ui/react-popover';
import { cn } from '@/shared/lib';
import { forwardRef } from 'react';

export const PopoverContent = forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(function PopoverContent(
{ className, align = 'center', sideOffset = 4, ...props },
ref,
) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
data-slot='popover-content'
align={align}
sideOffset={sideOffset}
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0',
'data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]: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',
'z-50 w-72 origin-(--radix-popover-content-transform-origin)',
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
);
});
Loading
Loading