Skip to content

Commit eeb2315

Browse files
refactor bar visualizer
1 parent 51ced70 commit eeb2315

File tree

2 files changed

+63
-37
lines changed

2 files changed

+63
-37
lines changed
Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,28 @@
1-
import { useMemo } from 'react';
1+
import { type ReactNode, useMemo } from 'react';
22
import { type VariantProps, cva } from 'class-variance-authority';
33
import { type LocalAudioTrack, type RemoteAudioTrack } from 'livekit-client';
44
import {
55
type AgentState,
66
type TrackReferenceOrPlaceholder,
77
useMultibandTrackVolume,
88
} from '@livekit/components-react';
9-
import { cn } from '@/lib/utils';
9+
import { cloneSingleChild, cn } from '@/lib/utils';
1010
import { useBarAnimator } from './hooks/useBarAnimator';
1111

12-
export const audioBarVisualizerVariants = cva(['relative flex items-center justify-center'], {
13-
variants: {
14-
size: {
15-
icon: 'h-[24px] gap-[2px]',
16-
sm: 'h-[56px] gap-[4px]',
17-
md: 'h-[112px] gap-[8px]',
18-
lg: 'h-[224px] gap-[16px]',
19-
xl: 'h-[448px] gap-[32px]',
20-
},
21-
},
22-
defaultVariants: {
23-
size: 'md',
24-
},
25-
});
26-
27-
export const audioBarVisualizerBarVariants = cva(
12+
export const audioBarVisualizerVariants = cva(
2813
[
29-
'rounded-full transition-colors duration-250 ease-linear bg-(--audio-visualizer-idle) data-[lk-highlighted=true]:bg-(--audio-visualizer-active)',
14+
'relative flex items-center justify-center',
15+
'[&_>_*]:rounded-full [&_>_*]:transition-colors [&_>_*]:duration-250 [&_>_*]:ease-linear',
16+
'[&_>_*]:bg-(--audio-visualizer-idle) [&_>_*]:data-[lk-highlighted=true]:bg-(--audio-visualizer-active)',
3017
],
3118
{
3219
variants: {
3320
size: {
34-
icon: 'w-[4px] min-h-[4px]',
35-
sm: 'w-[8px] min-h-[8px]',
36-
md: 'w-[16px] min-h-[16px]',
37-
lg: 'w-[32px] min-h-[32px]',
38-
xl: 'w-[64px] min-h-[64px]',
21+
icon: ['h-[24px] gap-[2px]', '[&_>_*]:w-[4px] [&_>_*]:min-h-[4px]'],
22+
sm: ['h-[56px] gap-[4px]', '[&_>_*]:w-[8px] [&_>_*]:min-h-[8px]'],
23+
md: ['h-[112px] gap-[8px]', '[&_>_*]:w-[16px] [&_>_*]:min-h-[16px]'],
24+
lg: ['h-[224px] gap-[16px]', '[&_>_*]:w-[32px] [&_>_*]:min-h-[32px]'],
25+
xl: ['h-[448px] gap-[32px]', '[&_>_*]:w-[64px] [&_>_*]:min-h-[64px]'],
3926
},
4027
},
4128
defaultVariants: {
@@ -49,7 +36,7 @@ interface AudioBarVisualizerProps {
4936
barCount?: number;
5037
audioTrack?: LocalAudioTrack | RemoteAudioTrack | TrackReferenceOrPlaceholder;
5138
className?: string;
52-
barClassName?: string;
39+
children?: ReactNode | ReactNode[];
5340
}
5441

5542
export function AudioBarVisualizer({
@@ -58,7 +45,7 @@ export function AudioBarVisualizer({
5845
barCount,
5946
audioTrack,
6047
className,
61-
barClassName,
48+
children,
6249
}: AudioBarVisualizerProps & VariantProps<typeof audioBarVisualizerVariants>) {
6350
const _barCount = useMemo(() => {
6451
if (barCount) {
@@ -95,19 +82,27 @@ export function AudioBarVisualizer({
9582
}, [state, _barCount]);
9683

9784
const highlightedIndices = useBarAnimator(state, _barCount, sequencerInterval);
98-
9985
const bands = audioTrack ? volumeBands : new Array(_barCount).fill(0);
86+
10087
return (
10188
<div className={cn(audioBarVisualizerVariants({ size }), className)}>
102-
{bands.map((band, idx) => (
103-
<div
104-
key={idx}
105-
data-lk-index={idx}
106-
data-lk-highlighted={highlightedIndices.includes(idx)}
107-
className={cn(audioBarVisualizerBarVariants({ size }), barClassName)}
108-
style={{ height: `${band * 100}%` }}
109-
/>
110-
))}
89+
{bands.map((band, idx) =>
90+
children ? (
91+
cloneSingleChild(children, {
92+
key: idx,
93+
'data-lk-index': idx,
94+
'data-lk-highlighted': highlightedIndices.includes(idx),
95+
style: { height: `${band * 100}%` },
96+
})
97+
) : (
98+
<div
99+
key={idx}
100+
data-lk-index={idx}
101+
data-lk-highlighted={highlightedIndices.includes(idx)}
102+
style={{ height: `${band * 100}%` }}
103+
/>
104+
)
105+
)}
111106
</div>
112107
);
113108
}

lib/utils.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { cache } from 'react';
1+
import {
2+
type CSSProperties,
3+
Children,
4+
type ReactNode,
5+
cache,
6+
cloneElement,
7+
isValidElement,
8+
} from 'react';
29
import { type ClassValue, clsx } from 'clsx';
310
import { twMerge } from 'tailwind-merge';
411
import { APP_CONFIG_DEFAULTS } from '@/app-config';
@@ -86,3 +93,27 @@ export function getStyles(appConfig: AppConfig) {
8693
.filter(Boolean)
8794
.join('\n');
8895
}
96+
97+
export function cloneSingleChild(
98+
children: ReactNode | ReactNode[],
99+
props?: Record<string, unknown>,
100+
key?: unknown
101+
) {
102+
return Children.map(children, (child) => {
103+
// Checking isValidElement is the safe way and avoids a typescript error too.
104+
if (isValidElement(child) && Children.only(children)) {
105+
const childProps = child.props as Record<string, unknown>;
106+
if (childProps.className) {
107+
// make sure we retain classnames of both passed props and child
108+
props ??= {};
109+
props.className = cn(childProps.className as string, props.className as string);
110+
props.style = {
111+
...(childProps.style as CSSProperties),
112+
...(props.style as CSSProperties),
113+
};
114+
}
115+
return cloneElement(child, { ...props, key: key ? String(key) : undefined });
116+
}
117+
return child;
118+
});
119+
}

0 commit comments

Comments
 (0)