@passui/react 0.1.17 → 0.1.20

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 (408) hide show
  1. package/README.md +140 -1
  2. package/THIRD_PARTY_NOTICES.md +25 -0
  3. package/dist/types/src/appearance/index.d.ts +2 -0
  4. package/dist/types/src/appearance/platform.d.ts +9 -0
  5. package/dist/types/src/appearance/theme.d.ts +71 -0
  6. package/dist/types/src/components/accordion/accordion.d.ts +19 -0
  7. package/dist/types/src/components/ambient/ambient.d.ts +12 -0
  8. package/dist/types/src/components/ambient/index.d.ts +1 -0
  9. package/dist/types/src/components/aspect-ratio/aspect-ratio.d.ts +7 -0
  10. package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +16 -0
  11. package/dist/types/src/components/button/button-styles.d.ts +12 -0
  12. package/dist/types/src/components/button/button.d.ts +32 -0
  13. package/dist/types/src/components/button/icon-button.d.ts +10 -0
  14. package/dist/types/src/components/button/pending-spinner.d.ts +5 -0
  15. package/dist/types/src/components/button/use-pending-indicator.d.ts +2 -0
  16. package/dist/types/src/components/chip/chip.d.ts +7 -0
  17. package/dist/types/src/components/code-block/code-block.d.ts +10 -0
  18. package/dist/types/src/components/combo-box/combo-box.d.ts +9 -0
  19. package/dist/types/src/components/command/command.d.ts +18 -0
  20. package/dist/types/src/components/compact-date-picker/calendar-month.d.ts +25 -0
  21. package/dist/types/src/components/compact-date-picker/compact-date-picker.d.ts +27 -0
  22. package/dist/types/src/components/compact-time-picker/compact-time-picker.d.ts +26 -0
  23. package/dist/types/src/components/compact-time-picker/time-value.d.ts +18 -0
  24. package/dist/types/src/components/content-unavailable-view/content-unavailable-view.d.ts +9 -0
  25. package/dist/types/src/components/content-view/content-view.d.ts +48 -0
  26. package/dist/types/src/components/content-view/index.d.ts +1 -0
  27. package/dist/types/src/components/context-menu/conceal-menu-source.d.ts +2 -0
  28. package/dist/types/src/components/context-menu/context-menu-host.d.ts +2 -0
  29. package/dist/types/src/components/context-menu/context-menu-store.d.ts +57 -0
  30. package/dist/types/src/components/context-menu/menu-navigation.d.ts +6 -0
  31. package/dist/types/src/components/context-menu/menu-position.d.ts +21 -0
  32. package/dist/types/src/components/context-menu/menu-reveal.d.ts +17 -0
  33. package/dist/types/src/components/context-menu/use-context-menu.d.ts +14 -0
  34. package/dist/types/src/components/conversation/conversation.d.ts +43 -0
  35. package/dist/types/src/components/conversation/index.d.ts +1 -0
  36. package/dist/types/src/components/date-picker/date-picker.d.ts +16 -0
  37. package/dist/types/src/components/date-picker/date-value.d.ts +2 -0
  38. package/dist/types/src/components/dialog/dialog-host.d.ts +6 -0
  39. package/dist/types/src/components/dialog/dialog-store.d.ts +23 -0
  40. package/dist/types/src/components/dialog/dialog.d.ts +30 -0
  41. package/dist/types/src/components/dialog/index.d.ts +1 -0
  42. package/dist/types/src/components/disclosure-group/disclosure-group.d.ts +15 -0
  43. package/dist/types/src/components/divider/divider.d.ts +9 -0
  44. package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +15 -0
  45. package/dist/types/src/components/keyboard-shortcut/keyboard-shortcut.d.ts +5 -0
  46. package/dist/types/src/components/labeled-content/labeled-content.d.ts +19 -0
  47. package/dist/types/src/components/locale-switcher/index.d.ts +1 -0
  48. package/dist/types/src/components/locale-switcher/locale-switcher.d.ts +10 -0
  49. package/dist/types/src/components/menu/menu.d.ts +16 -0
  50. package/dist/types/src/components/one-time-code/one-time-code.d.ts +18 -0
  51. package/dist/types/src/components/picker/picker-trigger.d.ts +2 -0
  52. package/dist/types/src/components/picker/picker.d.ts +43 -0
  53. package/dist/types/src/components/popover/anchored-surface.d.ts +13 -0
  54. package/dist/types/src/components/popover/popover-fit.d.ts +21 -0
  55. package/dist/types/src/components/popover/popover.d.ts +18 -0
  56. package/dist/types/src/components/profile-image/profile-image.d.ts +4 -0
  57. package/dist/types/src/components/progress-view/progress-view.d.ts +13 -0
  58. package/dist/types/src/components/progress-view/use-liquid-progress.d.ts +3 -0
  59. package/dist/types/src/components/radio-group/radio-group.d.ts +17 -0
  60. package/dist/types/src/components/search-field/expanding-search-field.d.ts +5 -0
  61. package/dist/types/src/components/search-field/search-field.d.ts +6 -0
  62. package/dist/types/src/components/search-list/search-list.d.ts +19 -0
  63. package/dist/types/src/components/section/section.d.ts +15 -0
  64. package/dist/types/src/components/secure-field/secure-field.d.ts +4 -0
  65. package/dist/types/src/components/segmented-control/segmented-control.d.ts +20 -0
  66. package/dist/types/src/components/sheet/modal-environment.d.ts +2 -0
  67. package/dist/types/src/components/sheet/sheet-portal.d.ts +5 -0
  68. package/dist/types/src/components/sheet/sheet.d.ts +21 -0
  69. package/dist/types/src/components/sheet/use-sheet-focus.d.ts +3 -0
  70. package/dist/types/src/components/sidebar/sidebar.d.ts +19 -0
  71. package/dist/types/src/components/skeleton/skeleton.d.ts +6 -0
  72. package/dist/types/src/components/slider/slider.d.ts +14 -0
  73. package/dist/types/src/components/slosh-slider/liquid-step.d.ts +6 -0
  74. package/dist/types/src/components/slosh-slider/slosh-slider.d.ts +18 -0
  75. package/dist/types/src/components/stat-tile/index.d.ts +1 -0
  76. package/dist/types/src/components/stat-tile/stat-tile.d.ts +15 -0
  77. package/dist/types/src/components/status-indicator/status-indicator.d.ts +14 -0
  78. package/dist/types/src/components/stepper/rolling-digit.d.ts +11 -0
  79. package/dist/types/src/components/stepper/stepper.d.ts +19 -0
  80. package/dist/types/src/components/swatch-picker/swatch-picker.d.ts +17 -0
  81. package/dist/types/src/components/tab-bar/tab-bar.d.ts +40 -0
  82. package/dist/types/src/components/tab-view/tab-view.d.ts +24 -0
  83. package/dist/types/src/components/table/table.d.ts +10 -0
  84. package/dist/types/src/components/text-editor/text-editor.d.ts +2 -0
  85. package/dist/types/src/components/text-field/text-field.d.ts +20 -0
  86. package/dist/types/src/components/toast/toast-store.d.ts +47 -0
  87. package/dist/types/src/components/toast/toaster.d.ts +10 -0
  88. package/dist/types/src/components/toggle/toggle.d.ts +17 -0
  89. package/dist/types/src/components/tooltip/tooltip-host.d.ts +6 -0
  90. package/dist/types/src/components/view-controller/index.d.ts +1 -0
  91. package/dist/types/src/components/view-controller/view-controller.d.ts +21 -0
  92. package/dist/types/src/components/wake-slider/wake-slider.d.ts +12 -0
  93. package/dist/types/src/hooks/scroll/minimize-decision.d.ts +2 -0
  94. package/dist/types/src/hooks/use-anchored-disclosure.d.ts +9 -0
  95. package/dist/types/src/hooks/use-anchored-placement.d.ts +7 -0
  96. package/dist/types/src/hooks/use-dismiss.d.ts +9 -0
  97. package/dist/types/src/hooks/use-floating-dismiss.d.ts +3 -0
  98. package/dist/types/src/hooks/use-media-query.d.ts +21 -0
  99. package/dist/types/src/hooks/use-minimize-on-scroll.d.ts +6 -0
  100. package/dist/types/src/hooks/use-visual-viewport.d.ts +4 -0
  101. package/dist/types/src/hooks/viewport/types.d.ts +19 -0
  102. package/dist/types/src/hooks/viewport/viewport-metrics.d.ts +3 -0
  103. package/dist/types/src/icons/icons.d.ts +612 -0
  104. package/dist/types/src/index.d.ts +71 -0
  105. package/dist/types/src/lib/anchored-placement.d.ts +39 -0
  106. package/dist/types/src/lib/class-names.d.ts +2 -0
  107. package/dist/types/src/lib/clipboard.d.ts +6 -0
  108. package/dist/types/src/lib/date.d.ts +50 -0
  109. package/dist/types/src/lib/floating-layers.d.ts +8 -0
  110. package/dist/types/src/lib/haptics.d.ts +8 -0
  111. package/dist/types/src/lib/language.d.ts +4 -0
  112. package/dist/types/src/lib/range-value.d.ts +4 -0
  113. package/dist/types/src/lib/screen-edges.d.ts +12 -0
  114. package/dist/types/src/lib/shapes.d.ts +17 -0
  115. package/dist/types/src/motion/transitions.d.ts +97 -0
  116. package/dist/types/src/tokens.d.ts +1 -0
  117. package/package.json +248 -40
  118. package/src/appearance/platform.tsx +10 -47
  119. package/src/appearance/theme.tsx +42 -142
  120. package/src/components/accordion/accordion.tsx +48 -0
  121. package/src/components/ambient/ambient.tsx +2 -10
  122. package/src/components/aspect-ratio/aspect-ratio.tsx +22 -0
  123. package/src/components/breadcrumb/breadcrumb.tsx +53 -0
  124. package/src/components/button/button-styles.ts +13 -31
  125. package/src/components/button/button.tsx +24 -15
  126. package/src/components/button/icon-button.tsx +14 -56
  127. package/src/components/button/use-pending-indicator.ts +2 -7
  128. package/src/components/chip/chip.tsx +8 -13
  129. package/src/components/code-block/code-block.tsx +41 -0
  130. package/src/components/combo-box/combo-box.tsx +13 -0
  131. package/src/components/command/command.tsx +119 -0
  132. package/src/components/compact-date-picker/calendar-month.ts +13 -57
  133. package/src/components/compact-date-picker/compact-date-picker.tsx +72 -195
  134. package/src/components/compact-time-picker/compact-time-picker.tsx +74 -195
  135. package/src/components/compact-time-picker/time-value.ts +4 -16
  136. package/src/components/content-unavailable-view/content-unavailable-view.tsx +37 -0
  137. package/src/components/content-view/content-view.tsx +13 -56
  138. package/src/components/context-menu/conceal-menu-source.ts +1 -4
  139. package/src/components/context-menu/context-menu-host.tsx +132 -198
  140. package/src/components/context-menu/context-menu-store.ts +5 -14
  141. package/src/components/context-menu/menu-navigation.ts +29 -0
  142. package/src/components/context-menu/menu-position.ts +6 -27
  143. package/src/components/context-menu/menu-reveal.ts +14 -52
  144. package/src/components/context-menu/use-context-menu.ts +6 -30
  145. package/src/components/conversation/conversation.tsx +26 -72
  146. package/src/components/date-picker/date-picker.tsx +8 -37
  147. package/src/components/date-picker/date-value.ts +5 -30
  148. package/src/components/dialog/dialog-host.tsx +37 -76
  149. package/src/components/dialog/dialog-store.ts +16 -7
  150. package/src/components/dialog/dialog.tsx +23 -80
  151. package/src/components/disclosure-group/disclosure-group.tsx +79 -0
  152. package/src/components/divider/divider.tsx +31 -0
  153. package/src/components/inline-disclosure/inline-disclosure.tsx +49 -0
  154. package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +17 -0
  155. package/src/components/labeled-content/labeled-content.tsx +24 -18
  156. package/src/components/locale-switcher/locale-switcher.tsx +20 -70
  157. package/src/components/menu/menu.tsx +68 -0
  158. package/src/components/one-time-code/one-time-code.tsx +168 -0
  159. package/src/components/picker/picker-trigger.ts +10 -0
  160. package/src/components/picker/picker.tsx +165 -283
  161. package/src/components/popover/anchored-surface.tsx +223 -0
  162. package/src/components/popover/popover-fit.ts +69 -0
  163. package/src/components/popover/popover.tsx +209 -63
  164. package/src/components/profile-image/profile-image.tsx +6 -28
  165. package/src/components/progress-view/progress-view.tsx +70 -0
  166. package/src/components/progress-view/use-liquid-progress.ts +43 -0
  167. package/src/components/radio-group/radio-group.tsx +63 -0
  168. package/src/components/search-field/expanding-search-field.tsx +183 -0
  169. package/src/components/search-field/search-field.tsx +25 -7
  170. package/src/components/search-list/search-list.tsx +58 -75
  171. package/src/components/section/section.tsx +18 -17
  172. package/src/components/secure-field/secure-field.tsx +5 -7
  173. package/src/components/segmented-control/segmented-control.tsx +20 -29
  174. package/src/components/sheet/modal-environment.ts +16 -54
  175. package/src/components/sheet/sheet-portal.tsx +10 -42
  176. package/src/components/sheet/sheet.tsx +17 -25
  177. package/src/components/sheet/use-sheet-focus.ts +15 -51
  178. package/src/components/sidebar/sidebar.tsx +44 -38
  179. package/src/components/skeleton/skeleton.tsx +19 -0
  180. package/src/components/slider/slider.tsx +76 -0
  181. package/src/components/slosh-slider/liquid-step.ts +42 -0
  182. package/src/components/slosh-slider/slosh-slider.tsx +251 -0
  183. package/src/components/stat-tile/stat-tile.tsx +9 -30
  184. package/src/components/status-indicator/status-indicator.tsx +12 -19
  185. package/src/components/stepper/rolling-digit.tsx +77 -0
  186. package/src/components/stepper/stepper.tsx +101 -0
  187. package/src/components/swatch-picker/swatch-picker.tsx +19 -15
  188. package/src/components/tab-bar/tab-bar.tsx +59 -59
  189. package/src/components/tab-view/tab-view.tsx +52 -57
  190. package/src/components/table/table.tsx +14 -62
  191. package/src/components/text-editor/text-editor.tsx +2 -12
  192. package/src/components/text-field/text-field.tsx +53 -44
  193. package/src/components/toast/toast-store.ts +9 -30
  194. package/src/components/toast/toaster.tsx +90 -104
  195. package/src/components/toggle/toggle.tsx +165 -44
  196. package/src/components/tooltip/tooltip-host.tsx +70 -67
  197. package/src/components/view-controller/view-controller.tsx +4 -21
  198. package/src/components/wake-slider/wake-slider.tsx +237 -0
  199. package/src/hooks/scroll/minimize-decision.ts +1 -5
  200. package/src/hooks/use-anchored-disclosure.ts +23 -0
  201. package/src/hooks/use-anchored-placement.ts +7 -14
  202. package/src/hooks/use-dismiss.ts +5 -23
  203. package/src/hooks/use-floating-dismiss.ts +28 -0
  204. package/src/hooks/use-media-query.ts +12 -31
  205. package/src/hooks/use-minimize-on-scroll.ts +10 -34
  206. package/src/hooks/use-visual-viewport.ts +5 -14
  207. package/src/hooks/viewport/viewport-metrics.ts +4 -16
  208. package/src/icons/icons.tsx +229 -183
  209. package/src/index.ts +27 -1
  210. package/src/lib/anchored-placement.ts +12 -18
  211. package/src/lib/class-names.ts +1 -2
  212. package/src/lib/clipboard.ts +14 -0
  213. package/src/lib/date.ts +4 -15
  214. package/src/lib/floating-layers.ts +18 -8
  215. package/src/lib/haptics.ts +1 -3
  216. package/src/lib/language.ts +2 -7
  217. package/src/lib/range-value.ts +32 -0
  218. package/src/lib/screen-edges.ts +2 -6
  219. package/src/lib/shapes.ts +5 -20
  220. package/src/motion/transitions.ts +18 -12
  221. package/src/styles/code-block.css +33 -0
  222. package/src/styles/motion-controls.css +189 -0
  223. package/src/styles/styles.css +3 -0
  224. package/src/components/ambient/ambient.docs.tsx +0 -68
  225. package/src/components/ambient/examples/ambient-images.tsx +0 -30
  226. package/src/components/ambient/examples/ambient-now-playing.tsx +0 -55
  227. package/src/components/ambient/examples/ambient-showcase.tsx +0 -33
  228. package/src/components/ambient/examples/ambient-usage.tsx +0 -19
  229. package/src/components/button/button.docs.tsx +0 -116
  230. package/src/components/button/examples/button-disabled.tsx +0 -19
  231. package/src/components/button/examples/button-icon.tsx +0 -39
  232. package/src/components/button/examples/button-link.tsx +0 -12
  233. package/src/components/button/examples/button-pending.tsx +0 -27
  234. package/src/components/button/examples/button-showcase.tsx +0 -115
  235. package/src/components/button/examples/button-sizes.tsx +0 -12
  236. package/src/components/button/examples/button-usage.tsx +0 -12
  237. package/src/components/button/examples/button-variants.tsx +0 -15
  238. package/src/components/button/examples/button-with-icon.tsx +0 -32
  239. package/src/components/button/examples/icon-button-player.tsx +0 -38
  240. package/src/components/button/examples/icon-button-showcase.tsx +0 -83
  241. package/src/components/button/examples/icon-button-usage.tsx +0 -11
  242. package/src/components/button/examples/icon-button-variants.tsx +0 -39
  243. package/src/components/button/icon-button.docs.tsx +0 -68
  244. package/src/components/chip/chip.docs.tsx +0 -37
  245. package/src/components/chip/examples/chip-in-rows.tsx +0 -19
  246. package/src/components/chip/examples/chip-showcase.tsx +0 -51
  247. package/src/components/chip/examples/chip-tones.tsx +0 -13
  248. package/src/components/chip/examples/chip-usage.tsx +0 -5
  249. package/src/components/compact-date-picker/compact-date-picker.docs.tsx +0 -81
  250. package/src/components/compact-date-picker/examples/compact-date-picker-showcase.tsx +0 -35
  251. package/src/components/compact-date-picker/examples/compact-date-picker-usage.tsx +0 -22
  252. package/src/components/compact-time-picker/compact-time-picker.docs.tsx +0 -80
  253. package/src/components/compact-time-picker/examples/compact-time-picker-showcase.tsx +0 -31
  254. package/src/components/compact-time-picker/examples/compact-time-picker-usage.tsx +0 -21
  255. package/src/components/content-view/content-view.docs.tsx +0 -61
  256. package/src/components/content-view/examples/content-view-header-action.tsx +0 -33
  257. package/src/components/content-view/examples/content-view-showcase.tsx +0 -65
  258. package/src/components/content-view/examples/content-view-stats.tsx +0 -35
  259. package/src/components/content-view/examples/content-view-usage.tsx +0 -14
  260. package/src/components/context-menu/context-menu.docs.tsx +0 -92
  261. package/src/components/context-menu/examples/context-menu-checked.tsx +0 -37
  262. package/src/components/context-menu/examples/context-menu-list.tsx +0 -85
  263. package/src/components/context-menu/examples/context-menu-showcase.tsx +0 -56
  264. package/src/components/context-menu/examples/context-menu-toolbar.tsx +0 -63
  265. package/src/components/context-menu/examples/context-menu-usage.tsx +0 -24
  266. package/src/components/conversation/conversation.docs.tsx +0 -88
  267. package/src/components/conversation/examples/conversation-composer.tsx +0 -36
  268. package/src/components/conversation/examples/conversation-days.tsx +0 -48
  269. package/src/components/conversation/examples/conversation-showcase.tsx +0 -71
  270. package/src/components/conversation/examples/conversation-usage.tsx +0 -28
  271. package/src/components/date-picker/date-picker.docs.tsx +0 -88
  272. package/src/components/date-picker/examples/date-picker-locales.tsx +0 -41
  273. package/src/components/date-picker/examples/date-picker-past-only.tsx +0 -29
  274. package/src/components/date-picker/examples/date-picker-showcase.tsx +0 -49
  275. package/src/components/date-picker/examples/date-picker-usage.tsx +0 -22
  276. package/src/components/dialog/dialog.docs.tsx +0 -114
  277. package/src/components/dialog/examples/dialog-confirm.tsx +0 -24
  278. package/src/components/dialog/examples/dialog-form.tsx +0 -46
  279. package/src/components/dialog/examples/dialog-inline.tsx +0 -48
  280. package/src/components/dialog/examples/dialog-showcase.tsx +0 -53
  281. package/src/components/dialog/examples/dialog-usage.tsx +0 -31
  282. package/src/components/labeled-content/examples/labeled-content-descriptions.tsx +0 -37
  283. package/src/components/labeled-content/examples/labeled-content-showcase.tsx +0 -54
  284. package/src/components/labeled-content/examples/labeled-content-usage.tsx +0 -22
  285. package/src/components/labeled-content/examples/labeled-content-values.tsx +0 -26
  286. package/src/components/labeled-content/examples/labeled-content-wide.tsx +0 -46
  287. package/src/components/labeled-content/labeled-content.docs.tsx +0 -69
  288. package/src/components/locale-switcher/examples/locale-switcher-native-names.tsx +0 -30
  289. package/src/components/locale-switcher/examples/locale-switcher-showcase.tsx +0 -46
  290. package/src/components/locale-switcher/examples/locale-switcher-translated-page.tsx +0 -35
  291. package/src/components/locale-switcher/examples/locale-switcher-usage.tsx +0 -20
  292. package/src/components/locale-switcher/locale-switcher.docs.tsx +0 -91
  293. package/src/components/picker/examples/picker-details.tsx +0 -44
  294. package/src/components/picker/examples/picker-searchable.tsx +0 -48
  295. package/src/components/picker/examples/picker-showcase.tsx +0 -66
  296. package/src/components/picker/examples/picker-usage.tsx +0 -20
  297. package/src/components/picker/picker.docs.tsx +0 -68
  298. package/src/components/popover/examples/popover-help.tsx +0 -57
  299. package/src/components/popover/examples/popover-menu.tsx +0 -51
  300. package/src/components/popover/examples/popover-showcase.tsx +0 -61
  301. package/src/components/popover/examples/popover-usage.tsx +0 -31
  302. package/src/components/popover/popover.docs.tsx +0 -84
  303. package/src/components/profile-image/examples/profile-image-fallbacks.tsx +0 -29
  304. package/src/components/profile-image/examples/profile-image-group.tsx +0 -34
  305. package/src/components/profile-image/examples/profile-image-showcase.tsx +0 -30
  306. package/src/components/profile-image/examples/profile-image-usage.tsx +0 -14
  307. package/src/components/profile-image/profile-image.docs.tsx +0 -81
  308. package/src/components/search-field/examples/search-field-filter.tsx +0 -53
  309. package/src/components/search-field/examples/search-field-showcase.tsx +0 -56
  310. package/src/components/search-field/examples/search-field-toolbar.tsx +0 -27
  311. package/src/components/search-field/examples/search-field-usage.tsx +0 -18
  312. package/src/components/search-field/search-field.docs.tsx +0 -58
  313. package/src/components/search-list/examples/search-list-dropdown.tsx +0 -53
  314. package/src/components/search-list/examples/search-list-groups.tsx +0 -64
  315. package/src/components/search-list/examples/search-list-showcase.tsx +0 -67
  316. package/src/components/search-list/examples/search-list-usage.tsx +0 -27
  317. package/src/components/search-list/search-list.docs.tsx +0 -87
  318. package/src/components/section/examples/section-actions.tsx +0 -35
  319. package/src/components/section/examples/section-groups.tsx +0 -41
  320. package/src/components/section/examples/section-showcase.tsx +0 -63
  321. package/src/components/section/examples/section-usage.tsx +0 -19
  322. package/src/components/section/section.docs.tsx +0 -51
  323. package/src/components/secure-field/examples/secure-field-new-password.tsx +0 -56
  324. package/src/components/secure-field/examples/secure-field-reveal.tsx +0 -20
  325. package/src/components/secure-field/examples/secure-field-showcase.tsx +0 -62
  326. package/src/components/secure-field/examples/secure-field-usage.tsx +0 -19
  327. package/src/components/secure-field/secure-field.docs.tsx +0 -72
  328. package/src/components/segmented-control/examples/segmented-control-overflow.tsx +0 -28
  329. package/src/components/segmented-control/examples/segmented-control-showcase.tsx +0 -59
  330. package/src/components/segmented-control/examples/segmented-control-usage.tsx +0 -20
  331. package/src/components/segmented-control/examples/segmented-control-views.tsx +0 -32
  332. package/src/components/segmented-control/segmented-control.docs.tsx +0 -61
  333. package/src/components/sheet/examples/sheet-choices.tsx +0 -58
  334. package/src/components/sheet/examples/sheet-share.tsx +0 -51
  335. package/src/components/sheet/examples/sheet-showcase.tsx +0 -74
  336. package/src/components/sheet/examples/sheet-usage.tsx +0 -32
  337. package/src/components/sheet/sheet.docs.tsx +0 -75
  338. package/src/components/sidebar/examples/sidebar-groups.tsx +0 -133
  339. package/src/components/sidebar/examples/sidebar-showcase.tsx +0 -123
  340. package/src/components/sidebar/examples/sidebar-unread.tsx +0 -66
  341. package/src/components/sidebar/examples/sidebar-usage.tsx +0 -52
  342. package/src/components/sidebar/sidebar.docs.tsx +0 -135
  343. package/src/components/stat-tile/examples/stat-tile-filters.tsx +0 -32
  344. package/src/components/stat-tile/examples/stat-tile-grid.tsx +0 -21
  345. package/src/components/stat-tile/examples/stat-tile-showcase.tsx +0 -52
  346. package/src/components/stat-tile/examples/stat-tile-usage.tsx +0 -13
  347. package/src/components/stat-tile/stat-tile.docs.tsx +0 -60
  348. package/src/components/status-indicator/examples/status-indicator-live.tsx +0 -34
  349. package/src/components/status-indicator/examples/status-indicator-showcase.tsx +0 -39
  350. package/src/components/status-indicator/examples/status-indicator-states.tsx +0 -17
  351. package/src/components/status-indicator/examples/status-indicator-usage.tsx +0 -9
  352. package/src/components/status-indicator/status-indicator.docs.tsx +0 -43
  353. package/src/components/swatch-picker/examples/swatch-picker-accents.tsx +0 -42
  354. package/src/components/swatch-picker/examples/swatch-picker-labels.tsx +0 -45
  355. package/src/components/swatch-picker/examples/swatch-picker-showcase.tsx +0 -70
  356. package/src/components/swatch-picker/examples/swatch-picker-usage.tsx +0 -39
  357. package/src/components/swatch-picker/swatch-picker.docs.tsx +0 -60
  358. package/src/components/tab-bar/examples/tab-bar-five-tabs.tsx +0 -66
  359. package/src/components/tab-bar/examples/tab-bar-minimize.tsx +0 -71
  360. package/src/components/tab-bar/examples/tab-bar-showcase.tsx +0 -82
  361. package/src/components/tab-bar/examples/tab-bar-usage.tsx +0 -54
  362. package/src/components/tab-bar/tab-bar.docs.tsx +0 -136
  363. package/src/components/tab-view/examples/tab-view-keyboard.tsx +0 -39
  364. package/src/components/tab-view/examples/tab-view-settings.tsx +0 -56
  365. package/src/components/tab-view/examples/tab-view-showcase.tsx +0 -53
  366. package/src/components/tab-view/examples/tab-view-usage.tsx +0 -23
  367. package/src/components/tab-view/tab-view.docs.tsx +0 -77
  368. package/src/components/table/examples/table-empty.tsx +0 -53
  369. package/src/components/table/examples/table-invoices.tsx +0 -102
  370. package/src/components/table/examples/table-selection.tsx +0 -150
  371. package/src/components/table/examples/table-showcase.tsx +0 -115
  372. package/src/components/table/examples/table-usage.tsx +0 -67
  373. package/src/components/table/examples/table-wide.tsx +0 -75
  374. package/src/components/table/table.docs.tsx +0 -131
  375. package/src/components/text-editor/examples/text-editor-comment.tsx +0 -48
  376. package/src/components/text-editor/examples/text-editor-count.tsx +0 -38
  377. package/src/components/text-editor/examples/text-editor-showcase.tsx +0 -58
  378. package/src/components/text-editor/examples/text-editor-usage.tsx +0 -25
  379. package/src/components/text-editor/text-editor.docs.tsx +0 -61
  380. package/src/components/text-field/examples/text-field-icons.tsx +0 -72
  381. package/src/components/text-field/examples/text-field-showcase.tsx +0 -50
  382. package/src/components/text-field/examples/text-field-usage.tsx +0 -18
  383. package/src/components/text-field/examples/text-field-validation.tsx +0 -27
  384. package/src/components/text-field/text-field.docs.tsx +0 -118
  385. package/src/components/toast/examples/toast-action.tsx +0 -32
  386. package/src/components/toast/examples/toast-promise.tsx +0 -45
  387. package/src/components/toast/examples/toast-showcase.tsx +0 -49
  388. package/src/components/toast/examples/toast-usage.tsx +0 -13
  389. package/src/components/toast/toast.docs.tsx +0 -79
  390. package/src/components/toggle/examples/toggle-checkboxes.tsx +0 -43
  391. package/src/components/toggle/examples/toggle-settings.tsx +0 -31
  392. package/src/components/toggle/examples/toggle-showcase.tsx +0 -59
  393. package/src/components/toggle/examples/toggle-usage.tsx +0 -13
  394. package/src/components/toggle/toggle.docs.tsx +0 -69
  395. package/src/components/tooltip/examples/tooltip-icon-buttons.tsx +0 -27
  396. package/src/components/tooltip/examples/tooltip-shortcuts.tsx +0 -38
  397. package/src/components/tooltip/examples/tooltip-showcase.tsx +0 -44
  398. package/src/components/tooltip/examples/tooltip-usage.tsx +0 -13
  399. package/src/components/tooltip/tooltip.docs.tsx +0 -56
  400. package/src/components/view-controller/examples/view-controller-action.tsx +0 -27
  401. package/src/components/view-controller/examples/view-controller-settings.tsx +0 -31
  402. package/src/components/view-controller/examples/view-controller-showcase.tsx +0 -45
  403. package/src/components/view-controller/examples/view-controller-usage.tsx +0 -19
  404. package/src/components/view-controller/view-controller.docs.tsx +0 -71
  405. package/src/docs/index.ts +0 -3
  406. package/src/docs/registry.ts +0 -113
  407. package/src/docs/snippet.ts +0 -17
  408. package/src/docs/types.ts +0 -180
@@ -2,11 +2,7 @@
2
2
  * App-wide toasts: short messages that confirm, report progress or explain
3
3
  * a failure. Call `toast` from anywhere; render one `<Toaster />`.
4
4
  */
5
- export type ToastTone =
6
- | "neutral"
7
- | "success"
8
- | "error"
9
- | "loading"
5
+ export type ToastTone = "neutral" | "success" | "error" | "loading"
10
6
 
11
7
  export interface ToastAction {
12
8
  label: string
@@ -41,11 +37,7 @@ const emit = () => {
41
37
  for (const listener of listeners) listener()
42
38
  }
43
39
 
44
- function show(
45
- tone: ToastTone,
46
- title: string,
47
- options: ToastOptions = {}
48
- ): number {
40
+ function show(tone: ToastTone, title: string, options: ToastOptions = {}): number {
49
41
  const id = nextId++
50
42
  const duration =
51
43
  options.duration !== undefined
@@ -70,14 +62,8 @@ function show(
70
62
  }
71
63
 
72
64
  /** Changes a toast in place; it morphs to its new state. */
73
- function update(
74
- id: number,
75
- tone: ToastTone,
76
- title: string,
77
- options: ToastOptions = {}
78
- ) {
79
- if (!toasts.some((t) => t.id === id))
80
- return show(tone, title, options)
65
+ function update(id: number, tone: ToastTone, title: string, options: ToastOptions = {}) {
66
+ if (!toasts.some((t) => t.id === id)) return show(tone, title, options)
81
67
  toasts = toasts.map((t) =>
82
68
  t.id === id
83
69
  ? {
@@ -112,22 +98,15 @@ interface PromiseMessages<T> {
112
98
  }
113
99
 
114
100
  export const toast = Object.assign(
115
- (title: string, options?: ToastOptions) =>
116
- show("neutral", title, options),
101
+ (title: string, options?: ToastOptions) => show("neutral", title, options),
117
102
  {
118
- success: (title: string, options?: ToastOptions) =>
119
- show("success", title, options),
120
- error: (title: string, options?: ToastOptions) =>
121
- show("error", title, options),
122
- loading: (title: string, options?: ToastOptions) =>
123
- show("loading", title, options),
103
+ success: (title: string, options?: ToastOptions) => show("success", title, options),
104
+ error: (title: string, options?: ToastOptions) => show("error", title, options),
105
+ loading: (title: string, options?: ToastOptions) => show("loading", title, options),
124
106
  update,
125
107
  dismiss,
126
108
  /** Shows progress, then morphs into the outcome. Returns the promise. */
127
- promise<T>(
128
- promise: Promise<T>,
129
- messages: PromiseMessages<T>
130
- ): Promise<T> {
109
+ promise<T>(promise: Promise<T>, messages: PromiseMessages<T>): Promise<T> {
131
110
  const id = show("loading", messages.loading)
132
111
  promise.then(
133
112
  (value) =>
@@ -1,11 +1,5 @@
1
- import { AnimatePresence, motion } from "motion/react"
2
- import {
3
- useEffect,
4
- useLayoutEffect,
5
- useRef,
6
- useState,
7
- useSyncExternalStore,
8
- } from "react"
1
+ import { AnimatePresence, motion, useReducedMotion } from "motion/react"
2
+ import { useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react"
9
3
  import { CheckIcon, CloseIcon } from "../../icons/icons"
10
4
  import { cn } from "../../lib/class-names"
11
5
  import { spring } from "../../motion/transitions"
@@ -19,7 +13,6 @@ import {
19
13
  } from "./toast-store"
20
14
 
21
15
  const PILL = 40
22
- const GOO_FILTER_ID = "nt-toast-goo"
23
16
  const EMPTY: Toast[] = []
24
17
  /** A little loose, so the body overshoots and settles like a drop of honey. */
25
18
  const BODY_SPRING = {
@@ -37,22 +30,22 @@ const BODY_SPRING = {
37
30
  */
38
31
  export function Toaster({
39
32
  className,
33
+ label = "Notifications",
40
34
  }: {
41
35
  className?: string
36
+ label?: string
42
37
  }) {
43
- const toasts = useSyncExternalStore(
44
- subscribeToasts,
45
- getToasts,
46
- () => EMPTY
47
- )
38
+ const toasts = useSyncExternalStore(subscribeToasts, getToasts, () => EMPTY)
39
+ const filterId = useId()
40
+ const reducedMotion = useReducedMotion()
48
41
  const [hovered, setHovered] = useState(false)
49
42
  const visible = toasts.slice(0, toastLimits.visible)
50
43
 
51
44
  return (
52
45
  <>
53
- <GooFilter />
46
+ <GooFilter id={filterId} />
54
47
  <section
55
- aria-label="Notifications"
48
+ aria-label={label}
56
49
  aria-live="polite"
57
50
  className={cn(
58
51
  "pointer-events-none fixed inset-x-0 top-[max(10px,env(safe-area-inset-top))] z-[60] flex justify-center px-3",
@@ -72,6 +65,8 @@ export function Toaster({
72
65
  index={index}
73
66
  expanded={hovered}
74
67
  paused={hovered}
68
+ filterId={filterId}
69
+ reducedMotion={Boolean(reducedMotion)}
75
70
  />
76
71
  ))}
77
72
  </AnimatePresence>
@@ -81,30 +76,19 @@ export function Toaster({
81
76
  )
82
77
  }
83
78
 
84
- function GooFilter() {
79
+ function GooFilter({ id }: { id: string }) {
85
80
  return (
86
- <svg
87
- aria-hidden="true"
88
- className="pointer-events-none absolute size-0"
89
- >
81
+ <svg aria-hidden="true" className="pointer-events-none absolute size-0">
90
82
  <defs>
91
- <filter id={GOO_FILTER_ID}>
92
- <feGaussianBlur
93
- in="SourceGraphic"
94
- stdDeviation="7"
95
- result="blur"
96
- />
83
+ <filter id={id}>
84
+ <feGaussianBlur in="SourceGraphic" stdDeviation="7" result="blur" />
97
85
  <feColorMatrix
98
86
  in="blur"
99
87
  mode="matrix"
100
88
  values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 22 -10"
101
89
  result="goo"
102
90
  />
103
- <feComposite
104
- in="SourceGraphic"
105
- in2="goo"
106
- operator="atop"
107
- />
91
+ <feComposite in="SourceGraphic" in2="goo" operator="atop" />
108
92
  </filter>
109
93
  </defs>
110
94
  </svg>
@@ -116,11 +100,15 @@ function ToastItem({
116
100
  index,
117
101
  expanded,
118
102
  paused,
103
+ filterId,
104
+ reducedMotion,
119
105
  }: {
120
106
  toast: Toast
121
107
  index: number
122
108
  expanded: boolean
123
109
  paused: boolean
110
+ filterId: string
111
+ reducedMotion: boolean
124
112
  }) {
125
113
  const content = useRef<HTMLDivElement>(null)
126
114
  const [size, setSize] = useState({
@@ -131,9 +119,9 @@ function ToastItem({
131
119
 
132
120
  // The body flows out of the pill a beat after it lands.
133
121
  useEffect(() => {
134
- const timer = setTimeout(() => setOpen(true), 140)
122
+ const timer = setTimeout(() => setOpen(true), reducedMotion ? 0 : 140)
135
123
  return () => clearTimeout(timer)
136
- }, [])
124
+ }, [reducedMotion])
137
125
 
138
126
  useLayoutEffect(() => {
139
127
  const element = content.current
@@ -151,10 +139,7 @@ function ToastItem({
151
139
 
152
140
  useEffect(() => {
153
141
  if (toast.duration === null || paused) return
154
- const timer = setTimeout(
155
- () => toastApi.dismiss(toast.id),
156
- toast.duration
157
- )
142
+ const timer = setTimeout(() => toastApi.dismiss(toast.id), toast.duration)
158
143
  return () => clearTimeout(timer)
159
144
  }, [toast.id, toast.duration, paused])
160
145
 
@@ -165,33 +150,31 @@ function ToastItem({
165
150
 
166
151
  return (
167
152
  <motion.li
168
- layout
153
+ layout={!reducedMotion}
169
154
  className="pointer-events-none absolute inset-x-0 top-0 flex justify-center"
170
155
  style={{ zIndex: 10 - index }}
171
- initial={{ opacity: 0, y: -24, scale: 0.6 }}
156
+ initial={reducedMotion ? false : { opacity: 0, y: -24, scale: 0.6 }}
172
157
  animate={{
173
158
  opacity: stacked ? 1 - index * 0.25 : 1,
174
- y: stacked
175
- ? index * 9
176
- : index * (PILL + 14) + index * 6,
159
+ y: stacked ? index * 9 : index * (PILL + 14) + index * 6,
177
160
  scale: stacked ? 1 - index * 0.05 : 1,
178
161
  }}
179
- exit={{
180
- opacity: 0,
181
- y: -18,
182
- scale: 0.6,
183
- transition: spring.snappy,
184
- }}
185
- transition={spring.bouncy}
186
- drag="y"
162
+ exit={
163
+ reducedMotion
164
+ ? { opacity: 0 }
165
+ : {
166
+ opacity: 0,
167
+ y: -18,
168
+ scale: 0.6,
169
+ transition: spring.snappy,
170
+ }
171
+ }
172
+ transition={reducedMotion ? { duration: 0 } : spring.bouncy}
173
+ drag={reducedMotion ? false : "y"}
187
174
  dragConstraints={{ top: 0, bottom: 0 }}
188
175
  dragElastic={{ top: 0.6, bottom: 0.1 }}
189
176
  onDragEnd={(_, info) => {
190
- if (
191
- info.offset.y < -30 ||
192
- info.velocity.y < -300
193
- )
194
- toastApi.dismiss(toast.id)
177
+ if (info.offset.y < -30 || info.velocity.y < -300) toastApi.dismiss(toast.id)
195
178
  }}
196
179
  >
197
180
  <motion.div
@@ -199,21 +182,21 @@ function ToastItem({
199
182
  className="pointer-events-auto relative"
200
183
  initial={{ width: PILL, height: PILL }}
201
184
  animate={{ width, height }}
202
- transition={BODY_SPRING}
185
+ transition={reducedMotion ? { duration: 0 } : BODY_SPRING}
203
186
  >
204
187
  {/* Shapes, under the goo filter; padded so the blur isn't clipped. */}
205
188
  <div
206
189
  aria-hidden="true"
207
190
  className="absolute -inset-3"
208
191
  style={{
209
- filter: `url(#${GOO_FILTER_ID})`,
192
+ filter: reducedMotion ? undefined : `url(#${filterId})`,
210
193
  }}
211
194
  >
212
195
  <motion.span
213
196
  className="absolute top-3 left-3 rounded-full bg-inverse shadow-xl"
214
197
  style={{ width: PILL, height: PILL }}
215
198
  animate={{ scale: open ? 1 : 1.06 }}
216
- transition={spring.bouncy}
199
+ transition={reducedMotion ? { duration: 0 } : spring.bouncy}
217
200
  />
218
201
  <motion.span
219
202
  className="absolute top-3 left-3 bg-inverse"
@@ -225,13 +208,9 @@ function ToastItem({
225
208
  animate={{
226
209
  width,
227
210
  height,
228
- borderRadius: Math.min(
229
- PILL / 2,
230
- height / 2,
231
- 22
232
- ),
211
+ borderRadius: Math.min(PILL / 2, height / 2, 22),
233
212
  }}
234
- transition={BODY_SPRING}
213
+ transition={reducedMotion ? { duration: 0 } : BODY_SPRING}
235
214
  />
236
215
  </div>
237
216
 
@@ -241,36 +220,45 @@ function ToastItem({
241
220
  className="absolute top-0 left-0 flex w-max max-w-[min(420px,calc(100vw-24px))] items-start gap-2.5 py-[10px] pr-4 pl-[10px] text-on-inverse"
242
221
  style={{ minHeight: PILL }}
243
222
  animate={{ opacity: open ? 1 : 0 }}
244
- transition={{
245
- duration: open ? 0.22 : 0.1,
246
- delay: open ? 0.08 : 0,
247
- }}
223
+ transition={
224
+ reducedMotion
225
+ ? { duration: 0 }
226
+ : {
227
+ duration: open ? 0.22 : 0.1,
228
+ delay: open ? 0.08 : 0,
229
+ }
230
+ }
248
231
  >
249
- <ToneIcon tone={toast.tone} />
232
+ <ToneIcon tone={toast.tone} reducedMotion={reducedMotion} />
250
233
  <div className="flex min-w-0 flex-col py-px">
251
- <AnimatePresence
252
- mode="popLayout"
253
- initial={false}
254
- >
234
+ <AnimatePresence mode="popLayout" initial={false}>
255
235
  <motion.span
256
236
  key={toast.title}
257
237
  className="text-subheadline leading-5 font-semibold"
258
- initial={{
259
- opacity: 0,
260
- y: 6,
261
- filter: "blur(4px)",
262
- }}
238
+ initial={
239
+ reducedMotion
240
+ ? false
241
+ : {
242
+ opacity: 0,
243
+ y: 6,
244
+ filter: "blur(4px)",
245
+ }
246
+ }
263
247
  animate={{
264
248
  opacity: 1,
265
249
  y: 0,
266
250
  filter: "blur(0px)",
267
251
  }}
268
- exit={{
269
- opacity: 0,
270
- y: -6,
271
- filter: "blur(4px)",
272
- }}
273
- transition={spring.snappy}
252
+ exit={
253
+ reducedMotion
254
+ ? { opacity: 0 }
255
+ : {
256
+ opacity: 0,
257
+ y: -6,
258
+ filter: "blur(4px)",
259
+ }
260
+ }
261
+ transition={reducedMotion ? { duration: 0 } : spring.snappy}
274
262
  >
275
263
  {toast.title}
276
264
  </motion.span>
@@ -306,7 +294,7 @@ const toneColors: Record<ToastTone, string> = {
306
294
  loading: "bg-on-inverse/15 text-on-inverse",
307
295
  }
308
296
 
309
- function ToneIcon({ tone }: { tone: ToastTone }) {
297
+ function ToneIcon({ tone, reducedMotion }: { tone: ToastTone; reducedMotion: boolean }) {
310
298
  return (
311
299
  <AnimatePresence mode="popLayout" initial={false}>
312
300
  <motion.span
@@ -315,37 +303,35 @@ function ToneIcon({ tone }: { tone: ToastTone }) {
315
303
  "flex size-5 shrink-0 items-center justify-center rounded-full",
316
304
  toneColors[tone]
317
305
  )}
318
- initial={{
319
- scale: 0.3,
320
- opacity: 0,
321
- rotate: -40,
322
- }}
306
+ initial={
307
+ reducedMotion
308
+ ? false
309
+ : {
310
+ scale: 0.3,
311
+ opacity: 0,
312
+ rotate: -40,
313
+ }
314
+ }
323
315
  animate={{ scale: 1, opacity: 1, rotate: 0 }}
324
316
  exit={{ scale: 0.3, opacity: 0 }}
325
- transition={spring.bouncy}
317
+ transition={reducedMotion ? { duration: 0 } : spring.bouncy}
326
318
  >
327
- {tone === "success" && (
328
- <CheckIcon size={13} strokeWidth={3} />
329
- )}
330
- {tone === "error" && (
331
- <CloseIcon size={12} strokeWidth={3} />
332
- )}
319
+ {tone === "success" && <CheckIcon size={13} strokeWidth={3} />}
320
+ {tone === "error" && <CloseIcon size={12} strokeWidth={3} />}
333
321
  {/* The full stop from the Notables mark. */}
334
- {tone === "neutral" && (
335
- <span className="size-2 rounded-full bg-[#EE8A2A]" />
336
- )}
337
- {tone === "loading" && <Spinner />}
322
+ {tone === "neutral" && <span className="size-2 rounded-full bg-[#EE8A2A]" />}
323
+ {tone === "loading" && <Spinner reducedMotion={reducedMotion} />}
338
324
  </motion.span>
339
325
  </AnimatePresence>
340
326
  )
341
327
  }
342
328
 
343
- function Spinner() {
329
+ function Spinner({ reducedMotion }: { reducedMotion: boolean }) {
344
330
  return (
345
331
  <motion.svg
346
332
  viewBox="0 0 20 20"
347
333
  className="size-4"
348
- animate={{ rotate: 360 }}
334
+ animate={reducedMotion ? undefined : { rotate: 360 }}
349
335
  transition={{
350
336
  repeat: Number.POSITIVE_INFINITY,
351
337
  duration: 0.8,
@@ -1,11 +1,35 @@
1
- import { motion } from "motion/react"
2
- import { useEffect, useRef } from "react"
1
+ import { CheckIcon } from "../../icons/icons"
2
+ import {
3
+ animate,
4
+ motion,
5
+ useMotionValue,
6
+ useSpring,
7
+ useTransform,
8
+ useReducedMotion,
9
+ } from "motion/react"
10
+ import { useEffect, useId, useRef } from "react"
3
11
  import { cn } from "../../lib/class-names"
4
12
  import { haptic } from "../../lib/haptics"
5
13
  import { spring } from "../../motion/transitions"
6
14
 
7
15
  export type ToggleStyle = "switch" | "checkbox"
8
16
 
17
+ export interface ToggleProps {
18
+ /** Current on or off state. */
19
+ checked: boolean
20
+ /** Called with the next checked state. */
21
+ onChange: (checked: boolean) => void
22
+ /** Accessible name, including when the visible label is hidden. */
23
+ label: string
24
+ disabled?: boolean
25
+ /** `switch` for a setting, `checkbox` for a selection. @default "switch" */
26
+ style?: ToggleStyle
27
+ /** Reports a mixed selection to assistive technology. @default false */
28
+ mixed?: boolean
29
+ /** Keeps the accessible name while hiding the visible label. @default false */
30
+ labelHidden?: boolean
31
+ }
32
+
9
33
  export function Toggle({
10
34
  checked,
11
35
  onChange,
@@ -14,47 +38,82 @@ export function Toggle({
14
38
  style = "switch",
15
39
  mixed = false,
16
40
  labelHidden = false,
17
- }: {
18
- checked: boolean
19
- onChange: (checked: boolean) => void
20
- label: string
21
- /** Shown but not yet usable, as iOS dims a switch it can't change. */
22
- disabled?: boolean
23
- style?: ToggleStyle
24
- mixed?: boolean
25
- labelHidden?: boolean
26
- }) {
41
+ }: ToggleProps) {
27
42
  const input = useRef<HTMLInputElement>(null)
28
43
  useEffect(() => {
29
44
  if (input.current) input.current.indeterminate = mixed
30
45
  }, [mixed])
31
46
  if (style === "checkbox") {
32
47
  return (
33
- <label className="flex cursor-pointer items-center gap-3 text-subheadline">
34
- <input
35
- ref={input}
36
- type="checkbox"
37
- aria-checked={mixed ? "mixed" : checked}
38
- checked={checked}
39
- disabled={disabled}
40
- onChange={(event) =>
41
- onChange(event.target.checked)
42
- }
43
- className={cn(
44
- "size-5 accent-accent",
45
- "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-text disabled:opacity-40"
48
+ <label
49
+ className={cn(
50
+ "flex cursor-pointer items-center gap-3 text-subheadline",
51
+ disabled && "cursor-not-allowed opacity-40"
52
+ )}
53
+ >
54
+ <span className="relative inline-flex size-5 shrink-0">
55
+ <input
56
+ ref={input}
57
+ type="checkbox"
58
+ aria-checked={mixed ? "mixed" : checked}
59
+ checked={checked}
60
+ disabled={disabled}
61
+ onChange={(event) => onChange(event.target.checked)}
62
+ className={cn(
63
+ "peer size-5 cursor-inherit appearance-none rounded-sm border border-separator/80 bg-elevated transition-colors checked:border-transparent checked:bg-accent indeterminate:border-transparent indeterminate:bg-accent motion-reduce:transition-none",
64
+ "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-text"
65
+ )}
66
+ />
67
+ {mixed ? (
68
+ <span
69
+ aria-hidden="true"
70
+ className="pointer-events-none absolute inset-0 m-auto h-0.5 w-2.5 rounded-full bg-on-accent"
71
+ />
72
+ ) : (
73
+ <CheckIcon
74
+ aria-hidden="true"
75
+ size={14}
76
+ strokeWidth={2.5}
77
+ className="pointer-events-none absolute inset-0 m-auto text-on-accent opacity-0 peer-checked:opacity-100"
78
+ />
46
79
  )}
47
- />
48
- <span
49
- className={
50
- labelHidden ? "sr-only" : undefined
51
- }
52
- >
53
- {label}
54
80
  </span>
81
+ <span className={labelHidden ? "sr-only" : undefined}>{label}</span>
55
82
  </label>
56
83
  )
57
84
  }
85
+ return <SwitchToggle checked={checked} onChange={onChange} label={label} disabled={disabled} />
86
+ }
87
+
88
+ function SwitchToggle({
89
+ checked,
90
+ onChange,
91
+ label,
92
+ disabled,
93
+ }: Pick<ToggleProps, "checked" | "onChange" | "label" | "disabled">) {
94
+ const reducedMotion = useReducedMotion()
95
+ const id = `toggle-goo-${useId().replace(/:/g, "")}`
96
+ const x = useMotionValue(checked ? 22 : 2)
97
+ // One position owns the thumb. The drop follows that position, not a delayed target.
98
+ const chase = useSpring(x, spring.snappy)
99
+ const dropX = useTransform(chase, (value) => value + 5)
100
+ const gesture = useRef({ start: 0, x: 0, moved: false, skipClick: false, held: false })
101
+ useEffect(() => {
102
+ if (reducedMotion) {
103
+ x.set(checked ? 22 : 2)
104
+ return
105
+ }
106
+ const animation = animate(x, checked ? 22 : 2, spring.snappy)
107
+ return () => animation.stop()
108
+ }, [checked, reducedMotion, x])
109
+ const choose = (next: boolean) => {
110
+ if (disabled) return
111
+ haptic("selection")
112
+ if (next !== checked) onChange(next)
113
+ // Releasing on the current side still needs a settling spring.
114
+ if (reducedMotion) x.set(next ? 22 : 2)
115
+ else animate(x, next ? 22 : 2, spring.snappy)
116
+ }
58
117
  return (
59
118
  <button
60
119
  type="button"
@@ -63,23 +122,85 @@ export function Toggle({
63
122
  aria-label={label}
64
123
  disabled={disabled}
65
124
  onClick={() => {
66
- haptic("selection")
67
- onChange(!checked)
125
+ if (gesture.current.skipClick) {
126
+ gesture.current.skipClick = false
127
+ return
128
+ }
129
+ choose(!checked)
130
+ }}
131
+ onPointerDown={(event) => {
132
+ if (event.button !== 0 || disabled) return
133
+ event.currentTarget.setPointerCapture(event.pointerId)
134
+ gesture.current = {
135
+ start: event.clientX,
136
+ x: x.get(),
137
+ moved: false,
138
+ skipClick: false,
139
+ held: true,
140
+ }
141
+ }}
142
+ onPointerMove={(event) => {
143
+ const drag = gesture.current
144
+ if (!drag.held || disabled) return
145
+ const direction = getComputedStyle(event.currentTarget).direction === "rtl" ? -1 : 1
146
+ const delta = (event.clientX - drag.start) * direction
147
+ if (Math.abs(delta) > 3) drag.moved = true
148
+ if (drag.moved) x.set(Math.max(2, Math.min(22, drag.x + delta)))
149
+ }}
150
+ onPointerUp={() => {
151
+ const drag = gesture.current
152
+ if (!drag.held) return
153
+ drag.held = false
154
+ if (!drag.moved) return
155
+ drag.skipClick = true
156
+ choose(x.get() >= 12)
157
+ }}
158
+ onPointerCancel={() => {
159
+ gesture.current.held = false
160
+ gesture.current.skipClick = false
161
+ choose(checked)
68
162
  }}
69
163
  className={cn(
70
- "group/switch flex h-[30px] w-[50px] shrink-0 items-center rounded-full p-[2px] transition-colors duration-normal",
164
+ "relative h-[30px] w-[50px] shrink-0 touch-pan-y rounded-full transition-colors duration-normal motion-reduce:transition-none",
71
165
  "shadow-[inset_0_1px_2px_rgb(0_0_0/0.10)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:opacity-45",
72
- checked
73
- ? "justify-end bg-accent ring-1 ring-accent-text"
74
- : "justify-start bg-separator/80"
166
+ checked ? "bg-accent" : "bg-separator/80"
75
167
  )}
76
168
  >
77
- {/* The thumb stretches while pressed, as on iOS. */}
78
- <motion.span
79
- layout
80
- transition={spring.snappy}
81
- className="h-[26px] w-[26px] rounded-full bg-white shadow-[0_0_0_0.5px_rgb(0_0_0/0.04),0_2px_4px_rgb(0_0_0/0.16),0_3px_8px_rgb(0_0_0/0.10)] transition-[width] duration-fast group-active/switch:w-[32px]"
82
- />
169
+ <svg aria-hidden="true" width="0" height="0" className="absolute">
170
+ <defs>
171
+ <filter
172
+ id={id}
173
+ x="-50%"
174
+ y="-100%"
175
+ width="200%"
176
+ height="300%"
177
+ colorInterpolationFilters="sRGB"
178
+ >
179
+ <feGaussianBlur stdDeviation="2.5" result="smear" />
180
+ <feColorMatrix
181
+ in="smear"
182
+ type="matrix"
183
+ values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9"
184
+ />
185
+ </filter>
186
+ </defs>
187
+ </svg>
188
+ <span
189
+ aria-hidden="true"
190
+ className="pointer-events-none absolute inset-0 rtl:-scale-x-100"
191
+ style={{ filter: reducedMotion ? undefined : `url(#${id})` }}
192
+ >
193
+ {!reducedMotion && (
194
+ <motion.span
195
+ className="absolute top-[7px] left-0 size-4 rounded-full bg-white"
196
+ style={{ x: dropX }}
197
+ />
198
+ )}
199
+ <motion.span
200
+ className="absolute top-0.5 left-0 size-[26px] rounded-full bg-white"
201
+ style={{ x }}
202
+ />
203
+ </span>
83
204
  </button>
84
205
  )
85
206
  }