@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
package/dist/theme.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
|
2
|
-
:root,:host{--color-amber-100:oklch(96.2% .059 95.617);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-300:oklch(81% .117 11.638);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-100:oklch(97% 0 0);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-white:#fff;--color-primary:light-dark(var(--color-neutral-950),var(--color-neutral-50));--color-primary-contrast:light-dark(var(--color-neutral-50),var(--color-neutral-950));--color-primary-hover:light-dark(var(--color-neutral-700),var(--color-neutral-200));--color-primary-subtle:light-dark(var(--color-neutral-200),var(--color-neutral-900));--color-primary-strong:light-dark(var(--color-neutral-900),var(--color-neutral-100));--color-accent:light-dark(var(--color-neutral-600),var(--color-neutral-400));--color-accent-contrast:light-dark(var(--color-neutral-50),var(--color-neutral-950));--color-accent-hover:light-dark(var(--color-neutral-700),var(--color-neutral-300));--color-accent-subtle:light-dark(var(--color-neutral-100),var(--color-neutral-800));--color-accent-strong:light-dark(var(--color-neutral-800),var(--color-neutral-200));--color-border:light-dark(var(--color-neutral-
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--ui-elevation:initial;--elevation-y:initial;--elevation-blur:initial}}}:root,:host{--color-amber-100:oklch(96.2% .059 95.617);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-100:oklch(95% .052 163.051);--color-emerald-300:oklch(84.5% .143 164.978);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-emerald-900:oklch(37.8% .077 168.94);--color-indigo-100:oklch(93% .034 272.788);--color-indigo-300:oklch(78.5% .115 274.713);--color-indigo-600:oklch(51.1% .262 276.966);--color-indigo-800:oklch(39.8% .195 277.366);--color-indigo-900:oklch(35.9% .144 278.697);--color-rose-100:oklch(94.1% .03 12.58);--color-rose-300:oklch(81% .117 11.638);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-rose-900:oklch(41% .159 10.272);--color-neutral-50:oklch(98.5% 0 0);--color-neutral-100:oklch(97% 0 0);--color-neutral-200:oklch(92.2% 0 0);--color-neutral-300:oklch(87% 0 0);--color-neutral-400:oklch(70.8% 0 0);--color-neutral-500:oklch(55.6% 0 0);--color-neutral-600:oklch(43.9% 0 0);--color-neutral-700:oklch(37.1% 0 0);--color-neutral-800:oklch(26.9% 0 0);--color-neutral-900:oklch(20.5% 0 0);--color-neutral-950:oklch(14.5% 0 0);--color-white:#fff;--color-primary:light-dark(var(--color-neutral-950),var(--color-neutral-50));--color-primary-contrast:light-dark(var(--color-neutral-50),var(--color-neutral-950));--color-primary-hover:light-dark(var(--color-neutral-700),var(--color-neutral-200));--color-primary-subtle:light-dark(var(--color-neutral-200),var(--color-neutral-900));--color-primary-strong:light-dark(var(--color-neutral-900),var(--color-neutral-100));--color-accent:light-dark(var(--color-neutral-600),var(--color-neutral-400));--color-accent-contrast:light-dark(var(--color-neutral-50),var(--color-neutral-950));--color-accent-hover:light-dark(var(--color-neutral-700),var(--color-neutral-300));--color-accent-subtle:light-dark(var(--color-neutral-100),var(--color-neutral-800));--color-accent-strong:light-dark(var(--color-neutral-800),var(--color-neutral-200));--color-border:light-dark(var(--color-neutral-400),var(--color-neutral-700));--color-control-border:var(--color-neutral-500);--color-surface:light-dark(var(--color-neutral-200),var(--color-neutral-800));--color-text:light-dark(var(--color-neutral-950),var(--color-neutral-50));--color-text-contrast:light-dark(var(--color-neutral-50),var(--color-neutral-950));--color-text-hover:light-dark(var(--color-neutral-700),var(--color-neutral-200));--color-text-strong:light-dark(var(--color-neutral-900),var(--color-neutral-100));--color-tooltip:light-dark(var(--color-white),var(--color-neutral-700));--color-tooltip-contrast:light-dark(var(--color-neutral-900),var(--color-neutral-100));--color-success:var(--color-emerald-700);--color-success-contrast:var(--color-neutral-50);--color-success-hover:light-dark(#007552,#008e66);--color-success-subtle:light-dark(var(--color-emerald-100),var(--color-emerald-900));--color-success-strong:light-dark(var(--color-emerald-800),var(--color-emerald-300));--color-warning:var(--color-amber-600);--color-warning-contrast:var(--color-neutral-950);--color-warning-hover:light-dark(#d46700,#e78300);--color-warning-subtle:light-dark(var(--color-amber-100),var(--color-amber-900));--color-warning-strong:light-dark(var(--color-amber-800),var(--color-amber-300));--color-destructive:var(--color-rose-700);--color-destructive-contrast:var(--color-neutral-50);--color-destructive-hover:light-dark(#c00036,#d1054b);--color-destructive-subtle:light-dark(var(--color-rose-100),var(--color-rose-900));--color-destructive-strong:light-dark(var(--color-rose-800),var(--color-rose-300));--color-info:var(--color-indigo-600);--color-info-contrast:var(--color-neutral-50);--color-info-hover:light-dark(#4b36e9,#5e4ff9);--color-info-subtle:light-dark(var(--color-indigo-100),var(--color-indigo-900));--color-info-strong:light-dark(var(--color-indigo-800),var(--color-indigo-300))}:root,.neutral{--intent-color:var(--color-text);--intent-contrast:var(--color-text-contrast);--intent-hover:var(--color-text-hover);--intent-strong:var(--color-text-strong);--intent-subtle:var(--color-surface);--intent-border:var(--ui-ink,var(--color-border));--intent-fill-max:20%}.primary{--intent-color:var(--color-primary);--intent-contrast:var(--color-primary-contrast);--intent-hover:var(--color-primary-hover);--intent-strong:var(--color-primary-strong);--intent-subtle:var(--color-primary-subtle);--intent-border:var(--color-primary);--intent-fill-max:100%}.secondary{--intent-color:var(--color-accent);--intent-contrast:var(--color-accent-contrast);--intent-hover:var(--color-accent-hover);--intent-strong:var(--color-accent-strong);--intent-subtle:var(--color-accent-subtle);--intent-border:var(--color-accent);--intent-fill-max:100%}.success{--intent-color:var(--color-success);--intent-contrast:var(--color-success-contrast);--intent-hover:var(--color-success-hover);--intent-strong:var(--color-success-strong);--intent-subtle:var(--color-success-subtle);--intent-border:var(--color-success);--intent-fill-max:100%}.warning{--intent-color:var(--color-warning);--intent-contrast:var(--color-warning-contrast);--intent-hover:var(--color-warning-hover);--intent-strong:var(--color-warning-strong);--intent-subtle:var(--color-warning-subtle);--intent-border:var(--color-warning);--intent-fill-max:100%}.destructive,.danger,.error{--intent-color:var(--color-destructive);--intent-contrast:var(--color-destructive-contrast);--intent-hover:var(--color-destructive-hover);--intent-strong:var(--color-destructive-strong);--intent-subtle:var(--color-destructive-subtle);--intent-border:var(--color-destructive);--intent-fill-max:100%}.info{--intent-color:var(--color-info);--intent-contrast:var(--color-info-contrast);--intent-hover:var(--color-info-hover);--intent-strong:var(--color-info-strong);--intent-subtle:var(--color-info-subtle);--intent-border:var(--color-info);--intent-fill-max:100%}:where(.btn,.ipt,.text-control,.surface,.textarea,.select,.input-group,.checkbox,.radio,.switch,.card,.panel,.alert,.data-table,.pre,.quote,.tooltip-bubble,.tooltip-icon,.badge,.kbd,.code,.loader,.skeleton,.progress,.divider){--intent-color:var(--color-text);--intent-contrast:var(--color-text-contrast);--intent-hover:var(--color-text-hover);--intent-strong:var(--color-text-strong);--intent-subtle:var(--color-surface);--intent-border:var(--ui-ink,var(--color-border));--intent-fill-max:20%}:where(.text-control,.ipt,.textarea,.select,.input-group,.checkbox,.radio,.switch){--intent-border:var(--ui-ink,var(--color-control-border))}:where(.tooltip-bubble),.tooltip-bubble.neutral{--intent-color:var(--color-tooltip);--intent-contrast:var(--color-tooltip-contrast);--intent-hover:var(--color-tooltip);--intent-strong:var(--color-tooltip-contrast);--intent-subtle:var(--color-surface);--intent-border:var(--ui-ink,var(--color-border));--intent-fill-max:100%}:where(.loader,.skeleton,.progress,.divider){--intent-color:currentColor;--intent-border:currentColor}.solid{--ui-fill:100%;--ui-fg-on-fill:100%}.soft{--ui-fill:12%;--ui-fg-on-fill:0%}.ghost{--ui-fill:0%;--ui-fg-on-fill:0%}.edged{--ui-border:100%}.edgeless{--ui-border:0%}@property --ui-elevation{syntax:"*";inherits:false}@property --elevation-y{syntax:"*";inherits:false}@property --elevation-blur{syntax:"*";inherits:false}.elevation-none,.flat{--ui-elevation:0}.elevation-sm,.raised,.elevation-md,.floating{--elevation-y:1px;--elevation-blur:3px}.elevation-sm,.raised{--ui-elevation:1}.elevation-md,.floating{--ui-elevation:2}:root{color-scheme:light dark;--container-narrow:48rem;--container-max:80rem;--container-wide:90rem;--layout-gutter:clamp(1rem, 4vw, 2rem);--layout-section-block:clamp(4rem, 9vw, 7rem);--layout-gap:1rem;--layout-grid-min:16rem;--radius-small:.25rem;--radius-control:.5rem;--radius-surface:.875rem;--control-height:2.5rem;--border-width:1px;--elevation-color:light-dark(#0f172a14,#0000008c);--elevation-none:none;--elevation-sm:0 1px 2px var(--elevation-color), 0 1px 3px var(--elevation-color);--elevation-md:0 4px 6px -1px var(--elevation-color), 0 2px 4px -2px var(--elevation-color);--elevation-lg:0 20px 25px -5px var(--elevation-color), 0 8px 10px -6px var(--elevation-color);--focus-ring:color-mix(in srgb, light-dark(var(--color-neutral-950),var(--color-neutral-50)) 64%, transparent)}@supports (color:color-mix(in lab, red, red)){:root{--focus-ring:color-mix(in srgb, var(--color-primary) 64%, transparent)}}:root{--focus-ring-offset:calc(-1 * var(--border-width));--focus-ring-width:3px;--motion-duration-fast:.12s;--motion-duration-normal:.2s;--motion-duration-slow:.4s;--motion-ease:cubic-bezier(.2, 0, 0, 1);--z-popover:40;--hover-step:14%;--ui-active-transform:scale(.97)}@media (prefers-reduced-motion:reduce){:root{--ui-active-transform:none}}.light,.theme-light,[data-theme=light]{color-scheme:light}.dark,.theme-dark,[data-theme=dark]{color-scheme:dark}
|
|
@@ -107,3 +107,19 @@
|
|
|
107
107
|
font-weight: 800;
|
|
108
108
|
letter-spacing: 0.04em;
|
|
109
109
|
}
|
|
110
|
+
|
|
111
|
+
/* `--tile-lift` alone cannot reach this: it resolves once, on `.chunky-tile`
|
|
112
|
+
itself (`--_tile-lift: var(--tile-lift, 4px)`), and inherits down already a
|
|
113
|
+
plain `4px` -- a nested `--tile-lift` on a descendant button is a fresh
|
|
114
|
+
declaration nothing downstream reads. `--ui-shadow-y` is what `box` actually
|
|
115
|
+
multiplies by elevation, so that is the token restated here, at half the
|
|
116
|
+
resting lift. A `.btn.icon` at `.dense`/`.p-xs` is close to the smallest
|
|
117
|
+
button the package draws (~20px, once the size floor both tiers drop for
|
|
118
|
+
`.icon` is gone -- see `button.css`), and a flat 4px bar under a target that
|
|
119
|
+
size sits close enough to the glyph's own footprint to read as a solid fill
|
|
120
|
+
behind it -- a `.ghost` button that looks `.solid`. 2px keeps the bar
|
|
121
|
+
legible as depth without swallowing the icon. */
|
|
122
|
+
.chunky-tile .btn.icon.dense,
|
|
123
|
+
.chunky-tile .btn.icon.p-xs {
|
|
124
|
+
--ui-shadow-y: 2px;
|
|
125
|
+
}
|
package/dist/tw/box.css
CHANGED
|
@@ -12,8 +12,30 @@
|
|
|
12
12
|
by `--intent-fill-max` -- neutral stops early because a full fill of the
|
|
13
13
|
page's ink is a slab. The cap lands before the hover step, so a capped
|
|
14
14
|
intent still deepens on hover instead of pinning rest and hover together.
|
|
15
|
-
See model.md#the-ink-is-gated-by-the-plate-not-tied-to-the-fill.
|
|
16
|
-
|
|
15
|
+
See model.md#the-ink-is-gated-by-the-plate-not-tied-to-the-fill.
|
|
16
|
+
|
|
17
|
+
`--_fill-cap` is the matching slot for a component that reinterprets what a
|
|
18
|
+
presentation means for itself rather than merely bounding it -- `.card.soft`
|
|
19
|
+
uses it to rest at 0% regardless of what `.soft` asks for. It has to be a
|
|
20
|
+
separate term rather than a rewrite of `--ui-fill`: `presentation.css`'s
|
|
21
|
+
`.soft` is unlayered CSS and `@utility card`'s own rule compiles into
|
|
22
|
+
Tailwind's `utilities` layer, and an unlayered declaration always wins over
|
|
23
|
+
a layered one for the same property, at any specificity -- so `.card.soft`
|
|
24
|
+
writing `--ui-fill: 0%` directly was silently losing to `.soft`'s `12%`
|
|
25
|
+
every time. `--_fill-cap` is a token `.soft` never touches, so nothing
|
|
26
|
+
contests it.
|
|
27
|
+
|
|
28
|
+
Restated here at `100%` -- a no-op -- for the same reason `--_d-ground`
|
|
29
|
+
is restated `transparent` below rather than left to its `var()` fallback:
|
|
30
|
+
`--_fill-cap` inherits like any custom property, so `.card.soft` setting
|
|
31
|
+
it to `0%` would otherwise reach every box nested inside, not just the
|
|
32
|
+
card itself -- a badge sitting in a `.data-table:not(.solid, .ghost)`
|
|
33
|
+
cell inherited the table's `0%` cap and rendered with no fill and no
|
|
34
|
+
visible chip at all. An own declaration beats an inherited one at any
|
|
35
|
+
specificity, so restating it here is what keeps a nested box reading its
|
|
36
|
+
own presentation instead of the container's reinterpretation of one. */
|
|
37
|
+
--_fill-cap: 100%;
|
|
38
|
+
--_capped: min(var(--ui-fill, var(--_d-fill)), var(--_fill-cap, 100%), var(--intent-fill-max, 100%));
|
|
17
39
|
--_fill: calc(var(--_capped) * var(--ui-bg-alpha, 1));
|
|
18
40
|
--_hover-fill: calc(min(100%, var(--_capped) + var(--hover-step)) * var(--ui-bg-alpha, 1));
|
|
19
41
|
/* How much of the foreground is the contrast ink: what the presentation asks
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
@reference "tailwindcss";
|
|
2
2
|
@import "../theme.css";
|
|
3
|
-
@reference "./loader.css";
|
|
4
3
|
@reference "../box.css";
|
|
5
4
|
|
|
6
5
|
/* A filled, edgeless, raised box you can press. `box` paints it; everything
|
|
@@ -39,17 +38,26 @@
|
|
|
39
38
|
`.destructive`/`.danger`/`.error` uses on the intent axis. Bare `sm`/`lg`
|
|
40
39
|
are already the size modifier's class names, so `p-` prefixes here to
|
|
41
40
|
avoid colliding with a button's own size. `.p-xs` is the tightest step,
|
|
42
|
-
below `.compact`: a chip-sized button in a toolbar or a table cell.
|
|
41
|
+
below `.compact`: a chip-sized button in a toolbar or a table cell.
|
|
42
|
+
|
|
43
|
+
`.icon` drops both floors -- `min-width` from its own base rule below and
|
|
44
|
+
`min-height` from `--control-height` -- at every padding tier, labeled
|
|
45
|
+
buttons keep the height floor everywhere but `.p-xs`/`.dense`. `.icon`
|
|
46
|
+
already asks to be square: `aspect-square` ties its width to whatever
|
|
47
|
+
height it lands on, so a height floor that survives keeps the button
|
|
48
|
+
bigger than its own glyph regardless of how tight the padding asked to be
|
|
49
|
+
-- the one combination `.p-xs`/`.dense` used to get right and the other two
|
|
50
|
+
tiers did not, leaving a `.icon.compact` floating in a box far bigger than
|
|
51
|
+
it needed and a `.icon.spacious` doing the same one step further out. */
|
|
43
52
|
&.p-xs,
|
|
44
53
|
&.dense {
|
|
45
54
|
@apply px-2 py-0.5;
|
|
46
55
|
|
|
47
|
-
/* The one padding step that also drops the size floor
|
|
48
|
-
button is a chip in a toolbar or a table
|
|
49
|
-
is taller than the row it sits in;
|
|
50
|
-
keeps the floor, so a plain button
|
|
51
|
-
|
|
52
|
-
box is padding around whatever the consumer sized their glyph to. */
|
|
56
|
+
/* The one padding step that also drops the size floor for a labeled
|
|
57
|
+
button. At this tier the button is a chip in a toolbar or a table
|
|
58
|
+
cell, where `--control-height` is taller than the row it sits in;
|
|
59
|
+
every other size and padding class keeps the floor, so a plain button
|
|
60
|
+
stays a comfortable target. */
|
|
53
61
|
min-height: 0;
|
|
54
62
|
|
|
55
63
|
&.icon {
|
|
@@ -65,6 +73,9 @@
|
|
|
65
73
|
|
|
66
74
|
&.icon {
|
|
67
75
|
@apply p-1;
|
|
76
|
+
|
|
77
|
+
min-width: 0;
|
|
78
|
+
min-height: 0;
|
|
68
79
|
}
|
|
69
80
|
}
|
|
70
81
|
|
|
@@ -74,35 +85,13 @@
|
|
|
74
85
|
|
|
75
86
|
&.icon {
|
|
76
87
|
@apply p-3;
|
|
88
|
+
|
|
89
|
+
min-width: 0;
|
|
90
|
+
min-height: 0;
|
|
77
91
|
}
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
&.pill {
|
|
81
95
|
@apply rounded-full;
|
|
82
96
|
}
|
|
83
|
-
|
|
84
|
-
&.loading {
|
|
85
|
-
/* `text-transparent!` forces the label invisible whatever the intent
|
|
86
|
-
resolved to, so the spinner is the only thing left to read. */
|
|
87
|
-
@apply pointer-events-none text-transparent!;
|
|
88
|
-
|
|
89
|
-
& > * {
|
|
90
|
-
opacity: 0 !important;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/* The spinner takes the label's colour, which `box` already resolved
|
|
94
|
-
against this button's own intent and presentation. It reads `--_fg`
|
|
95
|
-
rather than `currentColor`: the rule above forces `color` to transparent
|
|
96
|
-
to hide the label, and a spinner painted in `currentColor` inherits that
|
|
97
|
-
and paints nothing at all. */
|
|
98
|
-
&::after {
|
|
99
|
-
@apply loader-mask absolute block size-4 -translate-x-1/2 -translate-y-1/2;
|
|
100
|
-
|
|
101
|
-
background-color: var(--_fg);
|
|
102
|
-
|
|
103
|
-
top: calc(50% + var(--border-width));
|
|
104
|
-
left: calc(50% + var(--border-width));
|
|
105
|
-
content: "";
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
97
|
}
|
|
@@ -30,54 +30,59 @@
|
|
|
30
30
|
/* A soft, edged surface, so an aesthetic that blurs surfaces blurs this too: an
|
|
31
31
|
alert is a container carrying a message, not a control. */
|
|
32
32
|
@utility alert {
|
|
33
|
-
@apply surface flex p-4 leading-5;
|
|
33
|
+
@apply surface flex items-start gap-3 p-4 leading-5;
|
|
34
34
|
|
|
35
35
|
--_d-fill: 12%;
|
|
36
36
|
--_d-fg-on-fill: 0%;
|
|
37
37
|
--_d-border: 100%;
|
|
38
38
|
--_d-elevation: 0;
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
&.warning::before {
|
|
63
|
-
mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z'/><path d='M12 9v4'/><path d='M12 17h.01'/></svg>");
|
|
64
|
-
-webkit-mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z'/><path d='M12 9v4'/><path d='M12 17h.01'/></svg>");
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
&.destructive::before,
|
|
68
|
-
&.danger::before,
|
|
69
|
-
&.error::before {
|
|
70
|
-
mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><path d='m15 9-6 6'/><path d='m9 9 6 6'/></svg>");
|
|
71
|
-
-webkit-mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><path d='m15 9-6 6'/><path d='m9 9 6 6'/></svg>");
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
&.info::before {
|
|
75
|
-
mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><path d='M12 16v-4'/><path d='M12 8h.01'/></svg>");
|
|
76
|
-
-webkit-mask-image: url("data:image/svg+xml;charset=utf-8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><path d='M12 16v-4'/><path d='M12 8h.01'/></svg>");
|
|
77
|
-
}
|
|
40
|
+
/* Same carve-out as `.panel`'s, and for the same reason: a neutral alert
|
|
41
|
+
mixes 12% of near-black ink onto the page, measured `217, 217, 217`
|
|
42
|
+
against `250, 250, 250` -- the identical slab `.panel` read before its
|
|
43
|
+
own fix, not a status-signaling problem, since this is scoped to no
|
|
44
|
+
named intent. A named intent keeps its ordinary wash; untinting
|
|
45
|
+
`.alert.soft.destructive` too would silence the status it exists to
|
|
46
|
+
carry rather than quiet it, which is why an earlier, unconditional
|
|
47
|
+
version of this fix was rejected for `.alert` specifically. This
|
|
48
|
+
version never reaches that case: `:not(...)` names every intent class,
|
|
49
|
+
plus `.solid`/`.ghost` so an explicit presentation on the element is
|
|
50
|
+
never clamped to a cap it did not ask for.
|
|
51
|
+
|
|
52
|
+
Grounds on `--color-foreground` through the public `--ui-surface-ground`
|
|
53
|
+
escape hatch, exactly like `.panel` and a resting `.card.soft` -- an
|
|
54
|
+
alert has no hover state to reveal a tint into either, so this is
|
|
55
|
+
permanent rather than revealed. Unlike a neutral panel or badge, an
|
|
56
|
+
alert cannot lose its visual identity from this: `--_d-border: 100%`
|
|
57
|
+
above holds regardless of fill, so a neutral alert still reads as a
|
|
58
|
+
framed box, just a quiet one instead of a grey slab. */
|
|
59
|
+
&:not(.primary, .secondary, .success, .warning, .destructive, .danger, .error, .info, .solid, .ghost) {
|
|
60
|
+
--_fill-cap: 0%;
|
|
61
|
+
--ui-surface-ground: var(--color-foreground);
|
|
78
62
|
}
|
|
79
63
|
}
|
|
80
64
|
|
|
65
|
+
/* No icon is shipped: the five hardcoded per-intent masks this used to paint on
|
|
66
|
+
a `::before` are gone, the same way the per-input-type icon was removed from
|
|
67
|
+
`.input-group` -- see `docs/internal/model.md#input-group-owns-the-box-the-control-inside-gives-it-up`.
|
|
68
|
+
`.alert` is
|
|
69
|
+
already `flex`, so an icon dropped in as a real child is spaced by `gap-3`
|
|
70
|
+
with nothing to trigger: `.alert-icon` only fixes its size and alignment, not
|
|
71
|
+
whether one is present. Any icon element works, the same contract
|
|
72
|
+
`.input-group` documents -- an inline `<svg>`, an `<img>`, or a class from an
|
|
73
|
+
icon set such as `@codenhub/icons`. */
|
|
74
|
+
@utility alert-icon {
|
|
75
|
+
@apply inline-flex flex-none items-center justify-center;
|
|
76
|
+
|
|
77
|
+
/* `!important`: an icon-set glyph (`@codenhub/icons` among them) carries its
|
|
78
|
+
own `width`/`height` on a selector this utility cannot outrank -- an
|
|
79
|
+
attribute selector reads more specific than one class regardless of
|
|
80
|
+
stylesheet order. Sizing what you drop in is this utility's one job, so it
|
|
81
|
+
has to win over the glyph's own opinion. */
|
|
82
|
+
width: 1.25rem !important;
|
|
83
|
+
height: 1.25rem !important;
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
/* A soft, edgeless chip. Small enough that the structural silhouette would be
|
|
82
87
|
most of its height, so it takes the tight one where an aesthetic ships a pair. */
|
|
83
88
|
@utility badge {
|
|
@@ -114,10 +119,22 @@
|
|
|
114
119
|
|
|
115
120
|
/* The same: intent only. The track is 10px tall, which is most of a border under
|
|
116
121
|
any aesthetic thick enough to have an opinion, so the bar draws no edge and the
|
|
117
|
-
ceilings that used to keep one survivable are gone with it.
|
|
122
|
+
ceilings that used to keep one survivable are gone with it.
|
|
123
|
+
|
|
124
|
+
`--progress-surface` used to mix `--intent-subtle` at 60% -- a tint of the
|
|
125
|
+
same tone the track already sits on (a bare page, or a `.soft` card), which
|
|
126
|
+
measured under 1.2:1 against both in either theme, well under the 3:1 WCAG
|
|
127
|
+
1.4.11 floor `registry.json` already cites for the checkbox, radio, and
|
|
128
|
+
switch. `--color-border` was tried next and measured no better (1.05:1): it
|
|
129
|
+
is calibrated for a line against the page, and a `.soft` card already sits
|
|
130
|
+
close to that same lightness, so a border-strength token disappears against
|
|
131
|
+
it too. Mixing `--color-text-secondary` instead is what actually separates
|
|
132
|
+
the track from both -- it is a step read against text at rest everywhere
|
|
133
|
+
else in the package, and stepping toward it here clears 3:1 (3.63-4.26:1
|
|
134
|
+
measured) on a bare page and a `.soft` card, in both themes. */
|
|
118
135
|
@utility progress {
|
|
119
136
|
--progress-color: var(--intent-color);
|
|
120
|
-
--progress-surface: color-mix(in srgb, var(--
|
|
137
|
+
--progress-surface: color-mix(in srgb, var(--color-text-secondary) 70%, transparent);
|
|
121
138
|
|
|
122
139
|
@apply relative h-2.5 w-full overflow-hidden bg-(--progress-surface);
|
|
123
140
|
/* Round by default; an aesthetic that declares no radius squares the track and
|
|
@@ -58,8 +58,21 @@
|
|
|
58
58
|
tick on light grey.
|
|
59
59
|
|
|
60
60
|
Ramping against `--_fill` keeps the two in step at every bound, including
|
|
61
|
-
the checked state that lifts them.
|
|
62
|
-
|
|
61
|
+
the checked state that lifts them.
|
|
62
|
+
|
|
63
|
+
`--_fg-on-fill-floor` is the seam `:checked` pins instead of touching
|
|
64
|
+
`--ui-fg-on-fill` directly. `.ghost`/`.soft` are unlayered CSS (P3, and see
|
|
65
|
+
model.md's `--_fill-cap` history) and `text-control` compiles into
|
|
66
|
+
Tailwind's `utilities` layer, so a layered `:checked` writing
|
|
67
|
+
`--ui-fg-on-fill` the way it writes `--_fill-cap`/`--_fill-floor` would
|
|
68
|
+
lose to `.ghost`'s unlayered `--ui-fg-on-fill: 0%` regardless of
|
|
69
|
+
specificity -- the same trap `--_fill-cap` exists to avoid on the fill
|
|
70
|
+
itself. A private floor `.ghost`/`.soft` never touch has nothing
|
|
71
|
+
unlayered contesting it. */
|
|
72
|
+
--_on-fill: max(
|
|
73
|
+
var(--_fg-on-fill-floor, 0%),
|
|
74
|
+
min(var(--ui-fg-on-fill, var(--_d-fg-on-fill)), max(0%, calc(var(--_fill) * 2 - 100%)))
|
|
75
|
+
);
|
|
63
76
|
/* The resting line is a fraction of the intent rather than all of it, and the
|
|
64
77
|
fraction is what makes the pointer visible. Drawn at full strength a control
|
|
65
78
|
already sits at its intent's darkest, and hover moves it to a neighbouring
|
|
@@ -120,14 +133,41 @@
|
|
|
120
133
|
--_line-tone: var(--intent-color);
|
|
121
134
|
}
|
|
122
135
|
|
|
123
|
-
/* R8, P6: `:checked`
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
136
|
+
/* R8, P6: `:checked` pins the fill by making the floor and the cap the same
|
|
137
|
+
value, so `clamp()` returns exactly that value whatever `--ui-fill` asks --
|
|
138
|
+
still a bound on the input, still lifted rather than a rewrite of the
|
|
139
|
+
result (the failure mode P6 exists for: an early version pinned `--_fill`
|
|
140
|
+
and `--_fg` directly, which made a checked toggle ignore its own aesthetic
|
|
141
|
+
and elevation along with presentation).
|
|
142
|
+
|
|
143
|
+
The floor used to sit at `12%` rather than `100%`, which kept presentation
|
|
144
|
+
live on the checked mark too -- a checked `.soft` toggle rendered a pale
|
|
145
|
+
plate with just the mark at full strength, the classic ring-and-dot radio.
|
|
146
|
+
The stress-test pass found that cost more than it bought on a real screen:
|
|
147
|
+
three presentations gave a checkbox, a radio, and a switch three different
|
|
148
|
+
"on" identities to learn, while every *unchecked* toggle read the same
|
|
149
|
+
washed-out plate regardless of which class asked for it -- close enough to
|
|
150
|
+
the disabled look that a form full of ordinary, unchecked, un-intended
|
|
151
|
+
controls read as half-disabled. Pinning checked to one plate and freeing
|
|
152
|
+
presentation to separate the *unchecked* one instead (see
|
|
153
|
+
`checkbox`/`radio`/`switch` below) moves the axis to where it was doing
|
|
154
|
+
something. Lives here, not on the toggles, because a text input is never
|
|
155
|
+
`:checked`.
|
|
156
|
+
|
|
157
|
+
The ink is pinned alongside the fill, through `--_fg-on-fill-floor` rather
|
|
158
|
+
than `--ui-fg-on-fill` directly (see that seam, above): the other half of
|
|
159
|
+
what a fill class asks for is how much contrast ink it wants
|
|
160
|
+
(`--ui-fg-on-fill: 0%` is `.ghost`/`.soft` declaring "none, my plate stays
|
|
161
|
+
quiet"), and pinning the fill without pinning this left a checked
|
|
162
|
+
`.ghost`/`.soft` toggle with an opaque plate and a mark that still read as
|
|
163
|
+
though the plate were quiet -- both derived from the same intent tone,
|
|
164
|
+
close enough to disappear into each other. `--_on-fill` ramps against the
|
|
165
|
+
*composed* `--_fill`, so once that is pinned to 100% the ink has to reach
|
|
166
|
+
full contrast too, or the ramp's own ceiling has nothing to reach. */
|
|
128
167
|
&:checked {
|
|
129
168
|
--_fill-cap: 100%;
|
|
130
|
-
--_fill-floor:
|
|
169
|
+
--_fill-floor: 100%;
|
|
170
|
+
--_fg-on-fill-floor: 100%;
|
|
131
171
|
}
|
|
132
172
|
|
|
133
173
|
/* Native decorations a field replaces with its own artwork. */
|
|
@@ -164,11 +204,25 @@
|
|
|
164
204
|
--_fill-cap: 20%;
|
|
165
205
|
}
|
|
166
206
|
|
|
167
|
-
/* A checked
|
|
168
|
-
cap here.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
:
|
|
207
|
+
/* A checked text-control fills with its own intent, so neutral has to drop its
|
|
208
|
+
20% cap here. No toggle needs this arm any more (see below), but a text
|
|
209
|
+
input is never `:checked` in practice either -- kept for whatever custom
|
|
210
|
+
`.text-control` composition does check itself. Declared where the intents
|
|
211
|
+
are (0-2-0, unlayered): inside `text-control` `.neutral` would overrule it. */
|
|
212
|
+
:is(.text-control, input):checked {
|
|
213
|
+
--intent-fill-max: 100%;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* The three toggles read their own component cap (`--_fill-cap`, below) as the
|
|
217
|
+
ceiling regardless of intent, checked or not -- not just at `:checked`, the
|
|
218
|
+
way the arm above still works for a hypothetical checked text-control. A
|
|
219
|
+
neutral fill stops at 20% everywhere else because it is the page's own ink,
|
|
220
|
+
and a full one reads as a slab on a badge or a card. That reasoning does not
|
|
221
|
+
hold for a toggle at rest: a neutral checkbox is the common case, most
|
|
222
|
+
controls carry no intent class at all, and it has to look like a present,
|
|
223
|
+
interactive box rather than a washed-out one just because nothing colored
|
|
224
|
+
it. Declared where the intents are, for the reason given above. */
|
|
225
|
+
:is(.checkbox, .radio, .switch) {
|
|
172
226
|
--intent-fill-max: 100%;
|
|
173
227
|
}
|
|
174
228
|
|
|
@@ -182,6 +236,21 @@
|
|
|
182
236
|
--_edge-floor: 0%;
|
|
183
237
|
}
|
|
184
238
|
|
|
239
|
+
/* `.switch`'s one exception to the opt-out above: `.ghost` already asks for
|
|
240
|
+
zero fill, so a `.switch.ghost.edgeless` track has neither a line nor a
|
|
241
|
+
plate and disappears entirely -- nothing marks its bounds but the floating
|
|
242
|
+
knob. Every other fill on this component still has something to paint
|
|
243
|
+
(`.soft`/`.solid`/default all rest above 0%), so only this one pairing
|
|
244
|
+
needs the floor held up. `.switch.ghost.edgeless` renders identically to
|
|
245
|
+
`.switch.ghost.edged` now, published unsupported as a distinct look --
|
|
246
|
+
the same "produces a row indistinguishable from its neighbour" reason
|
|
247
|
+
`.solid.edgeless`/`.solid.edged` already collapse to one row for. Higher
|
|
248
|
+
specificity than the rule above (three classes, not an `:is()` of one)
|
|
249
|
+
wins regardless of source order. */
|
|
250
|
+
.switch.ghost.edgeless {
|
|
251
|
+
--_edge-floor: 100%;
|
|
252
|
+
}
|
|
253
|
+
|
|
185
254
|
/* An invalid control is destructive whatever else it was asked to be. A plain
|
|
186
255
|
unlayered rule, not `&[aria-invalid]` inside `@utility`: the intent reset is
|
|
187
256
|
unlayered too and would overrule a layered version (leaving a grey line and
|
|
@@ -307,9 +376,17 @@
|
|
|
307
376
|
No icons ship. Any element works in a slot -- an inline `<svg>`, an `<img>`,
|
|
308
377
|
or a class from an icon set such as `@codenhub/icons` -- placed before the
|
|
309
378
|
control for a leading adornment and after it for a trailing one. See
|
|
310
|
-
docs/usage/forms.md#icons.
|
|
379
|
+
docs/usage/forms.md#icons.
|
|
380
|
+
|
|
381
|
+
The gap between the control and its adornment is a token, `--input-group-gap`,
|
|
382
|
+
rather than a bare Tailwind `gap-2`: an icon reads that 0.5rem as the normal
|
|
383
|
+
breathing room a distinct glyph needs, but a text affix meant to glue onto the
|
|
384
|
+
value -- a URL host, a currency symbol, a unit -- reads the same gap as a
|
|
385
|
+
stray space breaking the two apart. A consumer sets `--input-group-gap: 0` on
|
|
386
|
+
their own `.input-group` for a flush affix; the default stays the icon-tuned
|
|
387
|
+
0.5rem so every existing icon composition is unchanged. */
|
|
311
388
|
@utility input-group {
|
|
312
|
-
@apply text-control flex w-full items-center
|
|
389
|
+
@apply text-control flex w-full items-center;
|
|
313
390
|
|
|
314
391
|
/* After the `@apply`, for the reason `ipt` states it: `text-control` inlines
|
|
315
392
|
its own resting geometry there, so a default written above it loses to the
|
|
@@ -320,9 +397,11 @@
|
|
|
320
397
|
--_d-border: 100%;
|
|
321
398
|
--_d-elevation: 0;
|
|
322
399
|
|
|
400
|
+
--input-group-gap: 0.5rem;
|
|
323
401
|
--input-group-pad: 0.75rem;
|
|
324
402
|
|
|
325
403
|
min-height: var(--control-height);
|
|
404
|
+
gap: var(--input-group-gap);
|
|
326
405
|
padding-inline: var(--input-group-pad);
|
|
327
406
|
|
|
328
407
|
/* The focus ring the wrapper cannot get from `:focus-visible` -- a `<div>` is
|
|
@@ -382,18 +461,26 @@
|
|
|
382
461
|
|
|
383
462
|
/* The three toggles compose `text-control` for its bounds, not its chrome: the
|
|
384
463
|
edge floor keeps a container's `.edgeless` from erasing the closed silhouette
|
|
385
|
-
(WCAG 1.4.11), and the checked state that
|
|
386
|
-
They raise the fill cap to
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
464
|
+
(WCAG 1.4.11), and the checked state that pins the fill (see `text-control`'s
|
|
465
|
+
`:checked` above) lives there. They raise the fill cap to 40% -- the 6% one
|
|
466
|
+
keeps typed text legible and a toggle has none, and 40% is where a resting
|
|
467
|
+
`.ghost` (0%), `.soft`/default (12%), and `.solid` (the cap itself) read as
|
|
468
|
+
three distinct plates rather than three shades of the same wash, the same
|
|
469
|
+
separation `.switch` was already measured against. Unclassed rests at
|
|
470
|
+
`.soft` rather than at the cap: `--_d-fill`/`--_d-fg-on-fill` below name the
|
|
471
|
+
same pair `.soft` does, so a bare `<input class="checkbox">` reads as the
|
|
472
|
+
quiet tint the stress-test pass preferred over the louder plate `.solid`
|
|
473
|
+
asks for -- a consumer still reaches that plate by writing `.solid` itself.
|
|
474
|
+
`.ghost` used to be unsupported here -- a checked one used to be a mark on
|
|
475
|
+
nothing -- and is not any more, now that checked reads the same plate
|
|
476
|
+
whatever the presentation.
|
|
477
|
+
See model.md#the-bounds-that-survive-and-the-test-for-keeping-one. */
|
|
391
478
|
@utility checkbox {
|
|
392
479
|
@apply text-control inline-grid size-4 cursor-pointer place-content-center;
|
|
393
480
|
|
|
394
|
-
--_d-fill:
|
|
395
|
-
--_d-fg-on-fill:
|
|
396
|
-
--_fill-cap:
|
|
481
|
+
--_d-fill: 12%;
|
|
482
|
+
--_d-fg-on-fill: 0%;
|
|
483
|
+
--_fill-cap: 40%;
|
|
397
484
|
--_line-rest: 100%;
|
|
398
485
|
--_d-border: 100%;
|
|
399
486
|
--_d-elevation: 0;
|
|
@@ -439,13 +526,14 @@
|
|
|
439
526
|
}
|
|
440
527
|
}
|
|
441
528
|
|
|
442
|
-
/* The same control with a round silhouette and a dot for its mark
|
|
529
|
+
/* The same control with a round silhouette and a dot for its mark, and the
|
|
530
|
+
same fill cap raise given on `checkbox`. */
|
|
443
531
|
@utility radio {
|
|
444
532
|
@apply text-control inline-grid size-4 cursor-pointer place-content-center rounded-full;
|
|
445
533
|
|
|
446
|
-
--_d-fill:
|
|
447
|
-
--_d-fg-on-fill:
|
|
448
|
-
--_fill-cap:
|
|
534
|
+
--_d-fill: 12%;
|
|
535
|
+
--_d-fg-on-fill: 0%;
|
|
536
|
+
--_fill-cap: 40%;
|
|
449
537
|
--_line-rest: 100%;
|
|
450
538
|
--_d-border: 100%;
|
|
451
539
|
--_d-elevation: 0;
|
|
@@ -470,12 +558,14 @@
|
|
|
470
558
|
content: "";
|
|
471
559
|
}
|
|
472
560
|
|
|
473
|
-
/* A checked radio thickens its ring and takes the intent whole on the line
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
561
|
+
/* A checked radio thickens its ring and takes the intent whole on the line.
|
|
562
|
+
The fill is pinned by `text-control`'s `:checked` now, not left to the
|
|
563
|
+
presentation -- a filled circle at every checked radio, whatever fill
|
|
564
|
+
class it carries, rather than the classic ring-and-dot a checked `.soft`
|
|
565
|
+
used to draw. The dot still takes `box`'s composed foreground, which walks
|
|
566
|
+
to the contrast tone as the (now always full) plate fills, so it stays
|
|
567
|
+
readable. `--_line-tone` rather than `--_edge`, so `text-control`'s edge
|
|
568
|
+
floor still draws the ring under a container's `.edgeless`. */
|
|
479
569
|
&:checked {
|
|
480
570
|
--_line-tone: var(--intent-color);
|
|
481
571
|
|
|
@@ -491,24 +581,28 @@
|
|
|
491
581
|
}
|
|
492
582
|
|
|
493
583
|
/* A2: every dimension derives from the border width `box` resolved, so the knob
|
|
494
|
-
cannot drift out of a track a thick aesthetic widened.
|
|
495
|
-
higher than the other toggles because its track is the whole component. */
|
|
584
|
+
cannot drift out of a track a thick aesthetic widened. */
|
|
496
585
|
@utility switch {
|
|
497
586
|
@apply text-control relative cursor-pointer;
|
|
498
587
|
|
|
499
|
-
--_d-fill:
|
|
500
|
-
--_d-fg-on-fill:
|
|
588
|
+
--_d-fill: 12%;
|
|
589
|
+
--_d-fg-on-fill: 0%;
|
|
501
590
|
--_line-rest: 100%;
|
|
502
591
|
--_d-border: 100%;
|
|
503
592
|
--_d-elevation: 0;
|
|
504
593
|
|
|
505
|
-
/* 40%, measured: at `text-control`'s 6% the three fills collapsed to
|
|
506
|
-
and the switch had to vary its *line* per fill class --
|
|
507
|
-
class decided an edge. 40% separates all three
|
|
508
|
-
light page, against ~20 for a visible one)
|
|
509
|
-
track's 100%, so the knob's position
|
|
510
|
-
|
|
511
|
-
|
|
594
|
+
/* 40%, measured: at `text-control`'s 6% the three resting fills collapsed to
|
|
595
|
+
two looks and the switch had to vary its *line* per fill class instead --
|
|
596
|
+
the one place a fill class decided an edge. 40% separates all three
|
|
597
|
+
(29/67/144 sRGB steps on the light page, against ~20 for a visible one)
|
|
598
|
+
without reaching a checked track's pinned 100%, so the knob's position
|
|
599
|
+
still says which. `axes.spec.ts` asserts all three. `checkbox`/`radio`
|
|
600
|
+
raise their own cap to the same value for the same reason -- a switch does
|
|
601
|
+
not need a higher one, a track is just the component every toggle's box
|
|
602
|
+
already was. A cap rather than a resting value, because it answers the
|
|
603
|
+
cascade case; `--intent-fill-max` (declared where the intents are, above)
|
|
604
|
+
lifts to 100% for all three toggles so a neutral one reads this cap too,
|
|
605
|
+
not the 20% ceiling a badge or a card stays under. */
|
|
512
606
|
--_fill-cap: 40%;
|
|
513
607
|
|
|
514
608
|
--switch-height: 1.25rem;
|
|
@@ -25,4 +25,4 @@
|
|
|
25
25
|
makes the emission deliberate; the composition-body check in
|
|
26
26
|
`exports.test.ts` fails the build if one is dropped. `text-control` and
|
|
27
27
|
`surface` are also documented as consumer-usable on their own. */
|
|
28
|
-
@source inline("box box-hover box-active loader-mask text-control surface input-group text-display text-title-lg text-title text-title-sm text-label-lg text-label text-body btn field label hint ipt email password url tel search date datetime month week time icon left right textarea select checkbox radio switch alert badge skeleton progress tooltip card panel interactive flush compact spacious neutral solid soft ghost edged edgeless flat raised floating loader");
|
|
28
|
+
@source inline("box box-hover box-active loader-mask text-control surface input-group text-display text-title-lg text-title text-title-sm text-label-lg text-label text-body btn field label hint ipt email password url tel search date datetime month week time icon left right textarea select checkbox radio switch alert alert-icon badge skeleton progress tooltip tooltip-bubble tooltip-icon card panel interactive flush compact spacious neutral solid soft ghost edged edgeless flat raised floating loader");
|
|
@@ -28,19 +28,6 @@
|
|
|
28
28
|
.bars-wave,
|
|
29
29
|
.pulse-ring
|
|
30
30
|
)
|
|
31
|
-
),
|
|
32
|
-
.loading:not(
|
|
33
|
-
:where(
|
|
34
|
-
.dots-wave,
|
|
35
|
-
.dots-fade,
|
|
36
|
-
.dots-queue,
|
|
37
|
-
.dots-rotate,
|
|
38
|
-
.dots-grow,
|
|
39
|
-
.dots-grow-alternate,
|
|
40
|
-
.dot-bounce,
|
|
41
|
-
.bars-wave,
|
|
42
|
-
.pulse-ring
|
|
43
|
-
)
|
|
44
31
|
) {
|
|
45
32
|
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg stroke='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.spinner_V8m1{transform-origin:center;animation:spinner_zKoa 2s linear infinite}.spinner_V8m1 circle{stroke-linecap:round;animation:spinner_YpZS 1.5s ease-in-out infinite}@keyframes spinner_zKoa{100%{transform:rotate(360deg)}}@keyframes spinner_YpZS{0%{stroke-dasharray:0 150;stroke-dashoffset:0}47.5%{stroke-dasharray:42 150;stroke-dashoffset:-16}95%,100%{stroke-dasharray:42 150;stroke-dashoffset:-59}}</style><g class='spinner_V8m1'><circle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'></circle></g></svg>");
|
|
46
33
|
}
|
|
@@ -87,15 +74,14 @@
|
|
|
87
74
|
a loader inside filled content matches the text around it and
|
|
88
75
|
`.loader.success` is green. There is nothing else to know. */
|
|
89
76
|
@utility loader {
|
|
90
|
-
@apply loader-mask inline-block size-
|
|
77
|
+
@apply loader-mask inline-block size-7 [&.lg]:size-9 [&.sm]:size-5;
|
|
91
78
|
|
|
92
79
|
color: var(--intent-color);
|
|
93
80
|
background-color: currentColor;
|
|
94
81
|
}
|
|
95
82
|
|
|
96
83
|
@media (prefers-reduced-motion: reduce) {
|
|
97
|
-
.loader
|
|
98
|
-
.loading {
|
|
84
|
+
.loader {
|
|
99
85
|
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='9.5' fill='none' stroke='black' stroke-width='3' stroke-linecap='round' stroke-dasharray='42 18'/></svg>");
|
|
100
86
|
}
|
|
101
87
|
|