@expokit/cli 0.2.0 → 0.4.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 (232) hide show
  1. package/README.md +20 -2
  2. package/bin/expo-kit.js +219 -44
  3. package/bin/lib.mjs +110 -1
  4. package/bin/mcp.mjs +48 -19
  5. package/package.json +9 -4
  6. package/registry/_docs/accordion.json +16 -0
  7. package/registry/_docs/action-sheet.json +28 -0
  8. package/registry/_docs/alert.json +28 -0
  9. package/registry/_docs/assistant-app.json +47 -0
  10. package/registry/_docs/avatar.json +25 -1
  11. package/registry/_docs/badge.json +36 -5
  12. package/registry/_docs/bottom-nav.json +30 -6
  13. package/registry/_docs/bottom-sheet.json +28 -0
  14. package/registry/_docs/bullet.json +3 -0
  15. package/registry/_docs/button-3d.json +18 -2
  16. package/registry/_docs/button.json +16 -0
  17. package/registry/_docs/calendar.json +21 -5
  18. package/registry/_docs/card-stack.json +16 -0
  19. package/registry/_docs/card.json +20 -0
  20. package/registry/_docs/carousel.json +20 -0
  21. package/registry/_docs/chat.json +184 -0
  22. package/registry/_docs/checkbox.json +16 -0
  23. package/registry/_docs/checkout.json +10 -1
  24. package/registry/_docs/chip-group.json +16 -0
  25. package/registry/_docs/confetti.json +23 -3
  26. package/registry/_docs/coupon.json +16 -0
  27. package/registry/_docs/cube-pager.json +16 -0
  28. package/registry/_docs/dashboard.json +3 -0
  29. package/registry/_docs/date-picker.json +24 -4
  30. package/registry/_docs/dialog.json +24 -0
  31. package/registry/_docs/donut-shop.json +88 -0
  32. package/registry/_docs/drawer.json +32 -4
  33. package/registry/_docs/fab.json +24 -0
  34. package/registry/_docs/fintech-app.json +49 -0
  35. package/registry/_docs/flip-card.json +16 -0
  36. package/registry/_docs/floating-input.json +25 -5
  37. package/registry/_docs/food-app.json +47 -0
  38. package/registry/_docs/gift-card.json +16 -0
  39. package/registry/_docs/glass-button.json +24 -0
  40. package/registry/_docs/glass-nav-bar.json +24 -0
  41. package/registry/_docs/glass-search-bar.json +24 -0
  42. package/registry/_docs/glass-segmented.json +28 -0
  43. package/registry/_docs/glass-sheet.json +28 -0
  44. package/registry/_docs/glass-tab-bar.json +24 -0
  45. package/registry/_docs/glass.json +17 -1
  46. package/registry/_docs/grain.json +80 -0
  47. package/registry/_docs/inbox.json +3 -0
  48. package/registry/_docs/isometric-bar.json +3 -0
  49. package/registry/_docs/kit.json +3 -0
  50. package/registry/_docs/liquidglass-tab.json +3 -0
  51. package/registry/_docs/list-item.json +16 -0
  52. package/registry/_docs/loyalty-card.json +16 -0
  53. package/registry/_docs/morphing-button-group.json +96 -0
  54. package/registry/_docs/number-ticker.json +16 -0
  55. package/registry/_docs/offers.json +3 -0
  56. package/registry/_docs/onboarding.json +3 -0
  57. package/registry/_docs/orb-reveal.json +132 -0
  58. package/registry/_docs/otp-input.json +28 -4
  59. package/registry/_docs/parallax-layers.json +20 -0
  60. package/registry/_docs/parallax-scroll.json +24 -0
  61. package/registry/_docs/password-input.json +23 -3
  62. package/registry/_docs/payment-card.json +16 -0
  63. package/registry/_docs/pixel-bar.json +3 -0
  64. package/registry/_docs/polar-area.json +3 -0
  65. package/registry/_docs/profile.json +3 -0
  66. package/registry/_docs/progress-steps.json +24 -0
  67. package/registry/_docs/progress.json +20 -0
  68. package/registry/_docs/qr-code-button.json +122 -0
  69. package/registry/_docs/radio-group.json +20 -0
  70. package/registry/_docs/rating.json +24 -0
  71. package/registry/_docs/ring-carousel.json +20 -0
  72. package/registry/_docs/scratch-card.json +20 -0
  73. package/registry/_docs/search-bar.json +23 -3
  74. package/registry/_docs/search-results.json +3 -0
  75. package/registry/_docs/segmented-control.json +20 -0
  76. package/registry/_docs/select.json +33 -8
  77. package/registry/_docs/settings.json +9 -0
  78. package/registry/_docs/sign-in.json +3 -0
  79. package/registry/_docs/skeleton.json +22 -2
  80. package/registry/_docs/slide-to-confirm.json +24 -0
  81. package/registry/_docs/slider.json +27 -7
  82. package/registry/_docs/spin-wheel.json +24 -0
  83. package/registry/_docs/spinner.json +41 -11
  84. package/registry/_docs/stateful-button.json +184 -0
  85. package/registry/_docs/stepper.json +23 -7
  86. package/registry/_docs/swipe-deck.json +16 -0
  87. package/registry/_docs/swipeable-row.json +24 -1
  88. package/registry/_docs/switch.json +20 -0
  89. package/registry/_docs/tabs.json +24 -0
  90. package/registry/_docs/text-animation.json +141 -0
  91. package/registry/_docs/textarea.json +18 -2
  92. package/registry/_docs/ticket-card.json +20 -0
  93. package/registry/_docs/ticket-shape.json +4 -1
  94. package/registry/_docs/tilt-card.json +20 -4
  95. package/registry/_docs/time-picker.json +22 -6
  96. package/registry/_docs/timeline-chart.json +3 -0
  97. package/registry/_docs/timeline.json +18 -2
  98. package/registry/_docs/toast.json +32 -8
  99. package/registry/_docs/treemap.json +3 -0
  100. package/registry/_docs/waterfall.json +3 -0
  101. package/registry/_docs/wheel-list.json +20 -0
  102. package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
  103. package/registry/accordion/files/src/lib/kit.tsx +8 -5
  104. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
  105. package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
  106. package/registry/alert/files/src/components/ui/alert.tsx +7 -4
  107. package/registry/alert/files/src/components/ui/button.tsx +4 -3
  108. package/registry/alert/files/src/lib/kit.tsx +8 -5
  109. package/registry/assistant-app.json +49 -0
  110. package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
  111. package/registry/avatar/files/src/lib/kit.tsx +8 -5
  112. package/registry/badge/files/src/components/ui/badge.tsx +345 -21
  113. package/registry/badge/files/src/lib/kit.tsx +8 -5
  114. package/registry/badge.json +1 -1
  115. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
  116. package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
  117. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
  118. package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
  119. package/registry/button/files/src/components/ui/button.tsx +4 -3
  120. package/registry/button/files/src/lib/kit.tsx +8 -5
  121. package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
  122. package/registry/button-3d/files/src/lib/kit.tsx +8 -5
  123. package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
  124. package/registry/calendar/files/src/lib/kit.tsx +8 -5
  125. package/registry/card/files/src/components/ui/card.tsx +8 -6
  126. package/registry/card/files/src/lib/kit.tsx +8 -5
  127. package/registry/chat.json +18 -0
  128. package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
  129. package/registry/checkbox/files/src/lib/kit.tsx +8 -5
  130. package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
  131. package/registry/chip-group/files/src/lib/kit.tsx +8 -5
  132. package/registry/confetti/files/src/lib/kit.tsx +8 -5
  133. package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
  134. package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
  135. package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
  136. package/registry/coupon/files/src/lib/kit.tsx +8 -5
  137. package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
  138. package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
  139. package/registry/date-picker/files/src/lib/kit.tsx +8 -5
  140. package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
  141. package/registry/dialog/files/src/lib/kit.tsx +8 -5
  142. package/registry/donut-shop.json +18 -0
  143. package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
  144. package/registry/drawer/files/src/lib/kit.tsx +8 -5
  145. package/registry/fintech-app.json +57 -0
  146. package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
  147. package/registry/flip-card/files/src/lib/kit.tsx +8 -5
  148. package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
  149. package/registry/floating-input/files/src/lib/kit.tsx +8 -5
  150. package/registry/food-app.json +55 -0
  151. package/registry/grain/files/src/components/ui/grain.tsx +113 -0
  152. package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
  153. package/registry/grain.json +17 -0
  154. package/registry/kit/files/src/lib/kit.tsx +8 -5
  155. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
  156. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
  157. package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
  158. package/registry/list-item/files/src/lib/kit.tsx +8 -5
  159. package/registry/morphing-button-group.json +18 -0
  160. package/registry/orb-reveal.json +20 -0
  161. package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
  162. package/registry/otp-input/files/src/lib/kit.tsx +8 -5
  163. package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
  164. package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
  165. package/registry/password-input/files/src/lib/kit.tsx +8 -5
  166. package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
  167. package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
  168. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
  169. package/registry/payment-card/files/src/lib/kit.tsx +8 -5
  170. package/registry/progress/files/src/components/ui/progress.tsx +8 -5
  171. package/registry/progress/files/src/lib/kit.tsx +8 -5
  172. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
  173. package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
  174. package/registry/qr-code-button.json +18 -0
  175. package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
  176. package/registry/radio-group/files/src/lib/kit.tsx +8 -5
  177. package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
  178. package/registry/search-bar/files/src/lib/kit.tsx +8 -5
  179. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
  180. package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
  181. package/registry/select/files/src/components/ui/select.tsx +3 -3
  182. package/registry/select/files/src/lib/kit.tsx +8 -5
  183. package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
  184. package/registry/skeleton/files/src/lib/kit.tsx +8 -5
  185. package/registry/slider/files/src/components/ui/slider.tsx +5 -4
  186. package/registry/slider/files/src/lib/kit.tsx +8 -5
  187. package/registry/spinner/files/src/components/ui/spinner.tsx +452 -34
  188. package/registry/spinner/files/src/lib/kit.tsx +8 -5
  189. package/registry/spinner.json +1 -1
  190. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +532 -0
  191. package/registry/{pagination-dots → stateful-button}/files/src/lib/kit.tsx +8 -5
  192. package/registry/stateful-button.json +18 -0
  193. package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
  194. package/registry/stepper/files/src/lib/kit.tsx +8 -5
  195. package/registry/switch/files/src/components/ui/switch.tsx +4 -3
  196. package/registry/switch/files/src/lib/kit.tsx +8 -5
  197. package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
  198. package/registry/tabs/files/src/lib/kit.tsx +8 -5
  199. package/registry/text-animation/files/src/components/ui/text-animation.tsx +650 -0
  200. package/registry/text-animation/files/src/lib/kit.tsx +222 -0
  201. package/registry/text-animation.json +15 -0
  202. package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
  203. package/registry/textarea/files/src/lib/kit.tsx +8 -5
  204. package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
  205. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
  206. package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
  207. package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
  208. package/registry/time-picker/files/src/lib/kit.tsx +8 -5
  209. package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
  210. package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
  211. package/registry/timeline/files/src/lib/kit.tsx +8 -5
  212. package/registry/toast/files/src/components/ui/button.tsx +4 -3
  213. package/registry/toast/files/src/components/ui/toast.tsx +13 -11
  214. package/registry/toast/files/src/lib/kit.tsx +8 -5
  215. package/schema/components.schema.json +1 -1
  216. package/registry/_docs/color-swatch.json +0 -66
  217. package/registry/_docs/kpi-card.json +0 -11
  218. package/registry/_docs/like-button.json +0 -72
  219. package/registry/_docs/morph-button.json +0 -108
  220. package/registry/_docs/pagination-dots.json +0 -88
  221. package/registry/_docs/shimmer-button.json +0 -78
  222. package/registry/_docs/tag-input.json +0 -128
  223. package/registry/color-swatch.json +0 -17
  224. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
  225. package/registry/kpi-card.json +0 -14
  226. package/registry/like-button.json +0 -18
  227. package/registry/morph-button.json +0 -18
  228. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
  229. package/registry/pagination-dots.json +0 -15
  230. package/registry/shimmer-button.json +0 -17
  231. package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
  232. package/registry/tag-input.json +0 -17
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/carousel.tsx"
8
8
  ],
9
+ "summary": "A snapping carousel whose neighbouring slides shrink, fade or tilt with the scroll.",
9
10
  "props": [
10
11
  [
11
12
  "data",
@@ -98,5 +99,24 @@
98
99
  "Style for the root view wrapping the slides and pagination."
99
100
  ]
100
101
  ],
102
+ "behavior": [
103
+ [
104
+ "Touch",
105
+ "Swipe to snap from slide to slide. Autoplay waits while you drag and resumes after."
106
+ ],
107
+ [
108
+ "Motion",
109
+ "Slide effects are interpolated from the scroll position on the native driver. renderItem also gets a progress value for your own parallax."
110
+ ],
111
+ [
112
+ "Screen readers",
113
+ "The scroller is adjustable, reads \"Slide 2 of 5\" and supports increment and decrement. Dots are buttons labelled \"Go to slide N\"."
114
+ ],
115
+ [
116
+ "Web",
117
+ "Settles on the nearest slide once scrolling stops, since the web has no snap momentum."
118
+ ]
119
+ ],
120
+ "usage": null,
101
121
  "examples": []
102
122
  }
@@ -0,0 +1,184 @@
1
+ {
2
+ "name": "chat",
3
+ "category": "Blocks",
4
+ "aliases": [
5
+ "ai-chat",
6
+ "chatbot",
7
+ "assistant",
8
+ "messages",
9
+ "chatgpt"
10
+ ],
11
+ "exportName": "ChatScreen",
12
+ "files": [
13
+ "src/components/blocks/chat.tsx"
14
+ ],
15
+ "summary": "AI assistant chat with streamed, fading-in replies, Thinking state and a Stop button.",
16
+ "props": [
17
+ [
18
+ "messages",
19
+ "{ id, role: 'user' | 'assistant', text, streaming?, feedback? }[]",
20
+ "[]",
21
+ "The conversation. Controlled when onSend is set; otherwise the starting messages for the built-in simulation."
22
+ ],
23
+ [
24
+ "onSend",
25
+ "(text: string) => void",
26
+ "—",
27
+ "The user sent a prompt (trimmed). Append it and the reply to messages yourself. Without it, replies are simulated from a canned list so the screen works as-is."
28
+ ],
29
+ [
30
+ "streaming",
31
+ "boolean",
32
+ "false",
33
+ "A reply is in flight: “Thinking…” until the last message is an assistant one with text, a caret while it grows, and Send becomes Stop."
34
+ ],
35
+ [
36
+ "onStop",
37
+ "() => void",
38
+ "—",
39
+ "Stop pressed while streaming."
40
+ ],
41
+ [
42
+ "suggestions",
43
+ "string[]",
44
+ "4 prompts",
45
+ "Chips above the input until the user sends something; tapping one sends it. [] hides them."
46
+ ],
47
+ [
48
+ "assistantName",
49
+ "string",
50
+ "'Assistant'",
51
+ "Used in the header, the placeholder and screen reader labels."
52
+ ],
53
+ [
54
+ "title / subtitle",
55
+ "string",
56
+ "assistantName / 'Ask me anything'",
57
+ "Header text. The subtitle shows “Thinking…” / “Typing…” while streaming."
58
+ ],
59
+ [
60
+ "header",
61
+ "ReactNode | null",
62
+ "built-in",
63
+ "Replace the header; null hides it."
64
+ ],
65
+ [
66
+ "avatar",
67
+ "ReactNode",
68
+ "sparkle mark",
69
+ "Replace the assistant mark beside replies, in the header and the empty state."
70
+ ],
71
+ [
72
+ "emptyTitle / emptySubtitle",
73
+ "string",
74
+ "'How can I help today?'",
75
+ "Empty state copy."
76
+ ],
77
+ [
78
+ "placeholder",
79
+ "string",
80
+ "`Message ${assistantName}`",
81
+ "Prompt placeholder and accessibility label."
82
+ ],
83
+ [
84
+ "variant",
85
+ "'plain' | 'bubbles'",
86
+ "'plain'",
87
+ "plain: assistant text sits on the page. bubbles: assistant replies in muted bubbles too (support bots)."
88
+ ],
89
+ [
90
+ "thinkingIndicator",
91
+ "'shimmer' | 'dots'",
92
+ "'shimmer'",
93
+ "What shows before the first token."
94
+ ],
95
+ [
96
+ "onAttach",
97
+ "() => void",
98
+ "—",
99
+ "Shows a + button left of the input."
100
+ ],
101
+ [
102
+ "onCopy",
103
+ "(message) => void",
104
+ "—",
105
+ "Copy pressed or a message long-pressed — write message.text to the clipboard here. The icon turns into a check."
106
+ ],
107
+ [
108
+ "onRegenerate",
109
+ "(message) => void",
110
+ "—",
111
+ "Regenerate pressed on the latest reply."
112
+ ],
113
+ [
114
+ "onFeedback",
115
+ "(message, 'up' | 'down' | null) => void",
116
+ "—",
117
+ "Thumbs toggled; null when cleared. Initial state comes from message.feedback."
118
+ ],
119
+ [
120
+ "onNewChat",
121
+ "() => void",
122
+ "—",
123
+ "Shows a new-chat button in the header (the simulation also clears itself)."
124
+ ],
125
+ [
126
+ "bottomInset",
127
+ "number",
128
+ "0",
129
+ "Extra padding under the input, e.g. useSafeAreaInsets().bottom. Ignored while the keyboard is open."
130
+ ],
131
+ [
132
+ "keyboardVerticalOffset",
133
+ "number",
134
+ "0",
135
+ "Height above the screen (navigator header) so the input clears the keyboard exactly."
136
+ ],
137
+ [
138
+ "haptics",
139
+ "boolean",
140
+ "true",
141
+ "Light haptics on send, stop, copy and feedback."
142
+ ],
143
+ [
144
+ "accent",
145
+ "string",
146
+ "theme accent",
147
+ "User bubbles, send button, focus ring and the assistant mark."
148
+ ],
149
+ [
150
+ "style",
151
+ "StyleProp<ViewStyle>",
152
+ "—",
153
+ "Root style."
154
+ ]
155
+ ],
156
+ "behavior": [
157
+ [
158
+ "Touch",
159
+ "Tap a suggestion chip to send it. Long-press a message to copy it; the row under each reply has copy, thumbs up / down and regenerate (latest reply). A round button jumps back to the latest message after scrolling up."
160
+ ],
161
+ [
162
+ "Motion",
163
+ "Messages rise and fade in on a spring, new words fade in as they stream with a soft blinking caret, “Thinking…” shimmers (or dots bounce), and the send arrow spins into a stop square. Opacity and transforms run on the native driver."
164
+ ],
165
+ [
166
+ "Reduced motion",
167
+ "No per-word fade, shimmer, bouncing dots or caret blink; simulated replies arrive in larger chunks and messages appear without the rise."
168
+ ],
169
+ [
170
+ "Screen readers",
171
+ "Messages read as “You: …” and “<assistant>: …” with markdown stripped, and long-press copy is exposed as an action. “<assistant> is thinking” is announced, then the full reply when streaming ends. Buttons have roles, labels and disabled / selected state."
172
+ ],
173
+ [
174
+ "Keyboard",
175
+ "The prompt stays above the keyboard on iOS and Android (KeyboardAvoidingView; pass keyboardVerticalOffset under a navigator header) and bottomInset is dropped while it is open. On web, Enter sends and Shift+Enter adds a line."
176
+ ],
177
+ [
178
+ "Haptics",
179
+ "A light tap on send, stop and copy, and a selection tick on chips, thumbs and regenerate (native only, haptics={false} to turn off)."
180
+ ]
181
+ ],
182
+ "usage": null,
183
+ "examples": []
184
+ }
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/checkbox.tsx"
8
8
  ],
9
+ "summary": "A checkbox whose tick draws itself in as the box squishes and a ring bursts out.",
9
10
  "props": [
10
11
  [
11
12
  "checked / onChange",
@@ -68,6 +69,21 @@
68
69
  "Style for the pressable row holding the box and label."
69
70
  ]
70
71
  ],
72
+ "behavior": [
73
+ [
74
+ "Touch",
75
+ "The whole row toggles. The box squishes on press-in and bounces back on release."
76
+ ],
77
+ [
78
+ "Motion",
79
+ "The fill and tick spring in together, and a ring bursts outward each time it becomes checked."
80
+ ],
81
+ [
82
+ "Screen readers",
83
+ "A checkbox with checked and disabled state."
84
+ ]
85
+ ],
86
+ "usage": "import { Checkbox } from '@/components/ui/checkbox';\n\n<Checkbox\n label=\"Email me about new releases\"\n description=\"About once a month, never spam.\"\n defaultChecked\n/>",
71
87
  "examples": [
72
88
  {
73
89
  "name": "Default",
@@ -6,12 +6,13 @@
6
6
  "files": [
7
7
  "src/components/blocks/checkout.tsx"
8
8
  ],
9
+ "summary": null,
9
10
  "props": [
10
11
  [
11
12
  "items",
12
13
  "{ id, name, variant?, price, quantity, image? }[]",
13
14
  "3 sample products",
14
- "Initial bag. Quantities and removals are kept in state."
15
+ "Initial bag. Quantities and removals are kept in state. Each item can carry a `thumbnail` node (your own drawing) instead of an `image` URL."
15
16
  ],
16
17
  [
17
18
  "onItemsChange",
@@ -49,6 +50,12 @@
49
50
  "—",
50
51
  "Return the discount fraction (0.1 = 10%) or null. Omit to hide the promo row."
51
52
  ],
53
+ [
54
+ "defaultPromo",
55
+ "string",
56
+ "—",
57
+ "A code to apply on mount (checked with onApplyPromo), e.g. a coupon picked on another screen."
58
+ ],
52
59
  [
53
60
  "taxRate",
54
61
  "number",
@@ -98,5 +105,7 @@
98
105
  "Root style."
99
106
  ]
100
107
  ],
108
+ "behavior": [],
109
+ "usage": null,
101
110
  "examples": []
102
111
  }
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/chip-group.tsx"
8
8
  ],
9
+ "summary": "Filter chips that widen to make room as a check mark draws itself in.",
9
10
  "props": [
10
11
  [
11
12
  "options",
@@ -86,6 +87,21 @@
86
87
  "Style for the wrapping row (the ScrollView when scrollable)."
87
88
  ]
88
89
  ],
90
+ "behavior": [
91
+ [
92
+ "Touch",
93
+ "Tap to select. In single mode, tapping the selected chip clears it unless deselectable is false. Short chips pad their hit area to 44pt."
94
+ ],
95
+ [
96
+ "Motion",
97
+ "Selection springs the check, width and colours together, and chips squish while pressed."
98
+ ],
99
+ [
100
+ "Screen readers",
101
+ "Chips are radios in single mode and checkboxes in multiple mode, with checked and disabled state."
102
+ ]
103
+ ],
104
+ "usage": "import { ChipGroup } from '@/components/ui/chip-group';\n\n<ChipGroup\n multiple\n defaultValue={['Vegan']}\n options={['Vegan', 'Vegetarian', 'Gluten-free', 'Halal']}\n/>",
89
105
  "examples": [
90
106
  {
91
107
  "name": "Solid",
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/confetti.tsx"
8
8
  ],
9
+ "summary": "Confetti that flies on real arcs with gravity and drag, then flutters as it falls.",
9
10
  "props": [
10
11
  [
11
12
  "count",
@@ -62,21 +63,40 @@
62
63
  "The overlay never takes touches."
63
64
  ]
64
65
  ],
66
+ "behavior": [
67
+ [
68
+ "Motion",
69
+ "Each burst launches pieces with velocity, gravity and air drag, all moved by one native-driven animation. Bursts can overlap."
70
+ ],
71
+ [
72
+ "Touch",
73
+ "The overlay fills its parent and never takes touches."
74
+ ],
75
+ [
76
+ "Reduced motion",
77
+ "Bursts launch a quarter of the pieces."
78
+ ],
79
+ [
80
+ "Screen readers",
81
+ "The overlay is hidden from assistive tech."
82
+ ]
83
+ ],
84
+ "usage": "import { useState } from 'react';\nimport { Confetti } from '@/components/ui/confetti';\n\nconst [orders, setOrders] = useState(0);\n<Confetti trigger={orders} origin=\"bottom\" />",
65
85
  "examples": [
66
86
  {
67
87
  "name": "Celebrate",
68
88
  "title": "Celebrate",
69
- "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\nexport default function Celebrate() {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <View style={{ height: 420, borderRadius: radius.xl, backgroundColor: colors.muted, overflow: 'hidden', alignItems: 'center', justifyContent: 'center', gap: 10, padding: 24 }}>\n <Text style={{ fontSize: 44 }}>🎉</Text>\n <Text style={{ color: colors.foreground, fontSize: 24, fontWeight: '800', letterSpacing: -0.5 }}>Order placed!</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 14, textAlign: 'center' }}>You saved ₹240 with coupon GLOW50. Arriving Thursday.</Text>\n <Pressable\n onPress={() => confetti.current?.burst()}\n accessibilityRole=\"button\"\n style={{ marginTop: 12, paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, backgroundColor: colors.primary }}\n >\n <Text style={{ color: colors.primaryForeground, fontSize: 15, fontWeight: '700' }}>Celebrate again</Text>\n </Pressable>\n <Confetti ref={confetti} autoplay />\n </View>\n );\n}\n"
89
+ "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Celebrate({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <View\n style={[\n { overflow: 'hidden', alignItems: 'center', justifyContent: 'center', gap: 10, padding: 24 },\n screen ? { flex: 1, backgroundColor: colors.background } : { height: 420, borderRadius: radius.xl, backgroundColor: colors.muted },\n ]}\n >\n <Text style={{ fontSize: 44 }}>🎉</Text>\n <Text style={{ color: colors.foreground, fontSize: 24, fontWeight: '800', letterSpacing: -0.5 }}>Order placed!</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 14, textAlign: 'center' }}>You saved ₹240 with coupon GLOW50. Arriving Thursday.</Text>\n <Pressable\n onPress={() => confetti.current?.burst()}\n accessibilityRole=\"button\"\n style={{ marginTop: 12, paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, backgroundColor: colors.primary }}\n >\n <Text style={{ color: colors.primaryForeground, fontSize: 15, fontWeight: '700' }}>Celebrate again</Text>\n </Pressable>\n <Confetti ref={confetti} autoplay />\n </View>\n );\n}\n"
70
90
  },
71
91
  {
72
92
  "name": "Rain",
73
93
  "title": "Rain",
74
- "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\nexport default function Rain() {\n const { colors, radius } = useKit();\n const [shower, setShower] = useState(0);\n return (\n <View style={{ height: 360, borderRadius: radius.xl, backgroundColor: colors.muted, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' }}>\n <Pressable onPress={() => setShower((n) => n + 1)} accessibilityRole=\"button\" style={{ paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, borderWidth: 1.5, borderColor: colors.foreground }}>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '700' }}>Make it rain</Text>\n </Pressable>\n <Confetti origin=\"top\" count={120} trigger={shower} colors={['#ffd23f', '#ffb000', '#fff3b0', '#ffffff']} />\n </View>\n );\n}\n"
94
+ "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Rain({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [shower, setShower] = useState(0);\n return (\n <View\n style={[\n { overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },\n screen ? { flex: 1, backgroundColor: colors.background } : { height: 360, borderRadius: radius.xl, backgroundColor: colors.muted },\n ]}\n >\n <Pressable onPress={() => setShower((n) => n + 1)} accessibilityRole=\"button\" style={{ paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, borderWidth: 1.5, borderColor: colors.foreground }}>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '700' }}>Make it rain</Text>\n </Pressable>\n <Confetti origin=\"top\" count={120} trigger={shower} colors={['#ffd23f', '#ffb000', '#fff3b0', '#ffffff']} />\n </View>\n );\n}\n"
75
95
  },
76
96
  {
77
97
  "name": "TapToPop",
78
98
  "title": "Tap To Pop",
79
- "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\nexport default function TapToPop() {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <Pressable\n onPress={(e) => confetti.current?.burst({ origin: 'center', x: e.nativeEvent.locationX, y: e.nativeEvent.locationY, count: 50 })}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Tap to pop confetti\"\n style={{ height: 360, borderRadius: radius.xl, backgroundColor: colors.muted, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' }}\n >\n {/* Not touchable, so locationX/Y are always relative to the panel. */}\n <View pointerEvents=\"none\">\n <Text style={{ color: colors.mutedForeground, fontSize: 15, fontWeight: '600' }}>Tap anywhere</Text>\n </View>\n <Confetti ref={confetti} />\n </Pressable>\n );\n}\n"
99
+ "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function TapToPop({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <Pressable\n onPress={(e) => confetti.current?.burst({ origin: 'center', x: e.nativeEvent.locationX, y: e.nativeEvent.locationY, count: 50 })}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Tap to pop confetti\"\n style={[\n { overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },\n screen ? { flex: 1, backgroundColor: colors.background } : { height: 360, borderRadius: radius.xl, backgroundColor: colors.muted },\n ]}\n >\n {/* Not touchable, so locationX/Y are always relative to the panel. */}\n <View pointerEvents=\"none\">\n <Text style={{ color: colors.mutedForeground, fontSize: 15, fontWeight: '600' }}>Tap anywhere</Text>\n </View>\n <Confetti ref={confetti} />\n </Pressable>\n );\n}\n"
80
100
  }
81
101
  ]
82
102
  }
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/coupon.tsx"
8
8
  ],
9
+ "summary": "A perforated coupon with a copyable code that can tilt in 3D and flip to its terms.",
9
10
  "props": [
10
11
  [
11
12
  "value",
@@ -146,6 +147,21 @@
146
147
  "Width, margins."
147
148
  ]
148
149
  ],
150
+ "behavior": [
151
+ [
152
+ "Touch",
153
+ "Tapping the code chip calls onCopy and flips its icon to a check. The T&C link turns the coupon over to its terms."
154
+ ],
155
+ [
156
+ "Motion",
157
+ "With tilt it leans toward your finger with a moving reflection, holo adds a rainbow sheen, and shine sweeps light across the value panel."
158
+ ],
159
+ [
160
+ "Screen readers",
161
+ "The code chip is a button that reads the copy label and the code. Apply is a button with disabled and selected state."
162
+ ]
163
+ ],
164
+ "usage": "import { Coupon } from '@/components/ui/coupon';\n\n<Coupon\n value=\"50%\"\n unit=\"Off\"\n title=\"Your first order, on us\"\n code=\"GLOW50\"\n onApply={applyCoupon}\n/>",
149
165
  "examples": [
150
166
  {
151
167
  "name": "Hero",
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/cube-pager.tsx"
8
8
  ],
9
+ "summary": "Swipeable pages that turn like the faces of a cube, with flick momentum.",
9
10
  "props": [
10
11
  [
11
12
  "children",
@@ -80,5 +81,20 @@
80
81
  "Style for the root view."
81
82
  ]
82
83
  ],
84
+ "behavior": [
85
+ [
86
+ "Touch",
87
+ "Pages follow your finger. A quick flick or a drag past 35% turns the page, and the first and last pages rubber-band."
88
+ ],
89
+ [
90
+ "Motion",
91
+ "Pages rotate around their shared edge in 3D (cube, inside or flip) and darken as they turn away when shade is on."
92
+ ],
93
+ [
94
+ "Screen readers",
95
+ "The pager is adjustable with a \"2 / 5\" value and increment and decrement actions. Off-screen pages are hidden and dots are buttons."
96
+ ]
97
+ ],
98
+ "usage": null,
83
99
  "examples": []
84
100
  }
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/blocks/dashboard.tsx"
8
8
  ],
9
+ "summary": null,
9
10
  "props": [
10
11
  [
11
12
  "data",
@@ -116,5 +117,7 @@
116
117
  "Root style."
117
118
  ]
118
119
  ],
120
+ "behavior": [],
121
+ "usage": null,
119
122
  "examples": []
120
123
  }
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/date-picker.tsx"
8
8
  ],
9
+ "summary": "A floating-label date field that opens the calendar in a draggable spring sheet.",
9
10
  "props": [
10
11
  [
11
12
  "label",
@@ -224,26 +225,45 @@
224
225
  "Sheet / dialog / popover panel — background, border, radius, padding, shadow (transform and opacity are animated)."
225
226
  ]
226
227
  ],
228
+ "behavior": [
229
+ [
230
+ "Touch",
231
+ "Tap the field to open the picker. In a sheet, drag the grabber header down (or flick it) to dismiss."
232
+ ],
233
+ [
234
+ "Motion",
235
+ "The label floats onto the border and a focus halo springs in while open. An error shakes the field once and its message springs in below."
236
+ ],
237
+ [
238
+ "Screen readers",
239
+ "The field is a button labelled with your label, with the date (or \"Not set\") as its value and an expanded state. Helper, error and success messages are polite live regions."
240
+ ],
241
+ [
242
+ "Platforms",
243
+ "With presentation 'auto' it opens a bottom sheet on phones and a centred dialog from 640pt wide."
244
+ ]
245
+ ],
246
+ "usage": "import { useState } from 'react';\nimport { DatePicker } from '@/components/ui/date-picker';\n\nconst [date, setDate] = useState<Date | null>(null);\n<DatePicker label=\"Date of birth\" value={date} onChange={setDate} maxDate={new Date()} />",
227
247
  "examples": [
228
248
  {
229
249
  "name": "Birthday",
230
250
  "title": "Birthday",
231
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\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={[\n s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\ntype Opens = Exclude<DatePickerPresentation, 'auto'>;\n\nconst OPENS: { key: Opens; label: string }[] = [\n { key: 'sheet', label: 'Sheet' },\n { key: 'dialog', label: 'Modal' },\n { key: 'popover', label: 'Popover' },\n];\n\n/** Card header with a compact switch for how the picker opens. */\nfunction Header({ title, opens, onOpens }: { title: string; opens: Opens; onOpens: (o: Opens) => void }) {\n const { colors, radius } = useKit();\n return (\n <View style={s.header}>\n <Text style={[s.title, { color: colors.foreground }]} numberOfLines={1}>\n {title}\n </Text>\n <View style={[s.switch, { backgroundColor: colors.muted, borderRadius: radius.md }]} accessibilityRole=\"radiogroup\">\n {OPENS.map((o) => {\n const on = o.key === opens;\n return (\n <Pressable\n key={o.key}\n onPress={() => onOpens(o.key)}\n accessibilityRole=\"radio\"\n accessibilityState={{ checked: on }}\n accessibilityLabel={`Open as ${o.label}`}\n style={[s.switchItem, { borderRadius: Math.max(0, radius.md - 3) }, on && { backgroundColor: colors.background }]}\n >\n <Text style={[s.switchText, { color: on ? colors.foreground : colors.mutedForeground }]}>{o.label}</Text>\n </Pressable>\n );\n })}\n </View>\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 14 },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Birthday() {\n const [date, setDate] = useState<Date | null>(new Date(1994, 5, 12));\n const age = date ? Math.floor((today.getTime() - date.getTime()) / (365.25 * 86400000)) : null;\n const [opens, setOpens] = useState<Opens>('sheet');\n return (\n <Card>\n <Header title=\"Profile\" opens={opens} onOpens={setOpens} />\n <DatePicker\n key={opens}\n presentation={opens}\n label=\"Date of birth\"\n value={date}\n onChange={setDate}\n maxDate={today}\n formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}\n helper={age !== null ? `You're ${age}. Tap the month title to jump years.` : 'Used to personalise your plan.'}\n />\n </Card>\n );\n}\n"
251
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n s.card,\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : [s.cardFrame, { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card }],\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\ntype Opens = Exclude<DatePickerPresentation, 'auto'>;\n\nconst OPENS: { key: Opens; label: string }[] = [\n { key: 'sheet', label: 'Sheet' },\n { key: 'dialog', label: 'Modal' },\n { key: 'popover', label: 'Popover' },\n];\n\n/** Card header with a compact switch for how the picker opens. */\nfunction Header({ title, opens, onOpens }: { title: string; opens: Opens; onOpens: (o: Opens) => void }) {\n const { colors, radius } = useKit();\n return (\n <View style={s.header}>\n <Text style={[s.title, { color: colors.foreground }]} numberOfLines={1}>\n {title}\n </Text>\n <View style={[s.switch, { backgroundColor: colors.muted, borderRadius: radius.md }]} accessibilityRole=\"radiogroup\">\n {OPENS.map((o) => {\n const on = o.key === opens;\n return (\n <Pressable\n key={o.key}\n onPress={() => onOpens(o.key)}\n accessibilityRole=\"radio\"\n accessibilityState={{ checked: on }}\n accessibilityLabel={`Open as ${o.label}`}\n style={[s.switchItem, { borderRadius: Math.max(0, radius.md - 3) }, on && { backgroundColor: colors.background }]}\n >\n <Text style={[s.switchText, { color: on ? colors.foreground : colors.mutedForeground }]}>{o.label}</Text>\n </Pressable>\n );\n })}\n </View>\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, gap: 14 },\n cardFrame: { borderWidth: StyleSheet.hairlineWidth },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Birthday({ screen }: DemoProps) {\n const [date, setDate] = useState<Date | null>(new Date(1994, 5, 12));\n const age = date ? Math.floor((today.getTime() - date.getTime()) / (365.25 * 86400000)) : null;\n const [opens, setOpens] = useState<Opens>('sheet');\n return (\n <Card screen={screen}>\n <Header title=\"Profile\" opens={opens} onOpens={setOpens} />\n <DatePicker\n key={opens}\n presentation={opens}\n label=\"Date of birth\"\n value={date}\n onChange={setDate}\n maxDate={today}\n formatOptions={{ day: 'numeric', month: 'long', year: 'numeric' }}\n helper={age !== null ? `You're ${age}. Tap the month title to jump years.` : 'Used to personalise your plan.'}\n />\n </Card>\n );\n}\n"
232
252
  },
233
253
  {
234
254
  "name": "TripDates",
235
255
  "title": "Trip Dates",
236
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\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={[\n s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\ntype Opens = Exclude<DatePickerPresentation, 'auto'>;\n\nconst OPENS: { key: Opens; label: string }[] = [\n { key: 'sheet', label: 'Sheet' },\n { key: 'dialog', label: 'Modal' },\n { key: 'popover', label: 'Popover' },\n];\n\n/** Card header with a compact switch for how the picker opens. */\nfunction Header({ title, opens, onOpens }: { title: string; opens: Opens; onOpens: (o: Opens) => void }) {\n const { colors, radius } = useKit();\n return (\n <View style={s.header}>\n <Text style={[s.title, { color: colors.foreground }]} numberOfLines={1}>\n {title}\n </Text>\n <View style={[s.switch, { backgroundColor: colors.muted, borderRadius: radius.md }]} accessibilityRole=\"radiogroup\">\n {OPENS.map((o) => {\n const on = o.key === opens;\n return (\n <Pressable\n key={o.key}\n onPress={() => onOpens(o.key)}\n accessibilityRole=\"radio\"\n accessibilityState={{ checked: on }}\n accessibilityLabel={`Open as ${o.label}`}\n style={[s.switchItem, { borderRadius: Math.max(0, radius.md - 3) }, on && { backgroundColor: colors.background }]}\n >\n <Text style={[s.switchText, { color: on ? colors.foreground : colors.mutedForeground }]}>{o.label}</Text>\n </Pressable>\n );\n })}\n </View>\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 14 },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function TripDates() {\n const [trip, setTrip] = useState<DateRange | null>(null);\n const nights = trip?.start && trip.end ? Math.round((trip.end.getTime() - trip.start.getTime()) / 86400000) : 0;\n const [opens, setOpens] = useState<Opens>('dialog');\n return (\n <Card>\n <Header title=\"Trip · Kyoto\" opens={opens} onOpens={setOpens} />\n <DatePicker\n key={opens}\n presentation={opens}\n mode=\"range\"\n label=\"Check-in – Check-out\"\n title=\"When are you travelling?\"\n placeholder=\"Add dates\"\n value={trip}\n onChange={setTrip}\n presets\n confirm\n clearable\n minDate={today}\n success={nights ? `${nights} night${nights > 1 ? 's' : ''} · free cancellation until 48h before` : undefined}\n />\n </Card>\n );\n}\n"
256
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n s.card,\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : [s.cardFrame, { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card }],\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\ntype Opens = Exclude<DatePickerPresentation, 'auto'>;\n\nconst OPENS: { key: Opens; label: string }[] = [\n { key: 'sheet', label: 'Sheet' },\n { key: 'dialog', label: 'Modal' },\n { key: 'popover', label: 'Popover' },\n];\n\n/** Card header with a compact switch for how the picker opens. */\nfunction Header({ title, opens, onOpens }: { title: string; opens: Opens; onOpens: (o: Opens) => void }) {\n const { colors, radius } = useKit();\n return (\n <View style={s.header}>\n <Text style={[s.title, { color: colors.foreground }]} numberOfLines={1}>\n {title}\n </Text>\n <View style={[s.switch, { backgroundColor: colors.muted, borderRadius: radius.md }]} accessibilityRole=\"radiogroup\">\n {OPENS.map((o) => {\n const on = o.key === opens;\n return (\n <Pressable\n key={o.key}\n onPress={() => onOpens(o.key)}\n accessibilityRole=\"radio\"\n accessibilityState={{ checked: on }}\n accessibilityLabel={`Open as ${o.label}`}\n style={[s.switchItem, { borderRadius: Math.max(0, radius.md - 3) }, on && { backgroundColor: colors.background }]}\n >\n <Text style={[s.switchText, { color: on ? colors.foreground : colors.mutedForeground }]}>{o.label}</Text>\n </Pressable>\n );\n })}\n </View>\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, gap: 14 },\n cardFrame: { borderWidth: StyleSheet.hairlineWidth },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function TripDates({ screen }: DemoProps) {\n const [trip, setTrip] = useState<DateRange | null>(null);\n const nights = trip?.start && trip.end ? Math.round((trip.end.getTime() - trip.start.getTime()) / 86400000) : 0;\n const [opens, setOpens] = useState<Opens>('dialog');\n return (\n <Card screen={screen}>\n <Header title=\"Trip · Kyoto\" opens={opens} onOpens={setOpens} />\n <DatePicker\n key={opens}\n presentation={opens}\n mode=\"range\"\n label=\"Check-in – Check-out\"\n title=\"When are you travelling?\"\n placeholder=\"Add dates\"\n value={trip}\n onChange={setTrip}\n presets\n confirm\n clearable\n minDate={today}\n success={nights ? `${nights} night${nights > 1 ? 's' : ''} · free cancellation until 48h before` : undefined}\n />\n </Card>\n );\n}\n"
237
257
  },
238
258
  {
239
259
  "name": "Deadline",
240
260
  "title": "Deadline",
241
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\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={[\n s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\ntype Opens = Exclude<DatePickerPresentation, 'auto'>;\n\nconst OPENS: { key: Opens; label: string }[] = [\n { key: 'sheet', label: 'Sheet' },\n { key: 'dialog', label: 'Modal' },\n { key: 'popover', label: 'Popover' },\n];\n\n/** Card header with a compact switch for how the picker opens. */\nfunction Header({ title, opens, onOpens }: { title: string; opens: Opens; onOpens: (o: Opens) => void }) {\n const { colors, radius } = useKit();\n return (\n <View style={s.header}>\n <Text style={[s.title, { color: colors.foreground }]} numberOfLines={1}>\n {title}\n </Text>\n <View style={[s.switch, { backgroundColor: colors.muted, borderRadius: radius.md }]} accessibilityRole=\"radiogroup\">\n {OPENS.map((o) => {\n const on = o.key === opens;\n return (\n <Pressable\n key={o.key}\n onPress={() => onOpens(o.key)}\n accessibilityRole=\"radio\"\n accessibilityState={{ checked: on }}\n accessibilityLabel={`Open as ${o.label}`}\n style={[s.switchItem, { borderRadius: Math.max(0, radius.md - 3) }, on && { backgroundColor: colors.background }]}\n >\n <Text style={[s.switchText, { color: on ? colors.foreground : colors.mutedForeground }]}>{o.label}</Text>\n </Pressable>\n );\n })}\n </View>\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 14 },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Deadline() {\n const { colors, radius } = useKit();\n const [due, setDue] = useState<Date | null>(null);\n const [submitted, setSubmitted] = useState(false);\n const [opens, setOpens] = useState<Opens>('popover');\n return (\n <Card>\n <Header title=\"New task\" opens={opens} onOpens={setOpens} />\n <DatePicker\n key={opens}\n presentation={opens}\n label=\"Due date\"\n value={due}\n onChange={setDue}\n minDate={today}\n isDateDisabled={(d) => d.getDay() === 0 || d.getDay() === 6}\n presets={[\n { label: 'Tomorrow', value: () => new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1) },\n { label: 'End of month', value: () => new Date(today.getFullYear(), today.getMonth() + 1, 0) },\n ]}\n error={submitted && !due ? 'Pick a due date so the team can plan around it' : undefined}\n helper=\"Weekdays only.\"\n />\n <Pressable\n onPress={() => setSubmitted(true)}\n style={({ pressed }) => [s.button, { backgroundColor: colors.accent, borderRadius: radius.md, opacity: pressed ? 0.85 : 1 }]}\n accessibilityRole=\"button\"\n >\n <Text style={[s.buttonText, { color: colors.accentForeground }]}>Create task</Text>\n </Pressable>\n </Card>\n );\n}\n"
261
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n s.card,\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : [s.cardFrame, { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card }],\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\ntype Opens = Exclude<DatePickerPresentation, 'auto'>;\n\nconst OPENS: { key: Opens; label: string }[] = [\n { key: 'sheet', label: 'Sheet' },\n { key: 'dialog', label: 'Modal' },\n { key: 'popover', label: 'Popover' },\n];\n\n/** Card header with a compact switch for how the picker opens. */\nfunction Header({ title, opens, onOpens }: { title: string; opens: Opens; onOpens: (o: Opens) => void }) {\n const { colors, radius } = useKit();\n return (\n <View style={s.header}>\n <Text style={[s.title, { color: colors.foreground }]} numberOfLines={1}>\n {title}\n </Text>\n <View style={[s.switch, { backgroundColor: colors.muted, borderRadius: radius.md }]} accessibilityRole=\"radiogroup\">\n {OPENS.map((o) => {\n const on = o.key === opens;\n return (\n <Pressable\n key={o.key}\n onPress={() => onOpens(o.key)}\n accessibilityRole=\"radio\"\n accessibilityState={{ checked: on }}\n accessibilityLabel={`Open as ${o.label}`}\n style={[s.switchItem, { borderRadius: Math.max(0, radius.md - 3) }, on && { backgroundColor: colors.background }]}\n >\n <Text style={[s.switchText, { color: on ? colors.foreground : colors.mutedForeground }]}>{o.label}</Text>\n </Pressable>\n );\n })}\n </View>\n </View>\n );\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, gap: 14 },\n cardFrame: { borderWidth: StyleSheet.hairlineWidth },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Deadline({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [due, setDue] = useState<Date | null>(null);\n const [submitted, setSubmitted] = useState(false);\n const [opens, setOpens] = useState<Opens>('popover');\n return (\n <Card screen={screen}>\n <Header title=\"New task\" opens={opens} onOpens={setOpens} />\n <DatePicker\n key={opens}\n presentation={opens}\n label=\"Due date\"\n value={due}\n onChange={setDue}\n minDate={today}\n isDateDisabled={(d) => d.getDay() === 0 || d.getDay() === 6}\n presets={[\n { label: 'Tomorrow', value: () => new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1) },\n { label: 'End of month', value: () => new Date(today.getFullYear(), today.getMonth() + 1, 0) },\n ]}\n error={submitted && !due ? 'Pick a due date so the team can plan around it' : undefined}\n helper=\"Weekdays only.\"\n />\n <Pressable\n onPress={() => setSubmitted(true)}\n style={({ pressed }) => [s.button, { backgroundColor: colors.accent, borderRadius: radius.md, opacity: pressed ? 0.85 : 1 }]}\n accessibilityRole=\"button\"\n >\n <Text style={[s.buttonText, { color: colors.accentForeground }]}>Create task</Text>\n </Pressable>\n </Card>\n );\n}\n"
242
262
  },
243
263
  {
244
264
  "name": "Sizes",
245
265
  "title": "Sizes",
246
- "code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\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={[\n s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 14 },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Sizes() {\n const d = new Date(today.getFullYear(), today.getMonth(), 24);\n return (\n <Card>\n <Ink tone=\"muted\" style={s.label}>Small</Ink>\n <DatePicker size=\"sm\" label=\"Invoice date\" defaultValue={d} />\n <Ink tone=\"muted\" style={s.label}>Medium</Ink>\n <DatePicker size=\"md\" label=\"Invoice date\" defaultValue={d} />\n <Ink tone=\"muted\" style={s.label}>Large</Ink>\n <DatePicker size=\"lg\" label=\"Invoice date\" defaultValue={d} />\n <Ink tone=\"muted\" style={s.label}>Disabled</Ink>\n <DatePicker disabled label=\"Invoice date\" defaultValue={d} />\n </Card>\n );\n}\n"
266
+ "code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { DatePicker, type DatePickerPresentation, type DateRange } from '@/components/ui/date-picker';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n s.card,\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : [s.cardFrame, { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card }],\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst today = new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate());\n\nconst s = StyleSheet.create({\n card: { padding: 16, gap: 14 },\n cardFrame: { borderWidth: StyleSheet.hairlineWidth },\n header: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', gap: 10 },\n title: { flexShrink: 1, fontSize: 15, fontWeight: '400' },\n switch: { flexDirection: 'row', padding: 3 },\n switchItem: { height: 28, paddingHorizontal: 10, alignItems: 'center', justifyContent: 'center' },\n switchText: { fontSize: 12.5, fontWeight: '400' },\n label: { fontSize: 12, fontWeight: '400', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: -6 },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Sizes({ screen }: DemoProps) {\n const d = new Date(today.getFullYear(), today.getMonth(), 24);\n return (\n <Card screen={screen}>\n <Ink tone=\"muted\" style={s.label}>Small</Ink>\n <DatePicker size=\"sm\" label=\"Invoice date\" defaultValue={d} />\n <Ink tone=\"muted\" style={s.label}>Medium</Ink>\n <DatePicker size=\"md\" label=\"Invoice date\" defaultValue={d} />\n <Ink tone=\"muted\" style={s.label}>Large</Ink>\n <DatePicker size=\"lg\" label=\"Invoice date\" defaultValue={d} />\n <Ink tone=\"muted\" style={s.label}>Disabled</Ink>\n <DatePicker disabled label=\"Invoice date\" defaultValue={d} />\n </Card>\n );\n}\n"
247
267
  }
248
268
  ]
249
269
  }
@@ -6,6 +6,7 @@
6
6
  "files": [
7
7
  "src/components/ui/dialog.tsx"
8
8
  ],
9
+ "summary": "A centred alert whose card springs up from 0.92 while its tone icon draws itself.",
9
10
  "props": [
10
11
  [
11
12
  "open / defaultOpen",
@@ -122,6 +123,29 @@
122
123
  "Card style."
123
124
  ]
124
125
  ],
126
+ "behavior": [
127
+ [
128
+ "Motion",
129
+ "The scrim fades in while the card springs to full size. A confirm that returns false keeps it open and shakes it."
130
+ ],
131
+ [
132
+ "Reduced motion",
133
+ "The card fades in without the scale spring, and the tone icon appears fully drawn instead of animating. The shake is skipped."
134
+ ],
135
+ [
136
+ "Haptics",
137
+ "A warning tick on open for destructive, warning and danger dialogs, success for the success tone, and an error buzz on shake."
138
+ ],
139
+ [
140
+ "Screen readers",
141
+ "The title is a header; destructive, warning and danger dialogs use the alert role. Buttons report disabled and busy, and the error text is a polite live region."
142
+ ],
143
+ [
144
+ "Keyboard",
145
+ "Escape closes it on web, and Return submits a prompt field."
146
+ ]
147
+ ],
148
+ "usage": "import { useState } from 'react';\nimport { Dialog } from '@/components/ui/dialog';\n\nconst [open, setOpen] = useState(false);\n<Dialog\n open={open} onOpenChange={setOpen}\n variant=\"destructive\"\n title=\"Delete this project?\"\n confirmLabel=\"Delete\"\n/>",
125
149
  "examples": [
126
150
  {
127
151
  "name": "Basic",