@tavus/cvi-ui 0.0.1-beta.1 → 0.0.1-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -31
- package/dev-components/components/README.md +162 -0
- package/dev-components/components/audio-wave/audio-wave.module.css +33 -0
- package/dev-components/components/audio-wave/index.tsx +55 -0
- package/dev-components/components/controls/controls.module.css +115 -0
- package/dev-components/components/controls/index.tsx +260 -0
- package/dev-components/components/conversation-01/conversation.module.css +222 -0
- package/dev-components/components/conversation-01/index.tsx +180 -0
- package/dev-components/components/hair-check-01/hair-check.module.css +212 -0
- package/dev-components/components/hair-check-01/index.tsx +184 -0
- package/dev-components/hooks/README.md +3 -2
- package/dev-components/hooks/cvi-events-hooks.tsx +1 -1
- package/dev-components/hooks/use-local-camera.tsx +8 -2
- package/dev-components/hooks/use-local-microphone.tsx +8 -2
- package/dev-components/hooks/use-local-screenshare.tsx +7 -2
- package/dev-components/hooks/use-remote-participant-ids.tsx +1 -1
- package/dev-components/hooks/use-replica-ids.tsx +1 -1
- package/dev-components/hooks/use-start-haircheck.tsx +7 -1
- package/dist/index.js +305 -101
- package/dist/templates/components/controls.tsx +279 -0
- package/dist/templates/components/cvi-provider.tsx +9 -0
- package/dist/templates/controls.tsx +279 -0
- package/dist/templates/cvi-hooks.tsx +38 -0
- package/dist/templates/cvi-provider.tsx +9 -0
- package/dist/templates/hooks/cvi-hooks.tsx +38 -0
- package/dist/templates/tsx/components/controls.tsx +279 -0
- package/dist/templates/tsx/components/cvi-provider.tsx +9 -0
- package/dist/types/templates/components.d.ts +44 -0
- package/dist/types/templates/index.d.ts +2 -0
- package/dist/types/templates/jsx/index.d.ts +14 -0
- package/dist/types/templates/tsx/index.d.ts +14 -0
- package/dist/types/utils/version-utils.d.ts +3 -0
- package/package.json +6 -4
- package/src/templates/components.ts +18 -0
- package/src/templates/index.ts +2 -0
- package/src/templates/jsx/components/audio-wave.json +5 -0
- package/src/templates/jsx/components/controls.json +10 -0
- package/src/templates/jsx/components/conversation-01.json +11 -0
- package/src/templates/jsx/components/cvi-provider.json +5 -0
- package/src/templates/jsx/components/hair-check-01.json +10 -0
- package/src/templates/jsx/hooks/cvi-events-hooks.json +5 -0
- package/src/templates/jsx/hooks/use-cvi-call.json +5 -0
- package/src/templates/jsx/hooks/use-local-camera.json +5 -0
- package/src/templates/jsx/hooks/use-local-microphone.json +5 -0
- package/src/templates/jsx/hooks/use-local-screenshare.json +5 -0
- package/src/templates/jsx/hooks/use-remote-participant-ids.json +5 -0
- package/src/templates/jsx/hooks/use-replica-ids.json +5 -0
- package/src/templates/jsx/hooks/use-request-permissions.json +5 -0
- package/src/templates/jsx/hooks/use-start-haircheck.json +5 -0
- package/src/templates/jsx/index.ts +14 -0
- package/src/templates/tsx/components/audio-wave.json +5 -0
- package/src/templates/tsx/components/controls.json +10 -0
- package/src/templates/tsx/components/conversation-01.json +11 -0
- package/src/templates/tsx/components/cvi-provider.json +5 -0
- package/src/templates/tsx/components/hair-check-01.json +10 -0
- package/src/templates/tsx/hooks/cvi-events-hooks.json +5 -0
- package/src/templates/tsx/hooks/use-cvi-call.json +5 -0
- package/src/templates/tsx/hooks/use-local-camera.json +5 -0
- package/src/templates/tsx/hooks/use-local-microphone.json +5 -0
- package/src/templates/tsx/hooks/use-local-screenshare.json +5 -0
- package/src/templates/tsx/hooks/use-remote-participant-ids.json +5 -0
- package/src/templates/tsx/hooks/use-replica-ids.json +5 -0
- package/src/templates/tsx/hooks/use-request-permissions.json +8 -0
- package/src/templates/tsx/hooks/use-start-haircheck.json +5 -0
- package/src/templates/tsx/index.ts +14 -0
- package/src/utils/resolve-components-tree.ts +59 -2
- package/src/utils/update-files.ts +38 -10
- package/src/utils/version-utils.ts +26 -0
package/README.md
CHANGED
|
@@ -6,49 +6,49 @@ A CLI tool for installing and managing CVI (Conversational Video Interface) comp
|
|
|
6
6
|
|
|
7
7
|
**Initialize the project**:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npx @tavus/cvi-ui@latest init
|
|
11
|
+
```
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
This will:
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
- Create a `cvi-components.json` configuration file
|
|
16
|
+
- Prompt for TypeScript preference
|
|
17
|
+
- Install the necessary dependencies
|
|
18
18
|
|
|
19
19
|
**Add components to your project**:
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
```bash
|
|
22
|
+
npx @tavus/cvi-ui@latest add conversation
|
|
23
|
+
```
|
|
24
24
|
|
|
25
25
|
**Wrap your app with the CVI provider**:
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
```tsx
|
|
28
|
+
import { CVIProvider } from './components/cvi/components/cvi-provider';
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
function App() {
|
|
31
|
+
return (
|
|
32
|
+
<CVIProvider>
|
|
33
|
+
{/* Your app content */}
|
|
34
|
+
</CVIProvider>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
38
|
|
|
39
39
|
**Add conversation components**:
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
```tsx
|
|
42
|
+
import { Conversation } from './components/cvi/components/conversation';
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
function Call() {
|
|
45
|
+
return (
|
|
46
|
+
<div>
|
|
47
|
+
<Conversation {...conversationProps} />
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
52
|
|
|
53
53
|
## Available Components
|
|
54
54
|
|
|
@@ -56,6 +56,30 @@ A CLI tool for installing and managing CVI (Conversational Video Interface) comp
|
|
|
56
56
|
|
|
57
57
|
- **`cvi-provider`** - Main provider component that wraps your app with CVI context
|
|
58
58
|
|
|
59
|
+
### UI Components
|
|
60
|
+
|
|
61
|
+
- **`controls`** - Video chat control buttons with device selection capabilities
|
|
62
|
+
|
|
63
|
+
- `MicrophoneButton` - Toggle microphone with device dropdown
|
|
64
|
+
- `CameraButton` - Toggle camera with device dropdown
|
|
65
|
+
- `ScreenShareButton` - Toggle screen sharing
|
|
66
|
+
|
|
67
|
+
- **`hair-check-01`** - Pre-call interface for testing and configuring audio/video devices
|
|
68
|
+
|
|
69
|
+
- Live camera preview with mirror effect
|
|
70
|
+
- Permission management and device controls
|
|
71
|
+
- Join interface with loading states
|
|
72
|
+
|
|
73
|
+
- **`conversation-01`** - Conversation component for video chat
|
|
74
|
+
|
|
75
|
+
- Video chat with audio wave indicator
|
|
76
|
+
- Device selection and permission management
|
|
77
|
+
- Screen sharing support
|
|
78
|
+
- Error handling
|
|
79
|
+
|
|
80
|
+
- **`audio-wave`** - Audio wave indicator for conversation
|
|
81
|
+
- Shows active speaker audio level
|
|
82
|
+
|
|
59
83
|
### Hooks
|
|
60
84
|
|
|
61
85
|
#### Core Call Management
|
|
@@ -88,4 +112,3 @@ MIT License - see the [LICENSE](LICENSE) file for details.
|
|
|
88
112
|
|
|
89
113
|
- [CVI Documentation](https://docs.tavus.io/sections/conversational-video-interface/cvi-overview)
|
|
90
114
|
- [Tavus Examples](https://github.com/Tavus-Engineering/tavus-examples)
|
|
91
|
-
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# CVI Components
|
|
2
|
+
|
|
3
|
+
## Components
|
|
4
|
+
|
|
5
|
+
### Controls
|
|
6
|
+
|
|
7
|
+
The `Controls` component provides a set of video chat control buttons with integrated device selection capabilities.
|
|
8
|
+
|
|
9
|
+
#### Exported Components:
|
|
10
|
+
|
|
11
|
+
- **`MicrophoneButton`**: A button component that toggles microphone on/off with a dropdown for device selection
|
|
12
|
+
- **`CameraButton`**: A button component that toggles camera on/off with a dropdown for device selection
|
|
13
|
+
- **`ScreenShareButton`**: A button component that toggles screen sharing on/off
|
|
14
|
+
- **`SelectDevice`**: A reusable dropdown component for selecting camera and microphone devices
|
|
15
|
+
|
|
16
|
+
#### Usage:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { MicrophoneButton, CameraButton, ScreenShareButton } from './controls';
|
|
20
|
+
|
|
21
|
+
// Use individual control buttons
|
|
22
|
+
<MicrophoneButton />
|
|
23
|
+
<CameraButton />
|
|
24
|
+
<ScreenShareButton />
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
#### Component Details:
|
|
28
|
+
|
|
29
|
+
**MicrophoneButton**
|
|
30
|
+
|
|
31
|
+
- Toggles microphone mute/unmute state
|
|
32
|
+
- Shows visual indicators for muted/unmuted states
|
|
33
|
+
- Includes device selection dropdown
|
|
34
|
+
- Disabled when microphone is not ready
|
|
35
|
+
- Uses `useLocalMicrophone` and `useDevices` hooks
|
|
36
|
+
|
|
37
|
+
**CameraButton**
|
|
38
|
+
|
|
39
|
+
- Toggles camera on/off
|
|
40
|
+
- Shows visual indicators for camera muted/unmuted states
|
|
41
|
+
- Includes device selection dropdown
|
|
42
|
+
- Disabled when camera is not ready or no camera is available
|
|
43
|
+
- Uses `useLocalCamera` and `useDevices` hooks
|
|
44
|
+
|
|
45
|
+
**ScreenShareButton**
|
|
46
|
+
|
|
47
|
+
- Toggles screen sharing on/off
|
|
48
|
+
- Shows visual feedback with different icons for active/inactive states
|
|
49
|
+
- Uses `useLocalScreenshare` hook
|
|
50
|
+
|
|
51
|
+
**SelectDevice**
|
|
52
|
+
|
|
53
|
+
- Reusable dropdown component for device selection
|
|
54
|
+
- Accepts devices array, current value, disabled state, and onChange handler
|
|
55
|
+
- Includes custom dropdown arrow styling
|
|
56
|
+
|
|
57
|
+
#### Key Features:
|
|
58
|
+
|
|
59
|
+
- Automatic device detection and switching via Daily.co hooks
|
|
60
|
+
- Visual state indicators with SVG icons
|
|
61
|
+
- Accessibility support with screen reader labels (`srOnly` class)
|
|
62
|
+
- Responsive design with mobile-friendly controls
|
|
63
|
+
- Integration with Daily.co device management system
|
|
64
|
+
- CSS modules for scoped styling
|
|
65
|
+
|
|
66
|
+
### Hair Check 01
|
|
67
|
+
|
|
68
|
+
The `HairCheck` component provides a pre-call interface for users to test and configure their audio/video devices before joining a video chat.
|
|
69
|
+
|
|
70
|
+
#### Features:
|
|
71
|
+
|
|
72
|
+
- **Device Testing**: Live preview of camera feed with mirror effect
|
|
73
|
+
- **Permission Management**: Handles camera and microphone permission requests
|
|
74
|
+
- **Device Controls**: Integrated microphone and camera controls
|
|
75
|
+
- **Join Interface**: Call-to-action button to join the video chat
|
|
76
|
+
- **Responsive Design**: Works on both desktop and mobile devices
|
|
77
|
+
|
|
78
|
+
#### Usage:
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
import { HairCheck } from './hair-check';
|
|
82
|
+
|
|
83
|
+
<HairCheck
|
|
84
|
+
isJoinBtnLoading={false}
|
|
85
|
+
onJoin={() => handleJoinCall()}
|
|
86
|
+
onCancel={() => handleCancel()}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### Props:
|
|
91
|
+
|
|
92
|
+
- `isJoinBtnLoading` (boolean): Shows loading state on join button
|
|
93
|
+
- `onJoin` (function): Callback when user clicks join
|
|
94
|
+
- `onCancel` (function, optional): Callback when user cancels
|
|
95
|
+
|
|
96
|
+
#### Key Features:
|
|
97
|
+
|
|
98
|
+
- Automatic permission request flow
|
|
99
|
+
- Visual feedback for different permission states
|
|
100
|
+
- Fallback user avatar when camera is unavailable
|
|
101
|
+
- Integrated device controls for easy testing
|
|
102
|
+
- Clean, intuitive interface for pre-call setup
|
|
103
|
+
|
|
104
|
+
### Conversation 01
|
|
105
|
+
|
|
106
|
+
The `Conversation` component provides a complete video chat interface for one-to-one conversations with AI replicas, featuring main video display, self-view preview, and integrated controls.
|
|
107
|
+
|
|
108
|
+
#### Features:
|
|
109
|
+
|
|
110
|
+
- **Main Video Display**: Large video area showing the AI replica or screen share
|
|
111
|
+
- **Self-View Preview**: Small preview window showing local camera feed
|
|
112
|
+
- **Screen Sharing Support**: Automatic switching between replica video and screen share
|
|
113
|
+
- **Device Controls**: Integrated microphone, camera, and screen share controls
|
|
114
|
+
- **Error Handling**: Graceful handling of camera/microphone permission errors
|
|
115
|
+
- **Responsive Layout**: Adaptive design for different screen sizes
|
|
116
|
+
|
|
117
|
+
#### Usage:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { Conversation } from './conversation-01';
|
|
121
|
+
|
|
122
|
+
<Conversation
|
|
123
|
+
conversation={conversationData}
|
|
124
|
+
onLeave={() => handleLeaveCall()}
|
|
125
|
+
/>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Props:
|
|
129
|
+
|
|
130
|
+
- `conversation` (ConversationData): Object containing conversation details
|
|
131
|
+
- `conversation_id` (string): Unique identifier for the conversation
|
|
132
|
+
- `conversation_name` (string): Display name for the conversation
|
|
133
|
+
- `status` ('active' | 'ended' | 'error'): Current conversation status
|
|
134
|
+
- `conversation_url` (string): Daily.co room URL for joining
|
|
135
|
+
- `replica_id` (string | null): ID of the AI replica participant
|
|
136
|
+
- `persona_id` (string | null): ID of the persona being used
|
|
137
|
+
- `created_at` (string): Timestamp of conversation creation
|
|
138
|
+
- `onLeave` (function): Callback when user leaves the conversation
|
|
139
|
+
|
|
140
|
+
### Audio Wave
|
|
141
|
+
|
|
142
|
+
The `AudioWave` component provides real-time audio level visualization for video chat participants, displaying animated bars that respond to audio input levels.
|
|
143
|
+
|
|
144
|
+
#### Features:
|
|
145
|
+
|
|
146
|
+
- **Real-time Audio Visualization**: Three animated bars that respond to audio levels
|
|
147
|
+
- **Active Speaker Detection**: Visual distinction between active and inactive speakers
|
|
148
|
+
- **Performance Optimized**: Uses `requestAnimationFrame` for smooth animations
|
|
149
|
+
- **Responsive Design**: Compact circular design that fits well in video previews
|
|
150
|
+
- **Audio Level Scaling**: Intelligent volume scaling for consistent visual feedback
|
|
151
|
+
|
|
152
|
+
#### Usage:
|
|
153
|
+
|
|
154
|
+
```tsx
|
|
155
|
+
import { AudioWave } from './audio-wave';
|
|
156
|
+
|
|
157
|
+
<AudioWave id={participantId} />
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### Props:
|
|
161
|
+
|
|
162
|
+
- `id` (string): The participant's session ID to monitor audio levels for
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
overflow: hidden;
|
|
3
|
+
border: 1px solid white;
|
|
4
|
+
width: 1.5rem;
|
|
5
|
+
height: 1.5rem;
|
|
6
|
+
border-radius: 9999px;
|
|
7
|
+
will-change: transform;
|
|
8
|
+
transform: translateZ(0);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.waveContainer {
|
|
12
|
+
display: flex;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: 0.125rem;
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.bar {
|
|
21
|
+
width: 0.25rem;
|
|
22
|
+
height: 0.25rem;
|
|
23
|
+
background-color: white;
|
|
24
|
+
border-radius: 9999px;
|
|
25
|
+
transition: height 200ms ease-out;
|
|
26
|
+
will-change: height;
|
|
27
|
+
transform: translateZ(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.barInactive {
|
|
31
|
+
width: 0.25rem !important;
|
|
32
|
+
height: 0.25rem !important;
|
|
33
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import React, { useCallback, useRef, memo } from "react";
|
|
2
|
+
import { useActiveSpeakerId } from "@daily-co/daily-react";
|
|
3
|
+
import { useAudioLevelObserver } from "@daily-co/daily-react";
|
|
4
|
+
import styles from "./audio-wave.module.css";
|
|
5
|
+
|
|
6
|
+
export const AudioWave = memo(({ id }: { id: string }) => {
|
|
7
|
+
const activeSpeakerId = useActiveSpeakerId();
|
|
8
|
+
const isActiveSpeaker = activeSpeakerId === id;
|
|
9
|
+
|
|
10
|
+
const leftBarRef = useRef<HTMLDivElement>(null);
|
|
11
|
+
const centerBarRef = useRef<HTMLDivElement>(null);
|
|
12
|
+
const rightBarRef = useRef<HTMLDivElement>(null);
|
|
13
|
+
const animationFrameRef = useRef<number | undefined>(undefined);
|
|
14
|
+
|
|
15
|
+
useAudioLevelObserver(
|
|
16
|
+
id,
|
|
17
|
+
useCallback((volume) => {
|
|
18
|
+
// Cancel any pending animation frame to prevent accumulation
|
|
19
|
+
if (animationFrameRef.current) {
|
|
20
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Use requestAnimationFrame to batch DOM updates
|
|
24
|
+
animationFrameRef.current = requestAnimationFrame(() => {
|
|
25
|
+
const scaledVolume = Number(Math.max(0.01, volume).toFixed(2));
|
|
26
|
+
if (leftBarRef.current && centerBarRef.current && rightBarRef.current) {
|
|
27
|
+
leftBarRef.current.style.height = `${20 + scaledVolume * 40}%`;
|
|
28
|
+
centerBarRef.current.style.height = `${20 + scaledVolume * 130}%`;
|
|
29
|
+
rightBarRef.current.style.height = `${20 + scaledVolume * 40}%`;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}, []),
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className={styles.container}>
|
|
37
|
+
<div className={styles.waveContainer}>
|
|
38
|
+
<div
|
|
39
|
+
ref={leftBarRef}
|
|
40
|
+
className={`${styles.bar} ${!isActiveSpeaker ? styles.barInactive : ''}`}
|
|
41
|
+
/>
|
|
42
|
+
<div
|
|
43
|
+
ref={centerBarRef}
|
|
44
|
+
className={`${styles.bar} ${!isActiveSpeaker ? styles.barInactive : ''}`}
|
|
45
|
+
/>
|
|
46
|
+
<div
|
|
47
|
+
ref={rightBarRef}
|
|
48
|
+
className={`${styles.bar} ${!isActiveSpeaker ? styles.barInactive : ''}`}
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
AudioWave.displayName = 'AudioWave';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/* SelectDevice styles */
|
|
2
|
+
.selectDevice {
|
|
3
|
+
height: 3rem;
|
|
4
|
+
width: 5.5rem;
|
|
5
|
+
border-radius: 9999px;
|
|
6
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
7
|
+
padding: 0 0.75rem;
|
|
8
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
9
|
+
backdrop-filter: blur(10px);
|
|
10
|
+
color: transparent;
|
|
11
|
+
padding-right: 2rem; /* space for arrow */
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
-webkit-appearance: none;
|
|
14
|
+
-moz-appearance: none;
|
|
15
|
+
appearance: none;
|
|
16
|
+
cursor: pointer;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.selectDevice::-ms-expand {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.selectDevice option {
|
|
24
|
+
color: transparent;
|
|
25
|
+
background-color: transparent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.selectDevice:focus {
|
|
29
|
+
outline: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Device button container styles */
|
|
33
|
+
.deviceButtonContainer {
|
|
34
|
+
position: relative;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
border-radius: 9999px;
|
|
39
|
+
backdrop-filter: blur(4px);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Device button styles */
|
|
43
|
+
.deviceButton {
|
|
44
|
+
position: absolute;
|
|
45
|
+
left: 0;
|
|
46
|
+
top: 0;
|
|
47
|
+
z-index: 10;
|
|
48
|
+
height: 3rem;
|
|
49
|
+
width: 3rem;
|
|
50
|
+
border-radius: 9999px;
|
|
51
|
+
background-color: white;
|
|
52
|
+
display: flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
border: 1px solid transparent;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.deviceButtonIcon {
|
|
60
|
+
display: flex;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.deviceButton:disabled {
|
|
64
|
+
opacity: 0.5;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Screen reader only text */
|
|
68
|
+
.srOnly {
|
|
69
|
+
position: absolute;
|
|
70
|
+
width: 1px;
|
|
71
|
+
height: 1px;
|
|
72
|
+
padding: 0;
|
|
73
|
+
margin: -1px;
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
clip: rect(0, 0, 0, 0);
|
|
76
|
+
white-space: nowrap;
|
|
77
|
+
border-width: 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* Controls container */
|
|
81
|
+
.controlsContainer {
|
|
82
|
+
display: flex;
|
|
83
|
+
gap: 1rem;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.selectDeviceContainer {
|
|
89
|
+
position: relative;
|
|
90
|
+
display: flex;
|
|
91
|
+
align-items: center;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.selectArrow {
|
|
95
|
+
position: absolute;
|
|
96
|
+
right: 1rem;
|
|
97
|
+
pointer-events: none;
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
height: 100%;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.screenShareButton {
|
|
104
|
+
cursor: pointer;
|
|
105
|
+
position: relative;
|
|
106
|
+
height: 3rem;
|
|
107
|
+
width: 3rem;
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: center;
|
|
111
|
+
border-radius: 9999px;
|
|
112
|
+
backdrop-filter: blur(4px);
|
|
113
|
+
background-color: rgba(255, 255, 255, 0.2);
|
|
114
|
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
115
|
+
}
|