diff --git a/.changeset/filtergroup-label-contrast.md b/.changeset/filtergroup-label-contrast.md new file mode 100644 index 00000000..2610b2dc --- /dev/null +++ b/.changeset/filtergroup-label-contrast.md @@ -0,0 +1,5 @@ +--- +"@codeatlasdev/ui": patch +--- + +`FilterGroup` draws its label with `--ca-text-secondary` instead of `--ca-text-hint`. The hint role can be set low by a custom preset, and the label failed the 4.5:1 axe check on a raised surface in dark mode. diff --git a/.changeset/kpicard-on-card.md b/.changeset/kpicard-on-card.md new file mode 100644 index 00000000..40665fdb --- /dev/null +++ b/.changeset/kpicard-on-card.md @@ -0,0 +1,5 @@ +--- +"@codeatlasdev/ui": patch +--- + +`KpiCard` is built on `Card` instead of the deprecated `GlassCard`, so rendering it no longer logs the `[ca-ui]` deprecation warning in dev. The look is unchanged (elevation 3). diff --git a/.gitignore b/.gitignore index 59b8e9e8..400f9c42 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ site/src/generated/ # wrangler local state .wrangler/ + +# docs sample type-check output +/site/.cache/ diff --git a/biome.json b/biome.json index 1220d8e2..e0b222bd 100644 --- a/biome.json +++ b/biome.json @@ -37,6 +37,7 @@ "!**/.turbo", "!**/.svelte-kit", "!**/.astro", + "!**/.cache", "!**/__screenshots__", "!**/.vitest", "!**/test-results", diff --git a/packages/ui/scripts/check-docs-samples.ts b/packages/ui/scripts/check-docs-samples.ts new file mode 100644 index 00000000..6b27bd70 --- /dev/null +++ b/packages/ui/scripts/check-docs-samples.ts @@ -0,0 +1,184 @@ +/** + * Type-checks the `svelte` code fences of the docs site with svelte-check, against the built package + * types (`dist`), exactly as a consumer app would compile them. + * + * A fence is checked when it imports from `@codeatlasdev/ui*`: those are the ones that claim API + * usage. Docs samples are often fragments, so the extractor makes them compilable without hiding + * real mistakes: + * - `…` and `/* … *\/` placeholders are removed; + * - names the sample uses but never declares (`users`, `save`, `page`, a `bind:` target) become + * ambient `any` declarations: a docs sample may assume app state; + * - a bare markup fence (no `\n${code}`; + return decl ? code.replace(tag[0], `${tag[0]}\n${decl}`) : code; +} + +export function extractSamples(): Sample[] { + rmSync(OUT, { recursive: true, force: true }); + mkdirSync(OUT, { recursive: true }); + const samples: Sample[] = []; + for (const mdx of walk(DOCS)) { + const src = readFileSync(mdx, "utf8"); + const page = relative(DOCS, mdx); + let index = 0; + for (const m of src.matchAll(/^([ \t]*)```svelte[^\n]*\n([\s\S]*?)^[ \t]*```/gm)) { + const indent = m[1].length; + const body = m[2] + .split("\n") + .map((l) => l.slice(Math.min(indent, l.length - l.trimStart().length))) + .join("\n"); + const code = normalize(body); + if (code === null) continue; + const line = src.slice(0, m.index).split("\n").length; + const file = `${page.replace(/\.mdx$/, "").replace(/[/.]/g, "_")}_${index}.svelte`; + writeFileSync(join(OUT, file), code); + samples.push({ page, index: index++, line, file }); + } + } + writeFileSync( + join(OUT, "tsconfig.json"), + JSON.stringify({ extends: "../../tsconfig.json", include: ["./*.svelte"] }) + ); + return samples; +} + +export interface SampleError { + page: string; + line: number; + message: string; +} + +/** Runs svelte-check over the extracted samples; returns one entry per error, mapped back to its docs page. */ +export async function checkDocsSamples( + extra: Record = {} +): Promise<{ checked: number; errors: SampleError[] }> { + const samples = extractSamples(); + // Extra fences (tests feed known-bad ones to prove the check can fail), reported under their own name. + for (const [name, body] of Object.entries(extra)) { + const code = normalize(body); + if (code === null) continue; + const file = `extra_${name}.svelte`; + writeFileSync(join(OUT, file), code); + samples.push({ page: name, index: 0, line: 0, file }); + } + const proc = Bun.spawn( + [ + SVELTE_CHECK, + "--tsconfig", + join(OUT, "tsconfig.json"), + "--workspace", + OUT, + "--output", + "machine", + ], + { cwd: join(REPO, "site"), stdout: "pipe", stderr: "pipe" } + ); + const [out, err] = await Promise.all([ + new Response(proc.stdout).text(), + new Response(proc.stderr).text(), + ]); + await proc.exited; + if (!/COMPLETED/.test(out)) throw new Error(`svelte-check did not complete:\n${out}${err}`); + const byFile = new Map(samples.map((s) => [s.file, s])); + const errors: SampleError[] = []; + for (const m of out.matchAll(/^\d+ ERROR "([^"]+)" (\d+):\d+ "((?:[^"\\]|\\.)*)"/gm)) { + const sample = byFile.get(m[1]); + errors.push({ + page: sample?.page ?? m[1], + line: (sample?.line ?? 0) + Number(m[2]), + message: (JSON.parse(`"${m[3]}"`) as string) + .replace(/\n/g, " ") + .split("https://svelte.dev")[0] + .trim(), + }); + } + return { checked: samples.length, errors }; +} + +if (import.meta.main) { + const { checked, errors } = await checkDocsSamples(); + for (const e of errors) console.log(`${e.page}:${e.line} ${e.message}`); + console.log(`${checked} samples checked, ${errors.length} errors`); + process.exit(errors.length ? 1 : 0); +} diff --git a/packages/ui/src/lib/components/FilterGroup/FilterGroup.svelte b/packages/ui/src/lib/components/FilterGroup/FilterGroup.svelte index 82875ab9..dd643560 100644 --- a/packages/ui/src/lib/components/FilterGroup/FilterGroup.svelte +++ b/packages/ui/src/lib/components/FilterGroup/FilterGroup.svelte @@ -74,7 +74,7 @@ .ca-filter-group__label { font-size: var(--ca-font-size-micro); font-weight: var(--ca-font-weight-semibold); - color: var(--ca-text-hint); + color: var(--ca-text-secondary); text-transform: uppercase; letter-spacing: var(--ca-overline-tracking); padding: 0 var(--ca-space-1-5) 0 var(--ca-space-1); diff --git a/packages/ui/src/lib/components/KpiCard/KpiCard.svelte b/packages/ui/src/lib/components/KpiCard/KpiCard.svelte index 72a793c6..370b38cb 100644 --- a/packages/ui/src/lib/components/KpiCard/KpiCard.svelte +++ b/packages/ui/src/lib/components/KpiCard/KpiCard.svelte @@ -1,7 +1,7 @@ - +

{title}

@@ -50,7 +50,7 @@ {/if}
- + diff --git a/site/src/demos/inputs/filter-group/Basic.svelte b/site/src/demos/inputs/filter-group/Basic.svelte new file mode 100644 index 00000000..600a42b4 --- /dev/null +++ b/site/src/demos/inputs/filter-group/Basic.svelte @@ -0,0 +1,13 @@ + + + +

Selected: {role}

diff --git a/site/src/demos/inputs/radio/Cards.svelte b/site/src/demos/inputs/radio/Cards.svelte new file mode 100644 index 00000000..015565a9 --- /dev/null +++ b/site/src/demos/inputs/radio/Cards.svelte @@ -0,0 +1,13 @@ + + + diff --git a/site/src/demos/inputs/setting-item/Basic.svelte b/site/src/demos/inputs/setting-item/Basic.svelte new file mode 100644 index 00000000..ceccbc9d --- /dev/null +++ b/site/src/demos/inputs/setting-item/Basic.svelte @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/site/src/demos/inputs/swatch-group/Custom.svelte b/site/src/demos/inputs/swatch-group/Custom.svelte new file mode 100644 index 00000000..ba00d4c5 --- /dev/null +++ b/site/src/demos/inputs/swatch-group/Custom.svelte @@ -0,0 +1,34 @@ + + +
+ +

value: {color}

+
+ + diff --git a/site/src/demos/inputs/swatch-group/Presets.svelte b/site/src/demos/inputs/swatch-group/Presets.svelte new file mode 100644 index 00000000..be77e28e --- /dev/null +++ b/site/src/demos/inputs/swatch-group/Presets.svelte @@ -0,0 +1,34 @@ + + +
+ +

value: {preset}

+
+ + diff --git a/site/src/demos/layout/error-page/Statuses.svelte b/site/src/demos/layout/error-page/Statuses.svelte new file mode 100644 index 00000000..e3aaebbc --- /dev/null +++ b/site/src/demos/layout/error-page/Statuses.svelte @@ -0,0 +1,5 @@ + + + diff --git a/site/src/demos/surfaces/accordion/Single.svelte b/site/src/demos/surfaces/accordion/Single.svelte index 54d12c0c..57a6a68a 100644 --- a/site/src/demos/surfaces/accordion/Single.svelte +++ b/site/src/demos/surfaces/accordion/Single.svelte @@ -5,14 +5,14 @@
- Liquid Glass is a design language that applies real-time backdrop blur, refraction and - tinting to UI surfaces, creating depth without heavy visual weight. + Liquid Glass is the opt-in look of the kit: backdrop blur, refraction and tinting on + surfaces. The default theme is solid. All modern evergreen browsers. Older environments receive a fallback without the blur effect. - - Yes. Both components use isolation: isolate to keep stacking contexts correct. + + Yes. A Card is a Surface, so any Surface can sit inside it.
diff --git a/site/src/demos/surfaces/card/Accents.svelte b/site/src/demos/surfaces/card/Accents.svelte index 62f6a929..8074bd10 100644 --- a/site/src/demos/surfaces/card/Accents.svelte +++ b/site/src/demos/surfaces/card/Accents.svelte @@ -5,11 +5,11 @@

Tinted

-

Brand gradient overlay on the surface.

+

Brand tint over the fill.

Glowing

-

Backlit halo behind the surface.

+

Brand halo behind the surface.

diff --git a/site/src/demos/surfaces/liquid-glass/Basic.svelte b/site/src/demos/surfaces/liquid-glass/Basic.svelte new file mode 100644 index 00000000..2c0ccabb --- /dev/null +++ b/site/src/demos/surfaces/liquid-glass/Basic.svelte @@ -0,0 +1,17 @@ + + +
+ +

Refraction layer behind this text.

+
+ +