@retray-dev/ui-kit 6.2.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 +444 -10
- 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 -873
- package/dist/index.d.ts +94 -873
- package/dist/index.js +751 -357
- package/dist/index.mjs +50 -3895
- 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 +3 -3
- 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 +10 -11
- package/src/components/ButtonGroup/ButtonGroup.tsx +13 -10
- package/src/components/Card/Card.tsx +17 -34
- package/src/components/CategoryStrip/CategoryStrip.tsx +24 -21
- package/src/components/Checkbox/Checkbox.tsx +11 -6
- package/src/components/Chip/Chip.tsx +17 -15
- 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 +4 -2
- 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 +9 -10
- package/src/components/MediaCard/MediaCard.tsx +7 -5
- package/src/components/MenuGroup/MenuGroup.tsx +145 -0
- package/src/components/MenuGroup/index.ts +1 -0
- package/src/components/MenuItem/MenuItem.tsx +7 -9
- package/src/components/MonthPicker/MonthPicker.tsx +2 -2
- package/src/components/RadioGroup/RadioGroup.tsx +11 -14
- package/src/components/Select/Select.tsx +6 -6
- package/src/components/Separator/Separator.tsx +1 -3
- package/src/components/Sheet/Sheet.tsx +81 -17
- package/src/components/Skeleton/Skeleton.tsx +1 -1
- package/src/components/Slider/Slider.tsx +2 -2
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Switch/Switch.tsx +28 -5
- package/src/components/Tabs/Tabs.tsx +22 -18
- package/src/components/Text/Text.tsx +3 -1
- package/src/components/Textarea/Textarea.tsx +18 -14
- package/src/components/Toast/Toast.tsx +6 -6
- package/src/components/Toggle/Toggle.tsx +47 -23
- 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/icons.ts +47 -20
- 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
|
@@ -25,6 +25,21 @@ export { BottomSheetModalProvider }
|
|
|
25
25
|
// Re-export BottomSheetTextInput as SheetTextInput for consumer convenience
|
|
26
26
|
export { BottomSheetTextInput as SheetTextInput }
|
|
27
27
|
|
|
28
|
+
export interface SheetHeaderProps {
|
|
29
|
+
children: React.ReactNode
|
|
30
|
+
style?: ViewStyle
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SheetContentProps {
|
|
34
|
+
children: React.ReactNode
|
|
35
|
+
style?: ViewStyle
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface SheetFooterProps {
|
|
39
|
+
children: React.ReactNode
|
|
40
|
+
style?: ViewStyle
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
export interface SheetProps {
|
|
29
44
|
open: boolean
|
|
30
45
|
onClose: () => void
|
|
@@ -80,6 +95,23 @@ export interface SheetProps {
|
|
|
80
95
|
snapPoints?: (string | number)[]
|
|
81
96
|
}
|
|
82
97
|
|
|
98
|
+
export function SheetHeader({ children, style }: SheetHeaderProps) {
|
|
99
|
+
return <View style={[styles.header, style]}>{children}</View>
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function SheetContent({ children, style }: SheetContentProps) {
|
|
103
|
+
return <View style={[styles.sheetContent, style]}>{children}</View>
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function SheetFooter({ children, style }: SheetFooterProps) {
|
|
107
|
+
const { colors } = useTheme()
|
|
108
|
+
return (
|
|
109
|
+
<View style={[styles.sheetFooter, { backgroundColor: colors.card, borderTopColor: colors.border }, style]}>
|
|
110
|
+
{children}
|
|
111
|
+
</View>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
114
|
+
|
|
83
115
|
export function Sheet({
|
|
84
116
|
open,
|
|
85
117
|
onClose,
|
|
@@ -125,20 +157,25 @@ export function Sheet({
|
|
|
125
157
|
/>
|
|
126
158
|
), [])
|
|
127
159
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
160
|
+
// Detect compound components in children
|
|
161
|
+
const childArray = React.Children.toArray(children)
|
|
162
|
+
const customHeader = childArray.find((child) => React.isValidElement(child) && child.type === SheetHeader)
|
|
163
|
+
const customContent = childArray.find((child) => React.isValidElement(child) && child.type === SheetContent)
|
|
164
|
+
const customFooter = childArray.find((child) => React.isValidElement(child) && child.type === SheetFooter)
|
|
165
|
+
|
|
166
|
+
// If using compound components, filter them out from main children
|
|
167
|
+
const filteredChildren = customHeader || customContent || customFooter
|
|
168
|
+
? childArray.filter(
|
|
169
|
+
(child) =>
|
|
170
|
+
!React.isValidElement(child) ||
|
|
171
|
+
(child.type !== SheetHeader && child.type !== SheetContent && child.type !== SheetFooter)
|
|
172
|
+
)
|
|
173
|
+
: children
|
|
136
174
|
|
|
137
175
|
const effectiveSubtitle = subtitle ?? description
|
|
176
|
+
const showHeader = !!(title || effectiveSubtitle || showCloseButton) && !customHeader
|
|
138
177
|
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
const headerNode = showHeader ? (
|
|
178
|
+
const headerNode = customHeader ? customHeader : (showHeader ? (
|
|
142
179
|
<View style={styles.header} accessibilityRole="header">
|
|
143
180
|
<View style={styles.headerRow}>
|
|
144
181
|
{title ? (
|
|
@@ -166,7 +203,19 @@ export function Sheet({
|
|
|
166
203
|
</Text>
|
|
167
204
|
) : null}
|
|
168
205
|
</View>
|
|
169
|
-
) : null
|
|
206
|
+
) : null)
|
|
207
|
+
|
|
208
|
+
const contentNode = customContent ? customContent : filteredChildren
|
|
209
|
+
const effectiveFooter = customFooter ? customFooter : footer
|
|
210
|
+
|
|
211
|
+
const renderFooter = useCallback((props: BottomSheetFooterProps) => {
|
|
212
|
+
if (!effectiveFooter) return null
|
|
213
|
+
return (
|
|
214
|
+
<BottomSheetFooter {...props}>
|
|
215
|
+
{effectiveFooter}
|
|
216
|
+
</BottomSheetFooter>
|
|
217
|
+
)
|
|
218
|
+
}, [effectiveFooter])
|
|
170
219
|
|
|
171
220
|
const useScroll = scrollable || !!maxHeight
|
|
172
221
|
const effectiveMaxHeight = maxHeight ?? DEFAULT_MAX_HEIGHT
|
|
@@ -182,7 +231,7 @@ export function Sheet({
|
|
|
182
231
|
maxDynamicContentSize={useDynamicSizing ? effectiveMaxHeight : undefined}
|
|
183
232
|
onDismiss={onClose}
|
|
184
233
|
backdropComponent={renderBackdrop}
|
|
185
|
-
footerComponent={
|
|
234
|
+
footerComponent={effectiveFooter ? renderFooter : undefined}
|
|
186
235
|
backgroundStyle={[styles.background, { backgroundColor: colors.card }]}
|
|
187
236
|
handleIndicatorStyle={[styles.handle, { backgroundColor: colors.border }]}
|
|
188
237
|
enablePanDownToClose
|
|
@@ -204,18 +253,22 @@ export function Sheet({
|
|
|
204
253
|
persistentScrollbar={isAndroid}
|
|
205
254
|
>
|
|
206
255
|
{headerNode}
|
|
207
|
-
{
|
|
256
|
+
{contentNode}
|
|
208
257
|
</BottomSheetScrollView>
|
|
209
258
|
) : (
|
|
210
259
|
<BottomSheetView style={[styles.content, contentStyle, style]}>
|
|
211
260
|
{headerNode}
|
|
212
|
-
{
|
|
261
|
+
{contentNode}
|
|
213
262
|
</BottomSheetView>
|
|
214
263
|
)}
|
|
215
264
|
</BottomSheetModal>
|
|
216
265
|
)
|
|
217
266
|
}
|
|
218
267
|
|
|
268
|
+
Sheet.Header = SheetHeader
|
|
269
|
+
Sheet.Content = SheetContent
|
|
270
|
+
Sheet.Footer = SheetFooter
|
|
271
|
+
|
|
219
272
|
const styles = StyleSheet.create({
|
|
220
273
|
background: {
|
|
221
274
|
borderTopLeftRadius: ms(16),
|
|
@@ -238,12 +291,12 @@ const styles = StyleSheet.create({
|
|
|
238
291
|
justifyContent: 'space-between',
|
|
239
292
|
},
|
|
240
293
|
title: {
|
|
241
|
-
fontFamily: '
|
|
294
|
+
fontFamily: 'Sohne-SemiBold',
|
|
242
295
|
fontSize: ms(18),
|
|
243
296
|
flex: 1,
|
|
244
297
|
},
|
|
245
298
|
subtitle: {
|
|
246
|
-
fontFamily: '
|
|
299
|
+
fontFamily: 'Sohne-Regular',
|
|
247
300
|
fontSize: ms(14),
|
|
248
301
|
lineHeight: mvs(20),
|
|
249
302
|
},
|
|
@@ -260,4 +313,15 @@ const styles = StyleSheet.create({
|
|
|
260
313
|
paddingBottom: vs(32),
|
|
261
314
|
paddingRight: s(16),
|
|
262
315
|
},
|
|
316
|
+
sheetContent: {
|
|
317
|
+
gap: vs(16),
|
|
318
|
+
},
|
|
319
|
+
sheetFooter: {
|
|
320
|
+
paddingHorizontal: s(16),
|
|
321
|
+
paddingVertical: vs(16),
|
|
322
|
+
borderTopWidth: 1,
|
|
323
|
+
flexDirection: 'row',
|
|
324
|
+
gap: s(12),
|
|
325
|
+
},
|
|
263
326
|
})
|
|
327
|
+
|
|
@@ -74,7 +74,7 @@ export function Skeleton({
|
|
|
74
74
|
<View
|
|
75
75
|
style={[
|
|
76
76
|
styles.base,
|
|
77
|
-
{ width: resolvedWidth as
|
|
77
|
+
{ width: resolvedWidth as number | `${number}%`, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
|
|
78
78
|
style,
|
|
79
79
|
]}
|
|
80
80
|
onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}
|
|
@@ -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: {
|
|
@@ -13,12 +13,12 @@ import { useTheme } from '../../theme'
|
|
|
13
13
|
import { s } from '../../utils/scaling'
|
|
14
14
|
import { SPRINGS, TIMINGS, EASINGS } from '../../utils/animations'
|
|
15
15
|
|
|
16
|
-
const TRACK_WIDTH
|
|
16
|
+
const TRACK_WIDTH = s(52)
|
|
17
17
|
const TRACK_HEIGHT = s(30)
|
|
18
|
-
const THUMB_SIZE
|
|
18
|
+
const THUMB_SIZE = s(24)
|
|
19
19
|
const THUMB_OFFSET = s(3)
|
|
20
20
|
const THUMB_TRAVEL = TRACK_WIDTH - THUMB_SIZE - THUMB_OFFSET * 2
|
|
21
|
-
const ICON_SIZE
|
|
21
|
+
const ICON_SIZE = s(13)
|
|
22
22
|
|
|
23
23
|
export interface SwitchProps {
|
|
24
24
|
checked?: boolean
|
|
@@ -31,7 +31,6 @@ export interface SwitchProps {
|
|
|
31
31
|
export function Switch({ checked = false, onCheckedChange, disabled, style, accessibilityLabel }: SwitchProps) {
|
|
32
32
|
const { colors } = useTheme()
|
|
33
33
|
|
|
34
|
-
// Single 0→1 progress drives thumb position, track color, and icon crossfade — all UI thread.
|
|
35
34
|
const progress = useSharedValue(checked ? 1 : 0)
|
|
36
35
|
|
|
37
36
|
useEffect(() => {
|
|
@@ -50,6 +49,19 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
|
|
|
50
49
|
),
|
|
51
50
|
}))
|
|
52
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
|
+
}))
|
|
64
|
+
|
|
53
65
|
const checkIconStyle = useAnimatedStyle(() => ({
|
|
54
66
|
opacity: withTiming(checked ? 1 : 0, { duration: TIMINGS.state.duration, easing: EASINGS.standard }),
|
|
55
67
|
}))
|
|
@@ -59,7 +71,7 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
|
|
|
59
71
|
}))
|
|
60
72
|
|
|
61
73
|
return (
|
|
62
|
-
<View style={[{ opacity: disabled ? 0.45 : 1 }, style]}>
|
|
74
|
+
<View style={[{ opacity: disabled ? 0.45 : 1, alignSelf: 'flex-start' }, style]}>
|
|
63
75
|
<TouchableOpacity
|
|
64
76
|
onPress={() => {
|
|
65
77
|
hapticSelection()
|
|
@@ -71,8 +83,10 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
|
|
|
71
83
|
accessibilityRole="switch"
|
|
72
84
|
accessibilityLabel={accessibilityLabel}
|
|
73
85
|
accessibilityState={{ checked, disabled: !!disabled }}
|
|
86
|
+
style={styles.touchable}
|
|
74
87
|
>
|
|
75
88
|
<Animated.View style={[styles.track, trackStyle]}>
|
|
89
|
+
<Animated.View style={[styles.trackBorder, trackBorderStyle]} pointerEvents="none" />
|
|
76
90
|
<Animated.View
|
|
77
91
|
style={[styles.thumb, { backgroundColor: colors.primaryForeground }, thumbStyle]}
|
|
78
92
|
>
|
|
@@ -90,11 +104,18 @@ export function Switch({ checked = false, onCheckedChange, disabled, style, acce
|
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
const styles = StyleSheet.create({
|
|
107
|
+
touchable: {
|
|
108
|
+
alignSelf: 'flex-start',
|
|
109
|
+
},
|
|
93
110
|
track: {
|
|
94
111
|
width: TRACK_WIDTH,
|
|
95
112
|
height: TRACK_HEIGHT,
|
|
96
113
|
borderRadius: TRACK_HEIGHT / 2,
|
|
97
114
|
},
|
|
115
|
+
trackBorder: {
|
|
116
|
+
...StyleSheet.absoluteFillObject,
|
|
117
|
+
borderRadius: TRACK_HEIGHT / 2,
|
|
118
|
+
},
|
|
98
119
|
thumb: {
|
|
99
120
|
position: 'absolute',
|
|
100
121
|
top: THUMB_OFFSET,
|
|
@@ -112,5 +133,7 @@ const styles = StyleSheet.create({
|
|
|
112
133
|
},
|
|
113
134
|
iconWrapper: {
|
|
114
135
|
position: 'absolute',
|
|
136
|
+
alignItems: 'center',
|
|
137
|
+
justifyContent: 'center',
|
|
115
138
|
},
|
|
116
139
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react'
|
|
1
|
+
import React, { useState, useRef, useEffect, useCallback } from 'react'
|
|
2
2
|
import { View, TouchableOpacity, Text, StyleSheet, ViewStyle, LayoutChangeEvent } from 'react-native'
|
|
3
3
|
import Animated, {
|
|
4
4
|
useSharedValue,
|
|
@@ -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 {
|
|
@@ -76,13 +74,16 @@ function TabTrigger({
|
|
|
76
74
|
<Animated.View style={animatedStyle}>
|
|
77
75
|
<View style={styles.triggerInner}>
|
|
78
76
|
{tab.icon ? (
|
|
79
|
-
|
|
77
|
+
typeof tab.icon === 'function' ? tab.icon(isActive) : tab.icon
|
|
80
78
|
) : null}
|
|
81
79
|
<Text
|
|
82
80
|
style={[
|
|
83
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.
|
|
84
86
|
{ color: isActive ? colors.foreground : colors.foregroundMuted },
|
|
85
|
-
isActive && (isUnderline ? styles.activeTriggerLabelUnderline : styles.activeTriggerLabel),
|
|
86
87
|
]}
|
|
87
88
|
allowFontScaling={true}
|
|
88
89
|
>
|
|
@@ -100,26 +101,27 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
100
101
|
const active = value ?? internal
|
|
101
102
|
|
|
102
103
|
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
104
|
const pillX = useSharedValue(0)
|
|
105
105
|
const pillWidth = useSharedValue(0)
|
|
106
106
|
const initialised = useRef(false)
|
|
107
107
|
|
|
108
|
-
const animatePill = (tabValue: string, animate: boolean) => {
|
|
108
|
+
const animatePill = useCallback((tabValue: string, animate: boolean) => {
|
|
109
109
|
const layout = tabLayouts.current[tabValue]
|
|
110
110
|
if (!layout) return
|
|
111
111
|
if (animate) {
|
|
112
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
112
113
|
pillX.value = withSpring(layout.x, SPRINGS.glide)
|
|
114
|
+
// eslint-disable-next-line react-hooks/immutability
|
|
113
115
|
pillWidth.value = withSpring(layout.width, SPRINGS.glide)
|
|
114
116
|
} else {
|
|
115
117
|
pillX.value = layout.x
|
|
116
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()
|
|
@@ -136,7 +138,9 @@ export function Tabs({ tabs, variant = 'pill', value, onValueChange, children, s
|
|
|
136
138
|
<View style={style}>
|
|
137
139
|
<View
|
|
138
140
|
style={[
|
|
139
|
-
variant === 'pill'
|
|
141
|
+
variant === 'pill'
|
|
142
|
+
? [styles.list, { backgroundColor: colors.surface }]
|
|
143
|
+
: styles.listUnderline,
|
|
140
144
|
]}
|
|
141
145
|
accessibilityRole="tablist"
|
|
142
146
|
>
|
|
@@ -198,6 +202,8 @@ const styles = StyleSheet.create({
|
|
|
198
202
|
},
|
|
199
203
|
listUnderline: {
|
|
200
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.
|
|
201
207
|
borderBottomWidth: 1,
|
|
202
208
|
},
|
|
203
209
|
pill: {},
|
|
@@ -224,15 +230,13 @@ const styles = StyleSheet.create({
|
|
|
224
230
|
justifyContent: 'center',
|
|
225
231
|
gap: s(4),
|
|
226
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.
|
|
227
238
|
triggerLabel: {
|
|
228
|
-
fontFamily: '
|
|
239
|
+
fontFamily: 'Sohne-SemiBold',
|
|
229
240
|
fontSize: ms(13),
|
|
230
241
|
},
|
|
231
|
-
activeTriggerLabel: {
|
|
232
|
-
fontFamily: 'Poppins-Medium',
|
|
233
|
-
},
|
|
234
|
-
activeTriggerLabelUnderline: {
|
|
235
|
-
fontFamily: 'Poppins-SemiBold',
|
|
236
|
-
fontSize: ms(14),
|
|
237
|
-
},
|
|
238
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)
|
|
@@ -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),
|
|
@@ -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
|
},
|
|
@@ -13,6 +13,40 @@ import { usePressScale } from '../../utils/usePressScale'
|
|
|
13
13
|
import { useColorTransition } from '../../utils/useColorTransition'
|
|
14
14
|
import { PRESS_SCALE } from '../../utils/animations'
|
|
15
15
|
|
|
16
|
+
interface ToggleIconProps {
|
|
17
|
+
pressed: boolean
|
|
18
|
+
iconName?: string
|
|
19
|
+
activeIconName?: string
|
|
20
|
+
icon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
|
|
21
|
+
activeIcon?: React.ReactNode | ((pressed: boolean) => React.ReactNode)
|
|
22
|
+
iconColor?: string
|
|
23
|
+
activeIconColor?: string
|
|
24
|
+
iconSize: number
|
|
25
|
+
primaryColor: string
|
|
26
|
+
mutedColor: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ToggleIcon({ pressed, iconName, activeIconName, icon, activeIcon, iconColor, activeIconColor, iconSize, primaryColor, mutedColor }: ToggleIconProps) {
|
|
30
|
+
const renderProp = (prop?: React.ReactNode | ((p: boolean) => React.ReactNode)) => {
|
|
31
|
+
if (!prop) return null
|
|
32
|
+
if (typeof prop === 'function') return (prop as (p: boolean) => React.ReactNode)(pressed)
|
|
33
|
+
return prop
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (pressed) {
|
|
37
|
+
if (activeIconName) return <>{renderIcon(activeIconName, iconSize, activeIconColor ?? primaryColor)}</>
|
|
38
|
+
const active = renderProp(activeIcon)
|
|
39
|
+
if (active) return <>{active}</>
|
|
40
|
+
return <FontAwesome5 name="check-circle" size={iconSize} color={primaryColor} />
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (iconName) return <>{renderIcon(iconName, iconSize, iconColor ?? mutedColor)}</>
|
|
44
|
+
const custom = renderProp(icon)
|
|
45
|
+
if (custom) return <>{custom}</>
|
|
46
|
+
|
|
47
|
+
return <FontAwesome5 name="circle" size={iconSize} color={mutedColor} />
|
|
48
|
+
}
|
|
49
|
+
|
|
16
50
|
export type ToggleVariant = 'default' | 'outline'
|
|
17
51
|
export type ToggleSize = 'sm' | 'md' | 'lg'
|
|
18
52
|
|
|
@@ -87,27 +121,6 @@ export function Toggle({
|
|
|
87
121
|
|
|
88
122
|
const iconSize = iconSizeMap[size]
|
|
89
123
|
|
|
90
|
-
const LeftIcon = () => {
|
|
91
|
-
const renderProp = (prop?: any) => {
|
|
92
|
-
if (!prop) return null
|
|
93
|
-
if (typeof prop === 'function') return prop(pressed)
|
|
94
|
-
return prop
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (pressed) {
|
|
98
|
-
if (activeIconName) return <>{renderIcon(activeIconName, iconSize, activeIconColor ?? colors.primary)}</>
|
|
99
|
-
const active = renderProp(activeIcon)
|
|
100
|
-
if (active) return <>{active}</>
|
|
101
|
-
return <FontAwesome5 name="check-circle" size={iconSize} color={colors.primary} />
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (iconName) return <>{renderIcon(iconName, iconSize, iconColor ?? colors.foregroundMuted)}</>
|
|
105
|
-
const custom = renderProp(icon)
|
|
106
|
-
if (custom) return <>{custom}</>
|
|
107
|
-
|
|
108
|
-
return <FontAwesome5 name="circle" size={iconSize} color={colors.foregroundMuted} />
|
|
109
|
-
}
|
|
110
|
-
|
|
111
124
|
return (
|
|
112
125
|
<Animated.View
|
|
113
126
|
style={[scaleStyle, disabled && styles.disabled, style]}
|
|
@@ -137,7 +150,18 @@ export function Toggle({
|
|
|
137
150
|
]}
|
|
138
151
|
>
|
|
139
152
|
<View style={styles.inner}>
|
|
140
|
-
<
|
|
153
|
+
<ToggleIcon
|
|
154
|
+
pressed={pressed}
|
|
155
|
+
iconName={iconName}
|
|
156
|
+
activeIconName={activeIconName}
|
|
157
|
+
icon={icon}
|
|
158
|
+
activeIcon={activeIcon}
|
|
159
|
+
iconColor={iconColor}
|
|
160
|
+
activeIconColor={activeIconColor}
|
|
161
|
+
iconSize={iconSize}
|
|
162
|
+
primaryColor={colors.primary}
|
|
163
|
+
mutedColor={colors.foregroundMuted}
|
|
164
|
+
/>
|
|
141
165
|
{label ? (
|
|
142
166
|
<Animated.Text style={[styles.label, textStyle]} allowFontScaling={true}>
|
|
143
167
|
{label}
|
|
@@ -164,7 +188,7 @@ const styles = StyleSheet.create({
|
|
|
164
188
|
opacity: 0.45,
|
|
165
189
|
},
|
|
166
190
|
label: {
|
|
167
|
-
fontFamily: '
|
|
191
|
+
fontFamily: 'Sohne-Medium',
|
|
168
192
|
fontSize: ms(14),
|
|
169
193
|
},
|
|
170
194
|
})
|