sellmate-design-system-react 0.1.9 → 0.1.11

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.
Files changed (148) hide show
  1. package/dist/components/SActionModal/SActionModal.cjs +3 -1
  2. package/dist/components/SActionModal/SActionModal.cjs.map +1 -1
  3. package/dist/components/SActionModal/SActionModal.js +3 -1
  4. package/dist/components/SActionModal/SActionModal.js.map +1 -1
  5. package/dist/components/SBarcodeInput/SBarcodeInput.cjs +16 -3
  6. package/dist/components/SBarcodeInput/SBarcodeInput.cjs.map +1 -1
  7. package/dist/components/SBarcodeInput/SBarcodeInput.d.ts +3 -1
  8. package/dist/components/SBarcodeInput/SBarcodeInput.js +16 -3
  9. package/dist/components/SBarcodeInput/SBarcodeInput.js.map +1 -1
  10. package/dist/components/SCalendar/SCalendar.cjs +2 -0
  11. package/dist/components/SCalendar/SCalendar.cjs.map +1 -1
  12. package/dist/components/SCalendar/SCalendar.js +2 -0
  13. package/dist/components/SCalendar/SCalendar.js.map +1 -1
  14. package/dist/components/SChip/SChip.cjs +2 -0
  15. package/dist/components/SChip/SChip.cjs.map +1 -1
  16. package/dist/components/SChip/SChip.js +2 -0
  17. package/dist/components/SChip/SChip.js.map +1 -1
  18. package/dist/components/SChipInput/SChipInput.cjs +22 -4
  19. package/dist/components/SChipInput/SChipInput.cjs.map +1 -1
  20. package/dist/components/SChipInput/SChipInput.d.ts +5 -0
  21. package/dist/components/SChipInput/SChipInput.js +22 -4
  22. package/dist/components/SChipInput/SChipInput.js.map +1 -1
  23. package/dist/components/SChipInput/index.d.ts +1 -1
  24. package/dist/components/SCircleProgress/SCircleProgress.cjs +69 -50
  25. package/dist/components/SCircleProgress/SCircleProgress.cjs.map +1 -1
  26. package/dist/components/SCircleProgress/SCircleProgress.d.ts +2 -0
  27. package/dist/components/SCircleProgress/SCircleProgress.js +69 -50
  28. package/dist/components/SCircleProgress/SCircleProgress.js.map +1 -1
  29. package/dist/components/SConfirmModal/SConfirmModal.cjs +13 -10
  30. package/dist/components/SConfirmModal/SConfirmModal.cjs.map +1 -1
  31. package/dist/components/SConfirmModal/SConfirmModal.d.ts +7 -4
  32. package/dist/components/SConfirmModal/SConfirmModal.js +13 -10
  33. package/dist/components/SConfirmModal/SConfirmModal.js.map +1 -1
  34. package/dist/components/SDatePicker/SDatePicker.cjs +16 -3
  35. package/dist/components/SDatePicker/SDatePicker.cjs.map +1 -1
  36. package/dist/components/SDatePicker/SDatePicker.d.ts +3 -1
  37. package/dist/components/SDatePicker/SDatePicker.js +16 -3
  38. package/dist/components/SDatePicker/SDatePicker.js.map +1 -1
  39. package/dist/components/SDateRangePicker/SDateRangePicker.cjs +16 -3
  40. package/dist/components/SDateRangePicker/SDateRangePicker.cjs.map +1 -1
  41. package/dist/components/SDateRangePicker/SDateRangePicker.d.ts +3 -1
  42. package/dist/components/SDateRangePicker/SDateRangePicker.js +16 -3
  43. package/dist/components/SDateRangePicker/SDateRangePicker.js.map +1 -1
  44. package/dist/components/SDropdownButton/SDropdownButton.cjs +2 -0
  45. package/dist/components/SDropdownButton/SDropdownButton.cjs.map +1 -1
  46. package/dist/components/SDropdownButton/SDropdownButton.js +2 -0
  47. package/dist/components/SDropdownButton/SDropdownButton.js.map +1 -1
  48. package/dist/components/SField/SField.cjs +14 -3
  49. package/dist/components/SField/SField.cjs.map +1 -1
  50. package/dist/components/SField/SField.d.ts +4 -1
  51. package/dist/components/SField/SField.js +14 -3
  52. package/dist/components/SField/SField.js.map +1 -1
  53. package/dist/components/SFilePicker/SFilePicker.cjs +51 -19
  54. package/dist/components/SFilePicker/SFilePicker.cjs.map +1 -1
  55. package/dist/components/SFilePicker/SFilePicker.d.ts +4 -2
  56. package/dist/components/SFilePicker/SFilePicker.js +50 -19
  57. package/dist/components/SFilePicker/SFilePicker.js.map +1 -1
  58. package/dist/components/SGhostButton/SGhostButton.cjs +2 -0
  59. package/dist/components/SGhostButton/SGhostButton.cjs.map +1 -1
  60. package/dist/components/SGhostButton/SGhostButton.js +2 -0
  61. package/dist/components/SGhostButton/SGhostButton.js.map +1 -1
  62. package/dist/components/SGuide/SGuide.cjs +2 -0
  63. package/dist/components/SGuide/SGuide.cjs.map +1 -1
  64. package/dist/components/SGuide/SGuide.js +2 -0
  65. package/dist/components/SGuide/SGuide.js.map +1 -1
  66. package/dist/components/SInput/SInput.cjs +16 -3
  67. package/dist/components/SInput/SInput.cjs.map +1 -1
  68. package/dist/components/SInput/SInput.d.ts +4 -1
  69. package/dist/components/SInput/SInput.js +16 -3
  70. package/dist/components/SInput/SInput.js.map +1 -1
  71. package/dist/components/SKeyValueTable/SKeyValueTable.cjs +530 -219
  72. package/dist/components/SKeyValueTable/SKeyValueTable.cjs.map +1 -1
  73. package/dist/components/SKeyValueTable/SKeyValueTable.d.ts +27 -6
  74. package/dist/components/SKeyValueTable/SKeyValueTable.js +529 -218
  75. package/dist/components/SKeyValueTable/SKeyValueTable.js.map +1 -1
  76. package/dist/components/SLoadingContainer/SLoadingContainer.cjs +69 -50
  77. package/dist/components/SLoadingContainer/SLoadingContainer.cjs.map +1 -1
  78. package/dist/components/SLoadingContainer/SLoadingContainer.js +69 -50
  79. package/dist/components/SLoadingContainer/SLoadingContainer.js.map +1 -1
  80. package/dist/components/SLoadingContainer/loading.cjs +69 -50
  81. package/dist/components/SLoadingContainer/loading.cjs.map +1 -1
  82. package/dist/components/SLoadingContainer/loading.js +69 -50
  83. package/dist/components/SLoadingContainer/loading.js.map +1 -1
  84. package/dist/components/SLoadingModal/SLoadingModal.cjs +75 -51
  85. package/dist/components/SLoadingModal/SLoadingModal.cjs.map +1 -1
  86. package/dist/components/SLoadingModal/SLoadingModal.d.ts +3 -1
  87. package/dist/components/SLoadingModal/SLoadingModal.js +75 -51
  88. package/dist/components/SLoadingModal/SLoadingModal.js.map +1 -1
  89. package/dist/components/SModalContainer/SModalContainer.cjs +3 -1
  90. package/dist/components/SModalContainer/SModalContainer.cjs.map +1 -1
  91. package/dist/components/SModalContainer/SModalContainer.js +3 -1
  92. package/dist/components/SModalContainer/SModalContainer.js.map +1 -1
  93. package/dist/components/SNumberInput/SNumberInput.cjs +16 -3
  94. package/dist/components/SNumberInput/SNumberInput.cjs.map +1 -1
  95. package/dist/components/SNumberInput/SNumberInput.d.ts +2 -0
  96. package/dist/components/SNumberInput/SNumberInput.js +16 -3
  97. package/dist/components/SNumberInput/SNumberInput.js.map +1 -1
  98. package/dist/components/SPopover/SPopover.cjs +2 -0
  99. package/dist/components/SPopover/SPopover.cjs.map +1 -1
  100. package/dist/components/SPopover/SPopover.js +2 -0
  101. package/dist/components/SPopover/SPopover.js.map +1 -1
  102. package/dist/components/SPortal/SPortal.cjs +2 -0
  103. package/dist/components/SPortal/SPortal.cjs.map +1 -1
  104. package/dist/components/SPortal/SPortal.js +2 -0
  105. package/dist/components/SPortal/SPortal.js.map +1 -1
  106. package/dist/components/SRadio/SRadioGroup.cjs +1 -1
  107. package/dist/components/SRadio/SRadioGroup.cjs.map +1 -1
  108. package/dist/components/SRadio/SRadioGroup.js +1 -1
  109. package/dist/components/SRadio/SRadioGroup.js.map +1 -1
  110. package/dist/components/SSelect/SSelect.cjs +530 -258
  111. package/dist/components/SSelect/SSelect.cjs.map +1 -1
  112. package/dist/components/SSelect/SSelect.d.ts +4 -2
  113. package/dist/components/SSelect/SSelect.js +531 -258
  114. package/dist/components/SSelect/SSelect.js.map +1 -1
  115. package/dist/components/STable/STable.cjs +603 -309
  116. package/dist/components/STable/STable.cjs.map +1 -1
  117. package/dist/components/STable/STable.js +604 -309
  118. package/dist/components/STable/STable.js.map +1 -1
  119. package/dist/components/STextLink/STextLink.cjs +2 -0
  120. package/dist/components/STextLink/STextLink.cjs.map +1 -1
  121. package/dist/components/STextLink/STextLink.js +2 -0
  122. package/dist/components/STextLink/STextLink.js.map +1 -1
  123. package/dist/components/STextarea/STextarea.cjs +16 -3
  124. package/dist/components/STextarea/STextarea.cjs.map +1 -1
  125. package/dist/components/STextarea/STextarea.d.ts +2 -0
  126. package/dist/components/STextarea/STextarea.js +16 -3
  127. package/dist/components/STextarea/STextarea.js.map +1 -1
  128. package/dist/components/SToast/SToast.cjs +2 -0
  129. package/dist/components/SToast/SToast.cjs.map +1 -1
  130. package/dist/components/SToast/SToast.js +2 -0
  131. package/dist/components/SToast/SToast.js.map +1 -1
  132. package/dist/components/SToast/SToastContainer.cjs +2 -0
  133. package/dist/components/SToast/SToastContainer.cjs.map +1 -1
  134. package/dist/components/SToast/SToastContainer.js +2 -0
  135. package/dist/components/SToast/SToastContainer.js.map +1 -1
  136. package/dist/components/STooltip/STooltip.cjs +3 -1
  137. package/dist/components/STooltip/STooltip.cjs.map +1 -1
  138. package/dist/components/STooltip/STooltip.js +3 -1
  139. package/dist/components/STooltip/STooltip.js.map +1 -1
  140. package/dist/index.cjs +522 -248
  141. package/dist/index.cjs.map +1 -1
  142. package/dist/index.d.ts +0 -1
  143. package/dist/index.js +523 -248
  144. package/dist/index.js.map +1 -1
  145. package/dist/lib/portal-container.d.ts +7 -0
  146. package/dist/styles.css +123 -58
  147. package/dist/theme.css +45 -21
  148. package/package.json +1 -1
@@ -58,7 +58,9 @@ var FLOATING_SLIDE_ANIM = [
58
58
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
59
59
  ].join(" ");
60
60
  var PortalContainerContext = react.createContext(null);
61
+ var InsideModalContext = react.createContext(false);
61
62
  PortalContainerContext.Provider;
63
+ InsideModalContext.Provider;
62
64
  function usePortalContainer() {
63
65
  return react.useContext(PortalContainerContext) ?? void 0;
64
66
  }
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,IAAM,UAAUA,iCAAA,CAAoB;AAAA,EAClC,MAAA,EAAQ;AAAA,IACN,WAAA,EAAa;AAAA,MACX,aAAa,CAAC,EAAE,IAAA,EAAM,CAAC,MAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAA,EAAM,MAAM,IAAA,EAAM,IAAI,GAAG;AAAA;AAC5F;AAEJ,CAAC,CAAA;AAMM,SAAS,MAAM,MAAA,EAA8B;AAClD,EAAA,OAAO,OAAA,CAAQC,SAAA,CAAK,MAAM,CAAC,CAAA;AAC7B;;;ACXO,IAAM,mBAAA,GAAsB;AAAA,EACjC,2DAAA;AAAA,EACA,mEAAA;AAAA,EACA,mEAAA;AAAA,EACA,8DAAA;AAAA,EACA,iEAAA;AAAA,EACA,yEAAA;AAAA,EACA,yEAAA;AAAA,EACA,oEAAA;AAAA,EACA,6DAAA;AAAA,EACA,qEAAA;AAAA,EACA,qEAAA;AAAA,EACA,gEAAA;AAAA,EACA,+DAAA;AAAA,EACA,uEAAA;AAAA,EACA,uEAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,GAAG,CAAA;ACdV,IAAM,sBAAA,GAAyBC,oBAAkC,IAAI,CAAA;AAE9B,sBAAA,CAAuB;AAOvD,SAAS,kBAAA,GAA8C;AAC5D,EAAA,OAAOC,gBAAA,CAAW,sBAAsB,CAAA,IAAK,MAAA;AAC/C;ACqCO,SAAS,OAAA,CAAQ;AAAA,EACtB,IAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA,SAAA;AAAA,EACA,SAAA,GAAY,QAAA;AAAA,EACZ,KAAA,GAAQ,OAAA;AAAA,EACR,MAAA,GAAS,CAAA;AAAA,EACT,WAAA,GAAc,CAAA;AAAA,EACd,GAAA,GAAM,KAAA;AAAA,EACN,SAAA;AAAA,EACA,SAAA,GAAY,KAAA;AAAA,EACZ,UAAA,GAAa,KAAA;AAAA,EACb,eAAA,GAAkB,CAAA;AAAA,EAClB,eAAA;AAAA,EACA,gBAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAAiB;AACf,EAAA,MAAM,kBAAkB,kBAAA,EAAmB;AAE3C,EAAA,uBACEC,eAAA;AAAA,IAAUC,mBAAA,CAAA,IAAA;AAAA,IAAT;AAAA,MACC,IAAA;AAAA,MACA,cAAc,CAAA,IAAA,KAAQ;AACpB,QAAA,YAAA,GAAe,IAAI,CAAA;AACnB,QAAA,IAAI,CAAC,MAAM,OAAA,IAAU;AAAA,MACvB,CAAA;AAAA,MAEA,QAAA,EAAA;AAAA,wBAAAC,cAAA,CAAUD,mBAAA,CAAA,MAAA,EAAT,EAAgB,UAAA,EAAY,SAAA,EAAoC,CAAA;AAAA,wBACjEC,cAAA,CAAUD,mBAAA,CAAA,MAAA,EAAT,EAAgB,SAAA,EAAW,eAAA,EAC1B,QAAA,kBAAAC,cAAA;AAAA,UAAUD,mBAAA,CAAA,OAAA;AAAA,UAAT;AAAA,YACC,IAAA,EAAM,SAAA;AAAA,YACN,KAAA;AAAA,YACA,UAAA,EAAY,MAAA;AAAA,YACZ,WAAA;AAAA,YACA,gBAAA,EAAkB,eAAA;AAAA,YAClB,YAAA,EAAY,SAAA;AAAA,YAGZ,sBAAsB,CAAA,CAAA,KAAK;AACzB,cAAA,IAAI,cAAc,SAAA,CAAU,OAAA,EAAS,QAAA,CAAS,CAAA,CAAE,MAAc,CAAA,EAAG;AAC/D,gBAAA,CAAA,CAAE,cAAA,EAAe;AAAA,cACnB;AAAA,YACF,CAAA;AAAA,YAGA,gBAAgB,CAAA,CAAA,KAAK;AACnB,cAAA,IAAI,cAAc,SAAA,CAAU,OAAA,EAAS,QAAA,CAAS,CAAA,CAAE,MAAc,CAAA,EAAG;AAC/D,gBAAA,CAAA,CAAE,cAAA,EAAe;AAAA,cACnB;AAAA,YACF,CAAA;AAAA,YACA,eAAA,EAAiB,UAAA,GAAa,CAAA,CAAA,KAAK,CAAA,CAAE,gBAAe,GAAI,MAAA;AAAA,YACxD,eAAA;AAAA,YACA,gBAAA;AAAA,YACA,OAAA,EAAS,SAAA,GAAY,MAAM,OAAA,IAAU,GAAI,MAAA;AAAA,YACzC,SAAA,EAAW,EAAA;AAAA,cACT,iGAAA;AAAA,cACA,aAAa,IAAA,IAAQ,iBAAA;AAAA,cACrB,GAAA,IAAO,uCAAA;AAAA,cACP,mBAAA;AAAA,cACA;AAAA,aACF;AAAA,YACA,KAAA,EAAO,EAAE,SAAA,EAAW,GAAG,KAAA,EAAM;AAAA,YAE5B;AAAA;AAAA,SACH,EACF;AAAA;AAAA;AAAA,GACF;AAEJ;AAEA,OAAA,CAAQ,WAAA,GAAc,SAAA","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);\n\nexport const PortalContainerProvider = PortalContainerContext.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","'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) 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"]}
@@ -36,7 +36,9 @@ var FLOATING_SLIDE_ANIM = [
36
36
  "data-[state=closed]:data-[side=right]:animate-floating-out-right"
37
37
  ].join(" ");
38
38
  var PortalContainerContext = createContext(null);
39
+ var InsideModalContext = createContext(false);
39
40
  PortalContainerContext.Provider;
41
+ InsideModalContext.Provider;
40
42
  function usePortalContainer() {
41
43
  return useContext(PortalContainerContext) ?? void 0;
42
44
  }
@@ -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);\n\nexport const PortalContainerProvider = PortalContainerContext.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","'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) 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"]}
@@ -122,7 +122,7 @@ function SRadioGroup({
122
122
  role: "radiogroup",
123
123
  className: cn(
124
124
  "inline-flex",
125
- direction === "vertical" ? "flex-col gap-[8px]" : "flex-row gap-[20px]",
125
+ direction === "vertical" ? "flex-col gap-[var(--cmp-radio-group-gap-vertical)]" : "flex-row gap-[var(--cmp-radio-group-gap-horizontal)]",
126
126
  className
127
127
  ),
128
128
  style,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SRadio/SRadio.tsx","../../../src/components/SRadio/SRadioGroup.tsx"],"names":["extendTailwindMerge","clsx","jsxs","jsx"],"mappings":"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","file":"SRadioGroup.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","'use client';\n\nimport { type CSSProperties, type HTMLAttributes } from 'react';\nimport { cn } from '../../lib/cn';\n\n/** 라디오 값 타입 (sd-radio SRadioValue) */\nexport type SRadioValue = string | number | boolean;\n\nexport interface SRadioProps {\n /** 이 라디오의 값 */\n val: SRadioValue;\n /** 그룹의 현재 선택 값 */\n value?: SRadioValue | null;\n /** 라벨 */\n label?: string;\n /** 비활성 */\n disabled?: boolean;\n /** native radio name (form-agnostic 그룹핑) */\n name?: string;\n /** 선택 변경 (sdUpdate) — 선택된 val 전달 */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadio — sd-radio 포팅. value===val 이면 선택. 단독 또는 SRadioGroup 안에서 사용. */\nexport function SRadio({\n val,\n value,\n label,\n disabled = false,\n name,\n onValueChange,\n className,\n style,\n}: SRadioProps) {\n // key/name 속성용 문자열 표현\n const valStr = String(val);\n const checked = value === val;\n const select = () => {\n if (!disabled && !checked) onValueChange?.(val);\n };\n\n // name 이 있으면 네이티브 <input type=\"radio\"> 가 실제 상호작용 컨트롤이 된다.\n // 이때 label 에는 role/tabIndex/키핸들러를 주지 않는다 — 상호작용 요소 중첩\n // (role=\"radio\" 안에 native radio) 및 radio 역할 중복을 피한다.\n const native = name != null && name !== '';\n\n // native 모드가 아니면 label 자체가 커스텀 radio 로 동작한다.\n const labelInteractiveProps: HTMLAttributes<HTMLLabelElement> = native\n ? {}\n : {\n 'role': 'radio',\n 'aria-checked': checked,\n 'tabIndex': disabled ? -1 : 0,\n 'onClick': select,\n 'onKeyDown': e => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n select();\n }\n },\n };\n\n return (\n <label\n {...labelInteractiveProps}\n className={cn(\n 'inline-flex items-center gap-[8px]',\n disabled ? 'cursor-not-allowed' : 'cursor-pointer',\n className,\n )}\n style={style}\n >\n {native && (\n <input\n type=\"radio\"\n name={name}\n value={valStr}\n checked={checked}\n disabled={disabled}\n // 네이티브 라디오가 포커스/키보드/선택을 담당한다. label 이 감싸므로\n // 라벨 클릭도 이 input 을 토글하며, onChange 는 값이 실제로 바뀔 때만 발생.\n onChange={() => onValueChange?.(val)}\n className=\"sr-only\"\n />\n )}\n <span\n className={cn(\n 'flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-full border border-solid border-[color:var(--rd-border)] bg-[var(--rd-bg)]',\n !disabled &&\n !checked &&\n 'hover:border-[color:var(--cmp-radio-border-hover)] hover:bg-[var(--cmp-radio-bg-hover)]',\n )}\n style={\n {\n '--rd-bg': disabled ? 'var(--cmp-radio-bg-disabled)' : 'var(--cmp-radio-bg-default)',\n '--rd-border': disabled\n ? 'var(--cmp-radio-border-disabled)'\n : checked\n ? 'var(--cmp-radio-border-active)'\n : 'var(--cmp-radio-border-default)',\n } as CSSProperties\n }\n >\n {checked && (\n <span\n className=\"h-[10px] w-[10px] rounded-full\"\n style={{\n background: disabled\n ? 'var(--cmp-radio-dot-disabled)'\n : 'var(--cmp-radio-dot-default)',\n }}\n />\n )}\n </span>\n {label && (\n <span\n className=\"grid text-[12px] leading-[20px]\"\n style={{\n color: disabled ? 'var(--cmp-radio-label-disabled)' : 'var(--cmp-radio-label-default)',\n }}\n >\n <span aria-hidden className=\"invisible font-medium\" style={{ gridArea: '1 / 1' }}>\n {label}\n </span>\n <span style={{ gridArea: '1 / 1', fontWeight: checked ? 500 : 400 }}>{label}</span>\n </span>\n )}\n </label>\n );\n}\n","'use client';\n\nimport { type CSSProperties } from 'react';\nimport { cn } from '../../lib/cn';\nimport { SRadio, type SRadioValue } from './SRadio';\n\nexport interface SRadioOption {\n /** 옵션 값 (sd-radio-group SRadioOption.value) */\n value: SRadioValue;\n label: string;\n disabled?: boolean;\n}\n\nexport interface SRadioGroupProps {\n /** 옵션 목록 */\n options?: SRadioOption[];\n /** 현재 선택 값 */\n value?: SRadioValue | null;\n /** 배치 방향 */\n direction?: 'vertical' | 'horizontal';\n /** 전체 비활성 */\n disabled?: boolean;\n /** 라디오 name 속성 (form-agnostic 그룹핑) */\n groupName?: string;\n /** 선택 변경 (sdUpdate) */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadioGroup — sd-radio-group 포팅. options를 SRadio로 렌더. */\nexport function SRadioGroup({\n options = [],\n value,\n direction = 'vertical',\n disabled = false,\n groupName,\n onValueChange,\n className,\n style,\n}: SRadioGroupProps) {\n return (\n <div\n role=\"radiogroup\"\n className={cn(\n 'inline-flex',\n direction === 'vertical' ? 'flex-col gap-[8px]' : 'flex-row gap-[20px]',\n className,\n )}\n style={style}\n >\n {options.map(o => (\n <SRadio\n key={String(o.value)}\n val={o.value}\n value={value}\n label={o.label}\n name={groupName}\n disabled={disabled || o.disabled}\n onValueChange={onValueChange}\n />\n ))}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SRadio/SRadio.tsx","../../../src/components/SRadio/SRadioGroup.tsx"],"names":["extendTailwindMerge","clsx","jsxs","jsx"],"mappings":"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","file":"SRadioGroup.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","'use client';\n\nimport { type CSSProperties, type HTMLAttributes } from 'react';\nimport { cn } from '../../lib/cn';\n\n/** 라디오 값 타입 (sd-radio SRadioValue) */\nexport type SRadioValue = string | number | boolean;\n\nexport interface SRadioProps {\n /** 이 라디오의 값 */\n val: SRadioValue;\n /** 그룹의 현재 선택 값 */\n value?: SRadioValue | null;\n /** 라벨 */\n label?: string;\n /** 비활성 */\n disabled?: boolean;\n /** native radio name (form-agnostic 그룹핑) */\n name?: string;\n /** 선택 변경 (sdUpdate) — 선택된 val 전달 */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadio — sd-radio 포팅. value===val 이면 선택. 단독 또는 SRadioGroup 안에서 사용. */\nexport function SRadio({\n val,\n value,\n label,\n disabled = false,\n name,\n onValueChange,\n className,\n style,\n}: SRadioProps) {\n // key/name 속성용 문자열 표현\n const valStr = String(val);\n const checked = value === val;\n const select = () => {\n if (!disabled && !checked) onValueChange?.(val);\n };\n\n // name 이 있으면 네이티브 <input type=\"radio\"> 가 실제 상호작용 컨트롤이 된다.\n // 이때 label 에는 role/tabIndex/키핸들러를 주지 않는다 — 상호작용 요소 중첩\n // (role=\"radio\" 안에 native radio) 및 radio 역할 중복을 피한다.\n const native = name != null && name !== '';\n\n // native 모드가 아니면 label 자체가 커스텀 radio 로 동작한다.\n const labelInteractiveProps: HTMLAttributes<HTMLLabelElement> = native\n ? {}\n : {\n 'role': 'radio',\n 'aria-checked': checked,\n 'tabIndex': disabled ? -1 : 0,\n 'onClick': select,\n 'onKeyDown': e => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n select();\n }\n },\n };\n\n return (\n <label\n {...labelInteractiveProps}\n className={cn(\n 'inline-flex items-center gap-[8px]',\n disabled ? 'cursor-not-allowed' : 'cursor-pointer',\n className,\n )}\n style={style}\n >\n {native && (\n <input\n type=\"radio\"\n name={name}\n value={valStr}\n checked={checked}\n disabled={disabled}\n // 네이티브 라디오가 포커스/키보드/선택을 담당한다. label 이 감싸므로\n // 라벨 클릭도 이 input 을 토글하며, onChange 는 값이 실제로 바뀔 때만 발생.\n onChange={() => onValueChange?.(val)}\n className=\"sr-only\"\n />\n )}\n <span\n className={cn(\n 'flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-full border border-solid border-[color:var(--rd-border)] bg-[var(--rd-bg)]',\n !disabled &&\n !checked &&\n 'hover:border-[color:var(--cmp-radio-border-hover)] hover:bg-[var(--cmp-radio-bg-hover)]',\n )}\n style={\n {\n '--rd-bg': disabled ? 'var(--cmp-radio-bg-disabled)' : 'var(--cmp-radio-bg-default)',\n '--rd-border': disabled\n ? 'var(--cmp-radio-border-disabled)'\n : checked\n ? 'var(--cmp-radio-border-active)'\n : 'var(--cmp-radio-border-default)',\n } as CSSProperties\n }\n >\n {checked && (\n <span\n className=\"h-[10px] w-[10px] rounded-full\"\n style={{\n background: disabled\n ? 'var(--cmp-radio-dot-disabled)'\n : 'var(--cmp-radio-dot-default)',\n }}\n />\n )}\n </span>\n {label && (\n <span\n className=\"grid text-[12px] leading-[20px]\"\n style={{\n color: disabled ? 'var(--cmp-radio-label-disabled)' : 'var(--cmp-radio-label-default)',\n }}\n >\n <span aria-hidden className=\"invisible font-medium\" style={{ gridArea: '1 / 1' }}>\n {label}\n </span>\n <span style={{ gridArea: '1 / 1', fontWeight: checked ? 500 : 400 }}>{label}</span>\n </span>\n )}\n </label>\n );\n}\n","'use client';\n\nimport { type CSSProperties } from 'react';\nimport { cn } from '../../lib/cn';\nimport { SRadio, type SRadioValue } from './SRadio';\n\nexport interface SRadioOption {\n /** 옵션 값 (sd-radio-group SRadioOption.value) */\n value: SRadioValue;\n label: string;\n disabled?: boolean;\n}\n\nexport interface SRadioGroupProps {\n /** 옵션 목록 */\n options?: SRadioOption[];\n /** 현재 선택 값 */\n value?: SRadioValue | null;\n /** 배치 방향 */\n direction?: 'vertical' | 'horizontal';\n /** 전체 비활성 */\n disabled?: boolean;\n /** 라디오 name 속성 (form-agnostic 그룹핑) */\n groupName?: string;\n /** 선택 변경 (sdUpdate) */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadioGroup — sd-radio-group 포팅. options를 SRadio로 렌더. */\nexport function SRadioGroup({\n options = [],\n value,\n direction = 'vertical',\n disabled = false,\n groupName,\n onValueChange,\n className,\n style,\n}: SRadioGroupProps) {\n return (\n <div\n role=\"radiogroup\"\n className={cn(\n 'inline-flex',\n direction === 'vertical'\n ? 'flex-col gap-[var(--cmp-radio-group-gap-vertical)]'\n : 'flex-row gap-[var(--cmp-radio-group-gap-horizontal)]',\n className,\n )}\n style={style}\n >\n {options.map(o => (\n <SRadio\n key={String(o.value)}\n val={o.value}\n value={value}\n label={o.label}\n name={groupName}\n disabled={disabled || o.disabled}\n onValueChange={onValueChange}\n />\n ))}\n </div>\n );\n}\n"]}
@@ -120,7 +120,7 @@ function SRadioGroup({
120
120
  role: "radiogroup",
121
121
  className: cn(
122
122
  "inline-flex",
123
- direction === "vertical" ? "flex-col gap-[8px]" : "flex-row gap-[20px]",
123
+ direction === "vertical" ? "flex-col gap-[var(--cmp-radio-group-gap-vertical)]" : "flex-row gap-[var(--cmp-radio-group-gap-horizontal)]",
124
124
  className
125
125
  ),
126
126
  style,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SRadio/SRadio.tsx","../../../src/components/SRadio/SRadioGroup.tsx"],"names":["jsx"],"mappings":"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","file":"SRadioGroup.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","'use client';\n\nimport { type CSSProperties, type HTMLAttributes } from 'react';\nimport { cn } from '../../lib/cn';\n\n/** 라디오 값 타입 (sd-radio SRadioValue) */\nexport type SRadioValue = string | number | boolean;\n\nexport interface SRadioProps {\n /** 이 라디오의 값 */\n val: SRadioValue;\n /** 그룹의 현재 선택 값 */\n value?: SRadioValue | null;\n /** 라벨 */\n label?: string;\n /** 비활성 */\n disabled?: boolean;\n /** native radio name (form-agnostic 그룹핑) */\n name?: string;\n /** 선택 변경 (sdUpdate) — 선택된 val 전달 */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadio — sd-radio 포팅. value===val 이면 선택. 단독 또는 SRadioGroup 안에서 사용. */\nexport function SRadio({\n val,\n value,\n label,\n disabled = false,\n name,\n onValueChange,\n className,\n style,\n}: SRadioProps) {\n // key/name 속성용 문자열 표현\n const valStr = String(val);\n const checked = value === val;\n const select = () => {\n if (!disabled && !checked) onValueChange?.(val);\n };\n\n // name 이 있으면 네이티브 <input type=\"radio\"> 가 실제 상호작용 컨트롤이 된다.\n // 이때 label 에는 role/tabIndex/키핸들러를 주지 않는다 — 상호작용 요소 중첩\n // (role=\"radio\" 안에 native radio) 및 radio 역할 중복을 피한다.\n const native = name != null && name !== '';\n\n // native 모드가 아니면 label 자체가 커스텀 radio 로 동작한다.\n const labelInteractiveProps: HTMLAttributes<HTMLLabelElement> = native\n ? {}\n : {\n 'role': 'radio',\n 'aria-checked': checked,\n 'tabIndex': disabled ? -1 : 0,\n 'onClick': select,\n 'onKeyDown': e => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n select();\n }\n },\n };\n\n return (\n <label\n {...labelInteractiveProps}\n className={cn(\n 'inline-flex items-center gap-[8px]',\n disabled ? 'cursor-not-allowed' : 'cursor-pointer',\n className,\n )}\n style={style}\n >\n {native && (\n <input\n type=\"radio\"\n name={name}\n value={valStr}\n checked={checked}\n disabled={disabled}\n // 네이티브 라디오가 포커스/키보드/선택을 담당한다. label 이 감싸므로\n // 라벨 클릭도 이 input 을 토글하며, onChange 는 값이 실제로 바뀔 때만 발생.\n onChange={() => onValueChange?.(val)}\n className=\"sr-only\"\n />\n )}\n <span\n className={cn(\n 'flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-full border border-solid border-[color:var(--rd-border)] bg-[var(--rd-bg)]',\n !disabled &&\n !checked &&\n 'hover:border-[color:var(--cmp-radio-border-hover)] hover:bg-[var(--cmp-radio-bg-hover)]',\n )}\n style={\n {\n '--rd-bg': disabled ? 'var(--cmp-radio-bg-disabled)' : 'var(--cmp-radio-bg-default)',\n '--rd-border': disabled\n ? 'var(--cmp-radio-border-disabled)'\n : checked\n ? 'var(--cmp-radio-border-active)'\n : 'var(--cmp-radio-border-default)',\n } as CSSProperties\n }\n >\n {checked && (\n <span\n className=\"h-[10px] w-[10px] rounded-full\"\n style={{\n background: disabled\n ? 'var(--cmp-radio-dot-disabled)'\n : 'var(--cmp-radio-dot-default)',\n }}\n />\n )}\n </span>\n {label && (\n <span\n className=\"grid text-[12px] leading-[20px]\"\n style={{\n color: disabled ? 'var(--cmp-radio-label-disabled)' : 'var(--cmp-radio-label-default)',\n }}\n >\n <span aria-hidden className=\"invisible font-medium\" style={{ gridArea: '1 / 1' }}>\n {label}\n </span>\n <span style={{ gridArea: '1 / 1', fontWeight: checked ? 500 : 400 }}>{label}</span>\n </span>\n )}\n </label>\n );\n}\n","'use client';\n\nimport { type CSSProperties } from 'react';\nimport { cn } from '../../lib/cn';\nimport { SRadio, type SRadioValue } from './SRadio';\n\nexport interface SRadioOption {\n /** 옵션 값 (sd-radio-group SRadioOption.value) */\n value: SRadioValue;\n label: string;\n disabled?: boolean;\n}\n\nexport interface SRadioGroupProps {\n /** 옵션 목록 */\n options?: SRadioOption[];\n /** 현재 선택 값 */\n value?: SRadioValue | null;\n /** 배치 방향 */\n direction?: 'vertical' | 'horizontal';\n /** 전체 비활성 */\n disabled?: boolean;\n /** 라디오 name 속성 (form-agnostic 그룹핑) */\n groupName?: string;\n /** 선택 변경 (sdUpdate) */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadioGroup — sd-radio-group 포팅. options를 SRadio로 렌더. */\nexport function SRadioGroup({\n options = [],\n value,\n direction = 'vertical',\n disabled = false,\n groupName,\n onValueChange,\n className,\n style,\n}: SRadioGroupProps) {\n return (\n <div\n role=\"radiogroup\"\n className={cn(\n 'inline-flex',\n direction === 'vertical' ? 'flex-col gap-[8px]' : 'flex-row gap-[20px]',\n className,\n )}\n style={style}\n >\n {options.map(o => (\n <SRadio\n key={String(o.value)}\n val={o.value}\n value={value}\n label={o.label}\n name={groupName}\n disabled={disabled || o.disabled}\n onValueChange={onValueChange}\n />\n ))}\n </div>\n );\n}\n"]}
1
+ {"version":3,"sources":["../../../src/lib/cn.ts","../../../src/components/SRadio/SRadio.tsx","../../../src/components/SRadio/SRadioGroup.tsx"],"names":["jsx"],"mappings":"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","file":"SRadioGroup.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","'use client';\n\nimport { type CSSProperties, type HTMLAttributes } from 'react';\nimport { cn } from '../../lib/cn';\n\n/** 라디오 값 타입 (sd-radio SRadioValue) */\nexport type SRadioValue = string | number | boolean;\n\nexport interface SRadioProps {\n /** 이 라디오의 값 */\n val: SRadioValue;\n /** 그룹의 현재 선택 값 */\n value?: SRadioValue | null;\n /** 라벨 */\n label?: string;\n /** 비활성 */\n disabled?: boolean;\n /** native radio name (form-agnostic 그룹핑) */\n name?: string;\n /** 선택 변경 (sdUpdate) — 선택된 val 전달 */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadio — sd-radio 포팅. value===val 이면 선택. 단독 또는 SRadioGroup 안에서 사용. */\nexport function SRadio({\n val,\n value,\n label,\n disabled = false,\n name,\n onValueChange,\n className,\n style,\n}: SRadioProps) {\n // key/name 속성용 문자열 표현\n const valStr = String(val);\n const checked = value === val;\n const select = () => {\n if (!disabled && !checked) onValueChange?.(val);\n };\n\n // name 이 있으면 네이티브 <input type=\"radio\"> 가 실제 상호작용 컨트롤이 된다.\n // 이때 label 에는 role/tabIndex/키핸들러를 주지 않는다 — 상호작용 요소 중첩\n // (role=\"radio\" 안에 native radio) 및 radio 역할 중복을 피한다.\n const native = name != null && name !== '';\n\n // native 모드가 아니면 label 자체가 커스텀 radio 로 동작한다.\n const labelInteractiveProps: HTMLAttributes<HTMLLabelElement> = native\n ? {}\n : {\n 'role': 'radio',\n 'aria-checked': checked,\n 'tabIndex': disabled ? -1 : 0,\n 'onClick': select,\n 'onKeyDown': e => {\n if (e.key === ' ' || e.key === 'Enter') {\n e.preventDefault();\n select();\n }\n },\n };\n\n return (\n <label\n {...labelInteractiveProps}\n className={cn(\n 'inline-flex items-center gap-[8px]',\n disabled ? 'cursor-not-allowed' : 'cursor-pointer',\n className,\n )}\n style={style}\n >\n {native && (\n <input\n type=\"radio\"\n name={name}\n value={valStr}\n checked={checked}\n disabled={disabled}\n // 네이티브 라디오가 포커스/키보드/선택을 담당한다. label 이 감싸므로\n // 라벨 클릭도 이 input 을 토글하며, onChange 는 값이 실제로 바뀔 때만 발생.\n onChange={() => onValueChange?.(val)}\n className=\"sr-only\"\n />\n )}\n <span\n className={cn(\n 'flex h-[16px] w-[16px] flex-shrink-0 items-center justify-center rounded-full border border-solid border-[color:var(--rd-border)] bg-[var(--rd-bg)]',\n !disabled &&\n !checked &&\n 'hover:border-[color:var(--cmp-radio-border-hover)] hover:bg-[var(--cmp-radio-bg-hover)]',\n )}\n style={\n {\n '--rd-bg': disabled ? 'var(--cmp-radio-bg-disabled)' : 'var(--cmp-radio-bg-default)',\n '--rd-border': disabled\n ? 'var(--cmp-radio-border-disabled)'\n : checked\n ? 'var(--cmp-radio-border-active)'\n : 'var(--cmp-radio-border-default)',\n } as CSSProperties\n }\n >\n {checked && (\n <span\n className=\"h-[10px] w-[10px] rounded-full\"\n style={{\n background: disabled\n ? 'var(--cmp-radio-dot-disabled)'\n : 'var(--cmp-radio-dot-default)',\n }}\n />\n )}\n </span>\n {label && (\n <span\n className=\"grid text-[12px] leading-[20px]\"\n style={{\n color: disabled ? 'var(--cmp-radio-label-disabled)' : 'var(--cmp-radio-label-default)',\n }}\n >\n <span aria-hidden className=\"invisible font-medium\" style={{ gridArea: '1 / 1' }}>\n {label}\n </span>\n <span style={{ gridArea: '1 / 1', fontWeight: checked ? 500 : 400 }}>{label}</span>\n </span>\n )}\n </label>\n );\n}\n","'use client';\n\nimport { type CSSProperties } from 'react';\nimport { cn } from '../../lib/cn';\nimport { SRadio, type SRadioValue } from './SRadio';\n\nexport interface SRadioOption {\n /** 옵션 값 (sd-radio-group SRadioOption.value) */\n value: SRadioValue;\n label: string;\n disabled?: boolean;\n}\n\nexport interface SRadioGroupProps {\n /** 옵션 목록 */\n options?: SRadioOption[];\n /** 현재 선택 값 */\n value?: SRadioValue | null;\n /** 배치 방향 */\n direction?: 'vertical' | 'horizontal';\n /** 전체 비활성 */\n disabled?: boolean;\n /** 라디오 name 속성 (form-agnostic 그룹핑) */\n groupName?: string;\n /** 선택 변경 (sdUpdate) */\n onValueChange?: (val: SRadioValue) => void;\n className?: string;\n style?: CSSProperties;\n}\n\n/** SRadioGroup — sd-radio-group 포팅. options를 SRadio로 렌더. */\nexport function SRadioGroup({\n options = [],\n value,\n direction = 'vertical',\n disabled = false,\n groupName,\n onValueChange,\n className,\n style,\n}: SRadioGroupProps) {\n return (\n <div\n role=\"radiogroup\"\n className={cn(\n 'inline-flex',\n direction === 'vertical'\n ? 'flex-col gap-[var(--cmp-radio-group-gap-vertical)]'\n : 'flex-row gap-[var(--cmp-radio-group-gap-horizontal)]',\n className,\n )}\n style={style}\n >\n {options.map(o => (\n <SRadio\n key={String(o.value)}\n val={o.value}\n value={value}\n label={o.label}\n name={groupName}\n disabled={disabled || o.disabled}\n onValueChange={onValueChange}\n />\n ))}\n </div>\n );\n}\n"]}