Skip to content

Commit dbabfba

Browse files
committed
main 🧊 add new button for device list
1 parent 2490026 commit dbabfba

42 files changed

Lines changed: 456 additions & 325 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/core/src/hooks/useDeviceList/useDeviceList.demo.tsx

Lines changed: 112 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
11
import { useClickOutside, useDeviceList, useDisclosure } from '@siberiacancode/reactuse';
2-
import { CheckIcon, MicIcon } from 'lucide-react';
2+
import { CameraIcon, CheckIcon, MicIcon } from 'lucide-react';
33
import { useState } from 'react';
44

55
const Demo = () => {
6-
const dropdownMenu = useDisclosure();
7-
const dropdownRef = useClickOutside<HTMLDivElement>(() => dropdownMenu.close());
6+
const microphoneMenu = useDisclosure();
7+
const cameraMenu = useDisclosure();
8+
const microphoneRef = useClickOutside<HTMLDivElement>(() => microphoneMenu.close());
9+
const cameraRef = useClickOutside<HTMLDivElement>(() => cameraMenu.close());
810

9-
const [deviceId, setDeviceId] = useState<string>();
10-
const deviceList = useDeviceList((list) => setDeviceId(list[0].deviceId));
11+
const [selectedDeviceIds, setSelectedDeviceIds] = useState<{
12+
audio?: string;
13+
video?: string;
14+
}>({});
15+
const deviceList = useDeviceList((list) => {
16+
setSelectedDeviceIds({
17+
audio: list.find((device) => device.kind === 'audioinput')?.deviceId,
18+
video: list.find((device) => device.kind === 'videoinput')?.deviceId
19+
});
20+
});
1121

12-
const onOpen = () => {
13-
dropdownMenu.toggle();
14-
};
22+
const onMicrophoneOpen = () => microphoneMenu.toggle();
23+
const onCameraOpen = () => cameraMenu.toggle();
24+
25+
const onDeviceSelect = (device: MediaDeviceInfo, menu: 'audio' | 'video') => {
26+
setSelectedDeviceIds((current) => ({ ...current, [menu]: device.deviceId }));
1527

16-
const onDeviceSelect = (device: MediaDeviceInfo) => {
17-
setDeviceId(device.deviceId);
18-
dropdownMenu.close();
28+
if (menu === 'audio') {
29+
microphoneMenu.close();
30+
return;
31+
}
32+
33+
cameraMenu.close();
1934
};
2035

2136
if (!deviceList.supported) {
@@ -36,47 +51,94 @@ const Demo = () => {
3651

3752
return (
3853
<section className='flex w-full max-w-sm justify-center p-8'>
39-
<div ref={dropdownRef} className='relative'>
40-
<button
41-
aria-expanded={dropdownMenu.opened}
42-
aria-label='Select microphone'
43-
className='rounded-full!'
44-
data-size='icon'
45-
data-variant='outline'
46-
disabled={!deviceList.audioInputs.length}
47-
type='button'
48-
onClick={onOpen}
49-
>
50-
<MicIcon className='text-foreground size-5' />
51-
</button>
54+
<div className='flex items-center gap-3'>
55+
<div ref={microphoneRef} className='relative'>
56+
<button
57+
aria-expanded={microphoneMenu.opened}
58+
aria-label='Select microphone'
59+
className='rounded-full!'
60+
data-size='icon'
61+
data-variant='outline'
62+
disabled={!deviceList.audioInputs.length}
63+
type='button'
64+
onClick={onMicrophoneOpen}
65+
>
66+
<MicIcon className='text-foreground size-5' />
67+
</button>
68+
69+
{microphoneMenu.opened && (
70+
<div
71+
className='absolute top-full left-1/2 z-10 mt-3 w-64 -translate-x-1/2'
72+
data-slot='dropdown-menu-content'
73+
>
74+
<div className='text-muted-foreground px-2 py-1.5 text-xs font-medium'>
75+
Microphones
76+
</div>
77+
78+
{!deviceList.audioInputs.length && (
79+
<div data-slot='dropdown-menu-item'>No microphones found</div>
80+
)}
5281

53-
{dropdownMenu.opened && (
54-
<div
55-
className='absolute top-full left-1/2 z-10 mt-3 w-64 -translate-x-1/2'
56-
data-slot='dropdown-menu-content'
82+
{deviceList.audioInputs.map((device, index) => {
83+
const selected = device.deviceId === selectedDeviceIds.audio;
84+
85+
return (
86+
<div
87+
key={device.deviceId}
88+
data-slot='dropdown-menu-item'
89+
onClick={() => onDeviceSelect(device, 'audio')}
90+
>
91+
<span className='truncate'>{device.label || `Microphone ${index + 1}`}</span>
92+
{selected && <CheckIcon className='size-4 shrink-0' />}
93+
</div>
94+
);
95+
})}
96+
</div>
97+
)}
98+
</div>
99+
100+
<div ref={cameraRef} className='relative'>
101+
<button
102+
aria-expanded={cameraMenu.opened}
103+
aria-label='Select camera'
104+
className='rounded-full!'
105+
data-size='icon'
106+
data-variant='outline'
107+
disabled={!deviceList.videoInputs.length}
108+
type='button'
109+
onClick={onCameraOpen}
57110
>
58-
<div className='text-muted-foreground px-2 py-1.5 text-xs font-medium'>Microphones</div>
59-
60-
{!deviceList.audioInputs.length && (
61-
<div data-slot='dropdown-menu-item'>No microphones found</div>
62-
)}
63-
64-
{deviceList.audioInputs.map((device, index) => {
65-
const selected = device.deviceId === deviceId;
66-
67-
return (
68-
<div
69-
key={device.deviceId}
70-
data-slot='dropdown-menu-item'
71-
onClick={() => onDeviceSelect(device)}
72-
>
73-
<span className='truncate'>{device.label || `Microphone ${index + 1}`}</span>
74-
{selected && <CheckIcon className='size-4 shrink-0' />}
75-
</div>
76-
);
77-
})}
78-
</div>
79-
)}
111+
<CameraIcon className='text-foreground size-5' />
112+
</button>
113+
114+
{cameraMenu.opened && (
115+
<div
116+
className='absolute top-full left-1/2 z-10 mt-3 w-64 -translate-x-1/2'
117+
data-slot='dropdown-menu-content'
118+
>
119+
<div className='text-muted-foreground px-2 py-1.5 text-xs font-medium'>Cameras</div>
120+
121+
{!deviceList.videoInputs.length && (
122+
<div data-slot='dropdown-menu-item'>No cameras found</div>
123+
)}
124+
125+
{deviceList.videoInputs.map((device, index) => {
126+
const selected = device.deviceId === selectedDeviceIds.video;
127+
128+
return (
129+
<div
130+
key={device.deviceId}
131+
data-slot='dropdown-menu-item'
132+
onClick={() => onDeviceSelect(device, 'video')}
133+
>
134+
<span className='truncate'>{device.label || `Camera ${index + 1}`}</span>
135+
{selected && <CheckIcon className='size-4 shrink-0' />}
136+
</div>
137+
);
138+
})}
139+
</div>
140+
)}
141+
</div>
80142
</div>
81143
</section>
82144
);

packages/newdocs/content/docs/(root)/functions.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,4 @@ A quick list of available functions.
178178
- [useWindowFocus](/functions/hooks/useWindowFocus): Hook that provides the current focus state of the window
179179
- [useWindowScroll](/functions/hooks/useWindowScroll): Hook that manages the window scroll position
180180
- [useWindowSize](/functions/hooks/useWindowSize): Hook that manages a window size
181-
- [useWizard](/functions/hooks/useWizard): Hook that manages a wizard
181+
- [useWizard](/functions/hooks/useWizard): Hook that manages a wizard

packages/newdocs/content/docs/integrations/webcam.mdx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
title: webcam
33
type: integration
44
description: Webcam React is a powerful library that provides easy integration of webcam.
5-
lastModifiedTime: 1783232868440
5+
lastModifiedTime: 1783262470784
66
---
77

88
import metadata from './webcam.meta.json';
99

10-
<FunctionBanner code={metadata.demo} type={metadata.type} name={metadata.name} language='tsx' />
10+
<FunctionBanner code={metadata.demo} type={metadata.type} name={metadata.name} language="tsx" />
1111

1212
## Installation
1313

@@ -21,54 +21,54 @@ npm install @webcam/react
2121

2222
Flips the video and the captured snapshots horizontally. Enabled by default.
2323

24-
<FunctionCode code={metadata.props[0].code} language='tsx' />
24+
<FunctionCode code={metadata.props[0].code} language="tsx" />
2525

2626
### muted
2727

2828
Mutes the underlying video element. Enabled by default.
2929

30-
<FunctionCode code={metadata.props[1].code} language='tsx' />
30+
<FunctionCode code={metadata.props[1].code} language="tsx" />
3131

3232
### stream
3333

3434
Displays an external media stream instead of requesting one internally.
3535

36-
<FunctionCode code={metadata.props[2].code} language='tsx' />
36+
<FunctionCode code={metadata.props[2].code} language="tsx" />
3737

3838
### requestTimeLimit
3939

4040
Aborts the getUserMedia request after the given number of milliseconds.
4141

42-
<FunctionCode code={metadata.props[3].code} language='tsx' />
42+
<FunctionCode code={metadata.props[3].code} language="tsx" />
4343

4444
### videoConstraints
4545

4646
Constraints passed to getUserMedia for the video track.
4747

48-
<FunctionCode code={metadata.props[4].code} language='tsx' />
48+
<FunctionCode code={metadata.props[4].code} language="tsx" />
4949

5050
### audioConstraints
5151

5252
Constraints passed to getUserMedia for the audio track.
5353

54-
<FunctionCode code={metadata.props[5].code} language='tsx' />
54+
<FunctionCode code={metadata.props[5].code} language="tsx" />
5555

5656
### children
5757

5858
A node, or a render prop exposing the element, getCanvas and getSnapshot helpers.
5959

60-
<FunctionCode code={metadata.props[6].code} language='tsx' />
60+
<FunctionCode code={metadata.props[6].code} language="tsx" />
6161

6262
### ref
6363

6464
A ref to the underlying video element.
6565

66-
<FunctionCode code={metadata.props[7].code} language='tsx' />
66+
<FunctionCode code={metadata.props[7].code} language="tsx" />
6767

6868
### events
6969

7070
Hook into the media stream lifecycle to react to permission requests, track when the camera goes live or shuts off, and handle failures.
7171

72-
<FunctionCode code={metadata.props[8].code} language='tsx' />
73-
## API Reference See the (webcam API Reference)[https://github.com/siberiacancode/webcam] for more
74-
information
72+
<FunctionCode code={metadata.props[8].code} language="tsx" />
73+
## API Reference
74+
See the (webcam API Reference)[https://github.com/siberiacancode/webcam] for more information

packages/newdocs/content/docs/integrations/webcam.meta.json

Lines changed: 2 additions & 2 deletions
Large diffs are not rendered by default.

packages/newdocs/content/functions/helpers/cn.mdx

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ lastModifiedTime: 1782218362000
1111

1212
import metadata from './cn.meta.json';
1313

14-
<FunctionBanner
15-
browserapi={metadata.browserapi}
16-
code={metadata.demo}
17-
type={metadata.type}
18-
name={metadata.name}
19-
language='tsx'
20-
/>
14+
<FunctionBanner browserapi={metadata.browserapi} code={metadata.demo} type={metadata.type} name={metadata.name} language="tsx" />
2115

2216
## Installation
2317

@@ -28,15 +22,25 @@ import metadata from './cn.meta.json';
2822
<TabsTrigger value='manual'>Manual</TabsTrigger>
2923
</TabsList>
3024
<TabsContent value='library'>
31-
```packages-install npm install @siberiacancode/reactuse ```
25+
```packages-install
26+
npm install @siberiacancode/reactuse
27+
```
28+
</TabsContent>
29+
<TabsContent value='cli'>
30+
```packages-install
31+
npx useverse@latest add cn
32+
```
3233
</TabsContent>
33-
<TabsContent value='cli'>```packages-install npx useverse@latest add cn ```</TabsContent>
3434
<TabsContent value='manual'>
3535
<Steps>
36-
<Step>Copy and paste the following code into your project.</Step>
37-
<FunctionCode code={metadata.code} language='tsx' />
38-
<Step>Update the import paths to match your project setup.</Step>
39-
</Steps>
36+
<Step>
37+
Copy and paste the following code into your project.
38+
</Step>
39+
<FunctionCode code={metadata.code} language="tsx" />
40+
<Step>
41+
Update the import paths to match your project setup.
42+
</Step>
43+
</Steps>
4044
</TabsContent>
4145
</FunctionTabs>
4246

@@ -48,12 +52,12 @@ cn('btn', ['btn-primary', { 'btn-disabled': disabled }]);
4852

4953
## Type Declarations
5054

51-
<FunctionCode code={metadata.typeDeclarations} language='tsx' />
55+
<FunctionCode code={metadata.typeDeclarations} language="tsx" />
5256

5357
## API
5458

5559
<FunctionApi apiParameters={metadata.apiParameters} />
5660

5761
## Contributors
5862

59-
<FunctionContributors contributors={metadata.contributors} />
63+
<FunctionContributors contributors={metadata.contributors} />

packages/newdocs/content/functions/hooks/useCycleList.mdx

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,7 @@ lastModifiedTime: 1782457293000
1111

1212
import metadata from './useCycleList.meta.json';
1313

14-
<FunctionBanner
15-
browserapi={metadata.browserapi}
16-
code={metadata.demo}
17-
type={metadata.type}
18-
name={metadata.name}
19-
language='tsx'
20-
/>
14+
<FunctionBanner browserapi={metadata.browserapi} code={metadata.demo} type={metadata.type} name={metadata.name} language="tsx" />
2115

2216
## Installation
2317

@@ -28,17 +22,25 @@ import metadata from './useCycleList.meta.json';
2822
<TabsTrigger value='manual'>Manual</TabsTrigger>
2923
</TabsList>
3024
<TabsContent value='library'>
31-
```packages-install npm install @siberiacancode/reactuse ```
25+
```packages-install
26+
npm install @siberiacancode/reactuse
27+
```
3228
</TabsContent>
3329
<TabsContent value='cli'>
34-
```packages-install npx useverse@latest add useCycleList ```
30+
```packages-install
31+
npx useverse@latest add useCycleList
32+
```
3533
</TabsContent>
3634
<TabsContent value='manual'>
3735
<Steps>
38-
<Step>Copy and paste the following code into your project.</Step>
39-
<FunctionCode code={metadata.code} language='tsx' />
40-
<Step>Update the import paths to match your project setup.</Step>
41-
</Steps>
36+
<Step>
37+
Copy and paste the following code into your project.
38+
</Step>
39+
<FunctionCode code={metadata.code} language="tsx" />
40+
<Step>
41+
Update the import paths to match your project setup.
42+
</Step>
43+
</Steps>
4244
</TabsContent>
4345
</FunctionTabs>
4446

@@ -50,12 +52,12 @@ const { value, index, next, prev, go } = useCycleList(['Dog', 'Cat', 'Lizard']);
5052

5153
## Type Declarations
5254

53-
<FunctionCode code={metadata.typeDeclarations} language='tsx' />
55+
<FunctionCode code={metadata.typeDeclarations} language="tsx" />
5456

5557
## API
5658

5759
<FunctionApi apiParameters={metadata.apiParameters} />
5860

5961
## Contributors
6062

61-
<FunctionContributors contributors={metadata.contributors} />
63+
<FunctionContributors contributors={metadata.contributors} />

0 commit comments

Comments
 (0)