Unify workspace dropdowns; Update layouts (#458)

Unified Account and Workspace drop-downs; Layout updates, now header lives within the PageBody component; Sidebars now use floating variant
This commit is contained in:
Giancarlo Buomprisco
2026-03-11 14:45:42 +08:00
committed by GitHub
parent ca585e09be
commit 4bc8448a1d
530 changed files with 14398 additions and 11198 deletions

View File

@@ -0,0 +1,58 @@
import { Footer } from '@kit/ui/marketing';
import { Trans } from '@kit/ui/trans';
import { AppLogo } from '~/components/app-logo';
import appConfig from '~/config/app.config';
export function SiteFooter() {
return (
<Footer
logo={<AppLogo className="w-[85px] md:w-[95px]" />}
description={<Trans i18nKey="marketing.footerDescription" />}
copyright={
<Trans
i18nKey="marketing.copyright"
values={{
product: appConfig.name,
year: new Date().getFullYear(),
}}
/>
}
sections={[
{
heading: <Trans i18nKey="marketing.about" />,
links: [
{ href: '/blog', label: <Trans i18nKey="marketing.blog" /> },
{ href: '/contact', label: <Trans i18nKey="marketing.contact" /> },
],
},
{
heading: <Trans i18nKey="marketing.product" />,
links: [
{
href: '/docs',
label: <Trans i18nKey="marketing.documentation" />,
},
],
},
{
heading: <Trans i18nKey="marketing.legal" />,
links: [
{
href: '/terms-of-service',
label: <Trans i18nKey="marketing.termsOfService" />,
},
{
href: '/privacy-policy',
label: <Trans i18nKey="marketing.privacyPolicy" />,
},
{
href: '/cookie-policy',
label: <Trans i18nKey="marketing.cookiePolicy" />,
},
],
},
]}
/>
);
}

View File

@@ -0,0 +1,107 @@
'use client';
import dynamic from 'next/dynamic';
import Link from 'next/link';
import { PersonalAccountDropdown } from '@kit/accounts/personal-account-dropdown';
import { useSignOut } from '@kit/supabase/hooks/use-sign-out';
import { JWTUserData } from '@kit/supabase/types';
import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if';
import { Trans } from '@kit/ui/trans';
import featuresFlagConfig from '~/config/feature-flags.config';
import pathsConfig from '~/config/paths.config';
const ModeToggle = dynamic(
() =>
import('@kit/ui/mode-toggle').then((mod) => ({
default: mod.ModeToggle,
})),
{ ssr: false },
);
const MobileModeToggle = dynamic(
() =>
import('@kit/ui/mobile-mode-toggle').then((mod) => ({
default: mod.MobileModeToggle,
})),
{ ssr: false },
);
const paths = {
home: pathsConfig.app.home,
profileSettings: pathsConfig.app.personalAccountSettings,
};
const features = {
enableThemeToggle: featuresFlagConfig.enableThemeToggle,
};
export function SiteHeaderAccountSection({
user,
}: {
user: JWTUserData | null;
}) {
const signOut = useSignOut();
if (user) {
return (
<PersonalAccountDropdown
showProfileName={false}
paths={paths}
features={features}
user={user}
signOutRequested={() => signOut.mutateAsync()}
/>
);
}
return <AuthButtons />;
}
function AuthButtons() {
return (
<div
className={'animate-in fade-in flex items-center gap-x-2 duration-500'}
>
<div className={'hidden md:flex'}>
<If condition={features.enableThemeToggle}>
<ModeToggle />
</If>
</div>
<div className={'md:hidden'}>
<If condition={features.enableThemeToggle}>
<MobileModeToggle />
</If>
</div>
<div className={'flex items-center gap-x-2'}>
<Button
nativeButton={false}
className={'hidden md:flex md:text-sm'}
render={
<Link href={pathsConfig.auth.signIn}>
<Trans i18nKey={'auth.signIn'} />
</Link>
}
variant={'outline'}
size={'sm'}
/>
<Button
nativeButton={false}
render={
<Link href={pathsConfig.auth.signUp}>
<Trans i18nKey={'auth.signUp'} />
</Link>
}
className="text-xs md:text-sm"
variant={'default'}
size={'sm'}
/>
</div>
</div>
);
}

View File

@@ -0,0 +1,17 @@
import { JWTUserData } from '@kit/supabase/types';
import { Header } from '@kit/ui/marketing';
import { AppLogo } from '~/components/app-logo';
import { SiteHeaderAccountSection } from './site-header-account-section';
import { SiteNavigation } from './site-navigation';
export function SiteHeader(props: { user?: JWTUserData | null }) {
return (
<Header
logo={<AppLogo className="mx-auto sm:mx-0" href="/" />}
navigation={<SiteNavigation />}
actions={<SiteHeaderAccountSection user={props.user ?? null} />}
/>
);
}

View File

@@ -0,0 +1,37 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { NavigationMenuItem } from '@kit/ui/navigation-menu';
import { cn, isRouteActive } from '@kit/ui/utils';
const getClassName = (path: string, currentPathName: string) => {
const isActive = isRouteActive(path, currentPathName);
return cn(
`inline-flex w-max text-sm font-medium transition-colors duration-300`,
{
'dark:text-gray-300 dark:hover:text-white': !isActive,
'text-current dark:text-white': isActive,
},
);
};
export function SiteNavigationItem({
path,
children,
}: React.PropsWithChildren<{
path: string;
}>) {
const currentPathName = usePathname();
const className = getClassName(path, currentPathName);
return (
<NavigationMenuItem key={path}>
<Link className={className} href={path} as={path} prefetch={true}>
{children}
</Link>
</NavigationMenuItem>
);
}

View File

@@ -0,0 +1,90 @@
import Link from 'next/link';
import { Menu } from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@kit/ui/dropdown-menu';
import { NavigationMenu, NavigationMenuList } from '@kit/ui/navigation-menu';
import { Trans } from '@kit/ui/trans';
import { SiteNavigationItem } from './site-navigation-item';
const links = {
Blog: {
label: 'marketing.blog',
path: '/blog',
},
Changelog: {
label: 'marketing.changelog',
path: '/changelog',
},
Docs: {
label: 'marketing.documentation',
path: '/docs',
},
Pricing: {
label: 'marketing.pricing',
path: '/pricing',
},
FAQ: {
label: 'marketing.faq',
path: '/faq',
},
};
export function SiteNavigation() {
const NavItems = Object.values(links).map((item) => {
return (
<SiteNavigationItem key={item.path} path={item.path}>
<Trans i18nKey={item.label} />
</SiteNavigationItem>
);
});
return (
<>
<div className={'hidden items-center justify-center md:flex'}>
<NavigationMenu>
<NavigationMenuList className={'gap-x-2.5'}>
{NavItems}
</NavigationMenuList>
</NavigationMenu>
</div>
<div className={'flex justify-start sm:items-center md:hidden'}>
<MobileDropdown />
</div>
</>
);
}
function MobileDropdown() {
return (
<DropdownMenu>
<DropdownMenuTrigger aria-label={'Open Menu'}>
<Menu className={'h-8 w-8'} />
</DropdownMenuTrigger>
<DropdownMenuContent className={'w-full'}>
{Object.values(links).map((item) => {
const className = 'flex w-full h-full items-center';
return (
<DropdownMenuItem
key={item.path}
render={
<Link className={className} href={item.path}>
<Trans i18nKey={item.label} />
</Link>
}
/>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -0,0 +1,47 @@
import { cn } from '@kit/ui/utils';
export function SitePageHeader({
title,
subtitle,
container = true,
className = '',
}: {
title: string;
subtitle: string;
container?: boolean;
className?: string;
}) {
const containerClass = container ? 'container' : '';
return (
<div
className={cn(
'border-border/40 border-b py-6 xl:py-8 2xl:py-10',
className,
)}
>
<div
className={cn(
'flex flex-col items-center gap-y-2 lg:gap-y-3',
containerClass,
)}
>
<h1
className={
'font-heading text-3xl tracking-tighter xl:text-5xl dark:text-white'
}
>
{title}
</h1>
<h2
className={
'text-muted-foreground text-lg tracking-tight 2xl:text-2xl'
}
>
{subtitle}
</h2>
</div>
</div>
);
}