📦 deps(thirdparty): update snapshots

This commit is contained in:
ci[bot]
2026-08-14 17:16:04 +08:00
parent ad5eeaaf5b
commit 14e7209e78
283 changed files with 201717 additions and 13469 deletions
+2
View File
@@ -1,3 +1,5 @@
node_modules/
.next/
out/
.test-dist/
*.tsbuildinfo
+28 -8
View File
@@ -1,6 +1,8 @@
"use client";
import { Filters } from "@/lib/filterStyles";
import { DEFAULT_FILTERS, type Filters } from "../lib/filterStyles";
const STATUS_OPTIONS = ["active", "supplemental", "deprecated"];
interface FilterBarProps {
filters: Filters;
@@ -24,12 +26,18 @@ function FilterGroup({
onChange: (v: string) => void;
}) {
return (
<div className="flex flex-wrap items-center gap-1.5">
<span className="text-xs font-medium text-gray-500 dark:text-gray-400 mr-1">
<div
aria-label={`${label} filter`}
className="flex flex-wrap items-center gap-1.5"
role="group"
>
<span aria-hidden="true" className="text-xs font-medium text-gray-500 dark:text-gray-400 mr-1">
{label}:
</span>
<button
aria-pressed={value === ""}
onClick={() => onChange("")}
type="button"
className={`px-2.5 py-1 text-xs rounded-full transition-colors ${
value === ""
? "bg-blue-500 text-white"
@@ -40,8 +48,10 @@ function FilterGroup({
</button>
{options.map((opt) => (
<button
aria-pressed={value === opt}
key={opt}
onClick={() => onChange(value === opt ? "" : opt)}
type="button"
className={`px-2.5 py-1 text-xs rounded-full transition-colors ${
value === opt
? "bg-blue-500 text-white"
@@ -64,12 +74,23 @@ export function FilterBar({
resultCount,
totalCount,
}: FilterBarProps) {
const hasFilters =
filters.search || filters.type || filters.complexity || filters.era;
const hasFilters = Boolean(
filters.search ||
filters.status !== DEFAULT_FILTERS.status ||
filters.type ||
filters.complexity ||
filters.era
);
return (
<div className="space-y-3">
<div className="flex flex-wrap gap-4">
<FilterGroup
label="Status"
options={STATUS_OPTIONS}
value={filters.status}
onChange={(status) => onChange({ ...filters, status })}
/>
<FilterGroup
label="Type"
options={types}
@@ -97,9 +118,8 @@ export function FilterBar({
</p>
{hasFilters && (
<button
onClick={() =>
onChange({ search: "", type: "", complexity: "", era: "" })
}
onClick={() => onChange(DEFAULT_FILTERS)}
type="button"
className="text-xs text-blue-500 hover:text-blue-600 transition-colors"
>
Clear all filters
@@ -22,17 +22,34 @@ export function GalleryGrid({ styles }: GalleryGridProps) {
const [filters, setFilters] = useState<Filters>(DEFAULT_FILTERS);
const [selectedStyle, setSelectedStyle] = useState<StyleData | null>(null);
const types = useMemo(() => getUniqueValues(styles, "type"), [styles]);
const complexities = useMemo(
() => getUniqueValues(styles, "complexity"),
[styles]
const statusScoped = useMemo(
() =>
styles.filter(
(style) => !filters.status || style.status === filters.status
),
[styles, filters.status]
);
const types = useMemo(
() => getUniqueValues(statusScoped, "type"),
[statusScoped]
);
const complexities = useMemo(
() => getUniqueValues(statusScoped, "complexity"),
[statusScoped]
);
const eras = useMemo(
() => getUniqueValues(statusScoped, "eraOrigin"),
[statusScoped]
);
const eras = useMemo(() => getUniqueValues(styles, "eraOrigin"), [styles]);
const filtered = useMemo(
() => filterStyles(styles, filters),
[styles, filters]
);
const activeCount = useMemo(
() => filterStyles(styles, DEFAULT_FILTERS).length,
[styles]
);
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
@@ -43,7 +60,7 @@ export function GalleryGrid({ styles }: GalleryGridProps) {
Style Gallery
</h1>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
Browse {styles.length} UI styles from Antigravity Kit
Browse {activeCount} active UI styles from Antigravity Kit
</p>
</div>
<DarkModeToggle />
@@ -66,7 +83,7 @@ export function GalleryGrid({ styles }: GalleryGridProps) {
complexities={complexities}
eras={eras}
resultCount={filtered.length}
totalCount={styles.length}
totalCount={statusScoped.length}
/>
</div>
@@ -74,7 +91,11 @@ export function GalleryGrid({ styles }: GalleryGridProps) {
{filtered.length > 0 ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filtered.map((style) => (
<StyleCard key={style.no} style={style} onSelect={setSelectedStyle} />
<StyleCard
key={style.styleId}
style={style}
onSelect={setSelectedStyle}
/>
))}
</div>
) : (
@@ -1,15 +1,19 @@
import { StyleData } from "@/lib/types";
import type { StyleData } from "@/lib/types";
interface MetadataBadgesProps {
style: StyleData;
}
type BadgeVariant = "green" | "yellow" | "red" | "blue" | "gray";
function Badge({
label,
accessibleLabel = label,
variant,
}: {
label: string;
variant: "green" | "yellow" | "red" | "blue" | "gray";
accessibleLabel?: string;
variant: BadgeVariant;
}) {
const colors = {
green: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400",
@@ -21,7 +25,8 @@ function Badge({
return (
<span className={`inline-flex items-center px-2 py-0.5 text-[10px] font-medium rounded-full ${colors[variant]}`}>
{label}
<span className="sr-only">{accessibleLabel}</span>
<span aria-hidden="true">{label}</span>
</span>
);
}
@@ -32,26 +37,74 @@ function getComplexityVariant(c: string): "green" | "yellow" | "red" {
return "red";
}
function getPerfVariant(p: string): "green" | "yellow" | "red" {
if (p.includes("Excellent")) return "green";
if (p.includes("Good")) return "yellow";
return "red";
export function getTaxonomyValue(metadata: string, key: string): string {
const prefix = `${key}:`;
const entry = metadata.split("|").find((part) => part.startsWith(prefix));
return entry?.slice(prefix.length) ?? "unknown";
}
function getA11yVariant(a: string): "green" | "yellow" | "red" {
if (a.includes("AAA")) return "green";
if (a.includes("AA") || a.includes("Good")) return "yellow";
return "red";
function formatTaxonomyValue(value: string): string {
return value
.split("-")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
}
export function getLevelVariant(value: string): BadgeVariant {
if (value === "low" || value === "supported") return "green";
if (value === "moderate" || value === "conditional") return "yellow";
if (value === "high" || value === "not-recommended") return "red";
return "gray";
}
function TaxonomyBadge({
accessibleLabel,
label,
value,
}: {
accessibleLabel: string;
label: string;
value: string;
}) {
const displayValue = formatTaxonomyValue(value);
return (
<Badge
accessibleLabel={`${accessibleLabel}: ${displayValue}`}
label={`${label}: ${displayValue}`}
variant={getLevelVariant(value)}
/>
);
}
export function MetadataBadges({ style }: MetadataBadgesProps) {
const performanceCost = getTaxonomyValue(style.performance, "cost");
const accessibilityRisk = getTaxonomyValue(style.accessibility, "risk");
return (
<div className="flex flex-wrap gap-1.5">
<Badge label={style.complexity} variant={getComplexityVariant(style.complexity)} />
<Badge label={style.performance.replace(/[⚡❌⚠]/g, "").trim()} variant={getPerfVariant(style.performance)} />
<Badge label={style.accessibility.replace(/[✓⚠✗]/g, "").trim()} variant={getA11yVariant(style.accessibility)} />
{style.lightMode.includes("Full") && <Badge label="Light" variant="blue" />}
{style.darkMode.includes("Full") && <Badge label="Dark" variant="gray" />}
<div aria-label="Style metadata" className="flex flex-wrap gap-1.5" role="group">
{style.status !== "active" && (
<Badge
label={`Status: ${formatTaxonomyValue(style.status)}`}
variant={style.status === "deprecated" ? "red" : "blue"}
/>
)}
<Badge
label={`Complexity: ${style.complexity}`}
variant={getComplexityVariant(style.complexity)}
/>
<TaxonomyBadge
accessibleLabel="Performance cost"
label="Cost"
value={performanceCost}
/>
<TaxonomyBadge
accessibleLabel="Accessibility risk"
label="A11y risk"
value={accessibilityRisk}
/>
<TaxonomyBadge accessibleLabel="Light mode support" label="Light" value={style.lightMode} />
<TaxonomyBadge accessibleLabel="Dark mode support" label="Dark" value={style.darkMode} />
</div>
);
}
@@ -0,0 +1,94 @@
import assert from "node:assert/strict";
import test from "node:test";
import { renderToStaticMarkup } from "react-dom/server";
import type { StyleData } from "../lib/types";
import {
getLevelVariant,
getTaxonomyValue,
MetadataBadges,
} from "./MetadataBadges";
import { FilterBar } from "./FilterBar";
import { DEFAULT_FILTERS } from "../lib/filterStyles";
const baseStyle: StyleData = {
no: 1,
styleId: "minimalism",
styleCategory: "Minimalism",
aliases: [],
status: "active",
parentStyleId: "",
replacementDomain: "",
replacementId: "",
preferredMode: "auto",
type: "General",
keywords: "",
primaryColors: "",
secondaryColors: "",
effectsAnimation: "",
bestFor: "",
doNotUseFor: "",
lightMode: "supported",
darkMode: "conditional",
performance: "cost:moderate|drivers:animation,blur",
accessibility: "risk:high|requires:contrast-text-4.5,keyboard",
mobileFriendly: "adaptable",
conversionFocused: "",
frameworkCompatibility: "tailwind",
eraOrigin: "",
complexity: "Medium",
aiPromptKeywords: "",
cssTechnicalKeywords: "",
implementationChecklist: "",
designSystemVariables: "",
extractedColors: [],
cssProperties: {},
};
test("extracts only the requested controlled taxonomy value", () => {
assert.equal(getTaxonomyValue(baseStyle.performance, "cost"), "moderate");
assert.equal(getTaxonomyValue(baseStyle.accessibility, "risk"), "high");
assert.equal(getTaxonomyValue(baseStyle.performance, "risk"), "unknown");
});
test("maps cost, risk, and mode support levels to semantic variants", () => {
assert.equal(getLevelVariant("low"), "green");
assert.equal(getLevelVariant("supported"), "green");
assert.equal(getLevelVariant("moderate"), "yellow");
assert.equal(getLevelVariant("conditional"), "yellow");
assert.equal(getLevelVariant("high"), "red");
assert.equal(getLevelVariant("not-recommended"), "red");
assert.equal(getLevelVariant("unknown"), "gray");
});
test("renders concise labels with explicit accessible meaning", () => {
const markup = renderToStaticMarkup(<MetadataBadges style={baseStyle} />);
assert.match(markup, /aria-label="Style metadata"/);
assert.match(markup, /class="sr-only">Performance cost: Moderate<\/span>/);
assert.match(markup, /class="sr-only">Accessibility risk: High<\/span>/);
assert.match(markup, /class="sr-only">Light mode support: Supported<\/span>/);
assert.match(markup, /class="sr-only">Dark mode support: Conditional<\/span>/);
assert.match(markup, /aria-hidden="true">A11y risk: High<\/span>/);
assert.doesNotMatch(markup, /<span aria-label=/);
assert.doesNotMatch(markup, /drivers:|requires:|WCAG|Excellent|Full/);
});
test("filter groups expose names and selected state", () => {
const markup = renderToStaticMarkup(
<FilterBar
complexities={["Low"]}
eras={["Modern"]}
filters={DEFAULT_FILTERS}
onChange={() => undefined}
resultCount={50}
totalCount={50}
types={["General"]}
/>,
);
assert.match(markup, /aria-label="Status filter"[^>]*role="group"/);
assert.match(markup, /aria-pressed="true"[^>]*>active<\/button>/);
assert.match(markup, /aria-label="Type filter"[^>]*role="group"/);
assert.match(markup, /aria-pressed="true"[^>]*>All<\/button>/);
});
+9 -3
View File
@@ -2,6 +2,7 @@ import { StyleData } from "./types";
export interface Filters {
search: string;
status: string;
type: string;
complexity: string;
era: string;
@@ -9,6 +10,7 @@ export interface Filters {
export const DEFAULT_FILTERS: Filters = {
search: "",
status: "active",
type: "",
complexity: "",
era: "",
@@ -30,15 +32,19 @@ export function filterStyles(
styles: StyleData[],
filters: Filters
): StyleData[] {
const query = filters.search.toLowerCase();
return styles.filter((s) => {
if (filters.status && s.status !== filters.status) return false;
if (filters.type && s.type !== filters.type) return false;
if (filters.complexity && s.complexity !== filters.complexity) return false;
if (filters.era && s.eraOrigin !== filters.era) return false;
if (filters.search) {
const q = filters.search.toLowerCase();
if (query) {
const searchable = [
s.styleId,
s.styleCategory,
...s.aliases,
s.keywords,
s.bestFor,
s.eraOrigin,
@@ -47,7 +53,7 @@ export function filterStyles(
]
.join(" ")
.toLowerCase();
if (!searchable.includes(q)) return false;
if (!searchable.includes(query)) return false;
}
return true;
@@ -0,0 +1,77 @@
import assert from "node:assert/strict";
import test from "node:test";
import { DEFAULT_FILTERS, filterStyles } from "./filterStyles";
import { parseStylesCSV } from "./parseStyles";
const headers = [
"Status", "Style Category", "No", "Style ID", "Aliases", "Parent Style ID",
"Replacement Domain", "Replacement ID", "Preferred Mode", "Type", "Keywords", "Primary Colors",
"Secondary Colors", "Effects & Animation", "Best For", "Do Not Use For",
"Light Mode ✓", "Dark Mode ✓", "Performance", "Accessibility",
"Mobile-Friendly", "Conversion-Focused", "Framework Compatibility", "Era/Origin",
"Complexity", "AI Prompt Keywords", "CSS/Technical Keywords",
"Implementation Checklist", "Design System Variables",
];
function row(status: string, id: string, replacement = ""): string {
const values: Record<string, string> = {
Status: status, "Style Category": id, No: status === "active" ? "1" : "2",
"Style ID": id, Aliases: `${id}-alias`, "Replacement Domain": replacement,
"Replacement ID": replacement ? "Hero + CTA" : "", Type: "General",
Keywords: "clean", "Primary Colors": "#ffffff", "Secondary Colors": "#000000",
"CSS/Technical Keywords": "display: grid", Complexity: "Low",
};
return headers.map((header) => JSON.stringify(values[header] || "")).join(",");
}
test("parses taxonomy fields by header name", () => {
const styles = parseStylesCSV([
headers.join(","),
row("deprecated", "legacy-layout", "landing"),
].join("\n"));
assert.equal(styles[0].styleId, "legacy-layout");
assert.equal(styles[0].status, "deprecated");
assert.equal(styles[0].replacementDomain, "landing");
assert.equal(styles[0].replacementId, "Hero + CTA");
});
test("gallery defaults to active and exposes explicit status views", () => {
const styles = parseStylesCSV([
headers.join(","),
row("active", "active-style"),
row("supplemental", "candidate-style"),
row("deprecated", "legacy-style", "landing"),
].join("\n"));
assert.deepEqual(filterStyles(styles, DEFAULT_FILTERS).map((s) => s.styleId), [
"active-style",
]);
assert.deepEqual(
filterStyles(styles, { ...DEFAULT_FILTERS, status: "supplemental" }).map(
(style) => style.styleId
),
["candidate-style"],
);
assert.equal(filterStyles(styles, { ...DEFAULT_FILTERS, status: "" }).length, 3);
});
test("parser rejects missing or unknown status", () => {
assert.throws(
() => parseStylesCSV([headers.join(","), row("", "missing-status")].join("\n")),
/Unknown or missing style status/,
);
assert.throws(
() => parseStylesCSV([headers.join(","), row("draft", "draft-style")].join("\n")),
/Unknown or missing style status/,
);
});
test("parses quoted multiline fields as one record", () => {
const multiline = row("active", "multiline-style").replace(
'"clean"',
'"clean\nlayered"',
);
const styles = parseStylesCSV([headers.join(","), multiline].join("\r\n"));
assert.equal(styles.length, 1);
assert.equal(styles[0].keywords, "clean\nlayered");
});
+68 -24
View File
@@ -35,41 +35,85 @@ function parseCSVLine(line: string): string[] {
return fields;
}
function splitCSVRecords(content: string): string[] {
const records: string[] = [];
let current = "";
let inQuotes = false;
for (let i = 0; i < content.length; i++) {
const char = content[i];
if (char === '"') {
current += char;
if (inQuotes && content[i + 1] === '"') {
current += content[++i];
} else {
inQuotes = !inQuotes;
}
} else if (!inQuotes && (char === "\n" || char === "\r")) {
if (current.trim()) records.push(current);
current = "";
if (char === "\r" && content[i + 1] === "\n") i++;
} else {
current += char;
}
}
if (current.trim()) records.push(current);
if (inQuotes) throw new Error("Unterminated quoted CSV field");
return records;
}
export function parseStylesCSV(csvContent: string): StyleData[] {
const lines = csvContent.split("\n").filter((l) => l.trim().length > 0);
const lines = splitCSVRecords(csvContent);
if (lines.length < 2) return [];
// Skip header
const headers = parseCSVLine(lines[0]);
const column = new Map(headers.map((header, index) => [header, index]));
const value = (fields: string[], header: string): string => {
const index = column.get(header);
return index === undefined ? "" : fields[index] || "";
};
const dataLines = lines.slice(1);
return dataLines.map((line) => {
const f = parseCSVLine(line);
const primaryColors = f[4] || "";
const secondaryColors = f[5] || "";
const cssTechnicalKeywords = f[19] || "";
const primaryColors = value(f, "Primary Colors");
const secondaryColors = value(f, "Secondary Colors");
const cssTechnicalKeywords = value(f, "CSS/Technical Keywords");
const status = value(f, "Status");
if (!(["active", "supplemental", "deprecated"] as string[]).includes(status)) {
throw new Error(`Unknown or missing style status: ${status || "<empty>"}`);
}
return {
no: parseInt(f[0]) || 0,
styleCategory: f[1] || "",
type: f[2] || "",
keywords: f[3] || "",
no: parseInt(value(f, "No")) || 0,
styleId: value(f, "Style ID"),
styleCategory: value(f, "Style Category"),
aliases: value(f, "Aliases").split("|").map((alias) => alias.trim()).filter(Boolean),
status: status as StyleData["status"],
parentStyleId: value(f, "Parent Style ID"),
replacementDomain: value(f, "Replacement Domain"),
replacementId: value(f, "Replacement ID"),
preferredMode: (value(f, "Preferred Mode") || "auto") as StyleData["preferredMode"],
type: value(f, "Type"),
keywords: value(f, "Keywords"),
primaryColors,
secondaryColors,
effectsAnimation: f[6] || "",
bestFor: f[7] || "",
doNotUseFor: f[8] || "",
lightMode: f[9] || "",
darkMode: f[10] || "",
performance: f[11] || "",
accessibility: f[12] || "",
mobileFriendly: f[13] || "",
conversionFocused: f[14] || "",
frameworkCompatibility: f[15] || "",
eraOrigin: f[16] || "",
complexity: f[17] || "",
aiPromptKeywords: f[18] || "",
effectsAnimation: value(f, "Effects & Animation"),
bestFor: value(f, "Best For"),
doNotUseFor: value(f, "Do Not Use For"),
lightMode: value(f, "Light Mode ✓"),
darkMode: value(f, "Dark Mode ✓"),
performance: value(f, "Performance"),
accessibility: value(f, "Accessibility"),
mobileFriendly: value(f, "Mobile-Friendly"),
conversionFocused: value(f, "Conversion-Focused"),
frameworkCompatibility: value(f, "Framework Compatibility"),
eraOrigin: value(f, "Era/Origin"),
complexity: value(f, "Complexity"),
aiPromptKeywords: value(f, "AI Prompt Keywords"),
cssTechnicalKeywords,
implementationChecklist: f[20] || "",
designSystemVariables: f[21] || "",
implementationChecklist: value(f, "Implementation Checklist"),
designSystemVariables: value(f, "Design System Variables"),
extractedColors: extractColors(`${primaryColors} ${secondaryColors}`),
cssProperties: parseCssKeywords(cssTechnicalKeywords),
};
+7
View File
@@ -1,6 +1,13 @@
export interface StyleData {
no: number;
styleId: string;
styleCategory: string;
aliases: string[];
status: "active" | "supplemental" | "deprecated";
parentStyleId: string;
replacementDomain: string;
replacementId: string;
preferredMode: "auto" | "light" | "dark";
type: string;
keywords: string;
primaryColors: string;
+1
View File
@@ -5,6 +5,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"test": "tsc -p tsconfig.test.json && node --test .test-dist/lib/parseStyles.test.js .test-dist/components/metadata-badges.test.js",
"start": "next start"
},
"dependencies": {
+27
View File
@@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "Node",
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
},
"outDir": ".test-dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": [
"lib/types.ts",
"lib/colorExtractor.ts",
"lib/cssGenerator.ts",
"lib/parseStyles.ts",
"lib/filterStyles.ts",
"lib/parseStyles.test.ts",
"components/MetadataBadges.tsx",
"components/FilterBar.tsx",
"components/metadata-badges.test.tsx"
]
}