@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,38 +1,31 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
StyleSheet,
|
|
8
|
-
ViewStyle,
|
|
9
|
-
Platform,
|
|
10
|
-
Easing,
|
|
11
|
-
} from 'react-native'
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { TouchableOpacity, View, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import Animated, {
|
|
4
|
+
useAnimatedStyle,
|
|
5
|
+
interpolateColor,
|
|
6
|
+
} from 'react-native-reanimated'
|
|
12
7
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
13
8
|
import { useTheme } from '../../theme'
|
|
14
9
|
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
15
10
|
import { renderIcon } from '../../utils/icons'
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
import { usePressScale } from '../../utils/usePressScale'
|
|
12
|
+
import { useColorTransition } from '../../utils/useColorTransition'
|
|
13
|
+
import { PRESS_SCALE } from '../../utils/animations'
|
|
18
14
|
|
|
19
15
|
export interface ChipProps {
|
|
20
16
|
label: string
|
|
21
17
|
selected?: boolean
|
|
22
18
|
onPress?: () => void
|
|
23
|
-
/** JSX icon rendered before the label. */
|
|
24
19
|
icon?: React.ReactNode
|
|
25
|
-
/** Icon name from @expo/vector-icons resolved automatically. */
|
|
26
20
|
iconName?: string
|
|
27
21
|
style?: ViewStyle
|
|
22
|
+
accessibilityLabel?: string
|
|
28
23
|
}
|
|
29
24
|
|
|
30
25
|
export interface ChipOption {
|
|
31
26
|
label: string
|
|
32
27
|
value: string | number
|
|
33
|
-
/** Icon name resolved via renderIcon (Feather, AntDesign, etc.). */
|
|
34
28
|
iconName?: string
|
|
35
|
-
/** Icon tint color override. */
|
|
36
29
|
iconColor?: string
|
|
37
30
|
disabled?: boolean
|
|
38
31
|
}
|
|
@@ -41,79 +34,50 @@ export interface ChipGroupProps {
|
|
|
41
34
|
options: ChipOption[]
|
|
42
35
|
value?: string | number | (string | number)[]
|
|
43
36
|
onValueChange?: (value: string | number | (string | number)[]) => void
|
|
44
|
-
/** When true, allows selecting multiple chips. `value` and `onValueChange` will use arrays. */
|
|
45
37
|
multiSelect?: boolean
|
|
46
38
|
style?: ViewStyle
|
|
47
39
|
}
|
|
48
40
|
|
|
49
|
-
|
|
41
|
+
function ChipBase({ label, selected = false, onPress, icon, iconName, style, accessibilityLabel }: ChipProps) {
|
|
50
42
|
const { colors } = useTheme()
|
|
51
|
-
const
|
|
52
|
-
|
|
43
|
+
const { animatedStyle: scaleStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
44
|
+
pressScale: PRESS_SCALE.chip,
|
|
45
|
+
})
|
|
46
|
+
const colorProgress = useColorTransition(selected)
|
|
53
47
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
easing: Easing.out(Easing.ease),
|
|
59
|
-
useNativeDriver: false,
|
|
60
|
-
}).start()
|
|
61
|
-
}, [selected, pressAnim])
|
|
48
|
+
const surfaceStyle = useAnimatedStyle(() => ({
|
|
49
|
+
backgroundColor: interpolateColor(colorProgress.value, [0, 1], [colors.surface, colors.primary]),
|
|
50
|
+
borderColor: interpolateColor(colorProgress.value, [0, 1], [colors.border, colors.primary]),
|
|
51
|
+
}))
|
|
62
52
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
useNativeDriver: nativeDriver,
|
|
67
|
-
speed: 40,
|
|
68
|
-
bounciness: 0,
|
|
69
|
-
}).start()
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const handlePressOut = () => {
|
|
73
|
-
Animated.spring(scale, {
|
|
74
|
-
toValue: 1,
|
|
75
|
-
useNativeDriver: nativeDriver,
|
|
76
|
-
speed: 40,
|
|
77
|
-
bounciness: 4,
|
|
78
|
-
}).start()
|
|
79
|
-
}
|
|
53
|
+
const textStyle = useAnimatedStyle(() => ({
|
|
54
|
+
color: interpolateColor(colorProgress.value, [0, 1], [colors.foreground, colors.primaryForeground]),
|
|
55
|
+
}))
|
|
80
56
|
|
|
81
57
|
const handlePress = () => {
|
|
82
58
|
hapticSelection()
|
|
83
59
|
onPress?.()
|
|
84
60
|
}
|
|
85
61
|
|
|
86
|
-
const backgroundColor = pressAnim.interpolate({
|
|
87
|
-
inputRange: [0, 1],
|
|
88
|
-
outputRange: [colors.surface, colors.primary],
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const textColor = pressAnim.interpolate({
|
|
92
|
-
inputRange: [0, 1],
|
|
93
|
-
outputRange: [colors.foreground, colors.primaryForeground],
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const borderColor = pressAnim.interpolate({
|
|
97
|
-
inputRange: [0, 1],
|
|
98
|
-
outputRange: [colors.border, colors.primary],
|
|
99
|
-
})
|
|
100
|
-
|
|
101
62
|
const resolvedIcon = iconName
|
|
102
63
|
? renderIcon(iconName, ms(13), selected ? colors.primaryForeground : colors.foreground)
|
|
103
64
|
: icon
|
|
104
65
|
|
|
105
66
|
return (
|
|
106
|
-
<Animated.View style={[styles.wrapper,
|
|
67
|
+
<Animated.View style={[styles.wrapper, scaleStyle, style]} {...hoverHandlers}>
|
|
107
68
|
<TouchableOpacity
|
|
108
69
|
onPress={handlePress}
|
|
109
|
-
onPressIn={
|
|
110
|
-
onPressOut={
|
|
70
|
+
onPressIn={onPressIn}
|
|
71
|
+
onPressOut={onPressOut}
|
|
111
72
|
activeOpacity={1}
|
|
112
73
|
touchSoundDisabled={true}
|
|
74
|
+
accessibilityRole="button"
|
|
75
|
+
accessibilityLabel={accessibilityLabel ?? label}
|
|
76
|
+
accessibilityState={{ selected }}
|
|
113
77
|
>
|
|
114
|
-
<Animated.View style={[styles.chip,
|
|
78
|
+
<Animated.View style={[styles.chip, surfaceStyle]}>
|
|
115
79
|
{resolvedIcon ? <View style={styles.chipIcon}>{resolvedIcon}</View> : null}
|
|
116
|
-
<Animated.Text style={[styles.label,
|
|
80
|
+
<Animated.Text style={[styles.label, textStyle]} allowFontScaling={true}>
|
|
117
81
|
{label}
|
|
118
82
|
</Animated.Text>
|
|
119
83
|
</Animated.View>
|
|
@@ -122,33 +86,24 @@ export function Chip({ label, selected = false, onPress, icon, iconName, style }
|
|
|
122
86
|
)
|
|
123
87
|
}
|
|
124
88
|
|
|
89
|
+
export const Chip = React.memo(ChipBase)
|
|
90
|
+
|
|
125
91
|
export function ChipGroup({ options, value, onValueChange, multiSelect = false, style }: ChipGroupProps) {
|
|
126
92
|
const handlePress = (optionValue: string | number) => {
|
|
127
93
|
if (!multiSelect) {
|
|
128
94
|
onValueChange?.(optionValue)
|
|
129
95
|
return
|
|
130
96
|
}
|
|
131
|
-
|
|
132
|
-
// Multiselect logic
|
|
133
97
|
const currentArray = Array.isArray(value) ? value : value ? [value] : []
|
|
134
98
|
const isSelected = currentArray.includes(optionValue)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// Remove from selection
|
|
139
|
-
newArray = currentArray.filter((v) => v !== optionValue)
|
|
140
|
-
} else {
|
|
141
|
-
// Add to selection
|
|
142
|
-
newArray = [...currentArray, optionValue]
|
|
143
|
-
}
|
|
144
|
-
|
|
99
|
+
const newArray: (string | number)[] = isSelected
|
|
100
|
+
? currentArray.filter((v) => v !== optionValue)
|
|
101
|
+
: [...currentArray, optionValue]
|
|
145
102
|
onValueChange?.(newArray)
|
|
146
103
|
}
|
|
147
104
|
|
|
148
105
|
const isSelected = (optionValue: string | number): boolean => {
|
|
149
|
-
if (Array.isArray(value))
|
|
150
|
-
return value.includes(optionValue)
|
|
151
|
-
}
|
|
106
|
+
if (Array.isArray(value)) return value.includes(optionValue)
|
|
152
107
|
return optionValue === value
|
|
153
108
|
}
|
|
154
109
|
|
|
@@ -161,7 +116,11 @@ export function ChipGroup({ options, value, onValueChange, multiSelect = false,
|
|
|
161
116
|
selected={isSelected(opt.value)}
|
|
162
117
|
onPress={opt.disabled ? undefined : () => handlePress(opt.value)}
|
|
163
118
|
iconName={opt.iconName}
|
|
164
|
-
style={
|
|
119
|
+
// AUDIT FIX: was style={{ opacity: 0.4 }} with no accessibilityState.
|
|
120
|
+
// Now passes disabled state to the Chip's TouchableOpacity via onPress=undefined
|
|
121
|
+
// and adds explicit accessibility state for screen readers.
|
|
122
|
+
style={opt.disabled ? styles.chipDisabled : undefined}
|
|
123
|
+
accessibilityLabel={opt.disabled ? `${opt.label}, unavailable` : opt.label}
|
|
165
124
|
/>
|
|
166
125
|
))}
|
|
167
126
|
</View>
|
|
@@ -173,19 +132,25 @@ const styles = StyleSheet.create({
|
|
|
173
132
|
chip: {
|
|
174
133
|
borderRadius: 999,
|
|
175
134
|
paddingHorizontal: s(14),
|
|
176
|
-
|
|
135
|
+
// AUDIT FIX: was vs(5) → ~28px total height — below WCAG 44px tap target.
|
|
136
|
+
// vs(10) → ~44px total height meets WCAG 2.5.5 (AAA) minimum.
|
|
137
|
+
paddingVertical: vs(10),
|
|
138
|
+
minHeight: 44,
|
|
177
139
|
borderWidth: 1,
|
|
178
140
|
alignItems: 'center',
|
|
179
141
|
justifyContent: 'center',
|
|
180
142
|
flexDirection: 'row',
|
|
181
143
|
gap: s(5),
|
|
182
144
|
},
|
|
145
|
+
chipDisabled: {
|
|
146
|
+
opacity: 0.4,
|
|
147
|
+
},
|
|
183
148
|
chipIcon: {
|
|
184
149
|
alignItems: 'center',
|
|
185
150
|
justifyContent: 'center',
|
|
186
151
|
},
|
|
187
152
|
label: {
|
|
188
|
-
fontFamily: '
|
|
153
|
+
fontFamily: 'Sohne-Medium',
|
|
189
154
|
fontSize: ms(13),
|
|
190
155
|
lineHeight: mvs(18),
|
|
191
156
|
},
|
|
@@ -120,12 +120,12 @@ const styles = StyleSheet.create({
|
|
|
120
120
|
gap: vs(12),
|
|
121
121
|
},
|
|
122
122
|
title: {
|
|
123
|
-
fontFamily: '
|
|
123
|
+
fontFamily: 'Sohne-SemiBold',
|
|
124
124
|
fontSize: ms(18),
|
|
125
125
|
lineHeight: mvs(26),
|
|
126
126
|
},
|
|
127
127
|
description: {
|
|
128
|
-
fontFamily: '
|
|
128
|
+
fontFamily: 'Sohne-Regular',
|
|
129
129
|
fontSize: ms(15),
|
|
130
130
|
lineHeight: mvs(22),
|
|
131
131
|
},
|
|
@@ -49,7 +49,7 @@ function formatValue(value: number | string, prefix: string, showDecimals: boole
|
|
|
49
49
|
return `${sign}${prefix}${intPart}`
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
function CurrencyDisplayBase({ value, prefix = '$', showDecimals = false, textColor, variant, autoScale, maxFontSize, style }: CurrencyDisplayProps) {
|
|
53
53
|
const { colors } = useTheme()
|
|
54
54
|
const formatted = formatValue(value, prefix, showDecimals)
|
|
55
55
|
const baseFontSize = variant ? variantFontSize[variant] : ms(56)
|
|
@@ -71,12 +71,14 @@ export function CurrencyDisplay({ value, prefix = '$', showDecimals = false, tex
|
|
|
71
71
|
)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export const CurrencyDisplay = React.memo(CurrencyDisplayBase)
|
|
75
|
+
|
|
74
76
|
const styles = StyleSheet.create({
|
|
75
77
|
container: {
|
|
76
78
|
alignSelf: 'flex-start',
|
|
77
79
|
},
|
|
78
80
|
amount: {
|
|
79
|
-
fontFamily: '
|
|
81
|
+
fontFamily: 'Sohne-Bold',
|
|
80
82
|
includeFontPadding: false,
|
|
81
83
|
textAlignVertical: 'top',
|
|
82
84
|
},
|
|
@@ -59,8 +59,8 @@ export function CurrencyInput({
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const inputStyle: TextStyle = size === 'large'
|
|
62
|
-
? { fontFamily: '
|
|
63
|
-
: { fontFamily: '
|
|
62
|
+
? { fontFamily: 'Sohne-Regular', fontSize: ms(36) }
|
|
63
|
+
: { fontFamily: 'Sohne-Regular' }
|
|
64
64
|
|
|
65
65
|
const dollarIcon = renderIcon('dollar-sign', size === 'large' ? 24 : 16, colors.foregroundMuted)
|
|
66
66
|
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native'
|
|
3
3
|
import { useTheme } from '../../theme'
|
|
4
|
-
import { s,
|
|
4
|
+
import { s, ms, mvs } from '../../utils/scaling'
|
|
5
5
|
import { renderIcon } from '../../utils/icons'
|
|
6
6
|
|
|
7
7
|
export type DetailRowSeparator = 'dotted' | 'solid' | 'dashed' | 'none'
|
|
8
8
|
export type DetailRowLabelWeight = 'normal' | 'medium' | 'semibold' | 'bold'
|
|
9
9
|
|
|
10
10
|
const weightMap: Record<DetailRowLabelWeight, string> = {
|
|
11
|
-
normal: '
|
|
12
|
-
medium: '
|
|
13
|
-
semibold: '
|
|
14
|
-
bold: '
|
|
11
|
+
normal: 'Sohne-Regular',
|
|
12
|
+
medium: 'Sohne-Medium',
|
|
13
|
+
semibold: 'Sohne-SemiBold',
|
|
14
|
+
bold: 'Sohne-Bold',
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface DetailRowProps {
|
|
@@ -38,7 +38,7 @@ export interface DetailRowProps {
|
|
|
38
38
|
valueStyle?: TextStyle
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
function DetailRowBase({
|
|
42
42
|
label,
|
|
43
43
|
value,
|
|
44
44
|
separator = 'dotted',
|
|
@@ -108,6 +108,8 @@ export function DetailRow({
|
|
|
108
108
|
)
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
export const DetailRow = React.memo(DetailRowBase)
|
|
112
|
+
|
|
111
113
|
const styles = StyleSheet.create({
|
|
112
114
|
row: {
|
|
113
115
|
flexDirection: 'row',
|
|
@@ -138,7 +140,7 @@ const styles = StyleSheet.create({
|
|
|
138
140
|
flexShrink: 0,
|
|
139
141
|
},
|
|
140
142
|
valueText: {
|
|
141
|
-
fontFamily: '
|
|
143
|
+
fontFamily: 'Sohne-SemiBold',
|
|
142
144
|
fontSize: ms(13),
|
|
143
145
|
lineHeight: mvs(18),
|
|
144
146
|
},
|
|
@@ -109,7 +109,7 @@ const styles = StyleSheet.create({
|
|
|
109
109
|
marginTop: vs(16),
|
|
110
110
|
},
|
|
111
111
|
title: {
|
|
112
|
-
fontFamily: '
|
|
112
|
+
fontFamily: 'Sohne-Medium',
|
|
113
113
|
fontSize: ms(18),
|
|
114
114
|
textAlign: 'center',
|
|
115
115
|
},
|
|
@@ -118,7 +118,7 @@ const styles = StyleSheet.create({
|
|
|
118
118
|
marginTop: vs(10),
|
|
119
119
|
},
|
|
120
120
|
description: {
|
|
121
|
-
fontFamily: '
|
|
121
|
+
fontFamily: 'Sohne-Regular',
|
|
122
122
|
fontSize: ms(14),
|
|
123
123
|
lineHeight: mvs(20),
|
|
124
124
|
textAlign: 'center',
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, StyleSheet, ViewStyle, TextStyle } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../theme'
|
|
4
|
+
import { s, vs } from '../../utils/scaling'
|
|
5
|
+
|
|
6
|
+
export interface FormProps {
|
|
7
|
+
children: React.ReactNode
|
|
8
|
+
style?: ViewStyle
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FormFieldProps {
|
|
12
|
+
children: React.ReactNode
|
|
13
|
+
label?: string
|
|
14
|
+
error?: string
|
|
15
|
+
required?: boolean
|
|
16
|
+
style?: ViewStyle
|
|
17
|
+
labelStyle?: TextStyle
|
|
18
|
+
errorStyle?: TextStyle
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FormSectionProps {
|
|
22
|
+
children: React.ReactNode
|
|
23
|
+
title?: string
|
|
24
|
+
description?: string
|
|
25
|
+
style?: ViewStyle
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FormFooterProps {
|
|
29
|
+
children: React.ReactNode
|
|
30
|
+
style?: ViewStyle
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Form wrapper with consistent spacing between fields.
|
|
35
|
+
* Use Form.Field for individual inputs with label + error,
|
|
36
|
+
* Form.Section for grouped fields, and Form.Footer for action buttons.
|
|
37
|
+
*/
|
|
38
|
+
export function Form({ children, style }: FormProps) {
|
|
39
|
+
return <View style={[styles.form, style]}>{children}</View>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Wraps a single form input with optional label and error message.
|
|
44
|
+
* Automatically spaces children properly.
|
|
45
|
+
*/
|
|
46
|
+
export function FormField({
|
|
47
|
+
children,
|
|
48
|
+
label,
|
|
49
|
+
error,
|
|
50
|
+
required,
|
|
51
|
+
style,
|
|
52
|
+
labelStyle,
|
|
53
|
+
errorStyle,
|
|
54
|
+
}: FormFieldProps) {
|
|
55
|
+
const { colors } = useTheme()
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<View style={[styles.field, style]}>
|
|
59
|
+
{label ? (
|
|
60
|
+
<Text style={[styles.label, { color: colors.foreground }, labelStyle]} allowFontScaling={true}>
|
|
61
|
+
{label}
|
|
62
|
+
{required ? <Text style={{ color: colors.destructive }}> *</Text> : null}
|
|
63
|
+
</Text>
|
|
64
|
+
) : null}
|
|
65
|
+
{children}
|
|
66
|
+
{error ? (
|
|
67
|
+
<Text style={[styles.error, { color: colors.destructive }, errorStyle]} allowFontScaling={true}>
|
|
68
|
+
{error}
|
|
69
|
+
</Text>
|
|
70
|
+
) : null}
|
|
71
|
+
</View>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Groups related form fields with optional title and description.
|
|
77
|
+
*/
|
|
78
|
+
export function FormSection({ children, title, description, style }: FormSectionProps) {
|
|
79
|
+
const { colors } = useTheme()
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<View style={[styles.section, style]}>
|
|
83
|
+
{title ? (
|
|
84
|
+
<View style={styles.sectionHeader}>
|
|
85
|
+
<Text style={[styles.sectionTitle, { color: colors.foreground }]} allowFontScaling={true}>
|
|
86
|
+
{title}
|
|
87
|
+
</Text>
|
|
88
|
+
{description ? (
|
|
89
|
+
<Text style={[styles.sectionDescription, { color: colors.foregroundMuted }]} allowFontScaling={true}>
|
|
90
|
+
{description}
|
|
91
|
+
</Text>
|
|
92
|
+
) : null}
|
|
93
|
+
</View>
|
|
94
|
+
) : null}
|
|
95
|
+
{children}
|
|
96
|
+
</View>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Footer area for submit/cancel buttons or form actions.
|
|
102
|
+
*/
|
|
103
|
+
export function FormFooter({ children, style }: FormFooterProps) {
|
|
104
|
+
return <View style={[styles.footer, style]}>{children}</View>
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
Form.Field = FormField
|
|
108
|
+
Form.Section = FormSection
|
|
109
|
+
Form.Footer = FormFooter
|
|
110
|
+
|
|
111
|
+
const styles = StyleSheet.create({
|
|
112
|
+
form: {
|
|
113
|
+
gap: vs(16),
|
|
114
|
+
},
|
|
115
|
+
field: {
|
|
116
|
+
gap: vs(6),
|
|
117
|
+
},
|
|
118
|
+
label: {
|
|
119
|
+
fontFamily: 'Sohne-Medium',
|
|
120
|
+
fontSize: 14,
|
|
121
|
+
lineHeight: 20,
|
|
122
|
+
},
|
|
123
|
+
error: {
|
|
124
|
+
fontFamily: 'Sohne-Regular',
|
|
125
|
+
fontSize: 12,
|
|
126
|
+
lineHeight: 16,
|
|
127
|
+
},
|
|
128
|
+
section: {
|
|
129
|
+
gap: vs(16),
|
|
130
|
+
},
|
|
131
|
+
sectionHeader: {
|
|
132
|
+
gap: vs(4),
|
|
133
|
+
},
|
|
134
|
+
sectionTitle: {
|
|
135
|
+
fontFamily: 'Sohne-SemiBold',
|
|
136
|
+
fontSize: 16,
|
|
137
|
+
lineHeight: 24,
|
|
138
|
+
},
|
|
139
|
+
sectionDescription: {
|
|
140
|
+
fontFamily: 'Sohne-Regular',
|
|
141
|
+
fontSize: 14,
|
|
142
|
+
lineHeight: 20,
|
|
143
|
+
},
|
|
144
|
+
footer: {
|
|
145
|
+
flexDirection: 'row',
|
|
146
|
+
gap: s(12),
|
|
147
|
+
paddingTop: vs(8),
|
|
148
|
+
},
|
|
149
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Form'
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from 'react'
|
|
2
2
|
import {
|
|
3
3
|
TouchableOpacity,
|
|
4
|
-
Animated,
|
|
5
4
|
ActivityIndicator,
|
|
6
5
|
StyleSheet,
|
|
7
6
|
View,
|
|
8
7
|
Text,
|
|
9
8
|
TouchableOpacityProps,
|
|
10
9
|
ViewStyle,
|
|
11
|
-
Platform,
|
|
12
10
|
} from 'react-native'
|
|
13
|
-
|
|
14
|
-
const nativeDriver = Platform.OS !== 'web'
|
|
11
|
+
import Animated from 'react-native-reanimated'
|
|
15
12
|
import { impactLight } from '../../utils/haptics'
|
|
16
13
|
import { useTheme } from '../../theme'
|
|
17
14
|
import { s, ms } from '../../utils/scaling'
|
|
18
15
|
import { renderIcon } from '../../utils/icons'
|
|
16
|
+
import { usePressScale } from '../../utils/usePressScale'
|
|
17
|
+
import { PRESS_SCALE } from '../../utils/animations'
|
|
19
18
|
|
|
20
19
|
// primary: filled primary
|
|
21
20
|
// secondary: filled surface — icon on neutral bg (Airbnb icon-button-circle)
|
|
@@ -45,7 +44,7 @@ const sizeMap: Record<IconButtonSize, { container: number; icon: number }> = {
|
|
|
45
44
|
lg: { container: s(52), icon: 24 },
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
function IconButtonBase({
|
|
49
48
|
iconName,
|
|
50
49
|
icon,
|
|
51
50
|
iconColor,
|
|
@@ -56,20 +55,16 @@ export function IconButton({
|
|
|
56
55
|
disabled,
|
|
57
56
|
style,
|
|
58
57
|
onPress,
|
|
58
|
+
accessibilityLabel,
|
|
59
|
+
accessibilityHint,
|
|
59
60
|
...props
|
|
60
61
|
}: IconButtonProps) {
|
|
61
62
|
const { colors } = useTheme()
|
|
62
63
|
const isDisabled = disabled || loading
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Animated.spring(scale, { toValue: 0.95, useNativeDriver: nativeDriver, speed: 40, bounciness: 0 }).start()
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const handlePressOut = () => {
|
|
71
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver: nativeDriver, speed: 40, bounciness: 4 }).start()
|
|
72
|
-
}
|
|
64
|
+
const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
65
|
+
pressScale: PRESS_SCALE.button,
|
|
66
|
+
disabled: isDisabled,
|
|
67
|
+
})
|
|
73
68
|
|
|
74
69
|
const handlePress: TouchableOpacityProps['onPress'] = (e) => {
|
|
75
70
|
impactLight()
|
|
@@ -109,7 +104,10 @@ export function IconButton({
|
|
|
109
104
|
const showCount = typeof badge === 'number' && badge > 0
|
|
110
105
|
|
|
111
106
|
return (
|
|
112
|
-
<Animated.View
|
|
107
|
+
<Animated.View
|
|
108
|
+
style={[styles.wrapper, animatedStyle]}
|
|
109
|
+
{...hoverHandlers}
|
|
110
|
+
>
|
|
113
111
|
<TouchableOpacity
|
|
114
112
|
style={[
|
|
115
113
|
styles.base,
|
|
@@ -122,8 +120,12 @@ export function IconButton({
|
|
|
122
120
|
activeOpacity={1}
|
|
123
121
|
touchSoundDisabled={true}
|
|
124
122
|
onPress={handlePress}
|
|
125
|
-
onPressIn={
|
|
126
|
-
onPressOut={
|
|
123
|
+
onPressIn={onPressIn}
|
|
124
|
+
onPressOut={onPressOut}
|
|
125
|
+
accessibilityRole="button"
|
|
126
|
+
accessibilityLabel={accessibilityLabel ?? iconName ?? 'icon button'}
|
|
127
|
+
accessibilityHint={accessibilityHint}
|
|
128
|
+
accessibilityState={{ disabled: isDisabled, busy: loading }}
|
|
127
129
|
{...props}
|
|
128
130
|
>
|
|
129
131
|
{loading ? (
|
|
@@ -149,6 +151,8 @@ export function IconButton({
|
|
|
149
151
|
)
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
export const IconButton = React.memo(IconButtonBase)
|
|
155
|
+
|
|
152
156
|
const styles = StyleSheet.create({
|
|
153
157
|
wrapper: {
|
|
154
158
|
alignSelf: 'flex-start',
|
|
@@ -180,7 +184,7 @@ const styles = StyleSheet.create({
|
|
|
180
184
|
paddingHorizontal: 3,
|
|
181
185
|
},
|
|
182
186
|
badgeText: {
|
|
183
|
-
fontFamily: '
|
|
187
|
+
fontFamily: 'Sohne-Bold',
|
|
184
188
|
fontSize: ms(9),
|
|
185
189
|
lineHeight: 14,
|
|
186
190
|
},
|