elements
lifecycle
browser
- useAudio
- useBattery
- useBluetooth
- useBreakpoints
- useBroadcastChannel
- useBrowserLocation
- useClipboard
- useCopy
- useCssVar
- useDisplayMedia
- useDocumentEvent
- useDocumentTitle
- useDocumentVisibility
- useEventListener
- useEventSource
- useEyeDropper
- useFavicon
- useFileSystemAccess
- useFps
- useFullscreen
- useGamepad
- useGeolocation
- useMeasure
- useMediaControls
- useMediaQuery
- useMemory
- useNetwork
- useObjectUrl
- useOnline
- useOtpCredential
- usePermission
- usePictureInPicture
- usePointerLock
- usePostMessage
- useRaf
- useShare
- useSpeechRecognition
- useSpeechSynthesis
- useSticky
utilities
state
user
sensors
Building open-source React hooks — the reactuse demo reel
SC
siberiacancode12.4K subscribers
import { useClickOutside, useDisclosure, usePictureInPicture } from '@siberiacancode/reactuse';
import {
FlagIcon,
MessageCircleIcon,
MoreHorizontalIcon,
PictureInPicture2Icon,
Share2Icon,
ThumbsUpIcon
} from 'lucide-react';
const formatCount = (count: number) => {
if (count < 1000) return count.toString();
return `${(count / 1000).toFixed(1)}K`;
};
const Demo = () => {
const pictureInPicture = usePictureInPicture();
const menu = useDisclosure();
const menuRef = useClickOutside<HTMLDivElement>(() => menu.close());
const onPictureInPicture = () => {
menu.close();
pictureInPicture.toggle();
};
return (
<section className='flex w-full max-w-md flex-col gap-3 p-4'>
<div className='border-border relative overflow-hidden rounded-xl border'>
<video
controls
ref={pictureInPicture.ref}
className='aspect-video w-full'
src='/new/videos/waves.mp4'
/>
</div>
<h3 className='text-foreground text-sm leading-snug font-semibold'>
Building open-source React hooks — the reactuse demo reel
</h3>
<div className='flex items-center justify-between gap-3'>
<div className='flex items-center gap-2'>
<div className='flex size-9 shrink-0 items-center justify-center rounded-full bg-gradient-to-br from-neutral-700 to-neutral-900 text-xs font-semibold text-white'>
SC
</div>
<div className='flex flex-col'>
<span className='text-foreground text-sm font-medium'>siberiacancode</span>
<span className='text-muted-foreground text-xs'>12.4K subscribers</span>
</div>
</div>
<div className='flex items-center gap-2'>
<button className='rounded-full!' data-size='sm' data-variant='secondary' type='button'>
<ThumbsUpIcon className='size-4' />
{formatCount(1284)}
</button>
<button className='rounded-full!' data-size='sm' data-variant='secondary' type='button'>
<MessageCircleIcon className='size-4' />
{formatCount(96)}
</button>
<div ref={menuRef} className='relative'>
<button
aria-label='More'
className='rounded-full!'
data-size='icon-sm'
data-variant='secondary'
type='button'
onClick={() => menu.toggle()}
>
<MoreHorizontalIcon className='size-4' />
</button>
{menu.opened && (
<div
className='absolute right-0 bottom-full mb-1.5 w-44'
data-slot='dropdown-menu-content'
>
{pictureInPicture.supported && (
<div data-slot='dropdown-menu-item' onClick={onPictureInPicture}>
<PictureInPicture2Icon />
{pictureInPicture.opened ? 'Exit Picture-in-Picture' : 'Picture-in-Picture'}
</div>
)}
<div data-slot='dropdown-menu-item' onClick={menu.close}>
<Share2Icon />
Share
</div>
<div data-slot='dropdown-menu-item' data-variant='destructive' onClick={menu.close}>
<FlagIcon />
Report
</div>
</div>
)}
</div>
</div>
</div>
</section>
);
};
export default Demo;
This hook uses window.PictureInPicture browser api to provide enhanced functionality. Make sure to check for compatibility with different browsers when using this api
Installation
pnpm add @siberiacancode/reactuseUsage
const { open, supported, enter, exit, toggle } = usePictureInPicture(videoRef);
// or
const { ref, open, supported, enter, exit, toggle } = usePictureInPicture();Type Declarations
import type { HookTarget } from '@/utils/helpers';
import type { StateRef } from '../useRefState/useRefState';
export interface UsePictureInPictureOptions {
/** The callback when Picture-in-Picture mode is entered */
onEnter?: () => void;
/** The callback when Picture-in-Picture mode is exited */
onExit?: () => void;
}
export interface UsePictureInPictureReturn {
/** Whether Picture-in-Picture mode is currently active */
opened: boolean;
/** Whether Picture-in-Picture mode is supported by the browser */
supported: boolean;
/** Request to enter Picture-in-Picture mode */
enter: () => Promise<void>;
/** Request to exit Picture-in-Picture mode */
exit: () => Promise<void>;
/** Toggle Picture-in-Picture mode */
toggle: () => Promise<void>;
}
export interface UsePictureInPicture {
(target: HookTarget, options?: UsePictureInPictureOptions): UsePictureInPictureReturn;
(options?: UsePictureInPictureOptions): UsePictureInPictureReturn & {
ref: StateRef<HTMLVideoElement>;
};
}API
Parameters
| Name | Type | Default | Note |
|---|---|---|---|
| target | HookTarget | - | The target video element |
| options.onEnter | () => void | - | Callback when Picture-in-Picture mode is entered |
| options.onExit | () => void | - | Callback when Picture-in-Picture mode is exited |
Returns
UsePictureInPictureReturnParameters
| Name | Type | Default | Note |
|---|---|---|---|
| options.onEnter | () => void | - | Callback when Picture-in-Picture mode is entered |
| options.onExit | () => void | - | Callback when Picture-in-Picture mode is exited |
Returns
UsePictureInPictureReturn & { ref: StateRef<HTMLVideoElement> }Contributors
ddebabin
Last updated on