@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,38 @@
1
+ import {
2
+ NextIcon,
3
+ PauseIcon,
4
+ PlayIcon,
5
+ PreviousIcon,
6
+ } from "@hugeicons/core-free-icons"
7
+ import { HugeiconsIcon } from "@hugeicons/react"
8
+ import { IconButton } from "@passui/react/icon-button"
9
+ import { useState } from "react"
10
+
11
+ export function IconButtonPlayer() {
12
+ const [playing, setPlaying] = useState(false)
13
+
14
+ return (
15
+ <div className="flex items-center gap-3">
16
+ <IconButton label="Previous" variant="ghost">
17
+ <HugeiconsIcon
18
+ icon={PreviousIcon}
19
+ size={20}
20
+ />
21
+ </IconButton>
22
+ <IconButton
23
+ label={playing ? "Pause" : "Play"}
24
+ variant="accent"
25
+ size="icon-lg"
26
+ onClick={() => setPlaying((value) => !value)}
27
+ >
28
+ <HugeiconsIcon
29
+ icon={playing ? PauseIcon : PlayIcon}
30
+ size={22}
31
+ />
32
+ </IconButton>
33
+ <IconButton label="Next" variant="ghost">
34
+ <HugeiconsIcon icon={NextIcon} size={20} />
35
+ </IconButton>
36
+ </div>
37
+ )
38
+ }
@@ -0,0 +1,83 @@
1
+ import {
2
+ ArrowLeft01Icon,
3
+ Bookmark02Icon,
4
+ MoreHorizontalIcon,
5
+ Share08Icon,
6
+ } from "@hugeicons/core-free-icons"
7
+ import { HugeiconsIcon } from "@hugeicons/react"
8
+ import { toast } from "@passui/react"
9
+ import { IconButton } from "@passui/react/icon-button"
10
+ import { useState } from "react"
11
+
12
+ export function IconButtonShowcase() {
13
+ const [saved, setSaved] = useState(false)
14
+
15
+ return (
16
+ <article className="flex w-full max-w-sm flex-col overflow-hidden rounded-4xl bg-elevated shadow-sm ring-1 ring-separator/60">
17
+ <header className="flex items-center justify-between p-3">
18
+ <IconButton
19
+ label="Back"
20
+ variant="ghost"
21
+ size="icon-sm"
22
+ >
23
+ <HugeiconsIcon
24
+ icon={ArrowLeft01Icon}
25
+ size={18}
26
+ />
27
+ </IconButton>
28
+ <div className="flex gap-1">
29
+ <IconButton
30
+ label={
31
+ saved
32
+ ? "Remove bookmark"
33
+ : "Bookmark"
34
+ }
35
+ variant={saved ? "accent" : "ghost"}
36
+ size="icon-sm"
37
+ onClick={() =>
38
+ setSaved((value) => !value)
39
+ }
40
+ >
41
+ <HugeiconsIcon
42
+ icon={Bookmark02Icon}
43
+ size={18}
44
+ />
45
+ </IconButton>
46
+ <IconButton
47
+ label="Share"
48
+ variant="ghost"
49
+ size="icon-sm"
50
+ onClick={() =>
51
+ toast.success("Link copied")
52
+ }
53
+ >
54
+ <HugeiconsIcon
55
+ icon={Share08Icon}
56
+ size={18}
57
+ />
58
+ </IconButton>
59
+ <IconButton
60
+ label="More"
61
+ variant="ghost"
62
+ size="icon-sm"
63
+ >
64
+ <HugeiconsIcon
65
+ icon={MoreHorizontalIcon}
66
+ size={18}
67
+ />
68
+ </IconButton>
69
+ </div>
70
+ </header>
71
+ <div className="h-36 bg-accent-soft" />
72
+ <div className="flex flex-col gap-1 p-5">
73
+ <h2 className="text-headline font-semibold text-label">
74
+ Designing for calm
75
+ </h2>
76
+ <p className="text-footnote text-label-secondary">
77
+ Six minutes · Saved items stay on every
78
+ device.
79
+ </p>
80
+ </div>
81
+ </article>
82
+ )
83
+ }
@@ -0,0 +1,11 @@
1
+ import { Settings02Icon } from "@hugeicons/core-free-icons"
2
+ import { HugeiconsIcon } from "@hugeicons/react"
3
+ import { IconButton } from "@passui/react/icon-button"
4
+
5
+ export function IconButtonUsage() {
6
+ return (
7
+ <IconButton label="Settings" variant="ghost">
8
+ <HugeiconsIcon icon={Settings02Icon} size={20} />
9
+ </IconButton>
10
+ )
11
+ }
@@ -0,0 +1,39 @@
1
+ import {
2
+ Delete02Icon,
3
+ FavouriteIcon,
4
+ PlusSignIcon,
5
+ Settings02Icon,
6
+ } from "@hugeicons/core-free-icons"
7
+ import { HugeiconsIcon } from "@hugeicons/react"
8
+ import { IconButton } from "@passui/react/icon-button"
9
+
10
+ export function IconButtonVariants() {
11
+ return (
12
+ <div className="flex flex-wrap items-center justify-center gap-3">
13
+ <IconButton label="Add" variant="accent">
14
+ <HugeiconsIcon
15
+ icon={PlusSignIcon}
16
+ size={20}
17
+ />
18
+ </IconButton>
19
+ <IconButton label="Like" variant="secondary">
20
+ <HugeiconsIcon
21
+ icon={FavouriteIcon}
22
+ size={20}
23
+ />
24
+ </IconButton>
25
+ <IconButton label="Settings" variant="outline">
26
+ <HugeiconsIcon
27
+ icon={Settings02Icon}
28
+ size={20}
29
+ />
30
+ </IconButton>
31
+ <IconButton label="Delete" variant="destructive">
32
+ <HugeiconsIcon
33
+ icon={Delete02Icon}
34
+ size={20}
35
+ />
36
+ </IconButton>
37
+ </div>
38
+ )
39
+ }
@@ -0,0 +1,68 @@
1
+ import { Settings02Icon } from "@hugeicons/core-free-icons"
2
+ import { HugeiconsIcon } from "@hugeicons/react"
3
+ import { defineDocs } from "../../docs/types"
4
+ import { IconButtonPlayer } from "./examples/icon-button-player"
5
+ import { IconButtonShowcase } from "./examples/icon-button-showcase"
6
+ import { IconButtonUsage } from "./examples/icon-button-usage"
7
+ import { IconButtonVariants } from "./examples/icon-button-variants"
8
+ import {
9
+ IconButton,
10
+ type IconButtonProps,
11
+ } from "./icon-button"
12
+
13
+ export const iconButtonDocs = defineDocs<IconButtonProps>()({
14
+ slug: "icon-button",
15
+ name: "IconButton",
16
+ importPath: "@passui/react/icon-button",
17
+ controls: {
18
+ variant: {
19
+ kind: "select",
20
+ options: {
21
+ default: true,
22
+ secondary: true,
23
+ outline: true,
24
+ ghost: true,
25
+ link: true,
26
+ accent: true,
27
+ destructive: true,
28
+ },
29
+ default: "ghost",
30
+ },
31
+ size: {
32
+ kind: "select",
33
+ options: {
34
+ icon: true,
35
+ "icon-xs": true,
36
+ "icon-sm": true,
37
+ "icon-lg": true,
38
+ },
39
+ default: "icon",
40
+ },
41
+ isPending: { kind: "boolean", default: false },
42
+ disabled: { kind: "boolean", default: false },
43
+ label: {
44
+ kind: "text",
45
+ default: "Settings",
46
+ required: true,
47
+ },
48
+ },
49
+ render: (values) => (
50
+ <IconButton {...values}>
51
+ <HugeiconsIcon icon={Settings02Icon} size={20} />
52
+ </IconButton>
53
+ ),
54
+ childrenSnippet: () => ({
55
+ imports: [
56
+ 'import { Settings02Icon } from "@hugeicons/core-free-icons"',
57
+ 'import { HugeiconsIcon } from "@hugeicons/react"',
58
+ ],
59
+ children:
60
+ "<HugeiconsIcon icon={Settings02Icon} size={20} />",
61
+ }),
62
+ usage: IconButtonUsage,
63
+ showcase: IconButtonShowcase,
64
+ examples: [
65
+ { id: "variants", component: IconButtonVariants },
66
+ { id: "player", component: IconButtonPlayer },
67
+ ],
68
+ })
@@ -0,0 +1,73 @@
1
+ import { type ButtonHTMLAttributes, forwardRef } from "react"
2
+ import { cn } from "../../lib/class-names"
3
+ import { PendingSpinner } from "./pending-spinner"
4
+ import { usePendingIndicator } from "./use-pending-indicator"
5
+ import {
6
+ buttonVariants,
7
+ iconButtonSizes,
8
+ type ButtonVariant,
9
+ type IconButtonSize,
10
+ } from "./button-styles"
11
+
12
+ export type { IconButtonSize }
13
+
14
+ export interface IconButtonProps
15
+ extends ButtonHTMLAttributes<HTMLButtonElement> {
16
+ /** Required: icon-only buttons need an accessible name. */
17
+ label: string
18
+ variant?: ButtonVariant
19
+ size?: IconButtonSize
20
+ isPending?: boolean
21
+ }
22
+
23
+ export const IconButton = forwardRef<
24
+ HTMLButtonElement,
25
+ IconButtonProps
26
+ >(function IconButton(
27
+ {
28
+ label,
29
+ variant = "default",
30
+ size = "icon",
31
+ isPending = false,
32
+ disabled,
33
+ children,
34
+ className,
35
+ type = "button",
36
+ ...props
37
+ },
38
+ ref
39
+ ) {
40
+ const showPendingIndicator =
41
+ usePendingIndicator(isPending)
42
+
43
+ return (
44
+ <button
45
+ ref={ref}
46
+ type={type}
47
+ aria-label={label}
48
+ data-tooltip={label}
49
+ className={cn(
50
+ "relative",
51
+ "group inline-flex shrink-0 items-center justify-center rounded-full transition-[background-color,filter,transform,box-shadow] duration-fast ease-standard active:scale-[0.92] disabled:pointer-events-none disabled:opacity-40 data-pending:cursor-progress data-pending:disabled:opacity-100",
52
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/70 focus-visible:ring-offset-1 focus-visible:ring-offset-background",
53
+ iconButtonSizes[size],
54
+ buttonVariants[variant],
55
+ className
56
+ )}
57
+ {...props}
58
+ data-pending={isPending || undefined}
59
+ disabled={disabled || isPending}
60
+ aria-busy={isPending || props["aria-busy"]}
61
+ >
62
+ <span
63
+ className={cn(
64
+ "inline-flex items-center justify-center transition-opacity duration-200",
65
+ showPendingIndicator && "opacity-0"
66
+ )}
67
+ >
68
+ {children}
69
+ </span>
70
+ {showPendingIndicator && <PendingSpinner />}
71
+ </button>
72
+ )
73
+ })
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Centred over the button while it is pending: fades in, spins, and is
3
+ * sized from the text so it suits every button size.
4
+ */
5
+ export function PendingSpinner() {
6
+ return (
7
+ <span
8
+ aria-hidden="true"
9
+ className="absolute inset-0 flex animate-in items-center justify-center fade-in zoom-in-75 duration-200 motion-reduce:animate-none"
10
+ >
11
+ <span className="size-[1.1em] animate-spin rounded-full border-2 border-current/25 border-t-current motion-reduce:animate-none" />
12
+ </span>
13
+ )
14
+ }
@@ -0,0 +1,27 @@
1
+ import { useEffect, useState } from "react"
2
+
3
+ const PENDING_INDICATOR_DELAY = 300
4
+
5
+ export function schedulePendingIndicator(onShow: () => void) {
6
+ const timeout = setTimeout(
7
+ onShow,
8
+ PENDING_INDICATOR_DELAY
9
+ )
10
+ return () => clearTimeout(timeout)
11
+ }
12
+
13
+ export function usePendingIndicator(isPending: boolean) {
14
+ const [isVisible, setIsVisible] = useState(false)
15
+
16
+ useEffect(() => {
17
+ setIsVisible(false)
18
+
19
+ if (!isPending) return
20
+
21
+ return schedulePendingIndicator(() =>
22
+ setIsVisible(true)
23
+ )
24
+ }, [isPending])
25
+
26
+ return isPending && isVisible
27
+ }
@@ -0,0 +1,37 @@
1
+ import { ChipInRows } from "./examples/chip-in-rows"
2
+ import { defineDocs } from "../../docs/types"
3
+ import { Chip, type ChipTone } from "./chip"
4
+ import { ChipShowcase } from "./examples/chip-showcase"
5
+ import { ChipTones } from "./examples/chip-tones"
6
+ import { ChipUsage } from "./examples/chip-usage"
7
+
8
+ type ChipProps = { tone?: ChipTone; children?: string }
9
+
10
+ export const chipDocs = defineDocs<ChipProps>()({
11
+ slug: "chip",
12
+ name: "Chip",
13
+ importPath: "@passui/react",
14
+ controls: {
15
+ tone: {
16
+ kind: "select",
17
+ options: {
18
+ neutral: true,
19
+ accent: true,
20
+ public: true,
21
+ family: true,
22
+ friends: true,
23
+ },
24
+ default: "neutral",
25
+ },
26
+ children: { kind: "text", default: "Draft" },
27
+ },
28
+ render: ({ children, ...values }) => (
29
+ <Chip {...values}>{children}</Chip>
30
+ ),
31
+ usage: ChipUsage,
32
+ showcase: ChipShowcase,
33
+ examples: [
34
+ { id: "tones", component: ChipTones },
35
+ { id: "in-rows", component: ChipInRows },
36
+ ],
37
+ })
@@ -0,0 +1,35 @@
1
+ import type { HTMLAttributes } from "react"
2
+ import { cn } from "../../lib/class-names"
3
+
4
+ export type ChipTone =
5
+ | "neutral"
6
+ | "accent"
7
+ | "public"
8
+ | "family"
9
+ | "friends"
10
+
11
+ const tones: Record<ChipTone, string> = {
12
+ neutral: "bg-fill text-label-secondary",
13
+ accent: "bg-elevated text-accent-text",
14
+ public: "bg-[#e8efff] text-[#2c55b8] dark:bg-[#1c2740] dark:text-[#a9c1ff]",
15
+ family: "bg-[#e3f3eb] text-[#24704f] dark:bg-[#15302a] dark:text-[#8fd6b6]",
16
+ friends:
17
+ "bg-[#ede8ff] text-[#5a3fc0] dark:bg-[#26203f] dark:text-[#c3b5ff]",
18
+ }
19
+
20
+ export function Chip({
21
+ tone = "neutral",
22
+ className,
23
+ ...props
24
+ }: HTMLAttributes<HTMLSpanElement> & { tone?: ChipTone }) {
25
+ return (
26
+ <span
27
+ className={cn(
28
+ "inline-flex items-center gap-1 rounded-full px-1.75 py-0.5 text-caption2 font-semibold",
29
+ tones[tone],
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ )
35
+ }
@@ -0,0 +1,19 @@
1
+ import { Chip, LabeledContent, Section } from "@passui/react"
2
+
3
+ export function ChipInRows() {
4
+ return (
5
+ <div className="w-full max-w-sm">
6
+ <Section title="Shared with">
7
+ <LabeledContent label="Aiko Kamado">
8
+ <Chip tone="accent">Owner</Chip>
9
+ </LabeledContent>
10
+ <LabeledContent label="Kenji Mori">
11
+ <Chip tone="family">Family</Chip>
12
+ </LabeledContent>
13
+ <LabeledContent label="Anyone with the link">
14
+ <Chip tone="public">Public</Chip>
15
+ </LabeledContent>
16
+ </Section>
17
+ </div>
18
+ )
19
+ }
@@ -0,0 +1,51 @@
1
+ import { Chip } from "@passui/react"
2
+
3
+ const notes = [
4
+ {
5
+ title: "Trip to Kyoto",
6
+ detail: "Edited today",
7
+ tone: "friends",
8
+ tag: "Friends",
9
+ },
10
+ {
11
+ title: "Grandma's recipes",
12
+ detail: "Edited yesterday",
13
+ tone: "family",
14
+ tag: "Family",
15
+ },
16
+ {
17
+ title: "Reading list",
18
+ detail: "Edited Monday",
19
+ tone: "public",
20
+ tag: "Public",
21
+ },
22
+ {
23
+ title: "Q4 plan",
24
+ detail: "Edited last week",
25
+ tone: "neutral",
26
+ tag: "Draft",
27
+ },
28
+ ] as const
29
+
30
+ export function ChipShowcase() {
31
+ return (
32
+ <ul className="m-0 flex w-full max-w-sm list-none flex-col divide-y divide-separator/60 overflow-hidden rounded-4xl bg-elevated p-0 shadow-sm ring-1 ring-separator/60">
33
+ {notes.map((note) => (
34
+ <li
35
+ key={note.title}
36
+ className="flex items-center justify-between gap-3 px-5 py-3.5"
37
+ >
38
+ <div className="flex flex-col">
39
+ <span className="text-subheadline font-semibold text-label">
40
+ {note.title}
41
+ </span>
42
+ <span className="text-footnote text-label-secondary">
43
+ {note.detail}
44
+ </span>
45
+ </div>
46
+ <Chip tone={note.tone}>{note.tag}</Chip>
47
+ </li>
48
+ ))}
49
+ </ul>
50
+ )
51
+ }
@@ -0,0 +1,13 @@
1
+ import { Chip } from "@passui/react"
2
+
3
+ export function ChipTones() {
4
+ return (
5
+ <div className="flex flex-wrap items-center justify-center gap-2">
6
+ <Chip tone="neutral">Draft</Chip>
7
+ <Chip tone="accent">Featured</Chip>
8
+ <Chip tone="public">Public</Chip>
9
+ <Chip tone="family">Family</Chip>
10
+ <Chip tone="friends">Friends</Chip>
11
+ </div>
12
+ )
13
+ }
@@ -0,0 +1,5 @@
1
+ import { Chip } from "@passui/react"
2
+
3
+ export function ChipUsage() {
4
+ return <Chip tone="accent">New</Chip>
5
+ }
@@ -0,0 +1,131 @@
1
+ /** A calendar day, with `month` counted from 1. */
2
+ export interface CalendarDay {
3
+ year: number
4
+ month: number
5
+ day: number
6
+ }
7
+
8
+ /** Reads `YYYY-MM-DD`; null for anything else, or a date that doesn't exist. */
9
+ export function parseDay(value: string): CalendarDay | null {
10
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value)
11
+ if (!match) return null
12
+ const [year, month, day] = [
13
+ Number(match[1]),
14
+ Number(match[2]),
15
+ Number(match[3]),
16
+ ]
17
+ const date = new Date(Date.UTC(year, month - 1, day))
18
+ const real =
19
+ date.getUTCFullYear() === year &&
20
+ date.getUTCMonth() === month - 1 &&
21
+ date.getUTCDate() === day
22
+ return real ? { year, month, day } : null
23
+ }
24
+
25
+ export function formatDay({
26
+ year,
27
+ month,
28
+ day,
29
+ }: CalendarDay): string {
30
+ const pad = (value: number, width: number) =>
31
+ String(value).padStart(width, "0")
32
+ return `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`
33
+ }
34
+
35
+ /** Moves by whole days, across months and years. */
36
+ export function addDays(
37
+ day: CalendarDay,
38
+ count: number
39
+ ): CalendarDay {
40
+ const date = new Date(
41
+ Date.UTC(day.year, day.month - 1, day.day + count)
42
+ )
43
+ return {
44
+ year: date.getUTCFullYear(),
45
+ month: date.getUTCMonth() + 1,
46
+ day: date.getUTCDate(),
47
+ }
48
+ }
49
+
50
+ /** Moves by months, keeping the day where the month allows (31 Jan + 1 is 28 or 29 Feb). */
51
+ export function addMonths(
52
+ day: CalendarDay,
53
+ count: number
54
+ ): CalendarDay {
55
+ const first = new Date(
56
+ Date.UTC(day.year, day.month - 1 + count, 1)
57
+ )
58
+ const year = first.getUTCFullYear()
59
+ const month = first.getUTCMonth() + 1
60
+ const last = new Date(
61
+ Date.UTC(year, month, 0)
62
+ ).getUTCDate()
63
+ return { year, month, day: Math.min(day.day, last) }
64
+ }
65
+
66
+ /**
67
+ * The first day of the week where `locale` is used, 1 (Monday) to 7
68
+ * (Sunday), from the browser where it knows, else Monday except in the
69
+ * places that start on Sunday.
70
+ */
71
+ export function firstWeekday(locale: string): number {
72
+ try {
73
+ const info = new Intl.Locale(
74
+ locale
75
+ ) as Intl.Locale & {
76
+ getWeekInfo?: () => { firstDay: number }
77
+ weekInfo?: { firstDay: number }
78
+ }
79
+ const first =
80
+ info.getWeekInfo?.().firstDay ??
81
+ info.weekInfo?.firstDay
82
+ if (first) return first
83
+ const region = info.maximize().region ?? ""
84
+ return [
85
+ "US",
86
+ "CA",
87
+ "JP",
88
+ "KR",
89
+ "TW",
90
+ "BR",
91
+ "MX",
92
+ "IL",
93
+ "PH",
94
+ "IN",
95
+ ].includes(region)
96
+ ? 7
97
+ : 1
98
+ } catch {
99
+ return 1
100
+ }
101
+ }
102
+
103
+ /** Six weeks covering `year`/`month`, starting on `firstDay` (1 to 7). */
104
+ export function monthGrid(
105
+ year: number,
106
+ month: number,
107
+ firstDay: number
108
+ ): Array<CalendarDay & { inMonth: boolean }> {
109
+ // getUTCDay counts Sunday as 0; weeks here count it as 7.
110
+ const weekday =
111
+ new Date(Date.UTC(year, month - 1, 1)).getUTCDay() ||
112
+ 7
113
+ const lead = (weekday - firstDay + 7) % 7
114
+ const start = addDays({ year, month, day: 1 }, -lead)
115
+ return Array.from({ length: 42 }, (_, index) => {
116
+ const day = addDays(start, index)
117
+ return {
118
+ ...day,
119
+ inMonth: day.month === month && day.year === year,
120
+ }
121
+ })
122
+ }
123
+
124
+ /** Whether `value` (`YYYY-MM-DD`) lies within the optional bounds. */
125
+ export function withinBounds(
126
+ value: string,
127
+ min?: string,
128
+ max?: string
129
+ ): boolean {
130
+ return (!min || value >= min) && (!max || value <= max)
131
+ }