Skip to content

Commit

Permalink
Updated Infrahub global layout (#5749)
Browse files Browse the repository at this point in the history
  • Loading branch information
bilalabbad authored Feb 14, 2025
1 parent b61ab3b commit 9eb17fd
Show file tree
Hide file tree
Showing 25 changed files with 89 additions and 60 deletions.
2 changes: 2 additions & 0 deletions changelog/+new-layout.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Improved Infrahub app layout for a cleaner look.
- Made the top menu more compact.
10 changes: 10 additions & 0 deletions frontend/app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"handlebars": "^4.7.8",
"jotai": "^2.11.3",
"json-to-graphql-query": "^2.2.5",
"lucide-react": "^0.475.0",
"openapi-fetch": "^0.13.4",
"prismjs": "^1.29.0",
"query-string": "^9.0.0",
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/assets/infrahub-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion frontend/app/src/entities/diff/ui/diff-empty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export interface DiffEmptyProps {

export function DiffEmpty({ branchName, lastRefreshedAt }: DiffEmptyProps) {
return (
<div className="flex flex-col items-center mt-10 gap-5">
<div className="flex flex-col items-center my-10 gap-5">
<div className="p-3 rounded-full bg-white inline-flex">
<Icon icon="mdi:circle-off-outline" className="text-2xl text-custom-blue-800" />
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/entities/events/ui/event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const Event = ({ __typename, ...props }: EventType) => {
<div className="flex gap-2">
<TimelineBorder />

<div className="flex flex-grow gap-3 p-2 rounded-md shadow-sm border">
<div className="flex flex-grow gap-3 p-2 rounded-md shadow-sm border bg-white">
<div className="flex flex-col gap-2 grow">
{__typename === NODE_MUTATED_EVENT && <NodeEvent {...props} />}

Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/entities/events/ui/global-events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const GlobalEvents = () => {
});

return (
<div className="flex flex-col gap-2 p-2">
<div className="flex flex-col gap-2 p-2 overflow-auto">
{activities?.map((activity) => (
<Event key={activity.id} {...activity} />
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function ObjectsTableManager({ schema }: ObjectsTableManagerProps) {

return (
<>
<div className="flex items-center h-14 px-3">
<div className="flex items-center h-14 px-3 shrink-0">
<FilterSearchInput schema={schema} />

{filters.length > 0 && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const ObjectsTable = ({

return (
<div
className="grid content-start h-[calc(100vh-14rem)] overflow-auto min-w-full"
className="grid content-start overflow-auto"
onScroll={(e) => fetchMoreOnBottomReached(e.currentTarget)}
ref={tableContainerRef}
style={{
Expand Down Expand Up @@ -89,7 +89,7 @@ export const ObjectsTable = ({

{(isPending || isFetchingNextPage) && (
<>
{[...Array(15)].map((_, rowIndex) => (
{[...Array(20)].map((_, rowIndex) => (
<React.Fragment key={`skeleton-row-${rowIndex}`}>
{[...Array(allHeaders.length)].map((_, colIndex) => (
<TableCell
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/entities/tasks/ui/task-status.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function TaskStatus() {
const commonButtonProps: LinkButtonProps = {
size: "square",
variant: "ghost",
className: "h-8 w-8 bg-neutral-50 border border-neutral-200 rounded-lg relative",
className: "h-8 w-8 bg-neutral-50 border border-neutral-200 rounded-lg relative shrink-0",
to: constructPath("/tasks", [{ name: QSP.FILTER, value: JSON.stringify([filter]) }]),
};

Expand Down
1 change: 1 addition & 0 deletions frontend/app/src/pages/graphql/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const GraphqlSandboxPage = () => {

return (
<GraphiQL
className="rounded-lg border"
defaultEditorToolsVisibility
query={query ?? undefined}
plugins={[explorerPlugin({ showAttribution: false })]}
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/pages/objects/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const ObjectPageLayout = () => {
}

return (
<Content.Card>
<Content.Card className="flex flex-col">
<ObjectHeader schema={schema} objectId={objectid} />

<Outlet />
Expand Down
7 changes: 1 addition & 6 deletions frontend/app/src/pages/objects/object-details.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ARTIFACT_OBJECT, GRAPHQL_QUERY_OBJECT, TASK_OBJECT } from "@/config/constants";
import ArtifactsDetails from "@/entities/artifacts/ui/artifact-details";
import { GRAPHQL_QUERY_OBJECT, TASK_OBJECT } from "@/config/constants";
import { useObjectDetails } from "@/entities/nodes/hooks/useObjectDetails";
import ObjectItemDetails from "@/entities/nodes/object-item-details/object-item-details-paginated";
import { genericsState, profilesAtom, schemaState } from "@/entities/schema/stores/schema.atom";
Expand Down Expand Up @@ -71,10 +70,6 @@ export const Component = () => {
return <Navigate to={constructPath(`/objects/${objectKind}`)} />;
}

if (objectKind === ARTIFACT_OBJECT) {
return <ArtifactsDetails artifactId={objectid} />;
}

if (objectKind === GRAPHQL_QUERY_OBJECT) {
return <GraphqlQueryDetailsPage graphqlQueryId={objectid} />;
}
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/pages/proposed-changes/details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ export function Component() {
}

return (
<Content.Card className="min-h-[calc(100%-1rem)] flex flex-col">
<Content.Card className="flex flex-col">
<Content.CardTitle
title={proposedChangesData.display_label}
description={
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/pages/resource-manager/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const ResourceManagerPage = () => {
if (!resourcePoolSchema) return <LoadingIndicator className="h-full" />;

return (
<Content.Card>
<Content.Card className="flex flex-col">
<ObjectHeader schema={resourcePoolSchema} />

<ObjectsTableManager schema={resourcePoolSchema} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ const ResourcePoolContent = ({ id, schema }: ResourcePoolContentProps) => {
}
/>

<div className="p-2 flex items-start h-[calc(100%-64px)] overflow-hidden">
<div className="p-2 flex items-start overflow-hidden">
<aside className="inline-flex flex-col gap-2 shrink-0 mr-1">
<Card className="shrink-0">
<CardWithBorder.Title className="flex items-center justify-between gap-1">
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/pages/schema.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function SchemaPage() {
const profiles = useAtomValue(profilesAtom);

return (
<Content.Card className="h-[calc(100%-1rem)] overflow-auto flex flex-col">
<Content.Card className="h-[calc(100%-1rem)] flex flex-col">
<Content.CardTitle
title="Schema Visualizer"
badgeContent={nodes.length + generics.length + profiles.length}
Expand Down
17 changes: 9 additions & 8 deletions frontend/app/src/shared/components/account-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useAuth } from "@/entities/authentication/ui/useAuth";
import { IModelSchema, genericsState } from "@/entities/schema/stores/schema.atom";
import { getProfileDetails } from "@/entities/user-profile/api/getProfileDetails";
import { constructPath } from "@/shared/api/rest/fetch";
import { Button } from "@/shared/components/buttons/button-primitive";
import { Button, LinkButton } from "@/shared/components/buttons/button-primitive";
import { Avatar } from "@/shared/components/display/avatar";
import { AppVersion } from "@/shared/components/layout/app-version";
import { Skeleton } from "@/shared/components/skeleton";
Expand Down Expand Up @@ -90,12 +90,13 @@ const UnauthenticatedAccountMenu = () => {

return (
<DropdownMenu>
<Link
className="flex items-center h-14 w-full rounded-lg p-2 gap-2 hover:bg-indigo-50 overflow-hidden shrink-0"
<LinkButton
variant="ghost"
className="p-2 h-auto w-full rounded-lg gap-2 hover:bg-indigo-50 overflow-hidden shrink-0"
to="/login"
state={{ from: location }}
>
<div className="bg-indigo-50 rounded-full h-10 w-10 flex items-center justify-center overflow-hidden border border-white shrink-0">
<div className="bg-indigo-50 rounded-full size-9 flex items-center justify-center overflow-hidden border border-white shrink-0">
<Icon icon="mdi:user" className="text-5xl relative top-1 text-neutral-600" />
</div>

Expand All @@ -119,7 +120,7 @@ const UnauthenticatedAccountMenu = () => {
<Icon icon="mdi:dots-vertical" className="text-lg" />
</Button>
</DropdownMenuTrigger>
</Link>
</LinkButton>

<DropdownMenuContent align="end" side="right">
<CommonMenuItems />
Expand Down Expand Up @@ -175,7 +176,7 @@ const AuthenticatedAccountMenu = ({
className="h-auto w-full justify-start gap-2 hover:bg-indigo-50 rounded-lg p-2 overflow-hidden text-left shrink-0"
data-testid="authenticated-menu-trigger"
>
<Avatar name={profile?.name?.value} className="h-10 w-10 shrink-0" />
<Avatar name={profile?.name?.value} className="size-9 shrink-0" />

<div className="group-data-[collapsed=true]/sidebar:hidden overflow-hidden">
<div className="font-semibold text-sm truncate">{profile?.label?.value}</div>
Expand Down Expand Up @@ -211,8 +212,8 @@ const AuthenticatedAccountMenu = ({

const AccountMenuSkeleton = () => {
return (
<div className="flex items-center gap-2 p-2 shrink-0">
<Skeleton className="rounded-full h-10 w-10" />
<div className="flex items-center gap-2 p-2 shrink-0 border border-transparent">
<Skeleton className="rounded-full size-9" />

<div className="flex-grow space-y-2 group-data-[collapsed=true]/sidebar:hidden">
<Skeleton className="h-4 w-4/5" />
Expand Down
2 changes: 1 addition & 1 deletion frontend/app/src/shared/components/layout/content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export const ContentTitle = ({
};

export const ContentCard = ({ className, ...props }: CardProps) => {
return <Card className={classNames("p-0 m-2 overflow-hidden", className)} {...props} />;
return <Card className={classNames("p-0 overflow-auto", className)} {...props} />;
};

export type ContentCardTitleProps = {
Expand Down
9 changes: 1 addition & 8 deletions frontend/app/src/shared/components/layout/header.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import InfrahubLogo from "@/assets/infrahub-logo.svg";
import { TaskStatus } from "@/entities/tasks/ui/task-status";
import { constructPath } from "@/shared/api/rest/fetch";
import BranchSelector from "@/shared/components/branch-selector";
import BreadcrumbNavigation from "@/shared/components/layout/breadcrumb-navigation/breadcrumb-navigation";
import { TimeFrameSelector } from "@/shared/components/time-selector";
import { Link } from "react-router";

export default function Header() {
return (
<header className="px-6 py-3 flex items-center gap-2 border-b bg-white">
<Link to={constructPath("/")} className="h-8 w-8">
<img src={InfrahubLogo} alt="Infrahub logo" />
</Link>

<header className="p-3 flex items-center gap-2 border bg-white rounded-lg">
<TimeFrameSelector />

<BranchSelector />
Expand Down
12 changes: 6 additions & 6 deletions frontend/app/src/shared/components/layout/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ function Layout() {
}, [branches.length, branchInQueryString]);

return (
<div className="h-screen w-screen overflow-hidden bg-stone-100 text-stone-800">
<Header />

<div className="flex items-stretch h-[calc(100vh-57px)]">
<div className="h-screen w-screen text-stone-800 p-px bg-stone-100">
<div className="h-full w-full flex gap-px">
<Sidebar />

<main className="flex-grow overflow-auto">
<div className="flex flex-col gap-px h-full grow overflow-hidden">
<Header />

<Outlet />
</main>
</div>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,8 @@ const TopLevelMenuItem: React.FC<{
side="left"
align="start"
sideOffset={isCollapsed ? 6 : 12}
className="h-[calc(100vh-57px)] mt-[57px] min-w-[275px] px-4 py-5 bg-white border rounded-r-lg rounded-l-none shadow-none relative -top-px overflow-auto data-[side=right]:slide-in-from-left-[100px]"
className="min-w-60 max-h-[calc(100vh-7rem)] overflow-auto"
>
<h3 className="text-xl font-medium text-neutral-800 mb-5">{item.label}</h3>
{item.children.map((child) => (
<RecursiveObjectMenuItem key={child.identifier} item={child} isCollapsed={isCollapsed} />
))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default function MenuNavigation({ isCollapsed }: MenuNavigationProps) {
<ScrollArea>
<MenuSectionObject items={menu.sections.object} isCollapsed={isCollapsed} />
</ScrollArea>
<Divider className="p-0" />
<Divider />
<MenuSectionInternal items={menu.sections.internal} isCollapsed={isCollapsed} />
</>
);
Expand Down
56 changes: 41 additions & 15 deletions frontend/app/src/shared/components/layout/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import InfrahubWithTextLogo from "@/assets/Infrahub-SVG-hori.svg";
import InfrahubLogo from "@/assets/infrahub-logo.svg";
import { SIDEBAR_COLLAPSED_KEY } from "@/config/localStorage";
import { constructPath } from "@/shared/api/rest/fetch";
import { AccountMenu } from "@/shared/components/account-menu";
import { Button } from "@/shared/components/buttons/button-primitive";
import MenuNavigation from "@/shared/components/layout/menu-navigation/menu-navigation";
import { SearchAnywhere } from "@/shared/components/search/search-anywhere";
import { Divider } from "@/shared/components/ui/divider";
import { focusVisibleStyle } from "@/shared/components/ui/style";
import { useLocalStorage } from "@/shared/hooks/useLocalStorage";
import { classNames } from "@/shared/utils/common";
import { Icon } from "@iconify-icon/react";
import { PanelLeftCloseIcon, PanelLeftOpenIcon } from "lucide-react";
import { Link } from "react-router";

export default function Sidebar() {
const [collapsed, setCollapsed] = useLocalStorage(SIDEBAR_COLLAPSED_KEY);
Expand All @@ -16,28 +22,48 @@ export default function Sidebar() {
<nav
data-collapsed={booleanCollapsed}
className={classNames(
"flex flex-col gap-3 shrink-0",
"relative -top-px",
"w-[256px] border rounded-lg py-5 px-4 bg-white",
"flex flex-col shrink-0",
"relative",
"w-[256px] border rounded-lg p-3 bg-white",
"group/sidebar transition-all",
booleanCollapsed && "w-[72px] px-2 items-center"
booleanCollapsed && "w-auto px-2 items-center"
)}
data-testid="sidebar"
>
<Button
variant="outline"
size="icon"
className={classNames(
"transition-all absolute -right-3.5 top-6",
booleanCollapsed ? "rotate-180" : "hidden group-hover/sidebar:inline-flex"
<div className="flex justify-between items-center mb-3">
<Link to={constructPath("/")} className={classNames(focusVisibleStyle, "rounded-md")}>
<img
src={booleanCollapsed ? InfrahubLogo : InfrahubWithTextLogo}
alt="Infrahub logo"
className={"h-8 px-1 m-0.5"}
/>
</Link>

{booleanCollapsed ? (
<Button
variant="outline"
size="icon"
className="absolute -right-3.5 top-11 transition-all hidden group-hover/sidebar:inline-flex"
onClick={() => setCollapsed(JSON.stringify(!booleanCollapsed))}
>
<PanelLeftOpenIcon className="size-4 text-neutral-600" />
</Button>
) : (
<Button
variant="ghost"
size="sm"
className="p-1 text-gray-400 hover:text-neutral-600"
onClick={() => setCollapsed(JSON.stringify(!booleanCollapsed))}
>
<PanelLeftCloseIcon className="size-5" />
</Button>
)}
onClick={() => setCollapsed(JSON.stringify(!booleanCollapsed))}
>
<Icon icon="mdi:chevron-left" className="text-xl text-neutral-600" />
</Button>
</div>

<SearchAnywhere isCollapsed={booleanCollapsed} />

<Divider />

<MenuNavigation isCollapsed={booleanCollapsed} />

<AccountMenu />
Expand Down
1 change: 1 addition & 0 deletions frontend/app/src/shared/components/time-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const TimeFrameSelector = () => {
maxDate={new Date()}
filterTime={(date) => isPast(date)}
popperPlacement="bottom-start"
popperClassName="!z-50"
/>

<Transition
Expand Down

0 comments on commit 9eb17fd

Please sign in to comment.