u
This commit is contained in:
1 parent
ab3eaed7ce
commit
cf23acdabc
28 files changed
+1292
-486
No files matched your search
@@ -47,6 +47,7 @@ const AInput = forwardRef<React.ComponentRef<typeof InputField>, AInputProps>(
|
||||
isFocused={isFocused}
|
||||
className={className}
|
||||
style={style}
|
||||
|
||||
>
|
||||
{leftIcon && (
|
||||
<InputSlot className="pl-3">
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import type { ViewStyle } from 'react-native'
|
||||
import { useState } from 'react'
|
||||
import { Text } from 'react-native'
|
||||
import { Text, TouchableOpacity, View } from 'react-native'
|
||||
import {
|
||||
ButtonSpinner,
|
||||
} from '@/components/ui/button'
|
||||
import { Heading } from '@/components/ui/heading'
|
||||
import { CloseIcon, Icon } from '@/components/ui/icon'
|
||||
|
||||
import {
|
||||
Modal,
|
||||
ModalBackdrop,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
} from '@/components/ui/modal'
|
||||
import AButton from '../ao/Button'
|
||||
|
||||
interface AModalProps {
|
||||
isOpen?: boolean
|
||||
@@ -23,7 +24,6 @@ interface AModalProps {
|
||||
onConfirm?: () => Promise<void> | void
|
||||
onCancel?: () => void
|
||||
showBackdrop?: boolean
|
||||
showCloseIcon?: boolean
|
||||
|
||||
className?: string
|
||||
style?: ViewStyle
|
||||
@@ -37,7 +37,6 @@ export default function AModal({
|
||||
onClose,
|
||||
onConfirm,
|
||||
showBackdrop = true,
|
||||
showCloseIcon = false,
|
||||
|
||||
className,
|
||||
style,
|
||||
@@ -76,42 +75,58 @@ export default function AModal({
|
||||
style={{
|
||||
...style,
|
||||
width: '80%',
|
||||
padding: 0,
|
||||
}}
|
||||
>
|
||||
<ModalHeader>
|
||||
{title && (typeof title === 'string' ? <Heading>{title}</Heading> : title)}
|
||||
{
|
||||
showCloseIcon && (
|
||||
<ModalCloseButton>
|
||||
<Icon as={CloseIcon} />
|
||||
</ModalCloseButton>
|
||||
)
|
||||
}
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
{body && (typeof body === 'string' ? <Text>{body}</Text> : body)}
|
||||
</ModalBody>
|
||||
{title && (
|
||||
<ModalHeader>
|
||||
{typeof body === 'string'
|
||||
? (
|
||||
<View className="items-center justify-center w-full py-2">
|
||||
<Heading>{title}</Heading>
|
||||
</View>
|
||||
)
|
||||
: (
|
||||
title
|
||||
)}
|
||||
</ModalHeader>
|
||||
)}
|
||||
|
||||
{body && (
|
||||
<ModalBody>
|
||||
{typeof body === 'string'
|
||||
? (
|
||||
<View className="items-center justify-center w-full">
|
||||
<Text>{body}</Text>
|
||||
</View>
|
||||
)
|
||||
: (
|
||||
body
|
||||
)}
|
||||
</ModalBody>
|
||||
)}
|
||||
<ModalFooter>
|
||||
{
|
||||
footer || (
|
||||
<>
|
||||
<AButton
|
||||
variant="outline"
|
||||
action="secondary"
|
||||
className="mr-3"
|
||||
<View className="flex-row border-t border-gray-300">
|
||||
<TouchableOpacity
|
||||
className="flex-1 items-center justify-center py-3 border-r border-gray-300"
|
||||
onPress={onClose}
|
||||
size="sm"
|
||||
>
|
||||
取消
|
||||
</AButton>
|
||||
<AButton
|
||||
<Text className="text-gray-500">取消</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
className="flex-1 flex-row gap-2 items-center justify-center py-3"
|
||||
onPress={handleConfirm}
|
||||
size="sm"
|
||||
loading={innerLoading}
|
||||
>
|
||||
确认
|
||||
</AButton>
|
||||
</>
|
||||
{
|
||||
innerLoading && (
|
||||
<ButtonSpinner color="#333" />
|
||||
)
|
||||
}
|
||||
<Text className="text-[#333]">确认</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
</ModalFooter>
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
import { useEffect } from 'react'
|
||||
import { Pressable, StyleSheet } from 'react-native'
|
||||
import Animated, {
|
||||
interpolateColor,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from 'react-native-reanimated'
|
||||
|
||||
interface SwitchProps {
|
||||
value: any
|
||||
onChange: (val: any) => void
|
||||
trueValue?: any
|
||||
falseValue?: any
|
||||
width?: number
|
||||
height?: number
|
||||
activeColor?: string
|
||||
inactiveColor?: string
|
||||
duration?: number
|
||||
disabled?: boolean // 新增 disabled
|
||||
}
|
||||
|
||||
export default function Switch({
|
||||
value,
|
||||
onChange,
|
||||
trueValue,
|
||||
falseValue,
|
||||
width = 52,
|
||||
height = 24,
|
||||
activeColor = '#333',
|
||||
inactiveColor = '#e5e7eb',
|
||||
duration = 200,
|
||||
disabled = false, // 默认可用
|
||||
}: SwitchProps) {
|
||||
// 默认值 fallback
|
||||
const trueVal = trueValue ?? true
|
||||
const falseVal = falseValue ?? false
|
||||
|
||||
// 当前状态是否为激活
|
||||
const isActive = Object.is(value, trueVal)
|
||||
|
||||
// 控制动画的 sharedValue
|
||||
const progress = useSharedValue(isActive ? 1 : 0)
|
||||
|
||||
useEffect(() => {
|
||||
progress.value = withTiming(isActive ? 1 : 0, { duration })
|
||||
}, [isActive, duration])
|
||||
|
||||
const knobSize = height - 4
|
||||
const translateX = width - knobSize - 4
|
||||
|
||||
// 动画轨道样式
|
||||
const trackStyle = useAnimatedStyle(() => {
|
||||
const baseColor = interpolateColor(
|
||||
progress.value,
|
||||
[0, 1],
|
||||
[inactiveColor, activeColor],
|
||||
)
|
||||
return {
|
||||
backgroundColor: baseColor,
|
||||
opacity: disabled ? 0.7 : 1, // disabled 半透明
|
||||
}
|
||||
})
|
||||
|
||||
// 动画滑块样式
|
||||
const knobStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
transform: [
|
||||
{
|
||||
translateX: progress.value * translateX,
|
||||
},
|
||||
],
|
||||
opacity: disabled ? 0.8 : 1, // knob 半透明
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
if (disabled)
|
||||
return
|
||||
const nextValue = isActive ? falseVal : trueVal
|
||||
onChange(nextValue)
|
||||
}}
|
||||
style={{ width, height }}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.track,
|
||||
{
|
||||
width,
|
||||
height,
|
||||
borderRadius: height / 2,
|
||||
},
|
||||
trackStyle,
|
||||
]}
|
||||
>
|
||||
<Animated.View
|
||||
style={[
|
||||
styles.knob,
|
||||
{
|
||||
width: knobSize,
|
||||
height: knobSize,
|
||||
borderRadius: knobSize / 2,
|
||||
},
|
||||
knobStyle,
|
||||
]}
|
||||
/>
|
||||
</Animated.View>
|
||||
</Pressable>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
track: {
|
||||
justifyContent: 'center',
|
||||
padding: 2,
|
||||
},
|
||||
knob: {
|
||||
backgroundColor: '#fff',
|
||||
elevation: 2,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.2,
|
||||
shadowRadius: 2,
|
||||
shadowOffset: { width: 0, height: 1 },
|
||||
},
|
||||
})
|
||||
@@ -15,6 +15,7 @@ import ListItem from './ListItem'
|
||||
import LucideIcon from './LucideIcon'
|
||||
import Modal from './Modal'
|
||||
import SegmentedTabs from './SegmentedTabs'
|
||||
import Switch from './Switch'
|
||||
import Tab from './Tab'
|
||||
|
||||
export {
|
||||
@@ -35,6 +36,7 @@ export {
|
||||
ListItem as AListItem,
|
||||
Modal as AModal,
|
||||
SegmentedTabs as ASegmentedTabs,
|
||||
Switch as ASwitch,
|
||||
Tab as ATab,
|
||||
LucideIcon,
|
||||
}
|
||||
Reference in new issue
Block a user