Skip to content

Commit 635d8bb

Browse files
StanBarrowsclaude
andcommitted
Ship JetBrains Mono, add AuthLayout maxWidth and PageHeading breadcrumbs
The design system named JetBrains Mono as --font-mono but never loaded it. The UI font was set on the Storybook canvas in .storybook/storybook.css, a file excluded from files: ["dist"], so it reached the canvas and no consumer. Every consuming app silently rendered in ui-monospace instead — SF Mono, Consolas or DejaVu Sans Mono depending on the developer's OS. Load it from the published src/tokens.css instead, self-hosted via @fontsource/jetbrains-mono as a real dependency so consumers get the files transitively. Latin subset, weights 400/500/600/700. Self-hosted rather than CDN-loaded: consumers ship their own privacy policy and DPA, and a webfont CDN sends every visitor's IP to a third party on first paint. Alongside it, two additive APIs that consuming apps were forking components to get: AuthLayout gains maxWidth (the card was hardcoded to max-w-md, so wider onboarding and consent screens had to copy its markup), and PageHeading gains breadcrumbs/breadcrumbAs (the trail and title are one visual unit, re-paired by hand on every nested page). PageHeading's title row is now min-h-11 so pages with and without an action cluster align their titles; AuthLayout pins its footer to the bottom of the viewport, which is a no-op unless a #footer slot is passed. Type-scale comments corrected: PageHeading renders text-xl, not the text-2xl the token claimed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent df1e244 commit 635d8bb

9 files changed

Lines changed: 255 additions & 52 deletions

File tree

CHANGELOG.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,58 @@ All notable changes to `@codebar-ag/storybook`.
55
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
66
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## v1.8.0
9+
10+
### Fixed
11+
12+
- **The design system declared JetBrains Mono but never shipped it, so every
13+
consuming app rendered in the OS monospace fallback.** `--font-mono` named
14+
`"JetBrains Mono"` in `tokens.css`, but the only font actually loaded was Open
15+
Sans (used by the DocuWare mirror). The UI font was set on the Storybook canvas
16+
via `.storybook/storybook.css` — a file excluded from `files: ["dist"]`, so it
17+
reached the canvas and no consumer. Apps fell through to `ui-monospace`: SF
18+
Mono on macOS, Consolas/Cascadia on Windows, DejaVu Sans Mono on Linux, with
19+
different metrics on each.
20+
21+
The font is now loaded from the published `src/tokens.css`, self-hosted via
22+
`@fontsource/jetbrains-mono` (a real `dependency`, so consumers receive the
23+
files transitively). Latin subset, weights 400/500/600/700. Self-hosted rather
24+
than CDN-loaded because consumers ship their own privacy policy and DPA, and a
25+
webfont CDN sends every visitor's IP to a third party on first paint.
26+
27+
**Consumers need no change**`@import "@codebar-ag/storybook/tokens.css"`
28+
already pulls this in. Expect a visible shift in glyph metrics on first
29+
upgrade: that is the app finally rendering in its own brand font.
30+
31+
### Added
32+
33+
- **`AuthLayout` gained a `maxWidth` prop** (`md` | `lg` | `xl` | `2xl`,
34+
default `md`). The card was hardcoded to `max-w-md`, which forced apps with
35+
wider auth-adjacent screens — onboarding with side-by-side billing fields,
36+
consent screens with a document preview — to fork the component's markup.
37+
38+
- **`PageHeading` gained `breadcrumbs` and `breadcrumbAs` props.** The trail and
39+
the title are one visual unit (the trail's bottom spacing is part of the
40+
heading block's rhythm), and consuming apps were otherwise re-pairing
41+
`Breadcrumbs` with `PageHeading` by hand on every nested page. Pass
42+
`breadcrumbAs` (e.g. Inertia's `Link`) for SPA navigation.
43+
44+
### Changed
45+
46+
- **`PageHeading`'s title row is now `min-h-11`.** A page with an action cluster
47+
and a page without now align their titles on the same baseline; previously the
48+
row's height tracked whether the page happened to have actions, so consecutive
49+
pages visibly jumped. Bare headings grow by up to 16px.
50+
51+
- **`AuthLayout` pins its footer to the bottom of the viewport** instead of
52+
centering the whole stack as one group. Renders identically when no `#footer`
53+
slot is passed; only footer-using callers see the difference.
54+
55+
- **Type-scale documentation corrected.** `--text-xl` (20px) was labelled
56+
"auth/section headers" and `--text-2xl` (24px) "page H1", but `PageHeading`
57+
renders `text-xl` and only `ErrorLayout` uses `text-2xl`. Comments and the
58+
token catalogue now match what the components do. No values changed.
59+
860
## v1.7.1
961

1062
### Fixed

package-lock.json

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codebar-ag/storybook",
3-
"version": "1.7.1",
3+
"version": "1.8.0",
44
"description": "codebar-ag DocuHub — shared Vue 3 + Tailwind v4 design-system atoms and tokens, documented in Storybook.",
55
"license": "MIT",
66
"author": "codebar Solutions AG",
@@ -34,6 +34,7 @@
3434
"test:ci": "playwright test"
3535
},
3636
"dependencies": {
37+
"@fontsource/jetbrains-mono": "^5.3.0",
3738
"tailwind-merge": "^3.0.0"
3839
},
3940
"peerDependencies": {

src/components/layouts/AuthLayout.stories.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,33 @@ export const Default: Story = {
3333
</AuthLayout>`,
3434
}),
3535
};
36+
37+
// `maxWidth` widens the card for auth-adjacent screens that outgrow a single
38+
// column — onboarding with side-by-side fields, consent screens with a preview.
39+
export const Wide: Story = {
40+
render: () => ({
41+
components: { AuthLayout, Button, Field, Input },
42+
template: `
43+
<AuthLayout
44+
max-width="2xl"
45+
title="Create your workspace"
46+
description="Billing details can be changed later."
47+
>
48+
<form class="grid gap-4 sm:grid-cols-2" novalidate @submit.prevent>
49+
<Field label="Company" name="company">
50+
<Input name="company" />
51+
</Field>
52+
<Field label="VAT number" name="vat">
53+
<Input name="vat" />
54+
</Field>
55+
<Field label="Street" name="street">
56+
<Input name="street" />
57+
</Field>
58+
<Field label="City" name="city">
59+
<Input name="city" />
60+
</Field>
61+
<Button type="submit" class="w-full sm:col-span-2">Continue</Button>
62+
</form>
63+
</AuthLayout>`,
64+
}),
65+
};

src/components/layouts/AuthLayout.vue

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,59 @@
11
<script setup lang="ts">
22
// Centered single-card shell for auth screens (login, register, reset, OTP).
33
// The brand slot sits above the card; footer links render quiet below it.
4+
import { computed } from 'vue';
45
import Card from '../molecules/Card.vue';
56
6-
withDefaults(
7+
// Tailwind scans for literal class names, so each width is spelled out here
8+
// rather than interpolated into `max-w-${maxWidth}` at render time.
9+
const MAX_WIDTHS = {
10+
md: 'max-w-md',
11+
lg: 'max-w-lg',
12+
xl: 'max-w-xl',
13+
'2xl': 'max-w-2xl',
14+
} as const;
15+
16+
// `maxWidth` defaults to md — the width this card was previously hardcoded to,
17+
// so existing callers render unchanged. The wider steps exist for auth-adjacent
18+
// screens that outgrow one column: onboarding with side-by-side billing fields,
19+
// consent screens showing a document alongside the form.
20+
const props = withDefaults(
721
defineProps<{
822
title?: string | null;
923
description?: string | null;
24+
maxWidth?: keyof typeof MAX_WIDTHS;
1025
}>(),
11-
{ title: null, description: null },
26+
{ title: null, description: null, maxWidth: 'md' },
1227
);
28+
29+
const cardWidth = computed(() => MAX_WIDTHS[props.maxWidth]);
1330
</script>
1431

1532
<template>
16-
<div class="flex min-h-dvh flex-col items-center justify-center bg-bg px-4 py-10">
17-
<div
18-
v-if="$slots.brand"
19-
class="mb-6"
20-
>
21-
<slot name="brand" />
22-
</div>
33+
<!-- Card centers in the space above the footer (`flex-1` + `justify-center`)
34+
rather than the whole stack centering as one group, so the footer sits at
35+
the bottom of the viewport on tall screens instead of floating just under
36+
the card. Renders identically to the old markup when no footer slot is
37+
passed; only footer-using callers see the difference. -->
38+
<div class="flex min-h-dvh flex-col items-center bg-bg px-4 py-10">
39+
<div class="flex w-full flex-1 flex-col items-center justify-center">
40+
<div
41+
v-if="$slots.brand"
42+
class="mb-6"
43+
>
44+
<slot name="brand" />
45+
</div>
2346

24-
<Card
25-
size="lg"
26-
:title="title"
27-
:description="description"
28-
class="w-full max-w-md animate-fade"
29-
>
30-
<slot />
31-
</Card>
47+
<Card
48+
size="lg"
49+
:title="title"
50+
:description="description"
51+
class="w-full animate-fade"
52+
:class="cardWidth"
53+
>
54+
<slot />
55+
</Card>
56+
</div>
3257

3358
<div
3459
v-if="$slots.footer"

src/components/molecules/PageHeading.stories.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,38 @@ export const WithActions: Story = {
4747
</PageHeading>`,
4848
}),
4949
};
50+
51+
export const WithBreadcrumbs: Story = {
52+
args: {
53+
breadcrumbs: [
54+
{ label: 'Gateways', href: '#' },
55+
{ label: 'Mustermann AG', href: '#' },
56+
{ label: 'File cabinets' },
57+
],
58+
},
59+
};
60+
61+
// The everything case, and what most consuming pages actually render: trail,
62+
// title, description and an action cluster as one heading block.
63+
export const WithBreadcrumbsAndActions: Story = {
64+
render: () => ({
65+
components: { PageHeading, Button },
66+
setup: () => ({
67+
breadcrumbs: [
68+
{ label: 'Gateways', href: '#' },
69+
{ label: 'Mustermann AG', href: '#' },
70+
{ label: 'File cabinets' },
71+
],
72+
}),
73+
template: `<PageHeading :breadcrumbs="breadcrumbs">
74+
File cabinets
75+
<template #description>
76+
Archives and document trays available to the connected MCP gateway.
77+
</template>
78+
<template #actions>
79+
<Button variant="secondary" size="sm">Export</Button>
80+
<Button size="sm">New cabinet</Button>
81+
</template>
82+
</PageHeading>`,
83+
}),
84+
};

src/components/molecules/PageHeading.vue

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,27 @@
44
// text-ink">` pattern repeated across most pages. An optional `#actions`
55
// slot renders a right-aligned action cluster beside the title (wraps below
66
// it on narrow screens) — omit the slot and this renders exactly as before.
7+
//
8+
// `breadcrumbs` renders a trail above the title. It lives here rather than
9+
// being composed by each caller because the two are a single visual unit: the
10+
// trail's spacing below itself is part of the heading block's rhythm, and every
11+
// consuming app was otherwise re-pairing them by hand on every nested page.
12+
// Pass `breadcrumbAs` (e.g. Inertia's `Link`) for SPA navigation, exactly as
13+
// Breadcrumbs' own `as` prop expects.
714
import { computed, useSlots } from 'vue';
815
import { cx } from '../../helpers/cx';
916
import { useRootAttrs } from '../../composables/useRootAttrs';
17+
import Breadcrumbs, { type BreadcrumbItem } from './Breadcrumbs.vue';
1018
1119
defineOptions({ inheritAttrs: false });
1220
1321
withDefaults(
1422
defineProps<{
1523
eyebrow?: string | null;
24+
breadcrumbs?: BreadcrumbItem[];
25+
breadcrumbAs?: string | object;
1626
}>(),
17-
{ eyebrow: null },
27+
{ eyebrow: null, breadcrumbs: () => [], breadcrumbAs: 'a' },
1828
);
1929
2030
const slots = useSlots();
@@ -24,32 +34,45 @@ const classes = computed(() => cx('text-xl font-semibold text-ink', classAttr.va
2434
</script>
2535

2636
<template>
27-
<div class="flex flex-wrap items-start justify-between gap-4">
28-
<div class="min-w-0">
29-
<p
30-
v-if="eyebrow"
31-
class="text-2xs uppercase tracking-wide text-muted"
32-
>
33-
{{ eyebrow }}
34-
</p>
35-
<h1
36-
:class="classes"
37-
v-bind="rootAttrs"
38-
>
39-
<slot />
40-
</h1>
41-
<p
42-
v-if="slots.description"
43-
class="mt-1 text-sm text-muted"
37+
<div>
38+
<Breadcrumbs
39+
v-if="breadcrumbs.length"
40+
:items="breadcrumbs"
41+
:as="breadcrumbAs"
42+
class="mb-2"
43+
/>
44+
45+
<!-- `min-h-11` matches the height of a Button, so a page WITH an action
46+
cluster and a page without still line their titles up at the same
47+
baseline. Without it the heading row's height tracks whether the page
48+
happens to have actions, and consecutive pages visibly jump. -->
49+
<div class="flex min-h-11 flex-wrap items-start justify-between gap-4">
50+
<div class="min-w-0">
51+
<p
52+
v-if="eyebrow"
53+
class="text-2xs uppercase tracking-wide text-muted"
54+
>
55+
{{ eyebrow }}
56+
</p>
57+
<h1
58+
:class="classes"
59+
v-bind="rootAttrs"
60+
>
61+
<slot />
62+
</h1>
63+
<p
64+
v-if="slots.description"
65+
class="mt-1 text-sm text-muted"
66+
>
67+
<slot name="description" />
68+
</p>
69+
</div>
70+
<div
71+
v-if="slots.actions"
72+
class="flex flex-wrap items-center gap-2 shrink-0"
4473
>
45-
<slot name="description" />
46-
</p>
47-
</div>
48-
<div
49-
v-if="slots.actions"
50-
class="flex flex-wrap items-center gap-2 shrink-0"
51-
>
52-
<slot name="actions" />
74+
<slot name="actions" />
75+
</div>
5376
</div>
5477
</div>
5578
</template>

src/foundations/tokenCatalog.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,8 @@ export const typeScale: TypeStep[] = [
7171
{ token: '--text-sm', textClass: 'text-sm', px: '13px / 18px', use: 'Body default, controls' },
7272
{ token: '--text-base', textClass: 'text-base', px: '14px / 20px', use: 'Emphasized body, buttons' },
7373
{ token: '--text-lg', textClass: 'text-lg', px: '16px / 24px', use: 'Card titles, section leads' },
74-
{ token: '--text-xl', textClass: 'text-xl', px: '20px / 28px', use: 'Auth/section headers' },
75-
{ token: '--text-2xl', textClass: 'text-2xl', px: '24px / 32px', use: 'Page H1' },
74+
{ token: '--text-xl', textClass: 'text-xl', px: '20px / 28px', use: 'Page H1, auth/section headers' },
75+
{ token: '--text-2xl', textClass: 'text-2xl', px: '24px / 32px', use: 'Error-page headline, display figures' },
7676
];
7777

7878
export interface RadiusToken {

0 commit comments

Comments
 (0)