improvement: refactor and update nav bar styling

This commit is contained in:
Scott Wilson
2025-11-25 14:01:33 -08:00
parent b674700569
commit 49f5bf2aef
13 changed files with 87 additions and 94 deletions
+2 -2
View File
@@ -47,8 +47,8 @@ export const Tab = ({
}) => ( }) => (
<TabsPrimitive.Trigger <TabsPrimitive.Trigger
className={twMerge( className={twMerge(
"flex h-10 cursor-pointer items-center justify-center border-transparent", "flex h-11 cursor-pointer items-center justify-center border-transparent",
"px-3 text-sm font-medium whitespace-nowrap text-mineshaft-400 transition-all select-none", "px-3 text-sm font-medium whitespace-nowrap text-mineshaft-300/75 transition-all select-none",
"data-[orientation=vertical]:xl:h-5 data-[orientation=vertical]:xl:border-b-0 data-[orientation=vertical]:xl:border-l", "data-[orientation=vertical]:xl:h-5 data-[orientation=vertical]:xl:border-b-0 data-[orientation=vertical]:xl:border-l",
"border-b hover:text-mineshaft-200", "border-b hover:text-mineshaft-200",
"data-[state=active]:border-mineshaft-400 data-[state=active]:text-white", "data-[state=active]:border-mineshaft-400 data-[state=active]:text-white",
+1 -1
View File
@@ -49,6 +49,7 @@ import { BitbucketConnectionMethod } from "@app/hooks/api/appConnections/types/b
import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection"; import { ChecklyConnectionMethod } from "@app/hooks/api/appConnections/types/checkly-connection";
import { ChefConnectionMethod } from "@app/hooks/api/appConnections/types/chef-connection"; import { ChefConnectionMethod } from "@app/hooks/api/appConnections/types/chef-connection";
import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean"; import { DigitalOceanConnectionMethod } from "@app/hooks/api/appConnections/types/digital-ocean";
import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection";
import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection"; import { HerokuConnectionMethod } from "@app/hooks/api/appConnections/types/heroku-connection";
import { LaravelForgeConnectionMethod } from "@app/hooks/api/appConnections/types/laravel-forge-connection"; import { LaravelForgeConnectionMethod } from "@app/hooks/api/appConnections/types/laravel-forge-connection";
import { NetlifyConnectionMethod } from "@app/hooks/api/appConnections/types/netlify-connection"; import { NetlifyConnectionMethod } from "@app/hooks/api/appConnections/types/netlify-connection";
@@ -57,7 +58,6 @@ import { OCIConnectionMethod } from "@app/hooks/api/appConnections/types/oci-con
import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection"; import { RailwayConnectionMethod } from "@app/hooks/api/appConnections/types/railway-connection";
import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection"; import { RenderConnectionMethod } from "@app/hooks/api/appConnections/types/render-connection";
import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/supabase-connection"; import { SupabaseConnectionMethod } from "@app/hooks/api/appConnections/types/supabase-connection";
import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection";
export const APP_CONNECTION_MAP: Record< export const APP_CONNECTION_MAP: Record<
AppConnection, AppConnection,
@@ -57,8 +57,8 @@ export * from "./camunda-connection";
export * from "./checkly-connection"; export * from "./checkly-connection";
export * from "./chef-connection"; export * from "./chef-connection";
export * from "./cloudflare-connection"; export * from "./cloudflare-connection";
export * from "./dns-made-easy-connection";
export * from "./databricks-connection"; export * from "./databricks-connection";
export * from "./dns-made-easy-connection";
export * from "./flyio-connection"; export * from "./flyio-connection";
export * from "./gcp-connection"; export * from "./gcp-connection";
export * from "./github-connection"; export * from "./github-connection";
+2 -2
View File
@@ -14,8 +14,8 @@ export const KmsLayout = () => {
const location = useLocation(); const location = useLocation();
return ( return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden"> <div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-b border-mineshaft-600 bg-mineshaft-900"> <div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
@@ -22,7 +22,7 @@ import {
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Link, useLocation, useNavigate, useRouter, useRouterState } from "@tanstack/react-router"; import { Link, useLocation, useNavigate, useRouter } from "@tanstack/react-router";
import { UserPlusIcon } from "lucide-react"; import { UserPlusIcon } from "lucide-react";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
@@ -31,7 +31,6 @@ import { createNotification } from "@app/components/notifications";
import { OrgPermissionCan } from "@app/components/permissions"; import { OrgPermissionCan } from "@app/components/permissions";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { import {
BreadcrumbContainer,
Button, Button,
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -43,7 +42,6 @@ import {
IconButton, IconButton,
Modal, Modal,
ModalContent, ModalContent,
TBreadcrumbFormat,
Tooltip Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { Badge, InstanceIcon, OrgIcon, SubOrgIcon } from "@app/components/v3"; import { Badge, InstanceIcon, OrgIcon, SubOrgIcon } from "@app/components/v3";
@@ -69,6 +67,7 @@ import { MfaMethod } from "@app/hooks/api/auth/types";
import { getAuthToken } from "@app/hooks/api/reactQuery"; import { getAuthToken } from "@app/hooks/api/reactQuery";
import { Organization, SubscriptionPlan } from "@app/hooks/api/types"; import { Organization, SubscriptionPlan } from "@app/hooks/api/types";
import { AuthMethod } from "@app/hooks/api/users/types"; import { AuthMethod } from "@app/hooks/api/users/types";
import { ProjectSelect } from "@app/layouts/ProjectLayout/components/ProjectSelect";
import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils"; import { navigateUserToOrg } from "@app/pages/auth/LoginPage/Login.utils";
import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel"; import { ServerAdminsPanel } from "../ServerAdminsPanel/ServerAdminsPanel";
@@ -183,9 +182,6 @@ export const Navbar = () => {
} }
}, [subscription, isBillingPage, isModalIntrusive]); }, [subscription, isBillingPage, isModalIntrusive]);
const matches = useRouterState({ select: (s) => s.matches.at(-1)?.context });
const breadcrumbs = matches && "breadcrumbs" in matches ? matches.breadcrumbs : undefined;
const handleOrgChange = async (orgId: string) => { const handleOrgChange = async (orgId: string) => {
queryClient.removeQueries({ queryKey: authKeys.getAuthToken }); queryClient.removeQueries({ queryKey: authKeys.getAuthToken });
queryClient.removeQueries({ queryKey: projectKeys.getAllUserProjects() }); queryClient.removeQueries({ queryKey: projectKeys.getAllUserProjects() });
@@ -249,7 +245,9 @@ export const Navbar = () => {
const isServerAdminPanel = location.pathname.startsWith("/admin"); const isServerAdminPanel = location.pathname.startsWith("/admin");
const isProjectScope = location.pathname.startsWith(`/organizations/${currentOrg.id}/projects`); const isProjectScope =
location.pathname.startsWith(`/organizations/${currentOrg.id}/projects`) &&
location.pathname !== `/organizations/${currentOrg.id}/projects`;
const handleOrgNav = async (org: Organization) => { const handleOrgNav = async (org: Organization) => {
if (currentOrg?.id === org.id) return; if (currentOrg?.id === org.id) return;
@@ -279,64 +277,59 @@ export const Navbar = () => {
}; };
return ( return (
<div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4 pt-1"> <div className="z-10 flex min-h-12 items-center bg-mineshaft-900 px-4">
<div className="mr-auto flex items-center overflow-hidden"> <div className="mr-auto flex h-full min-w-34 items-center">
<div className="shrink-0"> <div className="shrink-0">
<Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}> <Link to="/organizations/$orgId/projects" params={{ orgId: currentOrg.id }}>
<img alt="infisical logo" src="/images/logotransparent.png" className="h-4" /> <img alt="infisical logo" src="/images/logotransparent.png" className="h-4" />
</Link> </Link>
</div> </div>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p> <p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
{isServerAdminPanel ? ( {isServerAdminPanel ? (
<> <Link
<Link to="/admin"
to="/admin" className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary"
className="group flex cursor-pointer items-center gap-2 text-sm text-white transition-all duration-100 hover:text-primary" >
> <InstanceIcon className="size-3.5 text-xs text-bunker-300" />
<InstanceIcon className="size-3.5 text-xs text-bunker-300" /> <div className="whitespace-nowrap">Server Console</div>
<div className="whitespace-nowrap">Server Console</div> </Link>
</Link>
<p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
{breadcrumbs ? (
// scott: remove /admin as we show server console above
<BreadcrumbContainer breadcrumbs={breadcrumbs.slice(1) as TBreadcrumbFormat[]} />
) : null}
</>
) : ( ) : (
<> <>
<div className="flex min-w-12 items-center overflow-hidden"> <div
className={twMerge(
"relative flex min-w-16 items-center self-end rounded-t-md border-x border-t pt-1.5 pr-2 pb-2.5 pl-3",
!isProjectScope && !isSubOrganization
? "border-org/15 bg-gradient-to-b from-org/10 to-org/[0.075]"
: "border-transparent"
)}
>
{/* scott: the below is used to hide the top border from the org nav bar */}
{!isProjectScope && !isSubOrganization && (
<div className="absolute -bottom-px left-0 h-px w-full bg-mineshaft-900">
<div className="h-full bg-org/[0.075]" />
</div>
)}
<DropdownMenu modal={false} open={isOrgSelectOpen} onOpenChange={setIsOrgSelectOpen}> <DropdownMenu modal={false} open={isOrgSelectOpen} onOpenChange={setIsOrgSelectOpen}>
<div className="group flex cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100 hover:text-primary"> <div className="group mr-1 flex min-w-0 cursor-pointer items-center gap-2 overflow-hidden text-sm text-white transition-all duration-100">
<Badge <button
asChild className="flex cursor-pointer items-center gap-x-2 truncate whitespace-nowrap"
variant="org" type="button"
isTruncatable onClick={async () => {
// TODO(scott): either add badge size/style variant or create designated component for namespace/org nav bar navigate({
className={twMerge( to: "/organizations/$orgId/projects",
"gap-x-1.5 text-sm", params: { orgId: currentOrg.id }
(isProjectScope || isSubOrganization) && });
"bg-transparent text-mineshaft-200 hover:!bg-transparent hover:underline [&>svg]:!text-org" if (isSubOrganization) {
)} await router.invalidate({ sync: true }).catch(() => null);
}
}}
> >
<button <OrgIcon className={twMerge("size-[14px] shrink-0 text-org")} />
type="button" <span className="truncate">{currentOrg?.name}</span>
onClick={async () => { </button>
navigate({ <Badge variant="org" className="hidden md:inline-flex">
to: "/organizations/$orgId/projects", Organization
params: { orgId: currentOrg.id }
});
if (isSubOrganization) {
await router.invalidate({ sync: true }).catch(() => null);
}
}}
>
<OrgIcon className="size-[12px]" />
<span>{currentOrg?.name}</span>
</button>
</Badge> </Badge>
<div className="mr-1 hidden rounded-sm border border-mineshaft-500 px-1 text-xs text-bunker-300 no-underline! md:inline-block">
{getPlan(subscription)}
</div>
{subscription.cardDeclined && ( {subscription.cardDeclined && (
<Tooltip <Tooltip
content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`} content={`Your payment could not be processed${subscription.cardDeclinedReason ? `: ${subscription.cardDeclinedReason}` : ""}. Please update your payment method to continue enjoying premium features.`}
@@ -543,19 +536,15 @@ export const Navbar = () => {
)} )}
{isProjectScope && ( {isProjectScope && (
<> <>
<p className="pr-3 pl-1 text-lg text-mineshaft-400/70">/</p> <p className="pr-3 pl-3 text-lg text-mineshaft-400/70">/</p>
{breadcrumbs ? ( <ProjectSelect />
<BreadcrumbContainer
className="min-w-[15rem] flex-1"
breadcrumbs={[breadcrumbs[0]] as TBreadcrumbFormat[]}
/>
) : null}
</> </>
)} )}
</> </>
)} )}
</div> </div>
{subscription && subscription.slug === "starter" && !subscription.has_used_trial && (
{subscription && subscription.slug === "starter" && !subscription.has_used_trial ? (
<Tooltip content="Start Free Pro Trial"> <Tooltip content="Start Free Pro Trial">
<Button <Button
variant="plain" variant="plain"
@@ -576,6 +565,10 @@ export const Navbar = () => {
Free Pro Trial Free Pro Trial
</Button> </Button>
</Tooltip> </Tooltip>
) : (
<div className="mt-0.5 mr-3 hidden rounded-sm border border-mineshaft-400 px-1 text-xs text-mineshaft-100 no-underline! opacity-50 md:inline-block">
{getPlan(subscription)}
</div>
)} )}
{/* eslint-disable-next-line no-nested-ternary */} {/* eslint-disable-next-line no-nested-ternary */}
{!location.pathname.startsWith("/admin") ? ( {!location.pathname.startsWith("/admin") ? (
@@ -19,9 +19,9 @@ export const OrgNavBar = ({ isHidden }: Props) => {
const variant = isRootOrganization ? "org" : "namespace"; const variant = isRootOrganization ? "org" : "namespace";
return ( return (
<> <div className="bg-mineshaft-900">
{!isHidden && ( {!isHidden && (
<div className="dark flex w-full flex-col overflow-x-hidden border-b border-mineshaft-600 bg-mineshaft-900 px-4"> <div className="dark flex w-full flex-col overflow-x-hidden border-y border-t-org/15 border-b-org/5 bg-gradient-to-b from-org/[0.075] to-org/[0.025] px-4 pt-0.5">
<motion.div <motion.div
key="menu-org-items" key="menu-org-items"
initial={{ x: -150 }} initial={{ x: -150 }}
@@ -114,6 +114,6 @@ export const OrgNavBar = ({ isHidden }: Props) => {
isOpen={popUp?.createOrg?.isOpen} isOpen={popUp?.createOrg?.isOpen}
onClose={() => handlePopUpToggle("createOrg", false)} onClose={() => handlePopUpToggle("createOrg", false)}
/> />
</> </div>
); );
}; };
+2 -2
View File
@@ -29,8 +29,8 @@ export const PamLayout = () => {
return ( return (
<> <>
<div className="dark flex h-full w-full flex-col overflow-x-hidden"> <div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-b border-mineshaft-600 bg-mineshaft-900"> <div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
@@ -29,8 +29,8 @@ export const PkiManagerLayout = () => {
const location = useLocation(); const location = useLocation();
return ( return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden"> <div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-b border-mineshaft-600 bg-mineshaft-900"> <div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
@@ -92,7 +92,11 @@ export const ProjectSelect = () => {
}, [projects, projectFavorites, currentWorkspace]); }, [projects, projectFavorites, currentWorkspace]);
return ( return (
<div className="mr-2 flex items-center gap-1 overflow-hidden"> <div className="relative mr-2 flex min-w-16 items-center gap-1 self-end rounded-t-md border-x border-t border-project/10 bg-gradient-to-b from-project/10 to-project/[0.075] pt-1.5 pr-1 pb-2.5 pl-3">
{/* scott: the below is used to hide the top border from the org nav bar */}
<div className="absolute -bottom-px left-0 h-px w-full bg-mineshaft-900">
<div className="h-full bg-project/[0.075]" />
</div>
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<Link <Link
to={getProjectHomePage(currentWorkspace.type, currentWorkspace.environments)} to={getProjectHomePage(currentWorkspace.type, currentWorkspace.environments)}
@@ -100,16 +104,12 @@ export const ProjectSelect = () => {
projectId: currentWorkspace.id, projectId: currentWorkspace.id,
orgId: currentWorkspace.orgId orgId: currentWorkspace.orgId
}} }}
className="group flex cursor-pointer items-center gap-x-1.5 overflow-hidden hover:text-white" className="group flex cursor-pointer items-center gap-x-2 overflow-hidden pt-0.5 text-sm text-white"
> >
<p className="inline-block truncate text-mineshaft-200 group-hover:underline"> <ProjectIcon className="size-[14px] shrink-0 text-project" />
{currentWorkspace?.name} <span className="truncate">{currentWorkspace?.name}</span>
</p> <Badge variant="project" className="hidden md:inline-flex">
<Badge variant="project"> {currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"}
<ProjectIcon />
<span className="hidden sm:inline-block">
{currentWorkspace.type ? PROJECT_TYPE_NAME[currentWorkspace.type] : "Project"}
</span>
</Badge> </Badge>
</Link> </Link>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
@@ -118,7 +118,7 @@ export const ProjectSelect = () => {
variant="plain" variant="plain"
colorSchema="secondary" colorSchema="secondary"
ariaLabel="switch-project" ariaLabel="switch-project"
className="px-2 py-1" className="top-px px-2 py-1"
> >
<FontAwesomeIcon icon={faCaretDown} className="text-xs text-bunker-300" /> <FontAwesomeIcon icon={faCaretDown} className="text-xs text-bunker-300" />
</IconButton> </IconButton>
@@ -39,8 +39,8 @@ export const SecretManagerLayout = () => {
(secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0); (secretApprovalReqCount?.open || 0) + (accessApprovalRequestCount?.pendingCount || 0);
return ( return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden"> <div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-b border-mineshaft-600 bg-mineshaft-900"> <div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
@@ -38,8 +38,8 @@ export const SecretScanningLayout = () => {
); );
return ( return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden"> <div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-b border-mineshaft-600 bg-mineshaft-900"> <div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
+2 -2
View File
@@ -20,8 +20,8 @@ export const SshLayout = () => {
const location = useLocation(); const location = useLocation();
return ( return (
<div className="dark flex h-full w-full flex-col overflow-x-hidden"> <div className="dark flex h-full w-full flex-col overflow-x-hidden bg-mineshaft-900">
<div className="border-b border-mineshaft-600 bg-mineshaft-900"> <div className="border-y border-t-project/10 border-b-project/5 bg-gradient-to-b from-project/[0.075] to-project/[0.025] px-4 pt-0.5">
<motion.div <motion.div
key="menu-project-items" key="menu-project-items"
initial={{ x: -150 }} initial={{ x: -150 }}
@@ -1,5 +1,5 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { Controller, FormProvider, useForm } from "react-hook-form"; import { Controller, FormProvider, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { import {
@@ -14,8 +14,8 @@ import {
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections"; import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { TDNSMadeEasyConnection } from "@app/hooks/api/appConnections"; import { TDNSMadeEasyConnection } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection"; import { DNSMadeEasyConnectionMethod } from "@app/hooks/api/appConnections/types/dns-made-easy-connection";
import { import {
genericAppConnectionFieldsSchema, genericAppConnectionFieldsSchema,
GenericAppConnectionsFields GenericAppConnectionsFields