mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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:
@@ -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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user