import { createElement, isValidElement, type ReactNode } from 'react';
import { cn } from '../../../lib';
import { DropdownMenuShortcut } from '../dropdown-menu';
import type { MenuRowBase } from './types';
/**
* Resolve the `icon` field to a node. `icon` may be:
* - a ready React element (``)
* - a component *type*: a plain function component OR a
* `forwardRef`/`memo` object (lucide-react icons are forwardRef
* objects, so a bare `typeof === 'function'` check misses them).
*/
function renderIcon(icon: MenuRowBase['icon']): ReactNode {
if (!icon) return null;
if (isValidElement(icon)) return icon;
const isFunctionComponent = typeof icon === 'function';
const isObjectComponent =
typeof icon === 'object' && icon !== null && '$$typeof' in icon;
if (isFunctionComponent || isObjectComponent) {
return createElement(
icon as React.ComponentType<{ className?: string }>,
{ className: 'size-4 shrink-0' },
);
}
return icon as ReactNode;
}
export interface MenuRowProps {
icon?: MenuRowBase['icon'];
label: ReactNode;
description?: ReactNode;
shortcut?: string;
showDescription?: boolean;
}
/**
* Inner layout shared by every menu row kind: leading icon, label with
* an optional muted description line, trailing shortcut. Sits inside the
* Radix `Item` / `SubTrigger` / `CheckboxItem` wrappers.
*/
export function MenuRow({
icon,
label,
description,
shortcut,
showDescription = true,
}: MenuRowProps) {
const iconNode = renderIcon(icon);
return (
<>
{iconNode}
{label}
{showDescription && description ? (
{description}
) : null}
{shortcut ? {shortcut} : null}
>
);
}
/** Row layout for radio/checkbox rows — the Radix wrapper reserves the
* indicator column (`pl-8`) so the icon is placed inside the flex flow
* after that reserved space rather than as a leading element. */
export function MenuIndicatorRow({
icon,
label,
description,
shortcut,
showDescription = true,
}: MenuRowProps) {
const iconNode = renderIcon(icon);
return (
{iconNode}
{label}
{showDescription && description ? (
{description}
) : null}
{shortcut ? {shortcut} : null}
);
}