Skip to content

Commit 3568891

Browse files
polish
1 parent 41698e3 commit 3568891

File tree

13 files changed

+123
-373
lines changed

13 files changed

+123
-373
lines changed

app/ui/_container.tsx

Lines changed: 0 additions & 20 deletions
This file was deleted.

app/ui/layout.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,25 @@ export default async function ComponentsLayout({ children }: { children: React.R
1313
<div className="mx-auto max-w-3xl space-y-8">
1414
<header className="space-y-2">
1515
<h1 className="text-5xl font-bold tracking-tight">LiveKit UI</h1>
16-
<p className="text-muted-foreground max-w-prose text-balance">
17-
A set of beautifully designed components that you can customize, extend, and build on.
16+
<p className="text-muted-foreground max-w-80 leading-tight text-pretty">
17+
A set of UI components for building LiveKit-powered voice experiences.
1818
</p>
1919
<p className="text-muted-foreground max-w-prose text-balance">
20-
Built with Shadcn conventions.
20+
Built with{' '}
21+
<a href="https://shadcn.com" className="underline underline-offset-2">
22+
Shadcn
23+
</a>
24+
,{' '}
25+
<a href="https://motion.dev" className="underline underline-offset-2">
26+
Motion
27+
</a>
28+
, and{' '}
29+
<a href="https://livekit.io" className="underline underline-offset-2">
30+
LiveKit
31+
</a>
32+
.
2133
</p>
22-
<p className="text-foreground max-w-prose text-balance">Open Source. Open Code.</p>
34+
<p className="text-foreground max-w-prose text-balance">Open Source.</p>
2335
</header>
2436

2537
<main className="space-y-20">{children}</main>

app/ui/page.tsx

Lines changed: 70 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1+
import { type VariantProps } from 'class-variance-authority';
12
import { Track } from 'livekit-client';
2-
import { type ReceivedChatMessage } from '@livekit/components-react';
3-
import { PlusIcon } from '@phosphor-icons/react/dist/ssr';
3+
import { MicrophoneIcon } from '@phosphor-icons/react/dist/ssr';
44
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
55
import { TrackDeviceSelect } from '@/components/livekit/agent-control-bar/track-device-select';
66
import { TrackSelector } from '@/components/livekit/agent-control-bar/track-selector';
77
import { TrackToggle } from '@/components/livekit/agent-control-bar/track-toggle';
8-
import { Alert, AlertDescription, AlertTitle } from '@/components/livekit/alert';
8+
import { Alert, AlertDescription, AlertTitle, alertVariants } from '@/components/livekit/alert';
99
import { AlertToast } from '@/components/livekit/alert-toast';
10-
import { Button } from '@/components/livekit/button';
10+
import { Button, buttonVariants } from '@/components/livekit/button';
1111
import { ChatEntry } from '@/components/livekit/chat-entry';
1212
import {
1313
Select,
@@ -17,20 +17,33 @@ import {
1717
SelectValue,
1818
} from '@/components/livekit/select';
1919
import { ShimmerText } from '@/components/livekit/shimmer-text';
20-
import { Toggle } from '@/components/livekit/toggle';
21-
import { Container } from './_container';
20+
import { Toggle, toggleVariants } from '@/components/livekit/toggle';
21+
import { cn } from '@/lib/utils';
2222

23-
const buttonVariants = [
24-
'default',
25-
'primary',
26-
'secondary',
27-
'outline',
28-
'ghost',
29-
'link',
30-
'destructive',
31-
] as const;
32-
const toggleVariants = ['default', 'primary', 'secondary', 'outline'] as const;
33-
const alertVariants = ['default', 'destructive'] as const;
23+
type toggleVariantsType = VariantProps<typeof toggleVariants>['variant'];
24+
type toggleVariantsSizeType = VariantProps<typeof toggleVariants>['size'];
25+
type buttonVariantsType = VariantProps<typeof buttonVariants>['variant'];
26+
type buttonVariantsSizeType = VariantProps<typeof buttonVariants>['size'];
27+
type alertVariantsType = VariantProps<typeof alertVariants>['variant'];
28+
29+
interface ContainerProps {
30+
componentName?: string;
31+
children: React.ReactNode;
32+
className?: string;
33+
}
34+
35+
function Container({ componentName, children, className }: ContainerProps) {
36+
return (
37+
<div className={cn('space-y-4', className)}>
38+
<h3 className="text-foreground text-2xl font-bold">
39+
<span className="tracking-tight">{componentName}</span>
40+
</h3>
41+
<div className="bg-background border-input space-y-4 rounded-3xl border p-8 drop-shadow-lg/5">
42+
{children}
43+
</div>
44+
</div>
45+
);
46+
}
3447

3548
function StoryTitle({ children }: { children: React.ReactNode }) {
3649
return <h4 className="text-muted-foreground mb-2 font-mono text-xs uppercase">{children}</h4>;
@@ -54,29 +67,23 @@ export default function Base() {
5467
</tr>
5568
</thead>
5669
<tbody className="[&_td]:p-2 [&_td:not(:first-child)]:text-center">
57-
{buttonVariants.map((variant) => (
58-
<tr key={variant}>
59-
<td className="text-right font-mono text-xs font-normal uppercase">{variant}</td>
60-
<td>
61-
<Button variant={variant} size="sm">
62-
Button
63-
</Button>
64-
</td>
65-
<td>
66-
<Button variant={variant}>Button</Button>
67-
</td>
68-
<td>
69-
<Button variant={variant} size="lg">
70-
Button
71-
</Button>
72-
</td>
73-
<td>
74-
<Button variant={variant} size="icon">
75-
<PlusIcon size={16} weight="bold" />
76-
</Button>
77-
</td>
78-
</tr>
79-
))}
70+
{['default', 'primary', 'secondary', 'outline', 'ghost', 'link', 'destructive'].map(
71+
(variant) => (
72+
<tr key={variant}>
73+
<td className="text-right font-mono text-xs font-normal uppercase">{variant}</td>
74+
{['sm', 'default', 'lg', 'icon'].map((size) => (
75+
<td key={size}>
76+
<Button
77+
variant={variant as buttonVariantsType}
78+
size={size as buttonVariantsSizeType}
79+
>
80+
{size === 'icon' ? <MicrophoneIcon size={16} weight="bold" /> : 'Button'}
81+
</Button>
82+
</td>
83+
))}
84+
</tr>
85+
)
86+
)}
8087
</tbody>
8188
</table>
8289
</Container>
@@ -94,61 +101,31 @@ export default function Base() {
94101
</tr>
95102
</thead>
96103
<tbody className="[&_td]:p-2 [&_td:not(:first-child)]:text-center">
97-
{toggleVariants.map((variant) => (
104+
{['default', 'primary', 'secondary', 'outline'].map((variant) => (
98105
<tr key={variant}>
99106
<td className="text-right font-mono text-xs font-normal uppercase">{variant}</td>
100-
<td>
101-
<Toggle variant={variant} size="sm">
102-
Toggle
103-
</Toggle>
104-
</td>
105-
<td>
106-
<Toggle variant={variant}>Toggle</Toggle>
107-
</td>
108-
<td>
109-
<Toggle variant={variant} size="lg">
110-
Toggle
111-
</Toggle>
112-
</td>
113-
<td>
114-
<Toggle variant={variant} size="icon">
115-
<PlusIcon size={16} weight="bold" />
116-
</Toggle>
117-
</td>
107+
{['sm', 'default', 'lg', 'icon'].map((size) => (
108+
<td key={size}>
109+
<Toggle
110+
size={size as toggleVariantsSizeType}
111+
variant={variant as toggleVariantsType}
112+
>
113+
{size === 'icon' ? <MicrophoneIcon size={16} weight="bold" /> : 'Toggle'}
114+
</Toggle>
115+
</td>
116+
))}
118117
</tr>
119118
))}
120119
</tbody>
121120
</table>
122-
{/* {toggleVariants.map((variant) => (
123-
<div key={variant}>
124-
<StoryTitle>{variant}</StoryTitle>
125-
<div className="flex justify-center gap-8">
126-
<div>
127-
<Toggle key={variant} variant={variant} size="sm">
128-
Size sm
129-
</Toggle>
130-
</div>
131-
<div>
132-
<Toggle key={variant} variant={variant}>
133-
Size default
134-
</Toggle>
135-
</div>
136-
<div>
137-
<Toggle key={variant} variant={variant} size="lg">
138-
Size lg
139-
</Toggle>
140-
</div>
141-
</div>
142-
</div>
143-
))} */}
144121
</Container>
145122

146123
{/* Alert */}
147124
<Container componentName="Alert">
148-
{alertVariants.map((variant) => (
125+
{['default', 'destructive'].map((variant) => (
149126
<div key={variant}>
150127
<StoryTitle>{variant}</StoryTitle>
151-
<Alert key={variant} variant={variant}>
128+
<Alert key={variant} variant={variant as alertVariantsType}>
152129
<AlertTitle>Alert {variant} title</AlertTitle>
153130
<AlertDescription>This is a {variant} alert description.</AlertDescription>
154131
</Alert>
@@ -252,40 +229,18 @@ export default function Base() {
252229
<Container componentName="ChatEntry">
253230
<div className="mx-auto max-w-prose space-y-4">
254231
<ChatEntry
255-
entry={
256-
{
257-
id: '1',
258-
timestamp: Date.now(),
259-
message: 'Hello, how are you?',
260-
from: {
261-
identity: 'user',
262-
isLocal: true,
263-
name: 'User',
264-
audioTrackPublications: new Map(),
265-
videoTrackPublications: new Map(),
266-
trackPublications: new Map(),
267-
audioLevel: 0,
268-
},
269-
} as ReceivedChatMessage
270-
}
232+
locale="en-US"
233+
timestamp={Date.now() + 1000}
234+
message="Hello, how are you?"
235+
messageOrigin="local"
236+
name="User"
271237
/>
272238
<ChatEntry
273-
entry={
274-
{
275-
id: '1',
276-
timestamp: Date.now(),
277-
message: 'I am good, how about you?',
278-
from: {
279-
identity: 'agent',
280-
isLocal: false,
281-
name: 'Agent',
282-
audioTrackPublications: new Map(),
283-
videoTrackPublications: new Map(),
284-
trackPublications: new Map(),
285-
audioLevel: 0,
286-
},
287-
} as ReceivedChatMessage
288-
}
239+
locale="en-US"
240+
timestamp={Date.now() + 5000}
241+
message="I am good, how about you?"
242+
messageOrigin="remote"
243+
name="Agent"
289244
/>
290245
</div>
291246
</Container>

components/app/chat-transcript.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,13 @@ export function ChatTranscript({
6363
{!hidden && (
6464
<MotionContainer {...CONTAINER_MOTION_PROPS} {...props}>
6565
{messages.map(({ id, timestamp, from, message, editTimestamp }: ReceivedChatMessage) => {
66-
const name = from?.name ?? from?.identity;
6766
const locale = navigator?.language ?? 'en-US';
6867
const messageOrigin = from?.isLocal ? 'local' : 'remote';
6968
const hasBeenEdited = !!editTimestamp;
7069

7170
return (
7271
<MotionChatEntry
7372
key={id}
74-
name={name}
7573
locale={locale}
7674
timestamp={timestamp}
7775
message={message}

components/app/session-view.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,6 @@ export const SessionView = ({
9292
<Fade top className="absolute inset-x-4 top-0 h-40" />
9393
<ScrollArea className="px-4 pt-40 pb-[150px] md:px-6 md:pb-[180px]">
9494
<ChatTranscript
95-
hideName
9695
hidden={!chatOpen}
9796
messages={messages}
9897
className="mx-auto max-w-2xl space-y-3 transition-opacity duration-300 ease-out"

components/app/tile-layout.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,10 @@ export function TileLayout({ chatOpen }: TileLayoutProps) {
8686
const isScreenShareEnabled = screenShareTrack && !screenShareTrack.publication.isMuted;
8787
const hasSecondTile = isCameraEnabled || isScreenShareEnabled;
8888

89-
const isAvatar = agentVideoTrack !== undefined;
9089
const animationDelay = chatOpen ? 0 : 0.15;
90+
const isAvatar = agentVideoTrack !== undefined;
91+
const videoWidth = agentVideoTrack?.publication.dimensions?.width ?? 0;
92+
const videoHeight = agentVideoTrack?.publication.dimensions?.height ?? 0;
9193

9294
return (
9395
<div className="pointer-events-none fixed inset-x-0 top-8 bottom-32 z-50 md:top-12 md:bottom-40">
@@ -165,23 +167,21 @@ export function TileLayout({ chatOpen }: TileLayoutProps) {
165167
...ANIMATION_TRANSITION,
166168
delay: animationDelay,
167169
maskImage: {
168-
ease: 'easeIn',
169170
duration: 1,
170171
},
171172
filter: {
172-
ease: 'easeIn',
173173
duration: 1,
174174
},
175175
}}
176176
className={cn(
177-
'overflow-hidden drop-shadow-xl/80',
177+
'overflow-hidden bg-black drop-shadow-xl/80',
178178
chatOpen ? 'h-[90px]' : 'h-auto w-full'
179179
)}
180180
>
181181
<VideoTrack
182+
width={videoWidth}
183+
height={videoHeight}
182184
trackRef={agentVideoTrack}
183-
width={agentVideoTrack?.publication.dimensions?.width ?? 0}
184-
height={agentVideoTrack?.publication.dimensions?.height ?? 0}
185185
className={cn(chatOpen && 'size-[90px] object-cover')}
186186
/>
187187
</MotionContainer>

0 commit comments

Comments
 (0)