diff --git a/js/src/components/ui/toggle.tsx b/js/src/components/ui/toggle.tsx new file mode 100644 index 0000000..2284a77 --- /dev/null +++ b/js/src/components/ui/toggle.tsx @@ -0,0 +1,44 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Toggle as TogglePrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +const toggleVariants = cva( + "group/toggle inline-flex items-center justify-center gap-1 rounded-lg text-sm font-medium whitespace-nowrap transition-all outline-none hover:bg-muted hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 aria-pressed:bg-muted data-[state=on]:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-transparent", + outline: "border border-input bg-transparent hover:bg-muted", + }, + size: { + default: "h-8 min-w-8 px-2", + sm: "h-7 min-w-7 rounded-[min(var(--radius-md),12px)] px-1.5 text-[0.8rem]", + lg: "h-9 min-w-9 px-2.5", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Toggle({ + className, + variant = "default", + size = "default", + ...props +}: React.ComponentProps & + VariantProps) { + return ( + + ) +} + +export { Toggle, toggleVariants } diff --git a/js/src/features/layout.tsx b/js/src/features/layout.tsx index aac81ea..958564e 100644 --- a/js/src/features/layout.tsx +++ b/js/src/features/layout.tsx @@ -1,7 +1,9 @@ import { WindowControls } from "@/components/window-controls"; import { Button } from "@/components/ui/button"; -import { Outlet, useLocation, useNavigate, useParams } from "react-router-dom"; -import { Home, Settings } from "lucide-react"; +import { useNavigate, useParams } from "react-router-dom"; +import { Home, Settings, Volume2, VolumeOff } from "lucide-react"; +import { Toggle } from "@/components/ui/toggle"; +import { useAutoplayStore } from "@/stores/autoplay-store"; import { Breadcrumb, BreadcrumbItem, @@ -53,6 +55,23 @@ function NetworkBreadcrumbContent({ networkId }: { networkId: string }) { ); } +function AutoplayToggle() { + const muted = useAutoplayStore((s) => s.muted); + const toggleMuted = useAutoplayStore((s) => s.toggleMuted); + + return ( + + {muted ? : } + + ); +} + function TopBar() { const navigate = useNavigate(); const { networkId, "*": rest } = useParams(); @@ -116,6 +135,8 @@ function TopBar() {
+ + + ); + + const senderBar = ( +
+ + + {senderInitials} + + + {senderName} +
+ ); + return ( - -
- {/* Close button */} - - - {isVideo ? ( + {isVideo ? ( +
+ {closeButton}
+ ) : ( +
+ {closeButton}
- - - Playing voice note... - -
- )} -
- +
+ )} +
); } diff --git a/js/src/stores/autoplay-store.ts b/js/src/stores/autoplay-store.ts index b23fbb1..b974e5f 100644 --- a/js/src/stores/autoplay-store.ts +++ b/js/src/stores/autoplay-store.ts @@ -8,15 +8,32 @@ interface AutoplayState { activeParticle: MediaParticle | null; /** The stream this particle belongs to (for click-to-navigate) */ streamId: string | null; - /** Play a media particle, preempting any currently playing */ + /** When true, incoming play requests are ignored */ + muted: boolean; + /** Play a media particle, preempting any currently playing. No-op when muted. */ play: (particle: MediaParticle, streamId: string) => void; /** Stop autoplay and clear state */ stop: () => void; + /** Toggle muted state */ + toggleMuted: () => void; } -export const useAutoplayStore = create((set) => ({ +export const useAutoplayStore = create((set, get) => ({ activeParticle: null, streamId: null, - play: (particle, streamId) => set({ activeParticle: particle, streamId }), + muted: false, + play: (particle, streamId) => { + if (get().muted) return; + set({ activeParticle: particle, streamId }); + }, stop: () => set({ activeParticle: null, streamId: null }), + toggleMuted: () => { + const wasMuted = get().muted; + // If muting, also stop any current playback + if (!wasMuted) { + set({ muted: true, activeParticle: null, streamId: null }); + } else { + set({ muted: false }); + } + }, }));