@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
|
@@ -60,7 +60,7 @@ export function Select({
|
|
|
60
60
|
setPendingValue(value)
|
|
61
61
|
setPickerVisible(true)
|
|
62
62
|
} else if (isAndroid) {
|
|
63
|
-
;(pickerRef.current as
|
|
63
|
+
;(pickerRef.current as { focus?: () => void })?.focus?.()
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -234,7 +234,7 @@ const styles = StyleSheet.create({
|
|
|
234
234
|
gap: vs(8),
|
|
235
235
|
},
|
|
236
236
|
label: {
|
|
237
|
-
fontFamily: '
|
|
237
|
+
fontFamily: 'Sohne-Medium',
|
|
238
238
|
fontSize: ms(13),
|
|
239
239
|
},
|
|
240
240
|
trigger: {
|
|
@@ -247,7 +247,7 @@ const styles = StyleSheet.create({
|
|
|
247
247
|
paddingVertical: vs(11),
|
|
248
248
|
},
|
|
249
249
|
triggerText: {
|
|
250
|
-
fontFamily: '
|
|
250
|
+
fontFamily: 'Sohne-Regular',
|
|
251
251
|
fontSize: ms(15),
|
|
252
252
|
flex: 1,
|
|
253
253
|
},
|
|
@@ -255,7 +255,7 @@ const styles = StyleSheet.create({
|
|
|
255
255
|
marginLeft: s(8),
|
|
256
256
|
},
|
|
257
257
|
helperText: {
|
|
258
|
-
fontFamily: '
|
|
258
|
+
fontFamily: 'Sohne-Regular',
|
|
259
259
|
fontSize: ms(13),
|
|
260
260
|
},
|
|
261
261
|
iosBackdrop: {
|
|
@@ -276,14 +276,14 @@ const styles = StyleSheet.create({
|
|
|
276
276
|
borderBottomWidth: 1,
|
|
277
277
|
},
|
|
278
278
|
iosToolbarTitle: {
|
|
279
|
-
fontFamily: '
|
|
279
|
+
fontFamily: 'Sohne-SemiBold',
|
|
280
280
|
fontSize: ms(17),
|
|
281
281
|
},
|
|
282
282
|
iosDoneBtn: {
|
|
283
283
|
padding: s(4),
|
|
284
284
|
},
|
|
285
285
|
iosDoneBtnText: {
|
|
286
|
-
fontFamily: '
|
|
286
|
+
fontFamily: 'Sohne-SemiBold',
|
|
287
287
|
fontSize: ms(17),
|
|
288
288
|
},
|
|
289
289
|
androidHiddenPicker: {
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import React, { useState } from 'react'
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle, ScrollView } from 'react-native'
|
|
3
|
+
import Animated from 'react-native-reanimated'
|
|
4
|
+
import { useTheme } from '../../theme'
|
|
5
|
+
import { renderIcon } from '../../utils/icons'
|
|
6
|
+
import { usePressScale } from '../../utils/usePressScale'
|
|
7
|
+
import { selectionAsync as hapticSelection } from '../../utils/haptics'
|
|
8
|
+
import { PRESS_SCALE } from '../../utils/animations'
|
|
9
|
+
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
10
|
+
import { RADIUS } from '../../tokens'
|
|
11
|
+
|
|
12
|
+
export interface SelectableGridItem<T extends string | number = string> {
|
|
13
|
+
/** Unique value emitted on selection. */
|
|
14
|
+
value: T
|
|
15
|
+
/** Label rendered under the icon. */
|
|
16
|
+
label?: string
|
|
17
|
+
/** Icon name resolved via the icon registry. */
|
|
18
|
+
iconName?: string
|
|
19
|
+
/** Custom icon node — overrides `iconName`. */
|
|
20
|
+
icon?: React.ReactNode
|
|
21
|
+
disabled?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SelectableGridProps<T extends string | number = string> {
|
|
25
|
+
items: SelectableGridItem<T>[]
|
|
26
|
+
/** Selected value(s). Array when `multiple`. */
|
|
27
|
+
value: T | T[] | null
|
|
28
|
+
onChange: (value: T) => void
|
|
29
|
+
/** Allow multiple selections. `value` should be an array. Defaults to false. */
|
|
30
|
+
multiple?: boolean
|
|
31
|
+
/** Columns per row. Defaults to 4. Ignored when `orientation='horizontal'`. */
|
|
32
|
+
numColumns?: number
|
|
33
|
+
/** Gap between cells (dp). Defaults to 12. */
|
|
34
|
+
gap?: number
|
|
35
|
+
/** Layout orientation. 'grid' (default) wraps into rows. 'horizontal' creates a single scrollable row. */
|
|
36
|
+
orientation?: 'grid' | 'horizontal'
|
|
37
|
+
style?: ViewStyle
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isSelected<T extends string | number>(value: T | T[] | null, candidate: T): boolean {
|
|
41
|
+
if (value == null) return false
|
|
42
|
+
return Array.isArray(value) ? value.includes(candidate) : value === candidate
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface CellProps<T extends string | number> {
|
|
46
|
+
item: SelectableGridItem<T>
|
|
47
|
+
selected: boolean
|
|
48
|
+
width: number
|
|
49
|
+
onPress: () => void
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function Cell<T extends string | number>({ item, selected, width, onPress }: CellProps<T>) {
|
|
53
|
+
const { colors } = useTheme()
|
|
54
|
+
const { animatedStyle, onPressIn, onPressOut, hoverHandlers } = usePressScale({
|
|
55
|
+
pressScale: PRESS_SCALE.chip,
|
|
56
|
+
disabled: item.disabled,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const iconColor = selected ? colors.primary : colors.foregroundSubtle
|
|
60
|
+
const iconNode = item.icon ?? (item.iconName ? renderIcon(item.iconName, ms(24), iconColor) : null)
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Animated.View style={[{ width }, animatedStyle]}>
|
|
64
|
+
<TouchableOpacity
|
|
65
|
+
onPress={onPress}
|
|
66
|
+
onPressIn={onPressIn}
|
|
67
|
+
onPressOut={onPressOut}
|
|
68
|
+
disabled={item.disabled}
|
|
69
|
+
activeOpacity={1}
|
|
70
|
+
touchSoundDisabled={true}
|
|
71
|
+
accessibilityRole="button"
|
|
72
|
+
accessibilityState={{ selected, disabled: item.disabled }}
|
|
73
|
+
accessibilityLabel={item.label ?? String(item.value)}
|
|
74
|
+
{...hoverHandlers}
|
|
75
|
+
style={[
|
|
76
|
+
styles.cell,
|
|
77
|
+
{
|
|
78
|
+
backgroundColor: selected ? colors.primary + '14' : colors.surface,
|
|
79
|
+
borderColor: selected ? colors.primary : 'transparent',
|
|
80
|
+
},
|
|
81
|
+
item.disabled && styles.cellDisabled,
|
|
82
|
+
]}
|
|
83
|
+
>
|
|
84
|
+
{iconNode}
|
|
85
|
+
{item.label ? (
|
|
86
|
+
<Text
|
|
87
|
+
style={[styles.label, { color: selected ? colors.primary : colors.foreground }]}
|
|
88
|
+
numberOfLines={1}
|
|
89
|
+
allowFontScaling={true}
|
|
90
|
+
>
|
|
91
|
+
{item.label}
|
|
92
|
+
</Text>
|
|
93
|
+
) : null}
|
|
94
|
+
</TouchableOpacity>
|
|
95
|
+
</Animated.View>
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Grid of selectable cells (icon + label) — for store / category / emoji pickers
|
|
101
|
+
* where a list would be the wrong shape. Single or multi select.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* <SelectableGrid
|
|
105
|
+
* items={categories}
|
|
106
|
+
* value={selected}
|
|
107
|
+
* onChange={setSelected}
|
|
108
|
+
* numColumns={4}
|
|
109
|
+
* />
|
|
110
|
+
*/
|
|
111
|
+
export function SelectableGrid<T extends string | number = string>({
|
|
112
|
+
items,
|
|
113
|
+
value,
|
|
114
|
+
onChange,
|
|
115
|
+
multiple = false,
|
|
116
|
+
numColumns = 4,
|
|
117
|
+
gap = 12,
|
|
118
|
+
orientation = 'grid',
|
|
119
|
+
style,
|
|
120
|
+
}: SelectableGridProps<T>) {
|
|
121
|
+
const [containerWidth, setContainerWidth] = useState(0)
|
|
122
|
+
const gapPx = s(gap)
|
|
123
|
+
// Compute exact cell width so `numColumns` always fits — percentage widths + gap
|
|
124
|
+
// overflow and wrap one short. -0.5 guards against sub-pixel rounding overflow.
|
|
125
|
+
const cellWidth = containerWidth > 0 ? (containerWidth - gapPx * (numColumns - 1)) / numColumns - 0.5 : 0
|
|
126
|
+
// Horizontal mode: fixed 72dp cell width (same scale as grid cells)
|
|
127
|
+
const horizCellWidth = s(72)
|
|
128
|
+
|
|
129
|
+
const handlePress = (item: SelectableGridItem<T>) => {
|
|
130
|
+
if (item.disabled) return
|
|
131
|
+
hapticSelection()
|
|
132
|
+
onChange(item.value)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (orientation === 'horizontal') {
|
|
136
|
+
return (
|
|
137
|
+
<ScrollView
|
|
138
|
+
horizontal
|
|
139
|
+
showsHorizontalScrollIndicator={false}
|
|
140
|
+
contentContainerStyle={[styles.horizontal, { gap: gapPx }, style]}
|
|
141
|
+
accessibilityRole={multiple ? undefined : 'radiogroup'}
|
|
142
|
+
>
|
|
143
|
+
{items.map((item) => (
|
|
144
|
+
<Cell
|
|
145
|
+
key={String(item.value)}
|
|
146
|
+
item={item}
|
|
147
|
+
selected={isSelected(value, item.value)}
|
|
148
|
+
width={horizCellWidth}
|
|
149
|
+
onPress={() => handlePress(item)}
|
|
150
|
+
/>
|
|
151
|
+
))}
|
|
152
|
+
</ScrollView>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return (
|
|
157
|
+
<View
|
|
158
|
+
style={[styles.grid, { gap: gapPx }, style]}
|
|
159
|
+
onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}
|
|
160
|
+
accessibilityRole={multiple ? undefined : 'radiogroup'}
|
|
161
|
+
>
|
|
162
|
+
{cellWidth > 0
|
|
163
|
+
? items.map((item) => (
|
|
164
|
+
<Cell
|
|
165
|
+
key={String(item.value)}
|
|
166
|
+
item={item}
|
|
167
|
+
selected={isSelected(value, item.value)}
|
|
168
|
+
width={cellWidth}
|
|
169
|
+
onPress={() => handlePress(item)}
|
|
170
|
+
/>
|
|
171
|
+
))
|
|
172
|
+
: null}
|
|
173
|
+
</View>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const styles = StyleSheet.create({
|
|
178
|
+
grid: {
|
|
179
|
+
flexDirection: 'row',
|
|
180
|
+
flexWrap: 'wrap',
|
|
181
|
+
},
|
|
182
|
+
horizontal: {
|
|
183
|
+
flexDirection: 'row',
|
|
184
|
+
paddingHorizontal: s(4),
|
|
185
|
+
},
|
|
186
|
+
cell: {
|
|
187
|
+
flex: 1,
|
|
188
|
+
borderRadius: RADIUS.md,
|
|
189
|
+
borderWidth: 2,
|
|
190
|
+
alignItems: 'center',
|
|
191
|
+
justifyContent: 'center',
|
|
192
|
+
gap: vs(4),
|
|
193
|
+
paddingHorizontal: s(12),
|
|
194
|
+
paddingVertical: vs(12),
|
|
195
|
+
},
|
|
196
|
+
cellDisabled: {
|
|
197
|
+
opacity: 0.4,
|
|
198
|
+
},
|
|
199
|
+
label: {
|
|
200
|
+
fontFamily: 'Sohne-Medium',
|
|
201
|
+
fontSize: ms(12),
|
|
202
|
+
lineHeight: mvs(15),
|
|
203
|
+
textAlign: 'center',
|
|
204
|
+
},
|
|
205
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './SelectableGrid'
|
|
@@ -14,7 +14,7 @@ export function Separator({ orientation = 'horizontal', style }: SeparatorProps)
|
|
|
14
14
|
<View
|
|
15
15
|
style={[
|
|
16
16
|
orientation === 'horizontal' ? styles.horizontal : styles.vertical,
|
|
17
|
-
{ backgroundColor: colors.
|
|
17
|
+
{ backgroundColor: colors.separator },
|
|
18
18
|
style,
|
|
19
19
|
]}
|
|
20
20
|
/>
|
|
@@ -25,11 +25,9 @@ const styles = StyleSheet.create({
|
|
|
25
25
|
horizontal: {
|
|
26
26
|
height: 1,
|
|
27
27
|
width: '100%',
|
|
28
|
-
opacity: 0.7,
|
|
29
28
|
},
|
|
30
29
|
vertical: {
|
|
31
30
|
width: 1,
|
|
32
31
|
height: '100%',
|
|
33
|
-
opacity: 0.7,
|
|
34
32
|
},
|
|
35
33
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef } from 'react'
|
|
2
|
-
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle, Dimensions, Platform } from 'react-native'
|
|
2
|
+
import { View, Text, TouchableOpacity, StyleSheet, ViewStyle, Dimensions, Platform, Modal, ScrollView, useWindowDimensions, Pressable } from 'react-native'
|
|
3
3
|
import {
|
|
4
4
|
BottomSheetModal,
|
|
5
5
|
BottomSheetView,
|
|
@@ -16,6 +16,7 @@ import { AntDesign } from '@expo/vector-icons'
|
|
|
16
16
|
import { impactMedium } from '../../utils/haptics'
|
|
17
17
|
import { useTheme } from '../../theme'
|
|
18
18
|
import { s, vs, ms, mvs } from '../../utils/scaling'
|
|
19
|
+
import { BREAKPOINTS, RADIUS, SHADOWS } from '../../tokens'
|
|
19
20
|
|
|
20
21
|
const SCREEN_HEIGHT = Dimensions.get('window').height
|
|
21
22
|
const DEFAULT_MAX_HEIGHT = SCREEN_HEIGHT * 0.85
|
|
@@ -25,6 +26,21 @@ export { BottomSheetModalProvider }
|
|
|
25
26
|
// Re-export BottomSheetTextInput as SheetTextInput for consumer convenience
|
|
26
27
|
export { BottomSheetTextInput as SheetTextInput }
|
|
27
28
|
|
|
29
|
+
export interface SheetHeaderProps {
|
|
30
|
+
children: React.ReactNode
|
|
31
|
+
style?: ViewStyle
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SheetContentProps {
|
|
35
|
+
children: React.ReactNode
|
|
36
|
+
style?: ViewStyle
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface SheetFooterProps {
|
|
40
|
+
children: React.ReactNode
|
|
41
|
+
style?: ViewStyle
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
export interface SheetProps {
|
|
29
45
|
open: boolean
|
|
30
46
|
onClose: () => void
|
|
@@ -78,6 +94,35 @@ export interface SheetProps {
|
|
|
78
94
|
* When omitted, sheet uses dynamic sizing (auto-fits content).
|
|
79
95
|
*/
|
|
80
96
|
snapPoints?: (string | number)[]
|
|
97
|
+
/**
|
|
98
|
+
* When true, render as a centered modal dialog on wide screens (width ≥
|
|
99
|
+
* `BREAKPOINTS.wide`) instead of a bottom sheet. On narrow screens it stays a
|
|
100
|
+
* bottom sheet. Use for store/category/picker dialogs that should feel native
|
|
101
|
+
* on tablets and web.
|
|
102
|
+
*
|
|
103
|
+
* Note: the centered-dialog path uses a plain RN `Modal`, so `SheetTextInput`
|
|
104
|
+
* is not required there — use a regular `TextInput`.
|
|
105
|
+
*/
|
|
106
|
+
responsive?: boolean
|
|
107
|
+
/** Max width of the centered dialog (dp). Only applies when `responsive`. Defaults to 480. */
|
|
108
|
+
dialogMaxWidth?: number
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function SheetHeader({ children, style }: SheetHeaderProps) {
|
|
112
|
+
return <View style={[styles.header, style]}>{children}</View>
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function SheetContent({ children, style }: SheetContentProps) {
|
|
116
|
+
return <View style={[styles.sheetContent, style]}>{children}</View>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function SheetFooter({ children, style }: SheetFooterProps) {
|
|
120
|
+
const { colors } = useTheme()
|
|
121
|
+
return (
|
|
122
|
+
<View style={[styles.sheetFooter, { backgroundColor: colors.card, borderTopColor: colors.border }, style]}>
|
|
123
|
+
{children}
|
|
124
|
+
</View>
|
|
125
|
+
)
|
|
81
126
|
}
|
|
82
127
|
|
|
83
128
|
export function Sheet({
|
|
@@ -98,10 +143,14 @@ export function Sheet({
|
|
|
98
143
|
android_keyboardInputMode = 'adjustPan',
|
|
99
144
|
footer,
|
|
100
145
|
snapPoints,
|
|
146
|
+
responsive = false,
|
|
147
|
+
dialogMaxWidth = 480,
|
|
101
148
|
}: SheetProps) {
|
|
102
149
|
const { colors } = useTheme()
|
|
103
150
|
const insets = useSafeAreaInsets()
|
|
151
|
+
const { width: windowWidth } = useWindowDimensions()
|
|
104
152
|
const ref = useRef<BottomSheetModal>(null)
|
|
153
|
+
const asDialog = responsive && windowWidth >= BREAKPOINTS.wide
|
|
105
154
|
|
|
106
155
|
// 'interactive' + 'adjustPan' works properly with enableDynamicSizing on both platforms
|
|
107
156
|
// 'fillParent' + 'adjustResize' causes restore issues (transparent gap when keyboard dismisses)
|
|
@@ -125,20 +174,25 @@ export function Sheet({
|
|
|
125
174
|
/>
|
|
126
175
|
), [])
|
|
127
176
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
177
|
+
// Detect compound components in children
|
|
178
|
+
const childArray = React.Children.toArray(children)
|
|
179
|
+
const customHeader = childArray.find((child) => React.isValidElement(child) && child.type === SheetHeader)
|
|
180
|
+
const customContent = childArray.find((child) => React.isValidElement(child) && child.type === SheetContent)
|
|
181
|
+
const customFooter = childArray.find((child) => React.isValidElement(child) && child.type === SheetFooter)
|
|
182
|
+
|
|
183
|
+
// If using compound components, filter them out from main children
|
|
184
|
+
const filteredChildren = customHeader || customContent || customFooter
|
|
185
|
+
? childArray.filter(
|
|
186
|
+
(child) =>
|
|
187
|
+
!React.isValidElement(child) ||
|
|
188
|
+
(child.type !== SheetHeader && child.type !== SheetContent && child.type !== SheetFooter)
|
|
189
|
+
)
|
|
190
|
+
: children
|
|
136
191
|
|
|
137
192
|
const effectiveSubtitle = subtitle ?? description
|
|
193
|
+
const showHeader = !!(title || effectiveSubtitle || showCloseButton) && !customHeader
|
|
138
194
|
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
const headerNode = showHeader ? (
|
|
195
|
+
const headerNode = customHeader ? customHeader : (showHeader ? (
|
|
142
196
|
<View style={styles.header} accessibilityRole="header">
|
|
143
197
|
<View style={styles.headerRow}>
|
|
144
198
|
{title ? (
|
|
@@ -166,7 +220,48 @@ export function Sheet({
|
|
|
166
220
|
</Text>
|
|
167
221
|
) : null}
|
|
168
222
|
</View>
|
|
169
|
-
) : null
|
|
223
|
+
) : null)
|
|
224
|
+
|
|
225
|
+
const contentNode = customContent ? customContent : filteredChildren
|
|
226
|
+
const effectiveFooter = customFooter ? customFooter : footer
|
|
227
|
+
|
|
228
|
+
const renderFooter = useCallback((props: BottomSheetFooterProps) => {
|
|
229
|
+
if (!effectiveFooter) return null
|
|
230
|
+
return (
|
|
231
|
+
<BottomSheetFooter {...props}>
|
|
232
|
+
{effectiveFooter}
|
|
233
|
+
</BottomSheetFooter>
|
|
234
|
+
)
|
|
235
|
+
}, [effectiveFooter])
|
|
236
|
+
|
|
237
|
+
// Centered dialog path for wide screens — plain RN Modal, same header/content/footer.
|
|
238
|
+
if (asDialog) {
|
|
239
|
+
return (
|
|
240
|
+
<Modal visible={open} transparent animationType="fade" onRequestClose={onClose}>
|
|
241
|
+
<Pressable style={styles.dialogBackdrop} onPress={onClose} accessibilityRole="button" accessibilityLabel="Close">
|
|
242
|
+
{/* Inner Pressable swallows presses so taps inside the card don't close it. */}
|
|
243
|
+
<Pressable
|
|
244
|
+
style={[
|
|
245
|
+
styles.dialogCard,
|
|
246
|
+
{ backgroundColor: colors.card, maxWidth: dialogMaxWidth, maxHeight: SCREEN_HEIGHT * 0.85 },
|
|
247
|
+
]}
|
|
248
|
+
onPress={() => {}}
|
|
249
|
+
>
|
|
250
|
+
{headerNode}
|
|
251
|
+
<ScrollView
|
|
252
|
+
contentContainerStyle={[styles.dialogContent, style]}
|
|
253
|
+
style={contentStyle}
|
|
254
|
+
showsVerticalScrollIndicator={true}
|
|
255
|
+
bounces={false}
|
|
256
|
+
>
|
|
257
|
+
{contentNode}
|
|
258
|
+
</ScrollView>
|
|
259
|
+
{effectiveFooter}
|
|
260
|
+
</Pressable>
|
|
261
|
+
</Pressable>
|
|
262
|
+
</Modal>
|
|
263
|
+
)
|
|
264
|
+
}
|
|
170
265
|
|
|
171
266
|
const useScroll = scrollable || !!maxHeight
|
|
172
267
|
const effectiveMaxHeight = maxHeight ?? DEFAULT_MAX_HEIGHT
|
|
@@ -182,7 +277,7 @@ export function Sheet({
|
|
|
182
277
|
maxDynamicContentSize={useDynamicSizing ? effectiveMaxHeight : undefined}
|
|
183
278
|
onDismiss={onClose}
|
|
184
279
|
backdropComponent={renderBackdrop}
|
|
185
|
-
footerComponent={
|
|
280
|
+
footerComponent={effectiveFooter ? renderFooter : undefined}
|
|
186
281
|
backgroundStyle={[styles.background, { backgroundColor: colors.card }]}
|
|
187
282
|
handleIndicatorStyle={[styles.handle, { backgroundColor: colors.border }]}
|
|
188
283
|
enablePanDownToClose
|
|
@@ -204,18 +299,22 @@ export function Sheet({
|
|
|
204
299
|
persistentScrollbar={isAndroid}
|
|
205
300
|
>
|
|
206
301
|
{headerNode}
|
|
207
|
-
{
|
|
302
|
+
{contentNode}
|
|
208
303
|
</BottomSheetScrollView>
|
|
209
304
|
) : (
|
|
210
305
|
<BottomSheetView style={[styles.content, contentStyle, style]}>
|
|
211
306
|
{headerNode}
|
|
212
|
-
{
|
|
307
|
+
{contentNode}
|
|
213
308
|
</BottomSheetView>
|
|
214
309
|
)}
|
|
215
310
|
</BottomSheetModal>
|
|
216
311
|
)
|
|
217
312
|
}
|
|
218
313
|
|
|
314
|
+
Sheet.Header = SheetHeader
|
|
315
|
+
Sheet.Content = SheetContent
|
|
316
|
+
Sheet.Footer = SheetFooter
|
|
317
|
+
|
|
219
318
|
const styles = StyleSheet.create({
|
|
220
319
|
background: {
|
|
221
320
|
borderTopLeftRadius: ms(16),
|
|
@@ -238,12 +337,12 @@ const styles = StyleSheet.create({
|
|
|
238
337
|
justifyContent: 'space-between',
|
|
239
338
|
},
|
|
240
339
|
title: {
|
|
241
|
-
fontFamily: '
|
|
340
|
+
fontFamily: 'Sohne-SemiBold',
|
|
242
341
|
fontSize: ms(18),
|
|
243
342
|
flex: 1,
|
|
244
343
|
},
|
|
245
344
|
subtitle: {
|
|
246
|
-
fontFamily: '
|
|
345
|
+
fontFamily: 'Sohne-Regular',
|
|
247
346
|
fontSize: ms(14),
|
|
248
347
|
lineHeight: mvs(20),
|
|
249
348
|
},
|
|
@@ -260,4 +359,33 @@ const styles = StyleSheet.create({
|
|
|
260
359
|
paddingBottom: vs(32),
|
|
261
360
|
paddingRight: s(16),
|
|
262
361
|
},
|
|
362
|
+
sheetContent: {
|
|
363
|
+
gap: vs(16),
|
|
364
|
+
},
|
|
365
|
+
sheetFooter: {
|
|
366
|
+
paddingHorizontal: s(16),
|
|
367
|
+
paddingVertical: vs(16),
|
|
368
|
+
borderTopWidth: 1,
|
|
369
|
+
flexDirection: 'row',
|
|
370
|
+
gap: s(12),
|
|
371
|
+
},
|
|
372
|
+
dialogBackdrop: {
|
|
373
|
+
flex: 1,
|
|
374
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
375
|
+
alignItems: 'center',
|
|
376
|
+
justifyContent: 'center',
|
|
377
|
+
padding: s(24),
|
|
378
|
+
},
|
|
379
|
+
dialogCard: {
|
|
380
|
+
width: '100%',
|
|
381
|
+
borderRadius: RADIUS.lg,
|
|
382
|
+
paddingTop: vs(16),
|
|
383
|
+
overflow: 'hidden',
|
|
384
|
+
...SHADOWS.xl,
|
|
385
|
+
},
|
|
386
|
+
dialogContent: {
|
|
387
|
+
paddingHorizontal: s(16),
|
|
388
|
+
paddingBottom: vs(16),
|
|
389
|
+
},
|
|
263
390
|
})
|
|
391
|
+
|
|
@@ -9,8 +9,9 @@ import Animated, {
|
|
|
9
9
|
} from 'react-native-reanimated'
|
|
10
10
|
import { LinearGradient } from 'expo-linear-gradient'
|
|
11
11
|
import { useTheme } from '../../theme'
|
|
12
|
-
import { s } from '../../utils/scaling'
|
|
12
|
+
import { s, vs } from '../../utils/scaling'
|
|
13
13
|
import { TIMINGS } from '../../utils/animations'
|
|
14
|
+
import { RADIUS } from '../../tokens'
|
|
14
15
|
|
|
15
16
|
// circle: circular avatar placeholder text: short line preset base: custom dimensions
|
|
16
17
|
export type SkeletonPreset = 'base' | 'circle' | 'text'
|
|
@@ -74,7 +75,7 @@ export function Skeleton({
|
|
|
74
75
|
<View
|
|
75
76
|
style={[
|
|
76
77
|
styles.base,
|
|
77
|
-
{ width: resolvedWidth as
|
|
78
|
+
{ width: resolvedWidth as number | `${number}%`, height: resolvedHeight, borderRadius: resolvedRadius, backgroundColor: colors.surface },
|
|
78
79
|
style,
|
|
79
80
|
]}
|
|
80
81
|
onLayout={(e) => setContainerWidth(e.nativeEvent.layout.width)}
|
|
@@ -94,8 +95,148 @@ export function Skeleton({
|
|
|
94
95
|
)
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
// ─── Per-component skeletons ───────────────────────────────────────────────────
|
|
99
|
+
// Loading placeholders that mirror a component's footprint, so grids/lists don't
|
|
100
|
+
// reflow when real data arrives.
|
|
101
|
+
|
|
102
|
+
const aspectRatioMap = {
|
|
103
|
+
'1:1': 1,
|
|
104
|
+
'4:3': 3 / 4,
|
|
105
|
+
'16:9': 9 / 16,
|
|
106
|
+
'4:5': 5 / 4,
|
|
107
|
+
'3:2': 2 / 3,
|
|
108
|
+
} as const
|
|
109
|
+
|
|
110
|
+
export type MediaCardSkeletonAspectRatio = keyof typeof aspectRatioMap
|
|
111
|
+
|
|
112
|
+
export interface MediaCardSkeletonProps {
|
|
113
|
+
/** Image aspect ratio — match your `MediaCard`. Defaults to `'4:3'`. */
|
|
114
|
+
aspectRatio?: MediaCardSkeletonAspectRatio
|
|
115
|
+
/** Show the subtitle/caption line below the title. Defaults to true. */
|
|
116
|
+
showSubtitle?: boolean
|
|
117
|
+
style?: ViewStyle
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Loading placeholder matching `<MediaCard>` — image block + title/subtitle lines. */
|
|
121
|
+
export function MediaCardSkeleton({ aspectRatio = '4:3', showSubtitle = true, style }: MediaCardSkeletonProps) {
|
|
122
|
+
const ratio = aspectRatioMap[aspectRatio]
|
|
123
|
+
return (
|
|
124
|
+
<View style={style}>
|
|
125
|
+
<View style={{ paddingTop: `${ratio * 100}%` as `${number}%` }}>
|
|
126
|
+
<View style={StyleSheet.absoluteFill}>
|
|
127
|
+
<Skeleton width="100%" height={undefined as unknown as number} style={skeletonStyles.fill} borderRadius={RADIUS.md} />
|
|
128
|
+
</View>
|
|
129
|
+
</View>
|
|
130
|
+
<View style={skeletonStyles.meta}>
|
|
131
|
+
<Skeleton width="70%" height={vs(14)} borderRadius={RADIUS.xs} />
|
|
132
|
+
{showSubtitle ? <Skeleton width="45%" height={vs(12)} borderRadius={RADIUS.xs} /> : null}
|
|
133
|
+
</View>
|
|
134
|
+
</View>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface ListItemSkeletonProps {
|
|
139
|
+
/** Render a circular leading avatar placeholder. Defaults to true. */
|
|
140
|
+
showAvatar?: boolean
|
|
141
|
+
/** Render a secondary subtitle line. Defaults to true. */
|
|
142
|
+
showSubtitle?: boolean
|
|
143
|
+
style?: ViewStyle
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Loading placeholder matching `<ListItem>` — leading circle + title/subtitle lines. */
|
|
147
|
+
export function ListItemSkeleton({ showAvatar = true, showSubtitle = true, style }: ListItemSkeletonProps) {
|
|
148
|
+
return (
|
|
149
|
+
<View style={[skeletonStyles.row, style]}>
|
|
150
|
+
{showAvatar ? <Skeleton preset="circle" diameter={40} /> : null}
|
|
151
|
+
<View style={skeletonStyles.rowText}>
|
|
152
|
+
<Skeleton width="60%" height={vs(14)} borderRadius={RADIUS.xs} />
|
|
153
|
+
{showSubtitle ? <Skeleton width="40%" height={vs(12)} borderRadius={RADIUS.xs} /> : null}
|
|
154
|
+
</View>
|
|
155
|
+
</View>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface ListSkeletonProps {
|
|
160
|
+
/** Number of placeholder rows/cells. Defaults to 6. */
|
|
161
|
+
count?: number
|
|
162
|
+
/** 1 = stacked list of `ListItemSkeleton`; >1 = grid of `MediaCardSkeleton`. Defaults to 1. */
|
|
163
|
+
columns?: number
|
|
164
|
+
/** Gap between items (dp). Defaults to 12. */
|
|
165
|
+
gap?: number
|
|
166
|
+
/** Grid only — aspect ratio of each `MediaCardSkeleton`. Defaults to `'4:3'`. */
|
|
167
|
+
aspectRatio?: MediaCardSkeletonAspectRatio
|
|
168
|
+
/** List only — show the leading avatar circle. Defaults to true. */
|
|
169
|
+
showAvatar?: boolean
|
|
170
|
+
style?: ViewStyle
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Repeated loading placeholder for a `VirtualList` / list / grid. `columns={1}`
|
|
175
|
+
* renders stacked `ListItemSkeleton`s; `columns>1` renders a wrapping grid of
|
|
176
|
+
* `MediaCardSkeleton`s. Render this as the list's content while `data` is empty.
|
|
177
|
+
*/
|
|
178
|
+
export function ListSkeleton({
|
|
179
|
+
count = 6,
|
|
180
|
+
columns = 1,
|
|
181
|
+
gap = 12,
|
|
182
|
+
aspectRatio = '4:3',
|
|
183
|
+
showAvatar = true,
|
|
184
|
+
style,
|
|
185
|
+
}: ListSkeletonProps) {
|
|
186
|
+
if (columns <= 1) {
|
|
187
|
+
return (
|
|
188
|
+
<View style={[{ gap: vs(gap) }, style]}>
|
|
189
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
190
|
+
<ListItemSkeleton key={i} showAvatar={showAvatar} />
|
|
191
|
+
))}
|
|
192
|
+
</View>
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
const widthPct = `${100 / columns}%` as `${number}%`
|
|
196
|
+
// Gutter via per-cell padding + marginBottom (not container `gap`) so percentage
|
|
197
|
+
// widths sum to exactly 100% and never wrap one short.
|
|
198
|
+
return (
|
|
199
|
+
<View style={[skeletonStyles.grid, { marginHorizontal: -s(gap) / 2 }, style]}>
|
|
200
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
201
|
+
<View key={i} style={{ width: widthPct, paddingHorizontal: s(gap) / 2, marginBottom: vs(gap) }}>
|
|
202
|
+
<MediaCardSkeleton aspectRatio={aspectRatio} />
|
|
203
|
+
</View>
|
|
204
|
+
))}
|
|
205
|
+
</View>
|
|
206
|
+
)
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
Skeleton.MediaCard = MediaCardSkeleton
|
|
210
|
+
Skeleton.ListItem = ListItemSkeleton
|
|
211
|
+
Skeleton.List = ListSkeleton
|
|
212
|
+
|
|
97
213
|
const styles = StyleSheet.create({
|
|
98
214
|
base: {
|
|
99
215
|
overflow: 'hidden',
|
|
100
216
|
},
|
|
101
217
|
})
|
|
218
|
+
|
|
219
|
+
const skeletonStyles = StyleSheet.create({
|
|
220
|
+
grid: {
|
|
221
|
+
flexDirection: 'row',
|
|
222
|
+
flexWrap: 'wrap',
|
|
223
|
+
},
|
|
224
|
+
fill: {
|
|
225
|
+
width: '100%',
|
|
226
|
+
height: '100%',
|
|
227
|
+
},
|
|
228
|
+
meta: {
|
|
229
|
+
paddingTop: vs(8),
|
|
230
|
+
gap: vs(6),
|
|
231
|
+
},
|
|
232
|
+
row: {
|
|
233
|
+
flexDirection: 'row',
|
|
234
|
+
alignItems: 'center',
|
|
235
|
+
gap: s(12),
|
|
236
|
+
paddingVertical: vs(8),
|
|
237
|
+
},
|
|
238
|
+
rowText: {
|
|
239
|
+
flex: 1,
|
|
240
|
+
gap: vs(6),
|
|
241
|
+
},
|
|
242
|
+
})
|