@retray-dev/ui-kit 6.2.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +997 -20
- package/EXAMPLES.md +250 -2
- package/README.md +21 -14
- package/dist/Accordion.d.mts +28 -0
- package/dist/Accordion.d.ts +28 -0
- package/dist/Accordion.js +392 -0
- package/dist/Accordion.mjs +7 -0
- package/dist/AlertBanner.d.mts +16 -0
- package/dist/AlertBanner.d.ts +16 -0
- package/dist/AlertBanner.js +250 -0
- package/dist/AlertBanner.mjs +6 -0
- package/dist/AppHeader.d.mts +40 -0
- package/dist/AppHeader.d.ts +40 -0
- package/dist/AppHeader.js +515 -0
- package/dist/AppHeader.mjs +10 -0
- package/dist/Avatar.d.mts +20 -0
- package/dist/Avatar.d.ts +20 -0
- package/dist/Avatar.js +244 -0
- package/dist/Avatar.mjs +4 -0
- package/dist/Badge.d.mts +26 -0
- package/dist/Badge.d.ts +26 -0
- package/dist/Badge.js +257 -0
- package/dist/Badge.mjs +5 -0
- package/dist/Button.d.mts +30 -0
- package/dist/Button.d.ts +30 -0
- package/dist/Button.js +432 -0
- package/dist/Button.mjs +9 -0
- package/dist/ButtonGroup.d.mts +26 -0
- package/dist/ButtonGroup.d.ts +26 -0
- package/dist/ButtonGroup.js +52 -0
- package/dist/ButtonGroup.mjs +3 -0
- package/dist/Card.d.mts +39 -0
- package/dist/Card.d.ts +39 -0
- package/dist/Card.js +349 -0
- package/dist/Card.mjs +8 -0
- package/dist/CategoryStrip.d.mts +26 -0
- package/dist/CategoryStrip.d.ts +26 -0
- package/dist/CategoryStrip.js +453 -0
- package/dist/CategoryStrip.mjs +9 -0
- package/dist/Checkbox.d.mts +14 -0
- package/dist/Checkbox.d.ts +14 -0
- package/dist/Checkbox.js +336 -0
- package/dist/Checkbox.mjs +7 -0
- package/dist/Chip.d.mts +31 -0
- package/dist/Chip.d.ts +31 -0
- package/dist/Chip.js +403 -0
- package/dist/Chip.mjs +8 -0
- package/dist/ConfirmDialog.d.mts +15 -0
- package/dist/ConfirmDialog.d.ts +15 -0
- package/dist/ConfirmDialog.js +560 -0
- package/dist/ConfirmDialog.mjs +10 -0
- package/dist/CurrencyDisplay.d.mts +24 -0
- package/dist/CurrencyDisplay.d.ts +24 -0
- package/dist/CurrencyDisplay.js +189 -0
- package/dist/CurrencyDisplay.mjs +4 -0
- package/dist/CurrencyInput.d.mts +26 -0
- package/dist/CurrencyInput.d.ts +26 -0
- package/dist/CurrencyInput.js +408 -0
- package/dist/CurrencyInput.mjs +8 -0
- package/dist/DetailRow.d.mts +32 -0
- package/dist/DetailRow.d.ts +32 -0
- package/dist/DetailRow.js +275 -0
- package/dist/DetailRow.mjs +5 -0
- package/dist/EmptyState.d.mts +27 -0
- package/dist/EmptyState.d.ts +27 -0
- package/dist/EmptyState.js +523 -0
- package/dist/EmptyState.mjs +10 -0
- package/dist/ErrorBoundary.d.mts +42 -0
- package/dist/ErrorBoundary.d.ts +42 -0
- package/dist/ErrorBoundary.js +351 -0
- package/dist/ErrorBoundary.mjs +7 -0
- package/dist/Form.d.mts +52 -0
- package/dist/Form.d.ts +52 -0
- package/dist/Form.js +204 -0
- package/dist/Form.mjs +4 -0
- package/dist/HolographicCard.d.mts +55 -0
- package/dist/HolographicCard.d.ts +55 -0
- package/dist/HolographicCard.js +316 -0
- package/dist/HolographicCard.mjs +191 -0
- package/dist/IconButton.d.mts +27 -0
- package/dist/IconButton.d.ts +27 -0
- package/dist/IconButton.js +400 -0
- package/dist/IconButton.mjs +8 -0
- package/dist/ImageViewer.d.mts +23 -0
- package/dist/ImageViewer.d.ts +23 -0
- package/dist/ImageViewer.js +582 -0
- package/dist/ImageViewer.mjs +8 -0
- package/dist/Input.d.mts +23 -0
- package/dist/Input.d.ts +23 -0
- package/dist/Input.js +351 -0
- package/dist/Input.mjs +7 -0
- package/dist/LabelValue.d.mts +16 -0
- package/dist/LabelValue.d.ts +16 -0
- package/dist/LabelValue.js +225 -0
- package/dist/LabelValue.mjs +5 -0
- package/dist/ListGroup.d.mts +34 -0
- package/dist/ListGroup.d.ts +34 -0
- package/dist/ListGroup.js +217 -0
- package/dist/ListGroup.mjs +5 -0
- package/dist/ListItem.d.mts +64 -0
- package/dist/ListItem.d.ts +64 -0
- package/dist/ListItem.js +444 -0
- package/dist/ListItem.mjs +9 -0
- package/dist/MediaCard.d.mts +39 -0
- package/dist/MediaCard.d.ts +39 -0
- package/dist/MediaCard.js +475 -0
- package/dist/MediaCard.mjs +9 -0
- package/dist/MenuGroup.d.mts +34 -0
- package/dist/MenuGroup.d.ts +34 -0
- package/dist/MenuGroup.js +217 -0
- package/dist/MenuGroup.mjs +5 -0
- package/dist/MenuItem.d.mts +48 -0
- package/dist/MenuItem.d.ts +48 -0
- package/dist/MenuItem.js +415 -0
- package/dist/MenuItem.mjs +9 -0
- package/dist/MonthPicker.d.mts +28 -0
- package/dist/MonthPicker.d.ts +28 -0
- package/dist/MonthPicker.js +297 -0
- package/dist/MonthPicker.mjs +5 -0
- package/dist/PagerDots.d.mts +35 -0
- package/dist/PagerDots.d.ts +35 -0
- package/dist/PagerDots.js +392 -0
- package/dist/PagerDots.mjs +7 -0
- package/dist/Pressable.d.mts +34 -0
- package/dist/Pressable.d.ts +34 -0
- package/dist/Pressable.js +143 -0
- package/dist/Pressable.mjs +5 -0
- package/dist/PricingCard.d.mts +50 -0
- package/dist/PricingCard.d.ts +50 -0
- package/dist/PricingCard.js +636 -0
- package/dist/PricingCard.mjs +11 -0
- package/dist/Progress.d.mts +14 -0
- package/dist/Progress.d.ts +14 -0
- package/dist/Progress.js +191 -0
- package/dist/Progress.mjs +5 -0
- package/dist/RadioGroup.d.mts +19 -0
- package/dist/RadioGroup.d.ts +19 -0
- package/dist/RadioGroup.js +392 -0
- package/dist/RadioGroup.mjs +7 -0
- package/dist/RetrayProvider.d.mts +2 -0
- package/dist/RetrayProvider.d.ts +2 -0
- package/dist/RetrayProvider.js +214 -0
- package/dist/RetrayProvider.mjs +5 -0
- package/dist/Select.d.mts +22 -0
- package/dist/Select.d.ts +22 -0
- package/dist/Select.js +488 -0
- package/dist/Select.mjs +7 -0
- package/dist/SelectableGrid.d.mts +44 -0
- package/dist/SelectableGrid.d.ts +44 -0
- package/dist/SelectableGrid.js +448 -0
- package/dist/SelectableGrid.mjs +9 -0
- package/dist/Separator.d.mts +10 -0
- package/dist/Separator.d.ts +10 -0
- package/dist/Separator.js +156 -0
- package/dist/Separator.mjs +3 -0
- package/dist/Sheet.d.mts +93 -0
- package/dist/Sheet.d.ts +93 -0
- package/dist/Sheet.js +450 -0
- package/dist/Sheet.mjs +6 -0
- package/dist/Skeleton.d.mts +67 -0
- package/dist/Skeleton.d.ts +67 -0
- package/dist/Skeleton.js +266 -0
- package/dist/Skeleton.mjs +6 -0
- package/dist/Slider.d.mts +20 -0
- package/dist/Slider.d.ts +20 -0
- package/dist/Slider.js +279 -0
- package/dist/Slider.mjs +5 -0
- package/dist/Spinner.d.mts +12 -0
- package/dist/Spinner.d.ts +12 -0
- package/dist/Spinner.js +193 -0
- package/dist/Spinner.mjs +4 -0
- package/dist/Switch.d.mts +13 -0
- package/dist/Switch.d.ts +13 -0
- package/dist/Switch.js +311 -0
- package/dist/Switch.mjs +6 -0
- package/dist/TabBar.d.mts +42 -0
- package/dist/TabBar.d.ts +42 -0
- package/dist/TabBar.js +361 -0
- package/dist/TabBar.mjs +6 -0
- package/dist/Tabs.d.mts +27 -0
- package/dist/Tabs.d.ts +27 -0
- package/dist/Tabs.js +419 -0
- package/dist/Tabs.mjs +7 -0
- package/dist/Text.d.mts +12 -0
- package/dist/Text.d.ts +12 -0
- package/dist/Text.js +327 -0
- package/dist/Text.mjs +5 -0
- package/dist/Textarea.d.mts +16 -0
- package/dist/Textarea.d.ts +16 -0
- package/dist/Textarea.js +333 -0
- package/dist/Textarea.mjs +7 -0
- package/dist/Toast.d.mts +47 -0
- package/dist/Toast.d.ts +47 -0
- package/dist/Toast.js +185 -0
- package/dist/Toast.mjs +4 -0
- package/dist/Toggle.d.mts +36 -0
- package/dist/Toggle.d.ts +36 -0
- package/dist/Toggle.js +412 -0
- package/dist/Toggle.mjs +8 -0
- package/dist/VirtualList.d.mts +19 -0
- package/dist/VirtualList.d.ts +19 -0
- package/dist/VirtualList.js +38 -0
- package/dist/VirtualList.mjs +2 -0
- package/dist/chunk-26BCI223.mjs +14 -0
- package/dist/chunk-2CE3TQVY.mjs +11 -0
- package/dist/chunk-2TFTAWVJ.mjs +131 -0
- package/dist/chunk-2UYENBLV.mjs +49 -0
- package/dist/chunk-3BBOZ3OQ.mjs +41 -0
- package/dist/chunk-3DKJ2GIC.mjs +30 -0
- package/dist/chunk-3U4SSNWP.mjs +120 -0
- package/dist/chunk-4I7D47FH.mjs +139 -0
- package/dist/chunk-4K625MVM.mjs +142 -0
- package/dist/chunk-6OAZJ577.mjs +98 -0
- package/dist/chunk-6Q64UFIA.mjs +71 -0
- package/dist/chunk-756RAKE4.mjs +145 -0
- package/dist/chunk-7QHVVCB3.mjs +115 -0
- package/dist/chunk-A3A6KNQN.mjs +245 -0
- package/dist/chunk-A4MDAP7G.mjs +42 -0
- package/dist/chunk-AJ7ZDNBT.mjs +120 -0
- package/dist/chunk-AV4EMIRH.mjs +94 -0
- package/dist/chunk-AZJF2BLK.mjs +115 -0
- package/dist/chunk-BNP626TY.mjs +159 -0
- package/dist/chunk-BRKYVJVV.mjs +60 -0
- package/dist/chunk-DVK4G2GT.mjs +59 -0
- package/dist/chunk-EH745HE5.mjs +127 -0
- package/dist/chunk-EJ7ZPXOH.mjs +163 -0
- package/dist/chunk-GD6KXMG5.mjs +106 -0
- package/dist/chunk-GQYFLP3D.mjs +187 -0
- package/dist/chunk-ID72TK46.mjs +111 -0
- package/dist/chunk-IRRY3CRZ.mjs +82 -0
- package/dist/chunk-JB67UOB5.mjs +92 -0
- package/dist/chunk-JMOZEC77.mjs +90 -0
- package/dist/chunk-JT7HKXRB.mjs +114 -0
- package/dist/chunk-KIHCWCWL.mjs +124 -0
- package/dist/chunk-LXJIIOYQ.mjs +104 -0
- package/dist/chunk-M6ZXVBTK.mjs +64 -0
- package/dist/chunk-MAC465BB.mjs +61 -0
- package/dist/chunk-MBMXYJJV.mjs +36 -0
- package/dist/chunk-MLF3EZFW.mjs +119 -0
- package/dist/chunk-MX6HRKMI.mjs +29 -0
- package/dist/chunk-NA7PARID.mjs +147 -0
- package/dist/chunk-NC5ZTR2Y.mjs +32 -0
- package/dist/chunk-O3HA6TYM.mjs +139 -0
- package/dist/chunk-OB4JUQ3O.mjs +51 -0
- package/dist/chunk-PFZTM6D5.mjs +238 -0
- package/dist/chunk-QKH5ZOD5.mjs +97 -0
- package/dist/chunk-QY3X2UYR.mjs +191 -0
- package/dist/chunk-SOA2Z4RB.mjs +82 -0
- package/dist/chunk-SOYNZDVY.mjs +151 -0
- package/dist/chunk-T7XZ7H7Y.mjs +57 -0
- package/dist/chunk-TERDKCLE.mjs +74 -0
- package/dist/chunk-UREA2GYY.mjs +113 -0
- package/dist/chunk-VGTDN7SW.mjs +164 -0
- package/dist/chunk-VQ57HWPL.mjs +144 -0
- package/dist/chunk-WBOOUHSS.mjs +62 -0
- package/dist/chunk-WJLKJMKR.mjs +78 -0
- package/dist/chunk-X4G6APW6.mjs +134 -0
- package/dist/chunk-Y6FXYEAI.mjs +8 -0
- package/dist/chunk-YFZ3ELX5.mjs +16 -0
- package/dist/chunk-YNROWHQJ.mjs +46 -0
- package/dist/chunk-Z4BVUWW6.mjs +196 -0
- package/dist/chunk-ZJKGQMYH.mjs +131 -0
- package/dist/index-wt-orHUi.d.mts +85 -0
- package/dist/index-wt-orHUi.d.ts +85 -0
- package/dist/index.d.mts +149 -920
- package/dist/index.d.ts +149 -920
- package/dist/index.js +2560 -970
- package/dist/index.mjs +60 -3895
- package/package.json +55 -16
- package/src/assets/fonts/Sohne-Bold.otf +0 -0
- package/src/assets/fonts/Sohne-BoldItalic.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraBold.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraBoldItalic.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraLight.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraLightItalic.otf +0 -0
- package/src/assets/fonts/Sohne-Italic.otf +0 -0
- package/src/assets/fonts/Sohne-Light.otf +0 -0
- package/src/assets/fonts/Sohne-LightItalic.otf +0 -0
- package/src/assets/fonts/Sohne-Medium.otf +0 -0
- package/src/assets/fonts/Sohne-MediumItalic.otf +0 -0
- package/src/assets/fonts/Sohne-Regular.otf +0 -0
- package/src/assets/fonts/Sohne-SemiBold.otf +0 -0
- package/src/assets/fonts/Sohne-SemiBoldItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Bold.otf +0 -0
- package/src/assets/fonts/SohneMono-BoldItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraBold.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraBoldItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraLight.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraLightItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Italic.otf +0 -0
- package/src/assets/fonts/SohneMono-Light.otf +0 -0
- package/src/assets/fonts/SohneMono-LightItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Medium.otf +0 -0
- package/src/assets/fonts/SohneMono-MediumItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Regular.otf +0 -0
- package/src/assets/fonts/SohneMono-SemiBold.otf +0 -0
- package/src/assets/fonts/SohneMono-SemiBoldItalic.otf +0 -0
- package/src/components/Accordion/Accordion.tsx +15 -4
- package/src/components/AlertBanner/AlertBanner.tsx +38 -12
- package/src/components/AppHeader/AppHeader.tsx +172 -0
- package/src/components/AppHeader/index.ts +1 -0
- package/src/components/Avatar/Avatar.tsx +14 -4
- package/src/components/Badge/Badge.tsx +12 -3
- package/src/components/Button/Button.tsx +30 -38
- package/src/components/ButtonGroup/ButtonGroup.tsx +13 -10
- package/src/components/Card/Card.tsx +29 -57
- package/src/components/CategoryStrip/CategoryStrip.tsx +41 -42
- package/src/components/Checkbox/Checkbox.tsx +36 -45
- package/src/components/Chip/Chip.tsx +41 -48
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +2 -2
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +4 -2
- package/src/components/CurrencyInput/CurrencyInput.tsx +12 -10
- package/src/components/DetailRow/DetailRow.tsx +9 -7
- package/src/components/EmptyState/EmptyState.tsx +4 -3
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +153 -0
- package/src/components/ErrorBoundary/index.ts +1 -0
- package/src/components/Form/Form.tsx +149 -0
- package/src/components/Form/index.ts +1 -0
- package/src/components/HolographicCard/HolographicCard.tsx +315 -0
- package/src/components/HolographicCard/index.ts +1 -0
- package/src/components/IconButton/IconButton.tsx +23 -29
- package/src/components/ImageViewer/ImageViewer.tsx +290 -0
- package/src/components/ImageViewer/index.ts +1 -0
- package/src/components/Input/Input.tsx +27 -31
- package/src/components/LabelValue/LabelValue.tsx +6 -4
- package/src/components/ListGroup/ListGroup.tsx +145 -0
- package/src/components/ListGroup/index.ts +1 -0
- package/src/components/ListItem/ListItem.tsx +78 -76
- package/src/components/MediaCard/MediaCard.tsx +15 -7
- package/src/components/MenuGroup/MenuGroup.tsx +145 -0
- package/src/components/MenuGroup/index.ts +1 -0
- package/src/components/MenuItem/MenuItem.tsx +16 -33
- package/src/components/MonthPicker/MonthPicker.tsx +41 -15
- package/src/components/MonthPicker/index.ts +1 -1
- package/src/components/PagerDots/PagerDots.tsx +200 -0
- package/src/components/PagerDots/index.ts +1 -0
- package/src/components/Pressable/Pressable.tsx +19 -35
- package/src/components/PricingCard/PricingCard.tsx +220 -0
- package/src/components/PricingCard/index.ts +1 -0
- package/src/components/RadioGroup/RadioGroup.tsx +23 -39
- package/src/components/RetrayProvider/RetrayProvider.tsx +59 -0
- package/src/components/RetrayProvider/index.ts +1 -0
- package/src/components/Select/Select.tsx +6 -6
- package/src/components/SelectableGrid/SelectableGrid.tsx +205 -0
- package/src/components/SelectableGrid/index.ts +1 -0
- package/src/components/Separator/Separator.tsx +1 -3
- package/src/components/Sheet/Sheet.tsx +146 -18
- package/src/components/Skeleton/Skeleton.tsx +143 -2
- package/src/components/Slider/Slider.tsx +2 -2
- package/src/components/Spinner/Spinner.tsx +18 -3
- package/src/components/Switch/Switch.tsx +44 -49
- package/src/components/TabBar/TabBar.tsx +169 -0
- package/src/components/TabBar/index.ts +1 -0
- package/src/components/Tabs/Tabs.tsx +45 -44
- package/src/components/Text/Text.tsx +5 -1
- package/src/components/Textarea/Textarea.tsx +18 -14
- package/src/components/Toast/Toast.tsx +6 -6
- package/src/components/Toggle/Toggle.tsx +80 -72
- package/src/components/VirtualList/VirtualList.tsx +60 -0
- package/src/components/VirtualList/index.ts +1 -0
- package/src/fonts.ts +41 -20
- package/src/index.ts +28 -3
- package/src/theme/colors.ts +53 -39
- package/src/theme/types.ts +3 -0
- package/src/tokens.ts +49 -39
- package/src/utils/animations.ts +29 -1
- package/src/utils/fontGuard.ts +34 -0
- package/src/utils/haptics.ts +211 -9
- package/src/utils/icons.ts +47 -20
- package/src/utils/pressable.ts +66 -0
- package/src/utils/usePressScale.ts +2 -0
- package/src/assets/fonts/Poppins-Black.ttf +0 -0
- package/src/assets/fonts/Poppins-BlackItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Bold.ttf +0 -0
- package/src/assets/fonts/Poppins-BoldItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraBold.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraLight.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraLightItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Italic.ttf +0 -0
- package/src/assets/fonts/Poppins-Light.ttf +0 -0
- package/src/assets/fonts/Poppins-LightItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Medium.ttf +0 -0
- package/src/assets/fonts/Poppins-MediumItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Regular.ttf +0 -0
- package/src/assets/fonts/Poppins-SemiBold.ttf +0 -0
- package/src/assets/fonts/Poppins-SemiBoldItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Thin.ttf +0 -0
- package/src/assets/fonts/Poppins-ThinItalic.ttf +0 -0
|
@@ -101,11 +101,11 @@ const styles = StyleSheet.create({
|
|
|
101
101
|
alignItems: 'center',
|
|
102
102
|
},
|
|
103
103
|
label: {
|
|
104
|
-
fontFamily: '
|
|
104
|
+
fontFamily: 'Sohne-Medium',
|
|
105
105
|
fontSize: ms(15),
|
|
106
106
|
},
|
|
107
107
|
valueText: {
|
|
108
|
-
fontFamily: '
|
|
108
|
+
fontFamily: 'Sohne-Medium',
|
|
109
109
|
fontSize: ms(14),
|
|
110
110
|
},
|
|
111
111
|
slider: {
|
|
@@ -25,10 +25,16 @@ const labelFontSize: Record<SpinnerSize, number> = {
|
|
|
25
25
|
|
|
26
26
|
export function Spinner({ size = 'md', color, label, ...props }: SpinnerProps) {
|
|
27
27
|
const { colors } = useTheme()
|
|
28
|
+
const a11yLabel = label || 'Loading'
|
|
28
29
|
|
|
29
30
|
if (label) {
|
|
30
31
|
return (
|
|
31
|
-
<View
|
|
32
|
+
<View
|
|
33
|
+
style={styles.wrapper}
|
|
34
|
+
accessibilityRole="progressbar"
|
|
35
|
+
accessibilityLabel={a11yLabel}
|
|
36
|
+
accessibilityState={{ busy: true }}
|
|
37
|
+
>
|
|
32
38
|
<ActivityIndicator size={sizeMap[size]} color={color ?? colors.primary} {...props} />
|
|
33
39
|
<Text
|
|
34
40
|
style={[styles.label, { color: colors.foregroundMuted, fontSize: labelFontSize[size] }]}
|
|
@@ -40,7 +46,16 @@ export function Spinner({ size = 'md', color, label, ...props }: SpinnerProps) {
|
|
|
40
46
|
)
|
|
41
47
|
}
|
|
42
48
|
|
|
43
|
-
return
|
|
49
|
+
return (
|
|
50
|
+
<ActivityIndicator
|
|
51
|
+
size={sizeMap[size]}
|
|
52
|
+
color={color ?? colors.primary}
|
|
53
|
+
accessibilityRole="progressbar"
|
|
54
|
+
accessibilityLabel={a11yLabel}
|
|
55
|
+
accessibilityState={{ busy: true }}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
44
59
|
}
|
|
45
60
|
|
|
46
61
|
const styles = StyleSheet.create({
|
|
@@ -49,7 +64,7 @@ const styles = StyleSheet.create({
|
|
|
49
64
|
gap: vs(6),
|
|
50
65
|
},
|
|
51
66
|
label: {
|
|
52
|
-
fontFamily: '
|
|
67
|
+
fontFamily: 'Sohne-Regular',
|
|
53
68
|
lineHeight: mvs(18),
|
|
54
69
|
},
|
|
55
70
|
})
|
|
@@ -1,24 +1,18 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
import { TouchableOpacity, StyleSheet, ViewStyle, View } from 'react-native'
|
|
3
|
-
import
|
|
4
|
-
useSharedValue,
|
|
5
|
-
useAnimatedStyle,
|
|
6
|
-
withSpring,
|
|
7
|
-
withTiming,
|
|
8
|
-
interpolateColor,
|
|
9
|
-
} from 'react-native-reanimated'
|
|
3
|
+
import { EaseView } from 'react-native-ease'
|
|
10
4
|
import { Feather } from '@expo/vector-icons'
|
|
11
5
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
12
6
|
import { useTheme } from '../../theme'
|
|
13
7
|
import { s } from '../../utils/scaling'
|
|
14
|
-
import {
|
|
8
|
+
import { COLOR_TRANSITION, OPACITY_TRANSITION, SPRING_ELASTIC } from '../../utils/animations'
|
|
15
9
|
|
|
16
|
-
const TRACK_WIDTH
|
|
10
|
+
const TRACK_WIDTH = s(52)
|
|
17
11
|
const TRACK_HEIGHT = s(30)
|
|
18
|
-
const THUMB_SIZE
|
|
12
|
+
const THUMB_SIZE = s(24)
|
|
19
13
|
const THUMB_OFFSET = s(3)
|
|
20
14
|
const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
|
|
21
|
-
const ICON_SIZE
|
|
15
|
+
const ICON_SIZE = s(13)
|
|
22
16
|
|
|
23
17
|
export interface SwitchProps {
|
|
24
18
|
checked?: boolean
|
|
@@ -31,35 +25,8 @@ export interface SwitchProps {
|
|
|
31
25
|
export function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }: SwitchProps) {
|
|
32
26
|
const { colors } = useTheme()
|
|
33
27
|
|
|
34
|
-
// Single 0→1 progress drives thumb position, track color, and icon crossfade — all UI thread.
|
|
35
|
-
const progress = useSharedValue(checked ? 1 : 0)
|
|
36
|
-
|
|
37
|
-
useEffect(() => {
|
|
38
|
-
progress.value = withSpring(checked ? 1 : 0, SPRINGS.elastic)
|
|
39
|
-
}, [checked, progress])
|
|
40
|
-
|
|
41
|
-
const thumbStyle = useAnimatedStyle(() => ({
|
|
42
|
-
transform: [{ translateX: progress.value * THUMB_TRAVEL }],
|
|
43
|
-
}))
|
|
44
|
-
|
|
45
|
-
const trackStyle = useAnimatedStyle(() => ({
|
|
46
|
-
backgroundColor: interpolateColor(
|
|
47
|
-
progress.value,
|
|
48
|
-
[0, 1],
|
|
49
|
-
[colors.surfaceStrong, colors.primary],
|
|
50
|
-
),
|
|
51
|
-
}))
|
|
52
|
-
|
|
53
|
-
const checkIconStyle = useAnimatedStyle(() => ({
|
|
54
|
-
opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
|
|
55
|
-
}))
|
|
56
|
-
|
|
57
|
-
const crossIconStyle = useAnimatedStyle(() => ({
|
|
58
|
-
opacity: withTiming(checked ? 0 : 1, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
|
|
59
|
-
}))
|
|
60
|
-
|
|
61
28
|
return (
|
|
62
|
-
<View style={[{ opacity: disabled ? 0.45 : 1 }, style]}>
|
|
29
|
+
<View style={[{ opacity: disabled ? 0.45 : 1, alignSelf: 'flex-start' }, style]}>
|
|
63
30
|
<TouchableOpacity
|
|
64
31
|
onPress={() => {
|
|
65
32
|
hapticSelection()
|
|
@@ -71,30 +38,56 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
|
|
|
71
38
|
accessibilityRole="switch"
|
|
72
39
|
accessibilityLabel={accessibilityLabel}
|
|
73
40
|
accessibilityState={{ checked, disabled: !!disabled }}
|
|
41
|
+
style={styles.touchable}
|
|
74
42
|
>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
43
|
+
<EaseView
|
|
44
|
+
style={styles.track}
|
|
45
|
+
animate={{ backgroundColor: checked ? colors.primary : colors.surfaceStrong }}
|
|
46
|
+
transition={COLOR_TRANSITION}
|
|
47
|
+
>
|
|
48
|
+
{/*
|
|
49
|
+
AUDIT FIX: the off-state track used surfaceStrong (~#ebebeb in light mode)
|
|
50
|
+
with no border — nearly invisible on white page/card surfaces. A 1.5px border
|
|
51
|
+
that fades out as the track fills gives the off state clear visual definition
|
|
52
|
+
without adding visual weight to the on state.
|
|
53
|
+
*/}
|
|
54
|
+
<EaseView
|
|
55
|
+
style={[styles.trackBorder, { borderWidth: 1.5 }]}
|
|
56
|
+
pointerEvents="none"
|
|
57
|
+
animate={{ borderColor: checked ? 'transparent' : colors.border }}
|
|
58
|
+
transition={COLOR_TRANSITION}
|
|
59
|
+
/>
|
|
60
|
+
<EaseView
|
|
61
|
+
style={[styles.thumb, { backgroundColor: colors.primaryForeground }]}
|
|
62
|
+
animate={{ translateX: checked ? THUMB_TRAVEL : 0 }}
|
|
63
|
+
transition={SPRING_ELASTIC}
|
|
78
64
|
>
|
|
79
|
-
<
|
|
65
|
+
<EaseView style={styles.iconWrapper} animate={{ opacity: checked ? 1 : 0 }} transition={OPACITY_TRANSITION}>
|
|
80
66
|
<Feather name="check" size={ICON_SIZE} color={colors.primary} />
|
|
81
|
-
</
|
|
82
|
-
<
|
|
67
|
+
</EaseView>
|
|
68
|
+
<EaseView style={styles.iconWrapper} animate={{ opacity: checked ? 0 : 1 }} transition={OPACITY_TRANSITION}>
|
|
83
69
|
<Feather name="x" size={ICON_SIZE} color={colors.foregroundMuted} />
|
|
84
|
-
</
|
|
85
|
-
</
|
|
86
|
-
</
|
|
70
|
+
</EaseView>
|
|
71
|
+
</EaseView>
|
|
72
|
+
</EaseView>
|
|
87
73
|
</TouchableOpacity>
|
|
88
74
|
</View>
|
|
89
75
|
)
|
|
90
76
|
}
|
|
91
77
|
|
|
92
78
|
const styles = StyleSheet.create({
|
|
79
|
+
touchable: {
|
|
80
|
+
alignSelf: 'flex-start',
|
|
81
|
+
},
|
|
93
82
|
track: {
|
|
94
83
|
width: TRACK_WIDTH,
|
|
95
84
|
height: TRACK_HEIGHT,
|
|
96
85
|
borderRadius: TRACK_HEIGHT / 2,
|
|
97
86
|
},
|
|
87
|
+
trackBorder: {
|
|
88
|
+
...StyleSheet.absoluteFillObject,
|
|
89
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
90
|
+
},
|
|
98
91
|
thumb: {
|
|
99
92
|
position: 'absolute',
|
|
100
93
|
top: THUMB_OFFSET,
|
|
@@ -112,5 +105,7 @@ const styles = StyleSheet.create({
|
|
|
112
105
|
},
|
|
113
106
|
iconWrapper: {
|
|
114
107
|
position: 'absolute',
|
|
108
|
+
alignItems: 'center',
|
|
109
|
+
justifyContent: 'center',
|
|
115
110
|
},
|
|
116
111
|
})
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
4
|
+
import { useTheme } from '../../theme'
|
|
5
|
+
import { renderIcon } from '../../utils/icons'
|
|
6
|
+
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
7
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
8
|
+
|
|
9
|
+
export interface TabBarItem {
|
|
10
|
+
/** Unique key for the tab. */
|
|
11
|
+
key: string
|
|
12
|
+
/** Label under the icon. Omit for an icon-only tab. */
|
|
13
|
+
label?: string
|
|
14
|
+
/** Icon name (active + inactive share the name; color signals state). */
|
|
15
|
+
iconName?: string
|
|
16
|
+
/** Custom icon node — receives no state, overrides `iconName`. */
|
|
17
|
+
icon?: React.ReactNode
|
|
18
|
+
/** Badge overlay — `true` shows a dot, a number shows a count (capped at 99). */
|
|
19
|
+
badge?: boolean | number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TabBarProps {
|
|
23
|
+
items: TabBarItem[]
|
|
24
|
+
/** Key of the active tab. */
|
|
25
|
+
activeKey: string
|
|
26
|
+
onTabPress: (key: string) => void
|
|
27
|
+
/** Active tint. Defaults to theme `primary`. */
|
|
28
|
+
activeColor?: string
|
|
29
|
+
/** Inactive tint. Defaults to theme `foregroundMuted`. */
|
|
30
|
+
inactiveColor?: string
|
|
31
|
+
/** Apply the bottom safe-area inset as padding. Defaults to true. */
|
|
32
|
+
withSafeArea?: boolean
|
|
33
|
+
style?: ViewStyle
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Bottom tab bar — icon + label tabs with active tint and badge support. Pair
|
|
38
|
+
* with your navigator, or drive it with local state for a single-screen app.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* <TabBar
|
|
42
|
+
* items={[{ key: 'home', label: 'Home', iconName: 'home' }, { key: 'profile', label: 'Profile', iconName: 'user', badge: 3 }]}
|
|
43
|
+
* activeKey={tab}
|
|
44
|
+
* onTabPress={setTab}
|
|
45
|
+
* />
|
|
46
|
+
*/
|
|
47
|
+
export function TabBar({
|
|
48
|
+
items,
|
|
49
|
+
activeKey,
|
|
50
|
+
onTabPress,
|
|
51
|
+
activeColor,
|
|
52
|
+
inactiveColor,
|
|
53
|
+
withSafeArea = true,
|
|
54
|
+
style,
|
|
55
|
+
}: TabBarProps) {
|
|
56
|
+
const { colors } = useTheme()
|
|
57
|
+
const insets = useSafeAreaInsets()
|
|
58
|
+
const resolvedActive = activeColor ?? colors.primary
|
|
59
|
+
const resolvedInactive = inactiveColor ?? colors.foregroundMuted
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<View
|
|
63
|
+
style={[
|
|
64
|
+
styles.container,
|
|
65
|
+
{
|
|
66
|
+
backgroundColor: colors.card,
|
|
67
|
+
borderTopColor: colors.border,
|
|
68
|
+
paddingBottom: withSafeArea ? insets.bottom : 0,
|
|
69
|
+
},
|
|
70
|
+
style,
|
|
71
|
+
]}
|
|
72
|
+
accessibilityRole="tablist"
|
|
73
|
+
>
|
|
74
|
+
{items.map((item) => {
|
|
75
|
+
const active = item.key === activeKey
|
|
76
|
+
const tint = active ? resolvedActive : resolvedInactive
|
|
77
|
+
const iconNode = item.icon ?? (item.iconName ? renderIcon(item.iconName, ms(24), tint) : null)
|
|
78
|
+
const showBadge = item.badge !== undefined && item.badge !== false
|
|
79
|
+
const badgeCount = typeof item.badge === 'number' ? item.badge : undefined
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<TouchableOpacity
|
|
83
|
+
key={item.key}
|
|
84
|
+
style={styles.tab}
|
|
85
|
+
onPress={() => {
|
|
86
|
+
if (!active) hapticSelection()
|
|
87
|
+
onTabPress(item.key)
|
|
88
|
+
}}
|
|
89
|
+
activeOpacity={0.7}
|
|
90
|
+
touchSoundDisabled={true}
|
|
91
|
+
accessibilityRole="tab"
|
|
92
|
+
accessibilityState={{ selected: active }}
|
|
93
|
+
accessibilityLabel={item.label ?? item.key}
|
|
94
|
+
>
|
|
95
|
+
<View>
|
|
96
|
+
{iconNode}
|
|
97
|
+
{showBadge ? (
|
|
98
|
+
<View
|
|
99
|
+
style={[
|
|
100
|
+
styles.badge,
|
|
101
|
+
{ backgroundColor: colors.destructive, borderColor: colors.card },
|
|
102
|
+
badgeCount === undefined && styles.badgeDot,
|
|
103
|
+
]}
|
|
104
|
+
>
|
|
105
|
+
{badgeCount !== undefined ? (
|
|
106
|
+
<Text style={[styles.badgeText, { color: colors.destructiveForeground }]} allowFontScaling={false}>
|
|
107
|
+
{badgeCount > 99 ? '99+' : badgeCount}
|
|
108
|
+
</Text>
|
|
109
|
+
) : null}
|
|
110
|
+
</View>
|
|
111
|
+
) : null}
|
|
112
|
+
</View>
|
|
113
|
+
{item.label ? (
|
|
114
|
+
<Text style={[styles.label, { color: tint }]} numberOfLines={1} allowFontScaling={true}>
|
|
115
|
+
{item.label}
|
|
116
|
+
</Text>
|
|
117
|
+
) : null}
|
|
118
|
+
</TouchableOpacity>
|
|
119
|
+
)
|
|
120
|
+
})}
|
|
121
|
+
</View>
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const styles = StyleSheet.create({
|
|
126
|
+
container: {
|
|
127
|
+
flexDirection: 'row',
|
|
128
|
+
borderTopWidth: StyleSheet.hairlineWidth,
|
|
129
|
+
},
|
|
130
|
+
tab: {
|
|
131
|
+
flex: 1,
|
|
132
|
+
alignItems: 'center',
|
|
133
|
+
justifyContent: 'center',
|
|
134
|
+
paddingTop: vs(8),
|
|
135
|
+
paddingBottom: vs(6),
|
|
136
|
+
gap: vs(2),
|
|
137
|
+
minHeight: vs(48),
|
|
138
|
+
},
|
|
139
|
+
label: {
|
|
140
|
+
fontFamily: 'Sohne-Medium',
|
|
141
|
+
fontSize: ms(11),
|
|
142
|
+
lineHeight: mvs(14),
|
|
143
|
+
},
|
|
144
|
+
badge: {
|
|
145
|
+
position: 'absolute',
|
|
146
|
+
top: -vs(4),
|
|
147
|
+
right: -s(10),
|
|
148
|
+
minWidth: s(16),
|
|
149
|
+
height: s(16),
|
|
150
|
+
borderRadius: s(8),
|
|
151
|
+
borderWidth: 1.5,
|
|
152
|
+
alignItems: 'center',
|
|
153
|
+
justifyContent: 'center',
|
|
154
|
+
paddingHorizontal: s(3),
|
|
155
|
+
},
|
|
156
|
+
badgeDot: {
|
|
157
|
+
minWidth: s(10),
|
|
158
|
+
height: s(10),
|
|
159
|
+
borderRadius: s(5),
|
|
160
|
+
top: -vs(2),
|
|
161
|
+
right: -s(6),
|
|
162
|
+
paddingHorizontal: 0,
|
|
163
|
+
},
|
|
164
|
+
badgeText: {
|
|
165
|
+
fontFamily: 'Sohne-SemiBold',
|
|
166
|
+
fontSize: ms(9),
|
|
167
|
+
lineHeight: ms(11),
|
|
168
|
+
},
|
|
169
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './TabBar'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react'
|
|
2
|
-
import { View,
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react'
|
|
2
|
+
import { View, Text, StyleSheet, ViewStyle, LayoutChangeEvent } from 'react-native'
|
|
3
3
|
import Animated, {
|
|
4
4
|
useSharedValue,
|
|
5
5
|
useAnimatedStyle,
|
|
@@ -8,8 +8,8 @@ import Animated, {
|
|
|
8
8
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
9
9
|
import { useTheme } from '../../theme'
|
|
10
10
|
import { s, vs, ms } from '../../utils/scaling'
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { SPRINGS } from '../../utils/animations'
|
|
12
|
+
import { PressableTab } from '../../utils/pressable'
|
|
13
13
|
|
|
14
14
|
export interface TabItem {
|
|
15
15
|
label: string
|
|
@@ -17,8 +17,6 @@ export interface TabItem {
|
|
|
17
17
|
icon?: React.ReactNode | ((active: boolean) => React.ReactNode)
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
// pill: animated sliding pill background (default)
|
|
21
|
-
// underline: 2px bottom border on active tab — Airbnb product-tab style
|
|
22
20
|
export type TabsVariant = 'pill' | 'underline'
|
|
23
21
|
|
|
24
22
|
export interface TabsProps {
|
|
@@ -51,46 +49,43 @@ function TabTrigger({
|
|
|
51
49
|
variant: TabsVariant
|
|
52
50
|
}) {
|
|
53
51
|
const { colors } = useTheme()
|
|
54
|
-
const { animatedStyle, onPressIn, onPressOut } = usePressScale({
|
|
55
|
-
pressScale: PRESS_SCALE.button,
|
|
56
|
-
})
|
|
57
52
|
const isUnderline = variant === 'underline'
|
|
58
53
|
|
|
59
54
|
return (
|
|
60
|
-
<
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
accessibilityLabel={tab.label}
|
|
75
|
-
>
|
|
76
|
-
<Animated.View style={animatedStyle}>
|
|
55
|
+
<View onLayout={onLayout} style={styles.triggerWrap}>
|
|
56
|
+
<PressableTab
|
|
57
|
+
style={[
|
|
58
|
+
styles.trigger,
|
|
59
|
+
isUnderline && styles.triggerUnderline,
|
|
60
|
+
isUnderline && isActive && { borderBottomColor: colors.primary },
|
|
61
|
+
]}
|
|
62
|
+
onPress={onPress}
|
|
63
|
+
rippleColor="transparent"
|
|
64
|
+
touchSoundDisabled
|
|
65
|
+
accessibilityRole="tab"
|
|
66
|
+
accessibilityState={{ selected: isActive }}
|
|
67
|
+
accessibilityLabel={tab.label}
|
|
68
|
+
>
|
|
77
69
|
<View style={styles.triggerInner}>
|
|
78
70
|
{tab.icon ? (
|
|
79
|
-
|
|
71
|
+
typeof tab.icon === 'function' ? tab.icon(isActive) : tab.icon
|
|
80
72
|
) : null}
|
|
81
73
|
<Text
|
|
82
74
|
style={[
|
|
83
75
|
styles.triggerLabel,
|
|
76
|
+
// AUDIT FIX: active state now only changes color, never font metrics.
|
|
77
|
+
// Previously: inactive=Regular, active=Medium (pill) or SemiBold+fontSize14 (underline)
|
|
78
|
+
// The weight/size change caused measurable layout reflow every tab switch.
|
|
79
|
+
// Solution: all labels render at SemiBold always; active = foreground, inactive = foregroundMuted.
|
|
84
80
|
{ color: isActive ? colors.foreground : colors.foregroundMuted },
|
|
85
|
-
isActive && (isUnderline ? styles.activeTriggerLabelUnderline : styles.activeTriggerLabel),
|
|
86
81
|
]}
|
|
87
82
|
allowFontScaling={true}
|
|
88
83
|
>
|
|
89
84
|
{tab.label}
|
|
90
85
|
</Text>
|
|
91
86
|
</View>
|
|
92
|
-
</
|
|
93
|
-
</
|
|
87
|
+
</PressableTab>
|
|
88
|
+
</View>
|
|
94
89
|
)
|
|
95
90
|
}
|
|
96
91
|
|
|
@@ -100,26 +95,27 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
100
95
|
const active = value ?? internal
|
|
101
96
|
|
|
102
97
|
const tabLayouts = useRef<Record<string, { x: number; width: number }>>({})
|
|
103
|
-
// Shared values drive the pill position on the UI thread — no JS bridge cost on slide.
|
|
104
98
|
const pillX = useSharedValue(0)
|
|
105
99
|
const pillWidth = useSharedValue(0)
|
|
106
100
|
const initialised = useRef(false)
|
|
107
101
|
|
|
108
|
-
const animatePill = (tabValue: string, animate: boolean) => {
|
|
102
|
+
const animatePill = useCallback((tabValue: string, animate: boolean) => {
|
|
109
103
|
const layout = tabLayouts.current[tabValue]
|
|
110
104
|
if (!layout) return
|
|
111
105
|
if (animate) {
|
|
106
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
112
107
|
pillX.value = withSpring(layout.x, SPRINGS.glide)
|
|
108
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
113
109
|
pillWidth.value = withSpring(layout.width, SPRINGS.glide)
|
|
114
110
|
} else {
|
|
115
111
|
pillX.value = layout.x
|
|
116
112
|
pillWidth.value = layout.width
|
|
117
113
|
}
|
|
118
|
-
}
|
|
114
|
+
}, [pillX, pillWidth])
|
|
119
115
|
|
|
120
116
|
useEffect(() => {
|
|
121
117
|
if (initialised.current) animatePill(active, true)
|
|
122
|
-
}, [active])
|
|
118
|
+
}, [active, animatePill])
|
|
123
119
|
|
|
124
120
|
const handlePress = (v: string) => {
|
|
125
121
|
hapticSelection()
|
|
@@ -136,7 +132,9 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
136
132
|
<View style={style}>
|
|
137
133
|
<View
|
|
138
134
|
style={[
|
|
139
|
-
variant === 'pill'
|
|
135
|
+
variant === 'pill'
|
|
136
|
+
? [styles.list, { backgroundColor: colors.surface }]
|
|
137
|
+
: styles.listUnderline,
|
|
140
138
|
]}
|
|
141
139
|
accessibilityRole="tablist"
|
|
142
140
|
>
|
|
@@ -198,9 +196,14 @@ const styles = StyleSheet.create({
|
|
|
198
196
|
},
|
|
199
197
|
listUnderline: {
|
|
200
198
|
flexDirection: 'row',
|
|
199
|
+
// AUDIT FIX: was missing borderBottomColor — the 1px hairline would render
|
|
200
|
+
// as transparent on some platforms. Explicit token reference ensures visibility.
|
|
201
201
|
borderBottomWidth: 1,
|
|
202
202
|
},
|
|
203
203
|
pill: {},
|
|
204
|
+
triggerWrap: {
|
|
205
|
+
flex: 1,
|
|
206
|
+
},
|
|
204
207
|
trigger: {
|
|
205
208
|
flex: 1,
|
|
206
209
|
paddingVertical: vs(7),
|
|
@@ -211,7 +214,7 @@ const styles = StyleSheet.create({
|
|
|
211
214
|
zIndex: 1,
|
|
212
215
|
},
|
|
213
216
|
triggerUnderline: {
|
|
214
|
-
flex:
|
|
217
|
+
flex: 1,
|
|
215
218
|
paddingVertical: vs(12),
|
|
216
219
|
paddingHorizontal: s(16),
|
|
217
220
|
borderRadius: 0,
|
|
@@ -224,15 +227,13 @@ const styles = StyleSheet.create({
|
|
|
224
227
|
justifyContent: 'center',
|
|
225
228
|
gap: s(4),
|
|
226
229
|
},
|
|
230
|
+
// AUDIT FIX: was Sohne-Regular at rest, Sohne-Medium/SemiBold when active.
|
|
231
|
+
// Font-weight changes at runtime cause advance-width shifts → the tab bar would
|
|
232
|
+
// visibly jump/reflow on every selection. Now always SemiBold; active state
|
|
233
|
+
// is communicated by color alone (foreground vs foregroundMuted). The pill
|
|
234
|
+
// indicator provides additional active signal without text layout side-effects.
|
|
227
235
|
triggerLabel: {
|
|
228
|
-
fontFamily: '
|
|
236
|
+
fontFamily: 'Sohne-SemiBold',
|
|
229
237
|
fontSize: ms(13),
|
|
230
238
|
},
|
|
231
|
-
activeTriggerLabel: {
|
|
232
|
-
fontFamily: 'Poppins-Medium',
|
|
233
|
-
},
|
|
234
|
-
activeTriggerLabelUnderline: {
|
|
235
|
-
fontFamily: 'Poppins-SemiBold',
|
|
236
|
-
fontSize: ms(14),
|
|
237
|
-
},
|
|
238
239
|
})
|
|
@@ -3,6 +3,7 @@ import { Text as RNText, TextProps as RNTextProps, TextStyle } from 'react-nativ
|
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
4
|
import { TYPOGRAPHY } from '../../tokens'
|
|
5
5
|
import { ms, mvs } from '../../utils/scaling'
|
|
6
|
+
import { warnIfFontsMissing } from '../../utils/fontGuard'
|
|
6
7
|
|
|
7
8
|
export type TextVariant =
|
|
8
9
|
| 'display-hero'
|
|
@@ -67,7 +68,8 @@ const defaultColorVariant: Partial<Record<TextVariant, 'foreground' | 'foregroun
|
|
|
67
68
|
'button-sm': 'foreground',
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
|
|
71
|
+
function TextBase({ variant = 'body-md', color, style, children, ...props }: TextProps) {
|
|
72
|
+
warnIfFontsMissing()
|
|
71
73
|
const { colors } = useTheme()
|
|
72
74
|
|
|
73
75
|
const colorKey = defaultColorVariant[variant] ?? 'foreground'
|
|
@@ -83,3 +85,5 @@ export function Text({ variant = 'body-md', color, style, children, ...props }:
|
|
|
83
85
|
</RNText>
|
|
84
86
|
)
|
|
85
87
|
}
|
|
88
|
+
|
|
89
|
+
export const Text = React.memo(TextBase)
|
|
@@ -3,6 +3,7 @@ import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, Platform
|
|
|
3
3
|
import Animated, {
|
|
4
4
|
useAnimatedStyle,
|
|
5
5
|
interpolateColor,
|
|
6
|
+
interpolate,
|
|
6
7
|
} from 'react-native-reanimated'
|
|
7
8
|
import { useTheme } from '../../theme'
|
|
8
9
|
import { s, vs, ms } from '../../utils/scaling'
|
|
@@ -10,26 +11,19 @@ import { renderIcon } from '../../utils/icons'
|
|
|
10
11
|
import { useColorTransition } from '../../utils/useColorTransition'
|
|
11
12
|
import { TIMINGS } from '../../utils/animations'
|
|
12
13
|
|
|
13
|
-
const webInputResetStyle:
|
|
14
|
+
const webInputResetStyle: Record<string, unknown> =
|
|
14
15
|
Platform.OS === 'web'
|
|
15
16
|
? { outlineStyle: 'none', outlineWidth: 0, outlineColor: 'transparent', boxShadow: 'none' }
|
|
16
17
|
: {}
|
|
17
18
|
|
|
18
19
|
export interface TextareaProps extends TextInputProps {
|
|
19
20
|
label?: string
|
|
20
|
-
/** Red helper text below the textarea; also changes border to `destructive` color. Takes priority over `hint`. */
|
|
21
21
|
error?: string
|
|
22
|
-
/** Helper text shown below the textarea when there is no error. */
|
|
23
22
|
hint?: string
|
|
24
|
-
/** Number of visible text rows. Defaults to `4`. Controls `numberOfLines` and `minHeight`. */
|
|
25
23
|
rows?: number
|
|
26
|
-
/** Icon name from @expo/vector-icons rendered inside top-left corner. */
|
|
27
24
|
prefixIcon?: string
|
|
28
|
-
/** Custom icon node rendered top-left. */
|
|
29
25
|
prefixIconNode?: React.ReactNode
|
|
30
|
-
/** Override prefix icon color. Defaults to foregroundMuted. */
|
|
31
26
|
prefixIconColor?: string
|
|
32
|
-
/** Style for the outer container `View`. Use `style` (from `TextInputProps`) to style the `TextInput` itself. */
|
|
33
27
|
containerStyle?: ViewStyle
|
|
34
28
|
}
|
|
35
29
|
|
|
@@ -58,10 +52,15 @@ export function Textarea({
|
|
|
58
52
|
? renderIcon(prefixIcon, ms(16), prefixIconColor ?? colors.foregroundMuted)
|
|
59
53
|
: prefixIconNode
|
|
60
54
|
|
|
61
|
-
|
|
55
|
+
// Border drawn on an absolute overlay (mirrors Input.tsx) so the 1px→2px
|
|
56
|
+
// focus weight change never resizes the box / reflows content.
|
|
57
|
+
const borderAnimStyle = useAnimatedStyle(() => ({
|
|
62
58
|
borderColor: error
|
|
63
59
|
? colors.destructive
|
|
64
60
|
: interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary]),
|
|
61
|
+
borderWidth: error
|
|
62
|
+
? 2
|
|
63
|
+
: interpolate(focusProgress.value, [0, 1], [1, 2]),
|
|
65
64
|
}))
|
|
66
65
|
|
|
67
66
|
return (
|
|
@@ -71,9 +70,9 @@ export function Textarea({
|
|
|
71
70
|
style={[
|
|
72
71
|
styles.inputWrapper,
|
|
73
72
|
{ backgroundColor: colors.background },
|
|
74
|
-
borderColorStyle,
|
|
75
73
|
]}
|
|
76
74
|
>
|
|
75
|
+
<Animated.View style={[styles.borderOverlay, borderAnimStyle]} pointerEvents="none" />
|
|
77
76
|
{resolvedPrefixIcon ? <View style={styles.prefixIcon}>{resolvedPrefixIcon}</View> : null}
|
|
78
77
|
<TextInput
|
|
79
78
|
multiline
|
|
@@ -123,32 +122,37 @@ const styles = StyleSheet.create({
|
|
|
123
122
|
gap: vs(4),
|
|
124
123
|
},
|
|
125
124
|
label: {
|
|
126
|
-
fontFamily: '
|
|
125
|
+
fontFamily: 'Sohne-Medium',
|
|
127
126
|
fontSize: ms(13),
|
|
128
127
|
lineHeight: vs(18),
|
|
129
128
|
marginBottom: vs(2),
|
|
130
129
|
},
|
|
131
130
|
inputWrapper: {
|
|
132
|
-
|
|
131
|
+
// Border lives on borderOverlay (absolute); wrapper carries none so the
|
|
132
|
+
// focus weight change never reflows content.
|
|
133
133
|
borderRadius: 8,
|
|
134
134
|
paddingHorizontal: s(14),
|
|
135
135
|
paddingVertical: vs(11),
|
|
136
136
|
gap: s(8),
|
|
137
137
|
},
|
|
138
|
+
borderOverlay: {
|
|
139
|
+
...StyleSheet.absoluteFillObject,
|
|
140
|
+
borderRadius: 8,
|
|
141
|
+
},
|
|
138
142
|
prefixIcon: {
|
|
139
143
|
alignItems: 'flex-start',
|
|
140
144
|
justifyContent: 'flex-start',
|
|
141
145
|
paddingTop: vs(2),
|
|
142
146
|
},
|
|
143
147
|
input: {
|
|
144
|
-
fontFamily: '
|
|
148
|
+
fontFamily: 'Sohne-Regular',
|
|
145
149
|
fontSize: ms(14),
|
|
146
150
|
lineHeight: vs(22),
|
|
147
151
|
padding: 0,
|
|
148
152
|
margin: 0,
|
|
149
153
|
},
|
|
150
154
|
helperText: {
|
|
151
|
-
fontFamily: '
|
|
155
|
+
fontFamily: 'Sohne-Regular',
|
|
152
156
|
fontSize: ms(12),
|
|
153
157
|
lineHeight: vs(16),
|
|
154
158
|
marginTop: vs(4),
|