Add runtime skin contract and docs

This commit is contained in:
2026-03-20 11:22:03 +08:00
parent 5c9eb84c63
commit 246851a68e
9 changed files with 1053 additions and 1 deletions
+5 -1
View File
@@ -3,8 +3,12 @@
"version": "0.0.0",
"private": true,
"type": "module",
"sideEffects": [
"**/*.css"
],
"exports": {
".": "./src/index.ts"
".": "./src/index.ts",
"./skins.css": "./src/skins.css"
},
"files": [
"dist",
+7
View File
@@ -401,3 +401,10 @@ export {
motionScales,
type MotionRecipeName
} from "./lib/motion";
export {
defaultSkin,
setSkin,
skinDetails,
skinNames,
type SkinName
} from "./lib/skin";
+24
View File
@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { defaultSkin, setSkin, skinDetails, skinNames } from "./skin";
describe("skin contract", () => {
it("exposes a default skin that exists in the public name set", () => {
expect(skinNames).toContain(defaultSkin);
expect(skinDetails[defaultSkin].label).toBeTruthy();
});
it("sets the document root skin when no target element is provided", () => {
setSkin("glass");
expect(document.documentElement.dataset.skin).toBe("glass");
});
it("sets the provided target element instead of the document root", () => {
const target = document.createElement("div");
setSkin("pixel", target);
expect(target.dataset.skin).toBe("pixel");
});
});
+41
View File
@@ -0,0 +1,41 @@
export const skinNames = ["minimal", "glass", "pixel"] as const;
export type SkinName = (typeof skinNames)[number];
export const defaultSkin: SkinName = "minimal";
export const skinDetails = {
minimal: {
label: "Minimal",
note: "Restrained surfaces and low-ornament defaults"
},
glass: {
label: "Glass",
note: "Translucent layers, brighter edges, and blurred panels"
},
pixel: {
label: "Pixel",
note: "Hard edges, crisp borders, and stepped shadows"
}
} as const satisfies Record<SkinName, { label: string; note: string }>;
function getTargetElement(root?: HTMLElement) {
if (root) {
return root;
}
if (typeof document === "undefined") {
return undefined;
}
return document.documentElement;
}
export function setSkin(skin: SkinName, root?: HTMLElement) {
const target = getTargetElement(root);
if (!target) {
return;
}
target.dataset.skin = skin;
}
+82
View File
@@ -0,0 +1,82 @@
:root,
[data-skin="minimal"] {
--ui-canvas-image: radial-gradient(
circle at top,
color-mix(in oklch, var(--color-primary) 8%, transparent),
transparent 58%
);
--ui-canvas-size: auto;
--ui-surface-bg: color-mix(in oklch, var(--color-card) 88%, white 12%);
--ui-surface-border: color-mix(in oklch, var(--color-border) 92%, white 8%);
--ui-surface-shadow: var(--shadow-sm);
--ui-surface-radius: var(--radius-lg);
--ui-surface-backdrop-blur: 0px;
--ui-control-bg: color-mix(in oklch, var(--color-background) 92%, white 8%);
--ui-control-border: var(--color-border);
--ui-control-shadow: var(--shadow-xs);
--ui-control-radius: var(--radius-md);
--ui-ornament-opacity: 0.1;
--ui-ornament-mix: normal;
}
[data-skin="glass"] {
--ui-canvas-image:
radial-gradient(
circle at top left,
color-mix(in oklch, var(--color-primary) 22%, transparent),
transparent 42%
),
radial-gradient(
circle at top right,
color-mix(in oklch, var(--color-accent) 18%, transparent),
transparent 48%
),
linear-gradient(
180deg,
color-mix(in oklch, var(--color-background) 64%, white 36%),
var(--color-background)
);
--ui-canvas-size: auto;
--ui-surface-bg: color-mix(in oklch, var(--color-card) 58%, transparent);
--ui-surface-border: color-mix(in oklch, white 46%, var(--color-border));
--ui-surface-shadow: 0 24px 64px oklch(0.18 0.03 255 / 0.18);
--ui-surface-radius: var(--radius-xl);
--ui-surface-backdrop-blur: 20px;
--ui-control-bg: color-mix(in oklch, var(--color-card) 52%, transparent);
--ui-control-border: color-mix(in oklch, white 36%, var(--color-border-strong));
--ui-control-shadow: 0 14px 38px oklch(0.2 0.03 255 / 0.14);
--ui-control-radius: var(--radius-lg);
--ui-ornament-opacity: 0.36;
--ui-ornament-mix: screen;
}
[data-skin="pixel"] {
--ui-canvas-image:
linear-gradient(
90deg,
color-mix(in oklch, var(--color-foreground) 7%, transparent) 1px,
transparent 1px
),
linear-gradient(
180deg,
color-mix(in oklch, var(--color-foreground) 7%, transparent) 1px,
transparent 1px
),
linear-gradient(
180deg,
color-mix(in oklch, var(--color-background) 92%, black 8%),
var(--color-background)
);
--ui-canvas-size: 12px 12px, 12px 12px, auto;
--ui-surface-bg: color-mix(in oklch, var(--color-card) 96%, white 4%);
--ui-surface-border: var(--color-foreground);
--ui-surface-shadow: 6px 6px 0 color-mix(in oklch, var(--color-foreground) 38%, transparent);
--ui-surface-radius: 0px;
--ui-surface-backdrop-blur: 0px;
--ui-control-bg: var(--color-background);
--ui-control-border: var(--color-foreground);
--ui-control-shadow: 3px 3px 0 color-mix(in oklch, var(--color-foreground) 30%, transparent);
--ui-control-radius: 0px;
--ui-ornament-opacity: 0.2;
--ui-ornament-mix: multiply;
}