diff --git a/package.json b/package.json
index 9255dd2be..2145e6bb2 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@mirrorstack-ai/web-ui-kit",
"packageManager": "pnpm@10.29.3",
- "version": "0.5.11",
+ "version": "0.5.12",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
diff --git a/src/components/ui/navigation/dropdown-menu/DropdownMenu.stories.tsx b/src/components/ui/navigation/dropdown-menu/DropdownMenu.stories.tsx
index 51429069d..7d9bc24c7 100644
--- a/src/components/ui/navigation/dropdown-menu/DropdownMenu.stories.tsx
+++ b/src/components/ui/navigation/dropdown-menu/DropdownMenu.stories.tsx
@@ -32,6 +32,38 @@ export const Playground: Story = {
},
};
+/**
+ * An item's `icon` can be a custom node (e.g. a brand-logo SVG that Material
+ * Symbols doesn't ship) instead of an icon name — size it to ~the item
+ * icon size and use `currentColor`. `external: true` adds a trailing
+ * open-in-new glyph for items that open in a new tab.
+ */
+export const CustomIconAndExternal: Story = {
+ args: {
+ trigger: (
+
+ ),
+ notchRadius: 8,
+ notchInverseRadius: 6,
+ items: [
+ {
+ id: "repo",
+ label: "Repository",
+ external: true,
+ icon: (
+
+ ),
+ },
+ { id: "docs", label: "Docs", icon: "menu_book", external: true },
+ { type: "separator" as const },
+ { id: "settings", label: "Settings", icon: "settings" },
+ ],
+ onSelect: (item) => console.log("Selected:", item.id),
+ },
+};
+
/**
* The notch head tuned to a small trigger: `notchWidth`/`notchHeight` size the
* tab to the icon button, and `notchRadius`/`notchInverseRadius` round the head
diff --git a/src/components/ui/navigation/dropdown-menu/DropdownMenu.tsx b/src/components/ui/navigation/dropdown-menu/DropdownMenu.tsx
index e6966add4..07444d072 100644
--- a/src/components/ui/navigation/dropdown-menu/DropdownMenu.tsx
+++ b/src/components/ui/navigation/dropdown-menu/DropdownMenu.tsx
@@ -35,7 +35,14 @@ export const meta: ComponentMeta = {
export interface DropdownMenuItem {
id: string;
label: string;
- icon?: string;
+ /** Leading icon: a Material Symbols name, or a custom node (e.g. a brand-logo
+ * SVG that Material Symbols can't provide). A custom node should size itself
+ * to ~`size` and use `currentColor` so it inherits the muted icon colour. */
+ icon?: string | ReactNode;
+ /** Marks an item that opens externally (a new tab): renders a trailing
+ * open-in-new glyph as the affordance. Performing the navigation is still the
+ * caller's job in `onSelect`. */
+ external?: boolean;
/** `"danger"` is a deprecated alias for `"error"`. */
variant?: "default" | "error" | "danger";
disabled?: boolean;
@@ -283,17 +290,34 @@ export function DropdownMenu({
if (!item.disabled) setActiveIndex(index);
}}
>
- {item.icon && (
+ {item.icon != null &&
+ (typeof item.icon === "string" ? (
+
+ ) : (
+
+ {item.icon}
+
+ ))}
+ {item.label}
+ {item.external && (
)}
- {item.label}
);
})}