Skip to content

Commit 74866d1

Browse files
add motion library (#19)
1 parent e246b6b commit 74866d1

File tree

5 files changed

+185
-75
lines changed

5 files changed

+185
-75
lines changed

app/globals.css

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -152,27 +152,4 @@
152152
background-position-x: 300px;
153153
}
154154
}
155-
156-
.appear-from-bottom {
157-
opacity: 1;
158-
transform: translateY(0);
159-
transition-property: transform, opacity;
160-
transition-duration: var(--tw-duration);
161-
transition-timing-function: var(--tw-ease);
162-
163-
@starting-style {
164-
transform: translateY(100%);
165-
}
166-
}
167-
168-
.appear-fade-in {
169-
opacity: 1;
170-
transition-property: opacity;
171-
transition-duration: var(--tw-duration);
172-
transition-timing-function: var(--tw-ease);
173-
174-
@starting-style {
175-
opacity: 0;
176-
}
177-
}
178155
}

components/app.tsx

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import * as React from 'react';
44
import { Room, RoomEvent } from 'livekit-client';
5+
import { motion } from 'motion/react';
56
import { RoomAudioRenderer, RoomContext, StartAudio } from '@livekit/components-react';
67
import { toastAlert } from '@/components/alert-toast';
78
import SessionView from '@/components/session-view';
@@ -67,15 +68,57 @@ export function App({ appConfig }: AppProps) {
6768

6869
return (
6970
<>
70-
{sessionStarted ? (
71-
<RoomContext.Provider value={room}>
72-
<SessionView capabilities={capabilities} />
73-
<RoomAudioRenderer />
74-
<StartAudio label="Start Audio" />
75-
</RoomContext.Provider>
76-
) : (
71+
<header className="fixed top-0 left-0 z-50 hidden w-full flex-row justify-between p-6 md:flex">
72+
<a
73+
target="_blank"
74+
rel="noopener noreferrer"
75+
href="https://livekit.io"
76+
className="scale-100 transition-transform duration-300 hover:scale-110"
77+
>
78+
<img src="/lk-logo.svg" alt="LiveKit Logo" className="size-6" />
79+
</a>
80+
<span className="text-foreground font-mono text-xs font-bold tracking-wider uppercase">
81+
Built with{' '}
82+
<a
83+
target="_blank"
84+
rel="noopener noreferrer"
85+
href="https://github.com/livekit/agents"
86+
className="underline underline-offset-4"
87+
>
88+
LiveKit Agents
89+
</a>
90+
</span>
91+
</header>
92+
93+
<RoomContext.Provider value={room}>
94+
<motion.div
95+
key="session-view"
96+
inert={!sessionStarted}
97+
initial={{ opacity: 0 }}
98+
animate={{ opacity: sessionStarted ? 1 : 0 }}
99+
transition={{
100+
duration: 0.5,
101+
ease: 'linear',
102+
delay: sessionStarted ? 0.5 : 0,
103+
}}
104+
>
105+
<SessionView capabilities={capabilities} sessionStarted={sessionStarted} />
106+
</motion.div>
107+
<RoomAudioRenderer />
108+
<StartAudio label="Start Audio" />
109+
</RoomContext.Provider>
110+
111+
<motion.div
112+
key="welcome"
113+
inert={sessionStarted}
114+
initial={{ opacity: 0 }}
115+
animate={{ opacity: sessionStarted ? 0 : 1 }}
116+
transition={{ duration: 0.5, ease: 'linear', delay: sessionStarted ? 0 : 0.5 }}
117+
className="fixed inset-0 z-10"
118+
>
77119
<Welcome startButtonText={startButtonText} onStartCall={() => setSessionStarted(true)} />
78-
)}
120+
</motion.div>
121+
79122
<Toaster />
80123
</>
81124
);

components/session-view.tsx

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
'use client';
22

33
import * as React from 'react';
4+
import { AnimatePresence, motion } from 'motion/react';
45
import { ReceivedChatMessage } from '@livekit/components-react';
56
import { AgentAudioTile } from '@/components/livekit/agent-audio-tile';
67
import { AgentControlBar } from '@/components/livekit/agent-control-bar/agent-control-bar';
78
import { ChatEntry } from '@/components/livekit/chat/chat-entry';
89
import { ChatMessageView } from '@/components/livekit/chat/chat-message-view';
910
import useChatAndTranscription from '@/hooks/useChatAndTranscription';
1011
import { useDebugMode } from '@/hooks/useDebug';
11-
import { AppConfig } from '@/lib/types';
1212
import { cn } from '@/lib/utils';
1313

14-
interface SessionViewProp {
15-
capabilities: AppConfig['capabilities'];
14+
interface SessionViewProps {
15+
capabilities: {
16+
suportsChatInput: boolean;
17+
suportsVideoInput: boolean;
18+
suportsScreenShare: boolean;
19+
};
20+
sessionStarted: boolean;
1621
}
1722

18-
export default function SessionView({ capabilities }: SessionViewProp) {
23+
export default function SessionView({ capabilities, sessionStarted }: SessionViewProps) {
1924
const [chatOpen, setChatOpen] = React.useState(false);
2025
const { messages, send } = useChatAndTranscription();
2126

@@ -27,62 +32,86 @@ export default function SessionView({ capabilities }: SessionViewProp) {
2732

2833
return (
2934
<main>
30-
{chatOpen && (
31-
<ChatMessageView className="appear-fade-in mx-auto min-h-svh w-full max-w-2xl px-3 pt-56 pb-48 delay-300 duration-1000 ease-out md:px-0">
32-
<div className="space-y-3 whitespace-pre-wrap">
35+
<ChatMessageView
36+
className={cn(
37+
'mx-auto min-h-svh w-full max-w-2xl px-3 pt-56 pb-48 transition-[opacity,translate] duration-300 ease-out md:px-0',
38+
chatOpen ? 'translate-y-0 opacity-100' : 'translate-y-20 opacity-0'
39+
)}
40+
>
41+
<div className="space-y-3 whitespace-pre-wrap">
42+
<AnimatePresence>
3343
{messages.map((message: ReceivedChatMessage) => (
34-
<ChatEntry
35-
hideName
44+
<motion.div
3645
key={message.id}
37-
entry={message}
38-
className="appear-fade-in duration-2000 ease-out"
39-
/>
46+
initial={{ opacity: 0, height: 0 }}
47+
animate={{ opacity: 1, height: 'auto' }}
48+
exit={{ opacity: 1, height: 'auto', translateY: 0.001 }}
49+
transition={{ duration: 0.5, ease: 'easeOut' }}
50+
>
51+
<ChatEntry hideName key={message.id} entry={message} />
52+
</motion.div>
4053
))}
41-
</div>
42-
</ChatMessageView>
43-
)}
54+
</AnimatePresence>
55+
</div>
56+
</ChatMessageView>
4457

4558
<div className="bg-background mp-12 fixed top-0 right-0 left-0 h-40">
4659
{/* skrim */}
4760
<div className="from-background absolute bottom-0 left-0 h-12 w-full translate-y-full bg-gradient-to-b to-transparent" />
4861
</div>
4962

50-
<div
63+
<motion.div
5164
className={cn(
52-
'bg-background fixed left-1/2 z-50 rounded-2xl border border-transparent p-8 transition-[left,top,transform,shadow,border] duration-500 ease-out',
53-
chatOpen ? 'border-border top-24 drop-shadow-2xl/3' : 'top-1/2'
65+
'bg-background fixed left-1/2 z-50 -translate-1/2 rounded-2xl border border-transparent p-8 transition-[shadow,border] duration-500 ease-out',
66+
chatOpen && 'border-border drop-shadow-2xl/3'
5467
)}
55-
style={{
56-
transform: chatOpen
57-
? 'translate(-50%, -50%) scale(0.4)'
58-
: 'translate(-50%, -50%) scale(1)',
68+
animate={{
69+
top: chatOpen ? '96px' : '50%',
70+
scale: chatOpen ? 0.4 : 1,
5971
}}
72+
transition={{ type: 'spring', damping: 18 }}
6073
>
6174
<AgentAudioTile />
62-
</div>
75+
</motion.div>
6376

64-
<div className="bg-background appear-from-bottom fixed right-0 bottom-0 left-0 z-50 px-3 pb-3 delay-300 duration-300 ease-out md:px-12 md:pb-12">
65-
<div className="relative mx-auto w-full max-w-2xl">
66-
<div
67-
aria-hidden={messages.length > 0}
68-
className={cn(
69-
'appear-fade-in absolute inset-x-0 -top-12 delay-600 duration-2000 ease-in',
70-
messages.length === 0 ? 'opacity-100' : '!opacity-0'
71-
)}
72-
>
73-
<p className="animate-text-gradient from-foreground/20 via-foreground to-foreground/20 mx-auto bg-gradient-to-r bg-clip-text text-center text-sm font-semibold text-transparent">
74-
Agent is listening, ask it a question
75-
</p>
76-
</div>
77+
<div className="bg-background fixed right-0 bottom-0 left-0 z-50 px-3 pb-3 md:px-12 md:pb-12">
78+
<motion.div
79+
key="control-bar"
80+
initial={{ opacity: 0, translateY: '100%' }}
81+
animate={{
82+
opacity: sessionStarted ? 1 : 0,
83+
translateY: sessionStarted ? '0%' : '100%',
84+
}}
85+
transition={{ duration: 0.3, delay: sessionStarted ? 0.5 : 0, ease: 'easeOut' }}
86+
>
87+
<div className="relative mx-auto w-full max-w-2xl">
88+
<motion.div
89+
initial={{ opacity: 0 }}
90+
animate={{
91+
opacity: sessionStarted && messages.length === 0 ? 1 : 0,
92+
transition: {
93+
ease: 'easeIn',
94+
delay: messages.length > 0 ? 0 : 0.8,
95+
duration: messages.length > 0 ? 0.2 : 0.5,
96+
},
97+
}}
98+
aria-hidden={messages.length > 0}
99+
className={cn('absolute inset-x-0 -top-12')}
100+
>
101+
<p className="animate-text-gradient from-foreground/20 via-foreground to-foreground/20 mx-auto bg-gradient-to-r bg-clip-text text-center text-sm font-semibold text-transparent">
102+
Agent is listening, ask it a question
103+
</p>
104+
</motion.div>
77105

78-
<AgentControlBar
79-
capabilities={capabilities}
80-
onChatOpenChange={setChatOpen}
81-
onSendMessage={handleSendMessage}
82-
/>
83-
</div>
84-
{/* skrim */}
85-
<div className="from-background border-background absolute top-0 left-0 h-12 w-full -translate-y-full border-b-8 bg-gradient-to-t to-transparent" />
106+
<AgentControlBar
107+
capabilities={capabilities}
108+
onChatOpenChange={setChatOpen}
109+
onSendMessage={handleSendMessage}
110+
/>
111+
</div>
112+
{/* skrim */}
113+
<div className="from-background border-background absolute top-0 left-0 h-12 w-full -translate-y-full border-b-8 bg-gradient-to-t to-transparent" />
114+
</motion.div>
86115
</div>
87116
</main>
88117
);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"clsx": "^2.1.1",
2424
"livekit-client": "^2.13.3",
2525
"livekit-server-sdk": "^2.13.0",
26+
"motion": "^12.16.0",
2627
"next": "15.3.2",
2728
"next-themes": "^0.4.6",
2829
"react": "^19.0.0",

pnpm-lock.yaml

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)