setup basic components

This commit is contained in:
bluemoehre
2026-06-30 23:02:06 +02:00
parent 98649496bd
commit 7121715af9
43 changed files with 6523 additions and 63 deletions
@@ -0,0 +1,156 @@
import { Fragment, type ComponentProps, useMemo, useState } from 'react'
import { type VariantProps, cva } from 'class-variance-authority'
import { Track } from 'livekit-client'
import { MicIcon, MicOffIcon, MonitorUpIcon, MonitorOffIcon, LoaderIcon, VideoIcon, VideoOffIcon } from 'lucide-react'
import { Toggle } from '@/components/ui/toggle'
import { cn } from '@/lib/utils'
export const agentTrackToggleVariants = cva(['size-9'], {
variants: {
size: {
default: 'h-9 min-w-9 px-2',
sm: 'h-8 min-w-8 px-1.5',
lg: 'h-10 min-w-10 px-2.5',
},
variant: {
default: [
'data-[state=off]:bg-destructive/10 data-[state=off]:text-destructive',
'data-[state=off]:hover:bg-destructive/15',
'data-[state=off]:focus-visible:ring-destructive/30',
'data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
'data-[state=on]:hover:bg-foreground/10',
],
outline: [
'data-[state=off]:border-destructive/20 data-[state=off]:bg-destructive/10 data-[state=off]:text-destructive',
'data-[state=off]:hover:bg-destructive/15 data-[state=off]:hover:text-destructive',
'data-[state=off]:focus:text-destructive',
'data-[state=off]:focus-visible:border-destructive data-[state=off]:focus-visible:ring-destructive/30',
'data-[state=on]:hover:border-foreground/12 data-[state=on]:hover:bg-foreground/10',
'dark:data-[state=on]:hover:bg-foreground/10',
],
},
},
defaultVariants: {
variant: 'default',
},
})
function getSourceIcon(source: Track.Source, enabled: boolean, pending = false) {
if (pending) {
return LoaderIcon
}
switch (source) {
case Track.Source.Microphone:
return enabled ? MicIcon : MicOffIcon
case Track.Source.Camera:
return enabled ? VideoIcon : VideoOffIcon
case Track.Source.ScreenShare:
return enabled ? MonitorUpIcon : MonitorOffIcon
default:
return Fragment
}
}
/**
* Props for the AgentTrackToggle component.
*/
export type AgentTrackToggleProps = VariantProps<typeof agentTrackToggleVariants> &
ComponentProps<'button'> & {
/**
* The size of the toggle.
*/
size?: 'sm' | 'default' | 'lg'
/**
* The variant of the toggle.
* @defaultValue 'default'
*/
variant?: 'default' | 'outline'
/**
* The track source to toggle (Microphone, Camera, or ScreenShare).
*/
source: 'camera' | 'microphone' | 'screen_share'
/**
* Whether the toggle is in a pending/loading state.
* When true, displays a loading spinner icon.
* @defaultValue false
*/
pending?: boolean
/**
* Whether the toggle is currently pressed/enabled.
* @defaultValue false
*/
pressed?: boolean
/**
* The default pressed state when uncontrolled.
* @defaultValue false
*/
defaultPressed?: boolean
/**
* Callback fired when the pressed state changes.
*/
onPressedChange?: (pressed: boolean) => void
}
/**
* A toggle button for controlling track publishing state.
* Displays appropriate icons based on the track source and state.
*
* @extends ComponentProps<'button'>
*
* @example
* ```tsx
* <AgentTrackToggle
* source={Track.Source.Microphone}
* pressed={isMicEnabled}
* onPressedChange={(pressed) => setMicEnabled(pressed)}
* />
* ```
*/
export function AgentTrackToggle({
size = 'default',
variant = 'default',
source,
pending = false,
pressed,
defaultPressed = false,
className,
onPressedChange,
...props
}: AgentTrackToggleProps) {
const [uncontrolledPressed, setUncontrolledPressed] = useState(defaultPressed ?? false)
const isControlled = pressed !== undefined
const resolvedPressed = useMemo(
() => (isControlled ? pressed : uncontrolledPressed) ?? false,
[isControlled, pressed, uncontrolledPressed]
)
const IconComponent = getSourceIcon(source as Track.Source, resolvedPressed, pending)
const handlePressedChange = (nextPressed: boolean) => {
if (!isControlled) {
setUncontrolledPressed(nextPressed)
}
onPressedChange?.(nextPressed)
}
return (
<Toggle
size={size}
variant={variant}
pressed={isControlled ? pressed : undefined}
defaultPressed={isControlled ? undefined : defaultPressed}
aria-label={`Toggle ${source}`}
onPressedChange={handlePressedChange}
className={cn(
agentTrackToggleVariants({
size,
variant: variant ?? 'default',
className,
})
)}
{...props}
>
<IconComponent className={cn(pending && 'animate-spin')} />
{props.children}
</Toggle>
)
}