@stonedogcode/style 0.13.0 → 0.16.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.
@@ -25,6 +25,7 @@ import {
25
25
  } from "./recipes/separator";
26
26
  import { stackRecipe } from "./recipes/stack";
27
27
  import { stripedRecipe } from "./recipes/striped";
28
+ import { tagRecipe } from "./recipes/tag";
28
29
  import { textRecipe } from "./recipes/text";
29
30
  import { tooltipRecipe } from "./recipes/tooltip";
30
31
 
@@ -35,6 +36,7 @@ import {
35
36
  createSemanticFonts,
36
37
  createSemanticSizes,
37
38
  } from "./semantic-variables";
39
+ import { createZIndexTokens } from "./z-layers";
38
40
 
39
41
  export interface StonedogStylePresetOptions {
40
42
  /**
@@ -83,6 +85,7 @@ const recipes = {
83
85
  separatorVerticalRecipe,
84
86
  stackRecipe,
85
87
  stripedRecipe,
88
+ tagRecipe,
86
89
  textRecipe,
87
90
  tooltipRecipe,
88
91
  };
@@ -135,7 +138,7 @@ const staticCssAlignment = [
135
138
 
136
139
  /**
137
140
  * The @stonedogcode/style Panda preset: colour tokens, breakpoints, keyframes, and
138
- * the 22 recipes the component library is built on.
141
+ * the 23 recipes the component library is built on.
139
142
  *
140
143
  * Deliberately does NOT set `globalCss`, `preflight`, `include`, or `outdir` —
141
144
  * those are application decisions, and a preset that quietly restyles `body` is
@@ -182,6 +185,19 @@ export function stonedogStylePreset(options: StonedogStylePresetOptions = {}) {
182
185
  */
183
186
  fonts: createSemanticFonts(cssVarPrefix),
184
187
  fontWeights: createSemanticFontWeights(cssVarPrefix),
188
+ /**
189
+ * Named stacking layers (NEH-830). Neither this preset nor either
190
+ * base Panda preset defined any, so `drawerRecipe`'s `zIndex:
191
+ * "modal"` resolved to nothing and was emitted as the literal
192
+ * `z-index: modal` — invalid CSS the browser discards, leaving that
193
+ * panel with no z-index at all.
194
+ *
195
+ * Unlike the colour tokens these carry no custom property: a layer
196
+ * is not a brand decision and there is nothing for a theme to
197
+ * restyle. A HOST overrides the numbers in its own config; see
198
+ * z-layers.ts for why the names live here and the values do not.
199
+ */
200
+ zIndex: createZIndexTokens(),
185
201
  },
186
202
  keyframes: {
187
203
  spin: {
@@ -248,6 +264,13 @@ export {
248
264
  requiredCssCustomProperties,
249
265
  } from "./semantic-variables";
250
266
 
267
+ export {
268
+ Z_LAYERS,
269
+ createZIndexTokens,
270
+ zIndexTokenNames,
271
+ type ZLayerName,
272
+ } from "./z-layers";
273
+
251
274
  export { recipes as stonedogStyleRecipes };
252
275
 
253
276
  /* ------------------------------------------------------------------------- *
@@ -37,10 +37,20 @@ export const buttonRecipe = defineRecipe({
37
37
  variant: {
38
38
  solid: {
39
39
  bg: "buttonBgAccent",
40
- color: "textPrimary",
40
+ // The same pairing `outline` states below, and for the same reason
41
+ // (NEH-441, NEH-796). `textPrimary` is the colour that goes on
42
+ // `boxBgPrimary`, not on an accent surface: against optima's light
43
+ // theme that pairing measures 2.43:1, below WCAG AA, while
44
+ // `buttonTextAccent` measures 7.34:1. The dark theme happens to
45
+ // survive it, which is what let a default variant ship unreadable.
46
+ //
47
+ // `TEXT_BACKGROUND_PAIRS` in `semantic-variables.ts` is the contract
48
+ // being obeyed: `buttonTextAccent` sits on `buttonBgAccent`, and
49
+ // `buttonTextSecondary` on the `buttonBgSecondary` the hover repaints.
50
+ color: "buttonTextAccent",
41
51
  _hover: {
42
52
  bg: "buttonBgSecondary",
43
- color: "textPrimary",
53
+ color: "buttonTextSecondary",
44
54
  },
45
55
  },
46
56
  outline: {
@@ -77,7 +87,15 @@ export const buttonRecipe = defineRecipe({
77
87
  border: "2px solid",
78
88
  borderRadius: "xl",
79
89
  bg: "buttonBgAccent",
80
- color: "textPrimary",
90
+ // The same correction `solid` took in NEH-796, which this variant was
91
+ // missed by twice over (NEH-877): the pairing sweep that landed with it
92
+ // was scoped to this recipe, but it also skipped every variant named
93
+ // `glass` — on the reasoning that a translucent surface should inherit.
94
+ // This one is not translucent. It paints an opaque `buttonBgAccent` and
95
+ // blurs what is BEHIND it, so it owes a paired foreground like any
96
+ // other accent surface: 2.43:1 with `textPrimary` against optima's
97
+ // light theme, 7.34:1 with this.
98
+ color: "buttonTextAccent",
81
99
  boxShadow: "0 8px 32px rgba(0,0,0,0.2)",
82
100
  backdropFilter: "blur(12px)",
83
101
  WebkitBackdropFilter: "blur(12px)",
@@ -86,6 +104,13 @@ export const buttonRecipe = defineRecipe({
86
104
  transition: "all 0.3s ease",
87
105
  _hover: {
88
106
  bg: "buttonBgSecondary",
107
+ // Stated, because the hover repaints a DIFFERENT surface (NEH-877).
108
+ // Without it the label rides its base `buttonTextAccent` onto a
109
+ // secondary background — 1.06:1 in optima's light theme, white on
110
+ // near-white, i.e. the label vanishing on hover. This is the same
111
+ // move `solid` made in NEH-796 and the reason a base-colour fix has
112
+ // to look at every pseudo-state that repaints beneath it.
113
+ color: "buttonTextSecondary",
89
114
  borderColor: "rgba(255,255,255,0.4)",
90
115
  boxShadow: "0 8px 32px rgba(0,0,0,0.3), inset 0 0 20px rgba(255,255,255,0.1)",
91
116
  transform: "translateY(-1px)",
@@ -126,7 +151,12 @@ export const buttonRecipe = defineRecipe({
126
151
  },
127
152
  },
128
153
  selected: {
129
- color: "textPrimary",
154
+ // `textAccent`, not `textPrimary` — the same mispairing `solid` had
155
+ // (NEH-796). `boxBgAccent` is an accent surface and the contract's
156
+ // partner for it is `textAccent`; `textPrimary` is the colour for
157
+ // `boxBgPrimary`, a different surface. Adjacent instance of the defect
158
+ // the issue names, in the same recipe, so it is fixed here.
159
+ color: "textAccent",
130
160
  border: "3px dashed black",
131
161
  borderRadius: "xl",
132
162
  bg: "boxBgAccent",
@@ -15,7 +15,12 @@ export const formRecipe = defineRecipe({
15
15
  variant: {
16
16
  solid: {
17
17
  bg: "boxBgAccent",
18
- color: "textPrimary",
18
+ // `textAccent`, not `textPrimary` (NEH-877). `textPrimary` is the
19
+ // contract's partner for `boxBgPrimary`, a different surface: against
20
+ // optima's light theme, whose accent surface is a near-black graphite,
21
+ // that pairing measures 1.17:1 — a form whose every label is the exact
22
+ // colour of the panel behind it. `textAccent` measures 15.27:1.
23
+ color: "textAccent",
19
24
  borderColor: "borderBgPrimary",
20
25
  "& > li:not(:last-child)": {
21
26
  borderBottom: "1px solid",
@@ -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: "textPrimary",
84
+ color: "buttonTextAccent",
70
85
  _hover: {
71
86
  bg: "buttonBgSecondary",
72
- color: "textPrimary",
87
+ color: "buttonTextSecondary",
73
88
  },
74
89
  },
75
90
  outline: {
76
91
  bg: "buttonBgAccent",
77
- color: "textPrimary",
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
- color: "textPrimary",
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: "textPrimary",
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
- color: "textPrimary",
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
- color: "textPrimary",
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
- color: "textSecondary",
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
- color: "textPrimary",
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
- color: "textAccent",
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
- color: "textSecondary",
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: "textPrimary",
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
- color: "textPrimary",
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: "textPrimary",
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: "textSecondary",
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: "textSecondary",
164
+ color: "buttonTextSecondary",
138
165
  bg: "buttonBgSecondary",
139
166
  borderColor: "borderBgPrimary",
140
167
  "&::placeholder": {
141
- color: "textSecondary/60",
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
- color: "textSecondary",
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
  },
@@ -0,0 +1,96 @@
1
+ import { defineRecipe } from "@pandacss/dev";
2
+
3
+ /**
4
+ * A small label, in one of six tones.
5
+ *
6
+ * ## Why this is a recipe and not `css()` in the component
7
+ *
8
+ * `StyledTag` painted itself with an inline `css()` call, which was right while
9
+ * it had exactly one appearance. A tone chosen at runtime is a different
10
+ * problem: Panda's extractor reads source text, so `tone={STATUS_COLOR[status]}`
11
+ * resolves to nothing and emits no rule — while the class name still lands in
12
+ * the DOM. The result is an unstyled tag, with no build error and no console
13
+ * warning.
14
+ *
15
+ * Recipes are the escape from that, because `staticCssRecipes` in
16
+ * `preset/index.ts` forces **every variant of every recipe** into the
17
+ * stylesheet. So a tone computed from a status map is covered for free, which is
18
+ * how the consuming apps actually use this (NEH-721: HopperGuard drives 109 of
19
+ * its 155 tags from `STATUS_COLOR[item.status]` and friends).
20
+ *
21
+ * ## The tones reuse StyledAlert's vocabulary exactly
22
+ *
23
+ * `info` / `success` / `warning` / `error` are `AlertStatus`, and each pairs the
24
+ * **same tokens** the alert recipe pairs. Two status vocabularies in one package
25
+ * — one for banners and a different one for tags — is how a product ends up with
26
+ * a green that means "success" in one place and "active" in another.
27
+ *
28
+ * Two tones are additional rather than borrowed:
29
+ *
30
+ * - **`neutral`** is the historical default and stays the default, so every
31
+ * existing call site renders exactly as it did before this variant existed.
32
+ * - **`accent`** has no alert equivalent, because an alert is always *about*
33
+ * something being fine or not. A tag is often just a category — a label, a
34
+ * type, a group — and forcing those into `info` would make "informational"
35
+ * mean nothing.
36
+ *
37
+ * ## No border, unlike the alert
38
+ *
39
+ * The alert recipe pairs each background with a `border*` token. A tag is small
40
+ * and usually appears in groups; a 1px edge on each turns a row of five into
41
+ * visual noise, and the tinted background already separates it from the page.
42
+ * The border tokens stay available if a consumer disagrees.
43
+ */
44
+ export const tagRecipe = defineRecipe({
45
+ className: "tag",
46
+ base: {
47
+ display: "inline-flex",
48
+ alignItems: "center",
49
+ gap: "1",
50
+ paddingInline: "2",
51
+ /*
52
+ * Vertical padding is deliberately absent: the height comes from the line
53
+ * box and the horizontal padding, so a tag tracks the font scale instead of
54
+ * needing a re-tune whenever it moves.
55
+ */
56
+ borderRadius: "md",
57
+ /*
58
+ * Not a tap target. A plain tag is not interactive, so the 48px floor does
59
+ * not apply to it — the remove BUTTON inside `StyledTag` is, and states its
60
+ * own.
61
+ */
62
+ fontSize: "sm",
63
+ whiteSpace: "nowrap",
64
+ },
65
+ variants: {
66
+ tone: {
67
+ neutral: {
68
+ backgroundColor: "boxBgSecondary",
69
+ color: "textSecondary",
70
+ },
71
+ info: {
72
+ backgroundColor: "boxInfo",
73
+ color: "textMain",
74
+ },
75
+ success: {
76
+ backgroundColor: "boxSuccess",
77
+ color: "textSuccess",
78
+ },
79
+ warning: {
80
+ backgroundColor: "boxWarning",
81
+ color: "textWarning",
82
+ },
83
+ error: {
84
+ backgroundColor: "boxError",
85
+ color: "textError",
86
+ },
87
+ accent: {
88
+ backgroundColor: "boxBgAccent",
89
+ color: "textMain",
90
+ },
91
+ },
92
+ },
93
+ defaultVariants: {
94
+ tone: "neutral",
95
+ },
96
+ });
@@ -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
- color: "buttonTextSecondary",
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: "buttonTextSecondary",
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: "textSecondary",
61
+ color: "textPrimary",
40
62
  borderColor: "borderBgSecondary",
41
63
  border: "1px solid",
42
64
  },
43
65
  glass: {
44
66
  bg: "boxBgPrimary",
45
- color: "buttonTextPrimary",
67
+ color: "textPrimary",
46
68
  border: "1px solid",
47
69
  borderColor: "borderBgPrimary",
48
70
  },
49
71
  matte: {
50
72
  bg: "boxBgPrimary",
51
- color: "textSecondary",
73
+ color: "textPrimary",
52
74
  borderColor: "borderBgSecondary",
53
75
  border: "1px solid",
54
76
  },
55
77
  ghost: {
56
78
  bg: "boxBgPrimary",
57
- color: "buttonTextSecondary",
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: "textMain",
89
+ color: "textPrimary",
68
90
  border: "1px solid",
69
91
  borderColor: "borderBgPrimary",
70
92
  },
71
93
  unstyled: {
72
94
  bg: "boxBgPrimary",
73
- color: "textMain",
95
+ color: "textPrimary",
74
96
  },
75
97
  },
76
98
  },