@retray-dev/ui-kit 6.2.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +997 -20
- package/EXAMPLES.md +250 -2
- package/README.md +21 -14
- package/dist/Accordion.d.mts +28 -0
- package/dist/Accordion.d.ts +28 -0
- package/dist/Accordion.js +392 -0
- package/dist/Accordion.mjs +7 -0
- package/dist/AlertBanner.d.mts +16 -0
- package/dist/AlertBanner.d.ts +16 -0
- package/dist/AlertBanner.js +250 -0
- package/dist/AlertBanner.mjs +6 -0
- package/dist/AppHeader.d.mts +40 -0
- package/dist/AppHeader.d.ts +40 -0
- package/dist/AppHeader.js +515 -0
- package/dist/AppHeader.mjs +10 -0
- package/dist/Avatar.d.mts +20 -0
- package/dist/Avatar.d.ts +20 -0
- package/dist/Avatar.js +244 -0
- package/dist/Avatar.mjs +4 -0
- package/dist/Badge.d.mts +26 -0
- package/dist/Badge.d.ts +26 -0
- package/dist/Badge.js +257 -0
- package/dist/Badge.mjs +5 -0
- package/dist/Button.d.mts +30 -0
- package/dist/Button.d.ts +30 -0
- package/dist/Button.js +432 -0
- package/dist/Button.mjs +9 -0
- package/dist/ButtonGroup.d.mts +26 -0
- package/dist/ButtonGroup.d.ts +26 -0
- package/dist/ButtonGroup.js +52 -0
- package/dist/ButtonGroup.mjs +3 -0
- package/dist/Card.d.mts +39 -0
- package/dist/Card.d.ts +39 -0
- package/dist/Card.js +349 -0
- package/dist/Card.mjs +8 -0
- package/dist/CategoryStrip.d.mts +26 -0
- package/dist/CategoryStrip.d.ts +26 -0
- package/dist/CategoryStrip.js +453 -0
- package/dist/CategoryStrip.mjs +9 -0
- package/dist/Checkbox.d.mts +14 -0
- package/dist/Checkbox.d.ts +14 -0
- package/dist/Checkbox.js +336 -0
- package/dist/Checkbox.mjs +7 -0
- package/dist/Chip.d.mts +31 -0
- package/dist/Chip.d.ts +31 -0
- package/dist/Chip.js +403 -0
- package/dist/Chip.mjs +8 -0
- package/dist/ConfirmDialog.d.mts +15 -0
- package/dist/ConfirmDialog.d.ts +15 -0
- package/dist/ConfirmDialog.js +560 -0
- package/dist/ConfirmDialog.mjs +10 -0
- package/dist/CurrencyDisplay.d.mts +24 -0
- package/dist/CurrencyDisplay.d.ts +24 -0
- package/dist/CurrencyDisplay.js +189 -0
- package/dist/CurrencyDisplay.mjs +4 -0
- package/dist/CurrencyInput.d.mts +26 -0
- package/dist/CurrencyInput.d.ts +26 -0
- package/dist/CurrencyInput.js +408 -0
- package/dist/CurrencyInput.mjs +8 -0
- package/dist/DetailRow.d.mts +32 -0
- package/dist/DetailRow.d.ts +32 -0
- package/dist/DetailRow.js +275 -0
- package/dist/DetailRow.mjs +5 -0
- package/dist/EmptyState.d.mts +27 -0
- package/dist/EmptyState.d.ts +27 -0
- package/dist/EmptyState.js +523 -0
- package/dist/EmptyState.mjs +10 -0
- package/dist/ErrorBoundary.d.mts +42 -0
- package/dist/ErrorBoundary.d.ts +42 -0
- package/dist/ErrorBoundary.js +351 -0
- package/dist/ErrorBoundary.mjs +7 -0
- package/dist/Form.d.mts +52 -0
- package/dist/Form.d.ts +52 -0
- package/dist/Form.js +204 -0
- package/dist/Form.mjs +4 -0
- package/dist/HolographicCard.d.mts +55 -0
- package/dist/HolographicCard.d.ts +55 -0
- package/dist/HolographicCard.js +316 -0
- package/dist/HolographicCard.mjs +191 -0
- package/dist/IconButton.d.mts +27 -0
- package/dist/IconButton.d.ts +27 -0
- package/dist/IconButton.js +400 -0
- package/dist/IconButton.mjs +8 -0
- package/dist/ImageViewer.d.mts +23 -0
- package/dist/ImageViewer.d.ts +23 -0
- package/dist/ImageViewer.js +582 -0
- package/dist/ImageViewer.mjs +8 -0
- package/dist/Input.d.mts +23 -0
- package/dist/Input.d.ts +23 -0
- package/dist/Input.js +351 -0
- package/dist/Input.mjs +7 -0
- package/dist/LabelValue.d.mts +16 -0
- package/dist/LabelValue.d.ts +16 -0
- package/dist/LabelValue.js +225 -0
- package/dist/LabelValue.mjs +5 -0
- package/dist/ListGroup.d.mts +34 -0
- package/dist/ListGroup.d.ts +34 -0
- package/dist/ListGroup.js +217 -0
- package/dist/ListGroup.mjs +5 -0
- package/dist/ListItem.d.mts +64 -0
- package/dist/ListItem.d.ts +64 -0
- package/dist/ListItem.js +444 -0
- package/dist/ListItem.mjs +9 -0
- package/dist/MediaCard.d.mts +39 -0
- package/dist/MediaCard.d.ts +39 -0
- package/dist/MediaCard.js +475 -0
- package/dist/MediaCard.mjs +9 -0
- package/dist/MenuGroup.d.mts +34 -0
- package/dist/MenuGroup.d.ts +34 -0
- package/dist/MenuGroup.js +217 -0
- package/dist/MenuGroup.mjs +5 -0
- package/dist/MenuItem.d.mts +48 -0
- package/dist/MenuItem.d.ts +48 -0
- package/dist/MenuItem.js +415 -0
- package/dist/MenuItem.mjs +9 -0
- package/dist/MonthPicker.d.mts +28 -0
- package/dist/MonthPicker.d.ts +28 -0
- package/dist/MonthPicker.js +297 -0
- package/dist/MonthPicker.mjs +5 -0
- package/dist/PagerDots.d.mts +35 -0
- package/dist/PagerDots.d.ts +35 -0
- package/dist/PagerDots.js +392 -0
- package/dist/PagerDots.mjs +7 -0
- package/dist/Pressable.d.mts +34 -0
- package/dist/Pressable.d.ts +34 -0
- package/dist/Pressable.js +143 -0
- package/dist/Pressable.mjs +5 -0
- package/dist/PricingCard.d.mts +50 -0
- package/dist/PricingCard.d.ts +50 -0
- package/dist/PricingCard.js +636 -0
- package/dist/PricingCard.mjs +11 -0
- package/dist/Progress.d.mts +14 -0
- package/dist/Progress.d.ts +14 -0
- package/dist/Progress.js +191 -0
- package/dist/Progress.mjs +5 -0
- package/dist/RadioGroup.d.mts +19 -0
- package/dist/RadioGroup.d.ts +19 -0
- package/dist/RadioGroup.js +392 -0
- package/dist/RadioGroup.mjs +7 -0
- package/dist/RetrayProvider.d.mts +2 -0
- package/dist/RetrayProvider.d.ts +2 -0
- package/dist/RetrayProvider.js +214 -0
- package/dist/RetrayProvider.mjs +5 -0
- package/dist/Select.d.mts +22 -0
- package/dist/Select.d.ts +22 -0
- package/dist/Select.js +488 -0
- package/dist/Select.mjs +7 -0
- package/dist/SelectableGrid.d.mts +44 -0
- package/dist/SelectableGrid.d.ts +44 -0
- package/dist/SelectableGrid.js +448 -0
- package/dist/SelectableGrid.mjs +9 -0
- package/dist/Separator.d.mts +10 -0
- package/dist/Separator.d.ts +10 -0
- package/dist/Separator.js +156 -0
- package/dist/Separator.mjs +3 -0
- package/dist/Sheet.d.mts +93 -0
- package/dist/Sheet.d.ts +93 -0
- package/dist/Sheet.js +450 -0
- package/dist/Sheet.mjs +6 -0
- package/dist/Skeleton.d.mts +67 -0
- package/dist/Skeleton.d.ts +67 -0
- package/dist/Skeleton.js +266 -0
- package/dist/Skeleton.mjs +6 -0
- package/dist/Slider.d.mts +20 -0
- package/dist/Slider.d.ts +20 -0
- package/dist/Slider.js +279 -0
- package/dist/Slider.mjs +5 -0
- package/dist/Spinner.d.mts +12 -0
- package/dist/Spinner.d.ts +12 -0
- package/dist/Spinner.js +193 -0
- package/dist/Spinner.mjs +4 -0
- package/dist/Switch.d.mts +13 -0
- package/dist/Switch.d.ts +13 -0
- package/dist/Switch.js +311 -0
- package/dist/Switch.mjs +6 -0
- package/dist/TabBar.d.mts +42 -0
- package/dist/TabBar.d.ts +42 -0
- package/dist/TabBar.js +361 -0
- package/dist/TabBar.mjs +6 -0
- package/dist/Tabs.d.mts +27 -0
- package/dist/Tabs.d.ts +27 -0
- package/dist/Tabs.js +419 -0
- package/dist/Tabs.mjs +7 -0
- package/dist/Text.d.mts +12 -0
- package/dist/Text.d.ts +12 -0
- package/dist/Text.js +327 -0
- package/dist/Text.mjs +5 -0
- package/dist/Textarea.d.mts +16 -0
- package/dist/Textarea.d.ts +16 -0
- package/dist/Textarea.js +333 -0
- package/dist/Textarea.mjs +7 -0
- package/dist/Toast.d.mts +47 -0
- package/dist/Toast.d.ts +47 -0
- package/dist/Toast.js +185 -0
- package/dist/Toast.mjs +4 -0
- package/dist/Toggle.d.mts +36 -0
- package/dist/Toggle.d.ts +36 -0
- package/dist/Toggle.js +412 -0
- package/dist/Toggle.mjs +8 -0
- package/dist/VirtualList.d.mts +19 -0
- package/dist/VirtualList.d.ts +19 -0
- package/dist/VirtualList.js +38 -0
- package/dist/VirtualList.mjs +2 -0
- package/dist/chunk-26BCI223.mjs +14 -0
- package/dist/chunk-2CE3TQVY.mjs +11 -0
- package/dist/chunk-2TFTAWVJ.mjs +131 -0
- package/dist/chunk-2UYENBLV.mjs +49 -0
- package/dist/chunk-3BBOZ3OQ.mjs +41 -0
- package/dist/chunk-3DKJ2GIC.mjs +30 -0
- package/dist/chunk-3U4SSNWP.mjs +120 -0
- package/dist/chunk-4I7D47FH.mjs +139 -0
- package/dist/chunk-4K625MVM.mjs +142 -0
- package/dist/chunk-6OAZJ577.mjs +98 -0
- package/dist/chunk-6Q64UFIA.mjs +71 -0
- package/dist/chunk-756RAKE4.mjs +145 -0
- package/dist/chunk-7QHVVCB3.mjs +115 -0
- package/dist/chunk-A3A6KNQN.mjs +245 -0
- package/dist/chunk-A4MDAP7G.mjs +42 -0
- package/dist/chunk-AJ7ZDNBT.mjs +120 -0
- package/dist/chunk-AV4EMIRH.mjs +94 -0
- package/dist/chunk-AZJF2BLK.mjs +115 -0
- package/dist/chunk-BNP626TY.mjs +159 -0
- package/dist/chunk-BRKYVJVV.mjs +60 -0
- package/dist/chunk-DVK4G2GT.mjs +59 -0
- package/dist/chunk-EH745HE5.mjs +127 -0
- package/dist/chunk-EJ7ZPXOH.mjs +163 -0
- package/dist/chunk-GD6KXMG5.mjs +106 -0
- package/dist/chunk-GQYFLP3D.mjs +187 -0
- package/dist/chunk-ID72TK46.mjs +111 -0
- package/dist/chunk-IRRY3CRZ.mjs +82 -0
- package/dist/chunk-JB67UOB5.mjs +92 -0
- package/dist/chunk-JMOZEC77.mjs +90 -0
- package/dist/chunk-JT7HKXRB.mjs +114 -0
- package/dist/chunk-KIHCWCWL.mjs +124 -0
- package/dist/chunk-LXJIIOYQ.mjs +104 -0
- package/dist/chunk-M6ZXVBTK.mjs +64 -0
- package/dist/chunk-MAC465BB.mjs +61 -0
- package/dist/chunk-MBMXYJJV.mjs +36 -0
- package/dist/chunk-MLF3EZFW.mjs +119 -0
- package/dist/chunk-MX6HRKMI.mjs +29 -0
- package/dist/chunk-NA7PARID.mjs +147 -0
- package/dist/chunk-NC5ZTR2Y.mjs +32 -0
- package/dist/chunk-O3HA6TYM.mjs +139 -0
- package/dist/chunk-OB4JUQ3O.mjs +51 -0
- package/dist/chunk-PFZTM6D5.mjs +238 -0
- package/dist/chunk-QKH5ZOD5.mjs +97 -0
- package/dist/chunk-QY3X2UYR.mjs +191 -0
- package/dist/chunk-SOA2Z4RB.mjs +82 -0
- package/dist/chunk-SOYNZDVY.mjs +151 -0
- package/dist/chunk-T7XZ7H7Y.mjs +57 -0
- package/dist/chunk-TERDKCLE.mjs +74 -0
- package/dist/chunk-UREA2GYY.mjs +113 -0
- package/dist/chunk-VGTDN7SW.mjs +164 -0
- package/dist/chunk-VQ57HWPL.mjs +144 -0
- package/dist/chunk-WBOOUHSS.mjs +62 -0
- package/dist/chunk-WJLKJMKR.mjs +78 -0
- package/dist/chunk-X4G6APW6.mjs +134 -0
- package/dist/chunk-Y6FXYEAI.mjs +8 -0
- package/dist/chunk-YFZ3ELX5.mjs +16 -0
- package/dist/chunk-YNROWHQJ.mjs +46 -0
- package/dist/chunk-Z4BVUWW6.mjs +196 -0
- package/dist/chunk-ZJKGQMYH.mjs +131 -0
- package/dist/index-wt-orHUi.d.mts +85 -0
- package/dist/index-wt-orHUi.d.ts +85 -0
- package/dist/index.d.mts +149 -920
- package/dist/index.d.ts +149 -920
- package/dist/index.js +2560 -970
- package/dist/index.mjs +60 -3895
- package/package.json +55 -16
- package/src/assets/fonts/Sohne-Bold.otf +0 -0
- package/src/assets/fonts/Sohne-BoldItalic.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraBold.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraBoldItalic.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraLight.otf +0 -0
- package/src/assets/fonts/Sohne-ExtraLightItalic.otf +0 -0
- package/src/assets/fonts/Sohne-Italic.otf +0 -0
- package/src/assets/fonts/Sohne-Light.otf +0 -0
- package/src/assets/fonts/Sohne-LightItalic.otf +0 -0
- package/src/assets/fonts/Sohne-Medium.otf +0 -0
- package/src/assets/fonts/Sohne-MediumItalic.otf +0 -0
- package/src/assets/fonts/Sohne-Regular.otf +0 -0
- package/src/assets/fonts/Sohne-SemiBold.otf +0 -0
- package/src/assets/fonts/Sohne-SemiBoldItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Bold.otf +0 -0
- package/src/assets/fonts/SohneMono-BoldItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraBold.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraBoldItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraLight.otf +0 -0
- package/src/assets/fonts/SohneMono-ExtraLightItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Italic.otf +0 -0
- package/src/assets/fonts/SohneMono-Light.otf +0 -0
- package/src/assets/fonts/SohneMono-LightItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Medium.otf +0 -0
- package/src/assets/fonts/SohneMono-MediumItalic.otf +0 -0
- package/src/assets/fonts/SohneMono-Regular.otf +0 -0
- package/src/assets/fonts/SohneMono-SemiBold.otf +0 -0
- package/src/assets/fonts/SohneMono-SemiBoldItalic.otf +0 -0
- package/src/components/Accordion/Accordion.tsx +15 -4
- package/src/components/AlertBanner/AlertBanner.tsx +38 -12
- package/src/components/AppHeader/AppHeader.tsx +172 -0
- package/src/components/AppHeader/index.ts +1 -0
- package/src/components/Avatar/Avatar.tsx +14 -4
- package/src/components/Badge/Badge.tsx +12 -3
- package/src/components/Button/Button.tsx +30 -38
- package/src/components/ButtonGroup/ButtonGroup.tsx +13 -10
- package/src/components/Card/Card.tsx +29 -57
- package/src/components/CategoryStrip/CategoryStrip.tsx +41 -42
- package/src/components/Checkbox/Checkbox.tsx +36 -45
- package/src/components/Chip/Chip.tsx +41 -48
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +2 -2
- package/src/components/CurrencyDisplay/CurrencyDisplay.tsx +4 -2
- package/src/components/CurrencyInput/CurrencyInput.tsx +12 -10
- package/src/components/DetailRow/DetailRow.tsx +9 -7
- package/src/components/EmptyState/EmptyState.tsx +4 -3
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +153 -0
- package/src/components/ErrorBoundary/index.ts +1 -0
- package/src/components/Form/Form.tsx +149 -0
- package/src/components/Form/index.ts +1 -0
- package/src/components/HolographicCard/HolographicCard.tsx +315 -0
- package/src/components/HolographicCard/index.ts +1 -0
- package/src/components/IconButton/IconButton.tsx +23 -29
- package/src/components/ImageViewer/ImageViewer.tsx +290 -0
- package/src/components/ImageViewer/index.ts +1 -0
- package/src/components/Input/Input.tsx +27 -31
- package/src/components/LabelValue/LabelValue.tsx +6 -4
- package/src/components/ListGroup/ListGroup.tsx +145 -0
- package/src/components/ListGroup/index.ts +1 -0
- package/src/components/ListItem/ListItem.tsx +78 -76
- package/src/components/MediaCard/MediaCard.tsx +15 -7
- package/src/components/MenuGroup/MenuGroup.tsx +145 -0
- package/src/components/MenuGroup/index.ts +1 -0
- package/src/components/MenuItem/MenuItem.tsx +16 -33
- package/src/components/MonthPicker/MonthPicker.tsx +41 -15
- package/src/components/MonthPicker/index.ts +1 -1
- package/src/components/PagerDots/PagerDots.tsx +200 -0
- package/src/components/PagerDots/index.ts +1 -0
- package/src/components/Pressable/Pressable.tsx +19 -35
- package/src/components/PricingCard/PricingCard.tsx +220 -0
- package/src/components/PricingCard/index.ts +1 -0
- package/src/components/RadioGroup/RadioGroup.tsx +23 -39
- package/src/components/RetrayProvider/RetrayProvider.tsx +59 -0
- package/src/components/RetrayProvider/index.ts +1 -0
- package/src/components/Select/Select.tsx +6 -6
- package/src/components/SelectableGrid/SelectableGrid.tsx +205 -0
- package/src/components/SelectableGrid/index.ts +1 -0
- package/src/components/Separator/Separator.tsx +1 -3
- package/src/components/Sheet/Sheet.tsx +146 -18
- package/src/components/Skeleton/Skeleton.tsx +143 -2
- package/src/components/Slider/Slider.tsx +2 -2
- package/src/components/Spinner/Spinner.tsx +18 -3
- package/src/components/Switch/Switch.tsx +44 -49
- package/src/components/TabBar/TabBar.tsx +169 -0
- package/src/components/TabBar/index.ts +1 -0
- package/src/components/Tabs/Tabs.tsx +45 -44
- package/src/components/Text/Text.tsx +5 -1
- package/src/components/Textarea/Textarea.tsx +18 -14
- package/src/components/Toast/Toast.tsx +6 -6
- package/src/components/Toggle/Toggle.tsx +80 -72
- package/src/components/VirtualList/VirtualList.tsx +60 -0
- package/src/components/VirtualList/index.ts +1 -0
- package/src/fonts.ts +41 -20
- package/src/index.ts +28 -3
- package/src/theme/colors.ts +53 -39
- package/src/theme/types.ts +3 -0
- package/src/tokens.ts +49 -39
- package/src/utils/animations.ts +29 -1
- package/src/utils/fontGuard.ts +34 -0
- package/src/utils/haptics.ts +211 -9
- package/src/utils/icons.ts +47 -20
- package/src/utils/pressable.ts +66 -0
- package/src/utils/usePressScale.ts +2 -0
- package/src/assets/fonts/Poppins-Black.ttf +0 -0
- package/src/assets/fonts/Poppins-BlackItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Bold.ttf +0 -0
- package/src/assets/fonts/Poppins-BoldItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraBold.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraBoldItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraLight.ttf +0 -0
- package/src/assets/fonts/Poppins-ExtraLightItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Italic.ttf +0 -0
- package/src/assets/fonts/Poppins-Light.ttf +0 -0
- package/src/assets/fonts/Poppins-LightItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Medium.ttf +0 -0
- package/src/assets/fonts/Poppins-MediumItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Regular.ttf +0 -0
- package/src/assets/fonts/Poppins-SemiBold.ttf +0 -0
- package/src/assets/fonts/Poppins-SemiBoldItalic.ttf +0 -0
- package/src/assets/fonts/Poppins-Thin.ttf +0 -0
- package/src/assets/fonts/Poppins-ThinItalic.ttf +0 -0
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import {
|
|
3
|
-
TouchableOpacity,
|
|
4
3
|
View,
|
|
5
4
|
Text,
|
|
6
5
|
StyleSheet,
|
|
7
6
|
ViewStyle,
|
|
8
7
|
TextStyle,
|
|
9
8
|
} from 'react-native'
|
|
10
|
-
import Animated from 'react-native-reanimated'
|
|
11
9
|
import { Entypo } from '@expo/vector-icons'
|
|
12
10
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
13
11
|
import { useTheme } from '../../theme'
|
|
14
12
|
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
15
13
|
import { renderIcon } from '../../utils/icons'
|
|
16
14
|
import { RADIUS } from '../../tokens'
|
|
17
|
-
import {
|
|
18
|
-
import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
|
|
15
|
+
import { PressableRow } from '../../utils/pressable'
|
|
19
16
|
|
|
20
17
|
export type ListItemVariant = 'plain' | 'card'
|
|
21
18
|
|
|
@@ -81,7 +78,7 @@ export interface ListItemProps {
|
|
|
81
78
|
accessibilityLabel?: string
|
|
82
79
|
}
|
|
83
80
|
|
|
84
|
-
|
|
81
|
+
function ListItemBase({
|
|
85
82
|
leftRender,
|
|
86
83
|
rightRender,
|
|
87
84
|
trailing,
|
|
@@ -105,12 +102,6 @@ export function ListItem({
|
|
|
105
102
|
accessibilityLabel,
|
|
106
103
|
}: ListItemProps) {
|
|
107
104
|
const { colors } = useTheme()
|
|
108
|
-
const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
109
|
-
pressScale: PRESS_SCALE.row,
|
|
110
|
-
pressInSpring: SPRINGS.surfacePressIn,
|
|
111
|
-
pressOutSpring: SPRINGS.surfacePressOut,
|
|
112
|
-
disabled: !onPress || disabled,
|
|
113
|
-
})
|
|
114
105
|
|
|
115
106
|
const handlePress = () => {
|
|
116
107
|
hapticSelection()
|
|
@@ -142,90 +133,101 @@ export function ListItem({
|
|
|
142
133
|
|
|
143
134
|
const a11yLabel = accessibilityLabel ?? [title, subtitle, caption].filter(Boolean).join('. ')
|
|
144
135
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
style={
|
|
149
|
-
|
|
150
|
-
onPressIn={onPressIn}
|
|
151
|
-
onPressOut={onPressOut}
|
|
152
|
-
disabled={disabled}
|
|
153
|
-
activeOpacity={1}
|
|
154
|
-
touchSoundDisabled={true}
|
|
155
|
-
accessibilityRole={onPress ? 'button' : undefined}
|
|
156
|
-
accessibilityLabel={onPress ? a11yLabel : undefined}
|
|
157
|
-
accessibilityState={onPress ? { disabled: !!disabled } : undefined}
|
|
158
|
-
>
|
|
159
|
-
{effectiveLeft ? (
|
|
160
|
-
<View style={styles.leftContainer}>{effectiveLeft}</View>
|
|
161
|
-
) : null}
|
|
136
|
+
const content = (
|
|
137
|
+
<>
|
|
138
|
+
{effectiveLeft ? (
|
|
139
|
+
<View style={styles.leftContainer}>{effectiveLeft}</View>
|
|
140
|
+
) : null}
|
|
162
141
|
|
|
163
|
-
|
|
142
|
+
<View style={styles.content}>
|
|
143
|
+
<Text
|
|
144
|
+
style={[styles.title, { color: colors.foreground }, titleStyle]}
|
|
145
|
+
numberOfLines={2}
|
|
146
|
+
allowFontScaling={true}
|
|
147
|
+
>
|
|
148
|
+
{title}
|
|
149
|
+
</Text>
|
|
150
|
+
{subtitle ? (
|
|
164
151
|
<Text
|
|
165
|
-
style={[styles.
|
|
152
|
+
style={[styles.subtitle, { color: colors.foregroundMuted }, subtitleStyle]}
|
|
166
153
|
numberOfLines={2}
|
|
167
154
|
allowFontScaling={true}
|
|
168
155
|
>
|
|
169
|
-
{
|
|
156
|
+
{subtitle}
|
|
170
157
|
</Text>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
158
|
+
) : null}
|
|
159
|
+
{caption ? (
|
|
160
|
+
<Text
|
|
161
|
+
style={[styles.caption, { color: colors.foregroundMuted }, captionStyle]}
|
|
162
|
+
numberOfLines={1}
|
|
163
|
+
allowFontScaling={true}
|
|
164
|
+
>
|
|
165
|
+
{caption}
|
|
166
|
+
</Text>
|
|
167
|
+
) : null}
|
|
168
|
+
</View>
|
|
169
|
+
|
|
170
|
+
{effectiveRight !== undefined ? (
|
|
171
|
+
<View style={styles.rightContainer}>
|
|
172
|
+
{typeof effectiveRight === 'string' ? (
|
|
181
173
|
<Text
|
|
182
|
-
style={[styles.
|
|
183
|
-
numberOfLines={1}
|
|
174
|
+
style={[styles.rightText, { color: colors.foregroundMuted }]}
|
|
184
175
|
allowFontScaling={true}
|
|
185
176
|
>
|
|
186
|
-
{
|
|
177
|
+
{effectiveRight}
|
|
187
178
|
</Text>
|
|
188
|
-
) :
|
|
179
|
+
) : (
|
|
180
|
+
effectiveRight
|
|
181
|
+
)}
|
|
189
182
|
</View>
|
|
183
|
+
) : showChevron ? (
|
|
184
|
+
<Entypo name="chevron-with-circle-right" size={20} color={colors.foregroundMuted} />
|
|
185
|
+
) : null}
|
|
186
|
+
</>
|
|
187
|
+
)
|
|
190
188
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
189
|
+
if (onPress) {
|
|
190
|
+
return (
|
|
191
|
+
<View style={disabled && styles.disabled}>
|
|
192
|
+
<PressableRow
|
|
193
|
+
style={[styles.container, cardStyle, style]}
|
|
194
|
+
onPress={handlePress}
|
|
195
|
+
enabled={!disabled}
|
|
196
|
+
rippleColor="transparent"
|
|
197
|
+
touchSoundDisabled
|
|
198
|
+
activateOnHover
|
|
199
|
+
accessibilityRole="button"
|
|
200
|
+
accessibilityLabel={a11yLabel}
|
|
201
|
+
accessibilityState={{ disabled: !!disabled }}
|
|
202
|
+
>
|
|
203
|
+
{content}
|
|
204
|
+
</PressableRow>
|
|
205
|
+
{showSeparator ? (
|
|
206
|
+
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
|
206
207
|
) : null}
|
|
207
|
-
</
|
|
208
|
+
</View>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
208
211
|
|
|
212
|
+
return (
|
|
213
|
+
<View style={[disabled && styles.disabled]}>
|
|
214
|
+
<View style={[styles.container, cardStyle, style]}>
|
|
215
|
+
{content}
|
|
216
|
+
</View>
|
|
209
217
|
{showSeparator ? (
|
|
210
|
-
<View
|
|
211
|
-
style={[
|
|
212
|
-
styles.separator,
|
|
213
|
-
{
|
|
214
|
-
backgroundColor: colors.border,
|
|
215
|
-
marginLeft: effectiveLeft ? s(44) + s(12) : 0
|
|
216
|
-
},
|
|
217
|
-
]}
|
|
218
|
-
/>
|
|
218
|
+
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
|
219
219
|
) : null}
|
|
220
|
-
</
|
|
220
|
+
</View>
|
|
221
221
|
)
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
export const ListItem = React.memo(ListItemBase)
|
|
225
|
+
|
|
224
226
|
const styles = StyleSheet.create({
|
|
225
227
|
container: {
|
|
226
228
|
flexDirection: 'row',
|
|
227
229
|
alignItems: 'center',
|
|
228
|
-
paddingHorizontal:
|
|
230
|
+
paddingHorizontal: s(16),
|
|
229
231
|
paddingVertical: vs(10),
|
|
230
232
|
gap: s(12),
|
|
231
233
|
},
|
|
@@ -241,17 +243,17 @@ const styles = StyleSheet.create({
|
|
|
241
243
|
gap: vs(4),
|
|
242
244
|
},
|
|
243
245
|
title: {
|
|
244
|
-
fontFamily: '
|
|
246
|
+
fontFamily: 'Sohne-Medium',
|
|
245
247
|
fontSize: ms(15),
|
|
246
248
|
lineHeight: mvs(22),
|
|
247
249
|
},
|
|
248
250
|
subtitle: {
|
|
249
|
-
fontFamily: '
|
|
251
|
+
fontFamily: 'Sohne-Regular',
|
|
250
252
|
fontSize: ms(13),
|
|
251
253
|
lineHeight: mvs(18),
|
|
252
254
|
},
|
|
253
255
|
caption: {
|
|
254
|
-
fontFamily: '
|
|
256
|
+
fontFamily: 'Sohne-Regular',
|
|
255
257
|
fontSize: ms(12),
|
|
256
258
|
lineHeight: mvs(16),
|
|
257
259
|
opacity: 0.7,
|
|
@@ -263,7 +265,7 @@ const styles = StyleSheet.create({
|
|
|
263
265
|
maxWidth: s(160),
|
|
264
266
|
},
|
|
265
267
|
rightText: {
|
|
266
|
-
fontFamily: '
|
|
268
|
+
fontFamily: 'Sohne-Regular',
|
|
267
269
|
fontSize: ms(14),
|
|
268
270
|
},
|
|
269
271
|
separator: {
|
|
@@ -61,7 +61,7 @@ export interface MediaCardProps {
|
|
|
61
61
|
accessibilityLabel?: string
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
function MediaCardBase({
|
|
65
65
|
imageSource,
|
|
66
66
|
aspectRatio = '4:3',
|
|
67
67
|
badge,
|
|
@@ -112,7 +112,7 @@ export function MediaCard({
|
|
|
112
112
|
{...(Platform.OS === 'web' ? hoverHandlers : {})}
|
|
113
113
|
>
|
|
114
114
|
<View style={[styles.imageContainer, imageStyle]}>
|
|
115
|
-
<View style={{ paddingTop: `${ratio * 100}%` as
|
|
115
|
+
<View style={{ paddingTop: `${ratio * 100}%` as `${number}%` }}>
|
|
116
116
|
<View style={StyleSheet.absoluteFill}>
|
|
117
117
|
{imageSource ? (
|
|
118
118
|
<Image
|
|
@@ -135,10 +135,16 @@ export function MediaCard({
|
|
|
135
135
|
{(onActionPress || actionIcon || actionIconName) && (
|
|
136
136
|
<TouchableOpacity
|
|
137
137
|
style={[styles.actionButton, { backgroundColor: 'rgba(0,0,0,0.24)' }]}
|
|
138
|
-
onPress={() => {
|
|
138
|
+
onPress={(e) => {
|
|
139
|
+
// Stop propagation to prevent triggering parent onPress
|
|
140
|
+
e?.stopPropagation?.()
|
|
141
|
+
impactLight()
|
|
142
|
+
onActionPress?.()
|
|
143
|
+
}}
|
|
139
144
|
activeOpacity={0.8}
|
|
140
145
|
touchSoundDisabled={true}
|
|
141
|
-
|
|
146
|
+
// On web, avoid nested <button> by using a non-button role when parent is pressable
|
|
147
|
+
accessibilityRole={Platform.OS === 'web' && onPress ? undefined : 'button'}
|
|
142
148
|
accessibilityLabel={actionIconName ?? 'action'}
|
|
143
149
|
accessibilityState={{ selected: actionActive }}
|
|
144
150
|
>
|
|
@@ -191,6 +197,8 @@ export function MediaCard({
|
|
|
191
197
|
return cardContent
|
|
192
198
|
}
|
|
193
199
|
|
|
200
|
+
export const MediaCard = React.memo(MediaCardBase)
|
|
201
|
+
|
|
194
202
|
const styles = StyleSheet.create({
|
|
195
203
|
card: {
|
|
196
204
|
borderRadius: RADIUS.md,
|
|
@@ -233,17 +241,17 @@ const styles = StyleSheet.create({
|
|
|
233
241
|
gap: vs(2),
|
|
234
242
|
},
|
|
235
243
|
title: {
|
|
236
|
-
fontFamily: '
|
|
244
|
+
fontFamily: 'Sohne-SemiBold',
|
|
237
245
|
fontSize: ms(14),
|
|
238
246
|
lineHeight: mvs(20),
|
|
239
247
|
},
|
|
240
248
|
subtitle: {
|
|
241
|
-
fontFamily: '
|
|
249
|
+
fontFamily: 'Sohne-Regular',
|
|
242
250
|
fontSize: ms(13),
|
|
243
251
|
lineHeight: mvs(18),
|
|
244
252
|
},
|
|
245
253
|
caption: {
|
|
246
|
-
fontFamily: '
|
|
254
|
+
fontFamily: 'Sohne-Regular',
|
|
247
255
|
fontSize: ms(12),
|
|
248
256
|
lineHeight: mvs(16),
|
|
249
257
|
},
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { View, Text, StyleSheet, ViewStyle } from 'react-native'
|
|
3
|
+
import { useTheme } from '../../theme'
|
|
4
|
+
import { s, vs } from '../../utils/scaling'
|
|
5
|
+
import { RADIUS } from '../../tokens'
|
|
6
|
+
|
|
7
|
+
export type MenuGroupVariant = 'plain' | 'card'
|
|
8
|
+
|
|
9
|
+
export interface MenuGroupProps {
|
|
10
|
+
children: React.ReactNode
|
|
11
|
+
/**
|
|
12
|
+
* - `plain` (default): no background, plain MenuItems inside.
|
|
13
|
+
* - `card`: card surface with background + border wrapping plain MenuItems.
|
|
14
|
+
*/
|
|
15
|
+
variant?: MenuGroupVariant
|
|
16
|
+
style?: ViewStyle
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface MenuGroupHeaderProps {
|
|
20
|
+
children: React.ReactNode
|
|
21
|
+
style?: ViewStyle
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface MenuGroupFooterProps {
|
|
25
|
+
children: React.ReactNode
|
|
26
|
+
style?: ViewStyle
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* MenuGroup wraps multiple MenuItems and auto-adds separators between them.
|
|
31
|
+
* Use variant="card" for a standalone surface or "plain" for items inside another container.
|
|
32
|
+
*/
|
|
33
|
+
export function MenuGroup({ children, variant = 'plain', style }: MenuGroupProps) {
|
|
34
|
+
const { colors } = useTheme()
|
|
35
|
+
|
|
36
|
+
// Auto-inject showSeparator={true} to all MenuItem children except the last
|
|
37
|
+
const processedChildren = React.Children.map(children, (child, index) => {
|
|
38
|
+
if (!React.isValidElement(child)) return child
|
|
39
|
+
|
|
40
|
+
// Skip MenuGroup.Header and MenuGroup.Footer
|
|
41
|
+
if (child.type === MenuGroupHeader || child.type === MenuGroupFooter) {
|
|
42
|
+
return child
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check if it's a MenuItem (has onPress prop as a heuristic)
|
|
46
|
+
const childProps = child.props as Record<string, unknown>
|
|
47
|
+
const isMenuItem = 'onPress' in childProps
|
|
48
|
+
if (!isMenuItem) return child
|
|
49
|
+
|
|
50
|
+
const isLast = index === React.Children.count(children) - 1
|
|
51
|
+
|
|
52
|
+
// Only add separator if not already explicitly set and not last item
|
|
53
|
+
if (childProps['showSeparator'] === undefined && !isLast) {
|
|
54
|
+
return React.cloneElement(child as React.ReactElement<Record<string, unknown>>, {
|
|
55
|
+
showSeparator: true,
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return child
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const cardStyle: ViewStyle =
|
|
63
|
+
variant === 'card'
|
|
64
|
+
? {
|
|
65
|
+
backgroundColor: colors.card,
|
|
66
|
+
borderRadius: RADIUS.md,
|
|
67
|
+
borderWidth: 1,
|
|
68
|
+
borderColor: colors.border,
|
|
69
|
+
shadowColor: '#000',
|
|
70
|
+
shadowOffset: { width: 0, height: 2 },
|
|
71
|
+
shadowOpacity: 0.06,
|
|
72
|
+
shadowRadius: 6,
|
|
73
|
+
elevation: 2,
|
|
74
|
+
paddingVertical: vs(4),
|
|
75
|
+
}
|
|
76
|
+
: {}
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<View style={[styles.container, cardStyle, style]}>
|
|
80
|
+
{processedChildren}
|
|
81
|
+
</View>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function MenuGroupHeader({ children, style }: MenuGroupHeaderProps) {
|
|
86
|
+
const { colors } = useTheme()
|
|
87
|
+
|
|
88
|
+
if (typeof children === 'string') {
|
|
89
|
+
return (
|
|
90
|
+
<View style={[styles.header, { borderBottomColor: colors.separator }, style]}>
|
|
91
|
+
<Text style={[styles.headerText, { color: colors.foregroundMuted }]} allowFontScaling={true}>
|
|
92
|
+
{children}
|
|
93
|
+
</Text>
|
|
94
|
+
</View>
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return <View style={[styles.header, { borderBottomColor: colors.separator }, style]}>{children}</View>
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function MenuGroupFooter({ children, style }: MenuGroupFooterProps) {
|
|
102
|
+
const { colors } = useTheme()
|
|
103
|
+
|
|
104
|
+
if (typeof children === 'string') {
|
|
105
|
+
return (
|
|
106
|
+
<View style={[styles.footer, style]}>
|
|
107
|
+
<Text style={[styles.footerText, { color: colors.foregroundMuted }]} allowFontScaling={true}>
|
|
108
|
+
{children}
|
|
109
|
+
</Text>
|
|
110
|
+
</View>
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return <View style={[styles.footer, style]}>{children}</View>
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
MenuGroup.Header = MenuGroupHeader
|
|
118
|
+
MenuGroup.Footer = MenuGroupFooter
|
|
119
|
+
|
|
120
|
+
const styles = StyleSheet.create({
|
|
121
|
+
container: {
|
|
122
|
+
overflow: 'hidden',
|
|
123
|
+
},
|
|
124
|
+
header: {
|
|
125
|
+
paddingHorizontal: s(16),
|
|
126
|
+
paddingTop: vs(12),
|
|
127
|
+
paddingBottom: vs(8),
|
|
128
|
+
borderBottomWidth: StyleSheet.hairlineWidth,
|
|
129
|
+
},
|
|
130
|
+
headerText: {
|
|
131
|
+
fontFamily: 'Sohne-SemiBold',
|
|
132
|
+
fontSize: 13,
|
|
133
|
+
letterSpacing: 0.32,
|
|
134
|
+
textTransform: 'uppercase',
|
|
135
|
+
},
|
|
136
|
+
footer: {
|
|
137
|
+
paddingHorizontal: s(16),
|
|
138
|
+
paddingTop: vs(8),
|
|
139
|
+
paddingBottom: vs(12),
|
|
140
|
+
},
|
|
141
|
+
footerText: {
|
|
142
|
+
fontFamily: 'Sohne-Regular',
|
|
143
|
+
fontSize: 12,
|
|
144
|
+
},
|
|
145
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './MenuGroup'
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import {
|
|
3
|
-
TouchableOpacity,
|
|
4
3
|
View,
|
|
5
4
|
Text,
|
|
6
5
|
StyleSheet,
|
|
7
6
|
ViewStyle,
|
|
8
7
|
TextStyle,
|
|
9
8
|
} from 'react-native'
|
|
10
|
-
import Animated from 'react-native-reanimated'
|
|
11
9
|
import { Entypo } from '@expo/vector-icons'
|
|
12
10
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
13
11
|
import { useTheme } from '../../theme'
|
|
14
12
|
import { s, vs, ms } from '../../utils/scaling'
|
|
15
13
|
import { renderIcon } from '../../utils/icons'
|
|
16
14
|
import { RADIUS } from '../../tokens'
|
|
17
|
-
import {
|
|
18
|
-
import { SPRINGS, PRESS_SCALE } from '../../utils/animations'
|
|
15
|
+
import { PressableRow } from '../../utils/pressable'
|
|
19
16
|
|
|
20
17
|
export type MenuItemVariant = 'plain' | 'card'
|
|
21
18
|
|
|
@@ -60,7 +57,7 @@ export interface MenuItemProps {
|
|
|
60
57
|
accessibilityLabel?: string
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
|
|
60
|
+
function MenuItemBase({
|
|
64
61
|
label,
|
|
65
62
|
subtitle,
|
|
66
63
|
iconName,
|
|
@@ -77,12 +74,6 @@ export function MenuItem({
|
|
|
77
74
|
accessibilityLabel,
|
|
78
75
|
}: MenuItemProps) {
|
|
79
76
|
const { colors } = useTheme()
|
|
80
|
-
const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
81
|
-
pressScale: PRESS_SCALE.row,
|
|
82
|
-
pressInSpring: SPRINGS.surfacePressIn,
|
|
83
|
-
pressOutSpring: SPRINGS.surfacePressOut,
|
|
84
|
-
disabled,
|
|
85
|
-
})
|
|
86
77
|
|
|
87
78
|
const handlePress = () => {
|
|
88
79
|
hapticSelection()
|
|
@@ -111,15 +102,14 @@ export function MenuItem({
|
|
|
111
102
|
const a11yLabel = accessibilityLabel ?? (subtitle ? `${label}. ${subtitle}` : label)
|
|
112
103
|
|
|
113
104
|
return (
|
|
114
|
-
<
|
|
115
|
-
<
|
|
105
|
+
<View style={disabled && styles.disabled}>
|
|
106
|
+
<PressableRow
|
|
116
107
|
style={[styles.container, cardStyle, style]}
|
|
117
108
|
onPress={handlePress}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
touchSoundDisabled={true}
|
|
109
|
+
enabled={!disabled}
|
|
110
|
+
rippleColor="transparent"
|
|
111
|
+
touchSoundDisabled
|
|
112
|
+
activateOnHover
|
|
123
113
|
accessibilityRole="button"
|
|
124
114
|
accessibilityLabel={a11yLabel}
|
|
125
115
|
accessibilityState={{ disabled }}
|
|
@@ -158,29 +148,22 @@ export function MenuItem({
|
|
|
158
148
|
) : showChevron ? (
|
|
159
149
|
<Entypo name="chevron-right" size={18} color={colors.foregroundMuted} />
|
|
160
150
|
) : null}
|
|
161
|
-
</
|
|
151
|
+
</PressableRow>
|
|
162
152
|
|
|
163
153
|
{showSeparator ? (
|
|
164
|
-
<View
|
|
165
|
-
style={[
|
|
166
|
-
styles.separator,
|
|
167
|
-
{
|
|
168
|
-
backgroundColor: colors.border,
|
|
169
|
-
marginLeft: resolvedIcon ? s(22) + s(12) : 0,
|
|
170
|
-
opacity: 0.6,
|
|
171
|
-
},
|
|
172
|
-
]}
|
|
173
|
-
/>
|
|
154
|
+
<View style={[styles.separator, { backgroundColor: colors.separator }]} />
|
|
174
155
|
) : null}
|
|
175
|
-
</
|
|
156
|
+
</View>
|
|
176
157
|
)
|
|
177
158
|
}
|
|
178
159
|
|
|
160
|
+
export const MenuItem = React.memo(MenuItemBase)
|
|
161
|
+
|
|
179
162
|
const styles = StyleSheet.create({
|
|
180
163
|
container: {
|
|
181
164
|
flexDirection: 'row',
|
|
182
165
|
alignItems: 'center',
|
|
183
|
-
paddingHorizontal:
|
|
166
|
+
paddingHorizontal: s(16),
|
|
184
167
|
paddingVertical: vs(16),
|
|
185
168
|
minHeight: vs(54),
|
|
186
169
|
gap: s(12),
|
|
@@ -196,11 +179,11 @@ const styles = StyleSheet.create({
|
|
|
196
179
|
justifyContent: 'center',
|
|
197
180
|
},
|
|
198
181
|
label: {
|
|
199
|
-
fontFamily: '
|
|
182
|
+
fontFamily: 'Sohne-Medium',
|
|
200
183
|
fontSize: ms(15),
|
|
201
184
|
},
|
|
202
185
|
subtitle: {
|
|
203
|
-
fontFamily: '
|
|
186
|
+
fontFamily: 'Sohne-Regular',
|
|
204
187
|
fontSize: ms(12),
|
|
205
188
|
marginTop: vs(1),
|
|
206
189
|
},
|
|
@@ -3,7 +3,7 @@ import { View, Text, TouchableOpacity, StyleSheet, ViewStyle } from 'react-nativ
|
|
|
3
3
|
import { Entypo } from '@expo/vector-icons'
|
|
4
4
|
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
5
5
|
import { useTheme } from '../../theme'
|
|
6
|
-
import { s,
|
|
6
|
+
import { s, ms, mvs } from '../../utils/scaling'
|
|
7
7
|
|
|
8
8
|
const MONTH_NAMES: Record<string, string[]> = {
|
|
9
9
|
en: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
|
@@ -18,9 +18,27 @@ export interface MonthPickerValue {
|
|
|
18
18
|
year: number
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
/** Convert a JS `Date` to a `MonthPickerValue` (uses local time). */
|
|
22
|
+
export function dateToMonthPickerValue(date: Date): MonthPickerValue {
|
|
23
|
+
return { month: date.getMonth() + 1, year: date.getFullYear() }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Convert a `MonthPickerValue` to a `Date` at the first day of that month (local time). */
|
|
27
|
+
export function monthPickerValueToDate(value: MonthPickerValue): Date {
|
|
28
|
+
return new Date(value.year, value.month - 1, 1)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Absolute month index — lets us compare/clamp values with simple arithmetic.
|
|
32
|
+
const toIndex = (v: MonthPickerValue) => v.year * 12 + (v.month - 1)
|
|
33
|
+
const fromIndex = (i: number): MonthPickerValue => ({ year: Math.floor(i / 12), month: (i % 12) + 1 })
|
|
34
|
+
|
|
21
35
|
export interface MonthPickerProps {
|
|
22
36
|
value: MonthPickerValue
|
|
23
37
|
onChange: (value: MonthPickerValue) => void
|
|
38
|
+
/** Earliest selectable month (inclusive). Prev arrow disables at this bound. */
|
|
39
|
+
minValue?: MonthPickerValue
|
|
40
|
+
/** Latest selectable month (inclusive). Next arrow disables at this bound — e.g. cap at the current month. */
|
|
41
|
+
maxValue?: MonthPickerValue
|
|
24
42
|
/** BCP 47 locale tag. Built-in: 'en' | 'es' | 'pt' | 'fr'. For other locales supply formatLabel. */
|
|
25
43
|
locale?: string
|
|
26
44
|
/** Custom label formatter. Takes precedence over locale. */
|
|
@@ -28,9 +46,16 @@ export interface MonthPickerProps {
|
|
|
28
46
|
style?: ViewStyle
|
|
29
47
|
}
|
|
30
48
|
|
|
31
|
-
export function MonthPicker({ value, onChange, locale = 'en', formatLabel, style }: MonthPickerProps) {
|
|
49
|
+
export function MonthPicker({ value, onChange, minValue, maxValue, locale = 'en', formatLabel, style }: MonthPickerProps) {
|
|
32
50
|
const { colors } = useTheme()
|
|
33
51
|
|
|
52
|
+
const index = toIndex(value)
|
|
53
|
+
const minIndex = minValue ? toIndex(minValue) : -Infinity
|
|
54
|
+
const maxIndex = maxValue ? toIndex(maxValue) : Infinity
|
|
55
|
+
|
|
56
|
+
const prevDisabled = index - 1 < minIndex
|
|
57
|
+
const nextDisabled = index + 1 > maxIndex
|
|
58
|
+
|
|
34
59
|
const getLabel = (): string => {
|
|
35
60
|
if (formatLabel) return formatLabel(value)
|
|
36
61
|
const names = MONTH_NAMES[locale] ?? MONTH_NAMES.en
|
|
@@ -38,32 +63,28 @@ export function MonthPicker({ value, onChange, locale = 'en', formatLabel, style
|
|
|
38
63
|
}
|
|
39
64
|
|
|
40
65
|
const handlePrev = () => {
|
|
66
|
+
if (prevDisabled) return
|
|
41
67
|
hapticSelection()
|
|
42
|
-
|
|
43
|
-
onChange({ month: 12, year: value.year - 1 })
|
|
44
|
-
} else {
|
|
45
|
-
onChange({ month: value.month - 1, year: value.year })
|
|
46
|
-
}
|
|
68
|
+
onChange(fromIndex(index - 1))
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
const handleNext = () => {
|
|
72
|
+
if (nextDisabled) return
|
|
50
73
|
hapticSelection()
|
|
51
|
-
|
|
52
|
-
onChange({ month: 1, year: value.year + 1 })
|
|
53
|
-
} else {
|
|
54
|
-
onChange({ month: value.month + 1, year: value.year })
|
|
55
|
-
}
|
|
74
|
+
onChange(fromIndex(index + 1))
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
return (
|
|
59
78
|
<View style={[styles.container, style]} accessibilityRole="adjustable" accessibilityLabel={getLabel()}>
|
|
60
79
|
<TouchableOpacity
|
|
61
|
-
style={styles.arrow}
|
|
80
|
+
style={[styles.arrow, prevDisabled && styles.arrowDisabled]}
|
|
62
81
|
onPress={handlePrev}
|
|
82
|
+
disabled={prevDisabled}
|
|
63
83
|
activeOpacity={0.6}
|
|
64
84
|
touchSoundDisabled={true}
|
|
65
85
|
accessibilityRole="button"
|
|
66
86
|
accessibilityLabel="Previous month"
|
|
87
|
+
accessibilityState={{ disabled: prevDisabled }}
|
|
67
88
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
68
89
|
>
|
|
69
90
|
<Entypo name="chevron-left" size={22} color={colors.foreground} />
|
|
@@ -76,12 +97,14 @@ export function MonthPicker({ value, onChange, locale = 'en', formatLabel, style
|
|
|
76
97
|
{getLabel()}
|
|
77
98
|
</Text>
|
|
78
99
|
<TouchableOpacity
|
|
79
|
-
style={styles.arrow}
|
|
100
|
+
style={[styles.arrow, nextDisabled && styles.arrowDisabled]}
|
|
80
101
|
onPress={handleNext}
|
|
102
|
+
disabled={nextDisabled}
|
|
81
103
|
activeOpacity={0.6}
|
|
82
104
|
touchSoundDisabled={true}
|
|
83
105
|
accessibilityRole="button"
|
|
84
106
|
accessibilityLabel="Next month"
|
|
107
|
+
accessibilityState={{ disabled: nextDisabled }}
|
|
85
108
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
86
109
|
>
|
|
87
110
|
<Entypo name="chevron-right" size={22} color={colors.foreground} />
|
|
@@ -102,8 +125,11 @@ const styles = StyleSheet.create({
|
|
|
102
125
|
alignItems: 'center',
|
|
103
126
|
justifyContent: 'center',
|
|
104
127
|
},
|
|
128
|
+
arrowDisabled: {
|
|
129
|
+
opacity: 0.3,
|
|
130
|
+
},
|
|
105
131
|
label: {
|
|
106
|
-
fontFamily: '
|
|
132
|
+
fontFamily: 'Sohne-Medium',
|
|
107
133
|
fontSize: ms(17),
|
|
108
134
|
lineHeight: mvs(24),
|
|
109
135
|
textAlign: 'center',
|