@handamade/psi-tokens 0.20.0 → 0.21.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/dist/base.css +3 -0
- package/dist/components/app-shell.d.ts +16 -0
- package/dist/components/app-shell.js +21 -0
- package/dist/components/app-shell.vars.css +8 -0
- package/dist/components/banner.d.ts +11 -0
- package/dist/components/banner.js +21 -0
- package/dist/components/banner.vars.css +13 -0
- package/dist/components/dialog.js +3 -0
- package/dist/components/dialog.vars.css +1 -0
- package/dist/components/field.js +5 -0
- package/dist/components/field.vars.css +1 -0
- package/dist/components/inline-alert.d.ts +12 -0
- package/dist/components/inline-alert.js +27 -0
- package/dist/components/inline-alert.vars.css +18 -0
- package/dist/components/menu.js +2 -0
- package/dist/components/menu.vars.css +1 -0
- package/dist/components/nav-tree.d.ts +12 -0
- package/dist/components/nav-tree.js +26 -0
- package/dist/components/nav-tree.vars.css +12 -0
- package/dist/components/navbar.js +3 -0
- package/dist/components/navbar.vars.css +1 -0
- package/dist/components/navtree.d.ts +12 -0
- package/dist/components/navtree.js +26 -0
- package/dist/components/navtree.vars.css +12 -0
- package/dist/components/registry.js +12 -0
- package/dist/components/skeleton.d.ts +4 -0
- package/dist/components/skeleton.js +7 -0
- package/dist/components/skeleton.vars.css +6 -0
- package/dist/components/skip-link.d.ts +6 -0
- package/dist/components/skip-link.js +12 -0
- package/dist/components/skip-link.vars.css +9 -0
- package/dist/components/skiplink.d.ts +6 -0
- package/dist/components/skiplink.js +12 -0
- package/dist/components/skiplink.vars.css +9 -0
- package/dist/components.css +76 -0
- package/dist/contrast-matrix.js +8 -0
- package/dist/dsl/validator.js +1 -1
- package/dist/dtcg/acme.json +18 -2
- package/dist/dtcg/dark.json +18 -2
- package/dist/dtcg/ember.json +18 -2
- package/dist/dtcg/light.json +18 -2
- package/dist/guidance.json +2 -2
- package/dist/resolved/acme.json +9 -2
- package/dist/resolved/dark.json +9 -2
- package/dist/resolved/ember.json +9 -2
- package/dist/resolved/light.json +9 -2
- package/dist/scope-map.json +106 -2
- package/dist/themes/dark.js +2 -2
- package/dist/themes/light.js +4 -2
- package/llms.txt +5 -0
- package/package.json +1 -1
package/dist/base.css
CHANGED
|
@@ -85,6 +85,9 @@
|
|
|
85
85
|
--psi-z-nav: 100;
|
|
86
86
|
--psi-z-overlay: 1000;
|
|
87
87
|
--psi-z-tooltip: 1100;
|
|
88
|
+
--psi-focus-ring-width: 2px;
|
|
89
|
+
--psi-focus-ring-offset: 2px;
|
|
90
|
+
--psi-focus-ring-offset-inset: -2px;
|
|
88
91
|
|
|
89
92
|
--psi-font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
90
93
|
--psi-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** AppShell component tokens (--psi-app-shell-*) — D88.
|
|
2
|
+
*
|
|
3
|
+
* sidebar-width is a deliberate default for the sidebar column of the frame.
|
|
4
|
+
* 16rem is a standalone literal, matching --psi-toolbar-control-width: no
|
|
5
|
+
* shared scale reaches this range (sizeScale tops at 48, spacingScale at 144,
|
|
6
|
+
* both for heights and gaps, not column widths). The header's height stays
|
|
7
|
+
* --psi-navbar-height: the NavBar owns it.
|
|
8
|
+
*
|
|
9
|
+
* sidebar-bg, sidebar-fg and focus-ring alias the semantic layer, so under a
|
|
10
|
+
* `data-psi-theme="dark"` sidebar inside a light page they re-resolve dark
|
|
11
|
+
* (component tokens are declared under `:where(:root, [data-psi-theme])`).
|
|
12
|
+
* The theme paints no background of its own: the sidebar paints sidebar-bg.
|
|
13
|
+
*
|
|
14
|
+
* No contrast pair added: fgPrimary on bgPrimary is already gated in every
|
|
15
|
+
* theme, dark included, and the pinned pair count does not move. */
|
|
16
|
+
export declare const appShellVars: Record<string, string>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** AppShell component tokens (--psi-app-shell-*) — D88.
|
|
2
|
+
*
|
|
3
|
+
* sidebar-width is a deliberate default for the sidebar column of the frame.
|
|
4
|
+
* 16rem is a standalone literal, matching --psi-toolbar-control-width: no
|
|
5
|
+
* shared scale reaches this range (sizeScale tops at 48, spacingScale at 144,
|
|
6
|
+
* both for heights and gaps, not column widths). The header's height stays
|
|
7
|
+
* --psi-navbar-height: the NavBar owns it.
|
|
8
|
+
*
|
|
9
|
+
* sidebar-bg, sidebar-fg and focus-ring alias the semantic layer, so under a
|
|
10
|
+
* `data-psi-theme="dark"` sidebar inside a light page they re-resolve dark
|
|
11
|
+
* (component tokens are declared under `:where(:root, [data-psi-theme])`).
|
|
12
|
+
* The theme paints no background of its own: the sidebar paints sidebar-bg.
|
|
13
|
+
*
|
|
14
|
+
* No contrast pair added: fgPrimary on bgPrimary is already gated in every
|
|
15
|
+
* theme, dark included, and the pinned pair count does not move. */
|
|
16
|
+
export const appShellVars = {
|
|
17
|
+
"sidebar-width": "16rem",
|
|
18
|
+
"sidebar-bg": "var(--psi-bg-primary)",
|
|
19
|
+
"sidebar-fg": "var(--psi-fg-primary)",
|
|
20
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
21
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-app-shell-sidebar-width: 16rem;
|
|
4
|
+
--psi-app-shell-sidebar-bg: var(--psi-bg-primary);
|
|
5
|
+
--psi-app-shell-sidebar-fg: var(--psi-fg-primary);
|
|
6
|
+
--psi-app-shell-focus-ring: var(--psi-border-focus);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Banner component tokens (--psi-banner-*) — D86. Pure indirection, same
|
|
2
|
+
* posture as toast.ts (D64) and table.ts (D62).
|
|
3
|
+
*
|
|
4
|
+
* Unlike Toast, a Banner is a tinted surface: it spans the page edge to edge
|
|
5
|
+
* and is meant to be seen, so the variant tints the whole strip. Neutral sits
|
|
6
|
+
* on fillNeutral3, the status variants on the fillTint* family.
|
|
7
|
+
*
|
|
8
|
+
* The body text is fgPrimary on all four surfaces; fgPrimary on each
|
|
9
|
+
* fillTint* is gated at 4.5 in contrast-matrix.ts (D86). The icon foregrounds
|
|
10
|
+
* are not text and ride the status-foreground pairs already gated there. */
|
|
11
|
+
export declare const bannerVars: Record<string, string>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Banner component tokens (--psi-banner-*) — D86. Pure indirection, same
|
|
2
|
+
* posture as toast.ts (D64) and table.ts (D62).
|
|
3
|
+
*
|
|
4
|
+
* Unlike Toast, a Banner is a tinted surface: it spans the page edge to edge
|
|
5
|
+
* and is meant to be seen, so the variant tints the whole strip. Neutral sits
|
|
6
|
+
* on fillNeutral3, the status variants on the fillTint* family.
|
|
7
|
+
*
|
|
8
|
+
* The body text is fgPrimary on all four surfaces; fgPrimary on each
|
|
9
|
+
* fillTint* is gated at 4.5 in contrast-matrix.ts (D86). The icon foregrounds
|
|
10
|
+
* are not text and ride the status-foreground pairs already gated there. */
|
|
11
|
+
export const bannerVars = {
|
|
12
|
+
"bg-neutral": "var(--psi-fill-neutral3)",
|
|
13
|
+
"bg-success": "var(--psi-fill-tint-success)",
|
|
14
|
+
"bg-warning": "var(--psi-fill-tint-warning)",
|
|
15
|
+
"bg-danger": "var(--psi-fill-tint-danger)",
|
|
16
|
+
fg: "var(--psi-fg-primary)",
|
|
17
|
+
"icon-fg-neutral": "var(--psi-fg-secondary)",
|
|
18
|
+
"icon-fg-success": "var(--psi-fg-success)",
|
|
19
|
+
"icon-fg-warning": "var(--psi-fg-warning)",
|
|
20
|
+
"icon-fg-danger": "var(--psi-fg-danger)",
|
|
21
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-banner-bg-neutral: var(--psi-fill-neutral3);
|
|
4
|
+
--psi-banner-bg-success: var(--psi-fill-tint-success);
|
|
5
|
+
--psi-banner-bg-warning: var(--psi-fill-tint-warning);
|
|
6
|
+
--psi-banner-bg-danger: var(--psi-fill-tint-danger);
|
|
7
|
+
--psi-banner-fg: var(--psi-fg-primary);
|
|
8
|
+
--psi-banner-icon-fg-neutral: var(--psi-fg-secondary);
|
|
9
|
+
--psi-banner-icon-fg-success: var(--psi-fg-success);
|
|
10
|
+
--psi-banner-icon-fg-warning: var(--psi-fg-warning);
|
|
11
|
+
--psi-banner-icon-fg-danger: var(--psi-fg-danger);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -10,4 +10,7 @@ export const dialogVars = {
|
|
|
10
10
|
backdrop: "var(--psi-scrim-heavy)",
|
|
11
11
|
fg: "var(--psi-fg-primary)",
|
|
12
12
|
"title-fg": "var(--psi-fg-primary)",
|
|
13
|
+
// D82 — the ring on the <dialog> and on a drawer's scrolling panel, which
|
|
14
|
+
// are tab stops themselves; the controls inside carry their own.
|
|
15
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
13
16
|
};
|
package/dist/components/field.js
CHANGED
|
@@ -5,5 +5,10 @@ export const fieldVars = {
|
|
|
5
5
|
"message-fg": "var(--psi-fg-tertiary)",
|
|
6
6
|
"error-fg": "var(--psi-fg-danger)",
|
|
7
7
|
"marker-fg": "var(--psi-fg-danger)",
|
|
8
|
+
// D84: the word a required label shows instead of the asterisk. The label's
|
|
9
|
+
// own colour — gated at 4.5 on every surface — not marker-fg, which would
|
|
10
|
+
// make every required label read as an error, and not fg-tertiary, the one
|
|
11
|
+
// foreground the contrast matrix does not gate.
|
|
12
|
+
"required-text-fg": "var(--psi-fg-secondary)",
|
|
8
13
|
gap: "var(--psi-space-6)",
|
|
9
14
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** InlineAlert component tokens (--psi-inline-alert-*) — D86. Pure
|
|
2
|
+
* indirection, same posture as banner.ts.
|
|
3
|
+
*
|
|
4
|
+
* The same tinted surface as Banner, but it sits inside content, so it adds a
|
|
5
|
+
* 1px border in the variant's colour and a radius. Neutral sits on
|
|
6
|
+
* fillNeutral3 with borderNeutral; the status variants draw the border from
|
|
7
|
+
* the status foreground (fgSuccess and fgWarning carry the `border` scope
|
|
8
|
+
* since D86, so the D46 scope gate accepts these bindings).
|
|
9
|
+
*
|
|
10
|
+
* The body text is fgPrimary on all four surfaces; fgPrimary on each
|
|
11
|
+
* fillTint* is gated at 4.5 in contrast-matrix.ts (D86). */
|
|
12
|
+
export declare const inlineAlertVars: Record<string, string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** InlineAlert component tokens (--psi-inline-alert-*) — D86. Pure
|
|
2
|
+
* indirection, same posture as banner.ts.
|
|
3
|
+
*
|
|
4
|
+
* The same tinted surface as Banner, but it sits inside content, so it adds a
|
|
5
|
+
* 1px border in the variant's colour and a radius. Neutral sits on
|
|
6
|
+
* fillNeutral3 with borderNeutral; the status variants draw the border from
|
|
7
|
+
* the status foreground (fgSuccess and fgWarning carry the `border` scope
|
|
8
|
+
* since D86, so the D46 scope gate accepts these bindings).
|
|
9
|
+
*
|
|
10
|
+
* The body text is fgPrimary on all four surfaces; fgPrimary on each
|
|
11
|
+
* fillTint* is gated at 4.5 in contrast-matrix.ts (D86). */
|
|
12
|
+
export const inlineAlertVars = {
|
|
13
|
+
"bg-neutral": "var(--psi-fill-neutral3)",
|
|
14
|
+
"bg-success": "var(--psi-fill-tint-success)",
|
|
15
|
+
"bg-warning": "var(--psi-fill-tint-warning)",
|
|
16
|
+
"bg-danger": "var(--psi-fill-tint-danger)",
|
|
17
|
+
fg: "var(--psi-fg-primary)",
|
|
18
|
+
"icon-fg-neutral": "var(--psi-fg-secondary)",
|
|
19
|
+
"icon-fg-success": "var(--psi-fg-success)",
|
|
20
|
+
"icon-fg-warning": "var(--psi-fg-warning)",
|
|
21
|
+
"icon-fg-danger": "var(--psi-fg-danger)",
|
|
22
|
+
"border-neutral": "var(--psi-border-neutral)",
|
|
23
|
+
"border-success": "var(--psi-fg-success)",
|
|
24
|
+
"border-warning": "var(--psi-fg-warning)",
|
|
25
|
+
"border-danger": "var(--psi-fg-danger)",
|
|
26
|
+
radius: "var(--psi-radius-8)",
|
|
27
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-inline-alert-bg-neutral: var(--psi-fill-neutral3);
|
|
4
|
+
--psi-inline-alert-bg-success: var(--psi-fill-tint-success);
|
|
5
|
+
--psi-inline-alert-bg-warning: var(--psi-fill-tint-warning);
|
|
6
|
+
--psi-inline-alert-bg-danger: var(--psi-fill-tint-danger);
|
|
7
|
+
--psi-inline-alert-fg: var(--psi-fg-primary);
|
|
8
|
+
--psi-inline-alert-icon-fg-neutral: var(--psi-fg-secondary);
|
|
9
|
+
--psi-inline-alert-icon-fg-success: var(--psi-fg-success);
|
|
10
|
+
--psi-inline-alert-icon-fg-warning: var(--psi-fg-warning);
|
|
11
|
+
--psi-inline-alert-icon-fg-danger: var(--psi-fg-danger);
|
|
12
|
+
--psi-inline-alert-border-neutral: var(--psi-border-neutral);
|
|
13
|
+
--psi-inline-alert-border-success: var(--psi-fg-success);
|
|
14
|
+
--psi-inline-alert-border-warning: var(--psi-fg-warning);
|
|
15
|
+
--psi-inline-alert-border-danger: var(--psi-fg-danger);
|
|
16
|
+
--psi-inline-alert-radius: var(--psi-radius-8);
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/components/menu.js
CHANGED
|
@@ -16,4 +16,6 @@ export const menuVars = {
|
|
|
16
16
|
"item-fg-danger": "var(--psi-fg-danger)",
|
|
17
17
|
"item-fg-disabled": "var(--psi-fg-quaternary)",
|
|
18
18
|
"separator-border": "var(--psi-border-faint)",
|
|
19
|
+
// D82 — the ring on a focused item; until then the browser drew its own.
|
|
20
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
19
21
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** NavTree component tokens (--psi-nav-tree-*) — D88. Pure indirection onto the
|
|
2
|
+
* semantic layer (same posture as menu.ts and tabs.ts), so every key resolves
|
|
3
|
+
* under a `data-psi-theme="dark"` sidebar inside a light page: component
|
|
4
|
+
* tokens are declared under `:where(:root, [data-psi-theme])`.
|
|
5
|
+
*
|
|
6
|
+
* No new contrast pairs: wcagAAPairs already gates fgPrimary and fgSecondary
|
|
7
|
+
* on bgPrimary and on fillNeutral1-6 in every theme, dark included, which
|
|
8
|
+
* covers the item text on the sidebar, on the hover surface (neutral3) and on
|
|
9
|
+
* the current surface (neutral4). `indicator` and `focus-ring` are non-text
|
|
10
|
+
* accents and `radius` is geometry; none carries a gated pair, so the pinned
|
|
11
|
+
* pair count does not move. */
|
|
12
|
+
export declare const navTreeVars: Record<string, string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** NavTree component tokens (--psi-nav-tree-*) — D88. Pure indirection onto the
|
|
2
|
+
* semantic layer (same posture as menu.ts and tabs.ts), so every key resolves
|
|
3
|
+
* under a `data-psi-theme="dark"` sidebar inside a light page: component
|
|
4
|
+
* tokens are declared under `:where(:root, [data-psi-theme])`.
|
|
5
|
+
*
|
|
6
|
+
* No new contrast pairs: wcagAAPairs already gates fgPrimary and fgSecondary
|
|
7
|
+
* on bgPrimary and on fillNeutral1-6 in every theme, dark included, which
|
|
8
|
+
* covers the item text on the sidebar, on the hover surface (neutral3) and on
|
|
9
|
+
* the current surface (neutral4). `indicator` and `focus-ring` are non-text
|
|
10
|
+
* accents and `radius` is geometry; none carries a gated pair, so the pinned
|
|
11
|
+
* pair count does not move. */
|
|
12
|
+
export const navTreeVars = {
|
|
13
|
+
"item-fg": "var(--psi-fg-secondary)",
|
|
14
|
+
"item-fg-current": "var(--psi-fg-primary)",
|
|
15
|
+
"item-bg-hover": "var(--psi-fill-neutral3)",
|
|
16
|
+
"item-bg-current": "var(--psi-fill-neutral4)",
|
|
17
|
+
// The inline-start bar on the current item: weight and surface carry the
|
|
18
|
+
// state too, so colour is never the only signal.
|
|
19
|
+
indicator: "var(--psi-fill-accent)",
|
|
20
|
+
// A group's button reads like its items, one step quieter than the current.
|
|
21
|
+
"group-fg": "var(--psi-fg-secondary)",
|
|
22
|
+
// Alias of the control radius dial: a CSS Module may bind only its own
|
|
23
|
+
// component prefix, and the radius lives outside it.
|
|
24
|
+
radius: "var(--psi-control-radius)",
|
|
25
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
26
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-nav-tree-item-fg: var(--psi-fg-secondary);
|
|
4
|
+
--psi-nav-tree-item-fg-current: var(--psi-fg-primary);
|
|
5
|
+
--psi-nav-tree-item-bg-hover: var(--psi-fill-neutral3);
|
|
6
|
+
--psi-nav-tree-item-bg-current: var(--psi-fill-neutral4);
|
|
7
|
+
--psi-nav-tree-indicator: var(--psi-fill-accent);
|
|
8
|
+
--psi-nav-tree-group-fg: var(--psi-fg-secondary);
|
|
9
|
+
--psi-nav-tree-radius: var(--psi-control-radius);
|
|
10
|
+
--psi-nav-tree-focus-ring: var(--psi-border-focus);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -8,4 +8,7 @@ export const navbarVars = {
|
|
|
8
8
|
"link-fg": "var(--psi-fg-secondary)",
|
|
9
9
|
"link-fg-hover": "var(--psi-fg-primary)",
|
|
10
10
|
"focus-ring": "var(--psi-border-focus)",
|
|
11
|
+
// D88 — the side padding of a fluid row; the same gutter .psi-container pads
|
|
12
|
+
// with, reachable from the CSS Module only through a navbar token.
|
|
13
|
+
gutter: "var(--psi-gutter)",
|
|
11
14
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** NavTree component tokens (--psi-navtree-*) — D88. Pure indirection onto the
|
|
2
|
+
* semantic layer (same posture as menu.ts and tabs.ts), so every key resolves
|
|
3
|
+
* under a `data-psi-theme="dark"` sidebar inside a light page: component
|
|
4
|
+
* tokens are declared under `:where(:root, [data-psi-theme])`.
|
|
5
|
+
*
|
|
6
|
+
* No new contrast pairs: wcagAAPairs already gates fgPrimary and fgSecondary
|
|
7
|
+
* on bgPrimary and on fillNeutral1-6 in every theme, dark included, which
|
|
8
|
+
* covers the item text on the sidebar, on the hover surface (neutral3) and on
|
|
9
|
+
* the current surface (neutral4). `indicator` and `focus-ring` are non-text
|
|
10
|
+
* accents and `radius` is geometry; none carries a gated pair, so the pinned
|
|
11
|
+
* pair count does not move. */
|
|
12
|
+
export declare const navtreeVars: Record<string, string>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** NavTree component tokens (--psi-navtree-*) — D88. Pure indirection onto the
|
|
2
|
+
* semantic layer (same posture as menu.ts and tabs.ts), so every key resolves
|
|
3
|
+
* under a `data-psi-theme="dark"` sidebar inside a light page: component
|
|
4
|
+
* tokens are declared under `:where(:root, [data-psi-theme])`.
|
|
5
|
+
*
|
|
6
|
+
* No new contrast pairs: wcagAAPairs already gates fgPrimary and fgSecondary
|
|
7
|
+
* on bgPrimary and on fillNeutral1-6 in every theme, dark included, which
|
|
8
|
+
* covers the item text on the sidebar, on the hover surface (neutral3) and on
|
|
9
|
+
* the current surface (neutral4). `indicator` and `focus-ring` are non-text
|
|
10
|
+
* accents and `radius` is geometry; none carries a gated pair, so the pinned
|
|
11
|
+
* pair count does not move. */
|
|
12
|
+
export const navtreeVars = {
|
|
13
|
+
"item-fg": "var(--psi-fg-secondary)",
|
|
14
|
+
"item-fg-current": "var(--psi-fg-primary)",
|
|
15
|
+
"item-bg-hover": "var(--psi-fill-neutral3)",
|
|
16
|
+
"item-bg-current": "var(--psi-fill-neutral4)",
|
|
17
|
+
// The inline-start bar on the current item: weight and surface carry the
|
|
18
|
+
// state too, so colour is never the only signal.
|
|
19
|
+
indicator: "var(--psi-fill-accent)",
|
|
20
|
+
// A group's button reads like its items, one step quieter than the current.
|
|
21
|
+
"group-fg": "var(--psi-fg-secondary)",
|
|
22
|
+
// Alias of the control radius dial: a CSS Module may bind only its own
|
|
23
|
+
// component prefix, and the radius lives outside it.
|
|
24
|
+
radius: "var(--psi-control-radius)",
|
|
25
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
26
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-navtree-item-fg: var(--psi-fg-secondary);
|
|
4
|
+
--psi-navtree-item-fg-current: var(--psi-fg-primary);
|
|
5
|
+
--psi-navtree-item-bg-hover: var(--psi-fill-neutral3);
|
|
6
|
+
--psi-navtree-item-bg-current: var(--psi-fill-neutral4);
|
|
7
|
+
--psi-navtree-indicator: var(--psi-fill-accent);
|
|
8
|
+
--psi-navtree-group-fg: var(--psi-fg-secondary);
|
|
9
|
+
--psi-navtree-radius: var(--psi-control-radius);
|
|
10
|
+
--psi-navtree-focus-ring: var(--psi-border-focus);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// Component var registry (D81: moved out of scripts/build.ts) — the D46 scope
|
|
2
2
|
// gate reads it, in Psi's own build and in a consumer's theme build alike.
|
|
3
|
+
import { bannerVars } from "./banner.js";
|
|
4
|
+
import { inlineAlertVars } from "./inline-alert.js";
|
|
3
5
|
import { buttonVars } from "./button.js";
|
|
6
|
+
import { skeletonVars } from "./skeleton.js";
|
|
7
|
+
import { skipLinkVars } from "./skip-link.js";
|
|
8
|
+
import { navTreeVars } from "./nav-tree.js";
|
|
9
|
+
import { appShellVars } from "./app-shell.js";
|
|
4
10
|
import { inputVars } from "./input.js";
|
|
5
11
|
import { selectVars } from "./select.js";
|
|
6
12
|
import { surfaceVars } from "./surface.js";
|
|
@@ -22,6 +28,8 @@ import { tabsVars } from "./tabs.js";
|
|
|
22
28
|
import { fieldVars } from "./field.js";
|
|
23
29
|
import { descriptionListVars } from "./description-list.js";
|
|
24
30
|
export const componentVars = {
|
|
31
|
+
"app-shell": appShellVars,
|
|
32
|
+
banner: bannerVars,
|
|
25
33
|
button: buttonVars,
|
|
26
34
|
card: cardVars,
|
|
27
35
|
checkbox: checkboxVars,
|
|
@@ -29,11 +37,15 @@ export const componentVars = {
|
|
|
29
37
|
dialog: dialogVars,
|
|
30
38
|
field: fieldVars,
|
|
31
39
|
input: inputVars,
|
|
40
|
+
"inline-alert": inlineAlertVars,
|
|
32
41
|
media: mediaVars,
|
|
33
42
|
menu: menuVars,
|
|
34
43
|
navbar: navbarVars,
|
|
35
44
|
panel: panelVars,
|
|
36
45
|
select: selectVars,
|
|
46
|
+
skeleton: skeletonVars,
|
|
47
|
+
"nav-tree": navTreeVars,
|
|
48
|
+
"skip-link": skipLinkVars,
|
|
37
49
|
surface: surfaceVars,
|
|
38
50
|
switch: switchVars,
|
|
39
51
|
table: tableVars,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Skeleton component tokens (--psi-skeleton-*) — D86. Pure indirection, same
|
|
2
|
+
* posture as banner.ts. A placeholder carries no text, so there is no
|
|
3
|
+
* foreground pair to gate; it is a quiet neutral shape. */
|
|
4
|
+
export const skeletonVars = {
|
|
5
|
+
bg: "var(--psi-fill-neutral4)",
|
|
6
|
+
radius: "var(--psi-radius-4)",
|
|
7
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** SkipLink component tokens (--psi-skip-link-*) — D88. Pure indirection onto
|
|
2
|
+
* the shared surface and focus tokens (same posture as menu.ts and
|
|
3
|
+
* navbar.ts): the link shows only when focused, on a surface, so a brand
|
|
4
|
+
* retuning --psi-surface-* gets it for free. radius rides the control radius
|
|
5
|
+
* dial, as Button's does, so the link matches the controls around it. */
|
|
6
|
+
export declare const skipLinkVars: Record<string, string>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** SkipLink component tokens (--psi-skip-link-*) — D88. Pure indirection onto
|
|
2
|
+
* the shared surface and focus tokens (same posture as menu.ts and
|
|
3
|
+
* navbar.ts): the link shows only when focused, on a surface, so a brand
|
|
4
|
+
* retuning --psi-surface-* gets it for free. radius rides the control radius
|
|
5
|
+
* dial, as Button's does, so the link matches the controls around it. */
|
|
6
|
+
export const skipLinkVars = {
|
|
7
|
+
bg: "var(--psi-surface-bg)",
|
|
8
|
+
fg: "var(--psi-fg-primary)",
|
|
9
|
+
border: "var(--psi-surface-border)",
|
|
10
|
+
radius: "var(--psi-control-radius)",
|
|
11
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
12
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-skip-link-bg: var(--psi-surface-bg);
|
|
4
|
+
--psi-skip-link-fg: var(--psi-fg-primary);
|
|
5
|
+
--psi-skip-link-border: var(--psi-surface-border);
|
|
6
|
+
--psi-skip-link-radius: var(--psi-control-radius);
|
|
7
|
+
--psi-skip-link-focus-ring: var(--psi-border-focus);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** SkipLink component tokens (--psi-skiplink-*) — D88. Pure indirection onto
|
|
2
|
+
* the shared surface and focus tokens (same posture as menu.ts and
|
|
3
|
+
* navbar.ts): the link shows only when focused, on a surface, so a brand
|
|
4
|
+
* retuning --psi-surface-* gets it for free. radius rides the control radius
|
|
5
|
+
* dial, as Button's does, so the link matches the controls around it. */
|
|
6
|
+
export declare const skiplinkVars: Record<string, string>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** SkipLink component tokens (--psi-skiplink-*) — D88. Pure indirection onto
|
|
2
|
+
* the shared surface and focus tokens (same posture as menu.ts and
|
|
3
|
+
* navbar.ts): the link shows only when focused, on a surface, so a brand
|
|
4
|
+
* retuning --psi-surface-* gets it for free. radius rides the control radius
|
|
5
|
+
* dial, as Button's does, so the link matches the controls around it. */
|
|
6
|
+
export const skiplinkVars = {
|
|
7
|
+
bg: "var(--psi-surface-bg)",
|
|
8
|
+
fg: "var(--psi-fg-primary)",
|
|
9
|
+
border: "var(--psi-surface-border)",
|
|
10
|
+
radius: "var(--psi-control-radius)",
|
|
11
|
+
"focus-ring": "var(--psi-border-focus)",
|
|
12
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-skiplink-bg: var(--psi-surface-bg);
|
|
4
|
+
--psi-skiplink-fg: var(--psi-fg-primary);
|
|
5
|
+
--psi-skiplink-border: var(--psi-surface-border);
|
|
6
|
+
--psi-skiplink-radius: var(--psi-control-radius);
|
|
7
|
+
--psi-skiplink-focus-ring: var(--psi-border-focus);
|
|
8
|
+
}
|
|
9
|
+
}
|
package/dist/components.css
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
@layer psi.components {
|
|
2
|
+
:where(:root, [data-psi-theme]) {
|
|
3
|
+
--psi-app-shell-sidebar-width: 16rem;
|
|
4
|
+
--psi-app-shell-sidebar-bg: var(--psi-bg-primary);
|
|
5
|
+
--psi-app-shell-sidebar-fg: var(--psi-fg-primary);
|
|
6
|
+
--psi-app-shell-focus-ring: var(--psi-border-focus);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@layer psi.components {
|
|
11
|
+
:where(:root, [data-psi-theme]) {
|
|
12
|
+
--psi-banner-bg-neutral: var(--psi-fill-neutral3);
|
|
13
|
+
--psi-banner-bg-success: var(--psi-fill-tint-success);
|
|
14
|
+
--psi-banner-bg-warning: var(--psi-fill-tint-warning);
|
|
15
|
+
--psi-banner-bg-danger: var(--psi-fill-tint-danger);
|
|
16
|
+
--psi-banner-fg: var(--psi-fg-primary);
|
|
17
|
+
--psi-banner-icon-fg-neutral: var(--psi-fg-secondary);
|
|
18
|
+
--psi-banner-icon-fg-success: var(--psi-fg-success);
|
|
19
|
+
--psi-banner-icon-fg-warning: var(--psi-fg-warning);
|
|
20
|
+
--psi-banner-icon-fg-danger: var(--psi-fg-danger);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
1
24
|
@layer psi.components {
|
|
2
25
|
:where(:root, [data-psi-theme]) {
|
|
3
26
|
--psi-button-accent-bg: var(--psi-fill-accent);
|
|
@@ -123,6 +146,7 @@
|
|
|
123
146
|
--psi-dialog-backdrop: var(--psi-scrim-heavy);
|
|
124
147
|
--psi-dialog-fg: var(--psi-fg-primary);
|
|
125
148
|
--psi-dialog-title-fg: var(--psi-fg-primary);
|
|
149
|
+
--psi-dialog-focus-ring: var(--psi-border-focus);
|
|
126
150
|
}
|
|
127
151
|
}
|
|
128
152
|
|
|
@@ -132,6 +156,7 @@
|
|
|
132
156
|
--psi-field-message-fg: var(--psi-fg-tertiary);
|
|
133
157
|
--psi-field-error-fg: var(--psi-fg-danger);
|
|
134
158
|
--psi-field-marker-fg: var(--psi-fg-danger);
|
|
159
|
+
--psi-field-required-text-fg: var(--psi-fg-secondary);
|
|
135
160
|
--psi-field-gap: var(--psi-space-6);
|
|
136
161
|
}
|
|
137
162
|
}
|
|
@@ -161,6 +186,25 @@
|
|
|
161
186
|
}
|
|
162
187
|
}
|
|
163
188
|
|
|
189
|
+
@layer psi.components {
|
|
190
|
+
:where(:root, [data-psi-theme]) {
|
|
191
|
+
--psi-inline-alert-bg-neutral: var(--psi-fill-neutral3);
|
|
192
|
+
--psi-inline-alert-bg-success: var(--psi-fill-tint-success);
|
|
193
|
+
--psi-inline-alert-bg-warning: var(--psi-fill-tint-warning);
|
|
194
|
+
--psi-inline-alert-bg-danger: var(--psi-fill-tint-danger);
|
|
195
|
+
--psi-inline-alert-fg: var(--psi-fg-primary);
|
|
196
|
+
--psi-inline-alert-icon-fg-neutral: var(--psi-fg-secondary);
|
|
197
|
+
--psi-inline-alert-icon-fg-success: var(--psi-fg-success);
|
|
198
|
+
--psi-inline-alert-icon-fg-warning: var(--psi-fg-warning);
|
|
199
|
+
--psi-inline-alert-icon-fg-danger: var(--psi-fg-danger);
|
|
200
|
+
--psi-inline-alert-border-neutral: var(--psi-border-neutral);
|
|
201
|
+
--psi-inline-alert-border-success: var(--psi-fg-success);
|
|
202
|
+
--psi-inline-alert-border-warning: var(--psi-fg-warning);
|
|
203
|
+
--psi-inline-alert-border-danger: var(--psi-fg-danger);
|
|
204
|
+
--psi-inline-alert-radius: var(--psi-radius-8);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
164
208
|
@layer psi.components {
|
|
165
209
|
:where(:root, [data-psi-theme]) {
|
|
166
210
|
--psi-media-tint: none;
|
|
@@ -180,6 +224,7 @@
|
|
|
180
224
|
--psi-menu-item-fg-danger: var(--psi-fg-danger);
|
|
181
225
|
--psi-menu-item-fg-disabled: var(--psi-fg-quaternary);
|
|
182
226
|
--psi-menu-separator-border: var(--psi-border-faint);
|
|
227
|
+
--psi-menu-focus-ring: var(--psi-border-focus);
|
|
183
228
|
}
|
|
184
229
|
}
|
|
185
230
|
|
|
@@ -192,6 +237,7 @@
|
|
|
192
237
|
--psi-navbar-link-fg: var(--psi-fg-secondary);
|
|
193
238
|
--psi-navbar-link-fg-hover: var(--psi-fg-primary);
|
|
194
239
|
--psi-navbar-focus-ring: var(--psi-border-focus);
|
|
240
|
+
--psi-navbar-gutter: var(--psi-gutter);
|
|
195
241
|
}
|
|
196
242
|
}
|
|
197
243
|
|
|
@@ -238,6 +284,36 @@
|
|
|
238
284
|
}
|
|
239
285
|
}
|
|
240
286
|
|
|
287
|
+
@layer psi.components {
|
|
288
|
+
:where(:root, [data-psi-theme]) {
|
|
289
|
+
--psi-skeleton-bg: var(--psi-fill-neutral4);
|
|
290
|
+
--psi-skeleton-radius: var(--psi-radius-4);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@layer psi.components {
|
|
295
|
+
:where(:root, [data-psi-theme]) {
|
|
296
|
+
--psi-nav-tree-item-fg: var(--psi-fg-secondary);
|
|
297
|
+
--psi-nav-tree-item-fg-current: var(--psi-fg-primary);
|
|
298
|
+
--psi-nav-tree-item-bg-hover: var(--psi-fill-neutral3);
|
|
299
|
+
--psi-nav-tree-item-bg-current: var(--psi-fill-neutral4);
|
|
300
|
+
--psi-nav-tree-indicator: var(--psi-fill-accent);
|
|
301
|
+
--psi-nav-tree-group-fg: var(--psi-fg-secondary);
|
|
302
|
+
--psi-nav-tree-radius: var(--psi-control-radius);
|
|
303
|
+
--psi-nav-tree-focus-ring: var(--psi-border-focus);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@layer psi.components {
|
|
308
|
+
:where(:root, [data-psi-theme]) {
|
|
309
|
+
--psi-skip-link-bg: var(--psi-surface-bg);
|
|
310
|
+
--psi-skip-link-fg: var(--psi-fg-primary);
|
|
311
|
+
--psi-skip-link-border: var(--psi-surface-border);
|
|
312
|
+
--psi-skip-link-radius: var(--psi-control-radius);
|
|
313
|
+
--psi-skip-link-focus-ring: var(--psi-border-focus);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
241
317
|
@layer psi.components {
|
|
242
318
|
:where(:root, [data-psi-theme]) {
|
|
243
319
|
--psi-surface-bg: var(--psi-bg-secondary);
|
package/dist/contrast-matrix.js
CHANGED
|
@@ -108,6 +108,14 @@ export const wcagAAPairs = [
|
|
|
108
108
|
// the fgAccent/fillTintAccent pair above: a selected row keeps ordinary
|
|
109
109
|
// body text, it does not recolor it to the accent.
|
|
110
110
|
{ fg: "fgPrimary", bg: "fillTintAccent", minRatio: 4.5 },
|
|
111
|
+
// Body text on the status tints (D86): Banner and InlineAlert set their
|
|
112
|
+
// title and body in fgPrimary on fill-tint-{success,warning,danger}. The
|
|
113
|
+
// tints are 12–15% of the status foreground over the canvas, so every
|
|
114
|
+
// shipped theme clears this by a wide margin; it is gated because a
|
|
115
|
+
// consumer theme built with psi-theme (D81) is held to the same matrix.
|
|
116
|
+
{ fg: "fgPrimary", bg: "fillTintSuccess", minRatio: 4.5 },
|
|
117
|
+
{ fg: "fgPrimary", bg: "fillTintWarning", minRatio: 4.5 },
|
|
118
|
+
{ fg: "fgPrimary", bg: "fillTintDanger", minRatio: 4.5 },
|
|
111
119
|
// Toast's status icon on the elevated surface (D64/D65). The three pairs
|
|
112
120
|
// above gate these foregrounds on bgPrimary; --psi-surface-bg — what Toast,
|
|
113
121
|
// Menu and Dialog all alias — resolves to bgSecondary, which was ungated for
|
package/dist/dsl/validator.js
CHANGED
|
@@ -85,7 +85,7 @@ export function validateScopeConsistency(themes) {
|
|
|
85
85
|
}
|
|
86
86
|
// Both gates resolve --psi-* names scale-first, so a semantic token whose
|
|
87
87
|
// kebab name starts with a scale-family prefix would be silently shadowed.
|
|
88
|
-
const SCALE_FAMILY_PREFIX = /^(space|size|radius|text|font|duration|ease|z)-/;
|
|
88
|
+
const SCALE_FAMILY_PREFIX = /^(space|size|radius|text|font|duration|ease|z|focus-ring)-/;
|
|
89
89
|
/** D46 follow-up (HAN-21): reject semantic token kebab names that a
|
|
90
90
|
* scale-family lookup would shadow before the semantic lookup runs. */
|
|
91
91
|
export function validateNoScalePrefixShadow(kebabNames) {
|
package/dist/dtcg/acme.json
CHANGED
|
@@ -155,7 +155,8 @@
|
|
|
155
155
|
"$extensions": {
|
|
156
156
|
"psi": {
|
|
157
157
|
"scopes": [
|
|
158
|
-
"text"
|
|
158
|
+
"text",
|
|
159
|
+
"border"
|
|
159
160
|
]
|
|
160
161
|
}
|
|
161
162
|
}
|
|
@@ -167,7 +168,8 @@
|
|
|
167
168
|
"$extensions": {
|
|
168
169
|
"psi": {
|
|
169
170
|
"scopes": [
|
|
170
|
-
"text"
|
|
171
|
+
"text",
|
|
172
|
+
"border"
|
|
171
173
|
]
|
|
172
174
|
}
|
|
173
175
|
}
|
|
@@ -607,6 +609,20 @@
|
|
|
607
609
|
"$type": "dimension",
|
|
608
610
|
"$value": "1.5rem"
|
|
609
611
|
}
|
|
612
|
+
},
|
|
613
|
+
"focusRing": {
|
|
614
|
+
"width": {
|
|
615
|
+
"$type": "dimension",
|
|
616
|
+
"$value": "2px"
|
|
617
|
+
},
|
|
618
|
+
"offset": {
|
|
619
|
+
"$type": "dimension",
|
|
620
|
+
"$value": "2px"
|
|
621
|
+
},
|
|
622
|
+
"offsetInset": {
|
|
623
|
+
"$type": "dimension",
|
|
624
|
+
"$value": "-2px"
|
|
625
|
+
}
|
|
610
626
|
}
|
|
611
627
|
},
|
|
612
628
|
"typography": {
|
package/dist/dtcg/dark.json
CHANGED
|
@@ -155,7 +155,8 @@
|
|
|
155
155
|
"$extensions": {
|
|
156
156
|
"psi": {
|
|
157
157
|
"scopes": [
|
|
158
|
-
"text"
|
|
158
|
+
"text",
|
|
159
|
+
"border"
|
|
159
160
|
]
|
|
160
161
|
}
|
|
161
162
|
}
|
|
@@ -167,7 +168,8 @@
|
|
|
167
168
|
"$extensions": {
|
|
168
169
|
"psi": {
|
|
169
170
|
"scopes": [
|
|
170
|
-
"text"
|
|
171
|
+
"text",
|
|
172
|
+
"border"
|
|
171
173
|
]
|
|
172
174
|
}
|
|
173
175
|
}
|
|
@@ -607,6 +609,20 @@
|
|
|
607
609
|
"$type": "dimension",
|
|
608
610
|
"$value": "1.5rem"
|
|
609
611
|
}
|
|
612
|
+
},
|
|
613
|
+
"focusRing": {
|
|
614
|
+
"width": {
|
|
615
|
+
"$type": "dimension",
|
|
616
|
+
"$value": "2px"
|
|
617
|
+
},
|
|
618
|
+
"offset": {
|
|
619
|
+
"$type": "dimension",
|
|
620
|
+
"$value": "2px"
|
|
621
|
+
},
|
|
622
|
+
"offsetInset": {
|
|
623
|
+
"$type": "dimension",
|
|
624
|
+
"$value": "-2px"
|
|
625
|
+
}
|
|
610
626
|
}
|
|
611
627
|
},
|
|
612
628
|
"typography": {
|
package/dist/dtcg/ember.json
CHANGED
|
@@ -155,7 +155,8 @@
|
|
|
155
155
|
"$extensions": {
|
|
156
156
|
"psi": {
|
|
157
157
|
"scopes": [
|
|
158
|
-
"text"
|
|
158
|
+
"text",
|
|
159
|
+
"border"
|
|
159
160
|
]
|
|
160
161
|
}
|
|
161
162
|
}
|
|
@@ -167,7 +168,8 @@
|
|
|
167
168
|
"$extensions": {
|
|
168
169
|
"psi": {
|
|
169
170
|
"scopes": [
|
|
170
|
-
"text"
|
|
171
|
+
"text",
|
|
172
|
+
"border"
|
|
171
173
|
]
|
|
172
174
|
}
|
|
173
175
|
}
|
|
@@ -607,6 +609,20 @@
|
|
|
607
609
|
"$type": "dimension",
|
|
608
610
|
"$value": "1.5rem"
|
|
609
611
|
}
|
|
612
|
+
},
|
|
613
|
+
"focusRing": {
|
|
614
|
+
"width": {
|
|
615
|
+
"$type": "dimension",
|
|
616
|
+
"$value": "2px"
|
|
617
|
+
},
|
|
618
|
+
"offset": {
|
|
619
|
+
"$type": "dimension",
|
|
620
|
+
"$value": "2px"
|
|
621
|
+
},
|
|
622
|
+
"offsetInset": {
|
|
623
|
+
"$type": "dimension",
|
|
624
|
+
"$value": "-2px"
|
|
625
|
+
}
|
|
610
626
|
}
|
|
611
627
|
},
|
|
612
628
|
"typography": {
|
package/dist/dtcg/light.json
CHANGED
|
@@ -155,7 +155,8 @@
|
|
|
155
155
|
"$extensions": {
|
|
156
156
|
"psi": {
|
|
157
157
|
"scopes": [
|
|
158
|
-
"text"
|
|
158
|
+
"text",
|
|
159
|
+
"border"
|
|
159
160
|
]
|
|
160
161
|
}
|
|
161
162
|
}
|
|
@@ -167,7 +168,8 @@
|
|
|
167
168
|
"$extensions": {
|
|
168
169
|
"psi": {
|
|
169
170
|
"scopes": [
|
|
170
|
-
"text"
|
|
171
|
+
"text",
|
|
172
|
+
"border"
|
|
171
173
|
]
|
|
172
174
|
}
|
|
173
175
|
}
|
|
@@ -607,6 +609,20 @@
|
|
|
607
609
|
"$type": "dimension",
|
|
608
610
|
"$value": "1.5rem"
|
|
609
611
|
}
|
|
612
|
+
},
|
|
613
|
+
"focusRing": {
|
|
614
|
+
"width": {
|
|
615
|
+
"$type": "dimension",
|
|
616
|
+
"$value": "2px"
|
|
617
|
+
},
|
|
618
|
+
"offset": {
|
|
619
|
+
"$type": "dimension",
|
|
620
|
+
"$value": "2px"
|
|
621
|
+
},
|
|
622
|
+
"offsetInset": {
|
|
623
|
+
"$type": "dimension",
|
|
624
|
+
"$value": "-2px"
|
|
625
|
+
}
|
|
610
626
|
}
|
|
611
627
|
},
|
|
612
628
|
"typography": {
|
package/dist/guidance.json
CHANGED
|
@@ -54,14 +54,14 @@
|
|
|
54
54
|
"Override component tokens (--psi-{component}-*), not semantic tokens, for one-off theming.",
|
|
55
55
|
"--psi-button-font overrides button typography across all sizes (documented D34 override; ember → mono).",
|
|
56
56
|
"Wrap labeled form controls in Field — label association, description/error line, aria-describedby and aria-invalid come wired; don't hand-roll label+message rows.",
|
|
57
|
-
"Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field).",
|
|
57
|
+
"Field is for a control with a *visible* label. A toolbar filter control named by aria-label or placeholder takes neither a Field nor a hand-rolled label row — compare filter-toolbar (no labels) with table-pagination (\"Rows per page\" visible, so Field). Filters with visible labels submitted together by a Find button are filter-form: a Toolbar as=\"form\" align=\"end\" of Fields (D87).",
|
|
58
58
|
"Use Dialog for blocking modal flows — title/footer slots, dismissible gate; danger stays on the footer Buttons, one accent per group."
|
|
59
59
|
],
|
|
60
60
|
"states": {
|
|
61
61
|
"hover": "L - 0.04",
|
|
62
62
|
"active": "L - 0.08",
|
|
63
63
|
"disabled": "element opacity 0.4 (keeps hue)",
|
|
64
|
-
"focus": "
|
|
64
|
+
"focus": "outline: var(--psi-focus-ring-width) solid var(--psi-{component}-focus-ring) on :focus-visible, at --psi-focus-ring-offset (outside) or --psi-focus-ring-offset-inset (inside) — D82"
|
|
65
65
|
},
|
|
66
66
|
"typographyDefaults": {
|
|
67
67
|
"body": "16-24-regular",
|
package/dist/resolved/acme.json
CHANGED
|
@@ -157,7 +157,8 @@
|
|
|
157
157
|
"hex": "#007048",
|
|
158
158
|
"formula": "slot(success) → L set(0.48), C cap(0.1086)",
|
|
159
159
|
"scopes": [
|
|
160
|
-
"text"
|
|
160
|
+
"text",
|
|
161
|
+
"border"
|
|
161
162
|
]
|
|
162
163
|
},
|
|
163
164
|
"fgWarning": {
|
|
@@ -170,7 +171,8 @@
|
|
|
170
171
|
"hex": "#775800",
|
|
171
172
|
"formula": "slot(warning) → L set(0.48), C cap(0.0984)",
|
|
172
173
|
"scopes": [
|
|
173
|
-
"text"
|
|
174
|
+
"text",
|
|
175
|
+
"border"
|
|
174
176
|
]
|
|
175
177
|
},
|
|
176
178
|
"fgDanger": {
|
|
@@ -574,6 +576,11 @@
|
|
|
574
576
|
"tooltip": 1100
|
|
575
577
|
}
|
|
576
578
|
},
|
|
579
|
+
"focusRing": {
|
|
580
|
+
"width": 2,
|
|
581
|
+
"offset": 2,
|
|
582
|
+
"offsetInset": -2
|
|
583
|
+
},
|
|
577
584
|
"scopes": {
|
|
578
585
|
"space": [
|
|
579
586
|
"gap"
|
package/dist/resolved/dark.json
CHANGED
|
@@ -157,7 +157,8 @@
|
|
|
157
157
|
"hex": "#00cf77",
|
|
158
158
|
"formula": "slot(success) → L set(0.75), C cap(0.23)",
|
|
159
159
|
"scopes": [
|
|
160
|
-
"text"
|
|
160
|
+
"text",
|
|
161
|
+
"border"
|
|
161
162
|
]
|
|
162
163
|
},
|
|
163
164
|
"fgWarning": {
|
|
@@ -170,7 +171,8 @@
|
|
|
170
171
|
"hex": "#e79d00",
|
|
171
172
|
"formula": "slot(warning) → L set(0.75), C cap(0.1582)",
|
|
172
173
|
"scopes": [
|
|
173
|
-
"text"
|
|
174
|
+
"text",
|
|
175
|
+
"border"
|
|
174
176
|
]
|
|
175
177
|
},
|
|
176
178
|
"fgDanger": {
|
|
@@ -574,6 +576,11 @@
|
|
|
574
576
|
"tooltip": 1100
|
|
575
577
|
}
|
|
576
578
|
},
|
|
579
|
+
"focusRing": {
|
|
580
|
+
"width": 2,
|
|
581
|
+
"offset": 2,
|
|
582
|
+
"offsetInset": -2
|
|
583
|
+
},
|
|
577
584
|
"scopes": {
|
|
578
585
|
"space": [
|
|
579
586
|
"gap"
|
package/dist/resolved/ember.json
CHANGED
|
@@ -157,7 +157,8 @@
|
|
|
157
157
|
"hex": "#00cf77",
|
|
158
158
|
"formula": "slot(success) → L set(0.75), C cap(0.23)",
|
|
159
159
|
"scopes": [
|
|
160
|
-
"text"
|
|
160
|
+
"text",
|
|
161
|
+
"border"
|
|
161
162
|
]
|
|
162
163
|
},
|
|
163
164
|
"fgWarning": {
|
|
@@ -170,7 +171,8 @@
|
|
|
170
171
|
"hex": "#e79d00",
|
|
171
172
|
"formula": "slot(warning) → L set(0.75), C cap(0.1582)",
|
|
172
173
|
"scopes": [
|
|
173
|
-
"text"
|
|
174
|
+
"text",
|
|
175
|
+
"border"
|
|
174
176
|
]
|
|
175
177
|
},
|
|
176
178
|
"fgDanger": {
|
|
@@ -574,6 +576,11 @@
|
|
|
574
576
|
"tooltip": 1100
|
|
575
577
|
}
|
|
576
578
|
},
|
|
579
|
+
"focusRing": {
|
|
580
|
+
"width": 2,
|
|
581
|
+
"offset": 2,
|
|
582
|
+
"offsetInset": -2
|
|
583
|
+
},
|
|
577
584
|
"scopes": {
|
|
578
585
|
"space": [
|
|
579
586
|
"gap"
|
package/dist/resolved/light.json
CHANGED
|
@@ -157,7 +157,8 @@
|
|
|
157
157
|
"hex": "#00703e",
|
|
158
158
|
"formula": "slot(success) → L set(0.48), C cap(0.1187)",
|
|
159
159
|
"scopes": [
|
|
160
|
-
"text"
|
|
160
|
+
"text",
|
|
161
|
+
"border"
|
|
161
162
|
]
|
|
162
163
|
},
|
|
163
164
|
"fgWarning": {
|
|
@@ -170,7 +171,8 @@
|
|
|
170
171
|
"hex": "#7e5400",
|
|
171
172
|
"formula": "slot(warning) → L set(0.48), C cap(0.1013)",
|
|
172
173
|
"scopes": [
|
|
173
|
-
"text"
|
|
174
|
+
"text",
|
|
175
|
+
"border"
|
|
174
176
|
]
|
|
175
177
|
},
|
|
176
178
|
"fgDanger": {
|
|
@@ -574,6 +576,11 @@
|
|
|
574
576
|
"tooltip": 1100
|
|
575
577
|
}
|
|
576
578
|
},
|
|
579
|
+
"focusRing": {
|
|
580
|
+
"width": 2,
|
|
581
|
+
"offset": 2,
|
|
582
|
+
"offsetInset": -2
|
|
583
|
+
},
|
|
577
584
|
"scopes": {
|
|
578
585
|
"space": [
|
|
579
586
|
"gap"
|
package/dist/scope-map.json
CHANGED
|
@@ -107,10 +107,12 @@
|
|
|
107
107
|
"text"
|
|
108
108
|
],
|
|
109
109
|
"fg-success": [
|
|
110
|
-
"text"
|
|
110
|
+
"text",
|
|
111
|
+
"border"
|
|
111
112
|
],
|
|
112
113
|
"fg-warning": [
|
|
113
|
-
"text"
|
|
114
|
+
"text",
|
|
115
|
+
"border"
|
|
114
116
|
],
|
|
115
117
|
"fg-danger": [
|
|
116
118
|
"text",
|
|
@@ -191,6 +193,39 @@
|
|
|
191
193
|
]
|
|
192
194
|
},
|
|
193
195
|
"component": {
|
|
196
|
+
"app-shell-sidebar-bg": [
|
|
197
|
+
"surface"
|
|
198
|
+
],
|
|
199
|
+
"app-shell-sidebar-fg": [
|
|
200
|
+
"text"
|
|
201
|
+
],
|
|
202
|
+
"banner-bg-neutral": [
|
|
203
|
+
"surface"
|
|
204
|
+
],
|
|
205
|
+
"banner-bg-success": [
|
|
206
|
+
"surface"
|
|
207
|
+
],
|
|
208
|
+
"banner-bg-warning": [
|
|
209
|
+
"surface"
|
|
210
|
+
],
|
|
211
|
+
"banner-bg-danger": [
|
|
212
|
+
"surface"
|
|
213
|
+
],
|
|
214
|
+
"banner-fg": [
|
|
215
|
+
"text"
|
|
216
|
+
],
|
|
217
|
+
"banner-icon-fg-neutral": [
|
|
218
|
+
"text"
|
|
219
|
+
],
|
|
220
|
+
"banner-icon-fg-success": [
|
|
221
|
+
"text"
|
|
222
|
+
],
|
|
223
|
+
"banner-icon-fg-warning": [
|
|
224
|
+
"text"
|
|
225
|
+
],
|
|
226
|
+
"banner-icon-fg-danger": [
|
|
227
|
+
"text"
|
|
228
|
+
],
|
|
194
229
|
"button-accent-bg": [
|
|
195
230
|
"surface"
|
|
196
231
|
],
|
|
@@ -341,6 +376,9 @@
|
|
|
341
376
|
"field-marker-fg": [
|
|
342
377
|
"text"
|
|
343
378
|
],
|
|
379
|
+
"field-required-text-fg": [
|
|
380
|
+
"text"
|
|
381
|
+
],
|
|
344
382
|
"input-bg": [
|
|
345
383
|
"surface"
|
|
346
384
|
],
|
|
@@ -356,6 +394,45 @@
|
|
|
356
394
|
"input-border-error": [
|
|
357
395
|
"border"
|
|
358
396
|
],
|
|
397
|
+
"inline-alert-bg-neutral": [
|
|
398
|
+
"surface"
|
|
399
|
+
],
|
|
400
|
+
"inline-alert-bg-success": [
|
|
401
|
+
"surface"
|
|
402
|
+
],
|
|
403
|
+
"inline-alert-bg-warning": [
|
|
404
|
+
"surface"
|
|
405
|
+
],
|
|
406
|
+
"inline-alert-bg-danger": [
|
|
407
|
+
"surface"
|
|
408
|
+
],
|
|
409
|
+
"inline-alert-fg": [
|
|
410
|
+
"text"
|
|
411
|
+
],
|
|
412
|
+
"inline-alert-icon-fg-neutral": [
|
|
413
|
+
"text"
|
|
414
|
+
],
|
|
415
|
+
"inline-alert-icon-fg-success": [
|
|
416
|
+
"text"
|
|
417
|
+
],
|
|
418
|
+
"inline-alert-icon-fg-warning": [
|
|
419
|
+
"text"
|
|
420
|
+
],
|
|
421
|
+
"inline-alert-icon-fg-danger": [
|
|
422
|
+
"text"
|
|
423
|
+
],
|
|
424
|
+
"inline-alert-border-neutral": [
|
|
425
|
+
"border"
|
|
426
|
+
],
|
|
427
|
+
"inline-alert-border-success": [
|
|
428
|
+
"border"
|
|
429
|
+
],
|
|
430
|
+
"inline-alert-border-warning": [
|
|
431
|
+
"border"
|
|
432
|
+
],
|
|
433
|
+
"inline-alert-border-danger": [
|
|
434
|
+
"border"
|
|
435
|
+
],
|
|
359
436
|
"menu-bg": [
|
|
360
437
|
"surface"
|
|
361
438
|
],
|
|
@@ -422,6 +499,33 @@
|
|
|
422
499
|
"select-chevron-fg": [
|
|
423
500
|
"text"
|
|
424
501
|
],
|
|
502
|
+
"skeleton-bg": [
|
|
503
|
+
"surface"
|
|
504
|
+
],
|
|
505
|
+
"nav-tree-item-fg": [
|
|
506
|
+
"text"
|
|
507
|
+
],
|
|
508
|
+
"nav-tree-item-fg-current": [
|
|
509
|
+
"text"
|
|
510
|
+
],
|
|
511
|
+
"nav-tree-item-bg-hover": [
|
|
512
|
+
"surface"
|
|
513
|
+
],
|
|
514
|
+
"nav-tree-item-bg-current": [
|
|
515
|
+
"surface"
|
|
516
|
+
],
|
|
517
|
+
"nav-tree-group-fg": [
|
|
518
|
+
"text"
|
|
519
|
+
],
|
|
520
|
+
"skip-link-bg": [
|
|
521
|
+
"surface"
|
|
522
|
+
],
|
|
523
|
+
"skip-link-fg": [
|
|
524
|
+
"text"
|
|
525
|
+
],
|
|
526
|
+
"skip-link-border": [
|
|
527
|
+
"border"
|
|
528
|
+
],
|
|
425
529
|
"surface-bg": [
|
|
426
530
|
"surface"
|
|
427
531
|
],
|
package/dist/themes/dark.js
CHANGED
|
@@ -17,8 +17,8 @@ export const darkTheme = {
|
|
|
17
17
|
// tokens were already clamping to, so rendered hex is unchanged. fgSuccess
|
|
18
18
|
// keeps the loose cap: emerald at l 0.75 is already in-gamut.
|
|
19
19
|
fgAccent: token({ from: slot.accent, l: set(0.75), c: cap(0.1286), scopes: ["text"] }),
|
|
20
|
-
fgSuccess: token({ from: slot.success, l: set(0.75), c: cap(0.23), scopes: ["text"] }),
|
|
21
|
-
fgWarning: token({ from: slot.warning, l: set(0.75), c: cap(0.1582), scopes: ["text"] }),
|
|
20
|
+
fgSuccess: token({ from: slot.success, l: set(0.75), c: cap(0.23), scopes: ["text", "border"] }),
|
|
21
|
+
fgWarning: token({ from: slot.warning, l: set(0.75), c: cap(0.1582), scopes: ["text", "border"] }),
|
|
22
22
|
fgDanger: token({ from: slot.danger, l: set(0.75), c: cap(0.1505), scopes: ["text", "border"] }),
|
|
23
23
|
fillNeutral1: token({ from: slot.ink, l: set(0.18), c: set(0.012), scopes: ["surface"] }),
|
|
24
24
|
fillNeutral2: token({ from: slot.ink, l: set(0.2), c: set(0.015), scopes: ["surface"] }),
|
package/dist/themes/light.js
CHANGED
|
@@ -16,8 +16,10 @@ export const lightTheme = {
|
|
|
16
16
|
fgAccent: token({ from: slot.accent, l: set(0.48), c: cap(0.23), scopes: ["text"] }),
|
|
17
17
|
// D39: status-fg caps lowered to each hue's in-gamut max at l 0.48 — the
|
|
18
18
|
// values these tokens were already clamping to, so rendered hex is unchanged.
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
// text + border on all three status foregrounds: fgDanger for Input's error
|
|
20
|
+
// border (D46), fgSuccess and fgWarning for InlineAlert's (D86).
|
|
21
|
+
fgSuccess: token({ from: slot.success, l: set(0.48), c: cap(0.1187), scopes: ["text", "border"] }),
|
|
22
|
+
fgWarning: token({ from: slot.warning, l: set(0.48), c: cap(0.1013), scopes: ["text", "border"] }),
|
|
21
23
|
fgDanger: token({ from: slot.danger, l: set(0.48), c: cap(0.1946), scopes: ["text", "border"] }),
|
|
22
24
|
fillNeutral1: token({ from: slot.canvas, l: delta(+0.016), c: delta(-0.001), scopes: ["surface"] }),
|
|
23
25
|
fillNeutral2: token({ from: slot.canvas, scopes: ["surface"] }),
|
package/llms.txt
CHANGED
|
@@ -26,6 +26,11 @@ OKLCH formula-based design tokens. Code is the source of truth; all artifacts ge
|
|
|
26
26
|
- --psi-z-sticky (1), --psi-z-nav (100), --psi-z-overlay (1000), --psi-z-tooltip (1100). sticky is for elements that stack within their own local scroll container (e.g. a sticky table header), not above page chrome.
|
|
27
27
|
- Breakpoints (sm 560 / md 960) are build-time constants (D31), not CSS vars: `import { breakpoints } from "@handamade/psi-tokens/types"` for any @media-driven JS/CSS-in-JS.
|
|
28
28
|
|
|
29
|
+
## Focus ring (--psi-focus-ring-*)
|
|
30
|
+
- One ring for every focusable part (D82): `outline: var(--psi-focus-ring-width) solid <colour>` on `:focus-visible`, never on `:focus`, never a literal width or offset.
|
|
31
|
+
- --psi-focus-ring-width (2px). --psi-focus-ring-offset (2px) draws the ring outside the control. --psi-focus-ring-offset-inset (-2px) draws it inside, for a control whose outside ring would be clipped or would land on a neighbour: a text field, a tab, a menu item, a row in a list, a panel against the viewport edge.
|
|
32
|
+
- The colour is the component's own --psi-{component}-focus-ring in component CSS, and --psi-border-focus in app CSS. The three geometry tokens are independent px values, so an override on any sub-tree takes effect there; the inset offset is not derived from the width, so override both together.
|
|
33
|
+
|
|
29
34
|
## Typography: display, serif, mono
|
|
30
35
|
- Fluid display tier: --psi-display-{min}-{max}-{weight} (e.g. --psi-display-56-128-black), clamp-based and pixel-true at both endpoints; pair with the matching .psi-display-* utility class for tracking/uppercase.
|
|
31
36
|
- Serif and mono are first-class combo roles alongside sans: --psi-text-serif-{size}-{lh}-{weight} and --psi-text-mono-{size}-{lh}-{weight}, same naming shape as the sans scale.
|