nteract
componentcell-containersource: ok · hit
CellContainer
A focus and selection wrapper for notebook cells with gutter ribbon system. Color-coded ribbons indicate cell type, with action area for play button and execution count.
npx shadcn add "https://nteract-elements.vercel.app/r/cell-container.json"free
Visual style
terminal-mono
Interaction
dropdown
Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.
Files2 · source cached 10/14/2026, 3:37:07 PM
registry/cell/CellContainer.tsx
import { forwardRef, type ReactNode } from "react";
import { cn } from "@/lib/utils";
import { type GutterColorConfig, getGutterColors } from "./gutter-colors";
interface CellContainerProps {
id: string;
cellType: string;
isFocused?: boolean;
onFocus?: () => void;
/** Content for the code/editor section (use with outputContent for segmented ribbon) */
codeContent?: ReactNode;
/** Content for the output section (renders with a different ribbon color) */
outputContent?: ReactNode;
/** Hide the output section (useful for preloading content invisibly) */
hideOutput?: boolean;
/** Legacy children prop - use codeContent/outputContent for segmented ribbon support */
children?: ReactNode;
/** Content to render in the left gutter action area (e.g., play button, execution count) */
gutterContent?: ReactNode;
/** Content to render in the right margin (e.g., cell controls, kebab menu) */
rightGutterContent?: ReactNode;
/** Custom color configuration for cell types not in defaults */
customGutterColors?: Record<string, GutterColorConfig>;
onDragStart?: (e: React.DragEvent) => void;
onDragOver?: (e: React.DragEvent) => void;
onDrop?: (e: React.DragEvent) => void;
className?: string;
}
export const CellContainer = forwardRef<HTMLDivElement, CellContainerProps>(
(
{
id,
cellType,
isFocused = false,
onFocus,
codeContent,
outputContent,
hideOutput,
children,
gutterContent,
rightGutterContent,
customGutterColors,
onDragStart,
onDragOver,
onDrop,
className,
},
ref,
) => {
const colors = getGutterColors(cellType, customGutterColors);
const ribbonColor = isFocused
? colors.ribbon.focused
: colors.ribbon.default;
const outputRibbonColor = isFocused
? colors.outputRibbon.focused
: colors.outputRibbon.default;
const bgColor = isFocused ? colors.background.focused : undefined;
// Use segmented ribbon when codeContent is provided
const useSegmentedRibbon = codeContent !== undefined;
const hasOutput = outputContent !== undefined && outputContent !== null;
return (
<div
ref={ref}
data-slot="cell-container"
data-cell-id={id}
data-cell-type={cellType}
className={cn(
"cell-container group flex transition-colors duration-150",
bgColor,
isFocused && "-mx-16 px-16",
className,
)}
onMouseDown={onFocus}
draggable={!!onDragStart}
onDragStart={onDragStart}
onDragOver={onDragOver}
onDrop={onDrop}
>
{/* Gutter area - action content only (ribbon moves to content rows for segmented) */}
<div className="flex w-10 flex-shrink-0 flex-col items-end justify-start gap-0.5 pr-1 pt-3">
{gutterContent}
</div>
{/* Cell content with ribbon */}
{useSegmentedRibbon ? (
<div className="flex min-w-0 flex-1 flex-col">
{/* Code row - ribbon + content together so heights match */}
<div className="flex">
<div
className={cn(
"w-1 transition-colors duration-150",
ribbonColor,
)}
/>
<div className="min-w-0 flex-1 py-3 pl-6 pr-3">{codeContent}</div>
</div>
{/* Output row - ribbon + content together */}
{hasOutput && (
<div className={cn("flex", hideOutput && "hidden")}>
<div
className={cn(
"w-1 transition-colors duration-150",
outputRibbonColor,
)}
/>
<div
className={cn(
"min-w-0 flex-1 py-2 pl-6 pr-3 transition-opacity duration-150",
!isFocused && "opacity-70",
)}
>
{outputContent}
</div>
</div>
)}
</div>
) : (
/* Legacy layout - ribbon + content side by side */
<div className="flex min-w-0 flex-1">
<div
className={cn(
"w-1 self-stretch transition-colors duration-150",
ribbonColor,
)}
/>
<div className="min-w-0 flex-1 py-3 pl-6 pr-3">{children}</div>
</div>
)}
{/* Right margin - pt-3 aligns with left gutter, appears on hover/focus */}
{rightGutterContent && (
<div
className={cn(
"flex w-10 flex-shrink-0 flex-col items-center gap-1 pt-3",
"opacity-100 transition-opacity duration-150",
"sm:opacity-0 sm:group-hover:opacity-100 sm:focus-within:opacity-100",
isFocused && "sm:opacity-100",
)}
>
{rightGutterContent}
</div>
)}
</div>
);
},
);
CellContainer.displayName = "CellContainer";
registry/cell/gutter-colors.ts
export interface GutterColorConfig {
ribbon: {
default: string;
focused: string;
};
outputRibbon: {
default: string;
focused: string;
};
background: {
focused: string;
};
}
/**
* Default gutter colors for built-in cell types.
* Colors are designed to match the existing CellTypeButton color scheme.
*/
export const defaultGutterColors: Record<string, GutterColorConfig> = {
code: {
ribbon: {
default: "bg-gray-200 dark:bg-gray-700",
focused: "bg-sky-400 dark:bg-sky-600",
},
outputRibbon: {
default:
"bg-gradient-to-b from-gray-200/60 to-gray-200 dark:from-gray-700/60 dark:to-gray-700",
focused:
"bg-gradient-to-b from-sky-400/60 to-sky-400 dark:from-sky-600/60 dark:to-sky-600",
},
background: {
focused: "bg-sky-50/20 dark:bg-sky-900/10",
},
},
markdown: {
ribbon: {
default: "bg-gray-200 dark:bg-gray-700",
focused: "bg-emerald-400 dark:bg-emerald-600",
},
outputRibbon: {
default:
"bg-gradient-to-b from-gray-200/60 to-gray-200 dark:from-gray-700/60 dark:to-gray-700",
focused:
"bg-gradient-to-b from-emerald-400/60 to-emerald-400 dark:from-emerald-600/60 dark:to-emerald-600",
},
background: {
focused: "bg-emerald-50/20 dark:bg-emerald-900/10",
},
},
sql: {
ribbon: {
default: "bg-amber-200 dark:bg-amber-800",
focused: "bg-amber-400 dark:bg-amber-600",
},
outputRibbon: {
default:
"bg-gradient-to-b from-amber-200/60 to-amber-200 dark:from-amber-800/60 dark:to-amber-800",
focused:
"bg-gradient-to-b from-amber-400/60 to-amber-400 dark:from-amber-600/60 dark:to-amber-600",
},
background: {
focused: "bg-amber-50/20 dark:bg-amber-900/10",
},
},
ai: {
ribbon: {
default: "bg-purple-200 dark:bg-purple-800",
focused: "bg-purple-400 dark:bg-purple-600",
},
outputRibbon: {
default:
"bg-gradient-to-b from-purple-200/60 to-purple-200 dark:from-purple-800/60 dark:to-purple-800",
focused:
"bg-gradient-to-b from-purple-400/60 to-purple-400 dark:from-purple-600/60 dark:to-purple-600",
},
background: {
focused: "bg-purple-50/20 dark:bg-purple-900/10",
},
},
raw: {
ribbon: {
default: "bg-rose-200 dark:bg-rose-800",
focused: "bg-rose-400 dark:bg-rose-600",
},
outputRibbon: {
default:
"bg-gradient-to-b from-rose-200/60 to-rose-200 dark:from-rose-800/60 dark:to-rose-800",
focused:
"bg-gradient-to-b from-rose-400/60 to-rose-400 dark:from-rose-600/60 dark:to-rose-600",
},
background: {
focused: "bg-rose-50/20 dark:bg-rose-900/10",
},
},
};
/**
* Fallback colors for unknown cell types.
* Uses neutral gray styling.
*/
export const fallbackGutterColors: GutterColorConfig = {
ribbon: {
default: "bg-gray-200 dark:bg-gray-700",
focused: "bg-gray-400 dark:bg-gray-500",
},
outputRibbon: {
default:
"bg-gradient-to-b from-gray-200/60 to-gray-200 dark:from-gray-700/60 dark:to-gray-700",
focused:
"bg-gradient-to-b from-gray-400/60 to-gray-400 dark:from-gray-500/60 dark:to-gray-500",
},
background: {
focused: "bg-gray-50/50 dark:bg-gray-900/30",
},
};
/**
* Get gutter colors for a cell type.
* Falls back to neutral gray for unknown types.
*
* @param cellType - The cell type to get colors for
* @param customColors - Optional custom color overrides
*/
export function getGutterColors(
cellType: string,
customColors?: Record<string, GutterColorConfig>,
): GutterColorConfig {
// Check custom colors first
if (customColors?.[cellType]) {
return customColors[cellType];
}
// Then check defaults
if (defaultGutterColors[cellType]) {
return defaultGutterColors[cellType];
}
// Fall back to neutral
return fallbackGutterColors;
}