@webority-technologies/mobile 0.0.15 → 0.0.20

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 (202) hide show
  1. package/lib/commonjs/components/Accordion/Accordion.js +60 -19
  2. package/lib/commonjs/components/AppBar/AppBar.js +29 -20
  3. package/lib/commonjs/components/Avatar/Avatar.js +38 -8
  4. package/lib/commonjs/components/Badge/Badge.js +66 -4
  5. package/lib/commonjs/components/Banner/Banner.js +146 -66
  6. package/lib/commonjs/components/BottomNavigation/BottomNavigation.js +37 -15
  7. package/lib/commonjs/components/BottomSheet/BottomSheet.js +78 -53
  8. package/lib/commonjs/components/Button/Button.js +12 -5
  9. package/lib/commonjs/components/Card/Card.js +106 -16
  10. package/lib/commonjs/components/Carousel/Carousel.js +66 -12
  11. package/lib/commonjs/components/Checkbox/Checkbox.js +11 -7
  12. package/lib/commonjs/components/Chip/Chip.js +44 -12
  13. package/lib/commonjs/components/DatePicker/DatePicker.js +185 -76
  14. package/lib/commonjs/components/DateRangePicker/DateRangePicker.js +133 -59
  15. package/lib/commonjs/components/Dialog/Dialog.js +16 -10
  16. package/lib/commonjs/components/Drawer/Drawer.js +13 -10
  17. package/lib/commonjs/components/FieldBase/FieldBase.js +306 -0
  18. package/lib/commonjs/components/FieldBase/index.js +32 -0
  19. package/lib/commonjs/components/FloatingActionButton/FloatingActionButton.js +69 -44
  20. package/lib/commonjs/components/ForceUpdateDialog/ForceUpdateDialog.js +8 -2
  21. package/lib/commonjs/components/FormField/FormField.js +3 -2
  22. package/lib/commonjs/components/ImageGallery/ImageGallery.js +132 -44
  23. package/lib/commonjs/components/Input/Input.js +144 -181
  24. package/lib/commonjs/components/ListItem/ListItem.js +90 -11
  25. package/lib/commonjs/components/Modal/Modal.js +55 -27
  26. package/lib/commonjs/components/NumberInput/NumberInput.js +60 -106
  27. package/lib/commonjs/components/OTPInput/OTPInput.js +65 -58
  28. package/lib/commonjs/components/PickerTrigger/PickerTrigger.js +185 -0
  29. package/lib/commonjs/components/{AppIcon → PickerTrigger}/index.js +4 -4
  30. package/lib/commonjs/components/ProgressBar/ProgressBar.js +19 -11
  31. package/lib/commonjs/components/Radio/Radio.js +11 -6
  32. package/lib/commonjs/components/Rating/Rating.js +85 -19
  33. package/lib/commonjs/components/SearchBar/SearchBar.js +84 -107
  34. package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +22 -11
  35. package/lib/commonjs/components/Select/Select.js +62 -91
  36. package/lib/commonjs/components/Skeleton/Skeleton.js +131 -174
  37. package/lib/commonjs/components/Skeleton/SkeletonClock.js +117 -0
  38. package/lib/commonjs/components/Skeleton/SkeletonContent.js +164 -81
  39. package/lib/commonjs/components/Skeleton/SkeletonProvider.js +72 -10
  40. package/lib/commonjs/components/Skeleton/index.js +17 -16
  41. package/lib/commonjs/components/Slider/Slider.js +44 -25
  42. package/lib/commonjs/components/Stepper/Stepper.js +199 -29
  43. package/lib/commonjs/components/Swipeable/Swipeable.js +36 -19
  44. package/lib/commonjs/components/Switch/Switch.js +9 -2
  45. package/lib/commonjs/components/Tabs/Tabs.js +84 -21
  46. package/lib/commonjs/components/TimePicker/TimePicker.js +123 -45
  47. package/lib/commonjs/components/Toast/Toast.js +27 -16
  48. package/lib/commonjs/components/Tooltip/Tooltip.js +56 -32
  49. package/lib/commonjs/components/index.js +37 -37
  50. package/lib/commonjs/theme/tokens.js +55 -7
  51. package/lib/module/components/Accordion/Accordion.js +61 -20
  52. package/lib/module/components/AppBar/AppBar.js +29 -20
  53. package/lib/module/components/Avatar/Avatar.js +39 -9
  54. package/lib/module/components/Badge/Badge.js +67 -5
  55. package/lib/module/components/Banner/Banner.js +147 -67
  56. package/lib/module/components/BottomNavigation/BottomNavigation.js +37 -15
  57. package/lib/module/components/BottomSheet/BottomSheet.js +80 -55
  58. package/lib/module/components/Button/Button.js +12 -5
  59. package/lib/module/components/Card/Card.js +107 -17
  60. package/lib/module/components/Carousel/Carousel.js +67 -13
  61. package/lib/module/components/Checkbox/Checkbox.js +11 -7
  62. package/lib/module/components/Chip/Chip.js +45 -13
  63. package/lib/module/components/DatePicker/DatePicker.js +185 -76
  64. package/lib/module/components/DateRangePicker/DateRangePicker.js +134 -60
  65. package/lib/module/components/Dialog/Dialog.js +16 -10
  66. package/lib/module/components/Drawer/Drawer.js +13 -10
  67. package/lib/module/components/FieldBase/FieldBase.js +297 -0
  68. package/lib/module/components/FieldBase/index.js +4 -0
  69. package/lib/module/components/FloatingActionButton/FloatingActionButton.js +69 -44
  70. package/lib/module/components/ForceUpdateDialog/ForceUpdateDialog.js +8 -2
  71. package/lib/module/components/FormField/FormField.js +3 -2
  72. package/lib/module/components/ImageGallery/ImageGallery.js +128 -40
  73. package/lib/module/components/Input/Input.js +144 -179
  74. package/lib/module/components/ListItem/ListItem.js +91 -12
  75. package/lib/module/components/Modal/Modal.js +55 -27
  76. package/lib/module/components/NumberInput/NumberInput.js +60 -106
  77. package/lib/module/components/OTPInput/OTPInput.js +65 -58
  78. package/lib/module/components/PickerTrigger/PickerTrigger.js +181 -0
  79. package/lib/module/components/PickerTrigger/index.js +4 -0
  80. package/lib/module/components/ProgressBar/ProgressBar.js +19 -11
  81. package/lib/module/components/Radio/Radio.js +11 -6
  82. package/lib/module/components/Rating/Rating.js +86 -20
  83. package/lib/module/components/SearchBar/SearchBar.js +84 -107
  84. package/lib/module/components/SegmentedControl/SegmentedControl.js +22 -11
  85. package/lib/module/components/Select/Select.js +62 -91
  86. package/lib/module/components/Skeleton/Skeleton.js +135 -175
  87. package/lib/module/components/Skeleton/SkeletonClock.js +110 -0
  88. package/lib/module/components/Skeleton/SkeletonContent.js +167 -84
  89. package/lib/module/components/Skeleton/SkeletonProvider.js +71 -10
  90. package/lib/module/components/Skeleton/index.js +3 -2
  91. package/lib/module/components/Slider/Slider.js +44 -25
  92. package/lib/module/components/Stepper/Stepper.js +201 -31
  93. package/lib/module/components/Swipeable/Swipeable.js +36 -19
  94. package/lib/module/components/Switch/Switch.js +9 -2
  95. package/lib/module/components/Tabs/Tabs.js +84 -21
  96. package/lib/module/components/TimePicker/TimePicker.js +123 -45
  97. package/lib/module/components/Toast/Toast.js +27 -16
  98. package/lib/module/components/Tooltip/Tooltip.js +56 -32
  99. package/lib/module/components/index.js +2 -2
  100. package/lib/module/theme/tokens.js +55 -7
  101. package/lib/typescript/commonjs/components/Accordion/Accordion.d.ts +10 -5
  102. package/lib/typescript/commonjs/components/AppBar/AppBar.d.ts +8 -0
  103. package/lib/typescript/commonjs/components/Avatar/Avatar.d.ts +12 -6
  104. package/lib/typescript/commonjs/components/Badge/Badge.d.ts +7 -6
  105. package/lib/typescript/commonjs/components/Banner/Banner.d.ts +17 -6
  106. package/lib/typescript/commonjs/components/BottomSheet/BottomSheet.d.ts +7 -0
  107. package/lib/typescript/commonjs/components/Card/Card.d.ts +17 -6
  108. package/lib/typescript/commonjs/components/Carousel/Carousel.d.ts +7 -6
  109. package/lib/typescript/commonjs/components/Checkbox/Checkbox.d.ts +9 -1
  110. package/lib/typescript/commonjs/components/Chip/Chip.d.ts +13 -6
  111. package/lib/typescript/commonjs/components/DatePicker/DatePicker.d.ts +38 -3
  112. package/lib/typescript/commonjs/components/DateRangePicker/DateRangePicker.d.ts +36 -3
  113. package/lib/typescript/commonjs/components/Dialog/Dialog.d.ts +13 -1
  114. package/lib/typescript/commonjs/components/FieldBase/FieldBase.d.ts +141 -0
  115. package/lib/typescript/commonjs/components/FieldBase/index.d.ts +3 -0
  116. package/lib/typescript/commonjs/components/FloatingActionButton/FloatingActionButton.d.ts +8 -6
  117. package/lib/typescript/commonjs/components/FloatingActionButton/index.d.ts +1 -1
  118. package/lib/typescript/commonjs/components/ForceUpdateDialog/ForceUpdateDialog.d.ts +7 -0
  119. package/lib/typescript/commonjs/components/FormField/FormField.d.ts +7 -0
  120. package/lib/typescript/commonjs/components/ImageGallery/ImageGallery.d.ts +6 -4
  121. package/lib/typescript/commonjs/components/Input/Input.d.ts +6 -0
  122. package/lib/typescript/commonjs/components/ListItem/ListItem.d.ts +13 -6
  123. package/lib/typescript/commonjs/components/NumberInput/NumberInput.d.ts +3 -0
  124. package/lib/typescript/commonjs/components/PickerTrigger/PickerTrigger.d.ts +57 -0
  125. package/lib/typescript/commonjs/components/PickerTrigger/index.d.ts +3 -0
  126. package/lib/typescript/commonjs/components/ProgressBar/ProgressBar.d.ts +2 -0
  127. package/lib/typescript/commonjs/components/Radio/Radio.d.ts +3 -0
  128. package/lib/typescript/commonjs/components/Rating/Rating.d.ts +9 -6
  129. package/lib/typescript/commonjs/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  130. package/lib/typescript/commonjs/components/Skeleton/Skeleton.d.ts +49 -20
  131. package/lib/typescript/commonjs/components/Skeleton/SkeletonClock.d.ts +60 -0
  132. package/lib/typescript/commonjs/components/Skeleton/SkeletonContent.d.ts +80 -19
  133. package/lib/typescript/commonjs/components/Skeleton/SkeletonProvider.d.ts +39 -5
  134. package/lib/typescript/commonjs/components/Skeleton/index.d.ts +6 -4
  135. package/lib/typescript/commonjs/components/Slider/Slider.d.ts +12 -1
  136. package/lib/typescript/commonjs/components/Stepper/Stepper.d.ts +18 -6
  137. package/lib/typescript/commonjs/components/Swipeable/Swipeable.d.ts +2 -0
  138. package/lib/typescript/commonjs/components/Switch/Switch.d.ts +1 -0
  139. package/lib/typescript/commonjs/components/Tabs/Tabs.d.ts +26 -2
  140. package/lib/typescript/commonjs/components/TimePicker/TimePicker.d.ts +36 -3
  141. package/lib/typescript/commonjs/components/Toast/Toast.d.ts +8 -0
  142. package/lib/typescript/commonjs/components/Tooltip/Tooltip.d.ts +7 -1
  143. package/lib/typescript/commonjs/components/index.d.ts +5 -5
  144. package/lib/typescript/commonjs/index.d.ts +1 -1
  145. package/lib/typescript/commonjs/theme/index.d.ts +1 -1
  146. package/lib/typescript/commonjs/theme/types.d.ts +553 -11
  147. package/lib/typescript/module/components/Accordion/Accordion.d.ts +10 -5
  148. package/lib/typescript/module/components/AppBar/AppBar.d.ts +8 -0
  149. package/lib/typescript/module/components/Avatar/Avatar.d.ts +12 -6
  150. package/lib/typescript/module/components/Badge/Badge.d.ts +7 -6
  151. package/lib/typescript/module/components/Banner/Banner.d.ts +17 -6
  152. package/lib/typescript/module/components/BottomSheet/BottomSheet.d.ts +7 -0
  153. package/lib/typescript/module/components/Card/Card.d.ts +17 -6
  154. package/lib/typescript/module/components/Carousel/Carousel.d.ts +7 -6
  155. package/lib/typescript/module/components/Checkbox/Checkbox.d.ts +9 -1
  156. package/lib/typescript/module/components/Chip/Chip.d.ts +13 -6
  157. package/lib/typescript/module/components/DatePicker/DatePicker.d.ts +38 -3
  158. package/lib/typescript/module/components/DateRangePicker/DateRangePicker.d.ts +36 -3
  159. package/lib/typescript/module/components/Dialog/Dialog.d.ts +13 -1
  160. package/lib/typescript/module/components/FieldBase/FieldBase.d.ts +141 -0
  161. package/lib/typescript/module/components/FieldBase/index.d.ts +3 -0
  162. package/lib/typescript/module/components/FloatingActionButton/FloatingActionButton.d.ts +8 -6
  163. package/lib/typescript/module/components/FloatingActionButton/index.d.ts +1 -1
  164. package/lib/typescript/module/components/ForceUpdateDialog/ForceUpdateDialog.d.ts +7 -0
  165. package/lib/typescript/module/components/FormField/FormField.d.ts +7 -0
  166. package/lib/typescript/module/components/ImageGallery/ImageGallery.d.ts +6 -4
  167. package/lib/typescript/module/components/Input/Input.d.ts +6 -0
  168. package/lib/typescript/module/components/ListItem/ListItem.d.ts +13 -6
  169. package/lib/typescript/module/components/NumberInput/NumberInput.d.ts +3 -0
  170. package/lib/typescript/module/components/PickerTrigger/PickerTrigger.d.ts +57 -0
  171. package/lib/typescript/module/components/PickerTrigger/index.d.ts +3 -0
  172. package/lib/typescript/module/components/ProgressBar/ProgressBar.d.ts +2 -0
  173. package/lib/typescript/module/components/Radio/Radio.d.ts +3 -0
  174. package/lib/typescript/module/components/Rating/Rating.d.ts +9 -6
  175. package/lib/typescript/module/components/SegmentedControl/SegmentedControl.d.ts +3 -0
  176. package/lib/typescript/module/components/Skeleton/Skeleton.d.ts +49 -20
  177. package/lib/typescript/module/components/Skeleton/SkeletonClock.d.ts +60 -0
  178. package/lib/typescript/module/components/Skeleton/SkeletonContent.d.ts +80 -19
  179. package/lib/typescript/module/components/Skeleton/SkeletonProvider.d.ts +39 -5
  180. package/lib/typescript/module/components/Skeleton/index.d.ts +6 -4
  181. package/lib/typescript/module/components/Slider/Slider.d.ts +12 -1
  182. package/lib/typescript/module/components/Stepper/Stepper.d.ts +18 -6
  183. package/lib/typescript/module/components/Swipeable/Swipeable.d.ts +2 -0
  184. package/lib/typescript/module/components/Switch/Switch.d.ts +1 -0
  185. package/lib/typescript/module/components/Tabs/Tabs.d.ts +26 -2
  186. package/lib/typescript/module/components/TimePicker/TimePicker.d.ts +36 -3
  187. package/lib/typescript/module/components/Toast/Toast.d.ts +8 -0
  188. package/lib/typescript/module/components/Tooltip/Tooltip.d.ts +7 -1
  189. package/lib/typescript/module/components/index.d.ts +5 -5
  190. package/lib/typescript/module/index.d.ts +1 -1
  191. package/lib/typescript/module/theme/index.d.ts +1 -1
  192. package/lib/typescript/module/theme/types.d.ts +553 -11
  193. package/package.json +2 -6
  194. package/lib/commonjs/components/AppIcon/AppIcon.js +0 -120
  195. package/lib/commonjs/types/vector-icons.d.js +0 -2
  196. package/lib/module/components/AppIcon/AppIcon.js +0 -111
  197. package/lib/module/components/AppIcon/index.js +0 -4
  198. package/lib/module/types/vector-icons.d.js +0 -2
  199. package/lib/typescript/commonjs/components/AppIcon/AppIcon.d.ts +0 -20
  200. package/lib/typescript/commonjs/components/AppIcon/index.d.ts +0 -3
  201. package/lib/typescript/module/components/AppIcon/AppIcon.d.ts +0 -20
  202. package/lib/typescript/module/components/AppIcon/index.d.ts +0 -3
@@ -24,6 +24,11 @@ const Banner = exports.Banner = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
24
24
  loading = false,
25
25
  accessibilityLabel,
26
26
  style,
27
+ containerStyle,
28
+ iconCircleStyle,
29
+ tintBarStyle,
30
+ closeButtonStyle,
31
+ actionButtonStyle,
27
32
  testID
28
33
  } = props;
29
34
  const theme = (0, _index.useTheme)();
@@ -77,7 +82,7 @@ const Banner = exports.Banner = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
77
82
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
78
83
  style: [styles.iconCircle, {
79
84
  backgroundColor: tint.bar + '22'
80
- }],
85
+ }, iconCircleStyle],
81
86
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
82
87
  style: [styles.iconGlyph, {
83
88
  color: tint.glyphColor
@@ -104,11 +109,11 @@ const Banner = exports.Banner = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
104
109
  transform: [{
105
110
  translateY
106
111
  }]
107
- }, style],
112
+ }, style, containerStyle],
108
113
  children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
109
114
  style: [styles.tintBar, {
110
115
  backgroundColor: tint.bar
111
- }]
116
+ }, tintBarStyle]
112
117
  }), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
113
118
  style: styles.row,
114
119
  children: [renderIcon(), /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
@@ -145,11 +150,11 @@ const Banner = exports.Banner = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
145
150
  pressed
146
151
  }) => [styles.closeBtn, {
147
152
  backgroundColor: pressed ? theme.colors.surface.pressed : 'transparent'
148
- }],
153
+ }, closeButtonStyle],
149
154
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
150
155
  style: {
151
156
  color: theme.colors.text.secondary,
152
- fontSize: 18,
157
+ fontSize: theme.components.banner?.closeGlyphFontSize ?? 18,
153
158
  fontWeight: theme.typography.fontWeight.bold,
154
159
  lineHeight: 18
155
160
  },
@@ -169,7 +174,7 @@ const Banner = exports.Banner = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
169
174
  pressed
170
175
  }) => [styles.actionBtn, {
171
176
  backgroundColor: pressed ? theme.colors.surface.pressed : 'transparent'
172
- }],
177
+ }, actionButtonStyle],
173
178
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
174
179
  style: {
175
180
  color: isPrimary ? tint.glyphColor : theme.colors.text.secondary,
@@ -184,15 +189,86 @@ const Banner = exports.Banner = /*#__PURE__*/(0, _react.forwardRef)((props, ref)
184
189
  }) : null]
185
190
  });
186
191
  if (loading) {
187
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.SkeletonContent, {
188
- loading: true,
189
- mode: "auto",
190
- children: rendered
192
+ return /*#__PURE__*/(0, _jsxRuntime.jsx)(BannerSkeleton, {
193
+ ...props
191
194
  });
192
195
  }
193
196
  return rendered;
194
197
  });
195
198
  Banner.displayName = 'Banner';
199
+
200
+ /**
201
+ * Placeholder shape for `<Banner>`. Keeps the icon circle on the left and
202
+ * shows title + 1-2 message lines + optional action button row. Outer
203
+ * container retains the elevated background so the banner footprint
204
+ * doesn't shift when it loads.
205
+ */
206
+ const BannerSkeleton = ({
207
+ title,
208
+ actions,
209
+ icon,
210
+ containerStyle,
211
+ style
212
+ }) => {
213
+ const theme = (0, _index.useTheme)();
214
+ const showIcon = icon !== false;
215
+ return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
216
+ style: [{
217
+ flexDirection: 'row',
218
+ alignItems: 'flex-start',
219
+ backgroundColor: theme.colors.background.elevated,
220
+ borderRadius: theme.radius.md,
221
+ padding: theme.spacing.md,
222
+ borderWidth: 1,
223
+ borderColor: theme.colors.border.primary
224
+ }, containerStyle, style],
225
+ accessibilityRole: "progressbar",
226
+ accessibilityState: {
227
+ busy: true
228
+ },
229
+ children: [showIcon ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
230
+ style: {
231
+ marginRight: theme.spacing.sm
232
+ },
233
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.Skeleton, {
234
+ shape: "circle",
235
+ width: 32,
236
+ height: 32
237
+ })
238
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
239
+ style: {
240
+ flex: 1
241
+ },
242
+ children: [title !== undefined ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.Skeleton, {
243
+ shape: "text",
244
+ width: "55%",
245
+ height: 16,
246
+ style: {
247
+ marginBottom: theme.spacing.xs
248
+ }
249
+ }) : null, /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.SkeletonText, {
250
+ lines: 2,
251
+ fontSize: 13,
252
+ lastLineWidth: "70%"
253
+ }), actions && actions.length > 0 ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
254
+ style: {
255
+ flexDirection: 'row',
256
+ marginTop: theme.spacing.sm
257
+ },
258
+ children: actions.map((_, i) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_index2.Skeleton, {
259
+ width: 72,
260
+ height: 28,
261
+ shape: "rounded",
262
+ style: {
263
+ marginRight: theme.spacing.sm
264
+ }
265
+ }, `sk-banner-action-${i}`))
266
+ }) : null]
267
+ })]
268
+ });
269
+ };
270
+ BannerSkeleton.displayName = 'BannerSkeleton';
271
+ Banner.Skeleton = BannerSkeleton;
196
272
  const tintFor = (theme, variant) => {
197
273
  switch (variant) {
198
274
  case 'success':
@@ -228,61 +304,65 @@ const tintFor = (theme, variant) => {
228
304
  };
229
305
  }
230
306
  };
231
- const buildStyles = _theme => _reactNative.StyleSheet.create({
232
- container: {
233
- overflow: 'hidden',
234
- paddingLeft: 16
235
- },
236
- tintBar: {
237
- position: 'absolute',
238
- left: 0,
239
- top: 0,
240
- bottom: 0,
241
- width: 4
242
- },
243
- row: {
244
- flexDirection: 'row',
245
- alignItems: 'flex-start'
246
- },
247
- iconWrap: {
248
- marginRight: 12
249
- },
250
- iconCircle: {
251
- width: 28,
252
- height: 28,
253
- borderRadius: 14,
254
- alignItems: 'center',
255
- justifyContent: 'center',
256
- marginRight: 12
257
- },
258
- iconGlyph: {
259
- fontSize: 15,
260
- fontWeight: '700',
261
- lineHeight: 17
262
- },
263
- textBlock: {
264
- flex: 1,
265
- paddingRight: 8
266
- },
267
- closeBtn: {
268
- width: 28,
269
- height: 28,
270
- borderRadius: 14,
271
- alignItems: 'center',
272
- justifyContent: 'center',
273
- marginLeft: 4
274
- },
275
- actionsRow: {
276
- flexDirection: 'row',
277
- justifyContent: 'flex-end',
278
- marginTop: 8,
279
- gap: 4
280
- },
281
- actionBtn: {
282
- paddingHorizontal: 10,
283
- paddingVertical: 6,
284
- borderRadius: 8
285
- }
286
- });
307
+ const buildStyles = theme => {
308
+ const iconCircleSize = theme.components.banner?.iconCircleSize ?? 28;
309
+ const closeButtonSize = theme.components.banner?.closeButtonSize ?? 28;
310
+ return _reactNative.StyleSheet.create({
311
+ container: {
312
+ overflow: 'hidden',
313
+ paddingLeft: theme.components.banner?.containerPaddingLeft ?? theme.spacing.md
314
+ },
315
+ tintBar: {
316
+ position: 'absolute',
317
+ left: 0,
318
+ top: 0,
319
+ bottom: 0,
320
+ width: theme.components.banner?.tintBarWidth ?? 4
321
+ },
322
+ row: {
323
+ flexDirection: 'row',
324
+ alignItems: 'flex-start'
325
+ },
326
+ iconWrap: {
327
+ marginRight: theme.components.banner?.iconGap ?? theme.spacing.sm
328
+ },
329
+ iconCircle: {
330
+ width: iconCircleSize,
331
+ height: iconCircleSize,
332
+ borderRadius: theme.components.banner?.iconCircleRadius ?? iconCircleSize / 2,
333
+ alignItems: 'center',
334
+ justifyContent: 'center',
335
+ marginRight: theme.components.banner?.iconGap ?? theme.spacing.sm
336
+ },
337
+ iconGlyph: {
338
+ fontSize: theme.components.banner?.iconGlyphFontSize ?? 15,
339
+ fontWeight: '700',
340
+ lineHeight: theme.components.banner?.iconGlyphLineHeight ?? 17
341
+ },
342
+ textBlock: {
343
+ flex: 1,
344
+ paddingRight: theme.components.banner?.textBlockPaddingRight ?? theme.spacing.xs
345
+ },
346
+ closeBtn: {
347
+ width: closeButtonSize,
348
+ height: closeButtonSize,
349
+ borderRadius: theme.components.banner?.closeButtonRadius ?? closeButtonSize / 2,
350
+ alignItems: 'center',
351
+ justifyContent: 'center',
352
+ marginLeft: theme.components.banner?.closeButtonMarginLeft ?? theme.spacing.xxs ?? 4
353
+ },
354
+ actionsRow: {
355
+ flexDirection: 'row',
356
+ justifyContent: 'flex-end',
357
+ marginTop: theme.components.banner?.actionsRowMarginTop ?? theme.spacing.xs,
358
+ gap: theme.components.banner?.actionsRowGap ?? 4
359
+ },
360
+ actionBtn: {
361
+ paddingHorizontal: theme.components.banner?.actionButtonPaddingHorizontal ?? theme.spacing.sm,
362
+ paddingVertical: theme.components.banner?.actionButtonPaddingVertical ?? 6,
363
+ borderRadius: theme.components.banner?.actionButtonRadius ?? theme.radius.sm
364
+ }
365
+ });
366
+ };
287
367
  var _default = exports.default = Banner;
288
368
  //# sourceMappingURL=Banner.js.map
@@ -11,9 +11,11 @@ var _index = require("../../theme/index.js");
11
11
  var _index2 = require("../../utils/index.js");
12
12
  var _jsxRuntime = require("react/jsx-runtime");
13
13
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
14
- const ICON_SIZE = 24;
15
- const PILL_HEIGHT = 36;
16
- const UNDERLINE_HEIGHT = 3;
14
+ const DEFAULT_ICON_SIZE = 24;
15
+ const DEFAULT_PILL_HEIGHT = 36;
16
+ const DEFAULT_PILL_MAX_WIDTH = 120;
17
+ const DEFAULT_PILL_INSET = 16;
18
+ const DEFAULT_UNDERLINE_HEIGHT = 3;
17
19
  const formatBadge = value => {
18
20
  if (value === null || value === undefined || value === false) return null;
19
21
  if (value === true || value === '·') return {
@@ -62,8 +64,19 @@ const BottomNavigation = exports.BottomNavigation = /*#__PURE__*/(0, _react.forw
62
64
  const theme = (0, _index.useTheme)();
63
65
  const insets = (0, _reactNativeSafeAreaContext.useSafeAreaInsets)();
64
66
  const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
67
+ const tokens = theme.components.bottomNavigation;
68
+ const iconSize = tokens?.iconSize ?? DEFAULT_ICON_SIZE;
69
+ const pillHeight = tokens?.pillHeight ?? DEFAULT_PILL_HEIGHT;
70
+ const pillMaxWidth = tokens?.pillMaxWidth ?? DEFAULT_PILL_MAX_WIDTH;
71
+ const pillInset = tokens?.pillInset ?? DEFAULT_PILL_INSET;
65
72
  const activeIndex = Math.max(0, tabs.findIndex(tab => tab.key === activeTab));
66
73
  const [tabWidth, setTabWidth] = _react.default.useState(0);
74
+
75
+ // Single Animated.Value holds the pill's absolute pixel offset (active index
76
+ // × tab width + half the gap between tab edge and pill edge). Folding the
77
+ // offset into the same value avoids creating a fresh Animated.Value +
78
+ // Animated.add node on every render — earlier code did that and slowly
79
+ // leaked addition nodes as the user pressed tabs.
67
80
  const indicatorTranslateX = (0, _react.useRef)((0, _index.createAnimatedValue)(0)).current;
68
81
  const iconScales = (0, _react.useRef)({});
69
82
 
@@ -74,7 +87,17 @@ const BottomNavigation = exports.BottomNavigation = /*#__PURE__*/(0, _react.forw
74
87
  }
75
88
  });
76
89
 
77
- // Animate indicator when active tab changes or layout settles
90
+ // Pill width is intrinsic; we compute its rendered offset to keep it
91
+ // centered within each tab cell. The offset is applied to the pill itself
92
+ // (NOT folded into the shared indicatorTranslateX) so the underline indicator
93
+ // — which spans the full tab cell — stays aligned with the tab boundary
94
+ // instead of being shifted by the pill-centering math.
95
+ const pillWidth = tabWidth > 0 ? Math.min(tabWidth - pillInset, pillMaxWidth) : 0;
96
+ const pillOffset = tabWidth > 0 ? (tabWidth - pillWidth) / 2 : 0;
97
+
98
+ // Animate indicator to the active tab's cell edge. Pill mode applies the
99
+ // centering offset via a static `left` style below; underline mode uses
100
+ // this value directly.
78
101
  (0, _react.useEffect)(() => {
79
102
  if (tabWidth <= 0) return;
80
103
  _reactNative.Animated.spring(indicatorTranslateX, {
@@ -110,11 +133,6 @@ const BottomNavigation = exports.BottomNavigation = /*#__PURE__*/(0, _react.forw
110
133
  onTabPress(tab.key);
111
134
  };
112
135
  const paddingBottom = Math.max(8, insets.bottom);
113
-
114
- // Pill width is intrinsic; we compute its rendered offset to keep it centered
115
- // within each tab cell when it animates.
116
- const pillWidth = tabWidth > 0 ? Math.min(tabWidth - 16, 120) : 0;
117
- const pillOffset = tabWidth > 0 ? (tabWidth - pillWidth) / 2 : 0;
118
136
  return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
119
137
  ref: ref,
120
138
  testID: testID,
@@ -131,11 +149,12 @@ const BottomNavigation = exports.BottomNavigation = /*#__PURE__*/(0, _react.forw
131
149
  pointerEvents: "none",
132
150
  style: [styles.pill, {
133
151
  width: pillWidth,
134
- height: PILL_HEIGHT,
152
+ height: pillHeight,
153
+ left: pillOffset,
135
154
  backgroundColor: theme.colors.primaryMuted,
136
155
  borderRadius: theme.radius.full,
137
156
  transform: [{
138
- translateX: _reactNative.Animated.add(indicatorTranslateX, new _reactNative.Animated.Value(pillOffset))
157
+ translateX: indicatorTranslateX
139
158
  }]
140
159
  }, indicatorStyle]
141
160
  }) : null, tabs.map((tab, index) => {
@@ -167,13 +186,15 @@ const BottomNavigation = exports.BottomNavigation = /*#__PURE__*/(0, _react.forw
167
186
  testID: testID ? `${testID}-tab-${tab.key}` : undefined,
168
187
  children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Animated.View, {
169
188
  style: [styles.iconWrap, {
189
+ width: iconSize + 8,
190
+ height: iconSize + 8,
170
191
  transform: [{
171
192
  scale
172
193
  }]
173
194
  }],
174
195
  children: [renderIcon({
175
196
  color,
176
- size: ICON_SIZE,
197
+ size: iconSize,
177
198
  focused: isActive
178
199
  }), badge ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
179
200
  style: [badge.isDot ? styles.badgeDot : styles.badgePill, {
@@ -201,6 +222,7 @@ const BottomNavigation = exports.BottomNavigation = /*#__PURE__*/(0, _react.forw
201
222
  pointerEvents: "none",
202
223
  style: [styles.underline, indicatorPosition === 'top' ? styles.underlineTop : styles.underlineBottom, {
203
224
  width: tabWidth,
225
+ height: tokens?.underlineHeight ?? DEFAULT_UNDERLINE_HEIGHT,
204
226
  backgroundColor: theme.colors.primary,
205
227
  transform: [{
206
228
  translateX: indicatorTranslateX
@@ -231,8 +253,8 @@ const buildStyles = theme => _reactNative.StyleSheet.create({
231
253
  gap: 2
232
254
  },
233
255
  iconWrap: {
234
- width: ICON_SIZE + 8,
235
- height: ICON_SIZE + 8,
256
+ width: DEFAULT_ICON_SIZE + 8,
257
+ height: DEFAULT_ICON_SIZE + 8,
236
258
  alignItems: 'center',
237
259
  justifyContent: 'center',
238
260
  position: 'relative'
@@ -250,7 +272,7 @@ const buildStyles = theme => _reactNative.StyleSheet.create({
250
272
  underline: {
251
273
  position: 'absolute',
252
274
  left: 0,
253
- height: UNDERLINE_HEIGHT
275
+ height: DEFAULT_UNDERLINE_HEIGHT
254
276
  },
255
277
  underlineBottom: {
256
278
  bottom: -6,
@@ -47,7 +47,6 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
47
47
  onAnimate,
48
48
  enablePanDownToClose = true,
49
49
  enableBackdropPress = true,
50
- backdropOpacity = 0.5,
51
50
  keyboardBehavior = 'none',
52
51
  mode = 'modal',
53
52
  handleIndicatorStyle,
@@ -89,6 +88,11 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
89
88
  // synchronously on open and unmount only after the close animation finishes
90
89
  // so the slide-down stays visible.
91
90
  const [modalVisible, setModalVisible] = (0, _react.useState)(false);
91
+ // Inline-mode mirror of modalVisible: gates the absoluteFill backdrop +
92
+ // sheet tree so a closed inline sheet doesn't sit over the parent dimming
93
+ // it and swallowing touches. Set true on expand, cleared in
94
+ // markAnimationDone once the close animation completes.
95
+ const [inlineMounted, setInlineMounted] = (0, _react.useState)(false);
92
96
 
93
97
  // Convert a snap-point index → translateY position. -1 = closed.
94
98
  const yForIndex = (0, _react.useCallback)(idx => {
@@ -116,18 +120,20 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
116
120
  }, [onAnimate, translateY]);
117
121
  const markAnimationDone = (0, _react.useCallback)(() => {
118
122
  isAnimatingRef.current = false;
119
- // If we just finished a close animation, unmount the modal wrapper.
120
- if (mode === 'modal' && currentIndexShared.value < 0) {
121
- setModalVisible(false);
123
+ // If we just finished a close animation, unmount the sheet wrapper.
124
+ if (currentIndexShared.value < 0) {
125
+ if (mode === 'modal') setModalVisible(false);else setInlineMounted(false);
122
126
  }
123
127
  }, [mode, currentIndexShared]);
124
128
  const expand = (0, _react.useCallback)(idx => {
125
129
  const target = typeof idx === 'number' ? clamp(idx, 0, resolvedSnapPoints.length - 1) : currentIndex >= 0 ? currentIndex : 0;
126
130
  const fromIndex = currentIndexShared.value;
127
131
  const to = yForIndex(target);
128
- // Mount the Modal before kicking off the spring so the sheet has a
129
- // host to animate into.
130
- if (mode === 'modal') setModalVisible(true);
132
+ // Mount the host (Modal in modal mode, inline overlay in inline mode)
133
+ // before kicking off the spring so the sheet has somewhere to animate
134
+ // into. Inline mode also gates the backdrop on this so a closed sheet
135
+ // doesn't dim the parent.
136
+ if (mode === 'modal') setModalVisible(true);else setInlineMounted(true);
131
137
  // Reset translateY to the closed position so the first open animates
132
138
  // up from off-screen rather than snapping in place.
133
139
  if (fromIndex < 0) {
@@ -296,23 +302,22 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
296
302
  // status bar / notch.
297
303
  const lowerBound = keyboardOffset.value < 0 ? safeAreaTop : minTopY;
298
304
  const clamped = yWithKb < lowerBound ? lowerBound : yWithKb;
305
+ // Animate `top` (layout prop) instead of `transform: translateY`
306
+ // (visual only). Layout-prop animation under Reanimated still runs on
307
+ // the UI thread, but crucially the sheet's *hit-test box* moves with
308
+ // its visual position. With translateY the layout box stayed parked
309
+ // at top:0..maxSnap regardless of visual offset, so:
310
+ // (a) the empty top half intercepted backdrop taps (worked around
311
+ // previously via pointerEvents="box-none"), and
312
+ // (b) drag gestures on the handle could be claimed by sibling
313
+ // gesture handlers because the actual sheet view was effectively
314
+ // hidden from the responder chain.
315
+ // Animating `top` collapses both problems into nothing — visible area
316
+ // == hit area — so we can drop the box-none hack too.
299
317
  return {
300
- transform: [{
301
- translateY: clamped
302
- }]
318
+ top: clamped
303
319
  };
304
320
  });
305
- const backdropStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
306
- // 0 opacity when sheet is closed, full backdropOpacity at minSnap height (and above).
307
- const closedAt = screenHeight;
308
- const openAt = screenHeight - minSnapPoint;
309
- const opacity = (0, _reactNativeReanimated.interpolate)(translateY.value, [closedAt, openAt], [0, backdropOpacity], _reactNativeReanimated.Extrapolation.CLAMP);
310
- return {
311
- opacity,
312
- // Disable hit-test entirely when fully closed so it doesn't swallow taps.
313
- pointerEvents: translateY.value >= closedAt - 0.5 ? 'none' : 'auto'
314
- };
315
- }, [backdropOpacity, minSnapPoint, screenHeight]);
316
321
  const isExpanded = currentIndex >= 0;
317
322
  const styles = (0, _react.useMemo)(() => buildStyles(theme), [theme]);
318
323
  const handleBackdropPress = (0, _react.useCallback)(() => {
@@ -321,46 +326,51 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
321
326
  close();
322
327
  }, [enableBackdropPress, close]);
323
328
 
324
- // Don't render the heavy gesture tree at all when nothing's been opened yet.
325
- // For inline mode we keep the legacy "mount on first open" behavior; modal
326
- // mode is gated entirely by `modalVisible`.
327
- const everOpenedRef = (0, _react.useRef)(false);
328
- if (isExpanded || controlledIndex !== undefined) {
329
- everOpenedRef.current = true;
330
- }
331
- if (mode === 'inline' && !everOpenedRef.current) {
329
+ // RNGH Tap gesture for the backdrop. Plain RN Pressable on a sibling of a
330
+ // gesture-handler/reanimated sheet doesn't fire reliably on RN 0.85 / Fabric
331
+ // (the gesture system swallows the touch sequence). gorhom/bottom-sheet uses
332
+ // the same Gesture.Tap() pattern for the same reason.
333
+ const backdropTapGesture = (0, _react.useMemo)(() => _reactNativeGestureHandler.Gesture.Tap().maxDuration(250).onEnd((_e, success) => {
334
+ 'worklet';
335
+
336
+ if (!success) return;
337
+ (0, _reactNativeReanimated.runOnJS)(handleBackdropPress)();
338
+ }), [handleBackdropPress]);
339
+
340
+ // Don't render the backdrop / sheet tree when the sheet is fully closed.
341
+ // Inline: gated by `inlineMounted` (set in expand, cleared in
342
+ // markAnimationDone after close animation) — keeps an idle inline sheet
343
+ // from sitting over the parent and swallowing touches.
344
+ // Modal: gated by `modalVisible` for the same reason.
345
+ if (mode === 'inline' && !inlineMounted) {
332
346
  return null;
333
347
  }
334
348
  if (mode === 'modal' && !modalVisible) {
335
349
  return null;
336
350
  }
337
351
  const sheetTree = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
338
- style: _reactNative.StyleSheet.absoluteFill,
339
- pointerEvents: "box-none",
352
+ collapsable: false
353
+ // Modal mode: the backdrop is the modal's root, so it fills via flex.
354
+ // Inline mode: the backdrop overlays the parent, so it positions absolute.
355
+ ,
356
+ style: [mode === 'modal' ? styles.backdropModal : styles.backdropInline, {
357
+ backgroundColor: theme.colors.background.overlay
358
+ }],
340
359
  testID: testID,
341
360
  accessibilityViewIsModal: accessibilityViewIsModal ?? isExpanded,
342
- children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
343
- style: [styles.backdrop,
344
- // Solid scrim — the theme `background.overlay` token bakes in alpha
345
- // and double-multiplies with `backdropOpacity`, leaving the backdrop
346
- // looking washed out. Standard modal scrims are solid black so the
347
- // prop controls the actual final opacity.
348
- {
349
- backgroundColor: '#000'
350
- }, backdropStyle],
351
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
352
- style: _reactNative.StyleSheet.absoluteFill,
353
- onPress: handleBackdropPress,
361
+ children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
362
+ gesture: backdropTapGesture,
363
+ children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeReanimated.default.View, {
364
+ style: _reactNative.StyleSheet.absoluteFillObject,
354
365
  accessibilityRole: "button",
355
- accessibilityLabel: "Close bottom sheet",
356
- disabled: !enableBackdropPress || !isExpanded
366
+ accessibilityLabel: "Close bottom sheet"
357
367
  })
358
368
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureDetector, {
359
369
  gesture: panGesture,
360
370
  children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNativeReanimated.default.View, {
361
371
  // 'dialog' is a valid platform role on web/iOS; RN's typings list it as a string union
362
372
  // that varies by platform — cast keeps strict-mode happy.
363
- accessibilityRole: 'dialog',
373
+ accessibilityRole: "alert",
364
374
  accessibilityLabel: accessibilityLabel,
365
375
  accessibilityViewIsModal: accessibilityViewIsModal ?? isExpanded,
366
376
  style: [styles.sheet, {
@@ -376,7 +386,7 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
376
386
  pointerEvents: "box-none",
377
387
  children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
378
388
  style: [styles.handle, {
379
- backgroundColor: theme.colors.border.primary
389
+ backgroundColor: theme.components.bottomSheet?.handleColor ?? theme.colors.border.primary
380
390
  }, handleIndicatorStyle]
381
391
  })
382
392
  }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
@@ -391,14 +401,17 @@ const BottomSheet = exports.BottomSheet = /*#__PURE__*/(0, _react.forwardRef)((p
391
401
  transparent: true,
392
402
  visible: modalVisible,
393
403
  onRequestClose: close,
394
- statusBarTranslucent: true,
395
- presentationStyle: "overFullScreen",
404
+ statusBarTranslucent: true
405
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
406
+ // @ts-ignore — typed in RN 0.73+, the runtime prop exists on 0.71+
407
+ ,
408
+ navigationBarTranslucent: true,
396
409
  animationType: "none",
397
410
  supportedOrientations: ['portrait', 'landscape'],
398
- children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureHandlerRootView, {
411
+ children: _reactNative.Platform.OS === 'android' ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeGestureHandler.GestureHandlerRootView, {
399
412
  style: styles.modalRoot,
400
413
  children: sheetTree
401
- })
414
+ }) : sheetTree
402
415
  });
403
416
  }
404
417
  return sheetTree;
@@ -418,14 +431,26 @@ const resolveSnapPoints = (points, screenH) => {
418
431
  };
419
432
  const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
420
433
  const buildStyles = _theme => _reactNative.StyleSheet.create({
421
- backdrop: {
434
+ backdropModal: {
435
+ // The backdrop is the modal's root View. On Fabric, an absolute-
436
+ // positioned root inside RNModal has no positioned ancestor and
437
+ // collapses to zero size, taking the scrim with it. `flex: 1` fills
438
+ // the modal content area reliably.
439
+ flex: 1
440
+ },
441
+ backdropInline: {
442
+ // Inline mode: overlay the parent. Parent must be positioned for this
443
+ // to take effect (the consumer is expected to render BottomSheet inside
444
+ // a relatively-positioned container).
422
445
  ..._reactNative.StyleSheet.absoluteFillObject
423
446
  },
424
447
  sheet: {
425
448
  position: 'absolute',
426
449
  left: 0,
427
450
  right: 0,
428
- top: 0,
451
+ // `top` is driven by the animated style (see sheetStyle). Omitted
452
+ // here so the static `0` doesn't fight the animated value during
453
+ // initial paint.
429
454
  overflow: 'hidden'
430
455
  },
431
456
  handleRow: {
@@ -235,41 +235,48 @@ const toneFor = (theme, tone) => {
235
235
  };
236
236
  }
237
237
  };
238
+ // Every variant carries the SAME borderWidth (with a transparent colour on
239
+ // the non-outline ones). RN's layout treats border as part of the outer box,
240
+ // so a 1-px border on outline-only would make outline buttons 2 px wider /
241
+ // taller than solid / ghost buttons of identical content. Painting a
242
+ // transparent border on the others equalises the geometry without visible
243
+ // effect. Match the value with `BUTTON_BORDER_WIDTH` everywhere.
244
+ const BUTTON_BORDER_WIDTH = 1;
238
245
  const variantFor = (theme, variant, toneSet, disabled) => {
239
246
  switch (variant) {
240
247
  case 'solid':
241
248
  return {
242
249
  backgroundColor: disabled ? theme.colors.surface.disabled : toneSet.base,
243
250
  borderColor: 'transparent',
244
- borderWidth: 0,
251
+ borderWidth: BUTTON_BORDER_WIDTH,
245
252
  textColor: disabled ? theme.colors.text.disabled : toneSet.on
246
253
  };
247
254
  case 'outline':
248
255
  return {
249
256
  backgroundColor: 'transparent',
250
257
  borderColor: disabled ? theme.colors.border.primary : toneSet.base,
251
- borderWidth: 1,
258
+ borderWidth: BUTTON_BORDER_WIDTH,
252
259
  textColor: disabled ? theme.colors.text.disabled : toneSet.base
253
260
  };
254
261
  case 'ghost':
255
262
  return {
256
263
  backgroundColor: 'transparent',
257
264
  borderColor: 'transparent',
258
- borderWidth: 0,
265
+ borderWidth: BUTTON_BORDER_WIDTH,
259
266
  textColor: disabled ? theme.colors.text.disabled : toneSet.base
260
267
  };
261
268
  case 'link':
262
269
  return {
263
270
  backgroundColor: 'transparent',
264
271
  borderColor: 'transparent',
265
- borderWidth: 0,
272
+ borderWidth: BUTTON_BORDER_WIDTH,
266
273
  textColor: disabled ? theme.colors.text.disabled : theme.colors.text.link
267
274
  };
268
275
  default:
269
276
  return {
270
277
  backgroundColor: toneSet.base,
271
278
  borderColor: 'transparent',
272
- borderWidth: 0,
279
+ borderWidth: BUTTON_BORDER_WIDTH,
273
280
  textColor: toneSet.on
274
281
  };
275
282
  }