Files
HiveGrid-Wireframes/hivegrid-cloud/apps/web/app/components/public/PublicHeader.tsx
T

191 lines
8.4 KiB
TypeScript

import { Fragment, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocation } from "react-router";
import { Transition } from "@headlessui/react";
import clsx from "clsx";
import { MenuIcon, XIcon } from "~/components/ui/icons/vendor";
import Icon from "~/components/brand/Icon";
import Logo from "~/components/brand/Logo";
import ProfileButton from "~/components/layouts/buttons/ProfileButton";
import ButtonEvent from "~/components/ui/buttons/ButtonEvent";
import ButtonPrimary from "~/components/ui/buttons/ButtonPrimary";
import ButtonTertiary from "~/components/ui/buttons/ButtonTertiary";
import LocaleSelector from "~/components/ui/selectors/LocaleSelector";
import ThemeSelector from "~/components/ui/selectors/ThemeSelector";
import DarkModeToggle from "~/components/ui/toggles/DarkModeToggle";
import { useRootData } from "~/utils/data/useRootData";
import { absoluteAppRootPath } from "~/utils/app/CrossHostUrls";
type PublicHeaderLink = {
title: string;
path: string;
target?: "_blank";
};
type Props = {
links?: PublicHeaderLink[];
width?: "screen-2xl" | "7xl";
withLogo?: boolean;
withSignInAndSignUp?: boolean;
withDarkModeToggle?: boolean;
withLanguageSelector?: boolean;
withThemeSelector?: boolean;
};
export default function PublicHeader({
links,
width = "7xl",
withLogo = true,
withSignInAndSignUp = true,
withDarkModeToggle = true,
withLanguageSelector = true,
withThemeSelector = true,
}: Props) {
const { authenticated, appConfiguration, user, routing } = useRootData();
const { t } = useTranslation();
const location = useLocation();
const [open, setOpen] = useState(false);
const navLinks = links ?? [];
const hasProfileButton = appConfiguration?.app.features.tenantHome === "/";
const loginOrEnterRoute = authenticated ? absoluteAppRootPath(routing) : "/login";
const canPublicSignUp = Boolean(appConfiguration?.subscription.allowSignUpBeforeSubscribe);
return (
<header className="pb-6">
<div className="relative pt-6">
<div className={clsx("mx-auto px-4 sm:px-6", width === "screen-2xl" ? "max-w-(--breakpoint-2xl)" : "max-w-7xl")}>
<nav className="relative flex items-center justify-between sm:h-10 md:justify-center" aria-label={t("components.publicHeader.globalNavigation")}>
<div className="flex flex-1 items-center md:absolute md:inset-y-0 md:left-0">
<div className="flex w-full items-center justify-between md:w-auto">
{withLogo ? (
<>
<Logo className="hidden lg:block" size="h-9" />
<Icon className="lg:hidden" size="h-9" />
</>
) : (
<div />
)}
<div className="-mr-1 flex min-w-0 items-center gap-2 md:hidden">
{withSignInAndSignUp && (
<div className="flex min-w-0 flex-wrap justify-end gap-2 rounded-md">
{!authenticated && canPublicSignUp && (
<ButtonTertiary to="/register" className="h-auto min-h-9 min-w-0 text-center whitespace-normal">
{t("account.shared.signUp")}
</ButtonTertiary>
)}
{(!hasProfileButton || !authenticated) && (
<ButtonPrimary to={loginOrEnterRoute} className="h-auto min-h-9 min-w-0 text-center whitespace-normal">
{authenticated ? t("shared.enter") : t("account.shared.signIn")}
</ButtonPrimary>
)}
</div>
)}
{navLinks.length > 0 && (
<button
onClick={() => setOpen(!open)}
type="button"
className="text-muted-foreground hover:bg-secondary hover:text-secondary-foreground inline-flex items-center justify-center rounded-md p-2 focus:outline-hidden"
aria-expanded={open}
aria-label={open ? t("shared.closeMenu") : t("shared.openMenu")}
>
<span className="sr-only">{open ? t("shared.closeMenu") : t("shared.openMenu")}</span>
<MenuIcon className="size-6" />
</button>
)}
{hasProfileButton && authenticated && <ProfileButton user={user} layout="/" />}
</div>
</div>
</div>
<div className="hidden items-center gap-2 sm:gap-4 md:flex md:gap-6">
{navLinks.map((link) => (
<ButtonEvent
key={link.path}
to={link.path}
target={link.target}
className={clsx(
"truncate rounded-sm px-3 py-1 text-base leading-6 font-medium transition duration-150 ease-in-out focus:outline-hidden",
location.pathname !== link.path && "text-muted-foreground"
)}
event={{ action: "click", category: "header", label: link.title, value: link.path }}
>
{link.title}
</ButtonEvent>
))}
{withLanguageSelector && <LocaleSelector className="hidden lg:flex" />}
{withDarkModeToggle && <DarkModeToggle className="hidden lg:flex" />}
{withThemeSelector && <ThemeSelector className="hidden lg:flex" />}
</div>
<div className="hidden md:absolute md:inset-y-0 md:right-0 md:flex md:items-center md:justify-end">
<span className="inline-flex min-w-0 gap-2">
{hasProfileButton && authenticated ? (
<ProfileButton user={user} layout="/" />
) : (
withSignInAndSignUp && (
<>
{!authenticated && canPublicSignUp && (
<ButtonTertiary to="/register" className="h-auto min-h-9 min-w-0 text-center whitespace-normal">
{t("account.shared.signUp")}
</ButtonTertiary>
)}
<ButtonPrimary to={loginOrEnterRoute} className="h-auto min-h-9 min-w-0 text-center whitespace-normal">
{authenticated ? t("shared.enter") : t("account.shared.signIn")}
</ButtonPrimary>
</>
)
)}
</span>
</div>
</nav>
</div>
<Transition
show={open}
as={Fragment}
enter="duration-150 ease-out"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="duration-100 ease-in"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<div className="absolute inset-x-0 top-0 z-40 origin-top-right transform p-2 transition md:hidden">
<div className="bg-background border-border overflow-visible rounded-lg border shadow-xl">
<div className="flex items-center justify-between px-5 pt-4">
<div>{withLogo && <Icon />}</div>
<button
onClick={() => setOpen(false)}
type="button"
className="hover:bg-secondary hover:text-secondary-foreground inline-flex items-center justify-center rounded-md p-2 focus:outline-hidden"
>
<span className="sr-only">{t("shared.close")}</span>
<XIcon className="size-6" />
</button>
</div>
<div className="px-2 py-3">
{navLinks.map((link) => (
<ButtonEvent
key={link.path}
to={link.path}
target={link.target}
role="menuitem"
className={clsx(
"hover:bg-secondary hover:text-secondary-foreground block rounded-md px-3 py-2 text-base font-medium",
location.pathname === link.path && "bg-secondary"
)}
event={{ action: "click", category: "header", label: link.title, value: link.path }}
>
{link.title}
</ButtonEvent>
))}
</div>
</div>
</div>
</Transition>
</div>
</header>
);
}