Skip to content

Commit 93caf69

Browse files
authored
feat(notebook): redesign between-cell adder as gutter + button with dropdown (#981)
Replace the invisible hover-only "+ Code / + Markdown" text buttons between cells with a circular "+" button in the left gutter. The button appears on hover and opens a dropdown menu to pick Code or Markdown. This is much easier to discover in longer notebooks.
1 parent 2693111 commit 93caf69

File tree

1 file changed

+43
-34
lines changed

1 file changed

+43
-34
lines changed

apps/notebook/src/components/NotebookView.tsx

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ import {
1616
verticalListSortingStrategy,
1717
} from "@dnd-kit/sortable";
1818
import { CSS as DndCSS } from "@dnd-kit/utilities";
19-
import { Plus, RotateCcw, X } from "lucide-react";
19+
import { Code, LetterText, Plus, RotateCcw, X } from "lucide-react";
2020
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
2121
import { Button } from "@/components/ui/button";
22+
import {
23+
DropdownMenu,
24+
DropdownMenuContent,
25+
DropdownMenuItem,
26+
DropdownMenuTrigger,
27+
} from "@/components/ui/dropdown-menu";
2228
import type { Runtime } from "@/hooks/useSyncedSettings";
2329
import { ErrorBoundary } from "@/lib/error-boundary";
2430
import { cn } from "@/lib/utils";
@@ -62,45 +68,48 @@ interface NotebookViewProps {
6268
onSetCellOutputsHidden?: (cellId: string, hidden: boolean) => void;
6369
}
6470

65-
function AddCellButtons({
71+
function CellAdder({
6672
afterCellId,
6773
onAdd,
6874
}: {
6975
afterCellId?: string | null;
7076
onAdd: (type: "code" | "markdown", afterCellId?: string | null) => void;
7177
}) {
7278
return (
73-
<div className="group/betweener flex h-4 w-full items-center select-none">
74-
{/* Gutter spacer - matches cell gutter: action area + ribbon */}
75-
<div className="flex h-full flex-shrink-0">
76-
<div className="w-10" />
77-
<div className="w-1 bg-gray-200 dark:bg-gray-700" />
78-
</div>
79-
{/* Content area with centered buttons */}
80-
<div className="flex-1 relative flex items-center justify-center">
81-
{/* Thin line appears on hover */}
82-
<div className="absolute inset-x-0 h-px bg-transparent group-hover/betweener:bg-border transition-colors" />
83-
{/* Buttons appear on hover */}
84-
<div className="flex items-center gap-1 opacity-0 group-hover/betweener:opacity-100 transition-opacity z-10 bg-background px-2 select-none">
85-
<Button
86-
variant="ghost"
87-
size="sm"
88-
className="h-5 gap-1 px-2 text-xs text-muted-foreground hover:text-foreground select-none"
89-
onClick={() => onAdd("code", afterCellId)}
90-
>
91-
<Plus className="h-3 w-3" />
92-
Code
93-
</Button>
94-
<Button
95-
variant="ghost"
96-
size="sm"
97-
className="h-5 gap-1 px-2 text-xs text-muted-foreground hover:text-foreground select-none"
98-
onClick={() => onAdd("markdown", afterCellId)}
99-
>
100-
<Plus className="h-3 w-3" />
101-
Markdown
102-
</Button>
79+
<div className="group/adder flex h-5 w-full items-center select-none">
80+
{/* Gutter area - holds the + button */}
81+
<div className="flex h-full flex-shrink-0 items-center">
82+
<div className="flex w-10 items-center justify-end pr-1">
83+
<DropdownMenu>
84+
<DropdownMenuTrigger asChild>
85+
<button
86+
type="button"
87+
className="flex h-4 w-4 items-center justify-center rounded-full border border-transparent text-muted-foreground/40 opacity-0 transition-all hover:border-border hover:bg-muted hover:text-foreground group-hover/adder:opacity-100"
88+
>
89+
<Plus className="h-3 w-3" />
90+
</button>
91+
</DropdownMenuTrigger>
92+
<DropdownMenuContent
93+
side="right"
94+
align="start"
95+
className="min-w-[140px]"
96+
>
97+
<DropdownMenuItem onClick={() => onAdd("code", afterCellId)}>
98+
<Code className="h-4 w-4" />
99+
Code
100+
</DropdownMenuItem>
101+
<DropdownMenuItem onClick={() => onAdd("markdown", afterCellId)}>
102+
<LetterText className="h-4 w-4" />
103+
Markdown
104+
</DropdownMenuItem>
105+
</DropdownMenuContent>
106+
</DropdownMenu>
103107
</div>
108+
<div className="w-1" />
109+
</div>
110+
{/* Thin line across content area */}
111+
<div className="flex-1 relative flex items-center">
112+
<div className="absolute inset-x-0 h-px bg-transparent group-hover/adder:bg-border transition-colors" />
104113
</div>
105114
</div>
106115
);
@@ -291,7 +300,7 @@ function SortableCell({
291300

292301
return (
293302
<div ref={setNodeRef} style={style}>
294-
{index === 0 && <AddCellButtons afterCellId={null} onAdd={onAddCell} />}
303+
{index === 0 && <CellAdder afterCellId={null} onAdd={onAddCell} />}
295304
<ErrorBoundary
296305
fallback={(error, resetErrorBoundary) => (
297306
<CellErrorFallback
@@ -309,7 +318,7 @@ function SortableCell({
309318
isDragging={isDragging}
310319
/>
311320
</ErrorBoundary>
312-
<AddCellButtons afterCellId={cellId} onAdd={onAddCell} />
321+
<CellAdder afterCellId={cellId} onAdd={onAddCell} />
313322
</div>
314323
);
315324
}

0 commit comments

Comments
 (0)