@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
@@ -10,19 +10,23 @@ import { ModalRoot } from "./modal-root";
10
10
  import { ModalOverlay } from "./modal-overlay";
11
11
  import { ModalContent } from "./modal-content";
12
12
  import { ModalHeader } from "./modal-header";
13
+ import { ModalBody } from "./modal-body";
13
14
  import { ModalClose } from "./modal-close";
14
15
 
16
+ export type ModalSize = "xs" | "sm" | "md" | "lg" | "xl" | "full";
17
+
15
18
  export type ModalClassNames = {
16
19
  root?: string;
17
20
  overlay?: string;
18
21
  content?: string;
19
22
  header?: string;
23
+ body?: string;
20
24
  close?: string;
21
25
  };
22
26
 
23
- export interface ModalProps extends ComponentPropsWithoutRef<"div"> {
27
+ export interface ModalRootProps extends ComponentPropsWithoutRef<"div"> {
24
28
  /** Children containing modal subcomponents */
25
- children: ReactNode;
29
+ children?: ReactNode;
26
30
 
27
31
  /** State of the modal (controlled) */
28
32
  opened?: boolean;
@@ -48,6 +52,18 @@ export interface ModalProps extends ComponentPropsWithoutRef<"div"> {
48
52
  /** Radius for modal content @default `md` */
49
53
  radius?: RefraktorRadius;
50
54
 
55
+ /** Modal content width @default `md` */
56
+ size?: ModalSize;
57
+
58
+ /** Whether to center modal vertically @default `true` */
59
+ centered?: boolean;
60
+
61
+ /** Whether to trap focus within the modal @default `true` */
62
+ trapFocus?: boolean;
63
+
64
+ /** Whether to return focus to trigger after close @default `true` */
65
+ returnFocus?: boolean;
66
+
51
67
  /** Transition props for overlay/content, uses Transition internally */
52
68
  transitionProps?: Omit<TransitionProps, "children" | "mounted">;
53
69
 
@@ -58,7 +74,19 @@ export interface ModalProps extends ComponentPropsWithoutRef<"div"> {
58
74
  classNames?: ModalClassNames;
59
75
  }
60
76
 
61
- export type ModalRootProps = ModalProps;
77
+ export interface ModalProps extends Omit<ModalRootProps, "title"> {
78
+ /** Title text rendered in the header */
79
+ title?: ReactNode;
80
+
81
+ /** Whether to render the overlay @default `true` */
82
+ withOverlay?: boolean;
83
+
84
+ /** Whether to show the close button in the header @default `true` */
85
+ withCloseButton?: boolean;
86
+
87
+ /** Props passed to the Overlay subcomponent */
88
+ overlayProps?: ModalOverlayProps;
89
+ }
62
90
 
63
91
  export interface ModalOverlayProps extends ComponentPropsWithoutRef<"div"> {
64
92
  /** Whether clicking the overlay closes modal @default `true` */
@@ -86,9 +114,6 @@ export interface ModalHeaderProps extends ComponentPropsWithoutRef<"div"> {
86
114
  /** Header content */
87
115
  children?: ReactNode;
88
116
 
89
- /** Shorthand header text */
90
- text?: ReactNode;
91
-
92
117
  /** Whether to show close button inside header @default `true` */
93
118
  withClose?: boolean;
94
119
 
@@ -96,8 +121,18 @@ export interface ModalHeaderProps extends ComponentPropsWithoutRef<"div"> {
96
121
  className?: string;
97
122
  }
98
123
 
99
- export interface ModalCloseProps
100
- extends Omit<ComponentPropsWithoutRef<"button">, "onClick"> {
124
+ export interface ModalBodyProps extends ComponentPropsWithoutRef<"div"> {
125
+ /** Body content */
126
+ children?: ReactNode;
127
+
128
+ /** Used for editing root class name */
129
+ className?: string;
130
+ }
131
+
132
+ export interface ModalCloseProps extends Omit<
133
+ ComponentPropsWithoutRef<"button">,
134
+ "onClick"
135
+ > {
101
136
  /** Optional close content (defaults to `x`) */
102
137
  children?: ReactNode;
103
138
 
@@ -118,6 +153,7 @@ export interface ModalFactoryPayload extends FactoryPayload {
118
153
  Overlay: typeof ModalOverlay;
119
154
  Content: typeof ModalContent;
120
155
  Header: typeof ModalHeader;
156
+ Body: typeof ModalBody;
121
157
  Close: typeof ModalClose;
122
158
  };
123
159
  }
@@ -142,6 +178,11 @@ export interface ModalHeaderFactoryPayload extends FactoryPayload {
142
178
  ref: HTMLDivElement;
143
179
  }
144
180
 
181
+ export interface ModalBodyFactoryPayload extends FactoryPayload {
182
+ props: ModalBodyProps;
183
+ ref: HTMLDivElement;
184
+ }
185
+
145
186
  export interface ModalCloseFactoryPayload extends FactoryPayload {
146
187
  props: ModalCloseProps;
147
188
  ref: HTMLButtonElement;
@@ -1,5 +1,12 @@
1
1
  import { useUncontrolled } from "@refraktor/utils";
2
- import { useCallback, useEffect } from "react";
2
+ import {
3
+ FloatingContext,
4
+ useDismiss,
5
+ useFloating,
6
+ useInteractions,
7
+ useRole
8
+ } from "@floating-ui/react";
9
+ import { useCallback } from "react";
3
10
 
4
11
  interface UseModalProps {
5
12
  opened?: boolean;
@@ -7,7 +14,6 @@ interface UseModalProps {
7
14
  onOpenedChange?: (opened: boolean) => void;
8
15
  closeOnClickOutside?: boolean;
9
16
  closeOnEscape?: boolean;
10
- contentRef: React.MutableRefObject<HTMLElement | null>;
11
17
  }
12
18
 
13
19
  export interface UseModalReturn {
@@ -15,6 +21,15 @@ export interface UseModalReturn {
15
21
  open: () => void;
16
22
  close: () => void;
17
23
  toggle: () => void;
24
+ context: FloatingContext;
25
+ refs: {
26
+ setReference: (node: HTMLElement | null) => void;
27
+ setFloating: (node: HTMLElement | null) => void;
28
+ floating: React.MutableRefObject<HTMLElement | null>;
29
+ };
30
+ getFloatingProps: (
31
+ userProps?: React.HTMLAttributes<HTMLElement>
32
+ ) => Record<string, unknown>;
18
33
  }
19
34
 
20
35
  export function useModal(options: UseModalProps): UseModalReturn {
@@ -23,8 +38,7 @@ export function useModal(options: UseModalProps): UseModalReturn {
23
38
  defaultOpened,
24
39
  onOpenedChange,
25
40
  closeOnClickOutside = true,
26
- closeOnEscape = true,
27
- contentRef
41
+ closeOnEscape = true
28
42
  } = options;
29
43
 
30
44
  const [isOpen, setIsOpen] = useUncontrolled({
@@ -34,6 +48,23 @@ export function useModal(options: UseModalProps): UseModalReturn {
34
48
  onChange: onOpenedChange
35
49
  });
36
50
 
51
+ const floating = useFloating({
52
+ open: isOpen,
53
+ onOpenChange: setIsOpen
54
+ });
55
+
56
+ const dismiss = useDismiss(floating.context, {
57
+ outsidePress: closeOnClickOutside,
58
+ outsidePressEvent: "mousedown",
59
+ escapeKey: closeOnEscape
60
+ });
61
+
62
+ const role = useRole(floating.context, {
63
+ role: "dialog"
64
+ });
65
+
66
+ const { getFloatingProps } = useInteractions([dismiss, role]);
67
+
37
68
  const open = useCallback(() => {
38
69
  setIsOpen(true);
39
70
  }, [setIsOpen]);
@@ -46,56 +77,18 @@ export function useModal(options: UseModalProps): UseModalReturn {
46
77
  setIsOpen(!isOpen);
47
78
  }, [isOpen, setIsOpen]);
48
79
 
49
- useEffect(() => {
50
- if (!isOpen || !closeOnEscape) {
51
- return;
52
- }
53
-
54
- const handleKeyDown = (event: KeyboardEvent) => {
55
- if (event.key === "Escape") {
56
- setIsOpen(false);
57
- }
58
- };
59
-
60
- document.addEventListener("keydown", handleKeyDown);
61
-
62
- return () => {
63
- document.removeEventListener("keydown", handleKeyDown);
64
- };
65
- }, [closeOnEscape, isOpen, setIsOpen]);
66
-
67
- useEffect(() => {
68
- if (!isOpen || !closeOnClickOutside) {
69
- return;
70
- }
71
-
72
- const handlePointerDown = (event: MouseEvent | TouchEvent) => {
73
- const target = event.target;
74
-
75
- if (!(target instanceof Node)) {
76
- return;
77
- }
78
-
79
- if (contentRef.current?.contains(target)) {
80
- return;
81
- }
82
-
83
- setIsOpen(false);
84
- };
85
-
86
- document.addEventListener("mousedown", handlePointerDown);
87
- document.addEventListener("touchstart", handlePointerDown);
88
-
89
- return () => {
90
- document.removeEventListener("mousedown", handlePointerDown);
91
- document.removeEventListener("touchstart", handlePointerDown);
92
- };
93
- }, [closeOnClickOutside, contentRef, isOpen, setIsOpen]);
94
-
95
80
  return {
96
81
  opened: isOpen,
97
82
  open,
98
83
  close,
99
- toggle
84
+ toggle,
85
+ context: floating.context,
86
+ refs: {
87
+ setReference: floating.refs.setReference,
88
+ setFloating: floating.refs.setFloating,
89
+ floating: floating.refs
90
+ .floating as React.MutableRefObject<HTMLElement | null>
91
+ },
92
+ getFloatingProps
100
93
  };
101
94
  }
@@ -0,0 +1,2 @@
1
+ export { default as PasswordInput } from "./password-input";
2
+ export * from "./password-input.types";
@@ -0,0 +1,72 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { render, screen, userEvent } from "../../vitest";
3
+ import PasswordInput from "./password-input";
4
+
5
+ describe("@refraktor/core/PasswordInput", () => {
6
+ it("supports input wrapper props", async () => {
7
+ await render(
8
+ <PasswordInput
9
+ label="Password"
10
+ description="Use at least 8 characters"
11
+ error="Password is required"
12
+ />
13
+ );
14
+
15
+ const input = screen.getByLabelText("Password");
16
+
17
+ expect(input).toHaveAttribute("type", "password");
18
+ expect(input).toHaveAttribute("aria-invalid", "true");
19
+ expect(screen.getByText("Use at least 8 characters")).toBeInTheDocument();
20
+ expect(screen.getByText("Password is required")).toBeInTheDocument();
21
+ });
22
+
23
+ it("toggles password visibility", async () => {
24
+ const user = userEvent.setup();
25
+
26
+ await render(<PasswordInput label="Password" />);
27
+
28
+ const input = screen.getByLabelText("Password");
29
+ const showButton = screen.getByRole("button", {
30
+ name: "Show password"
31
+ });
32
+
33
+ expect(input).toHaveAttribute("type", "password");
34
+ await user.click(showButton);
35
+ expect(input).toHaveAttribute("type", "text");
36
+
37
+ const hideButton = screen.getByRole("button", {
38
+ name: "Hide password"
39
+ });
40
+
41
+ await user.click(hideButton);
42
+ expect(input).toHaveAttribute("type", "password");
43
+ });
44
+
45
+ it("respects disabled state", async () => {
46
+ const user = userEvent.setup();
47
+
48
+ await render(<PasswordInput label="Password" disabled />);
49
+
50
+ const input = screen.getByLabelText("Password");
51
+ const toggle = screen.getByRole("button", {
52
+ name: "Show password"
53
+ });
54
+
55
+ expect(toggle).toBeDisabled();
56
+ await user.click(toggle);
57
+ expect(input).toHaveAttribute("type", "password");
58
+ });
59
+
60
+ it("allows disabling visibility toggle", async () => {
61
+ await render(
62
+ <PasswordInput label="Password" withVisibilityToggle={false} />
63
+ );
64
+
65
+ const input = screen.getByLabelText("Password");
66
+
67
+ expect(input).toHaveAttribute("type", "password");
68
+ expect(
69
+ screen.queryByRole("button", { name: "Show password" })
70
+ ).not.toBeInTheDocument();
71
+ });
72
+ });
@@ -0,0 +1,85 @@
1
+ import { useUncontrolled } from "@refraktor/utils";
2
+ import { EyeIcon, EyeOffIcon } from "../../icons";
3
+ import { useTheme } from "../../theme";
4
+ import { createComponentConfig, factory, useProps } from "../../utils";
5
+ import { Input } from "../input";
6
+ import {
7
+ PasswordInputFactoryPayload,
8
+ PasswordInputProps
9
+ } from "./password-input.types";
10
+
11
+ const defaultProps = {
12
+ withVisibilityToggle: true,
13
+ showPasswordLabel: "Show password",
14
+ hidePasswordLabel: "Hide password"
15
+ } satisfies Partial<PasswordInputProps>;
16
+
17
+ const PasswordInput = factory<PasswordInputFactoryPayload>((_props, ref) => {
18
+ const { cx } = useTheme();
19
+ const {
20
+ visible,
21
+ defaultVisible,
22
+ onVisibilityChange,
23
+ withVisibilityToggle,
24
+ showPasswordLabel,
25
+ hidePasswordLabel,
26
+ rightSection,
27
+ disabled,
28
+ ...props
29
+ } = useProps("PasswordInput", defaultProps, _props);
30
+
31
+ const [isVisible, setVisible] = useUncontrolled({
32
+ value: visible,
33
+ defaultValue: defaultVisible,
34
+ finalValue: false,
35
+ onChange: onVisibilityChange
36
+ });
37
+
38
+ const Icon = isVisible ? EyeOffIcon : EyeIcon;
39
+ const toggleLabel = isVisible ? hidePasswordLabel : showPasswordLabel;
40
+
41
+ const visibilityToggle = (
42
+ <button
43
+ type="button"
44
+ disabled={disabled}
45
+ aria-label={toggleLabel}
46
+ aria-pressed={isVisible}
47
+ className={cx(
48
+ "inline-flex cursor-pointer items-center justify-center border-0 bg-transparent p-0 text-[var(--refraktor-text-secondary)] transition-colors hover:text-[var(--refraktor-text)]",
49
+ disabled && "cursor-not-allowed opacity-50"
50
+ )}
51
+ onMouseDown={(event) => event.preventDefault()}
52
+ onClick={() => setVisible(!isVisible)}
53
+ >
54
+ <Icon size={16} />
55
+ </button>
56
+ );
57
+
58
+ let resolvedRightSection = rightSection;
59
+
60
+ if (withVisibilityToggle) {
61
+ resolvedRightSection = rightSection ? (
62
+ <span className="inline-flex items-center gap-1">
63
+ {rightSection}
64
+ {visibilityToggle}
65
+ </span>
66
+ ) : (
67
+ visibilityToggle
68
+ );
69
+ }
70
+
71
+ return (
72
+ <Input
73
+ {...props}
74
+ ref={ref}
75
+ type={isVisible ? "text" : "password"}
76
+ disabled={disabled}
77
+ rightSection={resolvedRightSection}
78
+ />
79
+ );
80
+ });
81
+
82
+ PasswordInput.displayName = "@refraktor/core/PasswordInput";
83
+ PasswordInput.configure = createComponentConfig<PasswordInputProps>();
84
+
85
+ export default PasswordInput;
@@ -0,0 +1,30 @@
1
+ import { createComponentConfig, FactoryPayload } from "../../utils";
2
+ import { InputProps } from "../input";
3
+
4
+ export interface PasswordInputProps extends Omit<InputProps, "type"> {
5
+ /** Whether to render the visibility toggle icon @default `true` */
6
+ withVisibilityToggle?: boolean;
7
+
8
+ /** Controls visibility state */
9
+ visible?: boolean;
10
+
11
+ /** Initial visibility state */
12
+ defaultVisible?: boolean;
13
+
14
+ /** Callback called when visibility changes */
15
+ onVisibilityChange?: (visible: boolean) => void;
16
+
17
+ /** Accessible label for showing the password @default `Show password` */
18
+ showPasswordLabel?: string;
19
+
20
+ /** Accessible label for hiding the password @default `Hide password` */
21
+ hidePasswordLabel?: string;
22
+ }
23
+
24
+ export interface PasswordInputFactoryPayload extends FactoryPayload {
25
+ props: PasswordInputProps;
26
+ ref: HTMLInputElement;
27
+ compound: {
28
+ configure: ReturnType<typeof createComponentConfig<PasswordInputProps>>;
29
+ };
30
+ }
@@ -0,0 +1,2 @@
1
+ export { default as PinInput } from "./pin-input";
2
+ export * from "./pin-input.types";
@@ -0,0 +1,149 @@
1
+ import { useState } from "react";
2
+ import { describe, expect, it, vi } from "vitest";
3
+ import { render, screen, userEvent } from "../../vitest";
4
+ import PinInput from "./pin-input";
5
+
6
+ const getCells = (length: number) =>
7
+ Array.from({ length }, (_, index) =>
8
+ screen.getByLabelText(`Character ${index + 1} of ${length}`)
9
+ ) as HTMLInputElement[];
10
+
11
+ describe("@refraktor/core/PinInput", () => {
12
+ it("supports input wrapper props", async () => {
13
+ await render(
14
+ <PinInput
15
+ label="Verification code"
16
+ description="Enter the code from your authenticator app"
17
+ error="Code is required"
18
+ />
19
+ );
20
+
21
+ const firstCell = screen.getByLabelText("Verification code");
22
+
23
+ expect(firstCell).toHaveAttribute("aria-invalid", "true");
24
+ expect(
25
+ screen.getByText("Enter the code from your authenticator app")
26
+ ).toBeInTheDocument();
27
+ expect(screen.getByText("Code is required")).toBeInTheDocument();
28
+ });
29
+
30
+ it("auto-advances focus and fires onComplete", async () => {
31
+ const user = userEvent.setup();
32
+ const onComplete = vi.fn();
33
+
34
+ await render(<PinInput length={4} onComplete={onComplete} />);
35
+
36
+ const cells = getCells(4);
37
+
38
+ await user.type(cells[0], "1");
39
+ expect(cells[1]).toHaveFocus();
40
+
41
+ await user.type(cells[1], "2");
42
+ await user.type(cells[2], "3");
43
+ await user.type(cells[3], "4");
44
+
45
+ expect(onComplete).toHaveBeenCalledTimes(1);
46
+ expect(onComplete).toHaveBeenCalledWith("1234");
47
+ });
48
+
49
+ it("handles backspace navigation", async () => {
50
+ const user = userEvent.setup();
51
+
52
+ await render(<PinInput length={4} />);
53
+
54
+ const cells = getCells(4);
55
+
56
+ await user.type(cells[0], "1");
57
+ await user.type(cells[1], "2");
58
+ expect(cells[2]).toHaveFocus();
59
+
60
+ await user.keyboard("{Backspace}");
61
+
62
+ expect(cells[1]).toHaveFocus();
63
+ expect(cells[1]).toHaveValue("");
64
+ });
65
+
66
+ it("handles paste across cells", async () => {
67
+ const user = userEvent.setup();
68
+
69
+ await render(<PinInput length={6} characterSet="alphanumeric" />);
70
+
71
+ const cells = getCells(6);
72
+
73
+ cells[0].focus();
74
+ await user.paste("A1B2C3");
75
+
76
+ expect(cells.map((cell) => cell.value)).toEqual([
77
+ "A",
78
+ "1",
79
+ "B",
80
+ "2",
81
+ "C",
82
+ "3"
83
+ ]);
84
+ });
85
+
86
+ it("supports alphanumeric and custom filtering props", async () => {
87
+ const user = userEvent.setup();
88
+
89
+ await render(
90
+ <PinInput
91
+ length={4}
92
+ characterPattern={/[A-F0-9]/}
93
+ transform="uppercase"
94
+ />
95
+ );
96
+
97
+ const cells = getCells(4);
98
+
99
+ await user.type(cells[0], "a");
100
+ await user.type(cells[1], "g");
101
+ await user.type(cells[1], "9");
102
+
103
+ expect(cells[0]).toHaveValue("A");
104
+ expect(cells[1]).toHaveValue("9");
105
+ });
106
+
107
+ it("supports controlled usage", async () => {
108
+ const user = userEvent.setup();
109
+
110
+ function Demo() {
111
+ const [value, setValue] = useState("");
112
+ return <PinInput length={4} value={value} onChange={setValue} />;
113
+ }
114
+
115
+ await render(<Demo />);
116
+
117
+ const cells = getCells(4);
118
+ await user.type(cells[0], "1");
119
+ await user.type(cells[1], "2");
120
+
121
+ expect(cells[0]).toHaveValue("1");
122
+ expect(cells[1]).toHaveValue("2");
123
+ });
124
+
125
+ it("supports masking and hidden form value", async () => {
126
+ await render(
127
+ <PinInput
128
+ length={4}
129
+ defaultValue="A1B2"
130
+ characterSet="alphanumeric"
131
+ mask
132
+ name="otp"
133
+ />
134
+ );
135
+
136
+ const cells = getCells(4);
137
+
138
+ for (const cell of cells) {
139
+ expect(cell).toHaveAttribute("type", "password");
140
+ }
141
+
142
+ const hidden = document.querySelector(
143
+ 'input[type="hidden"][name="otp"]'
144
+ ) as HTMLInputElement | null;
145
+
146
+ expect(hidden).not.toBeNull();
147
+ expect(hidden?.value).toBe("A1B2");
148
+ });
149
+ });