@vvedantb/native-ui 0.17.2 → 0.18.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 (220) hide show
  1. package/dist/AlertDialog.jsx +123 -0
  2. package/dist/AmbientBackground.jsx +73 -0
  3. package/dist/ArtifactList.jsx +59 -0
  4. package/dist/Avatar.jsx +285 -0
  5. package/dist/Badge.jsx +154 -0
  6. package/dist/BottomSheetContainer.jsx +208 -0
  7. package/dist/BulkActionBar.jsx +717 -0
  8. package/dist/Button.jsx +344 -0
  9. package/dist/CaptureButton.jsx +104 -0
  10. package/dist/Card.jsx +371 -0
  11. package/dist/Checkbox.jsx +177 -0
  12. package/dist/Chip.jsx +79 -0
  13. package/dist/ColorSwatches.jsx +159 -0
  14. package/dist/ConfirmDialog.jsx +157 -0
  15. package/dist/DateTimeField.jsx +204 -0
  16. package/dist/Dialog.jsx +614 -0
  17. package/dist/DividedList.jsx +120 -0
  18. package/dist/DropdownMenu.jsx +849 -0
  19. package/dist/EmptyState.jsx +434 -0
  20. package/dist/ExpoWebRoot.jsx +134 -0
  21. package/dist/FloatingFAB.jsx +95 -0
  22. package/dist/FloatingSurface.jsx +226 -0
  23. package/dist/FloatingTabBar.jsx +403 -0
  24. package/dist/HeaderIconButton.jsx +53 -0
  25. package/dist/IconButton.jsx +157 -0
  26. package/dist/Input.jsx +96 -0
  27. package/dist/Label.jsx +96 -0
  28. package/dist/LabeledSwitchRow.jsx +170 -0
  29. package/dist/LiquidGlass.jsx +2 -0
  30. package/dist/ListRow.jsx +281 -0
  31. package/dist/LoadingState.jsx +205 -0
  32. package/dist/PageIndicator.jsx +128 -0
  33. package/dist/PageTitle.jsx +205 -0
  34. package/dist/Pager.jsx +105 -0
  35. package/dist/PasswordInput.jsx +120 -0
  36. package/dist/PillTabs.jsx +300 -0
  37. package/dist/PinnedBotTile.jsx +212 -0
  38. package/dist/Progress.jsx +147 -0
  39. package/dist/SafeAreaContainer.jsx +38 -0
  40. package/dist/ScreenContainer.jsx +117 -0
  41. package/dist/SearchInput.jsx +150 -0
  42. package/dist/SectionLabel.jsx +128 -0
  43. package/dist/Select.jsx +223 -0
  44. package/dist/Separator.jsx +95 -0
  45. package/dist/Sheet.jsx +343 -0
  46. package/dist/SheetRow.jsx +198 -0
  47. package/dist/Skeleton.jsx +311 -0
  48. package/dist/Spinner.jsx +195 -0
  49. package/dist/StackPageHeader.jsx +160 -0
  50. package/dist/StatGrid.jsx +74 -0
  51. package/dist/StatusDot.jsx +129 -0
  52. package/dist/SurfaceRow.jsx +143 -0
  53. package/dist/SwipeableRow.jsx +50 -0
  54. package/dist/Switch.jsx +130 -0
  55. package/dist/Tabs.jsx +385 -0
  56. package/dist/Textarea.jsx +103 -0
  57. package/dist/ThemeToggle.jsx +78 -0
  58. package/dist/Toast.jsx +396 -0
  59. package/dist/ai/activity-step-detail.jsx +179 -0
  60. package/dist/ai/activity-step-label.js +816 -0
  61. package/dist/ai/activity-step-types.js +14 -0
  62. package/dist/ai/activity-step.jsx +98 -0
  63. package/dist/ai/activity-tasks-utils.js +172 -0
  64. package/dist/ai/activity-timeline.js +47 -0
  65. package/dist/ai/attachments.jsx +258 -0
  66. package/dist/ai/card-button.jsx +188 -0
  67. package/dist/ai/chain-of-thought.jsx +459 -0
  68. package/dist/ai/chat-jump-rail.jsx +57 -0
  69. package/dist/ai/chat-text.js +44 -0
  70. package/dist/ai/chat-thread.jsx +135 -0
  71. package/dist/ai/choice-question.jsx +925 -0
  72. package/dist/ai/code-block.jsx +574 -0
  73. package/dist/ai/collapsible.jsx +275 -0
  74. package/dist/ai/conversation.jsx +723 -0
  75. package/dist/ai/file-chip.jsx +198 -0
  76. package/dist/ai/icon-button.jsx +179 -0
  77. package/dist/ai/index.js +37 -0
  78. package/dist/ai/message-action-sheet.jsx +408 -0
  79. package/dist/ai/message-quote.jsx +241 -0
  80. package/dist/ai/message.jsx +739 -0
  81. package/dist/ai/prompt-input-menu.jsx +365 -0
  82. package/dist/ai/prompt-input.jsx +626 -0
  83. package/dist/ai/reasoning.jsx +446 -0
  84. package/dist/ai/reply-quote-chip.jsx +278 -0
  85. package/dist/ai/response-text.jsx +130 -0
  86. package/dist/ai/shimmer.jsx +149 -0
  87. package/dist/ai/sources.jsx +260 -0
  88. package/dist/ai/subagent-cards.jsx +645 -0
  89. package/dist/ai/suggestion.jsx +186 -0
  90. package/dist/ai/tool-approval-card.jsx +359 -0
  91. package/dist/ai/trace.jsx +728 -0
  92. package/dist/ai/unified-diff.jsx +280 -0
  93. package/dist/ai/use-streaming-disclosure.js +70 -0
  94. package/dist/auth/AuthProviders.jsx +27 -0
  95. package/dist/auth/AuthScreen.jsx +292 -0
  96. package/dist/auth/GoogleSignInButton.jsx +188 -0
  97. package/dist/auth/SecondFactorScreen.jsx +133 -0
  98. package/dist/auth/SignInScreen.jsx +482 -0
  99. package/dist/auth/authErrors.js +131 -0
  100. package/dist/auth/index.js +11 -0
  101. package/dist/auth/totp.js +378 -0
  102. package/dist/blur.js +36 -0
  103. package/dist/brand/AppIcons.jsx +118 -0
  104. package/dist/brand/AppMark.jsx +128 -0
  105. package/dist/brand/AppWordmark.jsx +138 -0
  106. package/dist/brand/GoogleMark.jsx +71 -0
  107. package/dist/brand/ProviderIcon.jsx +192 -0
  108. package/dist/brand/familyApps.js +33 -0
  109. package/dist/brand/index.js +11 -0
  110. package/dist/brand/svg.js +252 -0
  111. package/dist/brand/vProAppBenefits.js +19 -0
  112. package/dist/cn.js +5 -0
  113. package/dist/colors.js +15 -0
  114. package/dist/configureTheme.js +72 -0
  115. package/dist/controlStyles.js +520 -0
  116. package/dist/definiteSize.js +12 -0
  117. package/dist/dialogLayout.js +18 -0
  118. package/dist/entries/ai.js +4 -0
  119. package/dist/entries/ambient.js +3 -0
  120. package/dist/entries/auth.js +5 -0
  121. package/dist/entries/bottom-sheet.js +2 -0
  122. package/dist/entries/brand.js +2 -0
  123. package/dist/entries/button.js +2 -0
  124. package/dist/entries/date-time.js +3 -0
  125. package/dist/entries/expo-router.js +4 -0
  126. package/dist/entries/expo-web-root.js +3 -0
  127. package/dist/entries/floating.js +7 -0
  128. package/dist/entries/gesture-handler.js +4 -0
  129. package/dist/entries/keyboard-controller.js +4 -0
  130. package/dist/entries/layout.js +16 -0
  131. package/dist/entries/markdown.js +4 -0
  132. package/dist/entries/marketing.js +3 -0
  133. package/dist/entries/page-title.js +7 -0
  134. package/dist/entries/password-input.js +3 -0
  135. package/dist/entries/primitives.js +57 -0
  136. package/dist/entries/search-input.js +3 -0
  137. package/dist/entries/tab-bar.js +4 -0
  138. package/dist/entries/theme.js +7 -0
  139. package/dist/entries/toast.js +5 -0
  140. package/dist/entries/video.js +3 -0
  141. package/dist/entries/webview.js +4 -0
  142. package/dist/examples/AiChatGallery.jsx +509 -0
  143. package/dist/examples/ParityGallery.jsx +1307 -0
  144. package/dist/floating.js +62 -0
  145. package/dist/glassBlur.jsx +145 -0
  146. package/dist/gradient.js +17 -0
  147. package/dist/haptics.js +44 -0
  148. package/dist/highlight.js +382 -0
  149. package/dist/index.js +85 -0
  150. package/dist/initials.js +4 -0
  151. package/dist/loadingGrid.js +81 -0
  152. package/dist/markdown/Markdown.jsx +515 -0
  153. package/dist/markdown/MarkdownTable.jsx +443 -0
  154. package/dist/markdown/tableText.js +68 -0
  155. package/dist/marketing/PricingModal.jsx +608 -0
  156. package/dist/marketing/UpgradeToProCard.jsx +164 -0
  157. package/dist/marketing/pricing.js +34 -0
  158. package/dist/media/ImageGallery.jsx +580 -0
  159. package/dist/media/MediaCard.jsx +162 -0
  160. package/dist/media/imageGrid.js +54 -0
  161. package/dist/motion.js +33 -0
  162. package/dist/pagerSwipe.js +104 -0
  163. package/dist/palette.js +104 -0
  164. package/dist/primitives/button.jsx +127 -0
  165. package/dist/primitives/card.jsx +249 -0
  166. package/dist/primitives/icon.jsx +37 -0
  167. package/dist/primitives/input.jsx +56 -0
  168. package/dist/primitives/keyboard-controller.jsx +207 -0
  169. package/dist/primitives/keyboard-parts.jsx +75 -0
  170. package/dist/primitives/native-only-animated-view.jsx +68 -0
  171. package/dist/primitives/overlay.jsx +391 -0
  172. package/dist/primitives/pager-gesture.jsx +131 -0
  173. package/dist/primitives/select-host.jsx +103 -0
  174. package/dist/primitives/select.jsx +555 -0
  175. package/dist/primitives/sheet-drag.jsx +420 -0
  176. package/dist/primitives/sheet-gesture.jsx +219 -0
  177. package/dist/primitives/swipe-row-gesture.jsx +341 -0
  178. package/dist/primitives/switch.jsx +105 -0
  179. package/dist/primitives/text.jsx +145 -0
  180. package/dist/primitives/toast-drag-gesture.jsx +94 -0
  181. package/dist/primitives/use-controllable-state.js +40 -0
  182. package/dist/primitives/web-click-guard.js +69 -0
  183. package/dist/push.js +166 -0
  184. package/dist/relativeTime.js +70 -0
  185. package/dist/router/RouterStackPageHeader.jsx +53 -0
  186. package/dist/selectA11y.js +32 -0
  187. package/dist/settings/MoreFromProjectV.jsx +168 -0
  188. package/dist/settings/SettingsEmptyState.jsx +102 -0
  189. package/dist/settings/SettingsField.jsx +49 -0
  190. package/dist/settings/SettingsSection.jsx +101 -0
  191. package/dist/settings/SettingsStack.jsx +17 -0
  192. package/dist/settings/SettingsToggleRow.jsx +163 -0
  193. package/dist/settings/projectVMobileApps.js +126 -0
  194. package/dist/settings/relaxedSizeStyle.js +11 -0
  195. package/dist/sheetDrag.js +76 -0
  196. package/dist/svgA11y.js +46 -0
  197. package/dist/swipeRow.js +161 -0
  198. package/dist/tabBarRoutes.js +41 -0
  199. package/dist/tabIndicator.js +139 -0
  200. package/dist/tailwind-preset.cjs +255 -0
  201. package/dist/tailwind-preset.d.cts +55 -0
  202. package/dist/theme.jsx +62 -0
  203. package/dist/toastChrome.js +110 -0
  204. package/dist/toastDrag.js +51 -0
  205. package/dist/toastStore.js +54 -0
  206. package/dist/tokens.js +70 -0
  207. package/dist/typography.js +40 -0
  208. package/dist/useReduceMotion.js +32 -0
  209. package/dist/useReduceTransparency.js +27 -0
  210. package/dist/useScheme.js +15 -0
  211. package/dist/video/InlineVideo.jsx +115 -0
  212. package/dist/webview/HtmlView.jsx +169 -0
  213. package/dist/webview/HtmlView.web.jsx +63 -0
  214. package/dist/webview/htmlView.js +43 -0
  215. package/package.json +70 -33
  216. package/src/DropdownMenu.tsx +28 -16
  217. package/src/FloatingTabBar.tsx +78 -41
  218. package/src/ai/conversation.tsx +12 -8
  219. package/src/glassBlur.tsx +47 -22
  220. package/src/primitives/swipe-row-gesture.tsx +11 -2
@@ -0,0 +1,520 @@
1
+ /**
2
+ * Colours for `Button`, `Input`, `Textarea`, the `Select` trigger, `Switch`,
3
+ * `Checkbox`, `Tabs`, `Card` and component text, taken from `palette` and
4
+ * `tokens` instead of NativeWind theme keys (`bg-primary`, `bg-input`,
5
+ * `text-foreground`) that apps do not define, or define without a dark value.
6
+ * Also text weights and sizes as styles, the page title type, the
7
+ * `SearchInput` metrics, the compact window test for dialogs and the
8
+ * touch-target maths. No React Native import, so the tests can load this file.
9
+ */
10
+ import { palette, withAlpha } from "./palette";
11
+ import { tokens } from "./tokens";
12
+
13
+ /**
14
+ * `primary` and `default` are the same (`default` is the web name). `outline`
15
+ * is the web bordered neutral button. `tinted` is the pink tint that `outline`
16
+ * drew before 0.3.0.
17
+ */
18
+
19
+ /**
20
+ * Fill, border and label colour per variant, from the web classes
21
+ * (`bg-primary text-primary-foreground`, `bg-default text-default-foreground`
22
+ * and so on). `null` for `tinted`, which keeps its pink NativeWind classes.
23
+ */
24
+ export function buttonColors(variant, scheme) {
25
+ const none = "transparent";
26
+ switch (variant) {
27
+ case "secondary":
28
+ return {
29
+ backgroundColor: palette.default[scheme],
30
+ borderColor: none,
31
+ color: palette.defaultForeground[scheme]
32
+ };
33
+ case "outline":
34
+ return {
35
+ backgroundColor: none,
36
+ borderColor: tokens.color.border[scheme],
37
+ color: tokens.color.foreground[scheme]
38
+ };
39
+ case "destructive":
40
+ return {
41
+ backgroundColor: palette.destructive,
42
+ borderColor: none,
43
+ color: tokens.color.white
44
+ };
45
+ case "ghost":
46
+ return {
47
+ backgroundColor: none,
48
+ borderColor: none,
49
+ color: tokens.color.muted[scheme]
50
+ };
51
+ case "link":
52
+ return {
53
+ backgroundColor: none,
54
+ borderColor: none,
55
+ color: palette.primary[scheme]
56
+ };
57
+ case "tinted":
58
+ return null;
59
+ default:
60
+ return {
61
+ backgroundColor: palette.primary[scheme],
62
+ borderColor: none,
63
+ color: palette.primaryForeground[scheme]
64
+ };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * The web field look (`rounded-field border border-border bg-field-background`),
70
+ * radius 10 as on the web. A flat object: on web the `Select` trigger is a
71
+ * Radix slot, which spreads `style` and so drops arrays.
72
+ */
73
+ export function fieldStyle(scheme) {
74
+ return {
75
+ backgroundColor: tokens.color.surface[scheme],
76
+ borderColor: tokens.color.border[scheme],
77
+ borderRadius: tokens.radius.field
78
+ };
79
+ }
80
+
81
+ /** Text on `fieldStyle`. From the same scheme as the fill, so the two cannot disagree. */
82
+ export function fieldTextStyle(scheme) {
83
+ return textStyle("foreground", scheme);
84
+ }
85
+
86
+ /**
87
+ * `foreground` and `muted` are the web `text-foreground` and
88
+ * `text-muted-foreground`; `overlayMuted` is the web `DialogDescription`.
89
+ */
90
+
91
+ /**
92
+ * Text colour as a style. Where an app defines the NativeWind `foreground` key
93
+ * with a nested `dark` shade, `text-foreground` keeps the light value in dark
94
+ * mode, so class-coloured text was near-black on dark surfaces.
95
+ */
96
+ export function textStyle(tone, scheme) {
97
+ return {
98
+ color: tone === "overlayMuted" ? palette.overlayMuted[scheme] : tokens.color[tone][scheme]
99
+ };
100
+ }
101
+
102
+ /** The web `font-medium` and `font-semibold`. */
103
+
104
+ const FONT_WEIGHT = {
105
+ medium: "500",
106
+ semibold: "600"
107
+ };
108
+
109
+ /**
110
+ * Font weight as a style, set beside the `font-instrument-sans-*` class with
111
+ * the same weight. Expo web and many apps map every one of those classes to
112
+ * the one `Instrument Sans` family with no weight, so the class alone draws
113
+ * Regular. There is no `bold`: the web titles are semibold, and on Android 700
114
+ * can add a synthetic bold to a face that is already bold.
115
+ */
116
+ export function typeStyle(weight) {
117
+ return {
118
+ fontWeight: FONT_WEIGHT[weight]
119
+ };
120
+ }
121
+
122
+ /** Tailwind `text-*` sizes with their default line heights, in points. */
123
+ const TEXT_SIZE = {
124
+ "text-xs": {
125
+ fontSize: 12,
126
+ lineHeight: 16
127
+ },
128
+ "text-sm": {
129
+ fontSize: 14,
130
+ lineHeight: 20
131
+ },
132
+ "text-base": {
133
+ fontSize: 16,
134
+ lineHeight: 24
135
+ },
136
+ "text-lg": {
137
+ fontSize: 18,
138
+ lineHeight: 28
139
+ },
140
+ "text-xl": {
141
+ fontSize: 20,
142
+ lineHeight: 28
143
+ },
144
+ "text-2xl": {
145
+ fontSize: 24,
146
+ lineHeight: 32
147
+ }
148
+ };
149
+ /** Tailwind `leading-tight`, `leading-snug` and `leading-relaxed`, as multiples of the size. */
150
+ const LEADING = {
151
+ tight: 1.25,
152
+ snug: 1.375,
153
+ relaxed: 1.625
154
+ };
155
+ /**
156
+ * Font size and line height of a `text-*` class as a style, set beside the
157
+ * class. On Expo web at 390 wide (0.3.6 gallery), `Button` labels with
158
+ * `text-sm` measured 16, so the class alone does not always reach the text.
159
+ * `"tight"`, `"snug"` and `"relaxed"` are the Tailwind `leading-tight` (1.25 ×
160
+ * the size), `leading-snug` (1.375 ×) and `leading-relaxed` (1.625 ×, the web
161
+ * `.markdown` body and the settings descriptions).
162
+ */
163
+ export function sizeStyle(size, leading) {
164
+ const {
165
+ fontSize,
166
+ lineHeight
167
+ } = TEXT_SIZE[size];
168
+ return {
169
+ fontSize,
170
+ lineHeight: leading ? fontSize * LEADING[leading] : lineHeight
171
+ };
172
+ }
173
+
174
+ /** Tailwind `tracking-tight` and `tracking-wide`, in em. */
175
+ const TRACKING = {
176
+ tight: -0.025,
177
+ wide: 0.025
178
+ };
179
+
180
+ /**
181
+ * Letter spacing of a `tracking-*` class at `fontSize`, as a style in points.
182
+ * React Native has no `em`: `tracking-tight` (-0.025em) at 20 is -0.5.
183
+ */
184
+ export function trackingStyle(fontSize, tracking) {
185
+ return {
186
+ letterSpacing: fontSize * TRACKING[tracking]
187
+ };
188
+ }
189
+
190
+ /**
191
+ * The web page title, `text-xl font-semibold leading-tight tracking-tight` on
192
+ * the `Breadcrumb` nav (`breadcrumb.tsx`), with the Instrument Sans semibold
193
+ * face for `font-semibold`. `PageTitle` sets `pageTitleStyle()` beside it.
194
+ */
195
+ export const PAGE_TITLE_CLASS = "font-instrument-sans-semibold text-xl leading-tight tracking-tight";
196
+
197
+ /**
198
+ * `PAGE_TITLE_CLASS` as a style: Instrument Sans 600 at 20 / 25 with -0.5
199
+ * letter spacing. On Android and Expo web the face class alone can draw
200
+ * Regular (see `typeStyle`).
201
+ */
202
+ export function pageTitleStyle() {
203
+ const size = sizeStyle("text-xl", "tight");
204
+ return {
205
+ ...size,
206
+ ...typeStyle("semibold"),
207
+ ...trackingStyle(size.fontSize, "tight")
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Height of the `PageHeader` row below the top inset, in points. A minimum,
213
+ * so the title sits at the same height with or without an action, and still
214
+ * grows with the system font size. Native only: the web title has no row.
215
+ */
216
+ export const PAGE_HEADER_HEIGHT = 56;
217
+
218
+ /**
219
+ * The web `COMPACT_OVERLAY_QUERY`, `(max-width: 639px), (max-height: 500px)`:
220
+ * phones, and short landscape windows such as 844 × 390.
221
+ */
222
+ export const COMPACT_OVERLAY = {
223
+ maxWidth: 639,
224
+ maxHeight: 500
225
+ };
226
+
227
+ /** Whether a window of `width` × `height` points is compact: `DialogContent` is a bottom sheet there. */
228
+ export function isCompactOverlay(width, height) {
229
+ return width <= COMPACT_OVERLAY.maxWidth || height <= COMPACT_OVERLAY.maxHeight;
230
+ }
231
+
232
+ /**
233
+ * The web `Card` root (`rounded-lg bg-surface-card`): a flat fill at radius 16,
234
+ * with no border and no shadow. Also `SettingsSection`. A flat object, like
235
+ * `fieldStyle`.
236
+ */
237
+ export function cardStyle(scheme) {
238
+ return {
239
+ backgroundColor: tokens.color.surfaceCard[scheme],
240
+ borderRadius: tokens.radius.lg
241
+ };
242
+ }
243
+
244
+ /** The smallest touch target on a phone, in points (Apple's 44; Material asks 48). */
245
+ export const MIN_TOUCH_TARGET = 44;
246
+
247
+ /**
248
+ * `hitSlop` that makes a control painted `width` × `height` points reach
249
+ * `MIN_TOUCH_TARGET` on each axis, so it can keep the web size on screen. An
250
+ * axis that is already large enough gets 0.
251
+ */
252
+ export function touchSlop(width, height = width) {
253
+ const pad = size => Math.max(0, Math.ceil((MIN_TOUCH_TARGET - size) / 2));
254
+ const x = pad(width);
255
+ const y = pad(height);
256
+ return {
257
+ top: y,
258
+ bottom: y,
259
+ left: x,
260
+ right: x
261
+ };
262
+ }
263
+ /**
264
+ * Field, icon and clear-button metrics for `SearchInput`, in points. Without
265
+ * larger text these are the web sizes (`search-input.tsx`, `clear-input.tsx`):
266
+ * `sm` is `h-8 pl-8 text-sm` with the icon at `left-2`, `md` is `h-10 pl-9`
267
+ * (the phone `text-base`) with the icon at `left-3`. Both have a 16 search
268
+ * icon, the field `px-3.5` at the end, and the `h-6 w-6` clear button at
269
+ * `right-2` with a 14 cross, for which a filled field reserves `pr-9`.
270
+ * `height` locks the field at the web height, border included (the web
271
+ * `h-8` and `h-10` are border-box), so the line height of the `text-*` class
272
+ * cannot grow it. `SearchInput` scales it with the system font size
273
+ * (`scaledFieldHeight`), so a large Dynamic Type size still grows the field.
274
+ * The padding leaves room for the line inside the lock: `sm` pads 4 above and
275
+ * below and `md` 7, not the web 8, because a native field adds its padding to
276
+ * the line height (8 drew `md` 42 high on Expo web). With larger text both
277
+ * sizes are the larger `Input` (64 high, `text-xl`), with a minimum height
278
+ * and no lock.
279
+ */
280
+ export function searchInputSize(size, largerText) {
281
+ if (largerText) {
282
+ return {
283
+ minHeight: 64,
284
+ height: undefined,
285
+ paddingY: 16,
286
+ paddingLeft: 48,
287
+ paddingRight: 16,
288
+ filledPaddingRight: 48,
289
+ text: "text-xl",
290
+ icon: 24,
291
+ iconLeft: 16,
292
+ clear: 32,
293
+ clearIcon: 20,
294
+ clearRight: 12
295
+ };
296
+ }
297
+ const shared = {
298
+ paddingRight: 14,
299
+ filledPaddingRight: 36,
300
+ icon: 16,
301
+ clear: 24,
302
+ clearIcon: 14,
303
+ clearRight: 8
304
+ };
305
+ return size === "md" ? {
306
+ ...shared,
307
+ minHeight: 40,
308
+ height: 40,
309
+ paddingY: 7,
310
+ paddingLeft: 36,
311
+ text: "text-base",
312
+ iconLeft: 12
313
+ } : {
314
+ ...shared,
315
+ minHeight: 32,
316
+ height: 32,
317
+ paddingY: 4,
318
+ paddingLeft: 32,
319
+ text: "text-sm",
320
+ iconLeft: 8
321
+ };
322
+ }
323
+
324
+ /**
325
+ * A locked field height at the system font scale (`useWindowDimensions().fontScale`):
326
+ * `height` up to a scale of 1, and `height` × the scale above it, as a web
327
+ * `rem` height grows with the browser text size. `undefined` (no lock) stays
328
+ * `undefined`.
329
+ */
330
+ export function scaledFieldHeight(height, fontScale) {
331
+ return height === undefined ? undefined : height * Math.max(1, fontScale);
332
+ }
333
+ /**
334
+ * Fill and label colour of one tab, from the web tab variables
335
+ * (`tabs-pills.css`). Unselected: no fill, `--muted` label. Selected: a
336
+ * `--default` pill or a `--segment` segment with the `--segment-foreground`
337
+ * label (the neutral primary, not the accent). `soft` is the web soft trigger
338
+ * (`bg-surface-secondary text-foreground`). Fill and label come from one
339
+ * scheme, so a selected label cannot match its fill.
340
+ */
341
+ export function tabColors(variant, selected, scheme) {
342
+ if (!selected) return {
343
+ backgroundColor: "transparent",
344
+ color: tokens.color.muted[scheme]
345
+ };
346
+ switch (variant) {
347
+ case "segmented":
348
+ return {
349
+ backgroundColor: palette.segment[scheme],
350
+ color: palette.segmentForeground[scheme]
351
+ };
352
+ case "soft":
353
+ return {
354
+ backgroundColor: tokens.color.surfaceSecondary[scheme],
355
+ color: tokens.color.foreground[scheme]
356
+ };
357
+ default:
358
+ return {
359
+ backgroundColor: palette.default[scheme],
360
+ color: palette.segmentForeground[scheme]
361
+ };
362
+ }
363
+ }
364
+ /**
365
+ * Trigger box and type for `TabsTrigger` and `PillTabs`. Phone-tuned from the
366
+ * web look: `pills` and `segmented` are the web `.t-tab` (`py-1`, `text-sm`)
367
+ * grown to 36 high with `px-3.5`, so the 4 slop above and below makes 44;
368
+ * `sm` is the web `sm` list (`px-2.5 py-1 text-xs`) with the label kept at 14
369
+ * (`text-sm`, `px-3`) and also 36 high; `soft` is the web soft trigger
370
+ * (`px-4 py-2 text-sm`). With larger text they are the 0.3.4 sizes.
371
+ * `minHeight` is a minimum, so a large Dynamic Type size grows the trigger
372
+ * instead of clipping its label. `letterSpacing` is the web type-scale
373
+ * tracking of the label (`tokens/src/css/type-scale.css`): `text-sm` is 0em.
374
+ */
375
+ export function tabTriggerSize(variant, size, largerText) {
376
+ if (largerText) {
377
+ return size === "sm" ? {
378
+ minHeight: 48,
379
+ className: "px-4 py-2",
380
+ textClassName: "text-lg",
381
+ letterSpacing: 0
382
+ } : {
383
+ minHeight: 56,
384
+ className: "px-4 py-3",
385
+ textClassName: "text-xl",
386
+ letterSpacing: 0
387
+ };
388
+ }
389
+ if (size === "sm") return {
390
+ minHeight: 36,
391
+ className: "px-3 py-1",
392
+ textClassName: "text-sm",
393
+ letterSpacing: 0
394
+ };
395
+ return variant === "soft" ? {
396
+ minHeight: 36,
397
+ className: "px-4 py-2",
398
+ textClassName: "text-sm",
399
+ letterSpacing: 0
400
+ } : {
401
+ minHeight: 36,
402
+ className: "px-3.5 py-1",
403
+ textClassName: "text-sm",
404
+ letterSpacing: 0
405
+ };
406
+ }
407
+
408
+ /** The segmented track, web `--tabs-bar-bg` (`--default`). */
409
+ export function tabTrackStyle(scheme) {
410
+ return {
411
+ backgroundColor: palette.default[scheme]
412
+ };
413
+ }
414
+
415
+ /**
416
+ * Fill and label of the `FloatingTabBar` active pill. On the web the raised
417
+ * `--segment` pill sits on a muted `--default` track. The floating bar is
418
+ * already the raised surface (`tokens.color.fill`, near-white in light), so in
419
+ * light the pill takes the muted track fill (`--default`), as a white
420
+ * `--segment` pill would not show on it. In dark it keeps the kit `--segment`,
421
+ * lighter than the dark glass. Both are `palette.tabBarPill`. The label is the
422
+ * segmented `--tabs-text-active`, as `tabColors("segmented", true, scheme)`.
423
+ */
424
+ export function floatingTabPillColors(scheme) {
425
+ return {
426
+ backgroundColor: palette.tabBarPill[scheme],
427
+ color: tabColors("segmented", true, scheme).color
428
+ };
429
+ }
430
+
431
+ /**
432
+ * Box and check colours, from the web `Checkbox` classes: `bg-field-background
433
+ * border-border`, and `bg-primary border-primary text-primary-foreground` when
434
+ * checked (also when indeterminate).
435
+ */
436
+ export function checkboxColors(checked, scheme) {
437
+ return checked ? {
438
+ backgroundColor: palette.primary[scheme],
439
+ borderColor: palette.primary[scheme],
440
+ color: palette.primaryForeground[scheme]
441
+ } : {
442
+ backgroundColor: tokens.color.surface[scheme],
443
+ borderColor: tokens.color.border[scheme],
444
+ color: palette.primaryForeground[scheme]
445
+ };
446
+ }
447
+ /**
448
+ * Track and thumb size classes, the thumb offset off and on, and the track
449
+ * size in points for the hit slop. `justify-center` on the track centres the
450
+ * thumb. Without larger text:
451
+ * - `sm` is the web `Switch`: a `h-5 w-9` track (20 × 36, with a 1 border)
452
+ * and a `size-4` thumb that moves `translate-x-4`.
453
+ * - `md` is the larger app switch some web apps set through `UiConfig.Switch`:
454
+ * a `h-6 w-10` track (24 × 40, with a 1 border) and a `size-5` thumb that
455
+ * sits at `translate-x-0.5` and moves to `translate-x-[18px]`.
456
+ * With larger text both are the 0.3.4 size.
457
+ */
458
+ export function switchSize(largerText, size = "sm") {
459
+ if (largerText) {
460
+ return {
461
+ track: "h-8 w-14",
462
+ thumb: "h-7 w-7",
463
+ off: "translate-x-0",
464
+ on: "translate-x-6",
465
+ width: 56,
466
+ height: 32
467
+ };
468
+ }
469
+ return size === "md" ? {
470
+ track: "h-6 w-10",
471
+ thumb: "h-5 w-5",
472
+ off: "translate-x-0.5",
473
+ on: "translate-x-[18px]",
474
+ width: 40,
475
+ height: 24
476
+ } : {
477
+ track: "h-5 w-9",
478
+ thumb: "h-4 w-4",
479
+ off: "translate-x-0",
480
+ on: "translate-x-4",
481
+ width: 36,
482
+ height: 20
483
+ };
484
+ }
485
+
486
+ /**
487
+ * Track and thumb colours, from the web `Switch` classes. The light off thumb
488
+ * is white (web `bg-background`) so it shows on the grey track. Flat objects,
489
+ * for the same reason as `fieldStyle`.
490
+ */
491
+ export function switchColors(checked, scheme) {
492
+ if (checked) {
493
+ return {
494
+ track: {
495
+ backgroundColor: palette.primary[scheme],
496
+ borderColor: palette.primary[scheme]
497
+ },
498
+ thumb: {
499
+ backgroundColor: palette.primaryForeground[scheme]
500
+ }
501
+ };
502
+ }
503
+ return scheme === "dark" ? {
504
+ track: {
505
+ backgroundColor: withAlpha(tokens.color.muted.dark, 0.4),
506
+ borderColor: withAlpha(tokens.color.muted.dark, 0.6)
507
+ },
508
+ thumb: {
509
+ backgroundColor: tokens.color.foreground.dark
510
+ }
511
+ } : {
512
+ track: {
513
+ backgroundColor: palette.default.light,
514
+ borderColor: tokens.color.border.light
515
+ },
516
+ thumb: {
517
+ backgroundColor: tokens.color.surface.light
518
+ }
519
+ };
520
+ }
@@ -0,0 +1,12 @@
1
+ import { StyleSheet } from "react-native";
2
+
3
+ /**
4
+ * True when `style` gives a view a definite height or a flex grow. A style with
5
+ * only `maxHeight`, `maxWidth` or `minHeight` does not: a `flex: 1` child of
6
+ * such a parent collapses to height 0 on Android.
7
+ */
8
+ export function hasDefiniteHeight(style) {
9
+ const flat = StyleSheet.flatten(style);
10
+ if (flat == null) return false;
11
+ return flat.height != null || flat.flex != null || flat.flexGrow != null;
12
+ }
@@ -0,0 +1,18 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ /**
4
+ * Where a `Button` sits in a dialog. The web sizes buttons in the compact
5
+ * dialog with descendant selectors, which React Native lacks, so `DialogContent`
6
+ * and `DialogFooter` pass this down instead. `compact`: inside the phone bottom
7
+ * sheet, where buttons are at least 44 high (web `[&_button]:min-h-11`).
8
+ * `stacked`: inside its footer too, where buttons fill the width (web
9
+ * `flex-col-reverse`).
10
+ */
11
+
12
+ export const DialogLayoutContext = createContext({
13
+ compact: false,
14
+ stacked: false
15
+ });
16
+ export function useDialogLayout() {
17
+ return useContext(DialogLayoutContext);
18
+ }
@@ -0,0 +1,4 @@
1
+ // `@vvedantb/native-ui/ai`: the AI chat parts. Reaches no optional peer, not
2
+ // even a loader; the composer uses `react-native-safe-area-context` and the
3
+ // shimmer `react-native-reanimated`, both required peers.
4
+ export * from "../ai";
@@ -0,0 +1,3 @@
1
+ // `@vvedantb/native-ui/ambient`. Loads `expo-linear-gradient` only through its
2
+ // optional-peer loader; without it the background is solid.
3
+ export { AmbientBackground } from "../AmbientBackground";
@@ -0,0 +1,5 @@
1
+ // `@vvedantb/native-ui/auth`: `AuthProviders`, `SignInScreen`, `SignUpScreen`,
2
+ // `GoogleSignInButton` and the auth frame. Not on the root entry: it imports
3
+ // `@clerk/clerk-expo`, `convex`, `expo-auth-session`, `expo-web-browser` and
4
+ // `expo-router`, and Metro does not tree-shake.
5
+ export * from "../auth";
@@ -0,0 +1,2 @@
1
+ // `@vvedantb/native-ui/bottom-sheet`. Imports `@gorhom/bottom-sheet`.
2
+ export { BottomSheetContainer } from "../BottomSheetContainer";
@@ -0,0 +1,2 @@
1
+ // `@vvedantb/native-ui/brand`: the family app marks and their pure data, and `GoogleMark`.
2
+ export * from "../brand";
@@ -0,0 +1,2 @@
1
+ // `@vvedantb/native-ui/button`.
2
+ export { Button } from "../Button";
@@ -0,0 +1,3 @@
1
+ // `@vvedantb/native-ui/date-time`. Imports
2
+ // `@react-native-community/datetimepicker` and `dayjs`.
3
+ export { DateTimeField } from "../DateTimeField";
@@ -0,0 +1,4 @@
1
+ // `@vvedantb/native-ui/expo-router`: components that need `expo-router`, an
2
+ // optional peer. Not on the root, `/layout` or `/page-title` entries: they
3
+ // never import it.
4
+ export { RouterStackPageHeader, goBackOrReplace } from "../router/RouterStackPageHeader";
@@ -0,0 +1,3 @@
1
+ // `@vvedantb/native-ui/expo-web-root`: `ExpoWebRoot` for `app/+html.tsx`. Not on
2
+ // the root entry: it imports `expo-router/html`, and Metro does not tree-shake.
3
+ export { ExpoWebRoot, EXPO_WEB_VIEWPORT, expoWebBackgroundCss } from "../ExpoWebRoot";
@@ -0,0 +1,7 @@
1
+ // `@vvedantb/native-ui/floating`: floating surfaces, glass blur targets and
2
+ // metrics. Reaches `expo-blur` only through its optional-peer loader.
3
+ export { FLOATING_INSET, FLOATING_TAB_BAR_HEIGHT, FLOATING_TAB_BAR_HEIGHT_LARGE, FLOATING_RADIUS, FLOATING_ROW_RADIUS, FLOATING_PILL_RADIUS, floatingShadow, floatingHairline, floatingFill, floatingSolidFill, tabBarReserve } from "../floating";
4
+ export { useReduceTransparency } from "../useReduceTransparency";
5
+ export { FloatingSurface, GlassSheetBackground } from "../FloatingSurface";
6
+ export { LiquidGlass, GlassBlurProvider, GlassBlurTarget } from "../LiquidGlass";
7
+ export { FloatingFAB } from "../FloatingFAB";
@@ -0,0 +1,4 @@
1
+ // `@vvedantb/native-ui/gesture-handler`. Imports `react-native-gesture-handler`
2
+ // (an optional peer): the only entry that does, so an app without it bundles
3
+ // the `PanResponder` sheets as before.
4
+ export { GestureHandlerProvider } from "../primitives/sheet-gesture";
@@ -0,0 +1,4 @@
1
+ // `@vvedantb/native-ui/keyboard-controller`. Imports
2
+ // `react-native-keyboard-controller` (an optional peer): the only entry that
3
+ // does, so an app without it keeps React Native's `KeyboardAvoidingView`.
4
+ export { KeyboardControllerProvider } from "../primitives/keyboard-controller";
@@ -0,0 +1,16 @@
1
+ // `@vvedantb/native-ui/layout`: screen containers, page headers, section
2
+ // labels and settings chrome.
3
+ export { SafeAreaContainer } from "../SafeAreaContainer";
4
+ export { ScreenContainer } from "../ScreenContainer";
5
+ export { PageTitle, PageHeader } from "../PageTitle";
6
+ export { StackPageHeader } from "../StackPageHeader";
7
+ export { SectionLabel } from "../SectionLabel";
8
+ export { SettingsSection } from "../settings/SettingsSection";
9
+ export { SettingsField } from "../settings/SettingsField";
10
+ export { SettingsToggleRow } from "../settings/SettingsToggleRow";
11
+ export { SettingsStack } from "../settings/SettingsStack";
12
+ export { SettingsEmptyState } from "../settings/SettingsEmptyState";
13
+ export { PinnedBotTile, BotPinGrid } from "../PinnedBotTile";
14
+ export { ArtifactList } from "../ArtifactList";
15
+ export { MoreFromProjectV, FromProjectV } from "../settings/MoreFromProjectV";
16
+ export { PROJECT_V_MOBILE_APPS, projectVMobileApp, otherProjectVMobileApps, openProjectVApp, projectVAppOpenSteps, VSTORE_HOME_URL, androidIntentUrl } from "../settings/projectVMobileApps";
@@ -0,0 +1,4 @@
1
+ // `@vvedantb/native-ui/markdown`: `Markdown`, chat markdown styled as the web
2
+ // `.markdown` block. Not on the root entry: it imports the optional peer
3
+ // `react-native-marked`, and Metro does not tree-shake.
4
+ export { Markdown, markdownStyles, inlineCodeStyle, MARKDOWN_SCALE, MARKDOWN_LIST } from "../markdown/Markdown";
@@ -0,0 +1,3 @@
1
+ // `@vvedantb/native-ui/marketing`: the upgrade card and pricing modal.
2
+ export { UpgradeToProCard } from "../marketing/UpgradeToProCard";
3
+ export { PricingModal } from "../marketing/PricingModal";
@@ -0,0 +1,7 @@
1
+ // `@vvedantb/native-ui/page-title`: `PageTitle`, `PageHeader` and (from 0.5.0)
2
+ // `StackPageHeader`. Reaches no optional peer, not even a loader.
3
+ // `StackPageHeader` imports one Tabler icon (a required peer) for its back
4
+ // button; Metro does not tree-shake by default, so that bundles the Tabler
5
+ // package with this entry.
6
+ export { PageTitle, PageHeader } from "../PageTitle";
7
+ export { StackPageHeader } from "../StackPageHeader";
@@ -0,0 +1,3 @@
1
+ // `@vvedantb/native-ui/password-input`: `PasswordInput` on its own. Reaches no
2
+ // optional peer, not even a loader.
3
+ export { PasswordInput } from "../PasswordInput";