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
160 changes: 150 additions & 10 deletions CONSUMING.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,142 @@ bump deliberately.

---

## `@ampl/kit/ui` — `AmplHeader`

`AmplHeader` is the single shared header component for every tool on `ampl.tools`.
It renders two bands: a white institutional band (the AMPL logo lockup and
lab-site nav, owned by the kit) and a deep-plum (#743A6A) WORKSHOP band (tool
switcher, contextual in-app nav, EN/ES, and the signed-in account chip or a
sign-in link). A hamburger-toggled mobile sheet covers narrow screens. All tool
variation comes through props — no per-tool fork of the component.

```typescript
import {
AmplHeader,
DEFAULT_TOOLS,
type AmplHeaderProps,
type NavItem,
type ToolLink,
type ToolId,
type AccountInfo,
type HeaderSize,
} from "@ampl/kit/ui";
```

### Compact case (all in-app pages)

```tsx
// Signed-in compact header — standard in-app layout
<AmplHeader
tool="calamus"
toolName="Palaeography"
localeSwitcher={
<LocaleSwitcher variant="on-dark" buildHref={buildLocaleSwitchHref} current={locale} />
}
account={{
name: user.name,
handle: user.handle,
avatarUrl: user.avatar_url,
}}
nav={[
{ label: t("nav.manuscripts"), href: "/palaeography/manuscripts", active: true },
{ label: t("nav.groups"), href: "/palaeography/groups" },
]}
/>
```

The `size` prop defaults to `"compact"` and can be omitted on every in-app
page.

### Full case (signed-out front door only)

```tsx
// Signed-out front door — size="full" masthead, no nav
<AmplHeader
tool="calamus"
toolName="Palaeography"
size="full"
localeSwitcher={
<LocaleSwitcher variant="on-dark" buildHref={buildLocaleSwitchHref} current={locale} />
}
signInHref="/auth/login?return_to=/palaeography"
/>
```

Use `size="full"` **only** on the signed-out front-door page. Every authenticated
page and every intermediate state uses the default `"compact"`. Only the
institutional band scales between the two sizes; the WORKSHOP band is constant
height in both modes.

### `account` vs `signInHref` — signed-in vs signed-out

When `account` is provided the WORKSHOP band renders the signed-in account chip
(avatar or initials + name, with a menu). When `account` is `null` or omitted
the band renders a sign-in link pointing to `signInHref`.

```tsx
// Signed-in
<AmplHeader account={{ name: user.name, avatarUrl: user.avatar_url }} ... />

// Signed-out
<AmplHeader account={null} signInHref="/auth/login?return_to=/palaeography" ... />
```

Override the sign-in link text with `signInLabel` when the default ("Sign in")
does not fit the tool's voice — for example, Scheduling passes
`signInLabel="Host sign in"`.

### `AccountInfo` sign-out

`AccountInfo.signOutHref` defaults to `"/auth/logout"`. Sign-out is a POST form.
Pass `signOutHref` only if you need to override the target (e.g. in a local dev
environment); standard deployments need no override.

### Tool switcher

The WORKSHOP band includes a cross-tool switcher populated from the `tools` prop.
It defaults to `DEFAULT_TOOLS` (Palaeography and Scheduling). Override it to
control which tools appear or to add future tools:

```typescript
import { DEFAULT_TOOLS } from "@ampl/kit/ui";

// Use the default registry — no prop needed
<AmplHeader tools={DEFAULT_TOOLS} ... />

// Override with a custom registry
<AmplHeader
tools={[
{ id: "calamus", name: "Palaeography", descriptor: "Practice reading manuscripts", href: "https://ampl.tools/palaeography" },
{ id: "scheduling", name: "Scheduling", descriptor: "booking & polls", href: "https://ampl.tools/scheduling" },
]}
... />
```

The `tool` prop (internal tool id) drives the switcher "current" highlight so
the active tool is visually distinguished.

### LocaleSwitcher variant

Pass `<LocaleSwitcher variant="on-dark" .../>` to `AmplHeader`. The `"on-dark"`
variant is styled for the plum WORKSHOP band; the default light variant is
unsuitable on that background.

### CSP: avatar host

When `account` carries a non-null `avatarUrl`, the header renders the GitHub
avatar as a plain `<img>`. The same CSP requirement applies as for
`AccountWidget` — add `avatars.githubusercontent.com` to `img-src`. See
[section 2, "CSP avatar host"](#2-csp-avatar-host) above.

### Deprecation note

`SiteHeader` is `@deprecated` as of v0.3.0. It is retained for backward
compatibility but new tools should use `AmplHeader`. Existing consumers (Calamus)
should migrate at the next milestone boundary.

---

## `@ampl/kit/email` — bilingual shell + `.ics` builder

The `@ampl/kit/email` subpath ships two pure TypeScript functions —
Expand Down Expand Up @@ -517,16 +653,20 @@ The git tag is the contract for `@ampl/kit`. Consumers pin to an exact tag:
"@ampl/kit": "github:UCSB-AMPLab/ampl-kit#v0.2.1"
```

**This release:** `v0.2.2` fixes a `return_to` bug in the hosted `ampl-auth`
service: the login page dropped `return_to` at the "Continue with GitHub"
button, so deep-link-after-login always landed at `/auth` (see the note in
section 4). It is a service-side fix with no library API change — every
consuming tool benefits once the shared service is deployed at v0.2.2, and no
consumer code change is required. (`v0.2.1` exported the `send()` RPC contract —
`SendMessage`, `SendResult` — from `./email`; `v0.2.0` added the `./email`
subpath itself: `renderEmailShell`, `buildIcs`, `EmailShellInput`, `EmailBlock`,
`IcsEvent`.) Consumers on `v0.1.0` are unaffected at the library level — the
`./auth` and `./ui` subpaths are unchanged.
**This release:** `v0.3.0` adds `AmplHeader` — a new UI component exported from
`@ampl/kit/ui`, along with the `DEFAULT_TOOLS` registry and the types
`AmplHeaderProps`, `NavItem`, `ToolLink`, `ToolId`, `AccountInfo`, and
`HeaderSize`. It also adds the `kit.nav`, `kit.switcher`, and `kit.header` i18n
string groups and the `"on-dark"` `LocaleSwitcher` variant. This is purely
additive — a **minor** bump. `SiteHeader` is deprecated but retained for
back-compat; no consumer is required to migrate. (`v0.2.2` fixed a `return_to`
bug in the hosted `ampl-auth` service: the login page dropped `return_to` at
the "Continue with GitHub" button, so deep-link-after-login always landed at
`/auth` — a service-side fix with no library API change. `v0.2.1` exported the
`send()` RPC contract — `SendMessage`, `SendResult` — from `./email`; `v0.2.0`
added the `./email` subpath itself: `renderEmailShell`, `buildIcs`,
`EmailShellInput`, `EmailBlock`, `IcsEvent`.) Consumers on `v0.1.0` are
unaffected at the library level — the `./auth` subpath is unchanged.

**Policy:**

Expand Down
62 changes: 11 additions & 51 deletions app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
*
* This file is the outermost wrapper around every page the app renders. It
* owns the HTML document itself — the `<html>`, `<head>`, and `<body>` — and
* paints the persistent chrome that surrounds each route: the AMPL site header
* with its "AMPL Auth" lockup, a language switcher, and the shared footer. Two
* paints the persistent chrome that surrounds each route: the shared
* `AmplHeader` (full masthead, `tool="auth"`, the signed-out front door) with
* its language switcher, and the shared footer. Two
* middlewares run here for every request — `securityMiddleware`, which mints
* the per-request CSP nonce, and `i18nextMiddleware`, which resolves the
* active language — and the root loader hands both the chosen `locale` and the
Expand All @@ -15,7 +16,7 @@
* — the friendly, translated fallback shown when a route throws or a page is
* not found, with a developer stack trace surfaced only in development.
*
* @version v0.1.0
* @version v0.3.0
*/

import {
Expand All @@ -35,8 +36,7 @@ import { i18nextMiddleware, getLocale } from "~/middleware/i18next";
import { securityMiddleware, nonceContext } from "~/middleware/security";
import { withBase, stripBasename } from "~/lib/paths";
import { kitFontLinks } from "@ampl/kit/fonts";
import { SiteHeader, SiteFooter, LocaleSwitcher } from "@ampl/kit/ui";
import amplLogo from "@ampl/kit/assets/ampl-logo.svg";
import { AmplHeader, SiteFooter, LocaleSwitcher } from "@ampl/kit/ui";
import "./app.css";

export const middleware = [securityMiddleware, i18nextMiddleware];
Expand All @@ -55,7 +55,6 @@ export function Layout({ children }: { children: React.ReactNode }) {
const locale = data?.locale ?? "en";
const nonce = data?.nonce ?? "";
const location = useLocation();
const { t } = useTranslation("common");

// Build the locale-switch href: strip basename from current path so the
// locale route can reconstruct it correctly, then prefix with withBase.
Expand All @@ -76,57 +75,18 @@ export function Layout({ children }: { children: React.ReactNode }) {
</head>
<body>
<div className="flex min-h-screen flex-col">
<SiteHeader
<AmplHeader
tool="auth"
toolName="Account"
size="full"
localeSwitcher={
<LocaleSwitcher
buildHref={buildLocaleHref}
current={locale as "en" | "es"}
variant="on-dark"
/>
}
nav={
<nav
aria-label={t("nav.ariaLabel")}
className="flex max-w-[480px] flex-wrap items-center justify-end gap-x-9 gap-y-1.5"
>
<a
href="https://ampl.clair.ucsb.edu/#tools"
className="font-title text-[20px] lg:text-[24px] xl:text-[28px] font-medium leading-none uppercase tracking-[0.4px] text-fg-1 hover:text-accent no-underline"
>
{t("nav.tools")}
</a>
<a
href="https://ampl.clair.ucsb.edu/#projects"
className="font-title text-[20px] lg:text-[24px] xl:text-[28px] font-medium leading-none uppercase tracking-[0.4px] text-fg-1 hover:text-accent no-underline"
>
{t("nav.projects")}
</a>
<a
href="https://ampl.clair.ucsb.edu/#opportunities"
className="font-title text-[20px] lg:text-[24px] xl:text-[28px] font-medium leading-none uppercase tracking-[0.4px] text-fg-1 hover:text-accent no-underline"
>
{t("nav.opportunities")}
</a>
<a
href="https://ampl.clair.ucsb.edu/people"
className="font-title text-[20px] lg:text-[24px] xl:text-[28px] font-medium leading-none uppercase tracking-[0.4px] text-fg-1 hover:text-accent no-underline"
>
{t("nav.people")}
</a>
</nav>
}
>
{/* AMPL logo — links to lab home; img is decorative (aria-label on anchor) */}
<a
href="https://ampl.clair.ucsb.edu/"
aria-label="AMPL — Archives, Memory, and Preservation Lab"
>
<img
src={amplLogo}
alt=""
className="h-auto w-[300px] lg:w-[400px] xl:w-[500px]"
/>
</a>
</SiteHeader>
/>
<div className="flex-1">{children}</div>
<SiteFooter />
</div>
Expand Down
39 changes: 38 additions & 1 deletion kit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ git dependency.
| `@ampl/kit/theme.css` | Tailwind v4 `@theme` tokens + base layer | ✅ |
| `@ampl/kit/fonts` | `kitFontLinks` for the RR root `links` export | ✅ |
| `@ampl/kit/locales/{en,es}` | i18n key fragments | ✅ (es pending review) |
| `@ampl/kit/ui` | footer, header, account widget, no-access, auth-error, report-a-problem, primitives | ⏳ scaffold |
| `@ampl/kit/ui` | footer, `AmplHeader` (unified two-band header), account widget, locale switcher, no-access, auth-error, report-a-problem, primitives | |
| `@ampl/kit/auth` | session-validation helper + read-only `AUTH_DB` contract | ✅ |

`@ampl/kit/ui` is presentational only — no secrets, no DB access. The single secret
Expand Down Expand Up @@ -104,3 +104,40 @@ export const links: Route.LinksFunction = () => [...kitFontLinks];
```

Then merge `@ampl/kit/locales/{en,es}` and import surfaces from `@ampl/kit/ui`.

## AmplHeader (`@ampl/kit/ui`)

`AmplHeader` is the unified shared header for all AMPL tools. It renders a white
institutional band (AMPL logo lockup + lab-site nav) and a deep-plum WORKSHOP
band (tool switcher + contextual nav + EN/ES + account chip or sign-in link), plus
a hamburger-toggled mobile sheet. All tool variation comes through props.

```tsx
import { AmplHeader, DEFAULT_TOOLS } from "@ampl/kit/ui";

// Compact signed-in header (standard in-app use)
<AmplHeader
tool="calamus"
toolName="Palaeography"
localeSwitcher={<LocaleSwitcher variant="on-dark" buildHref={buildHref} current={locale} />}
account={{ name: user.name, avatarUrl: user.avatar_url }}
nav={[
{ label: "Manuscripts", href: "/palaeography/manuscripts", active: true },
{ label: "Groups", href: "/palaeography/groups" },
]}
/>

// Full signed-out front door (size="full" only on the front door)
<AmplHeader
tool="calamus"
toolName="Palaeography"
size="full"
localeSwitcher={<LocaleSwitcher variant="on-dark" buildHref={buildHref} current={locale} />}
signInHref="/auth/login?return_to=/palaeography"
/>
```

The tool switcher defaults to `DEFAULT_TOOLS` (Palaeography + Scheduling); pass
a custom `tools` array to override. `SiteHeader` is deprecated as of v0.3.0 in
favour of `AmplHeader`. See `CONSUMING.md` for the full prop reference and CSP
requirements.
16 changes: 16 additions & 0 deletions kit/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ export default {
},
header: {
localeLabel: "Switch language",
signIn: "Sign in",
menuLabel: "Menu",
},
nav: {
ariaLabel: "Lab site navigation",
tools: "Tools",
projects: "Projects",
opportunities: "Opportunities",
people: "People",
},
switcher: {
heading: "AMPL Workshop",
tagline: {
calamus: "Practice reading manuscripts",
scheduling: "booking & polls",
},
},
accountWidget: {
signOut: "Sign out",
Expand Down
16 changes: 16 additions & 0 deletions kit/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ export default {
},
header: {
localeLabel: "Cambiar idioma",
signIn: "Iniciar sesión",
menuLabel: "Menú",
},
nav: {
ariaLabel: "Navegación del sitio del laboratorio",
tools: "Herramientas",
projects: "Proyectos",
opportunities: "Oportunidades",
people: "Personas",
},
switcher: {
heading: "Taller AMPL",
tagline: {
calamus: "Practica leer manuscritos",
scheduling: "reservas y encuestas",
},
},
accountWidget: {
signOut: "Cerrar sesión",
Expand Down
3 changes: 2 additions & 1 deletion kit/theme.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@

--color-accent: #A5469A; /* THE AMPL plum — the single UI accent */
--color-accent-ink: #8B467D; /* footer band plum */
--color-accent-deep: #743A6A; /* footer-bottom plum */
--color-accent-deep: #743A6A; /* footer-bottom plum + WORKSHOP band */
--color-accent-pale: #efd6e9; /* WORKSHOP eyebrow + current-tool in switcher (on plum) */

--color-hue-orange: #D97743; /* per-project accent override ONLY */
--color-hue-blue: #3B6BA5; /* per-project accent override ONLY */
Expand Down
Loading
Loading