Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 89 additions & 33 deletions apps/desktop/src/shared/main/header-listen-button.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { ChevronDownIcon } from "lucide-react";
import { useCallback, useState } from "react";
import { ChevronDown } from "lucide-react";
import {
type MouseEvent,
useCallback,
useEffect,
useRef,
useState,
} from "react";

import { Button } from "@hypr/ui/components/ui/button";
import {
Popover,
PopoverAnchor,
PopoverContent,
PopoverTrigger,
} from "@hypr/ui/components/ui/popover";
Expand Down Expand Up @@ -84,15 +91,50 @@ function useHeaderListenState() {

function HeaderListenButtonInner() {
const { isDisabled, warningMessage } = useHeaderListenState();
const [menuWidth, setMenuWidth] = useState<number | null>(null);
const containerRef = useRef<HTMLDivElement | null>(null);
const handleClick = useNewNoteAndListen();
const handleUpload = useNewNoteAndUpload();
const openNew = useTabs((state) => state.openNew);
const [open, setOpen] = useState(false);

useEffect(() => {
const node = containerRef.current;

if (!node) {
return;
}

const updateWidth = () => {
setMenuWidth(node.offsetWidth);
};

updateWidth();

const observer = new ResizeObserver(updateWidth);
observer.observe(node);

return () => {
observer.disconnect();
};
}, []);

const handleConfigure = useCallback(() => {
openNew({ type: "ai", state: { tab: "transcription" } });
}, [openNew]);

const handleMenuMouseDown = useCallback((event: MouseEvent) => {
if (event.button === 2) {
event.preventDefault();
}
}, []);

const handleOpenMenu = useCallback((event: MouseEvent) => {
event.preventDefault();
event.stopPropagation();
setOpen(true);
}, []);

const handleUploadAudio = useCallback(() => {
setOpen(false);
handleUpload("audio").catch((error) => {
Expand All @@ -111,15 +153,17 @@ function HeaderListenButtonInner() {
<button
type="button"
onClick={handleClick}
onMouseDown={handleMenuMouseDown}
onContextMenu={handleOpenMenu}
disabled={isDisabled}
className={cn([
"inline-flex items-center justify-center rounded-full text-sm font-medium text-white",
"inline-flex items-center justify-center rounded-full text-sm font-medium text-white select-none",
"gap-2",
"h-8 pr-8 pl-4",
"border-2 border-stone-600 bg-stone-800",
"transition-all duration-200 ease-out",
"hover:bg-stone-700",
"disabled:pointer-events-none disabled:opacity-50",
"disabled:opacity-50",
])}
>
<RecordingIcon />
Expand All @@ -130,54 +174,68 @@ function HeaderListenButtonInner() {
const chevron = (
<button
type="button"
className="absolute top-1/2 right-1.5 z-10 -translate-y-1/2 cursor-pointer text-white/70 transition-colors hover:text-white"
onClick={(e) => {
e.stopPropagation();
className="absolute inset-y-0 right-0 z-10 inline-flex w-9 cursor-pointer items-center justify-center rounded-r-full bg-transparent text-white/70 transition-colors select-none hover:text-white"
onMouseDown={handleMenuMouseDown}
onClick={(event) => {
event.stopPropagation();
}}
>
<ChevronDownIcon className="size-3.5" />
<ChevronDown className="size-3.5" />
<span className="sr-only">More options</span>
</button>
);

const content = (
return (
<Popover open={open} onOpenChange={setOpen}>
<div className="relative flex items-center">
{warningMessage ? (
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent side="bottom">
<ActionableTooltipContent
message={warningMessage}
action={{
label: "Configure",
handleClick: handleConfigure,
}}
/>
</TooltipContent>
</Tooltip>
) : (
button
)}
<PopoverTrigger asChild>{chevron}</PopoverTrigger>
</div>
<PopoverAnchor asChild>
<div
ref={containerRef}
className="relative flex items-center select-none"
onMouseDownCapture={handleMenuMouseDown}
onContextMenu={handleOpenMenu}
>
{warningMessage ? (
<Tooltip delayDuration={0}>
<TooltipTrigger asChild>
<span className="inline-flex">{button}</span>
</TooltipTrigger>
<TooltipContent side="bottom">
<ActionableTooltipContent
message={warningMessage}
action={{
label: "Configure",
handleClick: handleConfigure,
}}
/>
</TooltipContent>
</Tooltip>
) : (
button
)}
<PopoverTrigger asChild>{chevron}</PopoverTrigger>
</div>
</PopoverAnchor>
<PopoverContent
side="bottom"
align="end"
sideOffset={4}
className="w-43 rounded-xl p-1.5"
style={menuWidth ? { width: menuWidth } : undefined}
className={cn([
"overflow-hidden rounded-[1.25rem] border border-white/70 p-1.5 ring-1 ring-black/6 outline-none",
"bg-white/68 text-stone-900 shadow-[inset_0_1px_0_rgba(255,255,255,0.7),0_24px_48px_-24px_rgba(48,44,40,0.52),0_8px_18px_rgba(255,255,255,0.28)] backdrop-blur-md backdrop-saturate-150",
])}
>
<div className="flex flex-col gap-1">
<Button
variant="ghost"
className="h-9 justify-center px-3 whitespace-nowrap"
className="h-9 w-full justify-center rounded-[0.95rem] px-3 text-sm text-stone-900 shadow-none hover:bg-black/6 hover:text-stone-950 focus-visible:ring-0 focus-visible:outline-none"
onClick={handleUploadAudio}
>
<span className="text-sm">Upload audio</span>
</Button>
<Button
variant="ghost"
className="h-9 justify-center px-3 whitespace-nowrap"
className="h-9 w-full justify-center rounded-[0.95rem] px-3 text-sm text-stone-900 shadow-none hover:bg-black/6 hover:text-stone-950 focus-visible:ring-0 focus-visible:outline-none"
onClick={handleUploadTranscript}
>
<span className="text-sm">Upload transcript</span>
Expand All @@ -186,6 +244,4 @@ function HeaderListenButtonInner() {
</PopoverContent>
</Popover>
);

return content;
}
Loading