chore(site): remove unused SelectMenu component (#27800)

`SelectMenu` was only referenced by its Storybook story. Nothing in the
app consumed it.

Delete the component and story so we drop the unused MUI `MenuItem` /
`MenuList` wrapper and avoid treating dead demui code as a shared
primitive.
This commit is contained in:
Jake Howell
2026-08-04 13:22:52 +10:00
committed by GitHub
parent d899f02e42
commit cce5a21933
3 changed files with 0 additions and 284 deletions
-2
View File
@@ -11,8 +11,6 @@ export type PopoverContentProps = React.ComponentPropsWithRef<
disablePortal?: boolean;
};
export type PopoverTriggerProps = PopoverPrimitive.PopoverTriggerProps;
export const Popover = PopoverPrimitive.Root;
export const PopoverTrigger = PopoverPrimitive.Trigger;
@@ -1,133 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { action } from "storybook/actions";
import { userEvent, within } from "storybook/test";
import { Avatar } from "#/components/Avatar/Avatar";
import { withDesktopViewport } from "#/testHelpers/storybook";
import {
SelectMenu,
SelectMenuButton,
SelectMenuContent,
SelectMenuIcon,
SelectMenuItem,
SelectMenuList,
SelectMenuSearch,
SelectMenuTrigger,
} from "./SelectMenu";
const meta: Meta<typeof SelectMenu> = {
title: "components/SelectMenu",
component: SelectMenu,
render: function SelectMenuRender() {
const opts = options(50);
const selectedOpt = opts[20];
return (
<SelectMenu>
<SelectMenuTrigger>
<SelectMenuButton
startIcon={<Avatar size="sm" fallback={selectedOpt} />}
>
{selectedOpt}
</SelectMenuButton>
</SelectMenuTrigger>
<SelectMenuContent>
<SelectMenuSearch value="" onChange={() => {}} />
<SelectMenuList>
{opts.map((o) => (
<SelectMenuItem key={o} selected={o === selectedOpt}>
<SelectMenuIcon>
<Avatar size="sm" fallback={o} />
</SelectMenuIcon>
{o}
</SelectMenuItem>
))}
</SelectMenuList>
</SelectMenuContent>
</SelectMenu>
);
},
decorators: [withDesktopViewport],
};
function options(n: number): string[] {
return Array.from({ length: n }, (_, i) => `Item ${i + 1}`);
}
export default meta;
type Story = StoryObj<typeof SelectMenu>;
export const Closed: Story = {};
export const Open: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button");
await userEvent.click(button);
},
};
export const LongButtonText: Story = {
render: function SelectMenuRender() {
const longOption = "Very long text that should be truncated";
const opts = [...options(50), longOption];
const selectedOpt = longOption;
return (
<SelectMenu>
<SelectMenuTrigger>
<SelectMenuButton
className="w-48"
startIcon={<Avatar size="sm" fallback={selectedOpt} />}
>
{selectedOpt}
</SelectMenuButton>
</SelectMenuTrigger>
<SelectMenuContent>
<SelectMenuSearch value="" onChange={() => {}} />
<SelectMenuList>
{opts.map((o) => (
<SelectMenuItem key={o} selected={o === selectedOpt}>
<SelectMenuIcon>
<Avatar size="sm" fallback={o} />
</SelectMenuIcon>
{o}
</SelectMenuItem>
))}
</SelectMenuList>
</SelectMenuContent>
</SelectMenu>
);
},
};
export const NoSelectedOption: Story = {
render: function SelectMenuRender() {
const opts = options(50);
return (
<SelectMenu>
<SelectMenuTrigger>
<SelectMenuButton className="w-48">All users</SelectMenuButton>
</SelectMenuTrigger>
<SelectMenuContent>
<SelectMenuSearch value="" onChange={action("search")} />
<SelectMenuList>
{opts.map((o) => (
<SelectMenuItem key={o}>
<SelectMenuIcon>
<Avatar size="sm" fallback={o} />
</SelectMenuIcon>
{o}
</SelectMenuItem>
))}
</SelectMenuList>
</SelectMenuContent>
</SelectMenu>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button");
await userEvent.click(button);
},
};
@@ -1,149 +0,0 @@
import MenuItem, { type MenuItemProps } from "@mui/material/MenuItem";
import MenuList, { type MenuListProps } from "@mui/material/MenuList";
import { CheckIcon } from "lucide-react";
import {
Children,
type FC,
type HTMLProps,
isValidElement,
type ReactElement,
useMemo,
} from "react";
import { ChevronDownIcon } from "#/components/AnimatedIcons/ChevronDown";
import { Button, type ButtonProps } from "#/components/Button/Button";
import {
Popover,
PopoverContent,
type PopoverContentProps,
PopoverTrigger,
type PopoverTriggerProps,
} from "#/components/Popover/Popover";
import {
SearchField,
type SearchFieldProps,
} from "#/components/SearchField/SearchField";
import { cn } from "#/utils/cn";
export const SelectMenu = Popover;
export const SelectMenuTrigger: FC<PopoverTriggerProps> = (props) => {
return <PopoverTrigger asChild {...props} />;
};
export const SelectMenuContent: FC<PopoverContentProps> = (props) => {
return (
<PopoverContent
{...props}
className={cn(
"w-auto bg-surface-secondary border-surface-quaternary overflow-y-auto text-sm",
props.className,
)}
/>
);
};
type SelectMenuButtonProps = ButtonProps & {
startIcon?: React.ReactNode;
};
export const SelectMenuButton: React.FC<SelectMenuButtonProps> = ({
className,
startIcon,
children,
...props
}) => {
return (
<Button
variant="outline"
size="lg"
// Shrink padding right slightly to account for visual weight of
// the chevron
className={cn("flex flex-row gap-2 pr-1.5", className)}
{...props}
>
{startIcon}
<span className="text-left block overflow-hidden text-ellipsis flex-grow">
{children}
</span>
<ChevronDownIcon />
</Button>
);
};
export const SelectMenuSearch: FC<SearchFieldProps> = ({
className,
...props
}) => {
return (
<SearchField
className={cn(
"w-full border border-solid border-border [&_input]:text-sm",
className,
)}
autoFocus
{...props}
/>
);
};
export const SelectMenuList: FC<MenuListProps> = ({
children,
className,
...attrs
}) => {
const items = useMemo(() => {
let items = Children.toArray(children);
if (!items.every(isValidElement)) {
throw new Error("SelectMenuList only accepts MenuItem children");
}
items = moveSelectedElementToFirst(items as ReactElement<MenuItemProps>[]);
return items;
}, [children]);
return (
<MenuList className={cn("max-h-[480px]", className)} {...attrs}>
{items}
</MenuList>
);
};
function moveSelectedElementToFirst(items: ReactElement<MenuItemProps>[]) {
const selectedElement = items.find((i) => i.props.selected);
if (!selectedElement) {
return items;
}
const selectedElementIndex = items.indexOf(selectedElement);
const newItems = items.slice();
newItems.splice(selectedElementIndex, 1);
newItems.unshift(selectedElement);
return newItems;
}
export const SelectMenuIcon: FC<HTMLProps<HTMLDivElement>> = ({
children,
className,
...attrs
}) => {
return (
<div className={cn("mr-4", className)} {...attrs}>
{children}
</div>
);
};
export const SelectMenuItem: FC<MenuItemProps> = ({
children,
className,
selected,
...attrs
}) => {
return (
<MenuItem
className={cn("text-sm gap-0 leading-none py-3 px-4", className)}
{...attrs}
>
{children}
{selected && <CheckIcon className="size-icon-xs ml-auto" />}
</MenuItem>
);
};