Skip to content

Commit a275301

Browse files
committed
feat(lab-card): rework as title-on-top with one column per link
Replace the horizontal-band layout with a column-per-link design: - Title and tagline sit on top; a horizontal row of columns sits below, separated by a subtle top border on the columns row. - Each column heads with an uppercase kind label ("Demo", "Repository", "Case study"), followed by the icon-prefixed link itself. - Columns flow responsively via container queries: 1 column → 2 at 32rem → 4 at 56rem. This keeps Forms Lab's four links on a single row on desktop while single-link cards remain compact. - The link `label` is now the project/repo identifier (what you're linking to) rather than the action, since the action is carried by the column heading. - Drop the now-unneeded `group` field from the content schema and loader. Labels absorb the sub-project identity ("Forms Platform", "Forms Lab (experiment)") without a separate grouping construct.
1 parent 2bb32be commit a275301

10 files changed

Lines changed: 115 additions & 146 deletions

File tree

content/featured/document-extractor-lab.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ title: Document Extractor Lab
33
tagline: Accurately extract data from PDFs and images for faster application processing.
44
order: 3
55
links:
6-
- label: Repository
6+
- label: flexion/document-extractor
77
url: https://github.com/flexion/document-extractor
88
kind: repo
9-
- label: Case study
9+
- label: Flexion case study
1010
url: https://flexion.us/case-study/document-extraction-for-faster-processing/
1111
kind: case-study
1212
---

content/featured/forms-lab.md

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,16 @@ title: Forms Lab
33
tagline: Digitize forms to create modern, accessible experiences for public outreach.
44
order: 1
55
links:
6-
- label: Live demo
6+
- label: Forms Platform
77
url: https://pp4cc7kwbf.us-east-1.awsapprunner.com/
88
kind: demo
9-
group: Forms Platform
10-
- label: Repository
9+
- label: flexion/forms
1110
url: https://github.com/flexion/forms
1211
kind: repo
13-
group: Forms Platform
14-
- label: Live demo
12+
- label: Forms Lab (experiment)
1513
url: https://ec2-34-197-222-16.compute-1.amazonaws.com/
1614
kind: demo
17-
group: Forms Lab (experiment)
18-
- label: Repository
15+
- label: flexion/forms-lab
1916
url: https://github.com/flexion/forms-lab
2017
kind: repo
21-
group: Forms Lab (experiment)
2218
---

content/featured/messaging-lab.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ title: Messaging Lab
33
tagline: Text messaging services to deliver critical updates to the people you serve.
44
order: 2
55
links:
6-
- label: Repository
6+
- label: flexion/flexion-notify
77
url: https://github.com/flexion/flexion-notify
88
kind: repo
99
---

docs/featured-content.md

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ title: Forms Lab
1212
tagline: Digitize forms to create modern, accessible experiences for public outreach.
1313
order: 1
1414
links:
15-
- label: Live demo
15+
- label: Forms Platform
1616
url: https://pp4cc7kwbf.us-east-1.awsapprunner.com/
1717
kind: demo
18-
group: Forms Platform
19-
- label: Repository
18+
- label: flexion/forms
2019
url: https://github.com/flexion/forms
2120
kind: repo
22-
group: Forms Platform
2321
---
2422
```
2523

@@ -29,18 +27,17 @@ links:
2927
- `tagline` — one-sentence summary (string, required)
3028
- `order` — display order ascending (integer, required)
3129
- `links` — list of link objects (array, required). Each link has:
32-
- `label` — visible link text (string, required)
30+
- `label` — visible link text (string, required). This is the identifier of what you're linking to — a repo name, a project name, or the target resource.
3331
- `url` — destination URL (string, required)
34-
- `kind` — one of `demo`, `repo`, or `case-study` (required). Drives the icon shown before the label.
35-
- `group` — optional sub-project name (string). When multiple links share a `group`, they render together under a small heading; ungrouped links render without one. Use this for a lab that contains more than one distinct project (e.g., a production and an experiment variant).
32+
- `kind` — one of `demo`, `repo`, or `case-study` (required). Drives the column heading ("Demo", "Repository", or "Case study") and the icon shown before the label.
3633

3734
## Loader
3835

3936
`src/build/featured.ts` exports `loadFeatured(rootDir)` which reads every `.md` file in `content/featured/`, parses front-matter, validates the schema, and returns labs sorted by `order`.
4037

4138
## Rendering
4239

43-
The home page renders one `<LabCard />` per lab. Each card is a horizontal band — title and tagline on the left, grouped link list on the right — that collapses to a stacked layout on narrow viewports via a `@container (min-width: 40rem)` rule. Links are prefixed with an icon (globe for `demo`, GitHub mark for `repo`, document for `case-study`) and grouped under a small heading when a `group` is set. The home page's featured list is constrained to `72rem` to keep the bands at a readable width on wide displays.
40+
The home page renders one `<LabCard />` per lab. Each card places the title and tagline on top, with a horizontal row of columns below it — one column per link. Each column has a small uppercase kind heading ("Demo", "Repository", or "Case study") and the link itself, prefixed with an icon (globe for `demo`, GitHub mark for `repo`, document for `case-study`). Columns flow from 1 → 2 → 4 based on card width via container queries at 32rem and 56rem. The home page's featured list is constrained to `72rem` to keep cards at a readable width on wide displays.
4441

4542
## Adding a featured lab
4643

src/build/featured.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export type FeaturedLink = {
88
label: string
99
url: string
1010
kind: FeaturedLinkKind
11-
group?: string
1211
}
1312

1413
const LINK_KINDS: ReadonlySet<FeaturedLinkKind> = new Set([
@@ -71,15 +70,11 @@ function parseLinks(file: string, value: unknown): FeaturedLink[] {
7170
`content/featured/${file}: links[${i}].kind must be one of ${[...LINK_KINDS].join(', ')}`,
7271
)
7372
}
74-
const link: FeaturedLink = {
73+
return {
7574
label: requireString(file, `links[${i}].label`, o.label),
7675
url: requireString(file, `links[${i}].url`, o.url),
7776
kind: kindRaw as FeaturedLinkKind,
7877
}
79-
if (typeof o.group === 'string' && o.group.length > 0) {
80-
link.group = o.group
81-
}
82-
return link
8378
})
8479
}
8580

src/design/components/lab-card/examples.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ const multiProject: FeaturedLab = {
66
tagline: 'Digitize forms to create modern, accessible experiences for public outreach.',
77
order: 1,
88
links: [
9-
{ label: 'Live demo', url: 'https://example.com/demo', kind: 'demo', group: 'Forms Platform' },
10-
{ label: 'Repository', url: 'https://github.com/flexion/forms', kind: 'repo', group: 'Forms Platform' },
11-
{ label: 'Live demo', url: 'https://example.com/lab', kind: 'demo', group: 'Forms Lab (experiment)' },
12-
{ label: 'Repository', url: 'https://github.com/flexion/forms-lab', kind: 'repo', group: 'Forms Lab (experiment)' },
9+
{ label: 'Forms Platform', url: 'https://example.com/platform', kind: 'demo' },
10+
{ label: 'flexion/forms', url: 'https://github.com/flexion/forms', kind: 'repo' },
11+
{ label: 'Forms Lab (experiment)', url: 'https://example.com/lab', kind: 'demo' },
12+
{ label: 'flexion/forms-lab', url: 'https://github.com/flexion/forms-lab', kind: 'repo' },
1313
],
1414
}
1515

@@ -18,7 +18,7 @@ const singleLink: FeaturedLab = {
1818
tagline: 'Text messaging services to deliver critical updates to the people you serve.',
1919
order: 2,
2020
links: [
21-
{ label: 'Repository', url: 'https://github.com/flexion/flexion-notify', kind: 'repo' },
21+
{ label: 'flexion/flexion-notify', url: 'https://github.com/flexion/flexion-notify', kind: 'repo' },
2222
],
2323
}
2424

@@ -27,8 +27,8 @@ const withCaseStudy: FeaturedLab = {
2727
tagline: 'Accurately extract data from PDFs and images for faster application processing.',
2828
order: 3,
2929
links: [
30-
{ label: 'Repository', url: 'https://github.com/flexion/document-extractor', kind: 'repo' },
31-
{ label: 'Case study', url: 'https://flexion.us/case-study/document-extraction-for-faster-processing/', kind: 'case-study' },
30+
{ label: 'flexion/document-extractor', url: 'https://github.com/flexion/document-extractor', kind: 'repo' },
31+
{ label: 'Flexion case study', url: 'https://flexion.us/case-study/document-extraction-for-faster-processing/', kind: 'case-study' },
3232
],
3333
}
3434

@@ -37,10 +37,9 @@ export function LabCardExamples() {
3737
<section id="lab-card">
3838
<h2>Lab card</h2>
3939
<p>
40-
Featured-lab card on the home page. Cards read as a horizontal band on wide containers
41-
and stack vertically on narrow ones. Links are grouped by sub-project when a card has
42-
more than one, and each link is prefixed with an icon that signals its type (demo,
43-
repository, or case study).
40+
Featured-lab card on the home page. Title and tagline sit on top; each link becomes
41+
its own column below, titled with the link kind (Demo, Repository, or Case study).
42+
Columns flow from 1 → 2 → 4 based on card width.
4443
</p>
4544
<div class="l-stack" data-space="md">
4645
<LabCard lab={multiProject} />

src/design/components/lab-card/index.tsx

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,37 @@
1-
import type { FeaturedLab, FeaturedLink, FeaturedLinkKind } from '../../../build/featured'
1+
import type { FeaturedLab, FeaturedLinkKind } from '../../../build/featured'
2+
3+
const KIND_HEADING: Record<FeaturedLinkKind, string> = {
4+
demo: 'Demo',
5+
repo: 'Repository',
6+
'case-study': 'Case study',
7+
}
28

39
export function LabCard({ lab }: { lab: FeaturedLab }) {
4-
const groups = groupLinks(lab.links)
510
return (
611
<article class="lab-card">
712
<div class="lab-card__intro">
813
<h3 class="lab-card__title">{lab.title}</h3>
914
<p class="lab-card__tagline">{lab.tagline}</p>
1015
</div>
11-
<div class="lab-card__links">
12-
{groups.map((group) => (
13-
<div class="lab-card__group">
14-
{group.name ? (
15-
<p class="lab-card__group-name">{group.name}</p>
16-
) : null}
17-
<ul class="lab-card__link-list">
18-
{group.links.map((link) => (
19-
<li class="lab-card__link">
20-
<a
21-
class="lab-card__link-anchor"
22-
href={link.url}
23-
rel="noopener external"
24-
>
25-
<LinkIcon kind={link.kind} />
26-
<span>{link.label}</span>
27-
</a>
28-
</li>
29-
))}
30-
</ul>
31-
</div>
16+
<ul class="lab-card__columns">
17+
{lab.links.map((link) => (
18+
<li class="lab-card__column">
19+
<p class="lab-card__column-heading">{KIND_HEADING[link.kind]}</p>
20+
<a
21+
class="lab-card__column-link"
22+
href={link.url}
23+
rel="noopener external"
24+
>
25+
<LinkIcon kind={link.kind} />
26+
<span>{link.label}</span>
27+
</a>
28+
</li>
3229
))}
33-
</div>
30+
</ul>
3431
</article>
3532
)
3633
}
3734

38-
type LinkGroup = { name: string | null; links: FeaturedLink[] }
39-
40-
function groupLinks(links: FeaturedLink[]): LinkGroup[] {
41-
const groups: LinkGroup[] = []
42-
const byName = new Map<string | null, LinkGroup>()
43-
for (const link of links) {
44-
const name = link.group ?? null
45-
let group = byName.get(name)
46-
if (!group) {
47-
group = { name, links: [] }
48-
byName.set(name, group)
49-
groups.push(group)
50-
}
51-
group.links.push(link)
52-
}
53-
return groups
54-
}
55-
5635
function LinkIcon({ kind }: { kind: FeaturedLinkKind }) {
5736
switch (kind) {
5837
case 'demo':

src/design/components/lab-card/styles.css

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
border-radius: var(--radius-sm);
66
background: var(--color-surface);
77
box-shadow: var(--shadow-card);
8-
display: grid;
8+
display: flex;
9+
flex-direction: column;
910
gap: var(--space-5);
1011
container-type: inline-size;
1112
}
@@ -14,7 +15,6 @@
1415
display: flex;
1516
flex-direction: column;
1617
gap: var(--space-2);
17-
min-inline-size: 0;
1818
}
1919

2020
.lab-card__title {
@@ -31,20 +31,25 @@
3131
margin: 0;
3232
}
3333

34-
.lab-card__links {
35-
display: flex;
36-
flex-direction: column;
37-
gap: var(--space-4);
38-
min-inline-size: 0;
34+
.lab-card__columns {
35+
list-style: none;
36+
padding: 0;
37+
margin: 0;
38+
padding-block-start: var(--space-4);
39+
border-block-start: 1px solid var(--color-surface-alt);
40+
display: grid;
41+
grid-template-columns: 1fr;
42+
gap: var(--space-5);
3943
}
4044

41-
.lab-card__group {
45+
.lab-card__column {
4246
display: flex;
4347
flex-direction: column;
4448
gap: var(--space-2);
49+
min-inline-size: 0;
4550
}
4651

47-
.lab-card__group-name {
52+
.lab-card__column-heading {
4853
font-size: var(--step--1);
4954
font-weight: 600;
5055
text-transform: uppercase;
@@ -53,28 +58,19 @@
5358
margin: 0;
5459
}
5560

56-
.lab-card__link-list {
57-
list-style: none;
58-
padding: 0;
59-
margin: 0;
60-
display: flex;
61-
flex-direction: column;
62-
gap: var(--space-2);
63-
}
64-
65-
.lab-card__link-anchor {
61+
.lab-card__column-link {
6662
display: inline-flex;
6763
align-items: center;
6864
gap: var(--space-2);
6965
font-size: var(--step-0);
7066
font-weight: 500;
7167
color: var(--color-link);
7268
text-decoration: none;
73-
padding-block: var(--space-1);
69+
word-break: break-word;
7470
}
7571

76-
.lab-card__link-anchor:hover,
77-
.lab-card__link-anchor:focus-visible {
72+
.lab-card__column-link:hover,
73+
.lab-card__column-link:focus-visible {
7874
color: var(--color-link-hover);
7975
text-decoration: underline;
8076
text-underline-offset: 3px;
@@ -85,24 +81,22 @@
8581
color: var(--color-ink-subtle);
8682
}
8783

88-
.lab-card__link-anchor:hover .lab-card__icon,
89-
.lab-card__link-anchor:focus-visible .lab-card__icon {
84+
.lab-card__column-link:hover .lab-card__icon,
85+
.lab-card__column-link:focus-visible .lab-card__icon {
9086
color: var(--color-link-hover);
9187
}
9288

93-
/* Wide enough for a horizontal band — intro on the left, links on the right. */
94-
@container (min-width: 40rem) {
95-
.lab-card {
96-
grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);
97-
gap: var(--space-6);
98-
align-items: start;
89+
/* Flow to 2 columns once the card is wide enough to host them cleanly. */
90+
@container (min-width: 32rem) {
91+
.lab-card__columns {
92+
grid-template-columns: repeat(2, minmax(0, 1fr));
9993
}
100-
.lab-card__group {
101-
align-items: flex-end;
102-
text-align: end;
103-
}
104-
.lab-card__link-list {
105-
align-items: flex-end;
94+
}
95+
96+
/* And to a row of 4 on wide cards, so Forms Lab's four links sit side-by-side. */
97+
@container (min-width: 56rem) {
98+
.lab-card__columns {
99+
grid-template-columns: repeat(4, minmax(0, 1fr));
106100
}
107101
}
108102
}

tests/build/featured.test.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,28 @@ describe('loadFeatured', () => {
1313
])
1414
})
1515

16-
test('Forms Lab carries sub-project groups and typed links', async () => {
16+
test('Forms Lab has four typed links in document order', async () => {
1717
const labs = await loadFeatured(ROOT)
1818
const forms = labs.find((l) => l.title === 'Forms Lab')!
1919
expect(forms.tagline).toBe(
2020
'Digitize forms to create modern, accessible experiences for public outreach.',
2121
)
2222
expect(forms.order).toBe(1)
2323
expect(forms.links).toHaveLength(4)
24+
expect(forms.links.map((l) => l.kind)).toEqual(['demo', 'repo', 'demo', 'repo'])
2425
expect(forms.links[0]).toEqual({
25-
label: 'Live demo',
26+
label: 'Forms Platform',
2627
url: 'https://pp4cc7kwbf.us-east-1.awsapprunner.com/',
2728
kind: 'demo',
28-
group: 'Forms Platform',
2929
})
30-
// All four links belong to one of two groups
31-
const groups = new Set(forms.links.map((l) => l.group))
32-
expect(groups).toEqual(new Set(['Forms Platform', 'Forms Lab (experiment)']))
3330
})
3431

35-
test('Messaging Lab has a single typed link, no group', async () => {
32+
test('Messaging Lab has a single repo link', async () => {
3633
const labs = await loadFeatured(ROOT)
3734
const messaging = labs.find((l) => l.title === 'Messaging Lab')!
3835
expect(messaging.links).toEqual([
3936
{
40-
label: 'Repository',
37+
label: 'flexion/flexion-notify',
4138
url: 'https://github.com/flexion/flexion-notify',
4239
kind: 'repo',
4340
},

0 commit comments

Comments
 (0)