@expokit/cli 0.0.0-stage → 0.3.0

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 (312) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -2
  3. package/bin/expo-kit.js +1266 -0
  4. package/bin/lib.mjs +343 -0
  5. package/bin/mcp.mjs +345 -0
  6. package/package.json +49 -4
  7. package/registry/_docs/accordion.json +115 -0
  8. package/registry/_docs/action-sheet.json +138 -0
  9. package/registry/_docs/alert.json +155 -0
  10. package/registry/_docs/avatar.json +227 -0
  11. package/registry/_docs/badge.json +148 -0
  12. package/registry/_docs/bottom-nav.json +143 -0
  13. package/registry/_docs/bottom-sheet.json +186 -0
  14. package/registry/_docs/bullet.json +27 -0
  15. package/registry/_docs/button-3d.json +138 -0
  16. package/registry/_docs/button.json +131 -0
  17. package/registry/_docs/calendar.json +198 -0
  18. package/registry/_docs/card-stack.json +124 -0
  19. package/registry/_docs/card.json +131 -0
  20. package/registry/_docs/carousel.json +122 -0
  21. package/registry/_docs/chat.json +184 -0
  22. package/registry/_docs/checkbox.json +109 -0
  23. package/registry/_docs/checkout.json +105 -0
  24. package/registry/_docs/chip-group.json +132 -0
  25. package/registry/_docs/color-swatch.json +82 -0
  26. package/registry/_docs/confetti.json +102 -0
  27. package/registry/_docs/coupon.json +192 -0
  28. package/registry/_docs/cube-pager.json +100 -0
  29. package/registry/_docs/dashboard.json +123 -0
  30. package/registry/_docs/date-picker.json +269 -0
  31. package/registry/_docs/dialog.json +186 -0
  32. package/registry/_docs/drawer.json +199 -0
  33. package/registry/_docs/fab.json +144 -0
  34. package/registry/_docs/flip-card.json +127 -0
  35. package/registry/_docs/floating-input.json +211 -0
  36. package/registry/_docs/gift-card.json +124 -0
  37. package/registry/_docs/glass-button.json +142 -0
  38. package/registry/_docs/glass-nav-bar.json +102 -0
  39. package/registry/_docs/glass-search-bar.json +114 -0
  40. package/registry/_docs/glass-segmented.json +94 -0
  41. package/registry/_docs/glass-sheet.json +136 -0
  42. package/registry/_docs/glass-tab-bar.json +100 -0
  43. package/registry/_docs/glass.json +94 -0
  44. package/registry/_docs/inbox.json +75 -0
  45. package/registry/_docs/isometric-bar.json +75 -0
  46. package/registry/_docs/kit.json +14 -0
  47. package/registry/_docs/kpi-card.json +14 -0
  48. package/registry/_docs/like-button.json +92 -0
  49. package/registry/_docs/liquidglass-tab.json +30 -0
  50. package/registry/_docs/list-item.json +216 -0
  51. package/registry/_docs/loyalty-card.json +124 -0
  52. package/registry/_docs/morph-button.json +128 -0
  53. package/registry/_docs/number-ticker.json +112 -0
  54. package/registry/_docs/offers.json +81 -0
  55. package/registry/_docs/onboarding.json +69 -0
  56. package/registry/_docs/otp-input.json +158 -0
  57. package/registry/_docs/pagination-dots.json +108 -0
  58. package/registry/_docs/parallax-layers.json +104 -0
  59. package/registry/_docs/parallax-scroll.json +210 -0
  60. package/registry/_docs/password-input.json +112 -0
  61. package/registry/_docs/payment-card.json +169 -0
  62. package/registry/_docs/pixel-bar.json +69 -0
  63. package/registry/_docs/polar-area.json +81 -0
  64. package/registry/_docs/profile.json +87 -0
  65. package/registry/_docs/progress-steps.json +116 -0
  66. package/registry/_docs/progress.json +152 -0
  67. package/registry/_docs/radio-group.json +112 -0
  68. package/registry/_docs/rating.json +114 -0
  69. package/registry/_docs/ring-carousel.json +122 -0
  70. package/registry/_docs/scratch-card.json +110 -0
  71. package/registry/_docs/search-bar.json +160 -0
  72. package/registry/_docs/search-results.json +123 -0
  73. package/registry/_docs/segmented-control.json +123 -0
  74. package/registry/_docs/select.json +277 -0
  75. package/registry/_docs/settings.json +75 -0
  76. package/registry/_docs/shimmer-button.json +98 -0
  77. package/registry/_docs/sign-in.json +201 -0
  78. package/registry/_docs/skeleton.json +162 -0
  79. package/registry/_docs/slide-to-confirm.json +108 -0
  80. package/registry/_docs/slider.json +177 -0
  81. package/registry/_docs/spin-wheel.json +138 -0
  82. package/registry/_docs/spinner.json +121 -0
  83. package/registry/_docs/stateful-button.json +184 -0
  84. package/registry/_docs/stepper.json +184 -0
  85. package/registry/_docs/swipe-deck.json +118 -0
  86. package/registry/_docs/swipeable-row.json +95 -0
  87. package/registry/_docs/switch.json +124 -0
  88. package/registry/_docs/tabs.json +129 -0
  89. package/registry/_docs/tag-input.json +148 -0
  90. package/registry/_docs/text-animation.json +141 -0
  91. package/registry/_docs/textarea.json +191 -0
  92. package/registry/_docs/ticket-card.json +194 -0
  93. package/registry/_docs/ticket-shape.json +14 -0
  94. package/registry/_docs/tilt-card.json +157 -0
  95. package/registry/_docs/time-picker.json +269 -0
  96. package/registry/_docs/timeline-chart.json +33 -0
  97. package/registry/_docs/timeline.json +172 -0
  98. package/registry/_docs/toast.json +185 -0
  99. package/registry/_docs/treemap.json +33 -0
  100. package/registry/_docs/waterfall.json +39 -0
  101. package/registry/_docs/wheel-list.json +122 -0
  102. package/registry/accordion/files/src/components/ui/accordion.tsx +233 -0
  103. package/registry/accordion/files/src/lib/kit.tsx +219 -0
  104. package/registry/accordion.json +17 -0
  105. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +580 -0
  106. package/registry/action-sheet/files/src/lib/kit.tsx +219 -0
  107. package/registry/action-sheet.json +18 -0
  108. package/registry/alert/files/src/components/ui/alert.tsx +654 -0
  109. package/registry/alert/files/src/components/ui/button.tsx +274 -0
  110. package/registry/alert/files/src/lib/kit.tsx +219 -0
  111. package/registry/alert.json +20 -0
  112. package/registry/avatar/files/src/components/ui/avatar.tsx +790 -0
  113. package/registry/avatar/files/src/lib/kit.tsx +219 -0
  114. package/registry/avatar.json +18 -0
  115. package/registry/badge/files/src/components/ui/badge.tsx +626 -0
  116. package/registry/badge/files/src/lib/kit.tsx +219 -0
  117. package/registry/badge.json +17 -0
  118. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +1050 -0
  119. package/registry/bottom-nav/files/src/lib/kit.tsx +219 -0
  120. package/registry/bottom-nav.json +18 -0
  121. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +585 -0
  122. package/registry/bottom-sheet/files/src/lib/kit.tsx +219 -0
  123. package/registry/bottom-sheet.json +17 -0
  124. package/registry/bullet.json +14 -0
  125. package/registry/button/files/src/components/ui/button.tsx +274 -0
  126. package/registry/button/files/src/lib/kit.tsx +219 -0
  127. package/registry/button-3d/files/src/components/ui/button-3d.tsx +212 -0
  128. package/registry/button-3d/files/src/lib/kit.tsx +219 -0
  129. package/registry/button-3d.json +18 -0
  130. package/registry/button.json +18 -0
  131. package/registry/calendar/files/src/components/ui/calendar.tsx +822 -0
  132. package/registry/calendar/files/src/lib/kit.tsx +219 -0
  133. package/registry/calendar.json +17 -0
  134. package/registry/card/files/src/components/ui/card.tsx +300 -0
  135. package/registry/card/files/src/lib/kit.tsx +219 -0
  136. package/registry/card-stack.json +15 -0
  137. package/registry/card.json +17 -0
  138. package/registry/carousel.json +15 -0
  139. package/registry/chat.json +18 -0
  140. package/registry/checkbox/files/src/components/ui/checkbox.tsx +150 -0
  141. package/registry/checkbox/files/src/lib/kit.tsx +219 -0
  142. package/registry/checkbox.json +17 -0
  143. package/registry/checkout.json +26 -0
  144. package/registry/chip-group/files/src/components/ui/chip-group.tsx +269 -0
  145. package/registry/chip-group/files/src/lib/kit.tsx +219 -0
  146. package/registry/chip-group.json +17 -0
  147. package/registry/color-swatch.json +17 -0
  148. package/registry/confetti/files/src/components/ui/confetti.tsx +222 -0
  149. package/registry/confetti/files/src/lib/kit.tsx +219 -0
  150. package/registry/confetti.json +15 -0
  151. package/registry/coupon/files/src/components/ui/coupon.tsx +698 -0
  152. package/registry/coupon/files/src/components/ui/flip-card.tsx +205 -0
  153. package/registry/coupon/files/src/components/ui/tilt-card.tsx +635 -0
  154. package/registry/coupon/files/src/lib/kit.tsx +219 -0
  155. package/registry/coupon/files/src/lib/ticket-shape.tsx +543 -0
  156. package/registry/coupon.json +23 -0
  157. package/registry/cube-pager.json +15 -0
  158. package/registry/dashboard.json +32 -0
  159. package/registry/date-picker/files/src/components/ui/calendar.tsx +822 -0
  160. package/registry/date-picker/files/src/components/ui/date-picker.tsx +796 -0
  161. package/registry/date-picker/files/src/lib/kit.tsx +219 -0
  162. package/registry/date-picker.json +19 -0
  163. package/registry/dialog/files/src/components/ui/dialog.tsx +759 -0
  164. package/registry/dialog/files/src/lib/kit.tsx +219 -0
  165. package/registry/dialog.json +18 -0
  166. package/registry/drawer/files/src/components/ui/drawer.tsx +1135 -0
  167. package/registry/drawer/files/src/lib/kit.tsx +219 -0
  168. package/registry/drawer.json +18 -0
  169. package/registry/fab.json +18 -0
  170. package/registry/flip-card/files/src/components/ui/flip-card.tsx +205 -0
  171. package/registry/flip-card/files/src/lib/kit.tsx +219 -0
  172. package/registry/flip-card.json +15 -0
  173. package/registry/floating-input/files/src/components/ui/floating-input.tsx +528 -0
  174. package/registry/floating-input/files/src/lib/kit.tsx +219 -0
  175. package/registry/floating-input.json +17 -0
  176. package/registry/gift-card.json +21 -0
  177. package/registry/glass-button.json +22 -0
  178. package/registry/glass-nav-bar.json +21 -0
  179. package/registry/glass-search-bar.json +21 -0
  180. package/registry/glass-segmented.json +22 -0
  181. package/registry/glass-sheet.json +22 -0
  182. package/registry/glass-tab-bar.json +22 -0
  183. package/registry/glass.json +19 -0
  184. package/registry/inbox.json +30 -0
  185. package/registry/isometric-bar.json +14 -0
  186. package/registry/kit/files/src/lib/kit.tsx +219 -0
  187. package/registry/kit.json +11 -0
  188. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +45 -0
  189. package/registry/kpi-card.json +14 -0
  190. package/registry/like-button.json +18 -0
  191. package/registry/liquidglass-tab/files/src/app/(tabs)/_layout.tsx +97 -0
  192. package/registry/liquidglass-tab/files/src/app/(tabs)/alerts.tsx +5 -0
  193. package/registry/liquidglass-tab/files/src/app/(tabs)/explore.tsx +5 -0
  194. package/registry/liquidglass-tab/files/src/app/(tabs)/index.tsx +5 -0
  195. package/registry/liquidglass-tab/files/src/app/(tabs)/profile.tsx +5 -0
  196. package/registry/liquidglass-tab/files/src/app/_layout.tsx +33 -0
  197. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +259 -0
  198. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +122 -0
  199. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +61 -0
  200. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +130 -0
  201. package/registry/liquidglass-tab/files/src/lib/tabs.ts +108 -0
  202. package/registry/liquidglass-tab.json +23 -0
  203. package/registry/list-item/files/src/components/ui/list-item.tsx +508 -0
  204. package/registry/list-item/files/src/lib/kit.tsx +219 -0
  205. package/registry/list-item.json +17 -0
  206. package/registry/loyalty-card.json +23 -0
  207. package/registry/morph-button.json +18 -0
  208. package/registry/number-ticker.json +15 -0
  209. package/registry/offers.json +24 -0
  210. package/registry/onboarding.json +17 -0
  211. package/registry/otp-input/files/src/components/ui/otp-input.tsx +380 -0
  212. package/registry/otp-input/files/src/lib/kit.tsx +219 -0
  213. package/registry/otp-input.json +17 -0
  214. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +262 -0
  215. package/registry/pagination-dots/files/src/lib/kit.tsx +219 -0
  216. package/registry/pagination-dots.json +15 -0
  217. package/registry/parallax-layers.json +15 -0
  218. package/registry/parallax-scroll.json +17 -0
  219. package/registry/password-input/files/src/components/ui/floating-input.tsx +528 -0
  220. package/registry/password-input/files/src/components/ui/password-input.tsx +271 -0
  221. package/registry/password-input/files/src/lib/kit.tsx +219 -0
  222. package/registry/password-input.json +19 -0
  223. package/registry/payment-card/files/src/components/ui/flip-card.tsx +205 -0
  224. package/registry/payment-card/files/src/components/ui/payment-card.tsx +385 -0
  225. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +635 -0
  226. package/registry/payment-card/files/src/lib/kit.tsx +219 -0
  227. package/registry/payment-card.json +21 -0
  228. package/registry/pixel-bar.json +14 -0
  229. package/registry/polar-area.json +14 -0
  230. package/registry/profile.json +17 -0
  231. package/registry/progress/files/src/components/ui/progress.tsx +883 -0
  232. package/registry/progress/files/src/lib/kit.tsx +219 -0
  233. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +559 -0
  234. package/registry/progress-steps/files/src/lib/kit.tsx +219 -0
  235. package/registry/progress-steps.json +18 -0
  236. package/registry/progress.json +17 -0
  237. package/registry/radio-group/files/src/components/ui/radio-group.tsx +327 -0
  238. package/registry/radio-group/files/src/lib/kit.tsx +219 -0
  239. package/registry/radio-group.json +18 -0
  240. package/registry/rating.json +18 -0
  241. package/registry/ring-carousel.json +15 -0
  242. package/registry/scratch-card.json +18 -0
  243. package/registry/search-bar/files/src/components/ui/search-bar.tsx +530 -0
  244. package/registry/search-bar/files/src/lib/kit.tsx +219 -0
  245. package/registry/search-bar.json +17 -0
  246. package/registry/search-results.json +30 -0
  247. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +341 -0
  248. package/registry/segmented-control/files/src/lib/kit.tsx +219 -0
  249. package/registry/segmented-control.json +18 -0
  250. package/registry/select/files/src/components/ui/select.tsx +1398 -0
  251. package/registry/select/files/src/lib/kit.tsx +219 -0
  252. package/registry/select.json +17 -0
  253. package/registry/settings.json +26 -0
  254. package/registry/shimmer-button.json +17 -0
  255. package/registry/sign-in.json +26 -0
  256. package/registry/skeleton/files/src/components/ui/skeleton.tsx +799 -0
  257. package/registry/skeleton/files/src/lib/kit.tsx +219 -0
  258. package/registry/skeleton.json +17 -0
  259. package/registry/slide-to-confirm.json +18 -0
  260. package/registry/slider/files/src/components/ui/slider.tsx +716 -0
  261. package/registry/slider/files/src/lib/kit.tsx +219 -0
  262. package/registry/slider.json +17 -0
  263. package/registry/spin-wheel.json +18 -0
  264. package/registry/spinner/files/src/components/ui/spinner.tsx +748 -0
  265. package/registry/spinner/files/src/lib/kit.tsx +219 -0
  266. package/registry/spinner.json +17 -0
  267. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +532 -0
  268. package/registry/stateful-button/files/src/lib/kit.tsx +219 -0
  269. package/registry/stateful-button.json +18 -0
  270. package/registry/stepper/files/src/components/ui/stepper.tsx +646 -0
  271. package/registry/stepper/files/src/lib/kit.tsx +219 -0
  272. package/registry/stepper.json +17 -0
  273. package/registry/swipe-deck.json +15 -0
  274. package/registry/swipeable-row.json +15 -0
  275. package/registry/switch/files/src/components/ui/switch.tsx +344 -0
  276. package/registry/switch/files/src/lib/kit.tsx +219 -0
  277. package/registry/switch.json +18 -0
  278. package/registry/tabs/files/src/components/ui/tabs.tsx +539 -0
  279. package/registry/tabs/files/src/lib/kit.tsx +219 -0
  280. package/registry/tabs.json +17 -0
  281. package/registry/tag-input/files/src/components/ui/tag-input.tsx +720 -0
  282. package/registry/tag-input/files/src/lib/kit.tsx +219 -0
  283. package/registry/tag-input.json +17 -0
  284. package/registry/text-animation/files/src/components/ui/text-animation.tsx +650 -0
  285. package/registry/text-animation/files/src/lib/kit.tsx +219 -0
  286. package/registry/text-animation.json +15 -0
  287. package/registry/textarea/files/src/components/ui/textarea.tsx +553 -0
  288. package/registry/textarea/files/src/lib/kit.tsx +219 -0
  289. package/registry/textarea.json +17 -0
  290. package/registry/ticket-card.json +24 -0
  291. package/registry/ticket-shape/files/src/lib/kit.tsx +219 -0
  292. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +543 -0
  293. package/registry/ticket-shape.json +17 -0
  294. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +635 -0
  295. package/registry/tilt-card/files/src/lib/kit.tsx +219 -0
  296. package/registry/tilt-card.json +17 -0
  297. package/registry/time-picker/files/src/components/ui/time-picker.tsx +1159 -0
  298. package/registry/time-picker/files/src/lib/kit.tsx +219 -0
  299. package/registry/time-picker.json +17 -0
  300. package/registry/timeline/files/src/components/ui/avatar.tsx +790 -0
  301. package/registry/timeline/files/src/components/ui/timeline.tsx +1059 -0
  302. package/registry/timeline/files/src/lib/kit.tsx +219 -0
  303. package/registry/timeline-chart.json +14 -0
  304. package/registry/timeline.json +20 -0
  305. package/registry/toast/files/src/components/ui/button.tsx +274 -0
  306. package/registry/toast/files/src/components/ui/toast.tsx +1284 -0
  307. package/registry/toast/files/src/lib/kit.tsx +219 -0
  308. package/registry/toast.json +20 -0
  309. package/registry/treemap.json +14 -0
  310. package/registry/waterfall.json +14 -0
  311. package/registry/wheel-list.json +17 -0
  312. package/schema/components.schema.json +13 -0
@@ -0,0 +1,177 @@
1
+ {
2
+ "name": "slider",
3
+ "category": "Inputs",
4
+ "aliases": [
5
+ "range-slider",
6
+ "range",
7
+ "dual-slider"
8
+ ],
9
+ "exportName": "Slider",
10
+ "files": [
11
+ "src/components/ui/slider.tsx"
12
+ ],
13
+ "summary": "A native-driver slider with a swelling thumb, tap-to-seek and a two-thumb range mode.",
14
+ "props": [
15
+ [
16
+ "value / defaultValue",
17
+ "number | [number, number]",
18
+ "min / [min, max]",
19
+ "Controlled or uncontrolled. A number, or [from, to] when range is set (typed by the range prop)."
20
+ ],
21
+ [
22
+ "onValueChange",
23
+ "(value) => void",
24
+ "—",
25
+ "Fires when the stepped value changes (drag, tap, icon or accessibility action)."
26
+ ],
27
+ [
28
+ "onSlidingStart / onSlidingComplete",
29
+ "(value) => void",
30
+ "—",
31
+ "Drag lifecycle."
32
+ ],
33
+ [
34
+ "min / max / step",
35
+ "number",
36
+ "0 / 100 / 1",
37
+ "Bounds and snap interval (0 = continuous). Coarse steps spring between positions; fine ones follow the finger and snap on release."
38
+ ],
39
+ [
40
+ "range",
41
+ "boolean",
42
+ "false",
43
+ "Two thumbs selecting a span; value becomes [from, to]."
44
+ ],
45
+ [
46
+ "variant",
47
+ "\"default\" | \"bar\" | \"stepped\"",
48
+ "\"default\"",
49
+ "Thin track + round thumb, a tall Control-Center bar with no thumb, or a track with a tick at every step."
50
+ ],
51
+ [
52
+ "size",
53
+ "\"sm\" | \"md\" | \"lg\"",
54
+ "\"md\"",
55
+ "Scales track, thumb, bar height and text. Hit area is always ≥ 44pt."
56
+ ],
57
+ [
58
+ "marks",
59
+ "boolean | (number | { value, label })[]",
60
+ "stepped: true",
61
+ "Ticks at every step, or custom values with labels under the track."
62
+ ],
63
+ [
64
+ "showValue",
65
+ "\"drag\" | \"always\" | \"never\"",
66
+ "\"drag\" (bar: \"never\")",
67
+ "Value tooltip above the thumb. On a bar, \"always\" shows the value inside the bar instead."
68
+ ],
69
+ [
70
+ "formatValue",
71
+ "(value) => string",
72
+ "—",
73
+ "Text for the tooltip, labels and accessibility value."
74
+ ],
75
+ [
76
+ "showMinMax / minLabel / maxLabel",
77
+ "boolean / string / string",
78
+ "—",
79
+ "Labels under both ends of the track (formatted min / max by default)."
80
+ ],
81
+ [
82
+ "icon",
83
+ "SliderIconName | ReactNode | (color) => ReactNode",
84
+ "—",
85
+ "Bar only: icon inside the bar that changes colour as the fill passes over it."
86
+ ],
87
+ [
88
+ "leftIcon / rightIcon",
89
+ "SliderIconName | ReactNode | (color) => ReactNode",
90
+ "—",
91
+ "Icon beside the track. Built-ins ('volume-low', 'volume-high', 'volume-off', 'sun-low', 'sun-high', 'minus', 'plus') step the value when tapped."
92
+ ],
93
+ [
94
+ "thickFill",
95
+ "boolean",
96
+ "false",
97
+ "Default variant: draws the fill thicker than the track."
98
+ ],
99
+ [
100
+ "histogram / histogramHeight",
101
+ "number[] / number",
102
+ "— / 44",
103
+ "Bars above the track; bars inside the selection use the accent."
104
+ ],
105
+ [
106
+ "disabled",
107
+ "boolean",
108
+ "false",
109
+ "Dims and ignores touches."
110
+ ],
111
+ [
112
+ "accent",
113
+ "string",
114
+ "theme accent",
115
+ "Fill colour."
116
+ ],
117
+ [
118
+ "accessibilityLabel",
119
+ "string",
120
+ "—",
121
+ "Announced with the adjustable role; range thumbs get “minimum” / “maximum”."
122
+ ],
123
+ [
124
+ "style / trackStyle / fillStyle / thumbStyle / labelStyle",
125
+ "StyleProp",
126
+ "—",
127
+ "Root, inactive track, active fill, thumb and value text."
128
+ ]
129
+ ],
130
+ "behavior": [
131
+ [
132
+ "Touch",
133
+ "Tap anywhere on the track to seek, or grab a thumb and drag. Built-in leftIcon and rightIcon names step the value down or up when tapped."
134
+ ],
135
+ [
136
+ "Motion",
137
+ "Thumb and fill move on the native driver; the thumb swells under your finger and values spring-snap to the step."
138
+ ],
139
+ [
140
+ "Screen readers",
141
+ "Single mode is one adjustable element with min, max, current value and increment / decrement actions. In range mode each thumb is its own adjustable, labelled 'minimum' and 'maximum'."
142
+ ]
143
+ ],
144
+ "usage": "import { useState } from 'react';\nimport { Slider } from '@/components/ui/slider';\n\nconst [price, setPrice] = useState<[number, number]>([20, 80]);\n<Slider range value={price} onValueChange={setPrice} accessibilityLabel=\"Price\" />",
145
+ "examples": [
146
+ {
147
+ "name": "ControlCenter",
148
+ "title": "Control Center",
149
+ "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { Slider } from '@/components/ui/slider';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, value }: { title: string; value?: string }) {\n const { colors } = useKit();\n return (\n <View style={s.head}>\n <Text style={[s.title, { color: colors.foreground }]}>{title}</Text>\n {value ? <Text style={[s.value, { color: colors.mutedForeground }]}>{value}</Text> : null}\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline' },\n title: { fontSize: 15, fontWeight: '400' },\n value: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'] },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -8 },\n note: { fontSize: 13 },\n});\n\nexport default function ControlCenter() {\n const [brightness, setBrightness] = useState(72);\n return (\n <Card>\n <Header title=\"Display & sound\" />\n <Slider\n variant=\"bar\"\n size=\"lg\"\n step={0}\n value={brightness}\n onValueChange={setBrightness}\n icon=\"sun-high\"\n showValue=\"always\"\n formatValue={(v) => `${Math.round(v)}%`}\n accessibilityLabel=\"Brightness\"\n />\n <Slider variant=\"bar\" size=\"lg\" defaultValue={40} icon=\"volume-high\" accessibilityLabel=\"Volume\" />\n </Card>\n );\n}\n"
150
+ },
151
+ {
152
+ "name": "Default",
153
+ "title": "Default",
154
+ "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { Slider } from '@/components/ui/slider';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, value }: { title: string; value?: string }) {\n const { colors } = useKit();\n return (\n <View style={s.head}>\n <Text style={[s.title, { color: colors.foreground }]}>{title}</Text>\n {value ? <Text style={[s.value, { color: colors.mutedForeground }]}>{value}</Text> : null}\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline' },\n title: { fontSize: 15, fontWeight: '400' },\n value: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'] },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -8 },\n note: { fontSize: 13 },\n});\n\nexport default function Default() {\n const [volume, setVolume] = useState(64);\n return (\n <Card>\n <Header title=\"Media volume\" value={`${volume}%`} />\n <Slider\n value={volume}\n onValueChange={setVolume}\n leftIcon=\"volume-low\"\n rightIcon=\"volume-high\"\n formatValue={(v) => `${v}%`}\n accessibilityLabel=\"Media volume\"\n />\n </Card>\n );\n}\n"
155
+ },
156
+ {
157
+ "name": "PriceRange",
158
+ "title": "Price Range",
159
+ "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { Slider } from '@/components/ui/slider';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, value }: { title: string; value?: string }) {\n const { colors } = useKit();\n return (\n <View style={s.head}>\n <Text style={[s.title, { color: colors.foreground }]}>{title}</Text>\n {value ? <Text style={[s.value, { color: colors.mutedForeground }]}>{value}</Text> : null}\n </View>\n );\n}\n\nconst PRICE_MAX = 2000;\n\nconst rupees = (v: number) => `₹${String(v).replace(/(\\d)(?=(\\d\\d)+\\d$)/g, '$1,')}`;\n\nconst PRICES = [2, 4, 7, 11, 16, 22, 30, 34, 38, 33, 29, 26, 21, 18, 14, 12, 9, 7, 5, 4, 3, 2, 2, 1];\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline' },\n title: { fontSize: 15, fontWeight: '400' },\n value: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'] },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -8 },\n note: { fontSize: 13 },\n});\n\nexport default function PriceRange() {\n const { colors } = useKit();\n const [price, setPrice] = useState<[number, number]>([400, 1400]);\n const per = PRICE_MAX / PRICES.length;\n const results = PRICES.reduce((n, c, i) => (i * per + per / 2 >= price[0] && i * per + per / 2 <= price[1] ? n + c : n), 0);\n return (\n <Card>\n <Header title=\"Price\" value={`${rupees(price[0])} – ${rupees(price[1])}${price[1] === PRICE_MAX ? '+' : ''}`} />\n <Slider\n range\n min={0}\n max={PRICE_MAX}\n step={50}\n value={price}\n onValueChange={setPrice}\n histogram={PRICES}\n histogramHeight={40}\n showMinMax\n maxLabel={`${rupees(PRICE_MAX)}+`}\n formatValue={rupees}\n accessibilityLabel=\"Price\"\n />\n <Text style={[s.note, { color: colors.mutedForeground }]}>{results * 3} stays match</Text>\n </Card>\n );\n}\n"
160
+ },
161
+ {
162
+ "name": "FontSize",
163
+ "title": "Font Size",
164
+ "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { Slider } from '@/components/ui/slider';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, value }: { title: string; value?: string }) {\n const { colors } = useKit();\n return (\n <View style={s.head}>\n <Text style={[s.title, { color: colors.foreground }]}>{title}</Text>\n {value ? <Text style={[s.value, { color: colors.mutedForeground }]}>{value}</Text> : null}\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline' },\n title: { fontSize: 15, fontWeight: '400' },\n value: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'] },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -8 },\n note: { fontSize: 13 },\n});\n\nexport default function FontSize() {\n const { colors } = useKit();\n const [size, setSize] = useState(17);\n const glyph = (px: number) => <Text style={{ color: colors.mutedForeground, fontSize: px, fontWeight: '400' }}>A</Text>;\n return (\n <Card>\n <Header title=\"Text size\" value={`${size} pt`} />\n <Text style={{ color: colors.foreground, fontSize: size, lineHeight: size * 1.35 }}>\n Your reading view will look like this.\n </Text>\n <Slider\n variant=\"stepped\"\n min={13}\n max={23}\n step={2}\n value={size}\n onValueChange={setSize}\n leftIcon={glyph(13)}\n rightIcon={glyph(21)}\n showValue=\"never\"\n accessibilityLabel=\"Text size\"\n />\n </Card>\n );\n}\n"
165
+ },
166
+ {
167
+ "name": "Sizes",
168
+ "title": "Sizes",
169
+ "code": "import { StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { Slider } from '@/components/ui/slider';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]}\n {...rest}\n />\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline' },\n title: { fontSize: 15, fontWeight: '400' },\n value: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'] },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -8 },\n note: { fontSize: 13 },\n});\n\nexport default function Sizes() {\n const { colors } = useKit();\n const label = (t: string) => <Text style={[s.label, { color: colors.mutedForeground }]}>{t}</Text>;\n return (\n <Card>\n {label('Small')}\n <Slider size=\"sm\" defaultValue={25} accessibilityLabel=\"Small\" />\n {label('Medium')}\n <Slider size=\"md\" defaultValue={50} accessibilityLabel=\"Medium\" />\n {label('Large')}\n <Slider size=\"lg\" defaultValue={75} accessibilityLabel=\"Large\" />\n {label('Disabled')}\n <Slider defaultValue={40} disabled accessibilityLabel=\"Disabled\" />\n </Card>\n );\n}\n"
170
+ },
171
+ {
172
+ "name": "Colors",
173
+ "title": "Colors",
174
+ "code": "import { StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { Slider } from '@/components/ui/slider';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, value }: { title: string; value?: string }) {\n const { colors } = useKit();\n return (\n <View style={s.head}>\n <Text style={[s.title, { color: colors.foreground }]}>{title}</Text>\n {value ? <Text style={[s.value, { color: colors.mutedForeground }]}>{value}</Text> : null}\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'baseline' },\n title: { fontSize: 15, fontWeight: '400' },\n value: { fontSize: 15, fontWeight: '400', fontVariant: ['tabular-nums'] },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -8 },\n note: { fontSize: 13 },\n});\n\nexport default function Colors() {\n const { colors } = useKit();\n return (\n <Card>\n <Header title=\"Daily goals\" />\n <Slider defaultValue={80} accent={colors.success} showValue=\"always\" formatValue={(v) => `${v}% steps`} accessibilityLabel=\"Steps\" />\n <Slider variant=\"bar\" size=\"sm\" defaultValue={55} accent={colors.warning} accessibilityLabel=\"Calories\" />\n <Slider variant=\"stepped\" min={0} max={10} step={1} defaultValue={3} accent={colors.danger} accessibilityLabel=\"Screen time hours\" />\n </Card>\n );\n}\n"
175
+ }
176
+ ]
177
+ }
@@ -0,0 +1,138 @@
1
+ {
2
+ "name": "spin-wheel",
3
+ "category": "Coupons",
4
+ "aliases": [],
5
+ "exportName": "SpinWheel",
6
+ "files": [
7
+ "src/components/ui/spin-wheel.tsx"
8
+ ],
9
+ "summary": "A prize wheel whose flapper ticks over every divider as it eases onto the win.",
10
+ "props": [
11
+ [
12
+ "segments",
13
+ "{ label, sublabel?, color?, textColor?, weight? }[]",
14
+ "—",
15
+ "2–16 prizes, clockwise from the pointer. Long labels shrink or wrap to fit; weight sets the odds of a random pick (0 never wins at random)."
16
+ ],
17
+ [
18
+ "palette",
19
+ "string[]",
20
+ "vivid 8-colour set",
21
+ "Fills for segments without a color; the text picks black or white to read on each."
22
+ ],
23
+ [
24
+ "size",
25
+ "number",
26
+ "width, up to 340",
27
+ "Diameter in points."
28
+ ],
29
+ [
30
+ "turns",
31
+ "number",
32
+ "6",
33
+ "Full turns before it lands (a harder flick adds a few)."
34
+ ],
35
+ [
36
+ "duration",
37
+ "number",
38
+ "4200",
39
+ "Length of a spin in ms: a fast launch, a long slow crawl and a tiny settle."
40
+ ],
41
+ [
42
+ "getResult",
43
+ "() => number | Promise<number>",
44
+ "—",
45
+ "Decide the prize (e.g. on your server). The wheel keeps spinning while the promise is pending, then lands on it. Without it, weight picks at random."
46
+ ],
47
+ [
48
+ "onSpinStart",
49
+ "() => void",
50
+ "—",
51
+ "A spin began (tap, flick or ref)."
52
+ ],
53
+ [
54
+ "onSpinEnd",
55
+ "(index, segment) => void",
56
+ "—",
57
+ "The wheel landed; the winning wedge glows until the next spin."
58
+ ],
59
+ [
60
+ "onSpinError",
61
+ "(error) => void",
62
+ "—",
63
+ "getResult threw, rejected or gave an invalid index; the wheel coasts to a stop with no winner."
64
+ ],
65
+ [
66
+ "rimColor",
67
+ "string",
68
+ "\"#d9a62e\" (gold)",
69
+ "Metal of the rim, pins and dividers."
70
+ ],
71
+ [
72
+ "accent",
73
+ "string",
74
+ "theme accent",
75
+ "Hub button and pointer colour."
76
+ ],
77
+ [
78
+ "spinLabel",
79
+ "string",
80
+ "\"SPIN\"",
81
+ "Hub button label."
82
+ ],
83
+ [
84
+ "disabled",
85
+ "boolean",
86
+ "false",
87
+ "Ignore taps, flicks and ref.spin(); dims the hub and the lights."
88
+ ],
89
+ [
90
+ "haptics",
91
+ "boolean",
92
+ "true",
93
+ "A tick per divider, a tap on the hub and a success buzz on landing."
94
+ ],
95
+ [
96
+ "accessibilityLabel",
97
+ "string",
98
+ "\"Prize wheel\"",
99
+ "Screen-reader name; the prizes and the result are read after it, and the result is announced."
100
+ ],
101
+ [
102
+ "style",
103
+ "StyleProp<ViewStyle>",
104
+ "—",
105
+ "Style for the root view (margins)."
106
+ ],
107
+ [
108
+ "ref",
109
+ "{ spin(index?): void; reset(): void }",
110
+ "—",
111
+ "Spin from your own button (optionally onto a fixed index), or cancel and turn back to the start."
112
+ ]
113
+ ],
114
+ "behavior": [
115
+ [
116
+ "Touch",
117
+ "Tap the SPIN hub or flick the wheel; a harder flick throws it further. A slow drag just springs back to rest."
118
+ ],
119
+ [
120
+ "Motion",
121
+ "It keeps turning while getResult resolves, then eases onto the chosen wedge; the other wedges dim and the winner glows while the rim lights flash."
122
+ ],
123
+ [
124
+ "Reduced motion",
125
+ "The idle chasing lights, the win celebration and the winner pulse are skipped; the spin itself still plays."
126
+ ],
127
+ [
128
+ "Haptics",
129
+ "A selection tick each time the flapper crosses a divider, a light impact on the hub, and a success notification on the win (skipped on web)."
130
+ ],
131
+ [
132
+ "Screen readers",
133
+ "The wheel is an image whose label lists every prize; the hub is a button with busy state while spinning, and the result is announced."
134
+ ]
135
+ ],
136
+ "usage": null,
137
+ "examples": []
138
+ }
@@ -0,0 +1,121 @@
1
+ {
2
+ "name": "spinner",
3
+ "category": "Feedback",
4
+ "aliases": [
5
+ "loader",
6
+ "loading",
7
+ "activity-indicator",
8
+ "progress-indicator"
9
+ ],
10
+ "exportName": "Spinner",
11
+ "files": [
12
+ "src/components/ui/spinner.tsx"
13
+ ],
14
+ "summary": "Twelve loaders — ring, dots, bars, pulse, orbit, typing, line and more — from one API.",
15
+ "props": [
16
+ [
17
+ "variant",
18
+ "\"ring\" | \"dots\" | \"bars\" | \"pulse\" | \"orbit\" | \"bounce\" | \"wave\" | \"grid\" | \"typing\" | \"arc\" | \"square\" | \"line\"",
19
+ "\"ring\"",
20
+ "Indicator style — 12 loaders. \"line\" is an indeterminate bar size × 4 wide (override with style.width)."
21
+ ],
22
+ [
23
+ "size",
24
+ "\"sm\" | \"md\" | \"lg\" | \"xl\" | number",
25
+ "\"md\"",
26
+ "16 / 24 / 36 / 52pt, or any number. Every variant scales from it."
27
+ ],
28
+ [
29
+ "color",
30
+ "string",
31
+ "theme accent",
32
+ "Indicator colour."
33
+ ],
34
+ [
35
+ "speed",
36
+ "number",
37
+ "1",
38
+ "Speed multiplier."
39
+ ],
40
+ [
41
+ "label",
42
+ "string",
43
+ "\"Loading\"",
44
+ "Accessibility label; shown on screen when labelPosition is set."
45
+ ],
46
+ [
47
+ "labelPosition",
48
+ "\"none\" | \"right\" | \"bottom\"",
49
+ "\"none\"",
50
+ "Show the label beside or under the spinner, with an animated ellipsis."
51
+ ],
52
+ [
53
+ "track",
54
+ "boolean",
55
+ "false",
56
+ "Faint guide behind the motion: full circle (ring, orbit, arc), rail (line) or ghost bars (bars)."
57
+ ],
58
+ [
59
+ "style",
60
+ "StyleProp<ViewStyle>",
61
+ "—",
62
+ "Style for the root view (the square, or the spinner + label row/column)."
63
+ ]
64
+ ],
65
+ "behavior": [
66
+ [
67
+ "Motion",
68
+ "Every variant loops on the native driver (rotation, scale, translate, opacity); the ring's arc-length breathing is the only SVG prop animation and runs on its own JS-driven node."
69
+ ],
70
+ [
71
+ "Reduced motion",
72
+ "Loops stop and each loader holds a still, clearly-busy frame (staggered dots, a lit diagonal, a mid-line segment); the ring keeps a plain steady spin."
73
+ ],
74
+ [
75
+ "Screen readers",
76
+ "Announced as a progress bar with state busy and your label (default \"Loading\"); the visual is one accessible element."
77
+ ],
78
+ [
79
+ "Platforms",
80
+ "Pure React Native Animated plus react-native-svg — iOS, Android, Expo Go and web, no native setup."
81
+ ]
82
+ ],
83
+ "usage": "import { Spinner } from '@/components/ui/spinner';\n\nexport function Saving() {\n return <Spinner variant=\"bars\" size=\"lg\" label=\"Saving changes\" labelPosition=\"right\" />;\n}",
84
+ "examples": [
85
+ {
86
+ "name": "Variants",
87
+ "title": "Variants",
88
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Spinner, type SpinnerVariant } from '@/components/ui/spinner';\n\nexport default function Variants() {\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 40 }}>\n <Spinner variant=\"ring\" size=\"lg\" track />\n <Spinner variant=\"ring\" size=\"lg\" />\n <Spinner variant=\"dots\" size=\"lg\" />\n </View>\n );\n}\n"
89
+ },
90
+ {
91
+ "name": "LoaderGallery",
92
+ "title": "Loader Gallery",
93
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Spinner, type SpinnerVariant } from '@/components/ui/spinner';\nimport { readableOn, useKit } from '@/lib/kit';\n\nconst LOADERS: SpinnerVariant[] = ['ring', 'dots', 'bars', 'pulse', 'orbit', 'bounce', 'wave', 'grid', 'typing', 'arc', 'square', 'line'];\n\nexport default function LoaderGallery() {\n const { colors, radius } = useKit();\n return (\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', rowGap: 12 }}>\n {LOADERS.map((variant) => (\n <View key={variant} style={{ width: '33.33%', alignItems: 'center', gap: 8, paddingHorizontal: 4 }}>\n <View\n style={{\n alignSelf: 'stretch',\n height: 64,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: radius.md,\n backgroundColor: colors.muted,\n }}\n >\n <Spinner\n variant={variant}\n size={variant === 'line' ? 'sm' : 'lg'}\n track={variant === 'line'}\n label={`${variant} loader`}\n style={variant === 'line' ? { width: '72%' } : undefined}\n />\n </View>\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>{variant}</Text>\n </View>\n ))}\n </View>\n );\n}\n"
94
+ },
95
+ {
96
+ "name": "InButton",
97
+ "title": "In Button",
98
+ "code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Spinner, type SpinnerVariant } from '@/components/ui/spinner';\nimport { readableOn, useKit } from '@/lib/kit';\n\nexport default function InButton() {\n const { colors, radius } = useKit();\n const [busy, setBusy] = useState(false);\n const fg = readableOn(colors.accent);\n return (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityState={{ busy }}\n onPress={() => {\n setBusy(true);\n setTimeout(() => setBusy(false), 2200);\n }}\n style={{\n height: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: colors.accent,\n alignItems: 'center',\n justifyContent: 'center',\n alignSelf: 'stretch',\n }}\n >\n {busy ? (\n <Spinner size=\"sm\" color={fg} label=\"Placing order\" labelPosition=\"right\" />\n ) : (\n <Text style={{ color: fg, fontSize: 16, fontWeight: '400' }}>Place order · ₹749</Text>\n )}\n </Pressable>\n );\n}\n"
99
+ },
100
+ {
101
+ "name": "LoadingCard",
102
+ "title": "Loading Card",
103
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Spinner, type SpinnerVariant } from '@/components/ui/spinner';\nimport { readableOn, useKit } from '@/lib/kit';\n\nexport default function LoadingCard() {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n alignSelf: 'stretch',\n alignItems: 'center',\n gap: 6,\n paddingVertical: 32,\n paddingHorizontal: 20,\n borderRadius: radius.lg,\n backgroundColor: colors.card,\n borderWidth: 1,\n borderColor: colors.border,\n }}\n >\n <Spinner size=\"xl\" track label=\"Preparing your itinerary\" labelPosition=\"bottom\" />\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>\n Checking 3 flights and 2 hotels\n </Text>\n </View>\n );\n}\n"
104
+ },
105
+ {
106
+ "name": "InlineStatus",
107
+ "title": "Inline Status",
108
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Spinner, type SpinnerVariant } from '@/components/ui/spinner';\n\nexport default function InlineStatus() {\n return (\n <View style={{ gap: 16 }}>\n <Spinner size=\"sm\" label=\"Syncing 128 photos\" labelPosition=\"right\" />\n <Spinner variant=\"dots\" size=\"md\" label=\"Maya is typing\" labelPosition=\"right\" />\n </View>\n );\n}\n"
109
+ },
110
+ {
111
+ "name": "Colors",
112
+ "title": "Colors",
113
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Spinner, type SpinnerVariant } from '@/components/ui/spinner';\nimport { readableOn, useKit } from '@/lib/kit';\n\nexport default function Colors() {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-around', gap: 20 }}>\n <Spinner size=\"lg\" color={colors.success} track />\n <Spinner variant=\"dots\" size=\"lg\" color={colors.warning} />\n <Spinner size=\"lg\" color={colors.danger} />\n </View>\n );\n}\n"
114
+ },
115
+ {
116
+ "name": "Sizes",
117
+ "title": "Sizes",
118
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Spinner, type SpinnerVariant } from '@/components/ui/spinner';\n\nexport default function Sizes() {\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 24 }}>\n <Spinner size=\"sm\" />\n <Spinner size=\"md\" />\n <Spinner size=\"lg\" />\n <Spinner size=\"xl\" />\n </View>\n );\n}\n"
119
+ }
120
+ ]
121
+ }
@@ -0,0 +1,184 @@
1
+ {
2
+ "name": "stateful-button",
3
+ "category": "Buttons",
4
+ "aliases": [
5
+ "loading-button",
6
+ "async-button",
7
+ "status-button"
8
+ ],
9
+ "exportName": "StatefulButton",
10
+ "files": [
11
+ "src/components/ui/stateful-button.tsx"
12
+ ],
13
+ "summary": "Button whose label rolls and width springs through loading, success and error.",
14
+ "props": [
15
+ [
16
+ "children",
17
+ "string",
18
+ "—",
19
+ "Label while idle."
20
+ ],
21
+ [
22
+ "state",
23
+ "\"idle\" | \"loading\" | \"success\" | \"error\"",
24
+ "—",
25
+ "Controlled state. Omit it to let a Promise from onPress drive the button."
26
+ ],
27
+ [
28
+ "onPress",
29
+ "() => void | Promise<unknown>",
30
+ "—",
31
+ "Resolve → success, reject → error, then back to idle (when state is not set)."
32
+ ],
33
+ [
34
+ "loadingText",
35
+ "string",
36
+ "children",
37
+ "Label while loading."
38
+ ],
39
+ [
40
+ "successText",
41
+ "string",
42
+ "\"Done\"",
43
+ "Label after success."
44
+ ],
45
+ [
46
+ "errorText",
47
+ "string",
48
+ "\"Failed\"",
49
+ "Label after an error."
50
+ ],
51
+ [
52
+ "variant",
53
+ "\"primary\" | \"secondary\" | \"outline\" | \"ghost\"",
54
+ "\"primary\"",
55
+ "Idle look; success and error tint it green / red."
56
+ ],
57
+ [
58
+ "size",
59
+ "\"sm\" | \"md\" | \"lg\"",
60
+ "\"md\"",
61
+ "36 / 46 / 54pt tall."
62
+ ],
63
+ [
64
+ "color",
65
+ "string",
66
+ "theme primary",
67
+ "Tint for the idle look."
68
+ ],
69
+ [
70
+ "resetAfter",
71
+ "number",
72
+ "1600",
73
+ "ms the result shows before returning to idle (self-driving only)."
74
+ ],
75
+ [
76
+ "minLoading",
77
+ "number",
78
+ "500",
79
+ "Minimum ms of spinner (self-driving only)."
80
+ ],
81
+ [
82
+ "onStateChange",
83
+ "(state) => void",
84
+ "—",
85
+ "Called on every state change."
86
+ ],
87
+ [
88
+ "pill",
89
+ "boolean",
90
+ "false",
91
+ "Fully rounded ends."
92
+ ],
93
+ [
94
+ "block",
95
+ "boolean",
96
+ "false",
97
+ "Stretch to the parent's width (no width animation)."
98
+ ],
99
+ [
100
+ "disabled",
101
+ "boolean",
102
+ "false",
103
+ "Blocks presses and dims the button."
104
+ ],
105
+ [
106
+ "haptics",
107
+ "boolean",
108
+ "true",
109
+ "Taps on press, success and error."
110
+ ],
111
+ [
112
+ "accessibilityLabel",
113
+ "string",
114
+ "current label",
115
+ "Override what screen readers hear."
116
+ ],
117
+ [
118
+ "textStyle",
119
+ "StyleProp<TextStyle>",
120
+ "—",
121
+ "Style for the label letters."
122
+ ],
123
+ [
124
+ "style",
125
+ "StyleProp<ViewStyle>",
126
+ "—",
127
+ "Style for the outer wrapper (margins, alignment)."
128
+ ]
129
+ ],
130
+ "behavior": [
131
+ [
132
+ "Touch",
133
+ "Springs to 96% on press; at least 44pt tall to touch (hitSlop on the small size). Presses are ignored while loading and, when self-driving, until it returns to idle."
134
+ ],
135
+ [
136
+ "Motion",
137
+ "Label letters roll on the native driver; width, colour and the check / cross stroke spring or ease on the JS driver on separate views."
138
+ ],
139
+ [
140
+ "Reduced motion",
141
+ "Labels crossfade instead of rolling and the width changes instantly."
142
+ ],
143
+ [
144
+ "Haptics",
145
+ "Light tap on press, success / error notification on the result (expo-haptics; turn off with haptics={false})."
146
+ ],
147
+ [
148
+ "Screen readers",
149
+ "Button role with busy and disabled state; every new label (Saving…, Saved, Failed) is announced."
150
+ ],
151
+ [
152
+ "Platforms",
153
+ "iOS, Android and web."
154
+ ]
155
+ ],
156
+ "usage": "import { StatefulButton } from '@/components/ui/stateful-button';\n\n<StatefulButton loadingText=\"Saving…\" successText=\"Saved\" onPress={() => save()}>\n Save changes\n</StatefulButton>\n\n<StatefulButton state={state} errorText=\"Retry\">Upload</StatefulButton>",
157
+ "examples": [
158
+ {
159
+ "name": "SaveChanges",
160
+ "title": "Save Changes",
161
+ "code": "import { StatefulButton, type StatefulButtonState } from '@/components/ui/stateful-button';\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport default function SaveChanges() {\n return (\n <StatefulButton loadingText=\"Saving…\" successText=\"Saved\" onPress={() => sleep(1200)}>\n Save changes\n </StatefulButton>\n );\n}\n"
162
+ },
163
+ {
164
+ "name": "PaymentRetry",
165
+ "title": "Payment Retry",
166
+ "code": "import React, { useRef, useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { StatefulButton, type StatefulButtonState } from '@/components/ui/stateful-button';\nimport { useKit } from '@/lib/kit';\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport default function PaymentRetry() {\n const { colors } = useKit();\n const attempts = useRef(0);\n const [note, setNote] = useState('First attempt is declined, the retry goes through.');\n return (\n <View style={{ gap: 10 }}>\n <StatefulButton\n block\n size=\"lg\"\n loadingText=\"Processing…\"\n successText=\"Paid ₹1,299\"\n errorText=\"Card declined\"\n onPress={async () => {\n attempts.current += 1;\n await sleep(1300);\n if (attempts.current % 2 === 1) {\n setNote('Declined by the bank. Tap again to retry.');\n throw new Error('declined');\n }\n setNote('Payment received.');\n }}\n >\n Pay ₹1,299\n </StatefulButton>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{note}</Text>\n </View>\n );\n}\n"
167
+ },
168
+ {
169
+ "name": "Controlled",
170
+ "title": "Controlled",
171
+ "code": "import React, { useRef, useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { StatefulButton, type StatefulButtonState } from '@/components/ui/stateful-button';\nimport { useKit } from '@/lib/kit';\n\nconst ORDER: StatefulButtonState[] = ['idle', 'loading', 'success', 'error'];\n\nexport default function Controlled() {\n const { colors, radius } = useKit();\n const [state, setState] = useState<StatefulButtonState>('idle');\n return (\n <View style={{ gap: 14 }}>\n <StatefulButton state={state} loadingText=\"Uploading\" successText=\"Uploaded\" errorText=\"Retry upload\">\n Upload\n </StatefulButton>\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8 }}>\n {ORDER.map((s) => (\n <Text\n key={s}\n accessibilityRole=\"button\"\n accessibilityState={{ selected: s === state }}\n onPress={() => setState(s)}\n style={{\n paddingHorizontal: 12,\n paddingVertical: 8,\n borderRadius: radius.capsule(34),\n overflow: 'hidden',\n fontSize: 13,\n color: s === state ? colors.accentForeground : colors.foreground,\n backgroundColor: s === state ? colors.accent : colors.muted,\n }}\n >\n {s}\n </Text>\n ))}\n </View>\n </View>\n );\n}\n"
172
+ },
173
+ {
174
+ "name": "VariantsAndSizes",
175
+ "title": "Variants And Sizes",
176
+ "code": "import { Text, View } from 'react-native';\nimport { StatefulButton, type StatefulButtonState } from '@/components/ui/stateful-button';\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport default function VariantsAndSizes() {\n return (\n <View style={{ gap: 12 }}>\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}>\n <StatefulButton onPress={() => sleep(1000)}>Publish</StatefulButton>\n <StatefulButton variant=\"secondary\" onPress={() => sleep(1000)}>Sync</StatefulButton>\n <StatefulButton variant=\"outline\" onPress={() => sleep(1000)}>Export</StatefulButton>\n <StatefulButton variant=\"ghost\" onPress={() => sleep(1000)}>Archive</StatefulButton>\n </View>\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', gap: 10 }}>\n <StatefulButton size=\"sm\" pill onPress={() => sleep(1000)}>Small</StatefulButton>\n <StatefulButton size=\"md\" pill onPress={() => sleep(1000)}>Medium</StatefulButton>\n <StatefulButton size=\"lg\" pill onPress={() => sleep(1000)}>Large</StatefulButton>\n </View>\n </View>\n );\n}\n"
177
+ },
178
+ {
179
+ "name": "FollowToggle",
180
+ "title": "Follow Toggle",
181
+ "code": "import React, { useRef, useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { StatefulButton, type StatefulButtonState } from '@/components/ui/stateful-button';\nimport { useKit } from '@/lib/kit';\n\nconst sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));\n\nexport default function FollowToggle() {\n const { colors } = useKit();\n const [following, setFollowing] = useState(false);\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 12 }}>\n <View style={{ flexShrink: 1 }}>\n <Text style={{ fontSize: 16, fontWeight: '600', color: colors.foreground }}>Expo</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>@expo · 214K followers</Text>\n </View>\n <StatefulButton\n pill\n size=\"sm\"\n variant=\"secondary\"\n successText={following ? 'Following' : 'Unfollowed'}\n resetAfter={900}\n onPress={async () => {\n await sleep(700);\n setFollowing((f) => !f);\n }}\n >\n {following ? 'Following' : 'Follow'}\n </StatefulButton>\n </View>\n );\n}\n"
182
+ }
183
+ ]
184
+ }