@eventuras/ratio-ui-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -13
- package/dist/index.js.map +1 -1
- package/dist/resolveTheme.d.ts +33 -9
- package/dist/resolveTheme.d.ts.map +1 -1
- package/dist/theme-Bqyh6GYU.js.map +1 -1
- package/dist/theme.d.ts +27 -16
- package/dist/theme.d.ts.map +1 -1
- package/dist/themes/bureau.d.ts.map +1 -1
- package/dist/themes/bureau.js +20 -26
- package/dist/themes/bureau.js.map +1 -1
- package/dist/toCssValue.d.ts +2 -2
- package/dist/toCssValue.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,23 +25,23 @@ A theme is authored once, with references where the CSS has them:
|
|
|
25
25
|
import { bureau } from '@eventuras/ratio-ui-core/themes/bureau';
|
|
26
26
|
|
|
27
27
|
bureau.light.primary; // '#006a7d'
|
|
28
|
-
bureau.
|
|
28
|
+
bureau.components.card.shadow.xs; // [{ x: 2, y: 2, blur: 0, color: { ref: 'text', alpha: 0.62, … } }]
|
|
29
29
|
bureau.radius.md; // 3
|
|
30
30
|
bureau.font.display; // 'Fira Sans'
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
A renderer without a CSS engine takes the resolved view — sRGB strings,
|
|
34
|
-
references followed inside their own mode,
|
|
34
|
+
references followed inside their own mode, and the component tokens per
|
|
35
|
+
mode, every shadow layer in parts:
|
|
35
36
|
|
|
36
37
|
```ts
|
|
37
38
|
import { resolveTheme } from '@eventuras/ratio-ui-core';
|
|
38
39
|
|
|
39
40
|
const theme = resolveTheme(bureau);
|
|
40
|
-
theme.light.
|
|
41
|
-
theme.dark.
|
|
41
|
+
theme.components.light.card.shadow.xs[0].color; // 'rgba(32, 36, 44, 0.62)' — the dark ink
|
|
42
|
+
theme.components.dark.card.shadow.xs[0].color; // 'rgba(240, 231, 210, 0.62)' — the cream ink
|
|
42
43
|
theme.scale.primary[700]; // '#006a7d' — step 700 is the light arm's teal
|
|
43
44
|
theme.space.m; // 21 — points
|
|
44
|
-
theme.components.button.shadow; // 'shadowHard' — read it from the mode you draw in
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
`toCssValue` writes any value the way the web's CSS does
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { defineTheme, mix, isMix, isShadow, type ColorKey, type ColorSpec, type ColorString, type Components, type Font, type Mix, type Radius, type Scale, type ScaleStep, type Shadow, type
|
|
2
|
-
export { resolveTheme, type ResolvedMode, type ResolvedShadow, type ResolvedTheme, } from './resolveTheme';
|
|
1
|
+
export { defineTheme, mix, isMix, isShadow, type ColorKey, type ColorSpec, type ColorString, type Components, type Font, type Mix, type Radius, type Scale, type ScaleStep, type Shadow, type ShadowLayers, type Theme, type ThemeInput, type ThemeMode, } from './theme';
|
|
2
|
+
export { resolveTheme, type ResolvedComponents, type ResolvedMode, type ResolvedShadow, type ResolvedTheme, } from './resolveTheme';
|
|
3
3
|
export { fontSize, space, type FluidSize, type FontSizeStep, type SpaceStep, } from './scale';
|
|
4
4
|
export { parseColor, formatColor, oklchToRgba, type Rgba } from './color';
|
|
5
5
|
export { toCssValue } from './toCssValue';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,WAAW,EACX,GAAG,EACH,KAAK,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,GAAG,EACR,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,WAAW,EACX,GAAG,EACH,KAAK,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,GAAG,EACR,KAAK,MAAM,EACX,KAAK,KAAK,EACV,KAAK,SAAS,EACd,KAAK,MAAM,EACX,KAAK,YAAY,EACjB,KAAK,KAAK,EACV,KAAK,UAAU,EACf,KAAK,SAAS,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EACL,YAAY,EACZ,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,aAAa,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,QAAQ,EACR,KAAK,EACL,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,SAAS,GACf,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,KAAK,IAAI,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -133,23 +133,51 @@ function resolveRgba(spec, mode, seen = []) {
|
|
|
133
133
|
}
|
|
134
134
|
function resolveMode(mode) {
|
|
135
135
|
const out = {};
|
|
136
|
-
for (const key of Object.keys(mode))
|
|
137
|
-
const value = mode[key];
|
|
138
|
-
if (isShadow(value)) out[key] = {
|
|
139
|
-
...value,
|
|
140
|
-
color: formatColor(resolveRgba(value.color, mode))
|
|
141
|
-
};
|
|
142
|
-
else if (typeof value === "string" || isMix(value)) out[key] = formatColor(resolveRgba(value, mode));
|
|
143
|
-
}
|
|
136
|
+
for (const key of Object.keys(mode)) out[key] = formatColor(resolveRgba(mode[key], mode));
|
|
144
137
|
return out;
|
|
145
138
|
}
|
|
139
|
+
function resolveComponents(components, mode) {
|
|
140
|
+
const layers = (shadow) => shadow?.map(({ x, y, blur, spread = 0, color }) => ({
|
|
141
|
+
x,
|
|
142
|
+
y,
|
|
143
|
+
blur,
|
|
144
|
+
spread,
|
|
145
|
+
color: formatColor(resolveRgba(color, mode))
|
|
146
|
+
})) ?? null;
|
|
147
|
+
const { button, card, chat } = components;
|
|
148
|
+
return {
|
|
149
|
+
button: {
|
|
150
|
+
radius: button.radius,
|
|
151
|
+
shadow: layers(button.shadow),
|
|
152
|
+
pressedShadow: layers(button.pressedShadow),
|
|
153
|
+
pressedOffset: { ...button.pressedOffset },
|
|
154
|
+
pressedScale: button.pressedScale
|
|
155
|
+
},
|
|
156
|
+
card: {
|
|
157
|
+
shadow: {
|
|
158
|
+
xs: layers(card.shadow.xs),
|
|
159
|
+
sm: layers(card.shadow.sm),
|
|
160
|
+
md: layers(card.shadow.md)
|
|
161
|
+
},
|
|
162
|
+
hoverShadow: layers(card.hoverShadow),
|
|
163
|
+
featured: {
|
|
164
|
+
...card.featured,
|
|
165
|
+
shadow: layers(card.featured.shadow)
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
chat: {
|
|
169
|
+
barShadow: layers(chat.barShadow),
|
|
170
|
+
popoverShadow: layers(chat.popoverShadow)
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
146
174
|
var resolveScale = (scale) => Object.fromEntries(Object.entries(scale).map(([step, value]) => [step, formatColor(parseColor(value))]));
|
|
147
175
|
/**
|
|
148
176
|
* The theme as a renderer without a CSS engine reads it: colours as sRGB
|
|
149
177
|
* hex or rgba, references followed inside their own mode, radius, font
|
|
150
|
-
* sizes and spacing in points, fonts by family. Component tokens
|
|
151
|
-
*
|
|
152
|
-
* The authored theme is untouched.
|
|
178
|
+
* sizes and spacing in points, fonts by family. Component tokens come per
|
|
179
|
+
* mode, their shadows' colours resolved in it, so a renderer reads the mode
|
|
180
|
+
* it draws in. The authored theme is untouched.
|
|
153
181
|
*/
|
|
154
182
|
function resolveTheme(theme) {
|
|
155
183
|
return {
|
|
@@ -164,7 +192,10 @@ function resolveTheme(theme) {
|
|
|
164
192
|
},
|
|
165
193
|
fontSize: atSmallEnd(fontSize),
|
|
166
194
|
space: atSmallEnd(space),
|
|
167
|
-
components:
|
|
195
|
+
components: {
|
|
196
|
+
light: resolveComponents(theme.components, theme.light),
|
|
197
|
+
dark: resolveComponents(theme.components, theme.dark)
|
|
198
|
+
}
|
|
168
199
|
};
|
|
169
200
|
}
|
|
170
201
|
//#endregion
|
|
@@ -179,13 +210,18 @@ var px = (n) => n === 0 ? "0" : `${n}px`;
|
|
|
179
210
|
* what the drift tests hold the hand-written CSS against until it lands.
|
|
180
211
|
*/
|
|
181
212
|
function toCssValue(value) {
|
|
213
|
+
if (value === null) return "none";
|
|
214
|
+
if (Array.isArray(value)) return value.map((layer) => toCssValue(layer)).join(", ");
|
|
182
215
|
if (typeof value === "number") return px(value);
|
|
183
216
|
if (typeof value === "string") return value;
|
|
184
217
|
if (isMix(value)) {
|
|
185
218
|
const percent = Math.round(value.alpha * 100);
|
|
186
219
|
return `color-mix(in ${value.space}, var(--${cssName(value.ref)}) ${percent}%, transparent)`;
|
|
187
220
|
}
|
|
188
|
-
if (isShadow(value))
|
|
221
|
+
if (isShadow(value)) {
|
|
222
|
+
const spread = value.spread === void 0 ? "" : ` ${px(value.spread)}`;
|
|
223
|
+
return `${px(value.x)} ${px(value.y)} ${px(value.blur)}${spread} ${toCssValue(value.color)}`;
|
|
224
|
+
}
|
|
189
225
|
throw new Error(`Cannot write ${JSON.stringify(value)} as CSS`);
|
|
190
226
|
}
|
|
191
227
|
//#endregion
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/color.ts","../src/scale.ts","../src/resolveTheme.ts","../src/cssName.ts","../src/toCssValue.ts"],"sourcesContent":["// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/** A colour in sRGB: channels 0–255, alpha 0–1. */\nexport interface Rgba {\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\nconst to255 = (v: number) => Math.round(clamp01(v) * 255);\n\n// Linear sRGB → sRGB transfer curve.\nconst gamma = (c: number) => (c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055);\n\n/**\n * OKLCH → sRGB, by Björn Ottosson's matrices. Out-of-gamut colours are\n * clipped per channel; CSS gamut-maps instead, so a step at the edge of\n * sRGB can land a notch off what the browser shows.\n */\nexport function oklchToRgba(L: number, C: number, h: number, a = 1): Rgba {\n const rad = (h * Math.PI) / 180;\n const A = C * Math.cos(rad);\n const B = C * Math.sin(rad);\n\n const l_ = L + 0.3963377774 * A + 0.2158037573 * B;\n const m_ = L - 0.1055613458 * A - 0.0638541728 * B;\n const s_ = L - 0.0894841775 * A - 1.291485548 * B;\n const l = l_ ** 3;\n const m = m_ ** 3;\n const s = s_ ** 3;\n\n const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n const b = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;\n return { r: to255(gamma(r)), g: to255(gamma(g)), b: to255(gamma(b)), a };\n}\n\nconst HEX = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst FN = /^(rgba?|oklch)\\(\\s*([^)]*?)\\s*\\)$/i;\n\nconst num = (token: string, what: string): number => {\n const v = Number(token);\n if (Number.isNaN(v)) throw new Error(`Unexpected ${what} \"${token}\"`);\n return v;\n};\n\n/**\n * Reads the forms the themes are written in: `#rgb[a]`, `#rrggbb[aa]`,\n * `rgb()`/`rgba()` with commas or spaces, and `oklch()`. Anything else\n * throws — a theme value nobody can parse is a bug, not a fallback.\n */\nexport function parseColor(input: string): Rgba {\n const value = input.trim();\n\n const hex = HEX.exec(value)?.[1];\n if (hex) {\n const wide = hex.length <= 4 ? [...hex].map(ch => ch + ch).join('') : hex;\n const n = (i: number) => parseInt(wide.slice(i, i + 2), 16);\n return { r: n(0), g: n(2), b: n(4), a: wide.length === 8 ? n(6) / 255 : 1 };\n }\n\n const fn = FN.exec(value);\n if (!fn) throw new Error(`Unsupported colour \"${input}\"`);\n const [, name, body] = fn as unknown as [string, string, string];\n // `rgb(0 0 0 / 0.5)` and `rgba(0, 0, 0, 0.5)` both split to four tokens.\n const parts = body.split(/[\\s,/]+/).filter(Boolean);\n const alpha = parts.length === 4 ? num(parts[3]!, 'alpha') : 1;\n\n if (name.toLowerCase().startsWith('rgb')) {\n const [r, g, b] = parts.slice(0, 3).map(p => num(p, 'channel'));\n if (r === undefined || g === undefined || b === undefined) throw new Error(`Unsupported colour \"${input}\"`);\n return { r, g, b, a: alpha };\n }\n\n const [L, C, h] = parts.slice(0, 3);\n if (L === undefined || C === undefined || h === undefined) throw new Error(`Unsupported colour \"${input}\"`);\n const lightness = L.endsWith('%') ? num(L.slice(0, -1), 'lightness') / 100 : num(L, 'lightness');\n return oklchToRgba(lightness, num(C, 'chroma'), num(h, 'hue'), alpha);\n}\n\n/** `#rrggbb` when opaque, otherwise `rgba(r, g, b, a)` — both read by every renderer. */\nexport function formatColor({ r, g, b, a }: Rgba): string {\n if (a >= 1) return `#${[r, g, b].map(c => c.toString(16).padStart(2, '0')).join('')}`;\n return `rgba(${r}, ${g}, ${b}, ${Math.round(a * 1000) / 1000})`;\n}\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/**\n * A fluid size as the web writes it: the two ends of its `clamp()`, in rem.\n * The web grows from `min` on a phone to `max` on a wide screen.\n */\nexport type FluidSize = readonly [min: number, max: number];\n\n/**\n * The type scale, shared by every theme — `--font-size-*` in\n * `ratio-ui/src/tokens/typography.css`.\n * @beta May change before release.\n */\nexport const fontSize = {\n xs: [0.75, 0.81],\n sm: [0.88, 0.96],\n base: [1, 1.13],\n lg: [1.13, 1.27],\n xl: [1.27, 1.42],\n '2xl': [1.42, 1.6],\n '3xl': [1.6, 1.8],\n '4xl': [1.8, 2.03],\n '5xl': [2.03, 2.28],\n '6xl': [2.28, 2.57],\n} as const satisfies Record<string, FluidSize>;\n\n/**\n * The space scale, shared by every theme — `--space-*` in\n * `ratio-ui/src/tokens/spacing.css`. Single steps only; the web's\n * `--space-s-m` pairs are fluid ranges a fixed layout has no use for.\n * @beta May change before release.\n */\nexport const space = {\n '3xs': [0.25, 0.25],\n '2xs': [0.4375, 0.5],\n xs: [0.6875, 0.75],\n s: [0.875, 1],\n m: [1.3125, 1.5],\n l: [1.75, 2],\n xl: [2.625, 3],\n '2xl': [3.5, 4],\n '3xl': [5.25, 6],\n} as const satisfies Record<string, FluidSize>;\n\nexport type FontSizeStep = keyof typeof fontSize;\nexport type SpaceStep = keyof typeof space;\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\nimport { formatColor, parseColor, type Rgba } from './color';\nimport { fontSize, space, type FluidSize, type FontSizeStep, type SpaceStep } from './scale';\nimport {\n isMix,\n isShadow,\n type ColorKey,\n type ColorSpec,\n type Components,\n type Font,\n type Radius,\n type Scale,\n type ScaleStep,\n type Shadow,\n type Theme,\n type ThemeMode,\n} from './theme';\n\nexport interface ResolvedShadow {\n x: number;\n y: number;\n blur: number;\n color: string;\n}\n\n/** A mode with every colour an sRGB string and every shadow in parts. */\nexport type ResolvedMode = {\n [K in keyof ThemeMode]: ThemeMode[K] extends Shadow ? ResolvedShadow : string;\n};\n\nexport interface ResolvedTheme {\n name: string;\n light: ResolvedMode;\n dark: ResolvedMode;\n radius: Radius;\n font: Font;\n scale: { primary: Record<ScaleStep, string>; accent: Record<ScaleStep, string> };\n /** Font sizes in points, at the small end of the web's fluid range — a phone's. */\n fontSize: Record<FontSizeStep, number>;\n /** Spacing in points, at the small end of the web's fluid range. */\n space: Record<SpaceStep, number>;\n components: Components;\n}\n\n/** Points per rem, as the web's root size. */\nconst REM = 16;\n\nconst atSmallEnd = <K extends string>(sizes: Record<K, FluidSize>) =>\n Object.fromEntries(\n (Object.entries(sizes) as [K, FluidSize][]).map(([step, [min]]) => [step, Math.round(min * REM * 100) / 100]),\n ) as Record<K, number>;\n\nfunction resolveRgba(spec: ColorSpec, mode: ThemeMode, seen: ColorKey[] = []): Rgba {\n if (typeof spec === 'string') return parseColor(spec);\n if (seen.includes(spec.ref)) throw new Error(`Colour reference loops: ${[...seen, spec.ref].join(' → ')}`);\n const base = resolveRgba(mode[spec.ref], mode, [...seen, spec.ref]);\n return { ...base, a: base.a * spec.alpha };\n}\n\nfunction resolveMode(mode: ThemeMode): ResolvedMode {\n const out: Partial<Record<keyof ThemeMode, string | ResolvedShadow>> = {};\n for (const key of Object.keys(mode) as (keyof ThemeMode)[]) {\n const value = mode[key];\n if (isShadow(value)) {\n out[key] = { ...value, color: formatColor(resolveRgba(value.color, mode)) };\n } else if (typeof value === 'string' || isMix(value)) {\n out[key] = formatColor(resolveRgba(value, mode));\n }\n }\n return out as ResolvedMode;\n}\n\nconst resolveScale = (scale: Scale) =>\n Object.fromEntries(\n Object.entries(scale).map(([step, value]) => [step, formatColor(parseColor(value))]),\n ) as Record<ScaleStep, string>;\n\n/**\n * The theme as a renderer without a CSS engine reads it: colours as sRGB\n * hex or rgba, references followed inside their own mode, radius, font\n * sizes and spacing in points, fonts by family. Component tokens keep\n * naming shadows, so a renderer reads them from the mode it draws in.\n * The authored theme is untouched.\n */\nexport function resolveTheme(theme: Theme): ResolvedTheme {\n return {\n name: theme.name,\n light: resolveMode(theme.light),\n dark: resolveMode(theme.dark),\n radius: { ...theme.radius },\n font: { ...theme.font },\n scale: { primary: resolveScale(theme.scale.primary), accent: resolveScale(theme.scale.accent) },\n fontSize: atSmallEnd(fontSize),\n space: atSmallEnd(space),\n components: structuredClone(theme.components),\n };\n}\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/** The CSS token name of a key: `textOnPrimary` → `text-on-primary`, `border1` → `border-1`. */\nexport const cssName = (key: string): string =>\n key\n .replace(/([a-z])([A-Z0-9])/g, '$1-$2')\n .replace(/([0-9])([a-zA-Z])/g, '$1-$2')\n .toLowerCase();\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\nimport { cssName } from './cssName';\nimport { isMix, isShadow, type ColorSpec, type Shadow } from './theme';\n\nconst px = (n: number) => (n === 0 ? '0' : `${n}px`);\n\n/**\n * The CSS the web writes for a value: the seed of the theme generator, and\n * what the drift tests hold the hand-written CSS against until it lands.\n */\nexport function toCssValue(value: ColorSpec | Shadow | number): string {\n if (typeof value === 'number') return px(value);\n if (typeof value === 'string') return value;\n if (isMix(value)) {\n const percent = Math.round(value.alpha * 100);\n return `color-mix(in ${value.space}, var(--${cssName(value.ref)}) ${percent}%, transparent)`;\n }\n if (isShadow(value)) return `${px(value.x)} ${px(value.y)} ${px(value.blur)} ${toCssValue(value.color)}`;\n throw new Error(`Cannot write ${JSON.stringify(value)} as CSS`);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/color.ts","../src/scale.ts","../src/resolveTheme.ts","../src/cssName.ts","../src/toCssValue.ts"],"sourcesContent":["// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/** A colour in sRGB: channels 0–255, alpha 0–1. */\nexport interface Rgba {\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\nconst clamp01 = (v: number) => Math.min(1, Math.max(0, v));\nconst to255 = (v: number) => Math.round(clamp01(v) * 255);\n\n// Linear sRGB → sRGB transfer curve.\nconst gamma = (c: number) => (c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055);\n\n/**\n * OKLCH → sRGB, by Björn Ottosson's matrices. Out-of-gamut colours are\n * clipped per channel; CSS gamut-maps instead, so a step at the edge of\n * sRGB can land a notch off what the browser shows.\n */\nexport function oklchToRgba(L: number, C: number, h: number, a = 1): Rgba {\n const rad = (h * Math.PI) / 180;\n const A = C * Math.cos(rad);\n const B = C * Math.sin(rad);\n\n const l_ = L + 0.3963377774 * A + 0.2158037573 * B;\n const m_ = L - 0.1055613458 * A - 0.0638541728 * B;\n const s_ = L - 0.0894841775 * A - 1.291485548 * B;\n const l = l_ ** 3;\n const m = m_ ** 3;\n const s = s_ ** 3;\n\n const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;\n const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;\n const b = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;\n return { r: to255(gamma(r)), g: to255(gamma(g)), b: to255(gamma(b)), a };\n}\n\nconst HEX = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst FN = /^(rgba?|oklch)\\(\\s*([^)]*?)\\s*\\)$/i;\n\nconst num = (token: string, what: string): number => {\n const v = Number(token);\n if (Number.isNaN(v)) throw new Error(`Unexpected ${what} \"${token}\"`);\n return v;\n};\n\n/**\n * Reads the forms the themes are written in: `#rgb[a]`, `#rrggbb[aa]`,\n * `rgb()`/`rgba()` with commas or spaces, and `oklch()`. Anything else\n * throws — a theme value nobody can parse is a bug, not a fallback.\n */\nexport function parseColor(input: string): Rgba {\n const value = input.trim();\n\n const hex = HEX.exec(value)?.[1];\n if (hex) {\n const wide = hex.length <= 4 ? [...hex].map(ch => ch + ch).join('') : hex;\n const n = (i: number) => parseInt(wide.slice(i, i + 2), 16);\n return { r: n(0), g: n(2), b: n(4), a: wide.length === 8 ? n(6) / 255 : 1 };\n }\n\n const fn = FN.exec(value);\n if (!fn) throw new Error(`Unsupported colour \"${input}\"`);\n const [, name, body] = fn as unknown as [string, string, string];\n // `rgb(0 0 0 / 0.5)` and `rgba(0, 0, 0, 0.5)` both split to four tokens.\n const parts = body.split(/[\\s,/]+/).filter(Boolean);\n const alpha = parts.length === 4 ? num(parts[3]!, 'alpha') : 1;\n\n if (name.toLowerCase().startsWith('rgb')) {\n const [r, g, b] = parts.slice(0, 3).map(p => num(p, 'channel'));\n if (r === undefined || g === undefined || b === undefined) throw new Error(`Unsupported colour \"${input}\"`);\n return { r, g, b, a: alpha };\n }\n\n const [L, C, h] = parts.slice(0, 3);\n if (L === undefined || C === undefined || h === undefined) throw new Error(`Unsupported colour \"${input}\"`);\n const lightness = L.endsWith('%') ? num(L.slice(0, -1), 'lightness') / 100 : num(L, 'lightness');\n return oklchToRgba(lightness, num(C, 'chroma'), num(h, 'hue'), alpha);\n}\n\n/** `#rrggbb` when opaque, otherwise `rgba(r, g, b, a)` — both read by every renderer. */\nexport function formatColor({ r, g, b, a }: Rgba): string {\n if (a >= 1) return `#${[r, g, b].map(c => c.toString(16).padStart(2, '0')).join('')}`;\n return `rgba(${r}, ${g}, ${b}, ${Math.round(a * 1000) / 1000})`;\n}\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/**\n * A fluid size as the web writes it: the two ends of its `clamp()`, in rem.\n * The web grows from `min` on a phone to `max` on a wide screen.\n */\nexport type FluidSize = readonly [min: number, max: number];\n\n/**\n * The type scale, shared by every theme — `--font-size-*` in\n * `ratio-ui/src/tokens/typography.css`.\n * @beta May change before release.\n */\nexport const fontSize = {\n xs: [0.75, 0.81],\n sm: [0.88, 0.96],\n base: [1, 1.13],\n lg: [1.13, 1.27],\n xl: [1.27, 1.42],\n '2xl': [1.42, 1.6],\n '3xl': [1.6, 1.8],\n '4xl': [1.8, 2.03],\n '5xl': [2.03, 2.28],\n '6xl': [2.28, 2.57],\n} as const satisfies Record<string, FluidSize>;\n\n/**\n * The space scale, shared by every theme — `--space-*` in\n * `ratio-ui/src/tokens/spacing.css`. Single steps only; the web's\n * `--space-s-m` pairs are fluid ranges a fixed layout has no use for.\n * @beta May change before release.\n */\nexport const space = {\n '3xs': [0.25, 0.25],\n '2xs': [0.4375, 0.5],\n xs: [0.6875, 0.75],\n s: [0.875, 1],\n m: [1.3125, 1.5],\n l: [1.75, 2],\n xl: [2.625, 3],\n '2xl': [3.5, 4],\n '3xl': [5.25, 6],\n} as const satisfies Record<string, FluidSize>;\n\nexport type FontSizeStep = keyof typeof fontSize;\nexport type SpaceStep = keyof typeof space;\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\nimport { formatColor, parseColor, type Rgba } from './color';\nimport { fontSize, space, type FluidSize, type FontSizeStep, type SpaceStep } from './scale';\nimport {\n type ColorKey,\n type ColorSpec,\n type Components,\n type Font,\n type Radius,\n type Scale,\n type ScaleStep,\n type ShadowLayers,\n type Theme,\n type ThemeMode,\n} from './theme';\n\n/** One layer of a shadow, its colour an sRGB string and its spread filled in. */\nexport interface ResolvedShadow {\n x: number;\n y: number;\n blur: number;\n spread: number;\n color: string;\n}\n\n/** A mode with every colour an sRGB string. */\nexport type ResolvedMode = Record<keyof ThemeMode, string>;\n\ntype ResolvedLayers = readonly ResolvedShadow[] | null;\n\n/** The component tokens of one mode, every shadow resolved in that mode. */\nexport interface ResolvedComponents {\n button: Omit<Components['button'], 'shadow' | 'pressedShadow'> & {\n shadow: ResolvedLayers;\n pressedShadow: ResolvedLayers;\n };\n card: {\n shadow: Record<'xs' | 'sm' | 'md', ResolvedLayers>;\n hoverShadow: ResolvedLayers;\n featured: Omit<Components['card']['featured'], 'shadow'> & { shadow: ResolvedLayers };\n };\n chat: { barShadow: ResolvedLayers; popoverShadow: ResolvedLayers };\n}\n\nexport interface ResolvedTheme {\n name: string;\n light: ResolvedMode;\n dark: ResolvedMode;\n radius: Radius;\n font: Font;\n scale: { primary: Record<ScaleStep, string>; accent: Record<ScaleStep, string> };\n /** Font sizes in points, at the small end of the web's fluid range — a phone's. */\n fontSize: Record<FontSizeStep, number>;\n /** Spacing in points, at the small end of the web's fluid range. */\n space: Record<SpaceStep, number>;\n /** Per mode: a shadow's colour follows the mode it is drawn in. */\n components: { light: ResolvedComponents; dark: ResolvedComponents };\n}\n\n/** Points per rem, as the web's root size. */\nconst REM = 16;\n\nconst atSmallEnd = <K extends string>(sizes: Record<K, FluidSize>) =>\n Object.fromEntries(\n (Object.entries(sizes) as [K, FluidSize][]).map(([step, [min]]) => [step, Math.round(min * REM * 100) / 100]),\n ) as Record<K, number>;\n\nfunction resolveRgba(spec: ColorSpec, mode: ThemeMode, seen: ColorKey[] = []): Rgba {\n if (typeof spec === 'string') return parseColor(spec);\n if (seen.includes(spec.ref)) throw new Error(`Colour reference loops: ${[...seen, spec.ref].join(' → ')}`);\n const base = resolveRgba(mode[spec.ref], mode, [...seen, spec.ref]);\n return { ...base, a: base.a * spec.alpha };\n}\n\nfunction resolveMode(mode: ThemeMode): ResolvedMode {\n const out: Partial<ResolvedMode> = {};\n for (const key of Object.keys(mode) as (keyof ThemeMode)[]) {\n out[key] = formatColor(resolveRgba(mode[key], mode));\n }\n return out as ResolvedMode;\n}\n\nfunction resolveComponents(components: Components, mode: ThemeMode): ResolvedComponents {\n const layers = (shadow: ShadowLayers | null): ResolvedLayers =>\n shadow?.map(({ x, y, blur, spread = 0, color }) => ({\n x,\n y,\n blur,\n spread,\n color: formatColor(resolveRgba(color, mode)),\n })) ?? null;\n const { button, card, chat } = components;\n return {\n button: {\n radius: button.radius,\n shadow: layers(button.shadow),\n pressedShadow: layers(button.pressedShadow),\n pressedOffset: { ...button.pressedOffset },\n pressedScale: button.pressedScale,\n },\n card: {\n shadow: { xs: layers(card.shadow.xs), sm: layers(card.shadow.sm), md: layers(card.shadow.md) },\n hoverShadow: layers(card.hoverShadow),\n featured: { ...card.featured, shadow: layers(card.featured.shadow) },\n },\n chat: { barShadow: layers(chat.barShadow), popoverShadow: layers(chat.popoverShadow) },\n };\n}\n\nconst resolveScale = (scale: Scale) =>\n Object.fromEntries(\n Object.entries(scale).map(([step, value]) => [step, formatColor(parseColor(value))]),\n ) as Record<ScaleStep, string>;\n\n/**\n * The theme as a renderer without a CSS engine reads it: colours as sRGB\n * hex or rgba, references followed inside their own mode, radius, font\n * sizes and spacing in points, fonts by family. Component tokens come per\n * mode, their shadows' colours resolved in it, so a renderer reads the mode\n * it draws in. The authored theme is untouched.\n */\nexport function resolveTheme(theme: Theme): ResolvedTheme {\n return {\n name: theme.name,\n light: resolveMode(theme.light),\n dark: resolveMode(theme.dark),\n radius: { ...theme.radius },\n font: { ...theme.font },\n scale: { primary: resolveScale(theme.scale.primary), accent: resolveScale(theme.scale.accent) },\n fontSize: atSmallEnd(fontSize),\n space: atSmallEnd(space),\n components: {\n light: resolveComponents(theme.components, theme.light),\n dark: resolveComponents(theme.components, theme.dark),\n },\n };\n}\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/** The CSS token name of a key: `textOnPrimary` → `text-on-primary`, `border1` → `border-1`. */\nexport const cssName = (key: string): string =>\n key\n .replace(/([a-z])([A-Z0-9])/g, '$1-$2')\n .replace(/([0-9])([a-zA-Z])/g, '$1-$2')\n .toLowerCase();\n","// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\nimport { cssName } from './cssName';\nimport { isMix, isShadow, type ColorSpec, type Shadow, type ShadowLayers } from './theme';\n\nconst px = (n: number) => (n === 0 ? '0' : `${n}px`);\n\n/**\n * The CSS the web writes for a value: the seed of the theme generator, and\n * what the drift tests hold the hand-written CSS against until it lands.\n */\nexport function toCssValue(value: ColorSpec | Shadow | ShadowLayers | null | number): string {\n if (value === null) return 'none';\n if (Array.isArray(value)) return value.map(layer => toCssValue(layer)).join(', ');\n if (typeof value === 'number') return px(value);\n if (typeof value === 'string') return value;\n if (isMix(value)) {\n const percent = Math.round(value.alpha * 100);\n return `color-mix(in ${value.space}, var(--${cssName(value.ref)}) ${percent}%, transparent)`;\n }\n if (isShadow(value)) {\n const spread = value.spread === undefined ? '' : ` ${px(value.spread)}`;\n return `${px(value.x)} ${px(value.y)} ${px(value.blur)}${spread} ${toCssValue(value.color)}`;\n }\n throw new Error(`Cannot write ${JSON.stringify(value)} as CSS`);\n}\n"],"mappings":"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"}
|
package/dist/resolveTheme.d.ts
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
import { FontSizeStep, SpaceStep } from './scale';
|
|
2
|
-
import { Components, Font, Radius, ScaleStep,
|
|
2
|
+
import { Components, Font, Radius, ScaleStep, Theme, ThemeMode } from './theme';
|
|
3
|
+
/** One layer of a shadow, its colour an sRGB string and its spread filled in. */
|
|
3
4
|
export interface ResolvedShadow {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
6
7
|
blur: number;
|
|
8
|
+
spread: number;
|
|
7
9
|
color: string;
|
|
8
10
|
}
|
|
9
|
-
/** A mode with every colour an sRGB string
|
|
10
|
-
export type ResolvedMode =
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/** A mode with every colour an sRGB string. */
|
|
12
|
+
export type ResolvedMode = Record<keyof ThemeMode, string>;
|
|
13
|
+
type ResolvedLayers = readonly ResolvedShadow[] | null;
|
|
14
|
+
/** The component tokens of one mode, every shadow resolved in that mode. */
|
|
15
|
+
export interface ResolvedComponents {
|
|
16
|
+
button: Omit<Components['button'], 'shadow' | 'pressedShadow'> & {
|
|
17
|
+
shadow: ResolvedLayers;
|
|
18
|
+
pressedShadow: ResolvedLayers;
|
|
19
|
+
};
|
|
20
|
+
card: {
|
|
21
|
+
shadow: Record<'xs' | 'sm' | 'md', ResolvedLayers>;
|
|
22
|
+
hoverShadow: ResolvedLayers;
|
|
23
|
+
featured: Omit<Components['card']['featured'], 'shadow'> & {
|
|
24
|
+
shadow: ResolvedLayers;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
chat: {
|
|
28
|
+
barShadow: ResolvedLayers;
|
|
29
|
+
popoverShadow: ResolvedLayers;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
13
32
|
export interface ResolvedTheme {
|
|
14
33
|
name: string;
|
|
15
34
|
light: ResolvedMode;
|
|
@@ -24,14 +43,19 @@ export interface ResolvedTheme {
|
|
|
24
43
|
fontSize: Record<FontSizeStep, number>;
|
|
25
44
|
/** Spacing in points, at the small end of the web's fluid range. */
|
|
26
45
|
space: Record<SpaceStep, number>;
|
|
27
|
-
|
|
46
|
+
/** Per mode: a shadow's colour follows the mode it is drawn in. */
|
|
47
|
+
components: {
|
|
48
|
+
light: ResolvedComponents;
|
|
49
|
+
dark: ResolvedComponents;
|
|
50
|
+
};
|
|
28
51
|
}
|
|
29
52
|
/**
|
|
30
53
|
* The theme as a renderer without a CSS engine reads it: colours as sRGB
|
|
31
54
|
* hex or rgba, references followed inside their own mode, radius, font
|
|
32
|
-
* sizes and spacing in points, fonts by family. Component tokens
|
|
33
|
-
*
|
|
34
|
-
* The authored theme is untouched.
|
|
55
|
+
* sizes and spacing in points, fonts by family. Component tokens come per
|
|
56
|
+
* mode, their shadows' colours resolved in it, so a renderer reads the mode
|
|
57
|
+
* it draws in. The authored theme is untouched.
|
|
35
58
|
*/
|
|
36
59
|
export declare function resolveTheme(theme: Theme): ResolvedTheme;
|
|
60
|
+
export {};
|
|
37
61
|
//# sourceMappingURL=resolveTheme.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveTheme.d.ts","sourceRoot":"","sources":["../src/resolveTheme.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmC,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,
|
|
1
|
+
{"version":3,"file":"resolveTheme.d.ts","sourceRoot":"","sources":["../src/resolveTheme.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmC,KAAK,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7F,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,MAAM,EAEX,KAAK,SAAS,EAEd,KAAK,KAAK,EACV,KAAK,SAAS,EACf,MAAM,SAAS,CAAC;AAEjB,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,+CAA+C;AAC/C,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,SAAS,EAAE,MAAM,CAAC,CAAC;AAE3D,KAAK,cAAc,GAAG,SAAS,cAAc,EAAE,GAAG,IAAI,CAAC;AAEvD,4EAA4E;AAC5E,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,eAAe,CAAC,GAAG;QAC/D,MAAM,EAAE,cAAc,CAAC;QACvB,aAAa,EAAE,cAAc,CAAC;KAC/B,CAAC;IACF,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC;QACnD,WAAW,EAAE,cAAc,CAAC;QAC5B,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,GAAG;YAAE,MAAM,EAAE,cAAc,CAAA;SAAE,CAAC;KACvF,CAAC;IACF,IAAI,EAAE;QAAE,SAAS,EAAE,cAAc,CAAC;QAAC,aAAa,EAAE,cAAc,CAAA;KAAE,CAAC;CACpE;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,YAAY,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,IAAI,CAAC;IACX,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACjF,mFAAmF;IACnF,QAAQ,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IACvC,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACjC,mEAAmE;IACnE,UAAU,EAAE;QAAE,KAAK,EAAE,kBAAkB,CAAC;QAAC,IAAI,EAAE,kBAAkB,CAAA;KAAE,CAAC;CACrE;AAyDD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,CAexD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme-Bqyh6GYU.js","names":[],"sources":["../src/theme.ts"],"sourcesContent":["// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/** A colour as authored: hex, `rgb()`/`rgba()`, or `oklch()`. */\nexport type ColorString = string;\n\n/**\n * A colour taken from another token of the same mode, at an alpha — what\n * the CSS writes as `color-mix(in srgb, var(--text) 62%, transparent)`.\n * A reference, not a value, so it follows the token it points at: the hard\n * shadow is \"the ink at 62%\" in light and in dark alike.\n */\nexport interface Mix {\n ref: ColorKey;\n alpha: number;\n /** The space the CSS mixes in. Mixing with transparent only scales alpha, so the resolved colour is the same in any. */\n space: 'srgb' | 'oklch';\n}\n\nexport type ColorSpec = ColorString | Mix;\n\n/**
|
|
1
|
+
{"version":3,"file":"theme-Bqyh6GYU.js","names":[],"sources":["../src/theme.ts"],"sourcesContent":["// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\n/** A colour as authored: hex, `rgb()`/`rgba()`, or `oklch()`. */\nexport type ColorString = string;\n\n/**\n * A colour taken from another token of the same mode, at an alpha — what\n * the CSS writes as `color-mix(in srgb, var(--text) 62%, transparent)`.\n * A reference, not a value, so it follows the token it points at: the hard\n * shadow is \"the ink at 62%\" in light and in dark alike.\n */\nexport interface Mix {\n ref: ColorKey;\n alpha: number;\n /** The space the CSS mixes in. Mixing with transparent only scales alpha, so the resolved colour is the same in any. */\n space: 'srgb' | 'oklch';\n}\n\nexport type ColorSpec = ColorString | Mix;\n\n/** One layer of a shadow in parts, so each renderer draws it its own way. */\nexport interface Shadow {\n x: number;\n y: number;\n blur: number;\n /** Grows (or, negative, shrinks) the shadow before the blur. Left out, the CSS writes none. */\n spread?: number;\n color: ColorSpec;\n}\n\n/**\n * A shadow as the CSS writes it: one or more layers, the first on top. A\n * component's shadow is `null` for none.\n */\nexport type ShadowLayers = readonly Shadow[];\n\n/** The colours of one mode. Keys are the CSS token names in camelCase: `--text-muted` is `textMuted`. */\nexport interface ThemeMode {\n primary: ColorSpec;\n secondary: ColorSpec;\n accent: ColorSpec;\n text: ColorSpec;\n textMuted: ColorSpec;\n textSubtle: ColorSpec;\n textOnPrimary: ColorSpec;\n textOnSecondary: ColorSpec;\n textOnAccent: ColorSpec;\n surface: ColorSpec;\n surfaceGlass: ColorSpec;\n card: ColorSpec;\n cardHover: ColorSpec;\n border1: ColorSpec;\n border2: ColorSpec;\n focusRing: ColorSpec;\n errorSolid: ColorSpec;\n errorOnSolid: ColorSpec;\n errorBg: ColorSpec;\n errorBorder: ColorSpec;\n errorText: ColorSpec;\n successSolid: ColorSpec;\n successOnSolid: ColorSpec;\n successBg: ColorSpec;\n successBorder: ColorSpec;\n successText: ColorSpec;\n infoSolid: ColorSpec;\n infoOnSolid: ColorSpec;\n infoBg: ColorSpec;\n infoBorder: ColorSpec;\n infoText: ColorSpec;\n warningSolid: ColorSpec;\n warningOnSolid: ColorSpec;\n warningBg: ColorSpec;\n warningBorder: ColorSpec;\n warningText: ColorSpec;\n /**\n * The chat's colours, the web's `--chat-*`.\n * @beta Named and valued as the chat components are; may change before release.\n */\n chatSidebarBg: ColorSpec;\n /** The pointed-at message, and the active room in a list. */\n chatActiveBg: ColorSpec;\n chatActiveFg: ColorSpec;\n /** The band behind a message that mentions you; its edge is `accent`. */\n chatMentionBg: ColorSpec;\n chatNick: ColorSpec;\n chatNickOp: ColorSpec;\n chatNickVoice: ColorSpec;\n chatAvatarBg: ColorSpec;\n chatAvatarFg: ColorSpec;\n chatAvatarAltBg: ColorSpec;\n chatAvatarAltFg: ColorSpec;\n chatBubbleBg: ColorSpec;\n chatBubbleMeBg: ColorSpec;\n chatBubbleMeFg: ColorSpec;\n /** A reaction of your own. */\n chatReactionMeBg: ColorSpec;\n chatReactionMeBorder: ColorSpec;\n chatRowHoverBg: ColorSpec;\n chatPopoverBg: ColorSpec;\n}\n\n/** The colours of a mode — what a `Mix` may point at. */\nexport type ColorKey = keyof ThemeMode;\n\n/** Rounding in px, one scale for every corner. */\nexport interface Radius {\n xs: number;\n sm: number;\n md: number;\n lg: number;\n xl: number;\n pill: number;\n overlay: number;\n}\n\n/** Font families by role. Names only — each renderer loads the files. */\nexport interface Font {\n display: string;\n body: string;\n mono: string;\n}\n\n/**\n * How components wear the theme — the CSS's `--button-*`, `--card-*` and\n * the chat's shadows. A shadow is its layers, its colours references where\n * the CSS has them, so each mode inks its own; `null` is no shadow.\n * @beta Grows as renderers need more components; may change before release.\n */\nexport interface Components {\n button: {\n /** A step of `radius`. */\n radius: keyof Radius;\n shadow: ShadowLayers | null;\n /** The shadow while pressed. */\n pressedShadow: ShadowLayers | null;\n /** How far a pressed button moves, in px — Bureau presses into its shadow. */\n pressedOffset: { x: number; y: number };\n /** How a pressed button scales — the standard button grows a little; 1 is none. */\n pressedScale: number;\n };\n card: {\n /** The resting shadow of each elevation tier. */\n shadow: Record<'xs' | 'sm' | 'md', ShadowLayers | null>;\n /** The lift of an interactive card on hover, or on press where there is no hover. */\n hoverShadow: ShadowLayers | null;\n /** The card that should stand out (`featured`): its frame, and the shadow it keeps. */\n featured: {\n /** In px. */\n borderWidth: number;\n borderColor: ColorKey;\n shadow: ShadowLayers | null;\n };\n };\n chat: {\n /** Under the composer and the room's bar. */\n barShadow: ShadowLayers | null;\n /** Under the reaction picker and other popovers. */\n popoverShadow: ShadowLayers | null;\n };\n}\n\nexport type ScaleStep = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;\n/** An eleven-step colour scale, as authored. */\nexport type Scale = Record<ScaleStep, ColorString>;\n\nexport interface Theme {\n name: string;\n light: ThemeMode;\n dark: ThemeMode;\n radius: Radius;\n font: Font;\n /** `accent` is @beta: added for the chat's tokens; may change before release. */\n scale: { primary: Scale; accent: Scale };\n components: Components;\n}\n\n/** What a theme file writes: `dark` overlays `light`, as the CSS cascades. */\nexport interface ThemeInput extends Omit<Theme, 'dark'> {\n dark: Partial<ThemeMode>;\n}\n\n/** A colour taken from `ref` in the same mode, at `alpha`. */\nexport const mix = (ref: ColorKey, alpha: number, space: Mix['space'] = 'srgb'): Mix => ({\n ref,\n alpha,\n space,\n});\n\n/** Fills `dark` from `light`, so a theme writes only what changes. */\nexport function defineTheme(input: ThemeInput): Theme {\n return { ...input, dark: { ...input.light, ...input.dark } };\n}\n\nexport const isMix = (value: unknown): value is Mix =>\n typeof value === 'object' && value !== null && 'ref' in value;\n\nexport const isShadow = (value: unknown): value is Shadow =>\n typeof value === 'object' && value !== null && 'blur' in value;\n"],"mappings":";;AAwLA,IAAa,OAAO,KAAe,OAAe,QAAsB,YAAiB;CACvF;CACA;CACA;AACF;;AAGA,SAAgB,YAAY,OAA0B;CACpD,OAAO;EAAE,GAAG;EAAO,MAAM;GAAE,GAAG,MAAM;GAAO,GAAG,MAAM;EAAK;CAAE;AAC7D;AAEA,IAAa,SAAS,UACpB,OAAO,UAAU,YAAY,UAAU,QAAQ,SAAS;AAE1D,IAAa,YAAY,UACvB,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU"}
|
package/dist/theme.d.ts
CHANGED
|
@@ -13,13 +13,20 @@ export interface Mix {
|
|
|
13
13
|
space: 'srgb' | 'oklch';
|
|
14
14
|
}
|
|
15
15
|
export type ColorSpec = ColorString | Mix;
|
|
16
|
-
/**
|
|
16
|
+
/** One layer of a shadow in parts, so each renderer draws it its own way. */
|
|
17
17
|
export interface Shadow {
|
|
18
18
|
x: number;
|
|
19
19
|
y: number;
|
|
20
20
|
blur: number;
|
|
21
|
+
/** Grows (or, negative, shrinks) the shadow before the blur. Left out, the CSS writes none. */
|
|
22
|
+
spread?: number;
|
|
21
23
|
color: ColorSpec;
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* A shadow as the CSS writes it: one or more layers, the first on top. A
|
|
27
|
+
* component's shadow is `null` for none.
|
|
28
|
+
*/
|
|
29
|
+
export type ShadowLayers = readonly Shadow[];
|
|
23
30
|
/** The colours of one mode. Keys are the CSS token names in camelCase: `--text-muted` is `textMuted`. */
|
|
24
31
|
export interface ThemeMode {
|
|
25
32
|
primary: ColorSpec;
|
|
@@ -83,13 +90,9 @@ export interface ThemeMode {
|
|
|
83
90
|
chatReactionMeBorder: ColorSpec;
|
|
84
91
|
chatRowHoverBg: ColorSpec;
|
|
85
92
|
chatPopoverBg: ColorSpec;
|
|
86
|
-
shadowHard: Shadow;
|
|
87
|
-
shadowHardLg: Shadow;
|
|
88
|
-
shadowHardXl: Shadow;
|
|
89
93
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
export type ColorKey = Exclude<keyof ThemeMode, ShadowKey>;
|
|
94
|
+
/** The colours of a mode — what a `Mix` may point at. */
|
|
95
|
+
export type ColorKey = keyof ThemeMode;
|
|
93
96
|
/** Rounding in px, one scale for every corner. */
|
|
94
97
|
export interface Radius {
|
|
95
98
|
xs: number;
|
|
@@ -108,35 +111,43 @@ export interface Font {
|
|
|
108
111
|
}
|
|
109
112
|
/**
|
|
110
113
|
* How components wear the theme — the CSS's `--button-*`, `--card-*` and
|
|
111
|
-
* the chat's shadows.
|
|
112
|
-
*
|
|
113
|
-
* no shadow.
|
|
114
|
+
* the chat's shadows. A shadow is its layers, its colours references where
|
|
115
|
+
* the CSS has them, so each mode inks its own; `null` is no shadow.
|
|
114
116
|
* @beta Grows as renderers need more components; may change before release.
|
|
115
117
|
*/
|
|
116
118
|
export interface Components {
|
|
117
119
|
button: {
|
|
118
120
|
/** A step of `radius`. */
|
|
119
121
|
radius: keyof Radius;
|
|
120
|
-
shadow:
|
|
122
|
+
shadow: ShadowLayers | null;
|
|
121
123
|
/** The shadow while pressed. */
|
|
122
|
-
pressedShadow:
|
|
124
|
+
pressedShadow: ShadowLayers | null;
|
|
123
125
|
/** How far a pressed button moves, in px — Bureau presses into its shadow. */
|
|
124
126
|
pressedOffset: {
|
|
125
127
|
x: number;
|
|
126
128
|
y: number;
|
|
127
129
|
};
|
|
130
|
+
/** How a pressed button scales — the standard button grows a little; 1 is none. */
|
|
131
|
+
pressedScale: number;
|
|
128
132
|
};
|
|
129
133
|
card: {
|
|
130
134
|
/** The resting shadow of each elevation tier. */
|
|
131
|
-
shadow: Record<'xs' | 'sm' | 'md',
|
|
135
|
+
shadow: Record<'xs' | 'sm' | 'md', ShadowLayers | null>;
|
|
132
136
|
/** The lift of an interactive card on hover, or on press where there is no hover. */
|
|
133
|
-
hoverShadow:
|
|
137
|
+
hoverShadow: ShadowLayers | null;
|
|
138
|
+
/** The card that should stand out (`featured`): its frame, and the shadow it keeps. */
|
|
139
|
+
featured: {
|
|
140
|
+
/** In px. */
|
|
141
|
+
borderWidth: number;
|
|
142
|
+
borderColor: ColorKey;
|
|
143
|
+
shadow: ShadowLayers | null;
|
|
144
|
+
};
|
|
134
145
|
};
|
|
135
146
|
chat: {
|
|
136
147
|
/** Under the composer and the room's bar. */
|
|
137
|
-
barShadow:
|
|
148
|
+
barShadow: ShadowLayers | null;
|
|
138
149
|
/** Under the reaction picker and other popovers. */
|
|
139
|
-
popoverShadow:
|
|
150
|
+
popoverShadow: ShadowLayers | null;
|
|
140
151
|
};
|
|
141
152
|
}
|
|
142
153
|
export type ScaleStep = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
package/dist/theme.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAIA,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC;AAEjC;;;;;GAKG;AACH,MAAM,WAAW,GAAG;IAClB,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,wHAAwH;IACxH,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,GAAG,CAAC;AAE1C,
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bureau.d.ts","sourceRoot":"","sources":["../../src/themes/bureau.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bureau.d.ts","sourceRoot":"","sources":["../../src/themes/bureau.ts"],"names":[],"mappings":"AAsDA;;;;;;;GAOG;AACH,eAAO,MAAM,MAAM,oBAiKjB,CAAC"}
|
package/dist/themes/bureau.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { i as mix, t as defineTheme } from "../theme-Bqyh6GYU.js";
|
|
2
2
|
//#region src/themes/bureau.ts
|
|
3
3
|
var ink = mix("text", .62);
|
|
4
|
+
var hard = (offset) => [{
|
|
5
|
+
x: offset,
|
|
6
|
+
y: offset,
|
|
7
|
+
blur: 0,
|
|
8
|
+
color: ink
|
|
9
|
+
}];
|
|
4
10
|
var primary = {
|
|
5
11
|
50: "oklch(0.975 0.01 210)",
|
|
6
12
|
100: "oklch(0.945 0.024 210)",
|
|
@@ -55,24 +61,6 @@ var bureau = defineTheme({
|
|
|
55
61
|
border1: "#ddd6c5",
|
|
56
62
|
border2: "#20242c",
|
|
57
63
|
focusRing: "#006a7d",
|
|
58
|
-
shadowHard: {
|
|
59
|
-
x: 2,
|
|
60
|
-
y: 2,
|
|
61
|
-
blur: 0,
|
|
62
|
-
color: ink
|
|
63
|
-
},
|
|
64
|
-
shadowHardLg: {
|
|
65
|
-
x: 3,
|
|
66
|
-
y: 3,
|
|
67
|
-
blur: 0,
|
|
68
|
-
color: ink
|
|
69
|
-
},
|
|
70
|
-
shadowHardXl: {
|
|
71
|
-
x: 5,
|
|
72
|
-
y: 5,
|
|
73
|
-
blur: 0,
|
|
74
|
-
color: ink
|
|
75
|
-
},
|
|
76
64
|
errorSolid: "#b53026",
|
|
77
65
|
errorOnSolid: "#f4efe2",
|
|
78
66
|
errorBg: "#f7e6e3",
|
|
@@ -181,24 +169,30 @@ var bureau = defineTheme({
|
|
|
181
169
|
components: {
|
|
182
170
|
button: {
|
|
183
171
|
radius: "md",
|
|
184
|
-
shadow:
|
|
172
|
+
shadow: hard(2),
|
|
185
173
|
pressedShadow: null,
|
|
186
174
|
pressedOffset: {
|
|
187
175
|
x: 2,
|
|
188
176
|
y: 2
|
|
189
|
-
}
|
|
177
|
+
},
|
|
178
|
+
pressedScale: 1
|
|
190
179
|
},
|
|
191
180
|
card: {
|
|
192
181
|
shadow: {
|
|
193
|
-
xs:
|
|
194
|
-
sm:
|
|
195
|
-
md:
|
|
182
|
+
xs: hard(2),
|
|
183
|
+
sm: hard(2),
|
|
184
|
+
md: hard(2)
|
|
196
185
|
},
|
|
197
|
-
hoverShadow:
|
|
186
|
+
hoverShadow: hard(3),
|
|
187
|
+
featured: {
|
|
188
|
+
borderWidth: 8,
|
|
189
|
+
borderColor: "primary",
|
|
190
|
+
shadow: null
|
|
191
|
+
}
|
|
198
192
|
},
|
|
199
193
|
chat: {
|
|
200
|
-
barShadow:
|
|
201
|
-
popoverShadow:
|
|
194
|
+
barShadow: hard(2),
|
|
195
|
+
popoverShadow: hard(3)
|
|
202
196
|
}
|
|
203
197
|
}
|
|
204
198
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bureau.js","names":[],"sources":["../../src/themes/bureau.ts"],"sourcesContent":["// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\nimport { defineTheme, mix, type Scale } from '../theme';\n\n// The hard offset shadow, Bureau's signature: the ink at reduced alpha, so\n// it contrasts both the paper and the element without reading as a solid\n// block. A reference, so in dark mode it re-resolves to the cream ink.\nconst ink = mix('text', 0.62);\n\n// Teal, light to dark. Absolute steps serving both modes: 700 is the\n// light arm's primary, 400 sits next to the dark arm's muted teal.\nconst primary: Scale = {\n 50: 'oklch(0.975 0.01 210)',\n 100: 'oklch(0.945 0.024 210)',\n 200: 'oklch(0.89 0.045 211)',\n 300: 'oklch(0.81 0.068 210)',\n 400: 'oklch(0.7 0.085 208)',\n 500: 'oklch(0.62 0.095 212)',\n 600: 'oklch(0.55 0.097 215)',\n 700: 'oklch(0.483 0.086 216)',\n 800: 'oklch(0.4 0.07 217)',\n 900: 'oklch(0.32 0.055 218)',\n 950: 'oklch(0.24 0.04 218)',\n};\n\n// The standard theme's amber, which the CSS cascades into Bureau.\nconst accent: Scale = {\n 50: 'oklch(0.988 0.012 92)',\n 100: 'oklch(0.965 0.04 90)',\n 200: 'oklch(0.928 0.075 88)',\n 300: 'oklch(0.88 0.105 88)',\n 400: 'oklch(0.815 0.125 85)',\n 500: 'oklch(0.745 0.13 82)',\n 600: 'oklch(0.64 0.125 80)',\n 700: 'oklch(0.52 0.105 78)',\n 800: 'oklch(0.405 0.085 76)',\n 900: 'oklch(0.295 0.06 75)',\n 950: 'oklch(0.18 0.038 74)',\n};\n\n// Steps of the standard theme's warm-grey and neutral scales the chat reads.\nconst secondary100 = 'oklch(0.985 0.004 88)';\nconst secondary400 = 'oklch(0.88 0.025 82)';\nconst neutral50 = 'oklch(0.9851 0.0001 263.3)';\nconst neutral700 = 'oklch(0.3715 0 263.3)';\nconst neutral900 = 'oklch(0.2046 0 263.3)';\n\n/**\n * Bureau — \"moderne retro\", paper, teal and ink. Values as in\n * `ratio-ui/src/themes/bureau.css`, which stays the web's source until the\n * generator lands; `bureau.test.ts` holds the two to each other.\n * Semantic tokens, the chat's colours, the primary and accent scales, and\n * the button, card and chat-shadow tokens a native renderer needs; the rest of the component tokens stay in the CSS\n * until a renderer needs them.\n */\nexport const bureau = defineTheme({\n name: 'bureau',\n\n light: {\n // Dark enough to carry link text at AA on the paper and on cards.\n primary: '#006a7d', // teal\n secondary: '#d9d2bf', // muted paper, a step down from surface\n accent: '#f0b429', // ochre\n\n text: '#20242c',\n // Darkened for WCAG AA on the mid-tone paper.\n textMuted: '#5c5644',\n textSubtle: '#6b6450',\n textOnPrimary: '#f4efe2',\n textOnSecondary: '#20242c',\n textOnAccent: '#20242c',\n\n surface: '#e6e0d1', // paper\n surfaceGlass: mix('surface', 0.88, 'oklch'),\n card: '#fffdf7',\n cardHover: '#fbf6ea',\n\n border1: '#ddd6c5',\n border2: '#20242c', // the ink: the strong edge round panels\n\n focusRing: '#006a7d', // solid teal: a crisp ring, not a haze\n\n //
|
|
1
|
+
{"version":3,"file":"bureau.js","names":[],"sources":["../../src/themes/bureau.ts"],"sourcesContent":["// ratio-ui · design system for knowledge sharing\n// SPDX-FileCopyrightText: 2026 Losol AS\n// SPDX-License-Identifier: MPL-2.0\n\nimport { defineTheme, mix, type Scale, type ShadowLayers } from '../theme';\n\n// The hard offset shadow, Bureau's signature: the ink at reduced alpha, so\n// it contrasts both the paper and the element without reading as a solid\n// block. A reference, so in dark mode it re-resolves to the cream ink.\nconst ink = mix('text', 0.62);\n\n// The CSS's --shadow-hard (2px), --shadow-hard-lg (3px) and --shadow-hard-xl\n// (5px): an offset with no blur. 2px matches the press-down distance, so a\n// pressed button covers it.\nconst hard = (offset: number): ShadowLayers => [{ x: offset, y: offset, blur: 0, color: ink }];\n\n// Teal, light to dark. Absolute steps serving both modes: 700 is the\n// light arm's primary, 400 sits next to the dark arm's muted teal.\nconst primary: Scale = {\n 50: 'oklch(0.975 0.01 210)',\n 100: 'oklch(0.945 0.024 210)',\n 200: 'oklch(0.89 0.045 211)',\n 300: 'oklch(0.81 0.068 210)',\n 400: 'oklch(0.7 0.085 208)',\n 500: 'oklch(0.62 0.095 212)',\n 600: 'oklch(0.55 0.097 215)',\n 700: 'oklch(0.483 0.086 216)',\n 800: 'oklch(0.4 0.07 217)',\n 900: 'oklch(0.32 0.055 218)',\n 950: 'oklch(0.24 0.04 218)',\n};\n\n// The standard theme's amber, which the CSS cascades into Bureau.\nconst accent: Scale = {\n 50: 'oklch(0.988 0.012 92)',\n 100: 'oklch(0.965 0.04 90)',\n 200: 'oklch(0.928 0.075 88)',\n 300: 'oklch(0.88 0.105 88)',\n 400: 'oklch(0.815 0.125 85)',\n 500: 'oklch(0.745 0.13 82)',\n 600: 'oklch(0.64 0.125 80)',\n 700: 'oklch(0.52 0.105 78)',\n 800: 'oklch(0.405 0.085 76)',\n 900: 'oklch(0.295 0.06 75)',\n 950: 'oklch(0.18 0.038 74)',\n};\n\n// Steps of the standard theme's warm-grey and neutral scales the chat reads.\nconst secondary100 = 'oklch(0.985 0.004 88)';\nconst secondary400 = 'oklch(0.88 0.025 82)';\nconst neutral50 = 'oklch(0.9851 0.0001 263.3)';\nconst neutral700 = 'oklch(0.3715 0 263.3)';\nconst neutral900 = 'oklch(0.2046 0 263.3)';\n\n/**\n * Bureau — \"moderne retro\", paper, teal and ink. Values as in\n * `ratio-ui/src/themes/bureau.css`, which stays the web's source until the\n * generator lands; `bureau.test.ts` holds the two to each other.\n * Semantic tokens, the chat's colours, the primary and accent scales, and\n * the button, card and chat-shadow tokens a native renderer needs; the rest of the component tokens stay in the CSS\n * until a renderer needs them.\n */\nexport const bureau = defineTheme({\n name: 'bureau',\n\n light: {\n // Dark enough to carry link text at AA on the paper and on cards.\n primary: '#006a7d', // teal\n secondary: '#d9d2bf', // muted paper, a step down from surface\n accent: '#f0b429', // ochre\n\n text: '#20242c',\n // Darkened for WCAG AA on the mid-tone paper.\n textMuted: '#5c5644',\n textSubtle: '#6b6450',\n textOnPrimary: '#f4efe2',\n textOnSecondary: '#20242c',\n textOnAccent: '#20242c',\n\n surface: '#e6e0d1', // paper\n surfaceGlass: mix('surface', 0.88, 'oklch'),\n card: '#fffdf7',\n cardHover: '#fbf6ea',\n\n border1: '#ddd6c5',\n border2: '#20242c', // the ink: the strong edge round panels\n\n focusRing: '#006a7d', // solid teal: a crisp ring, not a haze\n\n // Brick — the only status family Bureau sets; the rest inherit.\n errorSolid: '#b53026',\n errorOnSolid: '#f4efe2',\n errorBg: '#f7e6e3',\n errorBorder: '#e3b3ac',\n errorText: '#8a241c',\n\n // Success, info and warning are the standard theme's, which the CSS\n // cascades into Bureau; written out here until the standard theme has\n // its own file to inherit from.\n successSolid: '#2e9a62',\n successOnSolid: 'oklch(0.9851 0.0001 263.3)',\n successBg: '#eaf7f0',\n successBorder: '#bfe8d2',\n successText: '#1f6b46',\n infoSolid: '#2f7fa0',\n infoOnSolid: 'oklch(0.9851 0.0001 263.3)',\n infoBg: '#eaf4fa',\n infoBorder: '#c6e3f1',\n infoText: '#225e77',\n warningSolid: '#d97706',\n warningOnSolid: '#1a1405',\n warningBg: '#fff6de',\n warningBorder: '#ffe0a3',\n warningText: '#8a4b05',\n\n // The chat's colours are the standard theme's (tokens/chat.css), read\n // through Bureau's scales.\n chatSidebarBg: secondary100,\n chatActiveBg: primary[100],\n chatActiveFg: primary[900],\n chatMentionBg: accent[100],\n chatNick: neutral700,\n chatNickOp: primary[700],\n chatNickVoice: accent[700],\n chatAvatarBg: primary[700],\n chatAvatarFg: secondary100,\n chatAvatarAltBg: accent[200],\n chatAvatarAltFg: accent[900],\n chatBubbleBg: '#fffdf7', // the card\n chatBubbleMeBg: primary[700],\n chatBubbleMeFg: secondary100,\n chatReactionMeBg: primary[100],\n chatReactionMeBorder: primary[300],\n chatRowHoverBg: 'rgb(0 0 0 / 0.035)',\n chatPopoverBg: neutral50,\n },\n\n dark: {\n // Muted: a dark page carries less colour.\n primary: '#4fa3ae', // teal\n secondary: '#20231b',\n accent: '#ffc23d', // gold\n\n text: '#f0e7d2', // ink\n textMuted: '#a39c86',\n textSubtle: '#938b75',\n textOnPrimary: '#14140f',\n textOnSecondary: '#f0e7d2',\n textOnAccent: '#14140f',\n\n surface: '#14140f',\n surfaceGlass: mix('surface', 0.88, 'oklch'),\n card: '#20231b',\n cardHover: '#272b20',\n\n border1: '#2c2c22',\n border2: '#565341',\n\n focusRing: '#4fa3ae',\n\n // Coral.\n errorSolid: '#ff6e52',\n errorOnSolid: '#14140f',\n errorBg: 'rgba(255, 110, 82, 0.18)',\n errorBorder: 'rgba(255, 110, 82, 0.32)',\n errorText: '#ffb3a4',\n\n // The standard dark tints; the solids carry over from light.\n successBg: 'rgba(46, 154, 98, 0.16)',\n successBorder: 'rgba(46, 154, 98, 0.28)',\n successText: '#a7e7c6',\n infoBg: 'rgba(47, 127, 160, 0.16)',\n infoBorder: 'rgba(47, 127, 160, 0.28)',\n infoText: '#a7d9ee',\n warningBg: 'rgba(217, 119, 6, 0.18)',\n warningBorder: 'rgba(217, 119, 6, 0.3)',\n warningText: '#ffd18a',\n\n chatSidebarBg: '#20231b', // the card\n chatActiveBg: primary[900],\n chatActiveFg: secondary100,\n chatMentionBg: 'oklch(0.26 0.05 75)',\n chatNick: secondary400,\n chatNickOp: primary[300],\n chatNickVoice: accent[300],\n chatAvatarBg: primary[400],\n chatAvatarFg: primary[950],\n chatBubbleBg: '#20231b', // the card\n chatBubbleMeBg: primary[400],\n chatBubbleMeFg: primary[950],\n chatReactionMeBg: primary[900],\n chatReactionMeBorder: primary[700],\n chatRowHoverBg: 'rgb(255 255 255 / 0.045)',\n chatPopoverBg: neutral900,\n // The shadows' ink is a reference, so here it is the cream text.\n },\n\n // Tight corners — every step the same hard edge.\n radius: { xs: 2, sm: 2, md: 3, lg: 4, xl: 4, pill: 3, overlay: 4 },\n\n // Families only; the web embeds woff2, native loads TTF.\n font: { display: 'Fira Sans', body: 'Fira Sans', mono: 'Space Mono' },\n\n scale: { primary, accent },\n\n components: {\n // The hard shadow at rest; a press moves the button into it, and the\n // shadow goes, so the button seems to sink into the page.\n button: {\n radius: 'md',\n shadow: hard(2),\n pressedShadow: null,\n pressedOffset: { x: 2, y: 2 },\n pressedScale: 1,\n },\n card: {\n shadow: { xs: hard(2), sm: hard(2), md: hard(2) },\n hoverShadow: hard(3),\n // Framed like a poster: a thick primary border, no shadow.\n featured: { borderWidth: 8, borderColor: 'primary', shadow: null },\n },\n chat: { barShadow: hard(2), popoverShadow: hard(3) },\n },\n});\n"],"mappings":"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"}
|
package/dist/toCssValue.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ColorSpec, Shadow } from './theme';
|
|
1
|
+
import { ColorSpec, Shadow, ShadowLayers } from './theme';
|
|
2
2
|
/**
|
|
3
3
|
* The CSS the web writes for a value: the seed of the theme generator, and
|
|
4
4
|
* what the drift tests hold the hand-written CSS against until it lands.
|
|
5
5
|
*/
|
|
6
|
-
export declare function toCssValue(value: ColorSpec | Shadow | number): string;
|
|
6
|
+
export declare function toCssValue(value: ColorSpec | Shadow | ShadowLayers | null | number): string;
|
|
7
7
|
//# sourceMappingURL=toCssValue.d.ts.map
|
package/dist/toCssValue.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toCssValue.d.ts","sourceRoot":"","sources":["../src/toCssValue.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"toCssValue.d.ts","sourceRoot":"","sources":["../src/toCssValue.ts"],"names":[],"mappings":"AAKA,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,MAAM,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAI1F;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,GAAG,YAAY,GAAG,IAAI,GAAG,MAAM,GAAG,MAAM,CAc3F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventuras/ratio-ui-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The design system as data — theme values, the token vocabulary, and the pure rules every renderer of Ratio UI must agree on. No React, no DOM, no CSS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-tokens",
|