@retray-dev/ui-kit 6.1.0 → 7.0.1
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 +447 -13
- package/EXAMPLES.md +248 -0
- package/README.md +11 -10
- package/dist/Accordion.d.mts +28 -0
- package/dist/Accordion.d.ts +28 -0
- package/dist/Accordion.js +340 -0
- package/dist/Accordion.mjs +6 -0
- package/dist/AlertBanner.d.mts +16 -0
- package/dist/AlertBanner.d.ts +16 -0
- package/dist/AlertBanner.js +247 -0
- package/dist/AlertBanner.mjs +5 -0
- package/dist/Avatar.d.mts +20 -0
- package/dist/Avatar.d.ts +20 -0
- package/dist/Avatar.js +234 -0
- package/dist/Avatar.mjs +3 -0
- package/dist/Badge.d.mts +26 -0
- package/dist/Badge.d.ts +26 -0
- package/dist/Badge.js +247 -0
- package/dist/Badge.mjs +4 -0
- package/dist/Button.d.mts +25 -0
- package/dist/Button.d.ts +25 -0
- package/dist/Button.js +414 -0
- package/dist/Button.mjs +8 -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 +2 -0
- package/dist/Card.d.mts +39 -0
- package/dist/Card.d.ts +39 -0
- package/dist/Card.js +329 -0
- package/dist/Card.mjs +7 -0
- package/dist/CategoryStrip.d.mts +26 -0
- package/dist/CategoryStrip.d.ts +26 -0
- package/dist/CategoryStrip.js +396 -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 +304 -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 +370 -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 +530 -0
- package/dist/ConfirmDialog.mjs +9 -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 +3 -0
- package/dist/CurrencyInput.d.mts +26 -0
- package/dist/CurrencyInput.d.ts +26 -0
- package/dist/CurrencyInput.js +404 -0
- package/dist/CurrencyInput.mjs +7 -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 +4 -0
- package/dist/EmptyState.d.mts +27 -0
- package/dist/EmptyState.d.ts +27 -0
- package/dist/EmptyState.js +503 -0
- package/dist/EmptyState.mjs +9 -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 +3 -0
- package/dist/IconButton.d.mts +22 -0
- package/dist/IconButton.d.ts +22 -0
- package/dist/IconButton.js +383 -0
- package/dist/IconButton.mjs +7 -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 +6 -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 +4 -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 +4 -0
- package/dist/ListItem.d.mts +64 -0
- package/dist/ListItem.d.ts +64 -0
- package/dist/ListItem.js +430 -0
- package/dist/ListItem.mjs +8 -0
- package/dist/MediaCard.d.mts +39 -0
- package/dist/MediaCard.d.ts +39 -0
- package/dist/MediaCard.js +427 -0
- package/dist/MediaCard.mjs +8 -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 +4 -0
- package/dist/MenuItem.d.mts +48 -0
- package/dist/MenuItem.d.ts +48 -0
- package/dist/MenuItem.js +403 -0
- package/dist/MenuItem.mjs +8 -0
- package/dist/MonthPicker.d.mts +20 -0
- package/dist/MonthPicker.d.ts +20 -0
- package/dist/MonthPicker.js +234 -0
- package/dist/MonthPicker.mjs +4 -0
- package/dist/Pressable.d.mts +34 -0
- package/dist/Pressable.d.ts +34 -0
- package/dist/Pressable.js +132 -0
- package/dist/Pressable.mjs +4 -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 +4 -0
- package/dist/RadioGroup.d.mts +19 -0
- package/dist/RadioGroup.d.ts +19 -0
- package/dist/RadioGroup.js +341 -0
- package/dist/RadioGroup.mjs +7 -0
- package/dist/Select.d.mts +22 -0
- package/dist/Select.d.ts +22 -0
- package/dist/Select.js +441 -0
- package/dist/Select.mjs +6 -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 +2 -0
- package/dist/Sheet.d.mts +81 -0
- package/dist/Sheet.d.ts +81 -0
- package/dist/Sheet.js +340 -0
- package/dist/Sheet.mjs +4 -0
- package/dist/Skeleton.d.mts +17 -0
- package/dist/Skeleton.d.ts +17 -0
- package/dist/Skeleton.js +205 -0
- package/dist/Skeleton.mjs +4 -0
- package/dist/Slider.d.mts +20 -0
- package/dist/Slider.d.ts +20 -0
- package/dist/Slider.js +232 -0
- package/dist/Slider.mjs +4 -0
- package/dist/Spinner.d.mts +12 -0
- package/dist/Spinner.d.ts +12 -0
- package/dist/Spinner.js +172 -0
- package/dist/Spinner.mjs +3 -0
- package/dist/Switch.d.mts +13 -0
- package/dist/Switch.d.ts +13 -0
- package/dist/Switch.js +261 -0
- package/dist/Switch.mjs +5 -0
- package/dist/Tabs.d.mts +27 -0
- package/dist/Tabs.d.ts +27 -0
- package/dist/Tabs.js +389 -0
- package/dist/Tabs.mjs +6 -0
- package/dist/Text.d.mts +12 -0
- package/dist/Text.d.ts +12 -0
- package/dist/Text.js +311 -0
- package/dist/Text.mjs +4 -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 +6 -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 +3 -0
- package/dist/Toggle.d.mts +33 -0
- package/dist/Toggle.d.ts +33 -0
- package/dist/Toggle.js +397 -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 +1 -0
- package/dist/chunk-2CE3TQVY.mjs +11 -0
- package/dist/chunk-2UYENBLV.mjs +49 -0
- package/dist/chunk-3BBOZ3OQ.mjs +41 -0
- package/dist/chunk-5IKW3VNC.mjs +43 -0
- package/dist/chunk-63357L2X.mjs +51 -0
- package/dist/chunk-6LQYY7HC.mjs +127 -0
- package/dist/chunk-6Q64UFIA.mjs +71 -0
- package/dist/chunk-76PFOSM2.mjs +41 -0
- package/dist/chunk-7H2OR44A.mjs +14 -0
- package/dist/chunk-A4MDAP7G.mjs +42 -0
- package/dist/chunk-AU2VDY4P.mjs +190 -0
- package/dist/chunk-BRKYVJVV.mjs +60 -0
- package/dist/chunk-CRYBX2CM.mjs +146 -0
- package/dist/chunk-DITNP6PL.mjs +106 -0
- package/dist/chunk-FTLJOUOQ.mjs +97 -0
- package/dist/chunk-GCWOGZYL.mjs +104 -0
- package/dist/chunk-GNGLDL6Z.mjs +60 -0
- package/dist/chunk-GPOUINK5.mjs +148 -0
- package/dist/chunk-HSPSMN6U.mjs +115 -0
- package/dist/chunk-IRRY3CRZ.mjs +82 -0
- package/dist/chunk-JB67UOB5.mjs +92 -0
- package/dist/chunk-JBLL7U3U.mjs +64 -0
- package/dist/chunk-KWCPOM6W.mjs +136 -0
- package/dist/chunk-KZJRQOIU.mjs +64 -0
- package/dist/chunk-L7E7TVEZ.mjs +145 -0
- package/dist/chunk-LG4DO3DK.mjs +174 -0
- package/dist/chunk-LWG526VX.mjs +139 -0
- package/dist/chunk-MN7OG7IY.mjs +96 -0
- package/dist/chunk-MX6HRKMI.mjs +29 -0
- package/dist/chunk-NC5ZTR2Y.mjs +32 -0
- package/dist/chunk-NQGVLMWG.mjs +90 -0
- package/dist/chunk-QCNARS3X.mjs +46 -0
- package/dist/chunk-QXGYKWI7.mjs +134 -0
- package/dist/chunk-QY3X2UYR.mjs +191 -0
- package/dist/chunk-RKLHUDZS.mjs +92 -0
- package/dist/chunk-RMMK64W5.mjs +54 -0
- package/dist/chunk-RR2VQLKE.mjs +190 -0
- package/dist/chunk-RTC3CFXF.mjs +29 -0
- package/dist/chunk-SBZYEV4S.mjs +61 -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-TAJ2PQ2O.mjs +163 -0
- package/dist/chunk-U4N7WF4Z.mjs +108 -0
- package/dist/chunk-URDE3EUU.mjs +132 -0
- package/dist/chunk-URLL5JBR.mjs +245 -0
- package/dist/chunk-XDMN67KV.mjs +59 -0
- package/dist/chunk-Y6MXOREN.mjs +120 -0
- package/dist/chunk-YZJAFS4P.mjs +131 -0
- package/dist/index.d.mts +94 -852
- package/dist/index.d.ts +94 -852
- package/dist/index.js +1387 -942
- package/dist/index.mjs +50 -3844
- package/package.json +23 -14
- 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 +13 -15
- package/src/components/AlertBanner/AlertBanner.tsx +33 -12
- package/src/components/Avatar/Avatar.tsx +4 -2
- package/src/components/Badge/Badge.tsx +4 -2
- package/src/components/Button/Button.tsx +30 -29
- package/src/components/ButtonGroup/ButtonGroup.tsx +13 -10
- package/src/components/Card/Card.tsx +36 -65
- package/src/components/CategoryStrip/CategoryStrip.tsx +68 -58
- package/src/components/Checkbox/Checkbox.tsx +41 -55
- package/src/components/Chip/Chip.tsx +49 -84
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +2 -2
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +4 -2
- package/src/components/CurrencyInput/CurrencyInput.tsx +2 -2
- package/src/components/DetailRow/DetailRow.tsx +9 -7
- package/src/components/EmptyState/EmptyState.tsx +2 -2
- package/src/components/Form/Form.tsx +149 -0
- package/src/components/Form/index.ts +1 -0
- package/src/components/IconButton/IconButton.tsx +24 -20
- package/src/components/Input/Input.tsx +63 -50
- 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 +30 -43
- package/src/components/MediaCard/MediaCard.tsx +31 -29
- package/src/components/MenuGroup/MenuGroup.tsx +145 -0
- package/src/components/MenuGroup/index.ts +1 -0
- package/src/components/MenuItem/MenuItem.tsx +29 -40
- package/src/components/MonthPicker/MonthPicker.tsx +14 -4
- package/src/components/Pressable/Pressable.tsx +27 -46
- package/src/components/Progress/Progress.tsx +21 -12
- package/src/components/RadioGroup/RadioGroup.tsx +55 -32
- package/src/components/Select/Select.tsx +23 -21
- package/src/components/Separator/Separator.tsx +1 -3
- package/src/components/Sheet/Sheet.tsx +85 -18
- package/src/components/Skeleton/Skeleton.tsx +25 -14
- package/src/components/Slider/Slider.tsx +13 -3
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Switch/Switch.tsx +70 -52
- package/src/components/Tabs/Tabs.tsx +59 -47
- package/src/components/Text/Text.tsx +3 -1
- package/src/components/Textarea/Textarea.tsx +44 -23
- package/src/components/Toast/Toast.tsx +6 -6
- package/src/components/Toggle/Toggle.tsx +86 -68
- package/src/components/VirtualList/VirtualList.tsx +60 -0
- package/src/components/VirtualList/index.ts +1 -0
- package/src/fonts.ts +38 -20
- package/src/index.ts +5 -1
- 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 +58 -0
- package/src/utils/icons.ts +47 -20
- package/src/utils/useColorTransition.ts +40 -0
- package/src/utils/usePressScale.ts +75 -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
|
@@ -1,66 +1,77 @@
|
|
|
1
|
-
import React, { useEffect
|
|
2
|
-
import { TouchableOpacity,
|
|
1
|
+
import React, { useEffect } from 'react'
|
|
2
|
+
import { TouchableOpacity, StyleSheet, ViewStyle, View } from 'react-native'
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withSpring,
|
|
7
|
+
withTiming,
|
|
8
|
+
interpolateColor,
|
|
9
|
+
} from 'react-native-reanimated'
|
|
3
10
|
import { Feather } from '@expo/vector-icons'
|
|
4
|
-
|
|
5
|
-
const nativeDriver = Platform.OS !== 'web'
|
|
6
11
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
7
12
|
import { useTheme } from '../../theme'
|
|
8
13
|
import { s } from '../../utils/scaling'
|
|
14
|
+
import { SPRINGS, TIMINGS, EASINGS } from '../../utils/animations'
|
|
9
15
|
|
|
10
|
-
const TRACK_WIDTH
|
|
16
|
+
const TRACK_WIDTH = s(52)
|
|
11
17
|
const TRACK_HEIGHT = s(30)
|
|
12
|
-
const THUMB_SIZE
|
|
18
|
+
const THUMB_SIZE = s(24)
|
|
13
19
|
const THUMB_OFFSET = s(3)
|
|
14
20
|
const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
|
|
21
|
+
const ICON_SIZE = s(13)
|
|
15
22
|
|
|
16
23
|
export interface SwitchProps {
|
|
17
24
|
checked?: boolean
|
|
18
25
|
onCheckedChange?: (checked: boolean) => void
|
|
19
26
|
disabled?: boolean
|
|
20
27
|
style?: ViewStyle
|
|
28
|
+
accessibilityLabel?: string
|
|
21
29
|
}
|
|
22
30
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export function Switch({ checked = false, onCheckedChange, disabled, style }: SwitchProps) {
|
|
31
|
+
export function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }: SwitchProps) {
|
|
26
32
|
const { colors } = useTheme()
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const checkOpacity = useRef(new Animated.Value(checked ? 1 : 0)).current
|
|
30
|
-
const crossOpacity = useRef(new Animated.Value(checked ? 0 : 1)).current
|
|
33
|
+
|
|
34
|
+
const progress = useSharedValue(checked ? 1 : 0)
|
|
31
35
|
|
|
32
36
|
useEffect(() => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
progress.value = withSpring(checked ? 1 : 0, SPRINGS.elastic)
|
|
38
|
+
}, [checked, progress])
|
|
39
|
+
|
|
40
|
+
const thumbStyle = useAnimatedStyle(() => ({
|
|
41
|
+
transform: [{ translateX: progress.value * THUMB_TRAVEL }],
|
|
42
|
+
}))
|
|
43
|
+
|
|
44
|
+
const trackStyle = useAnimatedStyle(() => ({
|
|
45
|
+
backgroundColor: interpolateColor(
|
|
46
|
+
progress.value,
|
|
47
|
+
[0, 1],
|
|
48
|
+
[colors.surfaceStrong, colors.primary],
|
|
49
|
+
),
|
|
50
|
+
}))
|
|
51
|
+
|
|
52
|
+
// AUDIT FIX: the off-state track used surfaceStrong (~#ebebeb in light mode)
|
|
53
|
+
// with no border — nearly invisible on white page/card surfaces. A 1.5px border
|
|
54
|
+
// that fades out as the track fills gives the off state clear visual definition
|
|
55
|
+
// without adding visual weight to the on state.
|
|
56
|
+
const trackBorderStyle = useAnimatedStyle(() => ({
|
|
57
|
+
borderWidth: 1.5,
|
|
58
|
+
borderColor: interpolateColor(
|
|
59
|
+
progress.value,
|
|
60
|
+
[0, 1],
|
|
61
|
+
[colors.border, 'transparent'],
|
|
62
|
+
),
|
|
63
|
+
}))
|
|
56
64
|
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
const checkIconStyle = useAnimatedStyle(() => ({
|
|
66
|
+
opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
|
|
67
|
+
}))
|
|
68
|
+
|
|
69
|
+
const crossIconStyle = useAnimatedStyle(() => ({
|
|
70
|
+
opacity: withTiming(checked ? 0 : 1, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
|
|
71
|
+
}))
|
|
61
72
|
|
|
62
73
|
return (
|
|
63
|
-
<View style={[{ opacity: disabled ? 0.45 : 1 }, style]}>
|
|
74
|
+
<View style={[{ opacity: disabled ? 0.45 : 1, alignSelf: 'flex-start' }, style]}>
|
|
64
75
|
<TouchableOpacity
|
|
65
76
|
onPress={() => {
|
|
66
77
|
hapticSelection()
|
|
@@ -69,19 +80,20 @@ export function Switch({ checked = false, onCheckedChange, disabled, style }: Sw
|
|
|
69
80
|
disabled={disabled}
|
|
70
81
|
activeOpacity={0.8}
|
|
71
82
|
touchSoundDisabled={true}
|
|
72
|
-
|
|
83
|
+
accessibilityRole="switch"
|
|
84
|
+
accessibilityLabel={accessibilityLabel}
|
|
85
|
+
accessibilityState={{ checked, disabled: !!disabled }}
|
|
86
|
+
style={styles.touchable}
|
|
73
87
|
>
|
|
74
|
-
<Animated.View style={[styles.track,
|
|
88
|
+
<Animated.View style={[styles.track, trackStyle]}>
|
|
89
|
+
<Animated.View style={[styles.trackBorder, trackBorderStyle]} pointerEvents="none" />
|
|
75
90
|
<Animated.View
|
|
76
|
-
style={[
|
|
77
|
-
styles.thumb,
|
|
78
|
-
{ backgroundColor: colors.primaryForeground, transform: [{ translateX }] },
|
|
79
|
-
]}
|
|
91
|
+
style={[styles.thumb, { backgroundColor: colors.primaryForeground }, thumbStyle]}
|
|
80
92
|
>
|
|
81
|
-
<Animated.View style={[styles.iconWrapper,
|
|
93
|
+
<Animated.View style={[styles.iconWrapper, checkIconStyle]}>
|
|
82
94
|
<Feather name="check" size={ICON_SIZE} color={colors.primary} />
|
|
83
95
|
</Animated.View>
|
|
84
|
-
<Animated.View style={[styles.iconWrapper,
|
|
96
|
+
<Animated.View style={[styles.iconWrapper, crossIconStyle]}>
|
|
85
97
|
<Feather name="x" size={ICON_SIZE} color={colors.foregroundMuted} />
|
|
86
98
|
</Animated.View>
|
|
87
99
|
</Animated.View>
|
|
@@ -92,13 +104,17 @@ export function Switch({ checked = false, onCheckedChange, disabled, style }: Sw
|
|
|
92
104
|
}
|
|
93
105
|
|
|
94
106
|
const styles = StyleSheet.create({
|
|
95
|
-
|
|
107
|
+
touchable: {
|
|
108
|
+
alignSelf: 'flex-start',
|
|
109
|
+
},
|
|
96
110
|
track: {
|
|
97
111
|
width: TRACK_WIDTH,
|
|
98
112
|
height: TRACK_HEIGHT,
|
|
99
113
|
borderRadius: TRACK_HEIGHT / 2,
|
|
100
|
-
|
|
101
|
-
|
|
114
|
+
},
|
|
115
|
+
trackBorder: {
|
|
116
|
+
...StyleSheet.absoluteFillObject,
|
|
117
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
102
118
|
},
|
|
103
119
|
thumb: {
|
|
104
120
|
position: 'absolute',
|
|
@@ -117,5 +133,7 @@ const styles = StyleSheet.create({
|
|
|
117
133
|
},
|
|
118
134
|
iconWrapper: {
|
|
119
135
|
position: 'absolute',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'center',
|
|
120
138
|
},
|
|
121
139
|
})
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react'
|
|
2
|
-
import { View, TouchableOpacity, Text,
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react'
|
|
2
|
+
import { View, TouchableOpacity, Text, StyleSheet, ViewStyle, LayoutChangeEvent } from 'react-native'
|
|
3
|
+
import Animated, {
|
|
4
|
+
useSharedValue,
|
|
5
|
+
useAnimatedStyle,
|
|
6
|
+
withSpring,
|
|
7
|
+
} from 'react-native-reanimated'
|
|
3
8
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
4
|
-
|
|
5
|
-
const nativeDriver = Platform.OS !== 'web'
|
|
6
9
|
import { useTheme } from '../../theme'
|
|
7
10
|
import { s, vs, ms } from '../../utils/scaling'
|
|
11
|
+
import { usePressScale } from '../../utils/usePressScale'
|
|
12
|
+
import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
|
|
8
13
|
|
|
9
14
|
export interface TabItem {
|
|
10
15
|
label: string
|
|
@@ -12,8 +17,6 @@ export interface TabItem {
|
|
|
12
17
|
icon?: React.ReactNode | ((active: boolean) => React.ReactNode)
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
// pill: animated sliding pill background (default)
|
|
16
|
-
// underline: 2px bottom border on active tab — Airbnb product-tab style
|
|
17
20
|
export type TabsVariant = 'pill' | 'underline'
|
|
18
21
|
|
|
19
22
|
export interface TabsProps {
|
|
@@ -42,20 +45,13 @@ function TabTrigger({
|
|
|
42
45
|
tab: TabItem
|
|
43
46
|
isActive: boolean
|
|
44
47
|
onPress: () => void
|
|
45
|
-
onLayout: (e:
|
|
48
|
+
onLayout: (e: LayoutChangeEvent) => void
|
|
46
49
|
variant: TabsVariant
|
|
47
50
|
}) {
|
|
48
51
|
const { colors } = useTheme()
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, stiffness: 600, damping: 35, mass: 0.8 }).start()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const handlePressOut = () => {
|
|
56
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, stiffness: 280, damping: 22, mass: 0.8 }).start()
|
|
57
|
-
}
|
|
58
|
-
|
|
52
|
+
const { animatedStyle, onPressIn, onPressOut } = usePressScale({
|
|
53
|
+
pressScale: PRESS_SCALE.button,
|
|
54
|
+
})
|
|
59
55
|
const isUnderline = variant === 'underline'
|
|
60
56
|
|
|
61
57
|
return (
|
|
@@ -66,22 +62,28 @@ function TabTrigger({
|
|
|
66
62
|
isUnderline && isActive && { borderBottomColor: colors.primary },
|
|
67
63
|
]}
|
|
68
64
|
onPress={onPress}
|
|
69
|
-
onPressIn={
|
|
70
|
-
onPressOut={
|
|
65
|
+
onPressIn={onPressIn}
|
|
66
|
+
onPressOut={onPressOut}
|
|
71
67
|
onLayout={onLayout}
|
|
72
68
|
activeOpacity={1}
|
|
73
69
|
touchSoundDisabled={true}
|
|
70
|
+
accessibilityRole="tab"
|
|
71
|
+
accessibilityState={{ selected: isActive }}
|
|
72
|
+
accessibilityLabel={tab.label}
|
|
74
73
|
>
|
|
75
|
-
<Animated.View style={
|
|
74
|
+
<Animated.View style={animatedStyle}>
|
|
76
75
|
<View style={styles.triggerInner}>
|
|
77
76
|
{tab.icon ? (
|
|
78
|
-
|
|
77
|
+
typeof tab.icon === 'function' ? tab.icon(isActive) : tab.icon
|
|
79
78
|
) : null}
|
|
80
79
|
<Text
|
|
81
80
|
style={[
|
|
82
81
|
styles.triggerLabel,
|
|
82
|
+
// AUDIT FIX: active state now only changes color, never font metrics.
|
|
83
|
+
// Previously: inactive=Regular, active=Medium (pill) or SemiBold+fontSize14 (underline)
|
|
84
|
+
// The weight/size change caused measurable layout reflow every tab switch.
|
|
85
|
+
// Solution: all labels render at SemiBold always; active = foreground, inactive = foregroundMuted.
|
|
83
86
|
{ color: isActive ? colors.foreground : colors.foregroundMuted },
|
|
84
|
-
isActive && (isUnderline ? styles.activeTriggerLabelUnderline : styles.activeTriggerLabel),
|
|
85
87
|
]}
|
|
86
88
|
allowFontScaling={true}
|
|
87
89
|
>
|
|
@@ -99,27 +101,27 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
99
101
|
const active = value ?? internal
|
|
100
102
|
|
|
101
103
|
const tabLayouts = useRef<Record<string, { x: number; width: number }>>({})
|
|
102
|
-
const pillX =
|
|
103
|
-
const pillWidth =
|
|
104
|
+
const pillX = useSharedValue(0)
|
|
105
|
+
const pillWidth = useSharedValue(0)
|
|
104
106
|
const initialised = useRef(false)
|
|
105
107
|
|
|
106
|
-
const animatePill = (tabValue: string, animate: boolean) => {
|
|
108
|
+
const animatePill = useCallback((tabValue: string, animate: boolean) => {
|
|
107
109
|
const layout = tabLayouts.current[tabValue]
|
|
108
110
|
if (!layout) return
|
|
109
111
|
if (animate) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
113
|
+
pillX.value = withSpring(layout.x, SPRINGS.glide)
|
|
114
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
115
|
+
pillWidth.value = withSpring(layout.width, SPRINGS.glide)
|
|
114
116
|
} else {
|
|
115
|
-
pillX.
|
|
116
|
-
pillWidth.
|
|
117
|
+
pillX.value = layout.x
|
|
118
|
+
pillWidth.value = layout.width
|
|
117
119
|
}
|
|
118
|
-
}
|
|
120
|
+
}, [pillX, pillWidth])
|
|
119
121
|
|
|
120
122
|
useEffect(() => {
|
|
121
123
|
if (initialised.current) animatePill(active, true)
|
|
122
|
-
}, [active])
|
|
124
|
+
}, [active, animatePill])
|
|
123
125
|
|
|
124
126
|
const handlePress = (v: string) => {
|
|
125
127
|
hapticSelection()
|
|
@@ -127,11 +129,21 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
127
129
|
onValueChange?.(v)
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
const pillAnimatedStyle = useAnimatedStyle(() => ({
|
|
133
|
+
transform: [{ translateX: pillX.value }],
|
|
134
|
+
width: pillWidth.value,
|
|
135
|
+
}))
|
|
136
|
+
|
|
130
137
|
return (
|
|
131
138
|
<View style={style}>
|
|
132
|
-
<View
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
<View
|
|
140
|
+
style={[
|
|
141
|
+
variant === 'pill'
|
|
142
|
+
? [styles.list, { backgroundColor: colors.surface }]
|
|
143
|
+
: styles.listUnderline,
|
|
144
|
+
]}
|
|
145
|
+
accessibilityRole="tablist"
|
|
146
|
+
>
|
|
135
147
|
{variant === 'pill' && (
|
|
136
148
|
<Animated.View
|
|
137
149
|
style={[
|
|
@@ -141,8 +153,7 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
141
153
|
position: 'absolute',
|
|
142
154
|
top: 4,
|
|
143
155
|
bottom: 4,
|
|
144
|
-
left:
|
|
145
|
-
width: pillWidth,
|
|
156
|
+
left: 0,
|
|
146
157
|
borderRadius: 8,
|
|
147
158
|
shadowColor: '#000',
|
|
148
159
|
shadowOffset: { width: 0, height: 1 },
|
|
@@ -150,6 +161,7 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
150
161
|
shadowRadius: 2,
|
|
151
162
|
elevation: 2,
|
|
152
163
|
},
|
|
164
|
+
pillAnimatedStyle,
|
|
153
165
|
]}
|
|
154
166
|
/>
|
|
155
167
|
)}
|
|
@@ -178,7 +190,7 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
178
190
|
|
|
179
191
|
export function TabsContent({ value, activeValue, children, style }: TabsContentProps) {
|
|
180
192
|
if (value !== activeValue) return null
|
|
181
|
-
return <View style={style}>{children}</View>
|
|
193
|
+
return <View style={style} accessibilityRole="none">{children}</View>
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
const styles = StyleSheet.create({
|
|
@@ -190,6 +202,8 @@ const styles = StyleSheet.create({
|
|
|
190
202
|
},
|
|
191
203
|
listUnderline: {
|
|
192
204
|
flexDirection: 'row',
|
|
205
|
+
// AUDIT FIX: was missing borderBottomColor — the 1px hairline would render
|
|
206
|
+
// as transparent on some platforms. Explicit token reference ensures visibility.
|
|
193
207
|
borderBottomWidth: 1,
|
|
194
208
|
},
|
|
195
209
|
pill: {},
|
|
@@ -216,15 +230,13 @@ const styles = StyleSheet.create({
|
|
|
216
230
|
justifyContent: 'center',
|
|
217
231
|
gap: s(4),
|
|
218
232
|
},
|
|
233
|
+
// AUDIT FIX: was Sohne-Regular at rest, Sohne-Medium/SemiBold when active.
|
|
234
|
+
// Font-weight changes at runtime cause advance-width shifts → the tab bar would
|
|
235
|
+
// visibly jump/reflow on every selection. Now always SemiBold; active state
|
|
236
|
+
// is communicated by color alone (foreground vs foregroundMuted). The pill
|
|
237
|
+
// indicator provides additional active signal without text layout side-effects.
|
|
219
238
|
triggerLabel: {
|
|
220
|
-
fontFamily: '
|
|
239
|
+
fontFamily: 'Sohne-SemiBold',
|
|
221
240
|
fontSize: ms(13),
|
|
222
241
|
},
|
|
223
|
-
activeTriggerLabel: {
|
|
224
|
-
fontFamily: 'Poppins-Medium',
|
|
225
|
-
},
|
|
226
|
-
activeTriggerLabelUnderline: {
|
|
227
|
-
fontFamily: 'Poppins-SemiBold',
|
|
228
|
-
fontSize: ms(14),
|
|
229
|
-
},
|
|
230
242
|
})
|
|
@@ -67,7 +67,7 @@ const defaultColorVariant: Partial<Record<TextVariant, 'foreground' | 'foregroun
|
|
|
67
67
|
'button-sm': 'foreground',
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
function TextBase({ variant = 'body-md', color, style, children, ...props }: TextProps) {
|
|
71
71
|
const { colors } = useTheme()
|
|
72
72
|
|
|
73
73
|
const colorKey = defaultColorVariant[variant] ?? 'foreground'
|
|
@@ -83,3 +83,5 @@ export function Text({ variant = 'body-md', color, style, children, ...props }:
|
|
|
83
83
|
</RNText>
|
|
84
84
|
)
|
|
85
85
|
}
|
|
86
|
+
|
|
87
|
+
export const Text = React.memo(TextBase)
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
import React, { useState } from 'react'
|
|
2
2
|
import { TextInput, View, Text, StyleSheet, TextInputProps, ViewStyle, Platform } from 'react-native'
|
|
3
|
+
import Animated, {
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
interpolateColor,
|
|
6
|
+
interpolate,
|
|
7
|
+
} from 'react-native-reanimated'
|
|
3
8
|
import { useTheme } from '../../theme'
|
|
4
9
|
import { s, vs, ms } from '../../utils/scaling'
|
|
5
10
|
import { renderIcon } from '../../utils/icons'
|
|
11
|
+
import { useColorTransition } from '../../utils/useColorTransition'
|
|
12
|
+
import { TIMINGS } from '../../utils/animations'
|
|
6
13
|
|
|
7
|
-
const webInputResetStyle:
|
|
14
|
+
const webInputResetStyle: Record<string, unknown> =
|
|
8
15
|
Platform.OS === 'web'
|
|
9
16
|
? { outlineStyle: 'none', outlineWidth: 0, outlineColor: 'transparent', boxShadow: 'none' }
|
|
10
17
|
: {}
|
|
11
18
|
|
|
12
19
|
export interface TextareaProps extends TextInputProps {
|
|
13
20
|
label?: string
|
|
14
|
-
/** Red helper text below the textarea; also changes border to `destructive` color. Takes priority over `hint`. */
|
|
15
21
|
error?: string
|
|
16
|
-
/** Helper text shown below the textarea when there is no error. */
|
|
17
22
|
hint?: string
|
|
18
|
-
/** Number of visible text rows. Defaults to `4`. Controls `numberOfLines` and `minHeight`. */
|
|
19
23
|
rows?: number
|
|
20
|
-
/** Icon name from @expo/vector-icons rendered inside top-left corner. */
|
|
21
24
|
prefixIcon?: string
|
|
22
|
-
/** Custom icon node rendered top-left. */
|
|
23
25
|
prefixIconNode?: React.ReactNode
|
|
24
|
-
/** Override prefix icon color. Defaults to foregroundMuted. */
|
|
25
26
|
prefixIconColor?: string
|
|
26
|
-
/** Style for the outer container `View`. Use `style` (from `TextInputProps`) to style the `TextInput` itself. */
|
|
27
27
|
containerStyle?: ViewStyle
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -39,31 +39,40 @@ export function Textarea({
|
|
|
39
39
|
style,
|
|
40
40
|
onFocus,
|
|
41
41
|
onBlur,
|
|
42
|
+
accessibilityLabel,
|
|
42
43
|
...props
|
|
43
44
|
}: TextareaProps) {
|
|
44
45
|
const { colors } = useTheme()
|
|
45
46
|
const [focused, setFocused] = useState(false)
|
|
47
|
+
const focusProgress = useColorTransition(focused, {
|
|
48
|
+
duration: focused ? TIMINGS.focusIn.duration : TIMINGS.focusOut.duration,
|
|
49
|
+
})
|
|
46
50
|
|
|
47
51
|
const resolvedPrefixIcon = prefixIcon
|
|
48
52
|
? renderIcon(prefixIcon, ms(16), prefixIconColor ?? colors.foregroundMuted)
|
|
49
53
|
: prefixIconNode
|
|
50
54
|
|
|
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(() => ({
|
|
58
|
+
borderColor: error
|
|
59
|
+
? colors.destructive
|
|
60
|
+
: interpolateColor(focusProgress.value, [0, 1], [colors.border, colors.primary]),
|
|
61
|
+
borderWidth: error
|
|
62
|
+
? 2
|
|
63
|
+
: interpolate(focusProgress.value, [0, 1], [1, 2]),
|
|
64
|
+
}))
|
|
65
|
+
|
|
51
66
|
return (
|
|
52
67
|
<View style={[styles.container, containerStyle]}>
|
|
53
68
|
{label ? <Text style={[styles.label, { color: colors.foreground }]} allowFontScaling={true}>{label}</Text> : null}
|
|
54
|
-
<View
|
|
69
|
+
<Animated.View
|
|
55
70
|
style={[
|
|
56
71
|
styles.inputWrapper,
|
|
57
|
-
{
|
|
58
|
-
borderColor: error
|
|
59
|
-
? colors.destructive
|
|
60
|
-
: focused
|
|
61
|
-
? (colors.ring ?? colors.primary)
|
|
62
|
-
: colors.border,
|
|
63
|
-
backgroundColor: colors.background,
|
|
64
|
-
},
|
|
72
|
+
{ backgroundColor: colors.background },
|
|
65
73
|
]}
|
|
66
74
|
>
|
|
75
|
+
<Animated.View style={[styles.borderOverlay, borderAnimStyle]} pointerEvents="none" />
|
|
67
76
|
{resolvedPrefixIcon ? <View style={styles.prefixIcon}>{resolvedPrefixIcon}</View> : null}
|
|
68
77
|
<TextInput
|
|
69
78
|
multiline
|
|
@@ -88,11 +97,18 @@ export function Textarea({
|
|
|
88
97
|
}}
|
|
89
98
|
placeholderTextColor={colors.foregroundMuted}
|
|
90
99
|
allowFontScaling={true}
|
|
100
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
91
101
|
{...props}
|
|
92
102
|
/>
|
|
93
|
-
</View>
|
|
103
|
+
</Animated.View>
|
|
94
104
|
{error ? (
|
|
95
|
-
<Text
|
|
105
|
+
<Text
|
|
106
|
+
style={[styles.helperText, { color: colors.destructive }]}
|
|
107
|
+
allowFontScaling={true}
|
|
108
|
+
accessibilityLiveRegion="polite"
|
|
109
|
+
>
|
|
110
|
+
{error}
|
|
111
|
+
</Text>
|
|
96
112
|
) : null}
|
|
97
113
|
{!error && hint ? (
|
|
98
114
|
<Text style={[styles.helperText, { color: colors.foregroundMuted }]} allowFontScaling={true}>{hint}</Text>
|
|
@@ -106,32 +122,37 @@ const styles = StyleSheet.create({
|
|
|
106
122
|
gap: vs(4),
|
|
107
123
|
},
|
|
108
124
|
label: {
|
|
109
|
-
fontFamily: '
|
|
125
|
+
fontFamily: 'Sohne-Medium',
|
|
110
126
|
fontSize: ms(13),
|
|
111
127
|
lineHeight: vs(18),
|
|
112
128
|
marginBottom: vs(2),
|
|
113
129
|
},
|
|
114
130
|
inputWrapper: {
|
|
115
|
-
|
|
131
|
+
// Border lives on borderOverlay (absolute); wrapper carries none so the
|
|
132
|
+
// focus weight change never reflows content.
|
|
116
133
|
borderRadius: 8,
|
|
117
134
|
paddingHorizontal: s(14),
|
|
118
135
|
paddingVertical: vs(11),
|
|
119
136
|
gap: s(8),
|
|
120
137
|
},
|
|
138
|
+
borderOverlay: {
|
|
139
|
+
...StyleSheet.absoluteFillObject,
|
|
140
|
+
borderRadius: 8,
|
|
141
|
+
},
|
|
121
142
|
prefixIcon: {
|
|
122
143
|
alignItems: 'flex-start',
|
|
123
144
|
justifyContent: 'flex-start',
|
|
124
145
|
paddingTop: vs(2),
|
|
125
146
|
},
|
|
126
147
|
input: {
|
|
127
|
-
fontFamily: '
|
|
148
|
+
fontFamily: 'Sohne-Regular',
|
|
128
149
|
fontSize: ms(14),
|
|
129
150
|
lineHeight: vs(22),
|
|
130
151
|
padding: 0,
|
|
131
152
|
margin: 0,
|
|
132
153
|
},
|
|
133
154
|
helperText: {
|
|
134
|
-
fontFamily: '
|
|
155
|
+
fontFamily: 'Sohne-Regular',
|
|
135
156
|
fontSize: ms(12),
|
|
136
157
|
lineHeight: vs(16),
|
|
137
158
|
marginTop: vs(4),
|
|
@@ -4,10 +4,8 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
|
|
4
4
|
import { useTheme } from '../../theme'
|
|
5
5
|
import { s, vs, ms } from '../../utils/scaling'
|
|
6
6
|
|
|
7
|
-
// Direct function API — no hook required
|
|
8
7
|
export { sonnerToast as toast }
|
|
9
8
|
|
|
10
|
-
// useToast — backward-compat wrapper
|
|
11
9
|
export function useToast() {
|
|
12
10
|
return {
|
|
13
11
|
toast: sonnerToast,
|
|
@@ -15,7 +13,6 @@ export function useToast() {
|
|
|
15
13
|
}
|
|
16
14
|
}
|
|
17
15
|
|
|
18
|
-
// ToastProvider — wraps children + renders the Toaster
|
|
19
16
|
export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
20
17
|
const { colorScheme } = useTheme()
|
|
21
18
|
const insets = useSafeAreaInsets()
|
|
@@ -26,7 +23,10 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
|
26
23
|
<Toaster
|
|
27
24
|
theme={colorScheme}
|
|
28
25
|
position="top-center"
|
|
29
|
-
richColors={false}
|
|
26
|
+
// AUDIT FIX: was richColors={false} — all semantic variants (error, success,
|
|
27
|
+
// warning) were visually identical. richColors={true} restores correct
|
|
28
|
+
// semantic coloring so users immediately recognise severity from colour alone.
|
|
29
|
+
richColors={true}
|
|
30
30
|
gap={vs(8)}
|
|
31
31
|
offset={insets.top + vs(8)}
|
|
32
32
|
visibleToasts={3}
|
|
@@ -40,11 +40,11 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
|
|
|
40
40
|
paddingVertical: vs(10),
|
|
41
41
|
},
|
|
42
42
|
titleStyle: {
|
|
43
|
-
fontFamily: '
|
|
43
|
+
fontFamily: 'Sohne-Medium',
|
|
44
44
|
fontSize: ms(13),
|
|
45
45
|
},
|
|
46
46
|
descriptionStyle: {
|
|
47
|
-
fontFamily: '
|
|
47
|
+
fontFamily: 'Sohne-Regular',
|
|
48
48
|
fontSize: ms(12),
|
|
49
49
|
opacity: 0.85,
|
|
50
50
|
},
|