Skip to content

Commit 9381508

Browse files
authored
Added object section on sidebar menu (#4596)
* Added object section on sidebar menu * fix component test * fix menu flatten computation * fix e2e test * keep qsp * keep qsp on internal menu * fix search spec * fix test
1 parent c4328a6 commit 9381508

23 files changed

+855
-453
lines changed

frontend/app/package-lock.json

Lines changed: 619 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@iconify-icon/react": "^2.1.0",
4141
"@iconify-json/mdi": "^1.2.0",
4242
"@popperjs/core": "^2.11.8",
43+
"@radix-ui/react-accordion": "^1.2.1",
4344
"@radix-ui/react-dropdown-menu": "^2.0.6",
4445
"@radix-ui/react-label": "^2.0.2",
4546
"@radix-ui/react-popover": "^1.0.7",

frontend/app/src/components/search/search-actions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Badge } from "@/components/ui/badge";
2-
import { MenuItem } from "@/screens/layout/sidebar/desktop-menu";
2+
import { MenuItem } from "@/screens/layout/menu-navigation/types";
33
import { IModelSchema, genericsState, menuFlatAtom, schemaState } from "@/state/atoms/schema.atom";
44
import { constructPath } from "@/utils/fetch";
55
import { Icon } from "@iconify-icon/react";
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
"use client";
2+
3+
import { classNames } from "@/utils/common";
4+
import { Icon } from "@iconify-icon/react";
5+
import * as AccordionPrimitive from "@radix-ui/react-accordion";
6+
import * as React from "react";
7+
8+
export const Accordion = AccordionPrimitive.Root;
9+
10+
export const AccordionItem = AccordionPrimitive.Item;
11+
12+
export const AccordionTrigger = React.forwardRef<
13+
React.ElementRef<typeof AccordionPrimitive.Trigger>,
14+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
15+
>(({ className, children, ...props }, ref) => (
16+
<AccordionPrimitive.Header className="flex">
17+
<AccordionPrimitive.Trigger
18+
ref={ref}
19+
className={classNames(
20+
"flex flex-1 items-center py-4 font-medium transition-all [&[data-state=open]>iconify-icon]:rotate-180",
21+
className
22+
)}
23+
{...props}
24+
>
25+
<Icon
26+
icon="mdi:chevron-down"
27+
className="text-xl shrink-0 transition-transform duration-200"
28+
/>
29+
{children}
30+
</AccordionPrimitive.Trigger>
31+
</AccordionPrimitive.Header>
32+
));
33+
34+
export const AccordionContent = React.forwardRef<
35+
React.ElementRef<typeof AccordionPrimitive.Content>,
36+
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
37+
>(({ className, children, ...props }, ref) => (
38+
<AccordionPrimitive.Content
39+
ref={ref}
40+
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
41+
{...props}
42+
>
43+
<div className={classNames(className)}>{children}</div>
44+
</AccordionPrimitive.Content>
45+
));

frontend/app/src/components/ui/dropdown-menu.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
import {
2+
Accordion,
3+
AccordionContent,
4+
AccordionItem,
5+
AccordionTrigger,
6+
} from "@/components/ui/accordion";
17
import { classNames } from "@/utils/common";
28
import { Icon } from "@iconify-icon/react";
39
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
@@ -89,3 +95,34 @@ export const DropdownMenuSubContent = forwardRef<
8995
{...props}
9096
/>
9197
));
98+
99+
export const DropdownMenuAccordion = forwardRef<
100+
ElementRef<typeof AccordionItem>,
101+
ComponentPropsWithoutRef<typeof AccordionItem>
102+
>((props, ref) => {
103+
return (
104+
<Accordion type="single" collapsible>
105+
<AccordionItem {...props} ref={ref} />
106+
</Accordion>
107+
);
108+
});
109+
110+
export const DropdownMenuAccordionTrigger = forwardRef<
111+
ElementRef<typeof DropdownMenuItem>,
112+
ComponentPropsWithoutRef<typeof AccordionTrigger>
113+
>((props, ref) => {
114+
return (
115+
<DropdownMenuItem
116+
ref={ref}
117+
onSelect={(e) => {
118+
e.preventDefault();
119+
e.stopPropagation();
120+
}}
121+
asChild
122+
>
123+
<AccordionTrigger className="font-normal" {...props} />
124+
</DropdownMenuItem>
125+
);
126+
});
127+
128+
export const DropdownMenuAccordionContent = AccordionContent;

frontend/app/src/screens/layout/menu-navigation/components/menu-section-internal.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import {
77
DropdownMenuSubTrigger,
88
DropdownMenuTrigger,
99
} from "@/components/ui/dropdown-menu";
10+
import { menuNavigationItemStyle } from "@/screens/layout/menu-navigation/styles";
1011
import { MenuItem } from "@/screens/layout/menu-navigation/types";
1112
import { constructPath } from "@/utils/fetch";
1213
import { Icon } from "@iconify-icon/react";
13-
import React from "react";
1414
import { Link } from "react-router-dom";
1515

1616
export interface MenuSectionInternalProps {
@@ -34,7 +34,7 @@ export function MenuSectionInternal({ items }: MenuSectionInternalProps) {
3434

3535
return (
3636
<DropdownMenu key={item.identifier}>
37-
<DropdownMenuTrigger className="flex items-center outline-none gap-2 px-3 py-2 rounded font-medium text-neutral-900 hover:bg-neutral-100 focus:bg-neutral-100 group data-[state=open]:bg-indigo-50 data-[state=open]:text-indigo-700">
37+
<DropdownMenuTrigger className={menuNavigationItemStyle}>
3838
<Icon icon={item.icon} className="text-lg min-w-4" />
3939
<span className="text-sm">{item.title}</span>
4040
<Icon
@@ -48,7 +48,7 @@ export function MenuSectionInternal({ items }: MenuSectionInternalProps) {
4848
if (!child.children || child.children.length === 0) {
4949
return (
5050
<DropdownMenuItem key={child.identifier} asChild>
51-
<Link to={child.path}>{child.title}</Link>
51+
<Link to={constructPath(child.path)}>{child.title}</Link>
5252
</DropdownMenuItem>
5353
);
5454
}
@@ -61,7 +61,7 @@ export function MenuSectionInternal({ items }: MenuSectionInternalProps) {
6161
{child.children.map((grandchild) => {
6262
return (
6363
<DropdownMenuItem key={grandchild.identifier} asChild>
64-
<Link to={grandchild.path}>{grandchild.title}</Link>
64+
<Link to={constructPath(grandchild.path)}>{grandchild.title}</Link>
6565
</DropdownMenuItem>
6666
);
6767
})}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import {
2+
DropdownMenu,
3+
DropdownMenuAccordion,
4+
DropdownMenuAccordionContent,
5+
DropdownMenuAccordionTrigger,
6+
DropdownMenuContent,
7+
DropdownMenuItem,
8+
DropdownMenuTrigger,
9+
} from "@/components/ui/dropdown-menu";
10+
import { ObjectAvatar } from "@/screens/layout/menu-navigation/components/object-avatar";
11+
import { menuNavigationItemStyle } from "@/screens/layout/menu-navigation/styles";
12+
import { MenuItem } from "@/screens/layout/menu-navigation/types";
13+
import { constructPath } from "@/utils/fetch";
14+
import { Icon } from "@iconify-icon/react";
15+
import { Link } from "react-router-dom";
16+
17+
export interface MenuSectionObjectsProps {
18+
items: MenuItem[];
19+
}
20+
21+
export function MenuSectionObject({ items }: MenuSectionObjectsProps) {
22+
return (
23+
<div className="flex flex-col">
24+
{items.map((item) => {
25+
if (!item.children || item.children.length === 0) {
26+
return (
27+
<Link to={constructPath(item.path)}>
28+
<Icon icon={item.icon} className="m-1 min-w-4" />
29+
<span className="text-sm">{item.title}</span>
30+
</Link>
31+
);
32+
}
33+
34+
return (
35+
<DropdownMenu key={item.identifier}>
36+
<DropdownMenuTrigger className={menuNavigationItemStyle}>
37+
<ObjectAvatar name={item.title} />
38+
<span className="text-sm">{item.title}</span>
39+
</DropdownMenuTrigger>
40+
41+
<DropdownMenuContent
42+
side="left"
43+
align="start"
44+
sideOffset={12}
45+
className="h-[calc(100vh-57px)] min-w-[224px] px-4 py-5 bg-white border rounded-r-lg rounded-l-none shadow-none relative -top-px overflow-auto"
46+
>
47+
<h3 className="text-xl font-medium text-neutral-800 mb-5">{item.title}</h3>
48+
{item.children.map((child) => {
49+
if (!child.children || child.children.length === 0) {
50+
return (
51+
<DropdownMenuItem key={child.identifier} className="px-3" asChild>
52+
<Link to={constructPath(child.path)}>
53+
<Icon icon={child.icon} className="w-5" />
54+
{child.title}
55+
</Link>
56+
</DropdownMenuItem>
57+
);
58+
}
59+
60+
return (
61+
<DropdownMenuAccordion key={child.identifier} value={child.identifier}>
62+
<DropdownMenuAccordionTrigger className={menuNavigationItemStyle}>
63+
{child.title}
64+
</DropdownMenuAccordionTrigger>
65+
66+
<DropdownMenuAccordionContent>
67+
<DropdownMenuItem key={child.identifier} className="pl-10" asChild>
68+
<Link to={constructPath(child.path)}>{child.title}</Link>
69+
</DropdownMenuItem>
70+
</DropdownMenuAccordionContent>
71+
</DropdownMenuAccordion>
72+
);
73+
})}
74+
</DropdownMenuContent>
75+
</DropdownMenu>
76+
);
77+
})}
78+
</div>
79+
);
80+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { classNames } from "@/utils/common";
2+
3+
const STYLES = [
4+
"bg-indigo-50 text-indigo-600",
5+
"bg-red-50 text-red-600",
6+
"bg-yellow-50 text-yellow-600",
7+
];
8+
9+
export function ObjectAvatar({ name }: { name: string }) {
10+
const firstLetter = name[0];
11+
if (!firstLetter) {
12+
return <div className="w-6 h-6 rounded flex items-center justify-center bg-gray-100" />;
13+
}
14+
15+
const styleIndex = firstLetter.charCodeAt(0) % STYLES.length;
16+
return (
17+
<div
18+
className={classNames("w-6 h-6 rounded flex items-center justify-center", STYLES[styleIndex])}
19+
>
20+
{firstLetter.toUpperCase()}
21+
</div>
22+
);
23+
}

frontend/app/src/screens/layout/menu-navigation/menu-navigation.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@ import { ALERT_TYPES, Alert } from "@/components/ui/alert";
22
import { Divider } from "@/components/ui/divider";
33
import { CONFIG } from "@/config/config";
44
import { MenuSectionInternal } from "@/screens/layout/menu-navigation/components/menu-section-internal";
5-
import { MenuData } from "@/screens/layout/menu-navigation/types";
6-
import { DesktopMenu } from "@/screens/layout/sidebar/desktop-menu";
5+
import { MenuSectionObject } from "@/screens/layout/menu-navigation/components/menu-section-object";
76
import { currentBranchAtom } from "@/state/atoms/branches.atom";
8-
import { currentSchemaHashAtom } from "@/state/atoms/schema.atom";
7+
import { currentSchemaHashAtom, menuAtom } from "@/state/atoms/schema.atom";
98
import { classNames } from "@/utils/common";
109
import { fetchUrl } from "@/utils/fetch";
11-
import { useAtomValue } from "jotai";
10+
import { useAtom, useAtomValue } from "jotai";
1211
import { useEffect, useState } from "react";
1312
import { toast } from "react-toastify";
1413

1514
export default function MenuNavigation({ className }: { className: string }) {
1615
const currentBranch = useAtomValue(currentBranchAtom);
1716
const currentSchemaHash = useAtomValue(currentSchemaHashAtom);
18-
const [menu, setMenu] = useState<MenuData>();
17+
const [menu, setMenu] = useAtom(menuAtom);
1918
const [isLoading, setIsLoading] = useState(false);
2019

2120
useEffect(() => {
@@ -36,8 +35,8 @@ export default function MenuNavigation({ className }: { className: string }) {
3635
if (!menu?.sections) return <div className="flex-grow" />;
3736

3837
return (
39-
<div className={classNames("flex flex-col", className)}>
40-
<DesktopMenu />
38+
<div className={classNames("flex flex-col", className)} data-testid="menu-navigation">
39+
<MenuSectionObject items={menu.sections.object} />
4140
<Divider />
4241
<MenuSectionInternal items={menu.sections.internal} />
4342
</div>
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const menuNavigationItemStyle =
2+
"flex items-center outline-none gap-2 px-3 py-2 rounded font-medium text-neutral-900 hover:bg-neutral-100 focus:bg-neutral-100 group data-[state=open]:bg-indigo-50 data-[state=open]:text-indigo-700";

0 commit comments

Comments
 (0)