256 lines
9.0 KiB
TypeScript
256 lines
9.0 KiB
TypeScript
import type { ComponentRecipe, DesignSystem, DesignToken, RecipeParts, TokenMap } from "./types.ts";
|
|
import { catalogRecipes } from "./catalog-recipes.ts";
|
|
|
|
const ref = (name: string) => ({ ref: name });
|
|
const token = (
|
|
type: DesignToken["type"],
|
|
value: DesignToken["value"],
|
|
legacy?: string,
|
|
): DesignToken => ({ type, value, ...(legacy ? { legacy } : {}) });
|
|
function colors(values: Record<string, string>): TokenMap {
|
|
const expanded = {
|
|
...values,
|
|
"surface-raised": values.surface!,
|
|
"surface-muted": values["surface-2"]!,
|
|
"surface-soft": values["surface-2"]!,
|
|
"text-subtle": values["text-muted"]!,
|
|
"border-strong": values.border!,
|
|
"primary-muted": values["primary-soft"]!,
|
|
};
|
|
return Object.fromEntries(
|
|
Object.entries(expanded).map(([key, value]) => [
|
|
`color.${key}`,
|
|
token("color", value, `color-${key}`),
|
|
]),
|
|
);
|
|
}
|
|
function recipes(angular: boolean, outlined: boolean): Record<string, ComponentRecipe> {
|
|
const recipe = (base: RecipeParts): ComponentRecipe => ({ contractVersion: 1, base });
|
|
const surface = {
|
|
borderRadius: ref("shape.panel"),
|
|
borderWidth: "1px",
|
|
borderStyle: "solid",
|
|
borderColor: ref("color.border"),
|
|
boxShadow: ref("elevation.panel"),
|
|
};
|
|
const field = {
|
|
borderRadius: ref("shape.control"),
|
|
borderWidth: "1px",
|
|
borderStyle: "solid",
|
|
borderColor: ref("color.border"),
|
|
backgroundColor: ref("color.surface"),
|
|
color: ref("color.text"),
|
|
fontFamily: ref("type.body"),
|
|
};
|
|
const result: Record<string, ComponentRecipe> = {
|
|
Button: {
|
|
contractVersion: 1,
|
|
base: {
|
|
control: {
|
|
borderRadius: ref("shape.control"),
|
|
borderWidth: "1px",
|
|
borderStyle: "solid",
|
|
paddingInline: ref("space.control"),
|
|
boxShadow: ref("elevation.panel"),
|
|
fontFamily: ref("type.body"),
|
|
...(angular ? { rotate: "-2deg" } : {}),
|
|
},
|
|
},
|
|
variants: [
|
|
{
|
|
when: { variant: "default" },
|
|
parts: {
|
|
control: {
|
|
backgroundImage: "none",
|
|
backgroundColor: outlined ? "transparent" : ref("color.primary"),
|
|
color: outlined ? ref("color.primary") : ref("color.primary-contrast"),
|
|
borderColor: ref("color.primary"),
|
|
},
|
|
},
|
|
},
|
|
],
|
|
states: [
|
|
{ state: "focusVisible", parts: { control: { outlineColor: ref("color.primary") } } },
|
|
{
|
|
state: "disabled",
|
|
parts: { control: { opacity: 0.55, ...(angular ? { rotate: "0deg" } : {}) } },
|
|
},
|
|
],
|
|
},
|
|
Input: recipe({ control: field }),
|
|
Textarea: recipe({ control: field }),
|
|
Select: recipe({ control: field }),
|
|
Checkbox: recipe({ control: { borderRadius: ref("shape.control"), borderWidth: "1px" } }),
|
|
Radio: recipe({ control: { borderWidth: "1px" } }),
|
|
Switch: recipe({ control: { borderRadius: ref("shape.control") } }),
|
|
Card: recipe({
|
|
panel: { ...surface, ...(angular ? { rotate: "1deg" } : {}) },
|
|
body: { paddingInline: ref("space.panel"), paddingBlock: ref("space.panel") },
|
|
heading: { fontFamily: ref("type.heading") },
|
|
}),
|
|
Alert: recipe({
|
|
root: { borderRadius: ref("shape.panel"), boxShadow: ref("elevation.panel") },
|
|
}),
|
|
Modal: recipe({
|
|
panel: { ...surface, borderRadius: ref("shape.dialog") },
|
|
heading: { fontFamily: ref("type.heading") },
|
|
}),
|
|
ContextMenu: recipe({
|
|
panel: { ...surface, borderRadius: ref("shape.dialog") },
|
|
item: { borderRadius: ref("shape.control"), fontFamily: ref("type.body") },
|
|
}),
|
|
Nav: {
|
|
contractVersion: 1,
|
|
base: { link: { borderRadius: ref("shape.control"), fontFamily: ref("type.body") } },
|
|
states: [{ state: "selected", parts: { link: { fontWeight: 700 } } }],
|
|
},
|
|
PageHeader: recipe({ title: { fontFamily: ref("type.heading"), letterSpacing: "-0.025em" } }),
|
|
Container: recipe({ root: { paddingInline: ref("space.page") } }),
|
|
};
|
|
return { ...catalogRecipes(), ...result };
|
|
}
|
|
function preset(id: string, name: string, soft: boolean, angular: boolean): DesignSystem {
|
|
const mono = '"IBM Plex Mono", "Courier New", monospace';
|
|
const body = soft ? "Georgia, serif" : angular ? "Arial, sans-serif" : mono;
|
|
const heading = angular ? '"Times New Roman", serif' : body;
|
|
const light = soft
|
|
? {
|
|
bg: "#E8DCC7",
|
|
background: "#E8DCC7",
|
|
surface: "#D4B895",
|
|
"surface-2": "#E8DCC7",
|
|
text: "#292D24",
|
|
foreground: "#292D24",
|
|
"text-muted": "#4B5141",
|
|
border: "#606C38",
|
|
primary: "#606C38",
|
|
"primary-hover": "#4B542C",
|
|
"primary-active": "#3D4424",
|
|
"primary-contrast": "#FFFFFF",
|
|
"primary-text": "#3D4424",
|
|
"primary-soft": "#D4B895",
|
|
}
|
|
: angular
|
|
? {
|
|
bg: "#E8E0C0",
|
|
background: "#E8E0C0",
|
|
surface: "#EDE4CF",
|
|
"surface-2": "#E8E0C0",
|
|
text: "#232320",
|
|
foreground: "#232320",
|
|
"text-muted": "#535146",
|
|
border: "#535146",
|
|
primary: "#8F2448",
|
|
"primary-hover": "#701C39",
|
|
"primary-active": "#59172D",
|
|
"primary-contrast": "#FFFFFF",
|
|
"primary-text": "#701C39",
|
|
"primary-soft": "#E8E0C0",
|
|
}
|
|
: {
|
|
bg: "#FFFFFF",
|
|
background: "#FFFFFF",
|
|
surface: "#F7F7F8",
|
|
"surface-2": "#FFFFFF",
|
|
text: "#0B0C0A",
|
|
foreground: "#0B0C0A",
|
|
"text-muted": "#454741",
|
|
border: "#696C63",
|
|
primary: "#705000",
|
|
"primary-hover": "#594000",
|
|
"primary-active": "#443100",
|
|
"primary-contrast": "#FFFFFF",
|
|
"primary-text": "#594000",
|
|
"primary-soft": "#F7F7F8",
|
|
};
|
|
const dark = soft
|
|
? {
|
|
bg: "#292D24",
|
|
background: "#292D24",
|
|
surface: "#363D2F",
|
|
"surface-2": "#414B37",
|
|
text: "#E8DCC7",
|
|
foreground: "#E8DCC7",
|
|
"text-muted": "#C9C0AA",
|
|
border: "#8B9D83",
|
|
primary: "#D4B895",
|
|
"primary-hover": "#E8DCC7",
|
|
"primary-active": "#E8DCC7",
|
|
"primary-contrast": "#292D24",
|
|
"primary-text": "#E8DCC7",
|
|
"primary-soft": "#414B37",
|
|
}
|
|
: angular
|
|
? {
|
|
bg: "#25221E",
|
|
background: "#25221E",
|
|
surface: "#353128",
|
|
"surface-2": "#454032",
|
|
text: "#EDE4CF",
|
|
foreground: "#EDE4CF",
|
|
"text-muted": "#D0C7B2",
|
|
border: "#B0A78D",
|
|
primary: "#FF9FC6",
|
|
"primary-hover": "#FFC0DA",
|
|
"primary-active": "#FFD2E5",
|
|
"primary-contrast": "#25221E",
|
|
"primary-text": "#FF9FC6",
|
|
"primary-soft": "#454032",
|
|
}
|
|
: {
|
|
bg: "#0B0C0A",
|
|
background: "#0B0C0A",
|
|
surface: "#000000",
|
|
"surface-2": "#0B0C0A",
|
|
text: "#F7F7F8",
|
|
foreground: "#F7F7F8",
|
|
"text-muted": "#BEC0B9",
|
|
border: "#696C63",
|
|
primary: "#FFB800",
|
|
"primary-hover": "#FFD15C",
|
|
"primary-active": "#FFE099",
|
|
"primary-contrast": "#000000",
|
|
"primary-text": "#FFB800",
|
|
"primary-soft": "#0B0C0A",
|
|
};
|
|
const tokens: TokenMap = {
|
|
"shape.control": token("dimension", soft ? "16px" : "0", "radius-sm"),
|
|
"shape.panel": token("dimension", soft ? "24px" : "0", "radius-md"),
|
|
"shape.dialog": token("dimension", soft ? "32px" : "0", "radius-lg"),
|
|
"shape.base": token("dimension", soft ? "24px" : "0", "radius"),
|
|
"space.control": token("dimension", soft ? "1.25rem" : "0.75rem"),
|
|
"space.panel": token("dimension", soft ? "1.5rem" : "1rem", "space-panel"),
|
|
"space.page": token("dimension", soft ? "1.5rem" : "1rem"),
|
|
"control.height": token("dimension", soft ? "2.75rem" : "2.5rem", "control-height"),
|
|
"type.body": token("fontFamily", body, "font-sans"),
|
|
"type.heading": token("fontFamily", heading),
|
|
"elevation.panel": token("shadow", angular ? "3px 3px 0 #535146" : "none", "shadow-1"),
|
|
...colors(dark),
|
|
};
|
|
return {
|
|
schemaVersion: 1,
|
|
id,
|
|
name,
|
|
version: "1.0.0",
|
|
defaultMode: soft || angular ? "light" : "dark",
|
|
tokens,
|
|
modes: {
|
|
light: { ...colors(light), scheme: token("colorScheme", "light", "color-scheme") },
|
|
dark: { ...colors(dark), scheme: token("colorScheme", "dark", "color-scheme") },
|
|
},
|
|
recipes: recipes(angular, !soft),
|
|
};
|
|
}
|
|
|
|
/** Industrial: black/amber, monospace, square outlined controls and flat panels. */
|
|
export const SQUARE_DESIGN_SYSTEM = preset("square", "Square / Industrial", false, false);
|
|
/** Organic: sand/sage surfaces, warm serif typography, generous rounded controls. */
|
|
export const SOFT_DESIGN_SYSTEM = preset("soft", "Soft / Organic", true, false);
|
|
/** Lo-Fi: paper-yellow, mixed system typography, rotated actions and hard shadows. */
|
|
export const ANGULAR_DESIGN_SYSTEM = preset("angular", "Angular / Lo-Fi", false, true);
|
|
export const CORE_DESIGN_SYSTEMS = {
|
|
square: SQUARE_DESIGN_SYSTEM,
|
|
soft: SOFT_DESIGN_SYSTEM,
|
|
angular: ANGULAR_DESIGN_SYSTEM,
|
|
} as const;
|