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

@@ -1,32 +0,0 @@
import Link from 'next/link';
export function DocsCard({
title,
subtitle,
children,
link,
}: React.PropsWithChildren<{
title: string;
subtitle?: string | null;
link: { url: string; label?: string };
}>) {
return (
<Link href={link.url} className="flex flex-col">
<div
className={`hover:bg-muted/70 flex grow flex-col gap-y-0.5 rounded border p-4`}
>
<h3 className="mt-0 text-lg font-medium hover:underline dark:text-white">
{title}
</h3>
{subtitle && (
<div className="text-muted-foreground text-sm">
<p dangerouslySetInnerHTML={{ __html: subtitle }}></p>
</div>
)}
{children && <div className="text-sm">{children}</div>}
</div>
</Link>
);
}

View File

@@ -1,24 +0,0 @@
import { Cms } from '@kit/cms';
import { DocsCard } from './docs-card';
export function DocsCards({ cards }: { cards: Cms.ContentItem[] }) {
const cardsSortedByOrder = [...cards].sort((a, b) => a.order - b.order);
return (
<div className={'absolute flex w-full flex-col gap-4 pb-48 lg:max-w-2xl'}>
{cardsSortedByOrder.map((item) => {
return (
<DocsCard
key={item.title}
title={item.title}
subtitle={item.description}
link={{
url: `/docs/${item.slug}`,
}}
/>
);
})}
</div>
);
}

View File

@@ -1,32 +0,0 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { SidebarMenuButton, SidebarMenuItem } from '@kit/ui/shadcn-sidebar';
import { cn, isRouteActive } from '@kit/ui/utils';
export function DocsNavLink({
label,
url,
children,
}: React.PropsWithChildren<{ label: string; url: string }>) {
const currentPath = usePathname();
const isCurrent = isRouteActive(url, currentPath, true);
return (
<SidebarMenuItem>
<SidebarMenuButton
asChild
isActive={isCurrent}
className={cn('text-secondary-foreground transition-all')}
>
<Link href={url}>
<span className="block max-w-full truncate">{label}</span>
{children}
</Link>
</SidebarMenuButton>
</SidebarMenuItem>
);
}

View File

@@ -1,32 +0,0 @@
'use client';
import { usePathname } from 'next/navigation';
import { Cms } from '@kit/cms';
import { Collapsible } from '@kit/ui/collapsible';
import { cn, isRouteActive } from '@kit/ui/utils';
export function DocsNavigationCollapsible(
props: React.PropsWithChildren<{
node: Cms.ContentItem;
prefix: string;
}>,
) {
const currentPath = usePathname();
const prefix = props.prefix;
const isChildActive = props.node.children.some((child) =>
isRouteActive(prefix + '/' + child.url, currentPath, false),
);
return (
<Collapsible
className={cn('group/collapsible', {
'group/active': isChildActive,
})}
defaultOpen={isChildActive ? true : !props.node.collapsed}
>
{props.children}
</Collapsible>
);
}

View File

@@ -1,167 +0,0 @@
import { ChevronDown } from 'lucide-react';
import { Cms } from '@kit/cms';
import { CollapsibleContent, CollapsibleTrigger } from '@kit/ui/collapsible';
import {
Sidebar,
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
} from '@kit/ui/shadcn-sidebar';
import { DocsNavLink } from '~/(marketing)/docs/_components/docs-nav-link';
import { DocsNavigationCollapsible } from '~/(marketing)/docs/_components/docs-navigation-collapsible';
import { FloatingDocumentationNavigation } from './floating-docs-navigation';
function Node({
node,
level,
prefix,
}: {
node: Cms.ContentItem;
level: number;
prefix: string;
}) {
const url = `${prefix}/${node.slug}`;
const label = node.label ? node.label : node.title;
return (
<NodeContainer node={node} prefix={prefix}>
<NodeTrigger node={node} label={label} url={url} />
<NodeContentContainer node={node}>
<Tree pages={node.children ?? []} level={level + 1} prefix={prefix} />
</NodeContentContainer>
</NodeContainer>
);
}
function NodeContentContainer({
node,
children,
}: {
node: Cms.ContentItem;
children: React.ReactNode;
}) {
if (node.collapsible) {
return <CollapsibleContent>{children}</CollapsibleContent>;
}
return children;
}
function NodeContainer({
node,
prefix,
children,
}: {
node: Cms.ContentItem;
prefix: string;
children: React.ReactNode;
}) {
if (node.collapsible) {
return (
<DocsNavigationCollapsible node={node} prefix={prefix}>
{children}
</DocsNavigationCollapsible>
);
}
return children;
}
function NodeTrigger({
node,
label,
url,
}: {
node: Cms.ContentItem;
label: string;
url: string;
}) {
if (node.collapsible) {
return (
<CollapsibleTrigger asChild>
<SidebarMenuItem>
<SidebarMenuButton>
{label}
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
</SidebarMenuButton>
</SidebarMenuItem>
</CollapsibleTrigger>
);
}
return <DocsNavLink label={label} url={url} />;
}
function Tree({
pages,
level,
prefix,
}: {
pages: Cms.ContentItem[];
level: number;
prefix: string;
}) {
if (level === 0) {
return pages.map((treeNode, index) => (
<Node key={index} node={treeNode} level={level} prefix={prefix} />
));
}
if (pages.length === 0) {
return null;
}
return (
<SidebarMenuSub>
{pages.map((treeNode, index) => (
<Node key={index} node={treeNode} level={level} prefix={prefix} />
))}
</SidebarMenuSub>
);
}
export function DocsNavigation({
pages,
prefix = '/docs',
}: {
pages: Cms.ContentItem[];
prefix?: string;
}) {
return (
<>
<Sidebar
variant={'ghost'}
className={
'border-border/50 sticky z-1 mt-4 max-h-full overflow-y-auto pr-4'
}
>
<SidebarGroup className="p-0">
<SidebarGroupContent>
<SidebarMenu className={'pb-48'}>
<Tree pages={pages} level={0} prefix={prefix} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</Sidebar>
<div className={'lg:hidden'}>
<FloatingDocumentationNavigation>
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
<Tree pages={pages} level={0} prefix={prefix} />
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</FloatingDocumentationNavigation>
</div>
</>
);
}

View File

@@ -1,72 +0,0 @@
'use client';
import { useEffect, useEffectEvent, useMemo, useState } from 'react';
import { usePathname } from 'next/navigation';
import { Menu } from 'lucide-react';
import { isBrowser } from '@kit/shared/utils';
import { Button } from '@kit/ui/button';
import { If } from '@kit/ui/if';
export function FloatingDocumentationNavigation(
props: React.PropsWithChildren,
) {
const activePath = usePathname();
const body = useMemo(() => {
return isBrowser() ? document.body : null;
}, []);
const [isVisible, setIsVisible] = useState(false);
const enableScrolling = useEffectEvent(
() => body && (body.style.overflowY = ''),
);
const disableScrolling = useEffectEvent(
() => body && (body.style.overflowY = 'hidden'),
);
// enable/disable body scrolling when the docs are toggled
useEffect(() => {
if (isVisible) {
disableScrolling();
} else {
enableScrolling();
}
}, [isVisible]);
// hide docs when navigating to another page
useEffect(() => {
// eslint-disable-next-line react-hooks/set-state-in-effect
setIsVisible(false);
}, [activePath]);
const onClick = () => {
setIsVisible(!isVisible);
};
return (
<>
<If condition={isVisible}>
<div
className={
'fixed top-0 left-0 z-10 h-screen w-full p-4' +
' dark:bg-background flex flex-col space-y-4 overflow-auto bg-white'
}
>
{props.children}
</div>
</If>
<Button
className={'fixed right-5 bottom-5 z-10 h-16 w-16 rounded-full'}
onClick={onClick}
>
<Menu className={'h-8'} />
</Button>
</>
);
}