@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,59 @@
1
+ import { LabeledContent, Section } from "@passui/react"
2
+ import { Toggle } from "@passui/react/toggle"
3
+ import { useState } from "react"
4
+
5
+ export function ToggleShowcase() {
6
+ const [airplane, setAirplane] = useState(false)
7
+ const [wifi, setWifi] = useState(true)
8
+ const [bluetooth, setBluetooth] = useState(true)
9
+ const [focus, setFocus] = useState(false)
10
+
11
+ return (
12
+ <div className="flex w-full max-w-sm flex-col gap-6">
13
+ <Section title="Connections">
14
+ <LabeledContent label="Airplane mode">
15
+ <Toggle
16
+ label="Airplane mode"
17
+ labelHidden
18
+ checked={airplane}
19
+ onChange={setAirplane}
20
+ />
21
+ </LabeledContent>
22
+ <LabeledContent
23
+ label="Wi-Fi"
24
+ description={wifi ? "Home" : "Off"}
25
+ >
26
+ <Toggle
27
+ label="Wi-Fi"
28
+ labelHidden
29
+ checked={wifi}
30
+ disabled={airplane}
31
+ onChange={setWifi}
32
+ />
33
+ </LabeledContent>
34
+ <LabeledContent label="Bluetooth">
35
+ <Toggle
36
+ label="Bluetooth"
37
+ labelHidden
38
+ checked={bluetooth}
39
+ disabled={airplane}
40
+ onChange={setBluetooth}
41
+ />
42
+ </LabeledContent>
43
+ </Section>
44
+ <Section
45
+ title="Focus"
46
+ footer="Silences calls and alerts until you turn it off."
47
+ >
48
+ <LabeledContent label="Do not disturb">
49
+ <Toggle
50
+ label="Do not disturb"
51
+ labelHidden
52
+ checked={focus}
53
+ onChange={setFocus}
54
+ />
55
+ </LabeledContent>
56
+ </Section>
57
+ </div>
58
+ )
59
+ }
@@ -0,0 +1,13 @@
1
+ import { Toggle } from "@passui/react/toggle"
2
+ import { useState } from "react"
3
+
4
+ export function ToggleUsage() {
5
+ const [enabled, setEnabled] = useState(true)
6
+ return (
7
+ <Toggle
8
+ label="Wi-Fi"
9
+ checked={enabled}
10
+ onChange={setEnabled}
11
+ />
12
+ )
13
+ }
@@ -0,0 +1,69 @@
1
+ import type { ComponentProps } from "react"
2
+ import { useState } from "react"
3
+ import { defineDocs } from "../../docs/types"
4
+ import { ToggleCheckboxes } from "./examples/toggle-checkboxes"
5
+ import { ToggleSettings } from "./examples/toggle-settings"
6
+ import { ToggleShowcase } from "./examples/toggle-showcase"
7
+ import { ToggleUsage } from "./examples/toggle-usage"
8
+ import { Toggle } from "./toggle"
9
+
10
+ type ToggleProps = ComponentProps<typeof Toggle>
11
+ type DemoProps = Omit<ToggleProps, "checked" | "onChange">
12
+
13
+ /** Holds its own state so the playground can flip it. */
14
+ function ToggleDemo(props: DemoProps) {
15
+ const [checked, setChecked] = useState(true)
16
+ return (
17
+ <Toggle
18
+ {...props}
19
+ checked={checked}
20
+ onChange={setChecked}
21
+ />
22
+ )
23
+ }
24
+
25
+ export const toggleDocs = defineDocs<ToggleProps>()({
26
+ slug: "toggle",
27
+ name: "Toggle",
28
+ importPath: "@passui/react/toggle",
29
+ controls: {
30
+ style: {
31
+ kind: "select",
32
+ options: { switch: true, checkbox: true },
33
+ default: "switch",
34
+ },
35
+ disabled: { kind: "boolean", default: false },
36
+ mixed: { kind: "boolean", default: false },
37
+ labelHidden: { kind: "boolean", default: false },
38
+ label: {
39
+ kind: "text",
40
+ default: "Wi-Fi",
41
+ required: true,
42
+ },
43
+ },
44
+ render: (values) => <ToggleDemo {...values} />,
45
+ childrenSnippet: () => ({
46
+ imports: ['import { useState } from "react"'],
47
+ setup: [
48
+ "const [checked, setChecked] = useState(true)",
49
+ ],
50
+ children: "",
51
+ expressionProps: {
52
+ checked: "checked",
53
+ onChange: "setChecked",
54
+ },
55
+ }),
56
+ usage: ToggleUsage,
57
+ showcase: ToggleShowcase,
58
+ examples: [
59
+ { id: "settings", component: ToggleSettings },
60
+ {
61
+ id: "checkboxes",
62
+ component: ToggleCheckboxes,
63
+ playground: {
64
+ style: "checkbox",
65
+ label: "Remember me",
66
+ },
67
+ },
68
+ ],
69
+ })
@@ -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
+ }