Add runtime skin contract and docs
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -401,3 +401,10 @@ export {
|
||||
motionScales,
|
||||
type MotionRecipeName
|
||||
} from "./lib/motion";
|
||||
export {
|
||||
defaultSkin,
|
||||
setSkin,
|
||||
skinDetails,
|
||||
skinNames,
|
||||
type SkinName
|
||||
} from "./lib/skin";
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user