feat: enhance typography and accessibility in design system

- Added new UI heading utility with specific styles in typography.css.
- Updated typography tests to include new UI heading role and ensure proper size and styling.
- Documented design system audit findings, focusing on accessibility improvements and component consistency.
- Introduced CalendarExamples component to demonstrate calendar functionality with habit tracking.
- Created a shared Field component for consistent labeling and hinting of form controls.
- Implemented comprehensive accessibility tests for calendar and editor components, ensuring proper focus management and keyboard navigation.
- Added structure tests for Field and Card components to verify correct ID generation and heading levels.
This commit is contained in:
syntaxbullet
2026-09-04 13:44:31 +02:00
parent bfa54851c2
commit dde5e77317
19 changed files with 1077 additions and 681 deletions

View File

@@ -0,0 +1,52 @@
import { expect, test } from "bun:test";
import { Window } from "happy-dom";
import { renderToStaticMarkup } from "react-dom/server";
import type { ReactNode } from "react";
import { Card } from "./Card";
import { ContainerShowcase } from "./ContainerShowcase";
import { Field } from "./Field";
function render(content: ReactNode) {
const window = new Window();
window.document.body.innerHTML = renderToStaticMarkup(content);
return window.document.body;
}
test("Field connects its label and hint to the rendered control", () => {
const body = render(
<Field id="habit" label="Habit name" hint="Keep it short.">
{(id, describedBy) => <input id={id} aria-describedby={describedBy} />}
</Field>,
);
expect(body.querySelector("label")?.htmlFor).toBe("habit");
expect(body.querySelector("input")?.getAttribute("aria-describedby")).toBe("habit-hint");
expect(body.querySelector("#habit-hint")?.textContent).toBe("Keep it short.");
});
test("Field generates unique control IDs and omits absent hint references", () => {
const body = render(<>
<Field label="First">{(id, describedBy) => <input id={id} aria-describedby={describedBy} />}</Field>
<Field label="Second">{(id, describedBy) => <input id={id} aria-describedby={describedBy} />}</Field>
</>);
const controls = [...body.querySelectorAll("input")];
expect(new Set(controls.map((control) => control.id)).size).toBe(2);
expect(controls.every((control) => !control.hasAttribute("aria-describedby"))).toBe(true);
expect(body.querySelectorAll("small")).toHaveLength(0);
});
test("Card preserves its title styling and accessible name at each heading level", () => {
for (const headingLevel of [2, 3, 4, 5, 6] as const) {
const body = render(<Card heading="A clear boundary" headingLevel={headingLevel}>Content</Card>);
const heading = body.querySelector(`h${headingLevel}.ds-card-title`);
expect(heading?.textContent).toBe("A clear boundary");
expect(body.querySelector("article")?.getAttribute("aria-labelledby")).toBe(heading?.id);
}
expect(render(<Card heading="Default" />).querySelector("h3")?.textContent).toBe("Default");
});
test("Layout uses subsection headings above the card titles", () => {
const body = render(<ContainerShowcase />);
expect(body.querySelectorAll("h2")).toHaveLength(1);
expect(body.querySelectorAll("h3")).toHaveLength(2);
expect(body.querySelectorAll("h4.ds-card-title")).toHaveLength(6);
});