@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,85 @@
1
+ import { motion } from "motion/react"
2
+ import { useEffect, useRef } from "react"
3
+ import { cn } from "../../lib/class-names"
4
+ import { haptic } from "../../lib/haptics"
5
+ import { spring } from "../../motion/transitions"
6
+
7
+ export type ToggleStyle = "switch" | "checkbox"
8
+
9
+ export function Toggle({
10
+ checked,
11
+ onChange,
12
+ label,
13
+ disabled,
14
+ style = "switch",
15
+ mixed = false,
16
+ 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
+ }) {
27
+ const input = useRef<HTMLInputElement>(null)
28
+ useEffect(() => {
29
+ if (input.current) input.current.indeterminate = mixed
30
+ }, [mixed])
31
+ if (style === "checkbox") {
32
+ 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"
46
+ )}
47
+ />
48
+ <span
49
+ className={
50
+ labelHidden ? "sr-only" : undefined
51
+ }
52
+ >
53
+ {label}
54
+ </span>
55
+ </label>
56
+ )
57
+ }
58
+ return (
59
+ <button
60
+ type="button"
61
+ role="switch"
62
+ aria-checked={checked}
63
+ aria-label={label}
64
+ disabled={disabled}
65
+ onClick={() => {
66
+ haptic("selection")
67
+ onChange(!checked)
68
+ }}
69
+ className={cn(
70
+ "group/switch flex h-[30px] w-[50px] shrink-0 items-center rounded-full p-[2px] transition-colors duration-normal",
71
+ "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"
75
+ )}
76
+ >
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
+ />
83
+ </button>
84
+ )
85
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ Copy01Icon,
3
+ Delete02Icon,
4
+ Share08Icon,
5
+ } from "@hugeicons/core-free-icons"
6
+ import { HugeiconsIcon } from "@hugeicons/react"
7
+ import { IconButton } from "@passui/react/icon-button"
8
+
9
+ export function TooltipIconButtons() {
10
+ // IconButton shows its label as a tooltip by itself
11
+ return (
12
+ <div className="flex gap-2">
13
+ <IconButton label="Copy" variant="secondary">
14
+ <HugeiconsIcon icon={Copy01Icon} size={20} />
15
+ </IconButton>
16
+ <IconButton label="Share" variant="secondary">
17
+ <HugeiconsIcon icon={Share08Icon} size={20} />
18
+ </IconButton>
19
+ <IconButton label="Delete" variant="secondary">
20
+ <HugeiconsIcon
21
+ icon={Delete02Icon}
22
+ size={20}
23
+ />
24
+ </IconButton>
25
+ </div>
26
+ )
27
+ }
@@ -0,0 +1,38 @@
1
+ import {
2
+ TextBoldIcon,
3
+ TextItalicIcon,
4
+ TextUnderlineIcon,
5
+ } from "@hugeicons/core-free-icons"
6
+ import { HugeiconsIcon } from "@hugeicons/react"
7
+ import { Button } from "@passui/react/button"
8
+
9
+ const tools = [
10
+ { label: "Bold", keys: "⌘B", icon: TextBoldIcon },
11
+ { label: "Italic", keys: "⌘I", icon: TextItalicIcon },
12
+ {
13
+ label: "Underline",
14
+ keys: "⌘U",
15
+ icon: TextUnderlineIcon,
16
+ },
17
+ ]
18
+
19
+ export function TooltipShortcuts() {
20
+ return (
21
+ <div className="flex gap-1 rounded-full bg-elevated p-1 shadow-sm ring-1 ring-separator/60">
22
+ {tools.map((tool) => (
23
+ <Button
24
+ key={tool.label}
25
+ variant="ghost"
26
+ size="icon-sm"
27
+ aria-label={tool.label}
28
+ data-tooltip={`${tool.label} ${tool.keys}`}
29
+ >
30
+ <HugeiconsIcon
31
+ icon={tool.icon}
32
+ size={18}
33
+ />
34
+ </Button>
35
+ ))}
36
+ </div>
37
+ )
38
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ Archive01Icon,
3
+ Delete02Icon,
4
+ MailReply01Icon,
5
+ StarIcon,
6
+ } from "@hugeicons/core-free-icons"
7
+ import { HugeiconsIcon } from "@hugeicons/react"
8
+ import { IconButton } from "@passui/react/icon-button"
9
+
10
+ const actions = [
11
+ { label: "Reply", icon: MailReply01Icon },
12
+ { label: "Star", icon: StarIcon },
13
+ { label: "Archive", icon: Archive01Icon },
14
+ { label: "Delete", icon: Delete02Icon },
15
+ ]
16
+
17
+ export function TooltipShowcase() {
18
+ return (
19
+ <div className="flex w-full max-w-sm flex-col gap-4 rounded-4xl bg-elevated p-5 shadow-sm ring-1 ring-separator/60">
20
+ <div className="flex items-center justify-between">
21
+ <span className="text-headline font-semibold text-label">
22
+ Inbox
23
+ </span>
24
+ <div className="flex gap-1">
25
+ {actions.map((action) => (
26
+ <IconButton
27
+ key={action.label}
28
+ label={action.label}
29
+ variant="ghost"
30
+ >
31
+ <HugeiconsIcon
32
+ icon={action.icon}
33
+ size={20}
34
+ />
35
+ </IconButton>
36
+ ))}
37
+ </div>
38
+ </div>
39
+ <p className="m-0 text-callout text-label-secondary">
40
+ Hover an icon to see what it does.
41
+ </p>
42
+ </div>
43
+ )
44
+ }
@@ -0,0 +1,13 @@
1
+ import { Button } from "@passui/react/button"
2
+
3
+ export function TooltipUsage() {
4
+ // render one <TooltipHost /> near the root of your app
5
+ return (
6
+ <Button
7
+ variant="secondary"
8
+ data-tooltip="Saves a copy to your downloads"
9
+ >
10
+ Download
11
+ </Button>
12
+ )
13
+ }
@@ -0,0 +1,156 @@
1
+ import { AnimatePresence, motion } from "motion/react"
2
+ import { useEffect, useState } from "react"
3
+
4
+ interface Tip {
5
+ text: string
6
+ x: number
7
+ y: number
8
+ /** Shown above the element when there's no room below. */
9
+ above: boolean
10
+ }
11
+
12
+ const DELAY = 450
13
+ /** Moving from one control to the next within this window shows at once. */
14
+ const WARM_WINDOW = 600
15
+ const MARGIN = 8
16
+
17
+ /**
18
+ * Tooltips for any element with `data-tooltip`, on devices that can hover.
19
+ * One listener for the whole app: a short delay before the first, then
20
+ * instant while moving between controls, as on macOS.
21
+ */
22
+ export function TooltipHost() {
23
+ const [tip, setTip] = useState<Tip | null>(null)
24
+
25
+ useEffect(() => {
26
+ if (
27
+ !window.matchMedia(
28
+ "(hover: hover) and (pointer: fine)"
29
+ ).matches
30
+ )
31
+ return
32
+ let timer: ReturnType<typeof setTimeout> | undefined
33
+ let current: Element | null = null
34
+ let lastShown = 0
35
+
36
+ const show = (element: Element) => {
37
+ const text = element.getAttribute("data-tooltip")
38
+ if (!text) return
39
+ const rect = element.getBoundingClientRect()
40
+ const above =
41
+ rect.bottom + 40 > window.innerHeight
42
+ setTip({
43
+ text,
44
+ x: rect.left + rect.width / 2,
45
+ y: above
46
+ ? rect.top - MARGIN
47
+ : rect.bottom + MARGIN,
48
+ above,
49
+ })
50
+ lastShown = Date.now()
51
+ }
52
+ const hide = () => {
53
+ clearTimeout(timer)
54
+ if (current) lastShown = Date.now()
55
+ current = null
56
+ setTip(null)
57
+ }
58
+ const onOver = (event: PointerEvent) => {
59
+ const element =
60
+ (event.target as Element | null)?.closest?.(
61
+ "[data-tooltip]"
62
+ ) ?? null
63
+ if (element === current) return
64
+ clearTimeout(timer)
65
+ current = element
66
+ if (!element) {
67
+ setTip(null)
68
+ return
69
+ }
70
+ const warm = Date.now() - lastShown < WARM_WINDOW
71
+ if (warm) show(element)
72
+ else {
73
+ setTip(null)
74
+ timer = setTimeout(
75
+ () =>
76
+ current === element && show(element),
77
+ DELAY
78
+ )
79
+ }
80
+ }
81
+
82
+ window.document.addEventListener(
83
+ "pointerover",
84
+ onOver
85
+ )
86
+ window.document.addEventListener(
87
+ "pointerdown",
88
+ hide,
89
+ true
90
+ )
91
+ window.document.addEventListener(
92
+ "keydown",
93
+ hide,
94
+ true
95
+ )
96
+ window.addEventListener("scroll", hide, true)
97
+ window.addEventListener("blur", hide)
98
+ return () => {
99
+ clearTimeout(timer)
100
+ window.document.removeEventListener(
101
+ "pointerover",
102
+ onOver
103
+ )
104
+ window.document.removeEventListener(
105
+ "pointerdown",
106
+ hide,
107
+ true
108
+ )
109
+ window.document.removeEventListener(
110
+ "keydown",
111
+ hide,
112
+ true
113
+ )
114
+ window.removeEventListener("scroll", hide, true)
115
+ window.removeEventListener("blur", hide)
116
+ }
117
+ }, [])
118
+
119
+ return (
120
+ <AnimatePresence>
121
+ {tip && (
122
+ <motion.div
123
+ key={tip.text + tip.x}
124
+ role="tooltip"
125
+ className="pointer-events-none fixed z-[80] max-w-[240px] rounded-md bg-inverse px-2.5 py-1.5 text-center text-caption leading-tight font-medium text-on-inverse shadow-lg"
126
+ style={{
127
+ left: Math.min(
128
+ Math.max(tip.x, 128),
129
+ window.innerWidth - 128
130
+ ),
131
+ top: tip.y,
132
+ translateX: "-50%",
133
+ translateY: tip.above ? "-100%" : 0,
134
+ }}
135
+ initial={{
136
+ opacity: 0,
137
+ scale: 0.92,
138
+ y: tip.above ? 4 : -4,
139
+ }}
140
+ animate={{ opacity: 1, scale: 1, y: 0 }}
141
+ exit={{
142
+ opacity: 0,
143
+ transition: { duration: 0.08 },
144
+ }}
145
+ transition={{
146
+ type: "spring",
147
+ stiffness: 520,
148
+ damping: 34,
149
+ }}
150
+ >
151
+ {tip.text}
152
+ </motion.div>
153
+ )}
154
+ </AnimatePresence>
155
+ )
156
+ }
@@ -0,0 +1,56 @@
1
+ import { TooltipShortcuts } from "./examples/tooltip-shortcuts"
2
+ import { Copy01Icon } from "@hugeicons/core-free-icons"
3
+ import { HugeiconsIcon } from "@hugeicons/react"
4
+ import { Button } from "../button/button"
5
+ import { jsxAttribute, jsxText } from "../../docs/snippet"
6
+ import { defineDocs } from "../../docs/types"
7
+ import { TooltipIconButtons } from "./examples/tooltip-icon-buttons"
8
+ import { TooltipShowcase } from "./examples/tooltip-showcase"
9
+ import { TooltipUsage } from "./examples/tooltip-usage"
10
+
11
+ // The host shows a tip for any element with data-tooltip; the playground
12
+ // sets that attribute on a button.
13
+ type DemoProps = {
14
+ tooltip: string
15
+ }
16
+
17
+ export const tooltipDocs = defineDocs<DemoProps>()({
18
+ slug: "tooltip",
19
+ name: "TooltipHost",
20
+ importPath: "@passui/react",
21
+ controls: {
22
+ tooltip: {
23
+ kind: "text",
24
+ default: "Copy to clipboard",
25
+ },
26
+ },
27
+ render: ({ tooltip }) => (
28
+ <Button variant="secondary" data-tooltip={tooltip}>
29
+ <HugeiconsIcon icon={Copy01Icon} size={18} />
30
+ Hover me
31
+ </Button>
32
+ ),
33
+ childrenSnippet: ({ tooltip }) => ({
34
+ imports: [
35
+ 'import { Button } from "@passui/react/button"',
36
+ ],
37
+ element: "div",
38
+ omit: ["tooltip"],
39
+ children: [
40
+ "",
41
+ " {/* once, near the root of your app */}",
42
+ " <TooltipHost />",
43
+ ` <Button ${jsxAttribute("data-tooltip", tooltip)}>`,
44
+ ` ${jsxText("Copy")}`,
45
+ " </Button>",
46
+ "",
47
+ ].join("\n"),
48
+ }),
49
+ usage: TooltipUsage,
50
+ showcase: TooltipShowcase,
51
+ examples: [
52
+ { id: "icon-buttons", component: TooltipIconButtons },
53
+ { id: "shortcuts", component: TooltipShortcuts },
54
+ ],
55
+ previewHeight: 200,
56
+ })
@@ -0,0 +1,27 @@
1
+ import { Button } from "@passui/react/button"
2
+ import { ViewController } from "@passui/react/view-controller"
3
+
4
+ export function ViewControllerAction() {
5
+ return (
6
+ <ViewController
7
+ className="py-6"
8
+ heading={
9
+ <ViewController.Heading
10
+ title="Applications"
11
+ description="Apps that sign people in with Pass."
12
+ action={
13
+ <Button
14
+ variant="accent"
15
+ size="sm"
16
+ className="ms-auto"
17
+ >
18
+ New application
19
+ </Button>
20
+ }
21
+ />
22
+ }
23
+ >
24
+ <div className="h-24 rounded-3xl bg-fill/60" />
25
+ </ViewController>
26
+ )
27
+ }
@@ -0,0 +1,31 @@
1
+ import { LabeledContent, Section } from "@passui/react"
2
+ import { ViewController } from "@passui/react/view-controller"
3
+
4
+ export function ViewControllerSettings() {
5
+ return (
6
+ <div className="w-full max-w-lg rounded-4xl bg-background px-6 shadow-sm ring-1 ring-separator/60">
7
+ <ViewController
8
+ className="py-8"
9
+ heading={
10
+ <ViewController.Heading
11
+ title="Personal info"
12
+ description="Your name and how to reach you."
13
+ />
14
+ }
15
+ >
16
+ <Section title="Basics">
17
+ <LabeledContent label="Name">
18
+ <span className="text-subheadline text-label-secondary">
19
+ Aiko Kamado
20
+ </span>
21
+ </LabeledContent>
22
+ <LabeledContent label="Birthday">
23
+ <span className="text-subheadline text-label-secondary">
24
+ 21 July
25
+ </span>
26
+ </LabeledContent>
27
+ </Section>
28
+ </ViewController>
29
+ </div>
30
+ )
31
+ }
@@ -0,0 +1,45 @@
1
+ import { toast } from "@passui/react"
2
+ import { Button } from "@passui/react/button"
3
+ import { TextField } from "@passui/react/text-field"
4
+ import { ViewController } from "@passui/react/view-controller"
5
+ import { useState } from "react"
6
+
7
+ export function ViewControllerShowcase() {
8
+ const [email, setEmail] = useState("")
9
+
10
+ return (
11
+ <div className="w-full max-w-sm rounded-4xl bg-elevated px-6 shadow-sm ring-1 ring-separator/60">
12
+ <ViewController
13
+ className="gap-6 py-8"
14
+ heading={
15
+ <ViewController.Heading
16
+ size="compact"
17
+ title="Sign in"
18
+ description="Use your Pass account."
19
+ />
20
+ }
21
+ >
22
+ <form
23
+ className="flex flex-col gap-4"
24
+ onSubmit={(event) => {
25
+ event.preventDefault()
26
+ toast.success("Check your email")
27
+ }}
28
+ >
29
+ <TextField
30
+ label="Email"
31
+ type="email"
32
+ placeholder="you@example.com"
33
+ value={email}
34
+ onChange={(event) =>
35
+ setEmail(event.target.value)
36
+ }
37
+ />
38
+ <Button type="submit" variant="accent">
39
+ Continue
40
+ </Button>
41
+ </form>
42
+ </ViewController>
43
+ </div>
44
+ )
45
+ }
@@ -0,0 +1,19 @@
1
+ import { ViewController } from "@passui/react/view-controller"
2
+
3
+ export function ViewControllerUsage() {
4
+ return (
5
+ <ViewController
6
+ className="py-6"
7
+ heading={
8
+ <ViewController.Heading
9
+ title="Devices"
10
+ description="Where you're signed in right now."
11
+ />
12
+ }
13
+ >
14
+ <p className="text-callout text-label-secondary">
15
+ The screen's content goes here.
16
+ </p>
17
+ </ViewController>
18
+ )
19
+ }
@@ -0,0 +1 @@
1
+ export * from "./view-controller"
@@ -0,0 +1,71 @@
1
+ import { ViewControllerSettings } from "./examples/view-controller-settings"
2
+ import { Button } from "../button/button"
3
+ import { jsxAttribute } from "../../docs/snippet"
4
+ import { defineDocs } from "../../docs/types"
5
+ import { ViewControllerAction } from "./examples/view-controller-action"
6
+ import { ViewControllerShowcase } from "./examples/view-controller-showcase"
7
+ import { ViewControllerUsage } from "./examples/view-controller-usage"
8
+ import { ViewController } from "./view-controller"
9
+
10
+ // The controls belong to ViewController.Heading, passed as `heading`;
11
+ // the snippet writes that element itself.
12
+ type DemoProps = {
13
+ title: string
14
+ description: string
15
+ size: "default" | "compact"
16
+ }
17
+
18
+ export const viewControllerDocs = defineDocs<DemoProps>()({
19
+ slug: "view-controller",
20
+ name: "ViewController",
21
+ importPath: "@passui/react/view-controller",
22
+ controls: {
23
+ title: { kind: "text", default: "Security" },
24
+ description: {
25
+ kind: "text",
26
+ default:
27
+ "Choose how you sign in and which devices stay signed in.",
28
+ },
29
+ size: {
30
+ kind: "select",
31
+ options: { default: true, compact: true },
32
+ default: "default",
33
+ },
34
+ },
35
+ render: ({ title, description, size }) => (
36
+ <ViewController
37
+ className="py-6"
38
+ heading={
39
+ <ViewController.Heading
40
+ title={title}
41
+ description={description || undefined}
42
+ size={size}
43
+ />
44
+ }
45
+ >
46
+ <Button
47
+ variant="secondary"
48
+ className="self-start"
49
+ >
50
+ Change password
51
+ </Button>
52
+ </ViewController>
53
+ ),
54
+ childrenSnippet: ({ title, description, size }) => ({
55
+ imports: [],
56
+ omit: ["title", "description", "size"],
57
+ expressionProps: {
58
+ heading: `<ViewController.Heading ${jsxAttribute("title", title)}${description ? ` ${jsxAttribute("description", description)}` : ""}${size === "compact" ? ' size="compact"' : ""} />`,
59
+ },
60
+ children: "\n {/* the screen's content */}\n",
61
+ }),
62
+ usage: ViewControllerUsage,
63
+ showcase: ViewControllerShowcase,
64
+ examples: [
65
+ {
66
+ id: "action",
67
+ component: ViewControllerAction,
68
+ },
69
+ { id: "settings", component: ViewControllerSettings },
70
+ ],
71
+ })