feat: release design system engine, studio and component contracts
This commit is contained in:
1 parent
ff1065fb27
commit
3943b946d6
693 files changed
+175546
-3894
No files matched your search
@@ -0,0 +1,255 @@
|
||||
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;
|
||||
Reference in new issue
Block a user