mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 15:40:47 +00:00
feat: completed all nav restructing and breadcrumbs cleanup
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link } from "@tanstack/react-router";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useTimedReset } from "@app/hooks";
|
||||
import { ProjectType } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { createNotification } from "../notifications";
|
||||
import { IconButton, Tooltip } from "../v2";
|
||||
|
||||
type Props = {
|
||||
secretPathSegments: string[];
|
||||
selectedPathSegmentIndex: number;
|
||||
environmentSlug: string;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export const SecretDashboardPathBreadcrumb = ({
|
||||
secretPathSegments,
|
||||
selectedPathSegmentIndex,
|
||||
environmentSlug,
|
||||
projectId
|
||||
}: Props) => {
|
||||
const [, isCopying, setIsCopying] = useTimedReset({
|
||||
initialState: false
|
||||
});
|
||||
|
||||
const newSecretPath = `/${secretPathSegments.slice(0, selectedPathSegmentIndex + 1).join("/")}`;
|
||||
const isLastItem = secretPathSegments.length === selectedPathSegmentIndex + 1;
|
||||
const folderName = secretPathSegments.at(selectedPathSegmentIndex);
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-3">
|
||||
{isLastItem ? (
|
||||
<div className="group flex items-center space-x-2">
|
||||
<span
|
||||
className={twMerge(
|
||||
"text-sm font-semibold transition-all",
|
||||
isCopying ? "text-bunker-200" : "text-bunker-300"
|
||||
)}
|
||||
>
|
||||
{folderName}
|
||||
</span>
|
||||
<Tooltip className="relative right-2" position="bottom" content="Copy secret path">
|
||||
<IconButton
|
||||
variant="plain"
|
||||
ariaLabel="copy"
|
||||
onClick={() => {
|
||||
if (isCopying) return;
|
||||
setIsCopying(true);
|
||||
navigator.clipboard.writeText(newSecretPath);
|
||||
|
||||
createNotification({
|
||||
text: "Copied secret path to clipboard",
|
||||
type: "info"
|
||||
});
|
||||
}}
|
||||
className="opacity-0 transition duration-75 hover:bg-bunker-100/10 group-hover:opacity-100"
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={!isCopying ? faCopy : faCheck}
|
||||
size="sm"
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : (
|
||||
<Link
|
||||
to={`/${ProjectType.SecretManager}/$projectId/secrets/$envSlug` as const}
|
||||
params={{
|
||||
projectId,
|
||||
envSlug: environmentSlug
|
||||
}}
|
||||
search={(query) => ({ ...query, secretPath: newSecretPath })}
|
||||
className={twMerge(
|
||||
"text-sm font-semibold transition-all hover:text-primary",
|
||||
isCopying && "text-primary"
|
||||
)}
|
||||
>
|
||||
{folderName}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +1,19 @@
|
||||
import * as React from "react";
|
||||
import { faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||
/* eslint-disable react/prop-types */
|
||||
import React from "react";
|
||||
import { faCaretDown, faChevronRight, faEllipsis } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Link, ReactNode } from "@tanstack/react-router";
|
||||
import { LinkComponentProps } from "node_modules/@tanstack/react-router/dist/esm/link";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger
|
||||
} from "../Dropdown";
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
@@ -27,21 +38,25 @@ BreadcrumbList.displayName = "BreadcrumbList";
|
||||
|
||||
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={twMerge("inline-flex items-center gap-1.5", className)} {...props} />
|
||||
<li
|
||||
ref={ref}
|
||||
className={twMerge("inline-flex items-center gap-1.5 font-medium", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
BreadcrumbItem.displayName = "BreadcrumbItem";
|
||||
|
||||
const BreadcrumbLink = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentPropsWithoutRef<"li"> & {
|
||||
HTMLDivElement,
|
||||
React.ComponentPropsWithoutRef<"div"> & {
|
||||
asChild?: boolean;
|
||||
}
|
||||
>(({ asChild, className, ...props }, ref) => {
|
||||
return (
|
||||
<li
|
||||
<div
|
||||
ref={ref}
|
||||
className={twMerge("transition-colors hover:text-bunker-200", className)}
|
||||
className={twMerge("transition-colors hover:text-primary-400", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -87,12 +102,116 @@ const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span"
|
||||
);
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis";
|
||||
|
||||
enum BreadcrumbTypes {
|
||||
Dropdown = "dropdown",
|
||||
Component = "component"
|
||||
}
|
||||
|
||||
export type TBreadcrumbFormat =
|
||||
| {
|
||||
type: BreadcrumbTypes.Dropdown;
|
||||
label: string;
|
||||
dropdownTitle?: string;
|
||||
links: { label: string; link: LinkComponentProps }[];
|
||||
}
|
||||
| {
|
||||
type: BreadcrumbTypes.Component;
|
||||
component: ReactNode;
|
||||
}
|
||||
| {
|
||||
type: undefined;
|
||||
link?: LinkComponentProps;
|
||||
label: string;
|
||||
icon?: ReactNode;
|
||||
};
|
||||
|
||||
const BreadcrumbContainer = ({ breadcrumbs }: { breadcrumbs: TBreadcrumbFormat[] }) => (
|
||||
<div className="mx-auto max-w-7xl py-4 capitalize text-white">
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
{(breadcrumbs as TBreadcrumbFormat[]).map((el, index) => {
|
||||
const isNotLastCrumb = index + 1 !== breadcrumbs.length;
|
||||
const BreadcrumbSegment = isNotLastCrumb ? BreadcrumbLink : BreadcrumbPage;
|
||||
|
||||
if (el.type === BreadcrumbTypes.Dropdown) {
|
||||
return (
|
||||
<React.Fragment key={`breadcrumb-group-${index + 1}`}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbSegment>
|
||||
{el.label} <FontAwesomeIcon icon={faCaretDown} size="sm" />
|
||||
</BreadcrumbSegment>
|
||||
</BreadcrumbItem>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{el?.dropdownTitle && <DropdownMenuLabel>{el.dropdownTitle}</DropdownMenuLabel>}
|
||||
{el.links.map((i, dropIndex) => (
|
||||
<Link
|
||||
{...i.link}
|
||||
key={`breadcrumb-group-${index + 1}-dropdown-${dropIndex + 1}`}
|
||||
>
|
||||
<DropdownMenuItem>{i.label}</DropdownMenuItem>
|
||||
</Link>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{isNotLastCrumb && <BreadcrumbSeparator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
if (el.type === BreadcrumbTypes.Component) {
|
||||
const Component = el.component;
|
||||
return (
|
||||
<React.Fragment key={`breadcrumb-group-${index + 1}`}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbSegment>
|
||||
<Component />
|
||||
</BreadcrumbSegment>
|
||||
</BreadcrumbItem>
|
||||
{isNotLastCrumb && <BreadcrumbSeparator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const Icon = el?.icon;
|
||||
return (
|
||||
<React.Fragment key={`breadcrumb-group-${index + 1}`}>
|
||||
{"link" in el && isNotLastCrumb ? (
|
||||
<Link {...el.link}>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbLink className="inline-flex items-center gap-1.5">
|
||||
{Icon && <Icon />}
|
||||
{el.label}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</Link>
|
||||
) : (
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage className="inline-flex items-center gap-1.5">
|
||||
{Icon && <Icon />}
|
||||
{el.label}
|
||||
</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
)}
|
||||
{isNotLastCrumb && <BreadcrumbSeparator />}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
</div>
|
||||
);
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbContainer,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbTypes
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { ComponentPropsWithRef, ElementType, ReactNode, Ref, useRef } from "react";
|
||||
import { DotLottie, DotLottieReact } from "@lottiefiles/dotlottie-react";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export type MenuProps = {
|
||||
|
||||
@@ -14,8 +14,6 @@ export const PageHeader = ({ title, description, children }: Props) => (
|
||||
</div>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mt-2 text-gray-400">{description}</p>
|
||||
</div>
|
||||
<div className="mt-2 text-gray-400">{description}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user