fix: improve VideoSocialButton styling

- Use vertical layout with icon above count (TikTok style)
- Show correct liked/favorited state with filled icons
- Add count formatting (1.5k, 2.8w)
- Improve button background and positioning
- Update tests for new component structure
This commit is contained in:
imeepos 2026-01-28 20:09:59 +08:00
parent e6416ee604
commit 20459ffd1d
2 changed files with 141 additions and 58 deletions

View File

@ -90,6 +90,20 @@ describe('VideoSocialButton Component', () => {
)
expect(getByText('50')).toBeTruthy()
})
it('应该在已点赞状态显示实心心形图标', () => {
const { getByTestId } = render(
<VideoSocialButton templateId="test-1" liked={true} testID="video-social-liked" />
)
expect(getByTestId('video-social-liked-like-button')).toBeTruthy()
})
it('应该在已收藏状态显示实心星形图标', () => {
const { getByTestId } = render(
<VideoSocialButton templateId="test-1" favorited={true} testID="video-social-favorited" />
)
expect(getByTestId('video-social-favorited-favorite-button')).toBeTruthy()
})
})
describe('交互', () => {
@ -113,6 +127,26 @@ describe('VideoSocialButton Component', () => {
expect(onFavorite).toHaveBeenCalledTimes(1)
})
it('已点赞状态点击应该调用 onUnlike', () => {
const onUnlike = jest.fn()
const { getByTestId } = render(
<VideoSocialButton templateId="test-1" liked={true} onUnlike={onUnlike} testID="video-social-on-unlike" />
)
const likeButton = getByTestId('video-social-on-unlike-like-button')
fireEvent.press(likeButton)
expect(onUnlike).toHaveBeenCalledTimes(1)
})
it('已收藏状态点击应该调用 onUnfavorite', () => {
const onUnfavorite = jest.fn()
const { getByTestId } = render(
<VideoSocialButton templateId="test-1" favorited={true} onUnfavorite={onUnfavorite} testID="video-social-on-unfavorite" />
)
const favoriteButton = getByTestId('video-social-on-unfavorite-favorite-button')
fireEvent.press(favoriteButton)
expect(onUnfavorite).toHaveBeenCalledTimes(1)
})
it('loading 状态下应该禁用按钮', () => {
const onLike = jest.fn()
const onFavorite = jest.fn()
@ -136,6 +170,46 @@ describe('VideoSocialButton Component', () => {
})
})
describe('数量格式化', () => {
it('应该显示零数量', () => {
const { getByText } = render(
<VideoSocialButton templateId="test-1" likeCount={0} favoriteCount={0} />
)
expect(getByText('0')).toBeTruthy()
})
it('应该格式化千位数量', () => {
const { getByText } = render(
<VideoSocialButton templateId="test-1" likeCount={1500} favoriteCount={2800} />
)
expect(getByText('1.5k')).toBeTruthy()
expect(getByText('2.8k')).toBeTruthy()
})
it('应该格式化万位数量', () => {
const { getByText } = render(
<VideoSocialButton templateId="test-1" likeCount={15000} favoriteCount={28000} />
)
expect(getByText('1.5w')).toBeTruthy()
expect(getByText('2.8w')).toBeTruthy()
})
it('应该处理大数量', () => {
const { getByText } = render(
<VideoSocialButton templateId="test-1" likeCount={9999} favoriteCount={8888} />
)
expect(getByText('9999')).toBeTruthy()
expect(getByText('8888')).toBeTruthy()
})
it('应该处理 undefined 数量', () => {
const { getAllByText } = render(
<VideoSocialButton templateId="test-1" />
)
expect(getAllByText('0').length).toBe(2)
})
})
describe('样式', () => {
it('应该使用垂直布局', () => {
const { getByTestId } = render(
@ -155,21 +229,6 @@ describe('VideoSocialButton Component', () => {
})
describe('边界情况', () => {
it('应该处理零数量', () => {
const { getByText } = render(
<VideoSocialButton templateId="test-1" likeCount={0} favoriteCount={0} />
)
expect(getByText('0')).toBeTruthy()
})
it('应该处理大数量', () => {
const { getByText } = render(
<VideoSocialButton templateId="test-1" likeCount={9999} favoriteCount={8888} />
)
expect(getByText('9999')).toBeTruthy()
expect(getByText('8888')).toBeTruthy()
})
it('应该处理缺失的回调函数', () => {
const { getByTestId } = render(
<VideoSocialButton templateId="test-1" testID="video-social-no-callback" />

View File

@ -1,7 +1,6 @@
import React, { memo, useCallback, useMemo } from 'react'
import { View, StyleSheet } from 'react-native'
import { LikeButton, LikeButtonProps } from './LikeButton'
import { FavoriteButton, FavoriteButtonProps } from './FavoriteButton'
import React, { memo, useCallback } from 'react'
import { View, StyleSheet, Text, Pressable } from 'react-native'
import { Ionicons } from '@expo/vector-icons'
export interface VideoSocialButtonProps {
templateId: string
@ -52,41 +51,53 @@ const VideoSocialButtonComponent: React.FC<VideoSocialButtonProps> = ({
}
}, [loading, favorited, onFavorite, onUnfavorite])
// LikeButton 的 props
const likeButtonProps: LikeButtonProps = useMemo(
() => ({
liked,
loading,
count: likeCount,
onPress: handleLikePress,
testID: testID ? `${testID}-like-button` : undefined,
}),
[liked, loading, likeCount, handleLikePress, testID]
)
// FavoriteButton 的 props
const favoriteButtonProps: FavoriteButtonProps = useMemo(
() => ({
favorited,
loading,
count: favoriteCount,
onPress: handleFavoritePress,
testID: testID ? `${testID}-favorite-button` : undefined,
}),
[favorited, loading, favoriteCount, handleFavoritePress, testID]
)
// 格式化数量显示
const formatCount = (count?: number): string => {
if (count === undefined || count === null) return '0'
if (count >= 10000) {
return `${(count / 10000).toFixed(1)}w`
}
if (count >= 1000) {
return `${(count / 1000).toFixed(1)}k`
}
return count.toString()
}
return (
<View style={styles.container} testID={testID}>
{/* 点赞按钮 */}
<View style={styles.button}>
<LikeButton {...likeButtonProps} />
</View>
<Pressable
style={styles.buttonWrapper}
onPress={handleLikePress}
disabled={loading}
testID={testID ? `${testID}-like-button` : undefined}
>
<View style={[styles.iconContainer, liked && styles.iconContainerActive]}>
<Ionicons
name={liked ? 'heart' : 'heart-outline'}
size={28}
color={liked ? '#FF3B30' : '#FFFFFF'}
/>
</View>
<Text style={styles.count}>{formatCount(likeCount)}</Text>
</Pressable>
{/* 收藏按钮 */}
<View style={styles.button}>
<FavoriteButton {...favoriteButtonProps} />
</View>
<Pressable
style={styles.buttonWrapper}
onPress={handleFavoritePress}
disabled={loading}
testID={testID ? `${testID}-favorite-button` : undefined}
>
<View style={[styles.iconContainer, favorited && styles.iconContainerActive]}>
<Ionicons
name={favorited ? 'star' : 'star-outline'}
size={28}
color={favorited ? '#FFD700' : '#FFFFFF'}
/>
</View>
<Text style={styles.count}>{formatCount(favoriteCount)}</Text>
</Pressable>
</View>
)
}
@ -96,20 +107,33 @@ export const VideoSocialButton = memo(VideoSocialButtonComponent)
const styles = StyleSheet.create({
container: {
position: 'absolute',
right: 13,
bottom: 100,
right: 12,
bottom: 180,
flexDirection: 'column',
alignItems: 'center',
gap: 16,
gap: 20,
},
button: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: 'rgba(25, 27, 31, 0.8)',
buttonWrapper: {
alignItems: 'center',
gap: 4,
},
iconContainer: {
width: 52,
height: 52,
borderRadius: 26,
backgroundColor: 'rgba(0, 0, 0, 0.3)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 1,
borderColor: 'rgba(47, 49, 52, 1)',
},
iconContainerActive: {
backgroundColor: 'rgba(255, 255, 255, 0.15)',
},
count: {
color: '#FFFFFF',
fontSize: 12,
fontWeight: '600',
textShadowColor: 'rgba(0, 0, 0, 0.5)',
textShadowOffset: { width: 0, height: 1 },
textShadowRadius: 2,
},
})