Skip to content

Commit 51ced70

Browse files
add next-themes and configure visualizers
1 parent 90ae2d8 commit 51ced70

File tree

8 files changed

+84
-78
lines changed

8 files changed

+84
-78
lines changed

app-config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface AppConfig {
1313
accent?: string;
1414
logoDark?: string;
1515
accentDark?: string;
16-
audioVisualizer?: 'bar' | 'radial' | 'aura' | 'wave';
16+
audioVisualizer?: 'bar' | 'radial' | 'grid' | 'aura' | 'wave';
1717

1818
// for LiveKit Cloud Sandbox
1919
sandboxId?: string;

app/layout.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Public_Sans } from 'next/font/google';
22
import localFont from 'next/font/local';
33
import { headers } from 'next/headers';
4-
import { ApplyThemeScript, ThemeToggle } from '@/components/app/theme-toggle';
4+
import { ThemeProvider } from '@/components/app/theme-provider';
5+
import { ThemeToggle } from '@/components/app/theme-toggle';
56
import { cn, getAppConfig, getStyles } from '@/lib/utils';
67
import '@/styles/globals.css';
78

@@ -61,13 +62,19 @@ export default async function RootLayout({ children }: RootLayoutProps) {
6162
{styles && <style>{styles}</style>}
6263
<title>{pageTitle}</title>
6364
<meta name="description" content={pageDescription} />
64-
<ApplyThemeScript />
6565
</head>
6666
<body className="overflow-x-hidden">
67-
{children}
68-
<div className="group fixed bottom-0 left-1/2 z-50 mb-2 -translate-x-1/2">
69-
<ThemeToggle className="translate-y-20 transition-transform delay-150 duration-300 group-hover:translate-y-0" />
70-
</div>
67+
<ThemeProvider
68+
attribute="class"
69+
defaultTheme="system"
70+
enableSystem
71+
disableTransitionOnChange
72+
>
73+
{children}
74+
<div className="group fixed bottom-0 left-1/2 z-50 mb-2 -translate-x-1/2">
75+
<ThemeToggle className="translate-y-20 transition-transform delay-150 duration-300 group-hover:translate-y-0" />
76+
</div>
77+
</ThemeProvider>
7178
</body>
7279
</html>
7380
);

components/app/theme-provider.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import { ThemeProvider as NextThemesProvider } from 'next-themes';
5+
6+
export function ThemeProvider({
7+
children,
8+
...props
9+
}: React.ComponentProps<typeof NextThemesProvider>) {
10+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
11+
}

components/app/theme-toggle.tsx

Lines changed: 24 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,15 @@
11
'use client';
22

3-
import { useEffect, useState } from 'react';
3+
import { useTheme } from 'next-themes';
44
import { MonitorIcon, MoonIcon, SunIcon } from '@phosphor-icons/react';
5-
import { THEME_MEDIA_QUERY, THEME_STORAGE_KEY, cn } from '@/lib/utils';
6-
7-
const THEME_SCRIPT = `
8-
const doc = document.documentElement;
9-
const theme = localStorage.getItem("${THEME_STORAGE_KEY}") ?? "system";
10-
11-
if (theme === "system") {
12-
if (window.matchMedia("${THEME_MEDIA_QUERY}").matches) {
13-
doc.classList.add("dark");
14-
} else {
15-
doc.classList.add("light");
16-
}
17-
} else {
18-
doc.classList.add(theme);
19-
}
20-
`
21-
.trim()
22-
.replace(/\n/g, '')
23-
.replace(/\s+/g, ' ');
24-
25-
export type ThemeMode = 'dark' | 'light' | 'system';
26-
27-
function applyTheme(theme: ThemeMode) {
28-
const doc = document.documentElement;
29-
30-
doc.classList.remove('dark', 'light');
31-
localStorage.setItem(THEME_STORAGE_KEY, theme);
32-
33-
if (theme === 'system') {
34-
if (window.matchMedia(THEME_MEDIA_QUERY).matches) {
35-
doc.classList.add('dark');
36-
} else {
37-
doc.classList.add('light');
38-
}
39-
} else {
40-
doc.classList.add(theme);
41-
}
42-
}
5+
import { cn } from '@/lib/utils';
436

447
interface ThemeToggleProps {
458
className?: string;
469
}
4710

48-
export function ApplyThemeScript() {
49-
return <script id="theme-script">{THEME_SCRIPT}</script>;
50-
}
51-
5211
export function ThemeToggle({ className }: ThemeToggleProps) {
53-
const [theme, setTheme] = useState<ThemeMode | undefined>(undefined);
54-
55-
useEffect(() => {
56-
const storedTheme = (localStorage.getItem(THEME_STORAGE_KEY) as ThemeMode) ?? 'system';
57-
58-
setTheme(storedTheme);
59-
}, []);
60-
61-
function handleThemeChange(theme: ThemeMode) {
62-
applyTheme(theme);
63-
setTheme(theme);
64-
}
12+
const { theme, setTheme } = useTheme();
6513

6614
return (
6715
<div
@@ -71,29 +19,40 @@ export function ThemeToggle({ className }: ThemeToggleProps) {
7119
)}
7220
>
7321
<span className="sr-only">Color scheme toggle</span>
74-
<button
75-
type="button"
76-
onClick={() => handleThemeChange('dark')}
77-
className="cursor-pointer p-1 pl-1.5"
78-
>
22+
<button type="button" onClick={() => setTheme('dark')} className="cursor-pointer p-1 pl-1.5">
7923
<span className="sr-only">Enable dark color scheme</span>
80-
<MoonIcon size={16} weight="bold" className={cn(theme !== 'dark' && 'opacity-25')} />
24+
<MoonIcon
25+
suppressHydrationWarning
26+
size={16}
27+
weight="bold"
28+
className={cn(theme !== 'dark' && 'opacity-25')}
29+
/>
8130
</button>
8231
<button
8332
type="button"
84-
onClick={() => handleThemeChange('light')}
33+
onClick={() => setTheme('light')}
8534
className="cursor-pointer px-1.5 py-1"
8635
>
8736
<span className="sr-only">Enable light color scheme</span>
88-
<SunIcon size={16} weight="bold" className={cn(theme !== 'light' && 'opacity-25')} />
37+
<SunIcon
38+
suppressHydrationWarning
39+
size={16}
40+
weight="bold"
41+
className={cn(theme !== 'light' && 'opacity-25')}
42+
/>
8943
</button>
9044
<button
9145
type="button"
92-
onClick={() => handleThemeChange('system')}
46+
onClick={() => setTheme('system')}
9347
className="cursor-pointer p-1 pr-1.5"
9448
>
9549
<span className="sr-only">Enable system color scheme</span>
96-
<MonitorIcon size={16} weight="bold" className={cn(theme !== 'system' && 'opacity-25')} />
50+
<MonitorIcon
51+
suppressHydrationWarning
52+
size={16}
53+
weight="bold"
54+
className={cn(theme !== 'system' && 'opacity-25')}
55+
/>
9756
</button>
9857
</div>
9958
);

components/app/tile-layout.tsx

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React, { useMemo } from 'react';
2+
import { useTheme } from 'next-themes';
23
import { Track } from 'livekit-client';
34
import { AnimatePresence, motion } from 'motion/react';
45
import {
@@ -12,6 +13,7 @@ import { AppConfig } from '@/app-config';
1213
import { AudioBarVisualizer } from '@/components/livekit/audio-visualizer/audio-bar-visualizer/audio-bar-visualizer';
1314
import { AudioShaderVisualizer } from '@/components/livekit/audio-visualizer/audio-shader-visualizer/audio-shader-visualizer';
1415
import { cn } from '@/lib/utils';
16+
import { AudioGridVisualizer } from '../livekit/audio-visualizer/audio-grid-visualizer/audio-grid-visualizer';
1517
import { AudioOscilloscopeVisualizer } from '../livekit/audio-visualizer/audio-oscilloscope-visualizer/audio-oscilloscope-visualizer';
1618
import { AudioRadialVisualizer } from '../livekit/audio-visualizer/audio-radial-visualizer/audio-radial-visualizer';
1719

@@ -79,6 +81,7 @@ interface TileLayoutProps {
7981
}
8082

8183
export function TileLayout({ chatOpen, appConfig }: TileLayoutProps) {
84+
const { theme } = useTheme();
8285
const {
8386
state: agentState,
8487
audioTrack: agentAudioTrack,
@@ -145,24 +148,53 @@ export function TileLayout({ chatOpen, appConfig }: TileLayoutProps) {
145148
{appConfig.audioVisualizer === 'radial' && (
146149
<AudioRadialVisualizer
147150
size="sm"
151+
barCount={12}
148152
state={agentState}
149153
audioTrack={agentAudioTrack!}
150154
className="mx-auto"
151155
/>
152156
)}
157+
{appConfig.audioVisualizer === 'grid' && (
158+
<AudioGridVisualizer
159+
state={agentState}
160+
audioTrack={agentAudioTrack!}
161+
options={{
162+
columnCount: 11,
163+
rowCount: 11,
164+
radius: 6,
165+
interval: 75,
166+
className: 'gap-1',
167+
baseClassName: 'size-0.75 rounded-full',
168+
offClassName: 'bg-foreground/10 scale-100',
169+
onClassName:
170+
'bg-foreground scale-125 shadow-[0px_0px_2px_1px_rgba(255,255,255,0.2)]',
171+
}}
172+
/>
173+
)}
153174
{appConfig.audioVisualizer === 'aura' && (
154175
<AudioShaderVisualizer
155176
size="sm"
156177
state={agentState}
157178
audioTrack={agentAudioTrack!}
158-
colorShift={0.4}
179+
colorShift={theme === 'dark' ? 0.4 : 0.0}
180+
rgbColor={
181+
theme === 'dark'
182+
? [0.12156862745098039, 0.8352941176470589, 0.9764705882352941]
183+
: [0.0, 0.0, 0.9]
184+
}
159185
/>
160186
)}
161187
{appConfig.audioVisualizer === 'wave' && (
162188
<AudioOscilloscopeVisualizer
163189
size="sm"
164190
state={agentState}
165191
audioTrack={agentAudioTrack!}
192+
lineWidth={3}
193+
rgbColor={
194+
theme === 'dark'
195+
? [0.12156862745098039, 0.8352941176470589, 0.9764705882352941]
196+
: [0.0, 0.0, 0.9]
197+
}
166198
/>
167199
)}
168200
</MotionContainer>

components/livekit/audio-visualizer/audio-radial-visualizer/audio-radial-visualizer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function AudioRadialVisualizer({
6969
switch (size) {
7070
case 'icon':
7171
case 'sm':
72-
return 9;
72+
return 8;
7373
default:
7474
return 12;
7575
}

components/livekit/audio-visualizer/audio-shader-visualizer/aurora-shaders.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export const AuroraShaders = forwardRef<HTMLDivElement, AuroraShadersProps>(
240240
frequency = 0.5,
241241
scale = 0.2,
242242
blur = 1.0,
243-
rgbColor = [31.0 / 255, 213.0 / 255, 249.0 / 255], // LiveKit Blue,
243+
rgbColor = [0.12156862745098039, 0.8352941176470589, 0.9764705882352941], // LiveKit Blue,
244244
colorShift = 1.0,
245245
brightness = 1.0,
246246
mode = typeof window !== 'undefined' && document.documentElement.classList.contains('dark')

lib/utils.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ import type { AppConfig } from '@/app-config';
77
export const CONFIG_ENDPOINT = process.env.NEXT_PUBLIC_APP_CONFIG_ENDPOINT;
88
export const SANDBOX_ID = process.env.SANDBOX_ID;
99

10-
export const THEME_STORAGE_KEY = 'theme-mode';
11-
export const THEME_MEDIA_QUERY = '(prefers-color-scheme: dark)';
12-
1310
export interface SandboxConfig {
1411
[key: string]:
1512
| { type: 'string'; value: string }

0 commit comments

Comments
 (0)