From ddd27083112018bf1119aa9212f44d0820c39208 Mon Sep 17 00:00:00 2001 From: kualta Date: Tue, 8 Sep 2026 08:45:55 -0500 Subject: [PATCH 1/2] Separate heading semantics and expose exact timestamps --- AGENTS.md | 1 - app/App.tsx | 8 ++++++ package.json | 2 +- src/components/ui/time.tsx | 35 ++++++++++++++++++++------ src/components/ui/typography.tsx | 17 +++++++++++-- tests/semantics.spec.ts | 42 ++++++++++++++++++++++++++++++++ 6 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 tests/semantics.spec.ts diff --git a/AGENTS.md b/AGENTS.md index 9801553..9b5da8d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,7 +27,6 @@ src/ bun run typecheck # tsc --noEmit bun run lint # biome check bun run check # biome check --write (auto-fix lint + formatting) -bun publish --access public # publish to npm ``` ### Before pushing: CI must pass diff --git a/app/App.tsx b/app/App.tsx index 7a2ec78..dd0b1c2 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -3279,6 +3279,14 @@ export function App() { {/* Typography */}
+
+ + Large section heading + +

+ An h2 with large title styling. +

+
{/* Fonts */}
diff --git a/package.json b/package.json index 03e48cd..ef13f67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@flow-industries/ui", - "version": "0.21.2", + "version": "0.22.0", "license": "MIT", "repository": { "type": "git", diff --git a/src/components/ui/time.tsx b/src/components/ui/time.tsx index b9b6de9..8ae6c97 100644 --- a/src/components/ui/time.tsx +++ b/src/components/ui/time.tsx @@ -24,7 +24,7 @@ function getTimeAgo(date: Date): string { return `${diffInYears}y`; } -interface TimeElapsedProps extends React.ComponentProps<"span"> { +interface TimeElapsedProps extends React.ComponentProps<"time"> { date: Date | string | number; longFormat?: string; } @@ -59,15 +59,24 @@ function TimeElapsed({ const diffInWeeks = (Date.now() - dateObj.getTime()) / (1000 * 60 * 60 * 24 * 7); const display = diffInWeeks > 3 ? format(dateObj, longFormat) : label; + const absoluteLabel = format(dateObj, "PPPPpppp"); return ( - - {display} - + ); } -interface TimeSinceProps extends React.ComponentProps<"span"> { +interface TimeSinceProps extends React.ComponentProps<"time"> { date: Date | string | number; format?: string; } @@ -79,10 +88,20 @@ function TimeSince({ ref, ...props }: TimeSinceProps) { + const dateObj = toDate(date); + const absoluteLabel = format(dateObj, "PPPPpppp"); return ( - - {format(toDate(date), fmt)} - + ); } diff --git a/src/components/ui/typography.tsx b/src/components/ui/typography.tsx index 0798866..5884dc9 100644 --- a/src/components/ui/typography.tsx +++ b/src/components/ui/typography.tsx @@ -9,12 +9,25 @@ const titleStyles = { const titleTags = { sm: "h3", default: "h2", md: "h2", lg: "h1" } as const; +const headingTags = { + 1: "h1", + 2: "h2", + 3: "h3", + 4: "h4", + 5: "h5", + 6: "h6", +} as const; + function Title({ className, size = "default", + level, ...props -}: React.ComponentProps<"h1"> & { size?: "sm" | "default" | "md" | "lg" }) { - const Tag = titleTags[size]; +}: React.ComponentProps<"h1"> & { + size?: "sm" | "default" | "md" | "lg"; + level?: 1 | 2 | 3 | 4 | 5 | 6; +}) { + const Tag = level ? headingTags[level] : titleTags[size]; return ( { + await page.goto("/#design"); + const heading = page.getByRole("heading", { + name: "Large section heading", + exact: true, + }); + await expect(heading).toHaveJSProperty("tagName", "H2"); + const defaultHeading = page.getByRole("heading", { + name: "@flow-industries/ui", + exact: true, + }); + await expect(defaultHeading).toHaveJSProperty("tagName", "H1"); + const fontSize = await defaultHeading.evaluate( + (element) => getComputedStyle(element).fontSize, + ); + await expect(heading).toHaveCSS("font-size", fontSize); +}); + +test("relative and calendar times expose exact timestamps", async ({ + page, +}) => { + await page.goto("/#components"); + for (const slot of ["time-elapsed", "time-since"]) { + const times = page.locator(`[data-slot="${slot}"]`); + await expect(times.first()).toBeVisible(); + for (const time of await times.all()) { + await expect(time).toHaveJSProperty("tagName", "TIME"); + const iso = await time.getAttribute("datetime"); + expect(iso).toBeTruthy(); + expect(Number.isNaN(Date.parse(iso ?? ""))).toBe(false); + await expect(time.locator(".sr-only")).toHaveText(/\d{4}/); + await expect(time).toHaveAttribute( + "title", + (await time.locator(".sr-only").textContent()) ?? "", + ); + } + } +}); From f22189b4d316fc76e1da4b14bfb5ea1269f55055 Mon Sep 17 00:00:00 2001 From: kualta Date: Tue, 8 Sep 2026 08:56:16 -0500 Subject: [PATCH 2/2] Reuse heading showcase and run semantic checks in CI --- .github/workflows/a11y.yml | 2 +- app/App.tsx | 12 +++--------- tests/semantics.spec.ts | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/.github/workflows/a11y.yml b/.github/workflows/a11y.yml index 667a3f9..a97951e 100644 --- a/.github/workflows/a11y.yml +++ b/.github/workflows/a11y.yml @@ -21,7 +21,7 @@ jobs: - run: npm install -g bun - run: bun install --frozen-lockfile - run: bun run build - - run: bunx playwright test tests/a11y.spec.ts tests/error-boundary.spec.ts + - run: bunx playwright test tests/a11y.spec.ts tests/error-boundary.spec.ts tests/semantics.spec.ts - if: failure() uses: actions/upload-artifact@v4 with: diff --git a/app/App.tsx b/app/App.tsx index dd0b1c2..dc5e18e 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -3279,14 +3279,6 @@ export function App() { {/* Typography */}
-
- - Large section heading - -

- An h2 with large title styling. -

-
{/* Fonts */}
@@ -3484,7 +3476,9 @@ export function App() { Title lg - Welcome to Flow + + Welcome to Flow +
diff --git a/tests/semantics.spec.ts b/tests/semantics.spec.ts index e2023ee..6b08200 100644 --- a/tests/semantics.spec.ts +++ b/tests/semantics.spec.ts @@ -5,7 +5,7 @@ test("heading level overrides size without changing its style", async ({ }) => { await page.goto("/#design"); const heading = page.getByRole("heading", { - name: "Large section heading", + name: "Welcome to Flow", exact: true, }); await expect(heading).toHaveJSProperty("tagName", "H2");