@vvedantb/native-ui 0.8.0 → 0.10.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.
- package/README.md +145 -7
- package/package.json +35 -8
- package/src/BulkActionBar.tsx +303 -0
- package/src/DividedList.tsx +37 -0
- package/src/DropdownMenu.tsx +439 -0
- package/src/FloatingFAB.tsx +4 -1
- package/src/ai/activity-step-detail.tsx +95 -0
- package/src/ai/choice-question.tsx +297 -0
- package/src/ai/code-block.tsx +50 -7
- package/src/ai/index.ts +10 -0
- package/src/ai/message.tsx +59 -12
- package/src/ai/prompt-input.tsx +79 -28
- package/src/ai/trace.tsx +32 -1
- package/src/auth/AuthScreen.tsx +32 -4
- package/src/auth/SecondFactorScreen.tsx +87 -0
- package/src/auth/SignInScreen.tsx +262 -1
- package/src/auth/index.ts +23 -1
- package/src/auth/totp.ts +202 -0
- package/src/configureTheme.ts +60 -0
- package/src/entries/markdown.ts +5 -0
- package/src/entries/primitives.ts +22 -0
- package/src/entries/theme.ts +2 -0
- package/src/examples/ParityGallery.tsx +56 -0
- package/src/floating.ts +2 -4
- package/src/highlight.ts +117 -0
- package/src/index.ts +24 -0
- package/src/markdown/Markdown.tsx +191 -0
- package/src/push.ts +186 -0
package/README.md
CHANGED
|
@@ -30,14 +30,17 @@ Optional and loaded only when installed, so the package imports and runs without
|
|
|
30
30
|
|
|
31
31
|
- `expo-blur` — glass blur on `FloatingSurface` and the chrome built on it. Without it, those surfaces draw a solid fill.
|
|
32
32
|
- `expo-haptics` — the selection haptic on `FloatingTabBar`, `Tabs` and `Checkbox` presses. Without it, presses are silent.
|
|
33
|
+
- `expo-notifications`, `expo-constants`, `expo-device` — only for `@vvedantb/native-ui/push` (0.9.0). Without them, `useExpoPushRegistration` does nothing and `getExpoPushToken` returns `null`.
|
|
33
34
|
- `expo-linear-gradient` — the gradient in `AmbientBackground`. Without it, the background is the solid `tokens.color.background`.
|
|
35
|
+
- `prism-react-renderer` (`>=2.0.0`) — syntax highlighting in `CodeBlock` and the `Markdown` code fences. Without it, code is plain text.
|
|
34
36
|
|
|
35
|
-
The package loads these
|
|
37
|
+
The package loads these with a `require` inside `try`. Metro treats that as an optional dependency when `transformer.allowOptionalDependencies` is on, which is the default in the Expo and React Native Metro configs. `@vvedantb/native-ui/blur` exports the loader (`loadBlur`) and `@vvedantb/native-ui/haptics` exports `loadHaptics` and `selectionHaptic`. TypeScript still resolves their types: an app that type-checks without them reports a missing module in `src/blur.ts`, `src/haptics.ts`, `src/gradient.ts` or `src/highlight.ts`. Declare the missing ones in a `.d.ts` file in the app:
|
|
36
38
|
|
|
37
39
|
```ts
|
|
38
40
|
declare module "expo-blur";
|
|
39
41
|
declare module "expo-haptics";
|
|
40
42
|
declare module "expo-linear-gradient";
|
|
43
|
+
declare module "prism-react-renderer";
|
|
41
44
|
```
|
|
42
45
|
|
|
43
46
|
`expo-router` stays an optional peer, but the package does not import it. `FloatingTabBar` declares the props it reads (`FloatingTabBarProps`) and fits the `tabBar` prop of Expo Router's `Tabs` on Expo SDK 57. Expo SDK 58 passes `emitter` and `navigateToTab` to `tabBar` in place of `navigation`, so the bar does not fit it yet.
|
|
@@ -62,21 +65,22 @@ The root entry exports everything. It still imports `BottomSheetContainer` and `
|
|
|
62
65
|
| `@vvedantb/native-ui/tab-bar` | `FloatingTabBar`, `FloatingTabBarProps`, `FLOATING_INSET`, `FLOATING_TAB_BAR_HEIGHT`, `FLOATING_TAB_BAR_HEIGHT_LARGE`, `tabBarReserve` | `expo-blur`, `expo-haptics` (loaders) |
|
|
63
66
|
| `@vvedantb/native-ui/brand` | `AppMark`, `AppWordmark`, `GoogleMark` (from 0.3.15), `APP_ICONS`, `IconVibot` and the other `Icon*`, the SVG helpers (`APP_MARKS`, `appMarkSvg`, `appTileSvg` and the rest), `FAMILY_APPS`, `familyApp`, `V_PRO_APP_BENEFITS` | none |
|
|
64
67
|
| `@vvedantb/native-ui/button` | `Button`, `ButtonProps`, `ButtonShape` (0.6.0), `ButtonSize`, `ButtonVariant` | none |
|
|
65
|
-
| `@vvedantb/native-ui/primitives` | `Button`, `Input`, `SearchInput`, `Textarea`, `Label`, `Select`, `Switch`, `LabeledSwitchRow`, `Checkbox`, `Card` and its parts, `Badge`, `StatusDot`, `Avatar`, `Separator`, `Progress`, `Spinner`, `Skeleton`, `EmptyState`, `ListRow`, `Chip`, `HeaderIconButton`, `ThemeToggle` (from 0.3.16), `Tabs`, `PillTabs`, `Dialog`, `AlertDialog`, `ConfirmDialog`, `Sheet`, `toast`, `Toaster`. From 0.5.0: `Text` and `TextClassContext` (only here, not on the root), `ColorSwatches`, `StatGrid`, `SectionLabel`, `SheetRow`, `PageIndicator`, `IconButton`, `CaptureButton`, and the `ListRowProps` and `CardProps` types. From 0.6.0: the `ButtonShape` type | `expo-blur`, `expo-haptics` (loaders) |
|
|
68
|
+
| `@vvedantb/native-ui/primitives` | `Button`, `Input`, `SearchInput`, `Textarea`, `Label`, `Select`, `Switch`, `LabeledSwitchRow`, `Checkbox`, `Card` and its parts, `Badge`, `StatusDot`, `Avatar`, `Separator`, `Progress`, `Spinner`, `Skeleton`, `EmptyState`, `ListRow`, `Chip`, `HeaderIconButton`, `ThemeToggle` (from 0.3.16), `Tabs`, `PillTabs`, `Dialog`, `AlertDialog`, `ConfirmDialog`, `Sheet`, `toast`, `Toaster`. From 0.5.0: `Text` and `TextClassContext` (only here, not on the root), `ColorSwatches`, `StatGrid`, `SectionLabel`, `SheetRow`, `PageIndicator`, `IconButton`, `CaptureButton`, and the `ListRowProps` and `CardProps` types. From 0.6.0: the `ButtonShape` type. Unreleased: `DropdownMenu` and its parts, `BulkActionBar` | `expo-blur`, `expo-haptics` (loaders) |
|
|
66
69
|
| `@vvedantb/native-ui/search-input` | `SearchInput`, `SearchInputProps` | none |
|
|
67
70
|
| `@vvedantb/native-ui/page-title` | `PageTitle`, `PageHeader`, `PageTitleProps`, `PageHeaderProps` (from 0.3.13), `StackPageHeader`, `StackPageHeaderProps` (from 0.5.0; it brings the Tabler package for its chevron) | none |
|
|
68
71
|
| `@vvedantb/native-ui/toast` | `toast`, `Toaster`, `ToastOptions`, `ToastVariant` | none (from 0.3.12) |
|
|
69
|
-
| `@vvedantb/native-ui/theme` | `NativeUiProvider`, `useNativeUi`, `useLargerText`, `useAccentColor`, `DEFAULT_ACCENT`, `tokens`, `palette`, `withAlpha`, `fontClass`, `cn`, `Colors` and the colour constants | none |
|
|
72
|
+
| `@vvedantb/native-ui/theme` | `NativeUiProvider`, `useNativeUi`, `useLargerText`, `useAccentColor`, `DEFAULT_ACCENT`, `tokens`, `palette`, `withAlpha`, `configureTheme` (with the `ThemeConfig`, `ThemeColors` and `ThemePalette` types), `fontClass`, `cn`, `Colors` and the colour constants | none |
|
|
70
73
|
| `@vvedantb/native-ui/floating` | `FloatingSurface`, `GlassSheetBackground`, `LiquidGlass`, `GlassBlurProvider`, `GlassBlurTarget`, `FloatingFAB`, `useReduceTransparency`, the floating metrics and styles | `expo-blur` (loader) |
|
|
71
74
|
| `@vvedantb/native-ui/layout` | `ScreenContainer`, `SafeAreaContainer`, `PageTitle`, `PageHeader`, `StackPageHeader` (0.5.0) and their props types, `SectionLabel` (0.5.0), `SettingsSection`, `SettingsField`, `SettingsToggleRow`, `SettingsStack`, `MoreFromProjectV` and `FromProjectV` (0.8.0) | `expo-blur` (loader) |
|
|
72
75
|
| `@vvedantb/native-ui/marketing` | `UpgradeToProCard`, `PricingModal` and the pricing types | none |
|
|
73
76
|
| `@vvedantb/native-ui/ambient` | `AmbientBackground` | `expo-linear-gradient` (loader) |
|
|
74
77
|
| `@vvedantb/native-ui/blur`, `/haptics` | the loaders | `expo-blur`, `expo-haptics` (loaders) |
|
|
78
|
+
| `@vvedantb/native-ui/push` | `useExpoPushRegistration`, `getExpoPushToken`, `showPushesInForeground`, the three loaders and the types (0.9.0). Pass `register` from your backend (for `@vvedantb/server`: `useMutation(api.push.register)`) and route taps in `onNotificationTap` | `expo-notifications`, `expo-constants`, `expo-device` (loaders) |
|
|
75
79
|
| `@vvedantb/native-ui/bottom-sheet` | `BottomSheetContainer` | `@gorhom/bottom-sheet` (import), `expo-blur` (loader) |
|
|
76
80
|
| `@vvedantb/native-ui/date-time` | `DateTimeField`, `DateTimeFieldProps` (0.5.0) | `@react-native-community/datetimepicker`, `dayjs` (import) |
|
|
77
81
|
| `@vvedantb/native-ui/ai` | The AI chat parts (from 0.4.0): `Conversation`, `Message`, `Reasoning`, `AgentTrace`, `ChainOfThought`, `ActivityStep`, `PromptInput`, `Suggestions`, `Sources`, `FileChip`, `Attachments`, `CodeBlock`, `ResponseText`, `Shimmer`, `Collapsible`, `useStreamingDisclosure` and their parts. See [AI chat](#ai-chat-040). Not on the root entry. | none |
|
|
78
82
|
| `@vvedantb/native-ui/ai-gallery` | `AiChatGallery`, `AiChatSamples` (from 0.4.0) | none |
|
|
79
|
-
| `@vvedantb/native-ui/auth` | From 0.7.0: `AuthProviders`, `SignInScreen`, `SignUpScreen`, `GoogleSignInButton`, `useGoogleSignIn`, `AuthScreen`, `OrDivider`, `FieldError`, `authErrorMessage` and their props types. See [Auth](#auth-070). Not on the root entry. | `@clerk/clerk-expo`, `convex`, `expo-auth-session`, `expo-web-browser`, `expo-router` (import) |
|
|
83
|
+
| `@vvedantb/native-ui/auth` | From 0.7.0: `AuthProviders`, `SignInScreen`, `SignUpScreen`, `GoogleSignInButton`, `useGoogleSignIn`, `AuthScreen`, `OrDivider`, `FieldError`, `authErrorMessage`, `SecondFactorScreen`, `useSecondFactor`, `submitTotpSecondFactor`, `submitSecondFactor`, `prepareSecondFactorIfNeeded`, `TOTP_MESSAGES` and their props types. See [Auth](#auth-070). Not on the root entry. | `@clerk/clerk-expo`, `convex`, `expo-auth-session`, `expo-web-browser`, `expo-router` (import) |
|
|
80
84
|
| `@vvedantb/native-ui/expo-web-root` | From 0.7.0: `ExpoWebRoot`, `ExpoWebRootProps`, `ExpoWebRootViewport`, `EXPO_WEB_VIEWPORT`, `expoWebBackgroundCss`. Not on the root entry. | `expo-router` (import of `expo-router/html`) |
|
|
81
85
|
| `@vvedantb/native-ui/tailwind-preset` | From 0.7.0, CommonJS for `tailwind.config.js`: `nativeUiPreset`, `nativeUiContent`, `cssVar`, `hslVar`, `rgbAlpha`, `cssVariables`, `schemeColors` and a default preset. See [Expo / NativeWind](#expo--nativewind). | `nativewind` (`require`, when installed) |
|
|
82
86
|
|
|
@@ -207,10 +211,52 @@ export default function SignUp() {
|
|
|
207
211
|
- `title`, `subtitle`, `showGoogle`, `googleLabel`, `errorDisplay` (`"toast"`, default, needs a `<Toaster />`; or `"inline"`), `testIDPrefix` (default the mode: `sign-in-email`, `sign-in-password`, `sign-in-submit`, `sign-in-google` and the rest).
|
|
208
212
|
- `header` and `footer` slots for app chrome, such as a brand header or credit. The screen itself is neutral: background fill, title, subtitle, fields.
|
|
209
213
|
- Messages are generic ("Sign in failed", "Invalid email or password", "Could not continue with Google") and never name the auth provider. `authErrorMessage(error, fallback)` shows an API error's own text only when it does not name it.
|
|
214
|
+
- Two-step verification: when sign-in returns `needs_second_factor` or `needs_client_trust`, the screen shows `SecondFactorScreen` with the account's default method: authenticator app (`totp`), then `email_code`, `phone_code`, `backup_code`. Email and text codes are sent first. A "Use a backup code" link (and a link back) switches method. A valid code runs the same success path. Back returns to the email and password form.
|
|
215
|
+
- `needs_new_password` shows a new-password step (`signIn.resetPassword`), then starts the session.
|
|
216
|
+
- `onIncomplete(signIn)`: optional. Called only for statuses the built-in UI cannot finish: `needs_identifier`, `needs_first_factor`, unknown statuses, and `needs_second_factor` or `needs_client_trust` when no supported strategy (totp, backup code, phone code, email code) exists. The built-in screen always runs `needs_second_factor`, `needs_client_trust` and `needs_new_password`. When `onIncomplete` is called, the screen shows no message. Without it, those statuses show "This account needs another step to sign in. Finish it on the web." and log the status and strategy ids with `console.warn`. Ignored in `sign-up`.
|
|
217
|
+
- Keyboard: `AuthScreen` (and so every screen here) pads for the soft keyboard on iOS and Android, including edge-to-edge Android, and scrolls the focused field and the button above it.
|
|
210
218
|
- No forgot-password step. An app that needs one keeps its own screen.
|
|
211
219
|
|
|
212
220
|
`GoogleSignInButton` (`scheme`, `redirectPath`, `afterAuthHref`, `replaceOptions`, `onSuccess`, `onError`, `label` and the `Button` props), `AuthScreen`, `OrDivider` and `FieldError` build custom screens. The module calls `WebBrowser.maybeCompleteAuthSession()` once when it loads, so the app no longer needs its own call.
|
|
213
221
|
|
|
222
|
+
### Second factor (0.10.0)
|
|
223
|
+
|
|
224
|
+
`SecondFactorScreen` is a one-time code step with no Clerk import. The caller owns the check.
|
|
225
|
+
|
|
226
|
+
| Prop | Default | Use |
|
|
227
|
+
| --- | --- | --- |
|
|
228
|
+
| `title` | required | Screen title, through `AuthScreen` |
|
|
229
|
+
| `description` | none | Text under the title |
|
|
230
|
+
| `codeLength` | `6` | Digits in the code |
|
|
231
|
+
| `onSubmit(code)` | required | Called with the full code |
|
|
232
|
+
| `error` | none | Inline error under the field |
|
|
233
|
+
| `loading` | `false` | Verify spinner; blocks a second submit |
|
|
234
|
+
| `onBack` | none | Shows a ghost Back button |
|
|
235
|
+
| `backLabel` | `"Back"` | Back button text |
|
|
236
|
+
| `header` | none | Above the title |
|
|
237
|
+
| `testIDPrefix` | `"second-factor"` | `-screen`, `-code`, `-error`, `-verify`, `-back` |
|
|
238
|
+
| `footer` | none | Below the buttons, such as a "Use a backup code" switch |
|
|
239
|
+
| `digitsOnly` | `true` | `false` takes any characters (backup codes) |
|
|
240
|
+
| `placeholder` | `"Code"` | Field placeholder when `digitsOnly` is `false` |
|
|
241
|
+
|
|
242
|
+
For Clerk, `useSecondFactor({ strategy? })` (default `"totp"`) returns `{ submit(code), prepare(strategy?, factor?), strategy, setStrategy, loading, error, clearError }`. `submit` calls `signIn.attemptSecondFactor({ strategy, code })`, then `setActive({ session: createdSessionId })`. `submit` never throws: it resolves `{ ok: true }` or `{ ok: false, error }`.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
const { submit, loading, error } = useSecondFactor();
|
|
246
|
+
|
|
247
|
+
<SecondFactorScreen
|
|
248
|
+
title="Two-step verification"
|
|
249
|
+
description="Enter the code from your authenticator app."
|
|
250
|
+
onSubmit={async (code) => {
|
|
251
|
+
if ((await submit(code)).ok) router.replace("/");
|
|
252
|
+
}}
|
|
253
|
+
error={error}
|
|
254
|
+
loading={loading}
|
|
255
|
+
/>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`submitTotpSecondFactor({ signIn, setActive, code })` is the TOTP call without the hook; `submitSecondFactor({ signIn, setActive, code, strategy })` takes any of `totp`, `backup_code`, `phone_code`, `email_code`. `prepareSecondFactorIfNeeded({ signIn, strategy, factor? })` sends the `email_code` or `phone_code` first (no call for the others). `pickSecondFactorStrategy(factors)` gives the default. `TOTP_MESSAGES` holds `invalidCode`, `invalidCodeGeneric`, `sendFailed`, `needsAnotherStep` and `notReady`. An API error shows its own text unless it names the provider.
|
|
259
|
+
|
|
214
260
|
## Theme
|
|
215
261
|
|
|
216
262
|
Wrap the tree (inside your colour-scheme provider):
|
|
@@ -229,6 +275,34 @@ import { NativeUiProvider, Button, Card } from "@vvedantb/native-ui";
|
|
|
229
275
|
|
|
230
276
|
**Switch the scheme through NativeWind only.** Call `useColorScheme().setColorScheme("dark"|"light")` (or use `ThemeToggle`). That updates `useScheme` and, with `darkMode: "class"`, the `<html>` class together. Do **not** toggle `document.documentElement.classList` (or a page-level CSS `.dark` rule) by hand while leaving NativeWind on light: the page background can go dark while every token-styled part stays on the light set. That dual-owner fault is an app bug, not a native-ui colour bug.
|
|
231
277
|
|
|
278
|
+
### App colours (`configureTheme`)
|
|
279
|
+
|
|
280
|
+
An app with its own palette calls `configureTheme` once at start, before the first render: at the top of the root `_layout.tsx`, outside the component. It writes into the shared `tokens.color` and `palette` objects, which every component reads at render. Keys you leave out keep the package value. Each pair needs both schemes. On the root entry and `/theme`.
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
import { configureTheme } from "@vvedantb/native-ui/theme";
|
|
284
|
+
|
|
285
|
+
// Eva's web theme (`globals.css`, RGB channels as hex).
|
|
286
|
+
configureTheme({
|
|
287
|
+
color: {
|
|
288
|
+
background: { light: "#f4f5f6", dark: "#020202" }, // --background
|
|
289
|
+
foreground: { light: "#09090b", dark: "#fafafa" }, // --foreground
|
|
290
|
+
muted: { light: "#646567", dark: "#9fa0a2" }, // --muted-foreground
|
|
291
|
+
border: { light: "#dddedf", dark: "#242424" }, // --border
|
|
292
|
+
surface: { light: "#ffffff", dark: "#101010" }, // --card
|
|
293
|
+
surfaceSecondary: { light: "#eaebec", dark: "#202020" }, // --secondary
|
|
294
|
+
},
|
|
295
|
+
palette: {
|
|
296
|
+
primary: { light: "#09090b", dark: "#fafafa" }, // --primary
|
|
297
|
+
primaryForeground: { light: "#fafafa", dark: "#09090b" }, // --primary-foreground
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
The keys map to the shadcn-style variables as follows. `tokens.color`: `background` (`--background`), `foreground` (`--foreground`), `muted` (`--muted-foreground`), `border` and `separator` (`--border`), `surface` and `surfaceCard` (`--card`), `surfaceSecondary` (`--secondary`). `palette`: `primary` (`--primary`), `danger` (`--destructive`), `default` (`--secondary`), `overlay` (`--popover`), each with its `Foreground`, and `fieldPlaceholder` (`--muted-foreground`).
|
|
303
|
+
|
|
304
|
+
Limits: a call after the first render does not redraw mounted components. Only the JS colours change. NativeWind classes keep the app's Tailwind theme, and `nativeUiPreset({ injectCssVariables: true })` still writes the package values. Constants copied out at import (`NEUTRAL_500`) keep the package value; `floatingFill`, `floatingHairline` and `floatingSolidFill` follow. The `tokens` and `palette` types still name the package hex values.
|
|
305
|
+
|
|
232
306
|
`useLargerText()` steps type and tap targets up. Without it, `Button`, `Input`, `Textarea` and the `Select` trigger have the web sizes (`Button` 32 / 40 / 44 high, fields 40); with it they keep the larger 0.3.2 sizes. `accentColor` tints FABs, card icons and progress bars (checked checkboxes use the palette primary, as on the web). Surfaces use the tokens in `src/tokens.ts` for fill, radius (card 24, field 10, pill 999; rows 20) and spacing (16). `Button` is a pill (999) by default, and 16 with `shape="rounded"`.
|
|
233
307
|
|
|
234
308
|
## Components (0.3.0)
|
|
@@ -238,7 +312,8 @@ import { NativeUiProvider, Button, Card } from "@vvedantb/native-ui";
|
|
|
238
312
|
| Actions and fields | `Button` (`default`/`primary`, `secondary`, `outline`, `destructive`, `ghost`, `link`, `tinted`; `sm`/`small`, `default`/`medium`, `lg`/`large`, `icon`; `shape` `pill`/`rounded` from 0.6.0), `IconButton` (0.5.0), `CaptureButton` (0.5.0), `Input`, `SearchInput` (0.3.9), `Textarea`, `Label`, `Select`, `Switch`, `Checkbox`, `LabeledSwitchRow`, `ColorSwatches` (0.5.0), `DateTimeField` (date-only, empty and clearable from 0.5.0) |
|
|
239
313
|
| Display | `Card` (+ `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`; `onPress` from 0.5.0), `Badge`, `StatusDot` (`info` from 0.5.0), `Avatar` (+ `AvatarImage`, `AvatarFallback`, `initials()`), `Separator`, `Progress`, `Spinner`, `CenteredSpinner`, `Skeleton`, `EmptyState` (+ `EmptyState.Root` and parts), `ListRow` (`leading`, `titleEnd`, `control`, `onLongPress` from 0.5.0), `StatGrid` (0.5.0), `SectionLabel` (0.5.0), `Chip`, `Text` (0.5.0, `/primitives` only) |
|
|
240
314
|
| Navigation | `Tabs` (+ `TabsList`, `TabsTrigger`, `TabsContent`), `PillTabs`, `PageIndicator` (0.5.0), `FloatingTabBar`, `FloatingFAB`, `HeaderIconButton`, `ThemeToggle` (0.3.16), `StackPageHeader` (0.5.0) |
|
|
241
|
-
| Overlays | `Dialog`, `AlertDialog`, `ConfirmDialog`, `Sheet` (RN `Modal`, each with the web part names), `SheetRow` (0.5.0), `BottomSheetContainer` (`@gorhom/bottom-sheet`) |
|
|
315
|
+
| Overlays | `Dialog`, `AlertDialog`, `ConfirmDialog`, `Sheet` (RN `Modal`, each with the web part names), `SheetRow` (0.5.0), `BottomSheetContainer` (`@gorhom/bottom-sheet`), `DropdownMenu` (Unreleased; see below) |
|
|
316
|
+
| Selection | `BulkActionBar` (Unreleased; see below) |
|
|
242
317
|
| Feedback | `toast()` and `<Toaster />` |
|
|
243
318
|
| Brand and upgrade | `APP_ICONS`, `IconVibot` and the other `Icon*`, `AppMark`, `AppWordmark`, `GoogleMark` (0.3.15), `FAMILY_APPS`, `V_PRO_APP_BENEFITS`, `UpgradeToProCard`, `PricingModal` |
|
|
244
319
|
| Settings and layout | `PageTitle`, `PageHeader` (0.3.13), `SettingsSection`, `SettingsField`, `SettingsToggleRow`, `SettingsStack`, `ScreenContainer`, `SafeAreaContainer`, `FloatingSurface`, `AmbientBackground` |
|
|
@@ -318,6 +393,7 @@ Components and props that the Verve, vibot and Velth apps need. They follow the
|
|
|
318
393
|
| `ColorSwatches` | A radio group of round swatches (`sm` 24, `md` 32, `lg` 40) with a foreground ring and a black or white check on the selected one. `labels` names each swatch. |
|
|
319
394
|
| `StatGrid` | Label and value tiles on the flat card, in rows of 2 or 3. |
|
|
320
395
|
| `SectionLabel` | The muted `text-xs` medium label above a group of rows; `uppercase` for capitals. Add `className="px-4"` to line it up with rows. |
|
|
396
|
+
| `DividedList` | Flat rows with a hairline between each pair, as the web `divide-y divide-border` lists. `inset` starts the dividers under the row text. |
|
|
321
397
|
| `SheetRow` | An action row for a `Sheet`: icon, title, detail; `active` adds a check, `danger` uses the danger colour, `disabled` dims it. |
|
|
322
398
|
| `PageIndicator` | The web carousel dots. With `onChange` each dot is a 44 button named "Page 2 of 5", so the dots sit 44 apart. |
|
|
323
399
|
| `StatusDot` `info` | A blue dot (`#3b82f6`, `#60a5fa` in dark), native only. |
|
|
@@ -399,14 +475,21 @@ import {
|
|
|
399
475
|
| `ActivityStep` | One tool step as a trace line: icon, label, a spinner while `running`, a check when `complete`, a warning on `error`, and a description such as `` `Worked for ${formatElapsed(12)}` ``. |
|
|
400
476
|
| `ChainOfThought` and its parts | Simplified: header, steps with a rail, search-result chips. |
|
|
401
477
|
| `PromptInput`, `PromptInputButton`, `PromptInputSubmit` | The web field surface with a `header` slot (attachments), a field, a `toolbar` slot and the send button. The field grows from one line to 160 high (about 6 lines), then scrolls. Send is primary with text and disabled without. While `status` is `submitted` or `streaming` it is a "Stop" square that calls `onStop`. It sits in a `KeyboardAvoidingView` (padding, on iOS and Android) and pads the safe-area insets: put it last in a full-height column. Under a navigation header, pass the header height as `keyboardVerticalOffset`; above a tab bar, pass `safeBottom={false}`. |
|
|
478
|
+
| `PromptInput layout="inline"` | One row, as the web `Composer layout="inline"` (Eva's chat pill): `leading` (such as a + button), the field (one line to start, then it grows), `toolbar`, then send, in a 20-radius field with `p-1.5`. 44 high on one line. |
|
|
402
479
|
| `Suggestions`, `Suggestion` | A horizontal row of round chips (or `orientation="vertical"`). `onPress` gets the suggestion text. |
|
|
403
480
|
| `Sources`, `SourcesTrigger`, `SourcesContent`, `Source` | A fold of link rows. A row opens its address with `Linking.openURL`, or hands it to `onPress`. |
|
|
404
481
|
| `FileChip`, `Attachments`, `Attachment` | A monospace file chip (a button with `onOpenFile`), and attachment chips with a media icon or image thumbnail and a labelled remove button. No hover cards and no grid previews. |
|
|
405
|
-
| `
|
|
482
|
+
| `ChoiceQuestion`, `choiceAnswers` | An agent's blocking multiple-choice question, as Eva's web question dock: question with a header badge and step count, lettered option rows with descriptions (a check on the primary fill when picked), "Other..." with a text field, then Back and Next or Submit. `onSubmit` gets question text to label (multi-select joined with ", "), the AskUserQuestion answer shape. `framed` (default) puts it in the dock card. |
|
|
483
|
+
| `AgentTraceFold` | A finished turn folded behind "Worked for 1m 5s" over a hairline, as the web `ActivityTasks` folded mode; the steps show in an `AgentTrace` when open. |
|
|
484
|
+
| `MessageMeta` | The muted clock after a message's actions ("10:21 PM · 1m"), caption size, tabular figures. |
|
|
485
|
+
| `ActivityStepDetail` | A tool step's body, as the web: exit code and "Truncated" badges, the shell transcript in a `CodeBlock`, red/green edit blocks, a write preview, and "Loading output..." while trimmed detail loads. |
|
|
486
|
+
| `CodeBlock`, `CodeBlockCopyButton` | Monospace code on the flat panel, scrolling sideways, with the language and a copy button in a header. With the optional peer `prism-react-renderer` and a `language`, tokens take the GitHub light or dark colours of the web shiki block; `highlight={false}` turns that off. Without the peer, plain text. |
|
|
406
487
|
| `ResponseText` | Plain, selectable reply text with newlines kept. It takes the colour of the `MessageContent` it is in. |
|
|
407
488
|
| `Shimmer` | "Live" text: the foreground fades to 0.45 and back on the UI thread. With Reduce Motion it holds still, muted. |
|
|
408
489
|
|
|
409
|
-
**Markdown.**
|
|
490
|
+
**Markdown.** `ResponseText` draws text as it is. For markdown, install the optional peer `react-native-marked` and use `Markdown` from `@vvedantb/native-ui/markdown` in place of `ResponseText`: `<Markdown onCopyCode={copy}>{reply}</Markdown>`. It follows the web `markdown.css`: compact headings (1.43, 1.29, 1.14 em), primary links underlined at 30%, inline code on a 6% foreground wash, and fenced code as `CodeBlock`. Text takes the `MessageContent` colour. It renders through `useMarkdown`, so it nests in `ConversationContent` without a `FlatList`. Fences with a language are highlighted when `prism-react-renderer` is installed. Other markdown packages (`react-native-markdown-display`, `@ronradtke/react-native-markdown-display`) still work in place of `ResponseText`.
|
|
491
|
+
|
|
492
|
+
**Eva's tinted bubble.** `MessageContent bubble="tint"` draws a user message as Eva's web chat does (`rounded-surface bg-primary/10 px-3 py-2 text-foreground`): a 10% wash of the primary colour with foreground text, radius 16 on every corner. The default, `"primary"`, stays the web `MessageContent` bubble.
|
|
410
493
|
|
|
411
494
|
**Clipboard.** React Native's own `Clipboard` is deprecated (moved out of core), and the package adds no clipboard package. `MessageAction kind="copy"` calls your `onPress`, and `CodeBlock` shows its copy button when given `onCopy(code)`. Pass your own clipboard call, for example `expo-clipboard`'s `setStringAsync`.
|
|
412
495
|
|
|
@@ -414,6 +497,61 @@ Every control has a role, an accessible name and a 44 pt target. Folds report `e
|
|
|
414
497
|
|
|
415
498
|
`@vvedantb/native-ui/ai-gallery` exports `AiChatGallery`, a full chat screen with a sample thread: a user message, a finished assistant message with folded reasoning and trace, code, sources and actions, and a second answer that streams with its reasoning open until you press Stop. It also has suggestions and the composer with sample attachments. Put it in a dev-only route with a `SafeAreaProvider` above it. `ParityGallery` shows the same thread (`AiChatSamples`) in its "AI chat" section.
|
|
416
499
|
|
|
500
|
+
## DropdownMenu (Unreleased)
|
|
501
|
+
|
|
502
|
+
A small menu anchored to its trigger, the native counterpart of the web `DropdownMenu`. On `/primitives` and the root.
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
<DropdownMenu>
|
|
506
|
+
<DropdownMenuTrigger accessibilityLabel="List options">
|
|
507
|
+
<IconDots size={18} color={tokens.color.muted[scheme]} />
|
|
508
|
+
</DropdownMenuTrigger>
|
|
509
|
+
<DropdownMenuContent align="end">
|
|
510
|
+
<DropdownMenuLabel>Sort</DropdownMenuLabel>
|
|
511
|
+
<DropdownMenuRadioItem checked={sort === "new"} onSelect={() => setSort("new")}>Newest first</DropdownMenuRadioItem>
|
|
512
|
+
<DropdownMenuRadioItem checked={sort === "old"} onSelect={() => setSort("old")}>Oldest first</DropdownMenuRadioItem>
|
|
513
|
+
<DropdownMenuSeparator />
|
|
514
|
+
<DropdownMenuItem icon={IconPencil} onSelect={rename}>Rename</DropdownMenuItem>
|
|
515
|
+
<DropdownMenuItem icon={IconTrash} destructive onSelect={remove}>Delete</DropdownMenuItem>
|
|
516
|
+
</DropdownMenuContent>
|
|
517
|
+
</DropdownMenu>
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
| Part | Props |
|
|
521
|
+
| --- | --- |
|
|
522
|
+
| `DropdownMenu` | `open`, `defaultOpen`, `onOpenChange`. Holds the open state; renders no view. |
|
|
523
|
+
| `DropdownMenuTrigger` | `Pressable` props and `asChild`. On press it measures itself (`measureInWindow`) and opens the menu; pressed again it closes it. Reports `expanded`. With `asChild` the child must pass `ref` on to its native view (`Button` and `IconButton` do). |
|
|
524
|
+
| `DropdownMenuContent` | `align` (`"start"`, default, or `"end"`), `sideOffset` (default 4), `View` props. A transparent RN `Modal`; the menu sits under the trigger and flips above it when it does not fit below. |
|
|
525
|
+
| `DropdownMenuLabel` | `children`. |
|
|
526
|
+
| `DropdownMenuItem` | `children`, `onSelect`, `icon` (a Tabler icon), `destructive`, `disabled`, `closeOnSelect` (default `true`). |
|
|
527
|
+
| `DropdownMenuRadioItem` | The same without `icon` and `destructive`, plus `checked`. |
|
|
528
|
+
| `DropdownMenuSeparator` | none. |
|
|
529
|
+
|
|
530
|
+
Sizes come from the web menu classes: content at least 192 wide, `p-1.5`, radius 16, on the opaque overlay surface of `Dialog` and `Sheet` (`FloatingSurface` without blur); items `px-2.5 py-2` at radius 8, `text-sm` with a 16 icon, at least 44 high; radio items `pl-8` with a 16 check; the label `text-xs` semibold, uppercase, `tracking-widest`, muted; the separator a 1-high `separator` line. Items are `palette.overlayForeground` (`palette.danger` when `destructive`) and take the `default` fill while pressed. A tap outside, Android back and the VoiceOver escape gesture close the menu. Roles: `menu`, `menuitem`, `radio` (with `checked`), `header`. With larger text, items are `text-lg` and 56 high. No sub-menus, checkbox items, shortcuts or `align="center"`.
|
|
531
|
+
|
|
532
|
+
## BulkActionBar (Unreleased)
|
|
533
|
+
|
|
534
|
+
The floating selection bar, as the web `BulkActionBar`. On `/primitives` and the root. Put it last in a full-size container: it sits at the bottom, centred.
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
<BulkActionBar
|
|
538
|
+
isSelecting={selecting}
|
|
539
|
+
selectedCount={selected.size}
|
|
540
|
+
totalCount={rows.length}
|
|
541
|
+
onExitSelect={exit}
|
|
542
|
+
onSelectAll={selectAll}
|
|
543
|
+
onClearSelection={clear}
|
|
544
|
+
primaryActions={[{ key: "archive", label: "Archive tasks", shortLabel: "Archive", icon: IconArchive, onClick: archive }]}
|
|
545
|
+
moreActions={[{ key: "move", label: "Move to project", icon: IconFolder, onClick: move }]}
|
|
546
|
+
destructiveAction={{ key: "delete", label: "Delete tasks", shortLabel: "Delete", icon: IconTrash, destructive: true, onClick: remove }}
|
|
547
|
+
bottomOffset={tabBarReserve(largerText, insets.bottom)}
|
|
548
|
+
/>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Props are the web names: `isSelecting`, `selectedCount`, `totalCount`, `onExitSelect`, `onSelectAll`, `onClearSelection`, `primaryActions`, `moreActions`, `destructiveAction` and `barKey` (here the `testID`). Each `BulkBarAction` has `key`, `label`, `shortLabel`, `icon`, `destructive`, `showSkipHint` (no effect on a phone) and `onClick`, plus a native-only `disabled`. Native also takes `bottomOffset`: the space under the bar before its 12 gap, by default the bottom safe-area inset. Above a `FloatingTabBar`, pass `tabBarReserve(largerText, insets.bottom)`.
|
|
552
|
+
|
|
553
|
+
The bar is the glass `FloatingSurface` at radius 16 with `px-2.5 py-2`. It holds the count chip (`text-xs` semibold, tabular figures, on `surface-secondary`), "Select all (n)" or "Clear", a divider, the actions, a More (dots) `DropdownMenu`, the destructive action after a divider, and "Cancel selection" (X). Buttons are the web `h-9` at radius 16, at least 44 wide and slopped to 44 high, with a 17 icon. Below 640 wide (the web `sm`) the actions are icon-only and "selected" is hidden, as on the web; labels stay as the accessible names. Actions are off (30% opacity) with no selection. It fades in from below over 150 ms (off with Reduce Motion) and scrolls sideways when it does not fit. With larger text, buttons are 48 high with a 22 icon and `text-lg`. No count pop animation or tooltip.
|
|
554
|
+
|
|
417
555
|
## Phones and accessibility
|
|
418
556
|
|
|
419
557
|
The components keep the web sizes on screen and reach a 44 pt touch target with `hitSlop` (`touchSlop` in `src/controlStyles.ts`): `Checkbox` (16), `Switch` (36 × 20), tab triggers (30 high), the `Select` trigger (40 high), the `SearchInput` clear button (24), the `UpgradeToProCard` pill (32 high, slop 8) and `Button` (the slop brings each size to at least 48). From 0.5.0 so do `ColorSwatches` swatches and `IconButton` (32 and 40); the `DateTimeField` clear button and the pressable `PageIndicator` dots are 44 squares. Controls set their role and state for VoiceOver and TalkBack: `Button` (`disabled`, `busy` while loading), `Checkbox` (`checked`, `mixed`), `Switch` (`checked`), `Tabs` and `PillTabs` (`tablist`, `tab`, `selected`) and the `Select` trigger (`combobox`, with an `accessibilityLabel` prop). Give a `Button` with no text child an `accessibilityLabel`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vvedantb/native-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Shared React Native / Expo UI primitives. Floating surfaces, forms, overlays, toasts, settings chrome, tab bar, AI chat parts, the family app marks, a NativeWind preset, the Expo web root and Clerk + Convex auth screens.",
|
|
6
6
|
"type": "module",
|
|
@@ -93,10 +93,18 @@
|
|
|
93
93
|
"types": "./src/haptics.ts",
|
|
94
94
|
"default": "./src/haptics.ts"
|
|
95
95
|
},
|
|
96
|
+
"./push": {
|
|
97
|
+
"types": "./src/push.ts",
|
|
98
|
+
"default": "./src/push.ts"
|
|
99
|
+
},
|
|
96
100
|
"./auth": {
|
|
97
101
|
"types": "./src/entries/auth.ts",
|
|
98
102
|
"default": "./src/entries/auth.ts"
|
|
99
103
|
},
|
|
104
|
+
"./markdown": {
|
|
105
|
+
"types": "./src/entries/markdown.ts",
|
|
106
|
+
"default": "./src/entries/markdown.ts"
|
|
107
|
+
},
|
|
100
108
|
"./expo-web-root": {
|
|
101
109
|
"types": "./src/entries/expo-web-root.ts",
|
|
102
110
|
"default": "./src/entries/expo-web-root.ts"
|
|
@@ -114,12 +122,6 @@
|
|
|
114
122
|
"LICENSE"
|
|
115
123
|
],
|
|
116
124
|
"sideEffects": false,
|
|
117
|
-
"scripts": {
|
|
118
|
-
"build": "node -e \"require('node:fs').accessSync('src/index.ts')\"",
|
|
119
|
-
"test": "vitest run",
|
|
120
|
-
"typecheck": "tsc -p tsconfig.typecheck.json",
|
|
121
|
-
"prepack": "node -e \"require('node:fs').accessSync('src/index.ts')\""
|
|
122
|
-
},
|
|
123
125
|
"peerDependencies": {
|
|
124
126
|
"@clerk/clerk-expo": "*",
|
|
125
127
|
"@expo/vector-icons": "*",
|
|
@@ -135,14 +137,19 @@
|
|
|
135
137
|
"dayjs": ">=1.11.0",
|
|
136
138
|
"expo-auth-session": "*",
|
|
137
139
|
"expo-blur": "*",
|
|
140
|
+
"expo-constants": "*",
|
|
141
|
+
"expo-device": "*",
|
|
138
142
|
"expo-haptics": "*",
|
|
139
143
|
"expo-linear-gradient": "*",
|
|
144
|
+
"expo-notifications": "*",
|
|
140
145
|
"expo-router": "*",
|
|
141
146
|
"expo-web-browser": "*",
|
|
142
147
|
"lucide-react-native": "*",
|
|
143
148
|
"nativewind": ">=4.0.0",
|
|
149
|
+
"prism-react-renderer": ">=2.0.0",
|
|
144
150
|
"react": ">=19.0.0",
|
|
145
151
|
"react-native": ">=0.76.0",
|
|
152
|
+
"react-native-marked": ">=8.0.0",
|
|
146
153
|
"react-native-reanimated": "*",
|
|
147
154
|
"react-native-safe-area-context": "*",
|
|
148
155
|
"react-native-screens": "*",
|
|
@@ -171,18 +178,33 @@
|
|
|
171
178
|
"expo-blur": {
|
|
172
179
|
"optional": true
|
|
173
180
|
},
|
|
181
|
+
"expo-constants": {
|
|
182
|
+
"optional": true
|
|
183
|
+
},
|
|
184
|
+
"expo-device": {
|
|
185
|
+
"optional": true
|
|
186
|
+
},
|
|
174
187
|
"expo-haptics": {
|
|
175
188
|
"optional": true
|
|
176
189
|
},
|
|
177
190
|
"expo-linear-gradient": {
|
|
178
191
|
"optional": true
|
|
179
192
|
},
|
|
193
|
+
"expo-notifications": {
|
|
194
|
+
"optional": true
|
|
195
|
+
},
|
|
180
196
|
"expo-router": {
|
|
181
197
|
"optional": true
|
|
182
198
|
},
|
|
183
199
|
"expo-web-browser": {
|
|
184
200
|
"optional": true
|
|
185
201
|
},
|
|
202
|
+
"prism-react-renderer": {
|
|
203
|
+
"optional": true
|
|
204
|
+
},
|
|
205
|
+
"react-native-marked": {
|
|
206
|
+
"optional": true
|
|
207
|
+
},
|
|
186
208
|
"react-native-svg": {
|
|
187
209
|
"optional": true
|
|
188
210
|
}
|
|
@@ -192,5 +214,10 @@
|
|
|
192
214
|
"react": "^19.1.0",
|
|
193
215
|
"typescript": "^5.9.0",
|
|
194
216
|
"vitest": "^3.2.0"
|
|
217
|
+
},
|
|
218
|
+
"scripts": {
|
|
219
|
+
"build": "node -e \"require('node:fs').accessSync('src/index.ts')\"",
|
|
220
|
+
"test": "vitest run",
|
|
221
|
+
"typecheck": "tsc -p tsconfig.typecheck.json"
|
|
195
222
|
}
|
|
196
|
-
}
|
|
223
|
+
}
|