From 7c37365df0c1531542dfe6b31995d816d0af11a2 Mon Sep 17 00:00:00 2001 From: Gab Date: Thu, 24 Sep 2026 22:04:30 -0300 Subject: [PATCH 1/4] feat(webkit): add the content-columns marketing component --- .specs/content-columns.md | 153 ++++++++++++++++ .../content-columns/ContentColumns.stories.js | 167 ++++++++++++++++++ packages/webkit/.size-limit.json | 5 + packages/webkit/catalog.json | 71 ++++++++ packages/webkit/package.json | 1 + .../content-columns/content-columns.test.ts | 142 +++++++++++++++ .../content-columns/content-columns.vue | 89 ++++++++++ 7 files changed, 628 insertions(+) create mode 100644 .specs/content-columns.md create mode 100644 apps/storybook/src/stories/components/marketing/content-columns/ContentColumns.stories.js create mode 100644 packages/webkit/src/components/marketing/content-columns/content-columns.test.ts create mode 100644 packages/webkit/src/components/marketing/content-columns/content-columns.vue diff --git a/.specs/content-columns.md b/.specs/content-columns.md new file mode 100644 index 000000000..2787f4e1d --- /dev/null +++ b/.specs/content-columns.md @@ -0,0 +1,153 @@ +--- +name: content-columns +category: marketing +structure: monolithic +status: implemented +spec_version: 2 +checksum: 4164a7ac1a16b8ed89832e54017508c7a06e2153282c7fd2bc959adbb17d0640 +created: 2026-09-22 +last_updated: 2026-09-23 +--- + +# Content Columns — Component Spec + +## Purpose + +A titled band whose body is two or three columns of short copy, drawn in one hairline `card-grid` so the columns read as a single surface. It is the plainest way to state a few parallel points under one section header. + +## When to use + +- For two or three parallel points that carry equal weight under one section heading. +- When each point is a short title and a sentence — no icon, no media, no link. +- As the body of a product or platform section that needs structure but not tiles. + +## When NOT to use + +- When the points are unequal and one deserves the full width → use `bento-grid`. +- When each point needs an icon, a link or its own surface → use `feature-card` in a grid the page owns. +- For measured figures → use `big-numbers`. +- For one point paired with a picture → use `media-split`. + +## Related + +- `bento-grid` — the same collapsed-rule grid with unequal, slot-composed cells. +- `feature-card` — the richer unit when a point needs an icon or a destination. +- `section-title` — the header this band renders above its columns. +- `big-numbers` — the numeric sibling of this grid. + +## Best practices + +- The band draws no frame of its own: the rule and the corner marks belong to the `section-module` or `frame-box` that the page assembles it into, so a band placed in a framed column never lands a second hairline on the column's own. + +- Keep every column to a title and one or two sentences; the grid stretches to the tallest, so one long column pads the rest. +- Use three columns for three peers and two for a contrast; four points want `bento-grid` or a card grid instead. +- Write the column titles in parallel grammar — they are read across, not down. +- Leave `title` out when the band sits directly under another header; two headings in a row is one too many. + +## Usage + +```vue + + + +``` + +## Props + +| Prop | Type | Default | Required | JSDoc | +|---|---|---|---|---| +| `items` | `ContentColumnItem[]` | `[]` | false | The columns, in reading order; each item is `{ title, description }`. | +| `title` | `string` | `''` | false | Headline of the band, rendered as its `h2` above the columns. | +| `description` | `string` | `''` | false | Supporting sentence under the headline. | +| `eyebrow` | `string` | `''` | false | Short uppercase overline rendered above the headline. | +| `columns` | `2 \| 3` | `3` | false | How many columns the grid holds from the medium breakpoint up; below it the columns stack. | + +## Events + +| _none_ | — | — | + +## Slots + +| _none_ | — | — | + +## States + +- Visual states: `default` +- `data-columns` mirrors the `columns` prop and drives the grid from the medium breakpoint up +- Below `md` the grid is a single column in DOM order, so the points are read in the order they are written +- Empty: when `items` is empty the band renders no grid and no rules, so an unfilled band leaves no empty frame + +## Motion & Animations + +_none_ + +## Tokens + +| Region | Token (DESIGN.md) | +|---|---| +| typography (column title) | `.text-heading-sm` | +| typography (column body) | `.text-body-sm` | +| column title text | `var(--text-default)` | +| column body text | `var(--text-muted)` | +| grid rules and marks | `var(--border-default)` | +| column surface | `var(--bg-surface)` | +| spacing (column padding) | `var(--spacing-xl)` | +| spacing (column stack) | `var(--spacing-sm)` | + +## Theme gaps + +| Figma variable | Temporary primitive | Follow-up | +|---|---|---| +| _none_ | — | — | + +## Accessibility (WCAG 2.1 AA) + +- Visible focus: not applicable — the band is static copy and holds no interactive control. +- Keyboard map: none of its own; the band contains no focusable element and is skipped by `Tab`. +- ARIA: the band is a `
` labelled by its `h2` when `title` is set; each column title is an `h3`, so the columns nest correctly under the band's heading and the document outline stays honest. +- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons): column titles are `var(--text-default)`; bodies use `var(--text-muted)`, which clears AA on the dark canvas and is a known theme-level shortfall on the light one (see `big-numbers`). +- `motion-reduce:transition-none motion-reduce:transform-none` — not applicable, the band is static. +- Touch target ≥40×40 px — not applicable, no interactive target. + +## Stories (Storybook) + +- Default +- Columns — composite story rendering both `columns` values one under the other + +## Constraints — DO NOT + + + +- Do not add props beyond the Props table above. If you need a prop that is not listed, emit `BLOCKED: missing prop ` and stop — do not invent. +- Do not add events beyond the Events table above. Same rule for slots and sub-components. +- Do not invent imports. Every `@aziontech/webkit/*` path must exist in `packages/webkit/package.json#exports`. Every relative import must resolve to a real file. Every npm package must be installed. +- Do not use HEX/RGB/HSL colors, Tailwind palette names (e.g. `bg-blue-500`), raw typography classes (e.g. `text-sm`), `any`, `@ts-ignore`, or `class` inside `defineProps`. +- Do not install or import positioning/animation libraries (`@floating-ui/*`, `popper.js`, `tippy.js`, `gsap`, `framer-motion`, `motion`, `@vueuse/motion`, `@formkit/auto-animate`, drag-drop runtimes, scroll virtualization libs). Use CSS + Vue primitives (``, ``). See `.claude/rules/dependencies.md`. +- Do not improvise animations. Every `animate-*` / `transition-*` class must come from `packages/theme/src/tokens/semantic/animations.js`; every motion-bearing class pairs with `motion-reduce:*` on the same class string; no component-local `@keyframes`. +- Do not create class presets in JavaScript (`const kindClasses = {...}`, `const sharedClasses = [...]`, `const sizeClasses = {...}`, `const rootClasses = computed(...)`). Variants live on `data-*` attributes consumed by Tailwind `data-[attr=value]:`. All utilities live inline on the root element's `class` attribute. No `