@passui/react 0.0.0-stage → 0.1.15

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 (275) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +83 -2
  3. package/package.json +96 -5
  4. package/src/appearance/index.ts +2 -0
  5. package/src/appearance/platform.tsx +123 -0
  6. package/src/appearance/theme.tsx +535 -0
  7. package/src/components/ambient/ambient.docs.tsx +68 -0
  8. package/src/components/ambient/ambient.tsx +43 -0
  9. package/src/components/ambient/examples/ambient-images.tsx +30 -0
  10. package/src/components/ambient/examples/ambient-now-playing.tsx +55 -0
  11. package/src/components/ambient/examples/ambient-showcase.tsx +33 -0
  12. package/src/components/ambient/examples/ambient-usage.tsx +19 -0
  13. package/src/components/ambient/index.ts +1 -0
  14. package/src/components/button/button-styles.ts +60 -0
  15. package/src/components/button/button.docs.tsx +116 -0
  16. package/src/components/button/button.tsx +91 -0
  17. package/src/components/button/examples/button-disabled.tsx +19 -0
  18. package/src/components/button/examples/button-icon.tsx +39 -0
  19. package/src/components/button/examples/button-link.tsx +12 -0
  20. package/src/components/button/examples/button-pending.tsx +27 -0
  21. package/src/components/button/examples/button-showcase.tsx +115 -0
  22. package/src/components/button/examples/button-sizes.tsx +12 -0
  23. package/src/components/button/examples/button-usage.tsx +12 -0
  24. package/src/components/button/examples/button-variants.tsx +15 -0
  25. package/src/components/button/examples/button-with-icon.tsx +32 -0
  26. package/src/components/button/examples/icon-button-player.tsx +38 -0
  27. package/src/components/button/examples/icon-button-showcase.tsx +83 -0
  28. package/src/components/button/examples/icon-button-usage.tsx +11 -0
  29. package/src/components/button/examples/icon-button-variants.tsx +39 -0
  30. package/src/components/button/icon-button.docs.tsx +68 -0
  31. package/src/components/button/icon-button.tsx +73 -0
  32. package/src/components/button/pending-spinner.tsx +14 -0
  33. package/src/components/button/use-pending-indicator.ts +27 -0
  34. package/src/components/chip/chip.docs.tsx +37 -0
  35. package/src/components/chip/chip.tsx +35 -0
  36. package/src/components/chip/examples/chip-in-rows.tsx +19 -0
  37. package/src/components/chip/examples/chip-showcase.tsx +51 -0
  38. package/src/components/chip/examples/chip-tones.tsx +13 -0
  39. package/src/components/chip/examples/chip-usage.tsx +5 -0
  40. package/src/components/compact-date-picker/calendar-month.ts +131 -0
  41. package/src/components/compact-date-picker/compact-date-picker.docs.tsx +81 -0
  42. package/src/components/compact-date-picker/compact-date-picker.tsx +438 -0
  43. package/src/components/compact-date-picker/examples/compact-date-picker-showcase.tsx +35 -0
  44. package/src/components/compact-date-picker/examples/compact-date-picker-usage.tsx +22 -0
  45. package/src/components/compact-time-picker/compact-time-picker.docs.tsx +80 -0
  46. package/src/components/compact-time-picker/compact-time-picker.tsx +432 -0
  47. package/src/components/compact-time-picker/examples/compact-time-picker-showcase.tsx +31 -0
  48. package/src/components/compact-time-picker/examples/compact-time-picker-usage.tsx +21 -0
  49. package/src/components/compact-time-picker/time-value.ts +67 -0
  50. package/src/components/content-view/content-view.docs.tsx +61 -0
  51. package/src/components/content-view/content-view.tsx +197 -0
  52. package/src/components/content-view/examples/content-view-header-action.tsx +33 -0
  53. package/src/components/content-view/examples/content-view-showcase.tsx +65 -0
  54. package/src/components/content-view/examples/content-view-stats.tsx +35 -0
  55. package/src/components/content-view/examples/content-view-usage.tsx +14 -0
  56. package/src/components/content-view/index.ts +1 -0
  57. package/src/components/context-menu/conceal-menu-source.ts +33 -0
  58. package/src/components/context-menu/context-menu-host.tsx +445 -0
  59. package/src/components/context-menu/context-menu-store.ts +165 -0
  60. package/src/components/context-menu/context-menu.docs.tsx +92 -0
  61. package/src/components/context-menu/examples/context-menu-checked.tsx +37 -0
  62. package/src/components/context-menu/examples/context-menu-list.tsx +85 -0
  63. package/src/components/context-menu/examples/context-menu-showcase.tsx +56 -0
  64. package/src/components/context-menu/examples/context-menu-toolbar.tsx +63 -0
  65. package/src/components/context-menu/examples/context-menu-usage.tsx +24 -0
  66. package/src/components/context-menu/menu-position.ts +55 -0
  67. package/src/components/context-menu/menu-reveal.ts +75 -0
  68. package/src/components/context-menu/use-context-menu.ts +90 -0
  69. package/src/components/conversation/conversation.docs.tsx +88 -0
  70. package/src/components/conversation/conversation.tsx +279 -0
  71. package/src/components/conversation/examples/conversation-composer.tsx +36 -0
  72. package/src/components/conversation/examples/conversation-days.tsx +48 -0
  73. package/src/components/conversation/examples/conversation-showcase.tsx +71 -0
  74. package/src/components/conversation/examples/conversation-usage.tsx +28 -0
  75. package/src/components/conversation/index.ts +1 -0
  76. package/src/components/date-picker/date-picker.docs.tsx +88 -0
  77. package/src/components/date-picker/date-picker.tsx +103 -0
  78. package/src/components/date-picker/date-value.ts +37 -0
  79. package/src/components/date-picker/examples/date-picker-locales.tsx +41 -0
  80. package/src/components/date-picker/examples/date-picker-past-only.tsx +29 -0
  81. package/src/components/date-picker/examples/date-picker-showcase.tsx +49 -0
  82. package/src/components/date-picker/examples/date-picker-usage.tsx +22 -0
  83. package/src/components/dialog/dialog-host.tsx +156 -0
  84. package/src/components/dialog/dialog-store.ts +56 -0
  85. package/src/components/dialog/dialog.docs.tsx +114 -0
  86. package/src/components/dialog/dialog.tsx +200 -0
  87. package/src/components/dialog/examples/dialog-confirm.tsx +24 -0
  88. package/src/components/dialog/examples/dialog-form.tsx +46 -0
  89. package/src/components/dialog/examples/dialog-inline.tsx +48 -0
  90. package/src/components/dialog/examples/dialog-showcase.tsx +53 -0
  91. package/src/components/dialog/examples/dialog-usage.tsx +31 -0
  92. package/src/components/dialog/index.ts +1 -0
  93. package/src/components/labeled-content/examples/labeled-content-descriptions.tsx +37 -0
  94. package/src/components/labeled-content/examples/labeled-content-showcase.tsx +54 -0
  95. package/src/components/labeled-content/examples/labeled-content-usage.tsx +22 -0
  96. package/src/components/labeled-content/examples/labeled-content-values.tsx +26 -0
  97. package/src/components/labeled-content/examples/labeled-content-wide.tsx +46 -0
  98. package/src/components/labeled-content/labeled-content.docs.tsx +69 -0
  99. package/src/components/labeled-content/labeled-content.tsx +66 -0
  100. package/src/components/locale-switcher/examples/locale-switcher-native-names.tsx +30 -0
  101. package/src/components/locale-switcher/examples/locale-switcher-showcase.tsx +46 -0
  102. package/src/components/locale-switcher/examples/locale-switcher-translated-page.tsx +35 -0
  103. package/src/components/locale-switcher/examples/locale-switcher-usage.tsx +20 -0
  104. package/src/components/locale-switcher/index.ts +1 -0
  105. package/src/components/locale-switcher/locale-switcher.docs.tsx +91 -0
  106. package/src/components/locale-switcher/locale-switcher.tsx +252 -0
  107. package/src/components/picker/examples/picker-details.tsx +44 -0
  108. package/src/components/picker/examples/picker-searchable.tsx +48 -0
  109. package/src/components/picker/examples/picker-showcase.tsx +66 -0
  110. package/src/components/picker/examples/picker-usage.tsx +20 -0
  111. package/src/components/picker/picker.docs.tsx +68 -0
  112. package/src/components/picker/picker.tsx +493 -0
  113. package/src/components/popover/examples/popover-help.tsx +57 -0
  114. package/src/components/popover/examples/popover-menu.tsx +51 -0
  115. package/src/components/popover/examples/popover-showcase.tsx +61 -0
  116. package/src/components/popover/examples/popover-usage.tsx +31 -0
  117. package/src/components/popover/popover.docs.tsx +84 -0
  118. package/src/components/popover/popover.tsx +138 -0
  119. package/src/components/profile-image/examples/profile-image-fallbacks.tsx +29 -0
  120. package/src/components/profile-image/examples/profile-image-group.tsx +34 -0
  121. package/src/components/profile-image/examples/profile-image-showcase.tsx +30 -0
  122. package/src/components/profile-image/examples/profile-image-usage.tsx +14 -0
  123. package/src/components/profile-image/profile-image.docs.tsx +81 -0
  124. package/src/components/profile-image/profile-image.tsx +80 -0
  125. package/src/components/search-field/examples/search-field-filter.tsx +53 -0
  126. package/src/components/search-field/examples/search-field-showcase.tsx +56 -0
  127. package/src/components/search-field/examples/search-field-toolbar.tsx +27 -0
  128. package/src/components/search-field/examples/search-field-usage.tsx +18 -0
  129. package/src/components/search-field/search-field.docs.tsx +58 -0
  130. package/src/components/search-field/search-field.tsx +29 -0
  131. package/src/components/search-list/examples/search-list-dropdown.tsx +53 -0
  132. package/src/components/search-list/examples/search-list-groups.tsx +64 -0
  133. package/src/components/search-list/examples/search-list-showcase.tsx +67 -0
  134. package/src/components/search-list/examples/search-list-usage.tsx +27 -0
  135. package/src/components/search-list/search-list.docs.tsx +87 -0
  136. package/src/components/search-list/search-list.tsx +265 -0
  137. package/src/components/section/examples/section-actions.tsx +35 -0
  138. package/src/components/section/examples/section-groups.tsx +41 -0
  139. package/src/components/section/examples/section-showcase.tsx +63 -0
  140. package/src/components/section/examples/section-usage.tsx +19 -0
  141. package/src/components/section/section.docs.tsx +51 -0
  142. package/src/components/section/section.tsx +41 -0
  143. package/src/components/secure-field/examples/secure-field-new-password.tsx +56 -0
  144. package/src/components/secure-field/examples/secure-field-reveal.tsx +20 -0
  145. package/src/components/secure-field/examples/secure-field-showcase.tsx +62 -0
  146. package/src/components/secure-field/examples/secure-field-usage.tsx +19 -0
  147. package/src/components/secure-field/secure-field.docs.tsx +72 -0
  148. package/src/components/secure-field/secure-field.tsx +10 -0
  149. package/src/components/segmented-control/examples/segmented-control-overflow.tsx +28 -0
  150. package/src/components/segmented-control/examples/segmented-control-showcase.tsx +59 -0
  151. package/src/components/segmented-control/examples/segmented-control-usage.tsx +20 -0
  152. package/src/components/segmented-control/examples/segmented-control-views.tsx +32 -0
  153. package/src/components/segmented-control/segmented-control.docs.tsx +61 -0
  154. package/src/components/segmented-control/segmented-control.tsx +122 -0
  155. package/src/components/sheet/examples/sheet-choices.tsx +58 -0
  156. package/src/components/sheet/examples/sheet-share.tsx +51 -0
  157. package/src/components/sheet/examples/sheet-showcase.tsx +74 -0
  158. package/src/components/sheet/examples/sheet-usage.tsx +32 -0
  159. package/src/components/sheet/modal-environment.ts +113 -0
  160. package/src/components/sheet/sheet-portal.tsx +89 -0
  161. package/src/components/sheet/sheet.docs.tsx +75 -0
  162. package/src/components/sheet/sheet.tsx +112 -0
  163. package/src/components/sheet/use-sheet-focus.ts +130 -0
  164. package/src/components/sidebar/examples/sidebar-groups.tsx +133 -0
  165. package/src/components/sidebar/examples/sidebar-showcase.tsx +123 -0
  166. package/src/components/sidebar/examples/sidebar-unread.tsx +66 -0
  167. package/src/components/sidebar/examples/sidebar-usage.tsx +52 -0
  168. package/src/components/sidebar/sidebar.docs.tsx +135 -0
  169. package/src/components/sidebar/sidebar.tsx +69 -0
  170. package/src/components/stat-tile/examples/stat-tile-filters.tsx +32 -0
  171. package/src/components/stat-tile/examples/stat-tile-grid.tsx +21 -0
  172. package/src/components/stat-tile/examples/stat-tile-showcase.tsx +52 -0
  173. package/src/components/stat-tile/examples/stat-tile-usage.tsx +13 -0
  174. package/src/components/stat-tile/index.ts +1 -0
  175. package/src/components/stat-tile/stat-tile.docs.tsx +60 -0
  176. package/src/components/stat-tile/stat-tile.tsx +68 -0
  177. package/src/components/status-indicator/examples/status-indicator-live.tsx +34 -0
  178. package/src/components/status-indicator/examples/status-indicator-showcase.tsx +39 -0
  179. package/src/components/status-indicator/examples/status-indicator-states.tsx +17 -0
  180. package/src/components/status-indicator/examples/status-indicator-usage.tsx +9 -0
  181. package/src/components/status-indicator/status-indicator.docs.tsx +43 -0
  182. package/src/components/status-indicator/status-indicator.tsx +35 -0
  183. package/src/components/swatch-picker/examples/swatch-picker-accents.tsx +42 -0
  184. package/src/components/swatch-picker/examples/swatch-picker-labels.tsx +45 -0
  185. package/src/components/swatch-picker/examples/swatch-picker-showcase.tsx +70 -0
  186. package/src/components/swatch-picker/examples/swatch-picker-usage.tsx +39 -0
  187. package/src/components/swatch-picker/swatch-picker.docs.tsx +60 -0
  188. package/src/components/swatch-picker/swatch-picker.tsx +96 -0
  189. package/src/components/tab-bar/examples/tab-bar-five-tabs.tsx +66 -0
  190. package/src/components/tab-bar/examples/tab-bar-minimize.tsx +71 -0
  191. package/src/components/tab-bar/examples/tab-bar-showcase.tsx +82 -0
  192. package/src/components/tab-bar/examples/tab-bar-usage.tsx +54 -0
  193. package/src/components/tab-bar/tab-bar.docs.tsx +136 -0
  194. package/src/components/tab-bar/tab-bar.tsx +148 -0
  195. package/src/components/tab-view/examples/tab-view-keyboard.tsx +39 -0
  196. package/src/components/tab-view/examples/tab-view-settings.tsx +56 -0
  197. package/src/components/tab-view/examples/tab-view-showcase.tsx +53 -0
  198. package/src/components/tab-view/examples/tab-view-usage.tsx +23 -0
  199. package/src/components/tab-view/tab-view.docs.tsx +77 -0
  200. package/src/components/tab-view/tab-view.tsx +130 -0
  201. package/src/components/table/examples/table-empty.tsx +53 -0
  202. package/src/components/table/examples/table-invoices.tsx +102 -0
  203. package/src/components/table/examples/table-selection.tsx +150 -0
  204. package/src/components/table/examples/table-showcase.tsx +115 -0
  205. package/src/components/table/examples/table-usage.tsx +67 -0
  206. package/src/components/table/examples/table-wide.tsx +75 -0
  207. package/src/components/table/table.docs.tsx +131 -0
  208. package/src/components/table/table.tsx +149 -0
  209. package/src/components/text-editor/examples/text-editor-comment.tsx +48 -0
  210. package/src/components/text-editor/examples/text-editor-count.tsx +38 -0
  211. package/src/components/text-editor/examples/text-editor-showcase.tsx +58 -0
  212. package/src/components/text-editor/examples/text-editor-usage.tsx +25 -0
  213. package/src/components/text-editor/text-editor.docs.tsx +61 -0
  214. package/src/components/text-editor/text-editor.tsx +19 -0
  215. package/src/components/text-field/examples/text-field-icons.tsx +72 -0
  216. package/src/components/text-field/examples/text-field-showcase.tsx +50 -0
  217. package/src/components/text-field/examples/text-field-usage.tsx +18 -0
  218. package/src/components/text-field/examples/text-field-validation.tsx +27 -0
  219. package/src/components/text-field/text-field.docs.tsx +118 -0
  220. package/src/components/text-field/text-field.tsx +135 -0
  221. package/src/components/toast/examples/toast-action.tsx +32 -0
  222. package/src/components/toast/examples/toast-promise.tsx +45 -0
  223. package/src/components/toast/examples/toast-showcase.tsx +49 -0
  224. package/src/components/toast/examples/toast-usage.tsx +13 -0
  225. package/src/components/toast/toast-store.ts +166 -0
  226. package/src/components/toast/toast.docs.tsx +79 -0
  227. package/src/components/toast/toaster.tsx +374 -0
  228. package/src/components/toggle/examples/toggle-checkboxes.tsx +43 -0
  229. package/src/components/toggle/examples/toggle-settings.tsx +31 -0
  230. package/src/components/toggle/examples/toggle-showcase.tsx +59 -0
  231. package/src/components/toggle/examples/toggle-usage.tsx +13 -0
  232. package/src/components/toggle/toggle.docs.tsx +69 -0
  233. package/src/components/toggle/toggle.tsx +85 -0
  234. package/src/components/tooltip/examples/tooltip-icon-buttons.tsx +27 -0
  235. package/src/components/tooltip/examples/tooltip-shortcuts.tsx +38 -0
  236. package/src/components/tooltip/examples/tooltip-showcase.tsx +44 -0
  237. package/src/components/tooltip/examples/tooltip-usage.tsx +13 -0
  238. package/src/components/tooltip/tooltip-host.tsx +156 -0
  239. package/src/components/tooltip/tooltip.docs.tsx +56 -0
  240. package/src/components/view-controller/examples/view-controller-action.tsx +27 -0
  241. package/src/components/view-controller/examples/view-controller-settings.tsx +31 -0
  242. package/src/components/view-controller/examples/view-controller-showcase.tsx +45 -0
  243. package/src/components/view-controller/examples/view-controller-usage.tsx +19 -0
  244. package/src/components/view-controller/index.ts +1 -0
  245. package/src/components/view-controller/view-controller.docs.tsx +71 -0
  246. package/src/components/view-controller/view-controller.tsx +81 -0
  247. package/src/docs/index.ts +3 -0
  248. package/src/docs/registry.ts +113 -0
  249. package/src/docs/snippet.ts +17 -0
  250. package/src/docs/types.ts +180 -0
  251. package/src/hooks/scroll/minimize-decision.ts +13 -0
  252. package/src/hooks/use-anchored-placement.ts +43 -0
  253. package/src/hooks/use-dismiss.ts +58 -0
  254. package/src/hooks/use-media-query.ts +106 -0
  255. package/src/hooks/use-minimize-on-scroll.ts +92 -0
  256. package/src/hooks/use-visual-viewport.ts +76 -0
  257. package/src/hooks/viewport/types.ts +19 -0
  258. package/src/hooks/viewport/viewport-metrics.ts +25 -0
  259. package/src/icons/icons.tsx +298 -0
  260. package/src/index.ts +78 -0
  261. package/src/lib/anchored-placement.ts +92 -0
  262. package/src/lib/class-names.ts +21 -0
  263. package/src/lib/date.ts +96 -0
  264. package/src/lib/floating-layers.ts +28 -0
  265. package/src/lib/haptics.ts +25 -0
  266. package/src/lib/language.ts +21 -0
  267. package/src/lib/screen-edges.ts +45 -0
  268. package/src/lib/shapes.ts +70 -0
  269. package/src/motion/transitions.ts +30 -0
  270. package/src/styles/base.css +1 -0
  271. package/src/styles/styles.css +3 -0
  272. package/src/styles/theme.css +1 -0
  273. package/src/styles/typeset.css +1 -0
  274. package/src/styles/utilities.css +1 -0
  275. package/src/tokens.ts +1 -0
@@ -0,0 +1,106 @@
1
+ import { useCallback, useSyncExternalStore } from "react"
2
+
3
+ const BREAKPOINTS = {
4
+ "2xl": 1536,
5
+ "3xl": 1600,
6
+ "4xl": 2000,
7
+ lg: 1024,
8
+ md: 800,
9
+ sm: 640,
10
+ xl: 1280,
11
+ } as const
12
+
13
+ export type Breakpoint = keyof typeof BREAKPOINTS
14
+ export type BreakpointQuery =
15
+ | Breakpoint
16
+ | `max-${Breakpoint}`
17
+ | `${Breakpoint}:max-${Breakpoint}`
18
+ function resolveMin(value: Breakpoint | number): string {
19
+ const px =
20
+ typeof value === "number" ? value : BREAKPOINTS[value]
21
+ return `(min-width: ${px}px)`
22
+ }
23
+
24
+ function resolveMax(value: Breakpoint | number): string {
25
+ const px =
26
+ typeof value === "number" ? value : BREAKPOINTS[value]
27
+ return `(max-width: ${px - 1}px)`
28
+ }
29
+
30
+ function parseQuery(
31
+ query: BreakpointQuery | MediaQueryInput | (string & {})
32
+ ): string {
33
+ if (typeof query !== "string") {
34
+ const parts: string[] = []
35
+ if (query.min != null)
36
+ parts.push(resolveMin(query.min))
37
+ if (query.max != null)
38
+ parts.push(resolveMax(query.max))
39
+ if (query.pointer === "coarse")
40
+ parts.push("(pointer: coarse)")
41
+ if (query.pointer === "fine")
42
+ parts.push("(pointer: fine)")
43
+ if (parts.length === 0) return "(min-width: 0px)"
44
+ return parts.join(" and ")
45
+ }
46
+
47
+ if (query.startsWith("(")) return query
48
+
49
+ const parts: string[] = []
50
+ for (const segment of query.split(":")) {
51
+ if (segment.startsWith("max-")) {
52
+ const bp = segment.slice(4)
53
+ if (bp in BREAKPOINTS)
54
+ parts.push(resolveMax(bp as Breakpoint))
55
+ } else if (segment in BREAKPOINTS) {
56
+ parts.push(resolveMin(segment as Breakpoint))
57
+ }
58
+ }
59
+
60
+ return parts.length > 0 ? parts.join(" and ") : query
61
+ }
62
+
63
+ function getServerSnapshot(): boolean {
64
+ return false
65
+ }
66
+
67
+ export type MediaQueryInput = {
68
+ min?: Breakpoint | number
69
+ max?: Breakpoint | number
70
+ /** Touch-like input (finger). Use "fine" for mouse/trackpad. */
71
+ pointer?: PointerPrecision
72
+ }
73
+
74
+ export type PointerPrecision = "coarse" | "fine"
75
+
76
+ export function useMediaQuery(
77
+ query: BreakpointQuery | MediaQueryInput | (string & {})
78
+ ): boolean {
79
+ const mediaQuery = parseQuery(query)
80
+
81
+ const subscribe = useCallback(
82
+ (callback: () => void) => {
83
+ if (typeof window === "undefined") return () => {}
84
+ const mql = window.matchMedia(mediaQuery)
85
+ mql.addEventListener("change", callback)
86
+ return () =>
87
+ mql.removeEventListener("change", callback)
88
+ },
89
+ [mediaQuery]
90
+ )
91
+
92
+ const getSnapshot = useCallback(() => {
93
+ if (typeof window === "undefined") return false
94
+ return window.matchMedia(mediaQuery).matches
95
+ }, [mediaQuery])
96
+
97
+ return useSyncExternalStore(
98
+ subscribe,
99
+ getSnapshot,
100
+ getServerSnapshot
101
+ )
102
+ }
103
+
104
+ export function useIsMobile(): boolean {
105
+ return useMediaQuery("(max-width: 768px)")
106
+ }
@@ -0,0 +1,92 @@
1
+ import { useEffect, useState } from "react"
2
+ import { minimizeDecision } from "./scroll/minimize-decision"
3
+
4
+ /**
5
+ * Minimized while you scroll down through content, restored when you scroll
6
+ * back up or reach the top: iOS 26's `tabBarMinimizeBehavior(.onScrollDown)`.
7
+ * Listens to every scroll container, since each screen scrolls its own pane.
8
+ */
9
+ export function useMinimizeOnScroll() {
10
+ const [minimized, setMinimized] = useState(false)
11
+ useEffect(() => {
12
+ const anchors = new WeakMap<Element, number>()
13
+ let intent: number | null = null
14
+ const onIntent = () => {
15
+ intent = performance.now()
16
+ }
17
+ const onKey = (event: KeyboardEvent) => {
18
+ if (
19
+ [
20
+ "ArrowDown",
21
+ "ArrowUp",
22
+ "PageDown",
23
+ "PageUp",
24
+ "Home",
25
+ "End",
26
+ " ",
27
+ ].includes(event.key)
28
+ )
29
+ onIntent()
30
+ }
31
+ const onPointer = (event: PointerEvent) => {
32
+ if (event.buttons === 1) onIntent()
33
+ }
34
+ const onScroll = (event: Event) => {
35
+ const pane =
36
+ event.target instanceof Element
37
+ ? event.target
38
+ : document.scrollingElement
39
+ if (
40
+ !pane ||
41
+ pane.closest(
42
+ "[role=dialog], [data-floating-layer]"
43
+ ) ||
44
+ pane.scrollHeight <= pane.clientHeight + 1
45
+ )
46
+ return
47
+ const top = pane.scrollTop
48
+ const userDriven =
49
+ intent !== null &&
50
+ performance.now() - intent < 2000
51
+ const anchor = anchors.get(pane) ?? 0
52
+ const next = minimizeDecision(
53
+ top,
54
+ anchor,
55
+ userDriven
56
+ )
57
+ if (next !== null) setMinimized(next)
58
+ if (next !== null || !userDriven)
59
+ anchors.set(pane, top)
60
+ }
61
+ document.addEventListener("touchmove", onIntent, {
62
+ passive: true,
63
+ })
64
+ document.addEventListener("wheel", onIntent, {
65
+ passive: true,
66
+ })
67
+ document.addEventListener("pointermove", onPointer, {
68
+ passive: true,
69
+ })
70
+ document.addEventListener("keydown", onKey)
71
+ document.addEventListener("scroll", onScroll, {
72
+ capture: true,
73
+ passive: true,
74
+ })
75
+ return () => {
76
+ document.removeEventListener("scroll", onScroll, {
77
+ capture: true,
78
+ })
79
+ document.removeEventListener(
80
+ "touchmove",
81
+ onIntent
82
+ )
83
+ document.removeEventListener("wheel", onIntent)
84
+ document.removeEventListener(
85
+ "pointermove",
86
+ onPointer
87
+ )
88
+ document.removeEventListener("keydown", onKey)
89
+ }
90
+ }, [])
91
+ return [minimized, setMinimized] as const
92
+ }
@@ -0,0 +1,76 @@
1
+ import { useLayoutEffect, useState } from "react"
2
+ import { viewportMetrics } from "./viewport/viewport-metrics"
3
+ import type { ViewportMetrics } from "./viewport/types"
4
+ export type { ViewportMetrics } from "./viewport/types"
5
+
6
+ /** Measures once per frame, including Android resize and Safari viewport panning. */
7
+ export function useVisualViewport(): ViewportMetrics | null {
8
+ const [metrics, setMetrics] =
9
+ useState<ViewportMetrics | null>(null)
10
+ useLayoutEffect(() => {
11
+ const viewport = window.visualViewport
12
+ let baseline = {
13
+ width: window.innerWidth,
14
+ height: window.innerHeight,
15
+ }
16
+ let frame = 0
17
+ const measure = () => {
18
+ cancelAnimationFrame(frame)
19
+ frame = requestAnimationFrame(() => {
20
+ const active = document.activeElement
21
+ const editing = !!active?.matches(
22
+ "input:not([type=button]):not([type=checkbox]):not([type=radio]), textarea, [contenteditable=true]"
23
+ )
24
+ const width =
25
+ viewport?.width ?? window.innerWidth
26
+ const height =
27
+ viewport?.height ?? window.innerHeight
28
+ if (
29
+ Math.abs(baseline.width - width) > 80 ||
30
+ !editing
31
+ )
32
+ baseline = {
33
+ width,
34
+ height: window.innerHeight,
35
+ }
36
+ const next = viewportMetrics(
37
+ {
38
+ width,
39
+ height,
40
+ layoutHeight: window.innerHeight,
41
+ top: viewport?.offsetTop ?? 0,
42
+ left: viewport?.offsetLeft ?? 0,
43
+ editing,
44
+ },
45
+ baseline
46
+ )
47
+ setMetrics((previous) =>
48
+ previous &&
49
+ Object.entries(next).every(
50
+ ([key, value]) =>
51
+ previous[
52
+ key as keyof ViewportMetrics
53
+ ] === value
54
+ )
55
+ ? previous
56
+ : next
57
+ )
58
+ })
59
+ }
60
+ measure()
61
+ viewport?.addEventListener("resize", measure)
62
+ viewport?.addEventListener("scroll", measure)
63
+ window.addEventListener("resize", measure)
64
+ document.addEventListener("focusin", measure)
65
+ document.addEventListener("focusout", measure)
66
+ return () => {
67
+ cancelAnimationFrame(frame)
68
+ viewport?.removeEventListener("resize", measure)
69
+ viewport?.removeEventListener("scroll", measure)
70
+ window.removeEventListener("resize", measure)
71
+ document.removeEventListener("focusin", measure)
72
+ document.removeEventListener("focusout", measure)
73
+ }
74
+ }, [])
75
+ return metrics
76
+ }
@@ -0,0 +1,19 @@
1
+ export type ViewportInput = {
2
+ width: number
3
+ height: number
4
+ top: number
5
+ left: number
6
+ layoutHeight: number
7
+ editing: boolean
8
+ }
9
+ export type ViewportMetrics = {
10
+ width: number
11
+ height: number
12
+ top: number
13
+ left: number
14
+ keyboardOpen: boolean
15
+ }
16
+ export type ViewportBaseline = {
17
+ width: number
18
+ height: number
19
+ }
@@ -0,0 +1,25 @@
1
+ import type {
2
+ ViewportBaseline,
3
+ ViewportInput,
4
+ ViewportMetrics,
5
+ } from "./types"
6
+
7
+ /** A resized WebView and Safari's overlaid keyboard share one visible rectangle. */
8
+ export function viewportMetrics(
9
+ input: ViewportInput,
10
+ baseline: ViewportBaseline
11
+ ): ViewportMetrics {
12
+ const visibleHeight = Math.min(
13
+ input.height,
14
+ Math.max(0, input.layoutHeight - input.top)
15
+ )
16
+ return {
17
+ width: input.width,
18
+ height: visibleHeight,
19
+ top: input.top,
20
+ left: input.left,
21
+ keyboardOpen:
22
+ input.editing &&
23
+ baseline.height - visibleHeight > 120,
24
+ }
25
+ }
@@ -0,0 +1,298 @@
1
+ import {
2
+ Add01Icon,
3
+ AiMagicIcon,
4
+ AlarmClockIcon,
5
+ Alert02Icon,
6
+ ArrowDown01Icon,
7
+ ArrowLeft01Icon,
8
+ ArrowRight01Icon,
9
+ ArrowUp01Icon,
10
+ BirthdayCakeIcon,
11
+ Book02Icon,
12
+ BookOpen01Icon,
13
+ BubbleChatIcon,
14
+ Calendar03Icon,
15
+ Camera01Icon,
16
+ Cancel01Icon,
17
+ CheckListIcon,
18
+ CheckmarkBadge01Icon,
19
+ CheckmarkCircle02Icon,
20
+ Clock01Icon,
21
+ Comment01Icon,
22
+ Copy01Icon,
23
+ Cursor01Icon,
24
+ Delete02Icon,
25
+ Download01Icon,
26
+ DragDropVerticalIcon,
27
+ Eraser01Icon,
28
+ FavouriteIcon,
29
+ Folder01Icon,
30
+ FileImportIcon,
31
+ FullScreenIcon,
32
+ Globe02Icon,
33
+ GoBackward15SecIcon,
34
+ GoForward30SecIcon,
35
+ CanvasIcon as HugeCanvasIcon,
36
+ HeadphonesIcon as HugeHeadphonesIcon,
37
+ HighlighterIcon as HugeHighlighterIcon,
38
+ PauseIcon as HugePauseIcon,
39
+ PlayIcon as HugePlayIcon,
40
+ RedoIcon as HugeRedoIcon,
41
+ RepeatIcon as HugeRepeatIcon,
42
+ StarIcon as HugeStarIcon,
43
+ ThumbsUpIcon as HugeThumbsUpIcon,
44
+ UndoIcon as HugeUndoIcon,
45
+ Image01Icon,
46
+ Invoice03Icon,
47
+ LayoutGridIcon,
48
+ LeftToRightListBulletIcon,
49
+ Link01Icon,
50
+ Location01Icon,
51
+ LockIcon as LockIconSvg,
52
+ Mic01Icon,
53
+ Moon02Icon,
54
+ MoreHorizontalIcon,
55
+ Mortarboard01Icon,
56
+ NextIcon,
57
+ NfcIcon,
58
+ Note01Icon,
59
+ NotebookIcon,
60
+ Notification03Icon,
61
+ PaintBrush02Icon,
62
+ PanelLeftIcon,
63
+ PencilEdit02Icon,
64
+ PencilIcon,
65
+ Pin02Icon,
66
+ PreviousIcon,
67
+ PrinterIcon,
68
+ QrCodeIcon,
69
+ QuillWrite02Icon,
70
+ ScanIcon,
71
+ Search01Icon,
72
+ SearchAddIcon,
73
+ Settings02Icon,
74
+ Share08Icon,
75
+ Store01Icon,
76
+ Task01Icon,
77
+ Tick02Icon,
78
+ Time04Icon,
79
+ Touch01Icon,
80
+ UnfoldMoreIcon,
81
+ UserAdd01Icon,
82
+ UserMultipleIcon,
83
+ ViewIcon,
84
+ ViewOffSlashIcon,
85
+ Wallet01Icon,
86
+ } from "@hugeicons/core-free-icons"
87
+ import {
88
+ HugeiconsIcon,
89
+ type IconSvgElement,
90
+ } from "@hugeicons/react"
91
+ import type { SVGProps } from "react"
92
+ import { cn } from "../lib/class-names"
93
+
94
+ export type IconProps = Omit<
95
+ SVGProps<SVGSVGElement>,
96
+ "ref"
97
+ > & {
98
+ size?: number
99
+ strokeWidth?: number
100
+ }
101
+
102
+ /** Hugeicons (stroke rounded), sized and weighted for the Notables UI. */
103
+ function icon(svg: IconSvgElement, displayName: string) {
104
+ const Icon = ({
105
+ size = 20,
106
+ strokeWidth = 1.6,
107
+ ...props
108
+ }: IconProps) => (
109
+ <HugeiconsIcon
110
+ icon={svg}
111
+ size={size}
112
+ strokeWidth={strokeWidth}
113
+ aria-hidden="true"
114
+ {...props}
115
+ />
116
+ )
117
+ Icon.displayName = displayName
118
+ return Icon
119
+ }
120
+
121
+ /** A filled variant for toggled states (hearted, starred). */
122
+ function toggleIcon(
123
+ svg: IconSvgElement,
124
+ displayName: string
125
+ ) {
126
+ const Base = icon(svg, displayName)
127
+ const Icon = ({
128
+ filled = false,
129
+ className,
130
+ ...props
131
+ }: IconProps & { filled?: boolean }) => (
132
+ <Base
133
+ className={cn(
134
+ filled && "[&_path]:fill-current",
135
+ className
136
+ )}
137
+ {...props}
138
+ />
139
+ )
140
+ Icon.displayName = displayName
141
+ return Icon
142
+ }
143
+
144
+ export const NoteIcon = icon(Note01Icon, "NoteIcon")
145
+ export const JournalIcon = icon(NotebookIcon, "JournalIcon")
146
+ export const PenIcon = icon(PencilEdit02Icon, "PenIcon")
147
+ export const StoryIcon = icon(QuillWrite02Icon, "StoryIcon")
148
+ export const BookIcon = icon(BookOpen01Icon, "BookIcon")
149
+ export const ArticleIcon = icon(Book02Icon, "ArticleIcon")
150
+ export const CanvasIcon = icon(HugeCanvasIcon, "CanvasIcon")
151
+ export const LessonIcon = icon(
152
+ Mortarboard01Icon,
153
+ "LessonIcon"
154
+ )
155
+ export const PlanIcon = icon(CheckListIcon, "PlanIcon")
156
+ export const MicIcon = icon(Mic01Icon, "MicIcon")
157
+ export const GlobeIcon = icon(Globe02Icon, "GlobeIcon")
158
+ export const ChecklistIcon = icon(
159
+ CheckmarkCircle02Icon,
160
+ "ChecklistIcon"
161
+ )
162
+ export const DrawIcon = icon(PencilIcon, "DrawIcon")
163
+ export const ShareIcon = icon(Share08Icon, "ShareIcon")
164
+ export const SearchIcon = icon(Search01Icon, "SearchIcon")
165
+ export const ChevronLeftIcon = icon(
166
+ ArrowLeft01Icon,
167
+ "ChevronLeftIcon"
168
+ )
169
+ export const ChevronRightIcon = icon(
170
+ ArrowRight01Icon,
171
+ "ChevronRightIcon"
172
+ )
173
+ export const ChevronUpIcon = icon(
174
+ ArrowUp01Icon,
175
+ "ChevronUpIcon"
176
+ )
177
+ export const ChevronDownIcon = icon(
178
+ ArrowDown01Icon,
179
+ "ChevronDownIcon"
180
+ )
181
+ export const CloseIcon = icon(Cancel01Icon, "CloseIcon")
182
+ export const PlusIcon = icon(Add01Icon, "PlusIcon")
183
+ export const LinkIcon = icon(Link01Icon, "LinkIcon")
184
+ export const CommentIcon = icon(Comment01Icon, "CommentIcon")
185
+ export const TrashIcon = icon(Delete02Icon, "TrashIcon")
186
+ export const PinIcon = icon(Pin02Icon, "PinIcon")
187
+ export const SidebarIcon = icon(PanelLeftIcon, "SidebarIcon")
188
+ export const SettingsIcon = icon(
189
+ Settings02Icon,
190
+ "SettingsIcon"
191
+ )
192
+ export const DragHandleIcon = icon(
193
+ DragDropVerticalIcon,
194
+ "DragHandleIcon"
195
+ )
196
+ export const VisibleIcon = icon(ViewIcon, "VisibleIcon")
197
+ export const HiddenIcon = icon(ViewOffSlashIcon, "HiddenIcon")
198
+ export const CheckIcon = icon(Tick02Icon, "CheckIcon")
199
+ export const InvoiceIcon = icon(Invoice03Icon, "InvoiceIcon")
200
+ export const QrCodeGlyph = icon(QrCodeIcon, "QrCodeGlyph")
201
+ export const WalletIcon = icon(Wallet01Icon, "WalletIcon")
202
+ export const ScanCodeIcon = icon(ScanIcon, "ScanCodeIcon")
203
+ export const VerifiedIcon = icon(
204
+ CheckmarkBadge01Icon,
205
+ "VerifiedIcon"
206
+ )
207
+ export const WarningIcon = icon(Alert02Icon, "WarningIcon")
208
+ export const PrintIcon = icon(PrinterIcon, "PrintIcon")
209
+ export const ImportIcon = icon(FileImportIcon, "ImportIcon")
210
+ export const DownloadIcon = icon(
211
+ Download01Icon,
212
+ "DownloadIcon"
213
+ )
214
+ export const PlayIcon = icon(HugePlayIcon, "PlayIcon")
215
+ export const PauseIcon = icon(HugePauseIcon, "PauseIcon")
216
+ export const SkipBackIcon = icon(
217
+ GoBackward15SecIcon,
218
+ "SkipBackIcon"
219
+ )
220
+ export const SkipForwardIcon = icon(
221
+ GoForward30SecIcon,
222
+ "SkipForwardIcon"
223
+ )
224
+ export const PreviousTrackIcon = icon(
225
+ PreviousIcon,
226
+ "PreviousTrackIcon"
227
+ )
228
+ export const NextTrackIcon = icon(NextIcon, "NextTrackIcon")
229
+ export const HeadphonesIcon = icon(
230
+ HugeHeadphonesIcon,
231
+ "HeadphonesIcon"
232
+ )
233
+ export const HighlighterIcon = icon(
234
+ HugeHighlighterIcon,
235
+ "HighlighterIcon"
236
+ )
237
+ export const ChaptersIcon = icon(
238
+ LeftToRightListBulletIcon,
239
+ "ChaptersIcon"
240
+ )
241
+ export const SleepIcon = icon(Moon02Icon, "SleepIcon")
242
+ export const ExpandIcon = icon(FullScreenIcon, "ExpandIcon")
243
+ export const SparkleIcon = icon(AiMagicIcon, "SparkleIcon")
244
+ export const BellIcon = icon(Notification03Icon, "BellIcon")
245
+ export const BusinessIcon = icon(Store01Icon, "BusinessIcon")
246
+ export const CalendarIcon = icon(
247
+ Calendar03Icon,
248
+ "CalendarIcon"
249
+ )
250
+ export const BirthdayIcon = icon(
251
+ BirthdayCakeIcon,
252
+ "BirthdayIcon"
253
+ )
254
+ export const AlarmIcon = icon(AlarmClockIcon, "AlarmIcon")
255
+ export const RepeatIcon = icon(HugeRepeatIcon, "RepeatIcon")
256
+ export const UndoIcon = icon(HugeUndoIcon, "UndoIcon")
257
+ export const RedoIcon = icon(HugeRedoIcon, "RedoIcon")
258
+ export const PointerIcon = icon(Cursor01Icon, "PointerIcon")
259
+ export const EraserIcon = icon(Eraser01Icon, "EraserIcon")
260
+ export const TouchIcon = icon(Touch01Icon, "TouchIcon")
261
+ export const BubbleIcon = icon(BubbleChatIcon, "BubbleIcon")
262
+ export const BrushIcon = icon(PaintBrush02Icon, "BrushIcon")
263
+ export const LayoutIcon = icon(LayoutGridIcon, "LayoutIcon")
264
+ export const ZoomIcon = icon(SearchAddIcon, "ZoomIcon")
265
+ export const SelectorIcon = icon(
266
+ UnfoldMoreIcon,
267
+ "SelectorIcon"
268
+ )
269
+ export const MoreIcon = icon(MoreHorizontalIcon, "MoreIcon")
270
+ export const PeopleIcon = icon(UserMultipleIcon, "PeopleIcon")
271
+ export const AddPersonIcon = icon(
272
+ UserAdd01Icon,
273
+ "AddPersonIcon"
274
+ )
275
+ export const ClockIcon = icon(Clock01Icon, "ClockIcon")
276
+ export const LocationIcon = icon(
277
+ Location01Icon,
278
+ "LocationIcon"
279
+ )
280
+ export const TaskIcon = icon(Task01Icon, "TaskIcon")
281
+ export const FolderIcon = icon(Folder01Icon, "FolderIcon")
282
+ export const RecentIcon = icon(Time04Icon, "RecentIcon")
283
+ export const ThumbsUpIcon = toggleIcon(
284
+ HugeThumbsUpIcon,
285
+ "ThumbsUpIcon"
286
+ )
287
+ export const HeartIcon = toggleIcon(
288
+ FavouriteIcon,
289
+ "HeartIcon"
290
+ )
291
+ export const StarIcon = toggleIcon(HugeStarIcon, "StarIcon")
292
+
293
+ // Wallet: adding, copying and protecting cards.
294
+ export const CameraIcon = icon(Camera01Icon, "CameraIcon")
295
+ export const CopyIcon = icon(Copy01Icon, "CopyIcon")
296
+ export const LockIcon = icon(LockIconSvg, "LockIcon")
297
+ export const NfcReadIcon = icon(NfcIcon, "NfcReadIcon")
298
+ export const PhotoIcon = icon(Image01Icon, "PhotoIcon")
package/src/index.ts ADDED
@@ -0,0 +1,78 @@
1
+ export * from "./components/button/button"
2
+ export * from "./components/text-field/text-field"
3
+ export * from "./components/secure-field/secure-field"
4
+ export * from "./components/text-editor/text-editor"
5
+ export * from "./components/tab-view/tab-view"
6
+ export * from "./components/profile-image/profile-image"
7
+ export * from "./components/search-list/search-list"
8
+ export * from "./components/table/table"
9
+ export * from "./components/button/icon-button"
10
+ export * from "./components/chip/chip"
11
+ export * from "./components/context-menu/context-menu-host"
12
+ export {
13
+ type ContextMenuItem,
14
+ type ContextMenuAlignX,
15
+ type ContextMenuAlignY,
16
+ type ContextMenuEdge,
17
+ closeContextMenu,
18
+ openContextMenu,
19
+ openFollowUpMenu,
20
+ openMenu,
21
+ } from "./components/context-menu/context-menu-store"
22
+ export * from "./components/context-menu/use-context-menu"
23
+ export * from "./components/dialog/dialog-host"
24
+ export {
25
+ type ConfirmOptions,
26
+ confirmDialog,
27
+ } from "./components/dialog/dialog-store"
28
+ export * from "./components/labeled-content/labeled-content"
29
+ export * from "./components/picker/picker"
30
+ export * from "./components/popover/popover"
31
+ export * from "./components/search-field/search-field"
32
+ export * from "./components/section/section"
33
+ export * from "./components/segmented-control/segmented-control"
34
+ export * from "./components/sheet/sheet"
35
+ export * from "./components/sidebar/sidebar"
36
+ export * from "./components/status-indicator/status-indicator"
37
+ export * from "./components/swatch-picker/swatch-picker"
38
+ export * from "./components/tab-bar/tab-bar"
39
+ export {
40
+ type ToastOptions,
41
+ type ToastTone,
42
+ toast,
43
+ } from "./components/toast/toast-store"
44
+ export * from "./components/toast/toaster"
45
+ export * from "./components/toggle/toggle"
46
+ export * from "./components/tooltip/tooltip-host"
47
+ export * from "./hooks/use-anchored-placement"
48
+ export * from "./hooks/use-dismiss"
49
+ export * from "./hooks/use-media-query"
50
+ export * from "./hooks/use-minimize-on-scroll"
51
+ export * from "./icons/icons"
52
+ export * from "./lib/anchored-placement"
53
+ export * from "./lib/class-names"
54
+ export * from "./lib/haptics"
55
+ export * from "./lib/screen-edges"
56
+ export * from "./motion/transitions"
57
+ export * from "./appearance"
58
+
59
+ export * from "./components/date-picker/date-picker"
60
+ export * from "./components/compact-date-picker/compact-date-picker"
61
+ export * from "./components/compact-time-picker/compact-time-picker"
62
+
63
+ export { Dialog } from "./components/dialog"
64
+ export type {
65
+ DialogPresentation,
66
+ DialogTone,
67
+ } from "./components/dialog/dialog"
68
+ export type {
69
+ ContentViewHeadingLevel,
70
+ ContentViewVariant,
71
+ } from "./components/content-view/content-view"
72
+ export type { StatTileTone } from "./components/stat-tile/stat-tile"
73
+
74
+ export * from "./hooks/use-visual-viewport"
75
+
76
+ export { registerFloatingLayer } from "./lib/floating-layers"
77
+
78
+ export * from "./hooks/use-minimize-on-scroll"