Skip to content

chore(site): refactor filter component to be more extendable #13688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Jul 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Apply review comments
  • Loading branch information
BrunoQuaresma committed Jun 28, 2024
commit 0d83bc8772ad743c577fdd84d38c76d1608786f5
3 changes: 1 addition & 2 deletions site/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ const sizeStyles = {
xl: {
width: 48,
height: 48,
// Should never be overrided
fontSize: "24px !important",
fontSize: 24,
},
} satisfies Record<string, Interpolation<Theme>>;

Expand Down
17 changes: 8 additions & 9 deletions site/src/components/Filter/SelectFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import visuallyHidden from "@mui/utils/visuallyHidden";
import { useState, type FC, type ReactNode } from "react";
import { Loader } from "components/Loader/Loader";
import {
Expand Down Expand Up @@ -32,7 +31,7 @@ export type SelectFilterProps = {
emptyText?: string;
onSelect: (option: SelectFilterOption | undefined) => void;
// SelectFilterSearch element
search?: ReactNode;
selectFilterSearch?: ReactNode;
};

export const SelectFilter: FC<SelectFilterProps> = ({
Expand All @@ -42,7 +41,7 @@ export const SelectFilter: FC<SelectFilterProps> = ({
onSelect,
placeholder,
emptyText,
search,
selectFilterSearch,
}) => {
const [open, setOpen] = useState(false);

Expand All @@ -52,24 +51,24 @@ export const SelectFilter: FC<SelectFilterProps> = ({
<SelectMenuButton
startIcon={selectedOption?.startIcon}
css={{ width: BASE_WIDTH }}
aria-label={label}
>
{selectedOption?.label ?? placeholder}
<span css={{ ...visuallyHidden }}>{label}</span>
</SelectMenuButton>
</SelectMenuTrigger>
<SelectMenuContent
horizontal="right"
css={{
"& .MuiPaper-root": {
// When including search, we aim for the width to be as wide as
// possible.
width: search ? "100%" : undefined,
// When including selectFilterSearch, we aim for the width to be as
// wide as possible.
width: selectFilterSearch ? "100%" : undefined,
maxWidth: POPOVER_WIDTH,
minWidth: BASE_WIDTH,
},
}}
>
{search}
{selectFilterSearch}
{options ? (
options.length > 0 ? (
<SelectMenuList>
Expand Down Expand Up @@ -103,7 +102,7 @@ export const SelectFilter: FC<SelectFilterProps> = ({
lineHeight: 1,
})}
>
{emptyText ?? "No options found"}
{emptyText || "No options found"}
</div>
)
) : (
Expand Down
32 changes: 32 additions & 0 deletions site/src/components/SelectMenu/SelectMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,35 @@ export const LongButtonText: Story = {
);
},
};

export const NoSelectedOption: Story = {
render: function SelectMenuRender() {
const opts = options(50);

return (
<SelectMenu>
<SelectMenuTrigger>
<SelectMenuButton css={{ width: 200 }}>All users</SelectMenuButton>
</SelectMenuTrigger>
<SelectMenuContent>
<SelectMenuSearch onChange={() => {}} />
<SelectMenuList>
{opts.map((o) => (
<SelectMenuItem key={o}>
<SelectMenuIcon>
<UserAvatar size="xs" username={o} />
</SelectMenuIcon>
{o}
</SelectMenuItem>
))}
</SelectMenuList>
</SelectMenuContent>
</SelectMenu>
);
},
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole("button");
await userEvent.click(button);
},
};
39 changes: 24 additions & 15 deletions site/src/components/SelectMenu/SelectMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
Children,
isValidElement,
type HTMLProps,
type ReactElement,
useMemo,
} from "react";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import {
Expand Down Expand Up @@ -75,14 +77,13 @@ export const SelectMenuSearch: FC<SearchFieldProps> = (props) => {
fullWidth
size="medium"
css={(theme) => ({
borderBottom: `1px solid ${theme.palette.divider}`,
"& input": {
fontSize: 14,
},

"& fieldset": {
border: 0,
borderRadius: 0,
borderBottom: `1px solid ${theme.palette.divider} !important`,
},
"& .MuiInputBase-root": {
padding: `12px ${SIDE_PADDING}px`,
Expand All @@ -97,27 +98,35 @@ export const SelectMenuSearch: FC<SearchFieldProps> = (props) => {
};

export const SelectMenuList: FC<MenuListProps> = (props) => {
const items = Children.toArray(props.children);
type ItemType = (typeof items)[number];
const selectedAsFirst = (a: ItemType, b: ItemType) => {
if (
!isValidElement<MenuItemProps>(a) ||
!isValidElement<MenuItemProps>(b)
) {
throw new Error(
"SelectMenuList children must be SelectMenuItem components",
);
const items = useMemo(() => {
let children = Children.toArray(props.children);
if (!children.every(isValidElement)) {
throw new Error("SelectMenuList only accepts MenuItem children");
}
return a.props.selected ? -1 : 0;
};
items.sort(selectedAsFirst);
children = moveSelectedElementToFirst(
children as ReactElement<MenuItemProps>[],
);
return children;
}, [props.children]);
return (
<MenuList css={{ maxHeight: 480 }} {...props}>
{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>> = (props) => {
return <div css={{ marginRight: 16 }} {...props} />;
};
Expand Down