From 4798f13b780df488f355f30e59eacc9d14e2f128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=9D=B0?= Date: Wed, 10 Jan 2024 11:28:59 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=A4=9A=E4=B8=AA?= =?UTF-8?q?=E7=BB=84=E4=BB=B6=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .changeset/selfish-squids-prove.md | 5 + packages/react-native/src/accordion/index.tsx | 27 +- .../src/accordion/useAccordion.ts | 78 ++--- .../src/float-button/ActionButtonItem.tsx | 132 -------- .../react-native/src/float-button/Actions.tsx | 36 -- .../src/float-button/MainButton.tsx | 78 ----- .../src/float-button/PlusIcon.tsx | 14 + .../react-native/src/float-button/index.md | 121 ++++--- .../react-native/src/float-button/index.tsx | 320 +++++++++++++----- .../react-native/src/float-button/type.ts | 60 ---- packages/react-native/src/notice-bar/type.ts | 2 +- .../src/progress/CircleProgress.tsx | 29 +- .../src/progress/LineProgress.tsx | 74 ++-- packages/react-native/src/progress/index.md | 51 ++- packages/react-native/src/progress/type.ts | 15 +- .../src/progress/useCircleProgress.ts | 12 +- .../src/progress/useLineProgress.ts | 12 +- packages/react-native/src/slider/index.md | 29 +- packages/react-native/src/slider/index.tsx | 68 ++-- 19 files changed, 509 insertions(+), 654 deletions(-) create mode 100644 .changeset/selfish-squids-prove.md delete mode 100644 packages/react-native/src/float-button/ActionButtonItem.tsx delete mode 100644 packages/react-native/src/float-button/Actions.tsx delete mode 100644 packages/react-native/src/float-button/MainButton.tsx create mode 100644 packages/react-native/src/float-button/PlusIcon.tsx delete mode 100644 packages/react-native/src/float-button/type.ts diff --git a/.changeset/selfish-squids-prove.md b/.changeset/selfish-squids-prove.md new file mode 100644 index 0000000000..23da2321b6 --- /dev/null +++ b/.changeset/selfish-squids-prove.md @@ -0,0 +1,5 @@ +--- +'@td-design/react-native': minor +--- + +fix: 修复多个组件的bug diff --git a/packages/react-native/src/accordion/index.tsx b/packages/react-native/src/accordion/index.tsx index 5deb33d374..a72a5fa22d 100644 --- a/packages/react-native/src/accordion/index.tsx +++ b/packages/react-native/src/accordion/index.tsx @@ -14,8 +14,6 @@ import { Theme } from '../theme'; import { AccordionProps, Section } from './type'; import useAccordion from './useAccordion'; -const { ONE_PIXEL, px } = helpers; - const Accordion: FC = ({ sections = [], multiple = true, @@ -76,7 +74,7 @@ const AccordionItem: FC< }) => { const theme = useTheme(); - const { bodyStyle, iconStyle, progress, handleLayout, handlePress } = useAccordion({ + const { iconStyle, heightStyle, progress, contentRef, handlePress } = useAccordion({ multiple, currentIndex, index, @@ -106,7 +104,7 @@ const AccordionItem: FC< }, [content]); return ( - + - + )} - - + + {Content} - + ); diff --git a/packages/react-native/src/accordion/useAccordion.ts b/packages/react-native/src/accordion/useAccordion.ts index b55916d219..c647ce0551 100644 --- a/packages/react-native/src/accordion/useAccordion.ts +++ b/packages/react-native/src/accordion/useAccordion.ts @@ -1,9 +1,15 @@ import { useEffect } from 'react'; -import { LayoutChangeEvent } from 'react-native'; -import { interpolate, useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; -import { mix } from 'react-native-redash'; +import Animated, { + measure, + runOnUI, + useAnimatedRef, + useAnimatedStyle, + useDerivedValue, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; -import { useMemoizedFn, useSafeState } from '@td-design/rn-hooks'; +import { useMemoizedFn } from '@td-design/rn-hooks'; export default function useAccordion({ multiple, @@ -16,52 +22,46 @@ export default function useAccordion({ index: number; onPress: (index: number) => void; }) { - const progress = useSharedValue(0); - const [bodySectionHeight, setBodySectionHeight] = useSafeState(0); + const contentRef = useAnimatedRef(); + const heightValue = useSharedValue(0); + const open = useSharedValue(false); - const handleLayout = (e: LayoutChangeEvent) => { - setBodySectionHeight(Math.ceil(e.nativeEvent.layout.height)); - }; - - const bodyStyle = useAnimatedStyle(() => { - return { - height: interpolate(progress.value, [0, 1], [0, bodySectionHeight]), - }; - }); - - const iconStyle = useAnimatedStyle(() => { - return { - transform: [ - { - rotateZ: `${mix(progress.value, 0, Math.PI)}rad`, - }, - ], - }; - }); + const progress = useDerivedValue(() => (open.value ? withTiming(1) : withTiming(0))); + /** 如果 multiple=false,则非当前index的都要收起来 */ useEffect(() => { - if (currentIndex === undefined) return; - - if (!multiple) { - if (currentIndex !== index) { - progress.value = withTiming(0); - } else { - progress.value = withTiming(1); - } + if (!multiple && currentIndex !== index) { + heightValue.value = withTiming(0); + open.value = false; } - }, [multiple, currentIndex, index, onPress]); + }, [multiple, currentIndex, index]); + + const iconStyle = useAnimatedStyle(() => ({ + transform: [{ rotate: `${progress.value * -180}deg` }], + })); + + const heightStyle = useAnimatedStyle(() => ({ + height: heightValue.value, + })); const handlePress = () => { - progress.value = withTiming(progress.value === 0 ? 1 : 0); + if (heightValue.value === 0) { + runOnUI(() => { + 'worklet'; + heightValue.value = withTiming(measure(contentRef)?.height ?? 0); + })(); + } else { + heightValue.value = withTiming(0); + } + open.value = !open.value; onPress(index); }; return { - bodyStyle, + contentRef, iconStyle, - progress, - - handleLayout, + heightStyle, handlePress: useMemoizedFn(handlePress), + progress, }; } diff --git a/packages/react-native/src/float-button/ActionButtonItem.tsx b/packages/react-native/src/float-button/ActionButtonItem.tsx deleted file mode 100644 index 7b301460ea..0000000000 --- a/packages/react-native/src/float-button/ActionButtonItem.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { FC, memo } from 'react'; -import { StyleProp, StyleSheet, ViewStyle } from 'react-native'; -import Animated, { useAnimatedStyle } from 'react-native-reanimated'; -import { mix } from 'react-native-redash'; - -import { useTheme } from '@shopify/restyle'; - -import Box from '../box'; -import helpers from '../helpers'; -import Pressable from '../pressable'; -import Text from '../text'; -import { Theme } from '../theme'; -import { ActionButtonItemProps, TitleProps } from './type'; - -const { deviceWidth, ONE_PIXEL } = helpers; - -const justifyContentMap = { - center: 'center', - left: 'flex-start', - right: 'flex-end', -}; - -const ActionButtonItem: FC = props => { - const theme = useTheme(); - const { - progress, - title, - textStyle, - textContainerStyle, - spaceBetween = theme.spacing.x1, - size, - position = 'left', - spacing, - backgroundColor, - onPress, - activeOpacity, - icon, - } = props; - - const styles = StyleSheet.create({ - container: { - flexDirection: 'row', - justifyContent: justifyContentMap[position] as any, - alignItems: 'center', - }, - button: { - borderRadius: size!, - backgroundColor, - }, - }); - - const style = useAnimatedStyle(() => ({ - height: mix(progress!.value, 0, size! + spacing!), - opacity: mix(progress!.value, 0, 1), - })); - - return ( - - {position === 'right' && ( - - )} - <Pressable activeOpacity={activeOpacity} onPress={onPress}> - <Box justifyContent={'center'} alignItems={'center'} width={size} height={size} style={styles.button}> - {icon} - </Box> - </Pressable> - {position !== 'right' && ( - <Title - {...{ - title, - textStyle, - textContainerStyle, - spaceBetween, - size, - position, - }} - /> - )} - </Animated.View> - ); -}; - -const getPosition = (position: 'left' | 'center' | 'right', size: number, spaceBetween: number) => { - const style: StyleProp<ViewStyle> = {}; - if (position === 'right') { - style.right = size + spaceBetween; - } else if (position === 'center') { - style.left = deviceWidth / 2 + size / 3 + spaceBetween; - } else { - style.left = size + spaceBetween; - } - return style; -}; - -const Title: FC<TitleProps> = ({ title, textStyle, textContainerStyle, spaceBetween, size, position = 'left' }) => { - if (!title) return null; - - const renderTitle = () => { - if (React.isValidElement(title)) return title; - return ( - <Text variant={'p1'} color="text" style={textStyle} numberOfLines={1}> - {title} - </Text> - ); - }; - - return ( - <Box - position="absolute" - padding="x1" - borderRadius={'x1'} - borderWidth={ONE_PIXEL} - borderColor={'border'} - backgroundColor={'white'} - style={[getPosition(position, size!, spaceBetween), textContainerStyle]} - > - {renderTitle()} - </Box> - ); -}; -ActionButtonItem.displayName = 'ActionButtonItem'; - -export default memo(ActionButtonItem); diff --git a/packages/react-native/src/float-button/Actions.tsx b/packages/react-native/src/float-button/Actions.tsx deleted file mode 100644 index f1ff2bc15a..0000000000 --- a/packages/react-native/src/float-button/Actions.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { FC, memo } from 'react'; -import { StyleSheet } from 'react-native'; - -import Box from '../box'; -import ActionButtonItem from './ActionButtonItem'; -import { ActionsProps } from './type'; - -const Actions: FC<ActionsProps> = props => { - const { verticalOrientation, spacing, items } = props; - - const styles = StyleSheet.create({ - up: { - paddingBottom: spacing / 2, - }, - down: { - paddingTop: spacing / 2, - }, - }); - - return ( - <Box - alignSelf={'stretch'} - justifyContent={verticalOrientation === 'up' ? 'flex-end' : 'flex-start'} - zIndex={'99'} - style={verticalOrientation === 'up' ? styles.up : styles.down} - pointerEvents="box-none" - > - {items.map((item, index) => { - return <ActionButtonItem key={index} {...props} {...item} />; - })} - </Box> - ); -}; -Actions.displayName = 'Actions'; - -export default memo(Actions); diff --git a/packages/react-native/src/float-button/MainButton.tsx b/packages/react-native/src/float-button/MainButton.tsx deleted file mode 100644 index b190ea093b..0000000000 --- a/packages/react-native/src/float-button/MainButton.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { FC, memo } from 'react'; -import { StyleSheet } from 'react-native'; -import Animated, { useAnimatedStyle } from 'react-native-reanimated'; -import { mix, mixColor } from 'react-native-redash'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; - -import { useTheme } from '@shopify/restyle'; - -import Pressable from '../pressable'; -import SvgIcon from '../svg-icon'; -import { Theme } from '../theme'; -import { MainButtonProps } from './type'; - -const MainButton: FC<MainButtonProps> = ({ - size, - progress, - buttonColor, - btnOutRange, - onPress, - outRangeScale, - customIcon, - activeOpacity, - verticalOrientation, -}) => { - const theme = useTheme<Theme>(); - const insets = useSafeAreaInsets(); - - const wrapperStyle = useAnimatedStyle(() => { - const style = { - zIndex: 99, - width: size, - height: size, - borderRadius: size, - backgroundColor: mixColor(progress.value, buttonColor, btnOutRange || buttonColor), - }; - if (verticalOrientation === 'up') { - Object.assign(style, { - marginBottom: insets.bottom, - }); - } - - return style; - }); - - const styles = StyleSheet.create({ - button: { - width: size, - height: size, - borderRadius: size, - alignItems: 'center', - justifyContent: 'center', - }, - }); - - const style = useAnimatedStyle(() => ({ - transform: [ - { - scale: mix(progress.value, 1, outRangeScale), - }, - { - rotateZ: `${mix(progress.value, 0, 135)}deg`, - }, - ], - })); - - return ( - <Animated.View style={wrapperStyle}> - <Animated.View style={[styles.button, style]}> - <Pressable style={styles.button} activeOpacity={activeOpacity} onPress={onPress}> - {customIcon ? customIcon : <SvgIcon name="plus" color={theme.colors.white} size={24} />} - </Pressable> - </Animated.View> - </Animated.View> - ); -}; -MainButton.displayName = 'MainButton'; - -export default memo(MainButton); diff --git a/packages/react-native/src/float-button/PlusIcon.tsx b/packages/react-native/src/float-button/PlusIcon.tsx new file mode 100644 index 0000000000..d248268815 --- /dev/null +++ b/packages/react-native/src/float-button/PlusIcon.tsx @@ -0,0 +1,14 @@ +import React, { memo } from 'react'; +import { SvgXml } from 'react-native-svg'; + +const PlusIcon = () => { + const xml = ` + <svg xmlns="http://www.w3.org/2000/svg" width="42" height="42" viewBox="0 0 24 24"> + <path fill="#fff" d="M11 13H5v-2h6V5h2v6h6v2h-6v6h-2z"/> + </svg> + `; + + return <SvgXml xml={xml} />; +}; + +export default memo(PlusIcon); diff --git a/packages/react-native/src/float-button/index.md b/packages/react-native/src/float-button/index.md index a4f65fa3b0..1e56099033 100644 --- a/packages/react-native/src/float-button/index.md +++ b/packages/react-native/src/float-button/index.md @@ -12,15 +12,14 @@ group: ## 效果演示 -### 1. 悬浮按钮在页面右上角 +### 1. 默认效果 ```tsx | pure <FloatButton - verticalOrientation="down" items={[ - { backgroundColor: '#9b59b6', title: 'New Task', icon: <IconHome /> }, - { backgroundColor: '#3498db', title: 'Notifications', icon: <IconNotification /> }, - { backgroundColor: '#1abc9c', title: 'All Tasks', icon: <IconCreate /> }, + { icon: <IconHome />, label: 'New Task', onPress: handlePress1 }, + { icon: <IconNotification />, label: 'Notifications', onPress: handlePress2 }, + { icon: <IconCreate />, label: 'All Tasks', onPress: handlePress3 }, ]} /> ``` @@ -29,25 +28,21 @@ group: <figure> <img alt="floatButton-ios1.gif" - src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1608120362148553983.gif" + src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1704857363855209553.gif" style="width: 375px; margin-right: 10px; border: 1px solid #ddd;" /> </figure> </center> -### 2. 自定义样式 +### 2. 修改按钮位置 ```tsx | pure <FloatButton - buttonColor="rgba(231,76,60,1)" - btnOutRange="gold" - position="left" - verticalOrientation="up" - spacing={10} + position="topLeft" items={[ - { backgroundColor: '#9b59b6', title: 'New Task', icon: <IconHome /> }, - { backgroundColor: '#3498db', title: 'Notifications', icon: <IconNotification /> }, - { backgroundColor: '#1abc9c', title: 'All Tasks', icon: <IconCreate /> }, + { icon: <IconHome />, label: 'New Task', onPress: handlePress1 }, + { icon: <IconNotification />, label: 'Notifications', onPress: handlePress2 }, + { icon: <IconCreate />, label: 'All Tasks', onPress: handlePress3 }, ]} /> ``` @@ -56,25 +51,27 @@ group: <figure> <img alt="floatButton-ios2.gif" - src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1608120451387252788.gif" + src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1704857368486252185.gif" style="width: 375px; margin-right: 10px; border: 1px solid #ddd;" /> </figure> </center> -### 3. 自定义悬浮按钮 +### 3. 自定义主按钮 ```tsx | pure <FloatButton - buttonColor="gold" - btnOutRange="red" - position="right" - customIcon={<Icon name="user" color="red" size={25} />} items={[ - { backgroundColor: '#9b59b6', title: 'New Task', icon: <IconHome /> }, - { backgroundColor: '#3498db', title: 'Notifications', icon: <IconNotification /> }, - { backgroundColor: '#1abc9c', title: 'All Tasks', icon: <IconCreate /> }, + { icon: <IconHome />, label: 'New Task', onPress: handlePress1 }, + { icon: <IconNotification />, label: 'Notifications', onPress: handlePress2 }, + { icon: <IconCreate />, label: 'All Tasks', onPress: handlePress3 }, ]} + customActionButton={(_, onPress) => <Button title="添加商品" onPress={onPress} />} + actionButtonProps={{ + width: 80, + height: 40, + borderRadius: 10, + }} /> ``` @@ -82,7 +79,30 @@ group: <figure> <img alt="floatButton-ios4.gif" - src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1608120590706733843.gif" + src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1704857373801391849.gif" + style="width: 375px; margin-right: 10px; border: 1px solid #ddd;" + /> + </figure> +</center> + +### 4. 按钮可拖拽 + +```tsx | pure +<FloatButton + items={[ + { icon: <IconHome />, label: 'New Task', onPress: handlePress1 }, + { icon: <IconNotification />, label: 'Notifications', onPress: handlePress2 }, + { icon: <IconCreate />, label: 'All Tasks', onPress: handlePress3 }, + ]} + draggable +/> +``` + +<center> + <figure> + <img + alt="floatButton-ios4.gif" + src="https://td-dev-public.oss-cn-hangzhou.aliyuncs.com/maoyes-app/1704857378202055121.gif" style="width: 375px; margin-right: 10px; border: 1px solid #ddd;" /> </figure> @@ -92,28 +112,29 @@ group: ### FloatButton -| 属性 | 必填 | 说明 | 类型 | 默认值 | -| ------------------- | ------- | -------------------------- | ----------------------------- | ------- | -| items | `true` | 展开按钮组 | `ActionButtonItemProps[]` | | -| size | `false` | 主按钮的大小 | `number` | `40` | -| verticalOrientation | `false` | 展开方向 | `up` \| `down` | `up` | -| buttonColor | `false` | 按钮的颜色 | `string` | `black` | -| btnOutRange | `false` | 按钮点击之后的颜色 | `string` | `black` | -| outRangeScale | `false` | 动画过程中主按钮的缩放比例 | `number` | `1` | -| customIcon | `false` | 自定义主按钮的图标 | `ReactNode` | | -| position | `false` | 主按钮的位置 | `left` \| `center` \| `right` | `right` | -| spacing | `false` | 展开按钮之间的间距 | `number` | `8` | -| style | `false` | 整个容器的样式 | `ViewStyle` | | -| activeOpacity | `false` | 按下时的不透明度 | `number` | `0.6` | - -### ActionButtonItemProps - -| 属性 | 必填 | 说明 | 类型 | 默认值 | -| ------------------ | ------- | ------------------ | -------------- | ------ | -| icon | `true` | 按钮图标 | `ReactElement` | | -| backgroundColor | `true` | 按钮的颜色 | `string` | | -| onPress | `false` | 按钮的点击事件 | `() => void` | | -| textStyle | `false` | 按钮的文字样式 | `TextStyle` | | -| textContainerStyle | `false` | 按钮的文字容器样式 | `ViewStyle` | | -| title | `false` | 按钮的文字标题 | `string` | | -| spaceBetween | `false` | 按钮和图标的间距 | `number` | `4` | +| 属性 | 必填 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | --- | +| items | `true` | 操作项 | `FloatButtonItemProps[]` | | +| itemHeight | `false` | 操作项行高 | `number` | | +| position | `false` | 按钮位置 | `'topLeft' \| 'topRight' \| 'bottomLeft' \| 'bottomRight'` | | +| customActionButton | `false` | 自定义主按钮 | `(progress: SharedValue<number>, onPress: () => void) => ReactNode` | | +| containerStyle | `false` | 容器样式 | `StyleProp<ViewStyle>` | | +| draggable | `false` | 是否可以拖拽(实验属性,不建议是用) | `boolean` | | +| actionButtonProps | `false` | 主按钮样式属性 | `ActionButtonProps` | | + +### FloatButtonItemProps + +| 属性 | 必填 | 说明 | 类型 | 默认值 | +| ------- | ------- | -------------- | ----------------------------- | ------ | +| icon | `false` | 按钮图标 | `ReactNode` | | +| label | `true` | 按钮的文字标题 | `ReactNode` | | +| onPress | `true` | 点击事件 | `() => void \| Promise<void>` | | +| style | `false` | 样式 | `StyleProp<ViewStyle>` | | + +### ActionButtonProps + +| 属性 | 必填 | 说明 | 类型 | 默认值 | +| ------------ | ------- | -------------- | -------- | ------ | +| width | `false` | 主按钮宽度 | `number` | | +| height | `false` | 主按钮高度 | `number` | | +| borderRadius | `false` | 主按钮圆角大小 | `number` | | diff --git a/packages/react-native/src/float-button/index.tsx b/packages/react-native/src/float-button/index.tsx index 084e7eddbe..98b4b382e3 100644 --- a/packages/react-native/src/float-button/index.tsx +++ b/packages/react-native/src/float-button/index.tsx @@ -1,103 +1,245 @@ -import React, { FC } from 'react'; -import { StyleSheet } from 'react-native'; -import { useDerivedValue, useSharedValue, withSpring, withTiming } from 'react-native-reanimated'; +import React, { ReactNode, useMemo } from 'react'; +import { Image, Pressable, StyleProp, StyleSheet, ViewStyle } from 'react-native'; +import { Gesture, GestureDetector } from 'react-native-gesture-handler'; +import Animated, { + SharedValue, + useAnimatedStyle, + useDerivedValue, + useSharedValue, + withTiming, +} from 'react-native-reanimated'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { useTheme } from '@shopify/restyle'; -import { useMemoizedFn } from '@td-design/rn-hooks'; +import { Box, Flex, helpers, Text, Theme, useTheme } from '@td-design/react-native'; +import { useSafeState } from '@td-design/rn-hooks'; -import Box from '../box'; -import helpers from '../helpers'; -import { Theme } from '../theme'; -import Actions from './Actions'; -import MainButton from './MainButton'; -import { ActionButtonProps } from './type'; +import PlusIcon from './PlusIcon'; -const { px } = helpers; +interface FloatButtonItem { + icon?: ReactNode; + label: ReactNode; + onPress: () => void | Promise<void>; + style?: StyleProp<ViewStyle>; +} -const alignItemsMap = { - center: 'center', - left: 'flex-start', - right: 'flex-end', -}; +export interface FloatButtonProps { + items: FloatButtonItem[]; + itemHeight?: number; + position?: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; + customActionButton?: (progress: SharedValue<number>, onPress: () => void) => ReactNode; + containerStyle?: StyleProp<ViewStyle>; + draggable?: boolean; + actionButtonProps?: { + width?: number; + height?: number; + borderRadius?: number; + }; +} -const ActionButton: FC<ActionButtonProps> = props => { +export default function FloatButton({ + customActionButton, + items, + itemHeight = 60, + position = 'bottomRight', + containerStyle, + draggable = false, + actionButtonProps, +}: FloatButtonProps) { + const insets = useSafeAreaInsets(); const theme = useTheme<Theme>(); - const { - items, - position = 'right', - verticalOrientation = 'up', - style, - size = px(40), - spacing = theme.spacing.x2, - buttonColor = theme.colors.gray500, - btnOutRange = theme.colors.black, - outRangeScale = 1, - customIcon, - activeOpacity = 0.6, - } = props; - - const active = useSharedValue(false); - const progress = useDerivedValue(() => (active.value ? withSpring(1) : withTiming(0))); - - const handlePress = useMemoizedFn(() => { - active.value = !active.value; + const width = useSharedValue(actionButtonProps?.width || itemHeight); + const height = useSharedValue(actionButtonProps?.height || itemHeight); + const borderRadius = useSharedValue(actionButtonProps?.borderRadius || itemHeight); + + const isOpen = useSharedValue(false); + const [opened, setOpened] = useSafeState(false); + + const progress = useDerivedValue(() => (isOpen.value ? withTiming(1) : withTiming(0))); + const scale = useDerivedValue(() => (isOpen.value ? withTiming(0) : withTiming(1))); + + const handlePress = () => { + if (!isOpen.value) { + width.value = withTiming(200); + height.value = withTiming((items.length + 1) * itemHeight); + borderRadius.value = withTiming(12); + } else { + width.value = withTiming(actionButtonProps?.width || itemHeight); + height.value = withTiming(actionButtonProps?.height || itemHeight); + borderRadius.value = withTiming(actionButtonProps?.borderRadius || itemHeight); + } + isOpen.value = !isOpen.value; + setOpened(opened => !opened); + }; + + const iconStyle = { + width: itemHeight / 2, + height: itemHeight / 2, + }; + + const positionX = useSharedValue(0); + const positionY = useSharedValue(0); + const context = useSharedValue({ x: 0, y: 0 }); + + const panGesture = Gesture.Pan() + .enabled(draggable && !opened) + .onStart(() => { + context.value = { x: positionX.value, y: positionY.value }; + }) + .onUpdate(e => { + positionX.value = e.translationX + context.value.x; + positionY.value = e.translationY + context.value.y; + }) + .onEnd(() => { + 'worklet'; + // 滑动结束后,判断位置,自动滑到左边或右边 + // 同时需要根据position,如果一开始位置在左侧,那么滑动结束后,如果超过屏幕一半,自动滑到右边 + // 如果一开始位置在右侧,那么滑动结束后,如果超过屏幕一半,自动滑到左边 + if (position.includes('Left')) { + if (positionX.value > (helpers.deviceWidth - itemHeight) / 2) { + // 超过屏幕一半,自动滑到右边 + positionX.value = withTiming(helpers.deviceWidth - itemHeight - 32); + } else { + positionX.value = withTiming(0); + } + } else { + if (positionX.value < -(helpers.deviceWidth - itemHeight) / 2) { + // 超过屏幕一半,自动滑到左边 + positionX.value = withTiming(-helpers.deviceWidth + itemHeight + 32); + } else { + positionX.value = withTiming(0); + } + } + }); + + const animatedStyle = useAnimatedStyle(() => { + return { + width: width.value, + height: height.value, + borderRadius: borderRadius.value, + transform: [{ translateX: positionX.value }, { translateY: positionY.value }], + }; }); - const styles = StyleSheet.create({ - container: { - backgroundColor: 'transparent', - zIndex: 99, - padding: theme.spacing.x2, - justifyContent: verticalOrientation === 'up' ? 'flex-end' : 'flex-start', - alignItems: alignItemsMap[position] as any, - }, + /** 根据position确定绝对定位的初始位置 */ + const positionStyle = useMemo(() => { + switch (position) { + case 'topLeft': + return { top: 0, left: 0 }; + case 'topRight': + return { top: 0, right: 0 }; + case 'bottomLeft': + return { bottom: insets.bottom, left: 0 }; + case 'bottomRight': + return { bottom: insets.bottom, right: 0 }; + default: + return { top: 0, left: 0 }; + } + }, [position]); + + const mainButtonStyle = useAnimatedStyle(() => { + return { + opacity: scale.value, + transform: [{ scale: scale.value }], + }; + }); + const closeButtonStyle = useAnimatedStyle(() => { + return { + opacity: progress.value, + transform: [{ scale: progress.value }], + }; }); return ( - <Box pointerEvents="box-none" style={[StyleSheet.absoluteFill, styles.container, style]}> - {verticalOrientation === 'up' && ( - <Actions - {...{ - items, - verticalOrientation, - spacing, - progress, - size, - position, - activeOpacity, - }} - /> - )} - <MainButton - {...{ - progress, - size, - buttonColor, - btnOutRange, - outRangeScale, - customIcon, - activeOpacity, - verticalOrientation, - }} - onPress={handlePress} - /> - {verticalOrientation === 'down' && ( - <Actions - {...{ - items, - verticalOrientation, - spacing, - progress, - size, - position, - activeOpacity, - }} - /> - )} - </Box> + <GestureDetector gesture={panGesture}> + <Animated.View + style={[ + { backgroundColor: theme.colors.primary200 }, + styles.container, + positionStyle, + containerStyle, + animatedStyle, + ]} + > + {!opened ? ( + <Animated.View style={mainButtonStyle}> + <Pressable onPress={handlePress}> + {customActionButton ? ( + customActionButton(progress, handlePress) + ) : ( + <Animated.View + style={[ + styles.iconContainer, + { + width: itemHeight, + height: itemHeight, + borderRadius: itemHeight, + }, + ]} + > + <PlusIcon /> + </Animated.View> + )} + </Pressable> + </Animated.View> + ) : ( + <Animated.View style={[closeButtonStyle]}> + <Pressable onPress={handlePress}> + <Animated.View + style={[ + styles.iconContainer, + { + width: itemHeight, + height: itemHeight, + transform: [{ rotate: '-45deg' }], + }, + ]} + > + <PlusIcon /> + </Animated.View> + </Pressable> + </Animated.View> + )} + + <Box> + {items.map((item, index) => ( + <Pressable + key={index} + onPress={async () => { + await item.onPress(); + handlePress(); + }} + > + <Flex alignItems={'center'} height={itemHeight} paddingHorizontal={'x3'} style={item.style}> + <Box marginRight={'x2'}> + {typeof item.icon === 'string' ? <Image source={{ uri: item.icon }} style={iconStyle} /> : item.icon} + </Box> + <Box> + {typeof item.label === 'string' ? ( + <Text variant={'p1'} color="white"> + {item.label} + </Text> + ) : ( + item.label + )} + </Box> + </Flex> + </Pressable> + ))} + </Box> + </Animated.View> + </GestureDetector> ); -}; -ActionButton.displayName = 'ActionButton'; +} -export default ActionButton; +const styles = StyleSheet.create({ + container: { + position: 'absolute', + overflow: 'hidden', + margin: 16, + }, + iconContainer: { + justifyContent: 'center', + alignItems: 'center', + }, +}); diff --git a/packages/react-native/src/float-button/type.ts b/packages/react-native/src/float-button/type.ts deleted file mode 100644 index f45592e078..0000000000 --- a/packages/react-native/src/float-button/type.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { StyleProp, TextStyle, ViewStyle } from 'react-native'; -import type Animated from 'react-native-reanimated'; - -export type ActionButtonProps = { - items: ActionButtonItemProps[]; - /** 按钮大小 */ - size?: number; - /** 展开方向。up向上展开;down向下展开 */ - verticalOrientation?: 'up' | 'down'; - /** 整个容器的样式 */ - style?: StyleProp<ViewStyle>; - /** 主按钮的颜色 */ - buttonColor?: string; - /** 主按钮点击之后的颜色 */ - btnOutRange?: string; - /** 动画过程中主按钮的缩放比例 */ - outRangeScale?: number; - /** 自定义主按钮的图标 */ - customIcon?: React.ReactElement; - /** 主按钮的位置。left在屏幕水平方向左侧;center在屏幕水平方向中间;right在屏幕水平方向右侧 */ - position?: 'left' | 'center' | 'right'; - /** 展开按钮之间的间距 */ - spacing?: number; - /** 主按钮按下时的透明度 */ - activeOpacity?: number; -}; - -export type MainButtonProps = Required< - Pick<ActionButtonProps, 'size' | 'buttonColor' | 'outRangeScale' | 'activeOpacity' | 'verticalOrientation'> -> & - Pick<ActionButtonProps, 'btnOutRange' | 'customIcon'> & { - progress: Animated.SharedValue<number>; - onPress: () => void; - }; - -export type ActionsProps = Required< - Pick<ActionButtonProps, 'items' | 'position' | 'size' | 'spacing' | 'verticalOrientation' | 'activeOpacity'> -> & { - progress: Animated.SharedValue<number>; -}; - -export type ActionButtonItemProps = Partial<ActionsProps> & { - /** 展开按钮的背景色 */ - backgroundColor: string; - /** 展开按钮的图标 */ - icon: React.ReactElement; - /** 按钮的文字标题 */ - title?: string; - /** 按钮的点击事件 */ - onPress?: () => void; - /** 按钮的文字样式 */ - textStyle?: StyleProp<TextStyle>; - /** 按钮的文字容器样式 */ - textContainerStyle?: StyleProp<ViewStyle>; - /** 按钮和图标的间距 */ - spaceBetween?: number; -}; - -export type TitleProps = Required<Pick<ActionButtonItemProps, 'position' | 'spaceBetween'>> & - Pick<ActionButtonItemProps, 'title' | 'textStyle' | 'textContainerStyle' | 'size'>; diff --git a/packages/react-native/src/notice-bar/type.ts b/packages/react-native/src/notice-bar/type.ts index 8b09086342..2a6366f142 100644 --- a/packages/react-native/src/notice-bar/type.ts +++ b/packages/react-native/src/notice-bar/type.ts @@ -1,7 +1,7 @@ import { ReactNode } from 'react'; import { StyleProp, ViewStyle } from 'react-native'; -import { Theme } from 'src/theme'; +import { Theme } from '../theme'; export interface NoticeBarProps { /** 左侧自定义图标 */ diff --git a/packages/react-native/src/progress/CircleProgress.tsx b/packages/react-native/src/progress/CircleProgress.tsx index a48ee1a6a1..d4a4c770aa 100644 --- a/packages/react-native/src/progress/CircleProgress.tsx +++ b/packages/react-native/src/progress/CircleProgress.tsx @@ -22,23 +22,23 @@ const CircleProgress: FC<Omit<ProgressProps, 'labelPosition'>> = props => { width = px(150), color = theme.colors.primary200, bgColor = theme.colors.gray200, - strokeWidth = theme.spacing.x2, - innerWidth = theme.spacing.x2, + strokeWidth = px(8), value = 0, - showLabel = true, - showUnit = true, label, + showLabel = true, + labelStyle, + unit, } = props; const { radius, textLabel, circumference, animatedProps } = useCircleProgress({ width, strokeWidth, - showUnit, + unit, value, }); return ( - <Box width={width} height={width}> + <Box width={width} height={width} position={'relative'}> <Svg width={width} height={width}> <Defs> <LinearGradient id="grad" x1="0" y1="0" x2="1" y2="0"> @@ -52,7 +52,7 @@ const CircleProgress: FC<Omit<ProgressProps, 'labelPosition'>> = props => { cy={width / 2} r={radius} stroke={bgColor} - strokeWidth={innerWidth > strokeWidth ? strokeWidth : innerWidth} + strokeWidth={strokeWidth} strokeOpacity={1} fill="none" /> @@ -69,15 +69,17 @@ const CircleProgress: FC<Omit<ProgressProps, 'labelPosition'>> = props => { /> </G> </Svg> - {label ? ( + {showLabel ? ( <Box style={[ StyleSheet.absoluteFillObject, { - display: 'flex', + width: '100%', + height: '100%', justifyContent: 'center', alignItems: 'center', }, + labelStyle, ]} > {typeof label === 'string' ? ( @@ -87,14 +89,9 @@ const CircleProgress: FC<Omit<ProgressProps, 'labelPosition'>> = props => { ) : ( label )} - </Box> - ) : ( - showLabel && - value > 0 && ( <ReText text={textLabel} style={[ - StyleSheet.absoluteFillObject, { fontSize: px(14), color: typeof color === 'string' ? color : theme.colors.primary200, @@ -103,8 +100,8 @@ const CircleProgress: FC<Omit<ProgressProps, 'labelPosition'>> = props => { }, ]} /> - ) - )} + </Box> + ) : null} </Box> ); }; diff --git a/packages/react-native/src/progress/LineProgress.tsx b/packages/react-native/src/progress/LineProgress.tsx index 8626d468fc..860d48cd96 100644 --- a/packages/react-native/src/progress/LineProgress.tsx +++ b/packages/react-native/src/progress/LineProgress.tsx @@ -26,11 +26,12 @@ const LineProgress: FC<Omit<ProgressProps, 'innerWidth'>> = props => { value = 0, showLabel = true, labelPosition = 'right', - showUnit = true, + unit, label, + labelStyle, } = props; - const { animatedProps, textLabel } = useLineProgress({ width, strokeWidth, showUnit, value }); + const { animatedProps, textLabel } = useLineProgress({ width, strokeWidth, unit, value }); const SvgComp = ( <Svg width={width} height={strokeWidth}> @@ -63,48 +64,43 @@ const LineProgress: FC<Omit<ProgressProps, 'innerWidth'>> = props => { </Svg> ); - const LabelComp = value > 0 && ( - <ReText - text={textLabel} - style={[ - { - fontSize: px(14), - color: typeof color === 'string' ? color : theme.colors.primary200, - fontWeight: '500', - }, - ]} - /> - ); + const LabelComp = showLabel ? ( + <Box style={labelStyle}> + {typeof label === 'string' ? ( + <Text variant="p1" color="primary_text"> + {label} + </Text> + ) : ( + label + )} + <ReText + text={textLabel} + style={[ + { + fontSize: px(14), + color: typeof color === 'string' ? color : theme.colors.primary200, + fontWeight: '500', + textAlign: 'center', + }, + ]} + /> + </Box> + ) : null; - if (showLabel) { - if (labelPosition === 'top') { - return ( - <Box> - {LabelComp} - {SvgComp} - </Box> - ); - } + if (labelPosition === 'top') return ( - <Flex> + <Box width={width}> + {LabelComp} {SvgComp} - - {label ? ( - typeof label === 'string' ? ( - <Text variant="p1" color="primary_text"> - {label} - </Text> - ) : ( - label - ) - ) : ( - <Box marginLeft="x2">{LabelComp}</Box> - )} - </Flex> + </Box> ); - } - return SvgComp; + return ( + <Flex width={width}> + {SvgComp} + {LabelComp} + </Flex> + ); }; LineProgress.displayName = 'LineProgress'; diff --git a/packages/react-native/src/progress/index.md b/packages/react-native/src/progress/index.md index c3eb817042..4a2833bc09 100644 --- a/packages/react-native/src/progress/index.md +++ b/packages/react-native/src/progress/index.md @@ -158,30 +158,27 @@ group: ## API -### LineProgress 属性 - -| 属性 | 必填 | 说明 | 类型 | 默认值 | -| ------------- | ------- | -------------- | ------------------------------ | ------------------------- | -| width | `false` | 长度 | `number` | `250` | -| color | `false` | 颜色 | `string` \| `[string, string]` | `theme.colors.primary200` | -| bgColor | `false` | 背景色 | `string` | `theme.colors.gray200` | -| strokeWidth | `false` | 外环宽度 | `number` | `8` | -| value | `false` | 值 | `number` | `100` | -| showLabel | `false` | 是否显示值文本 | `boolean` | `true` | -| labelPosition | `false` | 值文本位置 | `right` \| `top` | `right` | -| label | `false` | 自定义标签 | `ReactNode` | | -| showUnit | `false` | 是否显示单位 | `boolean` | `true` | - -### CircleProgress 属性 - -| 属性 | 必填 | 说明 | 类型 | 默认值 | -| ----------- | ------- | -------------- | ------------------------------ | ------------------------- | -| width | `false` | 长度 | `number` | `150` | -| color | `false` | 颜色 | `string` \| `[string, string]` | `theme.colors.primary200` | -| bgColor | `false` | 背景色 | `string` | `theme.colors.gray200` | -| strokeWidth | `false` | 外环宽度 | `number` | `10` | -| innerWidth | `false` | 内环宽度 | `number` | `10` | -| value | `false` | 值 | `number` | `0` | -| showLabel | `false` | 是否显示值文本 | `boolean` | `true` | -| label | `false` | 自定义标签 | `ReactNode` | | -| showUnit | `false` | 是否显示单位 | `boolean` | `true` | +```tsx | pure +export interface ProgressProps { + /** 长度 */ + width?: number; + /** 颜色, 支持渐变 */ + color?: string | string[]; + /** 背景色 */ + bgColor?: string; + /** 宽度 */ + strokeWidth?: number; + /** 值 */ + value?: number; + /** 值文本位置 */ + labelPosition?: 'right' | 'top'; + /** 是否显示单位 */ + unit?: string; + /** 自定义文本 */ + label?: ReactNode; + /** 是否显示文本 */ + showLabel?: boolean; + /** 文本样式 */ + labelStyle?: StyleProp<ViewStyle>; +} +``` diff --git a/packages/react-native/src/progress/type.ts b/packages/react-native/src/progress/type.ts index 8e29b1c442..70ec9263a4 100644 --- a/packages/react-native/src/progress/type.ts +++ b/packages/react-native/src/progress/type.ts @@ -1,24 +1,25 @@ import { ReactNode } from 'react'; +import { StyleProp, ViewStyle } from 'react-native'; export interface ProgressProps { /** 长度 */ width?: number; - /** 颜色 */ + /** 颜色, 支持渐变 */ color?: string | string[]; /** 背景色 */ bgColor?: string; - /** 外环宽度 */ + /** 宽度 */ strokeWidth?: number; - /** 内环宽度 */ - innerWidth?: number; /** 值 */ value?: number; - /** 是否显示值文本 */ - showLabel?: boolean; /** 值文本位置 */ labelPosition?: 'right' | 'top'; /** 是否显示单位 */ - showUnit?: boolean; + unit?: string; /** 自定义文本 */ label?: ReactNode; + /** 是否显示文本 */ + showLabel?: boolean; + /** 文本样式 */ + labelStyle?: StyleProp<ViewStyle>; } diff --git a/packages/react-native/src/progress/useCircleProgress.ts b/packages/react-native/src/progress/useCircleProgress.ts index b04b3d1280..7313ffe8a8 100644 --- a/packages/react-native/src/progress/useCircleProgress.ts +++ b/packages/react-native/src/progress/useCircleProgress.ts @@ -7,18 +7,18 @@ export default function useCircleProgress({ width = 0, strokeWidth = 0, value = 0, - showUnit, -}: Pick<ProgressProps, 'width' | 'strokeWidth' | 'value' | 'showUnit'>) { + unit, +}: Pick<ProgressProps, 'width' | 'strokeWidth' | 'value' | 'unit'>) { const radius = (width - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; - const progress = useSharedValue(value); - const textLabel = useSharedValue(showUnit ? `${value}%` : `${value}`); + const progress = useSharedValue(0); + const textLabel = useSharedValue(''); useEffect(() => { progress.value = withTiming(value, { duration: 600 }); - textLabel.value = showUnit ? `${value}%` : `${value}`; - }, [value, showUnit]); + textLabel.value = unit ? `${value}${unit}` : `${value}`; + }, [value, unit]); const animatedProps = useAnimatedProps(() => ({ strokeDashoffset: circumference - (progress.value * circumference) / 100, diff --git a/packages/react-native/src/progress/useLineProgress.ts b/packages/react-native/src/progress/useLineProgress.ts index b1f9ace467..9f85da0064 100644 --- a/packages/react-native/src/progress/useLineProgress.ts +++ b/packages/react-native/src/progress/useLineProgress.ts @@ -7,15 +7,15 @@ export default function useLineProgress({ width = 0, strokeWidth = 0, value = 0, - showUnit, -}: Pick<ProgressProps, 'width' | 'strokeWidth' | 'value' | 'showUnit'>) { - const progress = useSharedValue((value * width) / 100 - strokeWidth / 2); - const textLabel = useSharedValue(showUnit ? `${value}%` : `${value}`); + unit, +}: Pick<ProgressProps, 'width' | 'strokeWidth' | 'value' | 'unit'>) { + const progress = useSharedValue(0); + const textLabel = useSharedValue(''); useEffect(() => { progress.value = withTiming((value * width) / 100 - strokeWidth / 2, { duration: 600 }); - textLabel.value = showUnit ? `${value}%` : `${value}`; - }, [value, width, strokeWidth, showUnit]); + textLabel.value = unit ? `${value}${unit}` : `${value}`; + }, [value, width, strokeWidth, unit]); const animatedProps = useAnimatedProps(() => ({ x2: progress.value, diff --git a/packages/react-native/src/slider/index.md b/packages/react-native/src/slider/index.md index 57498f8828..e3ad93f106 100644 --- a/packages/react-native/src/slider/index.md +++ b/packages/react-native/src/slider/index.md @@ -110,17 +110,18 @@ group: ## API -| 属性 | 必填 | 说明 | 类型 | 默认值 | -| ---------------- | ------- | ------------------ | -------------------------------------- | ------------------- | -| min | `false` | 最小值 | `number` | `0` | -| max | `false` | 最大值 | `number` | `100` | -| value | `false` | 当前值 | `number` | `0` | -| width | `false` | 宽度 | `number` | `deviceWidth - 100` | -| height | `false` | 高度 | `number` | `20` | -| onChange | `false` | 滑块拖动后触发事件 | `(value: number) => void` | | -| foregroundColor | `false` | 滑块左侧颜色 | `string` | `主题色` | -| backgroundColor | `false` | 滑块右侧颜色 | `string` | `#fff` | -| handleBackground | `false` | 滑块背景色 | `string` | `#fff` | -| showText | `false` | 是否显示滑块数字 | `boolean` | `true` | -| textPosition | `false` | 滑块数字显示位置 | `top` \| `left` \| `right` \| `bottom` | `top` | -| textStyle | `false` | 文本样式 | `TextStyle` | | +| 属性 | 必填 | 说明 | 类型 | 默认值 | +| ---------------- | ------- | ------------------ | -------------------------------------- | --------- | +| min | `false` | 最小值 | `number` | `0` | +| max | `false` | 最大值 | `number` | `100` | +| value | `false` | 当前值 | `number` | `0` | +| width | `false` | 宽度 | `number` | `px(250)` | +| labelWidth | `false` | 文本宽度 | `number` | `px(40)` | +| height | `false` | 高度 | `number` | `20` | +| onChange | `false` | 滑块拖动后触发事件 | `(value: number) => void` | | +| foregroundColor | `false` | 滑块左侧颜色 | `string` | `主题色` | +| backgroundColor | `false` | 滑块右侧颜色 | `string` | `#fff` | +| handleBackground | `false` | 滑块背景色 | `string` | `#fff` | +| showText | `false` | 是否显示滑块数字 | `boolean` | `true` | +| textPosition | `false` | 滑块数字显示位置 | `top` \| `left` \| `right` \| `bottom` | `top` | +| textStyle | `false` | 文本样式 | `TextStyle` | | diff --git a/packages/react-native/src/slider/index.tsx b/packages/react-native/src/slider/index.tsx index 893ee6a0ee..6676a6cacd 100644 --- a/packages/react-native/src/slider/index.tsx +++ b/packages/react-native/src/slider/index.tsx @@ -1,4 +1,4 @@ -import React, { FC, useMemo } from 'react'; +import React, { FC } from 'react'; import { StyleSheet, TextStyle } from 'react-native'; import { PanGestureHandler } from 'react-native-gesture-handler'; import Animated from 'react-native-reanimated'; @@ -12,7 +12,8 @@ import helpers from '../helpers'; import { Theme } from '../theme'; import useSlider from './useSlider'; -const { px, deviceWidth } = helpers; +const { px } = helpers; + export interface SliderProps { /** 最小值 */ min?: number; @@ -22,6 +23,8 @@ export interface SliderProps { value?: number; /** 宽度 */ width?: number; + /** 标签文本宽度 */ + labelWidth?: number; /** 高度 */ height?: number; /** 滑块拖动后触发事件 */ @@ -40,8 +43,6 @@ export interface SliderProps { textStyle?: TextStyle; } -const LABEL_WIDTH = px(100); -const SLIDER_WIDTH = deviceWidth - LABEL_WIDTH; const SLIDER_HEIGHT = px(20); const Slider: FC<SliderProps> = props => { @@ -51,7 +52,8 @@ const Slider: FC<SliderProps> = props => { max = 100, value = 0, onChange, - width = SLIDER_WIDTH, + width = px(250), + labelWidth = px(40), height = SLIDER_HEIGHT, backgroundColor = theme.colors.gray200, foregroundColor = theme.colors.primary200, @@ -62,7 +64,7 @@ const Slider: FC<SliderProps> = props => { } = props; const KNOB_WIDTH = height; const sliderRange = width - KNOB_WIDTH; - const oneStepValue = sliderRange / 100; + const oneStepValue = sliderRange / max; const { progressStyle, knobStyle, onGestureEvent, label } = useSlider({ min, @@ -75,7 +77,6 @@ const Slider: FC<SliderProps> = props => { const styles = StyleSheet.create({ progress: { - ...StyleSheet.absoluteFillObject, backgroundColor: foregroundColor, borderRadius: KNOB_WIDTH, }, @@ -93,63 +94,40 @@ const Slider: FC<SliderProps> = props => { borderRadius: KNOB_WIDTH, backgroundColor, }, - labelLeft: { - marginRight: KNOB_WIDTH / 2, - }, - labelRight: { - marginLeft: height + KNOB_WIDTH / 2, - }, }); - const SliderContent = useMemo( - () => ( - <Box width={width} height={KNOB_WIDTH} justifyContent={'center'} style={styles.content}> - <Animated.View style={[styles.progress, progressStyle]} /> - <PanGestureHandler onGestureEvent={onGestureEvent}> - <Animated.View style={[styles.knob, knobStyle]} /> - </PanGestureHandler> - </Box> - ), - [width, KNOB_WIDTH, progressStyle, onGestureEvent, knobStyle] + const SliderContent = ( + <Box width={width} height={KNOB_WIDTH} style={styles.content}> + <Animated.View style={[StyleSheet.absoluteFillObject, styles.progress, progressStyle]} /> + <PanGestureHandler onGestureEvent={onGestureEvent}> + <Animated.View style={[styles.knob, knobStyle]} /> + </PanGestureHandler> + </Box> ); - if (!showText) { - return SliderContent; - } + const Label = <ReText style={{ fontSize: px(14), color: theme.colors.gray500, ...textStyle }} text={label} />; - const Label = useMemo( - () => <ReText style={{ fontSize: px(14), color: theme.colors.gray500, ...textStyle }} text={label} />, - [label, textStyle] - ); + if (!showText) return SliderContent; - if (textPosition === 'top' || textPosition === 'bottom') { + if (textPosition === 'top' || textPosition === 'bottom') return ( <Box> - {textPosition === 'top' && ( - <Flex justifyContent="center" marginBottom="x1" width={width + KNOB_WIDTH - height / 2}> - {Label} - </Flex> - )} + {textPosition === 'top' && <Box marginBottom="x1">{Label}</Box>} {SliderContent} - {textPosition === 'bottom' && ( - <Flex justifyContent="center" marginTop="x1" width={width + KNOB_WIDTH - height / 2}> - {Label} - </Flex> - )} + {textPosition === 'bottom' && <Box marginTop="x1">{Label}</Box>} </Box> ); - } return ( - <Flex> + <Flex position={'relative'}> {textPosition === 'left' && ( - <Box alignItems={'flex-end'} style={styles.labelLeft}> + <Box width={labelWidth} alignItems={'flex-start'}> {Label} </Box> )} {SliderContent} {textPosition === 'right' && ( - <Box alignItems={'flex-end'} style={styles.labelRight}> + <Box width={labelWidth} alignItems={'flex-end'}> {Label} </Box> )}