@codenhub/styles 0.0.3 → 0.1.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/LICENSE +201 -0
- package/NOTICE +52 -0
- package/README.md +60 -90
- package/dist/aesthetics/chunky-tile.css +1 -0
- package/dist/aesthetics/glass.css +1 -0
- package/dist/aesthetics/index.css +2 -0
- package/dist/aesthetics/neobrutalism.css +1 -0
- package/dist/aesthetics/pixel.css +1 -0
- package/dist/components.css +2 -2
- package/dist/index.css +2 -2
- package/dist/native.css +2 -0
- package/dist/theme.css +2 -2
- package/dist/tw/aesthetics/chunky-tile.css +107 -0
- package/dist/tw/aesthetics/glass.css +66 -0
- package/dist/tw/aesthetics/index.css +9 -0
- package/dist/tw/aesthetics/neobrutalism.css +63 -0
- package/dist/tw/aesthetics/pixel.css +88 -0
- package/dist/tw/box.css +185 -0
- package/dist/tw/components/button.css +88 -0
- package/dist/tw/components/feedback.css +163 -0
- package/dist/tw/components/form.css +601 -0
- package/dist/tw/components/index.css +18 -0
- package/dist/tw/components/loader.css +122 -0
- package/dist/tw/components/surface.css +98 -0
- package/dist/tw/components/tooltip.css +132 -0
- package/dist/tw/elevation.css +39 -0
- package/dist/tw/index.css +12 -0
- package/dist/tw/intent.css +189 -0
- package/dist/tw/native.css +203 -0
- package/dist/tw/presentation.css +77 -0
- package/dist/tw/reset.css +142 -0
- package/dist/tw/theme.css +218 -0
- package/dist/tw/typography.css +34 -0
- package/dist/tw/utilities/content.css +349 -0
- package/dist/tw/utilities/index.css +6 -0
- package/dist/tw/utilities/layout.css +107 -0
- package/docs/accessibility.md +43 -14
- package/docs/concepts.md +94 -0
- package/docs/index.md +62 -73
- package/docs/integrating/astro.md +29 -0
- package/docs/integrating/index.md +32 -0
- package/docs/integrating/nextjs.md +44 -0
- package/docs/integrating/svelte.md +37 -0
- package/docs/integrating/tailwind.md +40 -0
- package/docs/integrating/vue.md +25 -0
- package/docs/setup.md +116 -0
- package/docs/usage/aesthetics.md +197 -0
- package/docs/usage/buttons.md +74 -0
- package/docs/usage/composing.md +218 -0
- package/docs/usage/content-and-layout.md +200 -0
- package/docs/usage/customizing.md +213 -0
- package/docs/usage/feedback.md +117 -0
- package/docs/usage/forms.md +177 -0
- package/docs/usage/index.md +32 -0
- package/docs/usage/theming.md +210 -0
- package/docs/usage/tooltips.md +45 -0
- package/llms-full.txt +2122 -0
- package/llms.txt +26 -0
- package/package.json +96 -32
- package/docs/classes.md +0 -198
- package/docs/roadmap.md +0 -34
- package/docs/tests.md +0 -128
- package/docs/tokens.md +0 -101
- package/src/components/button.css +0 -155
- package/src/components/feedback.css +0 -214
- package/src/components/form.css +0 -296
- package/src/components/index.css +0 -9
- package/src/components/loader.css +0 -56
- package/src/components/surface.css +0 -6
- package/src/components/tooltip.css +0 -60
- package/src/index.css +0 -7
- package/src/overrides.css +0 -76
- package/src/theme.css +0 -136
- package/src/typography.css +0 -30
- package/src/utilities.css +0 -40
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
@reference "tailwindcss";
|
|
2
|
+
@source inline("loader dots-wave dots-fade dots-queue dots-rotate dots-grow dots-grow-alternate dot-bounce bars-wave pulse-ring");
|
|
3
|
+
|
|
4
|
+
/* The mask machinery, shared by the loader and by any component that shows a
|
|
5
|
+
spinner of its own. Artwork is a mask rather than an image so it takes its
|
|
6
|
+
colour from whatever paints behind it, which is the whole indicator contract:
|
|
7
|
+
an intent class recolours the loader and nothing else has to know. */
|
|
8
|
+
@utility loader-mask {
|
|
9
|
+
mask-image: var(--loader-art);
|
|
10
|
+
-webkit-mask-image: var(--loader-art);
|
|
11
|
+
mask-size: contain;
|
|
12
|
+
-webkit-mask-size: contain;
|
|
13
|
+
mask-repeat: no-repeat;
|
|
14
|
+
-webkit-mask-repeat: no-repeat;
|
|
15
|
+
mask-position: center;
|
|
16
|
+
-webkit-mask-position: center;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.loader:not(
|
|
20
|
+
:where(
|
|
21
|
+
.dots-wave,
|
|
22
|
+
.dots-fade,
|
|
23
|
+
.dots-queue,
|
|
24
|
+
.dots-rotate,
|
|
25
|
+
.dots-grow,
|
|
26
|
+
.dots-grow-alternate,
|
|
27
|
+
.dot-bounce,
|
|
28
|
+
.bars-wave,
|
|
29
|
+
.pulse-ring
|
|
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
|
+
) {
|
|
45
|
+
--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
|
+
}
|
|
47
|
+
|
|
48
|
+
@utility dots-wave {
|
|
49
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg fill='currentColor' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_8HQG{0%,57.14%{animation-timing-function:cubic-bezier(.33,.66,.66,1);transform:translate(0)}28.57%{animation-timing-function:cubic-bezier(.33,0,.66,.33);transform:translateY(-6px)}to{transform:translate(0)}}.spinner_qM83{animation:spinner_8HQG 1.05s infinite}</style><circle class='spinner_qM83' cx='4' cy='12' r='3'/><circle class='spinner_qM83' cx='12' cy='12' r='3' style='animation-delay:.1s'/><circle class='spinner_qM83' cx='20' cy='12' r='3' style='animation-delay:.2s'/></svg>");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@utility dots-fade {
|
|
53
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.dot{animation:dot-anim 1s ease-in-out infinite;fill:currentColor}.d1{animation-delay:-0.32s}.d2{animation-delay:-0.16s}@keyframes dot-anim{0%,80%,100%{transform:scale(0.6);opacity:0.2}40%{transform:scale(1);opacity:1}}</style><circle class='dot d1' cx='4' cy='12' r='3' transform-origin='4px 12px'></circle><circle class='dot d2' cx='12' cy='12' r='3' transform-origin='12px 12px'></circle><circle class='dot d3' cx='20' cy='12' r='3' transform-origin='20px 12px'></circle></svg>");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@utility dots-queue {
|
|
57
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_qtyZ{0%{r:0}25%{r:3px;cx:4px}50%{r:3px;cx:12px}75%{r:3px;cx:20px}to{r:0;cx:20px}}.spinner_nOfF{animation:spinner_qtyZ 2s cubic-bezier(.36,.6,.31,1) infinite}</style><circle class='spinner_nOfF' cx='4' cy='12' r='3'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-.5s'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-1s'/><circle class='spinner_nOfF' cx='4' cy='12' r='3' style='animation-delay:-1.5s'/></svg>");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@utility dots-rotate {
|
|
61
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_XVY9{50%{transform:rotate(180deg)}to{transform:rotate(360deg)}}</style><circle cx='12' cy='12' r='3'/><g style='transform-origin:center;animation:spinner_XVY9 1s cubic-bezier(.36,.6,.31,1) infinite'><circle cx='4' cy='12' r='3'/><circle cx='20' cy='12' r='3'/></g></svg>");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@utility dots-grow {
|
|
65
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_xe7Q{93.75%,to{r:3px}46.875%{r:.2px}}.spinner_b2T7{animation:spinner_xe7Q .8s linear infinite}</style><circle class='spinner_b2T7' cx='4' cy='12' r='3'/><circle class='spinner_b2T7' cx='12' cy='12' r='3' style='animation-delay:-.65s'/><circle class='spinner_b2T7' cx='20' cy='12' r='3' style='animation-delay:-.5s'/></svg>");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@utility dots-grow-alternate {
|
|
69
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_qhi1{0%,to{r:1.5px}50%{r:3px}}.spinner_I8Q1{animation:spinner_qhi1 .75s linear infinite}</style><circle class='spinner_I8Q1' cx='4' cy='12' r='1.5'/><circle class='spinner_I8Q1' cx='12' cy='12' r='3' style='animation-delay:-.375s'/><circle class='spinner_I8Q1' cx='20' cy='12' r='1.5'/></svg>");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@utility dot-bounce {
|
|
73
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>@keyframes spinner_YeBj{0%{animation-timing-function:cubic-bezier(.33,0,.66,.33);cy:5px}46.875%{cy:20px;rx:4px;ry:4px}50%{animation-timing-function:cubic-bezier(.33,.66,.66,1);cy:20.5px;rx:4.8px;ry:3px}53.125%{rx:4px;ry:4px}to{cy:5px}}</style><circle cx='12' cy='5' style='animation:spinner_YeBj .8s infinite' r='4'/></svg>");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@utility bars-wave {
|
|
77
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.bar{animation:bar-anim 1s ease-in-out infinite;fill:currentColor}.b1{animation-delay:-0.4s}.b2{animation-delay:-0.2s}@keyframes bar-anim{0%,100%{transform:scaleY(0.3)}50%{transform:scaleY(1)}}</style><rect class='bar b1' x='3' y='2' width='4' height='20' rx='2' transform-origin='5px 12px'></rect><rect class='bar b2' x='10' y='2' width='4' height='20' rx='2' transform-origin='12px 12px'></rect><rect class='bar b3' x='17' y='2' width='4' height='20' rx='2' transform-origin='19px 12px'></rect></svg>");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@utility pulse-ring {
|
|
81
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><style>.pulse{animation:pulse-anim 1.2s cubic-bezier(0, 0.2, 0.8, 1) infinite;stroke:currentColor;transform-origin:center}.p2{animation-delay:-0.6s}@keyframes pulse-anim{0%{transform:scale(0);opacity:1}100%{transform:scale(1);opacity:0}}</style><circle class='pulse p1' cx='12' cy='12' r='10' fill='none' stroke-width='3'></circle><circle class='pulse p2' cx='12' cy='12' r='10' fill='none' stroke-width='3'></circle></svg>");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* A loader takes a colour and its own geometry, and nothing else. No fill, no
|
|
85
|
+
edge, no silhouette: what used to be a bordered box wrapped around a mask is
|
|
86
|
+
now the mask. `--intent-color` defaults to `currentColor` for indicators, so
|
|
87
|
+
a loader inside filled content matches the text around it and
|
|
88
|
+
`.loader.success` is green. There is nothing else to know. */
|
|
89
|
+
@utility loader {
|
|
90
|
+
@apply loader-mask inline-block size-8 [&.lg]:size-10 [&.sm]:size-6;
|
|
91
|
+
|
|
92
|
+
color: var(--intent-color);
|
|
93
|
+
background-color: currentColor;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@media (prefers-reduced-motion: reduce) {
|
|
97
|
+
.loader,
|
|
98
|
+
.loading {
|
|
99
|
+
--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
|
+
}
|
|
101
|
+
|
|
102
|
+
.dots-wave,
|
|
103
|
+
.dots-fade,
|
|
104
|
+
.dots-queue,
|
|
105
|
+
.dots-rotate,
|
|
106
|
+
.dots-grow,
|
|
107
|
+
.dots-grow-alternate {
|
|
108
|
+
--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='4' cy='12' r='3'/><circle cx='12' cy='12' r='3'/><circle cx='20' cy='12' r='3'/></svg>");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.dot-bounce {
|
|
112
|
+
--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='4'/></svg>");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.bars-wave {
|
|
116
|
+
--loader-art: url("data:image/svg+xml;charset=utf-8,<svg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><rect x='3' y='5' width='4' height='14' rx='2'/><rect x='10' y='5' width='4' height='14' rx='2'/><rect x='17' y='5' width='4' height='14' rx='2'/></svg>");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.pulse-ring {
|
|
120
|
+
--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' fill='none' stroke='black' stroke-width='3'/></svg>");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
@reference "tailwindcss";
|
|
2
|
+
@import "../theme.css";
|
|
3
|
+
@reference "../box.css";
|
|
4
|
+
|
|
5
|
+
/* What every surface shares. Two slots exist here and nowhere else, and that is
|
|
6
|
+
how an aesthetic reaches surfaces without naming one: only a surface resolves
|
|
7
|
+
`--ui-backdrop`, so glass blurs cards and panels while controls stay solid,
|
|
8
|
+
and only a surface resolves `--ui-surface-shadow`, so a two-layer drop shadow
|
|
9
|
+
does not land under every button on the page. */
|
|
10
|
+
@utility surface {
|
|
11
|
+
@apply box block;
|
|
12
|
+
|
|
13
|
+
/* Bare and edged. `box` reads these three with no fallback, so `surface` --
|
|
14
|
+
typeable on its own -- owes all three; `card` restates its own. No shadow
|
|
15
|
+
geometry: depth is asked for (`.raised`/`.floating`, an aesthetic, the
|
|
16
|
+
registry elevation) rather than carried, so a plain card is flat. */
|
|
17
|
+
--_d-fill: 0%;
|
|
18
|
+
--_d-fg-on-fill: 0%;
|
|
19
|
+
--_d-border: 100%;
|
|
20
|
+
|
|
21
|
+
--_d-ground: var(--ui-surface-ground, var(--color-background));
|
|
22
|
+
|
|
23
|
+
/* The complete-shadow slot for a shadow that is not one layer (glass tucks two
|
|
24
|
+
under with negative spread). Only `surface` reads it -- through `--ui-shadow`
|
|
25
|
+
it would land an 18px drop under every button and chip -- like the backdrop. */
|
|
26
|
+
--_shadow: var(--ui-surface-shadow, var(--_shadow-parts));
|
|
27
|
+
|
|
28
|
+
border-radius: var(--ui-radius-surface, var(--radius-surface));
|
|
29
|
+
/* Prefixed property first, deliberately: written second the minifier folds the
|
|
30
|
+
pair into the prefixed one alone, and the standard property never reaches
|
|
31
|
+
`dist/` -- which drops the blur in Firefox (the one engine with
|
|
32
|
+
`backdrop-filter` and not the `-webkit-` alias). Measured on the built CSS. */
|
|
33
|
+
-webkit-backdrop-filter: var(--ui-backdrop, none);
|
|
34
|
+
backdrop-filter: var(--ui-backdrop, none);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* A ghost, edged container that takes one unit when depth is supplied. */
|
|
38
|
+
@utility card {
|
|
39
|
+
@apply surface p-(--surface-padding,1.25rem);
|
|
40
|
+
|
|
41
|
+
/* After the `@apply`, not before: `surface` inlines its own resting geometry
|
|
42
|
+
at that point, so defaults written above it would be overridden by the very
|
|
43
|
+
utility they are meant to specialise. */
|
|
44
|
+
--_d-fill: 0%;
|
|
45
|
+
--_d-fg-on-fill: 0%;
|
|
46
|
+
--_d-border: 100%;
|
|
47
|
+
--_d-elevation: 1;
|
|
48
|
+
|
|
49
|
+
/* `.p-sm`/`.p-lg` are the canonical names; `.compact`/`.spacious` are
|
|
50
|
+
first-class aliases -- see the identical note in `button.css`. */
|
|
51
|
+
&.p-sm,
|
|
52
|
+
&.compact {
|
|
53
|
+
--surface-padding: 0.75rem;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&.p-lg,
|
|
57
|
+
&.spacious {
|
|
58
|
+
--surface-padding: 2rem;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&.flush {
|
|
62
|
+
--surface-padding: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Opt-in, because a card is only sometimes a control. Applications still owe
|
|
66
|
+
it a real interactive element and an accessible name. Hover and press are
|
|
67
|
+
the derived ones every other pressable box gets; there is nothing
|
|
68
|
+
card-specific about either. */
|
|
69
|
+
&.interactive {
|
|
70
|
+
@apply box-hover box-active cursor-pointer;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* A panel is the flush, unelevated sibling: sidebars, toolbars, and wells that
|
|
75
|
+
sit flat against the layout rather than floating above it. Soft and edgeless
|
|
76
|
+
is what makes it read as a well without drawing a line to say so. */
|
|
77
|
+
@utility panel {
|
|
78
|
+
@apply surface p-(--surface-padding,1rem);
|
|
79
|
+
|
|
80
|
+
--_d-fill: 12%;
|
|
81
|
+
--_d-fg-on-fill: 0%;
|
|
82
|
+
--_d-border: 0%;
|
|
83
|
+
--_d-elevation: 0;
|
|
84
|
+
|
|
85
|
+
&.p-sm,
|
|
86
|
+
&.compact {
|
|
87
|
+
--surface-padding: 0.5rem;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
&.p-lg,
|
|
91
|
+
&.spacious {
|
|
92
|
+
--surface-padding: 1.5rem;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
&.flush {
|
|
96
|
+
--surface-padding: 0;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
@reference "tailwindcss";
|
|
2
|
+
@import "../theme.css";
|
|
3
|
+
@reference "../box.css";
|
|
4
|
+
@reference "./surface.css";
|
|
5
|
+
|
|
6
|
+
/* The host is a positioning wrapper and nothing else. Its bubble is a
|
|
7
|
+
pseudo-element placed outside the host's own box, so painting the host would
|
|
8
|
+
draw a box around the trigger rather than around the message -- which is
|
|
9
|
+
exactly what a blanket surface membership did to this component once already.
|
|
10
|
+
|
|
11
|
+
The bubble is the component. It is a surface, so an aesthetic that blurs
|
|
12
|
+
surfaces blurs it too, and it rests on a ground of its own, edged, and flat.
|
|
13
|
+
Depth is opt-in through `.raised` and `.floating` like every other component;
|
|
14
|
+
its boundary against arbitrary content is the floored edge below, not a
|
|
15
|
+
shadow. See model.md#elevation. */
|
|
16
|
+
@utility tooltip {
|
|
17
|
+
--tooltip-gap: 0.5rem;
|
|
18
|
+
--tooltip-top: calc(var(--tooltip-gap) * -1);
|
|
19
|
+
--tooltip-right: auto;
|
|
20
|
+
--tooltip-bottom: auto;
|
|
21
|
+
--tooltip-left: 50%;
|
|
22
|
+
--tooltip-origin: bottom;
|
|
23
|
+
--tooltip-transform: translate(-50%, -100%);
|
|
24
|
+
|
|
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
|
+
}
|
|
53
|
+
|
|
54
|
+
&[data-tooltip-position="bottom"] {
|
|
55
|
+
--tooltip-top: auto;
|
|
56
|
+
--tooltip-bottom: calc(var(--tooltip-gap) * -1);
|
|
57
|
+
--tooltip-origin: top;
|
|
58
|
+
--tooltip-transform: translate(-50%, 100%);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&[data-tooltip-position="left"] {
|
|
62
|
+
--tooltip-top: 50%;
|
|
63
|
+
--tooltip-left: calc(var(--tooltip-gap) * -1);
|
|
64
|
+
--tooltip-origin: right;
|
|
65
|
+
--tooltip-transform: translate(-100%, -50%);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
&[data-tooltip-position="right"] {
|
|
69
|
+
--tooltip-top: 50%;
|
|
70
|
+
--tooltip-left: auto;
|
|
71
|
+
--tooltip-right: calc(var(--tooltip-gap) * -1);
|
|
72
|
+
--tooltip-origin: left;
|
|
73
|
+
--tooltip-transform: translate(100%, -50%);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&::after {
|
|
77
|
+
@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);
|
|
78
|
+
|
|
79
|
+
/* Filled, over a ground of its own. An intent floods the fill
|
|
80
|
+
(`.tooltip.primary` is the primary colour); with no intent `intent.css`
|
|
81
|
+
resolves the bubble onto `--color-tooltip` and lifts the neutral cap, a
|
|
82
|
+
plate stated per theme because the derived version was a mid-grey slab in
|
|
83
|
+
light and lifted in dark. The ground stays the surface tone, not the
|
|
84
|
+
plate -- pointed at both, the fill axis went inert and `axes.spec.ts`
|
|
85
|
+
caught it. An opaque ground is also what keeps a cascaded `.ghost` from
|
|
86
|
+
leaving the bubble boundaryless, retiring the old pinned-fill bound.
|
|
87
|
+
`--ui-surface-ground` is read first so a glass tooltip stays glass. */
|
|
88
|
+
--_d-fill: 100%;
|
|
89
|
+
--_d-fg-on-fill: 100%;
|
|
90
|
+
--_d-border: 100%;
|
|
91
|
+
/* Flat, and stated rather than left to `box`'s fallback of 1: the bubble is
|
|
92
|
+
a surface, so an aesthetic's part-based depth would otherwise reach it
|
|
93
|
+
unasked -- `.neobrutalism`'s slab in particular. `.raised`/`.floating`
|
|
94
|
+
opt back in. */
|
|
95
|
+
--_d-elevation: 0;
|
|
96
|
+
--_d-ground: var(--ui-surface-ground, var(--intent-subtle));
|
|
97
|
+
|
|
98
|
+
/* The bubble's line is a boundary against content nobody chose, so `box`'s
|
|
99
|
+
edge blend is wrong here: at a full fill the line *is* the plate. Restate
|
|
100
|
+
the colour only; how much line comes from `--_edge-amount`, floored below. */
|
|
101
|
+
--_line: var(--intent-border);
|
|
102
|
+
|
|
103
|
+
/* Floored, for the same reason the ground is opaque: a cascade must not take
|
|
104
|
+
the boundary from a bubble nobody classed (worst in light, where the plate
|
|
105
|
+
is near-white). The element's own `.edgeless` lowers it -- same
|
|
106
|
+
element-vs-cascade idiom `text-control` uses -- and `axes.spec.ts` still
|
|
107
|
+
sees the edge move because it probes with the class on the element. */
|
|
108
|
+
--_edge-floor: 100%;
|
|
109
|
+
|
|
110
|
+
/* A bubble is a small transient popover, not a modal, so it takes the
|
|
111
|
+
control radius rather than the surface one `surface` hands it. */
|
|
112
|
+
border-radius: var(--ui-radius, var(--radius-control));
|
|
113
|
+
content: attr(data-tooltip);
|
|
114
|
+
left: var(--tooltip-left);
|
|
115
|
+
right: var(--tooltip-right);
|
|
116
|
+
top: var(--tooltip-top);
|
|
117
|
+
bottom: var(--tooltip-bottom);
|
|
118
|
+
transform: var(--tooltip-transform) scale(0);
|
|
119
|
+
transform-origin: var(--tooltip-origin);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&.edgeless::after {
|
|
123
|
+
--_edge-floor: 0%;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&:hover::after,
|
|
127
|
+
&:focus-visible::after,
|
|
128
|
+
&[data-state="open"]::after {
|
|
129
|
+
opacity: 1;
|
|
130
|
+
transform: var(--tooltip-transform) scale(1);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/* Elevation is a modifier, not an axis: depth is a per-element decision (raised
|
|
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. Unitless so
|
|
4
|
+
it inherits -- a container flattens a region, an element opts back in -- and
|
|
5
|
+
so `.flat` removes a part-based slab or blur without a conditional (complete
|
|
6
|
+
surface shadows like glass remain). The two classes that ask for depth carry
|
|
7
|
+
the geometry themselves; on a component it would inherit into everything
|
|
8
|
+
nested and a plain page would have a shadow under every card. See
|
|
9
|
+
model.md#elevation.
|
|
10
|
+
|
|
11
|
+
`.flat`/`.raised`/`.floating` and `.elevation-none`/`-sm`/`-md` are
|
|
12
|
+
interchangeable aliases, the second borrowing the raw `--elevation-*` scale's
|
|
13
|
+
naming. Bare `sm`/`md`/`lg` would collide with the size modifier;
|
|
14
|
+
`.elevation-lg` is reserved, not shipped. */
|
|
15
|
+
|
|
16
|
+
/* No part-based depth. Complete surface shadow values, such as glass, are not
|
|
17
|
+
reached by the elevation multiplier. */
|
|
18
|
+
.elevation-none,
|
|
19
|
+
.flat {
|
|
20
|
+
--ui-elevation: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* The aesthetic's depth as authored, said explicitly. The geometry is the
|
|
24
|
+
fallback for a page with no aesthetic in scope; an aesthetic that declares its
|
|
25
|
+
own overrides it, and the elevation multiplies whichever one lands. */
|
|
26
|
+
.elevation-sm,
|
|
27
|
+
.raised {
|
|
28
|
+
--ui-elevation: 1;
|
|
29
|
+
--elevation-y: 1px;
|
|
30
|
+
--elevation-blur: 3px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Twice it, for menus and popovers. */
|
|
34
|
+
.elevation-md,
|
|
35
|
+
.floating {
|
|
36
|
+
--ui-elevation: 2;
|
|
37
|
+
--elevation-y: 1px;
|
|
38
|
+
--elevation-blur: 3px;
|
|
39
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
@source not "../dist";
|
|
3
|
+
@source not "../tests";
|
|
4
|
+
@source not "../dev";
|
|
5
|
+
@source not "../debug";
|
|
6
|
+
@source not "../playground";
|
|
7
|
+
|
|
8
|
+
@import "./theme.css";
|
|
9
|
+
@import "./typography.css";
|
|
10
|
+
@import "./utilities/index.css";
|
|
11
|
+
@import "./components/index.css";
|
|
12
|
+
@import "./reset.css";
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/* An intent maps one colour family onto seven shared slots and nothing else.
|
|
2
|
+
It does not cascade: the reset at the bottom redeclares the neutral defaults
|
|
3
|
+
at every component's own root, so a `.success` container cannot recolour a
|
|
4
|
+
nested destructive button while `.btn.success` still wins. Every intent writes
|
|
5
|
+
every slot, `--intent-fill-max` included -- omitting it inherits the reset's
|
|
6
|
+
neutral cap, and `tests/integration/registry.test.ts` fails the build on one
|
|
7
|
+
that does. Neutral maps `--intent-subtle` to `--color-surface` (the tone
|
|
8
|
+
tooltips, key caps, code and table heads sit on) and `--intent-border` to
|
|
9
|
+
`--ui-ink` (how an aesthetic restates the neutral line without naming one). */
|
|
10
|
+
|
|
11
|
+
/* Neutral stops its fill short: its colour is the page's ink, so a full fill is
|
|
12
|
+
a slab and `.neutral.solid` was pixel-for-pixel `.primary.solid`. 20% is
|
|
13
|
+
chosen against `.soft`'s 12% -- a step past the tint, not a repeat.
|
|
14
|
+
`--intent-contrast` stays the page colour; a `.neutral.solid` badge's ink is
|
|
15
|
+
`box`'s question, answered by the same `--ui-fg-on-fill` cap. Pointing this
|
|
16
|
+
slot at `--color-text` instead rendered a checked neutral checkbox
|
|
17
|
+
black-on-black. */
|
|
18
|
+
.neutral {
|
|
19
|
+
--intent-color: var(--color-text);
|
|
20
|
+
--intent-contrast: var(--color-text-contrast);
|
|
21
|
+
--intent-hover: var(--color-text-hover);
|
|
22
|
+
--intent-strong: var(--color-text-strong);
|
|
23
|
+
--intent-subtle: var(--color-surface);
|
|
24
|
+
--intent-border: var(--ui-ink, var(--color-border));
|
|
25
|
+
--intent-fill-max: 20%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.primary {
|
|
29
|
+
--intent-color: var(--color-primary);
|
|
30
|
+
--intent-contrast: var(--color-primary-contrast);
|
|
31
|
+
--intent-hover: var(--color-primary-hover);
|
|
32
|
+
--intent-strong: var(--color-primary-strong);
|
|
33
|
+
--intent-subtle: var(--color-primary-subtle);
|
|
34
|
+
--intent-border: var(--color-primary);
|
|
35
|
+
--intent-fill-max: 100%;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.secondary {
|
|
39
|
+
--intent-color: var(--color-accent);
|
|
40
|
+
--intent-contrast: var(--color-accent-contrast);
|
|
41
|
+
--intent-hover: var(--color-accent-hover);
|
|
42
|
+
--intent-strong: var(--color-accent-strong);
|
|
43
|
+
--intent-subtle: var(--color-accent-subtle);
|
|
44
|
+
--intent-border: var(--color-accent);
|
|
45
|
+
--intent-fill-max: 100%;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.success {
|
|
49
|
+
--intent-color: var(--color-success);
|
|
50
|
+
--intent-contrast: var(--color-success-contrast);
|
|
51
|
+
--intent-hover: var(--color-success-hover);
|
|
52
|
+
--intent-strong: var(--color-success-strong);
|
|
53
|
+
--intent-subtle: var(--color-success-subtle);
|
|
54
|
+
--intent-border: var(--color-success);
|
|
55
|
+
--intent-fill-max: 100%;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.warning {
|
|
59
|
+
--intent-color: var(--color-warning);
|
|
60
|
+
--intent-contrast: var(--color-warning-contrast);
|
|
61
|
+
--intent-hover: var(--color-warning-hover);
|
|
62
|
+
--intent-strong: var(--color-warning-strong);
|
|
63
|
+
--intent-subtle: var(--color-warning-subtle);
|
|
64
|
+
--intent-border: var(--color-warning);
|
|
65
|
+
--intent-fill-max: 100%;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.destructive,
|
|
69
|
+
.danger,
|
|
70
|
+
.error {
|
|
71
|
+
--intent-color: var(--color-destructive);
|
|
72
|
+
--intent-contrast: var(--color-destructive-contrast);
|
|
73
|
+
--intent-hover: var(--color-destructive-hover);
|
|
74
|
+
--intent-strong: var(--color-destructive-strong);
|
|
75
|
+
--intent-subtle: var(--color-destructive-subtle);
|
|
76
|
+
--intent-border: var(--color-destructive);
|
|
77
|
+
--intent-fill-max: 100%;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.info {
|
|
81
|
+
--intent-color: var(--color-info);
|
|
82
|
+
--intent-contrast: var(--color-info-contrast);
|
|
83
|
+
--intent-hover: var(--color-info-hover);
|
|
84
|
+
--intent-strong: var(--color-info-strong);
|
|
85
|
+
--intent-subtle: var(--color-info-subtle);
|
|
86
|
+
--intent-border: var(--color-info);
|
|
87
|
+
--intent-fill-max: 100%;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Every component resolves the neutral defaults at its own root, at zero
|
|
91
|
+
specificity, so an intent class on the element wins while an inherited value
|
|
92
|
+
loses -- the one rule that lets every component drop its per-intent branches.
|
|
93
|
+
This list and the one in `native.css` are the package's only hand-maintained
|
|
94
|
+
selector lists; `tests/integration/registry.test.ts` fails the build on a
|
|
95
|
+
registry component missing from either. `.text-control` and `.surface` are on
|
|
96
|
+
it though they are not components: both are documented as typeable on their
|
|
97
|
+
own, and a bare `<div class="surface">` with no intent resolved computes a
|
|
98
|
+
transparent background and a zero-width border -- `box` collapses. */
|
|
99
|
+
:where(
|
|
100
|
+
.btn,
|
|
101
|
+
.ipt,
|
|
102
|
+
.text-control,
|
|
103
|
+
.surface,
|
|
104
|
+
.textarea,
|
|
105
|
+
.select,
|
|
106
|
+
.input-group,
|
|
107
|
+
.checkbox,
|
|
108
|
+
.radio,
|
|
109
|
+
.switch,
|
|
110
|
+
.card,
|
|
111
|
+
.panel,
|
|
112
|
+
.alert,
|
|
113
|
+
.data-table,
|
|
114
|
+
.pre,
|
|
115
|
+
.quote,
|
|
116
|
+
.tooltip,
|
|
117
|
+
.badge,
|
|
118
|
+
.kbd,
|
|
119
|
+
.code,
|
|
120
|
+
.loader,
|
|
121
|
+
.skeleton,
|
|
122
|
+
.progress,
|
|
123
|
+
.divider
|
|
124
|
+
) {
|
|
125
|
+
--intent-color: var(--color-text);
|
|
126
|
+
--intent-contrast: var(--color-text-contrast);
|
|
127
|
+
--intent-hover: var(--color-text-hover);
|
|
128
|
+
--intent-strong: var(--color-text-strong);
|
|
129
|
+
--intent-subtle: var(--color-surface);
|
|
130
|
+
--intent-border: var(--ui-ink, var(--color-border));
|
|
131
|
+
--intent-fill-max: 20%;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* The six text controls take a heavier line than everything else that rests on
|
|
135
|
+
the shared neutral. A card, a table or a divider sits on a filled surface or
|
|
136
|
+
separates content that identifies itself; a text control at rest is a
|
|
137
|
+
transparent box whose only mark is its border, and WCAG 1.4.11 asks that
|
|
138
|
+
border for 3:1. `--color-border` gives it 1.42:1.
|
|
139
|
+
|
|
140
|
+
Declared here at zero specificity for the same reason the indicators below
|
|
141
|
+
are: written inside `@utility text-control` it would land in the utilities
|
|
142
|
+
layer, the reset above lands in no layer at all, and unlayered beats layered
|
|
143
|
+
at any specificity. Written as a plain class list it would carry 0-1-0 and,
|
|
144
|
+
being later in the stylesheet, beat `.primary` -- which would make every
|
|
145
|
+
colored control draw a grey line. `:where()` keeps it under both. */
|
|
146
|
+
:where(.text-control, .ipt, .textarea, .select, .input-group, .checkbox, .radio, .switch) {
|
|
147
|
+
--intent-border: var(--ui-ink, var(--color-control-border));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* The bubble a tooltip draws is the one plate in the package that is chosen
|
|
151
|
+
rather than composed, so the tooltip resolves the no-intent case onto its own
|
|
152
|
+
pair instead of onto the shared neutral. Composed, it was 20% of the page's
|
|
153
|
+
ink over `--color-surface`, which lifts off a dark page and sinks into a light
|
|
154
|
+
one -- see `--color-tooltip` in `theme.css` for the measurements.
|
|
155
|
+
|
|
156
|
+
The cap goes with it. Neutral stops its fill at 20% because its colour is the
|
|
157
|
+
page's ink and a full fill of that is a slab; `--color-tooltip` is a plate
|
|
158
|
+
rather than an ink, so a full fill of it is the plate and there is nothing to
|
|
159
|
+
stop short of.
|
|
160
|
+
|
|
161
|
+
`--intent-subtle` stays the surface token. It is what the trigger badge rests
|
|
162
|
+
on, and a badge is a quiet marker on the page rather than the message, so it
|
|
163
|
+
keeps the tone every other unclassed component sits on.
|
|
164
|
+
|
|
165
|
+
Two selectors, because `none` and `neutral` have to agree here. `:where()`
|
|
166
|
+
loses to any intent class, so `.tooltip.primary` still floods with primary;
|
|
167
|
+
`.tooltip.neutral` carries 0-2-0 and beats `.neutral`, so writing the intent
|
|
168
|
+
out gives the same bubble as writing nothing. Without the second selector the
|
|
169
|
+
two would differ on a component where they mean the same thing. */
|
|
170
|
+
:where(.tooltip),
|
|
171
|
+
.tooltip.neutral {
|
|
172
|
+
--intent-color: var(--color-tooltip);
|
|
173
|
+
--intent-contrast: var(--color-tooltip-contrast);
|
|
174
|
+
--intent-hover: var(--color-tooltip);
|
|
175
|
+
--intent-strong: var(--color-tooltip-contrast);
|
|
176
|
+
--intent-subtle: var(--color-surface);
|
|
177
|
+
--intent-border: var(--ui-ink, var(--color-border));
|
|
178
|
+
--intent-fill-max: 100%;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Indicators take the color they sit in rather than the shared neutral, so a
|
|
182
|
+
loader inside filled or colored content still matches it. It has to be
|
|
183
|
+
declared here at zero specificity: written in the component's own block it
|
|
184
|
+
would carry 0-1-0 and, being later in the stylesheet, beat `.success` --
|
|
185
|
+
which silently makes every indicator ignore every intent class. */
|
|
186
|
+
:where(.loader, .skeleton, .progress, .divider) {
|
|
187
|
+
--intent-color: currentColor;
|
|
188
|
+
--intent-border: currentColor;
|
|
189
|
+
}
|