@entropy-softworks/ui 2026.7.31

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 (590) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +21 -0
  3. package/README.md +115 -0
  4. package/lib/components/auth/LockOverlay.d.ts +22 -0
  5. package/lib/components/auth/LockOverlay.d.ts.map +1 -0
  6. package/lib/components/auth/LockOverlay.jsx +82 -0
  7. package/lib/components/auth/LockOverlay.jsx.map +1 -0
  8. package/lib/components/auth/PasswordStrengthMeter.d.ts +12 -0
  9. package/lib/components/auth/PasswordStrengthMeter.d.ts.map +1 -0
  10. package/lib/components/auth/PasswordStrengthMeter.jsx +81 -0
  11. package/lib/components/auth/PasswordStrengthMeter.jsx.map +1 -0
  12. package/lib/components/auth/ProtectedRoute.d.ts +24 -0
  13. package/lib/components/auth/ProtectedRoute.d.ts.map +1 -0
  14. package/lib/components/auth/ProtectedRoute.jsx +56 -0
  15. package/lib/components/auth/ProtectedRoute.jsx.map +1 -0
  16. package/lib/components/auth/PwnedPasswordWarning.d.ts +13 -0
  17. package/lib/components/auth/PwnedPasswordWarning.d.ts.map +1 -0
  18. package/lib/components/auth/PwnedPasswordWarning.jsx +29 -0
  19. package/lib/components/auth/PwnedPasswordWarning.jsx.map +1 -0
  20. package/lib/components/auth/index.d.ts +5 -0
  21. package/lib/components/auth/index.d.ts.map +1 -0
  22. package/lib/components/auth/index.js +5 -0
  23. package/lib/components/auth/index.js.map +1 -0
  24. package/lib/components/bottom-nav.d.ts +25 -0
  25. package/lib/components/bottom-nav.d.ts.map +1 -0
  26. package/lib/components/bottom-nav.jsx +51 -0
  27. package/lib/components/bottom-nav.jsx.map +1 -0
  28. package/lib/components/button.d.ts +16 -0
  29. package/lib/components/button.d.ts.map +1 -0
  30. package/lib/components/button.jsx +73 -0
  31. package/lib/components/button.jsx.map +1 -0
  32. package/lib/components/card.d.ts +22 -0
  33. package/lib/components/card.d.ts.map +1 -0
  34. package/lib/components/card.jsx +36 -0
  35. package/lib/components/card.jsx.map +1 -0
  36. package/lib/components/checkbox.d.ts +12 -0
  37. package/lib/components/checkbox.d.ts.map +1 -0
  38. package/lib/components/checkbox.jsx +16 -0
  39. package/lib/components/checkbox.jsx.map +1 -0
  40. package/lib/components/connection-settings.d.ts +41 -0
  41. package/lib/components/connection-settings.d.ts.map +1 -0
  42. package/lib/components/connection-settings.jsx +354 -0
  43. package/lib/components/connection-settings.jsx.map +1 -0
  44. package/lib/components/context-menu.d.ts +52 -0
  45. package/lib/components/context-menu.d.ts.map +1 -0
  46. package/lib/components/context-menu.jsx +24 -0
  47. package/lib/components/context-menu.jsx.map +1 -0
  48. package/lib/components/data-table.d.ts +59 -0
  49. package/lib/components/data-table.d.ts.map +1 -0
  50. package/lib/components/data-table.jsx +101 -0
  51. package/lib/components/data-table.jsx.map +1 -0
  52. package/lib/components/dialog.d.ts +58 -0
  53. package/lib/components/dialog.d.ts.map +1 -0
  54. package/lib/components/dialog.jsx +197 -0
  55. package/lib/components/dialog.jsx.map +1 -0
  56. package/lib/components/dropdown-menu.d.ts +55 -0
  57. package/lib/components/dropdown-menu.d.ts.map +1 -0
  58. package/lib/components/dropdown-menu.jsx +38 -0
  59. package/lib/components/dropdown-menu.jsx.map +1 -0
  60. package/lib/components/entropy-boot-splash.d.ts +24 -0
  61. package/lib/components/entropy-boot-splash.d.ts.map +1 -0
  62. package/lib/components/entropy-boot-splash.jsx +83 -0
  63. package/lib/components/entropy-boot-splash.jsx.map +1 -0
  64. package/lib/components/entropy-mark-animated.d.ts +20 -0
  65. package/lib/components/entropy-mark-animated.d.ts.map +1 -0
  66. package/lib/components/entropy-mark-animated.jsx +148 -0
  67. package/lib/components/entropy-mark-animated.jsx.map +1 -0
  68. package/lib/components/entropy-mark-geometry.d.ts +8 -0
  69. package/lib/components/entropy-mark-geometry.d.ts.map +1 -0
  70. package/lib/components/entropy-mark-geometry.js +10 -0
  71. package/lib/components/entropy-mark-geometry.js.map +1 -0
  72. package/lib/components/entropy-mark-sequential.d.ts +12 -0
  73. package/lib/components/entropy-mark-sequential.d.ts.map +1 -0
  74. package/lib/components/entropy-mark-sequential.jsx +53 -0
  75. package/lib/components/entropy-mark-sequential.jsx.map +1 -0
  76. package/lib/components/entropy-mark.d.ts +29 -0
  77. package/lib/components/entropy-mark.d.ts.map +1 -0
  78. package/lib/components/entropy-mark.jsx +66 -0
  79. package/lib/components/entropy-mark.jsx.map +1 -0
  80. package/lib/components/error-boundary.d.ts +31 -0
  81. package/lib/components/error-boundary.d.ts.map +1 -0
  82. package/lib/components/error-boundary.jsx +40 -0
  83. package/lib/components/error-boundary.jsx.map +1 -0
  84. package/lib/components/index.d.ts +41 -0
  85. package/lib/components/index.d.ts.map +1 -0
  86. package/lib/components/index.js +43 -0
  87. package/lib/components/index.js.map +1 -0
  88. package/lib/components/input.d.ts +26 -0
  89. package/lib/components/input.d.ts.map +1 -0
  90. package/lib/components/input.jsx +95 -0
  91. package/lib/components/input.jsx.map +1 -0
  92. package/lib/components/item-icon.d.ts +32 -0
  93. package/lib/components/item-icon.d.ts.map +1 -0
  94. package/lib/components/item-icon.jsx +54 -0
  95. package/lib/components/item-icon.jsx.map +1 -0
  96. package/lib/components/label.d.ts +13 -0
  97. package/lib/components/label.d.ts.map +1 -0
  98. package/lib/components/label.jsx +9 -0
  99. package/lib/components/label.jsx.map +1 -0
  100. package/lib/components/legal-dialog.d.ts +18 -0
  101. package/lib/components/legal-dialog.d.ts.map +1 -0
  102. package/lib/components/legal-dialog.jsx +31 -0
  103. package/lib/components/legal-dialog.jsx.map +1 -0
  104. package/lib/components/license-gate.d.ts +17 -0
  105. package/lib/components/license-gate.d.ts.map +1 -0
  106. package/lib/components/license-gate.jsx +21 -0
  107. package/lib/components/license-gate.jsx.map +1 -0
  108. package/lib/components/logo.d.ts +15 -0
  109. package/lib/components/logo.d.ts.map +1 -0
  110. package/lib/components/logo.jsx +20 -0
  111. package/lib/components/logo.jsx.map +1 -0
  112. package/lib/components/onboarding-wizard.d.ts +89 -0
  113. package/lib/components/onboarding-wizard.d.ts.map +1 -0
  114. package/lib/components/onboarding-wizard.jsx +185 -0
  115. package/lib/components/onboarding-wizard.jsx.map +1 -0
  116. package/lib/components/pagination.d.ts +43 -0
  117. package/lib/components/pagination.d.ts.map +1 -0
  118. package/lib/components/pagination.jsx +72 -0
  119. package/lib/components/pagination.jsx.map +1 -0
  120. package/lib/components/password-generator.d.ts +15 -0
  121. package/lib/components/password-generator.d.ts.map +1 -0
  122. package/lib/components/password-generator.jsx +273 -0
  123. package/lib/components/password-generator.jsx.map +1 -0
  124. package/lib/components/pin-pad.d.ts +20 -0
  125. package/lib/components/pin-pad.d.ts.map +1 -0
  126. package/lib/components/pin-pad.jsx +93 -0
  127. package/lib/components/pin-pad.jsx.map +1 -0
  128. package/lib/components/popover.d.ts +27 -0
  129. package/lib/components/popover.d.ts.map +1 -0
  130. package/lib/components/popover.jsx +18 -0
  131. package/lib/components/popover.jsx.map +1 -0
  132. package/lib/components/profile/ProfileDialog.d.ts +25 -0
  133. package/lib/components/profile/ProfileDialog.d.ts.map +1 -0
  134. package/lib/components/profile/ProfileDialog.jsx +71 -0
  135. package/lib/components/profile/ProfileDialog.jsx.map +1 -0
  136. package/lib/components/profile/ProfileTab.d.ts +12 -0
  137. package/lib/components/profile/ProfileTab.d.ts.map +1 -0
  138. package/lib/components/profile/ProfileTab.jsx +112 -0
  139. package/lib/components/profile/ProfileTab.jsx.map +1 -0
  140. package/lib/components/profile/SecurityTab.d.ts +32 -0
  141. package/lib/components/profile/SecurityTab.d.ts.map +1 -0
  142. package/lib/components/profile/SecurityTab.jsx +145 -0
  143. package/lib/components/profile/SecurityTab.jsx.map +1 -0
  144. package/lib/components/profile/index.d.ts +4 -0
  145. package/lib/components/profile/index.d.ts.map +1 -0
  146. package/lib/components/profile/index.js +4 -0
  147. package/lib/components/profile/index.js.map +1 -0
  148. package/lib/components/segmented-toggle.d.ts +28 -0
  149. package/lib/components/segmented-toggle.d.ts.map +1 -0
  150. package/lib/components/segmented-toggle.jsx +64 -0
  151. package/lib/components/segmented-toggle.jsx.map +1 -0
  152. package/lib/components/separator.d.ts +13 -0
  153. package/lib/components/separator.d.ts.map +1 -0
  154. package/lib/components/separator.jsx +16 -0
  155. package/lib/components/separator.jsx.map +1 -0
  156. package/lib/components/server-settings.d.ts +27 -0
  157. package/lib/components/server-settings.d.ts.map +1 -0
  158. package/lib/components/server-settings.jsx +307 -0
  159. package/lib/components/server-settings.jsx.map +1 -0
  160. package/lib/components/settings/AccountSection.d.ts +26 -0
  161. package/lib/components/settings/AccountSection.d.ts.map +1 -0
  162. package/lib/components/settings/AccountSection.jsx +32 -0
  163. package/lib/components/settings/AccountSection.jsx.map +1 -0
  164. package/lib/components/settings/DangerZoneSection.d.ts +13 -0
  165. package/lib/components/settings/DangerZoneSection.d.ts.map +1 -0
  166. package/lib/components/settings/DangerZoneSection.jsx +16 -0
  167. package/lib/components/settings/DangerZoneSection.jsx.map +1 -0
  168. package/lib/components/settings/GeneralSection.d.ts +11 -0
  169. package/lib/components/settings/GeneralSection.d.ts.map +1 -0
  170. package/lib/components/settings/GeneralSection.jsx +12 -0
  171. package/lib/components/settings/GeneralSection.jsx.map +1 -0
  172. package/lib/components/settings/SupportSection.d.ts +11 -0
  173. package/lib/components/settings/SupportSection.d.ts.map +1 -0
  174. package/lib/components/settings/SupportSection.jsx +22 -0
  175. package/lib/components/settings/SupportSection.jsx.map +1 -0
  176. package/lib/components/settings/index.d.ts +5 -0
  177. package/lib/components/settings/index.d.ts.map +1 -0
  178. package/lib/components/settings/index.js +5 -0
  179. package/lib/components/settings/index.js.map +1 -0
  180. package/lib/components/settings-row.d.ts +28 -0
  181. package/lib/components/settings-row.d.ts.map +1 -0
  182. package/lib/components/settings-row.jsx +68 -0
  183. package/lib/components/settings-row.jsx.map +1 -0
  184. package/lib/components/skeleton.d.ts +8 -0
  185. package/lib/components/skeleton.d.ts.map +1 -0
  186. package/lib/components/skeleton.jsx +24 -0
  187. package/lib/components/skeleton.jsx.map +1 -0
  188. package/lib/components/social-connections.d.ts +15 -0
  189. package/lib/components/social-connections.d.ts.map +1 -0
  190. package/lib/components/social-connections.jsx +31 -0
  191. package/lib/components/social-connections.jsx.map +1 -0
  192. package/lib/components/stat-card.d.ts +29 -0
  193. package/lib/components/stat-card.d.ts.map +1 -0
  194. package/lib/components/stat-card.jsx +36 -0
  195. package/lib/components/stat-card.jsx.map +1 -0
  196. package/lib/components/tag-chip.d.ts +18 -0
  197. package/lib/components/tag-chip.d.ts.map +1 -0
  198. package/lib/components/tag-chip.jsx +54 -0
  199. package/lib/components/tag-chip.jsx.map +1 -0
  200. package/lib/components/tag-multi-select.d.ts +27 -0
  201. package/lib/components/tag-multi-select.d.ts.map +1 -0
  202. package/lib/components/tag-multi-select.jsx +65 -0
  203. package/lib/components/tag-multi-select.jsx.map +1 -0
  204. package/lib/components/text.d.ts +22 -0
  205. package/lib/components/text.d.ts.map +1 -0
  206. package/lib/components/text.jsx +43 -0
  207. package/lib/components/text.jsx.map +1 -0
  208. package/lib/components/toast/ToastPortal.d.ts +11 -0
  209. package/lib/components/toast/ToastPortal.d.ts.map +1 -0
  210. package/lib/components/toast/ToastPortal.jsx +15 -0
  211. package/lib/components/toast/ToastPortal.jsx.map +1 -0
  212. package/lib/components/toast/ToastPortal.web.d.ts +14 -0
  213. package/lib/components/toast/ToastPortal.web.d.ts.map +1 -0
  214. package/lib/components/toast/ToastPortal.web.jsx +39 -0
  215. package/lib/components/toast/ToastPortal.web.jsx.map +1 -0
  216. package/lib/components/toast/index.d.ts +3 -0
  217. package/lib/components/toast/index.d.ts.map +1 -0
  218. package/lib/components/toast/index.js +3 -0
  219. package/lib/components/toast/index.js.map +1 -0
  220. package/lib/components/toast/toast-config.d.ts +14 -0
  221. package/lib/components/toast/toast-config.d.ts.map +1 -0
  222. package/lib/components/toast/toast-config.jsx +24 -0
  223. package/lib/components/toast/toast-config.jsx.map +1 -0
  224. package/lib/components/tooltip.d.ts +30 -0
  225. package/lib/components/tooltip.d.ts.map +1 -0
  226. package/lib/components/tooltip.jsx +14 -0
  227. package/lib/components/tooltip.jsx.map +1 -0
  228. package/lib/components/totp-display.d.ts +23 -0
  229. package/lib/components/totp-display.d.ts.map +1 -0
  230. package/lib/components/totp-display.jsx +132 -0
  231. package/lib/components/totp-display.jsx.map +1 -0
  232. package/lib/components/user-avatar.d.ts +18 -0
  233. package/lib/components/user-avatar.d.ts.map +1 -0
  234. package/lib/components/user-avatar.jsx +25 -0
  235. package/lib/components/user-avatar.jsx.map +1 -0
  236. package/lib/context/AppConfigContext.d.ts +56 -0
  237. package/lib/context/AppConfigContext.d.ts.map +1 -0
  238. package/lib/context/AppConfigContext.jsx +14 -0
  239. package/lib/context/AppConfigContext.jsx.map +1 -0
  240. package/lib/context/NavigationContext.d.ts +42 -0
  241. package/lib/context/NavigationContext.d.ts.map +1 -0
  242. package/lib/context/NavigationContext.jsx +53 -0
  243. package/lib/context/NavigationContext.jsx.map +1 -0
  244. package/lib/context/NotificationContext.d.ts +32 -0
  245. package/lib/context/NotificationContext.d.ts.map +1 -0
  246. package/lib/context/NotificationContext.jsx +49 -0
  247. package/lib/context/NotificationContext.jsx.map +1 -0
  248. package/lib/context/ThemeContext.d.ts +12 -0
  249. package/lib/context/ThemeContext.d.ts.map +1 -0
  250. package/lib/context/ThemeContext.jsx +66 -0
  251. package/lib/context/ThemeContext.jsx.map +1 -0
  252. package/lib/context/index.d.ts +5 -0
  253. package/lib/context/index.d.ts.map +1 -0
  254. package/lib/context/index.js +5 -0
  255. package/lib/context/index.js.map +1 -0
  256. package/lib/flags.d.ts +9 -0
  257. package/lib/flags.d.ts.map +1 -0
  258. package/lib/flags.js +37 -0
  259. package/lib/flags.js.map +1 -0
  260. package/lib/forms/form-error.d.ts +12 -0
  261. package/lib/forms/form-error.d.ts.map +1 -0
  262. package/lib/forms/form-error.jsx +14 -0
  263. package/lib/forms/form-error.jsx.map +1 -0
  264. package/lib/forms/index.d.ts +3 -0
  265. package/lib/forms/index.d.ts.map +1 -0
  266. package/lib/forms/index.js +3 -0
  267. package/lib/forms/index.js.map +1 -0
  268. package/lib/forms/shake-field.d.ts +18 -0
  269. package/lib/forms/shake-field.d.ts.map +1 -0
  270. package/lib/forms/shake-field.jsx +30 -0
  271. package/lib/forms/shake-field.jsx.map +1 -0
  272. package/lib/hooks/index.d.ts +6 -0
  273. package/lib/hooks/index.d.ts.map +1 -0
  274. package/lib/hooks/index.js +6 -0
  275. package/lib/hooks/index.js.map +1 -0
  276. package/lib/hooks/useAuth.d.ts +109 -0
  277. package/lib/hooks/useAuth.d.ts.map +1 -0
  278. package/lib/hooks/useAuth.jsx +404 -0
  279. package/lib/hooks/useAuth.jsx.map +1 -0
  280. package/lib/hooks/useAutoLock.d.ts +22 -0
  281. package/lib/hooks/useAutoLock.d.ts.map +1 -0
  282. package/lib/hooks/useAutoLock.js +85 -0
  283. package/lib/hooks/useAutoLock.js.map +1 -0
  284. package/lib/hooks/useDebouncedValue.d.ts +19 -0
  285. package/lib/hooks/useDebouncedValue.d.ts.map +1 -0
  286. package/lib/hooks/useDebouncedValue.js +29 -0
  287. package/lib/hooks/useDebouncedValue.js.map +1 -0
  288. package/lib/hooks/useShakeAnimation.d.ts +17 -0
  289. package/lib/hooks/useShakeAnimation.d.ts.map +1 -0
  290. package/lib/hooks/useShakeAnimation.js +22 -0
  291. package/lib/hooks/useShakeAnimation.js.map +1 -0
  292. package/lib/hooks/useUploadBlobUrl.d.ts +13 -0
  293. package/lib/hooks/useUploadBlobUrl.d.ts.map +1 -0
  294. package/lib/hooks/useUploadBlobUrl.js +60 -0
  295. package/lib/hooks/useUploadBlobUrl.js.map +1 -0
  296. package/lib/index.d.ts +10 -0
  297. package/lib/index.d.ts.map +1 -0
  298. package/lib/index.js +47 -0
  299. package/lib/index.js.map +1 -0
  300. package/lib/screens/auth/AccountPickerScreen.d.ts +24 -0
  301. package/lib/screens/auth/AccountPickerScreen.d.ts.map +1 -0
  302. package/lib/screens/auth/AccountPickerScreen.jsx +47 -0
  303. package/lib/screens/auth/AccountPickerScreen.jsx.map +1 -0
  304. package/lib/screens/auth/AuthScreen.d.ts +59 -0
  305. package/lib/screens/auth/AuthScreen.d.ts.map +1 -0
  306. package/lib/screens/auth/AuthScreen.jsx +594 -0
  307. package/lib/screens/auth/AuthScreen.jsx.map +1 -0
  308. package/lib/screens/auth/ConsentScreen.d.ts +27 -0
  309. package/lib/screens/auth/ConsentScreen.d.ts.map +1 -0
  310. package/lib/screens/auth/ConsentScreen.jsx +55 -0
  311. package/lib/screens/auth/ConsentScreen.jsx.map +1 -0
  312. package/lib/screens/auth/ErrorScreen.d.ts +20 -0
  313. package/lib/screens/auth/ErrorScreen.d.ts.map +1 -0
  314. package/lib/screens/auth/ErrorScreen.jsx +43 -0
  315. package/lib/screens/auth/ErrorScreen.jsx.map +1 -0
  316. package/lib/screens/auth/ExpiredLinkScreen.d.ts +19 -0
  317. package/lib/screens/auth/ExpiredLinkScreen.d.ts.map +1 -0
  318. package/lib/screens/auth/ExpiredLinkScreen.jsx +38 -0
  319. package/lib/screens/auth/ExpiredLinkScreen.jsx.map +1 -0
  320. package/lib/screens/auth/ForgotPasswordScreen.d.ts +4 -0
  321. package/lib/screens/auth/ForgotPasswordScreen.d.ts.map +1 -0
  322. package/lib/screens/auth/ForgotPasswordScreen.jsx +123 -0
  323. package/lib/screens/auth/ForgotPasswordScreen.jsx.map +1 -0
  324. package/lib/screens/auth/ImportDataScreen.d.ts +17 -0
  325. package/lib/screens/auth/ImportDataScreen.d.ts.map +1 -0
  326. package/lib/screens/auth/ImportDataScreen.jsx +73 -0
  327. package/lib/screens/auth/ImportDataScreen.jsx.map +1 -0
  328. package/lib/screens/auth/LoginScreen.d.ts +38 -0
  329. package/lib/screens/auth/LoginScreen.d.ts.map +1 -0
  330. package/lib/screens/auth/LoginScreen.jsx +326 -0
  331. package/lib/screens/auth/LoginScreen.jsx.map +1 -0
  332. package/lib/screens/auth/MfaChallengeScreen.d.ts +4 -0
  333. package/lib/screens/auth/MfaChallengeScreen.d.ts.map +1 -0
  334. package/lib/screens/auth/MfaChallengeScreen.jsx +139 -0
  335. package/lib/screens/auth/MfaChallengeScreen.jsx.map +1 -0
  336. package/lib/screens/auth/MfaEnrollScreen.d.ts +12 -0
  337. package/lib/screens/auth/MfaEnrollScreen.d.ts.map +1 -0
  338. package/lib/screens/auth/MfaEnrollScreen.jsx +267 -0
  339. package/lib/screens/auth/MfaEnrollScreen.jsx.map +1 -0
  340. package/lib/screens/auth/PasskeyEnrollScreen.d.ts +18 -0
  341. package/lib/screens/auth/PasskeyEnrollScreen.d.ts.map +1 -0
  342. package/lib/screens/auth/PasskeyEnrollScreen.jsx +51 -0
  343. package/lib/screens/auth/PasskeyEnrollScreen.jsx.map +1 -0
  344. package/lib/screens/auth/PrivacyPolicyScreen.d.ts +19 -0
  345. package/lib/screens/auth/PrivacyPolicyScreen.d.ts.map +1 -0
  346. package/lib/screens/auth/PrivacyPolicyScreen.jsx +143 -0
  347. package/lib/screens/auth/PrivacyPolicyScreen.jsx.map +1 -0
  348. package/lib/screens/auth/RegisterScreen.d.ts +14 -0
  349. package/lib/screens/auth/RegisterScreen.d.ts.map +1 -0
  350. package/lib/screens/auth/RegisterScreen.jsx +296 -0
  351. package/lib/screens/auth/RegisterScreen.jsx.map +1 -0
  352. package/lib/screens/auth/ResetPasswordScreen.d.ts +12 -0
  353. package/lib/screens/auth/ResetPasswordScreen.d.ts.map +1 -0
  354. package/lib/screens/auth/ResetPasswordScreen.jsx +276 -0
  355. package/lib/screens/auth/ResetPasswordScreen.jsx.map +1 -0
  356. package/lib/screens/auth/ServerConfigScreen.d.ts +18 -0
  357. package/lib/screens/auth/ServerConfigScreen.d.ts.map +1 -0
  358. package/lib/screens/auth/ServerConfigScreen.jsx +259 -0
  359. package/lib/screens/auth/ServerConfigScreen.jsx.map +1 -0
  360. package/lib/screens/auth/TermsOfServiceScreen.d.ts +30 -0
  361. package/lib/screens/auth/TermsOfServiceScreen.d.ts.map +1 -0
  362. package/lib/screens/auth/TermsOfServiceScreen.jsx +130 -0
  363. package/lib/screens/auth/TermsOfServiceScreen.jsx.map +1 -0
  364. package/lib/screens/auth/VerifyEmailScreen.d.ts +4 -0
  365. package/lib/screens/auth/VerifyEmailScreen.d.ts.map +1 -0
  366. package/lib/screens/auth/VerifyEmailScreen.jsx +249 -0
  367. package/lib/screens/auth/VerifyEmailScreen.jsx.map +1 -0
  368. package/lib/screens/auth/WelcomeScreen.d.ts +17 -0
  369. package/lib/screens/auth/WelcomeScreen.d.ts.map +1 -0
  370. package/lib/screens/auth/WelcomeScreen.jsx +58 -0
  371. package/lib/screens/auth/WelcomeScreen.jsx.map +1 -0
  372. package/lib/screens/auth/index.d.ts +19 -0
  373. package/lib/screens/auth/index.d.ts.map +1 -0
  374. package/lib/screens/auth/index.js +19 -0
  375. package/lib/screens/auth/index.js.map +1 -0
  376. package/lib/screens/index.d.ts +2 -0
  377. package/lib/screens/index.d.ts.map +1 -0
  378. package/lib/screens/index.js +2 -0
  379. package/lib/screens/index.js.map +1 -0
  380. package/lib/services/api-config.d.ts +87 -0
  381. package/lib/services/api-config.d.ts.map +1 -0
  382. package/lib/services/api-config.js +202 -0
  383. package/lib/services/api-config.js.map +1 -0
  384. package/lib/services/auth.service.d.ts +140 -0
  385. package/lib/services/auth.service.d.ts.map +1 -0
  386. package/lib/services/auth.service.js +376 -0
  387. package/lib/services/auth.service.js.map +1 -0
  388. package/lib/services/favicon.d.ts +46 -0
  389. package/lib/services/favicon.d.ts.map +1 -0
  390. package/lib/services/favicon.js +84 -0
  391. package/lib/services/favicon.js.map +1 -0
  392. package/lib/services/hibp-cache.d.ts +23 -0
  393. package/lib/services/hibp-cache.d.ts.map +1 -0
  394. package/lib/services/hibp-cache.js +56 -0
  395. package/lib/services/hibp-cache.js.map +1 -0
  396. package/lib/services/hibp.d.ts +28 -0
  397. package/lib/services/hibp.d.ts.map +1 -0
  398. package/lib/services/hibp.js +92 -0
  399. package/lib/services/hibp.js.map +1 -0
  400. package/lib/services/index.d.ts +8 -0
  401. package/lib/services/index.d.ts.map +1 -0
  402. package/lib/services/index.js +7 -0
  403. package/lib/services/index.js.map +1 -0
  404. package/lib/services/passkey.d.ts +10 -0
  405. package/lib/services/passkey.d.ts.map +1 -0
  406. package/lib/services/passkey.js +192 -0
  407. package/lib/services/passkey.js.map +1 -0
  408. package/lib/utils/api-fetch.d.ts +11 -0
  409. package/lib/utils/api-fetch.d.ts.map +1 -0
  410. package/lib/utils/api-fetch.js +52 -0
  411. package/lib/utils/api-fetch.js.map +1 -0
  412. package/lib/utils/clipboard.d.ts +31 -0
  413. package/lib/utils/clipboard.d.ts.map +1 -0
  414. package/lib/utils/clipboard.js +66 -0
  415. package/lib/utils/clipboard.js.map +1 -0
  416. package/lib/utils/cn.d.ts +9 -0
  417. package/lib/utils/cn.d.ts.map +1 -0
  418. package/lib/utils/cn.js +12 -0
  419. package/lib/utils/cn.js.map +1 -0
  420. package/lib/utils/debounce.d.ts +13 -0
  421. package/lib/utils/debounce.d.ts.map +1 -0
  422. package/lib/utils/debounce.js +28 -0
  423. package/lib/utils/debounce.js.map +1 -0
  424. package/lib/utils/error.d.ts +9 -0
  425. package/lib/utils/error.d.ts.map +1 -0
  426. package/lib/utils/error.js +11 -0
  427. package/lib/utils/error.js.map +1 -0
  428. package/lib/utils/fetch.d.ts +17 -0
  429. package/lib/utils/fetch.d.ts.map +1 -0
  430. package/lib/utils/fetch.js +29 -0
  431. package/lib/utils/fetch.js.map +1 -0
  432. package/lib/utils/http-error.d.ts +41 -0
  433. package/lib/utils/http-error.d.ts.map +1 -0
  434. package/lib/utils/http-error.js +63 -0
  435. package/lib/utils/http-error.js.map +1 -0
  436. package/lib/utils/index.d.ts +11 -0
  437. package/lib/utils/index.d.ts.map +1 -0
  438. package/lib/utils/index.js +11 -0
  439. package/lib/utils/index.js.map +1 -0
  440. package/lib/utils/palette.d.ts +46 -0
  441. package/lib/utils/palette.d.ts.map +1 -0
  442. package/lib/utils/palette.js +75 -0
  443. package/lib/utils/palette.js.map +1 -0
  444. package/lib/utils/password.d.ts +20 -0
  445. package/lib/utils/password.d.ts.map +1 -0
  446. package/lib/utils/password.js +51 -0
  447. package/lib/utils/password.js.map +1 -0
  448. package/lib/utils/settings-storage.d.ts +3 -0
  449. package/lib/utils/settings-storage.d.ts.map +1 -0
  450. package/lib/utils/settings-storage.js +62 -0
  451. package/lib/utils/settings-storage.js.map +1 -0
  452. package/lib/utils/validation.d.ts +29 -0
  453. package/lib/utils/validation.d.ts.map +1 -0
  454. package/lib/utils/validation.js +58 -0
  455. package/lib/utils/validation.js.map +1 -0
  456. package/package.json +204 -0
  457. package/src/components/__tests__/data-table.test.ts +94 -0
  458. package/src/components/__tests__/pagination.test.ts +73 -0
  459. package/src/components/__tests__/stat-card.test.ts +21 -0
  460. package/src/components/auth/LockOverlay.tsx +122 -0
  461. package/src/components/auth/PasswordStrengthMeter.tsx +96 -0
  462. package/src/components/auth/ProtectedRoute.tsx +91 -0
  463. package/src/components/auth/PwnedPasswordWarning.tsx +54 -0
  464. package/src/components/auth/index.ts +10 -0
  465. package/src/components/bottom-nav.tsx +99 -0
  466. package/src/components/button.tsx +144 -0
  467. package/src/components/card.tsx +82 -0
  468. package/src/components/checkbox.tsx +48 -0
  469. package/src/components/connection-settings.tsx +583 -0
  470. package/src/components/context-menu.tsx +95 -0
  471. package/src/components/data-table.tsx +223 -0
  472. package/src/components/dialog.tsx +376 -0
  473. package/src/components/dropdown-menu.tsx +120 -0
  474. package/src/components/entropy-boot-splash.tsx +114 -0
  475. package/src/components/entropy-mark-animated.tsx +260 -0
  476. package/src/components/entropy-mark-geometry.ts +11 -0
  477. package/src/components/entropy-mark-sequential.tsx +132 -0
  478. package/src/components/entropy-mark.tsx +135 -0
  479. package/src/components/error-boundary.tsx +70 -0
  480. package/src/components/index.ts +156 -0
  481. package/src/components/input.tsx +246 -0
  482. package/src/components/item-icon.tsx +111 -0
  483. package/src/components/label.tsx +25 -0
  484. package/src/components/legal-dialog.tsx +47 -0
  485. package/src/components/license-gate.tsx +28 -0
  486. package/src/components/logo.tsx +34 -0
  487. package/src/components/onboarding-wizard.tsx +353 -0
  488. package/src/components/pagination.tsx +154 -0
  489. package/src/components/password-generator.tsx +393 -0
  490. package/src/components/pin-pad.tsx +156 -0
  491. package/src/components/popover.tsx +38 -0
  492. package/src/components/profile/ProfileDialog.tsx +129 -0
  493. package/src/components/profile/ProfileTab.tsx +151 -0
  494. package/src/components/profile/SecurityTab.tsx +229 -0
  495. package/src/components/profile/index.ts +3 -0
  496. package/src/components/segmented-toggle.tsx +119 -0
  497. package/src/components/separator.tsx +41 -0
  498. package/src/components/server-settings.tsx +442 -0
  499. package/src/components/settings/AccountSection.tsx +76 -0
  500. package/src/components/settings/DangerZoneSection.tsx +37 -0
  501. package/src/components/settings/GeneralSection.tsx +39 -0
  502. package/src/components/settings/SupportSection.tsx +53 -0
  503. package/src/components/settings/index.ts +4 -0
  504. package/src/components/settings-row.tsx +126 -0
  505. package/src/components/skeleton.tsx +48 -0
  506. package/src/components/social-connections.tsx +55 -0
  507. package/src/components/stat-card.tsx +64 -0
  508. package/src/components/tag-chip.tsx +78 -0
  509. package/src/components/tag-multi-select.tsx +136 -0
  510. package/src/components/text.tsx +77 -0
  511. package/src/components/toast/ToastPortal.tsx +16 -0
  512. package/src/components/toast/ToastPortal.web.tsx +47 -0
  513. package/src/components/toast/index.ts +2 -0
  514. package/src/components/toast/toast-config.tsx +31 -0
  515. package/src/components/tooltip.tsx +30 -0
  516. package/src/components/totp-display.tsx +207 -0
  517. package/src/components/user-avatar.tsx +46 -0
  518. package/src/context/AppConfigContext.tsx +76 -0
  519. package/src/context/NavigationContext.tsx +75 -0
  520. package/src/context/NotificationContext.tsx +69 -0
  521. package/src/context/ThemeContext.tsx +83 -0
  522. package/src/context/__tests__/NavigationContext.test.tsx +53 -0
  523. package/src/context/index.ts +24 -0
  524. package/src/flags.ts +44 -0
  525. package/src/forms/form-error.tsx +20 -0
  526. package/src/forms/index.ts +2 -0
  527. package/src/forms/shake-field.tsx +52 -0
  528. package/src/hooks/__tests__/useUploadBlobUrl.test.tsx +77 -0
  529. package/src/hooks/index.ts +18 -0
  530. package/src/hooks/useAuth.tsx +567 -0
  531. package/src/hooks/useAutoLock.ts +110 -0
  532. package/src/hooks/useDebouncedValue.ts +32 -0
  533. package/src/hooks/useShakeAnimation.ts +42 -0
  534. package/src/hooks/useUploadBlobUrl.ts +75 -0
  535. package/src/index.ts +48 -0
  536. package/src/screens/auth/AccountPickerScreen.tsx +103 -0
  537. package/src/screens/auth/AuthScreen.tsx +941 -0
  538. package/src/screens/auth/ConsentScreen.tsx +118 -0
  539. package/src/screens/auth/ErrorScreen.tsx +96 -0
  540. package/src/screens/auth/ExpiredLinkScreen.tsx +87 -0
  541. package/src/screens/auth/ForgotPasswordScreen.tsx +182 -0
  542. package/src/screens/auth/ImportDataScreen.tsx +112 -0
  543. package/src/screens/auth/LoginScreen.tsx +484 -0
  544. package/src/screens/auth/MfaChallengeScreen.tsx +216 -0
  545. package/src/screens/auth/MfaEnrollScreen.tsx +388 -0
  546. package/src/screens/auth/PasskeyEnrollScreen.tsx +96 -0
  547. package/src/screens/auth/PrivacyPolicyScreen.tsx +249 -0
  548. package/src/screens/auth/RegisterScreen.tsx +430 -0
  549. package/src/screens/auth/ResetPasswordScreen.tsx +377 -0
  550. package/src/screens/auth/ServerConfigScreen.tsx +388 -0
  551. package/src/screens/auth/TermsOfServiceScreen.tsx +236 -0
  552. package/src/screens/auth/VerifyEmailScreen.tsx +310 -0
  553. package/src/screens/auth/WelcomeScreen.tsx +78 -0
  554. package/src/screens/auth/index.ts +36 -0
  555. package/src/screens/index.ts +1 -0
  556. package/src/services/__tests__/api-config.test.ts +67 -0
  557. package/src/services/__tests__/favicon.test.ts +88 -0
  558. package/src/services/__tests__/hibp-cache.test.ts +37 -0
  559. package/src/services/__tests__/hibp.test.ts +39 -0
  560. package/src/services/api-config.ts +260 -0
  561. package/src/services/auth.service.ts +498 -0
  562. package/src/services/favicon.ts +102 -0
  563. package/src/services/hibp-cache.ts +71 -0
  564. package/src/services/hibp.ts +120 -0
  565. package/src/services/index.ts +52 -0
  566. package/src/services/passkey.ts +213 -0
  567. package/src/types/external.d.ts +24 -0
  568. package/src/utils/__tests__/clipboard.test.ts +59 -0
  569. package/src/utils/__tests__/cn.test.ts +24 -0
  570. package/src/utils/__tests__/debounce.test.ts +42 -0
  571. package/src/utils/__tests__/error.test.ts +18 -0
  572. package/src/utils/__tests__/http-error.test.ts +33 -0
  573. package/src/utils/__tests__/palette.test.ts +22 -0
  574. package/src/utils/__tests__/password.test.ts +55 -0
  575. package/src/utils/__tests__/settings-storage.test.ts +15 -0
  576. package/src/utils/__tests__/validation.test.ts +52 -0
  577. package/src/utils/api-fetch.ts +61 -0
  578. package/src/utils/clipboard.ts +69 -0
  579. package/src/utils/cn.ts +12 -0
  580. package/src/utils/debounce.ts +33 -0
  581. package/src/utils/error.ts +10 -0
  582. package/src/utils/fetch.ts +33 -0
  583. package/src/utils/http-error.ts +81 -0
  584. package/src/utils/index.ts +23 -0
  585. package/src/utils/palette.ts +105 -0
  586. package/src/utils/password.ts +64 -0
  587. package/src/utils/settings-storage.ts +68 -0
  588. package/src/utils/validation.ts +71 -0
  589. package/styles/globals.css +262 -0
  590. package/tailwind-preset.js +163 -0
@@ -0,0 +1,223 @@
1
+ import React from "react";
2
+ import { FlatList, Pressable, View, type ListRenderItem, type ViewStyle } from "react-native";
3
+ import { cn } from "../utils/cn";
4
+ import { MutedText, Text } from "./text";
5
+ import { Skeleton } from "./skeleton";
6
+
7
+ export interface DataTableColumn<T> {
8
+ /** Stable identifier for the column; also the default cell value source. */
9
+ key: string;
10
+ /** Header label. */
11
+ header: string;
12
+ /** Fixed width (px). Mutually exclusive with `flex`; `width` wins if both set. */
13
+ width?: number;
14
+ /** Flex grow factor when no fixed `width` is given. Defaults to 1. */
15
+ flex?: number;
16
+ /** Custom cell renderer. Receives the whole row. */
17
+ render?: (row: T) => React.ReactNode;
18
+ /** Cell + header horizontal alignment. Defaults to "left". */
19
+ align?: "left" | "center" | "right";
20
+ }
21
+
22
+ export interface DataTableProps<T> {
23
+ columns: DataTableColumn<T>[];
24
+ data: T[];
25
+ /** Stable key for each row — required by the underlying list. */
26
+ keyExtractor: (row: T, index: number) => string;
27
+ /** Row press handler; when set, rows become pressable. */
28
+ onRowPress?: (row: T) => void;
29
+ /** Replaces the body with skeleton rows while true. */
30
+ loading?: boolean;
31
+ /** Number of skeleton rows to show while loading. Defaults to 5. */
32
+ loadingRowCount?: number;
33
+ /** Rendered when `data` is empty and not loading. */
34
+ emptyState?: React.ReactNode;
35
+ /** Fallback empty message when `emptyState` is not provided. */
36
+ emptyMessage?: string;
37
+ className?: string;
38
+ }
39
+
40
+ const alignClass = {
41
+ left: "text-left",
42
+ center: "text-center",
43
+ right: "text-right",
44
+ } as const;
45
+
46
+ const alignItems: Record<
47
+ NonNullable<DataTableColumn<unknown>["align"]>,
48
+ ViewStyle["alignItems"]
49
+ > = {
50
+ left: "flex-start",
51
+ center: "center",
52
+ right: "flex-end",
53
+ };
54
+
55
+ /**
56
+ * Resolve a column's layout style. `width` (fixed px) takes precedence over
57
+ * `flex` (grow factor, default 1). Kept pure so layout rules are unit-testable
58
+ * without a render host.
59
+ */
60
+ export function resolveColumnStyle<T>(column: DataTableColumn<T>): ViewStyle {
61
+ const align = column.align ?? "left";
62
+ const base: ViewStyle = {
63
+ paddingHorizontal: 12,
64
+ paddingVertical: 12,
65
+ justifyContent: "center",
66
+ alignItems: alignItems[align],
67
+ };
68
+ if (typeof column.width === "number") {
69
+ return { ...base, width: column.width };
70
+ }
71
+ return { ...base, flex: column.flex ?? 1 };
72
+ }
73
+
74
+ /** Which body the table renders: skeleton rows, an empty placeholder, or data. */
75
+ export type DataTableBodyMode = "loading" | "empty" | "data";
76
+
77
+ /**
78
+ * Decide the body mode from the loading flag + row count. Loading wins over
79
+ * everything; an empty data set (when not loading) shows the empty state.
80
+ * Pure so the branch logic is unit-testable without a render host.
81
+ */
82
+ export function dataTableBodyMode(loading: boolean, rowCount: number): DataTableBodyMode {
83
+ if (loading) {
84
+ return "loading";
85
+ }
86
+ return rowCount === 0 ? "empty" : "data";
87
+ }
88
+
89
+ function HeaderRowInner<T>({ columns }: { columns: DataTableColumn<T>[] }) {
90
+ return (
91
+ <View
92
+ className={cn(
93
+ "flex-row border-b border-border bg-card dark:border-neutral-800 dark:bg-neutral-900/50"
94
+ )}
95
+ accessibilityRole="header"
96
+ >
97
+ {columns.map((column) => (
98
+ <View key={column.key} style={resolveColumnStyle(column)}>
99
+ <Text
100
+ className={cn(
101
+ "text-xs font-semibold uppercase tracking-wider text-muted-foreground",
102
+ alignClass[column.align ?? "left"]
103
+ )}
104
+ numberOfLines={1}
105
+ >
106
+ {column.header}
107
+ </Text>
108
+ </View>
109
+ ))}
110
+ </View>
111
+ );
112
+ }
113
+
114
+ // Memoized so an unchanged row (stable `columns` + `onPress` identity) skips
115
+ // re-rendering when the FlatList recycles. `React.memo` on a generic function
116
+ // erases the type parameter, so we cast back to a generic-preserving signature.
117
+ const HeaderRow = React.memo(HeaderRowInner) as typeof HeaderRowInner;
118
+
119
+ function DataRowInner<T>({
120
+ row,
121
+ columns,
122
+ onPress,
123
+ }: {
124
+ row: T;
125
+ columns: DataTableColumn<T>[];
126
+ onPress?: (row: T) => void;
127
+ }) {
128
+ const cells = (
129
+ <>
130
+ {columns.map((column) => (
131
+ <View key={column.key} style={resolveColumnStyle(column)}>
132
+ {column.render ? (
133
+ column.render(row)
134
+ ) : (
135
+ <Text className={cn("text-sm text-foreground", alignClass[column.align ?? "left"])}>
136
+ {String((row as Record<string, unknown>)[column.key] ?? "")}
137
+ </Text>
138
+ )}
139
+ </View>
140
+ ))}
141
+ </>
142
+ );
143
+
144
+ if (onPress) {
145
+ return (
146
+ <Pressable
147
+ className="flex-row border-b border-border active:bg-accent dark:border-neutral-800"
148
+ onPress={() => onPress(row)}
149
+ accessibilityRole="button"
150
+ >
151
+ {cells}
152
+ </Pressable>
153
+ );
154
+ }
155
+
156
+ return <View className="flex-row border-b border-border dark:border-neutral-800">{cells}</View>;
157
+ }
158
+
159
+ const DataRow = React.memo(DataRowInner) as typeof DataRowInner;
160
+
161
+ /**
162
+ * Generic, typed data table for admin dashboards. Renders a sticky-ish header
163
+ * row above a virtualised `FlatList` body (react-native's built-in list — the
164
+ * package does not depend on `@shopify/flash-list`, and adding a runtime dep is
165
+ * out of scope). Works on RN web (the primary admin target) and native.
166
+ *
167
+ * Loading shows skeleton rows; an empty data set shows `emptyState` (or
168
+ * `emptyMessage`). Columns size by fixed `width` or `flex`, support custom
169
+ * `render`, and alignment.
170
+ */
171
+ export function DataTable<T>({
172
+ columns,
173
+ data,
174
+ keyExtractor,
175
+ onRowPress,
176
+ loading = false,
177
+ loadingRowCount = 5,
178
+ emptyState,
179
+ emptyMessage = "No data",
180
+ className,
181
+ }: DataTableProps<T>) {
182
+ const mode = dataTableBodyMode(loading, data.length);
183
+ // Stable `renderItem` identity (only re-created when `columns`/`onRowPress`
184
+ // change) so the memoized `DataRow` can recycle. `extraData` tells FlatList
185
+ // to re-render rows when either of those closed-over deps change.
186
+ const renderItem = React.useCallback<ListRenderItem<T>>(
187
+ ({ item }) => <DataRow row={item} columns={columns} onPress={onRowPress} />,
188
+ [columns, onRowPress]
189
+ );
190
+ return (
191
+ <View
192
+ className={cn(
193
+ "overflow-hidden rounded-lg border border-border dark:border-neutral-800",
194
+ className
195
+ )}
196
+ >
197
+ <HeaderRow columns={columns} />
198
+ {mode === "loading" ? (
199
+ <View>
200
+ {Array.from({ length: loadingRowCount }).map((_, i) => (
201
+ <View
202
+ key={i}
203
+ className="flex-row items-center border-b border-border px-3 py-3 dark:border-neutral-800"
204
+ >
205
+ <Skeleton className="h-4 flex-1" />
206
+ </View>
207
+ ))}
208
+ </View>
209
+ ) : mode === "empty" ? (
210
+ <View className="items-center justify-center px-4 py-12">
211
+ {emptyState ?? <MutedText className="text-sm">{emptyMessage}</MutedText>}
212
+ </View>
213
+ ) : (
214
+ <FlatList
215
+ data={data}
216
+ keyExtractor={keyExtractor}
217
+ renderItem={renderItem}
218
+ extraData={[columns, onRowPress]}
219
+ />
220
+ )}
221
+ </View>
222
+ );
223
+ }
@@ -0,0 +1,376 @@
1
+ import React, { useRef, useState } from "react";
2
+ import {
3
+ type LayoutChangeEvent,
4
+ PanResponder,
5
+ Platform,
6
+ Pressable,
7
+ Text as RNText,
8
+ type TextStyle,
9
+ useWindowDimensions,
10
+ View,
11
+ type ViewStyle,
12
+ } from "react-native";
13
+ import * as DialogPrimitive from "@rn-primitives/dialog";
14
+ import { Ionicons } from "@expo/vector-icons";
15
+ import { useColorScheme } from "nativewind";
16
+ import { cn } from "../utils/cn";
17
+
18
+ // ── Main Dialog ──
19
+
20
+ interface DialogProps {
21
+ visible: boolean;
22
+ onClose: () => void;
23
+ title?: string;
24
+ children: React.ReactNode;
25
+ showCloseButton?: boolean;
26
+ draggable?: boolean;
27
+ maxWidth?: number;
28
+ /**
29
+ * When false, clicking the backdrop does NOT dismiss the dialog —
30
+ * required for any dialog that holds an unrecoverable input
31
+ * (master password, step-up confirmation, half-typed import). An
32
+ * accidental drag-then-click on the backdrop otherwise loses the
33
+ * user's typing with no undo. Defaults to true to keep the simple
34
+ * "click outside to cancel" pattern for non-input dialogs.
35
+ */
36
+ dismissOnBackdrop?: boolean;
37
+ }
38
+
39
+ export function Dialog({
40
+ visible,
41
+ onClose,
42
+ title,
43
+ children,
44
+ showCloseButton = true,
45
+ draggable = false,
46
+ maxWidth = 400,
47
+ dismissOnBackdrop = true,
48
+ }: DialogProps) {
49
+ const { colorScheme } = useColorScheme();
50
+ const isDark = colorScheme === "dark";
51
+ const { width: winW, height: winH } = useWindowDimensions();
52
+
53
+ // Drag state
54
+ const [offset, setOffset] = useState({ x: 0, y: 0 });
55
+ const offsetRef = useRef({ x: 0, y: 0 });
56
+ const dragStart = useRef({ x: 0, y: 0 });
57
+
58
+ // Reset position when the dialog opens.
59
+ const prevVisible = useRef(visible);
60
+ if (visible && !prevVisible.current) {
61
+ offsetRef.current = { x: 0, y: 0 };
62
+ setOffset({ x: 0, y: 0 });
63
+ }
64
+ prevVisible.current = visible;
65
+
66
+ const winWRef = useRef(winW);
67
+ const winHRef = useRef(winH);
68
+ const maxWidthRef = useRef(maxWidth);
69
+ winWRef.current = winW;
70
+ winHRef.current = winH;
71
+ maxWidthRef.current = maxWidth;
72
+
73
+ const dialogLayoutRef = useRef({ width: 0, height: 0 });
74
+ // Measured height also lives in state: the centering transform reads it at
75
+ // render time, and a ref alone never re-renders after onLayout — so tall
76
+ // dialogs (e.g. the legal popup) stayed pinned at the fallback height and
77
+ // rendered vertically off-center until a drag forced a re-render.
78
+ const [dialogH, setDialogH] = useState(0);
79
+ const onDialogLayout = React.useCallback((e: LayoutChangeEvent) => {
80
+ const { width, height } = e.nativeEvent.layout;
81
+ dialogLayoutRef.current = { width, height };
82
+ if (height > 0) {
83
+ setDialogH(height);
84
+ }
85
+ }, []);
86
+
87
+ const clampOffset = (raw: { x: number; y: number }) => {
88
+ const w = winWRef.current;
89
+ const h = winHRef.current;
90
+ const dw = dialogLayoutRef.current.width || Math.min(maxWidthRef.current, w * 0.9);
91
+ const dh = dialogLayoutRef.current.height || 200;
92
+ const pad = 0.25;
93
+ const maxX = (w - dw * pad) / 2;
94
+ const maxY = (h - dh * pad) / 2;
95
+ return {
96
+ x: Math.max(-maxX, Math.min(maxX, raw.x)),
97
+ y: Math.max(-maxY, Math.min(maxY, raw.y)),
98
+ };
99
+ };
100
+
101
+ const activePanResponder = useRef(
102
+ PanResponder.create({
103
+ onStartShouldSetPanResponder: (evt) => {
104
+ if (Platform.OS === "web") {
105
+ const webEvent = evt.nativeEvent as unknown as { target?: HTMLElement };
106
+ const target = webEvent.target;
107
+ if (target && typeof target.closest === "function") {
108
+ const interactive = target.closest(
109
+ 'input, textarea, select, button, a, label, [role="button"], [role="textbox"], [contenteditable="true"]'
110
+ );
111
+ if (interactive) {
112
+ return false;
113
+ }
114
+ }
115
+ }
116
+ return true;
117
+ },
118
+ onMoveShouldSetPanResponder: (_, gesture) =>
119
+ Math.abs(gesture.dx) > 8 || Math.abs(gesture.dy) > 8,
120
+ onPanResponderGrant: () => {
121
+ dragStart.current = { x: offsetRef.current.x, y: offsetRef.current.y };
122
+ },
123
+ onPanResponderMove: (_, gesture) => {
124
+ const next = {
125
+ x: dragStart.current.x + gesture.dx,
126
+ y: dragStart.current.y + gesture.dy,
127
+ };
128
+ const clamped = clampOffset(next);
129
+ offsetRef.current = clamped;
130
+ setOffset(clamped);
131
+ },
132
+ })
133
+ ).current;
134
+
135
+ return (
136
+ <DialogPrimitive.Root
137
+ open={visible}
138
+ onOpenChange={(open) => {
139
+ if (!open) {
140
+ onClose();
141
+ }
142
+ }}
143
+ >
144
+ <DialogPrimitive.Portal>
145
+ <DialogPrimitive.Overlay
146
+ className="absolute inset-0 bg-black/50"
147
+ closeOnPress={dismissOnBackdrop}
148
+ />
149
+ <DialogPrimitive.Content
150
+ onLayout={onDialogLayout}
151
+ className={cn(
152
+ "absolute self-center rounded-xl p-5",
153
+ isDark ? "bg-neutral-900" : "bg-white",
154
+ Platform.OS === "web" && "shadow-lg"
155
+ )}
156
+ style={
157
+ {
158
+ width: "90%",
159
+ maxWidth,
160
+ top: "50%",
161
+ left: "50%",
162
+ // Web: center with a CSS percentage translate of the element's
163
+ // OWN size — no height measurement (onLayout doesn't reliably
164
+ // fire for the portaled content, which left tall dialogs like the
165
+ // legal popup pinned to a stale fallback height and off-center).
166
+ // Native: RN has no % translate, so keep the measured math.
167
+ transform:
168
+ Platform.OS === "web"
169
+ ? `translate(calc(-50% + ${offset.x}px), calc(-50% + ${offset.y}px))`
170
+ : [
171
+ { translateX: -(Math.min(maxWidth, winW * 0.9) / 2) + offset.x },
172
+ { translateY: -(dialogH || 100) / 2 + offset.y },
173
+ ],
174
+ } as ViewStyle & Record<string, unknown>
175
+ }
176
+ >
177
+ {/* Radix accessibility: always render a Title. */}
178
+ {title ? null : (
179
+ <DialogPrimitive.Title
180
+ className="absolute h-px w-px overflow-hidden"
181
+ style={{ clip: "rect(0,0,0,0)" } as TextStyle & Record<string, unknown>}
182
+ >
183
+ Dialog
184
+ </DialogPrimitive.Title>
185
+ )}
186
+
187
+ {title || showCloseButton ? (
188
+ <View
189
+ className="mb-4 flex-row items-center justify-between"
190
+ {...(draggable ? activePanResponder.panHandlers : {})}
191
+ {...(draggable && Platform.OS === "web"
192
+ ? {
193
+ draggable: false,
194
+ onDragStart: (e: { preventDefault?: () => void }) => {
195
+ e.preventDefault?.();
196
+ },
197
+ }
198
+ : {})}
199
+ style={
200
+ draggable
201
+ ? ({
202
+ cursor: "grab",
203
+ userSelect: "none",
204
+ WebkitUserDrag: "none",
205
+ userDrag: "none",
206
+ } as unknown as ViewStyle)
207
+ : undefined
208
+ }
209
+ >
210
+ {title ? (
211
+ <DialogPrimitive.Title className="flex-1 text-xl font-bold text-foreground dark:text-white">
212
+ {title}
213
+ </DialogPrimitive.Title>
214
+ ) : null}
215
+ {showCloseButton ? (
216
+ <DialogPrimitive.Close asChild>
217
+ <Pressable
218
+ onPress={onClose}
219
+ hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
220
+ className="rounded p-2 active:opacity-70"
221
+ >
222
+ <Ionicons name="close" size={24} color={isDark ? "#999999" : "#666666"} />
223
+ </Pressable>
224
+ </DialogPrimitive.Close>
225
+ ) : null}
226
+ </View>
227
+ ) : null}
228
+
229
+ {children}
230
+ </DialogPrimitive.Content>
231
+ </DialogPrimitive.Portal>
232
+ </DialogPrimitive.Root>
233
+ );
234
+ }
235
+
236
+ // ── Sub-components ──
237
+
238
+ interface DialogContentProps {
239
+ children: React.ReactNode;
240
+ className?: string;
241
+ }
242
+
243
+ export function DialogContent({ children, className }: DialogContentProps) {
244
+ return <View className={cn("py-2", className)}>{children}</View>;
245
+ }
246
+
247
+ interface DialogFooterProps {
248
+ children: React.ReactNode;
249
+ className?: string;
250
+ style?: ViewStyle;
251
+ }
252
+
253
+ export function DialogFooter({ children, className, style }: DialogFooterProps) {
254
+ return (
255
+ <View className={cn("mt-5 flex-row items-center justify-end gap-3", className)} style={style}>
256
+ {children}
257
+ </View>
258
+ );
259
+ }
260
+
261
+ interface DialogButtonProps {
262
+ onPress: () => void;
263
+ variant?: "primary" | "secondary" | "ghost" | "destructive";
264
+ children: React.ReactNode;
265
+ accessibilityLabel?: string;
266
+ accessibilityRole?: "button" | "link" | "none";
267
+ }
268
+
269
+ export function DialogButton({
270
+ onPress,
271
+ variant = "primary",
272
+ children,
273
+ accessibilityLabel,
274
+ accessibilityRole,
275
+ }: DialogButtonProps) {
276
+ const { colorScheme } = useColorScheme();
277
+ const isDark = colorScheme === "dark";
278
+
279
+ const base = "rounded-lg px-4 py-2.5 active:opacity-70";
280
+
281
+ const variantClass = (() => {
282
+ switch (variant) {
283
+ case "primary":
284
+ return isDark ? "bg-white" : "bg-black";
285
+ case "secondary":
286
+ return cn("border", isDark ? "border-neutral-600" : "border-neutral-300");
287
+ case "ghost":
288
+ return "";
289
+ case "destructive":
290
+ return "bg-destructive";
291
+ }
292
+ })();
293
+
294
+ const textColor = (() => {
295
+ switch (variant) {
296
+ case "primary":
297
+ return isDark ? "#000000" : "#FFFFFF";
298
+ case "secondary":
299
+ case "ghost":
300
+ return isDark ? "#FFFFFF" : "#000000";
301
+ case "destructive":
302
+ return "#FFFFFF";
303
+ }
304
+ })();
305
+
306
+ return (
307
+ <Pressable
308
+ onPress={onPress}
309
+ className={cn(base, variantClass)}
310
+ accessibilityLabel={accessibilityLabel}
311
+ accessibilityRole={accessibilityRole}
312
+ >
313
+ <RNText style={{ color: textColor, fontSize: 14, fontWeight: "600", textAlign: "center" }}>
314
+ {children}
315
+ </RNText>
316
+ </Pressable>
317
+ );
318
+ }
319
+
320
+ // ── ConfirmDialog ──
321
+ //
322
+ // Cross-platform replacement for `window.confirm` / `Alert.alert`. The
323
+ // browser-native variants look out of place in our UI and split web vs.
324
+ // native flows; this gives both platforms one consistent prompt.
325
+
326
+ interface ConfirmDialogProps {
327
+ visible: boolean;
328
+ title: string;
329
+ message?: string;
330
+ /** Confirm-button label. Defaults to "Confirm". */
331
+ confirmLabel?: string;
332
+ /** Cancel-button label. Defaults to "Cancel". */
333
+ cancelLabel?: string;
334
+ /** Render the confirm button in the destructive (red) style. */
335
+ destructive?: boolean;
336
+ /** Hide the cancel button — useful for one-button info prompts. */
337
+ hideCancel?: boolean;
338
+ onConfirm: () => void;
339
+ onCancel: () => void;
340
+ }
341
+
342
+ export function ConfirmDialog({
343
+ visible,
344
+ title,
345
+ message,
346
+ confirmLabel = "Confirm",
347
+ cancelLabel = "Cancel",
348
+ destructive = false,
349
+ hideCancel = false,
350
+ onConfirm,
351
+ onCancel,
352
+ }: ConfirmDialogProps) {
353
+ return (
354
+ <Dialog visible={visible} onClose={onCancel} title={title} maxWidth={400}>
355
+ {message ? (
356
+ <DialogContent>
357
+ <RNText className="text-sm text-foreground dark:text-white">{message}</RNText>
358
+ </DialogContent>
359
+ ) : null}
360
+ <DialogFooter>
361
+ {!hideCancel && (
362
+ <DialogButton variant="secondary" onPress={onCancel} accessibilityLabel={cancelLabel}>
363
+ {cancelLabel}
364
+ </DialogButton>
365
+ )}
366
+ <DialogButton
367
+ variant={destructive ? "destructive" : "primary"}
368
+ onPress={onConfirm}
369
+ accessibilityLabel={confirmLabel}
370
+ >
371
+ {confirmLabel}
372
+ </DialogButton>
373
+ </DialogFooter>
374
+ </Dialog>
375
+ );
376
+ }