Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion apps/docs/astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,14 @@ export default defineConfig({
description:
'Run, self-host and observe a Lumyx SFU: quickstart, deployment, the six media-path metrics and the REST API.',
social: [{ icon: 'github', label: 'GitHub', href: REPO }],
sidebar: sidebarGroups().map((group) => ({ label: group.label, items: group.items })),
// Les squelettes portent le badge « Soon » de la maquette Docs v3.
sidebar: sidebarGroups().map((group) => ({
label: group.label,
items: group.items.map((slug) =>
stubs.includes(slug) ? { slug, badge: { text: 'Soon', class: 'lumyx-soon' } } : slug,
),
})),
editLink: { baseUrl: 'https://github.com/lumyx-dev/website/edit/main/apps/docs/' },
customCss: ['./src/styles/global.css'],
expressiveCode: false,
routeMiddleware: './src/route-middleware.ts',
Expand All @@ -38,6 +45,9 @@ export default defineConfig({
ThemeSelect: './src/components/starlight/ThemeSelect.astro',
PageTitle: './src/components/starlight/PageTitle.astro',
SiteTitle: './src/components/starlight/SiteTitle.astro',
SocialIcons: './src/components/starlight/SocialIcons.astro',
PageSidebar: './src/components/starlight/PageSidebar.astro',
Footer: './src/components/starlight/Footer.astro',
},
}),
// Voir apps/landing/astro.config.ts : les enfants passes depuis le MDX deviennent des
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"verify:ds": "node scripts/verify-ds.mjs"
},
"dependencies": {
"@astrojs/markdown-satteri": "0.4.6",
"@astrojs/react": "^7.1.2",
"@astrojs/sitemap": "^3.7.4",
"@astrojs/starlight": "^0.42.7",
Expand All @@ -25,6 +26,7 @@
"@tailwindcss/vite": "^4.3.3",
"astro": "^7.4.4",
"react": "19.2.8",
"satteri": "0.10.5",
"react-dom": "19.2.8",
"tailwindcss": "^4.3.3"
},
Expand Down
25 changes: 25 additions & 0 deletions apps/docs/src/components/code-panel.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
// Panneau de code de la maquette Docs v3 : toujours sombre, nom de fichier, bouton Copy.
import { CopyButton } from './copy-button';
import type { Token } from '@/lib/json-tokens';

interface Props {
label: string;
lines: Token[][];
class?: string;
}
const { label, lines, class: className } = Astro.props;
const text = lines.map((l) => l.map((t) => t.v).join('')).join('\n');
---

<div class:list={['not-content dark overflow-hidden rounded-lg bg-page text-body', className]}>
<div class="flex items-center justify-between border-b border-hairline py-2 pl-4 pr-2">
<span class="font-mono text-12 text-muted">{label}</span>
<CopyButton client:visible text={text} />
</div>
<pre class="m-0 overflow-x-auto bg-transparent p-4 font-mono text-13 leading-5"><code>{
lines.map((line) => (
<div class="min-h-5 whitespace-pre">{line.map((t) => <span class={t.cls}>{t.v}</span>)}</div>
))
}</code></pre>
</div>
24 changes: 24 additions & 0 deletions apps/docs/src/components/copy-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react";

/** Bouton « Copy » des panneaux de code. Ilot React : seul morceau interactif de la page. */
export function CopyButton({ text }: { text: string }) {
const [copied, setCopied] = React.useState(false);
const timer = React.useRef<ReturnType<typeof setTimeout>>(undefined);
React.useEffect(() => () => clearTimeout(timer.current), []);

return (
<button
type="button"
onClick={() => {
void navigator.clipboard?.writeText(text).catch(() => {});
setCopied(true);
clearTimeout(timer.current);
timer.current = setTimeout(() => setCopied(false), 1600);
}}
aria-label={copied ? "Copied" : "Copy to clipboard"}
className="cursor-pointer rounded-[6px] border border-stroke bg-transparent px-2 py-1 text-12 text-muted transition-colors hover:text-strong"
>
<span aria-live="polite">{copied ? "Copied" : "Copy"}</span>
</button>
);
}
36 changes: 36 additions & 0 deletions apps/docs/src/components/metric-detail.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
// Le corps d'UNE metrique (maquette Docs v3) : texte et encarts a gauche, exemple JSON a droite.
// Le titre reste un vrai `##` dans le MDX, avec son ancre epinglee (`## Jitter [#jitter_ms]`) :
// c'est lui qui alimente le sommaire. Ce composant ne rend donc ni titre ni `id`.
import { METRICS } from '@/lib/metrics-data';
import { tokenizeJson } from '@/lib/json-tokens';
import CodePanel from './code-panel.astro';

interface Props {
field: string;
}
const m = METRICS.find((metric) => metric.field === Astro.props.field);
if (!m) throw new Error(`MetricDetail: metrique inconnue "${Astro.props.field}"`);
---

<div class="not-content grid gap-8 min-[1100px]:grid-cols-2">
<div class="flex flex-col gap-4">
<span class="font-mono text-12 text-faint">{m.field} · {m.unit}</span>
<p class="m-0 text-16 leading-6 text-body text-pretty">{m.body}</p>
<dl class="m-0 grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-2 rounded-sm bg-sunken p-4 text-14">
<dt class="text-faint">Default</dt>
<dd class="m-0 font-mono text-danger">{m.threshold}</dd>
<dt class="text-faint">Scope</dt>
<dd class="m-0 text-body">{m.scope}</dd>
<dt class="text-faint">Breaks</dt>
<dd class="m-0 text-body">{m.breaks}</dd>
</dl>
<div class="flex gap-3 rounded-sm border border-accent-border bg-accent-tint p-4">
<svg class="mt-0.5 flex-none fill-none stroke-accent" width="16" height="16" viewBox="0 0 256 256" aria-hidden="true">
<path d="M96 208h64M104 176h48a64 64 0 1 0-48 0z" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"></path>
</svg>
<p class="m-0 text-14 leading-5 text-body"><span class="font-semibold text-strong">What to do. </span>{m.action}</p>
</div>
</div>
<CodePanel label="peer.json" lines={m.sample.map(tokenizeJson)} class="self-start min-[1100px]:sticky min-[1100px]:top-24" />
</div>
114 changes: 19 additions & 95 deletions apps/docs/src/components/metrics-reference.tsx
Original file line number Diff line number Diff line change
@@ -1,104 +1,28 @@
import {
Card,
CardContent,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@lumyx/ui';
import { METRICS } from '@/lib/metrics-data';

/**
* Tableau recapitulatif des six seuils par defaut, repris de l'ancienne page. Le brief de la
* tache 5 ne le mentionnait pas ; sans lui, la vue d'ensemble (six metriques sur une grille de
* six colonnes) disparaissait de la doc migree.
* « At a glance » (maquette Docs v3) : une carte par metrique, qui renvoie a sa section.
* Seuil par defaut en pastille, ce qu'elle casse, et le nom du champ.
*/
export function MetricsThresholds() {
export function MetricsGlance() {
return (
<Card className="overflow-hidden">
<Table>
<TableHeader>
<TableRow>
<TableHead>Metric</TableHead>
<TableHead>Field</TableHead>
<TableHead>Unit</TableHead>
<TableHead>Threshold</TableHead>
<TableHead>Scope</TableHead>
<TableHead>What it breaks</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{METRICS.map((m) => (
<TableRow key={m.field}>
<TableCell className="font-medium text-strong">{m.name}</TableCell>
<TableCell className="font-mono text-code text-muted">{m.field}</TableCell>
<TableCell className="text-muted">{m.unit}</TableCell>
{/* `sl-num` : c'est la colonne dont le travail est la comparaison verticale de
nombres (> 2% / > 200ms / < 100kbps). Sans chiffres tabulaires, ils ne
s'alignent pas. */}
<TableCell className="sl-num font-medium text-strong">{m.threshold}</TableCell>
<TableCell className="text-muted">{m.scope}</TableCell>
<TableCell className="text-muted">{m.breaks}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
);
}

/**
* Le corps d'UNE metrique : sept champs se tiennent mieux dans un objet que dans des
* paragraphes paralleles.
*
* Ce composant ne rend deliberement ni titre ni `id`. Le titre est un vrai `##` dans le MDX,
* avec son ancre epinglee (`## Packet loss [#packet_loss_ratio]`), pour deux raisons : Fumadocs
* derive la table des matieres des titres du document au moment de la compilation, donc un titre
* rendu a l'interieur d'un composant React n'y figure jamais ; et l'ancre explicite conserve
* `#packet_loss_ratio` la ou rehype-slug aurait produit `#packet-loss`. Poser ici un `id` en plus
* le dupliquerait.
*/
export function MetricDetail({ field }: { field: string }) {
const m = METRICS.find((metric) => metric.field === field);
if (!m) throw new Error(`MetricDetail: metrique inconnue "${field}"`);

return (
<div className="flex flex-col gap-3">
<span className="font-mono text-code text-muted">{m.field}</span>

<p className="max-w-[680px] text-14 leading-relaxed text-body text-pretty">{m.body}</p>

<Card>
<CardContent className="flex flex-col gap-2">
<dl className="grid grid-cols-1 gap-2 sm:grid-cols-3">
{[
['Unit', m.unit],
['Default threshold', m.threshold],
['Scope', m.scope],
].map(([label, value]) => (
<div key={label} className="flex flex-col gap-0.5">
<dt className="sl-label">{label}</dt>
<dd className="text-13 text-body">{value}</dd>
</div>
))}
</dl>
</CardContent>
</Card>

<div className="flex flex-col gap-1 rounded-md border border-subtle bg-sunken px-4 py-3.5">
{m.sample.map((line) => (
<span key={line} className="whitespace-pre font-mono text-code text-body">
{line}
<div className="not-content grid gap-3 sm:grid-cols-3">
{METRICS.map((m) => (
<a
key={m.field}
href={`#${m.field}`}
className="flex flex-col gap-3 rounded-lg border border-stroke bg-card p-4 text-body no-underline transition-all duration-500 ease-[cubic-bezier(0.32,0.72,0,1)] hover:-translate-y-0.5 hover:border-faint hover:text-body hover:no-underline"
>
<span className="flex items-center justify-between gap-2">
<span className="text-16 font-semibold text-strong">{m.name}</span>
<span className="sl-num whitespace-nowrap rounded-pill bg-danger-tint px-2 py-0.5 font-mono text-12 text-danger">
{m.threshold}
</span>
</span>
))}
</div>

<p className="max-w-[680px] text-13 text-muted text-pretty">
<span className="text-strong">What breaks:</span> {m.breaks}
</p>
<p className="max-w-[680px] text-13 text-muted text-pretty">{m.action}</p>
<span className="text-14 text-muted">{m.breaks}</span>
<span className="mt-auto font-mono text-12 text-faint">{m.field}</span>
</a>
))}
</div>
);
}
9 changes: 9 additions & 0 deletions apps/docs/src/components/starlight/Footer.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
// Pied de page (maquette Docs v3) : seulement la page precedente et la suivante. Le lien
// d'edition est monte dans l'en-tete de page (PageTitle.astro).
import Pagination from '@astrojs/starlight/components/Pagination.astro';
---

<footer class="lumyx-footer">
<Pagination />
</footer>
22 changes: 22 additions & 0 deletions apps/docs/src/components/starlight/PageSidebar.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
// Le sommaire de Starlight, suivi de l'encart « New to Lumyx? » de la maquette Docs v3.
// L'encart vit dans la colonne de droite : il n'apparait que la ou elle existe (page avec
// sommaire, ecran large), et pas sur le quickstart lui-meme.
import Default from '@astrojs/starlight/components/PageSidebar.astro';

const { toc, id } = Astro.locals.starlightRoute;
const showNewcomer = Boolean(toc) && id !== 'quickstart';
---

<Default><slot /></Default>
{
showNewcomer && (
<aside class="lumyx-newcomer sl-hidden lg:sl-block" aria-label="Quickstart">
<p class="lumyx-newcomer-title">New to Lumyx?</p>
<p class="lumyx-newcomer-body">Run the SFU in one command and open your first room.</p>
<a href="/quickstart" class="lumyx-newcomer-cta">
Quickstart
</a>
</aside>
)
}
34 changes: 30 additions & 4 deletions apps/docs/src/components/starlight/PageTitle.astro
Original file line number Diff line number Diff line change
@@ -1,15 +1,41 @@
---
// Remplace le PageTitle de Starlight : fil d'Ariane (nom de la section), titre au palier 44 et
// chapo au palier 16, comme sous Fumadocs. L'id `_top` est celui que Starlight attend.
const { entry, sidebar } = Astro.locals.starlightRoute;
// Remplace le PageTitle de Starlight (maquette Docs v3) : fil d'Ariane Docs › section › page,
// titre en degrade, chapo, puis la version et le lien d'edition. L'id `_top` est celui que
// Starlight attend.
import { Icon } from '@astrojs/starlight/components';
import { VERSION } from '@/lib/site-data';

const { entry, sidebar, editUrl } = Astro.locals.starlightRoute;
type SidebarEntry = (typeof sidebar)[number];

function hasCurrent(e: SidebarEntry): boolean {
return e.type === 'link' ? e.isCurrent : e.entries.some(hasCurrent);
}
const section = sidebar.find((e) => e.type === 'group' && hasCurrent(e))?.label;
const crumbs = ['Docs', section].filter(Boolean) as string[];
---

{section && <p class="lumyx-crumb sl-num">{section}</p>}
<nav aria-label="Breadcrumb" class="lumyx-crumb">
{
crumbs.map((c) => (
<>
<span>{c}</span>
<Icon name="right-caret" class="lumyx-crumb-sep" />
</>
))
}
<span class="lumyx-crumb-current" aria-current="page">{entry.data.title}</span>
</nav>
<h1 id="_top" class="lumyx-title">{entry.data.title}</h1>
{entry.data.description && <p class="lumyx-lead">{entry.data.description}</p>}
<p class="lumyx-meta">
<span class="sl-num">Lumyx {VERSION}</span>
{
editUrl && (
<>
<span aria-hidden="true">·</span>
<a href={editUrl}>Edit on GitHub</a>
</>
)
}
</p>
9 changes: 8 additions & 1 deletion apps/docs/src/components/starlight/SiteTitle.astro
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
---
// Le wordmark Lumyx, qui renvoie vers lumyx.dev comme sous Fumadocs.
// Le wordmark Lumyx, qui renvoie vers lumyx.dev, suivi de « Docs » en retrait (maquette Docs v3).
import { Wordmark } from '@lumyx/ui';
import { SITE_URL } from '@/lib/env';
---

<a href={SITE_URL} class="site-title" aria-label="Lumyx home">
<Wordmark />
</a>
<span class="site-title-docs" aria-hidden="true">Docs</span>

<style>
.site-title {
display: flex;
align-items: center;
text-decoration: none;
}
.site-title-docs {
margin-inline-start: 0.5rem;
font-size: var(--text-16);
line-height: 1.5;
color: var(--text-faint);
}
</style>
38 changes: 38 additions & 0 deletions apps/docs/src/components/starlight/SocialIcons.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
// Remplace les icones sociales de Starlight par la barre de liens de la maquette Docs v3 :
// retour au site, GitHub, version en pastille. Le bouton de theme suit, rendu par Starlight.
import { SITE_URL } from '@/lib/env';
import { REPO, VERSION } from '@/lib/site-data';
---

<nav aria-label="Lumyx" class="lumyx-links">
<a href={SITE_URL}>Product</a>
<a href={REPO}>GitHub</a>
<span class="lumyx-version sl-num">{VERSION}</span>
</nav>

<style>
.lumyx-links {
display: flex;
align-items: center;
gap: 1.5rem;
font-size: var(--text-14);
}
a {
color: var(--text-muted);
text-decoration: none;
transition: color 500ms cubic-bezier(0.32, 0.72, 0, 1);
}
a:hover {
color: var(--text-strong);
}
.lumyx-version {
padding: 2px 8px;
border-radius: var(--radius-pill, 999px);
background: var(--accent-tint);
color: var(--accent-text);
font-family: var(--font-mono);
font-size: var(--text-12);
line-height: 16px;
}
</style>
Loading
Loading