@codenhub/styles 0.4.0 → 0.6.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.
Files changed (61) hide show
  1. package/README.md +7 -7
  2. package/dist/aesthetics/chunky-tile.css +1 -1
  3. package/dist/aesthetics/cyber.css +1 -1
  4. package/dist/aesthetics/glass.css +1 -1
  5. package/dist/aesthetics/index.css +1 -1
  6. package/dist/aesthetics/neobrutalism.css +1 -1
  7. package/dist/aesthetics/pixel.css +1 -1
  8. package/dist/aesthetics/sketch.css +1 -0
  9. package/dist/components.css +1 -1
  10. package/dist/index.css +1 -1
  11. package/dist/native.css +1 -1
  12. package/dist/palette.css +1 -1
  13. package/dist/theme.css +1 -1
  14. package/dist/tw/aesthetics/chunky-tile.css +144 -128
  15. package/dist/tw/aesthetics/cyber.css +138 -96
  16. package/dist/tw/aesthetics/glass.css +332 -66
  17. package/dist/tw/aesthetics/index.css +3 -1
  18. package/dist/tw/aesthetics/neobrutalism.css +113 -54
  19. package/dist/tw/aesthetics/pixel.css +137 -87
  20. package/dist/tw/aesthetics/sketch.css +333 -0
  21. package/dist/tw/box.css +139 -52
  22. package/dist/tw/components/button.css +67 -28
  23. package/dist/tw/components/feedback.css +85 -67
  24. package/dist/tw/components/form.css +300 -230
  25. package/dist/tw/components/index.css +17 -9
  26. package/dist/tw/components/loader.css +38 -30
  27. package/dist/tw/components/surface.css +89 -129
  28. package/dist/tw/components/tooltip.css +91 -47
  29. package/dist/tw/corner.css +26 -0
  30. package/dist/tw/elevation.css +19 -28
  31. package/dist/tw/index.css +15 -19
  32. package/dist/tw/intent.css +47 -32
  33. package/dist/tw/native.css +58 -42
  34. package/dist/tw/palette.css +455 -446
  35. package/dist/tw/presentation.css +20 -29
  36. package/dist/tw/reset.css +21 -28
  37. package/dist/tw/surface.css +61 -0
  38. package/dist/tw/theme.css +156 -153
  39. package/dist/tw/utilities/content.css +283 -205
  40. package/dist/tw/utilities/layout.css +17 -27
  41. package/docs/accessibility.md +63 -46
  42. package/docs/changelog/0.5.0.md +38 -0
  43. package/docs/changelog/0.6.0.md +52 -0
  44. package/docs/changelog/index.md +2 -0
  45. package/docs/concepts.md +6 -6
  46. package/docs/index.md +12 -3
  47. package/docs/integrating/nextjs.md +1 -1
  48. package/docs/integrating/tailwind.md +14 -6
  49. package/docs/setup.md +28 -8
  50. package/docs/usage/aesthetics.md +88 -18
  51. package/docs/usage/buttons.md +14 -14
  52. package/docs/usage/composing.md +12 -12
  53. package/docs/usage/content-and-layout.md +25 -24
  54. package/docs/usage/customizing.md +92 -55
  55. package/docs/usage/feedback.md +5 -3
  56. package/docs/usage/forms.md +61 -45
  57. package/docs/usage/theming.md +41 -42
  58. package/docs/usage/tooltips.md +37 -17
  59. package/llms-full.txt +590 -304
  60. package/llms.txt +6 -0
  61. package/package.json +15 -5
@@ -14,16 +14,46 @@
14
14
  --_d-border: 0%;
15
15
  --_d-elevation: 1;
16
16
 
17
- @apply box box-hover box-active relative inline-flex w-fit cursor-pointer items-center justify-center gap-2 px-4 py-2 font-semibold whitespace-nowrap no-underline select-none;
17
+ @apply box box-hover box-active relative inline-flex w-fit cursor-pointer items-center justify-center gap-2 px-4 py-2 whitespace-nowrap no-underline select-none;
18
18
 
19
19
  min-height: var(--control-height);
20
+ /* The vertical padding gives back the line `box` draws, so a default button
21
+ is exactly `--control-height` tall like `.ipt` beside it, under any
22
+ aesthetic's line width. At the default 16px/1.5 text the label and the
23
+ padding fill it; the size and padding modifiers set their own. */
24
+ padding-block: calc(0.5rem - var(--_width));
25
+ /* The surrounding font, not the user agent's smaller button one, on every
26
+ entry: `/components` carries no reset to say so, and a `.btn` link and a
27
+ `.btn` button have to print the same label. `.sm` and `.lg` set their own
28
+ size over this. */
29
+ font-family: inherit;
30
+ font-size: inherit;
31
+ line-height: inherit;
32
+ /* The label is material, shared with `.badge`: weight, tracking, case, and a
33
+ text shadow, set by an aesthetic as tokens (`.chunky-tile`'s heavier,
34
+ tracked label), and a consumer's own `font-*`, `tracking-*`, `uppercase`, or
35
+ text-shadow utility still beats the aesthetic. Tracking has no fallback on
36
+ purpose: undefined, the declaration is invalid at computed-value time and
37
+ `letter-spacing` inherits, which is what it did before the slot existed.
38
+ Case and shadow fall back to `none`, which is what a button already does:
39
+ the user agent stops both inheriting into one. The shadow is written
40
+ without a colour, so it takes the label's own and cannot lower its
41
+ contrast. See docs/internal/model.md#cascade-layers. */
42
+ font-weight: var(--ui-label-weight, var(--font-weight-semibold));
43
+ letter-spacing: var(--ui-label-tracking);
44
+ text-transform: var(--ui-label-case, none);
45
+ text-shadow: var(--ui-label-shadow, none);
20
46
 
21
47
  &.sm {
22
48
  @apply min-h-8 px-3 py-1 text-sm;
49
+
50
+ --_scale-size: 0.75;
23
51
  }
24
52
 
25
53
  &.lg {
26
54
  @apply min-h-12 px-5 py-2.5 text-[1.0625rem];
55
+
56
+ --_scale-size: 1.25;
27
57
  }
28
58
 
29
59
  &.icon {
@@ -32,27 +62,34 @@
32
62
  min-width: var(--control-height);
33
63
  }
34
64
 
35
- /* `.p-xs`/`.p-sm`/`.p-lg` are the canonical names, matching the `sm`/`md`/`lg`
36
- vocabulary the elevation modifier uses; `.dense`/`.compact`/`.spacious` are
37
- first-class aliases, not a deprecation shim -- the same pattern
38
- `.destructive`/`.danger`/`.error` uses on the intent axis. Bare `sm`/`lg`
39
- are already the size modifier's class names, so `p-` prefixes here to
40
- avoid colliding with a button's own size. `.p-xs` is the tightest step,
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. */
52
- &.p-xs,
53
- &.dense {
65
+ /* The size steps for an icon button, square at each: `.sm`'s 2rem, with the
66
+ padding giving back the line width the way the default button's does, and
67
+ `.lg`'s 3rem. Each names its width as well as its height, because WebKit
68
+ does not let `aspect-ratio` widen a box past its `min-width`. */
69
+ &.icon.sm {
70
+ min-width: 2rem;
71
+ padding: calc(0.375rem - var(--_width));
72
+ }
73
+
74
+ &.icon.lg {
75
+ min-width: 3rem;
76
+ }
77
+
78
+ /* Bare `sm`/`lg` are the size modifier's class names, so `p-` prefixes here.
79
+ `.p-xs` is the tightest step, below `.p-sm`: a chip-sized button in a
80
+ toolbar or a table cell.
81
+
82
+ `.icon` drops both floors -- `min-width` from its own base rule above and
83
+ `min-height` from `--control-height` -- at every padding tier; labeled
84
+ buttons keep the height floor everywhere but `.p-xs`. `.icon` is already
85
+ square: `aspect-square` ties its width to whatever height it lands on, so a
86
+ surviving height floor would keep the button bigger than its own glyph
87
+ however tight the padding asked to be. */
88
+ &.p-xs {
54
89
  @apply px-2 py-0.5;
55
90
 
91
+ --_scale-pad: 0.5;
92
+
56
93
  /* The one padding step that also drops the size floor for a labeled
57
94
  button. At this tier the button is a chip in a toolbar or a table
58
95
  cell, where `--control-height` is taller than the row it sits in;
@@ -67,10 +104,11 @@
67
104
  }
68
105
  }
69
106
 
70
- &.p-sm,
71
- &.compact {
107
+ &.p-sm {
72
108
  @apply px-2.5 py-1;
73
109
 
110
+ --_scale-pad: 0.75;
111
+
74
112
  &.icon {
75
113
  @apply p-1;
76
114
 
@@ -79,10 +117,11 @@
79
117
  }
80
118
  }
81
119
 
82
- &.p-lg,
83
- &.spacious {
120
+ &.p-lg {
84
121
  @apply px-6 py-3;
85
122
 
123
+ --_scale-pad: 1.25;
124
+
86
125
  &.icon {
87
126
  @apply p-3;
88
127
 
@@ -91,10 +130,10 @@
91
130
  }
92
131
  }
93
132
 
94
- /* A pill is a shape the author asked for, so it outranks the aesthetic's
95
- radius. It still takes the corner shape from `box`, so under a bevelling
96
- aesthetic the full radius cuts to points at both ends. */
133
+ /* A pill normally takes the round material ahead of the box radius. Sketch
134
+ gives this token the button's own box outline; a bevel still cuts the full
135
+ radius to points under cyber. */
97
136
  &.pill {
98
- @apply rounded-full;
137
+ border-radius: var(--ui-radius-pill, calc(infinity * 1px));
99
138
  }
100
139
  }
@@ -1,7 +1,7 @@
1
1
  @reference "tailwindcss";
2
2
  @import "../theme.css";
3
3
  @reference "../box.css";
4
- @reference "./surface.css";
4
+ @reference "../surface.css";
5
5
 
6
6
  @keyframes anim-skeleton {
7
7
  0% {
@@ -38,49 +38,45 @@
38
38
  --_d-elevation: 0;
39
39
 
40
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) {
41
+ mixes 12% of near-black ink onto the page (`217, 217, 217` against `250,
42
+ 250, 250`), a slab rather than a status signal. Scoped to no named intent:
43
+ a named intent keeps its ordinary wash, because untinting
44
+ `.alert.soft.destructive` would silence the status it exists to carry
45
+ rather than quiet it. `:not(...)` names every intent class, plus
46
+ `.solid`/`.ghost` so an explicit presentation on the element is never
47
+ clamped to a cap it did not ask for.
48
+
49
+ Grounds on `--color-foreground` through `--_d-surface-ground`, exactly like
50
+ `.panel` and a resting `.card.soft`, so glass's ground and a consumer's
51
+ `--ui-surface-ground` still win over it. An alert has no hover state to
52
+ reveal a tint into either, so this is permanent. A neutral alert keeps its
53
+ identity: `--_d-border: 100%` above holds regardless of fill, so it still
54
+ reads as a framed box, just a quiet one instead of a grey slab. */
55
+ &:not(.primary, .secondary, .success, .warning, .destructive, .info, .solid, .ghost) {
60
56
  --_fill-cap: 0%;
61
- --ui-surface-ground: var(--color-foreground);
57
+ --_d-surface-ground: var(--color-foreground);
62
58
  }
63
59
  }
64
60
 
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`. */
61
+ /* No icon is shipped. `.alert` is already `flex`, so an icon dropped in as a
62
+ real child is spaced by `gap-3`: `.alert-icon` only fixes its size and
63
+ alignment, not whether one is present. Any icon element works, the same
64
+ contract `.input-group` documents -- an inline `<svg>`, an `<img>`, or a
65
+ class from an icon set such as `@codenhub/icons`. See
66
+ `docs/internal/model.md#input-group-owns-the-box-the-control-inside-gives-it-up`. */
74
67
  @utility alert-icon {
75
68
  @apply inline-flex flex-none items-center justify-center;
76
69
 
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;
70
+ /* A `@codenhub/icons` glyph sizes itself in an unlayered rule, which no
71
+ layered rule outranks whatever its specificity, but it reads its size from
72
+ `--ic-size`, so the size goes there as well as on the box for any other
73
+ icon. No `!important`: that would shut out the consumer's own resize too.
74
+ An icon set with another prefix, or one that sizes unlayered with no knob,
75
+ keeps its own size. */
76
+ --ic-size: 1.25rem;
77
+
78
+ width: 1.25rem;
79
+ height: 1.25rem;
84
80
  }
85
81
 
86
82
  /* A soft, edgeless chip. Small enough that the structural silhouette would be
@@ -91,22 +87,26 @@
91
87
  --_d-border: 0%;
92
88
  --_d-elevation: 0;
93
89
 
94
- @apply box inline-flex min-h-6 w-fit items-center gap-1 px-2.5 text-xs font-bold;
90
+ @apply box inline-flex min-h-6 w-fit items-center gap-1 px-2.5 text-xs;
91
+
92
+ /* The label material `.btn` reads. Unset, tracking, case, and shadow inherit,
93
+ as a badge's text always has. */
94
+ font-weight: var(--ui-label-weight, var(--font-weight-bold));
95
+ letter-spacing: var(--ui-label-tracking);
96
+ text-transform: var(--ui-label-case);
97
+ text-shadow: var(--ui-label-shadow);
95
98
 
96
99
  /* Pill by default, but an aesthetic that declares no radius has to mean it, so
97
100
  the round corner is this component's fallback rather than a hardcoded value.
98
101
  An aesthetic that keeps pills distinct from its control corner says so
99
102
  through `--ui-radius-pill` first. */
100
- border-radius: var(--ui-radius-pill, var(--ui-radius, calc(infinity * 1px)));
103
+ border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
101
104
  clip-path: var(--ui-clip-tight, var(--ui-clip, none));
102
105
  }
103
106
 
104
107
  /* An indicator reads intent and no presentation token at all. It is a stand-in
105
108
  for content rather than a box with a look of its own, so there is nothing for
106
- a container's `.solid` or `.edgeless` to say about it -- which is also what
107
- removes the visibility floor that used to fight `--ui-fill` for control of the
108
- base tone, and the transparent border that only ever appeared if something up
109
- the tree asked for an edge. */
109
+ a container's `.solid` or `.edgeless` to say about it. */
110
110
  @utility skeleton {
111
111
  --skeleton-base: color-mix(in oklab, var(--intent-color) 6%, var(--intent-subtle));
112
112
  --skeleton-sheen: color-mix(in oklab, var(--intent-color) 10%, var(--color-foreground));
@@ -116,9 +116,21 @@
116
116
  animation: anim-skeleton calc(var(--motion-duration-slow) * 4) linear infinite;
117
117
  background: linear-gradient(90deg, var(--skeleton-base), var(--skeleton-sheen), var(--skeleton-base));
118
118
  background-size: 200% 100%;
119
- border-radius: var(--ui-radius, var(--radius-control));
120
- /* Not a box, so it reads the corner shape itself, beside the radius. */
119
+ /* Not a box, so it draws the control corner and its pattern itself, the way
120
+ `box` does at scale 1, and reads the corner shape beside it. */
121
+ --_corner: var(--ui-corner, var(--radius-control));
122
+ border-radius: var(
123
+ --ui-radius,
124
+ calc(var(--_corner) * var(--ui-corner-tl, 1)) calc(var(--_corner) * var(--ui-corner-tr, 1))
125
+ calc(var(--_corner) * var(--ui-corner-br, 1)) calc(var(--_corner) * var(--ui-corner-bl, 1))
126
+ );
121
127
  corner-shape: var(--ui-corner-shape, round);
128
+
129
+ /* Reduced motion stops the shimmer here, for the entries without the reset;
130
+ see `box.css`. The gradient stays, so the placeholder still reads. */
131
+ @media (prefers-reduced-motion: reduce) {
132
+ animation: none;
133
+ }
122
134
  }
123
135
 
124
136
  /* The track is a box, the fill is not: `box.css`'s formula composes the frame,
@@ -126,8 +138,8 @@
126
138
  same contract as `.loader`/`.skeleton`. `.progress` cannot `@apply box`
127
139
  (the fill is a `::after`, not the element's own background), so it hand-
128
140
  reimplements the formula the way `.quote` does -- see
129
- docs/internal/progress-presentation-axes.md for the decision and why the
130
- other three indicators do not get the same treatment. */
141
+ docs/internal/model.md#components-that-do-not-take-the-whole-of-box for the
142
+ decision and why the other three indicators do not get the same treatment. */
131
143
  @utility progress {
132
144
  --_d-fill: 12%;
133
145
  --_d-fg-on-fill: 0%;
@@ -147,25 +159,25 @@
147
159
  not `.pre`/`.code`/`.kbd`'s opaque `--intent-subtle` ground. */
148
160
  --_progress-surface: color-mix(in oklab, var(--intent-color) var(--_progress-fill), transparent);
149
161
  /* One `color-mix()`, not `box`/`.quote`'s two chained through a `--_line`
150
- step: a WebKit build measurably destabilizes composing dozens of tracks
151
- each carrying a three-level color-mix chain into the same page -- fine
152
- at ten, a reproducible ~30-minute CI hang at forty, isolated by removing
153
- one variable at a time (element count, then border at all, then chain
154
- depth) until only chain depth reproduced it. `box`/`.quote`'s extra step
155
- blends the line toward the fill first so a heavily filled box does not
156
- draw a ring of a different colour around its own plate -- a real risk up
157
- to `.solid`'s 100% fill. `.progress` never reaches that: `.solid` is
158
- unsupported here (`registry.json`), so the fill this reads never exceeds
159
- `.soft`'s 12%, and mixing straight from `--intent-border` at that fill
160
- is indistinguishable from blending through the surface first. */
162
+ step: a WebKit build destabilizes when composing dozens of tracks each
163
+ carrying a three-level color-mix chain into one page (fine at ten, a
164
+ ~30-minute CI hang at forty; only chain depth reproduces it).
165
+ `box`/`.quote`'s extra step blends the line toward the fill first so a
166
+ heavily filled box does not draw a ring of a different colour around its
167
+ own plate -- a real risk up to `.solid`'s 100% fill. `.progress` never
168
+ reaches that: `.solid` is unsupported here (`registry.json`), so the fill
169
+ this reads never exceeds `.soft`'s 12%, and mixing straight from
170
+ `--intent-border` at that fill is indistinguishable from blending through
171
+ the surface first. */
161
172
  --_progress-edge: color-mix(in oklab, var(--intent-border) var(--ui-border, var(--_d-border)), transparent);
162
173
 
163
174
  @apply relative h-2.5 w-full overflow-hidden;
164
175
  background-color: var(--progress-surface, var(--_progress-surface));
165
- border: var(--ui-border-width, var(--border-width)) solid var(--progress-edge, var(--_progress-edge));
176
+ border: var(--ui-border-width, var(--border-width)) var(--ui-line-style, solid)
177
+ var(--progress-edge, var(--_progress-edge));
166
178
  /* Round by default; an aesthetic that declares no radius squares the track and
167
179
  its fill together, and a pill corner reaches both first. */
168
- border-radius: var(--ui-radius-pill, var(--ui-radius, calc(infinity * 1px)));
180
+ border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
169
181
  /* Not a box either, and the fill below restates it, so a bevelled track
170
182
  carries a bevelled fill rather than a pill inside a cut frame. */
171
183
  corner-shape: var(--ui-corner-shape, round);
@@ -174,7 +186,7 @@
174
186
  content: "";
175
187
  @apply absolute start-0 top-0 bottom-0 block h-full transition-[width] duration-(--motion-duration-slow) ease-(--motion-ease);
176
188
 
177
- border-radius: var(--ui-radius-pill, var(--ui-radius, calc(infinity * 1px)));
189
+ border-radius: var(--ui-radius-pill, var(--ui-radius, var(--ui-corner, calc(infinity * 1px))));
178
190
  corner-shape: var(--ui-corner-shape, round);
179
191
  background-color: var(--progress-color, var(--intent-color));
180
192
  width: var(--progress-value, 0%);
@@ -198,13 +210,19 @@
198
210
  &.indeterminate::after {
199
211
  width: 33%;
200
212
  animation: anim-progress-indeterminate 1.5s infinite ease-in-out;
201
- /* No logical form exists for transform-origin, so the inline-start edge
202
- is named directly and mirrored under :dir(rtl) to match the
203
- inset-inline-start positioning above. */
204
- transform-origin: left;
213
+ }
214
+
215
+ /* Reduced motion, for the entries without the reset; see `box.css`. The
216
+ value moves without sliding, and the shimmer and the indeterminate sweep
217
+ stop where the reset would stop them: the fill rests at the start. */
218
+ @media (prefers-reduced-motion: reduce) {
219
+ &::after {
220
+ transition-duration: 0.01ms;
221
+ }
205
222
 
206
- :dir(rtl) & {
207
- transform-origin: right;
223
+ &.active::before,
224
+ &.indeterminate::after {
225
+ animation: none;
208
226
  }
209
227
  }
210
228
  }