mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-24 20:09:02 +00:00
the front door and the docs chrome state their tracks, on @hanzo/ui
This commit is contained in:
+252
-146
@@ -15,17 +15,7 @@ import {
|
||||
Terminal,
|
||||
Workflow,
|
||||
} from 'lucide-react';
|
||||
import { buttonVariants } from '@hanzo/docs-base-ui/components/ui/button';
|
||||
import { Badge } from '@hanzo/docs-base-ui/components/ui/badge';
|
||||
import { cn } from '@/lib/cn';
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from '@hanzo/docs-base-ui/components/ui/card';
|
||||
import { Card, CardContent, CardTitle, XStack } from '@hanzo/ui';
|
||||
import { Grid } from '@hanzo/ui/grid';
|
||||
import { AgentActions } from '@/components/agent-actions';
|
||||
import { Tabs, Tab } from '@hanzo/docs-base-ui/components/tabs';
|
||||
@@ -146,7 +136,14 @@ export default function Page() {
|
||||
// because the sidebar takes 268px from `md` up and 768→1024→1280 step by 256.
|
||||
<main className="[grid-area:main] min-w-0 pb-6 md:pb-12">
|
||||
{/* -- Hero ---------------------------------------------------------- */}
|
||||
<section className="relative flex flex-col items-center text-center mx-auto w-full max-w-5xl px-6 pt-24 pb-16 md:pt-36 md:pb-24">
|
||||
{/* One column, centred. It was `flex flex-col items-center`, which is the
|
||||
same picture drawn with the wrong primitive: a column of blocks IS a
|
||||
one-track grid, and saying so means `justifyItems` centres the children
|
||||
without each of them having to be told. */}
|
||||
<section
|
||||
className="relative mx-auto w-full max-w-5xl px-6 pt-24 pb-16 md:pt-36 md:pb-24"
|
||||
style={{ display: 'grid', justifyItems: 'center', textAlign: 'center' }}
|
||||
>
|
||||
{/* The atmosphere behind the headline, as dots rather than a gradient —
|
||||
the halftone IS the mark, and a gradient carries none of it. `fade`
|
||||
dissolves the canvas rectangle on both axes so the field ends in the
|
||||
@@ -160,9 +157,10 @@ export default function Page() {
|
||||
href="https://hanzo.ai/enso"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className="relative mb-8 inline-flex items-center gap-2 rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm text-neutral-400 backdrop-blur transition-colors hover:border-white/20 hover:text-white"
|
||||
className="relative mb-8 rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-sm text-neutral-400 backdrop-blur transition-colors hover:border-white/20 hover:text-white"
|
||||
style={{ display: 'inline-grid', gridAutoFlow: 'column', alignItems: 'center', columnGap: 8 }}
|
||||
>
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-white" />
|
||||
<span className="rounded-full bg-white" style={{ width: 6, height: 6 }} />
|
||||
Meet Enso — our frontier model, default on every surface
|
||||
<ArrowRight className="size-3.5" />
|
||||
</Link>
|
||||
@@ -183,10 +181,30 @@ export default function Page() {
|
||||
|
||||
{/* -- Install command -- the main CTA ------------------------------ */}
|
||||
<div className="relative mt-10 w-full max-w-lg">
|
||||
<Card className="border-white/[0.08] bg-white/[0.03] p-1 shadow-none gap-0 py-0 rounded-2xl">
|
||||
<CardContent className="flex items-center gap-3 rounded-xl bg-[#0a0a0a] px-5 py-4 font-mono text-sm">
|
||||
<span className="text-neutral-400 select-none">$</span>
|
||||
<span className="text-white flex-1 text-left">curl hanzo.sh | sh</span>
|
||||
<Card
|
||||
backgroundColor="rgba(255,255,255,0.03)"
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
borderRadius={16}
|
||||
padding={4}
|
||||
paddingVertical={4}
|
||||
gap={0}
|
||||
>
|
||||
{/* `$` then the command: a fixed leader and a track that takes the
|
||||
rest. The `flex-1` that used to be on the span was the child
|
||||
claiming the width; the track owns it now. */}
|
||||
<CardContent
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto minmax(0, 1fr)',
|
||||
alignItems: 'center',
|
||||
columnGap: 12,
|
||||
borderRadius: 12,
|
||||
background: '#0a0a0a',
|
||||
padding: '16px 20px',
|
||||
}}
|
||||
>
|
||||
<span className="font-mono text-sm text-neutral-400 select-none">$</span>
|
||||
<span className="font-mono text-sm text-white" style={{ textAlign: 'left' }}>curl hanzo.sh | sh</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<p className="text-xs text-neutral-400 mt-3">
|
||||
@@ -206,7 +224,7 @@ export default function Page() {
|
||||
It also has to be here rather than in the bar: this page has no table
|
||||
of contents, which is where the doc pages host it, so without this the
|
||||
front door offered no way to hand anything to an agent at all. */}
|
||||
<div className="relative mt-6 flex flex-col items-center gap-2">
|
||||
<div className="relative mt-6" style={{ display: 'grid', justifyItems: 'center', rowGap: 8 }}>
|
||||
<AgentActions />
|
||||
<p className="text-xs text-neutral-400">
|
||||
Or hand this to your agent — it installs the CLI, the MCP server and
|
||||
@@ -262,16 +280,29 @@ export default function Page() {
|
||||
href: '/docs/openapi',
|
||||
},
|
||||
].map((d) => (
|
||||
// Three rows — eyebrow, title, body — so the gaps are declared once
|
||||
// on the card instead of as a margin on each child that has to know
|
||||
// what follows it.
|
||||
<Link
|
||||
key={d.title}
|
||||
href={d.href}
|
||||
{...(d.external ? { target: '_blank', rel: 'noreferrer' } : {})}
|
||||
className="group flex flex-col rounded-2xl border border-white/[0.08] bg-white/[0.02] p-6 text-left transition-all duration-200 hover:-translate-y-0.5 hover:border-white/20 hover:bg-white/[0.05]"
|
||||
className="group rounded-2xl border border-white/[0.08] bg-white/[0.02] p-6 transition-all duration-200 hover:-translate-y-0.5 hover:border-white/20 hover:bg-white/[0.05]"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateRows: 'auto auto auto',
|
||||
alignContent: 'start',
|
||||
rowGap: 6,
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
<span className="mb-2 text-xs font-medium text-neutral-300">
|
||||
<span className="text-xs font-medium text-neutral-300">
|
||||
{d.eyebrow}
|
||||
</span>
|
||||
<span className="mb-1.5 flex items-center gap-1.5 text-lg font-semibold text-white">
|
||||
<span
|
||||
className="text-lg font-semibold text-white"
|
||||
style={{ display: 'grid', gridAutoFlow: 'column', justifyContent: 'start', alignItems: 'center', columnGap: 6 }}
|
||||
>
|
||||
{d.title}
|
||||
<ArrowRight className="size-4 opacity-40 transition-all group-hover:translate-x-0.5 group-hover:opacity-100" />
|
||||
</span>
|
||||
@@ -369,9 +400,27 @@ export default function Page() {
|
||||
? { target: '_blank' as const, rel: 'noreferrer noopener' }
|
||||
: {};
|
||||
return (
|
||||
// Four rows: the icon/tag line, the name, the description, and
|
||||
// the footer pushed to the bottom by `1fr` on the description
|
||||
// row. Before, the footer was a separate flex child relying on
|
||||
// the frame's own gap; now the card states its own shape and the
|
||||
// minimum height has something to distribute.
|
||||
<Card
|
||||
key={item.name}
|
||||
className="group relative border-white/[0.08] bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.05] transition-all duration-300 shadow-none py-0 gap-0 rounded-2xl min-h-[140px] sm:min-h-[160px]"
|
||||
className="group"
|
||||
minHeight={140}
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
hoverStyle={{ borderColor: 'rgba(255,255,255,0.2)', backgroundColor: 'rgba(255,255,255,0.05)' }}
|
||||
borderRadius={16}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
style={{
|
||||
position: 'relative',
|
||||
display: 'grid',
|
||||
gridTemplateRows: 'auto auto 1fr auto',
|
||||
transition: 'all 300ms',
|
||||
}}
|
||||
>
|
||||
{/* The whole card is the click target, so the link has no text
|
||||
of its own — it takes its accessible name from the card
|
||||
@@ -387,27 +436,27 @@ export default function Page() {
|
||||
) : (
|
||||
<Link href={item.href} aria-label={item.name} className="absolute inset-0 z-10" />
|
||||
)}
|
||||
<CardHeader className="p-5 sm:p-6 pb-0 gap-0 grid-rows-none">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="rounded-xl bg-white/[0.06] p-2">
|
||||
<item.icon className="size-5 text-neutral-400 group-hover:text-white transition-colors" />
|
||||
</div>
|
||||
<Badge variant="ghost" className="text-[11px] text-neutral-400 font-medium border-none px-0">
|
||||
{item.tag}
|
||||
</Badge>
|
||||
<div
|
||||
className="p-5 sm:p-6 pb-0"
|
||||
style={{ display: 'grid', gridTemplateColumns: '1fr auto', alignItems: 'center', marginBottom: 12 }}
|
||||
>
|
||||
<div className="rounded-xl bg-white/[0.06] p-2" style={{ justifySelf: 'start' }}>
|
||||
<item.icon className="size-5 text-neutral-400 group-hover:text-white transition-colors" />
|
||||
</div>
|
||||
<CardTitle className="text-sm font-semibold text-white mb-1.5">
|
||||
{item.name}
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs text-neutral-400 leading-relaxed group-hover:text-neutral-200 transition-colors">
|
||||
{item.desc}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardFooter className="p-5 sm:p-6 pt-3">
|
||||
<span className="flex items-center gap-1 text-[10px] font-medium text-neutral-400 group-hover:text-white transition-colors">
|
||||
{isExternal ? 'Visit' : 'View docs'} <ArrowRight className="size-3" />
|
||||
</span>
|
||||
</CardFooter>
|
||||
<span className="text-[11px] font-medium text-neutral-400">{item.tag}</span>
|
||||
</div>
|
||||
<CardTitle paddingHorizontal={24} fontSize={14} fontWeight="600" color="#ffffff" marginBottom={6}>
|
||||
{item.name}
|
||||
</CardTitle>
|
||||
<p className="px-5 sm:px-6 text-xs text-neutral-400 leading-relaxed group-hover:text-neutral-200 transition-colors">
|
||||
{item.desc}
|
||||
</p>
|
||||
<span
|
||||
className="p-5 sm:p-6 pt-3 text-[10px] font-medium text-neutral-400 group-hover:text-white transition-colors"
|
||||
style={{ display: 'grid', gridAutoFlow: 'column', justifyContent: 'start', alignItems: 'center', columnGap: 4 }}
|
||||
>
|
||||
{isExternal ? 'Visit' : 'View docs'} <ArrowRight className="size-3" />
|
||||
</span>
|
||||
</Card>
|
||||
);
|
||||
})}
|
||||
@@ -422,21 +471,34 @@ export default function Page() {
|
||||
<p className="text-neutral-300 text-sm mb-8">
|
||||
SDKs, APIs, and protocols for every stack.
|
||||
</p>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-4 gap-4">
|
||||
{/* 230 is the 1 / 2 / 4 story the three breakpoints told, measured at
|
||||
the column rather than the window: 1-up on a phone, 2-up once the
|
||||
column clears ~476, and never more than four however wide it gets. */}
|
||||
<Grid columns={{ min: 230, max: 4 }} gap={16}>
|
||||
{devLinks.map((t) => (
|
||||
<Card
|
||||
key={t.name}
|
||||
className="group relative border-white/[0.08] bg-white/[0.02] hover:border-white/20 hover:bg-white/[0.04] transition-all duration-300 shadow-none py-0 gap-0 rounded-2xl"
|
||||
className="group"
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
hoverStyle={{ borderColor: 'rgba(255,255,255,0.2)', backgroundColor: 'rgba(255,255,255,0.04)' }}
|
||||
borderRadius={16}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
style={{ position: 'relative', transition: 'all 300ms' }}
|
||||
>
|
||||
<Link href={t.href} aria-label={t.name} className="absolute inset-0 z-10" />
|
||||
<CardContent className="p-6">
|
||||
<t.icon className="size-5 text-neutral-400 mb-4 group-hover:text-white transition-colors" />
|
||||
<CardTitle className="font-semibold text-sm text-white mb-1">{t.name}</CardTitle>
|
||||
<CardDescription className="text-xs text-neutral-400 group-hover:text-neutral-200 transition-colors">{t.desc}</CardDescription>
|
||||
<CardContent
|
||||
padding={24}
|
||||
style={{ display: 'grid', gridTemplateRows: 'auto auto auto', justifyItems: 'start', rowGap: 4 }}
|
||||
>
|
||||
<t.icon className="size-5 text-neutral-400 mb-3 group-hover:text-white transition-colors" />
|
||||
<CardTitle fontSize={14} fontWeight="600" color="#ffffff">{t.name}</CardTitle>
|
||||
<p className="text-xs text-neutral-400 group-hover:text-neutral-200 transition-colors">{t.desc}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Grid>
|
||||
</section>
|
||||
|
||||
{/* -- Models & providers ------------------------------------------ */}
|
||||
@@ -447,7 +509,7 @@ export default function Page() {
|
||||
<p className="text-neutral-300 text-sm mb-8">
|
||||
Over 400 models across every major provider — call any of them with one credential, one request shape.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 xl:grid-cols-8 gap-3">
|
||||
<Grid columns={{ min: 120, max: 8 }} gap={12}>
|
||||
{[
|
||||
{ name: 'Zen', spec: 'Open weights' },
|
||||
{ name: 'OpenAI', spec: 'GPT' },
|
||||
@@ -458,78 +520,111 @@ export default function Page() {
|
||||
{ name: 'Mistral', spec: 'Open' },
|
||||
{ name: 'Gemma', spec: 'Open' },
|
||||
].map((p) => (
|
||||
<Card key={p.name} className="border-white/[0.06] bg-white/[0.02] shadow-none py-0 gap-0 rounded-lg">
|
||||
<CardContent className="px-4 py-3 text-center">
|
||||
<div className="text-xs font-semibold text-white mb-1">{p.name}</div>
|
||||
<div className="text-[10px] text-neutral-400">{p.spec}</div>
|
||||
<Card
|
||||
key={p.name}
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.06)"
|
||||
borderRadius={8}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
>
|
||||
<CardContent style={{ display: 'grid', rowGap: 4, padding: '12px 16px', textAlign: 'center' }}>
|
||||
<span className="text-xs font-semibold text-white">{p.name}</span>
|
||||
<span className="text-[10px] text-neutral-400">{p.spec}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</Grid>
|
||||
</section>
|
||||
|
||||
{/* -- SDKs + connectors ------------------------------------------- */}
|
||||
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4">
|
||||
<Card className="border-white/[0.08] bg-white/[0.02] shadow-none py-0 gap-0 rounded-2xl overflow-hidden">
|
||||
<CardContent className="p-8">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Code2 className="size-5 text-neutral-400" />
|
||||
<CardTitle className="text-xl font-bold tracking-tight">SDKs in every language</CardTitle>
|
||||
</div>
|
||||
<CardDescription className="text-xs text-neutral-400 mb-6">
|
||||
Generated from one contract — the same <code className="font-mono">/v1</code> surface, typed for your stack.
|
||||
</CardDescription>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{['Python', 'TypeScript', 'Go', 'Rust', 'C++', 'Dart'].map((l) => (
|
||||
<span key={l} className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1.5 text-xs text-neutral-400">
|
||||
{l}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<Link href="/docs/sdks" className="mt-6 inline-flex items-center gap-1 text-xs font-medium text-neutral-400 hover:text-white transition-colors">
|
||||
SDK reference <ArrowRight className="size-3" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="border-white/[0.08] bg-white/[0.02] shadow-none py-0 gap-0 rounded-2xl overflow-hidden">
|
||||
<CardContent className="p-8">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<Workflow className="size-5 text-neutral-400" />
|
||||
<CardTitle className="text-xl font-bold tracking-tight">Every tool, one MCP surface</CardTitle>
|
||||
</div>
|
||||
<CardDescription className="text-xs text-neutral-400 mb-6">
|
||||
Native connectors + the open MCP registry — Slack, GitHub, Notion, Stripe, and more — exposed as MCP tools any agent can call.
|
||||
</CardDescription>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{['Slack', 'GitHub', 'Notion', 'Stripe', 'Google', 'Linear', '+700 more'].map((c) => (
|
||||
<span key={c} className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1.5 text-xs text-neutral-400">
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<Link href="/docs/mcp" className="mt-6 inline-flex items-center gap-1 text-xs font-medium text-neutral-400 hover:text-white transition-colors">
|
||||
MCP tools <ArrowRight className="size-3" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<Grid columns={{ min: 470, max: 2 }} gap={16}>
|
||||
{[
|
||||
{
|
||||
icon: Code2,
|
||||
title: 'SDKs in every language',
|
||||
body: (
|
||||
<>
|
||||
Generated from one contract — the same <code className="font-mono">/v1</code> surface, typed for your stack.
|
||||
</>
|
||||
),
|
||||
chips: ['Python', 'TypeScript', 'Go', 'Rust', 'C++', 'Dart'],
|
||||
href: '/docs/sdks',
|
||||
cta: 'SDK reference',
|
||||
},
|
||||
{
|
||||
icon: Workflow,
|
||||
title: 'Every tool, one MCP surface',
|
||||
body: 'Native connectors + the open MCP registry — Slack, GitHub, Notion, Stripe, and more — exposed as MCP tools any agent can call.',
|
||||
chips: ['Slack', 'GitHub', 'Notion', 'Stripe', 'Google', 'Linear', '+700 more'],
|
||||
href: '/docs/mcp',
|
||||
cta: 'MCP tools',
|
||||
},
|
||||
].map((s) => (
|
||||
<Card
|
||||
key={s.title}
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
borderRadius={16}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
overflow="hidden"
|
||||
>
|
||||
<CardContent
|
||||
padding={32}
|
||||
style={{ display: 'grid', gridTemplateRows: 'auto auto auto auto', justifyItems: 'start', rowGap: 8 }}
|
||||
>
|
||||
<span style={{ display: 'grid', gridAutoFlow: 'column', alignItems: 'center', columnGap: 12 }}>
|
||||
<s.icon className="size-5 text-neutral-400" />
|
||||
<CardTitle fontSize={20} fontWeight="700" letterSpacing={-0.4}>{s.title}</CardTitle>
|
||||
</span>
|
||||
<p className="text-xs text-neutral-400 mb-4">{s.body}</p>
|
||||
{/* A wrapping run of chips is the one place flex is still the
|
||||
right answer — grid cannot wrap items of unequal intrinsic
|
||||
width without giving them a track — so it is said with gui's
|
||||
XStack, whose style props travel to native, rather than with
|
||||
tailwind's. */}
|
||||
<XStack flexWrap="wrap" gap={8}>
|
||||
{s.chips.map((c) => (
|
||||
<span key={c} className="rounded-full border border-white/10 bg-white/[0.03] px-3 py-1.5 text-xs text-neutral-400">
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
</XStack>
|
||||
<Link
|
||||
href={s.href}
|
||||
className="mt-4 text-xs font-medium text-neutral-400 hover:text-white transition-colors"
|
||||
style={{ display: 'inline-grid', gridAutoFlow: 'column', alignItems: 'center', columnGap: 4 }}
|
||||
>
|
||||
{s.cta} <ArrowRight className="size-3" />
|
||||
</Link>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* -- What the CLI can do ----------------------------------------- */}
|
||||
<Card className="border-white/[0.08] bg-white/[0.02] shadow-none py-0 gap-0 rounded-2xl overflow-hidden">
|
||||
<CardContent className="p-8 md:p-10">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<Card
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
borderRadius={16}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
overflow="hidden"
|
||||
>
|
||||
<div className="p-8 md:p-10" style={{ display: 'grid', rowGap: 24 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: 'auto minmax(0, 1fr)', alignItems: 'center', columnGap: 12 }}>
|
||||
<div className="rounded-xl bg-white/[0.06] p-2.5">
|
||||
<Terminal className="size-5 text-neutral-400" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle className="text-2xl font-bold tracking-tight">
|
||||
<div style={{ display: 'grid', rowGap: 2 }}>
|
||||
<CardTitle fontSize={24} fontWeight="700" letterSpacing={-0.5}>
|
||||
The <code className="font-mono">hanzo</code> CLI
|
||||
</CardTitle>
|
||||
<CardDescription className="text-xs text-neutral-400 mt-0.5">A ~15 MB Rust client for any live cloud — prod, laptop, or self-host</CardDescription>
|
||||
<span className="text-xs text-neutral-400">A ~15 MB Rust client for any live cloud — prod, laptop, or self-host</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||
<Grid columns={{ min: 200, max: 3 }} gap={12}>
|
||||
{[
|
||||
{ cmd: 'hanzo chat', desc: 'Chat with any model interactively' },
|
||||
{ cmd: 'hanzo models list', desc: 'Browse every available model' },
|
||||
@@ -541,34 +636,46 @@ export default function Page() {
|
||||
{ cmd: 'hanzo bot', desc: 'Deploy and manage AI bots' },
|
||||
{ cmd: 'hanzo flow', desc: 'Run workflow automations' },
|
||||
].map((item) => (
|
||||
<Card key={item.cmd} className="border-white/[0.06] bg-white/[0.02] shadow-none py-0 gap-0 rounded-lg">
|
||||
<CardContent className="px-4 py-3">
|
||||
<div className="text-xs font-mono font-medium text-white mb-1">{item.cmd}</div>
|
||||
<div className="text-[11px] text-neutral-400">{item.desc}</div>
|
||||
<Card
|
||||
key={item.cmd}
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.06)"
|
||||
borderRadius={8}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
>
|
||||
<CardContent style={{ display: 'grid', rowGap: 4, padding: '12px 16px' }}>
|
||||
<span className="text-xs font-mono font-medium text-white">{item.cmd}</span>
|
||||
<span className="text-[11px] text-neutral-400">{item.desc}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Grid>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* -- Zen Models Banner ------------------------------------------- */}
|
||||
<Card className="border-white/[0.08] bg-white/[0.02] shadow-none py-0 gap-0 rounded-2xl overflow-hidden">
|
||||
<CardContent className="p-8 md:p-10">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Card
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.08)"
|
||||
borderRadius={16}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
overflow="hidden"
|
||||
>
|
||||
<div className="p-8 md:p-10" style={{ display: 'grid', rowGap: 24 }}>
|
||||
<div style={{ display: 'grid', gridAutoFlow: 'column', justifyContent: 'start', alignItems: 'center', columnGap: 12 }}>
|
||||
<Sparkle className="size-5 text-neutral-400" />
|
||||
<CardTitle className="text-2xl font-bold tracking-tight">
|
||||
<CardTitle fontSize={24} fontWeight="700" letterSpacing={-0.5}>
|
||||
Zen
|
||||
</CardTitle>
|
||||
<Badge variant="secondary" className="text-xs text-neutral-400 font-mono bg-white/5 px-2 py-0.5 rounded-full border-transparent">
|
||||
44 models
|
||||
</Badge>
|
||||
<span className="rounded-full bg-white/5 px-2 py-0.5 font-mono text-xs text-neutral-400">44 models</span>
|
||||
</div>
|
||||
<p className="text-neutral-300 text-sm leading-relaxed max-w-2xl mb-6">
|
||||
<p className="text-neutral-300 text-sm leading-relaxed max-w-2xl">
|
||||
Frontier AI models from 4B edge to 1T+ reasoning. MoDE (Mixture of Diverse Experts) architecture.
|
||||
Text, code, vision, audio, video, 3D, and safety. Open weights on HuggingFace.
|
||||
</p>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 xl:grid-cols-6 gap-3 mb-6">
|
||||
<Grid columns={{ min: 130, max: 6 }} gap={12}>
|
||||
{[
|
||||
{ name: 'zen4', spec: '~400B MoDE' },
|
||||
{ name: 'zen4-coder', spec: '~200B MoDE' },
|
||||
@@ -577,23 +684,27 @@ export default function Page() {
|
||||
{ name: 'zen3-nano', spec: '4B Edge' },
|
||||
{ name: 'zen3-guard', spec: '8B Safety' },
|
||||
].map((m) => (
|
||||
<Card key={m.name} className="border-white/[0.06] bg-white/[0.02] shadow-none py-0 gap-0 rounded-lg">
|
||||
<CardContent className="p-3">
|
||||
<div className="text-xs font-mono font-medium text-white mb-1">{m.name}</div>
|
||||
<div className="text-[10px] text-neutral-400">{m.spec}</div>
|
||||
<Card
|
||||
key={m.name}
|
||||
backgroundColor="rgba(255,255,255,0.02)"
|
||||
borderColor="rgba(255,255,255,0.06)"
|
||||
borderRadius={8}
|
||||
paddingVertical={0}
|
||||
gap={0}
|
||||
>
|
||||
<CardContent style={{ display: 'grid', rowGap: 4, padding: 12 }}>
|
||||
<span className="text-xs font-mono font-medium text-white">{m.name}</span>
|
||||
<span className="text-[10px] text-neutral-400">{m.spec}</span>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
</Grid>
|
||||
<XStack flexWrap="wrap" gap={12} alignItems="center">
|
||||
<a
|
||||
href="https://zenlm.org"
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'outline', size: 'sm' }),
|
||||
'rounded-full border-white/20 text-white hover:bg-white/5 bg-transparent text-xs px-4 py-2 h-auto',
|
||||
)}
|
||||
className="rounded-full border border-white/20 px-4 py-2 text-xs text-white transition-colors hover:bg-white/5"
|
||||
>
|
||||
zenlm.org →
|
||||
</a>
|
||||
@@ -605,8 +716,8 @@ export default function Page() {
|
||||
>
|
||||
HuggingFace →
|
||||
</a>
|
||||
</div>
|
||||
</CardContent>
|
||||
</XStack>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* -- CTA --------------------------------------------------------- */}
|
||||
@@ -621,27 +732,22 @@ export default function Page() {
|
||||
<div className="relative font-mono text-sm text-neutral-400 mb-8">
|
||||
curl hanzo.sh | sh
|
||||
</div>
|
||||
<div className="relative flex items-center justify-center gap-3 flex-wrap">
|
||||
<XStack flexWrap="wrap" gap={12} alignItems="center" justifyContent="center" position="relative">
|
||||
<a
|
||||
href="https://hanzo.id/signup?redirect_uri=https://console.hanzo.ai"
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'primary', size: 'lg' }),
|
||||
'rounded-full bg-white px-8 text-black hover:bg-neutral-200',
|
||||
)}
|
||||
className="rounded-full bg-white px-8 py-3 text-sm font-medium text-black transition-colors hover:bg-neutral-200"
|
||||
style={{ display: 'inline-grid', gridAutoFlow: 'column', alignItems: 'center', columnGap: 4 }}
|
||||
>
|
||||
Sign Up Free
|
||||
<ArrowRight className="size-4 ml-1" />
|
||||
<ArrowRight className="size-4" />
|
||||
</a>
|
||||
<Link
|
||||
href="/docs"
|
||||
className={cn(
|
||||
buttonVariants({ variant: 'outline', size: 'lg' }),
|
||||
'rounded-full border-white/15 px-8 text-white hover:bg-white/5 bg-transparent',
|
||||
)}
|
||||
className="rounded-full border border-white/15 px-8 py-3 text-sm font-medium text-white transition-colors hover:bg-white/5"
|
||||
>
|
||||
Browse Documentation
|
||||
</Link>
|
||||
</div>
|
||||
</XStack>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import { RootProvider } from '@hanzo/docs-base-ui/provider/base';
|
||||
import { Hanzo } from '@hanzo/ui';
|
||||
import dynamic from 'next/dynamic';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
@@ -8,6 +9,17 @@ const SearchDialog = dynamic(() => import('@/components/layouts/search'), {
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// `<Hanzo>` is what makes a @hanzo/ui component render as anything at all: it
|
||||
// carries gui's process-global config, its stylesheet, and the root theme gui
|
||||
// throws without. Before it was here, this app declared @hanzo/ui and @hanzo/gui
|
||||
// as dependencies and rendered zero gui classes — the same shape @zenlm/ui is in,
|
||||
// where the markup names classes no rule ever defines.
|
||||
//
|
||||
// INSIDE RootProvider, not above it. Both manage a theme, and the one that wins
|
||||
// has to be the one the reader can switch: fumadocs owns the toggle in the rail
|
||||
// and writes `.dark` on <html>, so gui takes the fixed dark identity and the
|
||||
// document class stays fumadocs' to set. Nesting the other way puts gui's theme
|
||||
// context above a subtree that never reads it.
|
||||
export function Provider({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<RootProvider
|
||||
@@ -19,7 +31,7 @@ export function Provider({ children }: { children: ReactNode }) {
|
||||
enableSystem: false,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<Hanzo theme="dark">{children}</Hanzo>
|
||||
</RootProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,8 +17,21 @@ import { linkItems } from '@/components/layouts/shared';
|
||||
export function DocsNavLinks() {
|
||||
const pathname = usePathname();
|
||||
|
||||
// A run of equal-height links, so the tracks come from the content and the
|
||||
// spacing is stated once on the container rather than as a margin each link
|
||||
// carries. `justify-content: start` keeps the row left-aligned inside the
|
||||
// stretched middle track of the bar above.
|
||||
return (
|
||||
<nav className="flex items-center gap-1 max-md:hidden">
|
||||
<nav
|
||||
className="max-md:hidden"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridAutoFlow: 'column',
|
||||
justifyContent: 'start',
|
||||
alignItems: 'center',
|
||||
columnGap: 4,
|
||||
}}
|
||||
>
|
||||
{linkItems
|
||||
.filter((item) => item.type !== 'icon' && 'url' in item)
|
||||
.map((item) => {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import type { ComponentProps } from 'react';
|
||||
import { useDocsLayout } from '@hanzo/docs-base-ui/layouts/docs';
|
||||
import { useSidebar } from '@hanzo/docs-base-ui/components/sidebar/base';
|
||||
import { Grid } from '@hanzo/ui/grid';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { DocsNavLinks } from '@/components/docs-nav-links';
|
||||
import { OrgBadge } from '@/components/org-badge';
|
||||
@@ -40,8 +41,21 @@ export function DocsNavbar(props: ComponentProps<'header'>) {
|
||||
id="nd-subnav"
|
||||
data-transparent={isNavTransparent}
|
||||
{...props}
|
||||
// THREE TRACKS: the wordmark, the section links, the account controls.
|
||||
// It was `flex items-center gap-3` with a `flex-1` wrapper in the middle
|
||||
// whose only job was to push its neighbours apart — which is a row asking
|
||||
// to be a grid. The stretch is a property of the TRACK, so saying it here
|
||||
// deletes the wrapper and the class that made it stretch, and neither
|
||||
// neighbour has to know the other exists.
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto minmax(0, 1fr) auto',
|
||||
alignItems: 'center',
|
||||
columnGap: 12,
|
||||
...props.style,
|
||||
}}
|
||||
className={cn(
|
||||
'[grid-area:header] sticky top-(--fd-docs-row-1) z-30 flex items-center gap-3 pe-2.5',
|
||||
'[grid-area:header] sticky top-(--fd-docs-row-1) z-30 pe-2.5',
|
||||
// A COLLAPSED RAIL TAKES NO COLUMN, SO THIS BAR STARTS AT THE VIEWPORT EDGE.
|
||||
// container.tsx sets `--fd-sidebar-col: 0px` when collapsed, and the rail's
|
||||
// floating control is `fixed inset-s-4` at 66px wide — x∈[16,82] — at z-40,
|
||||
@@ -65,10 +79,13 @@ export function DocsNavbar(props: ComponentProps<'header'>) {
|
||||
once here and once six inches to the left. Below md the sidebar collapses
|
||||
into a drawer and this is the only place a wordmark can live, so it stays
|
||||
there rather than being dropped outright. */}
|
||||
{slots.navTitle && (
|
||||
{/* The three tracks are always present, so the wordmark's cell is emitted
|
||||
even when the slot is not — a missing child would shift the links into
|
||||
the wordmark's track and the controls into theirs. */}
|
||||
{slots.navTitle ? (
|
||||
<slots.navTitle
|
||||
className={cn(
|
||||
'inline-flex items-center gap-2.5 font-semibold',
|
||||
'inline-grid grid-flow-col items-center gap-2.5 font-semibold',
|
||||
// Hidden on desktop only while the SIDEBAR is showing it — which it is
|
||||
// not when collapsed, and collapsed is exactly the landing page. Left
|
||||
// unconditional the front door rendered no wordmark at all: the rail
|
||||
@@ -77,9 +94,15 @@ export function DocsNavbar(props: ComponentProps<'header'>) {
|
||||
!collapsed && 'md:hidden',
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
|
||||
<div className="flex flex-1 items-center">
|
||||
{/* The links are `max-md:hidden`, and a hidden element is not a grid item
|
||||
at all — so below md it would vacate the stretch track and the controls
|
||||
would slide into it, left-aligned. The cell stays; only its contents
|
||||
disappear. */}
|
||||
<div>
|
||||
<DocsNavLinks />
|
||||
</div>
|
||||
|
||||
@@ -89,7 +112,7 @@ export function DocsNavbar(props: ComponentProps<'header'>) {
|
||||
the site lost its Sign in and Console buttons: they were configured
|
||||
correctly and rendered by nobody. Named explicitly so the omission
|
||||
cannot happen silently again. */}
|
||||
<div className="flex items-center gap-2">
|
||||
<Grid columns={['auto', 'auto']} gap={8} style={{ alignItems: 'center' }}>
|
||||
{/* NO SEARCH HERE. The sidebar carries the search field itself, and the
|
||||
collapsed rail carries a search button beside its own toggle — so a
|
||||
third one on the far right was the same action offered twice on one
|
||||
@@ -102,10 +125,6 @@ export function DocsNavbar(props: ComponentProps<'header'>) {
|
||||
out of the bar between 768 and 805px. A column can afford a long
|
||||
label; this row carries the short one instead, and only when the rail
|
||||
is not on screen to carry either. */}
|
||||
{/* One control for "hand this page to an agent", derived from the
|
||||
pathname so it works on every page. It replaced a Copy Markdown
|
||||
button and an Open menu buried under the title, plus a Copy prompt
|
||||
that existed only on the front page. */}
|
||||
<OrgBadge />
|
||||
{/* Shown exactly where the rail is not showing it: below md the rail is
|
||||
a drawer, and on desktop only while it is collapsed. Everywhere else
|
||||
@@ -113,7 +132,7 @@ export function DocsNavbar(props: ComponentProps<'header'>) {
|
||||
<div className={cn(collapsed ? '' : 'md:hidden')}>
|
||||
<NavAccount />
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,11 +69,15 @@ export function Docs({
|
||||
return {
|
||||
...option,
|
||||
icon: (
|
||||
// One cell holding one glyph, centred. `place-items` is the
|
||||
// grid word for it and needs no wrapper to align against.
|
||||
<div
|
||||
className="[&_svg]:size-full rounded-lg size-full text-(--tab-color) max-md:bg-(--tab-color)/10 max-md:border max-md:p-1.5"
|
||||
style={
|
||||
{
|
||||
'--tab-color': color,
|
||||
display: 'grid',
|
||||
placeItems: 'center',
|
||||
} as object
|
||||
}
|
||||
>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { ChevronsUpDown } from 'lucide-react'
|
||||
import { Grid } from '@hanzo/ui/grid'
|
||||
|
||||
import { currentUser, iam, type DocsUser } from '@/lib/iam'
|
||||
|
||||
@@ -48,7 +49,7 @@ export function SidebarAccount() {
|
||||
|
||||
function SignedOut() {
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Grid columns={1} gap={8}>
|
||||
{/* Sign in first: it is the step that costs nothing and the one most
|
||||
readers already have an account for. Getting a key is the commitment,
|
||||
so it sits at the foot as the thing you end on. */}
|
||||
@@ -64,7 +65,7 @@ function SignedOut() {
|
||||
>
|
||||
Get an API key
|
||||
</a>
|
||||
</div>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -136,16 +137,26 @@ function Account({ user, onSignOut }: { user: DocsUser; onSignOut: () => void })
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
aria-haspopup="menu"
|
||||
aria-expanded={open}
|
||||
className="flex w-full items-center gap-2 rounded-lg px-2 py-1.5 text-start transition-colors hover:bg-fd-accent"
|
||||
// Initial, name, chevron. The name track is `minmax(0, 1fr)`, which is
|
||||
// what lets it truncate — a flex child needed `min-w-0` spelled out to
|
||||
// do the same thing, and a track that forgets it is how a long email
|
||||
// pushes the chevron out of the rail.
|
||||
className="w-full rounded-lg px-2 py-1.5 text-start transition-colors hover:bg-fd-accent"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto minmax(0, 1fr) auto',
|
||||
alignItems: 'center',
|
||||
columnGap: 8,
|
||||
}}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className="grid size-6 shrink-0 place-items-center rounded-full bg-fd-primary text-[0.6875rem] font-medium text-fd-primary-foreground"
|
||||
className="grid size-6 place-items-center rounded-full bg-fd-primary text-[0.6875rem] font-medium text-fd-primary-foreground"
|
||||
>
|
||||
{label.slice(0, 1).toUpperCase()}
|
||||
</span>
|
||||
<span className="min-w-0 flex-1 truncate text-sm text-fd-foreground">{label}</span>
|
||||
<ChevronsUpDown className="size-3.5 shrink-0 text-fd-muted-foreground" />
|
||||
<span className="truncate text-sm text-fd-foreground">{label}</span>
|
||||
<ChevronsUpDown className="size-3.5 text-fd-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -28,10 +28,19 @@ export function SidebarSearch() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpenSearch(true)}
|
||||
className="flex w-full items-center gap-2 rounded-lg border bg-fd-secondary/50 px-2.5 py-1.5 text-start transition-colors hover:bg-fd-accent"
|
||||
// Icon, label, shortcut. The label used to claim the middle with `flex-1`;
|
||||
// the middle track claims it now, so the label is just text and the two
|
||||
// fixed ends cannot be squeezed by it.
|
||||
className="w-full rounded-lg border bg-fd-secondary/50 px-2.5 py-1.5 text-start transition-colors hover:bg-fd-accent"
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'auto minmax(0, 1fr) auto',
|
||||
alignItems: 'center',
|
||||
columnGap: 8,
|
||||
}}
|
||||
>
|
||||
<Search className="size-4 shrink-0 text-fd-muted-foreground" />
|
||||
<span className="flex-1 text-sm text-fd-muted-foreground">Search or ask AI</span>
|
||||
<Search className="size-4 text-fd-muted-foreground" />
|
||||
<span className="text-sm text-fd-muted-foreground">Search or ask AI</span>
|
||||
<kbd className="rounded border px-1.5 py-0.5 text-[10px] text-fd-muted-foreground">⌘K</kbd>
|
||||
</button>
|
||||
);
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"@hanzo/gui": "8.1.1",
|
||||
"@hanzo/iam": "^0.21.6",
|
||||
"@hanzo/logo": "^1.0.14",
|
||||
"@hanzo/ui": "8.2.2",
|
||||
"@hanzo/ui": "8.3.0",
|
||||
"@hanzogui/shell": "^7.6.3",
|
||||
"@llmgateway/ai-sdk-provider": "^3.8.0",
|
||||
"@openrouter/ai-sdk-provider": "^2.9.0",
|
||||
|
||||
@@ -30,7 +30,7 @@ export function Breadcrumb({
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn('flex items-center gap-1.5 text-sm text-fd-muted-foreground', props.className)}
|
||||
className={cn('grid grid-flow-col justify-start items-center gap-1.5 text-sm text-fd-muted-foreground', props.className)}
|
||||
>
|
||||
{items.map((item, i) => {
|
||||
const className = cn('truncate', i === items.length - 1 && 'text-fd-primary font-medium');
|
||||
|
||||
@@ -19,17 +19,31 @@ export function Header(props: ComponentProps<'header'>) {
|
||||
id="nd-subnav"
|
||||
data-transparent={isNavTransparent}
|
||||
{...props}
|
||||
// Four tracks: wordmark, nav, search, sidebar toggle. The nav track is the
|
||||
// one that stretches, which `flex` reached by putting `flex-1` on a
|
||||
// wrapper; a track says it once and the wrapper stops being load-bearing.
|
||||
className={cn(
|
||||
'[grid-area:header] sticky top-(--fd-docs-row-1) z-30 flex items-center ps-4 pe-2.5 border-b transition-colors backdrop-blur-sm h-(--fd-header-height) md:hidden max-md:layout:[--fd-header-height:--spacing(14)] data-[transparent=false]:bg-fd-background/80',
|
||||
'[grid-area:header] sticky top-(--fd-docs-row-1) z-30 grid grid-cols-[auto_minmax(0,1fr)_auto_auto] items-center ps-4 pe-2.5 border-b transition-colors backdrop-blur-sm h-(--fd-header-height) md:hidden max-md:layout:[--fd-header-height:--spacing(14)] data-[transparent=false]:bg-fd-background/80',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{slots.navTitle && (
|
||||
<slots.navTitle className="inline-flex items-center gap-2.5 font-semibold" />
|
||||
{/* Every cell is emitted even when its slot is empty. A track list counts
|
||||
items, so an absent LEADING slot slides each later one a track to the
|
||||
left — the search trigger lands in the stretch track and the toggle
|
||||
stops being at the right edge. An empty cell in an `auto` track is
|
||||
zero wide and costs nothing. */}
|
||||
{slots.navTitle ? (
|
||||
<slots.navTitle className="inline-grid grid-flow-col items-center gap-2.5 font-semibold" />
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<div className="flex-1">{nav?.children}</div>
|
||||
{slots.searchTrigger && <slots.searchTrigger.sm hideIfDisabled className="p-2" />}
|
||||
{slots.sidebar && (
|
||||
<div>{nav?.children}</div>
|
||||
{slots.searchTrigger ? (
|
||||
<slots.searchTrigger.sm hideIfDisabled className="p-2" />
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{slots.sidebar ? (
|
||||
<slots.sidebar.trigger
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
@@ -41,6 +55,8 @@ export function Header(props: ComponentProps<'header'>) {
|
||||
>
|
||||
<SidebarIcon />
|
||||
</slots.sidebar.trigger>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
|
||||
@@ -30,7 +30,7 @@ export function Breadcrumb({
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={cn('flex items-center gap-1.5 text-sm text-fd-muted-foreground', props.className)}
|
||||
className={cn('grid grid-flow-col justify-start items-center gap-1.5 text-sm text-fd-muted-foreground', props.className)}
|
||||
>
|
||||
{items.map((item, i) => {
|
||||
const className = cn('truncate', i === items.length - 1 && 'text-fd-primary font-medium');
|
||||
|
||||
@@ -19,17 +19,31 @@ export function Header(props: ComponentProps<'header'>) {
|
||||
id="nd-subnav"
|
||||
data-transparent={isNavTransparent}
|
||||
{...props}
|
||||
// Four tracks: wordmark, nav, search, sidebar toggle. The nav track is the
|
||||
// one that stretches, which `flex` reached by putting `flex-1` on a
|
||||
// wrapper; a track says it once and the wrapper stops being load-bearing.
|
||||
className={cn(
|
||||
'[grid-area:header] sticky top-(--fd-docs-row-1) z-30 flex items-center ps-4 pe-2.5 border-b transition-colors backdrop-blur-sm h-(--fd-header-height) md:hidden max-md:layout:[--fd-header-height:--spacing(14)] data-[transparent=false]:bg-fd-background/80',
|
||||
'[grid-area:header] sticky top-(--fd-docs-row-1) z-30 grid grid-cols-[auto_minmax(0,1fr)_auto_auto] items-center ps-4 pe-2.5 border-b transition-colors backdrop-blur-sm h-(--fd-header-height) md:hidden max-md:layout:[--fd-header-height:--spacing(14)] data-[transparent=false]:bg-fd-background/80',
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
{slots.navTitle && (
|
||||
<slots.navTitle className="inline-flex items-center gap-2.5 font-semibold" />
|
||||
{/* Every cell is emitted even when its slot is empty. A track list counts
|
||||
items, so an absent LEADING slot slides each later one a track to the
|
||||
left — the search trigger lands in the stretch track and the toggle
|
||||
stops being at the right edge. An empty cell in an `auto` track is
|
||||
zero wide and costs nothing. */}
|
||||
{slots.navTitle ? (
|
||||
<slots.navTitle className="inline-grid grid-flow-col items-center gap-2.5 font-semibold" />
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
<div className="flex-1">{nav?.children}</div>
|
||||
{slots.searchTrigger && <slots.searchTrigger.sm hideIfDisabled className="p-2" />}
|
||||
{slots.sidebar && (
|
||||
<div>{nav?.children}</div>
|
||||
{slots.searchTrigger ? (
|
||||
<slots.searchTrigger.sm hideIfDisabled className="p-2" />
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
{slots.sidebar ? (
|
||||
<slots.sidebar.trigger
|
||||
className={cn(
|
||||
buttonVariants({
|
||||
@@ -41,6 +55,8 @@ export function Header(props: ComponentProps<'header'>) {
|
||||
>
|
||||
<SidebarIcon />
|
||||
</slots.sidebar.trigger>
|
||||
) : (
|
||||
<span />
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
|
||||
Generated
+15
-66
Reference in New Issue
Block a user