sellmate-design-system-react 0.1.11 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/SActionModal/SActionModal.cjs +9 -3
- package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
- package/dist/components/SActionModal/SActionModal.js +9 -3
- package/dist/components/SActionModal/SActionModal.js.map +1 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs +9 -3
- package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
- package/dist/components/SBarcodeInput/SBarcodeInput.js +9 -3
- package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
- package/dist/components/SCalendar/SCalendar.cjs +9 -3
- package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
- package/dist/components/SCalendar/SCalendar.js +9 -3
- package/dist/components/SCalendar/SCalendar.js.map +1 -1
- package/dist/components/SChip/SChip.cjs +9 -3
- package/dist/components/SChip/SChip.cjs.map +1 -1
- package/dist/components/SChip/SChip.js +9 -3
- package/dist/components/SChip/SChip.js.map +1 -1
- package/dist/components/SChipInput/SChipInput.cjs +25 -10
- package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
- package/dist/components/SChipInput/SChipInput.js +25 -10
- package/dist/components/SChipInput/SChipInput.js.map +1 -1
- package/dist/components/SCircleProgress/SCircleProgress.cjs +6 -2
- package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
- package/dist/components/SCircleProgress/SCircleProgress.js +6 -2
- package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
- package/dist/components/SConfirmModal/SConfirmModal.cjs +9 -3
- package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
- package/dist/components/SConfirmModal/SConfirmModal.js +9 -3
- package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.cjs +9 -3
- package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
- package/dist/components/SDatePicker/SDatePicker.js +9 -3
- package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs +9 -3
- package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
- package/dist/components/SDateRangePicker/SDateRangePicker.js +9 -3
- package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.cjs +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.js +1 -1
- package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
- package/dist/components/SField/SField.cjs +9 -3
- package/dist/components/SField/SField.cjs.map +1 -1
- package/dist/components/SField/SField.js +9 -3
- package/dist/components/SField/SField.js.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.cjs +9 -3
- package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
- package/dist/components/SFilePicker/SFilePicker.js +9 -3
- package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
- package/dist/components/SGhostButton/SGhostButton.cjs +9 -3
- package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
- package/dist/components/SGhostButton/SGhostButton.d.ts +7 -1
- package/dist/components/SGhostButton/SGhostButton.js +9 -3
- package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
- package/dist/components/SGnb/SGnb.cjs +3478 -0
- package/dist/components/SGnb/SGnb.cjs.map +1 -0
- package/dist/components/SGnb/SGnb.d.ts +39 -0
- package/dist/components/SGnb/SGnb.js +3456 -0
- package/dist/components/SGnb/SGnb.js.map +1 -0
- package/dist/components/SGnb/gnb.config.d.ts +87 -0
- package/dist/components/SGnb/index.d.ts +2 -0
- package/dist/components/SGuide/SGuide.cjs +9 -3
- package/dist/components/SGuide/SGuide.cjs.map +1 -1
- package/dist/components/SGuide/SGuide.js +9 -3
- package/dist/components/SGuide/SGuide.js.map +1 -1
- package/dist/components/SInput/SInput.cjs +9 -3
- package/dist/components/SInput/SInput.cjs.map +1 -1
- package/dist/components/SInput/SInput.js +9 -3
- package/dist/components/SInput/SInput.js.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs +20 -6
- package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
- package/dist/components/SKeyValueTable/SKeyValueTable.js +20 -6
- package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
- package/dist/components/SLayout/SLayout.cjs +97 -0
- package/dist/components/SLayout/SLayout.cjs.map +1 -0
- package/dist/components/SLayout/SLayout.d.ts +47 -0
- package/dist/components/SLayout/SLayout.js +93 -0
- package/dist/components/SLayout/SLayout.js.map +1 -0
- package/dist/components/SLayout/index.d.ts +1 -0
- package/dist/components/SLoadingContainer/SLoadingContainer.cjs +6 -2
- package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
- package/dist/components/SLoadingContainer/SLoadingContainer.js +6 -2
- package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
- package/dist/components/SLoadingContainer/loading.cjs +6 -2
- package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
- package/dist/components/SLoadingContainer/loading.js +6 -2
- package/dist/components/SLoadingContainer/loading.js.map +1 -1
- package/dist/components/SLoadingModal/SLoadingModal.cjs +15 -5
- package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
- package/dist/components/SLoadingModal/SLoadingModal.js +15 -5
- package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
- package/dist/components/SModalContainer/SModalContainer.cjs +9 -3
- package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
- package/dist/components/SModalContainer/SModalContainer.d.ts +10 -0
- package/dist/components/SModalContainer/SModalContainer.js +9 -3
- package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.cjs +9 -3
- package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
- package/dist/components/SNumberInput/SNumberInput.js +9 -3
- package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
- package/dist/components/SPage/SPage.cjs +104 -0
- package/dist/components/SPage/SPage.cjs.map +1 -0
- package/dist/components/SPage/SPage.d.ts +13 -0
- package/dist/components/SPage/SPage.js +102 -0
- package/dist/components/SPage/SPage.js.map +1 -0
- package/dist/components/SPage/index.d.ts +1 -0
- package/dist/components/SPopover/SPopover.cjs +9 -3
- package/dist/components/SPopover/SPopover.cjs.map +1 -1
- package/dist/components/SPopover/SPopover.js +9 -3
- package/dist/components/SPopover/SPopover.js.map +1 -1
- package/dist/components/SPortal/SPortal.cjs +1 -1
- package/dist/components/SPortal/SPortal.cjs.map +1 -1
- package/dist/components/SPortal/SPortal.js +1 -1
- package/dist/components/SPortal/SPortal.js.map +1 -1
- package/dist/components/SSelect/SSelect.cjs +20 -6
- package/dist/components/SSelect/SSelect.cjs.map +1 -1
- package/dist/components/SSelect/SSelect.js +20 -6
- package/dist/components/SSelect/SSelect.js.map +1 -1
- package/dist/components/STable/STable.cjs +28 -10
- package/dist/components/STable/STable.cjs.map +1 -1
- package/dist/components/STable/STable.js +28 -10
- package/dist/components/STable/STable.js.map +1 -1
- package/dist/components/STextarea/STextarea.cjs +9 -3
- package/dist/components/STextarea/STextarea.cjs.map +1 -1
- package/dist/components/STextarea/STextarea.js +9 -3
- package/dist/components/STextarea/STextarea.js.map +1 -1
- package/dist/components/SToast/SToast.cjs +9 -3
- package/dist/components/SToast/SToast.cjs.map +1 -1
- package/dist/components/SToast/SToast.js +9 -3
- package/dist/components/SToast/SToast.js.map +1 -1
- package/dist/components/SToast/SToastContainer.cjs +9 -3
- package/dist/components/SToast/SToastContainer.cjs.map +1 -1
- package/dist/components/SToast/SToastContainer.js +9 -3
- package/dist/components/SToast/SToastContainer.js.map +1 -1
- package/dist/components/STooltip/STooltip.cjs +9 -3
- package/dist/components/STooltip/STooltip.cjs.map +1 -1
- package/dist/components/STooltip/STooltip.js +9 -3
- package/dist/components/STooltip/STooltip.js.map +1 -1
- package/dist/index.cjs +453 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +444 -19
- package/dist/index.js.map +1 -1
- package/dist/styles.css +530 -26
- package/dist/theme.css +70 -14
- package/package.json +1 -1
|
@@ -120,7 +120,7 @@ function SPortal({
|
|
|
120
120
|
onCloseAutoFocus,
|
|
121
121
|
onClick: autoClose ? () => onClose?.() : void 0,
|
|
122
122
|
className: cn(
|
|
123
|
-
"z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg)
|
|
123
|
+
"z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
|
|
124
124
|
maxHeight != null && "overflow-y-auto",
|
|
125
125
|
fit && "min-w-(--radix-popover-trigger-width)",
|
|
126
126
|
FLOATING_SLIDE_ANIM,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/lib/floating-anim.ts","../../../src/lib/portal-container.tsx","../../../src/components/SPortal/SPortal.tsx"],"names":["extendTailwindMerge","clsx","createContext","useContext","jsxs","RPopover","jsx"],"mappings":"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","file":"SPortal.cjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * theme.css 의 --text-* 는 기본 Tailwind 폰트사이즈 스케일(xs/sm/base...)이 아닌\n * 숫자 스케일(10~32)이라, 확장 없이는 tailwind-merge 가 이를 텍스트 컬러 그룹으로\n * 오인해 text-12 같은 클래스를 text-(--color-var) 뒤에서 충돌 제거해버린다.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['10', '11', '12', '13', '14', '16', '18', '20', '24', '28', '32'] }],\n },\n },\n});\n\n/**\n * className 병합 유틸.\n * clsx로 조건부 클래스를 합치고, tailwind-merge로 Tailwind 클래스 충돌을 해소한다.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Radix Floating(SPopover/STooltip/RTooltip) Content 의 placement 방향별 슬라이드+페이드.\n * Radix 가 붙이는 data-side(실제 렌더 방향)·data-state 에 맞춰 열림/닫힘 애니메이션을 적용한다.\n * (sd-floating-portal 의 sd-floating-slide-* 포팅 — 키프레임은 theme.css `--animate-floating-*`)\n *\n * 예: side=top 이면 아래(4px)에서 위로 올라오며 나타나고, 닫힐 때 다시 내려가며 사라진다.\n *\n * @radix-ui/react-popover · react-dropdown-menu 의 data-state 는 open/closed 지만,\n * @radix-ui/react-tooltip 은 open 대신 delayed-open/instant-open 을 쓴다 — 두 케이스 모두 커버한다.\n * Tailwind JIT는 소스에 리터럴로 등장하는 클래스명만 스캔하므로 템플릿으로 조합하지 않고 전부 나열한다.\n */\nexport const FLOATING_SLIDE_ANIM = [\n 'data-[state=open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=delayed-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=instant-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=closed]:data-[side=top]:animate-floating-out-top',\n 'data-[state=open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom',\n 'data-[state=open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=delayed-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=instant-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=closed]:data-[side=left]:animate-floating-out-left',\n 'data-[state=open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=delayed-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=instant-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=closed]:data-[side=right]:animate-floating-out-right',\n].join(' ');\n","'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Floating 컴포넌트(STooltip/SPopover/SSelect listbox/SDropdownButton/SDatePicker 등)가\n * 자신의 Portal 을 어디로 렌더할지 결정하는 컨텍스트.\n *\n * 기본값(null)이면 각 컴포넌트는 body 로 portal 한다.\n * SModalContainer 는 모달이 열려있는 동안 이 값을 \"모달 스태킹 밴드(z-1001) 안의 전용 노드\"로\n * 채워서, 모달 안에서 열린 floating 이 모달 딤(z-1000) 뒤에 가려지지 않고 위로 뜨게 한다.\n * 반대로 모달 밖(베이스 화면)에서 열린 floating 은 여전히 body(z-50)로 나가므로,\n * 나중에 뜬 모달이 정상적으로 그 위를 덮는다. (소속에 따라 상대적으로 결정)\n */\nconst PortalContainerContext = createContext<HTMLElement | null>(null);\nconst InsideModalContext = createContext(false);\n\nexport const PortalContainerProvider = PortalContainerContext.Provider;\nexport const InsideModalProvider = InsideModalContext.Provider;\n\n/**\n * 현재 소속된 Portal 컨테이너를 반환한다.\n * Radix `*.Portal` 의 `container`, Ariakit 의 `portalElement` 에 그대로 넘기면 되며,\n * 컨테이너가 없을 때(모달 밖)는 `undefined` 를 반환해 각 라이브러리 기본값(body)을 쓴다.\n */\nexport function usePortalContainer(): HTMLElement | undefined {\n return useContext(PortalContainerContext) ?? undefined;\n}\n\n/**\n * 현재 floating 이 모달 내부 컨텍스트에서 렌더링되는지 반환한다.\n * portal target ref 가 아직 준비되지 않은 첫 렌더에서도 true를 유지해\n * 모달 내부 전용 우회 로직이 깜빡이지 않도록 한다.\n */\nexport function useInsideModal(): boolean {\n return useContext(InsideModalContext);\n}\n","'use client';\n\nimport { type CSSProperties, type ReactNode, type RefObject } from 'react';\nimport * as RPopover from '@radix-ui/react-popover';\nimport { cn } from '../../lib/cn';\nimport { FLOATING_SLIDE_ANIM } from '../../lib/floating-anim';\nimport { usePortalContainer } from '../../lib/portal-container';\n\nexport type SPortalPlacement = 'top' | 'bottom' | 'left' | 'right';\nexport type SPortalAlign = 'start' | 'center' | 'end';\n\n/** Radix Anchor 가 위치 계산에 쓰는 최소 인터페이스 (HTMLElement 가 충족한다). */\ntype Measurable = { getBoundingClientRect(): DOMRect };\n\nexport interface SPortalProps {\n /** 표시 여부 (제어). onClose 와 함께 사용한다. */\n open: boolean;\n /** 외부 클릭 · ESC 등으로 닫힘이 요청될 때 (sd-portal `sdClose` 대응). */\n onClose?: () => void;\n /** 열림/닫힘 변경 전체 콜백 (선택). */\n onOpenChange?: (open: boolean) => void;\n /**\n * 위치 기준이 되는 앵커 엘리먼트 ref (sd-portal `parentRef` 대응).\n * 트리거 버튼 등에 그대로 걸면 그 엘리먼트에 붙어서 뜬다.\n */\n anchorRef: RefObject<HTMLElement | null>;\n /** 표시 방향 (공간이 부족하면 자동으로 반대 방향으로 전환). */\n placement?: SPortalPlacement;\n /** 교차축 정렬 (start·center·end). */\n align?: SPortalAlign;\n /** 앵커로부터의 간격 px (sd-portal offset[1]). */\n offset?: number;\n /** 정렬축 오프셋 px (sd-portal offset[0]). */\n alignOffset?: number;\n /** 앵커 너비에 맞춰 최소 너비를 잡는다. */\n fit?: boolean;\n /** 콘텐츠 최대 높이 px. 넘치면 세로 스크롤. */\n maxHeight?: number;\n /** 콘텐츠 내부를 클릭하면 자동으로 닫는다. */\n autoClose?: boolean;\n /** 외부 클릭 · ESC 로 닫히지 않게 한다. */\n persistent?: boolean;\n /** 뷰포트 경계 여백 px (충돌 회피 패딩). */\n viewportPadding?: number;\n /** 열릴 때 포커스 이동 처리 (기본은 Radix가 콘텐츠로 포커스를 옮김). e.preventDefault()로 막을 수 있다. */\n onOpenAutoFocus?: (event: Event) => void;\n /** 닫힐 때 포커스 복귀 처리 (기본은 Radix가 앵커로 포커스를 되돌림). e.preventDefault()로 막을 수 있다. */\n onCloseAutoFocus?: (event: Event) => void;\n /** 본문 내용 (콘텐츠 자유). */\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n ariaLabel?: string;\n}\n\n/**\n * SPortal — sd-portal/sd-floating-portal 의 사용성을 Radix Popover 로 옮긴 범용 앵커드 플로팅 컨테이너.\n *\n * 트리거를 감싸지 않고 `anchorRef` 로 임의의 엘리먼트에 붙는다(=sd-portal `parentRef`).\n * open/onClose 로 외부에서 제어하며, flip·collision·스크롤 재배치·외부클릭·ESC·모달 포털은\n * Radix 가 처리한다. 콘텐츠는 children 으로 완전히 자유롭다.\n */\nexport function SPortal({\n open,\n onClose,\n onOpenChange,\n anchorRef,\n placement = 'bottom',\n align = 'start',\n offset = 4,\n alignOffset = 0,\n fit = false,\n maxHeight,\n autoClose = false,\n persistent = false,\n viewportPadding = 0,\n onOpenAutoFocus,\n onCloseAutoFocus,\n children,\n className,\n style,\n ariaLabel,\n}: SPortalProps) {\n const portalContainer = usePortalContainer();\n\n return (\n <RPopover.Root\n open={open}\n onOpenChange={next => {\n onOpenChange?.(next);\n if (!next) onClose?.();\n }}\n >\n <RPopover.Anchor virtualRef={anchorRef as RefObject<Measurable>} />\n <RPopover.Portal container={portalContainer}>\n <RPopover.Content\n side={placement}\n align={align}\n sideOffset={offset}\n alignOffset={alignOffset}\n collisionPadding={viewportPadding}\n aria-label={ariaLabel}\n // persistent: 외부 클릭·ESC 로 닫히지 않음\n // 아니면: 앵커(트리거) 위 클릭은 소비자가 토글하므로 여기서 닫지 않는다(중복 토글 방지)\n onPointerDownOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n // 앵커(예: input)에 이미 포커스가 있는 상태로 열리는 경우(클릭 즉시 오픈), Radix는 포커스가\n // 레이어 밖에 있다고 판단해 열리자마자 닫아버린다 — 앵커 내부 포커스는 outside로 취급하지 않는다.\n onFocusOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n onEscapeKeyDown={persistent ? e => e.preventDefault() : undefined}\n onOpenAutoFocus={onOpenAutoFocus}\n onCloseAutoFocus={onCloseAutoFocus}\n onClick={autoClose ? () => onClose?.() : undefined}\n className={cn(\n 'z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm',\n maxHeight != null && 'overflow-y-auto',\n fit && 'min-w-(--radix-popover-trigger-width)',\n FLOATING_SLIDE_ANIM,\n className,\n )}\n style={{ maxHeight, ...style }}\n >\n {children}\n </RPopover.Content>\n </RPopover.Portal>\n </RPopover.Root>\n );\n}\n\nSPortal.displayName = 'SPortal';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/lib/floating-anim.ts","../../../src/lib/portal-container.tsx","../../../src/components/SPortal/SPortal.tsx"],"names":["extendTailwindMerge","clsx","createContext","useContext","jsxs","RPopover","jsx"],"mappings":"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","file":"SPortal.cjs","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * theme.css 의 --text-* 는 기본 Tailwind 폰트사이즈 스케일(xs/sm/base...)이 아닌\n * 숫자 스케일(10~32)이라, 확장 없이는 tailwind-merge 가 이를 텍스트 컬러 그룹으로\n * 오인해 text-12 같은 클래스를 text-(--color-var) 뒤에서 충돌 제거해버린다.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['10', '11', '12', '13', '14', '16', '18', '20', '24', '28', '32'] }],\n },\n },\n});\n\n/**\n * className 병합 유틸.\n * clsx로 조건부 클래스를 합치고, tailwind-merge로 Tailwind 클래스 충돌을 해소한다.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Radix Floating(SPopover/STooltip/RTooltip) Content 의 placement 방향별 슬라이드+페이드.\n * Radix 가 붙이는 data-side(실제 렌더 방향)·data-state 에 맞춰 열림/닫힘 애니메이션을 적용한다.\n * (sd-floating-portal 의 sd-floating-slide-* 포팅 — 키프레임은 theme.css `--animate-floating-*`)\n *\n * 예: side=top 이면 아래(4px)에서 위로 올라오며 나타나고, 닫힐 때 다시 내려가며 사라진다.\n *\n * @radix-ui/react-popover · react-dropdown-menu 의 data-state 는 open/closed 지만,\n * @radix-ui/react-tooltip 은 open 대신 delayed-open/instant-open 을 쓴다 — 두 케이스 모두 커버한다.\n * Tailwind JIT는 소스에 리터럴로 등장하는 클래스명만 스캔하므로 템플릿으로 조합하지 않고 전부 나열한다.\n */\nexport const FLOATING_SLIDE_ANIM = [\n 'data-[state=open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=delayed-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=instant-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=closed]:data-[side=top]:animate-floating-out-top',\n 'data-[state=open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom',\n 'data-[state=open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=delayed-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=instant-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=closed]:data-[side=left]:animate-floating-out-left',\n 'data-[state=open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=delayed-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=instant-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=closed]:data-[side=right]:animate-floating-out-right',\n].join(' ');\n","'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Floating 컴포넌트(STooltip/SPopover/SSelect listbox/SDropdownButton/SDatePicker 등)가\n * 자신의 Portal 을 어디로 렌더할지 결정하는 컨텍스트.\n *\n * 기본값(null)이면 각 컴포넌트는 body 로 portal 한다.\n * SModalContainer 는 모달이 열려있는 동안 이 값을 \"모달 스태킹 밴드(z-1001) 안의 전용 노드\"로\n * 채워서, 모달 안에서 열린 floating 이 모달 딤(z-1000) 뒤에 가려지지 않고 위로 뜨게 한다.\n * 반대로 모달 밖(베이스 화면)에서 열린 floating 은 여전히 body(z-50)로 나가므로,\n * 나중에 뜬 모달이 정상적으로 그 위를 덮는다. (소속에 따라 상대적으로 결정)\n */\nconst PortalContainerContext = createContext<HTMLElement | null>(null);\nconst InsideModalContext = createContext(false);\n\nexport const PortalContainerProvider = PortalContainerContext.Provider;\nexport const InsideModalProvider = InsideModalContext.Provider;\n\n/**\n * 현재 소속된 Portal 컨테이너를 반환한다.\n * Radix `*.Portal` 의 `container`, Ariakit 의 `portalElement` 에 그대로 넘기면 되며,\n * 컨테이너가 없을 때(모달 밖)는 `undefined` 를 반환해 각 라이브러리 기본값(body)을 쓴다.\n */\nexport function usePortalContainer(): HTMLElement | undefined {\n return useContext(PortalContainerContext) ?? undefined;\n}\n\n/**\n * 현재 floating 이 모달 내부 컨텍스트에서 렌더링되는지 반환한다.\n * portal target ref 가 아직 준비되지 않은 첫 렌더에서도 true를 유지해\n * 모달 내부 전용 우회 로직이 깜빡이지 않도록 한다.\n */\nexport function useInsideModal(): boolean {\n return useContext(InsideModalContext);\n}\n","'use client';\n\nimport { type CSSProperties, type ReactNode, type RefObject } from 'react';\nimport * as RPopover from '@radix-ui/react-popover';\nimport { cn } from '../../lib/cn';\nimport { FLOATING_SLIDE_ANIM } from '../../lib/floating-anim';\nimport { usePortalContainer } from '../../lib/portal-container';\n\nexport type SPortalPlacement = 'top' | 'bottom' | 'left' | 'right';\nexport type SPortalAlign = 'start' | 'center' | 'end';\n\n/** Radix Anchor 가 위치 계산에 쓰는 최소 인터페이스 (HTMLElement 가 충족한다). */\ntype Measurable = { getBoundingClientRect(): DOMRect };\n\nexport interface SPortalProps {\n /** 표시 여부 (제어). onClose 와 함께 사용한다. */\n open: boolean;\n /** 외부 클릭 · ESC 등으로 닫힘이 요청될 때 (sd-portal `sdClose` 대응). */\n onClose?: () => void;\n /** 열림/닫힘 변경 전체 콜백 (선택). */\n onOpenChange?: (open: boolean) => void;\n /**\n * 위치 기준이 되는 앵커 엘리먼트 ref (sd-portal `parentRef` 대응).\n * 트리거 버튼 등에 그대로 걸면 그 엘리먼트에 붙어서 뜬다.\n */\n anchorRef: RefObject<HTMLElement | null>;\n /** 표시 방향 (공간이 부족하면 자동으로 반대 방향으로 전환). */\n placement?: SPortalPlacement;\n /** 교차축 정렬 (start·center·end). */\n align?: SPortalAlign;\n /** 앵커로부터의 간격 px (sd-portal offset[1]). */\n offset?: number;\n /** 정렬축 오프셋 px (sd-portal offset[0]). */\n alignOffset?: number;\n /** 앵커 너비에 맞춰 최소 너비를 잡는다. */\n fit?: boolean;\n /** 콘텐츠 최대 높이 px. 넘치면 세로 스크롤. */\n maxHeight?: number;\n /** 콘텐츠 내부를 클릭하면 자동으로 닫는다. */\n autoClose?: boolean;\n /** 외부 클릭 · ESC 로 닫히지 않게 한다. */\n persistent?: boolean;\n /** 뷰포트 경계 여백 px (충돌 회피 패딩). */\n viewportPadding?: number;\n /** 열릴 때 포커스 이동 처리 (기본은 Radix가 콘텐츠로 포커스를 옮김). e.preventDefault()로 막을 수 있다. */\n onOpenAutoFocus?: (event: Event) => void;\n /** 닫힐 때 포커스 복귀 처리 (기본은 Radix가 앵커로 포커스를 되돌림). e.preventDefault()로 막을 수 있다. */\n onCloseAutoFocus?: (event: Event) => void;\n /** 본문 내용 (콘텐츠 자유). */\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n ariaLabel?: string;\n}\n\n/**\n * SPortal — sd-portal/sd-floating-portal 의 사용성을 Radix Popover 로 옮긴 범용 앵커드 플로팅 컨테이너.\n *\n * 트리거를 감싸지 않고 `anchorRef` 로 임의의 엘리먼트에 붙는다(=sd-portal `parentRef`).\n * open/onClose 로 외부에서 제어하며, flip·collision·스크롤 재배치·외부클릭·ESC·모달 포털은\n * Radix 가 처리한다. 콘텐츠는 children 으로 완전히 자유롭다.\n */\nexport function SPortal({\n open,\n onClose,\n onOpenChange,\n anchorRef,\n placement = 'bottom',\n align = 'start',\n offset = 4,\n alignOffset = 0,\n fit = false,\n maxHeight,\n autoClose = false,\n persistent = false,\n viewportPadding = 0,\n onOpenAutoFocus,\n onCloseAutoFocus,\n children,\n className,\n style,\n ariaLabel,\n}: SPortalProps) {\n const portalContainer = usePortalContainer();\n\n return (\n <RPopover.Root\n open={open}\n onOpenChange={next => {\n onOpenChange?.(next);\n if (!next) onClose?.();\n }}\n >\n <RPopover.Anchor virtualRef={anchorRef as RefObject<Measurable>} />\n <RPopover.Portal container={portalContainer}>\n <RPopover.Content\n side={placement}\n align={align}\n sideOffset={offset}\n alignOffset={alignOffset}\n collisionPadding={viewportPadding}\n aria-label={ariaLabel}\n // persistent: 외부 클릭·ESC 로 닫히지 않음\n // 아니면: 앵커(트리거) 위 클릭은 소비자가 토글하므로 여기서 닫지 않는다(중복 토글 방지)\n onPointerDownOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n // 앵커(예: input)에 이미 포커스가 있는 상태로 열리는 경우(클릭 즉시 오픈), Radix는 포커스가\n // 레이어 밖에 있다고 판단해 열리자마자 닫아버린다 — 앵커 내부 포커스는 outside로 취급하지 않는다.\n onFocusOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n onEscapeKeyDown={persistent ? e => e.preventDefault() : undefined}\n onOpenAutoFocus={onOpenAutoFocus}\n onCloseAutoFocus={onCloseAutoFocus}\n onClick={autoClose ? () => onClose?.() : undefined}\n className={cn(\n 'z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm',\n maxHeight != null && 'overflow-y-auto',\n fit && 'min-w-(--radix-popover-trigger-width)',\n FLOATING_SLIDE_ANIM,\n className,\n )}\n style={{ maxHeight, ...style }}\n >\n {children}\n </RPopover.Content>\n </RPopover.Portal>\n </RPopover.Root>\n );\n}\n\nSPortal.displayName = 'SPortal';\n"]}
|
|
@@ -98,7 +98,7 @@ function SPortal({
|
|
|
98
98
|
onCloseAutoFocus,
|
|
99
99
|
onClick: autoClose ? () => onClose?.() : void 0,
|
|
100
100
|
className: cn(
|
|
101
|
-
"z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg)
|
|
101
|
+
"z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
|
|
102
102
|
maxHeight != null && "overflow-y-auto",
|
|
103
103
|
fit && "min-w-(--radix-popover-trigger-width)",
|
|
104
104
|
FLOATING_SLIDE_ANIM,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/lib/floating-anim.ts","../../../src/lib/portal-container.tsx","../../../src/components/SPortal/SPortal.tsx"],"names":[],"mappings":"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","file":"SPortal.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * theme.css 의 --text-* 는 기본 Tailwind 폰트사이즈 스케일(xs/sm/base...)이 아닌\n * 숫자 스케일(10~32)이라, 확장 없이는 tailwind-merge 가 이를 텍스트 컬러 그룹으로\n * 오인해 text-12 같은 클래스를 text-(--color-var) 뒤에서 충돌 제거해버린다.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['10', '11', '12', '13', '14', '16', '18', '20', '24', '28', '32'] }],\n },\n },\n});\n\n/**\n * className 병합 유틸.\n * clsx로 조건부 클래스를 합치고, tailwind-merge로 Tailwind 클래스 충돌을 해소한다.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Radix Floating(SPopover/STooltip/RTooltip) Content 의 placement 방향별 슬라이드+페이드.\n * Radix 가 붙이는 data-side(실제 렌더 방향)·data-state 에 맞춰 열림/닫힘 애니메이션을 적용한다.\n * (sd-floating-portal 의 sd-floating-slide-* 포팅 — 키프레임은 theme.css `--animate-floating-*`)\n *\n * 예: side=top 이면 아래(4px)에서 위로 올라오며 나타나고, 닫힐 때 다시 내려가며 사라진다.\n *\n * @radix-ui/react-popover · react-dropdown-menu 의 data-state 는 open/closed 지만,\n * @radix-ui/react-tooltip 은 open 대신 delayed-open/instant-open 을 쓴다 — 두 케이스 모두 커버한다.\n * Tailwind JIT는 소스에 리터럴로 등장하는 클래스명만 스캔하므로 템플릿으로 조합하지 않고 전부 나열한다.\n */\nexport const FLOATING_SLIDE_ANIM = [\n 'data-[state=open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=delayed-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=instant-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=closed]:data-[side=top]:animate-floating-out-top',\n 'data-[state=open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom',\n 'data-[state=open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=delayed-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=instant-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=closed]:data-[side=left]:animate-floating-out-left',\n 'data-[state=open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=delayed-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=instant-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=closed]:data-[side=right]:animate-floating-out-right',\n].join(' ');\n","'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Floating 컴포넌트(STooltip/SPopover/SSelect listbox/SDropdownButton/SDatePicker 등)가\n * 자신의 Portal 을 어디로 렌더할지 결정하는 컨텍스트.\n *\n * 기본값(null)이면 각 컴포넌트는 body 로 portal 한다.\n * SModalContainer 는 모달이 열려있는 동안 이 값을 \"모달 스태킹 밴드(z-1001) 안의 전용 노드\"로\n * 채워서, 모달 안에서 열린 floating 이 모달 딤(z-1000) 뒤에 가려지지 않고 위로 뜨게 한다.\n * 반대로 모달 밖(베이스 화면)에서 열린 floating 은 여전히 body(z-50)로 나가므로,\n * 나중에 뜬 모달이 정상적으로 그 위를 덮는다. (소속에 따라 상대적으로 결정)\n */\nconst PortalContainerContext = createContext<HTMLElement | null>(null);\nconst InsideModalContext = createContext(false);\n\nexport const PortalContainerProvider = PortalContainerContext.Provider;\nexport const InsideModalProvider = InsideModalContext.Provider;\n\n/**\n * 현재 소속된 Portal 컨테이너를 반환한다.\n * Radix `*.Portal` 의 `container`, Ariakit 의 `portalElement` 에 그대로 넘기면 되며,\n * 컨테이너가 없을 때(모달 밖)는 `undefined` 를 반환해 각 라이브러리 기본값(body)을 쓴다.\n */\nexport function usePortalContainer(): HTMLElement | undefined {\n return useContext(PortalContainerContext) ?? undefined;\n}\n\n/**\n * 현재 floating 이 모달 내부 컨텍스트에서 렌더링되는지 반환한다.\n * portal target ref 가 아직 준비되지 않은 첫 렌더에서도 true를 유지해\n * 모달 내부 전용 우회 로직이 깜빡이지 않도록 한다.\n */\nexport function useInsideModal(): boolean {\n return useContext(InsideModalContext);\n}\n","'use client';\n\nimport { type CSSProperties, type ReactNode, type RefObject } from 'react';\nimport * as RPopover from '@radix-ui/react-popover';\nimport { cn } from '../../lib/cn';\nimport { FLOATING_SLIDE_ANIM } from '../../lib/floating-anim';\nimport { usePortalContainer } from '../../lib/portal-container';\n\nexport type SPortalPlacement = 'top' | 'bottom' | 'left' | 'right';\nexport type SPortalAlign = 'start' | 'center' | 'end';\n\n/** Radix Anchor 가 위치 계산에 쓰는 최소 인터페이스 (HTMLElement 가 충족한다). */\ntype Measurable = { getBoundingClientRect(): DOMRect };\n\nexport interface SPortalProps {\n /** 표시 여부 (제어). onClose 와 함께 사용한다. */\n open: boolean;\n /** 외부 클릭 · ESC 등으로 닫힘이 요청될 때 (sd-portal `sdClose` 대응). */\n onClose?: () => void;\n /** 열림/닫힘 변경 전체 콜백 (선택). */\n onOpenChange?: (open: boolean) => void;\n /**\n * 위치 기준이 되는 앵커 엘리먼트 ref (sd-portal `parentRef` 대응).\n * 트리거 버튼 등에 그대로 걸면 그 엘리먼트에 붙어서 뜬다.\n */\n anchorRef: RefObject<HTMLElement | null>;\n /** 표시 방향 (공간이 부족하면 자동으로 반대 방향으로 전환). */\n placement?: SPortalPlacement;\n /** 교차축 정렬 (start·center·end). */\n align?: SPortalAlign;\n /** 앵커로부터의 간격 px (sd-portal offset[1]). */\n offset?: number;\n /** 정렬축 오프셋 px (sd-portal offset[0]). */\n alignOffset?: number;\n /** 앵커 너비에 맞춰 최소 너비를 잡는다. */\n fit?: boolean;\n /** 콘텐츠 최대 높이 px. 넘치면 세로 스크롤. */\n maxHeight?: number;\n /** 콘텐츠 내부를 클릭하면 자동으로 닫는다. */\n autoClose?: boolean;\n /** 외부 클릭 · ESC 로 닫히지 않게 한다. */\n persistent?: boolean;\n /** 뷰포트 경계 여백 px (충돌 회피 패딩). */\n viewportPadding?: number;\n /** 열릴 때 포커스 이동 처리 (기본은 Radix가 콘텐츠로 포커스를 옮김). e.preventDefault()로 막을 수 있다. */\n onOpenAutoFocus?: (event: Event) => void;\n /** 닫힐 때 포커스 복귀 처리 (기본은 Radix가 앵커로 포커스를 되돌림). e.preventDefault()로 막을 수 있다. */\n onCloseAutoFocus?: (event: Event) => void;\n /** 본문 내용 (콘텐츠 자유). */\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n ariaLabel?: string;\n}\n\n/**\n * SPortal — sd-portal/sd-floating-portal 의 사용성을 Radix Popover 로 옮긴 범용 앵커드 플로팅 컨테이너.\n *\n * 트리거를 감싸지 않고 `anchorRef` 로 임의의 엘리먼트에 붙는다(=sd-portal `parentRef`).\n * open/onClose 로 외부에서 제어하며, flip·collision·스크롤 재배치·외부클릭·ESC·모달 포털은\n * Radix 가 처리한다. 콘텐츠는 children 으로 완전히 자유롭다.\n */\nexport function SPortal({\n open,\n onClose,\n onOpenChange,\n anchorRef,\n placement = 'bottom',\n align = 'start',\n offset = 4,\n alignOffset = 0,\n fit = false,\n maxHeight,\n autoClose = false,\n persistent = false,\n viewportPadding = 0,\n onOpenAutoFocus,\n onCloseAutoFocus,\n children,\n className,\n style,\n ariaLabel,\n}: SPortalProps) {\n const portalContainer = usePortalContainer();\n\n return (\n <RPopover.Root\n open={open}\n onOpenChange={next => {\n onOpenChange?.(next);\n if (!next) onClose?.();\n }}\n >\n <RPopover.Anchor virtualRef={anchorRef as RefObject<Measurable>} />\n <RPopover.Portal container={portalContainer}>\n <RPopover.Content\n side={placement}\n align={align}\n sideOffset={offset}\n alignOffset={alignOffset}\n collisionPadding={viewportPadding}\n aria-label={ariaLabel}\n // persistent: 외부 클릭·ESC 로 닫히지 않음\n // 아니면: 앵커(트리거) 위 클릭은 소비자가 토글하므로 여기서 닫지 않는다(중복 토글 방지)\n onPointerDownOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n // 앵커(예: input)에 이미 포커스가 있는 상태로 열리는 경우(클릭 즉시 오픈), Radix는 포커스가\n // 레이어 밖에 있다고 판단해 열리자마자 닫아버린다 — 앵커 내부 포커스는 outside로 취급하지 않는다.\n onFocusOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n onEscapeKeyDown={persistent ? e => e.preventDefault() : undefined}\n onOpenAutoFocus={onOpenAutoFocus}\n onCloseAutoFocus={onCloseAutoFocus}\n onClick={autoClose ? () => onClose?.() : undefined}\n className={cn(\n 'z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) py-4 shadow-normal-sm',\n maxHeight != null && 'overflow-y-auto',\n fit && 'min-w-(--radix-popover-trigger-width)',\n FLOATING_SLIDE_ANIM,\n className,\n )}\n style={{ maxHeight, ...style }}\n >\n {children}\n </RPopover.Content>\n </RPopover.Portal>\n </RPopover.Root>\n );\n}\n\nSPortal.displayName = 'SPortal';\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/lib/cn.ts","../../../src/lib/floating-anim.ts","../../../src/lib/portal-container.tsx","../../../src/components/SPortal/SPortal.tsx"],"names":[],"mappings":"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","file":"SPortal.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { extendTailwindMerge } from 'tailwind-merge';\n\n/**\n * theme.css 의 --text-* 는 기본 Tailwind 폰트사이즈 스케일(xs/sm/base...)이 아닌\n * 숫자 스케일(10~32)이라, 확장 없이는 tailwind-merge 가 이를 텍스트 컬러 그룹으로\n * 오인해 text-12 같은 클래스를 text-(--color-var) 뒤에서 충돌 제거해버린다.\n */\nconst twMerge = extendTailwindMerge({\n extend: {\n classGroups: {\n 'font-size': [{ text: ['10', '11', '12', '13', '14', '16', '18', '20', '24', '28', '32'] }],\n },\n },\n});\n\n/**\n * className 병합 유틸.\n * clsx로 조건부 클래스를 합치고, tailwind-merge로 Tailwind 클래스 충돌을 해소한다.\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","/**\n * Radix Floating(SPopover/STooltip/RTooltip) Content 의 placement 방향별 슬라이드+페이드.\n * Radix 가 붙이는 data-side(실제 렌더 방향)·data-state 에 맞춰 열림/닫힘 애니메이션을 적용한다.\n * (sd-floating-portal 의 sd-floating-slide-* 포팅 — 키프레임은 theme.css `--animate-floating-*`)\n *\n * 예: side=top 이면 아래(4px)에서 위로 올라오며 나타나고, 닫힐 때 다시 내려가며 사라진다.\n *\n * @radix-ui/react-popover · react-dropdown-menu 의 data-state 는 open/closed 지만,\n * @radix-ui/react-tooltip 은 open 대신 delayed-open/instant-open 을 쓴다 — 두 케이스 모두 커버한다.\n * Tailwind JIT는 소스에 리터럴로 등장하는 클래스명만 스캔하므로 템플릿으로 조합하지 않고 전부 나열한다.\n */\nexport const FLOATING_SLIDE_ANIM = [\n 'data-[state=open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=delayed-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=instant-open]:data-[side=top]:animate-floating-in-top',\n 'data-[state=closed]:data-[side=top]:animate-floating-out-top',\n 'data-[state=open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=delayed-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=instant-open]:data-[side=bottom]:animate-floating-in-bottom',\n 'data-[state=closed]:data-[side=bottom]:animate-floating-out-bottom',\n 'data-[state=open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=delayed-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=instant-open]:data-[side=left]:animate-floating-in-left',\n 'data-[state=closed]:data-[side=left]:animate-floating-out-left',\n 'data-[state=open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=delayed-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=instant-open]:data-[side=right]:animate-floating-in-right',\n 'data-[state=closed]:data-[side=right]:animate-floating-out-right',\n].join(' ');\n","'use client';\n\nimport { createContext, useContext } from 'react';\n\n/**\n * Floating 컴포넌트(STooltip/SPopover/SSelect listbox/SDropdownButton/SDatePicker 등)가\n * 자신의 Portal 을 어디로 렌더할지 결정하는 컨텍스트.\n *\n * 기본값(null)이면 각 컴포넌트는 body 로 portal 한다.\n * SModalContainer 는 모달이 열려있는 동안 이 값을 \"모달 스태킹 밴드(z-1001) 안의 전용 노드\"로\n * 채워서, 모달 안에서 열린 floating 이 모달 딤(z-1000) 뒤에 가려지지 않고 위로 뜨게 한다.\n * 반대로 모달 밖(베이스 화면)에서 열린 floating 은 여전히 body(z-50)로 나가므로,\n * 나중에 뜬 모달이 정상적으로 그 위를 덮는다. (소속에 따라 상대적으로 결정)\n */\nconst PortalContainerContext = createContext<HTMLElement | null>(null);\nconst InsideModalContext = createContext(false);\n\nexport const PortalContainerProvider = PortalContainerContext.Provider;\nexport const InsideModalProvider = InsideModalContext.Provider;\n\n/**\n * 현재 소속된 Portal 컨테이너를 반환한다.\n * Radix `*.Portal` 의 `container`, Ariakit 의 `portalElement` 에 그대로 넘기면 되며,\n * 컨테이너가 없을 때(모달 밖)는 `undefined` 를 반환해 각 라이브러리 기본값(body)을 쓴다.\n */\nexport function usePortalContainer(): HTMLElement | undefined {\n return useContext(PortalContainerContext) ?? undefined;\n}\n\n/**\n * 현재 floating 이 모달 내부 컨텍스트에서 렌더링되는지 반환한다.\n * portal target ref 가 아직 준비되지 않은 첫 렌더에서도 true를 유지해\n * 모달 내부 전용 우회 로직이 깜빡이지 않도록 한다.\n */\nexport function useInsideModal(): boolean {\n return useContext(InsideModalContext);\n}\n","'use client';\n\nimport { type CSSProperties, type ReactNode, type RefObject } from 'react';\nimport * as RPopover from '@radix-ui/react-popover';\nimport { cn } from '../../lib/cn';\nimport { FLOATING_SLIDE_ANIM } from '../../lib/floating-anim';\nimport { usePortalContainer } from '../../lib/portal-container';\n\nexport type SPortalPlacement = 'top' | 'bottom' | 'left' | 'right';\nexport type SPortalAlign = 'start' | 'center' | 'end';\n\n/** Radix Anchor 가 위치 계산에 쓰는 최소 인터페이스 (HTMLElement 가 충족한다). */\ntype Measurable = { getBoundingClientRect(): DOMRect };\n\nexport interface SPortalProps {\n /** 표시 여부 (제어). onClose 와 함께 사용한다. */\n open: boolean;\n /** 외부 클릭 · ESC 등으로 닫힘이 요청될 때 (sd-portal `sdClose` 대응). */\n onClose?: () => void;\n /** 열림/닫힘 변경 전체 콜백 (선택). */\n onOpenChange?: (open: boolean) => void;\n /**\n * 위치 기준이 되는 앵커 엘리먼트 ref (sd-portal `parentRef` 대응).\n * 트리거 버튼 등에 그대로 걸면 그 엘리먼트에 붙어서 뜬다.\n */\n anchorRef: RefObject<HTMLElement | null>;\n /** 표시 방향 (공간이 부족하면 자동으로 반대 방향으로 전환). */\n placement?: SPortalPlacement;\n /** 교차축 정렬 (start·center·end). */\n align?: SPortalAlign;\n /** 앵커로부터의 간격 px (sd-portal offset[1]). */\n offset?: number;\n /** 정렬축 오프셋 px (sd-portal offset[0]). */\n alignOffset?: number;\n /** 앵커 너비에 맞춰 최소 너비를 잡는다. */\n fit?: boolean;\n /** 콘텐츠 최대 높이 px. 넘치면 세로 스크롤. */\n maxHeight?: number;\n /** 콘텐츠 내부를 클릭하면 자동으로 닫는다. */\n autoClose?: boolean;\n /** 외부 클릭 · ESC 로 닫히지 않게 한다. */\n persistent?: boolean;\n /** 뷰포트 경계 여백 px (충돌 회피 패딩). */\n viewportPadding?: number;\n /** 열릴 때 포커스 이동 처리 (기본은 Radix가 콘텐츠로 포커스를 옮김). e.preventDefault()로 막을 수 있다. */\n onOpenAutoFocus?: (event: Event) => void;\n /** 닫힐 때 포커스 복귀 처리 (기본은 Radix가 앵커로 포커스를 되돌림). e.preventDefault()로 막을 수 있다. */\n onCloseAutoFocus?: (event: Event) => void;\n /** 본문 내용 (콘텐츠 자유). */\n children?: ReactNode;\n className?: string;\n style?: CSSProperties;\n ariaLabel?: string;\n}\n\n/**\n * SPortal — sd-portal/sd-floating-portal 의 사용성을 Radix Popover 로 옮긴 범용 앵커드 플로팅 컨테이너.\n *\n * 트리거를 감싸지 않고 `anchorRef` 로 임의의 엘리먼트에 붙는다(=sd-portal `parentRef`).\n * open/onClose 로 외부에서 제어하며, flip·collision·스크롤 재배치·외부클릭·ESC·모달 포털은\n * Radix 가 처리한다. 콘텐츠는 children 으로 완전히 자유롭다.\n */\nexport function SPortal({\n open,\n onClose,\n onOpenChange,\n anchorRef,\n placement = 'bottom',\n align = 'start',\n offset = 4,\n alignOffset = 0,\n fit = false,\n maxHeight,\n autoClose = false,\n persistent = false,\n viewportPadding = 0,\n onOpenAutoFocus,\n onCloseAutoFocus,\n children,\n className,\n style,\n ariaLabel,\n}: SPortalProps) {\n const portalContainer = usePortalContainer();\n\n return (\n <RPopover.Root\n open={open}\n onOpenChange={next => {\n onOpenChange?.(next);\n if (!next) onClose?.();\n }}\n >\n <RPopover.Anchor virtualRef={anchorRef as RefObject<Measurable>} />\n <RPopover.Portal container={portalContainer}>\n <RPopover.Content\n side={placement}\n align={align}\n sideOffset={offset}\n alignOffset={alignOffset}\n collisionPadding={viewportPadding}\n aria-label={ariaLabel}\n // persistent: 외부 클릭·ESC 로 닫히지 않음\n // 아니면: 앵커(트리거) 위 클릭은 소비자가 토글하므로 여기서 닫지 않는다(중복 토글 방지)\n onPointerDownOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n // 앵커(예: input)에 이미 포커스가 있는 상태로 열리는 경우(클릭 즉시 오픈), Radix는 포커스가\n // 레이어 밖에 있다고 판단해 열리자마자 닫아버린다 — 앵커 내부 포커스는 outside로 취급하지 않는다.\n onFocusOutside={e => {\n if (persistent || anchorRef.current?.contains(e.target as Node)) {\n e.preventDefault();\n }\n }}\n onEscapeKeyDown={persistent ? e => e.preventDefault() : undefined}\n onOpenAutoFocus={onOpenAutoFocus}\n onCloseAutoFocus={onCloseAutoFocus}\n onClick={autoClose ? () => onClose?.() : undefined}\n className={cn(\n 'z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm',\n maxHeight != null && 'overflow-y-auto',\n fit && 'min-w-(--radix-popover-trigger-width)',\n FLOATING_SLIDE_ANIM,\n className,\n )}\n style={{ maxHeight, ...style }}\n >\n {children}\n </RPopover.Content>\n </RPopover.Portal>\n </RPopover.Root>\n );\n}\n\nSPortal.displayName = 'SPortal';\n"]}
|
|
@@ -3091,9 +3091,12 @@ function SGhostButton({
|
|
|
3091
3091
|
size = "sm",
|
|
3092
3092
|
intent = "default",
|
|
3093
3093
|
ariaLabel = "",
|
|
3094
|
+
ariaPressed,
|
|
3094
3095
|
tooltipText,
|
|
3095
3096
|
disabled = false,
|
|
3096
3097
|
onClick,
|
|
3098
|
+
anchorClassName,
|
|
3099
|
+
anchorStyle,
|
|
3097
3100
|
className,
|
|
3098
3101
|
style
|
|
3099
3102
|
}) {
|
|
@@ -3116,6 +3119,8 @@ function SGhostButton({
|
|
|
3116
3119
|
};
|
|
3117
3120
|
const s = SIZE[size];
|
|
3118
3121
|
const contentColor = disabled ? intent === "inverse" ? "var(--cmp-ghostButton-inverse-disabled)" : "var(--cmp-ghostButton-icon-disabled)" : `var(--cmp-ghostButton-${intent}-content)`;
|
|
3122
|
+
const text = (tooltipText ?? "").trim();
|
|
3123
|
+
const hasTooltip = text !== "";
|
|
3119
3124
|
const btn = /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3120
3125
|
"button",
|
|
3121
3126
|
{
|
|
@@ -3123,6 +3128,7 @@ function SGhostButton({
|
|
|
3123
3128
|
disabled,
|
|
3124
3129
|
"aria-disabled": disabled ? "true" : void 0,
|
|
3125
3130
|
"aria-label": ariaLabel.trim() || void 0,
|
|
3131
|
+
"aria-pressed": ariaPressed,
|
|
3126
3132
|
tabIndex: disabled ? -1 : void 0,
|
|
3127
3133
|
onClick: (e) => {
|
|
3128
3134
|
if (disabled) {
|
|
@@ -3156,13 +3162,13 @@ function SGhostButton({
|
|
|
3156
3162
|
]
|
|
3157
3163
|
}
|
|
3158
3164
|
);
|
|
3159
|
-
|
|
3160
|
-
if (text === "") return btn;
|
|
3165
|
+
if (!hasTooltip) return btn;
|
|
3161
3166
|
return /* @__PURE__ */ jsxRuntime.jsxs(RPopover2__namespace.Root, { open, onOpenChange: setOpen, children: [
|
|
3162
3167
|
/* @__PURE__ */ jsxRuntime.jsx(RPopover2__namespace.Anchor, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
3163
3168
|
"span",
|
|
3164
3169
|
{
|
|
3165
|
-
className: "inline-flex",
|
|
3170
|
+
className: cn("inline-flex", anchorClassName),
|
|
3171
|
+
style: anchorStyle,
|
|
3166
3172
|
onMouseEnter: openNow,
|
|
3167
3173
|
onMouseLeave: startHide,
|
|
3168
3174
|
onFocus: openNow,
|
|
@@ -3535,7 +3541,7 @@ function SPortal({
|
|
|
3535
3541
|
onCloseAutoFocus,
|
|
3536
3542
|
onClick: autoClose ? () => onClose?.() : void 0,
|
|
3537
3543
|
className: cn(
|
|
3538
|
-
"z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg)
|
|
3544
|
+
"z-50 overflow-hidden rounded-(--cmp-listBox-radius) bg-(--cmp-listBox-bg) shadow-normal-sm",
|
|
3539
3545
|
maxHeight != null && "overflow-y-auto",
|
|
3540
3546
|
fit && "min-w-(--radix-popover-trigger-width)",
|
|
3541
3547
|
FLOATING_SLIDE_ANIM,
|
|
@@ -3885,7 +3891,7 @@ var SSelect = react.forwardRef(function SSelect2({
|
|
|
3885
3891
|
event.preventDefault();
|
|
3886
3892
|
triggerRef.current?.focus();
|
|
3887
3893
|
},
|
|
3888
|
-
className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)]
|
|
3894
|
+
className: "rounded-[var(--cmp-listBox-radius)] bg-[var(--cmp-listBox-bg)] shadow-[2px_2px_12px_2px_rgba(0,0,0,0.1)]",
|
|
3889
3895
|
style: {
|
|
3890
3896
|
...dropdownWidth ? { width: dropdownWidth } : {},
|
|
3891
3897
|
maxWidth: maxDropdownWidth
|
|
@@ -3936,6 +3942,10 @@ var SSelect = react.forwardRef(function SSelect2({
|
|
|
3936
3942
|
onSelectAll: handleSelectAll,
|
|
3937
3943
|
onSelect: handleSelect,
|
|
3938
3944
|
onHoverValue: (value2) => {
|
|
3945
|
+
if (value2 == null) {
|
|
3946
|
+
setFocusedIndex(-1);
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3939
3949
|
const index = navigableItems.findIndex(
|
|
3940
3950
|
(item) => item.isSelectAll ? value2 === "__select_all__" : item.value === value2
|
|
3941
3951
|
);
|
|
@@ -3984,6 +3994,7 @@ function ItemList({
|
|
|
3984
3994
|
),
|
|
3985
3995
|
style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: 12 },
|
|
3986
3996
|
onMouseEnter: () => onHoverValue("__select_all__"),
|
|
3997
|
+
onMouseLeave: () => onHoverValue(null),
|
|
3987
3998
|
onClick: onSelectAll,
|
|
3988
3999
|
children: [
|
|
3989
4000
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -4042,10 +4053,13 @@ function ItemList({
|
|
|
4042
4053
|
itemBase,
|
|
4043
4054
|
isSelected && !multi && "font-bold text-[color:var(--cmp-listItem-content-selected)]",
|
|
4044
4055
|
focusedValue === o.value && "bg-[var(--cmp-listItem-bg-hover)] text-[color:var(--cmp-listItem-content-hover)]",
|
|
4045
|
-
|
|
4056
|
+
// 비활성 항목은 hover/focus 스타일 대상에서 제외한다.
|
|
4057
|
+
// (:hover 는 disabled 버튼에도 매칭되므로 클래스 자체를 붙이지 않아야 한다)
|
|
4058
|
+
!o.disabled && activeClass
|
|
4046
4059
|
),
|
|
4047
4060
|
style: { paddingTop: 4, paddingBottom: 4, paddingRight: 12, paddingLeft: padLeft },
|
|
4048
4061
|
onMouseEnter: () => onHoverValue(o.value),
|
|
4062
|
+
onMouseLeave: () => onHoverValue(null),
|
|
4049
4063
|
onClick: () => onSelect(o.value),
|
|
4050
4064
|
children: [
|
|
4051
4065
|
multi && /* @__PURE__ */ jsxRuntime.jsx(
|