@retray-dev/ui-kit 5.1.0 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMPONENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- # @retray-dev/ui-kit — Component Reference (v5.0.0)
1
+ # @retray-dev/ui-kit — Component Reference (v5.4.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
 
@@ -499,6 +499,67 @@ const styles = StyleSheet.create({
499
499
 
500
500
  ---
501
501
 
502
+ ### ButtonGroup
503
+
504
+ **Import:** `import { ButtonGroup } from '@retray-dev/ui-kit'`
505
+
506
+ **When to use:** Auto-distribute space equally between buttons in horizontal or vertical layouts. Perfect for side-by-side CTAs (Cancel/Confirm, Back/Next) where both buttons should occupy equal width.
507
+
508
+ | Prop | Type | Default | Notes |
509
+ |------|------|---------|-------|
510
+ | children | `React.ReactNode` | required | Button components to lay out |
511
+ | gap | `number` | `12` | Spacing between buttons (in points) |
512
+ | vertical | `boolean` | `false` | Stack buttons vertically instead of horizontally |
513
+ | style | `ViewStyle` | — | Override container style |
514
+
515
+ **Behavior:** Automatically applies `flex: 1` to all direct children. Children share space equally regardless of label length.
516
+
517
+ **Mobile best practice:** When using 2 buttons with icons in a ButtonGroup, use `size="sm"` to prevent text clipping on narrow screens:
518
+ ```tsx
519
+ // ✅ Good — Small size prevents overflow
520
+ <ButtonGroup>
521
+ <Button label="Cancel" variant="secondary" size="sm" iconName="x" onPress={handleCancel} />
522
+ <Button label="Confirm" size="sm" iconName="check" onPress={handleConfirm} />
523
+ </ButtonGroup>
524
+
525
+ // ❌ Avoid — Default size may clip text when icons present
526
+ <ButtonGroup>
527
+ <Button label="Cancel" variant="secondary" iconName="x" onPress={handleCancel} />
528
+ <Button label="Confirm" iconName="check" onPress={handleConfirm} />
529
+ </ButtonGroup>
530
+ ```
531
+
532
+ **Examples:**
533
+ ```tsx
534
+ // Horizontal pair (50%/50%)
535
+ <ButtonGroup>
536
+ <Button label="Cancel" variant="secondary" onPress={handleCancel} />
537
+ <Button label="Confirm" onPress={handleConfirm} />
538
+ </ButtonGroup>
539
+
540
+ // Custom gap
541
+ <ButtonGroup gap={8}>
542
+ <Button label="Back" variant="text" onPress={handleBack} />
543
+ <Button label="Next" onPress={handleNext} />
544
+ </ButtonGroup>
545
+
546
+ // Vertical stack
547
+ <ButtonGroup vertical gap={8}>
548
+ <Button label="Primary Action" onPress={handlePrimary} />
549
+ <Button label="Secondary Action" variant="secondary" onPress={handleSecondary} />
550
+ <Button label="Delete" variant="destructive" onPress={handleDelete} />
551
+ </ButtonGroup>
552
+
553
+ // Three buttons (33%/33%/33%)
554
+ <ButtonGroup gap={6}>
555
+ <Button label="1D" variant="secondary" size="sm" onPress={() => setRange('1d')} />
556
+ <Button label="1W" variant="secondary" size="sm" onPress={() => setRange('1w')} />
557
+ <Button label="1M" size="sm" onPress={() => setRange('1m')} />
558
+ </ButtonGroup>
559
+ ```
560
+
561
+ ---
562
+
502
563
  ### IconButton
503
564
 
504
565
  **Import:** `import { IconButton } from '@retray-dev/ui-kit'`
@@ -682,6 +743,9 @@ const styles = StyleSheet.create({
682
743
  | error | `string` | — | Error text below; turns border destructive (2px) |
683
744
  | hint | `string` | — | Helper text below (hidden when `error` is set) |
684
745
  | rows | `number` | `4` | Minimum row count — each row ≈ 30px. Sets `numberOfLines` |
746
+ | prefixIcon | `string` | — | Icon name from `@expo/vector-icons` rendered inside top-left corner |
747
+ | prefixIconNode | `ReactNode` | — | Custom icon node rendered top-left |
748
+ | prefixIconColor | `string` | — | Override prefix icon color. Defaults to `foregroundMuted` |
685
749
  | containerStyle | `ViewStyle` | — | Outer container style |
686
750
  | style | `TextStyle` | — | TextInput element style |
687
751
 
@@ -689,9 +753,9 @@ const styles = StyleSheet.create({
689
753
 
690
754
  **Examples:**
691
755
  ```tsx
692
- <Textarea label="Bio" placeholder="Tell us about yourself" rows={5} />
693
- <Textarea label="Review" hint="Be honest and helpful" />
694
- <Textarea label="Notes" error="Notes are required" rows={3} />
756
+ <Textarea label="Bio" placeholder="Tell us about yourself" rows={5} prefixIcon="user" />
757
+ <Textarea label="Review" hint="Be honest and helpful" prefixIcon="message-circle" />
758
+ <Textarea label="Notes" error="Notes are required" rows={3} prefixIcon="edit-3" />
695
759
  ```
696
760
 
697
761
  ---
@@ -776,6 +840,9 @@ const [amount, setAmount] = useState(0)
776
840
  | prefix | `string` | `'$'` | Symbol prepended to formatted value |
777
841
  | showDecimals | `boolean` | `false` | Show two decimal places with comma separator (e.g. `$25.000,00`) |
778
842
  | textColor | `string` | — | Override text color. Defaults to `foreground` token |
843
+ | variant | `'hero' \| 'large' \| 'medium' \| 'small'` | — | Predefined size (48/32/18/14pt). Overrides default 56pt |
844
+ | autoScale | `boolean` | `false` | Enable `adjustsFontSizeToFit` — long values shrink to fit one line |
845
+ | maxFontSize | `number` | — | Max font size when `autoScale` true. Defaults to variant size or 56pt |
779
846
  | style | `ViewStyle` | — | Outer container style |
780
847
 
781
848
  **Format:** Dot (`.`) as thousands separator, comma (`,`) as decimal separator — Latin American / European format.
@@ -874,9 +941,10 @@ const [amount, setAmount] = useState(0)
874
941
 
875
942
  | Prop | Type | Default | Notes |
876
943
  |------|------|---------|-------|
877
- | src | `string` | — | Image URI (remote or local) |
878
- | fallback | `string` | — | Text shown when image fails or `src` is absent — first 2 characters shown, uppercased |
879
- | size | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Diameter in points |
944
+ | src | `string \| null` | — | Image URI (remote or local). `null` forces fallback |
945
+ | fallback | `string` | — | Manual initials (max 2 chars, uppercased) |
946
+ | fallbackText | `string` | | Full name extracts up to 2 initials (e.g. `"Julian Cruz"` `"JC"`) |
947
+ | size | `'sm' \| 'md' \| 'lg' \| 'xl' \| number` | `'md'` | Named size or custom diameter in points |
880
948
  | status | `'online' \| 'offline' \| 'busy' \| 'away'` | — | Status indicator dot, bottom-right |
881
949
  | style | `ViewStyle` | — | — |
882
950
 
@@ -903,6 +971,8 @@ const [amount, setAmount] = useState(0)
903
971
  <Avatar fallback="AN" size="md" />
904
972
  <Avatar src={user.avatar} fallback={user.initials} size="xl" status="online" />
905
973
  <Avatar fallback="BU" size="sm" status="busy" />
974
+ <Avatar fallbackText="Julian Cruz" size="md" /> // → "JC"
975
+ <Avatar src={avatarUrl} size={64} /> // custom numeric size
906
976
 
907
977
  // In a ListItem
908
978
  <ListItem
@@ -1773,7 +1843,14 @@ const [tab, setTab] = useState('profile')
1773
1843
 
1774
1844
  **AccordionItem type:**
1775
1845
  ```ts
1776
- { value: string; trigger: string; content: ReactNode }
1846
+ {
1847
+ value: string
1848
+ trigger: string
1849
+ content: ReactNode
1850
+ iconName?: string // Icon name from @expo/vector-icons
1851
+ icon?: ReactNode // Custom icon node
1852
+ iconColor?: string // Override icon color (defaults to foregroundMuted)
1853
+ }
1777
1854
  ```
1778
1855
 
1779
1856
  **Animation:** `react-native-reanimated` `withTiming` (220ms) for height and chevron rotation (180°). Press scale uses `withSpring`. Runs on UI thread at 60fps.
@@ -1787,11 +1864,13 @@ const [tab, setTab] = useState('profile')
1787
1864
  {
1788
1865
  value: 'q1',
1789
1866
  trigger: 'What payment methods do you accept?',
1867
+ iconName: 'credit-card',
1790
1868
  content: <Text variant="body-sm">We accept Visa, Mastercard, and bank transfers.</Text>,
1791
1869
  },
1792
1870
  {
1793
1871
  value: 'q2',
1794
1872
  trigger: 'Can I cancel my booking?',
1873
+ iconName: 'x-circle',
1795
1874
  content: <Text variant="body-sm">Yes — free cancellation within 48 hours of booking.</Text>,
1796
1875
  },
1797
1876
  ]}
@@ -1828,11 +1907,16 @@ Add `react-native-worklets/plugin` (not `react-native-reanimated/plugin`) to `ba
1828
1907
  | description | `string` | — | Supporting text below title |
1829
1908
  | children | `ReactNode` | — | Sheet content |
1830
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` |
1831
1914
 
1832
1915
  **Features:**
1833
1916
  - `enableDynamicSizing` — height auto-fits content, no `snapPoints` needed
1834
1917
  - `enablePanDownToClose` — swipe down to dismiss
1835
1918
  - 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
1836
1920
 
1837
1921
  **Styling:** `RADIUS.lg = 20px` top corners, 16px horizontal + 20px bottom padding.
1838
1922
 
@@ -1855,6 +1939,18 @@ const [open, setOpen] = useState(false)
1855
1939
  />
1856
1940
  </Sheet>
1857
1941
 
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
+ ))}
1947
+ </Sheet>
1948
+
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} />)}
1952
+ </Sheet>
1953
+
1858
1954
  // Filter sheet
1859
1955
  <Sheet open={filterOpen} onClose={() => setFilterOpen(false)} title="Filters">
1860
1956
  <View style={{ gap: SPACING.lg }}>
@@ -2165,7 +2261,7 @@ toast({ title: 'Upload complete', variant: 'success' })
2165
2261
 
2166
2262
  | Prop | Type | Default | Notes |
2167
2263
  |------|------|---------|-------|
2168
- | options | `ChipOption[]` | required | `{ label: string, value: string \| number }` |
2264
+ | options | `ChipOption[]` | required | `{ label, value, iconName?, iconColor?, disabled? }` |
2169
2265
  | value | `string \| number \| (string \| number)[]` | — | Selected value(s) |
2170
2266
  | onValueChange | `(value: ...) => void` | — | Returns single value or array depending on `multiSelect` |
2171
2267
  | multiSelect | `boolean` | `false` | Allow multiple chips selected simultaneously |
@@ -2310,27 +2406,96 @@ const [categories, setCategories] = useState<number[]>([1, 3])
2310
2406
  |------|------|---------|-------|
2311
2407
  | label | `string` | required | Caption label on the left |
2312
2408
  | value | `string \| ReactNode` | required | Value on the right. Strings auto-styled; pass `ReactNode` for custom content |
2409
+ | iconName | `string` | — | Icon name from `@expo/vector-icons` rendered left of label |
2410
+ | iconColor | `string` | — | Override icon color. Defaults to `foregroundMuted` |
2313
2411
  | style | `ViewStyle` | — | — |
2314
2412
 
2315
2413
  **Styling:** Row layout, `justifyContent: 'space-between'`. Label: 13pt / Regular / `foregroundMuted`. Value: 14pt / Medium / `foreground`. 12px gap.
2316
2414
 
2317
2415
  **Examples:**
2318
2416
  ```tsx
2319
- <LabelValue label="Date" value="12 mar 2025" />
2320
- <LabelValue label="Category" value="Food & drink" />
2321
- <LabelValue label="Status" value={<Badge label="Pending" variant="warningOutline" size="sm" />} />
2322
- <LabelValue label="Amount" value="$45.000" />
2417
+ <LabelValue label="Date" value="12 mar 2025" iconName="calendar" />
2418
+ <LabelValue label="Category" value="Food & drink" iconName="tag" />
2419
+ <LabelValue label="Status" value={<Badge label="Pending" variant="warningOutline" size="sm" />} iconName="clock" />
2420
+ <LabelValue label="Amount" value="$45.000" iconName="dollar-sign" />
2323
2421
 
2324
2422
  // Transaction detail
2325
2423
  <View style={{ gap: SPACING.sm }}>
2326
- <LabelValue label="Merchant" value={transaction.merchant} />
2327
- <LabelValue label="Date" value={formatDate(transaction.date)} />
2328
- <LabelValue label="Category" value={transaction.category} />
2424
+ <LabelValue label="Merchant" value={transaction.merchant} iconName="shopping-bag" />
2425
+ <LabelValue label="Date" value={formatDate(transaction.date)} iconName="calendar" />
2426
+ <LabelValue label="Category" value={transaction.category} iconName="tag" />
2427
+ <Separator />
2428
+ <LabelValue label="Amount" value={`$${transaction.amount}`} iconName="dollar-sign" />
2429
+ </View>
2430
+ ```
2431
+
2432
+ ---
2433
+
2434
+ ### DetailRow
2435
+
2436
+ **Import:** `import { DetailRow } from '@retray-dev/ui-kit'`
2437
+
2438
+ **When to use:** "Label ··· Value" ticket/receipt rows — financial summaries, split bills, transaction breakdowns. Dotted separator fills the space between label and value.
2439
+
2440
+ | Prop | Type | Default | Notes |
2441
+ |------|------|---------|-------|
2442
+ | label | `string \| ReactNode` | required | Left side. Strings auto-styled in `foregroundMuted` caption |
2443
+ | value | `string` | required | Right side value text |
2444
+ | separator | `'dotted' \| 'solid' \| 'dashed' \| 'none'` | `'dotted'` | Fill line between label and value |
2445
+ | labelWeight | `'normal' \| 'medium' \| 'semibold' \| 'bold'` | `'normal'` | Font weight of label text |
2446
+ | valueColor | `string` | — | Override value text color (hex or theme token value) |
2447
+ | leftIcon | `ReactNode` | — | Node rendered left of label (e.g. `Avatar`, `Icon`) |
2448
+ | leftIconName | `string` | — | Icon name from `@expo/vector-icons` rendered left of label. Takes precedence over `leftIcon` |
2449
+ | leftIconColor | `string` | — | Override left icon color. Defaults to `foregroundMuted` |
2450
+ | rightIconName | `string` | — | Icon name from `@expo/vector-icons` rendered right of value |
2451
+ | rightIconColor | `string` | — | Override right icon color. Defaults to `foregroundMuted` |
2452
+ | style | `ViewStyle` | — | Row container style |
2453
+ | labelStyle | `TextStyle` | — | Label text style override |
2454
+ | valueStyle | `TextStyle` | — | Value text style override |
2455
+
2456
+ **Examples:**
2457
+ ```tsx
2458
+ // Basic
2459
+ <DetailRow label="Total" value="$50.000" />
2460
+
2461
+ // With icons
2462
+ <DetailRow label="Food" value="$380.000" leftIconName="coffee" />
2463
+ <DetailRow label="Transport" value="$95.000" leftIconName="truck" />
2464
+ <DetailRow label="Total" value="$1.250.000" labelWeight="bold" rightIconName="trending-up" />
2465
+
2466
+ // Dotted separator with bold total
2467
+ <DetailRow label="Total" value="$50.000" labelWeight="bold" />
2468
+
2469
+ // With avatar left icon
2470
+ <DetailRow
2471
+ label="Juliana"
2472
+ value="$25.000"
2473
+ leftIcon={<Avatar src={avatarUrl} size={20} />}
2474
+ />
2475
+
2476
+ // Destructive value
2477
+ <DetailRow label="Deuda" value="$150.000" valueColor={colors.destructive} />
2478
+
2479
+ // No separator
2480
+ <DetailRow label="Subtotal" value="$45.000" separator="none" />
2481
+
2482
+ // Full receipt block
2483
+ <View style={{ gap: SPACING.xs }}>
2484
+ <DetailRow label="Juliana" value="$25.000" leftIcon={<Avatar fallback="J" size={20} />} />
2485
+ <DetailRow label="Julián" value="$25.000" leftIcon={<Avatar fallback="JC" size={20} />} />
2329
2486
  <Separator />
2330
- <LabelValue label="Amount" value={`$${transaction.amount}`} />
2487
+ <DetailRow label="Total" value="$50.000" labelWeight="bold" />
2331
2488
  </View>
2332
2489
  ```
2333
2490
 
2491
+ **Output:**
2492
+ ```
2493
+ Juliana ············ $25.000
2494
+ Julián ············· $25.000
2495
+ ─────────────────────────────
2496
+ Total ·············· $50.000
2497
+ ```
2498
+
2334
2499
  ---
2335
2500
 
2336
2501
  ### MonthPicker
@@ -2343,6 +2508,8 @@ const [categories, setCategories] = useState<number[]>([1, 3])
2343
2508
  |------|------|---------|-------|
2344
2509
  | value | `MonthPickerValue` | required | `{ month: 1–12, year: number }` |
2345
2510
  | onChange | `(value: MonthPickerValue) => void` | required | Called on navigation |
2511
+ | locale | `string` | `'en'` | BCP 47 locale. Built-in: `'en'`, `'es'`, `'pt'`, `'fr'`. Other locales: use `formatLabel` |
2512
+ | formatLabel | `(value: MonthPickerValue) => string` | — | Custom label formatter. Takes precedence over `locale` |
2346
2513
  | style | `ViewStyle` | — | — |
2347
2514
 
2348
2515
  **MonthPickerValue type:**
@@ -2366,6 +2533,16 @@ const [period, setPeriod] = useState({
2366
2533
  <MonthPicker value={period} onChange={setPeriod} />
2367
2534
  // Displays: "May 2026"
2368
2535
 
2536
+ <MonthPicker value={period} onChange={setPeriod} locale="es" />
2537
+ // Displays: "mayo 2026"
2538
+
2539
+ <MonthPicker
2540
+ value={period}
2541
+ onChange={setPeriod}
2542
+ formatLabel={({ month, year }) => `${month}/${year}`}
2543
+ />
2544
+ // Displays: "5/2026"
2545
+
2369
2546
  // In a transactions header
2370
2547
  <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>
2371
2548
  <Text variant="display-md">Transactions</Text>
@@ -2513,6 +2690,43 @@ All components with icon slots accept `iconName` — auto-resolved size and colo
2513
2690
 
2514
2691
  ---
2515
2692
 
2693
+ ### `getResponsiveFontSize` utility
2694
+
2695
+ Calculates a stepped-down font size for long numeric strings (currency amounts, large numbers) that would overflow at full size.
2696
+
2697
+ ```tsx
2698
+ import { getResponsiveFontSize } from '@retray-dev/ui-kit'
2699
+
2700
+ // Default steps: ≤10 chars → max, ≤12 → max-4, ≤14 → max-6, >14 → max-8
2701
+ const fontSize = getResponsiveFontSize(formatCOP(amount), 48)
2702
+
2703
+ <Text style={{ fontSize }} numberOfLines={1} adjustsFontSizeToFit>
2704
+ {formatCOP(amount)}
2705
+ </Text>
2706
+ ```
2707
+
2708
+ **Signature:**
2709
+ ```ts
2710
+ getResponsiveFontSize(
2711
+ text: string,
2712
+ maxSize: number,
2713
+ steps?: { maxLen: number; subtract: number }[]
2714
+ ): number
2715
+ ```
2716
+
2717
+ Custom steps example:
2718
+ ```tsx
2719
+ getResponsiveFontSize(text, 48, [
2720
+ { maxLen: 8, subtract: 0 },
2721
+ { maxLen: 11, subtract: 6 },
2722
+ { maxLen: 14, subtract: 10 },
2723
+ ])
2724
+ ```
2725
+
2726
+ **Alternative:** Use `<CurrencyDisplay autoScale maxFontSize={48} />` for currency values — no manual sizing needed.
2727
+
2728
+ ---
2729
+
2516
2730
  ## Hover Support (Web)
2517
2731
 
2518
2732
  ```tsx
@@ -2577,3 +2791,29 @@ All `Text` and `TextInput` components have `allowFontScaling={true}` — respect
2577
2791
 
2578
2792
  ### Scaling utilities (internal)
2579
2793
  Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale, `ms()` moderate scale relative to 350×680 base.
2794
+
2795
+ ---
2796
+
2797
+ ## Full Composition Examples
2798
+
2799
+ The package includes **EXAMPLES.md** with complete, working code for 3 real-world app screens:
2800
+
2801
+ 1. **Finance Dashboard** — MonthPicker, CurrencyDisplay, Progress, CategoryStrip, ListItem, DetailRow
2802
+ 2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
2803
+ 3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
2804
+
2805
+ **For AI agents:** Import EXAMPLES.md from the package for full source code and patterns:
2806
+
2807
+ ```markdown
2808
+ ## UI Kit Composition Examples
2809
+ @./node_modules/@retray-dev/ui-kit/EXAMPLES.md
2810
+ ```
2811
+
2812
+ Each example includes:
2813
+ - Complete component code (copy-paste ready)
2814
+ - State management setup
2815
+ - Proper imports and theme usage
2816
+ - Common patterns (spacing, colors, navigation, toast feedback)
2817
+ - StyleSheet definitions
2818
+
2819
+ **For human developers:** Examples are also live in the `example/` app. Clone the repo, run `pnpm install && pnpm build`, then `cd example && pnpm start` to see them in action. Access via "🖥 App Screen Compositions" accordion.