Files
awesome-copilot/website/src/components/brand/ExtensionDetail.tsx
T
Aaron PowellandCopilot App f29b88f27f feat(website): rebuild site on Primer Brand design prototype
Replace the Astro + Starlight site with the Brand Engineering design
prototype, ported component-for-component onto plain Astro + React
islands. The prototype is treated as the authority on markup and
styling; dynamic data is injected into its components rather than the
components being reinterpreted.

Framework:
- Remove @astrojs/starlight entirely, along with its document shell,
  search, footer and language selector. BaseLayout.astro now owns the
  document head, CSP, social meta and analytics.
- Add @primer/react-brand and @astrojs/react. Alias the package to its
  ESM build in vite.resolve, since the default CJS entrypoint breaks
  named-export detection during SSR, and mark it noExternal so its
  stylesheet imports resolve.
- Promote pagefind to an explicit devDependency; it was previously
  pulled in transitively by Starlight.

Pages: home, the five catalogs, the five detail routes, contributors,
Playbook index and articles, and the cookbook are all rendered by
ported prototype components inside a shared PageShell.

Detail pages share a DetailChassis (hero, breadcrumbs, sticky TOC with
scroll-spy, prev/next) while keeping what makes each type distinct: a
file switcher over bundled skill assets, an included-items grid and
external provenance for plugins, and a hero-scale preview for
extensions.

i18n: resolve translated Playbook entries via Astro.currentLocale
inside the shared article route instead of separate [locale] routes.
The explicit routes collided with the i18n fallback routes, so
translated articles were being shadowed by their English originals and
900 nonsensical double-locale pages were emitted.

Search: TopNavSearch now queries the Pagefind index client-side in
addition to the static resource index, merging on href and degrading
to the static index in dev, where no index has been built.

Also delete the superseded vanilla-TS page renderers and Astro
partials, which the ported React components fully replace.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 80686fef-efe3-4cdd-8cd6-bfa61a5d0af6
2026-08-14 17:32:36 +10:00

241 lines
7.0 KiB
TypeScript

import {
CheckIcon,
CopyIcon,
LinkExternalIcon,
MarkGithubIcon,
PlusIcon,
} from "@primer/octicons-react";
import React from "react";
import { Button, Token } from "@primer/react-brand";
import {
DetailChassis,
type DetailSibling,
type DetailTocItem,
} from "./DetailChassis";
import { ResourceMeta } from "./ResourceMeta";
import { pageHref } from "./pageHref";
import type { SearchItem } from "./searchIndex";
import styles from "./styles/dotnet-upgrade.module.css";
// The framed media block and the install command bar are prototype treatments
// that live in the Copilot app page's stylesheet; reused here verbatim.
import appStyles from "./styles/github-copilot-app.module.css";
export type ExtensionDetailImage = { url: string; alt: string };
export type ExtensionDetailItem = {
id: string;
name: string;
description?: string;
version?: string | null;
pluginName?: string | null;
external?: boolean;
externalSource?: string | null;
keywords?: string[];
author?: { name?: string; url?: string } | null;
};
export type ExtensionDetailProps = {
item: ExtensionDetailItem;
/** Preview screenshots, largest/primary first. */
images: ExtensionDetailImage[];
/** Rendered, sanitized README with heading ids already stamped. */
markdownHtml: string;
toc: DetailTocItem[];
/** `ghapp://` deep link — absent for external extensions. */
installUrl?: string | null;
/** Copilot CLI command — absent for external extensions. */
installCommand?: string | null;
/** Repository the extension is published from. */
sourceUrl?: string | null;
lastUpdated?: string | null;
previous?: DetailSibling;
next?: DetailSibling;
searchIndex?: SearchItem[];
contributorsTotal?: number;
};
const PREVIEW_SECTION_ID = "preview";
/** Copyable `copilot plugin install …` line, matching the prototype install bar. */
function InstallCommand({ command }: { command: string }) {
const [copied, setCopied] = React.useState(false);
const timer = React.useRef<number | undefined>(undefined);
React.useEffect(() => () => window.clearTimeout(timer.current), []);
const handleCopy = () => {
navigator.clipboard?.writeText(command).catch(() => undefined);
setCopied(true);
window.clearTimeout(timer.current);
timer.current = window.setTimeout(() => setCopied(false), 2000);
};
return (
<div className={appStyles.installBar}>
<code className={appStyles.installCommand}>{command}</code>
<button
type="button"
className={appStyles.installCopy}
onClick={handleCopy}
aria-label={copied ? "Copied to clipboard" : "Copy install command"}
>
{copied ? <CheckIcon size={16} /> : <CopyIcon size={16} />}
<span>{copied ? "Copied" : "Copy"}</span>
</button>
</div>
);
}
/**
* Detail route for a canvas extension.
*
* Two things set it apart from the other resource details: the preview
* screenshot leads the content column at hero scale (a gallery when an
* extension ships more than one), and installation has two paths — the
* `ghapp://` deep link and the Copilot CLI command. Externally hosted
* extensions carry neither, so they fall back to a link to their source repo.
*/
export function ExtensionDetail({
item,
images,
markdownHtml,
toc,
installUrl,
installCommand,
sourceUrl,
lastUpdated,
previous,
next,
searchIndex,
contributorsTotal,
}: ExtensionDetailProps) {
const hasPreview = images.length > 0;
const install = (
<>
{installUrl ? (
<Button
as="a"
href={installUrl}
variant="primary"
hasArrow={false}
leadingVisual={PlusIcon}
>
Open in Copilot app
</Button>
) : null}
{sourceUrl ? (
<Button
as="a"
href={sourceUrl}
variant={installUrl ? "secondary" : "primary"}
hasArrow={false}
leadingVisual={installUrl ? undefined : LinkExternalIcon}
className={installUrl ? styles.iconButton : undefined}
aria-label={
installUrl ? `View ${item.name} on GitHub` : undefined
}
>
{installUrl ? <MarkGithubIcon size={16} /> : "View source repository"}
</Button>
) : null}
</>
);
const heroExtras =
item.version || item.external ? (
<div className={styles.metaValues}>
{item.version ? <Token text={`v${item.version}`} /> : null}
{item.external ? <Token text="Externally hosted" /> : null}
</div>
) : null;
const fullToc = hasPreview
? [{ id: PREVIEW_SECTION_ID, label: "Preview" }, ...toc]
: toc;
return (
<DetailChassis
title={item.name}
description={item.description}
breadcrumbs={[
{ label: "Extensions", href: pageHref("extensions") },
{ label: item.name },
]}
install={install}
heroExtras={heroExtras}
toc={fullToc}
sidebar={
<ResourceMeta
kicker="Extension details"
groups={[
{ label: "Keywords", items: item.keywords ?? [] },
{
label: "Source",
items: item.externalSource ? [item.externalSource] : [],
},
]}
author={item.author?.name ?? null}
lastUpdated={lastUpdated}
sourceUrl={sourceUrl ?? undefined}
/>
}
previous={previous}
next={next}
currentPage="extensions"
searchIndex={searchIndex}
contributorsTotal={contributorsTotal}
>
{hasPreview ? (
<section id={PREVIEW_SECTION_ID} className={styles.articleSection}>
<h2 className={styles.articleHeading}>Preview</h2>
{images.map((image) => (
<figure key={image.url} className={appStyles.videoFigure}>
<img
className={appStyles.gif}
src={image.url}
alt={image.alt}
loading="eager"
decoding="async"
width={1280}
height={720}
/>
</figure>
))}
</section>
) : null}
{installUrl || installCommand ? (
<section className={styles.articleSection}>
<h2 className={styles.articleHeading}>Install</h2>
{installCommand ? <InstallCommand command={installCommand} /> : null}
{installUrl ? (
<p>
Or{" "}
<a href={installUrl}>open {item.name} directly in the Copilot app</a>
.
</p>
) : null}
</section>
) : sourceUrl ? (
<section className={styles.articleSection}>
<h2 className={styles.articleHeading}>Install</h2>
<p>
{item.name} is hosted outside this repository. Follow the
installation steps in its{" "}
<a href={sourceUrl}>source repository</a>.
</p>
</section>
) : null}
{markdownHtml ? (
<section className={styles.articleSection}>
<div dangerouslySetInnerHTML={{ __html: markdownHtml }} />
</section>
) : null}
</DetailChassis>
);
}