@appshell/tokens 1.0.0-alpha.15

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Navaris Technologies, LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # @appshell/tokens
2
+
3
+ The design token contract an Application supplies and every package it composes consumes.
4
+
5
+ A catalogue of components that cannot be themed to their host is not composable — visual
6
+ coherence is the point. This package is the vocabulary that makes it possible: 41 CSS
7
+ custom properties, a set of presets, and the contrast validation that keeps a theme
8
+ legible.
9
+
10
+ ## For a package author
11
+
12
+ Author against the names, always with a fallback so the package still renders standalone:
13
+
14
+ ```css
15
+ .button {
16
+ background: var(--appshell-primary, #0284c7);
17
+ color: var(--appshell-on-primary, #fff);
18
+ border-radius: var(--appshell-radius-md, 0.5rem);
19
+ padding: var(--appshell-space-sm, 0.5rem) var(--appshell-space-md, 1rem);
20
+ }
21
+ .button:hover { background: var(--appshell-primary-hover); }
22
+ .button:focus-visible { outline: 2px solid var(--appshell-focus-ring); outline-offset: 2px; }
23
+ ```
24
+
25
+ Never write a literal colour you expect to be themed. That is the one rule.
26
+
27
+ ## The contract
28
+
29
+ **Colour is semantic; dimensions are a scale.** `--appshell-surface` says what a colour
30
+ *means*; `--appshell-space-md` is honestly just a size. Nobody wants
31
+ `--appshell-space-card-padding`.
32
+
33
+ **Every colour that carries text has an `on-` partner.** Write
34
+ `background: var(--appshell-primary); color: var(--appshell-on-primary)` and you cannot
35
+ produce an illegible control whatever palette the Application supplies. It is also what
36
+ makes dark mode a change of values rather than a change of logic.
37
+
38
+ | Group | Roles |
39
+ | --- | --- |
40
+ | Surface | `surface`, `on-surface`, `surface-raised`, `on-surface-raised` |
41
+ | Text & line | `text-muted`, `border` |
42
+ | Action | `primary`, `on-primary`, `secondary`, `on-secondary` |
43
+ | Derived | `primary-hover`, `primary-active`, `secondary-hover`, `secondary-active`, `focus-ring` |
44
+ | Status | `danger`, `warning`, `success` and their `on-` partners |
45
+ | Type | `font-body`, `font-mono`, `font-size-h1`…`h6`, `-body`, `-small`, `line-height-tight`, `-normal` |
46
+ | Dimension | `space-xs`…`xl`, `radius-sm`/`md`/`lg` |
47
+
48
+ Type sizes are named for their role rather than a `sm/md/lg` scale on purpose. A numeric
49
+ scale reintroduces the divergence the contract exists to prevent — one author maps `h1` to
50
+ `2xl`, another to `xl`, and headings stop matching across a composed page.
51
+
52
+ **Derived roles cost a theme nothing.** Hover and active are a `color-mix` from their
53
+ accent, and the focus ring is picked per base and accent. A theme may override any of
54
+ them; none has to be supplied. So the contract publishes 41 names while an Application
55
+ authors 36 at most — and usually none, because it picks presets.
56
+
57
+ ## Presets: base × accent
58
+
59
+ Themes are two choices, not 41 values:
60
+
61
+ ```ts
62
+ import { toCss } from '@appshell/tokens';
63
+
64
+ toCss({ base: 'midnight', accent: 'ice' });
65
+ ```
66
+
67
+ **Bases** carry surfaces, text, border and status, as a light/dark pair: `neutral`,
68
+ `midnight`. **Accents** carry the two action colours: `ice`, `ember`, `emerald`, `violet`,
69
+ `steel`. Either slot also accepts raw token values, and `overrides` adjusts a preset
70
+ without forking it.
71
+
72
+ Every combination is verified — see below — and `midnight`/`ice` is derived from the first
73
+ application scheduled to migrate.
74
+
75
+ ## Contrast is the validation that matters
76
+
77
+ Syntax checking earns little. It catches `--appshell-primary: bananas`; it misses
78
+ `--appshell-on-primary: #fff` on `--appshell-primary: #fff`, which is perfectly valid CSS
79
+ and invisible text.
80
+
81
+ ```ts
82
+ import { composeTheme, validateTheme, describeFinding } from '@appshell/tokens';
83
+
84
+ validateTheme(composeTheme({ base: 'neutral', accent: 'ice' }, 'light')).map(describeFinding);
85
+ ```
86
+
87
+ Text pairs are checked at WCAG AA (4.5:1), the focus ring at 3:1 against its surface
88
+ (1.4.11, non-text). A value that will not parse is reported rather than thrown, so one bad
89
+ token does not hide the rest.
90
+
91
+ Two things this caught while the presets were being chosen, both of which would otherwise
92
+ have shipped:
93
+
94
+ - **Tailwind's `indigo-500` fails against both black and white** — 4.28 and 4.22. There is
95
+ no legible text colour for it. The `violet` accent uses `indigo-600` instead.
96
+ - **A focus ring that is simply the accent disappears** on 7 of the 20 preset combinations.
97
+ The rings are derived instead: hue and chroma are kept, lightness moves only as far as it
98
+ must to clear 3:1.
99
+
100
+ The presets are held to the same standard as everyone else's themes — the test suite
101
+ composes all 20 combinations and asserts no findings.
102
+
103
+ ## Colour format
104
+
105
+ Values are `oklch`. Tailwind v4 consumes them directly and uses `color-mix` for its opacity
106
+ modifiers. Tailwind v3 implements `bg-primary/50` as `rgb(var(--x) / <alpha-value>)` and
107
+ needs channel-separated values, so a v3 package either forgoes opacity modifiers on themed
108
+ colours or derives channel-split locals in its own stylesheet. The contract is not deformed
109
+ for the older version.
110
+
111
+ ## Modes
112
+
113
+ Three states, not two — an explicit choice in either direction plus the system default:
114
+
115
+ ```css
116
+ :root { /* light */ }
117
+ @media (prefers-color-scheme: dark) {
118
+ :root:not([data-appshell-theme="light"]) { /* dark, unless light was chosen */ }
119
+ }
120
+ :root[data-appshell-theme="dark"] { /* dark, chosen */ }
121
+ ```
122
+
123
+ Only the roles that actually differ are restated in the dark blocks. Type and dimensions
124
+ never vary, and repeating them would triple the size of something the registry inlines into
125
+ every page it serves.
package/dist/main.js ADDED
@@ -0,0 +1 @@
1
+ !function(e,o){if("object"==typeof exports&&"object"==typeof module)module.exports=o();else if("function"==typeof define&&define.amd)define([],o);else{var r=o();for(var n in r)("object"==typeof exports?exports:e)[n]=r[n]}}(this,(()=>(()=>{"use strict";var e={d:(o,r)=>{for(var n in r)e.o(r,n)&&!e.o(o,n)&&Object.defineProperty(o,n,{enumerable:!0,get:r[n]})},o:(e,o)=>Object.prototype.hasOwnProperty.call(e,o),r:e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})}},o={};e.r(o),e.d(o,{AA_NON_TEXT:()=>u,AA_TEXT:()=>k,ACCENTS:()=>_,ACCENT_COLOR_ROLES:()=>n,BASES:()=>$,BASE_COLOR_ROLES:()=>r,DEFAULT_TYPE_AND_DIMENSIONS:()=>A,DERIVED_COLOR_ROLES:()=>c,DIMENSION_ROLES:()=>s,FOCUS_RINGS:()=>x,MUTED_AGAINST:()=>h,NON_TEXT_PAIRS:()=>d,TEXT_PAIRS:()=>l,TOKEN_ROLES:()=>t,TYPE_ROLES:()=>a,composeTheme:()=>z,contrastRatio:()=>E,cssVar:()=>i,describeFinding:()=>w,parseColor:()=>b,toCss:()=>R,validateTheme:()=>S});const r=["surface","on-surface","surface-raised","on-surface-raised","text-muted","border","danger","on-danger","warning","on-warning","success","on-success"],n=["primary","on-primary","secondary","on-secondary"],c=["primary-hover","primary-active","secondary-hover","secondary-active","focus-ring"],a=["font-body","font-mono","font-size-h1","font-size-h2","font-size-h3","font-size-h4","font-size-h5","font-size-h6","font-size-body","font-size-small","line-height-tight","line-height-normal"],s=["space-xs","space-sm","space-md","space-lg","space-xl","radius-sm","radius-md","radius-lg"],t=[...r,...n,...c,...a,...s],i=e=>`--appshell-${e}`,l=[["surface","on-surface"],["surface-raised","on-surface-raised"],["primary","on-primary"],["secondary","on-secondary"],["danger","on-danger"],["warning","on-warning"],["success","on-success"]],h=["surface","surface-raised"],d=[["surface","focus-ring"]],k=4.5,u=3,m=e=>Math.min(1,Math.max(0,e)),f=e=>e<=.04045?e/12.92:((e+.055)/1.055)**2.4,p=e=>e<=.0031308?12.92*e:1.055*e**(1/2.4)-.055,g=/^oklch\(\s*([\d.]+)%\s+([\d.]+)\s+([\d.]+)\s*\)$/i,y=/^#([0-9a-f]{3}|[0-9a-f]{6})$/i,b=e=>{const o=e.trim(),r=g.exec(o);if(r)return((e,o,r)=>{const n=r*Math.PI/180,c=o*Math.cos(n),a=o*Math.sin(n),s=(e+.3963377774*c+.2158037573*a)**3,t=(e-.1055613458*c-.0638541728*a)**3,i=(e-.0894841775*c-1.291485548*a)**3;return[m(p(4.0767416621*s-3.3077115913*t+.2309699292*i)),m(p(-1.2684380046*s+2.6097574011*t-.3413193965*i)),m(p(-.0041960863*s-.7034186147*t+1.707614701*i))]})(Number(r[1])/100,Number(r[2]),Number(r[3]));const n=y.exec(o);if(n){const e=3===n[1].length?[...n[1]].map((e=>e+e)).join(""):n[1];return[0,2,4].map((o=>parseInt(e.slice(o,o+2),16)/255))}},O=([e,o,r])=>.2126*f(e)+.7152*f(o)+.0722*f(r),E=(e,o)=>{const r=O(e),n=O(o);return(Math.max(r,n)+.05)/(Math.min(r,n)+.05)},v=(e,o,r)=>o.flatMap((([o,n])=>{const c=[o,n],a=b(e[o]),s=b(e[n]);if(!a||!s)return[{roles:c,required:r,reason:"unparseable"}];const t=E(a,s);return t>=r?[]:[{roles:c,ratio:t,required:r,reason:"below-threshold"}]})),S=e=>[...v(e,l,k),...v(e,h.map((e=>[e,"text-muted"])),k),...v(e,d,u)],w=({roles:e,ratio:o,required:r,reason:n})=>"unparseable"===n?`${e[0]} / ${e[1]}: could not be measured — one of the values is not a colour this understands.`:`${e[0]} / ${e[1]}: ${o?.toFixed(2)}:1, needs ${r}:1.`,$={neutral:{light:{surface:"oklch(99.0% 0.000 0.0)","on-surface":"oklch(21.0% 0.000 0.0)","surface-raised":"oklch(96.5% 0.000 0.0)","on-surface-raised":"oklch(21.0% 0.000 0.0)","text-muted":"oklch(50.0% 0.000 0.0)",border:"oklch(90.0% 0.000 0.0)",danger:"oklch(57.7% 0.215 27.3)","on-danger":"oklch(98.5% 0.000 89.9)",warning:"oklch(55.5% 0.146 49.0)","on-warning":"oklch(98.5% 0.000 89.9)",success:"oklch(50.8% 0.105 165.6)","on-success":"oklch(98.5% 0.000 89.9)"},dark:{surface:"oklch(18.0% 0.000 0.0)","on-surface":"oklch(95.0% 0.000 0.0)","surface-raised":"oklch(23.0% 0.000 0.0)","on-surface-raised":"oklch(95.0% 0.000 0.0)","text-muted":"oklch(68.0% 0.000 0.0)",border:"oklch(32.0% 0.000 0.0)",danger:"oklch(71.1% 0.166 22.2)","on-danger":"oklch(17.9% 0.006 285.8)",warning:"oklch(83.7% 0.164 84.4)","on-warning":"oklch(17.9% 0.006 285.8)",success:"oklch(77.3% 0.153 163.2)","on-success":"oklch(17.9% 0.006 285.8)"}},midnight:{light:{surface:"oklch(100.0% 0.000 89.9)","on-surface":"oklch(21.0% 0.006 285.9)","surface-raised":"oklch(96.7% 0.001 286.4)","on-surface-raised":"oklch(21.0% 0.006 285.9)","text-muted":"oklch(47.4% 0.013 285.9)",border:"oklch(92.0% 0.004 286.3)",danger:"oklch(57.7% 0.215 27.3)","on-danger":"oklch(98.5% 0.000 89.9)",warning:"oklch(55.5% 0.146 49.0)","on-warning":"oklch(98.5% 0.000 89.9)",success:"oklch(50.8% 0.105 165.6)","on-success":"oklch(98.5% 0.000 89.9)"},dark:{surface:"oklch(14.1% 0.004 285.8)","on-surface":"oklch(94.5% 0.007 286.3)","surface-raised":"oklch(18.9% 0.010 285.4)","on-surface-raised":"oklch(94.5% 0.007 286.3)","text-muted":"oklch(64.0% 0.015 286.0)",border:"oklch(27.6% 0.013 285.5)",danger:"oklch(71.1% 0.166 22.2)","on-danger":"oklch(17.9% 0.006 285.8)",warning:"oklch(83.7% 0.164 84.4)","on-warning":"oklch(17.9% 0.006 285.8)",success:"oklch(77.3% 0.153 163.2)","on-success":"oklch(17.9% 0.006 285.8)"}}},_={ice:{primary:"oklch(68.5% 0.148 237.3)","on-primary":"oklch(17.9% 0.006 285.8)",secondary:"oklch(76.9% 0.165 70.1)","on-secondary":"oklch(17.9% 0.006 285.8)"},ember:{primary:"oklch(57.7% 0.215 27.3)","on-primary":"oklch(98.5% 0.000 89.9)",secondary:"oklch(68.5% 0.148 237.3)","on-secondary":"oklch(17.9% 0.006 285.8)"},emerald:{primary:"oklch(69.6% 0.149 162.5)","on-primary":"oklch(17.9% 0.006 285.8)",secondary:"oklch(76.9% 0.165 70.1)","on-secondary":"oklch(17.9% 0.006 285.8)"},violet:{primary:"oklch(51.1% 0.230 277.0)","on-primary":"oklch(98.5% 0.000 89.9)",secondary:"oklch(76.9% 0.165 70.1)","on-secondary":"oklch(17.9% 0.006 285.8)"},steel:{primary:"oklch(44.2% 0.015 285.8)","on-primary":"oklch(98.5% 0.000 89.9)",secondary:"oklch(76.9% 0.165 70.1)","on-secondary":"oklch(17.9% 0.006 285.8)"}},x={neutral:{ice:{light:"oklch(64.5% 0.148 237.3)",dark:"oklch(68.5% 0.148 237.3)"},ember:{light:"oklch(57.7% 0.215 27.3)",dark:"oklch(57.7% 0.215 27.3)"},emerald:{light:"oklch(63.6% 0.149 162.5)",dark:"oklch(69.6% 0.149 162.5)"},violet:{light:"oklch(51.1% 0.230 277.0)",dark:"oklch(53.1% 0.230 277.0)"},steel:{light:"oklch(44.2% 0.015 285.8)",dark:"oklch(50.2% 0.015 285.8)"}},midnight:{ice:{light:"oklch(64.5% 0.148 237.3)",dark:"oklch(68.5% 0.148 237.3)"},ember:{light:"oklch(57.7% 0.215 27.3)",dark:"oklch(57.7% 0.215 27.3)"},emerald:{light:"oklch(63.6% 0.149 162.5)",dark:"oklch(69.6% 0.149 162.5)"},violet:{light:"oklch(51.1% 0.230 277.0)",dark:"oklch(51.1% 0.230 277.0)"},steel:{light:"oklch(44.2% 0.015 285.8)",dark:"oklch(48.2% 0.015 285.8)"}}},T=(e,o,r)=>`color-mix(in oklch, var(${i(e)}), ${o} ${r})`,j=(e,o)=>{const r="dark"===e?"white":"black",n="dark"===e?"black":"white";return{"primary-hover":T("primary",r,"12%"),"primary-active":T("primary",n,"10%"),"secondary-hover":T("secondary",r,"12%"),"secondary-active":T("secondary",n,"10%"),"focus-ring":o}},A={"font-body":"system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif","font-mono":"ui-monospace, SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace","font-size-h1":"2.25rem","font-size-h2":"1.875rem","font-size-h3":"1.5rem","font-size-h4":"1.25rem","font-size-h5":"1.125rem","font-size-h6":"1rem","font-size-body":"1rem","font-size-small":"0.875rem","line-height-tight":"1.25","line-height-normal":"1.6","space-xs":"0.25rem","space-sm":"0.5rem","space-md":"1rem","space-lg":"1.5rem","space-xl":"2.5rem","radius-sm":"0.25rem","radius-md":"0.5rem","radius-lg":"1rem"},N=(e,o,r)=>{const{base:n,accent:c}=e;if("string"==typeof n&&"string"==typeof c){const e=x[n]?.[c]?.[r];if(e)return e}return o.primary},z=(e,o)=>{const r=((e,o)=>{if("string"!=typeof e)return e[o];const r=$[e];if(!r)throw new Error(`Unknown base '${e}'. Available: ${Object.keys($).join(", ")}.`);return r[o]})(e.base,o),n=(e=>{if("string"!=typeof e)return e;const o=_[e];if(!o)throw new Error(`Unknown accent '${e}'. Available: ${Object.keys(_).join(", ")}.`);return o})(e.accent);return{...A,...r,...n,...j(o,N(e,n,o)),...e.overrides}},M=(e,o,r)=>[`${r}${e} {`,...Object.entries(o).map((([e,o])=>`${r} ${i(e)}: ${o};`)),`${r}}`].join("\n"),R=e=>{const o=z(e,"light"),r=z(e,"dark"),n=(c=o,a=r,Object.fromEntries(Object.entries(a).filter((([e,o])=>c[e]!==o))));var c,a;return[M(":root",o,""),"","@media (prefers-color-scheme: dark) {",M(':root:not([data-appshell-theme="light"])',n," "),"}","",M(':root[data-appshell-theme="dark"]',n,""),""].join("\n")};return o})()));
@@ -0,0 +1,52 @@
1
+ /**
2
+ * The token contract: the complete, fixed vocabulary a package may consume and an
3
+ * Application must supply.
4
+ *
5
+ * Small and stable is the property that matters. A package authors against these names
6
+ * with fallbacks (`var(--appshell-primary, #0af)`) and renders standalone; an Application
7
+ * supplies the values and every package it composes moves together.
8
+ */
9
+ export type Mode = 'light' | 'dark';
10
+ /** Colour roles an Application's *base* supplies — everything not tied to the accent. */
11
+ export declare const BASE_COLOR_ROLES: readonly ["surface", "on-surface", "surface-raised", "on-surface-raised", "text-muted", "border", "danger", "on-danger", "warning", "on-warning", "success", "on-success"];
12
+ /** Colour roles an Application's *accent* supplies. */
13
+ export declare const ACCENT_COLOR_ROLES: readonly ["primary", "on-primary", "secondary", "on-secondary"];
14
+ /**
15
+ * Derived rather than authored. Hover and active are computed from their accent with
16
+ * `color-mix`, and the focus ring is picked per base+accent so it stays visible. A theme
17
+ * may override any of them; none has to be supplied.
18
+ */
19
+ export declare const DERIVED_COLOR_ROLES: readonly ["primary-hover", "primary-active", "secondary-hover", "secondary-active", "focus-ring"];
20
+ /**
21
+ * Named for their role, not their size. A numeric scale would reintroduce exactly the
22
+ * divergence this contract exists to prevent — one author mapping `h1` to `2xl` and
23
+ * another to `xl` is how headings stop matching across a composed page.
24
+ */
25
+ export declare const TYPE_ROLES: readonly ["font-body", "font-mono", "font-size-h1", "font-size-h2", "font-size-h3", "font-size-h4", "font-size-h5", "font-size-h6", "font-size-body", "font-size-small", "line-height-tight", "line-height-normal"];
26
+ /** Genuinely a scale. Nobody wants `--appshell-space-card-padding`. */
27
+ export declare const DIMENSION_ROLES: readonly ["space-xs", "space-sm", "space-md", "space-lg", "space-xl", "radius-sm", "radius-md", "radius-lg"];
28
+ export type BaseColorRole = (typeof BASE_COLOR_ROLES)[number];
29
+ export type AccentColorRole = (typeof ACCENT_COLOR_ROLES)[number];
30
+ export type DerivedColorRole = (typeof DERIVED_COLOR_ROLES)[number];
31
+ export type TypeRole = (typeof TYPE_ROLES)[number];
32
+ export type DimensionRole = (typeof DIMENSION_ROLES)[number];
33
+ export type TokenRole = BaseColorRole | AccentColorRole | DerivedColorRole | TypeRole | DimensionRole;
34
+ export declare const TOKEN_ROLES: readonly TokenRole[];
35
+ export type BaseTokens = Record<BaseColorRole, string>;
36
+ export type AccentTokens = Record<AccentColorRole, string>;
37
+ export type Theme = Record<TokenRole, string>;
38
+ /** The custom property a role is published as. */
39
+ export declare const cssVar: (role: TokenRole) => string;
40
+ /**
41
+ * Pairs the registry checks for contrast. Because every role that carries text has an
42
+ * `on-` partner, a theme cannot express illegible text without failing this list — which
43
+ * is the failure that actually reaches users, and the one a CSS parser alone never catches.
44
+ */
45
+ export declare const TEXT_PAIRS: readonly (readonly [TokenRole, TokenRole])[];
46
+ /** Muted text has no `on-` partner; it is read against both surfaces. */
47
+ export declare const MUTED_AGAINST: readonly TokenRole[];
48
+ /** Non-text, so 3:1 rather than 4.5:1 — WCAG 1.4.11. */
49
+ export declare const NON_TEXT_PAIRS: readonly (readonly [TokenRole, TokenRole])[];
50
+ export declare const AA_TEXT = 4.5;
51
+ export declare const AA_NON_TEXT = 3;
52
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,IAAI,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpC,yFAAyF;AACzF,eAAO,MAAM,gBAAgB,4KAanB,CAAC;AAEX,uDAAuD;AACvD,eAAO,MAAM,kBAAkB,iEAAkE,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,mGAMtB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,UAAU,qNAab,CAAC;AAEX,uEAAuE;AACvE,eAAO,MAAM,eAAe,8GASlB,CAAC;AAEX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAC9D,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAClE,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AACnD,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,MAAM,MAAM,SAAS,GACjB,aAAa,GACb,eAAe,GACf,gBAAgB,GAChB,QAAQ,GACR,aAAa,CAAC;AAElB,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAM3C,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AACvD,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;AAC3D,MAAM,MAAM,KAAK,GAAG,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAE9C,kDAAkD;AAClD,eAAO,MAAM,MAAM,SAAU,SAAS,KAAG,MAA8B,CAAC;AAExE;;;;GAIG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,EAQlE,CAAC;AAEF,yEAAyE;AACzE,eAAO,MAAM,aAAa,EAAE,SAAS,SAAS,EAAkC,CAAC;AAEjF,wDAAwD;AACxD,eAAO,MAAM,cAAc,EAAE,SAAS,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,EAEtE,CAAC;AAEF,eAAO,MAAM,OAAO,MAAM,CAAC;AAC3B,eAAO,MAAM,WAAW,IAAI,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Contrast checking for a theme.
3
+ *
4
+ * Syntax validation on its own earns little: it catches `--appshell-primary: bananas` and
5
+ * misses `--appshell-on-primary: #fff` on `--appshell-primary: #fff`, which is valid CSS
6
+ * and invisible text. Checking the `on-` pairs catches the failure that reaches users, and
7
+ * subsumes syntax validation for free — a value that will not parse cannot be measured.
8
+ */
9
+ import { type Theme, type TokenRole } from './contract';
10
+ type Rgb = [number, number, number];
11
+ /** Returns undefined rather than throwing — an unmeasurable value is reported, not fatal. */
12
+ export declare const parseColor: (value: string) => Rgb | undefined;
13
+ /** WCAG 2.1 contrast ratio, 1–21. */
14
+ export declare const contrastRatio: (a: Rgb, b: Rgb) => number;
15
+ export type ContrastFinding = {
16
+ roles: [TokenRole, TokenRole];
17
+ ratio?: number;
18
+ required: number;
19
+ reason: 'below-threshold' | 'unparseable';
20
+ };
21
+ /**
22
+ * Every finding for a theme. Empty means it passes.
23
+ *
24
+ * `color-mix` values are skipped rather than failed: hover and active are derived from
25
+ * roles that are themselves checked, and resolving them needs a browser.
26
+ */
27
+ export declare const validateTheme: (theme: Theme) => ContrastFinding[];
28
+ export declare const describeFinding: ({ roles, ratio, required, reason }: ContrastFinding) => string;
29
+ export {};
30
+ //# sourceMappingURL=contrast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contrast.d.ts","sourceRoot":"","sources":["../../src/contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAML,KAAK,KAAK,EACV,KAAK,SAAS,EACf,MAAM,YAAY,CAAC;AAEpB,KAAK,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AA2BpC,6FAA6F;AAC7F,eAAO,MAAM,UAAU,UAAW,MAAM,KAAG,GAAG,GAAG,SAgBhD,CAAC;AAKF,qCAAqC;AACrC,eAAO,MAAM,aAAa,MAAO,GAAG,KAAK,GAAG,KAAG,MAK9C,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,iBAAiB,GAAG,aAAa,CAAC;CAC3C,CAAC;AAqBF;;;;;GAKG;AACH,eAAO,MAAM,aAAa,UAAW,KAAK,KAAG,eAAe,EAQ3D,CAAC;AAEF,eAAO,MAAM,eAAe,uCAAwC,eAAe,KAAG,MAGP,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from './contract';
2
+ export { contrastRatio, describeFinding, parseColor, validateTheme, type ContrastFinding, } from './contrast';
3
+ export { ACCENTS, BASES, FOCUS_RINGS } from './presets';
4
+ export { composeTheme, DEFAULT_TYPE_AND_DIMENSIONS, toCss, type ThemeSelection } from './theme';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,aAAa,EACb,eAAe,EACf,UAAU,EACV,aAAa,EACb,KAAK,eAAe,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxD,OAAO,EAAE,YAAY,EAAE,2BAA2B,EAAE,KAAK,EAAE,KAAK,cAAc,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { AccentTokens, BaseTokens, Mode } from './contract';
2
+ export declare const BASES: Record<string, Record<Mode, BaseTokens>>;
3
+ export declare const ACCENTS: Record<string, AccentTokens>;
4
+ export declare const FOCUS_RINGS: Record<string, Record<string, Record<Mode, string>>>;
5
+ //# sourceMappingURL=presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presets.d.ts","sourceRoot":"","sources":["../../src/presets.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEjE,eAAO,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CA6D1D,CAAC;AAIF,eAAO,MAAM,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA+BhD,CAAC;AAKF,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CA6C5E,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { type AccentTokens, type BaseTokens, type Mode, type Theme } from './contract';
2
+ export type ThemeSelection = {
3
+ /** A base preset name, or the base's own token values. */
4
+ base: string | Record<Mode, BaseTokens>;
5
+ /** An accent preset name, or the accent's own token values. */
6
+ accent: string | AccentTokens;
7
+ /** Overrides applied last, so an Application can adjust a preset without forking it. */
8
+ overrides?: Partial<Theme>;
9
+ };
10
+ /** Type and dimensions do not vary by mode, and no preset currently changes them. */
11
+ export declare const DEFAULT_TYPE_AND_DIMENSIONS: Record<string, string>;
12
+ /** The full token map for one mode. */
13
+ export declare const composeTheme: (selection: ThemeSelection, mode: Mode) => Theme;
14
+ /**
15
+ * Three states, not two: an explicit choice in either direction, and the system default
16
+ * when nothing is stamped on the root. A theme that only handled `prefers-color-scheme`
17
+ * would give a viewer no way to override it.
18
+ */
19
+ export declare const toCss: (selection: ThemeSelection) => string;
20
+ //# sourceMappingURL=theme.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,IAAI,EACT,KAAK,KAAK,EAEX,MAAM,YAAY,CAAC;AAGpB,MAAM,MAAM,cAAc,GAAG;IAC3B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACxC,+DAA+D;IAC/D,MAAM,EAAE,MAAM,GAAG,YAAY,CAAC;IAC9B,wFAAwF;IACxF,SAAS,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;CAC5B,CAAC;AAwBF,qFAAqF;AACrF,eAAO,MAAM,2BAA2B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsB9D,CAAC;AAwCF,uCAAuC;AACvC,eAAO,MAAM,YAAY,cAAe,cAAc,QAAQ,IAAI,KAAG,KAWpE,CAAC;AAmBF;;;;GAIG;AACH,eAAO,MAAM,KAAK,cAAe,cAAc,KAAG,MAgBjD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@appshell/tokens",
3
+ "version": "1.0.0-alpha.15",
4
+ "description": "The Appshell design token contract, its presets, and contrast validation",
5
+ "main": "dist/main.js",
6
+ "types": "dist/types/index.d.ts",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/appshellhq/appshell.git"
10
+ },
11
+ "author": "Robert Hamilton <rh@navaris.com>",
12
+ "publishConfig": {
13
+ "access": "public",
14
+ "registry": "https://registry.npmjs.org/"
15
+ },
16
+ "scripts": {
17
+ "build": "webpack --mode production && npm run build:types",
18
+ "build:types": "tsc --emitDeclarationOnly",
19
+ "clean": "rm -rf dist",
20
+ "prebuild": "npm run clean",
21
+ "start": "webpack --watch --mode development && npm run build:types",
22
+ "stats": "webpack --json > stats.json"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "keywords": [
28
+ "design tokens",
29
+ "theming",
30
+ "micro-frontends",
31
+ "appshell"
32
+ ],
33
+ "license": "MIT"
34
+ }