@expokit/cli 0.0.0-stage → 0.2.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 (302) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -2
  3. package/bin/expo-kit.js +1204 -0
  4. package/bin/lib.mjs +343 -0
  5. package/bin/mcp.mjs +338 -0
  6. package/package.json +44 -4
  7. package/registry/_docs/accordion.json +99 -0
  8. package/registry/_docs/action-sheet.json +110 -0
  9. package/registry/_docs/alert.json +127 -0
  10. package/registry/_docs/avatar.json +203 -0
  11. package/registry/_docs/badge.json +117 -0
  12. package/registry/_docs/bottom-nav.json +119 -0
  13. package/registry/_docs/bottom-sheet.json +158 -0
  14. package/registry/_docs/bullet.json +24 -0
  15. package/registry/_docs/button-3d.json +122 -0
  16. package/registry/_docs/button.json +115 -0
  17. package/registry/_docs/calendar.json +182 -0
  18. package/registry/_docs/card-stack.json +108 -0
  19. package/registry/_docs/card.json +111 -0
  20. package/registry/_docs/carousel.json +102 -0
  21. package/registry/_docs/checkbox.json +93 -0
  22. package/registry/_docs/checkout.json +102 -0
  23. package/registry/_docs/chip-group.json +116 -0
  24. package/registry/_docs/color-swatch.json +66 -0
  25. package/registry/_docs/confetti.json +82 -0
  26. package/registry/_docs/coupon.json +176 -0
  27. package/registry/_docs/cube-pager.json +84 -0
  28. package/registry/_docs/dashboard.json +120 -0
  29. package/registry/_docs/date-picker.json +249 -0
  30. package/registry/_docs/dialog.json +162 -0
  31. package/registry/_docs/drawer.json +171 -0
  32. package/registry/_docs/fab.json +120 -0
  33. package/registry/_docs/flip-card.json +111 -0
  34. package/registry/_docs/floating-input.json +191 -0
  35. package/registry/_docs/gift-card.json +108 -0
  36. package/registry/_docs/glass-button.json +118 -0
  37. package/registry/_docs/glass-nav-bar.json +78 -0
  38. package/registry/_docs/glass-search-bar.json +90 -0
  39. package/registry/_docs/glass-segmented.json +66 -0
  40. package/registry/_docs/glass-sheet.json +108 -0
  41. package/registry/_docs/glass-tab-bar.json +76 -0
  42. package/registry/_docs/glass.json +78 -0
  43. package/registry/_docs/inbox.json +72 -0
  44. package/registry/_docs/isometric-bar.json +72 -0
  45. package/registry/_docs/kit.json +11 -0
  46. package/registry/_docs/kpi-card.json +11 -0
  47. package/registry/_docs/like-button.json +72 -0
  48. package/registry/_docs/liquidglass-tab.json +27 -0
  49. package/registry/_docs/list-item.json +200 -0
  50. package/registry/_docs/loyalty-card.json +108 -0
  51. package/registry/_docs/morph-button.json +108 -0
  52. package/registry/_docs/number-ticker.json +96 -0
  53. package/registry/_docs/offers.json +78 -0
  54. package/registry/_docs/onboarding.json +66 -0
  55. package/registry/_docs/otp-input.json +134 -0
  56. package/registry/_docs/pagination-dots.json +88 -0
  57. package/registry/_docs/parallax-layers.json +84 -0
  58. package/registry/_docs/parallax-scroll.json +186 -0
  59. package/registry/_docs/password-input.json +92 -0
  60. package/registry/_docs/payment-card.json +153 -0
  61. package/registry/_docs/pixel-bar.json +66 -0
  62. package/registry/_docs/polar-area.json +78 -0
  63. package/registry/_docs/profile.json +84 -0
  64. package/registry/_docs/progress-steps.json +92 -0
  65. package/registry/_docs/progress.json +132 -0
  66. package/registry/_docs/radio-group.json +92 -0
  67. package/registry/_docs/rating.json +90 -0
  68. package/registry/_docs/ring-carousel.json +102 -0
  69. package/registry/_docs/scratch-card.json +90 -0
  70. package/registry/_docs/search-bar.json +140 -0
  71. package/registry/_docs/search-results.json +120 -0
  72. package/registry/_docs/segmented-control.json +103 -0
  73. package/registry/_docs/select.json +252 -0
  74. package/registry/_docs/settings.json +72 -0
  75. package/registry/_docs/shimmer-button.json +78 -0
  76. package/registry/_docs/sign-in.json +198 -0
  77. package/registry/_docs/skeleton.json +142 -0
  78. package/registry/_docs/slide-to-confirm.json +84 -0
  79. package/registry/_docs/slider.json +157 -0
  80. package/registry/_docs/spin-wheel.json +114 -0
  81. package/registry/_docs/spinner.json +91 -0
  82. package/registry/_docs/stepper.json +168 -0
  83. package/registry/_docs/swipe-deck.json +102 -0
  84. package/registry/_docs/swipeable-row.json +72 -0
  85. package/registry/_docs/switch.json +104 -0
  86. package/registry/_docs/tabs.json +105 -0
  87. package/registry/_docs/tag-input.json +128 -0
  88. package/registry/_docs/textarea.json +175 -0
  89. package/registry/_docs/ticket-card.json +174 -0
  90. package/registry/_docs/ticket-shape.json +11 -0
  91. package/registry/_docs/tilt-card.json +141 -0
  92. package/registry/_docs/time-picker.json +253 -0
  93. package/registry/_docs/timeline-chart.json +30 -0
  94. package/registry/_docs/timeline.json +156 -0
  95. package/registry/_docs/toast.json +161 -0
  96. package/registry/_docs/treemap.json +30 -0
  97. package/registry/_docs/waterfall.json +36 -0
  98. package/registry/_docs/wheel-list.json +102 -0
  99. package/registry/accordion/files/src/components/ui/accordion.tsx +233 -0
  100. package/registry/accordion/files/src/lib/kit.tsx +219 -0
  101. package/registry/accordion.json +17 -0
  102. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +580 -0
  103. package/registry/action-sheet/files/src/lib/kit.tsx +219 -0
  104. package/registry/action-sheet.json +18 -0
  105. package/registry/alert/files/src/components/ui/alert.tsx +654 -0
  106. package/registry/alert/files/src/components/ui/button.tsx +274 -0
  107. package/registry/alert/files/src/lib/kit.tsx +219 -0
  108. package/registry/alert.json +20 -0
  109. package/registry/avatar/files/src/components/ui/avatar.tsx +790 -0
  110. package/registry/avatar/files/src/lib/kit.tsx +219 -0
  111. package/registry/avatar.json +18 -0
  112. package/registry/badge/files/src/components/ui/badge.tsx +316 -0
  113. package/registry/badge/files/src/lib/kit.tsx +219 -0
  114. package/registry/badge.json +17 -0
  115. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +1050 -0
  116. package/registry/bottom-nav/files/src/lib/kit.tsx +219 -0
  117. package/registry/bottom-nav.json +18 -0
  118. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +585 -0
  119. package/registry/bottom-sheet/files/src/lib/kit.tsx +219 -0
  120. package/registry/bottom-sheet.json +17 -0
  121. package/registry/bullet.json +14 -0
  122. package/registry/button/files/src/components/ui/button.tsx +274 -0
  123. package/registry/button/files/src/lib/kit.tsx +219 -0
  124. package/registry/button-3d/files/src/components/ui/button-3d.tsx +212 -0
  125. package/registry/button-3d/files/src/lib/kit.tsx +219 -0
  126. package/registry/button-3d.json +18 -0
  127. package/registry/button.json +18 -0
  128. package/registry/calendar/files/src/components/ui/calendar.tsx +822 -0
  129. package/registry/calendar/files/src/lib/kit.tsx +219 -0
  130. package/registry/calendar.json +17 -0
  131. package/registry/card/files/src/components/ui/card.tsx +300 -0
  132. package/registry/card/files/src/lib/kit.tsx +219 -0
  133. package/registry/card-stack.json +15 -0
  134. package/registry/card.json +17 -0
  135. package/registry/carousel.json +15 -0
  136. package/registry/checkbox/files/src/components/ui/checkbox.tsx +150 -0
  137. package/registry/checkbox/files/src/lib/kit.tsx +219 -0
  138. package/registry/checkbox.json +17 -0
  139. package/registry/checkout.json +26 -0
  140. package/registry/chip-group/files/src/components/ui/chip-group.tsx +269 -0
  141. package/registry/chip-group/files/src/lib/kit.tsx +219 -0
  142. package/registry/chip-group.json +17 -0
  143. package/registry/color-swatch.json +17 -0
  144. package/registry/confetti/files/src/components/ui/confetti.tsx +222 -0
  145. package/registry/confetti/files/src/lib/kit.tsx +219 -0
  146. package/registry/confetti.json +15 -0
  147. package/registry/coupon/files/src/components/ui/coupon.tsx +698 -0
  148. package/registry/coupon/files/src/components/ui/flip-card.tsx +205 -0
  149. package/registry/coupon/files/src/components/ui/tilt-card.tsx +635 -0
  150. package/registry/coupon/files/src/lib/kit.tsx +219 -0
  151. package/registry/coupon/files/src/lib/ticket-shape.tsx +543 -0
  152. package/registry/coupon.json +23 -0
  153. package/registry/cube-pager.json +15 -0
  154. package/registry/dashboard.json +32 -0
  155. package/registry/date-picker/files/src/components/ui/calendar.tsx +822 -0
  156. package/registry/date-picker/files/src/components/ui/date-picker.tsx +796 -0
  157. package/registry/date-picker/files/src/lib/kit.tsx +219 -0
  158. package/registry/date-picker.json +19 -0
  159. package/registry/dialog/files/src/components/ui/dialog.tsx +759 -0
  160. package/registry/dialog/files/src/lib/kit.tsx +219 -0
  161. package/registry/dialog.json +18 -0
  162. package/registry/drawer/files/src/components/ui/drawer.tsx +1135 -0
  163. package/registry/drawer/files/src/lib/kit.tsx +219 -0
  164. package/registry/drawer.json +18 -0
  165. package/registry/fab.json +18 -0
  166. package/registry/flip-card/files/src/components/ui/flip-card.tsx +205 -0
  167. package/registry/flip-card/files/src/lib/kit.tsx +219 -0
  168. package/registry/flip-card.json +15 -0
  169. package/registry/floating-input/files/src/components/ui/floating-input.tsx +528 -0
  170. package/registry/floating-input/files/src/lib/kit.tsx +219 -0
  171. package/registry/floating-input.json +17 -0
  172. package/registry/gift-card.json +21 -0
  173. package/registry/glass-button.json +22 -0
  174. package/registry/glass-nav-bar.json +21 -0
  175. package/registry/glass-search-bar.json +21 -0
  176. package/registry/glass-segmented.json +22 -0
  177. package/registry/glass-sheet.json +22 -0
  178. package/registry/glass-tab-bar.json +22 -0
  179. package/registry/glass.json +19 -0
  180. package/registry/inbox.json +30 -0
  181. package/registry/isometric-bar.json +14 -0
  182. package/registry/kit/files/src/lib/kit.tsx +219 -0
  183. package/registry/kit.json +11 -0
  184. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +45 -0
  185. package/registry/kpi-card.json +14 -0
  186. package/registry/like-button.json +18 -0
  187. package/registry/liquidglass-tab/files/src/app/(tabs)/_layout.tsx +97 -0
  188. package/registry/liquidglass-tab/files/src/app/(tabs)/alerts.tsx +5 -0
  189. package/registry/liquidglass-tab/files/src/app/(tabs)/explore.tsx +5 -0
  190. package/registry/liquidglass-tab/files/src/app/(tabs)/index.tsx +5 -0
  191. package/registry/liquidglass-tab/files/src/app/(tabs)/profile.tsx +5 -0
  192. package/registry/liquidglass-tab/files/src/app/_layout.tsx +33 -0
  193. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +259 -0
  194. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +122 -0
  195. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +61 -0
  196. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +130 -0
  197. package/registry/liquidglass-tab/files/src/lib/tabs.ts +108 -0
  198. package/registry/liquidglass-tab.json +23 -0
  199. package/registry/list-item/files/src/components/ui/list-item.tsx +508 -0
  200. package/registry/list-item/files/src/lib/kit.tsx +219 -0
  201. package/registry/list-item.json +17 -0
  202. package/registry/loyalty-card.json +23 -0
  203. package/registry/morph-button.json +18 -0
  204. package/registry/number-ticker.json +15 -0
  205. package/registry/offers.json +24 -0
  206. package/registry/onboarding.json +17 -0
  207. package/registry/otp-input/files/src/components/ui/otp-input.tsx +380 -0
  208. package/registry/otp-input/files/src/lib/kit.tsx +219 -0
  209. package/registry/otp-input.json +17 -0
  210. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +262 -0
  211. package/registry/pagination-dots/files/src/lib/kit.tsx +219 -0
  212. package/registry/pagination-dots.json +15 -0
  213. package/registry/parallax-layers.json +15 -0
  214. package/registry/parallax-scroll.json +17 -0
  215. package/registry/password-input/files/src/components/ui/floating-input.tsx +528 -0
  216. package/registry/password-input/files/src/components/ui/password-input.tsx +271 -0
  217. package/registry/password-input/files/src/lib/kit.tsx +219 -0
  218. package/registry/password-input.json +19 -0
  219. package/registry/payment-card/files/src/components/ui/flip-card.tsx +205 -0
  220. package/registry/payment-card/files/src/components/ui/payment-card.tsx +385 -0
  221. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +635 -0
  222. package/registry/payment-card/files/src/lib/kit.tsx +219 -0
  223. package/registry/payment-card.json +21 -0
  224. package/registry/pixel-bar.json +14 -0
  225. package/registry/polar-area.json +14 -0
  226. package/registry/profile.json +17 -0
  227. package/registry/progress/files/src/components/ui/progress.tsx +883 -0
  228. package/registry/progress/files/src/lib/kit.tsx +219 -0
  229. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +559 -0
  230. package/registry/progress-steps/files/src/lib/kit.tsx +219 -0
  231. package/registry/progress-steps.json +18 -0
  232. package/registry/progress.json +17 -0
  233. package/registry/radio-group/files/src/components/ui/radio-group.tsx +327 -0
  234. package/registry/radio-group/files/src/lib/kit.tsx +219 -0
  235. package/registry/radio-group.json +18 -0
  236. package/registry/rating.json +18 -0
  237. package/registry/ring-carousel.json +15 -0
  238. package/registry/scratch-card.json +18 -0
  239. package/registry/search-bar/files/src/components/ui/search-bar.tsx +530 -0
  240. package/registry/search-bar/files/src/lib/kit.tsx +219 -0
  241. package/registry/search-bar.json +17 -0
  242. package/registry/search-results.json +30 -0
  243. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +341 -0
  244. package/registry/segmented-control/files/src/lib/kit.tsx +219 -0
  245. package/registry/segmented-control.json +18 -0
  246. package/registry/select/files/src/components/ui/select.tsx +1398 -0
  247. package/registry/select/files/src/lib/kit.tsx +219 -0
  248. package/registry/select.json +17 -0
  249. package/registry/settings.json +26 -0
  250. package/registry/shimmer-button.json +17 -0
  251. package/registry/sign-in.json +26 -0
  252. package/registry/skeleton/files/src/components/ui/skeleton.tsx +799 -0
  253. package/registry/skeleton/files/src/lib/kit.tsx +219 -0
  254. package/registry/skeleton.json +17 -0
  255. package/registry/slide-to-confirm.json +18 -0
  256. package/registry/slider/files/src/components/ui/slider.tsx +716 -0
  257. package/registry/slider/files/src/lib/kit.tsx +219 -0
  258. package/registry/slider.json +17 -0
  259. package/registry/spin-wheel.json +18 -0
  260. package/registry/spinner/files/src/components/ui/spinner.tsx +335 -0
  261. package/registry/spinner/files/src/lib/kit.tsx +219 -0
  262. package/registry/spinner.json +17 -0
  263. package/registry/stepper/files/src/components/ui/stepper.tsx +646 -0
  264. package/registry/stepper/files/src/lib/kit.tsx +219 -0
  265. package/registry/stepper.json +17 -0
  266. package/registry/swipe-deck.json +15 -0
  267. package/registry/swipeable-row.json +15 -0
  268. package/registry/switch/files/src/components/ui/switch.tsx +344 -0
  269. package/registry/switch/files/src/lib/kit.tsx +219 -0
  270. package/registry/switch.json +18 -0
  271. package/registry/tabs/files/src/components/ui/tabs.tsx +539 -0
  272. package/registry/tabs/files/src/lib/kit.tsx +219 -0
  273. package/registry/tabs.json +17 -0
  274. package/registry/tag-input/files/src/components/ui/tag-input.tsx +720 -0
  275. package/registry/tag-input/files/src/lib/kit.tsx +219 -0
  276. package/registry/tag-input.json +17 -0
  277. package/registry/textarea/files/src/components/ui/textarea.tsx +553 -0
  278. package/registry/textarea/files/src/lib/kit.tsx +219 -0
  279. package/registry/textarea.json +17 -0
  280. package/registry/ticket-card.json +24 -0
  281. package/registry/ticket-shape/files/src/lib/kit.tsx +219 -0
  282. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +543 -0
  283. package/registry/ticket-shape.json +17 -0
  284. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +635 -0
  285. package/registry/tilt-card/files/src/lib/kit.tsx +219 -0
  286. package/registry/tilt-card.json +17 -0
  287. package/registry/time-picker/files/src/components/ui/time-picker.tsx +1159 -0
  288. package/registry/time-picker/files/src/lib/kit.tsx +219 -0
  289. package/registry/time-picker.json +17 -0
  290. package/registry/timeline/files/src/components/ui/avatar.tsx +790 -0
  291. package/registry/timeline/files/src/components/ui/timeline.tsx +1059 -0
  292. package/registry/timeline/files/src/lib/kit.tsx +219 -0
  293. package/registry/timeline-chart.json +14 -0
  294. package/registry/timeline.json +20 -0
  295. package/registry/toast/files/src/components/ui/button.tsx +274 -0
  296. package/registry/toast/files/src/components/ui/toast.tsx +1284 -0
  297. package/registry/toast/files/src/lib/kit.tsx +219 -0
  298. package/registry/toast.json +20 -0
  299. package/registry/treemap.json +14 -0
  300. package/registry/waterfall.json +14 -0
  301. package/registry/wheel-list.json +17 -0
  302. package/schema/components.schema.json +13 -0
@@ -0,0 +1,162 @@
1
+ {
2
+ "name": "dialog",
3
+ "category": "Overlays",
4
+ "aliases": [],
5
+ "exportName": "Dialog",
6
+ "files": [
7
+ "src/components/ui/dialog.tsx"
8
+ ],
9
+ "props": [
10
+ [
11
+ "open / defaultOpen",
12
+ "boolean",
13
+ "false",
14
+ "Controlled / uncontrolled visibility."
15
+ ],
16
+ [
17
+ "onOpenChange",
18
+ "(open: boolean) => void",
19
+ "—",
20
+ "Called on scrim tap, back, Escape and actions."
21
+ ],
22
+ [
23
+ "variant",
24
+ "\"default\" | \"destructive\"",
25
+ "\"default\"",
26
+ "Destructive adds a warning icon, a danger confirm and a warning haptic on open. The old \"sheet\" value is deprecated and renders as default."
27
+ ],
28
+ [
29
+ "tone",
30
+ "\"info\" | \"success\" | \"warning\" | \"danger\"",
31
+ "—",
32
+ "Living icon in a halo above the title with a soft glow of its colour; success / warning open with a matching haptic."
33
+ ],
34
+ [
35
+ "appearance",
36
+ "\"modern\" | \"ios\"",
37
+ "\"modern\"",
38
+ "Pill buttons, or iOS full-width text buttons split by hairlines."
39
+ ],
40
+ [
41
+ "title / description",
42
+ "string",
43
+ "—",
44
+ "Heading and body copy."
45
+ ],
46
+ [
47
+ "prompt",
48
+ "{ defaultValue?, value?, onChangeText?, placeholder?, secureTextEntry?, keyboardType?, maxLength?, … }",
49
+ "—",
50
+ "Adds a text field (a prompt dialog). Its text is passed to onConfirm / onPress; Return submits."
51
+ ],
52
+ [
53
+ "error",
54
+ "string",
55
+ "—",
56
+ "Error text under the field or description."
57
+ ],
58
+ [
59
+ "children",
60
+ "ReactNode",
61
+ "—",
62
+ "Custom content above the actions."
63
+ ],
64
+ [
65
+ "actions",
66
+ "{ label, onPress?, role?, disabled? }[]",
67
+ "Cancel + Confirm",
68
+ "Buttons. role: primary | default | destructive | cancel. onPress(value) may return a promise (spinner) or false (stays open and shakes)."
69
+ ],
70
+ [
71
+ "confirmLabel / cancelLabel",
72
+ "string / string | null",
73
+ "\"Continue\" / \"Cancel\"",
74
+ "Labels for the default buttons; cancelLabel null leaves a single button."
75
+ ],
76
+ [
77
+ "onConfirm / onCancel",
78
+ "(value) => void | false | Promise · () => void",
79
+ "—",
80
+ "Handlers for the default buttons (onCancel also fires on scrim, back and Escape)."
81
+ ],
82
+ [
83
+ "loading",
84
+ "boolean",
85
+ "false",
86
+ "Spinner on the confirm button; other buttons and dismissal are disabled."
87
+ ],
88
+ [
89
+ "actionsLayout",
90
+ "\"auto\" | \"row\" | \"stack\"",
91
+ "\"auto\"",
92
+ "Two short buttons side by side, otherwise stacked with Cancel last."
93
+ ],
94
+ [
95
+ "icon",
96
+ "ReactNode | false",
97
+ "tone icon",
98
+ "Custom node inside the halo, or false to hide it."
99
+ ],
100
+ [
101
+ "dismissible",
102
+ "boolean",
103
+ "true",
104
+ "Close on scrim tap, back and Escape."
105
+ ],
106
+ [
107
+ "haptics",
108
+ "boolean",
109
+ "true",
110
+ "Warning on open for destructive dialogs, success for success, error on shake."
111
+ ],
112
+ [
113
+ "accent",
114
+ "string",
115
+ "theme primary (accent in ios)",
116
+ "Colour of the primary action."
117
+ ],
118
+ [
119
+ "style",
120
+ "StyleProp<ViewStyle>",
121
+ "—",
122
+ "Card style."
123
+ ]
124
+ ],
125
+ "examples": [
126
+ {
127
+ "name": "Basic",
128
+ "title": "Basic",
129
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Dialog } from '@/components/ui/dialog';\n\nexport default function Basic() {\n const [open, setOpen] = useState(false);\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"accent\" onPress={() => setOpen(true)}>\n Enable notifications\n </Button>\n <Dialog\n open={open}\n onOpenChange={setOpen}\n tone=\"info\"\n title=\"Turn on notifications?\"\n description=\"Get a heads-up when someone mentions you, replies to your post or shares a file.\"\n cancelLabel=\"Not now\"\n confirmLabel=\"Allow\"\n />\n </View>\n );\n}\n"
130
+ },
131
+ {
132
+ "name": "Destructive",
133
+ "title": "Destructive",
134
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Dialog } from '@/components/ui/dialog';\nimport { useKit } from '@/lib/kit';\n\nfunction useMuted() {\n const { colors } = useKit();\n return { fontSize: 13, color: colors.mutedForeground, textAlign: 'center' as const };\n}\n\nexport default function Destructive() {\n const muted = useMuted();\n const [open, setOpen] = useState(false);\n const [status, setStatus] = useState('');\n return (\n <View style={{ alignItems: 'center', gap: 10 }}>\n <Button variant=\"destructive\" onPress={() => setOpen(true)}>\n Delete project\n </Button>\n {status ? <Text style={muted}>{status}</Text> : null}\n <Dialog\n open={open}\n onOpenChange={setOpen}\n variant=\"destructive\"\n title=\"Delete “Mobile redesign”?\"\n description=\"This permanently removes the project, its 48 files and all comments. This can't be undone.\"\n confirmLabel=\"Delete\"\n onConfirm={() =>\n new Promise<void>((resolve) =>\n setTimeout(() => {\n setStatus('“Mobile redesign” was deleted');\n resolve();\n }, 1200),\n )\n }\n />\n </View>\n );\n}\n"
135
+ },
136
+ {
137
+ "name": "WithForm",
138
+ "title": "With Form",
139
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Dialog } from '@/components/ui/dialog';\nimport { useKit } from '@/lib/kit';\n\nfunction useMuted() {\n const { colors } = useKit();\n return { fontSize: 13, color: colors.mutedForeground, textAlign: 'center' as const };\n}\n\nexport default function WithForm() {\n const muted = useMuted();\n const [open, setOpen] = useState(false);\n const [name, setName] = useState('Q3 roadmap');\n const [error, setError] = useState('');\n return (\n <View style={{ alignItems: 'center', gap: 10 }}>\n <Button variant=\"secondary\" onPress={() => setOpen(true)}>\n Rename board\n </Button>\n <Text style={muted}>Board: {name}</Text>\n <Dialog\n open={open}\n onOpenChange={(next) => {\n setOpen(next);\n setError('');\n }}\n title=\"Rename board\"\n description=\"Everyone with access will see the new name.\"\n prompt={{ defaultValue: name, placeholder: 'Board name', maxLength: 40, onChangeText: () => setError('') }}\n error={error}\n confirmLabel=\"Save\"\n onConfirm={(value) => {\n const next = value.trim();\n if (!next) {\n setError('Give the board a name.');\n return false;\n }\n setName(next);\n }}\n />\n </View>\n );\n}\n"
140
+ },
141
+ {
142
+ "name": "PaymentSent",
143
+ "title": "Payment Sent",
144
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Dialog } from '@/components/ui/dialog';\n\nexport default function PaymentSent() {\n const [open, setOpen] = useState(false);\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"accent\" onPress={() => setOpen(true)}>\n Send ₹1,850\n </Button>\n <Dialog\n open={open}\n onOpenChange={setOpen}\n tone=\"success\"\n title=\"Payment sent\"\n description=\"₹1,850 is on its way to Lena Fischer for “Dinner at Tasca”. She'll get it in a few seconds.\"\n cancelLabel={null}\n confirmLabel=\"Done\"\n />\n </View>\n );\n}\n"
145
+ },
146
+ {
147
+ "name": "SignOut",
148
+ "title": "Sign Out",
149
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Dialog } from '@/components/ui/dialog';\nimport { useKit } from '@/lib/kit';\n\nfunction useMuted() {\n const { colors } = useKit();\n return { fontSize: 13, color: colors.mutedForeground, textAlign: 'center' as const };\n}\n\nexport default function SignOut() {\n const muted = useMuted();\n const [open, setOpen] = useState(false);\n const [status, setStatus] = useState('iOS-style hairline buttons');\n return (\n <View style={{ alignItems: 'center', gap: 10 }}>\n <Button variant=\"outline\" onPress={() => setOpen(true)}>\n Sign out\n </Button>\n <Text style={muted}>{status}</Text>\n <Dialog\n open={open}\n onOpenChange={setOpen}\n appearance=\"ios\"\n title=\"Sign out of Northwind?\"\n description=\"Offline drafts on this device will be removed.\"\n actions={[\n { label: 'Cancel', role: 'cancel' },\n { label: 'Sign out', role: 'destructive', onPress: () => setStatus('Signed out') },\n ]}\n />\n </View>\n );\n}\n"
150
+ },
151
+ {
152
+ "name": "ThreeActions",
153
+ "title": "Three Actions",
154
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Dialog } from '@/components/ui/dialog';\n\nexport default function ThreeActions() {\n const [open, setOpen] = useState(false);\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"outline\" onPress={() => setOpen(true)}>\n Close document\n </Button>\n <Dialog\n open={open}\n onOpenChange={setOpen}\n appearance=\"ios\"\n title=\"Save changes to “Pitch deck”?\"\n description=\"Your edits will be lost if you don't save them.\"\n actions={[\n { label: 'Save', role: 'primary' },\n { label: \"Don't save\", role: 'destructive' },\n { label: 'Cancel', role: 'cancel' },\n ]}\n />\n </View>\n );\n}\n"
155
+ },
156
+ {
157
+ "name": "ActionSheetDemo",
158
+ "title": "Action Sheet Demo",
159
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { ActionSheet } from '@/components/ui/action-sheet';\nimport { Button } from '@/components/ui/button';\nimport { Dialog } from '@/components/ui/dialog';\nimport { useKit } from '@/lib/kit';\n\nfunction useMuted() {\n const { colors } = useKit();\n return { fontSize: 13, color: colors.mutedForeground, textAlign: 'center' as const };\n}\n\nexport default function ActionSheetDemo() {\n const muted = useMuted();\n const [open, setOpen] = useState(false);\n return (\n <View style={{ alignItems: 'center', gap: 8 }}>\n <Button variant=\"outline\" onPress={() => setOpen(true)}>\n Change profile photo\n </Button>\n <Text style={muted}>Bottom choices live in Action Sheet — not Dialog.</Text>\n <ActionSheet\n open={open}\n onOpenChange={setOpen}\n title=\"Profile photo\"\n message=\"Your photo is visible to everyone in your workspace.\"\n actions={[\n { label: 'Take photo' },\n { label: 'Choose from library' },\n { label: 'Remove photo', destructive: true },\n ]}\n />\n </View>\n );\n}\n"
160
+ }
161
+ ]
162
+ }
@@ -0,0 +1,171 @@
1
+ {
2
+ "name": "drawer",
3
+ "category": "Overlays",
4
+ "aliases": [],
5
+ "exportName": "Drawer",
6
+ "files": [
7
+ "src/components/ui/drawer.tsx"
8
+ ],
9
+ "props": [
10
+ [
11
+ "content",
12
+ "ReactNode",
13
+ "—",
14
+ "Panel content (usually DrawerItems); scrolls between header and footer."
15
+ ],
16
+ [
17
+ "header / footer",
18
+ "ReactNode",
19
+ "—",
20
+ "Pinned above / below the content (DrawerProfile, DrawerHeader, DrawerFooter…)."
21
+ ],
22
+ [
23
+ "children",
24
+ "ReactNode",
25
+ "—",
26
+ "The screen the drawer belongs to. Enables edge swipe and push; without children the drawer opens in a Modal."
27
+ ],
28
+ [
29
+ "open / defaultOpen",
30
+ "boolean",
31
+ "false",
32
+ "Controlled / uncontrolled visibility."
33
+ ],
34
+ [
35
+ "onOpenChange",
36
+ "(open: boolean) => void",
37
+ "—",
38
+ "Called on drags, scrim tap, Android back and Escape."
39
+ ],
40
+ [
41
+ "side",
42
+ "\"left\" | \"right\"",
43
+ "\"left\"",
44
+ "Edge the drawer slides from."
45
+ ],
46
+ [
47
+ "width",
48
+ "number | `${number}%`",
49
+ "\"80%\"",
50
+ "Panel width in points or percent of the container (max 360)."
51
+ ],
52
+ [
53
+ "variant",
54
+ "\"overlay\" | \"push\"",
55
+ "\"overlay\"",
56
+ "Slide over a dimmed screen, or shrink the screen to a rounded 0.92 card and slide it aside (inline only)."
57
+ ],
58
+ [
59
+ "edgeSwipe",
60
+ "boolean",
61
+ "true",
62
+ "Swipe in from the edge to open (needs children). Taps in the edge zone still reach the screen."
63
+ ],
64
+ [
65
+ "edgeWidth",
66
+ "number",
67
+ "24",
68
+ "Width of the edge-swipe zone."
69
+ ],
70
+ [
71
+ "dismissible",
72
+ "boolean",
73
+ "true",
74
+ "Close on scrim tap and drag."
75
+ ],
76
+ [
77
+ "scaleContent",
78
+ "boolean",
79
+ "true",
80
+ "Overlay variant: scale the screen behind to 0.96 with rounded corners while open (inline)."
81
+ ],
82
+ [
83
+ "haptics",
84
+ "boolean",
85
+ "true",
86
+ "Light impact on open; DrawerItems tick on press."
87
+ ],
88
+ [
89
+ "topInset / bottomInset",
90
+ "number",
91
+ "platform estimate",
92
+ "Space above the header / below the footer — pass safe-area insets for exact values."
93
+ ],
94
+ [
95
+ "accessibilityLabel",
96
+ "string",
97
+ "\"Navigation drawer\"",
98
+ "Panel label."
99
+ ],
100
+ [
101
+ "style / panelStyle",
102
+ "StyleProp<ViewStyle>",
103
+ "—",
104
+ "Root container / panel style."
105
+ ],
106
+ [
107
+ "DrawerProfile",
108
+ "{ name, email?, src?, status?, layout?: 'row' | 'stacked', onPress?, accessory? }",
109
+ "—",
110
+ "Account block: soft solid initials (or image) avatar with presence dot, name and email; pressable as an account / workspace switcher."
111
+ ],
112
+ [
113
+ "DrawerHeader",
114
+ "{ title?, subtitle?, leading?, showClose?, onClose? }",
115
+ "—",
116
+ "Title chrome for the header slot."
117
+ ],
118
+ [
119
+ "DrawerItem",
120
+ "{ label, icon?, badge?, badgeTone?, active?, tone?, onPress?, closeOnPress?, accent?, haptics?, trailing?, disabled? }",
121
+ "—",
122
+ "Nav row with a spring-in accent-tinted active pill, popping icon and count badge (neutral plain count / accent tinted pill / danger solid pill); tone=\"danger\" for Log out."
123
+ ],
124
+ [
125
+ "DrawerSection",
126
+ "{ title, trailing?, divider? }",
127
+ "—",
128
+ "Quiet heading between groups with a hairline divider above (divider={false} for the first group) and an optional trailing node."
129
+ ],
130
+ [
131
+ "DrawerFooter",
132
+ "{ label, detail?, icon?, progress?, onPress?, trailing? }",
133
+ "—",
134
+ "Compact footer row, e.g. a storage meter."
135
+ ],
136
+ [
137
+ "DrawerIcon",
138
+ "{ name, size?, color?, strokeWidth? }",
139
+ "—",
140
+ "The drawer's line icons, for menu buttons and headers."
141
+ ],
142
+ [
143
+ "useDrawer()",
144
+ "() => { open, setOpen, toggle, close }",
145
+ "—",
146
+ "Control the surrounding Drawer from inside it."
147
+ ]
148
+ ],
149
+ "examples": [
150
+ {
151
+ "name": "AppNavigation",
152
+ "title": "App Navigation",
153
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport {\n Drawer,\n DrawerFooter,\n DrawerHeader,\n DrawerIcon,\n DrawerItem,\n DrawerProfile,\n DrawerSection,\n useDrawer,\n type DrawerIconName,\n} from '@/components/ui/drawer';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst STATUS_H = 40;\n\n/** Phone-sized frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 600,\n borderRadius: radius.scale(28),\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.background,\n overflow: 'hidden',\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Fake status bar: time, signal and battery, drawn with Views. */\nfunction StatusBar() {\n const { colors, radius } = useKit();\n const ink = colors.foreground;\n return (\n <View style={{ height: STATUS_H, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24 }}>\n <Text style={{ fontSize: 15, color: ink, fontVariant: ['tabular-nums'] }}>9:41</Text>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>\n <View style={{ flexDirection: 'row', alignItems: 'flex-end', gap: 2 }}>\n {[4, 6, 8, 10].map((h) => (\n <View key={h} style={{ width: 3, height: h, borderRadius: radius.capsule(3), backgroundColor: ink }} />\n ))}\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 1 }}>\n <View style={{ width: 22, height: 11, padding: 1.5, borderRadius: radius.scale(3.5), borderWidth: 1, borderColor: alpha(ink, 0.4) }}>\n <View style={{ width: '72%', flex: 1, borderRadius: radius.scale(2), backgroundColor: ink }} />\n </View>\n <View style={{ width: 1.5, height: 4, borderRadius: 1, backgroundColor: alpha(ink, 0.4) }} />\n </View>\n </View>\n </View>\n );\n}\n\n/** The app screen behind the drawer: status bar plus your content. */\nfunction Phone({ children }: { children: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flex: 1, backgroundColor: colors.background }}>\n <StatusBar />\n {children}\n </View>\n );\n}\n\nfunction IconButton({ icon, label, onPress, color }: { icon: DrawerIconName; label: string; onPress?: () => void; color?: string }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n style={({ pressed }) => ({\n width: 44,\n height: 44,\n borderRadius: radius.capsule(44),\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: pressed ? colors.muted : 'transparent',\n })}\n >\n <DrawerIcon name={icon} size={22} color={color ?? colors.foreground} />\n </Pressable>\n );\n}\n\n/** The hamburger: toggles the surrounding Drawer through useDrawer(). */\nfunction MenuButton() {\n const { open, setOpen } = useDrawer();\n return <IconButton icon=\"menu\" label=\"Open navigation\" onPress={() => setOpen(!open)} />;\n}\n\n/** A plain inline top bar: hamburger, title (+ subtitle) and trailing actions. */\nfunction TopBar({ title, subtitle, right, menu = true }: { title: string; subtitle?: string; right?: React.ReactNode; menu?: boolean }) {\n const { colors } = useKit();\n return (\n <View style={{ height: 56, flexDirection: 'row', alignItems: 'center', paddingLeft: menu ? 6 : 16, paddingRight: 8, gap: 4 }}>\n {menu ? <MenuButton /> : null}\n <View style={{ flex: 1, minWidth: 0 }}>\n <Text numberOfLines={1} style={{ fontSize: subtitle ? 19 : 22, letterSpacing: -0.4, color: colors.foreground }}>\n {title}\n </Text>\n {subtitle ? (\n <Text numberOfLines={1} style={{ fontSize: 12.5, color: colors.mutedForeground }}>\n {subtitle}\n </Text>\n ) : null}\n </View>\n {right}\n </View>\n );\n}\n\nfunction Initials({ name, size = 32, square, ring }: { name: string; size?: number; square?: boolean; ring?: string }) {\n const { colors, radius } = useKit();\n const letters = name\n .split(/\\s+/)\n .slice(0, 2)\n .map((w) => w[0])\n .join('');\n return (\n <View\n style={{\n width: size,\n height: size,\n borderRadius: square ? radius.scale(size * 0.28) : radius.capsule(size),\n backgroundColor: square ? colors.foreground : alpha(colors.accent, 0.14),\n borderWidth: ring ? 2 : 0,\n borderColor: ring,\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: size * 0.38, color: square ? colors.background : colors.accent }}>{letters}</Text>\n </View>\n );\n}\n\nfunction Dot({ color, size = 10 }: { color: string; size?: number }) {\n const { radius } = useKit();\n return <View style={{ width: size, height: size, borderRadius: radius.capsule(size), backgroundColor: color }} />;\n}\n\n/* Mail ------------------------------------------------------------------------------------------ */\n\nconst MAIL = [\n { from: 'Lena Fischer', subject: 'Offsite agenda — final draft', snippet: 'Attached the plan for Thursday. Shout if the 10:00 slot clashes.', time: '9:41', unread: true },\n { from: 'GitHub', service: true, subject: '[northwind/app] PR #482 merged', snippet: 'feat: offline drafts for the editor (#482) was merged into main.', time: '9:12', unread: true },\n { from: 'Omar Haddad', subject: 'Re: Invoice 2041', snippet: 'Paid this morning — the receipt is in the thread below.', time: '8:03', unread: true },\n { from: 'Figma', service: true, subject: 'Priya commented on Checkout v3', snippet: '“Can we try the sheet at 90% height here?”', time: 'Tue' },\n { from: 'Stripe', service: true, subject: 'Your October payout is on its way', snippet: '₹2,48,000 will arrive in your account in 2 business days.', time: 'Mon' },\n { from: 'Jonas Weber', subject: 'Photos from Lisbon', snippet: 'Finally sorted them — 48 shots, the tram ones came out great.', time: 'Sun' },\n { from: 'Ana Costa', subject: 'Lunch next week?', snippet: 'There is a new ramen place near the office, Wednesday works.', time: 'Sat' },\n];\n\nfunction MailRow({ from, service, subject, snippet, time, unread }: (typeof MAIL)[number]) {\n const { colors } = useKit();\n return (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={`${unread ? 'Unread. ' : ''}${from}, ${subject}`}\n style={({ pressed }) => ({ flexDirection: 'row', gap: 12, paddingVertical: 11, paddingLeft: 16, paddingRight: 14, backgroundColor: pressed ? colors.muted : 'transparent' })}\n >\n <Initials name={from} size={40} square={service} />\n <View style={{ flex: 1, minWidth: 0, gap: 1 }}>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>\n <Text numberOfLines={1} style={{ flex: 1, fontSize: 15, color: unread ? colors.foreground : colors.mutedForeground }}>\n {from}\n </Text>\n <Text style={{ fontSize: 12.5, color: unread ? colors.accent : colors.mutedForeground }}>{time}</Text>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>\n <Text numberOfLines={1} style={{ flex: 1, fontSize: 14, color: colors.foreground, opacity: unread ? 1 : 0.8 }}>\n {subject}\n </Text>\n {unread ? <Dot color={colors.accent} size={8} /> : null}\n </View>\n <Text numberOfLines={1} style={{ fontSize: 13.5, color: colors.mutedForeground }}>\n {snippet}\n </Text>\n </View>\n </Pressable>\n );\n}\n\nexport default function AppNavigation() {\n const { colors, radius, dark } = useKit();\n const [page, setPage] = useState('Inbox');\n const nav: [DrawerIconName, string, number?][] = [\n ['inbox', 'Inbox', 12],\n ['star', 'Starred'],\n ['send', 'Sent'],\n ['file', 'Drafts', 2],\n ['archive', 'Archive'],\n ['trash', 'Trash'],\n ];\n const labels: [string, string, number?][] = [\n ['Design reviews', colors.accent, 4],\n ['Receipts', colors.success],\n ['Travel', colors.warning, 1],\n ];\n return (\n <Frame>\n <Drawer\n variant=\"push\"\n width=\"78%\"\n topInset={STATUS_H}\n header={<DrawerProfile name=\"Maya Patel\" email=\"maya@northwind.app\" status=\"online\" onPress={() => {}} />}\n content={\n <>\n {nav.map(([icon, label, badge]) => (\n <DrawerItem key={label} icon={icon} label={label} badge={badge} active={page === label} onPress={() => setPage(label)} />\n ))}\n <DrawerSection title=\"Labels\" trailing={<IconButton icon=\"plus\" label=\"New label\" color={colors.mutedForeground} />} />\n {labels.map(([label, color, badge]) => (\n <DrawerItem key={label} icon={<Dot color={color} />} label={label} badge={badge} active={page === label} onPress={() => setPage(label)} />\n ))}\n </>\n }\n footer={<DrawerFooter icon=\"cloud\" label=\"Storage\" detail=\"12.4 GB of 15 GB used\" progress={0.83} />}\n >\n <Phone>\n <TopBar\n title={page}\n subtitle={page === 'Inbox' ? '3 unread · maya@northwind.app' : undefined}\n right={\n <>\n <IconButton icon=\"search\" label=\"Search mail\" />\n <View style={{ paddingRight: 6 }}>\n <Initials name=\"Maya Patel\" size={32} />\n </View>\n </>\n }\n />\n <Text style={{ paddingHorizontal: 16, paddingTop: 4, paddingBottom: 2, fontSize: 13, color: colors.mutedForeground }}>Today</Text>\n {MAIL.map((m) => (\n <MailRow key={m.subject} {...m} />\n ))}\n <View\n style={{\n position: 'absolute',\n right: 16,\n bottom: 18,\n height: 52,\n paddingHorizontal: 18,\n flexDirection: 'row',\n alignItems: 'center',\n gap: 8,\n borderRadius: radius.scale(18),\n backgroundColor: colors.accent,\n shadowColor: dark ? colors.background : colors.foreground,\n shadowOpacity: 0.18,\n shadowRadius: 14,\n shadowOffset: { width: 0, height: 6 },\n elevation: 6,\n }}\n >\n <DrawerIcon name=\"plus\" size={20} color={colors.accentForeground} strokeWidth={2} />\n <Text style={{ fontSize: 15, color: colors.accentForeground }}>Compose</Text>\n </View>\n </Phone>\n </Drawer>\n </Frame>\n );\n}\n\n/* Workspace ------------------------------------------------------------------------------------- */\n"
154
+ },
155
+ {
156
+ "name": "WorkspaceSwitcher",
157
+ "title": "Workspace Switcher",
158
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport {\n Drawer,\n DrawerFooter,\n DrawerHeader,\n DrawerIcon,\n DrawerItem,\n DrawerProfile,\n DrawerSection,\n useDrawer,\n type DrawerIconName,\n} from '@/components/ui/drawer';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst STATUS_H = 40;\n\n/** Phone-sized frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 600,\n borderRadius: radius.scale(28),\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.background,\n overflow: 'hidden',\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Fake status bar: time, signal and battery, drawn with Views. */\nfunction StatusBar() {\n const { colors, radius } = useKit();\n const ink = colors.foreground;\n return (\n <View style={{ height: STATUS_H, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24 }}>\n <Text style={{ fontSize: 15, color: ink, fontVariant: ['tabular-nums'] }}>9:41</Text>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>\n <View style={{ flexDirection: 'row', alignItems: 'flex-end', gap: 2 }}>\n {[4, 6, 8, 10].map((h) => (\n <View key={h} style={{ width: 3, height: h, borderRadius: radius.capsule(3), backgroundColor: ink }} />\n ))}\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 1 }}>\n <View style={{ width: 22, height: 11, padding: 1.5, borderRadius: radius.scale(3.5), borderWidth: 1, borderColor: alpha(ink, 0.4) }}>\n <View style={{ width: '72%', flex: 1, borderRadius: radius.scale(2), backgroundColor: ink }} />\n </View>\n <View style={{ width: 1.5, height: 4, borderRadius: 1, backgroundColor: alpha(ink, 0.4) }} />\n </View>\n </View>\n </View>\n );\n}\n\n/** The app screen behind the drawer: status bar plus your content. */\nfunction Phone({ children }: { children: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flex: 1, backgroundColor: colors.background }}>\n <StatusBar />\n {children}\n </View>\n );\n}\n\nfunction IconButton({ icon, label, onPress, color }: { icon: DrawerIconName; label: string; onPress?: () => void; color?: string }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n style={({ pressed }) => ({\n width: 44,\n height: 44,\n borderRadius: radius.capsule(44),\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: pressed ? colors.muted : 'transparent',\n })}\n >\n <DrawerIcon name={icon} size={22} color={color ?? colors.foreground} />\n </Pressable>\n );\n}\n\n/** The hamburger: toggles the surrounding Drawer through useDrawer(). */\nfunction MenuButton() {\n const { open, setOpen } = useDrawer();\n return <IconButton icon=\"menu\" label=\"Open navigation\" onPress={() => setOpen(!open)} />;\n}\n\n/** A plain inline top bar: hamburger, title (+ subtitle) and trailing actions. */\nfunction TopBar({ title, subtitle, right, menu = true }: { title: string; subtitle?: string; right?: React.ReactNode; menu?: boolean }) {\n const { colors } = useKit();\n return (\n <View style={{ height: 56, flexDirection: 'row', alignItems: 'center', paddingLeft: menu ? 6 : 16, paddingRight: 8, gap: 4 }}>\n {menu ? <MenuButton /> : null}\n <View style={{ flex: 1, minWidth: 0 }}>\n <Text numberOfLines={1} style={{ fontSize: subtitle ? 19 : 22, letterSpacing: -0.4, color: colors.foreground }}>\n {title}\n </Text>\n {subtitle ? (\n <Text numberOfLines={1} style={{ fontSize: 12.5, color: colors.mutedForeground }}>\n {subtitle}\n </Text>\n ) : null}\n </View>\n {right}\n </View>\n );\n}\n\nfunction Initials({ name, size = 32, square, ring }: { name: string; size?: number; square?: boolean; ring?: string }) {\n const { colors, radius } = useKit();\n const letters = name\n .split(/\\s+/)\n .slice(0, 2)\n .map((w) => w[0])\n .join('');\n return (\n <View\n style={{\n width: size,\n height: size,\n borderRadius: square ? radius.scale(size * 0.28) : radius.capsule(size),\n backgroundColor: square ? colors.foreground : alpha(colors.accent, 0.14),\n borderWidth: ring ? 2 : 0,\n borderColor: ring,\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: size * 0.38, color: square ? colors.background : colors.accent }}>{letters}</Text>\n </View>\n );\n}\n\nfunction Dot({ color, size = 10 }: { color: string; size?: number }) {\n const { radius } = useKit();\n return <View style={{ width: size, height: size, borderRadius: radius.capsule(size), backgroundColor: color }} />;\n}\n\n/* Mail ------------------------------------------------------------------------------------------ */\n\nconst WORKSPACES = [\n { name: 'Northwind', detail: 'northwind.app · 24 members' },\n { name: 'Side Quests', detail: 'Personal · 1 member' },\n { name: 'Acme Studio', detail: 'acme.studio · 8 members' },\n];\n\ntype TaskState = 'In progress' | 'In review' | 'Todo';\n\nconst TASKS: { id: string; title: string; state: TaskState; due: string; done: number; total: number; people: string[] }[] = [\n { id: 'MOB-212', title: 'Sheet snap points jitter on Android 14', state: 'In progress', due: 'Today', done: 3, total: 5, people: ['Maya Patel', 'Omar Haddad'] },\n { id: 'MOB-208', title: 'Offline drafts for the editor', state: 'In review', due: 'Thu', done: 6, total: 6, people: ['Lena Fischer'] },\n { id: 'MOB-199', title: 'Rewrite the push permission prompt', state: 'Todo', due: 'Mon', done: 0, total: 3, people: ['Priya Nair', 'Maya Patel'] },\n { id: 'MOB-187', title: 'Haptics on tab change', state: 'Todo', due: 'Oct 28', done: 1, total: 2, people: ['Jonas Weber'] },\n];\n\nfunction StatePill({ state }: { state: TaskState }) {\n const { colors, radius } = useKit();\n const tone = state === 'In progress' ? colors.accent : state === 'In review' ? colors.warning : colors.mutedForeground;\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6, height: 24, paddingHorizontal: 9, borderRadius: radius.capsule(24), backgroundColor: alpha(tone, 0.13) }}>\n <Dot color={tone} size={7} />\n <Text style={{ fontSize: 12, color: colors.foreground }}>{state}</Text>\n </View>\n );\n}\n\nfunction TaskCard({ id, title, state, due, done, total, people }: (typeof TASKS)[number]) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n padding: 14,\n gap: 10,\n borderRadius: radius.scale(16),\n backgroundColor: colors.card,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n shadowColor: dark ? colors.background : colors.foreground,\n shadowOpacity: dark ? 0.4 : 0.05,\n shadowRadius: 10,\n shadowOffset: { width: 0, height: 3 },\n elevation: 1,\n }}\n >\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>\n <StatePill state={state} />\n <Text style={{ fontSize: 12, color: colors.mutedForeground, fontVariant: ['tabular-nums'] }}>{id}</Text>\n </View>\n <Text numberOfLines={1} style={{ fontSize: 15.5, letterSpacing: -0.2, color: colors.foreground }}>\n {title}\n </Text>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 14 }}>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>\n <DrawerIcon name=\"calendar\" size={14} color={due === 'Today' ? colors.danger : colors.mutedForeground} />\n <Text style={{ fontSize: 12.5, color: due === 'Today' ? colors.danger : colors.mutedForeground }}>{due}</Text>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>\n <DrawerIcon name=\"check\" size={14} color={colors.mutedForeground} />\n <Text style={{ fontSize: 12.5, color: colors.mutedForeground, fontVariant: ['tabular-nums'] }}>\n {done}/{total}\n </Text>\n </View>\n <View style={{ flex: 1 }} />\n <View style={{ flexDirection: 'row' }}>\n {people.map((p, i) => (\n <View key={p} style={{ marginLeft: i ? -8 : 0 }}>\n <Initials name={p} size={26} ring={colors.card} />\n </View>\n ))}\n </View>\n </View>\n </View>\n );\n}\n\nexport default function WorkspaceSwitcher() {\n const { colors, radius } = useKit();\n const [workspace, setWorkspace] = useState(WORKSPACES[0]);\n const [picking, setPicking] = useState(false);\n const [page, setPage] = useState('My issues');\n const [filter, setFilter] = useState('Active');\n return (\n <Frame>\n <Drawer\n topInset={STATUS_H}\n header={<DrawerProfile name={workspace.name} email={workspace.detail} onPress={() => setPicking((v) => !v)} accessibilityHint=\"Switch workspace\" />}\n content={\n picking ? (\n <>\n <DrawerSection title=\"Workspaces\" divider={false} />\n {WORKSPACES.map((w) => (\n <DrawerItem\n key={w.name}\n label={w.name}\n icon={w.name === workspace.name ? 'check' : 'layers'}\n active={w.name === workspace.name}\n closeOnPress={false}\n onPress={() => {\n setWorkspace(w);\n setPicking(false);\n }}\n />\n ))}\n <DrawerItem icon=\"plus\" label=\"Create workspace\" closeOnPress={false} />\n </>\n ) : (\n <>\n {(\n [\n ['inbox', 'Inbox', 5, 'accent'],\n ['target', 'My issues', 14],\n ['search', 'Search'],\n ] as [DrawerIconName, string, number?, 'accent'?][]\n ).map(([icon, label, badge, badgeTone]) => (\n <DrawerItem key={label} icon={icon} label={label} badge={badge} badgeTone={badgeTone} active={page === label} onPress={() => setPage(label)} />\n ))}\n <DrawerSection title=\"Teams\" trailing={<IconButton icon=\"plus\" label=\"New team\" color={colors.mutedForeground} />} />\n {(\n [\n ['layers', 'Mobile', 8],\n ['layers', 'Platform', 3],\n ['users', 'Design'],\n ] as [DrawerIconName, string, number?][]\n ).map(([icon, label, badge]) => (\n <DrawerItem key={label} icon={icon} label={label} badge={badge} active={page === label} onPress={() => setPage(label)} />\n ))}\n </>\n )\n }\n footer={\n <>\n <DrawerItem icon=\"settings\" label=\"Settings\" active={page === 'Settings'} onPress={() => setPage('Settings')} />\n <DrawerItem icon=\"logout\" label=\"Log out\" tone=\"danger\" />\n </>\n }\n >\n <Phone>\n <TopBar title={page} subtitle={`${workspace.name} · Mobile team`} right={<IconButton icon=\"plus\" label=\"New issue\" />} />\n <View style={{ flexDirection: 'row', gap: 8, paddingHorizontal: 16, paddingTop: 2, paddingBottom: 12 }}>\n {[\n ['Active', '4'],\n ['Backlog', '9'],\n ['Done', '23'],\n ].map(([label, n]) => {\n const on = filter === label;\n return (\n <Pressable\n key={label}\n onPress={() => setFilter(label)}\n accessibilityRole=\"button\"\n accessibilityState={{ selected: on }}\n style={{\n height: 32,\n paddingHorizontal: 12,\n flexDirection: 'row',\n alignItems: 'center',\n gap: 6,\n borderRadius: radius.capsule(32),\n backgroundColor: on ? alpha(colors.accent, 0.13) : 'transparent',\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: on ? alpha(colors.accent, 0.3) : colors.border,\n }}\n >\n <Text style={{ fontSize: 13.5, color: on ? colors.accent : colors.foreground }}>{label}</Text>\n <Text style={{ fontSize: 12.5, color: on ? colors.accent : colors.mutedForeground }}>{n}</Text>\n </Pressable>\n );\n })}\n </View>\n <View style={{ paddingHorizontal: 16, gap: 10 }}>\n {TASKS.map((t) => (\n <TaskCard key={t.id} {...t} />\n ))}\n </View>\n </Phone>\n </Drawer>\n </Frame>\n );\n}\n\n/* Stays (right panel) --------------------------------------------------------------------------- */\n"
159
+ },
160
+ {
161
+ "name": "RightPanel",
162
+ "title": "Right Panel",
163
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport {\n Drawer,\n DrawerFooter,\n DrawerHeader,\n DrawerIcon,\n DrawerItem,\n DrawerProfile,\n DrawerSection,\n useDrawer,\n type DrawerIconName,\n} from '@/components/ui/drawer';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst STATUS_H = 40;\n\n/** Phone-sized frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 600,\n borderRadius: radius.scale(28),\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.background,\n overflow: 'hidden',\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Fake status bar: time, signal and battery, drawn with Views. */\nfunction StatusBar() {\n const { colors, radius } = useKit();\n const ink = colors.foreground;\n return (\n <View style={{ height: STATUS_H, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24 }}>\n <Text style={{ fontSize: 15, color: ink, fontVariant: ['tabular-nums'] }}>9:41</Text>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>\n <View style={{ flexDirection: 'row', alignItems: 'flex-end', gap: 2 }}>\n {[4, 6, 8, 10].map((h) => (\n <View key={h} style={{ width: 3, height: h, borderRadius: radius.capsule(3), backgroundColor: ink }} />\n ))}\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 1 }}>\n <View style={{ width: 22, height: 11, padding: 1.5, borderRadius: radius.scale(3.5), borderWidth: 1, borderColor: alpha(ink, 0.4) }}>\n <View style={{ width: '72%', flex: 1, borderRadius: radius.scale(2), backgroundColor: ink }} />\n </View>\n <View style={{ width: 1.5, height: 4, borderRadius: 1, backgroundColor: alpha(ink, 0.4) }} />\n </View>\n </View>\n </View>\n );\n}\n\n/** The app screen behind the drawer: status bar plus your content. */\nfunction Phone({ children }: { children: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flex: 1, backgroundColor: colors.background }}>\n <StatusBar />\n {children}\n </View>\n );\n}\n\nfunction IconButton({ icon, label, onPress, color }: { icon: DrawerIconName; label: string; onPress?: () => void; color?: string }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n style={({ pressed }) => ({\n width: 44,\n height: 44,\n borderRadius: radius.capsule(44),\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: pressed ? colors.muted : 'transparent',\n })}\n >\n <DrawerIcon name={icon} size={22} color={color ?? colors.foreground} />\n </Pressable>\n );\n}\n\n/** The hamburger: toggles the surrounding Drawer through useDrawer(). */\nfunction MenuButton() {\n const { open, setOpen } = useDrawer();\n return <IconButton icon=\"menu\" label=\"Open navigation\" onPress={() => setOpen(!open)} />;\n}\n\n/** A plain inline top bar: hamburger, title (+ subtitle) and trailing actions. */\nfunction TopBar({ title, subtitle, right, menu = true }: { title: string; subtitle?: string; right?: React.ReactNode; menu?: boolean }) {\n const { colors } = useKit();\n return (\n <View style={{ height: 56, flexDirection: 'row', alignItems: 'center', paddingLeft: menu ? 6 : 16, paddingRight: 8, gap: 4 }}>\n {menu ? <MenuButton /> : null}\n <View style={{ flex: 1, minWidth: 0 }}>\n <Text numberOfLines={1} style={{ fontSize: subtitle ? 19 : 22, letterSpacing: -0.4, color: colors.foreground }}>\n {title}\n </Text>\n {subtitle ? (\n <Text numberOfLines={1} style={{ fontSize: 12.5, color: colors.mutedForeground }}>\n {subtitle}\n </Text>\n ) : null}\n </View>\n {right}\n </View>\n );\n}\n\ntype ArtKind = 'sunset' | 'grid' | 'rings' | 'stripes' | 'split';\n\n/**\n * Album art drawn with plain Views in theme colours. `size` 0 stretches to the parent width\n * (then pass `height`).\n */\nfunction Art({ kind, size, height, radiusSize }: { kind: ArtKind; size: number; height?: number; radiusSize?: number }) {\n const { colors, radius } = useKit();\n const h = height ?? size;\n const w = size || undefined;\n const d = Math.min(size || h, h);\n const box = {\n width: w,\n alignSelf: size ? undefined : ('stretch' as const),\n height: h,\n borderRadius: radiusSize ?? radius.scale(size > 60 ? 14 : 8),\n overflow: 'hidden' as const,\n };\n const on = colors.accentForeground;\n if (kind === 'sunset') {\n return (\n <View style={[box, { backgroundColor: colors.accent, alignItems: 'center' }]}>\n <View style={{ position: 'absolute', top: h * 0.3, width: d * 0.5, height: d * 0.5, borderRadius: radius.capsule(d), backgroundColor: alpha(on, 0.85) }} />\n <View style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: h * 0.34, backgroundColor: alpha(colors.foreground, 0.35) }} />\n <View style={{ position: 'absolute', left: 0, right: 0, bottom: h * 0.22, height: Math.max(1, h * 0.03), backgroundColor: alpha(on, 0.35) }} />\n </View>\n );\n }\n if (kind === 'grid') {\n const cell = d * 0.18;\n return (\n <View style={[box, { backgroundColor: colors.foreground, alignItems: 'center', justifyContent: 'center' }]}>\n <View style={{ width: cell * 3 + cell * 0.5 * 2, flexDirection: 'row', flexWrap: 'wrap', gap: cell * 0.5 }}>\n {[1, 0.25, 0.6, 0.25, 1, 0.25, 0.6, 0.25, 1].map((a, i) => (\n <View key={i} style={{ width: cell, height: cell, borderRadius: radius.scale(cell * 0.3), backgroundColor: alpha(colors.accent, a) }} />\n ))}\n </View>\n </View>\n );\n }\n if (kind === 'rings') {\n return (\n <View style={[box, { backgroundColor: alpha(colors.accent, 0.16), alignItems: 'center', justifyContent: 'center' }]}>\n {[0.95, 0.7, 0.45, 0.2].map((f) => (\n <View\n key={f}\n style={{\n position: 'absolute',\n width: d * f,\n height: d * f,\n borderRadius: radius.capsule(d),\n borderWidth: Math.max(1.5, d * 0.03),\n borderColor: alpha(colors.accent, 1 - f * 0.6),\n }}\n />\n ))}\n </View>\n );\n }\n if (kind === 'stripes') {\n return (\n <View style={[box, { backgroundColor: colors.muted, justifyContent: 'center', alignItems: 'center' }]}>\n <View style={{ width: d * 1.6, gap: d * 0.07, transform: [{ rotate: '-30deg' }] }}>\n {[0.9, 0.55, 0.3, 0.55, 0.9].map((a, i) => (\n <View key={i} style={{ height: d * 0.1, backgroundColor: alpha(colors.accent, a) }} />\n ))}\n </View>\n </View>\n );\n }\n return (\n <View style={[box, { backgroundColor: colors.foreground, flexDirection: 'row' }]}>\n <View style={{ flex: 1, backgroundColor: colors.accent }} />\n <View style={{ flex: 1 }} />\n <View\n style={{\n position: 'absolute',\n left: (w ?? d) / 2 - d * 0.22,\n top: h / 2 - d * 0.22,\n width: d * 0.44,\n height: d * 0.44,\n borderRadius: radius.capsule(d),\n backgroundColor: colors.background,\n }}\n />\n </View>\n );\n}\n\nexport default function RightPanel() {\n const { colors, radius } = useKit();\n const [open, setOpen] = useState(false);\n const [price, setPrice] = useState('Any price');\n const [tags, setTags] = useState<string[]>(['Free cancellation']);\n const toggle = (t: string) => setTags((s) => (s.includes(t) ? s.filter((x) => x !== t) : [...s, t]));\n const count = 48 - tags.length * 9 - (price === 'Any price' ? 0 : 11);\n const active = tags.length + (price !== 'Any price' ? 1 : 0);\n return (\n <Frame>\n <Drawer\n open={open}\n onOpenChange={setOpen}\n side=\"right\"\n width=\"86%\"\n topInset={STATUS_H}\n accessibilityLabel=\"Filters\"\n header={<DrawerHeader title=\"Filters\" subtitle=\"Porto · Jun 3 – 7 · 2 guests\" showClose onClose={() => setOpen(false)} />}\n content={\n <>\n <DrawerSection title=\"Price per night\" divider={false} />\n {['Any price', 'Under ₹5,000', '₹5,000 – ₹10,000', '₹10,000+'].map((p) => (\n <DrawerItem key={p} label={p} icon={price === p ? 'check' : <View />} active={price === p} closeOnPress={false} onPress={() => setPrice(p)} />\n ))}\n <DrawerSection title=\"Amenities\" />\n {['Free cancellation', 'Breakfast included', 'Pet friendly', 'Workspace'].map((t) => (\n <DrawerItem key={t} label={t} icon={tags.includes(t) ? 'check' : <View />} active={tags.includes(t)} closeOnPress={false} onPress={() => toggle(t)} />\n ))}\n </>\n }\n footer={\n <View style={{ flexDirection: 'row', gap: 8, paddingHorizontal: 4 }}>\n <Button\n variant=\"outline\"\n style={{ flex: 1 }}\n onPress={() => {\n setPrice('Any price');\n setTags([]);\n }}\n >\n Reset\n </Button>\n <Button variant=\"accent\" style={{ flex: 1.4 }} onPress={() => setOpen(false)}>\n {`Show ${count} stays`}\n </Button>\n </View>\n }\n >\n <Phone>\n <TopBar\n menu={false}\n title=\"Stays in Porto\"\n subtitle={`${count} places · Jun 3 – 7`}\n right={\n <Button size=\"sm\" variant=\"secondary\" pill left={<DrawerIcon name=\"filter\" size={16} />} onPress={() => setOpen(true)}>\n {active ? `Filters · ${active}` : 'Filters'}\n </Button>\n }\n />\n <View style={{ paddingHorizontal: 16, paddingTop: 4, gap: 16 }}>\n {[\n { name: 'Casa da Ribeira', meta: 'River view · 1 bedroom', price: '₹6,800', rating: '4.92', art: 'sunset' as const },\n { name: 'Azulejo Loft', meta: 'Breakfast included', price: '₹4,800', rating: '4.81', art: 'grid' as const },\n { name: 'Miradouro Suites', meta: 'Free cancellation', price: '₹12,500', rating: '4.97', art: 'rings' as const },\n ].map((s) => (\n <View key={s.name} style={{ gap: 8 }}>\n <Art kind={s.art} size={0} height={112} radiusSize={radius.scale(18)} />\n <View style={{ flexDirection: 'row', alignItems: 'flex-start', gap: 8 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ fontSize: 15, color: colors.foreground }}>{s.name}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{s.meta}</Text>\n </View>\n <View style={{ alignItems: 'flex-end' }}>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 4 }}>\n <DrawerIcon name=\"star\" size={13} color={colors.foreground} />\n <Text style={{ fontSize: 13, color: colors.foreground }}>{s.rating}</Text>\n </View>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>\n <Text style={{ color: colors.foreground }}>{s.price}</Text> night\n </Text>\n </View>\n </View>\n </View>\n ))}\n </View>\n </Phone>\n </Drawer>\n </Frame>\n );\n}\n\n/* Music ----------------------------------------------------------------------------------------- */\n"
164
+ },
165
+ {
166
+ "name": "PushLayout",
167
+ "title": "Push Layout",
168
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport {\n Drawer,\n DrawerFooter,\n DrawerHeader,\n DrawerIcon,\n DrawerItem,\n DrawerProfile,\n DrawerSection,\n useDrawer,\n type DrawerIconName,\n} from '@/components/ui/drawer';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst STATUS_H = 40;\n\n/** Phone-sized frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 600,\n borderRadius: radius.scale(28),\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.background,\n overflow: 'hidden',\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Fake status bar: time, signal and battery, drawn with Views. */\nfunction StatusBar() {\n const { colors, radius } = useKit();\n const ink = colors.foreground;\n return (\n <View style={{ height: STATUS_H, flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', paddingHorizontal: 24 }}>\n <Text style={{ fontSize: 15, color: ink, fontVariant: ['tabular-nums'] }}>9:41</Text>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 6 }}>\n <View style={{ flexDirection: 'row', alignItems: 'flex-end', gap: 2 }}>\n {[4, 6, 8, 10].map((h) => (\n <View key={h} style={{ width: 3, height: h, borderRadius: radius.capsule(3), backgroundColor: ink }} />\n ))}\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 1 }}>\n <View style={{ width: 22, height: 11, padding: 1.5, borderRadius: radius.scale(3.5), borderWidth: 1, borderColor: alpha(ink, 0.4) }}>\n <View style={{ width: '72%', flex: 1, borderRadius: radius.scale(2), backgroundColor: ink }} />\n </View>\n <View style={{ width: 1.5, height: 4, borderRadius: 1, backgroundColor: alpha(ink, 0.4) }} />\n </View>\n </View>\n </View>\n );\n}\n\n/** The app screen behind the drawer: status bar plus your content. */\nfunction Phone({ children }: { children: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flex: 1, backgroundColor: colors.background }}>\n <StatusBar />\n {children}\n </View>\n );\n}\n\nfunction IconButton({ icon, label, onPress, color }: { icon: DrawerIconName; label: string; onPress?: () => void; color?: string }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n onPress={onPress}\n accessibilityRole=\"button\"\n accessibilityLabel={label}\n style={({ pressed }) => ({\n width: 44,\n height: 44,\n borderRadius: radius.capsule(44),\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: pressed ? colors.muted : 'transparent',\n })}\n >\n <DrawerIcon name={icon} size={22} color={color ?? colors.foreground} />\n </Pressable>\n );\n}\n\n/** The hamburger: toggles the surrounding Drawer through useDrawer(). */\nfunction MenuButton() {\n const { open, setOpen } = useDrawer();\n return <IconButton icon=\"menu\" label=\"Open navigation\" onPress={() => setOpen(!open)} />;\n}\n\n/** A plain inline top bar: hamburger, title (+ subtitle) and trailing actions. */\nfunction TopBar({ title, subtitle, right, menu = true }: { title: string; subtitle?: string; right?: React.ReactNode; menu?: boolean }) {\n const { colors } = useKit();\n return (\n <View style={{ height: 56, flexDirection: 'row', alignItems: 'center', paddingLeft: menu ? 6 : 16, paddingRight: 8, gap: 4 }}>\n {menu ? <MenuButton /> : null}\n <View style={{ flex: 1, minWidth: 0 }}>\n <Text numberOfLines={1} style={{ fontSize: subtitle ? 19 : 22, letterSpacing: -0.4, color: colors.foreground }}>\n {title}\n </Text>\n {subtitle ? (\n <Text numberOfLines={1} style={{ fontSize: 12.5, color: colors.mutedForeground }}>\n {subtitle}\n </Text>\n ) : null}\n </View>\n {right}\n </View>\n );\n}\n\ntype ArtKind = 'sunset' | 'grid' | 'rings' | 'stripes' | 'split';\n\n/**\n * Album art drawn with plain Views in theme colours. `size` 0 stretches to the parent width\n * (then pass `height`).\n */\nfunction Art({ kind, size, height, radiusSize }: { kind: ArtKind; size: number; height?: number; radiusSize?: number }) {\n const { colors, radius } = useKit();\n const h = height ?? size;\n const w = size || undefined;\n const d = Math.min(size || h, h);\n const box = {\n width: w,\n alignSelf: size ? undefined : ('stretch' as const),\n height: h,\n borderRadius: radiusSize ?? radius.scale(size > 60 ? 14 : 8),\n overflow: 'hidden' as const,\n };\n const on = colors.accentForeground;\n if (kind === 'sunset') {\n return (\n <View style={[box, { backgroundColor: colors.accent, alignItems: 'center' }]}>\n <View style={{ position: 'absolute', top: h * 0.3, width: d * 0.5, height: d * 0.5, borderRadius: radius.capsule(d), backgroundColor: alpha(on, 0.85) }} />\n <View style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: h * 0.34, backgroundColor: alpha(colors.foreground, 0.35) }} />\n <View style={{ position: 'absolute', left: 0, right: 0, bottom: h * 0.22, height: Math.max(1, h * 0.03), backgroundColor: alpha(on, 0.35) }} />\n </View>\n );\n }\n if (kind === 'grid') {\n const cell = d * 0.18;\n return (\n <View style={[box, { backgroundColor: colors.foreground, alignItems: 'center', justifyContent: 'center' }]}>\n <View style={{ width: cell * 3 + cell * 0.5 * 2, flexDirection: 'row', flexWrap: 'wrap', gap: cell * 0.5 }}>\n {[1, 0.25, 0.6, 0.25, 1, 0.25, 0.6, 0.25, 1].map((a, i) => (\n <View key={i} style={{ width: cell, height: cell, borderRadius: radius.scale(cell * 0.3), backgroundColor: alpha(colors.accent, a) }} />\n ))}\n </View>\n </View>\n );\n }\n if (kind === 'rings') {\n return (\n <View style={[box, { backgroundColor: alpha(colors.accent, 0.16), alignItems: 'center', justifyContent: 'center' }]}>\n {[0.95, 0.7, 0.45, 0.2].map((f) => (\n <View\n key={f}\n style={{\n position: 'absolute',\n width: d * f,\n height: d * f,\n borderRadius: radius.capsule(d),\n borderWidth: Math.max(1.5, d * 0.03),\n borderColor: alpha(colors.accent, 1 - f * 0.6),\n }}\n />\n ))}\n </View>\n );\n }\n if (kind === 'stripes') {\n return (\n <View style={[box, { backgroundColor: colors.muted, justifyContent: 'center', alignItems: 'center' }]}>\n <View style={{ width: d * 1.6, gap: d * 0.07, transform: [{ rotate: '-30deg' }] }}>\n {[0.9, 0.55, 0.3, 0.55, 0.9].map((a, i) => (\n <View key={i} style={{ height: d * 0.1, backgroundColor: alpha(colors.accent, a) }} />\n ))}\n </View>\n </View>\n );\n }\n return (\n <View style={[box, { backgroundColor: colors.foreground, flexDirection: 'row' }]}>\n <View style={{ flex: 1, backgroundColor: colors.accent }} />\n <View style={{ flex: 1 }} />\n <View\n style={{\n position: 'absolute',\n left: (w ?? d) / 2 - d * 0.22,\n top: h / 2 - d * 0.22,\n width: d * 0.44,\n height: d * 0.44,\n borderRadius: radius.capsule(d),\n backgroundColor: colors.background,\n }}\n />\n </View>\n );\n}\n\n/** A right-pointing triangle made from borders (play / skip glyphs). */\nfunction Tri({ size, color, flip }: { size: number; color: string; flip?: boolean }) {\n return (\n <View\n style={{\n width: 0,\n height: 0,\n borderTopWidth: size / 2,\n borderBottomWidth: size / 2,\n borderLeftWidth: size * 0.85,\n borderTopColor: 'transparent',\n borderBottomColor: 'transparent',\n borderLeftColor: color,\n transform: flip ? [{ scaleX: -1 }] : undefined,\n }}\n />\n );\n}\n\nfunction Skip({ color, back }: { color: string; back?: boolean }) {\n const { radius } = useKit();\n return (\n <View style={{ flexDirection: back ? 'row-reverse' : 'row', alignItems: 'center', gap: 1 }}>\n <Tri size={14} color={color} flip={back} />\n <View style={{ width: 2.5, height: 14, borderRadius: radius.capsule(2.5), backgroundColor: color }} />\n </View>\n );\n}\n\nconst TRACKS: { title: string; artist: string; time: string; art: ArtKind; playing?: boolean }[] = [\n { title: 'Midnight City Lights', artist: 'Nova Arcade', time: '4:12', art: 'sunset', playing: true },\n { title: 'Paper Planes', artist: 'Juniper Fields', time: '3:27', art: 'rings' },\n { title: 'Tidal', artist: 'Okra & The Moon', time: '5:03', art: 'stripes' },\n { title: 'Slow Burn', artist: 'Halcyon Days', time: '3:48', art: 'grid' },\n { title: 'Northern Line', artist: 'The Lanterns', time: '4:31', art: 'split' },\n];\n\nexport default function PushLayout() {\n const { colors, radius, dark } = useKit();\n const [view, setView] = useState('Listen now');\n const [playing, setPlaying] = useState(true);\n return (\n <Frame>\n <Drawer\n variant=\"push\"\n width={252}\n topInset={STATUS_H}\n header={<DrawerProfile layout=\"stacked\" name=\"Jonas Weber\" email=\"Premium · 2 devices\" />}\n content={\n <>\n {(\n [\n ['home', 'Listen now'],\n ['search', 'Search'],\n ['music', 'Library', 3],\n ['bell', 'New releases', 8, 'accent'],\n ] as [DrawerIconName, string, number?, 'accent'?][]\n ).map(([icon, label, badge, badgeTone]) => (\n <DrawerItem key={label} icon={icon} label={label} badge={badge} badgeTone={badgeTone} active={view === label} onPress={() => setView(label)} />\n ))}\n <DrawerSection title=\"Playlists\" />\n {['Late Night Drive', 'Focus Flow', 'Sunday Coffee'].map((p, i) => (\n <DrawerItem\n key={p}\n icon={<Art kind={(['sunset', 'rings', 'stripes'] as const)[i]} size={22} radiusSize={radius.scale(5)} />}\n label={p}\n active={view === p}\n onPress={() => setView(p)}\n />\n ))}\n </>\n }\n footer={<DrawerItem icon=\"help\" label=\"Help & feedback\" closeOnPress={false} />}\n >\n <Phone>\n <TopBar title={view} right={<IconButton icon=\"search\" label=\"Search music\" />} />\n <View style={{ paddingHorizontal: 16, gap: 14 }}>\n <View\n style={{\n padding: 14,\n gap: 12,\n borderRadius: radius.scale(20),\n backgroundColor: colors.card,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n shadowColor: dark ? colors.background : colors.foreground,\n shadowOpacity: dark ? 0.4 : 0.06,\n shadowRadius: 14,\n shadowOffset: { width: 0, height: 4 },\n elevation: 2,\n }}\n >\n <View style={{ flexDirection: 'row', gap: 14 }}>\n <Art kind=\"sunset\" size={92} />\n <View style={{ flex: 1, minWidth: 0, justifyContent: 'center', gap: 2 }}>\n <Text style={{ fontSize: 12, color: colors.accent }}>Now playing</Text>\n <Text numberOfLines={1} style={{ fontSize: 17, letterSpacing: -0.3, color: colors.foreground }}>\n Midnight City Lights\n </Text>\n <Text numberOfLines={1} style={{ fontSize: 13.5, color: colors.mutedForeground }}>\n Nova Arcade · Afterglow\n </Text>\n </View>\n </View>\n <View style={{ gap: 6 }}>\n <View style={{ height: 4, borderRadius: radius.capsule(4), backgroundColor: colors.muted, overflow: 'hidden' }}>\n <View style={{ width: '43%', height: '100%', borderRadius: radius.capsule(4), backgroundColor: colors.accent }} />\n </View>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n <Text style={{ fontSize: 11.5, color: colors.mutedForeground, fontVariant: ['tabular-nums'] }}>1:48</Text>\n <Text style={{ fontSize: 11.5, color: colors.mutedForeground, fontVariant: ['tabular-nums'] }}>-2:24</Text>\n </View>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center', gap: 36 }}>\n <Skip color={colors.foreground} back />\n <Pressable\n onPress={() => setPlaying((v) => !v)}\n accessibilityRole=\"button\"\n accessibilityLabel={playing ? 'Pause' : 'Play'}\n style={{ width: 48, height: 48, borderRadius: radius.capsule(48), backgroundColor: colors.accent, alignItems: 'center', justifyContent: 'center' }}\n >\n {playing ? (\n <View style={{ flexDirection: 'row', gap: 5 }}>\n <View style={{ width: 4.5, height: 16, borderRadius: radius.capsule(4.5), backgroundColor: colors.accentForeground }} />\n <View style={{ width: 4.5, height: 16, borderRadius: radius.capsule(4.5), backgroundColor: colors.accentForeground }} />\n </View>\n ) : (\n <View style={{ marginLeft: 3 }}>\n <Tri size={17} color={colors.accentForeground} />\n </View>\n )}\n </Pressable>\n <Skip color={colors.foreground} />\n </View>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'baseline', justifyContent: 'space-between' }}>\n <Text style={{ fontSize: 17, letterSpacing: -0.3, color: colors.foreground }}>Up next</Text>\n <Text style={{ fontSize: 13.5, color: colors.accent }}>See all</Text>\n </View>\n <View style={{ gap: 12 }}>\n {TRACKS.map((t) => (\n <View key={t.title} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <Art kind={t.art} size={44} />\n <View style={{ flex: 1, minWidth: 0 }}>\n <Text numberOfLines={1} style={{ fontSize: 15, color: t.playing ? colors.accent : colors.foreground }}>\n {t.title}\n </Text>\n <Text numberOfLines={1} style={{ fontSize: 13, color: colors.mutedForeground }}>\n {t.artist}\n </Text>\n </View>\n {t.playing ? (\n <View style={{ flexDirection: 'row', alignItems: 'flex-end', gap: 2, height: 14 }}>\n {[8, 14, 10].map((hh, i) => (\n <View key={i} style={{ width: 3, height: hh, borderRadius: radius.capsule(3), backgroundColor: colors.accent }} />\n ))}\n </View>\n ) : (\n <Text style={{ fontSize: 13, color: colors.mutedForeground, fontVariant: ['tabular-nums'] }}>{t.time}</Text>\n )}\n </View>\n ))}\n </View>\n </View>\n </Phone>\n </Drawer>\n </Frame>\n );\n}\n"
169
+ }
170
+ ]
171
+ }
@@ -0,0 +1,120 @@
1
+ {
2
+ "name": "fab",
3
+ "category": "Buttons",
4
+ "aliases": [],
5
+ "exportName": "Fab",
6
+ "files": [
7
+ "src/components/ui/fab.tsx"
8
+ ],
9
+ "props": [
10
+ [
11
+ "actions",
12
+ "{ label, icon, onPress?, color? }[]",
13
+ "—",
14
+ "Speed-dial items, bottom → top (arc: first → last). icon can be a node or ({ color }) => node; with color the icon colour contrasts automatically. Omit for a plain FAB."
15
+ ],
16
+ [
17
+ "onPress",
18
+ "() => void",
19
+ "—",
20
+ "Called when there are no actions."
21
+ ],
22
+ [
23
+ "icon",
24
+ "ReactNode | ({ color }) => ReactNode",
25
+ "plus",
26
+ "Main icon (rotates 135° when open). A function is re-coloured as the button turns neutral when open."
27
+ ],
28
+ [
29
+ "label",
30
+ "string",
31
+ "—",
32
+ "Makes it an extended FAB (icon + label)."
33
+ ],
34
+ [
35
+ "extended",
36
+ "boolean",
37
+ "true",
38
+ "Set false (e.g. while scrolling) to collapse the label into a circle."
39
+ ],
40
+ [
41
+ "open / onOpenChange",
42
+ "boolean",
43
+ "—",
44
+ "Controlled speed dial (or use defaultOpen)."
45
+ ],
46
+ [
47
+ "defaultOpen",
48
+ "boolean",
49
+ "false",
50
+ "Speed dial starts open when uncontrolled."
51
+ ],
52
+ [
53
+ "layout",
54
+ "\"list\" | \"arc\"",
55
+ "\"list\"",
56
+ "Labelled column, or mini buttons fanned out on a quarter circle (half circle for bottom-center). Arc hides the chips; labels stay as accessibility labels."
57
+ ],
58
+ [
59
+ "position",
60
+ "\"bottom-right\" | \"bottom-left\" | \"bottom-center\"",
61
+ "\"bottom-right\"",
62
+ "Corner of the parent it sits in."
63
+ ],
64
+ [
65
+ "variant",
66
+ "\"accent\" | \"gradient\" | \"tonal\" | \"primary\" | \"secondary\"",
67
+ "\"accent\"",
68
+ "accent: solid fill + same-colour glow; gradient: opt-in two-tone; tonal: soft tinted surface; primary / secondary: theme surfaces."
69
+ ],
70
+ [
71
+ "size",
72
+ "\"sm\" | \"md\" | \"lg\"",
73
+ "\"md\"",
74
+ "Main button 40, 56 or 64pt (speed-dial buttons 36 / 42 / 46pt)."
75
+ ],
76
+ [
77
+ "color",
78
+ "string",
79
+ "theme accent",
80
+ "Tint for accent, gradient and tonal; glow, foreground (and the opt-in gradient stops) are derived from it."
81
+ ],
82
+ [
83
+ "backdrop",
84
+ "boolean",
85
+ "true",
86
+ "Dim the parent while open; tap to close."
87
+ ],
88
+ [
89
+ "pulse",
90
+ "boolean",
91
+ "false",
92
+ "Looping attention ring (skipped with Reduce Motion), e.g. for empty-state calls to action."
93
+ ],
94
+ [
95
+ "offset",
96
+ "number",
97
+ "20",
98
+ "Distance from the parent's edges."
99
+ ],
100
+ [
101
+ "rotate",
102
+ "boolean",
103
+ "true",
104
+ "Rotate the icon into × when open."
105
+ ],
106
+ [
107
+ "haptics",
108
+ "boolean",
109
+ "true",
110
+ "Tap on toggle."
111
+ ],
112
+ [
113
+ "style",
114
+ "StyleProp<ViewStyle>",
115
+ "—",
116
+ "Style for the absolutely positioned stack holding the main button and list actions."
117
+ ]
118
+ ],
119
+ "examples": []
120
+ }