Skip to content

Commit 86946e5

Browse files
Extend shadcn design system (#16)
1 parent 710c96a commit 86946e5

File tree

21 files changed

+541
-286
lines changed

21 files changed

+541
-286
lines changed

app/components/Container.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { cn } from '@/lib/utils';
2+
3+
interface ContainerProps {
4+
children: React.ReactNode;
5+
className?: string;
6+
}
7+
8+
export function Container({ children, className }: ContainerProps) {
9+
return (
10+
<div className={cn('relative space-y-4 rounded-lg border p-4', className)}>{children}</div>
11+
);
12+
}

app/components/base/page.tsx

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { PlusIcon } from '@phosphor-icons/react/dist/ssr';
2+
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
3+
import { Button } from '@/components/ui/button';
4+
import {
5+
Select,
6+
SelectContent,
7+
SelectItem,
8+
SelectTrigger,
9+
SelectValue,
10+
} from '@/components/ui/select';
11+
import { Toggle } from '@/components/ui/toggle';
12+
import { Container } from '../Container';
13+
14+
export default function Base() {
15+
return (
16+
<>
17+
{/* Button */}
18+
<Container>
19+
<h3 className="text-muted-foreground text-sm">A button component.</h3>
20+
<div className="space-y-2">
21+
{['default', 'secondary', 'outline', 'ghost', 'link', 'destructive'].map((variant) => (
22+
<div key={variant}>
23+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">{variant}</h4>
24+
<div className="grid w-full grid-cols-4 gap-2">
25+
<div>
26+
<Button variant={variant as any} size="sm">
27+
Size sm
28+
</Button>
29+
</div>
30+
<div>
31+
<Button variant={variant as any}>Size default</Button>
32+
</div>
33+
<div>
34+
<Button variant={variant as any} size="lg">
35+
Size lg
36+
</Button>
37+
</div>
38+
<div>
39+
<Button variant={variant as any} size="icon">
40+
<PlusIcon size={16} weight="bold" />
41+
</Button>
42+
</div>
43+
</div>
44+
</div>
45+
))}
46+
</div>
47+
</Container>
48+
49+
{/* Toggle */}
50+
<Container>
51+
<h3 className="text-muted-foreground text-sm">A toggle component.</h3>
52+
<div className="space-y-2">
53+
{['default', 'outline'].map((variant) => (
54+
<div key={variant}>
55+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">{variant}</h4>
56+
<div className="grid w-full grid-cols-3 gap-2">
57+
<div>
58+
<Toggle key={variant} variant={variant as any} size="sm">
59+
Size sm
60+
</Toggle>
61+
</div>
62+
<div>
63+
<Toggle key={variant} variant={variant as any}>
64+
Size default
65+
</Toggle>
66+
</div>
67+
<div>
68+
<Toggle key={variant} variant={variant as any} size="lg">
69+
Size lg
70+
</Toggle>
71+
</div>
72+
</div>
73+
</div>
74+
))}
75+
</div>
76+
</Container>
77+
78+
{/* Alert */}
79+
<Container>
80+
<h3 className="text-muted-foreground text-sm">An alert component.</h3>
81+
<div className="space-y-6">
82+
{['default', 'destructive'].map((variant) => (
83+
<div key={variant}>
84+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">{variant}</h4>
85+
<Alert key={variant} variant={variant as any}>
86+
<AlertTitle>Alert {variant} title</AlertTitle>
87+
<AlertDescription>This is a {variant} alert description.</AlertDescription>
88+
</Alert>
89+
</div>
90+
))}
91+
</div>
92+
</Container>
93+
94+
{/* Select */}
95+
<Container>
96+
<h3 className="text-muted-foreground text-sm">A select component.</h3>
97+
<div className="grid w-full grid-cols-2 gap-2">
98+
<div>
99+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size default</h4>
100+
<Select>
101+
<SelectTrigger>
102+
<SelectValue placeholder="Select a track" />
103+
</SelectTrigger>
104+
<SelectContent>
105+
<SelectItem value="1">Track 1</SelectItem>
106+
<SelectItem value="2">Track 2</SelectItem>
107+
<SelectItem value="3">Track 3</SelectItem>
108+
</SelectContent>
109+
</Select>
110+
</div>
111+
<div>
112+
<h3 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size sm</h3>
113+
<Select>
114+
<SelectTrigger size="sm">
115+
<SelectValue placeholder="Select a track" />
116+
</SelectTrigger>
117+
<SelectContent>
118+
<SelectItem value="1">Track 1</SelectItem>
119+
<SelectItem value="2">Track 2</SelectItem>
120+
<SelectItem value="3">Track 3</SelectItem>
121+
</SelectContent>
122+
</Select>
123+
</div>
124+
</div>
125+
</Container>
126+
</>
127+
);
128+
}

app/components/layout.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import Link from 'next/link';
5+
import { usePathname } from 'next/navigation';
6+
import { Room } from 'livekit-client';
7+
import { RoomContext } from '@livekit/components-react';
8+
import { toastAlert } from '@/components/alert-toast';
9+
import useConnectionDetails from '@/hooks/useConnectionDetails';
10+
import { cn } from '@/lib/utils';
11+
12+
export default function ComponentsLayout({ children }: { children: React.ReactNode }) {
13+
const connectionDetails = useConnectionDetails();
14+
15+
const pathname = usePathname();
16+
const room = React.useMemo(() => new Room(), []);
17+
18+
React.useEffect(() => {
19+
if (room.state === 'disconnected' && connectionDetails) {
20+
Promise.all([
21+
room.localParticipant.setMicrophoneEnabled(true, undefined, {
22+
preConnectBuffer: true,
23+
}),
24+
room.connect(connectionDetails.serverUrl, connectionDetails.participantToken),
25+
]).catch((error) => {
26+
toastAlert({
27+
title: 'There was an error connecting to the agent',
28+
description: `${error.name}: ${error.message}`,
29+
});
30+
});
31+
}
32+
return () => {
33+
room.disconnect();
34+
};
35+
}, [room, connectionDetails]);
36+
37+
return (
38+
<div className="mx-auto min-h-svh max-w-3xl space-y-8 px-4 py-8">
39+
<header className="flex flex-col gap-1">
40+
<h1 className="text-3xl font-bold tracking-tight">Quick Start UI overview</h1>
41+
<p className="text-muted-foreground">
42+
A quick start UI overview for the LiveKit Voice Assistant.
43+
</p>
44+
</header>
45+
46+
<div className="flex flex-row justify-between border-b">
47+
<Link
48+
href="/components/base"
49+
className={cn(
50+
'text-fg0 -mb-px cursor-pointer px-4 pt-2 text-xl font-bold tracking-tight uppercase',
51+
pathname === '/components/base' &&
52+
'bg-background rounded-t-lg border-t border-r border-l'
53+
)}
54+
>
55+
Base components
56+
</Link>
57+
<Link
58+
href="/components/livekit"
59+
className={cn(
60+
'text-fg0 -mb-px cursor-pointer px-4 py-2 text-xl font-bold tracking-tight uppercase',
61+
pathname === '/components/livekit' &&
62+
'bg-background rounded-t-lg border-t border-r border-l'
63+
)}
64+
>
65+
LiveKit components
66+
</Link>
67+
</div>
68+
69+
<RoomContext.Provider value={room}>
70+
<main className="flex w-full flex-1 flex-col items-stretch gap-8">{children}</main>
71+
</RoomContext.Provider>
72+
</div>
73+
);
74+
}

app/components/livekit/page.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Track } from 'livekit-client';
2+
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
3+
import { DeviceSelect } from '@/components/livekit/device-select';
4+
import { TrackToggle } from '@/components/livekit/track-toggle';
5+
import { Container } from '../Container';
6+
7+
export default function LiveKit() {
8+
return (
9+
<>
10+
{/* Device select */}
11+
<Container>
12+
<div className="flex items-center justify-between">
13+
<h3 className="text-muted-foreground text-sm">A device select component.</h3>
14+
</div>
15+
<div className="grid grid-cols-2 gap-4">
16+
<div>
17+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size default</h4>
18+
<DeviceSelect kind="audioinput" />
19+
</div>
20+
<div>
21+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">Size sm</h4>
22+
<DeviceSelect size="sm" kind="audioinput" />
23+
</div>
24+
</div>
25+
</Container>
26+
27+
{/* Track toggle */}
28+
<Container>
29+
<div className="flex items-center justify-between">
30+
<h3 className="text-muted-foreground text-sm">A track toggle component.</h3>
31+
</div>
32+
<div className="grid grid-cols-2 gap-4">
33+
<div>
34+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">
35+
Track.Source.Microphone
36+
</h4>
37+
<TrackToggle variant="outline" source={Track.Source.Microphone} />
38+
</div>
39+
<div>
40+
<h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">
41+
Track.Source.Camera
42+
</h4>
43+
<TrackToggle variant="outline" source={Track.Source.Camera} />
44+
</div>
45+
</div>
46+
</Container>
47+
48+
{/* Agent control bar */}
49+
<Container>
50+
<div className="flex items-center justify-between">
51+
<h3 className="text-muted-foreground text-sm">A control bar component.</h3>
52+
</div>
53+
<div className="relative flex items-center justify-center">
54+
<AgentControlBar className="w-full" />
55+
</div>
56+
</Container>
57+
</>
58+
);
59+
}

app/components/page.tsx

Lines changed: 3 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,83 +1,5 @@
1-
'use client';
1+
import { redirect } from 'next/navigation';
22

3-
import * as React from 'react';
4-
import { Room, Track } from 'livekit-client';
5-
import { RoomContext } from '@livekit/components-react';
6-
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
7-
import { DeviceSelect } from '@/components/livekit/device-select';
8-
import { TrackToggle } from '@/components/livekit/track-toggle';
9-
import {
10-
Select,
11-
SelectContent,
12-
SelectItem,
13-
SelectTrigger,
14-
SelectValue,
15-
} from '@/components/ui/select';
16-
17-
export default function ComponentsOverview() {
18-
const [source, setSource] = React.useState<Track.Source>(Track.Source.Microphone);
19-
const room = React.useMemo(() => new Room(), []);
20-
21-
React.useEffect(() => {
22-
if (room.state === 'disconnected') {
23-
room.connect(process.env.NEXT_PUBLIC_LIVEKIT_URL, process.env.NEXT_PUBLIC_LIVEKIT_TOKEN);
24-
}
25-
return () => {
26-
room.disconnect();
27-
};
28-
}, [room]);
29-
30-
return (
31-
<div className="mx-auto flex min-h-svh max-w-3xl flex-col gap-8 px-4 py-8">
32-
<header className="flex flex-col gap-1">
33-
<h1 className="text-3xl font-bold tracking-tight">Quick Start UI overview</h1>
34-
<p className="text-muted-foreground">
35-
A quick start UI overview for the LiveKit Voice Assistant.
36-
</p>
37-
</header>
38-
<RoomContext.Provider value={room}>
39-
<main className="flex flex-1 flex-col gap-8">
40-
<div className="relative flex min-h-[250px] flex-col gap-4 rounded-lg border p-4">
41-
<div className="flex items-center justify-between">
42-
<h2 className="text-muted-foreground text-sm sm:pl-3">A device select component.</h2>
43-
</div>
44-
<div className="relative flex min-h-[100px] items-center justify-center">
45-
<DeviceSelect variant="default" kind="audioinput" />
46-
</div>
47-
48-
<div className="relative flex min-h-[100px] items-center justify-center">
49-
<DeviceSelect variant="small" kind="audioinput" />
50-
</div>
51-
</div>
52-
53-
<div className="relative flex min-h-[450px] flex-col gap-4 rounded-lg border p-4">
54-
<div className="flex items-center justify-between">
55-
<h2 className="text-muted-foreground text-sm sm:pl-3">A track toggle component.</h2>
56-
<Select value={source} onValueChange={(value) => setSource(value as Track.Source)}>
57-
<SelectTrigger>
58-
<SelectValue placeholder="Select a track" />
59-
</SelectTrigger>
60-
<SelectContent>
61-
<SelectItem value={Track.Source.Microphone}>Microphone</SelectItem>
62-
<SelectItem value={Track.Source.Camera}>Camera</SelectItem>
63-
</SelectContent>
64-
</Select>
65-
</div>
66-
<div className="relative flex min-h-[400px] items-center justify-center">
67-
<TrackToggle variant="outline" source={source} />
68-
</div>
69-
</div>
70-
71-
<div className="relative flex min-h-[450px] flex-col gap-4 rounded-lg border p-4">
72-
<div className="flex items-center justify-between">
73-
<h2 className="text-muted-foreground text-sm sm:pl-3">A control bar component.</h2>
74-
</div>
75-
<div className="relative flex min-h-[200px] items-center justify-center">
76-
<AgentControlBar />
77-
</div>
78-
</div>
79-
</main>
80-
</RoomContext.Provider>
81-
</div>
82-
);
3+
export default function Components() {
4+
return redirect('/components/base');
835
}

0 commit comments

Comments
 (0)