@passui/react 0.0.0-stage → 0.1.14

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 +117 -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,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"
@@ -0,0 +1,92 @@
1
+ import { screenEdges } from "./screen-edges"
2
+
3
+ export interface AnchoredPlacement {
4
+ left: number
5
+ width: number
6
+ top?: number
7
+ bottom?: number
8
+ /** Opens upward, so motion should grow from the bottom edge. */
9
+ above: boolean
10
+ /**
11
+ * Set when the chosen side has less room than the panel needs: the
12
+ * panel scrolls inside this height instead of crossing the edge.
13
+ */
14
+ maxHeight?: number
15
+ }
16
+
17
+ /** The measurements placement decides on, so the math stays testable. */
18
+ export interface PlacementFrame {
19
+ anchor: { top: number; bottom: number; left: number }
20
+ viewport: { width: number; height: number }
21
+ edges: {
22
+ top: number
23
+ right: number
24
+ bottom: number
25
+ left: number
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Places a panel of `width` under its anchor, or above it when there's
31
+ * less than `needed` height below and more room above, kept inside the
32
+ * screen's safe edges.
33
+ */
34
+ export function placeInViewport(
35
+ { anchor, viewport, edges }: PlacementFrame,
36
+ width: number,
37
+ needed: number,
38
+ gap = 6
39
+ ): AnchoredPlacement {
40
+ const fitted = Math.min(
41
+ width,
42
+ viewport.width - edges.left - edges.right
43
+ )
44
+ const left = Math.min(
45
+ Math.max(edges.left, anchor.left),
46
+ viewport.width - fitted - edges.right
47
+ )
48
+ const roomBelow =
49
+ viewport.height - anchor.bottom - gap - edges.bottom
50
+ const roomAbove = anchor.top - gap - edges.top
51
+ const above = roomBelow < needed && roomAbove > roomBelow
52
+ const room = above ? roomAbove : roomBelow
53
+ const placement: AnchoredPlacement = {
54
+ left,
55
+ width: fitted,
56
+ above,
57
+ ...(above
58
+ ? { bottom: viewport.height - anchor.top + gap }
59
+ : { top: anchor.bottom + gap }),
60
+ }
61
+ if (room < needed) placement.maxHeight = room
62
+ return placement
63
+ }
64
+
65
+ /** `placeInViewport` from live DOM measurements. */
66
+ export function placeAnchored(
67
+ anchor: HTMLElement,
68
+ width: number,
69
+ needed: number,
70
+ gap = 6
71
+ ): AnchoredPlacement {
72
+ const rect = anchor.getBoundingClientRect()
73
+ const viewport = window.visualViewport
74
+ return placeInViewport(
75
+ {
76
+ anchor: {
77
+ top: rect.top,
78
+ bottom: rect.bottom,
79
+ left: rect.left,
80
+ },
81
+ viewport: {
82
+ width: viewport?.width ?? window.innerWidth,
83
+ height:
84
+ viewport?.height ?? window.innerHeight,
85
+ },
86
+ edges: screenEdges(),
87
+ },
88
+ width,
89
+ needed,
90
+ gap
91
+ )
92
+ }