@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,80 @@
1
+ import {
2
+ createContext,
3
+ useContext,
4
+ useEffect,
5
+ useState,
6
+ type ComponentProps,
7
+ } from "react"
8
+ import { cn } from "../../lib/class-names"
9
+
10
+ const ImageContext = createContext<{
11
+ loaded: boolean
12
+ setLoaded: (loaded: boolean) => void
13
+ }>({ loaded: false, setLoaded: () => {} })
14
+ export function ProfileImage({
15
+ className,
16
+ ...props
17
+ }: ComponentProps<"span">) {
18
+ const [loaded, setLoaded] = useState(false)
19
+ return (
20
+ <ImageContext value={{ loaded, setLoaded }}>
21
+ <span
22
+ {...props}
23
+ className={cn(
24
+ "relative inline-flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-full bg-fill text-label-secondary",
25
+ className
26
+ )}
27
+ />
28
+ </ImageContext>
29
+ )
30
+ }
31
+ export function ProfileImageContent({
32
+ src,
33
+ className,
34
+ onLoad,
35
+ onError,
36
+ ...props
37
+ }: ComponentProps<"img">) {
38
+ const { setLoaded } = useContext(ImageContext)
39
+ useEffect(() => {
40
+ if (src) setLoaded(false)
41
+ }, [src, setLoaded])
42
+ if (!src) return null
43
+ return (
44
+ <img
45
+ key={src}
46
+ alt=""
47
+ {...props}
48
+ src={src}
49
+ className={cn(
50
+ "absolute inset-0 size-full object-cover",
51
+ className
52
+ )}
53
+ onLoad={(event) => {
54
+ setLoaded(true)
55
+ onLoad?.(event)
56
+ }}
57
+ onError={(event) => {
58
+ setLoaded(false)
59
+ onError?.(event)
60
+ event.currentTarget.style.visibility =
61
+ "hidden"
62
+ }}
63
+ />
64
+ )
65
+ }
66
+ export function ProfileImageFallback({
67
+ className,
68
+ ...props
69
+ }: ComponentProps<"span">) {
70
+ const { loaded } = useContext(ImageContext)
71
+ return loaded ? null : (
72
+ <span
73
+ {...props}
74
+ className={cn(
75
+ "text-footnote font-medium",
76
+ className
77
+ )}
78
+ />
79
+ )
80
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ LabeledContent,
3
+ SearchField,
4
+ Section,
5
+ } from "@passui/react"
6
+ import { useState } from "react"
7
+
8
+ const apps = [
9
+ { name: "Pass console", people: 12 },
10
+ { name: "Accounts", people: 1284 },
11
+ { name: "Notables", people: 431 },
12
+ { name: "Showcase", people: 58 },
13
+ ]
14
+
15
+ export function SearchFieldFilter() {
16
+ const [query, setQuery] = useState("")
17
+ const matches = apps.filter((app) =>
18
+ app.name
19
+ .toLowerCase()
20
+ .includes(query.trim().toLowerCase())
21
+ )
22
+
23
+ return (
24
+ <div className="flex w-full max-w-sm flex-col gap-3">
25
+ <SearchField
26
+ aria-label="Search applications"
27
+ placeholder="Search applications"
28
+ value={query}
29
+ onChange={(event) =>
30
+ setQuery(event.target.value)
31
+ }
32
+ />
33
+ <Section>
34
+ {matches.length ? (
35
+ matches.map((app) => (
36
+ <LabeledContent
37
+ key={app.name}
38
+ label={app.name}
39
+ >
40
+ <span className="text-footnote text-label-secondary tabular-nums">
41
+ {app.people} people
42
+ </span>
43
+ </LabeledContent>
44
+ ))
45
+ ) : (
46
+ <p className="px-4 py-6 text-center text-footnote text-label-secondary">
47
+ No applications match “{query}”
48
+ </p>
49
+ )}
50
+ </Section>
51
+ </div>
52
+ )
53
+ }
@@ -0,0 +1,56 @@
1
+ import { SearchField } from "@passui/react"
2
+ import { useState } from "react"
3
+
4
+ const people = [
5
+ "Aiko Kamado",
6
+ "Ben Ito",
7
+ "Chiara Rossi",
8
+ "Dev Patel",
9
+ "Emma Laurent",
10
+ "Farah Haddad",
11
+ ]
12
+
13
+ export function SearchFieldShowcase() {
14
+ const [query, setQuery] = useState("")
15
+ const results = people.filter((person) =>
16
+ person
17
+ .toLowerCase()
18
+ .includes(query.trim().toLowerCase())
19
+ )
20
+
21
+ return (
22
+ <div className="flex w-full max-w-sm flex-col gap-3 rounded-4xl bg-elevated p-4 shadow-sm ring-1 ring-separator/60">
23
+ <SearchField
24
+ aria-label="Search people"
25
+ placeholder="Search people"
26
+ value={query}
27
+ onChange={(event) =>
28
+ setQuery(event.target.value)
29
+ }
30
+ />
31
+ <ul className="m-0 flex list-none flex-col p-0">
32
+ {results.map((person) => (
33
+ <li
34
+ key={person}
35
+ className="flex items-center gap-3 rounded-2xl px-2 py-2 hover:bg-fill/60"
36
+ >
37
+ <span className="flex size-8 items-center justify-center rounded-full bg-accent-soft text-caption font-bold text-accent-text">
38
+ {person
39
+ .split(" ")
40
+ .map((part) => part[0])
41
+ .join("")}
42
+ </span>
43
+ <span className="text-subheadline text-label">
44
+ {person}
45
+ </span>
46
+ </li>
47
+ ))}
48
+ {results.length === 0 && (
49
+ <li className="px-2 py-6 text-center text-footnote text-label-secondary">
50
+ No one matches “{query}”.
51
+ </li>
52
+ )}
53
+ </ul>
54
+ </div>
55
+ )
56
+ }
@@ -0,0 +1,27 @@
1
+ import { SearchField } from "@passui/react"
2
+ import { Button } from "@passui/react/button"
3
+ import { useState } from "react"
4
+
5
+ export function SearchFieldToolbar() {
6
+ const [query, setQuery] = useState("")
7
+
8
+ return (
9
+ <div className="flex w-full max-w-lg items-center gap-2 rounded-4xl bg-elevated p-2 shadow-sm ring-1 ring-separator/60">
10
+ <SearchField
11
+ aria-label="Search people"
12
+ placeholder="Search people"
13
+ className="flex-1"
14
+ value={query}
15
+ onChange={(event) =>
16
+ setQuery(event.target.value)
17
+ }
18
+ />
19
+ <Button variant="secondary" size="sm">
20
+ Filters
21
+ </Button>
22
+ <Button variant="accent" size="sm">
23
+ Invite
24
+ </Button>
25
+ </div>
26
+ )
27
+ }
@@ -0,0 +1,18 @@
1
+ import { SearchField } from "@passui/react"
2
+ import { useState } from "react"
3
+
4
+ export function SearchFieldUsage() {
5
+ const [query, setQuery] = useState("")
6
+ return (
7
+ <div className="w-full max-w-xs">
8
+ <SearchField
9
+ aria-label="Search notes"
10
+ placeholder="Search notes"
11
+ value={query}
12
+ onChange={(event) =>
13
+ setQuery(event.target.value)
14
+ }
15
+ />
16
+ </div>
17
+ )
18
+ }
@@ -0,0 +1,58 @@
1
+ import { SearchFieldToolbar } from "./examples/search-field-toolbar"
2
+ import { SearchFieldFilter } from "./examples/search-field-filter"
3
+ import type { InputHTMLAttributes } from "react"
4
+ import { useState } from "react"
5
+ import { defineDocs } from "../../docs/types"
6
+ import { SearchFieldShowcase } from "./examples/search-field-showcase"
7
+ import { SearchFieldUsage } from "./examples/search-field-usage"
8
+ import { SearchField } from "./search-field"
9
+
10
+ type SearchFieldProps = InputHTMLAttributes<HTMLInputElement>
11
+
12
+ function SearchFieldDemo(
13
+ props: Pick<SearchFieldProps, "placeholder" | "disabled">
14
+ ) {
15
+ const [query, setQuery] = useState("")
16
+ return (
17
+ <div className="w-full max-w-xs">
18
+ <SearchField
19
+ {...props}
20
+ aria-label="Search"
21
+ value={query}
22
+ onChange={(event) =>
23
+ setQuery(event.target.value)
24
+ }
25
+ />
26
+ </div>
27
+ )
28
+ }
29
+
30
+ export const searchFieldDocs = defineDocs<SearchFieldProps>()(
31
+ {
32
+ slug: "search-field",
33
+ name: "SearchField",
34
+ importPath: "@passui/react",
35
+ controls: {
36
+ placeholder: { kind: "text", default: "Search" },
37
+ disabled: { kind: "boolean", default: false },
38
+ },
39
+ render: (values) => <SearchFieldDemo {...values} />,
40
+ childrenSnippet: () => ({
41
+ imports: ['import { useState } from "react"'],
42
+ setup: ['const [query, setQuery] = useState("")'],
43
+ children: "",
44
+ extraProps: { "aria-label": "Search" },
45
+ expressionProps: {
46
+ value: "query",
47
+ onChange:
48
+ "(event) => setQuery(event.target.value)",
49
+ },
50
+ }),
51
+ usage: SearchFieldUsage,
52
+ showcase: SearchFieldShowcase,
53
+ examples: [
54
+ { id: "filter", component: SearchFieldFilter },
55
+ { id: "toolbar", component: SearchFieldToolbar },
56
+ ],
57
+ }
58
+ )
@@ -0,0 +1,29 @@
1
+ import { forwardRef, type InputHTMLAttributes } from "react"
2
+ import { SearchIcon } from "../../icons/icons"
3
+ import { cn } from "../../lib/class-names"
4
+
5
+ export const SearchField = forwardRef<
6
+ HTMLInputElement,
7
+ InputHTMLAttributes<HTMLInputElement>
8
+ >(function SearchField(
9
+ { className, placeholder = "Search", ...props },
10
+ ref
11
+ ) {
12
+ return (
13
+ <label
14
+ className={cn(
15
+ "control-field flex items-center gap-2 rounded-lg px-2.5 py-2 text-subheadline text-label-tertiary",
16
+ className
17
+ )}
18
+ >
19
+ <SearchIcon size={15} strokeWidth={2} />
20
+ <input
21
+ ref={ref}
22
+ type="search"
23
+ placeholder={placeholder}
24
+ className="min-w-0 grow bg-transparent text-label outline-none placeholder:text-label-tertiary"
25
+ {...props}
26
+ />
27
+ </label>
28
+ )
29
+ })
@@ -0,0 +1,53 @@
1
+ import { toast } from "@passui/react"
2
+ import {
3
+ SearchList,
4
+ SearchListEmpty,
5
+ SearchListField,
6
+ SearchListItem,
7
+ SearchListResults,
8
+ } from "@passui/react/search-list"
9
+ import { useState } from "react"
10
+
11
+ const pages = [
12
+ "Profile",
13
+ "Password",
14
+ "Passkeys",
15
+ "Devices",
16
+ "Language",
17
+ "Privacy",
18
+ ]
19
+
20
+ export function SearchListDropdown() {
21
+ const [open, setOpen] = useState(false)
22
+
23
+ return (
24
+ <div className="h-72 w-full max-w-xs">
25
+ <SearchList open={open} className="w-full">
26
+ <SearchListField
27
+ placeholder="Search your account"
28
+ onFocus={() => setOpen(true)}
29
+ // let a click on a result land before closing
30
+ onBlur={() =>
31
+ setTimeout(() => setOpen(false), 120)
32
+ }
33
+ />
34
+ <SearchListResults className="glass-menu absolute inset-x-0 top-full z-10 mt-2 max-h-56 rounded-4xl">
35
+ {pages.map((page) => (
36
+ <SearchListItem
37
+ key={page}
38
+ value={page}
39
+ onSelect={(value) =>
40
+ toast(`Opened ${value}`)
41
+ }
42
+ >
43
+ {page}
44
+ </SearchListItem>
45
+ ))}
46
+ <SearchListEmpty className="px-3 py-4 text-footnote text-label-secondary">
47
+ No pages found
48
+ </SearchListEmpty>
49
+ </SearchListResults>
50
+ </SearchList>
51
+ </div>
52
+ )
53
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ SearchList,
3
+ SearchListEmpty,
4
+ SearchListField,
5
+ SearchListGroup,
6
+ SearchListItem,
7
+ SearchListResults,
8
+ } from "@passui/react/search-list"
9
+
10
+ const groups = [
11
+ {
12
+ heading: "Security",
13
+ items: [
14
+ { value: "Password", keywords: ["sign in"] },
15
+ {
16
+ value: "Passkeys",
17
+ keywords: ["face", "touch"],
18
+ },
19
+ {
20
+ value: "Two-step verification",
21
+ keywords: ["2fa", "code"],
22
+ },
23
+ ],
24
+ },
25
+ {
26
+ heading: "Preferences",
27
+ items: [
28
+ { value: "Language", keywords: ["locale"] },
29
+ {
30
+ value: "Appearance",
31
+ keywords: ["dark", "theme"],
32
+ },
33
+ ],
34
+ },
35
+ ]
36
+
37
+ export function SearchListGroups() {
38
+ return (
39
+ <SearchList className="w-full max-w-xs rounded-3xl bg-elevated p-2 ring-1 ring-separator/60">
40
+ <SearchListField placeholder="Try 2fa or dark" />
41
+ <SearchListResults className="mt-2 max-h-72">
42
+ {groups.map((group) => (
43
+ <SearchListGroup
44
+ key={group.heading}
45
+ heading={group.heading}
46
+ >
47
+ {group.items.map((item) => (
48
+ <SearchListItem
49
+ key={item.value}
50
+ value={item.value}
51
+ keywords={item.keywords}
52
+ >
53
+ {item.value}
54
+ </SearchListItem>
55
+ ))}
56
+ </SearchListGroup>
57
+ ))}
58
+ <SearchListEmpty className="px-3 py-6 text-center text-footnote text-label-secondary">
59
+ Nothing matches that search
60
+ </SearchListEmpty>
61
+ </SearchListResults>
62
+ </SearchList>
63
+ )
64
+ }
@@ -0,0 +1,67 @@
1
+ import { toast } from "@passui/react"
2
+ import {
3
+ SearchList,
4
+ SearchListEmpty,
5
+ SearchListField,
6
+ SearchListGroup,
7
+ SearchListItem,
8
+ SearchListResults,
9
+ } from "@passui/react/search-list"
10
+
11
+ const people = [
12
+ { name: "Aiko Kamado", role: "Design" },
13
+ { name: "Kenji Mori", role: "Engineering" },
14
+ { name: "Mira Patel", role: "Support" },
15
+ ]
16
+
17
+ const actions = [
18
+ { name: "New project", keywords: ["create"] },
19
+ { name: "Invite people", keywords: ["team", "share"] },
20
+ { name: "Open settings", keywords: ["preferences"] },
21
+ ]
22
+
23
+ export function SearchListShowcase() {
24
+ return (
25
+ <SearchList className="w-full max-w-sm rounded-4xl bg-elevated p-3 shadow-sm ring-1 ring-separator/60">
26
+ <SearchListField placeholder="Search people and actions" />
27
+ <SearchListResults className="mt-2 max-h-80">
28
+ <SearchListGroup heading="People">
29
+ {people.map((person) => (
30
+ <SearchListItem
31
+ key={person.name}
32
+ value={person.name}
33
+ keywords={[person.role]}
34
+ onSelect={(name) =>
35
+ toast(`Opened ${name}`)
36
+ }
37
+ >
38
+ <span className="flex-1 text-subheadline text-label">
39
+ {person.name}
40
+ </span>
41
+ <span className="text-caption text-label-tertiary">
42
+ {person.role}
43
+ </span>
44
+ </SearchListItem>
45
+ ))}
46
+ </SearchListGroup>
47
+ <SearchListGroup heading="Actions">
48
+ {actions.map((action) => (
49
+ <SearchListItem
50
+ key={action.name}
51
+ value={action.name}
52
+ keywords={action.keywords}
53
+ onSelect={(name) => toast(name)}
54
+ >
55
+ <span className="text-subheadline text-label">
56
+ {action.name}
57
+ </span>
58
+ </SearchListItem>
59
+ ))}
60
+ </SearchListGroup>
61
+ <SearchListEmpty className="px-3 py-8 text-center text-footnote text-label-secondary">
62
+ No people or actions match
63
+ </SearchListEmpty>
64
+ </SearchListResults>
65
+ </SearchList>
66
+ )
67
+ }
@@ -0,0 +1,27 @@
1
+ import {
2
+ SearchList,
3
+ SearchListEmpty,
4
+ SearchListField,
5
+ SearchListItem,
6
+ SearchListResults,
7
+ } from "@passui/react/search-list"
8
+
9
+ const fruits = ["Apple", "Banana", "Cherry", "Mango"]
10
+
11
+ export function SearchListUsage() {
12
+ return (
13
+ <SearchList className="w-full max-w-xs">
14
+ <SearchListField placeholder="Search fruit" />
15
+ <SearchListResults className="mt-2">
16
+ {fruits.map((fruit) => (
17
+ <SearchListItem key={fruit} value={fruit}>
18
+ {fruit}
19
+ </SearchListItem>
20
+ ))}
21
+ <SearchListEmpty className="px-3 py-4 text-footnote text-label-secondary">
22
+ No fruit found
23
+ </SearchListEmpty>
24
+ </SearchListResults>
25
+ </SearchList>
26
+ )
27
+ }
@@ -0,0 +1,87 @@
1
+ import { SearchListDropdown } from "./examples/search-list-dropdown"
2
+ import { jsxAttribute, jsxText } from "../../docs/snippet"
3
+ import { defineDocs } from "../../docs/types"
4
+ import { SearchListGroups } from "./examples/search-list-groups"
5
+ import { SearchListShowcase } from "./examples/search-list-showcase"
6
+ import { SearchListUsage } from "./examples/search-list-usage"
7
+ import {
8
+ SearchList,
9
+ SearchListEmpty,
10
+ SearchListField,
11
+ SearchListItem,
12
+ SearchListResults,
13
+ } from "./search-list"
14
+
15
+ // `placeholder` belongs to SearchListField, `empty` to SearchListEmpty;
16
+ // the snippet writes both into the children.
17
+ type DemoProps = {
18
+ open?: boolean
19
+ placeholder: string
20
+ empty: string
21
+ }
22
+
23
+ const settings = [
24
+ "Password",
25
+ "Passkeys",
26
+ "Two-step verification",
27
+ "Devices",
28
+ "Language",
29
+ ]
30
+
31
+ export const searchListDocs = defineDocs<DemoProps>()({
32
+ slug: "search-list",
33
+ name: "SearchList",
34
+ importPath: "@passui/react/search-list",
35
+ controls: {
36
+ open: { kind: "boolean", default: true },
37
+ placeholder: {
38
+ kind: "text",
39
+ default: "Search settings",
40
+ },
41
+ empty: { kind: "text", default: "No results" },
42
+ },
43
+ render: ({ open, placeholder, empty }) => (
44
+ <SearchList
45
+ open={open}
46
+ className="w-full max-w-xs rounded-3xl bg-elevated p-2 ring-1 ring-separator/60"
47
+ >
48
+ <SearchListField placeholder={placeholder} />
49
+ <SearchListResults className="mt-2 max-h-56">
50
+ {settings.map((item) => (
51
+ <SearchListItem key={item} value={item}>
52
+ {item}
53
+ </SearchListItem>
54
+ ))}
55
+ <SearchListEmpty className="px-3 py-6 text-center text-footnote text-label-secondary">
56
+ {empty}
57
+ </SearchListEmpty>
58
+ </SearchListResults>
59
+ </SearchList>
60
+ ),
61
+ childrenSnippet: ({ placeholder, empty }) => ({
62
+ imports: [
63
+ 'import { SearchListEmpty, SearchListField, SearchListItem, SearchListResults } from "@passui/react/search-list"',
64
+ ],
65
+ omit: ["placeholder", "empty"],
66
+ children: [
67
+ "",
68
+ ` <SearchListField ${jsxAttribute("placeholder", placeholder)} />`,
69
+ " <SearchListResults>",
70
+ ' <SearchListItem value="Password">Password</SearchListItem>',
71
+ ' <SearchListItem value="Passkeys">Passkeys</SearchListItem>',
72
+ ` <SearchListEmpty>${jsxText(empty)}</SearchListEmpty>`,
73
+ " </SearchListResults>",
74
+ "",
75
+ ].join("\n"),
76
+ }),
77
+ usage: SearchListUsage,
78
+ showcase: SearchListShowcase,
79
+ examples: [
80
+ { id: "groups", component: SearchListGroups },
81
+ {
82
+ id: "dropdown",
83
+ component: SearchListDropdown,
84
+ playground: { open: false },
85
+ },
86
+ ],
87
+ })