@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,265 @@
1
+ import {
2
+ createContext,
3
+ useCallback,
4
+ useContext,
5
+ useId,
6
+ useRef,
7
+ useState,
8
+ useEffect,
9
+ type ComponentProps,
10
+ } from "react"
11
+ import { cn } from "../../lib/class-names"
12
+
13
+ type SearchState = {
14
+ query: string
15
+ setQuery: (query: string) => void
16
+ active: string
17
+ listId: string
18
+ open: boolean
19
+ visible: number
20
+ register: (id: string, visible: boolean | null) => void
21
+ }
22
+ const SearchContext = createContext<SearchState | null>(null)
23
+ function useSearch() {
24
+ const search = useContext(SearchContext)
25
+ if (!search)
26
+ throw new Error(
27
+ "Search list controls need a SearchList parent"
28
+ )
29
+ return search
30
+ }
31
+
32
+ export function SearchList({
33
+ loop: _loop,
34
+ open = true,
35
+ onKeyDown,
36
+ className,
37
+ ...props
38
+ }: ComponentProps<"div"> & {
39
+ loop?: boolean
40
+ open?: boolean
41
+ }) {
42
+ const [query, setQuery] = useState("")
43
+ const [active, setActive] = useState("")
44
+ const [results, setResults] = useState<
45
+ Record<string, boolean>
46
+ >({})
47
+ const listId = useId()
48
+ const ref = useRef<HTMLDivElement>(null)
49
+ const register = useCallback(
50
+ (id: string, visible: boolean | null) =>
51
+ setResults((previous) => {
52
+ if (visible === null) {
53
+ const next = { ...previous }
54
+ delete next[id]
55
+ return next
56
+ }
57
+ if (previous[id] === visible) return previous
58
+ return { ...previous, [id]: visible }
59
+ }),
60
+ []
61
+ )
62
+ return (
63
+ <SearchContext
64
+ value={{
65
+ query,
66
+ setQuery: (next) => {
67
+ setQuery(next)
68
+ setActive("")
69
+ },
70
+ active,
71
+ listId,
72
+ open,
73
+ visible:
74
+ Object.values(results).filter(Boolean)
75
+ .length,
76
+ register,
77
+ }}
78
+ >
79
+ <div
80
+ {...props}
81
+ ref={ref}
82
+ className={cn("relative", className)}
83
+ onKeyDown={(event) => {
84
+ onKeyDown?.(event)
85
+ if (event.defaultPrevented || !open)
86
+ return
87
+ const items = Array.from(
88
+ ref.current?.querySelectorAll<HTMLButtonElement>(
89
+ "[data-search-result]:not([hidden]):not(:disabled)"
90
+ ) ?? []
91
+ )
92
+ if (!items.length) return
93
+ const index = items.findIndex(
94
+ (item) => item.id === active
95
+ )
96
+ if (event.key === "Enter") {
97
+ event.preventDefault()
98
+ ;(items[index] ?? items[0])?.click()
99
+ return
100
+ }
101
+ if (
102
+ event.key !== "ArrowDown" &&
103
+ event.key !== "ArrowUp"
104
+ )
105
+ return
106
+ event.preventDefault()
107
+ const next =
108
+ index < 0
109
+ ? event.key === "ArrowDown"
110
+ ? 0
111
+ : items.length - 1
112
+ : (index +
113
+ (event.key === "ArrowDown"
114
+ ? 1
115
+ : -1) +
116
+ items.length) %
117
+ items.length
118
+ const item = items[next]
119
+ if (!item) return
120
+ setActive(item.id)
121
+ item.scrollIntoView({
122
+ block: "nearest",
123
+ })
124
+ }}
125
+ />
126
+ </SearchContext>
127
+ )
128
+ }
129
+ export function SearchListField({
130
+ value,
131
+ onValueChange,
132
+ className,
133
+ ...props
134
+ }: Omit<ComponentProps<"input">, "value" | "onChange"> & {
135
+ value?: string
136
+ onValueChange?: (value: string) => void
137
+ }) {
138
+ const search = useSearch()
139
+ return (
140
+ <input
141
+ {...props}
142
+ role="combobox"
143
+ aria-autocomplete="list"
144
+ aria-expanded={search.open}
145
+ aria-controls={search.listId}
146
+ aria-activedescendant={
147
+ search.open && search.active
148
+ ? search.active
149
+ : undefined
150
+ }
151
+ aria-label={
152
+ props["aria-label"] ?? props.placeholder
153
+ }
154
+ value={value ?? search.query}
155
+ onChange={(event) => {
156
+ search.setQuery(event.target.value)
157
+ onValueChange?.(event.target.value)
158
+ }}
159
+ className={cn(
160
+ "min-h-11 w-full rounded-full border border-separator bg-fill/60 px-4 text-subheadline text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
161
+ className
162
+ )}
163
+ />
164
+ )
165
+ }
166
+ export function SearchListResults({
167
+ className,
168
+ ...props
169
+ }: ComponentProps<"div">) {
170
+ const search = useSearch()
171
+ return (
172
+ <div
173
+ {...props}
174
+ id={search.listId}
175
+ role="listbox"
176
+ hidden={!search.open}
177
+ className={cn("overflow-y-auto p-1", className)}
178
+ />
179
+ )
180
+ }
181
+ export function SearchListEmpty(
182
+ props: ComponentProps<"div">
183
+ ) {
184
+ const search = useSearch()
185
+ return search.visible ? null : (
186
+ <div {...props} role="status" />
187
+ )
188
+ }
189
+ export function SearchListGroup({
190
+ heading,
191
+ children,
192
+ className,
193
+ ...props
194
+ }: ComponentProps<"fieldset"> & {
195
+ heading?: React.ReactNode
196
+ }) {
197
+ const id = useId()
198
+ return (
199
+ <fieldset
200
+ {...props}
201
+ className={cn(
202
+ "not-has-[[data-search-result]:not([hidden])]:hidden",
203
+ className
204
+ )}
205
+ aria-labelledby={heading ? id : undefined}
206
+ >
207
+ {heading && (
208
+ <legend
209
+ id={id}
210
+ className="px-2 py-2 text-caption font-medium text-label-tertiary"
211
+ >
212
+ {heading}
213
+ </legend>
214
+ )}
215
+ {children}
216
+ </fieldset>
217
+ )
218
+ }
219
+ export function SearchListItem({
220
+ value,
221
+ keywords = [],
222
+ onSelect,
223
+ className,
224
+ ...props
225
+ }: Omit<ComponentProps<"button">, "value" | "onSelect"> & {
226
+ value: string
227
+ keywords?: string[]
228
+ onSelect?: (value: string) => void
229
+ }) {
230
+ const search = useSearch()
231
+ const id = useId()
232
+ const haystack = [value, ...keywords]
233
+ .join(" ")
234
+ .toLocaleLowerCase()
235
+ const visible = search.query
236
+ .toLocaleLowerCase()
237
+ .trim()
238
+ .split(/\s+/)
239
+ .every((word) => haystack.includes(word))
240
+ useEffect(() => {
241
+ search.register(id, visible)
242
+ return () => search.register(id, null)
243
+ }, [id, visible, search.register])
244
+ return (
245
+ <button
246
+ {...props}
247
+ type="button"
248
+ id={id}
249
+ role="option"
250
+ tabIndex={-1}
251
+ hidden={!visible}
252
+ data-search-result=""
253
+ aria-selected={search.active === id}
254
+ data-selected={
255
+ search.active === id ? "true" : undefined
256
+ }
257
+ className={cn(
258
+ "flex w-full items-center gap-2 rounded-xl px-3 py-2 text-left hover:bg-fill aria-selected:bg-accent-soft",
259
+ className
260
+ )}
261
+ onMouseDown={(event) => event.preventDefault()}
262
+ onClick={() => onSelect?.(value)}
263
+ />
264
+ )
265
+ }
@@ -0,0 +1,35 @@
1
+ import { Section, toast } from "@passui/react"
2
+ import { Button } from "@passui/react/button"
3
+
4
+ export function SectionActions() {
5
+ return (
6
+ <div className="flex w-full max-w-sm flex-col gap-6">
7
+ <Section title="Data">
8
+ <Button
9
+ variant="ghost"
10
+ className="w-full justify-start rounded-none"
11
+ onClick={() =>
12
+ toast("Preparing your export")
13
+ }
14
+ >
15
+ Export my data
16
+ </Button>
17
+ <Button
18
+ variant="ghost"
19
+ className="w-full justify-start rounded-none"
20
+ onClick={() => toast("Cache cleared")}
21
+ >
22
+ Clear cache
23
+ </Button>
24
+ </Section>
25
+ <Section footer="This can't be undone.">
26
+ <Button
27
+ variant="ghost"
28
+ className="w-full justify-start rounded-none text-danger"
29
+ >
30
+ Delete account
31
+ </Button>
32
+ </Section>
33
+ </div>
34
+ )
35
+ }
@@ -0,0 +1,41 @@
1
+ import { LabeledContent, Section } from "@passui/react"
2
+ import { Toggle } from "@passui/react/toggle"
3
+ import { useState } from "react"
4
+
5
+ export function SectionGroups() {
6
+ const [passkeys, setPasskeys] = useState(true)
7
+ const [twoStep, setTwoStep] = useState(true)
8
+
9
+ return (
10
+ <div className="flex w-full max-w-sm flex-col gap-6">
11
+ <Section title="Sign in">
12
+ <LabeledContent label="Passkeys">
13
+ <Toggle
14
+ label="Passkeys"
15
+ labelHidden
16
+ checked={passkeys}
17
+ onChange={setPasskeys}
18
+ />
19
+ </LabeledContent>
20
+ <LabeledContent label="Two-step verification">
21
+ <Toggle
22
+ label="Two-step verification"
23
+ labelHidden
24
+ checked={twoStep}
25
+ onChange={setTwoStep}
26
+ />
27
+ </LabeledContent>
28
+ </Section>
29
+ <Section
30
+ title="Recovery"
31
+ footer="Used only if you lose access to every device."
32
+ >
33
+ <LabeledContent label="Recovery email">
34
+ <span className="text-subheadline text-label-secondary">
35
+ a•••@example.com
36
+ </span>
37
+ </LabeledContent>
38
+ </Section>
39
+ </div>
40
+ )
41
+ }
@@ -0,0 +1,63 @@
1
+ import {
2
+ LabeledContent,
3
+ Picker,
4
+ Section,
5
+ } from "@passui/react"
6
+ import { Button } from "@passui/react/button"
7
+ import { Toggle } from "@passui/react/toggle"
8
+ import { useState } from "react"
9
+
10
+ export function SectionShowcase() {
11
+ const [backup, setBackup] = useState(true)
12
+ const [quality, setQuality] = useState("high")
13
+
14
+ return (
15
+ <div className="flex w-full max-w-sm flex-col gap-6">
16
+ <Section
17
+ title="Storage"
18
+ footer="Backups run at night while charging."
19
+ >
20
+ <LabeledContent label="Back up automatically">
21
+ <Toggle
22
+ label="Back up automatically"
23
+ labelHidden
24
+ checked={backup}
25
+ onChange={setBackup}
26
+ />
27
+ </LabeledContent>
28
+ <LabeledContent label="Photo quality">
29
+ <Picker
30
+ label="Photo quality"
31
+ value={quality}
32
+ onChange={setQuality}
33
+ options={[
34
+ { value: "high", label: "High" },
35
+ {
36
+ value: "saver",
37
+ label: "Space saver",
38
+ },
39
+ ]}
40
+ />
41
+ </LabeledContent>
42
+ <LabeledContent
43
+ label="Used"
44
+ description="12.4 GB of 50 GB"
45
+ >
46
+ <span className="text-subheadline text-label-secondary">
47
+ 25%
48
+ </span>
49
+ </LabeledContent>
50
+ </Section>
51
+ <Section>
52
+ <LabeledContent
53
+ label="Free up space"
54
+ description="Removes copies already backed up."
55
+ >
56
+ <Button size="sm" variant="secondary">
57
+ Review
58
+ </Button>
59
+ </LabeledContent>
60
+ </Section>
61
+ </div>
62
+ )
63
+ }
@@ -0,0 +1,19 @@
1
+ import { LabeledContent, Section } from "@passui/react"
2
+
3
+ export function SectionUsage() {
4
+ return (
5
+ <div className="w-full max-w-sm">
6
+ <Section
7
+ title="Account"
8
+ footer="Your name appears on shared notes."
9
+ >
10
+ <LabeledContent label="Name">
11
+ Aiko Kamado
12
+ </LabeledContent>
13
+ <LabeledContent label="Email">
14
+ aiko@example.com
15
+ </LabeledContent>
16
+ </Section>
17
+ </div>
18
+ )
19
+ }
@@ -0,0 +1,51 @@
1
+ import { SectionActions } from "./examples/section-actions"
2
+ import { SectionGroups } from "./examples/section-groups"
3
+ import { defineDocs } from "../../docs/types"
4
+ import { LabeledContent } from "../labeled-content/labeled-content"
5
+ import { SectionShowcase } from "./examples/section-showcase"
6
+ import { SectionUsage } from "./examples/section-usage"
7
+ import { Section } from "./section"
8
+
9
+ type DemoProps = { title?: string; footer?: string }
10
+
11
+ export const sectionDocs = defineDocs<DemoProps>()({
12
+ slug: "section",
13
+ name: "Section",
14
+ importPath: "@passui/react",
15
+ controls: {
16
+ title: { kind: "text", default: "Account" },
17
+ footer: {
18
+ kind: "text",
19
+ default: "Your name appears on shared notes.",
20
+ },
21
+ },
22
+ render: (values) => (
23
+ <div className="w-full max-w-sm">
24
+ <Section {...values}>
25
+ <LabeledContent label="Name">
26
+ <span className="text-subheadline text-label-secondary">
27
+ Aiko Kamado
28
+ </span>
29
+ </LabeledContent>
30
+ <LabeledContent label="Email">
31
+ <span className="text-subheadline text-label-secondary">
32
+ aiko@example.com
33
+ </span>
34
+ </LabeledContent>
35
+ </Section>
36
+ </div>
37
+ ),
38
+ childrenSnippet: () => ({
39
+ imports: [
40
+ 'import { LabeledContent } from "@passui/react"',
41
+ ],
42
+ children:
43
+ '<LabeledContent label="Name">Aiko Kamado</LabeledContent>',
44
+ }),
45
+ usage: SectionUsage,
46
+ showcase: SectionShowcase,
47
+ examples: [
48
+ { id: "groups", component: SectionGroups },
49
+ { id: "actions", component: SectionActions },
50
+ ],
51
+ })
@@ -0,0 +1,41 @@
1
+ import type { ReactNode } from "react"
2
+
3
+ /**
4
+ * A titled, inset group of rows with an optional footer, as in iOS Settings
5
+ * and SwiftUI's grouped `List` with `Section(header:footer:)`. Rows are
6
+ * usually `LabeledContent`; dividers between them come for free.
7
+ */
8
+ export function Section({
9
+ title,
10
+ footer,
11
+ children,
12
+ }: {
13
+ /** iOS groups can stand without a header. */
14
+ title?: string
15
+ footer?: ReactNode
16
+ children?: ReactNode
17
+ }) {
18
+ return (
19
+ <section
20
+ aria-label={title}
21
+ className="flex flex-col gap-2"
22
+ >
23
+ {title && (
24
+ <h2 className="px-4 text-footnote font-medium text-label-tertiary">
25
+ {title}
26
+ </h2>
27
+ )}
28
+ {/* A group can be only a note, e.g. where to find something on this device. */}
29
+ {children ? (
30
+ <div className="flex flex-col divide-y divide-separator/60 overflow-hidden rounded-3xl border border-separator/60 bg-elevated">
31
+ {children}
32
+ </div>
33
+ ) : null}
34
+ {footer && (
35
+ <p className="px-4 text-caption leading-snug text-label-tertiary">
36
+ {footer}
37
+ </p>
38
+ )}
39
+ </section>
40
+ )
41
+ }
@@ -0,0 +1,56 @@
1
+ import { Button } from "@passui/react/button"
2
+ import { SecureField } from "@passui/react/secure-field"
3
+ import { toast } from "@passui/react"
4
+ import { useState } from "react"
5
+
6
+ export function SecureFieldNewPassword() {
7
+ const [next, setNext] = useState("")
8
+ const [again, setAgain] = useState("")
9
+ const short = next.length > 0 && next.length < 12
10
+ const differs = again.length > 0 && again !== next
11
+
12
+ return (
13
+ <form
14
+ className="flex w-full max-w-sm flex-col gap-4 rounded-4xl bg-elevated p-6 shadow-sm ring-1 ring-separator/60"
15
+ onSubmit={(event) => {
16
+ event.preventDefault()
17
+ toast.success("Password changed")
18
+ }}
19
+ >
20
+ <SecureField
21
+ label="New password"
22
+ autoComplete="new-password"
23
+ description="At least 12 characters."
24
+ errors={
25
+ short
26
+ ? ["Use at least 12 characters."]
27
+ : []
28
+ }
29
+ value={next}
30
+ onChange={(event) =>
31
+ setNext(event.target.value)
32
+ }
33
+ />
34
+ <SecureField
35
+ label="Confirm password"
36
+ autoComplete="new-password"
37
+ errors={
38
+ differs
39
+ ? ["The passwords don't match."]
40
+ : []
41
+ }
42
+ value={again}
43
+ onChange={(event) =>
44
+ setAgain(event.target.value)
45
+ }
46
+ />
47
+ <Button
48
+ type="submit"
49
+ variant="accent"
50
+ disabled={!next || short || differs || !again}
51
+ >
52
+ Change password
53
+ </Button>
54
+ </form>
55
+ )
56
+ }
@@ -0,0 +1,20 @@
1
+ import { SecureField } from "@passui/react/secure-field"
2
+ import { useState } from "react"
3
+
4
+ export function SecureFieldReveal() {
5
+ const [password, setPassword] = useState("correct-horse")
6
+
7
+ return (
8
+ <div className="w-full max-w-xs">
9
+ <SecureField
10
+ label="Password"
11
+ autoComplete="current-password"
12
+ revealLabels={{ show: "Show", hide: "Hide" }}
13
+ value={password}
14
+ onChange={(event) =>
15
+ setPassword(event.target.value)
16
+ }
17
+ />
18
+ </div>
19
+ )
20
+ }
@@ -0,0 +1,62 @@
1
+ import { toast } from "@passui/react"
2
+ import { Button } from "@passui/react/button"
3
+ import { SecureField } from "@passui/react/secure-field"
4
+ import { useState } from "react"
5
+
6
+ export function SecureFieldShowcase() {
7
+ const [current, setCurrent] = useState("")
8
+ const [next, setNext] = useState("")
9
+ const tooShort = next.length > 0 && next.length < 8
10
+ const reveal = { show: "Show", hide: "Hide" }
11
+
12
+ return (
13
+ <form
14
+ className="flex w-full max-w-sm flex-col gap-4 rounded-4xl bg-elevated p-6 shadow-sm ring-1 ring-separator/60"
15
+ onSubmit={(event) => {
16
+ event.preventDefault()
17
+ toast.success("Password changed")
18
+ }}
19
+ >
20
+ <div className="flex flex-col gap-1">
21
+ <h2 className="text-headline font-semibold text-label">
22
+ Change password
23
+ </h2>
24
+ <p className="text-footnote text-label-secondary">
25
+ You stay signed in on this device.
26
+ </p>
27
+ </div>
28
+ <SecureField
29
+ label="Current password"
30
+ autoComplete="current-password"
31
+ revealLabels={reveal}
32
+ value={current}
33
+ onChange={(event) =>
34
+ setCurrent(event.target.value)
35
+ }
36
+ />
37
+ <SecureField
38
+ label="New password"
39
+ autoComplete="new-password"
40
+ description="At least 8 characters."
41
+ revealLabels={reveal}
42
+ aria-invalid={tooShort}
43
+ errors={
44
+ tooShort
45
+ ? ["Use 8 characters or more."]
46
+ : []
47
+ }
48
+ value={next}
49
+ onChange={(event) =>
50
+ setNext(event.target.value)
51
+ }
52
+ />
53
+ <Button
54
+ type="submit"
55
+ variant="accent"
56
+ disabled={!current || next.length < 8}
57
+ >
58
+ Change password
59
+ </Button>
60
+ </form>
61
+ )
62
+ }
@@ -0,0 +1,19 @@
1
+ import { SecureField } from "@passui/react/secure-field"
2
+ import { useState } from "react"
3
+
4
+ export function SecureFieldUsage() {
5
+ const [password, setPassword] = useState("")
6
+ return (
7
+ <div className="w-full max-w-xs">
8
+ <SecureField
9
+ label="Password"
10
+ autoComplete="current-password"
11
+ revealLabels={{ show: "Show", hide: "Hide" }}
12
+ value={password}
13
+ onChange={(event) =>
14
+ setPassword(event.target.value)
15
+ }
16
+ />
17
+ </div>
18
+ )
19
+ }