@retray-dev/ui-kit 13.0.0 → 13.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/CHANGELOG.md +680 -0
- package/CONSUMER.md +26 -9
- package/README.md +11 -12
- package/{COMPONENTS.md → SKILL.md} +515 -815
- package/dist/Accordion.d.mts +8 -6
- package/dist/Accordion.d.ts +8 -6
- package/dist/Accordion.js +40 -40
- package/dist/Accordion.mjs +4 -5
- package/dist/AlertBanner.d.mts +3 -3
- package/dist/AlertBanner.d.ts +3 -3
- package/dist/AlertBanner.js +7 -13
- package/dist/AlertBanner.mjs +4 -5
- package/dist/AppHeader.d.mts +8 -5
- package/dist/AppHeader.d.ts +8 -5
- package/dist/AppHeader.js +44 -31
- package/dist/AppHeader.mjs +6 -7
- package/dist/Avatar.d.mts +4 -4
- package/dist/Avatar.d.ts +4 -4
- package/dist/Avatar.mjs +2 -3
- package/dist/Badge.d.mts +5 -5
- package/dist/Badge.d.ts +5 -5
- package/dist/Badge.js +7 -13
- package/dist/Badge.mjs +3 -4
- package/dist/Button.d.mts +5 -5
- package/dist/Button.d.ts +5 -5
- package/dist/Button.js +31 -29
- package/dist/Button.mjs +5 -6
- package/dist/ButtonGroup.d.mts +3 -3
- package/dist/ButtonGroup.d.ts +3 -3
- package/dist/ButtonGroup.mjs +0 -1
- package/dist/Card.d.mts +13 -13
- package/dist/Card.d.ts +13 -13
- package/dist/Card.js +23 -14
- package/dist/Card.mjs +4 -5
- package/dist/CategoryStrip.d.mts +3 -3
- package/dist/CategoryStrip.d.ts +3 -3
- package/dist/CategoryStrip.js +30 -27
- package/dist/CategoryStrip.mjs +5 -6
- package/dist/Checkbox.d.mts +3 -2
- package/dist/Checkbox.d.ts +3 -2
- package/dist/Checkbox.js +26 -15
- package/dist/Checkbox.mjs +3 -4
- package/dist/Chip.d.mts +5 -5
- package/dist/Chip.d.ts +5 -5
- package/dist/Chip.js +30 -27
- package/dist/Chip.mjs +5 -6
- package/dist/ConfirmDialog.d.mts +3 -2
- package/dist/ConfirmDialog.d.ts +3 -2
- package/dist/ConfirmDialog.js +67 -49
- package/dist/ConfirmDialog.mjs +7 -7
- package/dist/CurrencyDisplay.d.mts +3 -3
- package/dist/CurrencyDisplay.d.ts +3 -3
- package/dist/CurrencyDisplay.mjs +2 -3
- package/dist/CurrencyInput.d.mts +2 -2
- package/dist/CurrencyInput.d.ts +2 -2
- package/dist/CurrencyInput.js +7 -13
- package/dist/CurrencyInput.mjs +4 -5
- package/dist/DetailRow.d.mts +6 -6
- package/dist/DetailRow.d.ts +6 -6
- package/dist/DetailRow.js +7 -13
- package/dist/DetailRow.mjs +3 -4
- package/dist/EmptyState.d.mts +4 -4
- package/dist/EmptyState.d.ts +4 -4
- package/dist/EmptyState.js +31 -29
- package/dist/EmptyState.mjs +6 -7
- package/dist/ErrorBoundary.d.mts +9 -7
- package/dist/ErrorBoundary.d.ts +9 -7
- package/dist/ErrorBoundary.js +33 -29
- package/dist/ErrorBoundary.mjs +5 -6
- package/dist/Form.d.mts +9 -9
- package/dist/Form.d.ts +9 -9
- package/dist/Form.mjs +2 -3
- package/dist/HolographicCard.d.mts +2 -2
- package/dist/HolographicCard.d.ts +2 -2
- package/dist/HolographicCard.js +23 -14
- package/dist/HolographicCard.mjs +2 -3
- package/dist/IconButton.d.mts +4 -4
- package/dist/IconButton.d.ts +4 -4
- package/dist/IconButton.js +30 -27
- package/dist/IconButton.mjs +4 -5
- package/dist/IconPicker.d.mts +2 -2
- package/dist/IconPicker.d.ts +2 -2
- package/dist/IconPicker.js +40 -45
- package/dist/IconPicker.mjs +6 -7
- package/dist/Image.d.mts +18 -0
- package/dist/Image.d.ts +18 -0
- package/dist/Image.js +53 -0
- package/dist/Image.mjs +2 -0
- package/dist/ImageUpload.d.mts +2 -4
- package/dist/ImageUpload.d.ts +2 -4
- package/dist/ImageUpload.js +50 -40
- package/dist/ImageUpload.mjs +5 -6
- package/dist/ImageViewer.d.mts +2 -2
- package/dist/ImageViewer.d.ts +2 -2
- package/dist/ImageViewer.js +31 -28
- package/dist/ImageViewer.mjs +6 -7
- package/dist/Input.d.mts +4 -4
- package/dist/Input.d.ts +4 -4
- package/dist/Input.js +7 -13
- package/dist/Input.mjs +3 -4
- package/dist/ItemGroup.d.mts +23 -0
- package/dist/ItemGroup.d.ts +23 -0
- package/dist/{ListGroup.js → ItemGroup.js} +11 -13
- package/dist/ItemGroup.mjs +4 -0
- package/dist/LabelValue.d.mts +4 -4
- package/dist/LabelValue.d.ts +4 -4
- package/dist/LabelValue.js +7 -13
- package/dist/LabelValue.mjs +3 -4
- package/dist/ListItem.d.mts +7 -6
- package/dist/ListItem.d.ts +7 -6
- package/dist/ListItem.js +33 -28
- package/dist/ListItem.mjs +5 -6
- package/dist/MediaCard.d.mts +6 -6
- package/dist/MediaCard.d.ts +6 -6
- package/dist/MediaCard.js +30 -27
- package/dist/MediaCard.mjs +5 -6
- package/dist/MenuItem.d.mts +6 -5
- package/dist/MenuItem.d.ts +6 -5
- package/dist/MenuItem.js +33 -28
- package/dist/MenuItem.mjs +5 -6
- package/dist/MonthPicker.d.mts +2 -2
- package/dist/MonthPicker.d.ts +2 -2
- package/dist/MonthPicker.js +23 -14
- package/dist/MonthPicker.mjs +3 -4
- package/dist/NumberStepper.d.mts +4 -3
- package/dist/NumberStepper.d.ts +4 -3
- package/dist/NumberStepper.js +34 -28
- package/dist/NumberStepper.mjs +5 -6
- package/dist/PagerDots.d.mts +2 -2
- package/dist/PagerDots.d.ts +2 -2
- package/dist/PagerDots.js +30 -27
- package/dist/PagerDots.mjs +4 -5
- package/dist/Pressable.d.mts +3 -27
- package/dist/Pressable.d.ts +3 -27
- package/dist/Pressable.js +23 -14
- package/dist/Pressable.mjs +2 -3
- package/dist/PricingCard.d.mts +2 -2
- package/dist/PricingCard.d.ts +2 -2
- package/dist/PricingCard.js +31 -29
- package/dist/PricingCard.mjs +7 -8
- package/dist/Progress.d.mts +2 -2
- package/dist/Progress.d.ts +2 -2
- package/dist/Progress.mjs +2 -3
- package/dist/RadioGroup.d.mts +2 -2
- package/dist/RadioGroup.d.ts +2 -2
- package/dist/RadioGroup.js +23 -14
- package/dist/RadioGroup.mjs +3 -4
- package/dist/RetrayProvider.d.mts +1 -1
- package/dist/RetrayProvider.d.ts +1 -1
- package/dist/RetrayProvider.js +14 -34
- package/dist/RetrayProvider.mjs +3 -4
- package/dist/ScreenContainer.d.mts +24 -0
- package/dist/ScreenContainer.d.ts +24 -0
- package/dist/ScreenContainer.js +85 -0
- package/dist/ScreenContainer.mjs +3 -0
- package/dist/Select.d.mts +3 -2
- package/dist/Select.d.ts +3 -2
- package/dist/Select.js +41 -46
- package/dist/Select.mjs +3 -4
- package/dist/SelectableCard.d.mts +5 -5
- package/dist/SelectableCard.d.ts +5 -5
- package/dist/SelectableCard.js +30 -27
- package/dist/SelectableCard.mjs +5 -6
- package/dist/SelectableGrid.d.mts +5 -4
- package/dist/SelectableGrid.d.ts +5 -4
- package/dist/SelectableGrid.js +80 -45
- package/dist/SelectableGrid.mjs +5 -6
- package/dist/Separator.d.mts +4 -2
- package/dist/Separator.d.ts +4 -2
- package/dist/Separator.js +29 -1
- package/dist/Separator.mjs +3 -3
- package/dist/Sheet.d.mts +11 -11
- package/dist/Sheet.d.ts +11 -11
- package/dist/Sheet.js +62 -34
- package/dist/Sheet.mjs +4 -4
- package/dist/SheetSelect.d.mts +2 -2
- package/dist/SheetSelect.d.ts +2 -2
- package/dist/SheetSelect.js +30 -27
- package/dist/SheetSelect.mjs +5 -6
- package/dist/Skeleton.d.mts +5 -5
- package/dist/Skeleton.d.ts +5 -5
- package/dist/Skeleton.mjs +3 -4
- package/dist/Slider.d.mts +3 -2
- package/dist/Slider.d.ts +3 -2
- package/dist/Slider.js +25 -14
- package/dist/Slider.mjs +3 -4
- package/dist/Spinner.d.mts +2 -2
- package/dist/Spinner.d.ts +2 -2
- package/dist/Spinner.mjs +2 -3
- package/dist/Stats.d.mts +6 -6
- package/dist/Stats.d.ts +6 -6
- package/dist/Stats.js +30 -27
- package/dist/Stats.mjs +5 -6
- package/dist/Switch.d.mts +3 -2
- package/dist/Switch.d.ts +3 -2
- package/dist/Switch.js +25 -15
- package/dist/Switch.mjs +3 -4
- package/dist/TabBar.d.mts +3 -3
- package/dist/TabBar.d.ts +3 -3
- package/dist/TabBar.js +30 -27
- package/dist/TabBar.mjs +4 -5
- package/dist/Tabs.d.mts +13 -13
- package/dist/Tabs.d.ts +13 -13
- package/dist/Tabs.js +23 -14
- package/dist/Tabs.mjs +3 -4
- package/dist/Text.d.mts +4 -4
- package/dist/Text.d.ts +4 -4
- package/dist/Text.js +20 -2
- package/dist/Text.mjs +3 -4
- package/dist/Textarea.d.mts +3 -3
- package/dist/Textarea.d.ts +3 -3
- package/dist/Textarea.js +7 -13
- package/dist/Textarea.mjs +3 -4
- package/dist/Toast.d.mts +15 -13
- package/dist/Toast.d.ts +15 -13
- package/dist/Toast.mjs +2 -3
- package/dist/Toggle.d.mts +4 -4
- package/dist/Toggle.d.ts +4 -4
- package/dist/Toggle.js +30 -27
- package/dist/Toggle.mjs +4 -5
- package/dist/VirtualizedList.d.mts +28 -0
- package/dist/VirtualizedList.d.ts +28 -0
- package/dist/VirtualizedList.js +130 -0
- package/dist/VirtualizedList.mjs +3 -0
- package/dist/{chunk-MZ6WRTD2.mjs → chunk-24JTXQ2M.mjs} +7 -13
- package/dist/{chunk-OBV72JD4.mjs → chunk-2DDJ53DK.mjs} +9 -11
- package/dist/{chunk-6CR4S6W2.mjs → chunk-2J5OZOMX.mjs} +19 -8
- package/dist/{chunk-4NQFTHN3.mjs → chunk-3GE4UFV5.mjs} +2 -2
- package/dist/{chunk-KAGADD2O.mjs → chunk-3RIZCKRM.mjs} +2 -2
- package/dist/{chunk-DE25XTVQ.mjs → chunk-3VHFOSZR.mjs} +2 -2
- package/dist/{chunk-UOKFSFNJ.mjs → chunk-4PF4LKNT.mjs} +4 -2
- package/dist/{chunk-5MYNAAFE.mjs → chunk-5J7VKFSZ.mjs} +4 -4
- package/dist/{chunk-BTUW5LSG.mjs → chunk-5TNQ573V.mjs} +4 -3
- package/dist/{chunk-6QLBHUEG.mjs → chunk-6T2DVIQT.mjs} +7 -5
- package/dist/{chunk-L3YKPTJQ.mjs → chunk-7CE6PDCQ.mjs} +2 -2
- package/dist/{chunk-Y6YS33GM.mjs → chunk-AHFEAY6M.mjs} +4 -4
- package/dist/{chunk-4ZO5PTKF.mjs → chunk-AZRATPNP.mjs} +5 -3
- package/dist/{chunk-V2ZB2XNS.mjs → chunk-BGXOEFDM.mjs} +9 -22
- package/dist/{chunk-KC5QDYGZ.mjs → chunk-BMAAAJWN.mjs} +2 -2
- package/dist/{chunk-IJCMPVW5.mjs → chunk-BQMJQMWY.mjs} +2 -2
- package/dist/{chunk-E4EQSCKR.mjs → chunk-BTPCY4C7.mjs} +7 -5
- package/dist/chunk-BVJAYPAD.mjs +55 -0
- package/dist/{chunk-RA6SAAFE.mjs → chunk-BWLVX2SQ.mjs} +4 -4
- package/dist/{chunk-EROPDCB5.mjs → chunk-CCEM3HIJ.mjs} +30 -25
- package/dist/chunk-CTUFFKGS.mjs +30 -0
- package/dist/{chunk-EHGBHFMH.mjs → chunk-CYGYC7XT.mjs} +8 -4
- package/dist/{chunk-ESQDPO5E.mjs → chunk-DLAOTHHS.mjs} +7 -6
- package/dist/{chunk-QY3X2UYR.mjs → chunk-DYYPDQA2.mjs} +21 -7
- package/dist/{chunk-S44XWTTC.mjs → chunk-E4BJ5WXG.mjs} +3 -3
- package/dist/{chunk-HUSSF6TF.mjs → chunk-EQNCMDZC.mjs} +1 -1
- package/dist/{chunk-PI6RULJX.mjs → chunk-EQYTDFDD.mjs} +1 -1
- package/dist/{chunk-BULKGOIZ.mjs → chunk-FE26TPCI.mjs} +4 -4
- package/dist/{chunk-DBHSUUKU.mjs → chunk-FOUSI6JD.mjs} +1 -1
- package/dist/{chunk-KPTY7UYQ.mjs → chunk-GR7PKEKD.mjs} +1 -1
- package/dist/{chunk-RRKM4MKB.mjs → chunk-HLWGFBIF.mjs} +3 -3
- package/dist/chunk-HMKJGVXA.mjs +35 -0
- package/dist/{chunk-U6DEBYU5.mjs → chunk-IFGZUJFH.mjs} +3 -3
- package/dist/{chunk-2VIDP72N.mjs → chunk-K3V6OTVB.mjs} +1 -1
- package/dist/{chunk-K7TKID3V.mjs → chunk-K4YFTUMC.mjs} +3 -3
- package/dist/{chunk-NGEN2EES.mjs → chunk-MQAK2W6L.mjs} +14 -22
- package/dist/{chunk-CM2DG4MR.mjs → chunk-MSS3CD6F.mjs} +4 -4
- package/dist/{chunk-TETMEKZE.mjs → chunk-NQYS6RPX.mjs} +8 -5
- package/dist/{chunk-62BBSSUF.mjs → chunk-P5KC3RTG.mjs} +1 -1
- package/dist/{chunk-K3QX2M26.mjs → chunk-PPKCGCZ3.mjs} +5 -5
- package/dist/{chunk-ITG4JQM3.mjs → chunk-QEE3EQ3N.mjs} +2 -2
- package/dist/{chunk-URIH43IJ.mjs → chunk-RLPPRIJ7.mjs} +20 -34
- package/dist/{chunk-XCIG6HT2.mjs → chunk-S433IOQE.mjs} +2 -2
- package/dist/{chunk-IGU223UM.mjs → chunk-SWUZKVYO.mjs} +1 -1
- package/dist/{chunk-MP7GLMIR.mjs → chunk-T4KMKHTI.mjs} +55 -23
- package/dist/{chunk-2QOHHBJC.mjs → chunk-UBTP4NPP.mjs} +5 -21
- package/dist/{chunk-TMH263OK.mjs → chunk-UEA2VYGW.mjs} +3 -3
- package/dist/chunk-VISIOH33.mjs +37 -0
- package/dist/{chunk-SZEKQAOY.mjs → chunk-VSKBODEY.mjs} +1 -1
- package/dist/{chunk-FTTI6T5Q.mjs → chunk-W422TEH2.mjs} +3 -3
- package/dist/{chunk-WIPEDNSD.mjs → chunk-WD5LBXPR.mjs} +4 -4
- package/dist/chunk-WFNGSYS4.mjs +111 -0
- package/dist/chunk-WR6DCNAE.mjs +65 -0
- package/dist/{chunk-ERWJPVX7.mjs → chunk-XKBB2UZU.mjs} +2 -2
- package/dist/{chunk-CBIZLRYH.mjs → chunk-Y5TPAKOS.mjs} +14 -17
- package/dist/{chunk-AZV7KNJI.mjs → chunk-YKWIMVGU.mjs} +2 -2
- package/dist/{chunk-ZKDKKQCE.mjs → chunk-YOXSXHDE.mjs} +4 -4
- package/dist/{chunk-PGQ6FMXS.mjs → chunk-ZO5BRTCW.mjs} +2 -2
- package/dist/{chunk-KSSVIFYR.mjs → chunk-ZQGCQ7SA.mjs} +14 -34
- package/dist/{chunk-ZTPYUU5C.mjs → chunk-ZRUUUVOO.mjs} +3 -3
- package/dist/fonts.mjs +0 -2
- package/dist/{index-CY34hxPN.d.ts → index-CinAt5Uo.d.mts} +3 -3
- package/dist/{index-CY34hxPN.d.mts → index-CinAt5Uo.d.ts} +3 -3
- package/dist/index.d.mts +69 -19
- package/dist/index.d.ts +69 -19
- package/dist/index.js +1023 -839
- package/dist/index.mjs +76 -70
- package/package.json +15 -12
- package/src/components/Accordion/Accordion.tsx +12 -18
- package/src/components/AppHeader/AppHeader.tsx +33 -10
- package/src/components/Checkbox/Checkbox.tsx +3 -0
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +7 -21
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +5 -2
- package/src/components/Image/Image.tsx +50 -0
- package/src/components/Image/index.ts +2 -0
- package/src/components/ImageUpload/ImageUpload.tsx +34 -26
- package/src/components/{ListGroup/ListGroup.tsx → ItemGroup/ItemGroup.tsx} +15 -29
- package/src/components/ItemGroup/index.ts +2 -0
- package/src/components/ListGroup/index.tsx +20 -0
- package/src/components/ListItem/ListItem.tsx +3 -0
- package/src/components/MenuGroup/index.tsx +20 -0
- package/src/components/MenuItem/MenuItem.tsx +3 -0
- package/src/components/NumberStepper/NumberStepper.tsx +4 -0
- package/src/components/Pressable/Pressable.tsx +0 -24
- package/src/components/ScreenContainer/ScreenContainer.tsx +94 -0
- package/src/components/ScreenContainer/index.ts +2 -0
- package/src/components/Select/Select.tsx +25 -30
- package/src/components/SelectableGrid/SelectableGrid.tsx +51 -20
- package/src/components/Separator/Separator.tsx +35 -2
- package/src/components/Sheet/Sheet.tsx +3 -21
- package/src/components/Sheet/index.ts +2 -2
- package/src/components/Slider/Slider.tsx +3 -0
- package/src/components/Switch/Switch.tsx +3 -1
- package/src/components/Tabs/Tabs.tsx +9 -9
- package/src/components/Tabs/index.ts +1 -1
- package/src/components/Text/Text.tsx +7 -0
- package/src/components/VirtualizedList/VirtualizedList.tsx +154 -0
- package/src/components/VirtualizedList/index.ts +2 -0
- package/src/hooks/useConfirmDialog.ts +2 -11
- package/src/hooks/useSheetModal.ts +40 -0
- package/src/index.ts +5 -1
- package/src/theme/colors.ts +19 -57
- package/src/tokens.ts +21 -7
- package/src/utils/curatedIcons.ts +9 -18
- package/src/utils/haptics.ts +10 -21
- package/src/utils/icons.ts +7 -14
- package/dist/ListGroup.d.mts +0 -34
- package/dist/ListGroup.d.ts +0 -34
- package/dist/ListGroup.mjs +0 -5
- package/dist/MenuGroup.d.mts +0 -34
- package/dist/MenuGroup.d.ts +0 -34
- package/dist/MenuGroup.js +0 -106
- package/dist/MenuGroup.mjs +0 -5
- package/dist/chunk-ARONDO7M.mjs +0 -40
- package/dist/chunk-EW2FIDSM.mjs +0 -29
- package/dist/chunk-S2VGME7X.mjs +0 -82
- package/dist/chunk-Y6FXYEAI.mjs +0 -8
- package/src/components/ListGroup/index.ts +0 -1
- package/src/components/MenuGroup/MenuGroup.tsx +0 -145
- package/src/components/MenuGroup/index.ts +0 -1
|
@@ -1,836 +1,559 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
name: retray-ui-kit
|
|
3
|
+
description: >
|
|
4
|
+
Complete guide for @retray-dev/ui-kit — a React Native / Expo component
|
|
5
|
+
library (~57 components). Airbnb-inspired design, Sohne typography,
|
|
6
|
+
haptic-rich interactions, animated with pressto + react-native-ease.
|
|
7
|
+
Theme system, BottomSheet patterns, icon resolution, and conventions.
|
|
8
|
+
---
|
|
2
9
|
|
|
3
|
-
|
|
10
|
+
# @retray-dev/ui-kit — Agent Skill
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```
|
|
12
|
+
> **Recommended — copy to repo root (no dependency on node_modules existing):**
|
|
13
|
+
> ```bash
|
|
14
|
+
> cp node_modules/@retray-dev/ui-kit/SKILL.md SKILL.md
|
|
15
|
+
> cp node_modules/@retray-dev/ui-kit/CONSUMER.md CONSUMER.md
|
|
16
|
+
> cp node_modules/@retray-dev/ui-kit/EXAMPLES.md EXAMPLES.md
|
|
17
|
+
> ```
|
|
18
|
+
>
|
|
19
|
+
> Then add to `CLAUDE.md`:
|
|
20
|
+
> ```markdown
|
|
21
|
+
> ## UI Kit
|
|
22
|
+
> @./SKILL.md
|
|
23
|
+
> @./CONSUMER.md
|
|
24
|
+
> @./EXAMPLES.md
|
|
25
|
+
> ```
|
|
11
26
|
|
|
12
27
|
---
|
|
13
28
|
|
|
14
|
-
## Setup
|
|
15
|
-
|
|
16
|
-
### Recommended: `RetrayProvider` (one wrapper)
|
|
29
|
+
## Required Setup
|
|
17
30
|
|
|
18
|
-
|
|
31
|
+
### Providers
|
|
32
|
+
Use `RetrayProvider` (single wrapper) or manual tree. Order is mandatory:
|
|
19
33
|
|
|
20
|
-
```tsx
|
|
21
|
-
import { RetrayProvider } from '@retray-dev/ui-kit'
|
|
22
|
-
|
|
23
|
-
export default function App() {
|
|
24
|
-
const [fontsLoaded] = useFonts(SohneFonts)
|
|
25
|
-
if (!fontsLoaded) return null
|
|
26
|
-
return (
|
|
27
|
-
<RetrayProvider colorScheme="system">
|
|
28
|
-
{/* your app */}
|
|
29
|
-
</RetrayProvider>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
34
|
```
|
|
33
|
-
|
|
34
|
-
### Manual (equivalent) — if you need a custom tree
|
|
35
|
-
|
|
36
|
-
`RetrayProvider` is exactly this; the individual providers stay exported. Order is mandatory:
|
|
37
|
-
|
|
38
|
-
```tsx
|
|
39
|
-
import { SafeAreaProvider, initialWindowMetrics } from 'react-native-safe-area-context'
|
|
40
|
-
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
41
|
-
import { ThemeProvider, BottomSheetModalProvider, ToastProvider } from '@retray-dev/ui-kit'
|
|
42
|
-
|
|
43
|
-
export default function App() {
|
|
44
|
-
return (
|
|
45
|
-
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
|
|
46
|
-
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
47
|
-
<ThemeProvider colorScheme="system">
|
|
48
|
-
<BottomSheetModalProvider>
|
|
49
|
-
<ToastProvider>
|
|
50
|
-
{/* your app */}
|
|
51
|
-
</ToastProvider>
|
|
52
|
-
</BottomSheetModalProvider>
|
|
53
|
-
</ThemeProvider>
|
|
54
|
-
</GestureHandlerRootView>
|
|
55
|
-
</SafeAreaProvider>
|
|
56
|
-
)
|
|
57
|
-
}
|
|
35
|
+
SafeAreaProvider > GestureHandlerRootView > ThemeProvider > BottomSheetModalProvider > ToastProvider
|
|
58
36
|
```
|
|
59
37
|
|
|
60
|
-
|
|
61
|
-
- `SafeAreaProvider` must be outermost — required by `@gorhom/bottom-sheet`
|
|
62
|
-
- `initialMetrics={initialWindowMetrics}` is required on Android to avoid a "No safe area value available" crash on first render
|
|
63
|
-
- `GestureHandlerRootView` must wrap everything — required by `@gorhom/bottom-sheet`
|
|
64
|
-
- `BottomSheetModalProvider` must be inside `GestureHandlerRootView`
|
|
65
|
-
- `ToastProvider` wraps children and renders `Toaster` (from `sonner-native`) internally
|
|
66
|
-
|
|
67
|
-
### Peer dependencies
|
|
38
|
+
`initialMetrics={initialWindowMetrics}` required on Android.
|
|
68
39
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
pnpm add expo-font expo-haptics expo-linear-gradient react-native-safe-area-context @gorhom/bottom-sheet react-native-reanimated react-native-gesture-handler react-native-worklets @react-native-picker/picker @react-native-community/slider @expo/vector-icons react-native-size-matters react-native-svg react-native-screens sonner-native pressto react-native-ease
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
Then add the Worklets Babel plugin to `babel.config.js` (required by `@gorhom/bottom-sheet` and the pressable animations):
|
|
40
|
+
### Peer Dependencies (all required unless noted)
|
|
41
|
+
- **Hard:** `expo-haptics`, `expo-linear-gradient`, `expo-font`, `expo-image`, `react-native-reanimated`, `react-native-gesture-handler`, `react-native-worklets`, `react-native-safe-area-context`, `react-native-screens`, `react-native-svg`, `@gorhom/bottom-sheet`, `@react-native-picker/picker`, `@react-native-community/slider`, `@expo/vector-icons`, `react-native-size-matters`, `sonner-native`, `react-native-ease`, `pressto`
|
|
42
|
+
- **Optional:** `react-native-image-picker` (ImageUpload), `@shopify/react-native-skia` + `expo-sensors` (HolographicCard deep-import only)
|
|
76
43
|
|
|
44
|
+
### Babel Config
|
|
77
45
|
```js
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
presets: ['babel-preset-expo'],
|
|
82
|
-
plugins: ['react-native-worklets/plugin'], // NOT react-native-reanimated/plugin
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
| Peer | Required? | Used by | Notes |
|
|
88
|
-
|------|-----------|---------|-------|
|
|
89
|
-
| `pressto` | **Required** | Every interactive component | Press-scale animations (`src/utils/pressable.ts`). Static import via the barrel — omitting it crashes the module load, not just one component. |
|
|
90
|
-
| `sonner-native` | **Required** | `Toast` | Also pulls `react-native-svg` + `react-native-screens`. |
|
|
91
|
-
| `react-native-reanimated` (≥4.0) | **Required** | Animations, `Sheet`, pressables | Needs the `react-native-worklets/plugin` Babel plugin. |
|
|
92
|
-
| `react-native-gesture-handler` | **Required** | `Sheet`, pressables | Wrap app in `GestureHandlerRootView`. |
|
|
93
|
-
| `@gorhom/bottom-sheet` (≥5.2.0) | **Required** | `Sheet`, `ConfirmDialog` | 5.1.x crashes on Reanimated v4. |
|
|
94
|
-
| `expo-haptics` | **Required** | Haptic feedback (all interactions) | Web-safe wrapper, no-op on web. |
|
|
95
|
-
| `expo-font` | **Required** | All `Text` | Load `SohneFonts` at app root before rendering. |
|
|
96
|
-
| `expo-linear-gradient` | **Required** | `Skeleton` shimmer | |
|
|
97
|
-
| `@react-native-picker/picker` | **Required** | `Select` | |
|
|
98
|
-
| `@react-native-community/slider` | **Required** | `Slider` | |
|
|
99
|
-
| `@expo/vector-icons` | **Required** | Icons everywhere | |
|
|
100
|
-
| `react-native-size-matters` | **Required** | Responsive scaling | |
|
|
101
|
-
| `react-native-ease` | **Required** | `Checkbox`, `Chip`, `CategoryStrip`, `Switch`, `RadioGroup`, `Toggle` | Declarative `EaseView` animation layer. Static import in source — omitting it crashes the module load. `pnpm add react-native-ease` |
|
|
102
|
-
|
|
103
|
-
| `@shopify/react-native-skia` + `expo-sensors` | **Optional** | Deep-import `HolographicCard` only | `pnpm add @shopify/react-native-skia expo-sensors` |
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
### Troubleshooting: `react-native-screens` codegen on RN 0.83
|
|
108
|
-
|
|
109
|
-
Toast pulls `sonner-native`, which imports `react-native-screens` via its `src/*` path. On **React Native 0.83** that source trips a `@react-native/codegen` parse error (`CT.WithDefault` not parseable). It is a `react-native-screens` issue, not the kit — but since Toast surfaces it, add this resolver to your `metro.config.js` to force the compiled `lib/commonjs` build:
|
|
110
|
-
|
|
111
|
-
```js
|
|
112
|
-
// metro.config.js
|
|
113
|
-
const { getDefaultConfig } = require('expo/metro-config')
|
|
114
|
-
const config = getDefaultConfig(__dirname)
|
|
115
|
-
|
|
116
|
-
const defaultResolveRequest = config.resolver.resolveRequest
|
|
117
|
-
config.resolver.resolveRequest = (context, moduleName, platform) => {
|
|
118
|
-
// Force react-native-screens to its compiled output, bypassing the src codegen bug.
|
|
119
|
-
if (moduleName.startsWith('react-native-screens/src')) {
|
|
120
|
-
moduleName = moduleName.replace('react-native-screens/src', 'react-native-screens/lib/commonjs')
|
|
121
|
-
}
|
|
122
|
-
return (defaultResolveRequest ?? context.resolveRequest)(context, moduleName, platform)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
module.exports = config
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## Typography — Sohne (Required)
|
|
129
|
-
|
|
130
|
-
All components use **Sohne** as the font family. You **must** load it before rendering any UI kit component.
|
|
131
|
-
|
|
132
|
-
### How it works
|
|
133
|
-
|
|
134
|
-
1. When you install `@retray-dev/ui-kit`, the **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
|
|
135
|
-
2. You must define `SohneFonts` with static `require()` calls in your `App.tsx` (see boilerplate below)
|
|
136
|
-
3. Pass it to `expo-font`'s `useFonts()` hook at your app root
|
|
137
|
-
4. All library components reference fonts by family name (e.g., `fontFamily: 'Sohne-SemiBold'`)
|
|
138
|
-
|
|
139
|
-
### SohneFonts boilerplate — copy this into your App.tsx
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
import { useFonts } from 'expo-font'
|
|
143
|
-
|
|
144
|
-
// Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
|
|
145
|
-
const SohneFonts = {
|
|
146
|
-
'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
|
|
147
|
-
'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
|
|
148
|
-
'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
|
|
149
|
-
'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
|
|
150
|
-
'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
|
|
151
|
-
'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
|
|
152
|
-
'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
|
|
153
|
-
'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
|
|
154
|
-
'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
|
|
155
|
-
'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
|
|
156
|
-
'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
|
|
157
|
-
'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
|
|
158
|
-
'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
|
|
159
|
-
'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
|
|
160
|
-
'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
|
|
161
|
-
'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
|
|
162
|
-
'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
|
|
163
|
-
'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
|
|
164
|
-
'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
|
|
165
|
-
'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
|
|
166
|
-
'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
|
|
167
|
-
'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
|
|
168
|
-
'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
|
|
169
|
-
'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
|
|
170
|
-
'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
|
|
171
|
-
'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
|
|
172
|
-
'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
|
|
173
|
-
'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export default function App() {
|
|
177
|
-
const [fontsLoaded] = useFonts(SohneFonts)
|
|
178
|
-
if (!fontsLoaded) return null
|
|
179
|
-
return (
|
|
180
|
-
// ... your providers and app
|
|
181
|
-
)
|
|
182
|
-
}
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
### .gitignore recommendation
|
|
186
|
-
|
|
187
|
-
Fonts are copied to `assets/fonts/sohne/` on install. You can either:
|
|
188
|
-
- **Commit them** (no network needed during CI builds)
|
|
189
|
-
- **Ignore them** (re-copied on every `pnpm install`)
|
|
190
|
-
|
|
191
|
-
```gitignore
|
|
192
|
-
# Sohne fonts — copied by @retray-dev/ui-kit postinstall
|
|
193
|
-
# Either commit these or ignore them (re-copied on install)
|
|
194
|
-
assets/fonts/sohne/
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
**Included weights (28 files):**
|
|
198
|
-
- Sohne: `Sohne-ExtraLight`, `Sohne-Light`, `Sohne-Regular`, `Sohne-Medium`, `Sohne-SemiBold`, `Sohne-Bold`, `Sohne-ExtraBold` + italic variants
|
|
199
|
-
- SohneMono: `SohneMono-ExtraLight`, `SohneMono-Light`, `SohneMono-Regular`, `SohneMono-Medium`, `SohneMono-SemiBold`, `SohneMono-Bold`, `SohneMono-ExtraBold` + italic variants
|
|
200
|
-
|
|
201
|
-
Pair with `expo-splash-screen` in production:
|
|
202
|
-
```tsx
|
|
203
|
-
import * as SplashScreen from 'expo-splash-screen'
|
|
204
|
-
SplashScreen.preventAutoHideAsync()
|
|
205
|
-
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
if (fontsLoaded) SplashScreen.hideAsync()
|
|
208
|
-
}, [fontsLoaded])
|
|
46
|
+
plugins: ['react-native-worklets/plugin']
|
|
47
|
+
// NOT react-native-reanimated/plugin
|
|
209
48
|
```
|
|
210
49
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
## Theme System
|
|
214
|
-
|
|
215
|
-
### ThemeProvider Props
|
|
216
|
-
|
|
217
|
-
| Prop | Type | Default | Notes |
|
|
218
|
-
|------|------|---------|-------|
|
|
219
|
-
| colorScheme | `'light' \| 'dark' \| 'system'` | `'system'` | `'system'` auto-detects device setting and updates when it changes |
|
|
220
|
-
| theme | `{ light?: Partial<ThemeColors>, dark?: Partial<ThemeColors> }` | — | Override any subset of the 12 public tokens per scheme |
|
|
221
|
-
|
|
222
|
-
**Custom theme example:**
|
|
223
|
-
```tsx
|
|
224
|
-
const myTheme = {
|
|
225
|
-
light: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
226
|
-
dark: { primary: '#ff385c', primaryForeground: '#ffffff' },
|
|
227
|
-
}
|
|
228
|
-
<ThemeProvider theme={myTheme} colorScheme="system">
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
### useTheme Hook
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
import { useTheme } from '@retray-dev/ui-kit'
|
|
235
|
-
|
|
236
|
-
function MyComponent() {
|
|
237
|
-
const { colors, colorScheme } = useTheme()
|
|
238
|
-
return <View style={{ backgroundColor: colors.background }} />
|
|
239
|
-
}
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Returns `colors` (full `ResolvedColors` palette) and `colorScheme` (`'light' | 'dark'`).
|
|
50
|
+
### Fonts (Sohne)
|
|
51
|
+
Postinstall copies 28 `.otf` to `assets/fonts/sohne/`. Consumer defines static `require()` calls in App.tsx with `expo-font`'s `useFonts()`.
|
|
243
52
|
|
|
244
53
|
---
|
|
245
54
|
|
|
246
|
-
##
|
|
55
|
+
## Design System (Airbnb-inspired)
|
|
247
56
|
|
|
248
|
-
|
|
57
|
+
| Token | Rule |
|
|
58
|
+
|---|---|
|
|
59
|
+
| **Touch targets** | ≥44pt height on all interactive elements |
|
|
60
|
+
| **Spacing** | 8pt grid, 4pt micro-steps |
|
|
61
|
+
| **Radius** | sm=6, md=8, lg=12, xl=16 |
|
|
62
|
+
| **Colors** | 12 public tokens → `deriveColors()` computes 26 resolved tokens |
|
|
63
|
+
| **Typography** | Sohne, modest weights (500-600 display), `allowFontScaling={true}` everywhere |
|
|
64
|
+
| **Shadows** | Max 2 tiers. Flat baseline + single float tier |
|
|
65
|
+
| **Elevation** | Depth from rounded corners + surface separation, not shadows |
|
|
249
66
|
|
|
250
|
-
|
|
67
|
+
### Theme Tokens — 12 Public (consumer can override)
|
|
251
68
|
|
|
252
|
-
| Token | Light Default | Dark Default |
|
|
253
|
-
|
|
69
|
+
| Token | Light Default | Dark Default | Role |
|
|
70
|
+
|-------|--------------|--------------|------|
|
|
254
71
|
| `background` | `#ffffff` | `#0f0f0f` | Screen / page background |
|
|
255
|
-
| `foreground` | `#1a1a1a` | `#fafafa` | Primary text
|
|
256
|
-
| `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface
|
|
257
|
-
| `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states
|
|
258
|
-
| `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon
|
|
72
|
+
| `foreground` | `#1a1a1a` | `#fafafa` | Primary text |
|
|
73
|
+
| `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface |
|
|
74
|
+
| `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states) |
|
|
75
|
+
| `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon on primary |
|
|
259
76
|
| `border` | `#dddddd` | `#303030` | Borders, dividers, input outlines |
|
|
260
|
-
| `destructive` | `#c72828` | `#ef5350` | Error / danger / delete
|
|
261
|
-
| `destructiveForeground` | `#ffffff` | `#ffffff` | Text
|
|
262
|
-
| `success` | `#1a7a45` | `#2e7d52` | Success / confirmation
|
|
263
|
-
| `successForeground` | `#ffffff` | `#ffffff` | Text
|
|
264
|
-
| `warning` | `#9a5200` | `#f5a623` | Warning / caution
|
|
265
|
-
| `warningForeground` | `#ffffff` | `#0f0f0f` | Text
|
|
266
|
-
| `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop
|
|
267
|
-
| `accent` *(optional)* | `#d4561d` | `#e87645` |
|
|
268
|
-
| `accentForeground` *(optional)* |
|
|
269
|
-
|
|
270
|
-
### Derived Tokens
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
|
275
|
-
|
|
276
|
-
| `
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
| `
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `destructiveBorder` | `destructive` @ 30% | Alert banner border, badge outline |
|
|
283
|
-
| `successTint` | `success` blended to bg | Success banner background |
|
|
77
|
+
| `destructive` | `#c72828` | `#ef5350` | Error / danger / delete |
|
|
78
|
+
| `destructiveForeground` | `#ffffff` | `#ffffff` | Text on destructive |
|
|
79
|
+
| `success` | `#1a7a45` | `#2e7d52` | Success / confirmation |
|
|
80
|
+
| `successForeground` | `#ffffff` | `#ffffff` | Text on success |
|
|
81
|
+
| `warning` | `#9a5200` | `#f5a623` | Warning / caution |
|
|
82
|
+
| `warningForeground` | `#ffffff` | `#0f0f0f` | Text on warning |
|
|
83
|
+
| `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop behind sheets |
|
|
84
|
+
| `accent` *(optional)* | `#d4561d` | `#e87645` | Brand accent (falls back to `primary`) |
|
|
85
|
+
| `accentForeground` *(optional)* | `primaryForeground` | `primaryForeground` | Text on accent |
|
|
86
|
+
|
|
87
|
+
### Derived Tokens — 26 ResolvedColors (read-only via `useTheme().colors`)
|
|
88
|
+
|
|
89
|
+
| Token | Source | Purpose |
|
|
90
|
+
|-------|--------|---------|
|
|
91
|
+
| `foregroundSubtle` | `foreground` @ ~70% | Body text, subtitles |
|
|
92
|
+
| `foregroundMuted` | `foreground` @ ~62% | Captions, placeholders (WCAG AA 4.5:1) |
|
|
93
|
+
| `surface` | `background` off-canvas | Chip backgrounds, input fills |
|
|
94
|
+
| `surfaceStrong` | `background` stronger offset | Pressed/hover states |
|
|
95
|
+
| `skeleton` | `background` @ ±10% | Skeleton placeholder |
|
|
96
|
+
| `destructiveTint` | `destructive` to bg | Alert banner bg |
|
|
97
|
+
| `destructiveBorder` | `destructive` @ 30% | Alert banner border |
|
|
98
|
+
| `successTint` | `success` to bg | Success banner bg |
|
|
284
99
|
| `successBorder` | `success` @ 30% | Success banner border |
|
|
285
|
-
| `warningTint` | `warning`
|
|
100
|
+
| `warningTint` | `warning` to bg | Warning banner bg |
|
|
286
101
|
| `warningBorder` | `warning` @ 30% | Warning banner border |
|
|
287
|
-
| `ring` | `= primary` | Focus ring
|
|
288
|
-
| `input` | `= border` | Input
|
|
289
|
-
| `separator` | `border` @ ±16-22% | Divider
|
|
290
|
-
| `overlay` | `overlay` token or `rgba(0,0,0,0.45)` |
|
|
291
|
-
| `accentResolved` | `accent` token or `= primary` | Resolved accent
|
|
292
|
-
| `accentForegroundResolved` | `accentForeground`
|
|
293
|
-
|
|
294
|
-
|
|
102
|
+
| `ring` | `= primary` | Focus ring |
|
|
103
|
+
| `input` | `= border` | Input border |
|
|
104
|
+
| `separator` | `border` @ ±16-22% | Divider line |
|
|
105
|
+
| `overlay` | `overlay` token or `rgba(0,0,0,0.45)` | Sheet backdrop |
|
|
106
|
+
| `accentResolved` | `accent` token or `= primary` | Resolved accent |
|
|
107
|
+
| `accentForegroundResolved` | `accentForeground` or `= primaryForeground` | Resolved text on accent |
|
|
108
|
+
|
|
109
|
+
### Color Utility
|
|
295
110
|
```tsx
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
// Text hierarchy
|
|
299
|
-
<Text style={{ color: colors.foreground }}>Primary text</Text>
|
|
300
|
-
<Text style={{ color: colors.foregroundSubtle }}>Secondary text</Text>
|
|
301
|
-
<Text style={{ color: colors.foregroundMuted }}>Caption / timestamp</Text>
|
|
302
|
-
|
|
303
|
-
// Surface fills (unselected chips, inactive backgrounds)
|
|
304
|
-
<View style={{ backgroundColor: colors.surface }}>
|
|
305
|
-
|
|
306
|
-
// Warning alert banner
|
|
307
|
-
<View style={{ backgroundColor: colors.warningTint, borderColor: colors.warningBorder }}>
|
|
308
|
-
<Text style={{ color: colors.warning }}>Warning message</Text>
|
|
111
|
+
import { withAlpha } from '@retray-dev/ui-kit'
|
|
112
|
+
// hex → rgba: withAlpha('#1a1a1a', 0.15) → 'rgba(26,26,26,0.15)'
|
|
309
113
|
```
|
|
310
114
|
|
|
311
|
-
###
|
|
312
|
-
|
|
313
|
-
```tsx
|
|
314
|
-
import { deriveColors } from '@retray-dev/ui-kit'
|
|
315
|
-
|
|
316
|
-
const resolved = deriveColors(myThemeColors, 'light')
|
|
317
|
-
// resolved contains all 26 ResolvedColors tokens
|
|
318
|
-
```
|
|
115
|
+
### Shape Language
|
|
116
|
+
Soft rounded rects everywhere. Buttons: `RADIUS.md=14px` (NOT pill). IconButton: `RADIUS.full` (circle). Inputs: 8px. Cards: 14px. Modals: 16px top corners only.
|
|
319
117
|
|
|
320
118
|
---
|
|
321
119
|
|
|
322
|
-
###
|
|
323
|
-
|
|
324
|
-
**Import:** `import { withAlpha } from '@retray-dev/ui-kit'`
|
|
325
|
-
|
|
326
|
-
Convert a hex color to rgba with the given alpha. Useful for semi-transparent backgrounds, borders, and overlays derived from theme colors.
|
|
327
|
-
|
|
328
|
-
```tsx
|
|
329
|
-
import { useTheme, withAlpha } from '@retray-dev/ui-kit'
|
|
330
|
-
|
|
331
|
-
const { colors } = useTheme()
|
|
332
|
-
|
|
333
|
-
<View style={{ backgroundColor: withAlpha(colors.primary, 0.15) }}>
|
|
334
|
-
<Text style={{ color: colors.primary }}>Tinted background</Text>
|
|
335
|
-
</View>
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
---
|
|
339
|
-
|
|
340
|
-
## Design Tokens
|
|
341
|
-
|
|
342
|
-
Static structural constants — no context or provider needed.
|
|
120
|
+
### Design Tokens — Static Constants, No Provider Needed
|
|
343
121
|
|
|
344
122
|
```ts
|
|
345
123
|
import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@retray-dev/ui-kit'
|
|
346
124
|
```
|
|
347
125
|
|
|
348
|
-
|
|
126
|
+
**SPACING — 8pt grid**
|
|
349
127
|
|
|
350
128
|
| Key | Value | Use |
|
|
351
129
|
|-----|-------|-----|
|
|
352
|
-
| `xxs` | 2 | Micro gaps
|
|
130
|
+
| `xxs` | 2 | Micro gaps |
|
|
353
131
|
| `xs` | 4 | Tight internal gaps |
|
|
354
132
|
| `sm` | 8 | Component internal padding |
|
|
355
133
|
| `md` | 12 | Medium gaps |
|
|
356
134
|
| `base` | 16 | Default content padding |
|
|
357
135
|
| `lg` | 24 | Section internal padding |
|
|
358
|
-
| `xl` | 32 | Between major
|
|
136
|
+
| `xl` | 32 | Between major blocks |
|
|
359
137
|
| `xxl` | 48 | Section padding |
|
|
360
|
-
| `section` | 64 | Major band separators
|
|
361
|
-
|
|
362
|
-
**Types:** `Spacing`, `SpacingKey`
|
|
363
|
-
|
|
364
|
-
```tsx
|
|
365
|
-
<View style={{ gap: SPACING.md, padding: SPACING.base }} />
|
|
366
|
-
```
|
|
138
|
+
| `section` | 64 | Major band separators |
|
|
367
139
|
|
|
368
|
-
|
|
140
|
+
**ICON_SIZES**
|
|
369
141
|
|
|
370
142
|
| Key | Value | Use |
|
|
371
143
|
|-----|-------|-----|
|
|
372
|
-
| `sm` | 14 | Badge icons
|
|
144
|
+
| `sm` | 14 | Badge icons |
|
|
373
145
|
| `md` | 18 | Standard component icons |
|
|
374
146
|
| `lg` | 22 | Larger inline icons |
|
|
375
147
|
| `xl` | 28 | Feature icons |
|
|
376
148
|
| `2xl` | 32 | Display icons |
|
|
377
149
|
|
|
378
|
-
**
|
|
379
|
-
|
|
380
|
-
### RADIUS — Airbnb shape language
|
|
150
|
+
**RADIUS — Airbnb shape language**
|
|
381
151
|
|
|
382
152
|
| Key | Value | Used in |
|
|
383
153
|
|-----|-------|---------|
|
|
384
|
-
| `none` | 0 |
|
|
154
|
+
| `none` | 0 | — |
|
|
385
155
|
| `xs` | 4 | Micro chips, tags |
|
|
386
156
|
| `sm` | 8 | Inputs, Textarea, Select, Checkbox |
|
|
387
|
-
| `md` | 14 | Cards, Buttons
|
|
157
|
+
| `md` | 14 | Cards, Buttons, MediaCard, AlertBanner, Toast |
|
|
388
158
|
| `lg` | 20 | Sheet top corners |
|
|
389
159
|
| `xl` | 32 | Large decorative elements |
|
|
390
160
|
| `full` | 9999 | IconButton (circle), CategoryStrip chips |
|
|
391
161
|
|
|
392
|
-
**
|
|
393
|
-
|
|
394
|
-
```tsx
|
|
395
|
-
<View style={{ borderRadius: RADIUS.md }} />
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
### SHADOWS — Cross-platform shadow presets
|
|
399
|
-
|
|
400
|
-
| Key | shadowOffset.height | shadowOpacity | shadowRadius | elevation | Use |
|
|
401
|
-
|-----|---------------------|---------------|--------------|-----------|-----|
|
|
402
|
-
| `sm` | 1 | 0.06 | 4 | 2 | Default card shadow |
|
|
403
|
-
| `md` | 2 | 0.10 | 8 | 5 | Hover float / elevated elements |
|
|
404
|
-
| `lg` | 6 | 0.16 | 16 | 10 | Modals, overlays |
|
|
405
|
-
| `xl` | 12 | 0.24 | 24 | 18 | High-elevation dialogs |
|
|
162
|
+
**SHADOWS — Cross-platform**
|
|
406
163
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
164
|
+
| Key | opacity | radius | elevation | Use |
|
|
165
|
+
|-----|---------|--------|-----------|-----|
|
|
166
|
+
| `sm` | 0.06 | 4 | 2 | Default card |
|
|
167
|
+
| `md` | 0.10 | 8 | 5 | Hover float |
|
|
168
|
+
| `lg` | 0.12 | 12 | 8 | Modals |
|
|
169
|
+
| `xl` | 0.18 | 24 | 16 | High-elevation |
|
|
412
170
|
|
|
413
|
-
|
|
171
|
+
**BREAKPOINTS**
|
|
414
172
|
|
|
415
173
|
| Key | Value | Use |
|
|
416
174
|
|-----|-------|-----|
|
|
417
175
|
| `wide` | 700 | Tablet / wide layout threshold |
|
|
418
176
|
|
|
419
|
-
|
|
420
|
-
const isWide = useWindowDimensions().width >= BREAKPOINTS.wide
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
### TYPOGRAPHY — 16 Airbnb-aligned variants
|
|
424
|
-
|
|
425
|
-
All components use these tokens for text styling. Import and use in custom components for consistency.
|
|
177
|
+
**TYPOGRAPHY — 17 variants**
|
|
426
178
|
|
|
427
|
-
| Key | Size | Weight |
|
|
428
|
-
|
|
429
|
-
| `display-hero` | 64 | 700 | 70 | -1 | Large number display
|
|
179
|
+
| Key | Size | Weight | lh | ls | Use |
|
|
180
|
+
|-----|------|--------|----|----|-----|
|
|
181
|
+
| `display-hero` | 64 | 700 | 70 | -1 | Large number display |
|
|
430
182
|
| `display-xl` | 28 | 700 | 40 | 0 | Screen titles, hero headings |
|
|
431
|
-
| `display-lg` |
|
|
432
|
-
| `display-md` |
|
|
433
|
-
| `display-sm` |
|
|
434
|
-
| `title-md` |
|
|
435
|
-
| `title-sm` |
|
|
183
|
+
| `display-lg` | 24 | 600 | 32 | -0.3 | Section headings |
|
|
184
|
+
| `display-md` | 20 | 600 | 28 | 0 | Card titles |
|
|
185
|
+
| `display-sm` | 18 | 600 | 24 | -0.18 | Sub-section headings |
|
|
186
|
+
| `title-md` | 17 | 600 | 22 | 0 | Row titles |
|
|
187
|
+
| `title-sm` | 15 | 500 | 20 | 0 | Secondary titles |
|
|
436
188
|
| `body-md` | 16 | 400 | 24 | 0 | Primary body copy |
|
|
437
|
-
| `body-sm` | 14 | 400 | 20 | 0 | Secondary body
|
|
438
|
-
| `caption` | 14 | 500 | 18 | 0 |
|
|
439
|
-
| `caption-sm` | 13 | 400 | 16 | 0 | Timestamps
|
|
440
|
-
| `badge-text` | 11 | 600 |
|
|
441
|
-
| `
|
|
442
|
-
| `
|
|
443
|
-
| `
|
|
444
|
-
| `button-
|
|
445
|
-
|
|
446
|
-
**Types:** `Typography`, `TypographyKey`
|
|
447
|
-
|
|
448
|
-
```tsx
|
|
449
|
-
import { TYPOGRAPHY } from '@retray-dev/ui-kit'
|
|
450
|
-
|
|
451
|
-
// Use in StyleSheet
|
|
452
|
-
const styles = StyleSheet.create({
|
|
453
|
-
heading: {
|
|
454
|
-
...TYPOGRAPHY['display-xl'],
|
|
455
|
-
color: colors.foreground,
|
|
456
|
-
},
|
|
457
|
-
})
|
|
458
|
-
```
|
|
189
|
+
| `body-sm` | 14 | 400 | 20 | 0 | Secondary body |
|
|
190
|
+
| `caption` | 14 | 500 | 18 | 0 | Input labels |
|
|
191
|
+
| `caption-sm` | 13 | 400 | 16 | 0 | Timestamps |
|
|
192
|
+
| `badge-text` | 11 | 600 | 14 | 0 | Badge labels |
|
|
193
|
+
| `badge-text-md` | 13 | 600 | 16 | 0 | Badge labels (md size) |
|
|
194
|
+
| `micro-label` | 12 | 700 | 16 | 0 | Overlines |
|
|
195
|
+
| `uppercase-tag` | 11 | 700 | 14 | 0.6 | Decorative tags (auto-uppercase) |
|
|
196
|
+
| `button-lg` | 16 | 500 | 22 | 0 | Button (md/lg) |
|
|
197
|
+
| `button-sm` | 14 | 500 | 18 | 0 | Button (sm) |
|
|
459
198
|
|
|
460
199
|
---
|
|
461
200
|
|
|
462
|
-
##
|
|
463
|
-
|
|
464
|
-
### Breaking Changes
|
|
465
|
-
|
|
466
|
-
**Button variants renamed:**
|
|
467
|
-
| v4 | v5 |
|
|
468
|
-
|----|-----|
|
|
469
|
-
| `outline` | `secondary` |
|
|
470
|
-
| `ghost` | `text` |
|
|
471
|
-
| `secondary` (filled gray) | removed — use Card/surface instead |
|
|
472
|
-
|
|
473
|
-
**Text variants replaced:**
|
|
474
|
-
| v4 | v5 equivalent |
|
|
475
|
-
|----|---------------|
|
|
476
|
-
| `h1` | `display-xl` |
|
|
477
|
-
| `h2` | `display-lg` or `display-md` |
|
|
478
|
-
| `h3` | `display-sm` |
|
|
479
|
-
| `body` | `body-md` |
|
|
480
|
-
| `label` | `title-sm` or `caption` |
|
|
481
|
-
| `caption` | `caption-sm` |
|
|
482
|
-
|
|
483
|
-
**Theme tokens changed:**
|
|
484
|
-
| v4 | v5 |
|
|
485
|
-
|----|-----|
|
|
486
|
-
| `secondary`, `secondaryForeground` | removed (use `surface` derived token) |
|
|
487
|
-
| `accent`, `accentForeground` | removed (use `surfaceStrong`) |
|
|
488
|
-
| `muted` | removed → `surface` (derived) |
|
|
489
|
-
| `mutedForeground` | removed → `foregroundSubtle` / `foregroundMuted` (derived) |
|
|
490
|
-
| Added | `warning`, `warningForeground` |
|
|
491
|
-
| Added (derived) | `warningTint`, `warningBorder` |
|
|
492
|
-
|
|
493
|
-
**IconButton variant:**
|
|
494
|
-
| v4 | v5 |
|
|
495
|
-
|----|-----|
|
|
496
|
-
| `ghost` | `text` |
|
|
201
|
+
## Icons
|
|
497
202
|
|
|
498
|
-
|
|
203
|
+
### Icon Component
|
|
204
|
+
```tsx
|
|
205
|
+
import { Icon } from '@retray-dev/ui-kit'
|
|
206
|
+
<Icon name="home" size={24} color="#000" />
|
|
207
|
+
// Returns null if name not found — no crash
|
|
208
|
+
```
|
|
499
209
|
|
|
500
|
-
|
|
210
|
+
### Resolution Order (first-match wins)
|
|
211
|
+
Feather > AntDesign > Entypo > FontAwesome5 > MaterialIcons > Ionicons
|
|
501
212
|
|
|
502
|
-
|
|
213
|
+
**Feather is first** — most names resolve there (outlined style). Names with `-outline` suffix resolve to Ionicons (no Feather collision).
|
|
503
214
|
|
|
504
|
-
|
|
215
|
+
### `iconName` props on components
|
|
216
|
+
| Component | Prop(s) | Color |
|
|
217
|
+
|-----------|---------|-------|
|
|
218
|
+
| `Button` | `iconName`, `iconColor` | Variant label |
|
|
219
|
+
| `IconButton` | `iconName`, `iconColor` | Variant foreground |
|
|
220
|
+
| `Input` | `prefixIcon`, `prefixIconColor`, `suffixIcon`, `suffixIconColor` | `foregroundMuted` |
|
|
221
|
+
| `ListItem` | `leftIcon`, `leftIconColor`, `rightIcon`, `rightIconColor` | `foreground` / `foregroundMuted` |
|
|
222
|
+
| `Badge` | `iconName`, `iconColor` | Variant foreground |
|
|
223
|
+
| `Toggle` | `iconName`, `iconColor`, `activeIconName`, `activeIconColor` | `foregroundMuted` / `primary` |
|
|
224
|
+
| `AlertBanner` | `iconName`, `iconColor` | Title color |
|
|
225
|
+
| `EmptyState` | `iconName`, `iconColor` | `foregroundMuted` |
|
|
226
|
+
| `MediaCard` | `actionIconName` | White |
|
|
227
|
+
| `AvatarGroup` | — | — (avatars use own colors) |
|
|
228
|
+
| `Chip` | `iconName` | Variant foreground |
|
|
229
|
+
| `DetailRow` | `leftIconName`, `leftIconColor`, `rightIconName`, `rightIconColor` | `foregroundMuted` |
|
|
230
|
+
| `MenuItem` | `iconName`, `iconColor` | `foreground` |
|
|
231
|
+
|
|
232
|
+
### curatedIcons.ts
|
|
233
|
+
8 categories, ~20 icons each, all Feather (outlined). Actions, comunicación, navegación, comida, negocios, perfil, multimedia, texto.
|
|
234
|
+
|
|
235
|
+
### Rules
|
|
236
|
+
- Prefer Feather (outlined) over FA5 solid (filled). FA5 `defaultStyle='regular'` but some names only exist in `solid` — avoid those.
|
|
237
|
+
- Icon lookup introspects `glyphMap` per call — no caching between renders.
|
|
505
238
|
|
|
239
|
+
### `getResponsiveFontSize` utility
|
|
506
240
|
```tsx
|
|
507
|
-
import {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
<MenuItem label="Notifications" iconName="bell" rightRender={<Switch value={on} onValueChange={setOn} />} showChevron={false} onPress={() => {}} />
|
|
241
|
+
import { getResponsiveFontSize } from '@retray-dev/ui-kit'
|
|
242
|
+
const fontSize = getResponsiveFontSize(text, 48)
|
|
243
|
+
// Steps: ≤10→max, ≤12→max-4, ≤14→max-6, >14→max-8
|
|
511
244
|
```
|
|
512
245
|
|
|
513
|
-
|
|
246
|
+
---
|
|
514
247
|
|
|
515
|
-
|
|
516
|
-
| Prop | v5 default | v6 default |
|
|
517
|
-
|------|-----------|-----------|
|
|
518
|
-
| `keyboardBehavior` | `'fillParent'` (Android) / `'interactive'` (iOS) | `'interactive'` (both) |
|
|
519
|
-
| `android_keyboardInputMode` | `'adjustResize'` | `'adjustPan'` |
|
|
248
|
+
## Animations & Interactions
|
|
520
249
|
|
|
521
|
-
|
|
250
|
+
### Pressables (pressto)
|
|
251
|
+
| Pressable | Scale | Used by |
|
|
252
|
+
|---|---|---|
|
|
253
|
+
| `PressableButton` | 0.95 | Button, IconButton, Toggle, Checkbox |
|
|
254
|
+
| `PressableCard` | 0.98 | Card, MediaCard, Stats, Pressable |
|
|
255
|
+
| `PressableRow` | 0.97 | ListItem, MenuItem |
|
|
256
|
+
| `PressableChip` | 0.94 | Chip |
|
|
257
|
+
| `PressableTab` | 0.95 | Tabs triggers |
|
|
522
258
|
|
|
523
|
-
|
|
259
|
+
All use `enabled={!disabled}`, `rippleColor="transparent"`, `touchSoundDisabled`.
|
|
524
260
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
toast.success('Done')
|
|
261
|
+
### Spring Presets
|
|
262
|
+
- `glide`: `{ stiffness: 380, damping: 38, mass: 1.0 }` — sliding indicators
|
|
263
|
+
- `elastic`: `{ stiffness: 320, damping: 22, mass: 0.7 }` — Switch thumb, RadioGroup dot
|
|
529
264
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
265
|
+
### Timing Presets
|
|
266
|
+
- `state`: 160ms (checkbox/toggle color)
|
|
267
|
+
- `expand` / `collapse`: 240ms / 200ms (Accordion)
|
|
268
|
+
- `shimmer`: 1400ms (Skeleton)
|
|
534
269
|
|
|
535
|
-
###
|
|
270
|
+
### EaseView (react-native-ease)
|
|
271
|
+
Declarative color/opacity transitions: Switch track, Checkbox fill, RadioGroup dot. UI thread.
|
|
536
272
|
|
|
537
|
-
|
|
273
|
+
### Reanimated v4
|
|
274
|
+
Complex animations only (gestures, layout interpolations). Simple opacity: use RN `Animated` + `useNativeDriver`.
|
|
538
275
|
|
|
539
|
-
|
|
540
|
-
|-------|---------|---------|
|
|
541
|
-
| `overlay` | `rgba(0,0,0,0.45)` | Backdrop color behind sheets and dialogs |
|
|
542
|
-
| `accent` | `= primary` | Secondary brand accent color |
|
|
543
|
-
| `accentForeground` | `= primaryForeground` | Text on accent backgrounds |
|
|
276
|
+
---
|
|
544
277
|
|
|
545
|
-
|
|
278
|
+
## Sheets (BottomSheetModal)
|
|
546
279
|
|
|
547
|
-
###
|
|
280
|
+
### Strict rules (from @gorhom/bottom-sheet)
|
|
281
|
+
1. **Always `BottomSheetModal`** — never `BottomSheet` base. `present()` in handler directly (not `useEffect`).
|
|
282
|
+
2. **`enableDynamicSizing`** — no `snapPoints` (mutually exclusive)
|
|
283
|
+
3. **`topInset={insets.top}`** — prevents notch overlap
|
|
284
|
+
4. **`keyboardBehavior="interactive"`** + **`android_keyboardInputMode="adjustPan"`** — keyboard
|
|
285
|
+
5. **`SheetTextInput`** inside sheets — never `<TextInput />`
|
|
286
|
+
6. **`onDismiss`** for cleanup, not `onClose`
|
|
287
|
+
7. **`renderBackdrop`** wrapped in `useCallback`
|
|
288
|
+
8. **`enableBlurKeyboardOnGesture={true}`** — dismiss keyboard on drag
|
|
289
|
+
9. **`BottomSheetModalProvider`** re-exported, included in `RetrayProvider`
|
|
548
290
|
|
|
549
|
-
|
|
291
|
+
### Input inside Sheet
|
|
292
|
+
Use `<Input sheetMode />` — transparently swaps to `BottomSheetTextInput`. For low-level: `SheetTextInput`.
|
|
550
293
|
|
|
551
294
|
---
|
|
552
295
|
|
|
553
|
-
##
|
|
554
|
-
|
|
555
|
-
### Breaking Changes
|
|
296
|
+
## Haptics (expo-haptics)
|
|
556
297
|
|
|
557
|
-
|
|
298
|
+
Web-safe via static `import * as Haptics` with `Platform.OS === 'web'` guard at call sites.
|
|
558
299
|
|
|
559
|
-
|
|
300
|
+
| Function | Usage |
|
|
301
|
+
|---|---|
|
|
302
|
+
| `selectionAsync()` | Checkbox, Switch, Toggle, RadioGroup, Select, Slider (step), Accordion, ListItem, MenuItem, ConfirmDialog cancel |
|
|
303
|
+
| `impactLight()` / `impactMedium()` / `impactHeavy()` | Button, Sheet open, ConfirmDialog open, Stats |
|
|
304
|
+
| `notificationSuccess()` / `notificationError()` / `notificationWarning()` | ConfirmDialog confirm, form validation |
|
|
560
305
|
|
|
561
|
-
|
|
562
|
-
|-------|----|----|--------|
|
|
563
|
-
| `display-lg` | 22px / 500 | 24px / 600 | Removed weight inversion vs display-md |
|
|
564
|
-
| `display-md` | 21px / 700 | 20px / 600 | 4px gap preserved; weight normalised |
|
|
565
|
-
| `title-md` | 16px / 600 | 17px / 600 | Now visibly distinct from title-sm |
|
|
566
|
-
| `title-sm` | 16px / 500 | 15px / 500 | Was same px as title-md |
|
|
567
|
-
| `uppercase-tag` | 10px / 0.8 letterSpacing | 11px / 0.6 | Below Apple HIG 11pt minimum |
|
|
568
|
-
| `badge-text-md` | (missing) | 13px / 600 | New canonical token for Badge md |
|
|
569
|
-
|
|
570
|
-
**Default color values changed (WCAG AA fixes):**
|
|
571
|
-
|
|
572
|
-
| Token | v6 | v7 | WCAG impact |
|
|
573
|
-
|-------|----|----|-------------|
|
|
574
|
-
| `foregroundMuted` opacity | 0.38 (~#ababab) | 0.62 (~#767676) | 2.2:1 ❌ → 4.5:1 ✓ |
|
|
575
|
-
| `foregroundSubtle` opacity | 0.55 (~#858585) | 0.70 (~#646464) | 3.5:1 ❌ → 5.9:1 ✓ |
|
|
576
|
-
| `warning` (light) | `#e67e00` | `#9a5200` | 2.86:1 ❌ → 5.86:1 ✓ |
|
|
577
|
-
| `warningForeground` (dark) | `#ffffff` | `#0f0f0f` | Dark text on amber, 8.6:1 ✓ |
|
|
578
|
-
| `destructive` (light) | `#e53935` | `#c72828` | 4.22:1 ❌ → 5.59:1 ✓ |
|
|
579
|
-
| `accent` (light) | `= primary` | `#d4561d` | Explicit brand accent |
|
|
580
|
-
| `accent` (dark) | `= primary` | `#e87645` | Warm accent for dark surfaces |
|
|
306
|
+
---
|
|
581
307
|
|
|
582
|
-
|
|
308
|
+
## Theming
|
|
583
309
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
| `Button` (text variant) | Label color: `foreground` → `accentResolved` — clearer CTA signal |
|
|
588
|
-
| `Card` (elevated variant) | `borderWidth: 0` — shadow is sole depth signal; border was redundant |
|
|
589
|
-
| `Chip` | `paddingVertical` doubled to hit 44pt WCAG 2.5.5 tap target |
|
|
590
|
-
| `Input` / `Textarea` | Border: 1px at rest → animates to 2px on focus (was always 2px) |
|
|
591
|
-
| `Switch` | Off-state now shows animated 1.5px border (invisible on white surfaces before) |
|
|
592
|
-
| `Tabs` | Labels always `Sohne-SemiBold`; active = color only — eliminates layout reflow on selection |
|
|
593
|
-
| `Checkbox` / `RadioGroup` | Disabled `opacity: 0.45` now on full row (was box only) |
|
|
594
|
-
| `Toast` | `richColors={true}` — semantic variants now visually distinct by color |
|
|
310
|
+
### Consumer supplies 12 ThemeColors
|
|
311
|
+
```tsx
|
|
312
|
+
import { ThemeProvider, ThemeColors } from '@retray-dev/ui-kit'
|
|
595
313
|
|
|
596
|
-
|
|
314
|
+
const customLight: ThemeColors = {
|
|
315
|
+
background: '#ffffff',
|
|
316
|
+
foreground: '#1a1a1a',
|
|
317
|
+
primary: '#d4561d',
|
|
318
|
+
}
|
|
319
|
+
```
|
|
597
320
|
|
|
598
|
-
###
|
|
321
|
+
### deriveColors()
|
|
322
|
+
Computes 26 `ResolvedColors` from 12 `ThemeColors`. Components consume `useTheme().colors` — never raw `ThemeColors`.
|
|
599
323
|
|
|
600
|
-
|
|
324
|
+
### Structural tokens (not in theme)
|
|
325
|
+
Spacing, radii, shadows, typography = static `as const` exports from `tokens.ts`. No provider needed.
|
|
601
326
|
|
|
602
327
|
---
|
|
603
328
|
|
|
604
|
-
##
|
|
329
|
+
## Toast (sonner-native)
|
|
605
330
|
|
|
606
|
-
|
|
331
|
+
```tsx
|
|
332
|
+
import { toast } from 'sonner-native'
|
|
333
|
+
// or
|
|
334
|
+
import { useToast } from '@retray-dev/ui-kit'
|
|
335
|
+
const { toast } = useToast()
|
|
607
336
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
337
|
+
toast('Hello', { description: 'World' })
|
|
338
|
+
toast.success('Saved')
|
|
339
|
+
toast.error('Failed', { description: 'Check network' })
|
|
340
|
+
toast.promise(save(), { loading: 'Saving...', success: 'Done', error: 'Failed' })
|
|
612
341
|
```
|
|
613
|
-
(You already need its companions `react-native-svg` and `react-native-screens`.)
|
|
614
342
|
|
|
615
|
-
|
|
616
|
-
5.1.x crashes with Reanimated v4 (`useWorkletCallback is not a function`), which broke `Sheet`, `ConfirmDialog`, and `Select` on open. The range no longer admits broken versions. Pin `5.2.8` if you also use Worklets `0.5.x`.
|
|
343
|
+
`Toaster` rendered inside `ToastProvider`. Config: `richColors: false`, `swipeToDismissDirection="up"`, `duration: 4000`.
|
|
617
344
|
|
|
618
|
-
|
|
619
|
-
The `./fonts` export deliberately points at `src/fonts.ts`, not `dist`. The `.otf` files ship as raw assets in `src/assets/fonts/` and are resolved by Metro at your build time via `require()`. Compiling this entry to `dist` and repointing the `require()` paths breaks Metro asset resolution under pnpm symlinked workspaces (`requiring unknown module ../src/assets/fonts/...`), so it is intentionally left as source. No API change — import as before:
|
|
620
|
-
```ts
|
|
621
|
-
import { SohneFonts } from '@retray-dev/ui-kit/fonts'
|
|
622
|
-
```
|
|
345
|
+
---
|
|
623
346
|
|
|
624
|
-
|
|
347
|
+
## Data Display Patterns
|
|
625
348
|
|
|
626
|
-
|
|
627
|
-
-
|
|
349
|
+
### ListItem
|
|
350
|
+
- `rightActions` prop for swipe-to-reveal (iOS Mail style)
|
|
351
|
+
- Uses `PressableRow` (scale 0.97)
|
|
352
|
+
- Haptic: `selectionAsync()` on press
|
|
353
|
+
- Zero horizontal padding by design
|
|
628
354
|
|
|
629
|
-
###
|
|
355
|
+
### ListGroup
|
|
356
|
+
- Optional `Header` / `Footer` sub-components
|
|
357
|
+
- Auto-separators between items except last
|
|
630
358
|
|
|
631
|
-
|
|
359
|
+
### MenuItem / MenuGroup
|
|
360
|
+
- Settings/nav rows. Auto-separators.
|
|
361
|
+
- `rightRender` for Switch, Badge, etc.
|
|
362
|
+
- `variant="card"` for standalone surface
|
|
632
363
|
|
|
633
|
-
###
|
|
364
|
+
### Chip / ChipGroup
|
|
365
|
+
- Multi-select toggle chips. `PressableChip` (scale 0.94)
|
|
366
|
+
- `ChipGroup` wraps to rows; `CategoryStrip` scrolls horizontally
|
|
634
367
|
|
|
635
|
-
|
|
368
|
+
### LabelValue
|
|
369
|
+
- Key-value rows (label left, value right)
|
|
370
|
+
- Icon support
|
|
636
371
|
|
|
637
|
-
|
|
372
|
+
### DetailRow
|
|
373
|
+
- Ticket/receipt rows with `···` dotted separator
|
|
374
|
+
- Icon + label + value
|
|
638
375
|
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
**1. `SohneFonts` export removed from `@retray-dev/ui-kit/fonts`**
|
|
644
|
-
|
|
645
|
-
The `SohneFonts` object with `require()` calls is no longer exported. Metro cannot reliably resolve `require()` from inside `node_modules`, especially in monorepos. The export now returns `undefined` and logs a deprecation warning.
|
|
646
|
-
|
|
647
|
-
**New approach — postinstall script:**
|
|
648
|
-
1. When you install `@retray-dev/ui-kit`, a **postinstall script** automatically copies 28 `.otf` font files to `assets/fonts/sohne/` in your project root
|
|
649
|
-
2. Define `SohneFonts` locally in your `App.tsx` with static `require()` calls (see below)
|
|
650
|
-
3. Pass it to `expo-font`'s `useFonts()` hook
|
|
651
|
-
|
|
652
|
-
**Migration:**
|
|
653
|
-
|
|
654
|
-
```diff
|
|
655
|
-
// App.tsx
|
|
656
|
-
import { useFonts } from 'expo-font'
|
|
657
|
-
-import { SohneFonts } from '@retray-dev/ui-kit/fonts'
|
|
658
|
-
|
|
659
|
-
+// Fonts copied to assets/fonts/sohne/ by @retray-dev/ui-kit postinstall
|
|
660
|
-
+const SohneFonts = {
|
|
661
|
-
+ 'Sohne-ExtraLight': require('./assets/fonts/sohne/Sohne-ExtraLight.otf'),
|
|
662
|
-
+ 'Sohne-ExtraLightItalic': require('./assets/fonts/sohne/Sohne-ExtraLightItalic.otf'),
|
|
663
|
-
+ 'Sohne-Light': require('./assets/fonts/sohne/Sohne-Light.otf'),
|
|
664
|
-
+ 'Sohne-LightItalic': require('./assets/fonts/sohne/Sohne-LightItalic.otf'),
|
|
665
|
-
+ 'Sohne-Regular': require('./assets/fonts/sohne/Sohne-Regular.otf'),
|
|
666
|
-
+ 'Sohne-Italic': require('./assets/fonts/sohne/Sohne-Italic.otf'),
|
|
667
|
-
+ 'Sohne-Medium': require('./assets/fonts/sohne/Sohne-Medium.otf'),
|
|
668
|
-
+ 'Sohne-MediumItalic': require('./assets/fonts/sohne/Sohne-MediumItalic.otf'),
|
|
669
|
-
+ 'Sohne-SemiBold': require('./assets/fonts/sohne/Sohne-SemiBold.otf'),
|
|
670
|
-
+ 'Sohne-SemiBoldItalic': require('./assets/fonts/sohne/Sohne-SemiBoldItalic.otf'),
|
|
671
|
-
+ 'Sohne-Bold': require('./assets/fonts/sohne/Sohne-Bold.otf'),
|
|
672
|
-
+ 'Sohne-BoldItalic': require('./assets/fonts/sohne/Sohne-BoldItalic.otf'),
|
|
673
|
-
+ 'Sohne-ExtraBold': require('./assets/fonts/sohne/Sohne-ExtraBold.otf'),
|
|
674
|
-
+ 'Sohne-ExtraBoldItalic': require('./assets/fonts/sohne/Sohne-ExtraBoldItalic.otf'),
|
|
675
|
-
+ 'SohneMono-ExtraLight': require('./assets/fonts/sohne/SohneMono-ExtraLight.otf'),
|
|
676
|
-
+ 'SohneMono-ExtraLightItalic': require('./assets/fonts/sohne/SohneMono-ExtraLightItalic.otf'),
|
|
677
|
-
+ 'SohneMono-Light': require('./assets/fonts/sohne/SohneMono-Light.otf'),
|
|
678
|
-
+ 'SohneMono-LightItalic': require('./assets/fonts/sohne/SohneMono-LightItalic.otf'),
|
|
679
|
-
+ 'SohneMono-Regular': require('./assets/fonts/sohne/SohneMono-Regular.otf'),
|
|
680
|
-
+ 'SohneMono-Italic': require('./assets/fonts/sohne/SohneMono-Italic.otf'),
|
|
681
|
-
+ 'SohneMono-Medium': require('./assets/fonts/sohne/SohneMono-Medium.otf'),
|
|
682
|
-
+ 'SohneMono-MediumItalic': require('./assets/fonts/sohne/SohneMono-MediumItalic.otf'),
|
|
683
|
-
+ 'SohneMono-SemiBold': require('./assets/fonts/sohne/SohneMono-SemiBold.otf'),
|
|
684
|
-
+ 'SohneMono-SemiBoldItalic': require('./assets/fonts/sohne/SohneMono-SemiBoldItalic.otf'),
|
|
685
|
-
+ 'SohneMono-Bold': require('./assets/fonts/sohne/SohneMono-Bold.otf'),
|
|
686
|
-
+ 'SohneMono-BoldItalic': require('./assets/fonts/sohne/SohneMono-BoldItalic.otf'),
|
|
687
|
-
+ 'SohneMono-ExtraBold': require('./assets/fonts/sohne/SohneMono-ExtraBold.otf'),
|
|
688
|
-
+ 'SohneMono-ExtraBoldItalic': require('./assets/fonts/sohne/SohneMono-ExtraBoldItalic.otf'),
|
|
689
|
-
+}
|
|
376
|
+
### Skeleton
|
|
377
|
+
- `Skeleton.MediaCard`, `Skeleton.ListItem`, `Skeleton.List` (6 items, 2 columns)
|
|
378
|
+
- Shimmer animation loops every 1200ms
|
|
690
379
|
|
|
691
|
-
|
|
692
|
-
const [fontsLoaded] = useFonts(SohneFonts)
|
|
693
|
-
// ...
|
|
694
|
-
}
|
|
695
|
-
```
|
|
380
|
+
---
|
|
696
381
|
|
|
697
|
-
|
|
698
|
-
```gitignore
|
|
699
|
-
# Sohne fonts — copied by @retray-dev/ui-kit postinstall
|
|
700
|
-
assets/fonts/sohne/
|
|
701
|
-
```
|
|
382
|
+
## Accessibility (WCAG AA)
|
|
702
383
|
|
|
703
|
-
|
|
384
|
+
- `allowFontScaling={true}` on all `<Text>` and `<TextInput>`
|
|
385
|
+
- Touch targets ≥44pt
|
|
386
|
+
- `foregroundMuted` = `#9a9a9a` (dark) / `#a2a2a2` (light) — ≥4.5:1 contrast
|
|
387
|
+
- Accessibility props passed through via `...props`
|
|
704
388
|
|
|
705
389
|
---
|
|
706
390
|
|
|
707
|
-
##
|
|
708
|
-
|
|
709
|
-
### New — public utility
|
|
710
|
-
|
|
711
|
-
**`withAlpha(hex, alpha)`** — hex-to-rgba color helper, now exported from the package root. Useful for semi-transparent overlays derived from theme colors without adding a new token.
|
|
391
|
+
## Deep Import Pattern
|
|
712
392
|
|
|
393
|
+
Prefer deep imports in production for smaller bundle:
|
|
713
394
|
```tsx
|
|
714
|
-
import {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
<View style={{ backgroundColor: withAlpha(colors.primary, 0.15) }} />
|
|
395
|
+
import { Button } from '@retray-dev/ui-kit/Button' // deep
|
|
396
|
+
// vs
|
|
397
|
+
import { Button } from '@retray-dev/ui-kit' // barrel
|
|
718
398
|
```
|
|
719
399
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
No breaking changes in v11. Safe minor upgrade from v10.
|
|
400
|
+
Deep-import only (NOT in barrel):
|
|
401
|
+
```tsx
|
|
402
|
+
import { HolographicCard } from '@retray-dev/ui-kit/HolographicCard'
|
|
403
|
+
```
|
|
726
404
|
|
|
727
405
|
---
|
|
728
406
|
|
|
729
|
-
##
|
|
730
|
-
|
|
731
|
-
### Breaking Changes
|
|
407
|
+
## Anti-Patterns
|
|
732
408
|
|
|
733
|
-
|
|
409
|
+
| Anti-pattern | Why | Fix |
|
|
410
|
+
|---|---|---|
|
|
411
|
+
| `<TextInput />` inside Sheet | Keyboard broken | Use `<Input sheetMode />` or `<SheetTextInput />` |
|
|
412
|
+
| `BottomSheet` base with `snapToIndex` | Fragile timing | Use `BottomSheetModal` with `present()` |
|
|
413
|
+
| `snapPoints` + `enableDynamicSizing` | Runtime error | Use one or the other |
|
|
414
|
+
| Static import of optional peer | Crash at module init | Dynamic `import()` in handler |
|
|
415
|
+
| Bundling native modules | Duplicate Context | Externalize in tsup |
|
|
416
|
+
| `--legacy-peer-deps` | Hides conflicts | Use `overrides` in package.json |
|
|
734
417
|
|
|
735
|
-
|
|
418
|
+
---
|
|
736
419
|
|
|
737
|
-
|
|
420
|
+
### Image
|
|
738
421
|
|
|
739
|
-
|
|
740
|
-
<Sheet
|
|
741
|
-
open={open}
|
|
742
|
-
onClose={() => setOpen(false)}
|
|
743
|
-
title="Options"
|
|
744
|
-
- responsive
|
|
745
|
-
- dialogMaxWidth={600}
|
|
746
|
-
/>
|
|
747
|
-
```
|
|
422
|
+
**Import:** `import { Image } from '@retray-dev/ui-kit'`
|
|
748
423
|
|
|
749
|
-
**
|
|
424
|
+
**When to use:** Image display with automatic fallback when source is null or fails to load. Wraps `expo-image` for performant caching and cross-fade transitions. Supports aspect ratio containers.
|
|
750
425
|
|
|
751
|
-
|
|
426
|
+
| Prop | Type | Default | Notes |
|
|
427
|
+
|------|------|---------|-------|
|
|
428
|
+
| src | `string \| null` | — | Image URI. `null` or load failure shows fallback/skeleton |
|
|
429
|
+
| fallback | `ReactNode` | — | Custom fallback when src is null or image fails |
|
|
430
|
+
| aspectRatio | `number` | — | Width/height ratio — sets container proportionally (e.g. `16/9`, `4/3`) |
|
|
431
|
+
| borderRadius | `number` | `0` | Corner radius on the image |
|
|
432
|
+
| style | `ImageStyle` | — | — |
|
|
752
433
|
|
|
753
|
-
|
|
434
|
+
All remaining `expo-image` props (`contentFit`, `transition`, `placeholder`, etc.) pipe through.
|
|
754
435
|
|
|
755
|
-
|
|
436
|
+
**Fallback behavior:** When `src` is null/falsy or the image errors on load:
|
|
437
|
+
- If `fallback` is provided: renders fallback node inside a container
|
|
438
|
+
- Otherwise: renders a `skeleton`-colored placeholder matching the image dimensions
|
|
756
439
|
|
|
440
|
+
**Examples:**
|
|
757
441
|
```tsx
|
|
758
|
-
<
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
<ToastProvider>{/* app */}</ToastProvider>
|
|
763
|
-
</BottomSheetModalProvider>
|
|
764
|
-
</ThemeProvider>
|
|
765
|
-
</GestureHandlerRootView>
|
|
766
|
-
</SafeAreaProvider>
|
|
442
|
+
<Image src="https://example.com/photo.jpg" aspectRatio={4/3} borderRadius={12} />
|
|
443
|
+
<Image src={null} fallback={<Icon name="image" size={32} color={colors.foregroundMuted} />} />
|
|
444
|
+
<Image src={avatar} borderRadius={40} style={{ width: 80, height: 80 }} />
|
|
445
|
+
<Image src={banner} aspectRatio={16/9} contentFit="cover" transition={500} />
|
|
767
446
|
```
|
|
768
447
|
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
| Prop | v11 | v12 default |
|
|
772
|
-
|------|-----|-------------|
|
|
773
|
-
| `keyboardBehavior` | `'interactive'` | `'interactive'` (unchanged) |
|
|
774
|
-
| `android_keyboardInputMode` | `'adjustPan'` | `'adjustPan'` (unchanged) |
|
|
775
|
-
|
|
776
|
-
**5. Removed top-level imports of `Modal`, `ScrollView`, `useWindowDimensions`, `BREAKPOINTS` from `Sheet.tsx`**
|
|
777
|
-
|
|
778
|
-
Internal only — no public API.
|
|
448
|
+
---
|
|
779
449
|
|
|
780
|
-
###
|
|
450
|
+
### ScreenContainer
|
|
781
451
|
|
|
782
|
-
|
|
783
|
-
- **`topInset={insets.top}`** is now applied automatically from safe-area context — sheet never crosses the notch / status bar on iOS or Android.
|
|
784
|
-
- **`snapPoints` + `enableDynamicSizing` are mutually exclusive.** If you pass `snapPoints`, dynamic sizing is disabled and the sheet snaps to those points. Omit `snapPoints` to use dynamic sizing (default).
|
|
452
|
+
**Import:** `import { ScreenContainer } from '@retray-dev/ui-kit'`
|
|
785
453
|
|
|
786
|
-
|
|
454
|
+
**When to use:** Consistent screen-level layout wrapper. Sets background color, safe area padding, optional scroll, keyboard avoidance, and centers content with a max-width constraint for wide screens.
|
|
787
455
|
|
|
788
|
-
|
|
456
|
+
| Prop | Type | Default | Notes |
|
|
457
|
+
|------|------|---------|-------|
|
|
458
|
+
| children | `ReactNode` | required | Screen content |
|
|
459
|
+
| maxWidth | `number` | `700` (BREAKPOINTS.wide) | Max content width. Set to `0` to disable centering |
|
|
460
|
+
| scroll | `boolean` | `false` | Wrap content in ScrollView |
|
|
461
|
+
| backgroundColor | `string` | Theme `background` | Override background color |
|
|
462
|
+
| paddingTop | `number` | Safe area top | Override top padding |
|
|
463
|
+
| paddingBottom | `number` | Safe area bottom | Override bottom padding |
|
|
464
|
+
| paddingHorizontal | `number` | `0` | Horizontal padding |
|
|
465
|
+
| keyboard | `boolean` | `false` | Enable KeyboardAvoidingView (iOS: padding behavior) |
|
|
466
|
+
| style | `ViewStyle` | — | Additional container styles |
|
|
467
|
+
|
|
468
|
+
**Behavior:** Wraps children in a centered View (maxWidth with `alignSelf: 'center'`). When `keyboard={true}`, wraps in `KeyboardAvoidingView`. When `scroll={true}`, wraps in `ScrollView` with `keyboardShouldPersistTaps="handled"`.
|
|
789
469
|
|
|
470
|
+
**Examples:**
|
|
790
471
|
```tsx
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
<
|
|
800
|
-
|
|
472
|
+
// Basic screen
|
|
473
|
+
<ScreenContainer>
|
|
474
|
+
<Text variant="display-xl">Home</Text>
|
|
475
|
+
{/* screen content */}
|
|
476
|
+
</ScreenContainer>
|
|
477
|
+
|
|
478
|
+
// Scrollable with keyboard avoidance
|
|
479
|
+
<ScreenContainer scroll keyboard paddingHorizontal={16}>
|
|
480
|
+
<Input label="Name" />
|
|
481
|
+
<Input label="Email" />
|
|
482
|
+
<Button label="Submit" fullWidth />
|
|
483
|
+
</ScreenContainer>
|
|
484
|
+
|
|
485
|
+
// Wide desktop constraint
|
|
486
|
+
<ScreenContainer maxWidth={700} paddingHorizontal={16}>
|
|
487
|
+
<Text variant="display-lg">Dashboard</Text>
|
|
488
|
+
</ScreenContainer>
|
|
801
489
|
```
|
|
802
490
|
|
|
803
|
-
`SheetTextInput` is still re-exported for low-level use.
|
|
804
|
-
|
|
805
491
|
---
|
|
806
492
|
|
|
807
|
-
|
|
493
|
+
### VirtualizedList
|
|
808
494
|
|
|
809
|
-
|
|
495
|
+
**Import:** `import { VirtualizedList } from '@retray-dev/ui-kit'`
|
|
810
496
|
|
|
811
|
-
|
|
497
|
+
**When to use:** Performant sectioned lists with sticky headers, pull-to-refresh, and empty state. Built on React Native `SectionList` for memory-efficient rendering of large datasets.
|
|
812
498
|
|
|
813
|
-
|
|
499
|
+
| Prop | Type | Default | Notes |
|
|
500
|
+
|------|------|---------|-------|
|
|
501
|
+
| sections | `VirtualizedListSection<T>[]` | required | `{ title?: string, data: T[] }` — sections with optional headers |
|
|
502
|
+
| renderItem | `(info: SectionListRenderItemInfo<T>) => ReactElement` | required | Row renderer for each item |
|
|
503
|
+
| emptyTitle | `string` | `'Sin contenido'` | Title shown when no data |
|
|
504
|
+
| emptyDescription | `string` | — | Description below empty title |
|
|
505
|
+
| emptyComponent | `ReactNode` | — | Custom empty state (overrides emptyTitle/emptyDescription) |
|
|
506
|
+
| refreshing | `boolean` | `false` | Pull-to-refresh loading state |
|
|
507
|
+
| onRefresh | `() => void` | — | Pull-to-refresh handler |
|
|
508
|
+
| stickyHeaders | `boolean` | `true` | Section headers stick to top while scrolling |
|
|
509
|
+
| headerColor | `string` | `foregroundMuted` | Section header text color override |
|
|
510
|
+
| style | `ViewStyle` | — | List container style |
|
|
814
511
|
|
|
815
|
-
|
|
512
|
+
Also accepts all `SectionListProps` (e.g. `ListHeaderComponent`, `ListFooterComponent`, `ItemSeparatorComponent`).
|
|
816
513
|
|
|
817
|
-
|
|
514
|
+
**Section headers:** Uppercase 13pt SemiBold text. Sections with empty `data` arrays are filtered out automatically.
|
|
818
515
|
|
|
819
|
-
|
|
516
|
+
**Empty state:** When all sections have empty data, shows centered emptyTitle/emptyDescription or custom emptyComponent.
|
|
820
517
|
|
|
821
|
-
|
|
518
|
+
**Examples:**
|
|
519
|
+
```tsx
|
|
520
|
+
// Simple sectioned list
|
|
521
|
+
<VirtualizedList
|
|
522
|
+
sections={[
|
|
523
|
+
{ title: 'Fruits', data: ['Apple', 'Banana', 'Cherry'] },
|
|
524
|
+
{ title: 'Vegetables', data: ['Carrot', 'Broccoli'] },
|
|
525
|
+
]}
|
|
526
|
+
renderItem={({ item }) => <ListItem title={item} showSeparator />}
|
|
527
|
+
/>
|
|
528
|
+
|
|
529
|
+
// With pull-to-refresh
|
|
530
|
+
<VirtualizedList
|
|
531
|
+
sections={data}
|
|
532
|
+
renderItem={renderRow}
|
|
533
|
+
refreshing={loading}
|
|
534
|
+
onRefresh={handleRefresh}
|
|
535
|
+
/>
|
|
822
536
|
|
|
823
|
-
|
|
537
|
+
// Custom empty state
|
|
538
|
+
<VirtualizedList
|
|
539
|
+
sections={[]}
|
|
540
|
+
renderItem={() => null}
|
|
541
|
+
emptyTitle="No items found"
|
|
542
|
+
emptyDescription="Try adjusting your filters"
|
|
543
|
+
/>
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
---
|
|
824
547
|
|
|
825
|
-
|
|
548
|
+
## Version
|
|
826
549
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
550
|
+
**Current: 13.4.0.** Requires Expo SDK 54+, React Native 0.81+, React 19.
|
|
551
|
+
|
|
552
|
+
For full setup guide, see `CONSUMER.md`. For usage examples, see `EXAMPLES.md`.
|
|
830
553
|
|
|
831
554
|
---
|
|
832
555
|
|
|
833
|
-
##
|
|
556
|
+
## Component Reference
|
|
834
557
|
|
|
835
558
|
---
|
|
836
559
|
|
|
@@ -857,18 +580,19 @@ No consumer action required — the fix is internal.
|
|
|
857
580
|
|---------|------|--------|--------------|-------------|
|
|
858
581
|
| `display-hero` | 64 | 700 | `foreground` | Large numeric displays — balance totals, stats, hero numbers |
|
|
859
582
|
| `display-xl` | 28 | 700 | `foreground` | Screen-level titles, onboarding hero headings |
|
|
860
|
-
| `display-lg` |
|
|
861
|
-
| `display-md` |
|
|
862
|
-
| `display-sm` |
|
|
863
|
-
| `title-md` |
|
|
864
|
-
| `title-sm` |
|
|
583
|
+
| `display-lg` | 24 | 600 | `foreground` | Section headings, card group labels |
|
|
584
|
+
| `display-md` | 20 | 600 | `foreground` | Card titles, dialog headings |
|
|
585
|
+
| `display-sm` | 18 | 600 | `foreground` | Sub-section titles |
|
|
586
|
+
| `title-md` | 17 | 600 | `foreground` | List row titles, navigation labels |
|
|
587
|
+
| `title-sm` | 15 | 500 | `foreground` | Secondary row titles |
|
|
865
588
|
| `body-md` | 16 | 400 | `foregroundSubtle` | Main body copy, descriptions |
|
|
866
589
|
| `body-sm` | 14 | 400 | `foregroundSubtle` | Secondary descriptions, detail text |
|
|
867
590
|
| `caption` | 14 | 500 | `foreground` | Labels above inputs, item captions |
|
|
868
591
|
| `caption-sm` | 13 | 400 | `foregroundMuted` | Timestamps, metadata, helper text |
|
|
869
592
|
| `badge-text` | 11 | 600 | `foreground` | Badge labels, small status tags |
|
|
593
|
+
| `badge-text-md` | 13 | 600 | `foreground` | Badge labels (md size) |
|
|
870
594
|
| `micro-label` | 12 | 700 | `foreground` | Micro labels, overlines |
|
|
871
|
-
| `uppercase-tag` |
|
|
595
|
+
| `uppercase-tag` | 11 | 700 | `foreground` | Decorative uppercase category labels (auto-transforms text) |
|
|
872
596
|
| `button-lg` | 16 | 500 | `foreground` | Internal use in Button component (md/lg) |
|
|
873
597
|
| `button-sm` | 14 | 500 | `foreground` | Internal use in Button component (sm) |
|
|
874
598
|
|
|
@@ -1835,9 +1559,10 @@ const [guests, setGuests] = useState(2)
|
|
|
1835
1559
|
| Prop | Type | Default | Notes |
|
|
1836
1560
|
|------|------|---------|-------|
|
|
1837
1561
|
| orientation | `'horizontal' \| 'vertical'` | `'horizontal'` | Direction of the line |
|
|
1562
|
+
| label | `string` | — | Label shown in center of horizontal separator — renders as `── label ──` |
|
|
1838
1563
|
| style | `ViewStyle` | — | — |
|
|
1839
1564
|
|
|
1840
|
-
**Styling:** 1px thickness, `border` token color.
|
|
1565
|
+
**Styling:** 1px thickness, `border` token color. When `label` is provided, uses uppercase medium text with letter-spacing.
|
|
1841
1566
|
|
|
1842
1567
|
**Examples:**
|
|
1843
1568
|
```tsx
|
|
@@ -1851,6 +1576,9 @@ const [guests, setGuests] = useState(2)
|
|
|
1851
1576
|
<Text>Right</Text>
|
|
1852
1577
|
</View>
|
|
1853
1578
|
|
|
1579
|
+
// With label
|
|
1580
|
+
<Separator label="o" />
|
|
1581
|
+
|
|
1854
1582
|
// With custom spacing
|
|
1855
1583
|
<Separator style={{ marginVertical: SPACING.lg }} />
|
|
1856
1584
|
```
|
|
@@ -2501,15 +2229,21 @@ const [accepted, setAccepted] = useState(false)
|
|
|
2501
2229
|
| tabs | `TabItem[]` | required | Tab definitions |
|
|
2502
2230
|
| variant | `'pill' \| 'underline'` | `'pill'` | Visual style |
|
|
2503
2231
|
| value | `string` | — | Controlled active tab |
|
|
2504
|
-
| onValueChange | `(value:
|
|
2232
|
+
| onValueChange | `(value: T) => void` | — | Generic — infers from `value` prop type |
|
|
2505
2233
|
| children | `ReactNode` | — | `TabsContent` components |
|
|
2506
2234
|
| style | `ViewStyle` | — | — |
|
|
2507
2235
|
|
|
2508
2236
|
**TabItem type:**
|
|
2509
2237
|
```ts
|
|
2510
|
-
|
|
2238
|
+
interface TabItem<T extends string = string> {
|
|
2239
|
+
label: string
|
|
2240
|
+
value: T
|
|
2241
|
+
icon?: ReactNode | ((isActive: boolean) => ReactNode)
|
|
2242
|
+
}
|
|
2511
2243
|
```
|
|
2512
2244
|
|
|
2245
|
+
The generic `T` propagates from `TabItem<T>` through `onValueChange(value: T)`, so callbacks receive the correct union type without manual casting.
|
|
2246
|
+
|
|
2513
2247
|
**Variants:**
|
|
2514
2248
|
- `pill` — Animated background pill slides to active tab. Full-width distributed tabs. Active: filled `primary` + `primaryForeground` text. Default for top-of-screen navigation.
|
|
2515
2249
|
- `underline` — 2px bottom border on active tab, no background. Tabs don't stretch — natural width. Airbnb-style filter tabs.
|
|
@@ -3847,7 +3581,8 @@ export default function Screen() {
|
|
|
3847
3581
|
| subtitle | `string` | — | Secondary line under the title |
|
|
3848
3582
|
| onBack | `() => void` | — | Shows a back button on the left when provided |
|
|
3849
3583
|
| backIconName | `string` | `'chevron-left'` | Icon for the back button |
|
|
3850
|
-
|
|
|
3584
|
+
| iconName | `string` | — | Decorative icon left of title, after back button. Ignored when `left` is provided |
|
|
3585
|
+
| left | `ReactNode` | — | Custom left content — overrides the back button and `iconName` |
|
|
3851
3586
|
| right | `ReactNode` | — | Custom right content (actions) |
|
|
3852
3587
|
| titleAlign | `'auto' \| 'left' \| 'center'` | `'auto'` | `auto` = left on narrow, centered on wide |
|
|
3853
3588
|
| bordered | `boolean` | `true` | Hairline border underneath |
|
|
@@ -3862,6 +3597,13 @@ export default function Screen() {
|
|
|
3862
3597
|
onBack={navigation.goBack}
|
|
3863
3598
|
right={<IconButton iconName="more-horizontal" variant="text" onPress={openMenu} />}
|
|
3864
3599
|
/>
|
|
3600
|
+
|
|
3601
|
+
{/* With decorative icon and back button */}
|
|
3602
|
+
<AppHeader
|
|
3603
|
+
title="Perfil"
|
|
3604
|
+
iconName="user"
|
|
3605
|
+
onBack={() => router.back()}
|
|
3606
|
+
/>
|
|
3865
3607
|
```
|
|
3866
3608
|
|
|
3867
3609
|
---
|
|
@@ -4135,17 +3877,23 @@ export default function TabLayout() {
|
|
|
4135
3877
|
| title | `string` | `'Something went wrong'` | Default fallback title |
|
|
4136
3878
|
| message | `string` | `error.message` | Default fallback body |
|
|
4137
3879
|
| onError | `(error, info) => void` | — | Wire your crash reporter here |
|
|
3880
|
+
| onCatch | `(error, componentStack) => void` | — | Simplified callback — just error + stack string |
|
|
4138
3881
|
|
|
4139
3882
|
**Default fallback:** centered themed card with a destructive icon, title, message, and a "Try again" button that calls `reset()`.
|
|
4140
3883
|
|
|
4141
|
-
**
|
|
3884
|
+
**Examples:**
|
|
4142
3885
|
```tsx
|
|
4143
3886
|
<ErrorBoundary onError={reportCrash}>
|
|
4144
3887
|
<DocumentViewer />
|
|
4145
3888
|
</ErrorBoundary>
|
|
4146
3889
|
|
|
4147
|
-
//
|
|
4148
|
-
<ErrorBoundary fallback={({ error, reset }) => <MyFallback error={error} onRetry={reset} />}>
|
|
3890
|
+
// Use componentStack in custom fallback
|
|
3891
|
+
<ErrorBoundary fallback={({ error, componentStack, reset }) => <MyFallback error={error} stack={componentStack} onRetry={reset} />}>
|
|
3892
|
+
<RiskyScreen />
|
|
3893
|
+
</ErrorBoundary>
|
|
3894
|
+
|
|
3895
|
+
// Simplified crash reporter
|
|
3896
|
+
<ErrorBoundary onCatch={(error, stack) => logCrash(error, stack)}>
|
|
4149
3897
|
<RiskyScreen />
|
|
4150
3898
|
</ErrorBoundary>
|
|
4151
3899
|
```
|
|
@@ -4309,7 +4057,7 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
|
|
|
4309
4057
|
|
|
4310
4058
|
**When to use:** Image selection from the device library — product photos, profile avatars, tenant logos. Shows a dashed placeholder when empty, the selected image when filled, and a loading overlay while uploading.
|
|
4311
4059
|
|
|
4312
|
-
**Requires:** `
|
|
4060
|
+
**Requires:** `react-native-image-picker` installed in the consuming app (`pnpm add react-native-image-picker`). It is an optional peer dep — consumers who don't use `ImageUpload` never pull it. It is dynamically imported at tap time.
|
|
4313
4061
|
|
|
4314
4062
|
| Prop | Type | Default | Notes |
|
|
4315
4063
|
|------|------|---------|-------|
|
|
@@ -4323,9 +4071,9 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
|
|
|
4323
4071
|
| borderRadius | `number` | `RADIUS.lg` | — |
|
|
4324
4072
|
| resizeMode | `'cover' \| 'contain' \| 'stretch'` | `'cover'` | Image resize mode |
|
|
4325
4073
|
| disabled | `boolean` | `false` | Prevents pressing |
|
|
4326
|
-
| allowsEditing | `boolean` | `true` | When `true`, iOS opens the crop/editing screen after selecting an image. Set `false` to accept the image directly without cropping |
|
|
4327
4074
|
| style | `ViewStyle` | — | — |
|
|
4328
4075
|
| accessibilityLabel | `string` | — | — |
|
|
4076
|
+
| onPickerStarting | `() => void` | — | Called synchronously when user taps the upload area, before dynamic import and permission request |
|
|
4329
4077
|
|
|
4330
4078
|
**Examples:**
|
|
4331
4079
|
```tsx
|
|
@@ -4465,171 +4213,123 @@ const { visible, loading, open, onConfirm, onCancel } = useConfirmDialog({
|
|
|
4465
4213
|
|
|
4466
4214
|
---
|
|
4467
4215
|
|
|
4468
|
-
|
|
4216
|
+
### Image
|
|
4469
4217
|
|
|
4470
|
-
|
|
4218
|
+
**Import:** `import { Image } from '@retray-dev/ui-kit'`
|
|
4471
4219
|
|
|
4472
|
-
|
|
4220
|
+
**When to use:** Display images with automatic fallback on load failure. Wraps `expo-image` for performant loading and caching.
|
|
4473
4221
|
|
|
4474
|
-
|
|
|
4475
|
-
|
|
4476
|
-
|
|
|
4477
|
-
|
|
|
4478
|
-
|
|
|
4479
|
-
|
|
|
4480
|
-
| 5 | `MaterialIcons` | Material-style icons |
|
|
4481
|
-
| 6 | `Ionicons` | Fallback |
|
|
4222
|
+
| Prop | Type | Default | Notes |
|
|
4223
|
+
|------|------|---------|-------|
|
|
4224
|
+
| src | `string \| null` | — | Image URI. `null` or failed load shows fallback |
|
|
4225
|
+
| fallback | `ReactNode` | — | Custom fallback content when image fails to load |
|
|
4226
|
+
| aspectRatio | `number` | — | Width/height ratio — e.g. `16/9`, `4/3`. Sets container height proportionally |
|
|
4227
|
+
| borderRadius | `number` | `0` | Border radius override |
|
|
4482
4228
|
|
|
4483
|
-
|
|
4229
|
+
**All other `expo-image` props pass through** (except `source` — use `src` instead).
|
|
4484
4230
|
|
|
4485
|
-
|
|
4231
|
+
**Behavior:** On load error or `null` src: if `fallback` node provided, renders it inside container; otherwise renders a skeleton-colored placeholder with `accessibilityLabel="Imagen no disponible"`.
|
|
4486
4232
|
|
|
4233
|
+
**Examples:**
|
|
4487
4234
|
```tsx
|
|
4488
|
-
|
|
4235
|
+
// Basic
|
|
4236
|
+
<Image src="https://example.com/photo.jpg" aspectRatio={16/9} />
|
|
4489
4237
|
|
|
4490
|
-
|
|
4491
|
-
<
|
|
4238
|
+
// With fallback
|
|
4239
|
+
<Image src={user.avatar} fallback={<Avatar fallbackText={user.name} />} />
|
|
4492
4240
|
|
|
4493
|
-
//
|
|
4494
|
-
<
|
|
4241
|
+
// Rounded
|
|
4242
|
+
<Image src={photo.uri} borderRadius={12} aspectRatio={4/3} />
|
|
4495
4243
|
```
|
|
4496
4244
|
|
|
4497
|
-
**Props:**
|
|
4498
|
-
|
|
4499
|
-
| Prop | Type | Required | Notes |
|
|
4500
|
-
|------|------|----------|-------|
|
|
4501
|
-
| name | `string` | yes | Icon name (e.g. `"home"`, `"arrow-right"`) |
|
|
4502
|
-
| size | `number` | yes | Icon size in points |
|
|
4503
|
-
| color | `string` | yes | Icon color |
|
|
4504
|
-
| family | `IconFamily` | no | Force a specific family |
|
|
4505
|
-
|
|
4506
|
-
Returns `null` (no crash) if name not found in any family.
|
|
4507
|
-
|
|
4508
|
-
> **v13:** `renderIcon`, `getValidIconNames`, and `configureIconFamilies` were removed. Use `Icon` component directly for all icon rendering.
|
|
4509
|
-
|
|
4510
|
-
### `iconName` props on components
|
|
4511
|
-
|
|
4512
|
-
All components with icon slots accept `iconName` — auto-resolved size and color:
|
|
4513
|
-
|
|
4514
|
-
| Component | Prop(s) | Slot | Default color |
|
|
4515
|
-
|-----------|---------|------|---------------|
|
|
4516
|
-
| `Button` | `iconName`, `iconColor` | Left or right of label | Variant label color |
|
|
4517
|
-
| `IconButton` | `iconName`, `iconColor` | Center | Variant foreground |
|
|
4518
|
-
| `Input` | `prefixIcon`, `prefixIconColor` | Before input | `foregroundMuted` |
|
|
4519
|
-
| `Input` | `suffixIcon`, `suffixIconColor` | After input | `foregroundMuted` |
|
|
4520
|
-
| `ListItem` | `leftIcon`, `leftIconColor` | Left 44×44 slot | `foreground` |
|
|
4521
|
-
| `ListItem` | `rightIcon`, `rightIconColor` | Right slot | `foregroundMuted` |
|
|
4522
|
-
| `Badge` | `iconName`, `iconColor` | Before label | Variant foreground |
|
|
4523
|
-
| `Toggle` | `iconName`, `iconColor` | When not pressed | `foregroundMuted` |
|
|
4524
|
-
| `Toggle` | `activeIconName`, `activeIconColor` | When pressed | `primary` |
|
|
4525
|
-
| `AlertBanner` | `iconName`, `iconColor` | Left of content | Variant title color |
|
|
4526
|
-
| `EmptyState` | `iconName`, `iconColor` | Center icon slot | `foregroundMuted` |
|
|
4527
|
-
| `Toast` | `iconName`, `iconColor` | Left of message | Variant text color |
|
|
4528
|
-
| `MediaCard` | `actionIconName` | Top-right of image | `#ffffff` |
|
|
4529
|
-
| `Chip` | `iconName` | Before label | Variant foreground |
|
|
4530
|
-
|
|
4531
|
-
**Precedence:** `iconName` always takes precedence over the corresponding `ReactNode` prop when both are supplied.
|
|
4532
|
-
|
|
4533
4245
|
---
|
|
4534
4246
|
|
|
4535
|
-
###
|
|
4536
|
-
|
|
4537
|
-
Calculates a stepped-down font size for long numeric strings (currency amounts, large numbers) that would overflow at full size.
|
|
4538
|
-
|
|
4539
|
-
```tsx
|
|
4540
|
-
import { getResponsiveFontSize } from '@retray-dev/ui-kit'
|
|
4247
|
+
### ScreenContainer
|
|
4541
4248
|
|
|
4542
|
-
|
|
4543
|
-
const fontSize = getResponsiveFontSize(formatCOP(amount), 48)
|
|
4249
|
+
**Import:** `import { ScreenContainer } from '@retray-dev/ui-kit'`
|
|
4544
4250
|
|
|
4545
|
-
|
|
4546
|
-
{formatCOP(amount)}
|
|
4547
|
-
</Text>
|
|
4548
|
-
```
|
|
4251
|
+
**When to use:** Consistent screen layout with safe area insets, optional scroll, keyboard avoidance, and responsive max-width centering.
|
|
4549
4252
|
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4253
|
+
| Prop | Type | Default | Notes |
|
|
4254
|
+
|------|------|---------|-------|
|
|
4255
|
+
| children | `ReactNode` | required | Screen content |
|
|
4256
|
+
| maxWidth | `number` | `700` | Max content width (points). `0` disables |
|
|
4257
|
+
| scroll | `boolean` | `false` | Enable scroll view |
|
|
4258
|
+
| backgroundColor | `string` | theme `background` | — |
|
|
4259
|
+
| paddingTop | `number` | safe area top | Override top padding |
|
|
4260
|
+
| paddingBottom | `number` | safe area bottom | Override bottom padding |
|
|
4261
|
+
| paddingHorizontal | `number` | `0` | Horizontal padding |
|
|
4262
|
+
| keyboard | `boolean` | `false` | Enable `KeyboardAvoidingView` (iOS `padding` behavior) |
|
|
4263
|
+
| style | `ViewStyle` | — | Additional wrapper style |
|
|
4264
|
+
|
|
4265
|
+
**Behavior:** Applies top/bottom safe area insets, centers content horizontally when screen exceeds `maxWidth`. When `keyboard` is enabled, wraps content in `KeyboardAvoidingView` with iOS `padding` behavior.
|
|
4558
4266
|
|
|
4559
|
-
|
|
4267
|
+
**Examples:**
|
|
4560
4268
|
```tsx
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4269
|
+
// Basic scroll screen
|
|
4270
|
+
<ScreenContainer scroll paddingHorizontal={16}>
|
|
4271
|
+
<Text variant="title-md">Welcome</Text>
|
|
4272
|
+
</ScreenContainer>
|
|
4273
|
+
|
|
4274
|
+
// With keyboard avoidance for forms
|
|
4275
|
+
<ScreenContainer keyboard paddingHorizontal={16}>
|
|
4276
|
+
<Input label="Name" />
|
|
4277
|
+
<Input label="Email" />
|
|
4278
|
+
<Button label="Submit" />
|
|
4279
|
+
</ScreenContainer>
|
|
4280
|
+
|
|
4281
|
+
// Non-scroll, max width disabled
|
|
4282
|
+
<ScreenContainer maxWidth={0}>
|
|
4283
|
+
<FullWidthContent />
|
|
4284
|
+
</ScreenContainer>
|
|
4566
4285
|
```
|
|
4567
4286
|
|
|
4568
|
-
**Alternative:** Use `<CurrencyDisplay autoScale maxFontSize={48} />` for currency values — no manual sizing needed.
|
|
4569
|
-
|
|
4570
4287
|
---
|
|
4571
4288
|
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
### Touch targets
|
|
4575
|
-
All interactive elements maintain ≥44pt touch height per Apple HIG:
|
|
4576
|
-
- Button md: 48px, sm: 40px, lg: 56px
|
|
4577
|
-
- Input / Textarea: 56px (14px vertical padding)
|
|
4578
|
-
- IconButton md: 44px, lg: 52px
|
|
4579
|
-
- Checkbox: 24×24px box
|
|
4580
|
-
- Switch: 30px track height
|
|
4581
|
-
- MonthPicker arrows: 44×44px
|
|
4582
|
-
|
|
4583
|
-
### Haptic patterns
|
|
4584
|
-
- `impactLight` — Button, Card (press), Sheet open, Toast, MediaCard action
|
|
4585
|
-
- `selectionAsync` — Checkbox, Switch, Toggle, RadioGroup, Select, Slider steps, Accordion, ListItem, Chip, CategoryStrip, Tabs, MonthPicker
|
|
4586
|
-
- `notificationSuccess` — Toast `success`
|
|
4587
|
-
- `notificationError` — Toast `destructive`, Toast `warning`
|
|
4588
|
-
|
|
4589
|
-
### Animation press scales
|
|
4590
|
-
- `Button` → 0.95 (strong spring feedback)
|
|
4591
|
-
- `Card` → 0.98 (subtle, appropriate for large surfaces)
|
|
4592
|
-
- `ListItem` → 0.97 (between — medium row targets)
|
|
4593
|
-
- `MediaCard` → 0.98 (large surface)
|
|
4594
|
-
- `IconButton` → 0.95
|
|
4595
|
-
- `Chip`, `CategoryStrip chip` → 0.95
|
|
4596
|
-
|
|
4597
|
-
### Platform differences
|
|
4598
|
-
- `useNativeDriver` — `Platform.OS !== 'web'` everywhere (native driver disabled on web)
|
|
4599
|
-
- `Select` — wheel modal on iOS, dialog on Android, `<select>` on web
|
|
4600
|
-
- `Toast` — full width on mobile, 400px centered on web
|
|
4601
|
-
- Hover states — web only
|
|
4602
|
-
- `Skeleton` shimmer highlight — adapts opacity for light/dark mode
|
|
4603
|
-
|
|
4604
|
-
### Dynamic Type
|
|
4605
|
-
All `Text` and `TextInput` components have `allowFontScaling={true}` — respects user font size accessibility settings.
|
|
4606
|
-
|
|
4607
|
-
### Scaling utilities (internal)
|
|
4608
|
-
Used internally — not exported. `s()` horizontal scale, `vs()` vertical scale, `ms()` moderate scale relative to 350×680 base.
|
|
4289
|
+
### VirtualizedList
|
|
4609
4290
|
|
|
4610
|
-
|
|
4291
|
+
**Import:** `import { VirtualizedList } from '@retray-dev/ui-kit'`
|
|
4611
4292
|
|
|
4612
|
-
|
|
4293
|
+
**When to use:** Sectioned lists with sticky headers, empty state, and pull-to-refresh. Wraps React Native `SectionList` with Spanish defaults.
|
|
4613
4294
|
|
|
4614
|
-
|
|
4295
|
+
| Prop | Type | Default | Notes |
|
|
4296
|
+
|------|------|---------|-------|
|
|
4297
|
+
| sections | `VirtualizedListSection<T>[]` | required | `{ title?: string; data: T[] }` — only non-empty sections render |
|
|
4298
|
+
| renderItem | `(info) => ReactElement` | required | Standard `SectionList` render function |
|
|
4299
|
+
| emptyTitle | `string` | `'Sin contenido'` | Title shown when no data |
|
|
4300
|
+
| emptyDescription | `string` | — | Description below title |
|
|
4301
|
+
| emptyComponent | `ReactNode` | — | Custom empty state (overrides titles) |
|
|
4302
|
+
| refreshing | `boolean` | `false` | Pull-to-refresh state |
|
|
4303
|
+
| onRefresh | `() => void` | — | Refresh handler |
|
|
4304
|
+
| stickyHeaders | `boolean` | `true` | Enable sticky section headers |
|
|
4305
|
+
| headerColor | `string` | `foregroundMuted` | Section header text color |
|
|
4615
4306
|
|
|
4616
|
-
|
|
4617
|
-
2. **Edit Profile** — Avatar, Badge, Input, Select, Switch, Card, AlertBanner
|
|
4618
|
-
3. **Onboarding Flow** — Badge, Progress, Input, RadioGroup, EmptyState (multi-step wizard)
|
|
4619
|
-
4. **Settings Screen** — MenuGroup, ListGroup, Form, MenuItem, ListItem, compound component patterns
|
|
4307
|
+
**All other `SectionList` props pass through** (except `sections`, `renderItem`, `ListEmptyComponent`).
|
|
4620
4308
|
|
|
4621
|
-
**
|
|
4309
|
+
**Behavior:** Sections with empty `data` arrays are filtered out. When no sections have data, renders the empty state (custom component or default title). Sticky headers render section titles in uppercase with `Sohne-SemiBold`.
|
|
4310
|
+
|
|
4311
|
+
**Examples:**
|
|
4312
|
+
```tsx
|
|
4313
|
+
const sections = [
|
|
4314
|
+
{ title: 'Hoy', data: todayItems },
|
|
4315
|
+
{ title: 'Ayer', data: yesterdayItems },
|
|
4316
|
+
]
|
|
4317
|
+
|
|
4318
|
+
<VirtualizedList
|
|
4319
|
+
sections={sections}
|
|
4320
|
+
renderItem={({ item }) => <ListItem title={item.name} />}
|
|
4321
|
+
refreshing={refreshing}
|
|
4322
|
+
onRefresh={handleRefresh}
|
|
4323
|
+
/>
|
|
4622
4324
|
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4325
|
+
// With custom empty state
|
|
4326
|
+
<VirtualizedList
|
|
4327
|
+
sections={[]}
|
|
4328
|
+
renderItem={() => null}
|
|
4329
|
+
emptyTitle="No hay transacciones"
|
|
4330
|
+
emptyDescription="Tus transacciones aparecerán aquí"
|
|
4331
|
+
/>
|
|
4626
4332
|
```
|
|
4627
4333
|
|
|
4628
|
-
|
|
4629
|
-
- Complete component code (copy-paste ready)
|
|
4630
|
-
- State management setup
|
|
4631
|
-
- Proper imports and theme usage
|
|
4632
|
-
- Common patterns (spacing, colors, navigation, toast feedback)
|
|
4633
|
-
- StyleSheet definitions
|
|
4334
|
+
---
|
|
4634
4335
|
|
|
4635
|
-
**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.
|