@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.
- package/README.md +49 -0
- package/package.json +5 -5
- package/src/components/StyledCollapsible.tsx +14 -27
- package/src/components/StyledFieldHelp.tsx +248 -0
- package/src/components/StyledToaster.tsx +325 -0
- package/src/components/StyledTooltip.tsx +117 -23
- package/src/components/toaster-store.ts +334 -0
- package/src/components/useDisclosure.ts +139 -0
- package/src/config/font-size.ts +25 -0
- package/src/index.ts +37 -0
- package/src/preset/index.ts +5 -2
- package/src/preset/recipes/toast.ts +161 -0
- package/src/preset/semantic-variables.ts +22 -5
|
@@ -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
|
-
* ##
|
|
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. */
|