133 lines · 4.4 KB · TypeScript
RawDownload
"use client"; import { Minus, MoreVertical, Plus as PlusIcon, RotateCcw } from "lucide-react"; import { Button } from "~/components/ui/button";import { Menu, MenuItem, MenuPopup, MenuSeparator, MenuTrigger } from "~/components/ui/menu";import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; import { previewBridge } from "./previewBridge"; interface Props {  /** Active preview tab id. Tab-targeting actions are disabled without it. */  tabId: string | null;  /**   * True only after the desktop bridge has registered a `webContentsId` for   * the active tab. Tab-targeting actions throw on the desktop side until   * then; we disable those items so the menu doesn't fire silent no-ops.   */  hasWebContents: boolean;  /** Current zoom factor as a number (1.0 = 100%). */  zoomFactor: number;  /** Fixed viewport modes expose the device toolbar and resize rails. */  deviceToolbarVisible: boolean;  /** Switches between fill-panel mode and a fixed responsive viewport. */  onToggleDeviceToolbar: () => void;} /** * Three-dot menu in the chrome row. Wires Hard reload, DevTools, zoom * controls, and storage-clearing actions. Only mounted by `PreviewView` * when the desktop bridge is present, so we can call it unconditionally. */export function PreviewMoreMenu({  tabId,  hasWebContents,  zoomFactor,  deviceToolbarVisible,  onToggleDeviceToolbar,}: Props) {  if (!previewBridge) return null;  const bridge = previewBridge;  const tabDisabled = !tabId || !hasWebContents;  const callTab = (op: (tabId: string) => Promise<void>) => () => {    if (!tabId) return;    void op(tabId).catch(() => undefined);  };   const zoomLabel = `${Math.round(zoomFactor * 100)}%`;  return (    <Menu>      <Tooltip>        <TooltipTrigger          render={            <MenuTrigger              render={                <Button variant="ghost" size="icon-xs" type="button" aria-label="Preview menu" />              }            />          }        >          <MoreVertical />        </TooltipTrigger>        <TooltipPopup>More</TooltipPopup>      </Tooltip>      <MenuPopup align="end" sideOffset={6} className="min-w-56">        <MenuItem onClick={callTab(bridge.hardReload)} disabled={tabDisabled}>          Hard reload        </MenuItem>        <MenuItem onClick={callTab(bridge.openDevTools)} disabled={tabDisabled}>          Open DevTools        </MenuItem>        <MenuItem onClick={onToggleDeviceToolbar} disabled={tabDisabled}>          {deviceToolbarVisible ? "Hide device toolbar" : "Show device toolbar"}        </MenuItem>        <MenuSeparator />        {/*          Zoom row: label + inline control cluster. `closeOnClick=false`          keeps the menu open while the user clicks the +/− buttons.        */}        <MenuItem          closeOnClick={false}          onClick={(event: React.MouseEvent) => event.preventDefault()}          className="justify-between"          disabled={tabDisabled}        >          <span>Zoom</span>          <span className="flex items-center gap-1">            <Button              variant="outline"              size="icon-xs"              type="button"              onClick={callTab(bridge.zoomOut)}              aria-label="Zoom out"              disabled={tabDisabled}            >              <Minus />            </Button>            <span className="min-w-12 text-center text-xs tabular-nums text-muted-foreground">              {zoomLabel}            </span>            <Button              variant="outline"              size="icon-xs"              type="button"              onClick={callTab(bridge.zoomIn)}              aria-label="Zoom in"              disabled={tabDisabled}            >              <PlusIcon />            </Button>            <Button              variant="ghost"              size="icon-xs"              type="button"              onClick={callTab(bridge.resetZoom)}              aria-label="Reset zoom"              disabled={tabDisabled}            >              <RotateCcw />            </Button>          </span>        </MenuItem>        <MenuSeparator />        <MenuItem onClick={() => void bridge.clearCookies().catch(() => undefined)}>          Clear cookies        </MenuItem>        <MenuItem onClick={() => void bridge.clearCache().catch(() => undefined)}>          Clear cache        </MenuItem>      </MenuPopup>    </Menu>  );}