/* Copyright 2026 Marimo. All rights reserved. */
import { DatabaseIcon, DiamondPlusIcon, PlusIcon } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/editor/inputs/Inputs";
import { MinimalHotkeys } from "@/components/shortcuts/renderShortcut";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { maybeAddMarimoImport } from "@/core/cells/add-missing-import";
import { useCellActions } from "@/core/cells/cells";
import { LanguageAdapters } from "@/core/codemirror/language/LanguageAdapters";
import { MARKDOWN_INITIAL_HIDE_CODE } from "@/core/codemirror/language/languages/markdown";
import {
getConnectionTooltip,
isAppInteractionDisabled,
} from "@/core/websocket/connection-utils";
import type { WebSocketState } from "@/core/websocket/types";
import { cn } from "@/utils/cn";
import { Tooltip } from "../../ui/tooltip";
import { MarkdownIcon, PythonIcon } from "./code/icons";
export const CreateCellButton = ({
connectionState,
onClick,
tooltipContent,
oneClickShortcut,
}: {
connectionState: WebSocketState;
tooltipContent: React.ReactNode;
onClick: ((opts: { code: string; hideCode?: boolean }) => void) | undefined;
oneClickShortcut: "shift" | "mod";
}) => {
const { createNewCell, addSetupCellIfDoesntExist } = useCellActions();
const shortcut = `${oneClickShortcut}-Click`;
const [open, setOpen] = useState(false);
const [justOpened, setJustOpened] = useState(false);
const baseTooltipContent =
getConnectionTooltip(connectionState) || tooltipContent;
const finalTooltipContent = isAppInteractionDisabled(connectionState) ? (
baseTooltipContent
) : (
{baseTooltipContent}
{}{" "}
for other cell types
);
const addPythonCell = () => {
onClick?.({ code: "" });
};
// NB: When adding the marimo import for markdown and SQL, we run it
// automatically regardless of whether autoinstantiate or lazy execution is
// enabled; the user experience is confusing otherwise (how does the user
// know they need to run import marimo as mo. first?).
const addMarkdownCell = () => {
maybeAddMarimoImport({ autoInstantiate: true, createNewCell });
onClick?.({
code: LanguageAdapters.markdown.defaultCode,
hideCode: MARKDOWN_INITIAL_HIDE_CODE,
});
};
const addSQLCell = () => {
maybeAddMarimoImport({ autoInstantiate: true, createNewCell });
onClick?.({ code: LanguageAdapters.sql.defaultCode });
};
const addSetupCell = () => {
addSetupCellIfDoesntExist({});
};
const renderIcon = (icon: React.ReactNode) => {
return {icon}
;
};
const openDropdown = () => {
setOpen(true);
setJustOpened(true);
// Allow interactions after a brief delay to prevent the dropdown items immediately being clicked
setTimeout(() => setJustOpened(false), 200);
};
// We use onPointerDownCapture (not onPointerDown) to intercept events in
// capture phase before Radix's DropdownMenuTrigger sees them. Radix ignores
// Ctrl+Click (likely to avoid interfering with browser), so we bypass its
// trigger entirely and manage the dropdown's open state ourselves.
const handlePointerDownCapture = (e: React.MouseEvent) => {
// Ignore right-clicks, handled by onContextMenuCapture
if (e.button === 2) {
return;
}
// Don't propagate event to Radix
e.preventDefault();
e.stopPropagation();
const hasModifier =
oneClickShortcut === "shift" ? e.shiftKey : e.metaKey || e.ctrlKey;
if (hasModifier) {
openDropdown();
} else {
addPythonCell();
}
};
const handleContextMenu = (e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
openDropdown();
};
const handleOpenChange = (isOpen: boolean) => {
setOpen(isOpen);
if (isOpen) {
setJustOpened(true);
// Allow interactions after a brief delay
setTimeout(() => setJustOpened(false), 200);
}
};
const handleFirstItemClick = (e: React.MouseEvent) => {
// Hack to prevent the first item from being clicked when the dropdown is opened
if (justOpened) {
e.preventDefault();
e.stopPropagation();
return;
}
addPythonCell();
};
return (
{renderIcon()}
Python cell
{renderIcon()}
Markdown cell
{renderIcon()}
SQL cell
{renderIcon()}
Setup cell
);
};