d3ro-voice/packages/ui-native/src/components/WaveBars.tsx
yunchan8804 55eb62189a feat(mobile): Phase M-2 D3RO 디자인 시스템 모바일 적용
DS 컴포넌트 9개 (ScreenPanel, WaveBars, AppStatusBar, Header, FilterChip 신규),
5개 탭 + 로그인 화면 D3RO 인스트루먼트 미학 적용,
i18n 연결 (I18nProvider + 모바일 전용 키 80+개),
모노레포 workspaces에 apps/mobile 추가.
2026-04-13 17:32:31 +09:00

87 lines
2.2 KiB
TypeScript

// packages/ui-native/src/components/WaveBars.tsx
// RN WaveBars — animated audio level bars using core Animated API
import { useEffect, useRef } from 'react'
import { View, Animated, Easing, type ViewStyle, type StyleProp } from 'react-native'
import { d3roNativePalette } from '../theme'
export interface WaveBarsProps {
active?: boolean
barCount?: number
style?: StyleProp<ViewStyle>
}
const BAR_MIN = 8
const BAR_MAX = 32
const DURATIONS = [800, 1000, 900, 1200, 1100, 950, 1050, 1300, 850, 1150, 1000]
function WaveBar({ index, active }: { index: number; active: boolean }): React.ReactElement {
const height = useRef(new Animated.Value(BAR_MIN)).current
useEffect(() => {
if (active) {
const duration = DURATIONS[index % DURATIONS.length]
const animation = Animated.loop(
Animated.sequence([
Animated.timing(height, {
toValue: BAR_MAX,
duration,
easing: Easing.inOut(Easing.ease),
useNativeDriver: false,
delay: index * 60
}),
Animated.timing(height, {
toValue: BAR_MIN,
duration,
easing: Easing.inOut(Easing.ease),
useNativeDriver: false
})
])
)
animation.start()
return () => animation.stop()
} else {
Animated.timing(height, {
toValue: BAR_MIN,
duration: 300,
useNativeDriver: false
}).start()
}
}, [active, height, index])
return (
<Animated.View
style={{
width: 6,
height,
backgroundColor: d3roNativePalette.accent.amber,
borderRadius: 3
}}
/>
)
}
export function WaveBars({
active = false,
barCount = 11,
style
}: WaveBarsProps): React.ReactElement {
const containerStyle: ViewStyle = {
height: 128,
backgroundColor: d3roNativePalette.bg.inset,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 6,
borderBottomWidth: 1,
borderBottomColor: d3roNativePalette.border.subtle
}
return (
<View style={[containerStyle, style]}>
{Array.from({ length: barCount }).map((_, i) => (
<WaveBar key={i} index={i} active={active} />
))}
</View>
)
}