@refraktor/core 0.0.3 → 0.0.4

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 (284) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/build/components/button/button.styles.js +5 -5
  3. package/build/components/chip/chip-group/chip-group.d.ts +4 -0
  4. package/build/components/chip/chip-group/chip-group.d.ts.map +1 -0
  5. package/build/components/chip/chip-group/chip-group.js +43 -0
  6. package/build/components/chip/chip-group/index.d.ts +2 -0
  7. package/build/components/chip/chip-group/index.d.ts.map +1 -0
  8. package/build/components/chip/chip-group/index.js +1 -0
  9. package/build/components/chip/chip.context.d.ts +15 -0
  10. package/build/components/chip/chip.context.d.ts.map +1 -0
  11. package/build/components/chip/chip.context.js +2 -0
  12. package/build/components/chip/chip.d.ts +4 -0
  13. package/build/components/chip/chip.d.ts.map +1 -0
  14. package/build/components/chip/chip.js +85 -0
  15. package/build/components/chip/chip.styles.d.ts +5 -0
  16. package/build/components/chip/chip.styles.d.ts.map +1 -0
  17. package/build/components/chip/chip.styles.js +19 -0
  18. package/build/components/chip/chip.test.d.ts +2 -0
  19. package/build/components/chip/chip.test.d.ts.map +1 -0
  20. package/build/components/chip/chip.test.js +107 -0
  21. package/build/components/chip/chip.types.d.ts +95 -0
  22. package/build/components/chip/chip.types.d.ts.map +1 -0
  23. package/build/components/chip/chip.types.js +1 -0
  24. package/build/components/chip/index.d.ts +4 -0
  25. package/build/components/chip/index.d.ts.map +1 -0
  26. package/build/components/chip/index.js +2 -0
  27. package/build/components/combobox/combobox-dropdown/combobox-dropdown.d.ts +4 -0
  28. package/build/components/combobox/combobox-dropdown/combobox-dropdown.d.ts.map +1 -0
  29. package/build/components/combobox/combobox-dropdown/combobox-dropdown.js +21 -0
  30. package/build/components/combobox/combobox-dropdown/index.d.ts +2 -0
  31. package/build/components/combobox/combobox-dropdown/index.d.ts.map +1 -0
  32. package/build/components/combobox/combobox-dropdown/index.js +1 -0
  33. package/build/components/combobox/combobox-group/combobox-group.d.ts +4 -0
  34. package/build/components/combobox/combobox-group/combobox-group.d.ts.map +1 -0
  35. package/build/components/combobox/combobox-group/combobox-group.js +15 -0
  36. package/build/components/combobox/combobox-group/index.d.ts +2 -0
  37. package/build/components/combobox/combobox-group/index.d.ts.map +1 -0
  38. package/build/components/combobox/combobox-group/index.js +1 -0
  39. package/build/components/combobox/combobox-input/combobox-input.d.ts +4 -0
  40. package/build/components/combobox/combobox-input/combobox-input.d.ts.map +1 -0
  41. package/build/components/combobox/combobox-input/combobox-input.js +101 -0
  42. package/build/components/combobox/combobox-input/index.d.ts +2 -0
  43. package/build/components/combobox/combobox-input/index.d.ts.map +1 -0
  44. package/build/components/combobox/combobox-input/index.js +1 -0
  45. package/build/components/combobox/combobox-option/combobox-option.d.ts +4 -0
  46. package/build/components/combobox/combobox-option/combobox-option.d.ts.map +1 -0
  47. package/build/components/combobox/combobox-option/combobox-option.js +86 -0
  48. package/build/components/combobox/combobox-option/index.d.ts +2 -0
  49. package/build/components/combobox/combobox-option/index.d.ts.map +1 -0
  50. package/build/components/combobox/combobox-option/index.js +1 -0
  51. package/build/components/combobox/combobox-root/combobox-root.d.ts +4 -0
  52. package/build/components/combobox/combobox-root/combobox-root.d.ts.map +1 -0
  53. package/build/components/combobox/combobox-root/combobox-root.js +282 -0
  54. package/build/components/combobox/combobox-root/index.d.ts +2 -0
  55. package/build/components/combobox/combobox-root/index.d.ts.map +1 -0
  56. package/build/components/combobox/combobox-root/index.js +1 -0
  57. package/build/components/combobox/combobox.context.d.ts +73 -0
  58. package/build/components/combobox/combobox.context.d.ts.map +1 -0
  59. package/build/components/combobox/combobox.context.js +50 -0
  60. package/build/components/combobox/combobox.d.ts +4 -0
  61. package/build/components/combobox/combobox.d.ts.map +1 -0
  62. package/build/components/combobox/combobox.js +31 -0
  63. package/build/components/combobox/combobox.test.d.ts +2 -0
  64. package/build/components/combobox/combobox.test.d.ts.map +1 -0
  65. package/build/components/combobox/combobox.test.js +104 -0
  66. package/build/components/combobox/combobox.types.d.ts +205 -0
  67. package/build/components/combobox/combobox.types.d.ts.map +1 -0
  68. package/build/components/combobox/combobox.types.js +1 -0
  69. package/build/components/combobox/index.d.ts +8 -0
  70. package/build/components/combobox/index.d.ts.map +1 -0
  71. package/build/components/combobox/index.js +6 -0
  72. package/build/components/combobox/use-combobox.d.ts +32 -0
  73. package/build/components/combobox/use-combobox.d.ts.map +1 -0
  74. package/build/components/combobox/use-combobox.js +80 -0
  75. package/build/components/drawer/drawer-body/drawer-body.d.ts +4 -0
  76. package/build/components/drawer/drawer-body/drawer-body.d.ts.map +1 -0
  77. package/build/components/drawer/drawer-body/drawer-body.js +11 -0
  78. package/build/components/drawer/drawer-body/index.d.ts +2 -0
  79. package/build/components/drawer/drawer-body/index.d.ts.map +1 -0
  80. package/build/components/drawer/drawer-body/index.js +1 -0
  81. package/build/components/drawer/drawer-content/drawer-content.d.ts.map +1 -1
  82. package/build/components/drawer/drawer-content/drawer-content.js +9 -6
  83. package/build/components/drawer/drawer-overlay/drawer-overlay.d.ts.map +1 -1
  84. package/build/components/drawer/drawer-overlay/drawer-overlay.js +4 -4
  85. package/build/components/drawer/drawer-root/drawer-root.d.ts.map +1 -1
  86. package/build/components/drawer/drawer-root/drawer-root.js +8 -9
  87. package/build/components/drawer/drawer.context.d.ts +2 -1
  88. package/build/components/drawer/drawer.context.d.ts.map +1 -1
  89. package/build/components/drawer/drawer.d.ts.map +1 -1
  90. package/build/components/drawer/drawer.js +5 -3
  91. package/build/components/drawer/drawer.test.js +55 -16
  92. package/build/components/drawer/drawer.types.d.ts +29 -3
  93. package/build/components/drawer/drawer.types.d.ts.map +1 -1
  94. package/build/components/drawer/index.d.ts +2 -1
  95. package/build/components/drawer/index.d.ts.map +1 -1
  96. package/build/components/drawer/index.js +1 -0
  97. package/build/components/drawer/use-drawer.d.ts +8 -1
  98. package/build/components/drawer/use-drawer.d.ts.map +1 -1
  99. package/build/components/drawer/use-drawer.js +25 -38
  100. package/build/components/file-input/file-input.d.ts +4 -0
  101. package/build/components/file-input/file-input.d.ts.map +1 -0
  102. package/build/components/file-input/file-input.js +88 -0
  103. package/build/components/file-input/file-input.test.d.ts +2 -0
  104. package/build/components/file-input/file-input.test.d.ts.map +1 -0
  105. package/build/components/file-input/file-input.test.js +74 -0
  106. package/build/components/file-input/file-input.types.d.ts +46 -0
  107. package/build/components/file-input/file-input.types.d.ts.map +1 -0
  108. package/build/components/file-input/file-input.types.js +1 -0
  109. package/build/components/file-input/file-input.utils.d.ts +11 -0
  110. package/build/components/file-input/file-input.utils.d.ts.map +1 -0
  111. package/build/components/file-input/file-input.utils.js +67 -0
  112. package/build/components/file-input/file-input.utils.test.d.ts +2 -0
  113. package/build/components/file-input/file-input.utils.test.d.ts.map +1 -0
  114. package/build/components/file-input/file-input.utils.test.js +27 -0
  115. package/build/components/file-input/index.d.ts +3 -0
  116. package/build/components/file-input/index.d.ts.map +1 -0
  117. package/build/components/file-input/index.js +2 -0
  118. package/build/components/for/for.d.ts +8 -0
  119. package/build/components/for/for.d.ts.map +1 -0
  120. package/build/components/for/for.js +32 -0
  121. package/build/components/for/for.test.d.ts +2 -0
  122. package/build/components/for/for.test.d.ts.map +1 -0
  123. package/build/components/for/for.test.js +31 -0
  124. package/build/components/for/for.types.d.ts +33 -0
  125. package/build/components/for/for.types.d.ts.map +1 -0
  126. package/build/components/for/for.types.js +1 -0
  127. package/build/components/for/index.d.ts +3 -0
  128. package/build/components/for/index.d.ts.map +1 -0
  129. package/build/components/for/index.js +1 -0
  130. package/build/components/index.d.ts +6 -0
  131. package/build/components/index.d.ts.map +1 -1
  132. package/build/components/index.js +6 -0
  133. package/build/components/modal/index.d.ts +2 -1
  134. package/build/components/modal/index.d.ts.map +1 -1
  135. package/build/components/modal/index.js +1 -0
  136. package/build/components/modal/modal-body/index.d.ts +2 -0
  137. package/build/components/modal/modal-body/index.d.ts.map +1 -0
  138. package/build/components/modal/modal-body/index.js +1 -0
  139. package/build/components/modal/modal-body/modal-body.d.ts +4 -0
  140. package/build/components/modal/modal-body/modal-body.d.ts.map +1 -0
  141. package/build/components/modal/modal-body/modal-body.js +11 -0
  142. package/build/components/modal/modal-content/modal-content.d.ts.map +1 -1
  143. package/build/components/modal/modal-content/modal-content.js +13 -5
  144. package/build/components/modal/modal-header/modal-header.js +2 -2
  145. package/build/components/modal/modal-overlay/modal-overlay.d.ts.map +1 -1
  146. package/build/components/modal/modal-overlay/modal-overlay.js +4 -4
  147. package/build/components/modal/modal-root/modal-root.d.ts.map +1 -1
  148. package/build/components/modal/modal-root/modal-root.js +12 -9
  149. package/build/components/modal/modal.context.d.ts +5 -2
  150. package/build/components/modal/modal.context.d.ts.map +1 -1
  151. package/build/components/modal/modal.d.ts.map +1 -1
  152. package/build/components/modal/modal.js +5 -3
  153. package/build/components/modal/modal.test.js +78 -13
  154. package/build/components/modal/modal.types.d.ts +34 -5
  155. package/build/components/modal/modal.types.d.ts.map +1 -1
  156. package/build/components/modal/use-modal.d.ts +8 -1
  157. package/build/components/modal/use-modal.d.ts.map +1 -1
  158. package/build/components/modal/use-modal.js +25 -38
  159. package/build/components/password-input/index.d.ts +3 -0
  160. package/build/components/password-input/index.d.ts.map +1 -0
  161. package/build/components/password-input/index.js +2 -0
  162. package/build/components/password-input/password-input.d.ts +4 -0
  163. package/build/components/password-input/password-input.d.ts.map +1 -0
  164. package/build/components/password-input/password-input.js +32 -0
  165. package/build/components/password-input/password-input.test.d.ts +2 -0
  166. package/build/components/password-input/password-input.test.d.ts.map +1 -0
  167. package/build/components/password-input/password-input.test.js +47 -0
  168. package/build/components/password-input/password-input.types.d.ts +24 -0
  169. package/build/components/password-input/password-input.types.d.ts.map +1 -0
  170. package/build/components/password-input/password-input.types.js +1 -0
  171. package/build/components/pin-input/index.d.ts +3 -0
  172. package/build/components/pin-input/index.d.ts.map +1 -0
  173. package/build/components/pin-input/index.js +2 -0
  174. package/build/components/pin-input/pin-input.d.ts +4 -0
  175. package/build/components/pin-input/pin-input.d.ts.map +1 -0
  176. package/build/components/pin-input/pin-input.js +245 -0
  177. package/build/components/pin-input/pin-input.test.d.ts +2 -0
  178. package/build/components/pin-input/pin-input.test.d.ts.map +1 -0
  179. package/build/components/pin-input/pin-input.test.js +87 -0
  180. package/build/components/pin-input/pin-input.types.d.ts +44 -0
  181. package/build/components/pin-input/pin-input.types.d.ts.map +1 -0
  182. package/build/components/pin-input/pin-input.types.js +1 -0
  183. package/build/components/scroll-area/index.d.ts +3 -0
  184. package/build/components/scroll-area/index.d.ts.map +1 -0
  185. package/build/components/scroll-area/index.js +1 -0
  186. package/build/components/scroll-area/scroll-area.d.ts +4 -0
  187. package/build/components/scroll-area/scroll-area.d.ts.map +1 -0
  188. package/build/components/scroll-area/scroll-area.js +30 -0
  189. package/build/components/scroll-area/scroll-area.test.d.ts +2 -0
  190. package/build/components/scroll-area/scroll-area.test.d.ts.map +1 -0
  191. package/build/components/scroll-area/scroll-area.test.js +39 -0
  192. package/build/components/scroll-area/scroll-area.types.d.ts +25 -0
  193. package/build/components/scroll-area/scroll-area.types.d.ts.map +1 -0
  194. package/build/components/scroll-area/scroll-area.types.js +1 -0
  195. package/build/components/segmented-control/segmented-control.d.ts.map +1 -1
  196. package/build/components/segmented-control/segmented-control.js +3 -3
  197. package/build/components/segmented-control/segmented-control.styles.js +13 -13
  198. package/build/components/segmented-control/segmented-control.test.js +11 -0
  199. package/build/components/segmented-control/segmented-control.types.d.ts +2 -0
  200. package/build/components/segmented-control/segmented-control.types.d.ts.map +1 -1
  201. package/build/icons/eye-off.d.ts +4 -0
  202. package/build/icons/eye-off.d.ts.map +1 -0
  203. package/build/icons/eye-off.js +5 -0
  204. package/build/icons/eye.d.ts +4 -0
  205. package/build/icons/eye.d.ts.map +1 -0
  206. package/build/icons/eye.js +5 -0
  207. package/build/icons/index.d.ts +2 -0
  208. package/build/icons/index.d.ts.map +1 -1
  209. package/build/icons/index.js +2 -0
  210. package/build/style.css +1 -1
  211. package/package.json +2 -2
  212. package/src/components/button/button.styles.ts +5 -5
  213. package/src/components/chip/chip-group/chip-group.tsx +107 -0
  214. package/src/components/chip/chip-group/index.ts +1 -0
  215. package/src/components/chip/chip.context.ts +15 -0
  216. package/src/components/chip/chip.styles.ts +36 -0
  217. package/src/components/chip/chip.test.tsx +197 -0
  218. package/src/components/chip/chip.tsx +208 -0
  219. package/src/components/chip/chip.types.ts +134 -0
  220. package/src/components/chip/index.ts +10 -0
  221. package/src/components/drawer/drawer-body/drawer-body.tsx +29 -0
  222. package/src/components/drawer/drawer-body/index.ts +1 -0
  223. package/src/components/drawer/drawer-content/drawer-content.tsx +63 -26
  224. package/src/components/drawer/drawer-overlay/drawer-overlay.tsx +6 -5
  225. package/src/components/drawer/drawer-root/drawer-root.tsx +17 -18
  226. package/src/components/drawer/drawer.context.ts +2 -1
  227. package/src/components/drawer/drawer.test.tsx +144 -36
  228. package/src/components/drawer/drawer.tsx +31 -3
  229. package/src/components/drawer/drawer.types.ts +37 -3
  230. package/src/components/drawer/index.ts +2 -0
  231. package/src/components/drawer/use-drawer.ts +44 -51
  232. package/src/components/file-input/file-input.test.tsx +134 -0
  233. package/src/components/file-input/file-input.tsx +224 -0
  234. package/src/components/file-input/file-input.types.ts +78 -0
  235. package/src/components/file-input/file-input.utils.test.ts +36 -0
  236. package/src/components/file-input/file-input.utils.ts +130 -0
  237. package/src/components/file-input/index.ts +2 -0
  238. package/src/components/for/for.test.tsx +66 -0
  239. package/src/components/for/for.tsx +53 -0
  240. package/src/components/for/for.types.ts +40 -0
  241. package/src/components/for/index.ts +2 -0
  242. package/src/components/index.ts +6 -0
  243. package/src/components/menu/menu-dropdown/menu-dropdown.tsx +220 -220
  244. package/src/components/menu/menu-sub-dropdown/menu-sub-dropdown.tsx +221 -221
  245. package/src/components/modal/index.ts +4 -1
  246. package/src/components/modal/modal-body/index.ts +1 -0
  247. package/src/components/modal/modal-body/modal-body.tsx +29 -0
  248. package/src/components/modal/modal-content/modal-content.tsx +71 -24
  249. package/src/components/modal/modal-header/modal-header.tsx +2 -2
  250. package/src/components/modal/modal-overlay/modal-overlay.tsx +46 -45
  251. package/src/components/modal/modal-root/modal-root.tsx +22 -17
  252. package/src/components/modal/modal.context.ts +5 -2
  253. package/src/components/modal/modal.test.tsx +234 -64
  254. package/src/components/modal/modal.tsx +36 -4
  255. package/src/components/modal/modal.types.ts +49 -8
  256. package/src/components/modal/use-modal.ts +44 -51
  257. package/src/components/password-input/index.ts +2 -0
  258. package/src/components/password-input/password-input.test.tsx +72 -0
  259. package/src/components/password-input/password-input.tsx +85 -0
  260. package/src/components/password-input/password-input.types.ts +30 -0
  261. package/src/components/pin-input/index.ts +2 -0
  262. package/src/components/pin-input/pin-input.test.tsx +149 -0
  263. package/src/components/pin-input/pin-input.tsx +473 -0
  264. package/src/components/pin-input/pin-input.types.ts +78 -0
  265. package/src/components/scroll-area/index.ts +6 -0
  266. package/src/components/scroll-area/scroll-area.test.tsx +72 -0
  267. package/src/components/scroll-area/scroll-area.tsx +70 -0
  268. package/src/components/scroll-area/scroll-area.types.ts +37 -0
  269. package/src/components/segmented-control/segmented-control.styles.ts +13 -13
  270. package/src/components/segmented-control/segmented-control.test.tsx +18 -0
  271. package/src/components/segmented-control/segmented-control.tsx +11 -1
  272. package/src/components/segmented-control/segmented-control.types.ts +3 -0
  273. package/src/components/select/select-dropdown/select-dropdown.tsx +299 -299
  274. package/src/components/select/select-root/select-root.tsx +333 -333
  275. package/src/components/select/select-trigger/select-trigger.tsx +123 -123
  276. package/src/components/select/select.context.ts +140 -140
  277. package/src/components/select/select.test.tsx +190 -190
  278. package/src/components/select/select.types.ts +272 -272
  279. package/src/components/select/use-select.ts +170 -170
  280. package/src/icons/eye-off.tsx +30 -0
  281. package/src/icons/eye.tsx +24 -0
  282. package/src/icons/index.ts +2 -0
  283. package/src/style.css +14 -8
  284. package/tsconfig.tsbuildinfo +1 -1
@@ -1,37 +1,38 @@
1
+ import { FloatingOverlay, FloatingPortal } from "@floating-ui/react";
1
2
  import { useTheme } from "../../../theme";
2
3
  import { factory } from "../../../utils";
3
- import { Portal } from "../../portal";
4
4
  import { Transition } from "../../transition";
5
5
  import { useModalContext } from "../modal.context";
6
6
  import { ModalOverlayFactoryPayload } from "../modal.types";
7
7
 
8
- const ModalOverlay = factory<ModalOverlayFactoryPayload>(
9
- (
10
- {
11
- closeOnClick = true,
12
- backgroundOpacity = 0.5,
13
- blur = 0,
14
- className,
15
- onMouseDown,
16
- style,
17
- ...props
18
- },
19
- ref
20
- ) => {
21
- const { cx } = useTheme();
22
- const {
23
- modal,
24
- closeOnClickOutside,
25
- withinPortal,
26
- transitionProps,
27
- getStyles
28
- } = useModalContext();
29
-
30
- const blurValue = typeof blur === "number" ? `${blur}px` : blur;
31
- const backdropFilterValue =
32
- blurValue !== "0" && blurValue !== "0px"
33
- ? `blur(${blurValue})`
34
- : undefined;
8
+ const ModalOverlay = factory<ModalOverlayFactoryPayload>(
9
+ (
10
+ {
11
+ closeOnClick = true,
12
+ backgroundOpacity = 0.5,
13
+ blur = 0,
14
+ className,
15
+ onMouseDown,
16
+ style,
17
+ ...props
18
+ },
19
+ ref
20
+ ) => {
21
+ const { cx } = useTheme();
22
+ const {
23
+ modal,
24
+ closeOnClickOutside,
25
+ lockScroll,
26
+ withinPortal,
27
+ transitionProps,
28
+ getStyles
29
+ } = useModalContext();
30
+
31
+ const blurValue = typeof blur === "number" ? `${blur}px` : blur;
32
+ const backdropFilterValue =
33
+ blurValue !== "0" && blurValue !== "0px"
34
+ ? `blur(${blurValue})`
35
+ : undefined;
35
36
 
36
37
  const handleMouseDown = (event: React.MouseEvent<HTMLDivElement>) => {
37
38
  onMouseDown?.(event);
@@ -54,27 +55,27 @@ const ModalOverlay = factory<ModalOverlayFactoryPayload>(
54
55
  mounted={modal.opened}
55
56
  {...transitionProps}
56
57
  >
57
- <div
58
- ref={ref}
59
- aria-hidden="true"
60
- className={cx(
61
- "fixed inset-0 z-40",
62
- getStyles("overlay"),
63
- className
64
- )}
65
- style={{
66
- backgroundColor: `rgba(0, 0, 0, ${backgroundOpacity})`,
67
- backdropFilter: backdropFilterValue,
68
- ...style
69
- }}
70
- onMouseDown={handleMouseDown}
71
- {...props}
72
- />
58
+ <FloatingOverlay
59
+ lockScroll={lockScroll}
60
+ ref={ref}
61
+ className={cx(
62
+ "z-40",
63
+ getStyles("overlay"),
64
+ className
65
+ )}
66
+ style={{
67
+ backgroundColor: `rgba(0, 0, 0, ${backgroundOpacity})`,
68
+ backdropFilter: backdropFilterValue,
69
+ ...style
70
+ }}
71
+ onMouseDown={handleMouseDown}
72
+ {...props}
73
+ />
73
74
  </Transition>
74
75
  );
75
76
 
76
77
  return withinPortal ? (
77
- <Portal>{overlayContent}</Portal>
78
+ <FloatingPortal>{overlayContent}</FloatingPortal>
78
79
  ) : (
79
80
  overlayContent
80
81
  );
@@ -1,5 +1,4 @@
1
1
  import { useId } from "@refraktor/utils";
2
- import { useRef } from "react";
3
2
  import { useTheme } from "../../../theme";
4
3
  import { factory, useClassNames, useProps } from "../../../utils";
5
4
  import { ModalProvider } from "../modal.context";
@@ -9,14 +8,17 @@ import {
9
8
  ModalRootFactoryPayload,
10
9
  ModalRootProps
11
10
  } from "../modal.types";
12
- import { RemoveScroll } from "react-remove-scroll";
13
11
 
14
12
  const defaultProps = {
15
13
  closeOnClickOutside: true,
16
14
  closeOnEscape: true,
17
15
  lockScroll: true,
18
16
  withinPortal: true,
19
- radius: "md"
17
+ radius: "md",
18
+ size: "md",
19
+ centered: true,
20
+ trapFocus: true,
21
+ returnFocus: true
20
22
  } satisfies Partial<ModalRootProps>;
21
23
 
22
24
  const ModalRoot = factory<ModalRootFactoryPayload>((_props, ref) => {
@@ -32,6 +34,10 @@ const ModalRoot = factory<ModalRootFactoryPayload>((_props, ref) => {
32
34
  lockScroll,
33
35
  withinPortal,
34
36
  radius,
37
+ size,
38
+ centered,
39
+ trapFocus,
40
+ returnFocus,
35
41
  transitionProps,
36
42
  className,
37
43
  classNames,
@@ -41,15 +47,13 @@ const ModalRoot = factory<ModalRootFactoryPayload>((_props, ref) => {
41
47
 
42
48
  const _id = useId(id);
43
49
  const headerId = `${_id}-header`;
44
- const contentRef = useRef<HTMLDivElement | null>(null);
45
50
 
46
51
  const modal = useModal({
47
52
  opened,
48
53
  defaultOpened,
49
54
  onOpenedChange,
50
55
  closeOnClickOutside,
51
- closeOnEscape,
52
- contentRef
56
+ closeOnEscape
53
57
  });
54
58
 
55
59
  const getStyles = (part: keyof ModalClassNames) => classes[part];
@@ -62,23 +66,24 @@ const ModalRoot = factory<ModalRootFactoryPayload>((_props, ref) => {
62
66
  lockScroll,
63
67
  withinPortal,
64
68
  radius,
69
+ size,
70
+ centered,
71
+ trapFocus,
72
+ returnFocus,
65
73
  transitionProps,
66
74
  headerId,
67
- contentRef,
68
75
  classNames,
69
76
  getStyles
70
77
  }}
71
78
  >
72
- <RemoveScroll enabled={modal.opened && lockScroll}>
73
- <div
74
- ref={ref}
75
- id={_id}
76
- className={cx(classes.root, className)}
77
- {...props}
78
- >
79
- {children}
80
- </div>
81
- </RemoveScroll>
79
+ <div
80
+ ref={ref}
81
+ id={_id}
82
+ className={cx(classes.root, className)}
83
+ {...props}
84
+ >
85
+ {children}
86
+ </div>
82
87
  </ModalProvider>
83
88
  );
84
89
  });
@@ -1,7 +1,7 @@
1
1
  import { createSafeContext } from "@refraktor/utils";
2
2
  import { RefraktorRadius } from "../../theme";
3
3
  import { TransitionProps } from "../transition";
4
- import { ModalClassNames } from "./modal.types";
4
+ import { ModalClassNames, ModalSize } from "./modal.types";
5
5
  import { UseModalReturn } from "./use-modal";
6
6
 
7
7
  export interface ModalContextValue {
@@ -10,9 +10,12 @@ export interface ModalContextValue {
10
10
  lockScroll: boolean;
11
11
  withinPortal: boolean;
12
12
  radius: RefraktorRadius;
13
+ size: ModalSize;
14
+ centered: boolean;
15
+ trapFocus: boolean;
16
+ returnFocus: boolean;
13
17
  transitionProps?: Omit<TransitionProps, "children" | "mounted">;
14
18
  headerId: string;
15
- contentRef: React.MutableRefObject<HTMLDivElement | null>;
16
19
  classNames?: ModalClassNames;
17
20
  getStyles: (part: keyof ModalClassNames) => string | undefined;
18
21
  }
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import { render, screen, userEvent, waitFor } from "../../vitest";
3
3
  import Modal from "./modal";
4
+ import { ModalBody } from "./modal-body";
4
5
  import { ModalContent } from "./modal-content";
5
6
  import { ModalOverlay } from "./modal-overlay";
6
7
  import { ModalRoot } from "./modal-root";
@@ -15,14 +16,16 @@ describe("@refraktor/core/Modal", () => {
15
16
  const user = userEvent.setup();
16
17
 
17
18
  await render(
18
- <Modal defaultOpened transitionProps={transitionProps}>
19
+ <Modal.Root defaultOpened transitionProps={transitionProps}>
19
20
  <Modal.Overlay />
20
21
 
21
22
  <Modal.Content>
22
- <Modal.Header text="Delete item" />
23
- <p>Are you sure?</p>
23
+ <Modal.Header>Delete item</Modal.Header>
24
+ <Modal.Body>
25
+ <p>Are you sure?</p>
26
+ </Modal.Body>
24
27
  </Modal.Content>
25
- </Modal>
28
+ </Modal.Root>
26
29
  );
27
30
 
28
31
  expect(
@@ -41,14 +44,14 @@ describe("@refraktor/core/Modal", () => {
41
44
  const onOpenedChange = vi.fn();
42
45
 
43
46
  await render(
44
- <Modal
47
+ <Modal.Root
45
48
  opened
46
49
  onOpenedChange={onOpenedChange}
47
50
  transitionProps={transitionProps}
48
51
  >
49
52
  <Modal.Overlay data-testid="overlay" />
50
53
  <Modal.Content>Controlled modal</Modal.Content>
51
- </Modal>
54
+ </Modal.Root>
52
55
  );
53
56
 
54
57
  await user.click(await screen.findByTestId("overlay"));
@@ -60,9 +63,9 @@ describe("@refraktor/core/Modal", () => {
60
63
  const user = userEvent.setup();
61
64
 
62
65
  await render(
63
- <Modal defaultOpened transitionProps={transitionProps}>
66
+ <Modal.Root defaultOpened transitionProps={transitionProps}>
64
67
  <Modal.Content>Keyboard close</Modal.Content>
65
- </Modal>
68
+ </Modal.Root>
66
69
  );
67
70
 
68
71
  await screen.findByRole("dialog");
@@ -74,73 +77,240 @@ describe("@refraktor/core/Modal", () => {
74
77
  });
75
78
  });
76
79
 
77
- it("supports standalone subcomponents with ModalRoot", async () => {
78
- await render(
79
- <ModalRoot defaultOpened transitionProps={transitionProps}>
80
- <ModalOverlay />
81
- <ModalContent>Standalone composition</ModalContent>
80
+ it("supports standalone subcomponents with ModalRoot", async () => {
81
+ await render(
82
+ <ModalRoot defaultOpened transitionProps={transitionProps}>
83
+ <ModalOverlay />
84
+ <ModalContent>Standalone composition</ModalContent>
82
85
  </ModalRoot>
83
86
  );
84
-
85
- expect(await screen.findByRole("dialog")).toBeInTheDocument();
86
- });
87
-
88
- it("applies custom overlay background opacity and blur", async () => {
89
- await render(
90
- <Modal defaultOpened transitionProps={transitionProps}>
91
- <Modal.Overlay
92
- data-testid="overlay"
93
- backgroundOpacity={0.4}
94
- blur={6}
95
- />
96
- <Modal.Content>Styled overlay</Modal.Content>
97
- </Modal>
98
- );
99
-
100
- const overlay = await screen.findByTestId("overlay");
101
-
102
- expect(overlay).toHaveStyle({
103
- backgroundColor: "rgba(0, 0, 0, 0.4)",
104
- backdropFilter: "blur(6px)"
105
- });
106
- });
107
-
108
- it("does not set backdrop blur for zero blur", async () => {
109
- await render(
110
- <Modal defaultOpened transitionProps={transitionProps}>
111
- <Modal.Overlay data-testid="overlay" blur={0} />
112
- <Modal.Content>No blur</Modal.Content>
113
- </Modal>
114
- );
115
-
116
- const overlay = await screen.findByTestId("overlay");
117
-
118
- expect(overlay).toHaveStyle({
119
- backgroundColor: "rgba(0, 0, 0, 0.5)"
120
- });
121
- expect(overlay.style.backdropFilter).toBe("");
122
- });
123
-
124
- it("locks and unlocks body scroll when enabled", async () => {
125
- const user = userEvent.setup();
126
87
 
88
+ expect(await screen.findByRole("dialog")).toBeInTheDocument();
89
+ });
90
+
91
+ it("applies custom overlay background opacity and blur", async () => {
92
+ await render(
93
+ <Modal.Root defaultOpened transitionProps={transitionProps}>
94
+ <Modal.Overlay
95
+ data-testid="overlay"
96
+ backgroundOpacity={0.4}
97
+ blur={6}
98
+ />
99
+ <Modal.Content>Styled overlay</Modal.Content>
100
+ </Modal.Root>
101
+ );
102
+
103
+ const overlay = await screen.findByTestId("overlay");
104
+
105
+ expect(overlay).toHaveStyle({
106
+ backgroundColor: "rgba(0, 0, 0, 0.4)",
107
+ backdropFilter: "blur(6px)"
108
+ });
109
+ });
110
+
111
+ it("does not set backdrop blur for zero blur", async () => {
112
+ await render(
113
+ <Modal.Root defaultOpened transitionProps={transitionProps}>
114
+ <Modal.Overlay data-testid="overlay" blur={0} />
115
+ <Modal.Content>No blur</Modal.Content>
116
+ </Modal.Root>
117
+ );
118
+
119
+ const overlay = await screen.findByTestId("overlay");
120
+
121
+ expect(overlay).toHaveStyle({
122
+ backgroundColor: "rgba(0, 0, 0, 0.5)"
123
+ });
124
+ expect(overlay.style.backdropFilter).toBe("");
125
+ });
126
+
127
+ it("renders Modal.Body subcomponent", async () => {
127
128
  await render(
128
- <Modal defaultOpened lockScroll transitionProps={transitionProps}>
129
+ <Modal.Root defaultOpened transitionProps={transitionProps}>
129
130
  <Modal.Content>
130
- Scroll locked
131
- <Modal.Close />
131
+ <Modal.Body data-testid="body">Body content</Modal.Body>
132
132
  </Modal.Content>
133
- </Modal>
133
+ </Modal.Root>
134
134
  );
135
135
 
136
- await waitFor(() => {
137
- expect(document.body).toHaveAttribute("data-scroll-locked");
136
+ const body = await screen.findByTestId("body");
137
+ expect(body).toBeInTheDocument();
138
+ expect(body).toHaveTextContent("Body content");
139
+ });
140
+
141
+ it("renders standalone ModalBody component", async () => {
142
+ await render(
143
+ <ModalRoot defaultOpened transitionProps={transitionProps}>
144
+ <ModalContent>
145
+ <ModalBody data-testid="body">Standalone body</ModalBody>
146
+ </ModalContent>
147
+ </ModalRoot>
148
+ );
149
+
150
+ expect(await screen.findByTestId("body")).toHaveTextContent(
151
+ "Standalone body"
152
+ );
153
+ });
154
+
155
+ describe("single-component shorthand API", () => {
156
+ it("renders with title, overlay, close button, and body", async () => {
157
+ await render(
158
+ <Modal
159
+ defaultOpened
160
+ title="Confirm action"
161
+ transitionProps={transitionProps}
162
+ >
163
+ <p>Are you sure?</p>
164
+ </Modal>
165
+ );
166
+
167
+ expect(
168
+ await screen.findByRole("dialog", { name: "Confirm action" })
169
+ ).toBeInTheDocument();
170
+ expect(screen.getByText("Are you sure?")).toBeInTheDocument();
171
+ expect(
172
+ screen.getByRole("button", { name: "Close" })
173
+ ).toBeInTheDocument();
138
174
  });
139
175
 
140
- await user.click(screen.getByRole("button", { name: "Close" }));
176
+ it("hides overlay when withOverlay is false", async () => {
177
+ const { container } = await render(
178
+ <Modal
179
+ defaultOpened
180
+ title="No overlay"
181
+ withOverlay={false}
182
+ transitionProps={transitionProps}
183
+ />
184
+ );
141
185
 
142
- await waitFor(() => {
143
- expect(document.body).not.toHaveAttribute("data-scroll-locked");
186
+ expect(await screen.findByRole("dialog")).toBeInTheDocument();
187
+ expect(
188
+ container.ownerDocument.querySelector("[aria-hidden='true']")
189
+ ).toBeNull();
190
+ });
191
+
192
+ it("hides close button when withCloseButton is false", async () => {
193
+ await render(
194
+ <Modal
195
+ defaultOpened
196
+ title="No close"
197
+ withCloseButton={false}
198
+ transitionProps={transitionProps}
199
+ />
200
+ );
201
+
202
+ await screen.findByRole("dialog");
203
+ expect(
204
+ screen.queryByRole("button", { name: "Close" })
205
+ ).not.toBeInTheDocument();
206
+ });
207
+
208
+ it("closes via shorthand close button", async () => {
209
+ const user = userEvent.setup();
210
+
211
+ await render(
212
+ <Modal
213
+ defaultOpened
214
+ title="Closeable"
215
+ transitionProps={transitionProps}
216
+ >
217
+ Content
218
+ </Modal>
219
+ );
220
+
221
+ await screen.findByRole("dialog");
222
+
223
+ await user.click(screen.getByRole("button", { name: "Close" }));
224
+
225
+ await waitFor(() => {
226
+ expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
227
+ });
228
+ });
229
+
230
+ it("passes overlayProps to the overlay", async () => {
231
+ await render(
232
+ <Modal
233
+ defaultOpened
234
+ title="Custom overlay"
235
+ overlayProps={{
236
+ backgroundOpacity: 0.8,
237
+ blur: 10,
238
+ "data-testid": "shorthand-overlay"
239
+ } as any}
240
+ transitionProps={transitionProps}
241
+ >
242
+ Content
243
+ </Modal>
244
+ );
245
+
246
+ const overlay = await screen.findByTestId("shorthand-overlay");
247
+
248
+ expect(overlay).toHaveStyle({
249
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
250
+ backdropFilter: "blur(10px)"
251
+ });
252
+ });
253
+ });
254
+
255
+ describe("size prop", () => {
256
+ it("applies md size by default", async () => {
257
+ await render(
258
+ <Modal.Root defaultOpened transitionProps={transitionProps}>
259
+ <Modal.Content data-testid="content">
260
+ Default size
261
+ </Modal.Content>
262
+ </Modal.Root>
263
+ );
264
+
265
+ const dialog = await screen.findByRole("dialog");
266
+ expect(dialog.className).toContain("max-w-md");
267
+ });
268
+
269
+ it("applies custom size", async () => {
270
+ await render(
271
+ <Modal.Root
272
+ defaultOpened
273
+ size="lg"
274
+ transitionProps={transitionProps}
275
+ >
276
+ <Modal.Content>Large modal</Modal.Content>
277
+ </Modal.Root>
278
+ );
279
+
280
+ const dialog = await screen.findByRole("dialog");
281
+ expect(dialog.className).toContain("max-w-lg");
282
+ });
283
+ });
284
+
285
+ describe("centered prop", () => {
286
+ it("centers vertically by default", async () => {
287
+ await render(
288
+ <Modal.Root defaultOpened transitionProps={transitionProps}>
289
+ <Modal.Content>Centered</Modal.Content>
290
+ </Modal.Root>
291
+ );
292
+
293
+ await screen.findByRole("dialog");
294
+ const wrapper =
295
+ screen.getByRole("dialog").parentElement?.parentElement;
296
+ expect(wrapper?.className).toContain("place-items-center");
297
+ });
298
+
299
+ it("positions at top when centered is false", async () => {
300
+ await render(
301
+ <Modal.Root
302
+ defaultOpened
303
+ centered={false}
304
+ transitionProps={transitionProps}
305
+ >
306
+ <Modal.Content>Top aligned</Modal.Content>
307
+ </Modal.Root>
308
+ );
309
+
310
+ await screen.findByRole("dialog");
311
+ const wrapper =
312
+ screen.getByRole("dialog").parentElement?.parentElement;
313
+ expect(wrapper?.className).toContain("items-start");
144
314
  });
145
315
  });
146
316
  });
@@ -3,16 +3,47 @@ import {
3
3
  createComponentConfig,
4
4
  factory
5
5
  } from "../../utils";
6
+ import { ModalBody } from "./modal-body";
6
7
  import { ModalClose } from "./modal-close";
7
8
  import { ModalContent } from "./modal-content";
8
9
  import { ModalHeader } from "./modal-header";
9
10
  import { ModalOverlay } from "./modal-overlay";
10
11
  import { ModalRoot } from "./modal-root";
11
- import { ModalClassNames, ModalFactoryPayload, ModalProps } from "./modal.types";
12
+ import {
13
+ ModalClassNames,
14
+ ModalFactoryPayload,
15
+ ModalProps
16
+ } from "./modal.types";
17
+
18
+ const Modal = factory<ModalFactoryPayload>(
19
+ (
20
+ {
21
+ title,
22
+ withOverlay = true,
23
+ withCloseButton = true,
24
+ overlayProps,
25
+ children,
26
+ ...rootProps
27
+ },
28
+ ref
29
+ ) => {
30
+ return (
31
+ <ModalRoot {...rootProps} ref={ref}>
32
+ {withOverlay && <ModalOverlay {...overlayProps} />}
33
+
34
+ <ModalContent>
35
+ {(title || withCloseButton) && (
36
+ <ModalHeader withClose={withCloseButton}>
37
+ {title}
38
+ </ModalHeader>
39
+ )}
12
40
 
13
- const Modal = factory<ModalFactoryPayload>((props, ref) => {
14
- return <ModalRoot {...props} ref={ref} />;
15
- });
41
+ <ModalBody>{children}</ModalBody>
42
+ </ModalContent>
43
+ </ModalRoot>
44
+ );
45
+ }
46
+ );
16
47
 
17
48
  Modal.displayName = "@refraktor/core/Modal";
18
49
  Modal.configure = createComponentConfig<ModalProps>();
@@ -21,6 +52,7 @@ Modal.Root = ModalRoot;
21
52
  Modal.Overlay = ModalOverlay;
22
53
  Modal.Content = ModalContent;
23
54
  Modal.Header = ModalHeader;
55
+ Modal.Body = ModalBody;
24
56
  Modal.Close = ModalClose;
25
57
 
26
58
  export default Modal;