@retray-dev/ui-kit 10.0.0 → 10.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/COMPONENTS.md +150 -17
  2. package/CONSUMER.md +1 -1
  3. package/README.md +4 -4
  4. package/dist/Accordion.d.mts +1 -1
  5. package/dist/Accordion.d.ts +1 -1
  6. package/dist/Accordion.js +3 -3
  7. package/dist/Accordion.mjs +2 -2
  8. package/dist/AlertBanner.js +1 -1
  9. package/dist/AlertBanner.mjs +2 -2
  10. package/dist/AppHeader.js +1 -1
  11. package/dist/AppHeader.mjs +3 -3
  12. package/dist/Badge.js +1 -1
  13. package/dist/Badge.mjs +2 -2
  14. package/dist/Button.js +1 -1
  15. package/dist/Button.mjs +2 -2
  16. package/dist/CategoryStrip.js +1 -1
  17. package/dist/CategoryStrip.mjs +2 -2
  18. package/dist/Chip.js +1 -1
  19. package/dist/Chip.mjs +2 -2
  20. package/dist/ConfirmDialog.d.mts +6 -1
  21. package/dist/ConfirmDialog.d.ts +6 -1
  22. package/dist/ConfirmDialog.js +45 -15
  23. package/dist/ConfirmDialog.mjs +3 -3
  24. package/dist/CurrencyInput.js +1 -1
  25. package/dist/CurrencyInput.mjs +3 -3
  26. package/dist/DetailRow.d.mts +1 -1
  27. package/dist/DetailRow.d.ts +1 -1
  28. package/dist/DetailRow.js +1 -1
  29. package/dist/DetailRow.mjs +2 -2
  30. package/dist/EmptyState.js +1 -1
  31. package/dist/EmptyState.mjs +3 -3
  32. package/dist/ErrorBoundary.js +1 -1
  33. package/dist/ErrorBoundary.mjs +2 -2
  34. package/dist/IconButton.js +1 -1
  35. package/dist/IconButton.mjs +2 -2
  36. package/dist/IconPicker.d.mts +17 -0
  37. package/dist/IconPicker.d.ts +17 -0
  38. package/dist/IconPicker.js +997 -0
  39. package/dist/IconPicker.mjs +7 -0
  40. package/dist/ImageUpload.d.mts +3 -1
  41. package/dist/ImageUpload.d.ts +3 -1
  42. package/dist/ImageUpload.js +28 -10
  43. package/dist/ImageUpload.mjs +1 -1
  44. package/dist/ImageViewer.js +282 -141
  45. package/dist/ImageViewer.mjs +5 -3
  46. package/dist/Input.js +1 -1
  47. package/dist/Input.mjs +2 -2
  48. package/dist/LabelValue.js +1 -1
  49. package/dist/LabelValue.mjs +2 -2
  50. package/dist/ListItem.js +1 -1
  51. package/dist/ListItem.mjs +2 -2
  52. package/dist/MediaCard.js +1 -1
  53. package/dist/MediaCard.mjs +2 -2
  54. package/dist/MenuItem.js +1 -1
  55. package/dist/MenuItem.mjs +2 -2
  56. package/dist/NumberStepper.d.mts +19 -0
  57. package/dist/NumberStepper.d.ts +19 -0
  58. package/dist/NumberStepper.js +410 -0
  59. package/dist/NumberStepper.mjs +9 -0
  60. package/dist/PagerDots.js +1 -1
  61. package/dist/PagerDots.mjs +2 -2
  62. package/dist/PricingCard.js +1 -1
  63. package/dist/PricingCard.mjs +4 -4
  64. package/dist/SelectableGrid.js +1 -1
  65. package/dist/SelectableGrid.mjs +2 -2
  66. package/dist/Sheet.js +16 -13
  67. package/dist/Sheet.mjs +1 -1
  68. package/dist/SheetSelect.js +1 -1
  69. package/dist/SheetSelect.mjs +2 -2
  70. package/dist/Switch.js +40 -17
  71. package/dist/Switch.mjs +1 -1
  72. package/dist/TabBar.js +1 -1
  73. package/dist/TabBar.mjs +2 -2
  74. package/dist/Textarea.js +1 -1
  75. package/dist/Textarea.mjs +2 -2
  76. package/dist/Toggle.js +1 -1
  77. package/dist/Toggle.mjs +2 -2
  78. package/dist/chunk-53Z3NYGE.mjs +742 -0
  79. package/dist/{chunk-VQ57HWPL.mjs → chunk-6L4G6PBT.mjs} +1 -1
  80. package/dist/{chunk-6OAZJ577.mjs → chunk-6SECQ2ZF.mjs} +2 -2
  81. package/dist/{chunk-KIHCWCWL.mjs → chunk-7LWRKMF5.mjs} +1 -1
  82. package/dist/{chunk-4I7D47FH.mjs → chunk-AJRVDP2H.mjs} +3 -3
  83. package/dist/{chunk-6MKGPAR2.mjs → chunk-BEMIQXXU.mjs} +1 -1
  84. package/dist/chunk-BUMAMSTZ.mjs +126 -0
  85. package/dist/{chunk-UREA2GYY.mjs → chunk-DYT7BG5I.mjs} +1 -1
  86. package/dist/{chunk-Z4BVUWW6.mjs → chunk-ELXBDILQ.mjs} +20 -32
  87. package/dist/{chunk-A4MDAP7G.mjs → chunk-FCSSQK3L.mjs} +1 -1
  88. package/dist/{chunk-2TFTAWVJ.mjs → chunk-HTHGSXFG.mjs} +1 -1
  89. package/dist/{chunk-VGTDN7SW.mjs → chunk-IX3NYLYQ.mjs} +1 -1
  90. package/dist/{chunk-T7XZ7H7Y.mjs → chunk-KA7LTET3.mjs} +17 -3
  91. package/dist/{chunk-URI2WBIV.mjs → chunk-KOO4WITD.mjs} +1 -1
  92. package/dist/{chunk-JUXSWN54.mjs → chunk-NMU5FMQJ.mjs} +1 -1
  93. package/dist/{chunk-LXJIIOYQ.mjs → chunk-RYZC432S.mjs} +1 -1
  94. package/dist/{chunk-JB67UOB5.mjs → chunk-S2R7UVOE.mjs} +1 -1
  95. package/dist/{chunk-ZUR7AU5R.mjs → chunk-SXLKNTA4.mjs} +1 -1
  96. package/dist/{chunk-3U4SSNWP.mjs → chunk-T2KCAHOS.mjs} +1 -1
  97. package/dist/{chunk-ZJKGQMYH.mjs → chunk-TB6SD2FT.mjs} +1 -1
  98. package/dist/{chunk-AZJF2BLK.mjs → chunk-TBNZHU6C.mjs} +1 -1
  99. package/dist/{chunk-Y4GL2MHX.mjs → chunk-TZDGAP5N.mjs} +28 -10
  100. package/dist/{chunk-CZCQZHG6.mjs → chunk-U2XJFYED.mjs} +1 -1
  101. package/dist/{chunk-TERDKCLE.mjs → chunk-VF2ATYN3.mjs} +1 -1
  102. package/dist/{chunk-OHBNABL5.mjs → chunk-VKID2D2I.mjs} +1 -1
  103. package/dist/{chunk-QKH5ZOD5.mjs → chunk-WF2XDFRK.mjs} +40 -17
  104. package/dist/{chunk-FZZLPJ6B.mjs → chunk-WYEUNUTP.mjs} +44 -15
  105. package/dist/{chunk-PFZTM6D5.mjs → chunk-Y2NS74WS.mjs} +9 -7
  106. package/dist/{chunk-O3HA6TYM.mjs → chunk-YJ7I257J.mjs} +3 -3
  107. package/dist/{chunk-NA7PARID.mjs → chunk-Z4VHZ7B5.mjs} +1 -1
  108. package/dist/{chunk-MLF3EZFW.mjs → chunk-Z6SFHN6T.mjs} +1 -1
  109. package/dist/{chunk-4K625MVM.mjs → chunk-ZZ2R6KZ3.mjs} +1 -1
  110. package/dist/index.d.mts +4 -1
  111. package/dist/index.d.ts +4 -1
  112. package/dist/index.js +1011 -88
  113. package/dist/index.mjs +34 -32
  114. package/package.json +1 -1
  115. package/src/components/Accordion/Accordion.tsx +7 -3
  116. package/src/components/ConfirmDialog/ConfirmDialog.tsx +61 -23
  117. package/src/components/DetailRow/DetailRow.tsx +1 -1
  118. package/src/components/IconPicker/IconPicker.tsx +383 -0
  119. package/src/components/IconPicker/index.ts +1 -0
  120. package/src/components/ImageUpload/ImageUpload.tsx +34 -12
  121. package/src/components/ImageViewer/ImageViewer.tsx +25 -30
  122. package/src/components/NumberStepper/NumberStepper.tsx +147 -0
  123. package/src/components/NumberStepper/index.ts +1 -0
  124. package/src/components/Sheet/Sheet.tsx +10 -9
  125. package/src/components/Switch/Switch.tsx +30 -17
  126. package/src/index.ts +3 -1
  127. package/src/utils/curatedIcons.ts +286 -0
  128. package/src/utils/icons.ts +20 -2
package/COMPONENTS.md CHANGED
@@ -1,4 +1,4 @@
1
- # @retray-dev/ui-kit — Component Reference (v10.0.0)
1
+ # @retray-dev/ui-kit — Component Reference (v10.2.0)
2
2
 
3
3
  This file is the AI reference for this package. Add all three lines below to your project's `CLAUDE.md` to give Claude full context — components, setup guide, and usage examples:
4
4
 
@@ -252,19 +252,19 @@ These are the only values you need to supply when customizing the theme. The lib
252
252
  | Token | Light Default | Dark Default | Semantic Role |
253
253
  |-------|--------------|--------------|---------------|
254
254
  | `background` | `#ffffff` | `#0f0f0f` | Screen / page background |
255
- | `foreground` | `#222222` | `#fafafa` | Primary text — deep near-black, not pure black |
255
+ | `foreground` | `#1a1a1a` | `#fafafa` | Primary text — deep near-black, not pure black |
256
256
  | `card` | `#ffffff` | `#1c1c1c` | Card / elevated surface background |
257
257
  | `primary` | `#1a1a1a` | `#fafafa` | Primary action (buttons, selected states, active indicators) |
258
258
  | `primaryForeground` | `#ffffff` | `#0f0f0f` | Text/icon placed on primary-colored backgrounds |
259
259
  | `border` | `#dddddd` | `#303030` | Borders, dividers, input outlines |
260
- | `destructive` | `#e53935` | `#ef5350` | Error / danger / delete actions |
260
+ | `destructive` | `#c72828` | `#ef5350` | Error / danger / delete actions |
261
261
  | `destructiveForeground` | `#ffffff` | `#ffffff` | Text/icon on destructive backgrounds |
262
262
  | `success` | `#1a7a45` | `#2e7d52` | Success / confirmation states |
263
263
  | `successForeground` | `#ffffff` | `#ffffff` | Text/icon on success backgrounds |
264
- | `warning` | `#e67e00` | `#f57c00` | Warning / caution states |
265
- | `warningForeground` | `#ffffff` | `#ffffff` | Text/icon on warning backgrounds |
264
+ | `warning` | `#9a5200` | `#f5a623` | Warning / caution states |
265
+ | `warningForeground` | `#ffffff` | `#0f0f0f` | Text/icon on warning backgrounds |
266
266
  | `overlay` *(optional)* | `rgba(0,0,0,0.45)` | `rgba(0,0,0,0.45)` | Backdrop/overlay color behind sheets and dialogs |
267
- | `accent` *(optional)* | same as `primary` | same as `primary` | Secondary brand accent (e.g. Airbnb coral). Falls back to `primary` |
267
+ | `accent` *(optional)* | `#d4561d` | `#e87645` | Secondary brand accent (e.g. Airbnb coral). Falls back to `primary` |
268
268
  | `accentForeground` *(optional)* | same as `primaryForeground` | same as `primaryForeground` | Text/icon on accent backgrounds. Falls back to `primaryForeground` |
269
269
 
270
270
  ### Derived Tokens (ResolvedColors) — read-only via useTheme().colors
@@ -273,8 +273,8 @@ The full palette components consume. Never supply these directly — they are co
273
273
 
274
274
  | Token | Derived From | Purpose |
275
275
  |-------|-------------|---------|
276
- | `foregroundSubtle` | `foreground` @ ~55% | Body text, subtitles, secondary content |
277
- | `foregroundMuted` | `foreground` @ ~38% | Captions, timestamps, placeholders |
276
+ | `foregroundSubtle` | `foreground` @ ~70% | Body text, subtitles, secondary content |
277
+ | `foregroundMuted` | `foreground` @ ~62% | Captions, timestamps, placeholders |
278
278
  | `surface` | `background` slightly off-canvas | Chip backgrounds, input fills, tag backgrounds, skeleton |
279
279
  | `surfaceStrong` | `background` stronger offset | Pressed/hover fill states |
280
280
  | `destructiveTint` | `destructive` blended to bg | Alert banner background, toast background |
@@ -1175,6 +1175,69 @@ const [amount, setAmount] = useState(0)
1175
1175
 
1176
1176
  ---
1177
1177
 
1178
+ ### NumberStepper
1179
+
1180
+ **Import:** `import { NumberStepper } from '@retray-dev/ui-kit'`
1181
+
1182
+ **When to use:** Quantity +/- controls — cart item counts, guest selectors, inventory adjustments, portion sizes. Use when the user needs to increment/decrement a discrete numeric value within a constrained range (min–max).
1183
+
1184
+ | Prop | Type | Default | Notes |
1185
+ |------|------|---------|-------|
1186
+ | value | `number` | required | Current numeric value |
1187
+ | onValueChange | `(value: number) => void` | required | Called with clamped value on each step |
1188
+ | min | `number` | `1` | Minimum allowed value |
1189
+ | max | `number` | `99` | Maximum allowed value |
1190
+ | step | `number` | `1` | Increment/decrement amount. Supports decimals (e.g. `0.5`) |
1191
+ | size | `'sm' \| 'md' \| 'lg'` | `'md'` | Controls button and value text size |
1192
+ | disabled | `boolean` | `false` | Disables both buttons |
1193
+ | accessibilityLabel | `string` | — | Custom a11y label for value text. Default: `"Quantity: {value}"` |
1194
+ | style | `ViewStyle` | — | Outer container style |
1195
+
1196
+ **Styling:** Row layout with two square buttons flanking a centered value label. Buttons use `RADIUS.md` (14pt), `borderWidth: 1.5`, `surface` background with `border` outline. Value text: `Sohne-Medium`, centered, `foreground` color.
1197
+
1198
+ **Button states:** The decrement button disables (opacity 0.35, non-pressable) when `value ≤ min`. The increment button disables when `value ≥ max`. Both disable when the `disabled` prop is `true`.
1199
+
1200
+ **Haptics:** `impactLight` on each press.
1201
+
1202
+ **Accessibility:** Each button has a descriptive label (`"Decrease, current value X"` / `"Increase, current value X"`). The value text has `accessibilityRole="text"`. Disabled state passed to `accessibilityState`.
1203
+
1204
+ **Animation:** `PressableButton` (scale 0.95) on each button — matches the Button/IconButton press feel.
1205
+
1206
+ **Examples:**
1207
+ ```tsx
1208
+ // Basic quantity stepper
1209
+ const [qty, setQty] = useState(1)
1210
+ <NumberStepper value={qty} onValueChange={setQty} />
1211
+
1212
+ // Custom range and step
1213
+ const [rating, setRating] = useState(3)
1214
+ <NumberStepper value={rating} onValueChange={setRating} min={0} max={5} step={0.5} />
1215
+
1216
+ // Large size for prominent use
1217
+ const [guests, setGuests] = useState(2)
1218
+ <NumberStepper value={guests} onValueChange={setGuests} min={1} max={16} size="lg" />
1219
+
1220
+ // Disabled during async operation
1221
+ <NumberStepper
1222
+ value={qty}
1223
+ onValueChange={setQty}
1224
+ disabled={isUpdating}
1225
+ />
1226
+ ```
1227
+
1228
+ **Composition — cart item row:**
1229
+ ```tsx
1230
+ <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
1231
+ <View>
1232
+ <Text variant="body-md">Margherita Pizza</Text>
1233
+ <Text variant="caption-sm" color={colors.foregroundMuted}>$12.000</Text>
1234
+ </View>
1235
+ <NumberStepper value={quantity} onValueChange={setQuantity} />
1236
+ </View>
1237
+ ```
1238
+
1239
+ ---
1240
+
1178
1241
  ### CurrencyDisplay
1179
1242
 
1180
1243
  **Import:** `import { CurrencyDisplay } from '@retray-dev/ui-kit'`
@@ -1926,7 +1989,7 @@ const [accepted, setAccepted] = useState(false)
1926
1989
 
1927
1990
  **Dimensions:** Track 52×30px (pill), Thumb 24×24px with 3px offset.
1928
1991
 
1929
- **Animation:** Thumb translates via spring; track color transitions via opacity (150ms).
1992
+ **Animation:** Thumb translates via spring (`SPRINGS.elastic`); track color and border animate via `EaseView` `COLOR_TRANSITION`; icons crossfade via `OPACITY_TRANSITION`. All declarative — UI thread.
1930
1993
 
1931
1994
  **Haptics:** `selectionAsync` on toggle.
1932
1995
 
@@ -2277,7 +2340,7 @@ const [tab, setTab] = useState('profile')
2277
2340
  ```ts
2278
2341
  {
2279
2342
  value: string
2280
- trigger: string
2343
+ trigger: string | ReactNode
2281
2344
  content: ReactNode
2282
2345
  iconName?: string // Icon name from @expo/vector-icons
2283
2346
  icon?: ReactNode // Custom icon node
@@ -2285,7 +2348,7 @@ const [tab, setTab] = useState('profile')
2285
2348
  }
2286
2349
  ```
2287
2350
 
2288
- **Animation:** `react-native-reanimated` `withTiming` (220ms) for height and chevron rotation (180°). Press scale uses `withSpring`. Runs on UI thread at 60fps.
2351
+ **Animation:** `react-native-reanimated` `withTiming` (240ms expand / 200ms collapse) for height and chevron rotation (180°). Runs on UI thread at 60fps.
2289
2352
 
2290
2353
  **Haptics:** `selectionAsync` on toggle.
2291
2354
 
@@ -2693,11 +2756,13 @@ dismiss(id)
2693
2756
  |------|------|---------|-------|
2694
2757
  | visible | `boolean` | required | Controls dialog visibility |
2695
2758
  | title | `string` | required | Dialog heading |
2696
- | description | `string` | — | Supporting text describe what exactly will happen |
2759
+ | subtitle | `string` | — | Secondary text below title |
2760
+ | description | `string` | — | **Deprecated** — use `subtitle` instead |
2697
2761
  | confirmLabel | `string` | `'Confirm'` | Confirm button text |
2698
2762
  | cancelLabel | `string` | `'Cancel'` | Cancel button text |
2699
2763
  | confirmVariant | `'primary' \| 'destructive'` | `'primary'` | Use `'destructive'` for delete/remove actions |
2700
2764
  | loading | `boolean` | `false` | Show spinner in confirm button and disable it (for async `onConfirm`) |
2765
+ | showCloseButton | `boolean` | `false` | Show an X close button in the top-right corner |
2701
2766
  | onConfirm | `() => void` | required | Called when confirm is tapped |
2702
2767
  | onCancel | `() => void` | required | Called when cancel is tapped or backdrop pressed |
2703
2768
 
@@ -3075,7 +3140,7 @@ dismiss(id)
3075
3140
 
3076
3141
  **Import:** `import { Chip, ChipGroup } from '@retray-dev/ui-kit'`
3077
3142
 
3078
- **When to use:** Inline filter options, quick selections, multi-select toggles. Use `Chip` for standalone custom logic; use `ChipGroup` for managed selection (single or multi).
3143
+ **When to use:** Inline filter options, quick selections, multi-select toggles. Use `Chip` for standalone custom logic; use `ChipGroup` for managed selection (single or multi). **ChipGroup vs CategoryStrip:** ChipGroup wraps to multiple rows and supports disabled items — best for filter tags, feature toggles, and tray-style selections. CategoryStrip scrolls horizontally and supports badge counts — best for top-of-screen category browsers.
3079
3144
 
3080
3145
  **`Chip` Props:**
3081
3146
 
@@ -3147,7 +3212,7 @@ const [categories, setCategories] = useState<number[]>([1, 3])
3147
3212
 
3148
3213
  **Import:** `import { CategoryStrip } from '@retray-dev/ui-kit'`
3149
3214
 
3150
- **When to use:** Horizontal scrollable filter/category bar at the top of browse screens — marketplace categories, content type filters, location tabs. Airbnb-style pill chips that scroll horizontally.
3215
+ **When to use:** Horizontal scrollable filter/category bar at the top of browse screens — marketplace categories, content type filters, location tabs. Airbnb-style pill chips that scroll horizontally. **CategoryStrip vs ChipGroup:** CategoryStrip scrolls horizontally and supports badge counts — best for top-of-screen category browsers. ChipGroup wraps to multiple rows and supports disabled items — best for filter tags and tray-style selections.
3151
3216
 
3152
3217
  | Prop | Type | Default | Notes |
3153
3218
  |------|------|---------|-------|
@@ -3272,7 +3337,7 @@ const [categories, setCategories] = useState<number[]>([1, 3])
3272
3337
  | Prop | Type | Default | Notes |
3273
3338
  |------|------|---------|-------|
3274
3339
  | label | `string \| ReactNode` | required | Left side. Strings auto-styled in `foregroundMuted` caption |
3275
- | value | `string` | required | Right side value text |
3340
+ | value | `string \| number \| ReactNode` | required | Right side value. Strings and numbers auto-styled in `Sohne-SemiBold`; pass `ReactNode` for custom content |
3276
3341
  | separator | `'dotted' \| 'solid' \| 'dashed' \| 'none'` | `'dotted'` | Fill line between label and value |
3277
3342
  | labelWeight | `'normal' \| 'medium' \| 'semibold' \| 'bold'` | `'normal'` | Font weight of label text |
3278
3343
  | valueColor | `string` | — | Override value text color (hex or theme token value) |
@@ -3766,7 +3831,7 @@ export default function TabLayout() {
3766
3831
  | onClose | `() => void` | required | — |
3767
3832
  | initialIndex | `number` | `0` | Page to open on |
3768
3833
 
3769
- **Behavior:** pinch to zoom (max 3×), double-tap to toggle 2.5× zoom, pan while zoomed. Paging locks automatically while a page is zoomed in. `PagerDots` shown when there is more than one image.
3834
+ **Behavior:** pinch to zoom (max 3×), double-tap to toggle 2.5× zoom, pan while zoomed. Paging locks automatically while a page is zoomed in. Swipe down to dismiss (drag down or fling; backdrop fades proportionally). `PagerDots` shown when there is more than one image.
3770
3835
 
3771
3836
  **Example:**
3772
3837
  ```tsx
@@ -3918,6 +3983,7 @@ import { HolographicCard, FOIL_PRESETS } from '@retray-dev/ui-kit/HolographicCar
3918
3983
  | onChange | `(uri: string \| null) => void` | — | Called with selected URI after picker completes |
3919
3984
  | loading | `boolean` | `false` | Show spinner overlay (e.g. while uploading to server) |
3920
3985
  | placeholder | `string` | `'Tap to add image'` | Text shown when no image selected |
3986
+ | showPlaceholderText | `boolean` | `true` | Whether to show the placeholder text. Use `false` for compact/avatar variants |
3921
3987
  | width | `number` | — | Width of the upload area. Defaults to full width |
3922
3988
  | height | `number` | `200` | Height of the upload area |
3923
3989
  | borderRadius | `number` | `RADIUS.lg` | — |
@@ -3947,16 +4013,68 @@ const handleChange = async (uri: string | null) => {
3947
4013
  height={200}
3948
4014
  />
3949
4015
 
3950
- // Avatar upload (square)
4016
+ // Avatar upload (square, no text)
3951
4017
  <ImageUpload
3952
4018
  value={avatarUri}
3953
4019
  onChange={setAvatarUri}
3954
4020
  width={100}
3955
4021
  height={100}
3956
4022
  borderRadius={50}
4023
+ showPlaceholderText={false}
4024
+ />
4025
+ ```
4026
+
4027
+ ---
4028
+
4029
+ ### IconPicker
4030
+
4031
+ **Import:** `import { IconPicker } from '@retray-dev/ui-kit'`
4032
+
4033
+ **When to use:** Selecting an icon from a curated catalog of ~320 icons (across all 6 `@expo/vector-icons` families) organized by 12 categories. The trigger is a simple tappable square showing the selected icon (or a + placeholder). Tapping opens a bottom sheet with category chips (icon + label) and a scrollable grid. No search, no text clutter — purely visual selection.
4034
+
4035
+ **Requires:** `@gorhom/bottom-sheet` (already a peer dependency of the UI kit).
4036
+
4037
+ | Prop | Type | Default | Notes |
4038
+ |------|------|---------|-------|
4039
+ | value | `string \| null` | — | Currently selected icon name |
4040
+ | onChange | `(iconName: string) => void` | — | Called when an icon is selected |
4041
+ | label | `string` | — | Optional label above the trigger |
4042
+ | error | `string` | — | Error message (red border + helper text) |
4043
+ | hint | `string` | — | Hint text below trigger |
4044
+ | disabled | `boolean` | `false` | Prevents opening |
4045
+ | numColumns | `number` | `6` | Icons per row in the sheet grid |
4046
+ | gap | `number` | `6` | Gap between cells (dp) |
4047
+ | style | `ViewStyle` | — | — |
4048
+
4049
+ **Examples:**
4050
+ ```tsx
4051
+ const [icon, setIcon] = useState<string | null>(null)
4052
+
4053
+ // Basic usage
4054
+ <IconPicker
4055
+ label="Icon"
4056
+ value={icon}
4057
+ onChange={setIcon}
4058
+ />
4059
+
4060
+ // With validation error
4061
+ <IconPicker
4062
+ label="Icon"
4063
+ value={icon}
4064
+ onChange={setIcon}
4065
+ error={!icon ? 'Please select an icon' : undefined}
3957
4066
  />
3958
4067
  ```
3959
4068
 
4069
+ **Notes:**
4070
+ - Uses a static curated list of ~320 icons in 12 categories (food, sports, business, objects, status, actions, communication, navigation, media, layout, nature, brands) — instant load, no runtime glyphMap scanning.
4071
+ - Category chips use representative icons (coffee, activity, briefcase, folder, alert-circle, edit-3, message-circle, compass, image, grid, sun, globe) with Spanish labels and a "Todos" (All) chip.
4072
+ - Grid cells show only icons — clean, fast visual scanning. Cell size adapts to container width.
4073
+ - Sheet uses `enableDynamicSizing` with `maxDynamicContentSize` (70% screen height) — height auto-fits content up to that cap.
4074
+ - Category strip is a horizontal `ScrollView` of pill-shaped chips. The grid is rendered in rows inside a `BottomSheetScrollView`.
4075
+ - Selection closes the sheet immediately and resets category to "Todos".
4076
+ - Haptics: medium impact on open, selection on icon tap.
4077
+
3960
4078
  ---
3961
4079
 
3962
4080
  ### useConfirmDialog
@@ -4061,6 +4179,21 @@ import { renderIcon } from '@retray-dev/ui-kit'
4061
4179
  const icon = renderIcon('check', 18, colors.primary)
4062
4180
  ```
4063
4181
 
4182
+ ### `getValidIconNames` utility
4183
+
4184
+ ```tsx
4185
+ import { getValidIconNames } from '@retray-dev/ui-kit'
4186
+
4187
+ // All icon names across all configured families
4188
+ const allIcons: string[] = getValidIconNames()
4189
+ // => ["home", "user", "heart", "star", ...]
4190
+
4191
+ // Scoped to specific families (does not mutate global config)
4192
+ const featherOnly: string[] = getValidIconNames(['Feather'])
4193
+ ```
4194
+
4195
+ Returns `string[]` — all resolvable icon names from the configured families. Respects `configureIconFamilies()` global config. The optional `families` parameter builds a temporary cache scoped to those families without mutating global state. Use to build icon pickers, search, and galleries.
4196
+
4064
4197
  ### `iconName` props on components
4065
4198
 
4066
4199
  All components with icon slots accept `iconName` — auto-resolved size and color:
package/CONSUMER.md CHANGED
@@ -1,4 +1,4 @@
1
- # @retray-dev/ui-kit — Consumer Setup Guide (v9.2.0)
1
+ # @retray-dev/ui-kit — Consumer Setup Guide (v10.1.0)
2
2
 
3
3
  > **For AI assistants (Claude Code):** Add all three lines below to your project's `CLAUDE.md` so Claude has full context about this UI kit:
4
4
  > ```markdown
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A personal React Native / Expo UI component library with a built-in design system, dark mode support, haptic feedback, and smooth animations.
4
4
 
5
- - 51 components across 9 categories (plus the deep-import `HolographicCard`)
5
+ - 53 components across 9 categories (plus the deep-import `HolographicCard`)
6
6
  - Light/dark theme with 12 public tokens (25 resolved) and full customization
7
7
  - Apple HIG–compliant touch targets and haptic feedback
8
8
  - Animated interactions: spring press, sliding tabs, accordion easing, animated progress
@@ -23,14 +23,14 @@ pnpm add @retray-dev/ui-kit
23
23
  Install these in your app if not already present:
24
24
 
25
25
  ```bash
26
- 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
26
+ 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 expo-image-picker
27
27
  ```
28
28
 
29
29
  For Expo projects, run `npx expo install` instead to get SDK-compatible versions.
30
30
 
31
31
  `pressto` is **required** — it powers the press animations on every interactive component. Omitting it crashes the import. `sonner-native` is **required** for `Toast`.
32
32
 
33
- **Optional:** for richer haptics in a custom dev build (not Expo Go), also `pnpm add react-native-pulsar`. The kit falls back to `expo-haptics` automatically when it is absent. For the deep-import `HolographicCard`, add `@shopify/react-native-skia expo-sensors`.
33
+ **Optional:** for richer haptics in a custom dev build (not Expo Go), also `pnpm add react-native-pulsar`. The kit falls back to `expo-haptics` automatically when it is absent. For `ImageUpload`, add `expo-image-picker`. For the deep-import `HolographicCard`, add `@shopify/react-native-skia expo-sensors`.
34
34
 
35
35
  Add the Worklets Babel plugin to `babel.config.js` (required by `@gorhom/bottom-sheet`):
36
36
 
@@ -183,7 +183,7 @@ import { SPACING, ICON_SIZES, RADIUS, SHADOWS, BREAKPOINTS, TYPOGRAPHY } from '@
183
183
  | ----------- | ----------------------------------------------------------------------------------------------- |
184
184
  | Display | `Text`, `Badge`, `Avatar`, `Separator`, `Spinner`, `Skeleton`, `Progress`, `CurrencyDisplay` |
185
185
  | Surfaces | `Card`, `AlertBanner`, `EmptyState`, `MediaCard`, `PricingCard` |
186
- | Form | `Form` (+ `Form.Field` / `Form.Section` / `Form.Footer`), `Button`, `ButtonGroup`, `IconButton`, `Input`, `CurrencyInput`, `Textarea`, `Checkbox`, `Switch`, `Toggle`, `RadioGroup`, `Select`, `Slider`, `SelectableGrid`, `SheetSelect`, `ImageUpload` |
186
+ | Form | `Form` (+ `Form.Field` / `Form.Section` / `Form.Footer`), `Button`, `ButtonGroup`, `IconButton`, `Input`, `CurrencyInput`, `Textarea`, `Checkbox`, `Switch`, `Toggle`, `RadioGroup`, `Select`, `Slider`, `SelectableGrid`, `SheetSelect`, `ImageUpload`, `IconPicker`, `NumberStepper` |
187
187
  | Composition | `Tabs`, `Accordion` |
188
188
  | Navigation | `AppHeader`, `TabBar`, `PagerDots` |
189
189
  | Overlays | `Sheet`, `ConfirmDialog`, `ImageViewer` |
@@ -3,7 +3,7 @@ import { ViewStyle } from 'react-native';
3
3
 
4
4
  interface AccordionItem {
5
5
  value: string;
6
- trigger: string;
6
+ trigger: string | React.ReactNode;
7
7
  content: React.ReactNode;
8
8
  /** Icon name from @expo/vector-icons rendered left of trigger. */
9
9
  iconName?: string;
@@ -3,7 +3,7 @@ import { ViewStyle } from 'react-native';
3
3
 
4
4
  interface AccordionItem {
5
5
  value: string;
6
- trigger: string;
6
+ trigger: string | React.ReactNode;
7
7
  content: React.ReactNode;
8
8
  /** Icon name from @expo/vector-icons rendered left of trigger. */
9
9
  iconName?: string;
package/dist/Accordion.js CHANGED
@@ -217,7 +217,7 @@ var ALL_FAMILIES = [
217
217
  ];
218
218
  var activeFamilies = ALL_FAMILIES;
219
219
  var resolvedCache = null;
220
- function buildCache() {
220
+ function buildCache(families) {
221
221
  const cache = /* @__PURE__ */ new Map();
222
222
  for (const family of activeFamilies) {
223
223
  const glyphMap = family.getGlyphMap();
@@ -302,9 +302,9 @@ function AccordionItemComponent({
302
302
  },
303
303
  accessibilityRole: "button",
304
304
  accessibilityState: { expanded: isOpen },
305
- accessibilityLabel: item.trigger
305
+ accessibilityLabel: typeof item.trigger === "string" ? item.trigger : void 0
306
306
  },
307
- /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.triggerContent }, resolvedIcon ? /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.icon }, resolvedIcon) : null, /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.triggerText, { color: colors.foreground }], allowFontScaling: true }, item.trigger)),
307
+ /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.triggerContent }, resolvedIcon ? /* @__PURE__ */ React3__default.default.createElement(reactNative.View, { style: styles.icon }, resolvedIcon) : null, typeof item.trigger === "string" ? /* @__PURE__ */ React3__default.default.createElement(reactNative.Text, { style: [styles.triggerText, { color: colors.foreground }], allowFontScaling: true }, item.trigger) : item.trigger),
308
308
  /* @__PURE__ */ React3__default.default.createElement(Animated__default.default.View, { style: [styles.chevron, rotationStyle] }, /* @__PURE__ */ React3__default.default.createElement(vectorIcons.Entypo, { name: "chevron-down", size: 18, color: colors.foregroundMuted }))
309
309
  ), /* @__PURE__ */ React3__default.default.createElement(Animated__default.default.View, { style: bodyStyle }, /* @__PURE__ */ React3__default.default.createElement(
310
310
  reactNative.View,
@@ -1,7 +1,7 @@
1
- export { Accordion } from './chunk-O3HA6TYM.mjs';
1
+ export { Accordion } from './chunk-YJ7I257J.mjs';
2
2
  import './chunk-EJ7ZPXOH.mjs';
3
3
  import './chunk-DVK4G2GT.mjs';
4
- import './chunk-T7XZ7H7Y.mjs';
4
+ import './chunk-KA7LTET3.mjs';
5
5
  import './chunk-SOYNZDVY.mjs';
6
6
  import './chunk-2CE3TQVY.mjs';
7
7
  import './chunk-Y6FXYEAI.mjs';
@@ -156,7 +156,7 @@ var ALL_FAMILIES = [
156
156
  ];
157
157
  var activeFamilies = ALL_FAMILIES;
158
158
  var resolvedCache = null;
159
- function buildCache() {
159
+ function buildCache(families) {
160
160
  const cache = /* @__PURE__ */ new Map();
161
161
  for (const family of activeFamilies) {
162
162
  const glyphMap = family.getGlyphMap();
@@ -1,6 +1,6 @@
1
- export { AlertBanner } from './chunk-6MKGPAR2.mjs';
1
+ export { AlertBanner } from './chunk-BEMIQXXU.mjs';
2
2
  import './chunk-QY3X2UYR.mjs';
3
- import './chunk-T7XZ7H7Y.mjs';
3
+ import './chunk-KA7LTET3.mjs';
4
4
  import './chunk-SOYNZDVY.mjs';
5
5
  import './chunk-2CE3TQVY.mjs';
6
6
  import './chunk-Y6FXYEAI.mjs';
package/dist/AppHeader.js CHANGED
@@ -218,7 +218,7 @@ var ALL_FAMILIES = [
218
218
  ];
219
219
  var activeFamilies = ALL_FAMILIES;
220
220
  var resolvedCache = null;
221
- function buildCache() {
221
+ function buildCache(families) {
222
222
  const cache = /* @__PURE__ */ new Map();
223
223
  for (const family of activeFamilies) {
224
224
  const glyphMap = family.getGlyphMap();
@@ -1,10 +1,10 @@
1
- export { AppHeader } from './chunk-AZJF2BLK.mjs';
2
- import './chunk-3U4SSNWP.mjs';
1
+ export { AppHeader } from './chunk-TBNZHU6C.mjs';
2
+ import './chunk-T2KCAHOS.mjs';
3
3
  import './chunk-3DKJ2GIC.mjs';
4
4
  import './chunk-EJ7ZPXOH.mjs';
5
5
  import './chunk-DVK4G2GT.mjs';
6
6
  import './chunk-QY3X2UYR.mjs';
7
- import './chunk-T7XZ7H7Y.mjs';
7
+ import './chunk-KA7LTET3.mjs';
8
8
  import './chunk-SOYNZDVY.mjs';
9
9
  import './chunk-2CE3TQVY.mjs';
10
10
  import './chunk-Y6FXYEAI.mjs';
package/dist/Badge.js CHANGED
@@ -155,7 +155,7 @@ var ALL_FAMILIES = [
155
155
  ];
156
156
  var activeFamilies = ALL_FAMILIES;
157
157
  var resolvedCache = null;
158
- function buildCache() {
158
+ function buildCache(families) {
159
159
  const cache = /* @__PURE__ */ new Map();
160
160
  for (const family of activeFamilies) {
161
161
  const glyphMap = family.getGlyphMap();
package/dist/Badge.mjs CHANGED
@@ -1,5 +1,5 @@
1
- export { Badge } from './chunk-TERDKCLE.mjs';
2
- import './chunk-T7XZ7H7Y.mjs';
1
+ export { Badge } from './chunk-VF2ATYN3.mjs';
2
+ import './chunk-KA7LTET3.mjs';
3
3
  import './chunk-SOYNZDVY.mjs';
4
4
  import './chunk-2CE3TQVY.mjs';
5
5
  import './chunk-Y6FXYEAI.mjs';
package/dist/Button.js CHANGED
@@ -217,7 +217,7 @@ var ALL_FAMILIES = [
217
217
  ];
218
218
  var activeFamilies = ALL_FAMILIES;
219
219
  var resolvedCache = null;
220
- function buildCache() {
220
+ function buildCache(families) {
221
221
  const cache = /* @__PURE__ */ new Map();
222
222
  for (const family of activeFamilies) {
223
223
  const glyphMap = family.getGlyphMap();
package/dist/Button.mjs CHANGED
@@ -1,9 +1,9 @@
1
- export { Button } from './chunk-2TFTAWVJ.mjs';
1
+ export { Button } from './chunk-HTHGSXFG.mjs';
2
2
  import './chunk-3DKJ2GIC.mjs';
3
3
  import './chunk-EJ7ZPXOH.mjs';
4
4
  import './chunk-DVK4G2GT.mjs';
5
5
  import './chunk-QY3X2UYR.mjs';
6
- import './chunk-T7XZ7H7Y.mjs';
6
+ import './chunk-KA7LTET3.mjs';
7
7
  import './chunk-SOYNZDVY.mjs';
8
8
  import './chunk-2CE3TQVY.mjs';
9
9
  import './chunk-Y6FXYEAI.mjs';
@@ -217,7 +217,7 @@ var ALL_FAMILIES = [
217
217
  ];
218
218
  var activeFamilies = ALL_FAMILIES;
219
219
  var resolvedCache = null;
220
- function buildCache() {
220
+ function buildCache(families) {
221
221
  const cache = /* @__PURE__ */ new Map();
222
222
  for (const family of activeFamilies) {
223
223
  const glyphMap = family.getGlyphMap();
@@ -1,9 +1,9 @@
1
- export { CategoryStrip } from './chunk-VQ57HWPL.mjs';
1
+ export { CategoryStrip } from './chunk-6L4G6PBT.mjs';
2
2
  import './chunk-YNROWHQJ.mjs';
3
3
  import './chunk-EJ7ZPXOH.mjs';
4
4
  import './chunk-DVK4G2GT.mjs';
5
5
  import './chunk-QY3X2UYR.mjs';
6
- import './chunk-T7XZ7H7Y.mjs';
6
+ import './chunk-KA7LTET3.mjs';
7
7
  import './chunk-SOYNZDVY.mjs';
8
8
  import './chunk-2CE3TQVY.mjs';
9
9
  import './chunk-Y6FXYEAI.mjs';
package/dist/Chip.js CHANGED
@@ -218,7 +218,7 @@ var ALL_FAMILIES = [
218
218
  ];
219
219
  var activeFamilies = ALL_FAMILIES;
220
220
  var resolvedCache = null;
221
- function buildCache() {
221
+ function buildCache(families) {
222
222
  const cache = /* @__PURE__ */ new Map();
223
223
  for (const family of activeFamilies) {
224
224
  const glyphMap = family.getGlyphMap();
package/dist/Chip.mjs CHANGED
@@ -1,8 +1,8 @@
1
- export { Chip, ChipGroup } from './chunk-UREA2GYY.mjs';
1
+ export { Chip, ChipGroup } from './chunk-DYT7BG5I.mjs';
2
2
  import './chunk-3DKJ2GIC.mjs';
3
3
  import './chunk-EJ7ZPXOH.mjs';
4
4
  import './chunk-DVK4G2GT.mjs';
5
- import './chunk-T7XZ7H7Y.mjs';
5
+ import './chunk-KA7LTET3.mjs';
6
6
  import './chunk-SOYNZDVY.mjs';
7
7
  import './chunk-2CE3TQVY.mjs';
8
8
  import './chunk-Y6FXYEAI.mjs';
@@ -3,15 +3,20 @@ import React from 'react';
3
3
  interface ConfirmDialogProps {
4
4
  visible: boolean;
5
5
  title: string;
6
+ /** Secondary text below title. */
7
+ subtitle?: string;
8
+ /** @deprecated Use `subtitle` instead. */
6
9
  description?: string;
7
10
  confirmLabel?: string;
8
11
  cancelLabel?: string;
9
12
  confirmVariant?: 'primary' | 'destructive';
10
13
  /** Show a loading spinner in the confirm button (e.g. while async action completes). */
11
14
  loading?: boolean;
15
+ /** Show an X close button in the top-right corner. */
16
+ showCloseButton?: boolean;
12
17
  onConfirm: () => void;
13
18
  onCancel: () => void;
14
19
  }
15
- declare function ConfirmDialog({ visible, title, description, confirmLabel, cancelLabel, confirmVariant, loading, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
20
+ declare function ConfirmDialog({ visible, title, subtitle, description, confirmLabel, cancelLabel, confirmVariant, loading, showCloseButton, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
16
21
 
17
22
  export { ConfirmDialog, type ConfirmDialogProps };
@@ -3,15 +3,20 @@ import React from 'react';
3
3
  interface ConfirmDialogProps {
4
4
  visible: boolean;
5
5
  title: string;
6
+ /** Secondary text below title. */
7
+ subtitle?: string;
8
+ /** @deprecated Use `subtitle` instead. */
6
9
  description?: string;
7
10
  confirmLabel?: string;
8
11
  cancelLabel?: string;
9
12
  confirmVariant?: 'primary' | 'destructive';
10
13
  /** Show a loading spinner in the confirm button (e.g. while async action completes). */
11
14
  loading?: boolean;
15
+ /** Show an X close button in the top-right corner. */
16
+ showCloseButton?: boolean;
12
17
  onConfirm: () => void;
13
18
  onCancel: () => void;
14
19
  }
15
- declare function ConfirmDialog({ visible, title, description, confirmLabel, cancelLabel, confirmVariant, loading, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
20
+ declare function ConfirmDialog({ visible, title, subtitle, description, confirmLabel, cancelLabel, confirmVariant, loading, showCloseButton, onConfirm, onCancel, }: ConfirmDialogProps): React.JSX.Element;
16
21
 
17
22
  export { ConfirmDialog, type ConfirmDialogProps };