Skip to content

Commit 6f8253c

Browse files
authored
Migrated menu to react-aria-components (#6504)
* Migrated menu to react-aria-components
1 parent a9ac73b commit 6f8253c

File tree

9 files changed

+236
-172
lines changed

9 files changed

+236
-172
lines changed

changelog/+menu.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improved the design and accessibility of the menu in the object view

frontend/app/src/app/router.tsx

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,45 @@ import { constructPath } from "@/shared/api/rest/fetch";
99
import { ErrorBoundaryRouter } from "@/shared/components/errors/error-boundary-router";
1010
import { ReactRouter7Adapter } from "@/shared/libs/use-query-params";
1111
import queryString from "query-string";
12-
import { Navigate, Outlet, UIMatch, createBrowserRouter } from "react-router";
12+
import { RouterProvider } from "react-aria-components";
13+
import {
14+
Navigate,
15+
type NavigateOptions,
16+
Outlet,
17+
UIMatch,
18+
createBrowserRouter,
19+
useHref,
20+
useNavigate,
21+
} from "react-router";
1322
import { Slide, ToastContainer } from "react-toastify";
1423
import { QueryParamProvider } from "use-query-params";
1524

16-
export const router = createBrowserRouter([
17-
{
18-
path: "",
19-
errorElement: <ErrorBoundaryRouter />,
20-
element: (
21-
<QueryParamProvider
22-
adapter={ReactRouter7Adapter}
23-
options={{
24-
searchStringToObject: queryString.parse,
25-
objectToSearchString: queryString.stringify,
26-
}}
27-
>
25+
declare module "react-aria-components" {
26+
interface RouterConfig {
27+
routerOptions: NavigateOptions;
28+
}
29+
}
30+
31+
function useAbsoluteHref(path: string) {
32+
const relative = useHref(path);
33+
if (path.startsWith("https://") || path.startsWith("http://") || path.startsWith("mailto:")) {
34+
return path;
35+
}
36+
return relative;
37+
}
38+
39+
function RootProviders({ children }: { children: React.ReactNode }) {
40+
const navigate = useNavigate();
41+
42+
return (
43+
<QueryParamProvider
44+
adapter={ReactRouter7Adapter}
45+
options={{
46+
searchStringToObject: queryString.parse,
47+
objectToSearchString: queryString.stringify,
48+
}}
49+
>
50+
<RouterProvider navigate={navigate} useHref={useAbsoluteHref}>
2851
<ToastContainer
2952
hideProgressBar={true}
3053
transition={Slide}
@@ -33,8 +56,20 @@ export const router = createBrowserRouter([
3356
newestOnTop
3457
position="bottom-right"
3558
/>
59+
{children}
60+
</RouterProvider>
61+
</QueryParamProvider>
62+
);
63+
}
64+
65+
export const router = createBrowserRouter([
66+
{
67+
path: "",
68+
errorElement: <ErrorBoundaryRouter />,
69+
element: (
70+
<RootProviders>
3671
<Outlet />
37-
</QueryParamProvider>
72+
</RootProviders>
3873
),
3974
children: [
4075
{

frontend/app/src/entities/artifacts/ui/artifact-details-menu.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { CopyToClipboard } from "@/shared/components/buttons/copy-to-clipboard";
1+
import { CopyToClipboardMenuItem } from "@/shared/components/aria/menu";
22
import {
33
ObjectDetailsButton,
44
ObjectDetailsButtonProps,
55
} from "@/shared/components/menu/object-details-button";
6-
import { DropdownMenuItem } from "@/shared/components/ui/dropdown-menu";
76

87
export interface ArtifactDetailsMenuProps extends ObjectDetailsButtonProps {
98
checksum?: string;
@@ -14,27 +13,11 @@ export function ArtifactDetailsMenu({ id, hfid, checksum, storageId }: ArtifactD
1413
return (
1514
<ObjectDetailsButton id={id} hfid={hfid}>
1615
{checksum && (
17-
<DropdownMenuItem className="p-0">
18-
<CopyToClipboard
19-
size={"default"}
20-
className="grow justify-start gap-2 p-2"
21-
text={checksum}
22-
>
23-
Copy Checksum
24-
</CopyToClipboard>
25-
</DropdownMenuItem>
16+
<CopyToClipboardMenuItem textToCopy={checksum}>Copy Checksum</CopyToClipboardMenuItem>
2617
)}
2718

2819
{storageId && (
29-
<DropdownMenuItem className="p-0">
30-
<CopyToClipboard
31-
size={"default"}
32-
className="grow justify-start gap-2 p-2"
33-
text={storageId}
34-
>
35-
Copy Storage ID
36-
</CopyToClipboard>
37-
</DropdownMenuItem>
20+
<CopyToClipboardMenuItem textToCopy={storageId}>Copy Storage ID</CopyToClipboardMenuItem>
3821
)}
3922
</ObjectDetailsButton>
4023
);

frontend/app/src/entities/repository/ui/repository-action-menu.tsx

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,27 @@ import {
66
} from "@/entities/repository/api/actions";
77
import { useMutation } from "@/shared/api/graphql/useQuery";
88
import { queryClient } from "@/shared/api/rest/client";
9+
import {
10+
Menu,
11+
MenuItem,
12+
MenuPopover,
13+
MenuSection,
14+
MenuTrigger,
15+
} from "@/shared/components/aria/menu";
916
import { Button, ButtonWithTooltip } from "@/shared/components/buttons/button-primitive";
1017
import { ALERT_TYPES, Alert } from "@/shared/components/ui/alert";
11-
import {
12-
DropdownMenu,
13-
DropdownMenuContent,
14-
DropdownMenuItem,
15-
DropdownMenuTrigger,
16-
} from "@/shared/components/ui/dropdown-menu";
1718
import { Dialog } from "@headlessui/react";
1819
import { useState } from "react";
20+
import { Pressable } from "react-aria-components";
1921
import { toast } from "react-toastify";
2022

2123
const RepositoryActionMenu = ({ repositoryId }: { repositoryId: string }) => {
2224
const [isOpen, setIsOpen] = useState(false);
2325

2426
return (
2527
<>
26-
<DropdownMenu>
27-
<DropdownMenuTrigger asChild>
28+
<MenuTrigger>
29+
<Pressable>
2830
<ButtonWithTooltip
2931
tooltipContent="More"
3032
tooltipEnabled
@@ -34,17 +36,21 @@ const RepositoryActionMenu = ({ repositoryId }: { repositoryId: string }) => {
3436
>
3537
<Icon icon="mdi:dots-vertical" className="text-custom-blue-900 text-lg p-4" />
3638
</ButtonWithTooltip>
37-
</DropdownMenuTrigger>
38-
39-
<DropdownMenuContent align="end">
40-
<DropdownMenuItem onClick={() => setIsOpen(true)}>
41-
<Icon icon="mdi:access-point" className="text-lg" />
42-
Check connectivity
43-
</DropdownMenuItem>
44-
45-
<ReimportLastCommitAction repositoryId={repositoryId} />
46-
</DropdownMenuContent>
47-
</DropdownMenu>
39+
</Pressable>
40+
41+
<MenuPopover placement="bottom end">
42+
<Menu>
43+
<MenuSection>
44+
<MenuItem onAction={() => setIsOpen(true)}>
45+
<Icon icon="mdi:access-point" className="text-lg" />
46+
Check connectivity
47+
</MenuItem>
48+
49+
<ReimportLastCommitAction repositoryId={repositoryId} />
50+
</MenuSection>
51+
</Menu>
52+
</MenuPopover>
53+
</MenuTrigger>
4854

4955
<CheckConnectivityModal repositoryId={repositoryId} isOpen={isOpen} setIsOpen={setIsOpen} />
5056
</>
@@ -156,10 +162,10 @@ const ReimportLastCommitAction = ({ repositoryId }: { repositoryId: string }) =>
156162
});
157163

158164
return (
159-
<DropdownMenuItem onClick={() => reimportLastCommit()}>
165+
<MenuItem onAction={() => reimportLastCommit()}>
160166
<Icon icon="mdi:reload" className="text-lg" />
161167
Reimport last commit
162-
</DropdownMenuItem>
168+
</MenuItem>
163169
);
164170
};
165171

frontend/app/src/entities/schema/ui/schema-help-menu.tsx

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@ import { INFRAHUB_DOC_LOCAL } from "@/config/config";
22
import { MENU_EXCLUDELIST } from "@/config/constants";
33
import { getObjectDetailsUrl } from "@/entities/nodes/utils";
44
import { ModelSchema } from "@/entities/schema/types";
5-
import { Button } from "@/shared/components/buttons/button-primitive";
65
import {
7-
DropdownMenu,
8-
DropdownMenuContent,
9-
DropdownMenuItem,
10-
DropdownMenuTrigger,
11-
} from "@/shared/components/ui/dropdown-menu";
6+
Menu,
7+
MenuItem,
8+
MenuPopover,
9+
MenuSection,
10+
MenuTrigger,
11+
} from "@/shared/components/aria/menu";
12+
import { Button } from "@/shared/components/buttons/button-primitive";
1213
import { Icon } from "@iconify-icon/react";
13-
import { Link } from "react-router";
14+
import { Pressable } from "react-aria-components";
1415

1516
type SchemaHelpMenuProps = {
1617
schema: ModelSchema;
@@ -24,29 +25,32 @@ export const SchemaHelpMenu = ({ schema }: SchemaHelpMenuProps) => {
2425
: INFRAHUB_DOC_LOCAL;
2526

2627
return (
27-
<DropdownMenu>
28-
<DropdownMenuTrigger asChild>
28+
<MenuTrigger>
29+
<Pressable>
2930
<Button size="icon" variant="outline" data-testid="schema-help-menu-trigger">
3031
?
3132
</Button>
32-
</DropdownMenuTrigger>
33+
</Pressable>
3334

34-
<DropdownMenuContent data-testid="schema-help-menu-content">
35-
<DropdownMenuItem disabled={!schema.documentation} asChild>
36-
<Link to={documentationUrl} target="_blank" className="flex gap-2">
37-
<Icon icon="mdi:book-open-variant-outline" className="text-lg text-custom-blue-700" />
38-
Documentation
39-
<Icon icon="mdi:open-in-new" />
40-
</Link>
41-
</DropdownMenuItem>
35+
<MenuPopover placement="bottom end">
36+
<Menu data-testid="schema-help-menu-content">
37+
<MenuSection>
38+
<MenuItem isDisabled={!schema.documentation} href={documentationUrl} target="_blank">
39+
<Icon icon="mdi:book-open-variant-outline" className="text-lg text-custom-blue-700" />
40+
Documentation
41+
<Icon icon="mdi:open-in-new" />
42+
</MenuItem>
4243

43-
<DropdownMenuItem disabled={isListViewDisabled} asChild>
44-
<Link to={getObjectDetailsUrl(schema.kind as string)} className="flex gap-2">
45-
<Icon icon="mdi:table-eye" className="text-lg text-custom-blue-700" />
46-
Open list view
47-
</Link>
48-
</DropdownMenuItem>
49-
</DropdownMenuContent>
50-
</DropdownMenu>
44+
<MenuItem
45+
isDisabled={isListViewDisabled}
46+
href={getObjectDetailsUrl(schema.kind as string)}
47+
>
48+
<Icon icon="mdi:table-eye" className="text-lg text-custom-blue-700" />
49+
Open list view
50+
</MenuItem>
51+
</MenuSection>
52+
</Menu>
53+
</MenuPopover>
54+
</MenuTrigger>
5155
);
5256
};
Lines changed: 52 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,60 @@
1+
import { Popover } from "@/shared/components/aria/popover";
2+
import { disabledStyle } from "@/shared/components/style-rac";
3+
import { useCopyToClipboard } from "@/shared/hooks/useCopyToClipboard";
4+
import { classNames } from "@/shared/utils/common";
5+
import { CopyIcon } from "lucide-react";
16
import {
7+
Header as AriaHeader,
8+
HeadingProps as AriaHeadingProps,
29
Menu as AriaMenu,
310
MenuItem as AriaMenuItem,
411
MenuItemProps as AriaMenuItemProps,
512
MenuProps as AriaMenuProps,
13+
MenuSection as AriaMenuSection,
14+
MenuSectionProps as AriaMenuSectionProps,
615
MenuTrigger as AriaMenuTrigger,
7-
SubmenuTrigger as AriaSubmenuTrigger,
8-
PopoverProps,
16+
PopoverProps as AriaPopoverProps,
917
composeRenderProps,
1018
} from "react-aria-components";
1119

12-
import { disabledStyle } from "@/shared/components/style-rac";
13-
import { classNames } from "@/shared/utils/common";
14-
import { SelectPopover } from "./select";
15-
1620
export const MenuTrigger = AriaMenuTrigger;
1721

18-
export const MenuSubTrigger = AriaSubmenuTrigger;
19-
20-
export const MenuPopover = ({ className, ...props }: PopoverProps) => {
22+
export interface MenuPopoverProps extends AriaPopoverProps {}
23+
export const MenuPopover = ({ className, ...props }: MenuPopoverProps) => {
2124
return (
22-
<SelectPopover
23-
className={composeRenderProps(className, (className) => classNames("w-auto", className))}
25+
<Popover
26+
className={composeRenderProps(className, (className) => {
27+
return classNames("p-1 rounded-lg bg-stone-100 border-stone-200", className);
28+
})}
2429
{...props}
2530
/>
2631
);
2732
};
2833

29-
export const Menu = <T extends object>({ className, ...props }: AriaMenuProps<T>) => {
34+
export interface MenuProps<T> extends AriaMenuProps<T> {}
35+
export const Menu = <T extends object>({ className, ...props }: MenuProps<T>) => {
3036
return (
3137
<AriaMenu
3238
className={classNames(
33-
"max-h-[inherit] overflow-auto rounded-md outline outline-0 [clip-path:inset(0_0_0_0_round_calc(var(--radius)-2px))]",
39+
"max-h-[inherit] overflow-auto rounded-md outline-hidden",
40+
"*:[[role='group']:not(:last-child)]:mb-2",
3441
className
3542
)}
3643
{...props}
3744
/>
3845
);
3946
};
4047

41-
export const MenuItem = ({ children, className, ...props }: AriaMenuItemProps) => {
48+
export interface MenuItemProps extends AriaMenuItemProps {}
49+
export const MenuItem = ({ children, className, ...props }: MenuItemProps) => {
4250
return (
4351
<AriaMenuItem
4452
textValue={props.textValue || (typeof children === "string" ? children : undefined)}
4553
className={composeRenderProps(className, (className) =>
4654
classNames(
47-
"relative flex cursor-default select-none items-center gap-2 rounded-xs px-2 py-1.5 text-sm outline-hidden transition-colors",
4855
disabledStyle,
49-
"data-focused:bg-gray-100",
56+
"transition-colors min-w-40 flex items-center gap-2 cursor-pointer select-none outline-hidden bg-white border border-transparent text-sm text-stone-600 rounded-md py-1 px-2",
57+
"data-focused:border-stone-300",
5058
className
5159
)
5260
)}
@@ -56,3 +64,31 @@ export const MenuItem = ({ children, className, ...props }: AriaMenuItemProps) =
5664
</AriaMenuItem>
5765
);
5866
};
67+
68+
export interface MenuSectionProps<T> extends AriaMenuSectionProps<T> {}
69+
export const MenuSection = <T extends object>({ className, ...props }: MenuSectionProps<T>) => {
70+
return <AriaMenuSection className={classNames("flex flex-col gap-0.5", className)} {...props} />;
71+
};
72+
73+
export interface MenuHeaderProps extends AriaHeadingProps {}
74+
export const MenuHeader = ({ className, ...props }: MenuHeaderProps) => {
75+
return <AriaHeader className={classNames("text-xs text-stone-500 px-1", className)} {...props} />;
76+
};
77+
78+
export interface CopyToClipboardMenuItemProps extends Omit<MenuItemProps, "onAction" | "children"> {
79+
textToCopy: string;
80+
children?: React.ReactNode;
81+
}
82+
export function CopyToClipboardMenuItem({
83+
textToCopy,
84+
children,
85+
...props
86+
}: CopyToClipboardMenuItemProps) {
87+
const { copyToClipboard } = useCopyToClipboard();
88+
return (
89+
<MenuItem onAction={() => copyToClipboard(textToCopy)} {...props}>
90+
<CopyIcon className="size-3" />
91+
{children}
92+
</MenuItem>
93+
);
94+
}

0 commit comments

Comments
 (0)