@handamade/psi-tokens 0.22.0 → 0.24.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 +1 -1
- package/dist/components/button.js +12 -0
- package/dist/components/button.vars.css +5 -0
- package/dist/components/menu.js +3 -0
- package/dist/components/menu.vars.css +1 -0
- package/dist/components.css +6 -0
- package/dist/guidance.json +42 -1
- package/dist/scope-map.json +3 -0
- package/dist/utilities.css +14 -0
- package/llms.txt +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ Then set the theme on your root element:
|
|
|
29
29
|
|
|
30
30
|
All tokens are CSS custom properties: `--psi-bg-primary`, `--psi-fg-secondary`, `--psi-space-12`, etc.
|
|
31
31
|
|
|
32
|
-
**utilities.css is required**: it carries `.psi-container`, `.psi-text-*`, `.psi-display-*` utility classes and the `prefers-reduced-motion` zeroing for all duration tokens (D30) — required for NavBar rendering and accessibility compliance.
|
|
32
|
+
**utilities.css is required**: it carries `.psi-container`, `.psi-text-*`, `.psi-display-*`, `.psi-fg-*` (D94) utility classes and the `prefers-reduced-motion` zeroing for all duration tokens (D30) — required for NavBar rendering and accessibility compliance.
|
|
33
33
|
|
|
34
34
|
## Creating a custom theme
|
|
35
35
|
|
|
@@ -73,4 +73,16 @@ export const buttonVars = {
|
|
|
73
73
|
"32-font": "var(--psi-control-32-font)",
|
|
74
74
|
"40-font": "var(--psi-control-40-font)",
|
|
75
75
|
"48-font": "var(--psi-control-48-font)",
|
|
76
|
+
// ── Icon size (D97) — a direct svg child, half the height: 12/16/20/24.
|
|
77
|
+
// Derived, not restated, so a height retuned at the root moves the icon.
|
|
78
|
+
// The calc() resolves where it is declared: a retune on a narrower scope
|
|
79
|
+
// must set the icon size there too. IconButton does not bind it.
|
|
80
|
+
"24-icon-size": "calc(var(--psi-button-24-height) / 2)",
|
|
81
|
+
"32-icon-size": "calc(var(--psi-button-32-height) / 2)",
|
|
82
|
+
"40-icon-size": "calc(var(--psi-button-40-height) / 2)",
|
|
83
|
+
"48-icon-size": "calc(var(--psi-button-48-height) / 2)",
|
|
84
|
+
// ── Label cap (D98) — off by default. A consumer sets it on its own class;
|
|
85
|
+
// it caps a label wrapped in a direct <span> and ends it in an ellipsis.
|
|
86
|
+
// Size-invariant: the surrounding layout sets the cap, not the button.
|
|
87
|
+
"label-max-width": "none",
|
|
76
88
|
};
|
|
@@ -56,5 +56,10 @@
|
|
|
56
56
|
--psi-button-32-font: var(--psi-control-32-font);
|
|
57
57
|
--psi-button-40-font: var(--psi-control-40-font);
|
|
58
58
|
--psi-button-48-font: var(--psi-control-48-font);
|
|
59
|
+
--psi-button-24-icon-size: calc(var(--psi-button-24-height) / 2);
|
|
60
|
+
--psi-button-32-icon-size: calc(var(--psi-button-32-height) / 2);
|
|
61
|
+
--psi-button-40-icon-size: calc(var(--psi-button-40-height) / 2);
|
|
62
|
+
--psi-button-48-icon-size: calc(var(--psi-button-48-height) / 2);
|
|
63
|
+
--psi-button-label-max-width: none;
|
|
59
64
|
}
|
|
60
65
|
}
|
package/dist/components/menu.js
CHANGED
|
@@ -18,4 +18,7 @@ export const menuVars = {
|
|
|
18
18
|
"separator-border": "var(--psi-border-faint)",
|
|
19
19
|
// D82 — the ring on a focused item; until then the browser drew its own.
|
|
20
20
|
"focus-ring": "var(--psi-border-focus)",
|
|
21
|
+
// D99 — MenuText, a line that is not an action. fgSecondary on the menu
|
|
22
|
+
// surface (bgSecondary) is already gated at 4.5:1, so no new pair.
|
|
23
|
+
"text-fg": "var(--psi-fg-secondary)",
|
|
21
24
|
};
|
package/dist/components.css
CHANGED
|
@@ -79,6 +79,11 @@
|
|
|
79
79
|
--psi-button-32-font: var(--psi-control-32-font);
|
|
80
80
|
--psi-button-40-font: var(--psi-control-40-font);
|
|
81
81
|
--psi-button-48-font: var(--psi-control-48-font);
|
|
82
|
+
--psi-button-24-icon-size: calc(var(--psi-button-24-height) / 2);
|
|
83
|
+
--psi-button-32-icon-size: calc(var(--psi-button-32-height) / 2);
|
|
84
|
+
--psi-button-40-icon-size: calc(var(--psi-button-40-height) / 2);
|
|
85
|
+
--psi-button-48-icon-size: calc(var(--psi-button-48-height) / 2);
|
|
86
|
+
--psi-button-label-max-width: none;
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
|
|
@@ -226,6 +231,7 @@
|
|
|
226
231
|
--psi-menu-item-fg-disabled: var(--psi-fg-quaternary);
|
|
227
232
|
--psi-menu-separator-border: var(--psi-border-faint);
|
|
228
233
|
--psi-menu-focus-ring: var(--psi-border-focus);
|
|
234
|
+
--psi-menu-text-fg: var(--psi-fg-secondary);
|
|
229
235
|
}
|
|
230
236
|
}
|
|
231
237
|
|
package/dist/guidance.json
CHANGED
|
@@ -151,6 +151,12 @@
|
|
|
151
151
|
12,
|
|
152
152
|
16
|
|
153
153
|
],
|
|
154
|
+
"iconSize": [
|
|
155
|
+
12,
|
|
156
|
+
16,
|
|
157
|
+
20,
|
|
158
|
+
24
|
|
159
|
+
],
|
|
154
160
|
"gap": [
|
|
155
161
|
4,
|
|
156
162
|
8,
|
|
@@ -186,11 +192,13 @@
|
|
|
186
192
|
},
|
|
187
193
|
"note": "Per-size geometry is data, not CSS literals (D54). Arrays are indexed by sizes[]. Read a value as --psi-control-{size}-{prop} for the label ramp and --psi-control-value-{size}-{prop} for the value ramp; components alias these as --psi-{component}-{size}-{prop}, which is the layer to override. The value ramp is one step tighter than the label ramp because a left-aligned value wants less air than a centred label (D55).",
|
|
188
194
|
"iconInset": "A leading icon sits one step closer to the edge than text — an icon is a solid shape with no side bearing (D55). Applied by .size{n}:has(> svg:first-child) on Button. IconButton is height-only: it is square with padding 0, so no padding/gap/font token applies to it.",
|
|
195
|
+
"iconSize": "A direct svg child of a Button renders at --psi-button-{size}-icon-size, half the button's height (12/16/20/24 at 24/32/40/48), whatever the icon's own size prop: CSS width and height beat the attributes an icon writes (D97). Pass icons without a size. A wrapped icon is not sized, as it gets no inset. IconButton does not bind the ramp; its icon keeps its own size (20 by default).",
|
|
196
|
+
"labelMaxWidth": "A label wrapped in a direct <span> is capped by --psi-button-label-max-width, default none, and ends in an ellipsis past the cap (D98). Set the token on your own class; it is size-invariant. A bare text label is not capped (wrap it, as a trailing icon already requires). The full text stays the accessible name, and Psi adds no title.",
|
|
189
197
|
"iconInsetLimits": "The selector matches on element order, and :first-child ignores text nodes. Two consequences. (1) An icon wrapped in a <span> never matches — fails safe, no inset; set --psi-button-{size}-padding-inline-icon yourself. (2) A TRAILING icon (<Button>Next<IconChevronRight /></Button>) makes the svg the first ELEMENT child, so the rule fires and narrows the start (text) side while the icon keeps full padding — the inverse of the intent. CSS cannot tell the two apart without a DOM signal. Put the icon first, or override --psi-button-{size}-padding-inline-icon to match --psi-button-{size}-padding-inline to disable the inset.",
|
|
190
198
|
"radius": "Control radius is size-invariant (D56) — it does NOT ride the size ramp, because border-radius sits on each component's base rule and all four sizes resolve to radius-8. One dial, --psi-control-radius, is the layer to override, aliased per component: --psi-button-radius (Button, and IconButton via the same token), --psi-input-radius, --psi-select-radius, --psi-checkbox-box-radius, and --psi-tooltip-radius. Checkbox and Tooltip cap themselves with min(var(--psi-control-radius), var(--psi-radius-4)) and min(…, var(--psi-radius-6)) respectively so neither over-rounds a small object — but the published radius scale's floor is radius-4, so Checkbox's cap is a no-op for every on-scale value a theme can set; it only bites at an off-scale value like 0px. Tooltip's higher ceiling does track a sharper theme, down to radius-4. Tag and Switch stay --psi-radius-full: pill-ness is component identity, not theme expression. A theme retunes every control's shape by setting --psi-control-radius once. Psi now has two radius dials, not one: --psi-control-radius (here, for interactive controls) and --psi-surface-radius (for elevated surfaces — Dialog, Panel, Menu). --psi-card-radius sits on neither dial, so a theme that retunes controls or surfaces leaves Card at its own default of radius-8."
|
|
191
199
|
},
|
|
192
200
|
"utilities": {
|
|
193
|
-
"note": "Every utility class shipped in utilities.css, generated from the same source as the CSS (D79). Scaled utility families set one property each and create no layout context: .psi-gap-* sets gap only, so pair it with display:flex or grid yourself. The bare/fixed utilities (.psi-container, .psi-media-tint, .psi-sr-only) set several. Import @handamade/psi-tokens/utilities.css — it is required, not optional; .psi-container and the reduced-motion duration zeroing live there.",
|
|
201
|
+
"note": "Every utility class shipped in utilities.css, generated from the same source as the CSS (D79). Scaled utility families set one property each and create no layout context: .psi-gap-* sets gap only, so pair it with display:flex or grid yourself. The bare/fixed utilities (.psi-container, .psi-media-tint, .psi-sr-only) set several. Every utility sits in @layer psi.utilities, above @layer psi.components, so on a component root it beats the component's own value (D94). Import @handamade/psi-tokens/utilities.css — it is required, not optional; .psi-container and the reduced-motion duration zeroing live there.",
|
|
194
202
|
"families": [
|
|
195
203
|
{
|
|
196
204
|
"prefix": "psi-gap-*",
|
|
@@ -486,6 +494,26 @@
|
|
|
486
494
|
"psi-my-144"
|
|
487
495
|
]
|
|
488
496
|
},
|
|
497
|
+
{
|
|
498
|
+
"prefix": "psi-fg-*",
|
|
499
|
+
"property": "color",
|
|
500
|
+
"scale": null,
|
|
501
|
+
"classes": [
|
|
502
|
+
"psi-fg-primary",
|
|
503
|
+
"psi-fg-secondary",
|
|
504
|
+
"psi-fg-tertiary",
|
|
505
|
+
"psi-fg-quaternary",
|
|
506
|
+
"psi-fg-primary-inverted",
|
|
507
|
+
"psi-fg-static-white",
|
|
508
|
+
"psi-fg-static-black",
|
|
509
|
+
"psi-fg-on-accent",
|
|
510
|
+
"psi-fg-accent",
|
|
511
|
+
"psi-fg-success",
|
|
512
|
+
"psi-fg-warning",
|
|
513
|
+
"psi-fg-danger",
|
|
514
|
+
"psi-fg-on-inverted"
|
|
515
|
+
]
|
|
516
|
+
},
|
|
489
517
|
{
|
|
490
518
|
"prefix": "psi-text-*",
|
|
491
519
|
"property": "font",
|
|
@@ -562,6 +590,19 @@
|
|
|
562
590
|
"psi-display-32-32-extrabold",
|
|
563
591
|
"psi-display-36-64-black",
|
|
564
592
|
"psi-display-56-128-black",
|
|
593
|
+
"psi-fg-accent",
|
|
594
|
+
"psi-fg-danger",
|
|
595
|
+
"psi-fg-on-accent",
|
|
596
|
+
"psi-fg-on-inverted",
|
|
597
|
+
"psi-fg-primary",
|
|
598
|
+
"psi-fg-primary-inverted",
|
|
599
|
+
"psi-fg-quaternary",
|
|
600
|
+
"psi-fg-secondary",
|
|
601
|
+
"psi-fg-static-black",
|
|
602
|
+
"psi-fg-static-white",
|
|
603
|
+
"psi-fg-success",
|
|
604
|
+
"psi-fg-tertiary",
|
|
605
|
+
"psi-fg-warning",
|
|
565
606
|
"psi-gap-0",
|
|
566
607
|
"psi-gap-12",
|
|
567
608
|
"psi-gap-120",
|
package/dist/scope-map.json
CHANGED
package/dist/utilities.css
CHANGED
|
@@ -121,6 +121,20 @@
|
|
|
121
121
|
.psi-mx-144 { margin-inline: var(--psi-space-144); }
|
|
122
122
|
.psi-my-144 { margin-block: var(--psi-space-144); }
|
|
123
123
|
|
|
124
|
+
.psi-fg-primary { color: var(--psi-fg-primary); }
|
|
125
|
+
.psi-fg-secondary { color: var(--psi-fg-secondary); }
|
|
126
|
+
.psi-fg-tertiary { color: var(--psi-fg-tertiary); }
|
|
127
|
+
.psi-fg-quaternary { color: var(--psi-fg-quaternary); }
|
|
128
|
+
.psi-fg-primary-inverted { color: var(--psi-fg-primary-inverted); }
|
|
129
|
+
.psi-fg-static-white { color: var(--psi-fg-static-white); }
|
|
130
|
+
.psi-fg-static-black { color: var(--psi-fg-static-black); }
|
|
131
|
+
.psi-fg-on-accent { color: var(--psi-fg-on-accent); }
|
|
132
|
+
.psi-fg-accent { color: var(--psi-fg-accent); }
|
|
133
|
+
.psi-fg-success { color: var(--psi-fg-success); }
|
|
134
|
+
.psi-fg-warning { color: var(--psi-fg-warning); }
|
|
135
|
+
.psi-fg-danger { color: var(--psi-fg-danger); }
|
|
136
|
+
.psi-fg-on-inverted { color: var(--psi-fg-on-inverted); }
|
|
137
|
+
|
|
124
138
|
.psi-text-12-16-regular { font: var(--psi-text-12-16-regular); }
|
|
125
139
|
.psi-text-12-16-medium { font: var(--psi-text-12-16-medium); }
|
|
126
140
|
.psi-text-14-20-regular { font: var(--psi-text-14-20-regular); }
|
package/llms.txt
CHANGED
|
@@ -14,6 +14,7 @@ OKLCH formula-based design tokens. Code is the source of truth; all artifacts ge
|
|
|
14
14
|
- Override --psi-{component}-* tokens for one-off component theming; never fork semantic tokens.
|
|
15
15
|
- Scales are pixel-true: --psi-space-8 is 8px (0.5rem). Typography: --psi-text-{size}-{lh}-{weight}.
|
|
16
16
|
- Themes: import base.css + {theme}.css + components.css + utilities.css; set data-psi-theme="{name}" ({light,dark,acme,ember}).
|
|
17
|
+
- Foreground utilities (D94): `.psi-fg-<role>` sets `color: var(--psi-fg-<role>)` for every fg token (`psi-fg-primary`, `psi-fg-secondary`, `psi-fg-tertiary`, `psi-fg-on-accent`, `psi-fg-danger`, …). Like every utility it sits in `@layer psi.utilities` and beats a component's own colour.
|
|
17
18
|
- Scoped tokens bind only their declared property groups (D46): fg* → text properties, bg*/fill*/scrim* → surfaces, border* → borders, --psi-space-* → gaps/padding/margin. Cross-family bindings that are design decisions carry both scopes (fgDanger: text+border for error borders; fillAccent: surface+border for selected controls). The token build enforces component bindings; the repo's stylelint rule (psi/token-scopes) enforces first-party consumer CSS (component modules + apps/). External consumers aren't gated automatically — dist/scope-map.json carries the machine-readable scopes to lint against.
|
|
18
19
|
|
|
19
20
|
## Motion (--psi-duration-*, --psi-ease-*)
|