@codenhub/styles 0.1.1 → 0.2.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 +2 -1
- package/dist/aesthetics/chunky-tile.css +1 -1
- package/dist/aesthetics/index.css +1 -1
- package/dist/components.css +1 -1
- package/dist/index.css +1 -1
- package/dist/native.css +1 -1
- package/dist/theme.css +1 -1
- package/dist/tw/aesthetics/chunky-tile.css +16 -0
- package/dist/tw/box.css +24 -2
- package/dist/tw/components/button.css +22 -33
- package/dist/tw/components/feedback.css +58 -41
- package/dist/tw/components/form.css +139 -45
- package/dist/tw/components/index.css +1 -1
- package/dist/tw/components/loader.css +2 -16
- package/dist/tw/components/surface.css +93 -0
- package/dist/tw/components/tooltip.css +102 -91
- package/dist/tw/elevation.css +43 -7
- package/dist/tw/index.css +7 -4
- package/dist/tw/intent.css +16 -12
- package/dist/tw/reset.css +21 -0
- package/dist/tw/theme.css +40 -7
- package/dist/tw/utilities/content.css +90 -16
- package/dist/tw/utilities/index.css +1 -1
- package/dist/tw/utilities/layout.css +33 -0
- package/docs/accessibility.md +33 -23
- package/docs/changelog/0.2.0.md +41 -0
- package/docs/changelog/index.md +1 -0
- package/docs/concepts.md +1 -0
- package/docs/setup.md +41 -22
- package/docs/usage/aesthetics.md +4 -3
- package/docs/usage/buttons.md +17 -13
- package/docs/usage/composing.md +12 -8
- package/docs/usage/content-and-layout.md +10 -3
- package/docs/usage/feedback.md +16 -9
- package/docs/usage/forms.md +36 -26
- package/docs/usage/tooltips.md +48 -22
- package/llms-full.txt +258 -129
- package/package.json +1 -1
|
@@ -67,6 +67,53 @@
|
|
|
67
67
|
--surface-padding: 0;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/* `.soft` rests untinted for no named intent, rather than mixing its 12% --
|
|
71
|
+
measured on a neutral card, 12% of `--color-text` onto any of
|
|
72
|
+
`--color-background`, `--color-foreground`, or `--color-surface` lands
|
|
73
|
+
within a few units of the others (a near-black ink dominates the result
|
|
74
|
+
regardless of which near-white ground it is mixed onto), which is why
|
|
75
|
+
swapping the ground token alone does not read as quieter. A resting
|
|
76
|
+
no-intent `.card.soft` is instead `--color-foreground` with no ink mixed
|
|
77
|
+
in at all -- a plain step up from the page that marks a different
|
|
78
|
+
container without drawing attention to it, the way a card that is not
|
|
79
|
+
currently interesting should.
|
|
80
|
+
|
|
81
|
+
A named intent keeps its ordinary 12% wash rather than being untinted
|
|
82
|
+
unconditionally: 12% of an actual hue does not read as a slab the way
|
|
83
|
+
12% of near-black ink does -- measured, `.card.soft.destructive` at 12%
|
|
84
|
+
is a gentle `249, 225, 225`, nothing like the `217, 217, 217` a neutral
|
|
85
|
+
surface mixing the same 12% of its own ink used to read as -- and
|
|
86
|
+
untinting it anyway would silence a `.card.soft.destructive` at rest
|
|
87
|
+
rather than merely quiet it, the same reason `.panel`'s own carve-out
|
|
88
|
+
(below) is scoped the same way. `:not(...)` names every intent class for
|
|
89
|
+
that reason; a version of this fix that untinted every intent shipped
|
|
90
|
+
first and was corrected once `.panel` needed the identical, narrower
|
|
91
|
+
carve-out to keep a resting `.panel.destructive` legible.
|
|
92
|
+
|
|
93
|
+
`--_fill-cap: 0%` (box.css's override slot, not `--ui-fill` directly --
|
|
94
|
+
see the comment there) composed with `box-hover`'s own formula
|
|
95
|
+
(`min(100%, resting + 14%)`) is what reveals the intent on hover without
|
|
96
|
+
a second fill declaration, for the no-intent case: resting at 0% means
|
|
97
|
+
hover lands at exactly the hover step, 14%, in `--intent-hover` as
|
|
98
|
+
always. The ground steps to `--color-surface` at the same moment,
|
|
99
|
+
through the public `--ui-surface-ground` escape hatch rather than the
|
|
100
|
+
private `--_d-ground`, so a consumer's own override still wins over both
|
|
101
|
+
states the way it always has. Scoped to `.interactive` and `.hoverable`,
|
|
102
|
+
the two classes that already opt a card into `box-hover` -- a plain
|
|
103
|
+
`.card.soft` with neither has no hover state to reveal into and stays at
|
|
104
|
+
the quiet, untinted rest. A named intent needs neither special case: it
|
|
105
|
+
already rests tinted, so the same generic `box-hover` formula deepens it
|
|
106
|
+
on hover (12% to 26%) with no ground shift required. */
|
|
107
|
+
&.soft:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info) {
|
|
108
|
+
--_fill-cap: 0%;
|
|
109
|
+
--ui-surface-ground: var(--color-foreground);
|
|
110
|
+
|
|
111
|
+
&.interactive:hover,
|
|
112
|
+
&.hoverable:hover {
|
|
113
|
+
--ui-surface-ground: var(--color-surface);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
70
117
|
/* Opt-in, because a card is only sometimes a control. Applications still owe
|
|
71
118
|
it a real interactive element and an accessible name. Hover and press are
|
|
72
119
|
the derived ones every other pressable box gets; there is nothing
|
|
@@ -119,4 +166,50 @@
|
|
|
119
166
|
&.flush {
|
|
120
167
|
--surface-padding: 0;
|
|
121
168
|
}
|
|
169
|
+
|
|
170
|
+
/* Neutral panels are the common case -- most are plain layout wells,
|
|
171
|
+
sidebars, and toolbars with no named intent -- and 12% of `--color-text`
|
|
172
|
+
(near-black) mixed onto the page is a far more assertive move than 12%
|
|
173
|
+
of an actual hue: measured, `217, 217, 217` against a `250, 250, 250`
|
|
174
|
+
page, the same "neutral ink reads louder" effect `.card.soft`'s fix
|
|
175
|
+
already answered, and the same reason `--intent-fill-max` stops
|
|
176
|
+
neutral's fill short everywhere else in the model.
|
|
177
|
+
|
|
178
|
+
Scoped to no named intent rather than untinted unconditionally the way
|
|
179
|
+
`data-table` is: a panel is not interactive, so there is no hover to
|
|
180
|
+
reveal a colored intent back into later, and it is edgeless by default,
|
|
181
|
+
so the fill is the only channel a `.primary` bulk-actions bar or a
|
|
182
|
+
`.destructive` danger-zone panel has to say what it is. Untinting those
|
|
183
|
+
unconditionally would silence them, not just quiet them -- measured, a
|
|
184
|
+
resting `.panel.destructive` is a gentle `249, 225, 225` warm wash,
|
|
185
|
+
nothing like the neutral slab above. `.card.soft` carries the identical
|
|
186
|
+
no-named-intent scoping now, corrected to match once this carve-out
|
|
187
|
+
shipped -- see the comment on `.card`'s own `&.soft`, above. `:not(...)`
|
|
188
|
+
names every intent class for that reason, plus `.solid`/`.ghost` so an
|
|
189
|
+
explicit presentation on the element is never clamped to a cap it did
|
|
190
|
+
not ask for -- the same guard `data-table`'s own `:not(.solid, .ghost)`
|
|
191
|
+
uses.
|
|
192
|
+
|
|
193
|
+
Grounds on `--color-foreground` through the public `--ui-surface-ground`
|
|
194
|
+
escape hatch, not the private `--_d-ground`, the same token `.card.soft`
|
|
195
|
+
steps to -- so a consumer's own override still wins, and a bare `.panel`
|
|
196
|
+
rests at exactly the plate a `.card.soft` does (measured: `245, 245, 245`
|
|
197
|
+
light, `23, 23, 23` dark, identical both ways). Permanent rather than
|
|
198
|
+
hover-revealed: neither a panel nor `data-table` has an interaction
|
|
199
|
+
state to reveal a tint into, so both rest quiet rather than reveal.
|
|
200
|
+
|
|
201
|
+
The one cost, inherited rather than new: an own declaration always beats
|
|
202
|
+
an inherited one, so this also beats `.glass` setting the same token on
|
|
203
|
+
an ancestor for its translucent ground -- a neutral panel under `.glass`
|
|
204
|
+
is quiet but opaque rather than blurred. `.card.soft` has carried this
|
|
205
|
+
exact trade-off since it shipped; nothing exercised it there because
|
|
206
|
+
`tests/browser/aesthetics.spec.ts`'s glass coverage tests a default
|
|
207
|
+
(ghost) card, never a soft one. Accepted rather than solved differently:
|
|
208
|
+
there is no way to ask "did an ancestor already set this token" in CSS,
|
|
209
|
+
only which declaration wins, and the quiet neutral rest is the more
|
|
210
|
+
common case a panel needs to get right. */
|
|
211
|
+
&:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid, .ghost) {
|
|
212
|
+
--_fill-cap: 0%;
|
|
213
|
+
--ui-surface-ground: var(--color-foreground);
|
|
214
|
+
}
|
|
122
215
|
}
|
|
@@ -3,16 +3,20 @@
|
|
|
3
3
|
@reference "../box.css";
|
|
4
4
|
@reference "./surface.css";
|
|
5
5
|
|
|
6
|
-
/* The host is a positioning wrapper and nothing else
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
/* The host is a positioning wrapper and nothing else -- it paints no fill, no
|
|
7
|
+
edge, no material of its own. The bubble is a real element beside the
|
|
8
|
+
trigger rather than a pseudo-element attached to it, so intent,
|
|
9
|
+
presentation, aesthetic, and elevation classes go on `.tooltip-bubble`, the
|
|
10
|
+
thing actually shown, not on the wrapper -- the same rule every other
|
|
11
|
+
component in this package follows (theming.md: "Intent does not cascade").
|
|
12
|
+
|
|
13
|
+
Composing the bubble as a real element fixes a real gap, not just a styling
|
|
14
|
+
one: `content: attr(data-tooltip)` text is not reliably exposed to
|
|
15
|
+
assistive tech and cannot be the target of an `aria-describedby`, since that
|
|
16
|
+
needs a real element with a real `id`. A real bubble can carry
|
|
17
|
+
`role="tooltip"` and be referenced by the trigger's own `aria-describedby`;
|
|
18
|
+
wiring that association is the consumer's, this package only styles the
|
|
19
|
+
bubble. See docs/usage/tooltips.md. */
|
|
16
20
|
@utility tooltip {
|
|
17
21
|
--tooltip-gap: 0.5rem;
|
|
18
22
|
--tooltip-top: calc(var(--tooltip-gap) * -1);
|
|
@@ -22,34 +26,7 @@
|
|
|
22
26
|
--tooltip-origin: bottom;
|
|
23
27
|
--tooltip-transform: translate(-50%, -100%);
|
|
24
28
|
|
|
25
|
-
@apply relative;
|
|
26
|
-
|
|
27
|
-
/* The trigger badge, for a page with no element to hang the message on. Quiet
|
|
28
|
-
-- ghost, edged, on `--intent-subtle`, a key cap's resting shape -- because
|
|
29
|
-
it marks that there is a message, it is not one. The line earns its keep:
|
|
30
|
-
`--intent-subtle` is a near-page tint that reads the same across families,
|
|
31
|
-
so a plate alone left three of eight intents indistinguishable;
|
|
32
|
-
`--intent-border` is the slot that differs. Painting the host is right only
|
|
33
|
-
here -- everywhere else the bubble is outside the host's box, so painting it
|
|
34
|
-
boxes the trigger. `.tooltip-icon` is the opt-in that says the host *is* the
|
|
35
|
-
badge. */
|
|
36
|
-
&.tooltip-icon {
|
|
37
|
-
@apply box inline-flex size-5 cursor-default items-center justify-center text-sm font-bold;
|
|
38
|
-
|
|
39
|
-
--_d-fill: 0%;
|
|
40
|
-
--_d-fg-on-fill: 0%;
|
|
41
|
-
--_d-border: 100%;
|
|
42
|
-
--_d-elevation: 0;
|
|
43
|
-
--_d-ground: var(--intent-subtle);
|
|
44
|
-
|
|
45
|
-
border-radius: var(--ui-radius, calc(infinity * 1px));
|
|
46
|
-
/* The one thing `box` hands it that it must refuse. An aesthetic's `clip-path`
|
|
47
|
-
clips a host's pseudo-elements along with the host, and this host's
|
|
48
|
-
pseudo-element is the bubble, positioned outside its box -- so a stepped
|
|
49
|
-
badge under `.pixel` would clip the message away entirely. The bubble
|
|
50
|
-
carries the stepped shape itself; the badge stays round. */
|
|
51
|
-
clip-path: none;
|
|
52
|
-
}
|
|
29
|
+
@apply relative inline-block;
|
|
53
30
|
|
|
54
31
|
&[data-tooltip-position="bottom"] {
|
|
55
32
|
--tooltip-top: auto;
|
|
@@ -72,61 +49,95 @@
|
|
|
72
49
|
--tooltip-origin: left;
|
|
73
50
|
--tooltip-transform: translate(100%, -50%);
|
|
74
51
|
}
|
|
52
|
+
}
|
|
75
53
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
&.edgeless
|
|
54
|
+
/* The bubble. It is a surface, so an aesthetic that blurs surfaces blurs it
|
|
55
|
+
too, and it rests on a ground of its own, edged, and flat -- depth is
|
|
56
|
+
opt-in through its own `.raised`/`.floating`, like any other component, now
|
|
57
|
+
that it is one. No proxy inheritance is needed to carry elevation across a
|
|
58
|
+
host-to-pseudo-element gap: there is no gap, `.tooltip-bubble.raised` reads
|
|
59
|
+
`--ui-elevation` the ordinary way every other component does. See
|
|
60
|
+
model.md#elevation. */
|
|
61
|
+
@utility tooltip-bubble {
|
|
62
|
+
@apply surface pointer-events-none absolute z-(--z-popover) w-max max-w-72 px-2.5 py-2 text-sm font-normal whitespace-normal opacity-0 transition-[opacity,transform] duration-(--motion-duration-normal) ease-(--motion-ease);
|
|
63
|
+
|
|
64
|
+
/* Filled, over a ground of its own. An intent floods the fill
|
|
65
|
+
(`.tooltip-bubble.primary` is the primary colour); with no intent
|
|
66
|
+
`intent.css` resolves the bubble onto `--color-tooltip` and lifts the
|
|
67
|
+
neutral cap, a plate stated per theme because the derived version was a
|
|
68
|
+
mid-grey slab in light and lifted in dark. The ground stays the surface
|
|
69
|
+
tone, not the plate -- pointed at both, the fill axis went inert and
|
|
70
|
+
`axes.spec.ts` caught it. An opaque ground is also what keeps a cascaded
|
|
71
|
+
`.ghost` from leaving the bubble boundaryless. `--ui-surface-ground` is
|
|
72
|
+
read first so a glass tooltip stays glass. */
|
|
73
|
+
--_d-fill: 100%;
|
|
74
|
+
--_d-fg-on-fill: 100%;
|
|
75
|
+
--_d-border: 100%;
|
|
76
|
+
--_d-elevation: 0;
|
|
77
|
+
--_d-ground: var(--ui-surface-ground, var(--intent-subtle));
|
|
78
|
+
|
|
79
|
+
/* The bubble's line is a boundary against content nobody chose, so `box`'s
|
|
80
|
+
edge blend is wrong here: at a full fill the line *is* the plate. Restate
|
|
81
|
+
the colour only; how much line comes from `--_edge-amount`, floored below. */
|
|
82
|
+
--_line: var(--intent-border);
|
|
83
|
+
|
|
84
|
+
/* Floored, for the same reason the ground is opaque: a cascade must not take
|
|
85
|
+
the boundary from a bubble nobody classed (worst in light, where the plate
|
|
86
|
+
is near-white). The element's own `.edgeless` lowers it -- same
|
|
87
|
+
element-vs-cascade idiom `text-control` uses. */
|
|
88
|
+
--_edge-floor: 100%;
|
|
89
|
+
|
|
90
|
+
/* A bubble is a small transient popover, not a modal, so it takes the
|
|
91
|
+
control radius rather than the surface one `surface` hands it. */
|
|
92
|
+
border-radius: var(--ui-radius, var(--radius-control));
|
|
93
|
+
left: var(--tooltip-left);
|
|
94
|
+
right: var(--tooltip-right);
|
|
95
|
+
top: var(--tooltip-top);
|
|
96
|
+
bottom: var(--tooltip-bottom);
|
|
97
|
+
transform: var(--tooltip-transform) scale(0);
|
|
98
|
+
transform-origin: var(--tooltip-origin);
|
|
99
|
+
|
|
100
|
+
&.edgeless {
|
|
123
101
|
--_edge-floor: 0%;
|
|
124
102
|
}
|
|
103
|
+
}
|
|
125
104
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
105
|
+
/* Shown on hover or focus anywhere in the host, or pinned open with
|
|
106
|
+
`data-state="open"`. `:focus-within` rather than the trigger's own
|
|
107
|
+
`:focus-visible`, the same substitution `.input-group` makes for the
|
|
108
|
+
identical reason: the bubble is a sibling, not a pseudo-element, of the
|
|
109
|
+
trigger, so nothing but the shared ancestor can see both at once. The cost
|
|
110
|
+
is the one `.input-group` already accepts -- the bubble also shows on a
|
|
111
|
+
mouse click, which the old `:focus-visible`-only version did not. */
|
|
112
|
+
.tooltip:hover .tooltip-bubble,
|
|
113
|
+
.tooltip:focus-within .tooltip-bubble,
|
|
114
|
+
.tooltip[data-state="open"] .tooltip-bubble {
|
|
115
|
+
opacity: 1;
|
|
116
|
+
transform: var(--tooltip-transform) scale(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* The trigger badge, for a message with no other element to hang it on. Quiet
|
|
120
|
+
-- ghost, edged, on `--intent-subtle`, a key cap's resting shape -- because
|
|
121
|
+
it marks that there is a message, it is not one. The line earns its keep:
|
|
122
|
+
`--intent-subtle` is a near-page tint that reads the same across families,
|
|
123
|
+
so a plate alone left three of eight intents indistinguishable;
|
|
124
|
+
`--intent-border` is the slot that differs.
|
|
125
|
+
|
|
126
|
+
`clip-path: none` used to exist to protect the bubble, a pseudo-element of
|
|
127
|
+
this same host, from being clipped away along with it under an aesthetic
|
|
128
|
+
like `.pixel`. The bubble is independent now, so that reason is gone, but
|
|
129
|
+
the declaration stays: the docs call this a circular fixed identity, not a
|
|
130
|
+
shape a container's aesthetic reaches, the same way `.radio` keeps its own
|
|
131
|
+
circle instead of taking `.pixel`'s stepped silhouette. */
|
|
132
|
+
@utility tooltip-icon {
|
|
133
|
+
@apply box inline-flex size-5 cursor-default items-center justify-center text-sm font-bold;
|
|
134
|
+
|
|
135
|
+
--_d-fill: 0%;
|
|
136
|
+
--_d-fg-on-fill: 0%;
|
|
137
|
+
--_d-border: 100%;
|
|
138
|
+
--_d-elevation: 0;
|
|
139
|
+
--_d-ground: var(--intent-subtle);
|
|
140
|
+
|
|
141
|
+
border-radius: var(--ui-radius, calc(infinity * 1px));
|
|
142
|
+
clip-path: none;
|
|
132
143
|
}
|
package/dist/tw/elevation.css
CHANGED
|
@@ -1,18 +1,54 @@
|
|
|
1
1
|
/* Elevation is a modifier, not an axis: depth is a per-element decision (raised
|
|
2
2
|
tiles beside a flat panel in one aesthetic), so one unitless number multiplies
|
|
3
|
-
the aesthetic's shadow geometry where the component composes it.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
the aesthetic's shadow geometry where the component composes it. `.flat`
|
|
4
|
+
removes a part-based slab or blur without a conditional (complete surface
|
|
5
|
+
shadows like glass remain). See model.md#elevation.
|
|
6
|
+
|
|
7
|
+
`--ui-elevation`, and the plain-page fallback geometry (`--elevation-y`,
|
|
8
|
+
`--elevation-blur`) below, are registered non-inheriting: a class sets them
|
|
9
|
+
on the element it is written on and nothing else picks them up. They used to
|
|
10
|
+
be ordinary inherited custom properties, on the reasoning that a container
|
|
11
|
+
could flatten or lift a whole region in one class and any element inside
|
|
12
|
+
could opt back out -- but the same mechanism let a `.floating` dialog's
|
|
13
|
+
ambient ×2 reach its own unclassed "Cancel"/"Remove" buttons, doubling their
|
|
14
|
+
shadow with nothing in the markup asking for that. Depth is not the same
|
|
15
|
+
kind of thing as color or material: a button inside a raised card is not
|
|
16
|
+
itself more raised, it is just resting normally inside an already-raised
|
|
17
|
+
surface. `--ui-shadow-*` -- what an aesthetic supplies -- is unaffected and
|
|
18
|
+
still cascades normally; that is a different mechanism (the material tokens
|
|
19
|
+
in `src/aesthetics/*.css`), and an aesthetic still has to reach every nested
|
|
20
|
+
component uniformly the way it always did. Registering these three is what
|
|
21
|
+
scopes the change to the elevation modifier alone.
|
|
10
22
|
|
|
11
23
|
`.flat`/`.raised`/`.floating` and `.elevation-none`/`-sm`/`-md` are
|
|
12
24
|
interchangeable aliases, the second borrowing the raw `--elevation-*` scale's
|
|
13
25
|
naming. Bare `sm`/`md`/`lg` would collide with the size modifier;
|
|
14
26
|
`.elevation-lg` is reserved, not shipped. */
|
|
15
27
|
|
|
28
|
+
/* `syntax: "*"` with no `initial-value` is deliberate: a numeric syntax would
|
|
29
|
+
require one (CSS Properties and Values API), and any initial-value at all
|
|
30
|
+
would mean the property is never actually unset on an element that does not
|
|
31
|
+
write it -- which is exactly the state `var(--ui-elevation, var(--_d-elevation,
|
|
32
|
+
1))` in `box.css` needs to fall through to a component's own default. With
|
|
33
|
+
no initial-value and `inherits: false`, an element that does not set the
|
|
34
|
+
property directly computes it as guaranteed-invalid, the same as if it had
|
|
35
|
+
never been registered at all -- `var()` takes its fallback precisely where
|
|
36
|
+
inheritance used to hand it an ambient value instead. */
|
|
37
|
+
@property --ui-elevation {
|
|
38
|
+
syntax: "*";
|
|
39
|
+
inherits: false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@property --elevation-y {
|
|
43
|
+
syntax: "*";
|
|
44
|
+
inherits: false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@property --elevation-blur {
|
|
48
|
+
syntax: "*";
|
|
49
|
+
inherits: false;
|
|
50
|
+
}
|
|
51
|
+
|
|
16
52
|
/* No part-based depth. Complete surface shadow values, such as glass, are not
|
|
17
53
|
reached by the elevation multiplier. */
|
|
18
54
|
.elevation-none,
|
package/dist/tw/index.css
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
sees only this package's own files. The `src/` scan is load-bearing -- it is
|
|
5
5
|
how the composition utilities (`box`, `surface`, `text-control`,
|
|
6
6
|
`input-group`, `loader-mask`) get emitted as standalone rules, since nothing
|
|
7
|
-
`@source inline`s them -- so `source(none)` is not an option.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
`@source inline`s them -- so `source(none)` is not an option. A handful of
|
|
8
|
+
Tailwind utility names also collide with a CSS property or a word this
|
|
9
|
+
file's own comments use (`backdrop-filter`, `filter`, `transition`,
|
|
10
|
+
`transform`, `container`), which reads like a risk of the same scan --
|
|
11
|
+
verified against Tailwind 4.3.2, none of them emit.
|
|
12
|
+
`tests/integration/exports.test.ts` guards it, so a scanner change that
|
|
13
|
+
starts emitting one is caught rather than shipping silently.
|
|
11
14
|
|
|
12
15
|
The `@source not` lines below keep everything that is not `src/` out of that
|
|
13
16
|
scan: the sibling app and fixture directories, the `docs/` tree, the build
|
package/dist/tw/intent.css
CHANGED
|
@@ -133,7 +133,8 @@
|
|
|
133
133
|
.data-table,
|
|
134
134
|
.pre,
|
|
135
135
|
.quote,
|
|
136
|
-
.tooltip,
|
|
136
|
+
.tooltip-bubble,
|
|
137
|
+
.tooltip-icon,
|
|
137
138
|
.badge,
|
|
138
139
|
.kbd,
|
|
139
140
|
.code,
|
|
@@ -168,8 +169,8 @@
|
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
/* The bubble a tooltip draws is the one plate in the package that is chosen
|
|
171
|
-
rather than composed, so
|
|
172
|
-
|
|
172
|
+
rather than composed, so it resolves the no-intent case onto its own pair
|
|
173
|
+
instead of onto the shared neutral. Composed, it was 20% of the page's
|
|
173
174
|
ink over `--color-surface`, which lifts off a dark page and sinks into a light
|
|
174
175
|
one -- see `--color-tooltip` in `theme.css` for the measurements.
|
|
175
176
|
|
|
@@ -178,17 +179,20 @@
|
|
|
178
179
|
rather than an ink, so a full fill of it is the plate and there is nothing to
|
|
179
180
|
stop short of.
|
|
180
181
|
|
|
181
|
-
`--intent-subtle` stays the surface token
|
|
182
|
-
|
|
183
|
-
|
|
182
|
+
`--intent-subtle` stays the surface token, the same tone every other
|
|
183
|
+
unclassed component sits on. `.tooltip-icon`, the trigger badge, is a
|
|
184
|
+
separate element now and does not read this block at all -- it never fills
|
|
185
|
+
past `0%`, so the generic neutral reset above it is enough, and it keeps its
|
|
186
|
+
own resting ground and line independent of whatever intent the bubble shows.
|
|
184
187
|
|
|
185
188
|
Two selectors, because `none` and `neutral` have to agree here. `:where()`
|
|
186
|
-
loses to any intent class, so `.tooltip.primary` still floods with
|
|
187
|
-
`.tooltip.neutral` carries 0-2-0 and beats `.neutral`, so
|
|
188
|
-
out gives the same bubble as writing nothing. Without the
|
|
189
|
-
two would differ on a component where they mean the
|
|
190
|
-
|
|
191
|
-
.tooltip
|
|
189
|
+
loses to any intent class, so `.tooltip-bubble.primary` still floods with
|
|
190
|
+
primary; `.tooltip-bubble.neutral` carries 0-2-0 and beats `.neutral`, so
|
|
191
|
+
writing the intent out gives the same bubble as writing nothing. Without the
|
|
192
|
+
second selector the two would differ on a component where they mean the
|
|
193
|
+
same thing. */
|
|
194
|
+
:where(.tooltip-bubble),
|
|
195
|
+
.tooltip-bubble.neutral {
|
|
192
196
|
--intent-color: var(--color-tooltip);
|
|
193
197
|
--intent-contrast: var(--color-tooltip-contrast);
|
|
194
198
|
--intent-hover: var(--color-tooltip);
|
package/dist/tw/reset.css
CHANGED
|
@@ -127,3 +127,24 @@
|
|
|
127
127
|
border-color: CanvasText;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
|
+
|
|
131
|
+
/* The user agent already promises both of these for a native `<dialog>` --
|
|
132
|
+
`dialog:not([open]) { display: none }` and a centered `margin: auto` on the
|
|
133
|
+
open box -- but both are user-agent origin, which loses to any author rule
|
|
134
|
+
at all. `.card`'s own `display: block` (`surface`'s `@apply box block`)
|
|
135
|
+
beats the first outright, so a `<dialog class="card">` renders open with no
|
|
136
|
+
`open` attribute and no `.showModal()` call -- the registry has no
|
|
137
|
+
dialog/modal component of its own, and `.card` is the closest primitive a
|
|
138
|
+
consumer reaches for. A margin reset on every element -- Tailwind's own
|
|
139
|
+
Preflight on the entries that carry it -- beats the second, so an opened
|
|
140
|
+
dialog pins to the top-left instead of centering. Restated here, unlayered like
|
|
141
|
+
the block above, for the same reason: a behavior override has to win over
|
|
142
|
+
any layered rule whatever the specificity, and `.card` is layered in the
|
|
143
|
+
utilities layer regardless of how it composes `box`. */
|
|
144
|
+
dialog:not([open]) {
|
|
145
|
+
display: none;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
dialog[open] {
|
|
149
|
+
margin: auto;
|
|
150
|
+
}
|
package/dist/tw/theme.css
CHANGED
|
@@ -24,7 +24,17 @@
|
|
|
24
24
|
--color-accent-hover: light-dark(var(--color-neutral-700), var(--color-neutral-300));
|
|
25
25
|
--color-accent-subtle: light-dark(var(--color-neutral-100), var(--color-neutral-800));
|
|
26
26
|
--color-accent-strong: light-dark(var(--color-neutral-800), var(--color-neutral-200));
|
|
27
|
-
|
|
27
|
+
/* Light was neutral-300 (1.42:1 against the page, already under 3:1 by the
|
|
28
|
+
text-control comment below) until a `.card.soft.edged` measured its own
|
|
29
|
+
border against its own fill rather than against the page: 12% of
|
|
30
|
+
`--intent-color` mixed into the ground and a line resolving to nearly the
|
|
31
|
+
same tone landed the two 4 RGB units apart, which reads as no boundary at
|
|
32
|
+
all. Dark's neutral-700 pairing already separated -- 27 units, still under
|
|
33
|
+
3:1 but at least legible -- so only light moved. Neutral-400 is not a new
|
|
34
|
+
number: `.chunky-tile` already measured it at 2.48:1 against the light
|
|
35
|
+
page and picked it for the same reason, "the heaviest grey that still
|
|
36
|
+
leaves a field legible." Dark is unchanged. */
|
|
37
|
+
--color-border: light-dark(var(--color-neutral-400), var(--color-neutral-700));
|
|
28
38
|
--color-border-hover: light-dark(var(--color-neutral-600), var(--color-neutral-500));
|
|
29
39
|
|
|
30
40
|
/* A text control's line is the only thing identifying it: the box is
|
|
@@ -214,10 +224,33 @@
|
|
|
214
224
|
color-scheme: dark;
|
|
215
225
|
}
|
|
216
226
|
|
|
217
|
-
/* The
|
|
227
|
+
/* The first arm mirrors the `color-scheme: dark` block above, all three
|
|
218
228
|
spellings included -- a consumer writing `.theme-dark` for the token theme
|
|
219
|
-
was otherwise left without the matching `dark:` variant.
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
was otherwise left without the matching `dark:` variant.
|
|
230
|
+
|
|
231
|
+
The second arm closes the gap that left open: `--color-*` tokens resolve
|
|
232
|
+
through `light-dark()` against the computed `color-scheme`, so with no
|
|
233
|
+
explicit selector at all `:root`'s `color-scheme: light dark` already
|
|
234
|
+
follows the OS preference -- see `concepts.md#theme-selection`. A selector
|
|
235
|
+
list cannot query computed style the way `light-dark()` does, but it can
|
|
236
|
+
still match "OS prefers dark, and not under a forced-light subtree" with a
|
|
237
|
+
plain descendant exclusion: `:not(:where(.light, .light *, ...))` matches
|
|
238
|
+
only when no ancestor (or the element itself) carries one of the three
|
|
239
|
+
light spellings. That is the one case the exclusion cannot see -- a
|
|
240
|
+
subtree forced light through some means other than these three selectors,
|
|
241
|
+
where `light-dark()` would still honor it but this variant could not
|
|
242
|
+
detect it. Out of contract: those three spellings are the only supported
|
|
243
|
+
way to force a theme, so nothing else is expected to set `color-scheme`
|
|
244
|
+
directly. Each arm is independent, so an explicit selector still wins at
|
|
245
|
+
any depth even inside a subtree the other arm would otherwise match. */
|
|
246
|
+
@custom-variant dark {
|
|
247
|
+
&:where(.dark, .dark *, .theme-dark, .theme-dark *, [data-theme="dark"], [data-theme="dark"] *) {
|
|
248
|
+
@slot;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@media (prefers-color-scheme: dark) {
|
|
252
|
+
&:not(:where(.light, .light *, .theme-light, .theme-light *, [data-theme="light"], [data-theme="light"] *)) {
|
|
253
|
+
@slot;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|