@stonedogcode/style 0.15.0 → 0.17.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 +49 -0
- package/package.json +5 -5
- package/src/components/StyledFieldHelp.tsx +248 -0
- package/src/components/StyledPage.tsx +22 -0
- package/src/components/StyledTooltip.tsx +308 -39
- package/src/config/font-size.ts +25 -0
- package/src/index.ts +13 -0
- package/src/preset/index.ts +21 -0
- package/src/preset/recipes/button.ts +34 -4
- package/src/preset/recipes/form.ts +6 -1
- package/src/preset/recipes/icon-button.ts +36 -5
- package/src/preset/recipes/input-bool.ts +19 -3
- package/src/preset/recipes/input-radio.ts +11 -3
- package/src/preset/recipes/input-surface.ts +33 -6
- package/src/preset/recipes/list.ts +7 -1
- package/src/preset/recipes/tooltip.ts +30 -8
- package/src/preset/semantic-variables.ts +22 -5
- package/src/preset/z-layers.ts +101 -0
|
@@ -64,23 +64,42 @@ export const buttonIconRecipe = defineRecipe({
|
|
|
64
64
|
},
|
|
65
65
|
},
|
|
66
66
|
variant: {
|
|
67
|
+
/**
|
|
68
|
+
* The NEH-796 pairing, applied to the recipe it was never applied to
|
|
69
|
+
* (NEH-877).
|
|
70
|
+
*
|
|
71
|
+
* `buttonRecipe` and this one are the same control in two shapes, and
|
|
72
|
+
* `iconButton` carried the identical defect in three variants: an accent
|
|
73
|
+
* fill labelled with `textPrimary`, the contract's partner for
|
|
74
|
+
* `boxBgPrimary`. Against optima's light theme that measures 2.43:1,
|
|
75
|
+
* below WCAG AA; `buttonTextAccent` measures 7.34:1. The dark theme
|
|
76
|
+
* clears AA either way, which is what let it ship.
|
|
77
|
+
*
|
|
78
|
+
* Each `_hover` that repaints a different surface states its own colour
|
|
79
|
+
* for the same reason — the base colour riding onto a secondary fill is
|
|
80
|
+
* the failure one state along.
|
|
81
|
+
*/
|
|
67
82
|
solid: {
|
|
68
83
|
bg: "buttonBgAccent",
|
|
69
|
-
color: "
|
|
84
|
+
color: "buttonTextAccent",
|
|
70
85
|
_hover: {
|
|
71
86
|
bg: "buttonBgSecondary",
|
|
72
|
-
color: "
|
|
87
|
+
color: "buttonTextSecondary",
|
|
73
88
|
},
|
|
74
89
|
},
|
|
75
90
|
outline: {
|
|
76
91
|
bg: "buttonBgAccent",
|
|
77
|
-
color: "
|
|
92
|
+
color: "buttonTextAccent",
|
|
78
93
|
border: "1px solid",
|
|
79
94
|
borderColor: "borderBgSecondary",
|
|
80
95
|
borderRadius: 0,
|
|
81
96
|
_hover: {
|
|
82
97
|
border: "2px solid",
|
|
83
98
|
bg: "buttonBgAccentHover",
|
|
99
|
+
// The hover surface has its own partner in the contract, and it is
|
|
100
|
+
// not the base one. Both are white in optima's palette today; the
|
|
101
|
+
// point is that a host is free to make them differ.
|
|
102
|
+
color: "buttonTextAccentHover",
|
|
84
103
|
},
|
|
85
104
|
},
|
|
86
105
|
aurora: {
|
|
@@ -98,7 +117,10 @@ export const buttonIconRecipe = defineRecipe({
|
|
|
98
117
|
borderColor: "black",
|
|
99
118
|
borderRadius: "xl",
|
|
100
119
|
bg: "buttonBgAccent",
|
|
101
|
-
|
|
120
|
+
// Not translucent, whatever the name suggests: the fill is an opaque
|
|
121
|
+
// `buttonBgAccent` and the blur applies to what is behind it. So it
|
|
122
|
+
// takes the accent partner like its neighbours above (NEH-877).
|
|
123
|
+
color: "buttonTextAccent",
|
|
102
124
|
boxShadow: "0 8px 32px rgba(0,0,0,0.2)",
|
|
103
125
|
backdropFilter: "blur(12px)",
|
|
104
126
|
WebkitBackdropFilter: "blur(12px)",
|
|
@@ -107,6 +129,10 @@ export const buttonIconRecipe = defineRecipe({
|
|
|
107
129
|
transition: "all 0.3s ease",
|
|
108
130
|
_hover: {
|
|
109
131
|
bg: "buttonBgSecondary",
|
|
132
|
+
// Stated because the hover repaints a different surface — otherwise
|
|
133
|
+
// the glyph keeps `buttonTextAccent` over a secondary fill, 1.06:1 in
|
|
134
|
+
// optima's light theme (NEH-877).
|
|
135
|
+
color: "buttonTextSecondary",
|
|
110
136
|
borderColor: "rgba(255,255,255,0.4)",
|
|
111
137
|
boxShadow: "0 8px 32px rgba(0,0,0,0.3), inset 0 0 20px rgba(255,255,255,0.1)",
|
|
112
138
|
transform: "translateY(-1px)",
|
|
@@ -140,11 +166,16 @@ export const buttonIconRecipe = defineRecipe({
|
|
|
140
166
|
fontWeight: "bold",
|
|
141
167
|
},
|
|
142
168
|
ghost: {
|
|
169
|
+
// The base stays `textPrimary`: a 50% accent fill is a BLEND with
|
|
170
|
+
// whatever is behind it, so the contract has no partner for it and
|
|
171
|
+
// `textPrimary` measures 5.9:1 over the light-theme blend. The hover
|
|
172
|
+
// takes the fill to full opacity, which IS a contract surface — and
|
|
173
|
+
// there `textPrimary` is the 2.43:1 pairing again (NEH-877).
|
|
143
174
|
bg: "buttonBgAccent/50",
|
|
144
175
|
color: "textPrimary",
|
|
145
176
|
_hover: {
|
|
146
177
|
bg: "buttonBgAccent",
|
|
147
|
-
color: "
|
|
178
|
+
color: "buttonTextAccent",
|
|
148
179
|
border: "1px solid",
|
|
149
180
|
borderColor: "gray.700",
|
|
150
181
|
},
|
|
@@ -103,7 +103,19 @@ export const inputBoolRecipe = defineSlotRecipe({
|
|
|
103
103
|
solid: {
|
|
104
104
|
control: {
|
|
105
105
|
bg: "buttonBgAccent",
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* The contract's partner for this fill (NEH-877), and INERT today.
|
|
108
|
+
*
|
|
109
|
+
* Neither `bg` nor `color` paints on a checkbox at `appearance:
|
|
110
|
+
* auto` — the note in `base` and NEH-234 are about exactly that — so
|
|
111
|
+
* this changes nothing anyone can see, and that is the honest reason
|
|
112
|
+
* to make it: the declarations are left in place per that note, and
|
|
113
|
+
* a declaration left in place should not be left WRONG. `textPrimary`
|
|
114
|
+
* is the partner of `boxBgPrimary`; the day option 2 on NEH-310
|
|
115
|
+
* (`appearance: none` plus a hand-drawn tick) makes these paint, it
|
|
116
|
+
* would paint at 2.43:1 against optima's light theme.
|
|
117
|
+
*/
|
|
118
|
+
color: "buttonTextAccent",
|
|
107
119
|
accentColor: "buttonBgPrimary",
|
|
108
120
|
boxShadow: "none",
|
|
109
121
|
},
|
|
@@ -111,7 +123,8 @@ export const inputBoolRecipe = defineSlotRecipe({
|
|
|
111
123
|
outline: {
|
|
112
124
|
control: {
|
|
113
125
|
bg: "buttonBgAccent",
|
|
114
|
-
|
|
126
|
+
// Same fill, same partner, same inertness as `solid` above.
|
|
127
|
+
color: "buttonTextAccent",
|
|
115
128
|
/**
|
|
116
129
|
* The SAME checked fill as `solid`, deliberately.
|
|
117
130
|
*
|
|
@@ -227,7 +240,10 @@ export const inputBoolRecipe = defineSlotRecipe({
|
|
|
227
240
|
},
|
|
228
241
|
ghost: {
|
|
229
242
|
control: {
|
|
230
|
-
|
|
243
|
+
// `buttonTextSecondary` is the partner of the fill below;
|
|
244
|
+
// `textSecondary` belongs to `boxBgSecondary` (NEH-877). Inert for
|
|
245
|
+
// the same reason as `solid`.
|
|
246
|
+
color: "buttonTextSecondary",
|
|
231
247
|
bg: "buttonBgSecondary",
|
|
232
248
|
accentColor: "buttonBgPrimary",
|
|
233
249
|
// The thinnest ring in the set, in the secondary border colour.
|
|
@@ -82,7 +82,11 @@ export const inputRadioRootRecipe = defineSlotRecipe({
|
|
|
82
82
|
solid: {
|
|
83
83
|
item: {
|
|
84
84
|
bg: "boxBgAccent",
|
|
85
|
-
|
|
85
|
+
// `textAccent` is the contract's partner for this surface;
|
|
86
|
+
// `textPrimary` belongs to `boxBgPrimary` and measures 1.17:1 on it
|
|
87
|
+
// against optima's light theme, whose accent surface is a near-black
|
|
88
|
+
// graphite (NEH-877). This is the DEFAULT variant of this recipe.
|
|
89
|
+
color: "textAccent",
|
|
86
90
|
borderColor: "borderBgPrimary",
|
|
87
91
|
},
|
|
88
92
|
},
|
|
@@ -116,12 +120,16 @@ export const inputRadioRootRecipe = defineSlotRecipe({
|
|
|
116
120
|
matte: {
|
|
117
121
|
item: {
|
|
118
122
|
bg: "buttonBgSecondary",
|
|
119
|
-
|
|
123
|
+
// Was `textAccent` — the partner of `boxBgAccent`, a surface this
|
|
124
|
+
// variant does not paint. 1.14:1 in optima's light theme, where the
|
|
125
|
+
// accent text is white and this fill is near-white (NEH-877).
|
|
126
|
+
color: "buttonTextSecondary",
|
|
120
127
|
},
|
|
121
128
|
},
|
|
122
129
|
ghost: {
|
|
123
130
|
item: {
|
|
124
|
-
|
|
131
|
+
// `textSecondary` is `boxBgSecondary`'s partner, not this fill's.
|
|
132
|
+
color: "buttonTextSecondary",
|
|
125
133
|
bg: "buttonBgSecondary",
|
|
126
134
|
border: "none",
|
|
127
135
|
},
|
|
@@ -66,12 +66,31 @@ export const inputSurfaceBase = {
|
|
|
66
66
|
* The variant map. Every variant states its own `bg`, `color`, and
|
|
67
67
|
* `borderColor` rather than inheriting some and not others — that asymmetry is
|
|
68
68
|
* what let the two recipes disagree without either looking wrong on its own.
|
|
69
|
+
*
|
|
70
|
+
* ## The colour has to be the surface's PARTNER, not just a colour (NEH-877)
|
|
71
|
+
*
|
|
72
|
+
* Four of these paired a surface with the wrong contract token, and because
|
|
73
|
+
* `inputTextRecipe` and `inputDropdownRecipe` both spread this map, each one was
|
|
74
|
+
* eight offending selectors rather than four. `solid` and `glass` paint
|
|
75
|
+
* `boxBgAccent` and said `textPrimary` — the partner of `boxBgPrimary` — which
|
|
76
|
+
* measures **1.17:1** against optima's light theme, a control whose typed value
|
|
77
|
+
* is the exact colour of the field. `matte` and `ghost` paint
|
|
78
|
+
* `buttonBgSecondary` and said `textSecondary`, the partner of `boxBgSecondary`.
|
|
79
|
+
*
|
|
80
|
+
* **The placeholder moves with the text**, which is the part easy to miss here:
|
|
81
|
+
* `inputSurfaceBase` sets `&::placeholder { color: textPrimary }` for the
|
|
82
|
+
* base's own `textPrimary`, so a variant that repaints the surface and only
|
|
83
|
+
* fixes `color` leaves the placeholder on the old pairing — the same defect,
|
|
84
|
+
* surviving in the one piece of text an empty field actually shows.
|
|
69
85
|
*/
|
|
70
86
|
export const inputSurfaceVariants = {
|
|
71
87
|
solid: {
|
|
72
88
|
bg: "boxBgAccent",
|
|
73
|
-
color: "
|
|
89
|
+
color: "textAccent",
|
|
74
90
|
borderColor: "borderBgPrimary",
|
|
91
|
+
"&::placeholder": {
|
|
92
|
+
color: "textAccent",
|
|
93
|
+
},
|
|
75
94
|
},
|
|
76
95
|
outline: {
|
|
77
96
|
// Stated, not omitted. An outline control is meant to show whatever is
|
|
@@ -97,12 +116,15 @@ export const inputSurfaceVariants = {
|
|
|
97
116
|
borderStyle: "solid",
|
|
98
117
|
borderRadius: "xl",
|
|
99
118
|
bg: "boxBgAccent",
|
|
100
|
-
|
|
119
|
+
// An opaque accent fill with a blur applied to what is behind it — not a
|
|
120
|
+
// translucent surface, whatever the name says, so it takes the accent
|
|
121
|
+
// partner like `solid` (NEH-877).
|
|
122
|
+
color: "textAccent",
|
|
101
123
|
borderColor: "borderBgSecondary",
|
|
102
124
|
backdropFilter: "blur(12px)",
|
|
103
125
|
fontWeight: "normal",
|
|
104
126
|
_placeholder: {
|
|
105
|
-
color: "
|
|
127
|
+
color: "textAccent",
|
|
106
128
|
opacity: 0.8,
|
|
107
129
|
},
|
|
108
130
|
_focusVisible: {
|
|
@@ -130,15 +152,20 @@ export const inputSurfaceVariants = {
|
|
|
130
152
|
},
|
|
131
153
|
matte: {
|
|
132
154
|
bg: "buttonBgSecondary",
|
|
133
|
-
color: "
|
|
155
|
+
color: "buttonTextSecondary",
|
|
134
156
|
borderColor: "borderBgPrimary",
|
|
157
|
+
// Otherwise the placeholder keeps the base's `textPrimary` while the value
|
|
158
|
+
// above it moves — see the note on this map (NEH-877).
|
|
159
|
+
"&::placeholder": {
|
|
160
|
+
color: "buttonTextSecondary",
|
|
161
|
+
},
|
|
135
162
|
},
|
|
136
163
|
ghost: {
|
|
137
|
-
color: "
|
|
164
|
+
color: "buttonTextSecondary",
|
|
138
165
|
bg: "buttonBgSecondary",
|
|
139
166
|
borderColor: "borderBgPrimary",
|
|
140
167
|
"&::placeholder": {
|
|
141
|
-
color: "
|
|
168
|
+
color: "buttonTextSecondary/60",
|
|
142
169
|
},
|
|
143
170
|
},
|
|
144
171
|
none: {
|
|
@@ -164,12 +164,18 @@ export const listRecipe = defineSlotRecipe({
|
|
|
164
164
|
},
|
|
165
165
|
ghost: {
|
|
166
166
|
item: {
|
|
167
|
-
|
|
167
|
+
// The partner of `buttonBgSecondary` (NEH-877). `textSecondary`
|
|
168
|
+
// belongs to `boxBgSecondary`, a surface this variant does not paint.
|
|
169
|
+
color: "buttonTextSecondary",
|
|
168
170
|
bg: "buttonBgSecondary",
|
|
169
171
|
borderRadius: "md",
|
|
170
172
|
cursor: "pointer",
|
|
171
173
|
_hover: {
|
|
172
174
|
bg: "boxBgAccent",
|
|
175
|
+
// The hover repaints an accent surface, so the row must take that
|
|
176
|
+
// surface's partner rather than ride the base colour onto it —
|
|
177
|
+
// 1.14:1 in optima's light theme if it did.
|
|
178
|
+
color: "textAccent",
|
|
173
179
|
},
|
|
174
180
|
},
|
|
175
181
|
},
|
|
@@ -1,12 +1,34 @@
|
|
|
1
1
|
import { defineRecipe } from "@pandacss/dev";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Every variant here paints the SAME surface, so every variant owes the same
|
|
5
|
+
* text colour (NEH-877).
|
|
6
|
+
*
|
|
7
|
+
* `boxBgPrimary` is the background of all nine, base included — the variants
|
|
8
|
+
* differ by border and nothing else. Yet seven of them named a text token
|
|
9
|
+
* belonging to some other surface: `buttonTextSecondary`, `textSecondary`,
|
|
10
|
+
* `textMain`, `buttonTextPrimary`. The contract's partner for `boxBgPrimary` is
|
|
11
|
+
* `textPrimary`, and there is no reading of these variants under which a
|
|
12
|
+
* different one is intended — they are copy-paste drift, not design.
|
|
13
|
+
*
|
|
14
|
+
* Measured against optima-cloud-saas's light theme, the worst was `glass` at
|
|
15
|
+
* **1.05:1** — `buttonTextPrimary` is white and `boxBgPrimary` is #f8f9fa, so
|
|
16
|
+
* that tooltip rendered white text on a near-white panel and said nothing at
|
|
17
|
+
* all. The dark theme reads it at 17.84:1, which is why nobody saw it.
|
|
18
|
+
* `textPrimary` measures 16.92:1 light and 15.02:1 dark.
|
|
19
|
+
*
|
|
20
|
+
* A tooltip is the one surface where this class of defect is invisible in
|
|
21
|
+
* review: it appears on hover, over the thing it describes, and an empty-looking
|
|
22
|
+
* one reads as a tooltip that has not opened yet.
|
|
23
|
+
*/
|
|
3
24
|
export const tooltipRecipe = defineRecipe({
|
|
4
25
|
className: "tooltip",
|
|
5
26
|
base: {
|
|
6
27
|
position: "absolute",
|
|
7
28
|
zIndex: 9999,
|
|
8
29
|
pointerEvents: "none",
|
|
9
|
-
|
|
30
|
+
// The partner of the `boxBgPrimary` this base paints (NEH-877).
|
|
31
|
+
color: "textPrimary",
|
|
10
32
|
padding: "2px",
|
|
11
33
|
borderRadius: "md",
|
|
12
34
|
fontSize: "var(--font-sizes-lg, 1rem)",
|
|
@@ -25,7 +47,7 @@ export const tooltipRecipe = defineRecipe({
|
|
|
25
47
|
variant: {
|
|
26
48
|
solid: {
|
|
27
49
|
bg: "boxBgPrimary",
|
|
28
|
-
color: "
|
|
50
|
+
color: "textPrimary",
|
|
29
51
|
borderColor: "borderBgPrimary",
|
|
30
52
|
},
|
|
31
53
|
outline: {
|
|
@@ -36,25 +58,25 @@ export const tooltipRecipe = defineRecipe({
|
|
|
36
58
|
},
|
|
37
59
|
aurora: {
|
|
38
60
|
bg: "boxBgPrimary",
|
|
39
|
-
color: "
|
|
61
|
+
color: "textPrimary",
|
|
40
62
|
borderColor: "borderBgSecondary",
|
|
41
63
|
border: "1px solid",
|
|
42
64
|
},
|
|
43
65
|
glass: {
|
|
44
66
|
bg: "boxBgPrimary",
|
|
45
|
-
color: "
|
|
67
|
+
color: "textPrimary",
|
|
46
68
|
border: "1px solid",
|
|
47
69
|
borderColor: "borderBgPrimary",
|
|
48
70
|
},
|
|
49
71
|
matte: {
|
|
50
72
|
bg: "boxBgPrimary",
|
|
51
|
-
color: "
|
|
73
|
+
color: "textPrimary",
|
|
52
74
|
borderColor: "borderBgSecondary",
|
|
53
75
|
border: "1px solid",
|
|
54
76
|
},
|
|
55
77
|
ghost: {
|
|
56
78
|
bg: "boxBgPrimary",
|
|
57
|
-
color: "
|
|
79
|
+
color: "textPrimary",
|
|
58
80
|
},
|
|
59
81
|
link: {
|
|
60
82
|
bg: "boxBgPrimary",
|
|
@@ -64,13 +86,13 @@ export const tooltipRecipe = defineRecipe({
|
|
|
64
86
|
},
|
|
65
87
|
none: {
|
|
66
88
|
bg: "boxBgPrimary",
|
|
67
|
-
color: "
|
|
89
|
+
color: "textPrimary",
|
|
68
90
|
border: "1px solid",
|
|
69
91
|
borderColor: "borderBgPrimary",
|
|
70
92
|
},
|
|
71
93
|
unstyled: {
|
|
72
94
|
bg: "boxBgPrimary",
|
|
73
|
-
color: "
|
|
95
|
+
color: "textPrimary",
|
|
74
96
|
},
|
|
75
97
|
},
|
|
76
98
|
},
|
|
@@ -154,13 +154,30 @@ const COLOR_TOKENS: TokenMap = {
|
|
|
154
154
|
* with a sensible default so every project can adopt it immediately — applied
|
|
155
155
|
* to the case where a default is genuinely knowable.
|
|
156
156
|
*
|
|
157
|
-
* ##
|
|
157
|
+
* ## `textMuted` is measured. `textSubtle` is still only chosen (NEH-974)
|
|
158
158
|
*
|
|
159
159
|
* Alpha de-emphasis trades contrast for hierarchy, and past some point it
|
|
160
|
-
* trades away legibility
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
160
|
+
* trades away legibility, so these percentages want measuring rather than
|
|
161
|
+
* picking.
|
|
162
|
+
*
|
|
163
|
+
* This comment claimed both tiers were measured by a file called
|
|
164
|
+
* `emphasis-contrast.ct.tsx`, **and no such file has ever existed** — the whole
|
|
165
|
+
* repo contains exactly one reference to that name, this one. A documented
|
|
166
|
+
* guard nobody implemented is worse than an absent one: it is a guard everybody
|
|
167
|
+
* believes in, and it is what made "the values below are what passed" read as a
|
|
168
|
+
* measurement rather than as a guess.
|
|
169
|
+
*
|
|
170
|
+
* What is true today: `components/StyledFieldHelp.contrast.ct.tsx` measures
|
|
171
|
+
* **`textMuted`** in a real browser, composited over the surface it actually
|
|
172
|
+
* paints on rather than over the page, and asserts WCAG 1.4.3 AA (4.5:1). On
|
|
173
|
+
* the harness theme it clears comfortably — 11.7:1 on the page, 9.1:1 on an
|
|
174
|
+
* opaque card, 8.2:1 on a translucent chip over that card.
|
|
175
|
+
*
|
|
176
|
+
* **`textSubtle` at 64% is not measured anywhere.** It is the tier closer to
|
|
177
|
+
* the legibility floor, so it is the one that needed the check more. Tracked;
|
|
178
|
+
* do not restore the claim that it passed something.
|
|
179
|
+
*
|
|
180
|
+
* A host that wants a stronger or weaker step defines the property.
|
|
164
181
|
*/
|
|
165
182
|
const EMPHASIS_TOKENS: Record<string, [suffix: string, fallback: string]> = {
|
|
166
183
|
/** Secondary information: still read, just not first. */
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The stacking-order vocabulary: names for the layers a UI actually has.
|
|
3
|
+
*
|
|
4
|
+
* ## The bug this exists to fix
|
|
5
|
+
*
|
|
6
|
+
* `drawerRecipe` has said `zIndex: "modal"` since it was extracted, and there
|
|
7
|
+
* has never been a `zIndex` token scale for `"modal"` to resolve against —
|
|
8
|
+
* neither here nor in either base Panda preset. Panda passes an unknown token
|
|
9
|
+
* through as a **literal**, so the generated stylesheet said
|
|
10
|
+
*
|
|
11
|
+
* .drawer { position: fixed; z-index: modal; }
|
|
12
|
+
*
|
|
13
|
+
* `modal` is not a valid `z-index` value, so the browser discards the whole
|
|
14
|
+
* declaration. **The drawer panel has never had a z-index at all.** No build
|
|
15
|
+
* error, no console warning, no type error — the class is in the DOM and the
|
|
16
|
+
* rule behind it is one line shorter than it looks.
|
|
17
|
+
*
|
|
18
|
+
* This is precisely the defect class CLAUDE.md records under "Token
|
|
19
|
+
* compliance", one property along from `bg: "buttonBgHover"`. The colour half
|
|
20
|
+
* is guarded by `token-contract.test.ts`; the z-index half is now guarded
|
|
21
|
+
* beside it.
|
|
22
|
+
*
|
|
23
|
+
* ## This package owns the NAMES. The host owns the NUMBERS
|
|
24
|
+
*
|
|
25
|
+
* A z-index ladder is an application concern — it encodes which of *that
|
|
26
|
+
* product's* surfaces may cover which, and no two products agree. So the
|
|
27
|
+
* values below are conventional defaults chosen to be sane for a fresh
|
|
28
|
+
* consumer, and a host is expected to override them:
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* // the host's panda.config.ts
|
|
32
|
+
* theme: { extend: { tokens: { zIndex: { modal: { value: 99999 } } } } }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Overriding a value keeps the name, and the name is the part that makes the
|
|
36
|
+
* order reviewable. A literal at a call site expresses nothing; `zIndex:
|
|
37
|
+
* "menu"` says what the element *is*, and a reader can check it against this
|
|
38
|
+
* table without opening a second file.
|
|
39
|
+
*
|
|
40
|
+
* ## The ladder
|
|
41
|
+
*
|
|
42
|
+
* Ascending, and the ORDER is the contract — not the numbers:
|
|
43
|
+
*
|
|
44
|
+
* hide behind its own box (decorative pseudo-elements)
|
|
45
|
+
* base the ordinary flow
|
|
46
|
+
* raised lifted within its own stacking context
|
|
47
|
+
* docked a bar or rail pinned inside a region
|
|
48
|
+
* sticky a sticky header inside a scroll region
|
|
49
|
+
* banner page-level chrome above sticky content
|
|
50
|
+
* surface a page surface that fills the viewport
|
|
51
|
+
* dialog a modal dialog and its scrim
|
|
52
|
+
* menu menus, drawer scrims, docked panels — the floating band's floor
|
|
53
|
+
* popover a popover that has to clear an open menu
|
|
54
|
+
* overlay a full-viewport cover: a splash, a loading shade
|
|
55
|
+
* toast toasts, pickers, transient chrome
|
|
56
|
+
* modal a drawer or modal panel that must clear everything but a tip
|
|
57
|
+
* tooltip the top of the application
|
|
58
|
+
*
|
|
59
|
+
* **A dialog sits LOW on purpose.** Menus, toasts, tooltips and dropdowns all
|
|
60
|
+
* have to be able to open *on* a dialog, so every one of them is above it.
|
|
61
|
+
* Raising the dialog to "win" is the change that looks right and breaks every
|
|
62
|
+
* control opened inside one.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The layer names, in ascending order, with this package's default values.
|
|
67
|
+
*
|
|
68
|
+
* Exported as plain numbers as well as tokens because a **portalled** element
|
|
69
|
+
* usually sets its z-index from an inline `style`, and an inline style cannot
|
|
70
|
+
* name a Panda token. Both readings have to come from one place or they drift.
|
|
71
|
+
*/
|
|
72
|
+
export const Z_LAYERS = {
|
|
73
|
+
hide: -1,
|
|
74
|
+
base: 0,
|
|
75
|
+
raised: 1,
|
|
76
|
+
docked: 10,
|
|
77
|
+
sticky: 20,
|
|
78
|
+
banner: 50,
|
|
79
|
+
surface: 100,
|
|
80
|
+
dialog: 200,
|
|
81
|
+
menu: 300,
|
|
82
|
+
popover: 400,
|
|
83
|
+
overlay: 500,
|
|
84
|
+
toast: 600,
|
|
85
|
+
modal: 700,
|
|
86
|
+
tooltip: 800,
|
|
87
|
+
} as const;
|
|
88
|
+
|
|
89
|
+
export type ZLayerName = keyof typeof Z_LAYERS;
|
|
90
|
+
|
|
91
|
+
/** Every layer name. Useful to a consumer's guard test. */
|
|
92
|
+
export function zIndexTokenNames(): ZLayerName[] {
|
|
93
|
+
return Object.keys(Z_LAYERS) as ZLayerName[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The scale in the shape Panda's `theme.extend.tokens.zIndex` wants. */
|
|
97
|
+
export function createZIndexTokens(): Record<string, { value: number }> {
|
|
98
|
+
return Object.fromEntries(
|
|
99
|
+
Object.entries(Z_LAYERS).map(([token, value]) => [token, { value }]),
|
|
100
|
+
);
|
|
101
|
+
}
|