@stonedogcode/style 0.16.0 → 0.19.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.
@@ -0,0 +1,161 @@
1
+ import { defineSlotRecipe } from "@pandacss/dev";
2
+
3
+ /**
4
+ * A transient message: the region it lives in, and the card itself.
5
+ *
6
+ * Extracted from HopperGuard, where the card was a `cva` in the component file
7
+ * and the region was a nine-cell CSS grid of which one cell was ever used. Both
8
+ * are here now, for the reason every recipe is: a `cva` inside a component is
9
+ * invisible to a consumer's Panda run unless that consumer globs this package's
10
+ * source, whereas a recipe is emitted from config with no parsing at all.
11
+ *
12
+ * ## The accent is a border, not a background
13
+ *
14
+ * Status is carried by a 4px bar down the leading edge rather than by tinting
15
+ * the whole card. Two reasons, and the second is the load-bearing one:
16
+ *
17
+ * - A toast sits over arbitrary page content, so it needs an opaque surface of
18
+ * its own to stay readable. A tint would fight that.
19
+ * - **Colour is never the only cue.** The accent says the same thing as the
20
+ * glyph the component renders beside the message, so a reader who cannot
21
+ * distinguish the hues loses nothing — WCAG 1.4.1, Level A. Deleting the
22
+ * glyph "because the colour already says it" is the regression this note
23
+ * exists to stop.
24
+ *
25
+ * `borderInlineStart` rather than `borderLeft`: in a right-to-left document the
26
+ * accent belongs on the right, and the logical property is what moves it there.
27
+ */
28
+ export const toastRecipe = defineSlotRecipe({
29
+ className: "toast",
30
+ description: "A transient message and the region that stacks them",
31
+ slots: [
32
+ "region",
33
+ "root",
34
+ "indicator",
35
+ "content",
36
+ "title",
37
+ "description",
38
+ "action",
39
+ "close",
40
+ ],
41
+ base: {
42
+ region: {
43
+ position: "fixed",
44
+ // Anchored to one corner rather than laid out in a grid of nine cells:
45
+ // the extracted version declared all nine and rendered into exactly one,
46
+ // so eight of them were markup nothing could ever reach.
47
+ insetBlockEnd: "4",
48
+ insetInlineEnd: "4",
49
+ display: "flex",
50
+ flexDirection: "column",
51
+ alignItems: "flex-end",
52
+ gap: "3",
53
+ // The region spans far enough to stack wide toasts but must not swallow
54
+ // clicks meant for the page beneath it — `none` here, `auto` on each
55
+ // card. Without this pairing a dismissed-but-still-animating toast
56
+ // leaves an invisible plate over the corner of the app.
57
+ pointerEvents: "none",
58
+ maxWidth: "calc(100vw - {spacing.8})",
59
+ zIndex: "toast",
60
+ },
61
+ root: {
62
+ pointerEvents: "auto",
63
+ display: "flex",
64
+ alignItems: "center",
65
+ gap: "4",
66
+ borderRadius: "md",
67
+ boxShadow: "lg",
68
+ paddingInline: "4",
69
+ paddingBlock: "3",
70
+ minWidth: { base: "320px", lg: "600px" },
71
+ maxWidth: { base: "400px", lg: "700px" },
72
+ fontSize: "md",
73
+ // Stated, not inherited: a themed typeface otherwise reaches the page and
74
+ // stops at the edge of the component (NEH-289).
75
+ fontFamily: "body",
76
+ borderWidth: "1px",
77
+ borderStyle: "solid",
78
+ // `borderBgPrimary`, not the `borderSubtle` this was first written with:
79
+ // that token belongs to HopperGuard's vocabulary, not this package's, and
80
+ // Panda passes an unknown token through as a literal — the card would
81
+ // have rendered with `border-color: borderSubtle`, which the browser
82
+ // discards, so the toast would have had no border at all and nothing
83
+ // would have said so. The package's own token-contract test caught it.
84
+ borderColor: "borderBgPrimary",
85
+ backgroundColor: "boxBgPrimary",
86
+ color: "textPrimary",
87
+ transition: "opacity 200ms ease, transform 200ms ease",
88
+ // `data-state` rather than a class: the renderer flips one attribute and
89
+ // the same rule drives both directions, so there is no window in which a
90
+ // toast has neither state.
91
+ "&[data-state='closed']": {
92
+ opacity: "0",
93
+ transform: "translateY(0.5rem)",
94
+ },
95
+ "&[data-state='open']": {
96
+ opacity: "1",
97
+ transform: "translateY(0)",
98
+ },
99
+ },
100
+ indicator: {
101
+ flexShrink: 0,
102
+ display: "flex",
103
+ alignItems: "center",
104
+ justifyContent: "center",
105
+ lineHeight: "1",
106
+ fontSize: "lg",
107
+ },
108
+ content: {
109
+ flex: "1",
110
+ minWidth: "0",
111
+ display: "flex",
112
+ flexDirection: "column",
113
+ gap: "1",
114
+ },
115
+ title: {
116
+ fontWeight: "bold",
117
+ },
118
+ description: {
119
+ display: "block",
120
+ },
121
+ action: {
122
+ flexShrink: 0,
123
+ },
124
+ close: {
125
+ flexShrink: 0,
126
+ display: "inline-flex",
127
+ alignItems: "center",
128
+ justifyContent: "center",
129
+ // 48px, matching every other interactive floor in this package. A close
130
+ // control is the one thing on a toast a person is *aiming* at, often
131
+ // while it is animating, so it is the last place to shave a target down
132
+ // to the size of its glyph.
133
+ minWidth: "48px",
134
+ minHeight: "48px",
135
+ borderRadius: "md",
136
+ borderWidth: "1px",
137
+ borderStyle: "solid",
138
+ borderColor: "transparent",
139
+ background: "transparent",
140
+ color: "inherit",
141
+ cursor: "pointer",
142
+ },
143
+ },
144
+ variants: {
145
+ /**
146
+ * The status accent. `default` deliberately has none — a toast that means
147
+ * nothing in particular should not borrow a colour that means something.
148
+ */
149
+ type: {
150
+ success: { root: { borderInlineStartWidth: "4px", borderInlineStartColor: "borderSuccess" } },
151
+ error: { root: { borderInlineStartWidth: "4px", borderInlineStartColor: "borderError" } },
152
+ warning: { root: { borderInlineStartWidth: "4px", borderInlineStartColor: "borderWarning" } },
153
+ info: { root: { borderInlineStartWidth: "4px", borderInlineStartColor: "borderBgAccent" } },
154
+ loading: { root: { borderInlineStartWidth: "4px", borderInlineStartColor: "borderBgAccent" } },
155
+ default: {},
156
+ },
157
+ },
158
+ defaultVariants: {
159
+ type: "default",
160
+ },
161
+ });
@@ -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
- * ## The percentages are measured, not chosen
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. `emphasis-contrast.ct.tsx` measures both tiers
161
- * against the harness theme in a real browser and asserts they clear WCAG AA
162
- * (4.5:1); the values below are what passed. A host that wants a stronger or
163
- * weaker step defines the property.
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. */