@retray-dev/ui-kit 5.4.0 → 6.0.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/COMPONENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- # @retray-dev/ui-kit — Component Reference (v5.4.0)
1
+ # @retray-dev/ui-kit — Component Reference (v6.0.0)
2
2
 
3
3
  This file is the AI reference for this package. It is shipped inside the npm package so consuming projects can import it into their `CLAUDE.md` with:
4
4
 
@@ -36,11 +36,11 @@ export default function App() {
36
36
  ```
37
37
 
38
38
  **Provider order is mandatory:**
39
- - `SafeAreaProvider` must be outermost — required by `useSafeAreaInsets` in `ToastProvider`
39
+ - `SafeAreaProvider` must be outermost — required by `@gorhom/bottom-sheet`
40
40
  - `initialMetrics={initialWindowMetrics}` is required on Android to avoid a "No safe area value available" crash on first render
41
41
  - `GestureHandlerRootView` must wrap everything — required by `@gorhom/bottom-sheet`
42
42
  - `BottomSheetModalProvider` must be inside `GestureHandlerRootView`
43
- - `ToastProvider` must be inside `SafeAreaProvider`
43
+ - `ToastProvider` wraps children and renders `Toaster` (from `sonner-native`) internally
44
44
 
45
45
  ## Typography — Poppins (Required)
46
46
 
@@ -136,6 +136,9 @@ These are the only values you need to supply when customizing the theme. The lib
136
136
  | `successForeground` | `#ffffff` | `#ffffff` | Text/icon on success backgrounds |
137
137
  | `warning` | `#e67e00` | `#f57c00` | Warning / caution states |
138
138
  | `warningForeground` | `#ffffff` | `#ffffff` | Text/icon on warning backgrounds |
139
+ | `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop/overlay color behind sheets and dialogs |
140
+ | `accent` *(optional)* | same as `primary` | same as `primary` | Secondary brand accent (e.g. Airbnb coral). Falls back to `primary` |
141
+ | `accentForeground` *(optional)* | same as `primaryForeground` | same as `primaryForeground` | Text/icon on accent backgrounds. Falls back to `primaryForeground` |
139
142
 
140
143
  ### Derived Tokens (ResolvedColors) — read-only via useTheme().colors
141
144
 
@@ -155,6 +158,9 @@ The full palette components consume. Never supply these directly — they are co
155
158
  | `warningBorder` | `warning` @ 30% | Warning banner border |
156
159
  | `ring` | `= primary` | Focus ring color (always matches primary) |
157
160
  | `input` | `= border` | Input field border (always matches border) |
161
+ | `overlay` | `overlay` token or `rgba(0,0,0,0.45)` | Backdrop behind sheets and dialogs |
162
+ | `accentResolved` | `accent` token or `= primary` | Resolved accent color — always present |
163
+ | `accentForegroundResolved` | `accentForeground` token or `= primaryForeground` | Resolved text on accent — always present |
158
164
 
159
165
  **Usage example — building a custom component using derived tokens:**
160
166
  ```tsx
@@ -231,10 +237,10 @@ import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@
231
237
  | `none` | 0 | No rounding |
232
238
  | `xs` | 4 | Micro chips, tags |
233
239
  | `sm` | 8 | Inputs, Textarea, Select, Checkbox |
234
- | `md` | 14 | Cards, MediaCard, AlertBanner, Toast, EmptyState |
240
+ | `md` | 14 | Cards, Buttons (all variants), MediaCard, AlertBanner, Toast, EmptyState |
235
241
  | `lg` | 20 | Sheet top corners |
236
- | `xl` | 32 | Primary CTA buttons (pill-like) |
237
- | `full` | 9999 | Circular elements, CategoryStrip chips |
242
+ | `xl` | 32 | Large decorative elements |
243
+ | `full` | 9999 | IconButton (circle), CategoryStrip chips |
238
244
 
239
245
  **Types:** `Radius`, `RadiusKey`
240
246
 
@@ -286,7 +292,7 @@ All components use these tokens for text styling. Import and use in custom compo
286
292
  | `caption-sm` | 13 | 400 | 16 | 0 | Timestamps, metadata |
287
293
  | `badge-text` | 11 | 600 | 13 | 0 | Badge labels, small tags |
288
294
  | `micro-label` | 12 | 700 | 16 | 0 | Micro labels, overlines |
289
- | `uppercase-tag` | 8 | 700 | 10 | 0.32 | Uppercase decorative tags (auto-uppercase) |
295
+ | `uppercase-tag` | 10 | 700 | 13 | 0.8 | Uppercase decorative tags (auto-uppercase) |
290
296
  | `button-lg` | 16 | 500 | 20 | 0 | Button labels (md/lg size) |
291
297
  | `button-sm` | 14 | 500 | 18 | 0 | Button labels (sm size) |
292
298
 
@@ -344,6 +350,59 @@ const styles = StyleSheet.create({
344
350
 
345
351
  ---
346
352
 
353
+ ## Migration Guide: v5 → v6
354
+
355
+ ### New Components
356
+
357
+ **`MenuItem`** — Navigation row with icon, label, and optional right slot (`rightRender`). Replaces ad-hoc `ListItem` usage for settings/nav menus. Zero horizontal padding by design — consumer controls spacing.
358
+
359
+ ```tsx
360
+ import { MenuItem } from '@retray-dev/ui-kit'
361
+ // variants: 'plain' (default) | 'card'
362
+ <MenuItem label="Profile" iconName="user" onPress={() => {}} />
363
+ <MenuItem label="Notifications" iconName="bell" rightRender={<Switch value={on} onValueChange={setOn} />} showChevron={false} onPress={() => {}} />
364
+ ```
365
+
366
+ ### Breaking Changes
367
+
368
+ **Sheet keyboard defaults changed:**
369
+ | Prop | v5 default | v6 default |
370
+ |------|-----------|-----------|
371
+ | `keyboardBehavior` | `'fillParent'` (Android) / `'interactive'` (iOS) | `'interactive'` (both) |
372
+ | `android_keyboardInputMode` | `'adjustResize'` | `'adjustPan'` |
373
+
374
+ If you relied on `adjustResize`, set it explicitly: `android_keyboardInputMode="adjustResize"`.
375
+
376
+ **Toast API simplified:**
377
+
378
+ ```tsx
379
+ // v5 — hook-only API
380
+ const { toast } = useToast()
381
+ toast.success('Done')
382
+
383
+ // v6 — direct import preferred (hook still works for compat)
384
+ import { toast } from '@retray-dev/ui-kit'
385
+ toast.success('Done')
386
+ ```
387
+
388
+ ### New Theme Tokens
389
+
390
+ Three optional `ThemeColors` tokens — fall back gracefully if omitted:
391
+
392
+ | Token | Default | Purpose |
393
+ |-------|---------|---------|
394
+ | `overlay` | `rgba(0,0,0,0.45)` | Backdrop color behind sheets and dialogs |
395
+ | `accent` | `= primary` | Secondary brand accent color |
396
+ | `accentForeground` | `= primaryForeground` | Text on accent backgrounds |
397
+
398
+ Access resolved values via `useTheme().colors.overlay`, `.accentResolved`, `.accentForegroundResolved`.
399
+
400
+ ### Token Corrections
401
+
402
+ - `TYPOGRAPHY['uppercase-tag']` — size corrected to `10` (was documented as `8`, actual value was always `10`)
403
+
404
+ ---
405
+
347
406
  ## Components
348
407
 
349
408
  ---
@@ -381,7 +440,7 @@ const styles = StyleSheet.create({
381
440
  | `caption-sm` | 13 | 400 | `foregroundMuted` | Timestamps, metadata, helper text |
382
441
  | `badge-text` | 11 | 600 | `foreground` | Badge labels, small status tags |
383
442
  | `micro-label` | 12 | 700 | `foreground` | Micro labels, overlines |
384
- | `uppercase-tag` | 8 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
443
+ | `uppercase-tag` | 10 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
385
444
  | `button-lg` | 16 | 500 | `foreground` | Internal use in Button component (md/lg) |
386
445
  | `button-sm` | 14 | 500 | `foreground` | Internal use in Button component (sm) |
387
446
 
@@ -456,7 +515,7 @@ const styles = StyleSheet.create({
456
515
  | `md` | 48px | 24px | 18px | `button-lg` (16pt/500) |
457
516
  | `lg` | 56px | 28px | 20px | `button-lg` (16pt/500) |
458
517
 
459
- **Shape:** `borderRadius: RADIUS.xl = 32px` — pill-shaped on all sizes.
518
+ **Shape:** `borderRadius: RADIUS.md = 14px` — Airbnb-aligned rounded rect on all variants. Exception: `IconButton` uses `RADIUS.full` (perfect circle).
460
519
 
461
520
  **Animations:** Scale springs to 0.95 on `onPressIn`, back to 1.0 on `onPressOut`.
462
521
 
@@ -1887,7 +1946,7 @@ const [tab, setTab] = useState('profile')
1887
1946
 
1888
1947
  ### Sheet
1889
1948
 
1890
- **Import:** `import { Sheet, BottomSheetModalProvider } from '@retray-dev/ui-kit'`
1949
+ **Import:** `import { Sheet, SheetTextInput, BottomSheetModalProvider } from '@retray-dev/ui-kit'`
1891
1950
 
1892
1951
  **When to use:** Bottom sheet for contextual actions, filters, pickers, or detail views that don't need a full screen. Auto-sizes to content — no snap points needed.
1893
1952
 
@@ -1904,160 +1963,156 @@ Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to `ba
1904
1963
  | open | `boolean` | required | `true` presents the sheet, `false` dismisses it |
1905
1964
  | onClose | `() => void` | required | Called on swipe-dismiss or backdrop press |
1906
1965
  | title | `string` | — | Sheet heading |
1907
- | description | `string` | — | Supporting text below title |
1966
+ | subtitle | `string` | — | Supporting text below title (replaces deprecated `description`) |
1967
+ | showCloseButton | `boolean` | `false` | Show X close button in the header |
1908
1968
  | children | `ReactNode` | — | Sheet content |
1909
- | style | `ViewStyle` | — | Inner content container style |
1910
- | scrollable | `boolean` | `false` | Wraps children in `BottomSheetScrollView` — fixes gesture conflict on both platforms when content needs to scroll |
1911
- | maxHeight | `number` | | Caps sheet height (dp). Automatically enables scrolling when content exceeds this value |
1912
- | keyboardBehavior | `'padding' \| 'height' \| 'position' \| 'none'` | | Wraps content in `KeyboardAvoidingView` when set. Use when sheet contains text inputs |
1913
- | keyboardOffset | `number` | `0` | Extra vertical offset for `KeyboardAvoidingView` |
1969
+ | style | `ViewStyle` | — | Inner scroll/content container style |
1970
+ | contentStyle | `ViewStyle` | | Outer content wrapper style |
1971
+ | scrollable | `boolean` | `false` | Wraps children in `BottomSheetScrollView` |
1972
+ | maxHeight | `number` | `~85% screen` | Caps sheet height (dp). Defaults to 85% of screen height. Enables scrolling when content overflows |
1973
+ | keyboardBehavior | `'extend' \| 'fillParent' \| 'interactive'` | `'interactive'` | How sheet responds to keyboard. `'interactive'` (default) offsets sheet by keyboard size — works on both platforms. `'fillParent'` extends to fill parent view. `'extend'` extends to max snap point |
1974
+ | keyboardBlurBehavior | `'none' \| 'restore'` | `'restore'` | What happens when keyboard dismisses: `'restore'` → return to pre-keyboard position (recommended), `'none'` → stay at keyboard-adjusted position |
1975
+ | enableBlurKeyboardOnGesture | `boolean` | `true` | Dismiss keyboard when user starts dragging sheet down (recommended for better UX) |
1976
+ | android_keyboardInputMode | `'adjustPan' \| 'adjustResize'` | `'adjustPan'` | Android-only: `'adjustPan'` moves the window (default — fixes restore issues with dynamic sizing). `'adjustResize'` resizes the container (can cause transparent gap when keyboard dismisses) |
1977
+ | footer | `ReactNode` | — | Sticky footer below scroll area (sticky above keyboard) |
1978
+ | snapPoints | `(string \| number)[]` | — | Optional snap points (e.g., `['50%', '85%']`). When omitted, uses dynamic sizing (auto-fits content) |
1914
1979
 
1915
1980
  **Features:**
1916
- - `enableDynamicSizing` — height auto-fits content, no `snapPoints` needed
1981
+ - `enableDynamicSizing` — height auto-fits content, no `snapPoints` needed (default behavior when `snapPoints` is omitted)
1982
+ - `snapPoints` — optionally provide custom snap points (e.g., `['50%', '85%']`). Disables dynamic sizing when provided
1917
1983
  - `enablePanDownToClose` — swipe down to dismiss
1918
1984
  - Backdrop press dismisses
1919
- - **Scrollable content:** use `scrollable` prop (explicit) or `maxHeight` prop (capped height). Both use `BottomSheetScrollView` internally — do NOT use plain `ScrollView` inside Sheet, it breaks gesture handling on iOS
1920
-
1921
- **Styling:** `RADIUS.lg = 20px` top corners, 16px horizontal + 20px bottom padding.
1922
-
1923
- **Haptics:** `impactLight` on open.
1985
+ - **Scrollable content:** use `scrollable` prop or `maxHeight`. Both use `BottomSheetScrollView` — do NOT use plain `ScrollView` inside Sheet
1986
+ - **Keyboard handling:** Full keyboard awareness via `@gorhom/bottom-sheet` v5. Professional defaults:
1987
+ - `keyboardBehavior="interactive"` sheet offsets by keyboard size on both platforms
1988
+ - `android_keyboardInputMode="adjustPan"` — moves window instead of resizing (fixes restore issues on Android)
1989
+ - `keyboardBlurBehavior="restore"` returns to pre-keyboard position when keyboard dismisses
1990
+ - `enableBlurKeyboardOnGesture={true}` — dismisses keyboard when dragging sheet down
1991
+ - `topInset` — automatically applied from safe area context to prevent going above notch
1992
+ - **Text inputs inside sheet:** **MUST use `SheetTextInput`** (re-exported `BottomSheetTextInput`) — handles focus/blur internally, communicates with sheet's keyboard system. **Never use regular `TextInput`** inside sheets — keyboard handling will break
1993
+ - **Custom TextInput:** If using custom input components, you must copy `handleOnFocus`/`handleOnBlur` from [BottomSheetTextInput source](https://github.com/gorhom/react-native-bottom-sheet/blob/master/src/components/bottomSheetTextInput/BottomSheetTextInput.tsx)
1994
+
1995
+ **Haptics:** `impactMedium` on open.
1924
1996
 
1925
1997
  **Examples:**
1926
1998
  ```tsx
1927
1999
  const [open, setOpen] = useState(false)
1928
2000
 
2001
+ // Basic
1929
2002
  <Sheet open={open} onClose={() => setOpen(false)} title="Sort by">
1930
- <RadioGroup
1931
- options={[
1932
- { label: 'Newest', value: 'newest' },
1933
- { label: 'Price: Low to High', value: 'price_asc' },
1934
- { label: 'Price: High to Low', value: 'price_desc' },
1935
- { label: 'Rating', value: 'rating' },
1936
- ]}
1937
- value={sort}
1938
- onValueChange={(v) => { setSort(v); setOpen(false) }}
2003
+ <RadioGroup options={sortOptions} value={sort} onValueChange={setSort} />
2004
+ </Sheet>
2005
+
2006
+ // Scrollable list
2007
+ <Sheet open={open} onClose={() => setOpen(false)} title="Results" scrollable maxHeight={500}>
2008
+ {items.map((r) => <ListItem key={r.id} title={r.name} showSeparator />)}
2009
+ </Sheet>
2010
+
2011
+ // With text input — keyboard handling works automatically (platform-optimized defaults)
2012
+ <Sheet
2013
+ open={open}
2014
+ onClose={() => setOpen(false)}
2015
+ title="Add note"
2016
+ subtitle="Keyboard handling is automatic with platform-optimized defaults"
2017
+ >
2018
+ <SheetTextInput
2019
+ placeholder="Write your note..."
2020
+ multiline
2021
+ style={{
2022
+ borderWidth: 1,
2023
+ borderColor: '#ddd',
2024
+ borderRadius: 8,
2025
+ padding: 12,
2026
+ minHeight: 80,
2027
+ }}
1939
2028
  />
2029
+ <Button label="Save" fullWidth style={{ marginTop: 12 }} onPress={() => setOpen(false)} />
1940
2030
  </Sheet>
1941
2031
 
1942
- // Scrollable sheet long list
1943
- <Sheet open={open} onClose={() => setOpen(false)} title="Logs" scrollable>
1944
- {logs.map((log) => (
1945
- <Text key={log.id}>{log.message}</Text>
1946
- ))}
2032
+ // With custom snap points (manual sizing)
2033
+ <Sheet
2034
+ open={open}
2035
+ onClose={() => setOpen(false)}
2036
+ title="Filters"
2037
+ snapPoints={['50%', '90%']}
2038
+ >
2039
+ <RadioGroup options={filterOptions} value={filter} onValueChange={setFilter} />
1947
2040
  </Sheet>
1948
2041
 
1949
- // Capped height — scroll kicks in when content overflows 400dp
1950
- <Sheet open={open} onClose={() => setOpen(false)} title="Results" maxHeight={400}>
1951
- {results.map((r) => <ListItem key={r.id} title={r.name} />)}
2042
+ // With sticky footer
2043
+ <Sheet open={open} onClose={() => setOpen(false)} title="Filters" scrollable
2044
+ footer={<View style={{ padding: 16 }}><Button label="Apply" fullWidth onPress={() => setOpen(false)} /></View>}>
2045
+ {/* long filter content */}
1952
2046
  </Sheet>
1953
2047
 
1954
- // Filter sheet
1955
- <Sheet open={filterOpen} onClose={() => setFilterOpen(false)} title="Filters">
1956
- <View style={{ gap: SPACING.lg }}>
1957
- <View>
1958
- <Text variant="title-sm">Price range</Text>
1959
- <Slider value={maxPrice} minimumValue={0} maximumValue={1000} step={50} onValueChange={setMaxPrice} showValue formatValue={(v) => `$${v}`} />
1960
- </View>
1961
- <Separator />
1962
- <View>
1963
- <Text variant="title-sm">Category</Text>
1964
- <ChipGroup options={categoryOptions} value={selectedCategory} onValueChange={setSelectedCategory} />
1965
- </View>
1966
- <Button label="Apply filters" fullWidth onPress={() => { applyFilters(); setFilterOpen(false) }} />
1967
- </View>
2048
+ // With close button
2049
+ <Sheet open={open} onClose={() => setOpen(false)} title="Details" subtitle="Product info" showCloseButton>
2050
+ <Text>Content here...</Text>
1968
2051
  </Sheet>
1969
2052
  ```
1970
2053
 
2054
+ **Keyboard handling notes:**
2055
+ - No `KeyboardAvoidingView` needed — `@gorhom/bottom-sheet` handles everything
2056
+ - Always use `SheetTextInput` (not plain `TextInput`) for auto-focus/blur handling
2057
+ - Default `keyboardBehavior="interactive"` works on both platforms
2058
+ - Default `android_keyboardInputMode="adjustPan"` fixes the transparent gap that occurs with `adjustResize` when keyboard dismisses
2059
+ - `enableBlurKeyboardOnGesture={true}` (default) dismisses keyboard when dragging sheet
2060
+
1971
2061
  ---
1972
2062
 
1973
2063
  ### Toast / useToast
1974
2064
 
1975
- **Import:** `import { ToastProvider, useToast } from '@retray-dev/ui-kit'`
2065
+ **Import:** `import { toast, ToastProvider, useToast } from '@retray-dev/ui-kit'`
1976
2066
 
1977
2067
  **When to use:** Ephemeral feedback messages — save confirmations, errors, copy notifications, background process updates. Auto-dismiss after duration.
1978
2068
 
1979
- **Required setup:** `ToastProvider` must wrap app inside `SafeAreaProvider`. See Setup section above.
2069
+ **Powered by:** `sonner-native` white/black background, colored icon marks the variant (not the background), swipe-to-dismiss, promise support, stacking.
2070
+
2071
+ **Required setup:** `ToastProvider` must wrap the app. See Setup section above. Also requires `react-native-svg` and `react-native-screens` as peer deps.
1980
2072
 
1981
- **Usage:**
2073
+ **API — direct function (preferred):**
1982
2074
  ```tsx
1983
- function MyComponent() {
1984
- const { toast, dismiss } = useToast()
2075
+ import { toast } from '@retray-dev/ui-kit'
2076
+
2077
+ // Variants:
2078
+ toast.success('Saved successfully')
2079
+ toast.error('Connection error', { description: 'Check your network' })
2080
+ toast.warning('Slow connection detected')
2081
+ toast('Neutral message')
2082
+ toast.loading('Processing...')
2083
+
2084
+ // Promise — auto-resolves:
2085
+ toast.promise(saveData(), {
2086
+ loading: 'Saving...',
2087
+ success: 'Saved',
2088
+ error: 'Failed to save',
2089
+ })
1985
2090
 
1986
- return (
1987
- <Button
1988
- label="Save"
1989
- onPress={async () => {
1990
- await save()
1991
- toast({ title: 'Saved', variant: 'success' })
1992
- }}
1993
- />
1994
- )
1995
- }
2091
+ // Programmatic dismiss:
2092
+ const id = toast.loading('Uploading...')
2093
+ await upload()
2094
+ toast.dismiss(id)
2095
+ toast.success('Upload complete')
1996
2096
  ```
1997
2097
 
1998
- **`toast()` options:**
1999
-
2000
- | Field | Type | Default | Notes |
2001
- |-------|------|---------|-------|
2002
- | title | `string` | — | Bold heading |
2003
- | description | `string` | — | Detail text below title |
2004
- | variant | `'default' \| 'destructive' \| 'success' \| 'warning'` | `'default'` | Background and icon color |
2005
- | duration | `number` (ms) | `3000` | Auto-dismiss delay. Pass `Infinity` to prevent auto-dismiss |
2006
- | icon | `ReactNode` | — | Custom icon. Defaults to variant symbol |
2007
- | iconName | `string` | — | Icon name from `@expo/vector-icons`. Takes precedence over `icon` |
2008
- | iconColor | `string` | — | Override icon color |
2009
- | action | `{ label: string, onPress: () => void }` | — | Optional action button beside dismiss |
2010
-
2011
- **`dismiss(id)`:** Dismiss a specific toast programmatically. `id` is returned by the `toast()` call.
2012
-
2013
- **Variant details:**
2014
- - `default` — dark/primary background, `impactLight` haptic
2015
- - `success` — `success` token background, `notificationSuccess` haptic
2016
- - `destructive` — `destructive` token background, `notificationError` haptic
2017
- - `warning` — `warning` token background, `notificationError` haptic
2018
-
2019
- **Behavior:**
2020
- - Max 3 toasts shown simultaneously — oldest removed when 4th arrives
2021
- - Appear at top of screen below status bar (dynamic safe area inset)
2022
- - Swipe left or right to dismiss early (threshold: 80px or 800pt/s velocity)
2023
- - **Web:** 400px max width, centered
2024
-
2025
- **Animation:** Entrance: `withTiming(120ms, Easing.out(Easing.exp))` slide-down + opacity. Exit: `withTiming(200ms)` slide-up + opacity fade.
2026
-
2027
- **Examples:**
2098
+ **API — hook (backward compat):**
2028
2099
  ```tsx
2029
2100
  const { toast, dismiss } = useToast()
2030
-
2031
- // Basic variants
2032
- toast({ title: 'Saved', variant: 'success' })
2033
- toast({ title: 'Connection error', variant: 'destructive' })
2034
- toast({ title: 'Check your email', variant: 'warning' })
2035
- toast({ title: 'Link copied' })
2036
-
2037
- // With description
2038
- toast({
2039
- title: 'Payment sent',
2040
- description: '$250 sent to John Doe',
2041
- variant: 'success',
2042
- iconName: 'check-circle',
2043
- })
2044
-
2045
- // With action button
2046
- toast({
2047
- title: 'Message deleted',
2048
- action: { label: 'Undo', onPress: () => restoreMessage() },
2049
- })
2050
-
2051
- // Custom duration (longer)
2052
- toast({ title: 'Processing your request...', duration: 8000 })
2053
-
2054
- // Programmatic dismiss
2055
- const id = toast({ title: 'Uploading...', duration: Infinity })
2056
- await upload()
2101
+ toast.success('Saved')
2057
2102
  dismiss(id)
2058
- toast({ title: 'Upload complete', variant: 'success' })
2059
2103
  ```
2060
2104
 
2105
+ **Behavior:**
2106
+ - Max 3 toasts shown simultaneously
2107
+ - Appear top-center with safe area insets (respects notch/status bar)
2108
+ - Swipe up to dismiss early
2109
+ - Auto-dismiss after 4000ms
2110
+ - Follows `colorScheme` from `ThemeProvider` (light/dark background)
2111
+
2112
+ **Notes:**
2113
+ - `richColors: false` — background stays white (light) / black (dark). Variant shown via icon color only.
2114
+ - `closeButton: false` — swipe-to-dismiss is the gesture.
2115
+
2061
2116
  ---
2062
2117
 
2063
2118
  ### ConfirmDialog
@@ -2130,6 +2185,8 @@ toast({ title: 'Upload complete', variant: 'success' })
2130
2185
 
2131
2186
  **When to use:** Rows in lists, feeds, menus, settings screens. The most compositional component — composes left slot, text block, and right slot in a standard horizontal layout. Supports plain (list items) and card (standalone cards) variants.
2132
2187
 
2188
+ **Important:** ListItem has **zero horizontal padding** by design — the consumer controls spacing. Wrap in a container with padding for standalone use, or use `variant="card"` for auto-styled surfaces.
2189
+
2133
2190
  | Prop | Type | Default | Notes |
2134
2191
  |------|------|---------|-------|
2135
2192
  | title | `string` | required | Primary text |
@@ -2240,6 +2297,84 @@ toast({ title: 'Upload complete', variant: 'success' })
2240
2297
 
2241
2298
  ---
2242
2299
 
2300
+ ### MenuItem
2301
+
2302
+ **Import:** `import { MenuItem } from '@retray-dev/ui-kit'`
2303
+
2304
+ **When to use:** Settings screens, navigation lists, menu entries — icon + label + optional right content. Purpose-built for Airbnb-style settings rows. Use `ListItem` when you need subtitle, caption, or more complex layouts.
2305
+
2306
+ **Important:** MenuItem has **zero horizontal padding** by design — the consumer controls spacing. Wrap in a container with padding for standalone use, or use `variant="card"` for auto-styled surfaces.
2307
+
2308
+ | Prop | Type | Default | Notes |
2309
+ |------|------|---------|-------|
2310
+ | label | `string` | required | Row label |
2311
+ | iconName | `string` | — | Icon from `@expo/vector-icons`. Auto-resolved across all 6 families |
2312
+ | icon | `ReactNode` | — | Custom icon (used if `iconName` not set) |
2313
+ | iconColor | `string` | — | Override icon color. Defaults to `foreground` |
2314
+ | rightRender | `ReactNode` | — | Custom content on the right. Replaces default chevron. Use for Switch, Checkbox, Badge, etc. |
2315
+ | showChevron | `boolean` | `true` | Show chevron on the right. Ignored when `rightRender` is set |
2316
+ | onPress | `() => void` | required | Press handler |
2317
+ | variant | `'plain' \| 'card'` | `'plain'` | `plain` sits inside parent surface. `card` has own background + border |
2318
+ | disabled | `boolean` | `false` | Dims and disables press |
2319
+ | showSeparator | `boolean` | `false` | Hairline separator at bottom |
2320
+ | style | `ViewStyle` | — | Container style override |
2321
+ | labelStyle | `TextStyle` | — | Label text style override |
2322
+
2323
+ **Features:**
2324
+ - Height: 54dp — consistent settings row
2325
+ - Chevron: shown by default (`showChevron={true}`). Can be replaced via `rightRender` prop
2326
+ - `rightRender` — arbitrary right slot content (Switch, Checkbox, Badge, etc). When set, chevron is hidden
2327
+ - Press scale: 0.97 spring (`stiffness: 350, damping: 28, mass: 0.9`)
2328
+ - Haptics: `selectionAsync` on press
2329
+
2330
+ **Example:**
2331
+ ```tsx
2332
+ // Settings screen (wrap in container with padding)
2333
+ <View style={{ padding: 16, backgroundColor: colors.card, borderRadius: 12 }}>
2334
+ <MenuItem label="Personal info" iconName="user" onPress={() => navigate('profile')} showSeparator />
2335
+ <MenuItem label="Security" iconName="shield" onPress={() => navigate('security')} showSeparator />
2336
+ <MenuItem label="Privacy" iconName="lock" onPress={() => navigate('privacy')} showSeparator />
2337
+ <MenuItem
2338
+ label="Notifications"
2339
+ iconName="bell"
2340
+ onPress={() => setNotifs(!notifs)}
2341
+ rightRender={<Switch value={notifs} onValueChange={setNotifs} />}
2342
+ showSeparator
2343
+ />
2344
+ <MenuItem
2345
+ label="Dark Mode"
2346
+ iconName="moon"
2347
+ onPress={() => setDark(!dark)}
2348
+ rightRender={<Checkbox checked={dark} onPress={() => setDark(!dark)} />}
2349
+ showSeparator
2350
+ />
2351
+ <MenuItem
2352
+ label="Payments"
2353
+ iconName="credit-card"
2354
+ onPress={() => navigate('payments')}
2355
+ rightRender={<Badge label="New" variant="warning" size="sm" />}
2356
+ />
2357
+ </View>
2358
+
2359
+ // Card variant (standalone)
2360
+ <MenuItem
2361
+ variant="card"
2362
+ label="Refer a friend"
2363
+ iconName="gift"
2364
+ onPress={() => navigate('referral')}
2365
+ />
2366
+
2367
+ // No chevron (custom right content)
2368
+ <MenuItem
2369
+ label="Language"
2370
+ iconName="globe"
2371
+ rightRender={<Text>English</Text>}
2372
+ onPress={() => navigate('language')}
2373
+ />
2374
+ ```
2375
+
2376
+ ---
2377
+
2243
2378
  ### Chip / ChipGroup
2244
2379
 
2245
2380
  **Import:** `import { Chip, ChipGroup } from '@retray-dev/ui-kit'`