📦 deps(thirdparty): update snapshots
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
node_modules/
|
||||
.next/
|
||||
out/
|
||||
.test-dist/
|
||||
*.tsbuildinfo
|
||||
|
||||
@@ -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>/);
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user