@warmio/design 1.3.0 → 1.5.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 +20 -7
- package/assets/subtle-dots-dark.svg +8 -0
- package/assets/subtle-dots.png +0 -0
- package/components.css +495 -0
- package/dist/button.d.ts +49 -0
- package/dist/button.js +49 -0
- package/dist/logo.d.ts +33 -0
- package/dist/logo.js +10 -0
- package/globals.css +1 -0
- package/package.json +19 -3
- package/theme.css +97 -9
- package/themes.css +3 -0
package/README.md
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
# @warmio/design
|
|
2
2
|
|
|
3
|
-
Warm's public
|
|
4
|
-
|
|
3
|
+
Warm's public design contract: CSS tokens (fonts, colors, spacing, radii, shadows, motion,
|
|
4
|
+
reduced-motion, marketing surfaces + dot patterns) and Button/Logo primitives.
|
|
5
5
|
|
|
6
6
|
```css
|
|
7
7
|
@import '@warmio/design/globals.css';
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
Import primitives directly:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { Button, ButtonLink } from '@warmio/design/button';
|
|
14
|
+
import { Logo, WarmLogoIcon } from '@warmio/design/logo';
|
|
15
|
+
```
|
|
16
|
+
|
|
10
17
|
The default product typefaces are Geist and Geist Mono. The `font-editorial`
|
|
11
18
|
utility uses Inter and should be reserved for deliberate editorial accents.
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
`--accent-solid` / `--accent-solid-text` are the chromatic accent for links,
|
|
21
|
+
badges, and highlights. They derive from `--brand-accent-solid` in `themes.css`.
|
|
22
|
+
|
|
23
|
+
Package ships `components.css` (token-only styles for the primitives), `assets/`,
|
|
24
|
+
and `dist/` (ESM + .d.ts). `dependencies` and `devDependencies` are empty.
|
|
25
|
+
`peerDependencies.react` is `^19.0.0`. No root JS export.
|
|
14
26
|
|
|
15
27
|
## Releases
|
|
16
28
|
|
|
17
|
-
|
|
18
|
-
spacing, or
|
|
19
|
-
[docs/reference/DESIGN_PACKAGE.md](../../docs/reference/DESIGN_PACKAGE.md)
|
|
29
|
+
Bump this `package.json` version for additive tokens (incl. motion/surface), fonts,
|
|
30
|
+
spacing, or primitive changes. Follow the exact sequence in
|
|
31
|
+
[docs/reference/DESIGN_PACKAGE.md](../../docs/reference/DESIGN_PACKAGE.md)
|
|
32
|
+
including `build`, inventory refresh, pack:check, and dual publish readbacks.
|
|
20
33
|
|
|
21
34
|
App uses the workspace package immediately. Chat and Warm Static stay on the last
|
|
22
|
-
published npm version
|
|
35
|
+
published npm version.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="27" height="15" viewBox="0 0 27 15">
|
|
2
|
+
<defs>
|
|
3
|
+
<pattern id="dots" width="3" height="3" patternUnits="userSpaceOnUse">
|
|
4
|
+
<rect width="1" height="1" fill="#fff" fill-opacity=".14" />
|
|
5
|
+
</pattern>
|
|
6
|
+
</defs>
|
|
7
|
+
<rect width="27" height="15" fill="url(#dots)" />
|
|
8
|
+
</svg>
|
|
Binary file
|
package/components.css
ADDED
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/* @warmio/design components.css - token-only, parity with prior Button/Logo contract.
|
|
2
|
+
* Rebuilt from exact UIWarm primitive/button.variants + presentation maps + variant overrides + button-edge recipes.
|
|
3
|
+
* Uses only design tokens + data attrs. No literals where tokens exist.
|
|
4
|
+
* Motion via --motion-* (aliased); reduced-motion zeros non-essentials.
|
|
5
|
+
* All 16 presentations, 6 variants, 3 sizes + full + states produce matching computed geometry/styles.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
[data-warm-button] {
|
|
9
|
+
/* Strip native button chrome when this stylesheet is used without preflight.
|
|
10
|
+
* `appearance` is deliberately left at its UA default (matches the
|
|
11
|
+
* pre-migration PrimitiveButton/buttonVariants recipe, which never applied
|
|
12
|
+
* `appearance-none`): every other property below already fully overrides
|
|
13
|
+
* native border/background/padding rendering, and resetting `appearance`
|
|
14
|
+
* here would diverge from the origin recipe's actual computed style. */
|
|
15
|
+
border: 0 solid;
|
|
16
|
+
background: transparent;
|
|
17
|
+
background-clip: padding-box;
|
|
18
|
+
padding: 0;
|
|
19
|
+
margin: 0;
|
|
20
|
+
font: inherit;
|
|
21
|
+
color: inherit;
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
align-items: center;
|
|
24
|
+
justify-content: center;
|
|
25
|
+
border-radius: var(--radius-full);
|
|
26
|
+
font-size: var(--button-font-size);
|
|
27
|
+
font-weight: 400;
|
|
28
|
+
line-height: var(--button-line-height);
|
|
29
|
+
white-space: nowrap;
|
|
30
|
+
transition-property: var(--button-transition-property);
|
|
31
|
+
/* Exact legacy duration (200ms), derived from the locked motion scale
|
|
32
|
+
(fast=100ms) rather than a literal: fast x 2 = 200ms exactly, and it
|
|
33
|
+
zeroes correctly under reduced motion since `--motion-duration-fast`
|
|
34
|
+
itself is zeroed there (see the reduced-motion block below). */
|
|
35
|
+
transition-duration: calc(var(--motion-duration-fast) * 2);
|
|
36
|
+
transition-timing-function: ease-out;
|
|
37
|
+
outline: none;
|
|
38
|
+
user-select: none;
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
/* border handled by variant shadows/tokens; no default regression border */
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
[data-warm-button]:focus-visible {
|
|
46
|
+
box-shadow: 0 0 0 var(--button-focus-ring) color-mix(in oklab, var(--ring) var(--button-mix-50), transparent);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[data-warm-button][data-width="full"] {
|
|
50
|
+
width: var(--button-width-full);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Active scale + full motion reduce (legacy: active:not-aria-[haspopup]:scale-[0.97] motion-reduce:transition-none motion-reduce:active:scale-100) */
|
|
54
|
+
[data-warm-button]:active:not([aria-haspopup]) {
|
|
55
|
+
transform: scale(0.97);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@media (prefers-reduced-motion: reduce) {
|
|
59
|
+
[data-warm-button] {
|
|
60
|
+
transition: none;
|
|
61
|
+
}
|
|
62
|
+
[data-warm-button]:active:not([aria-haspopup]) {
|
|
63
|
+
transform: none;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Variants (from cva + overrides + edge; tokens for color/shadow/radius) */
|
|
68
|
+
[data-warm-button][data-variant="default"] {
|
|
69
|
+
background-color: var(--button-primary-bg);
|
|
70
|
+
background-image: var(--button-primary-gradient);
|
|
71
|
+
color: var(--button-primary-foreground);
|
|
72
|
+
box-shadow: var(--button-primary-shadow);
|
|
73
|
+
/* no explicit border to avoid regression; shadow provides chrome */
|
|
74
|
+
}
|
|
75
|
+
[data-warm-button][data-variant="default"]:hover {
|
|
76
|
+
background-color: var(--button-primary-hover);
|
|
77
|
+
background-image: var(--button-primary-gradient-hover);
|
|
78
|
+
}
|
|
79
|
+
[data-warm-button][data-variant="default"]:active {
|
|
80
|
+
background-color: var(--button-primary-active);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
[data-warm-button][data-variant="outline"] {
|
|
84
|
+
background-color: var(--background);
|
|
85
|
+
box-shadow: var(--button-outline-shadow);
|
|
86
|
+
border-style: none;
|
|
87
|
+
}
|
|
88
|
+
[data-warm-button][data-variant="outline"]:hover {
|
|
89
|
+
background-color: var(--muted);
|
|
90
|
+
color: var(--foreground);
|
|
91
|
+
}
|
|
92
|
+
.dark [data-warm-button][data-variant="outline"] {
|
|
93
|
+
background-color: var(--warm-surface-raised-dark, var(--input));
|
|
94
|
+
box-shadow: var(--button-outline-shadow-inset);
|
|
95
|
+
}
|
|
96
|
+
.dark [data-warm-button][data-variant="outline"]:hover {
|
|
97
|
+
background-color: var(--warm-surface-raised-hover-dark, var(--muted));
|
|
98
|
+
}
|
|
99
|
+
[data-warm-button][data-variant="outline"]:disabled {
|
|
100
|
+
opacity: 1;
|
|
101
|
+
background-color: var(--muted);
|
|
102
|
+
color: var(--muted-foreground);
|
|
103
|
+
box-shadow: var(--button-outline-shadow);
|
|
104
|
+
}
|
|
105
|
+
.dark [data-warm-button][data-variant="outline"]:disabled {
|
|
106
|
+
background-color: color-mix(in oklab, var(--input) var(--button-mix-20), transparent);
|
|
107
|
+
box-shadow: var(--button-outline-shadow-inset);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[data-warm-button][data-variant="ghost"] {
|
|
111
|
+
/* `background-color` (not the `background` shorthand): the shorthand
|
|
112
|
+
implicitly resets `background-clip` to its initial `border-box`,
|
|
113
|
+
diverging from the base recipe's `padding-box` (origin: `bg-clip-padding`
|
|
114
|
+
with no resting-state Tailwind `bg-*` utility on ghost). */
|
|
115
|
+
background-color: transparent;
|
|
116
|
+
}
|
|
117
|
+
[data-warm-button][data-variant="ghost"]:hover {
|
|
118
|
+
background-color: var(--muted);
|
|
119
|
+
color: var(--foreground);
|
|
120
|
+
}
|
|
121
|
+
.dark [data-warm-button][data-variant="ghost"]:hover {
|
|
122
|
+
background-color: color-mix(in oklab, var(--muted) var(--button-mix-50), transparent);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
[data-warm-button][data-variant="destructive"] {
|
|
126
|
+
background-color: color-mix(in oklab, var(--destructive) var(--button-mix-10), transparent);
|
|
127
|
+
color: var(--destructive);
|
|
128
|
+
}
|
|
129
|
+
[data-warm-button][data-variant="destructive"]:hover {
|
|
130
|
+
background-color: color-mix(in oklab, var(--destructive) var(--button-mix-20), transparent);
|
|
131
|
+
}
|
|
132
|
+
.dark [data-warm-button][data-variant="destructive"] {
|
|
133
|
+
background-color: color-mix(in oklab, var(--destructive) var(--button-mix-20), transparent);
|
|
134
|
+
}
|
|
135
|
+
.dark [data-warm-button][data-variant="destructive"]:hover {
|
|
136
|
+
background-color: color-mix(in oklab, var(--destructive) var(--button-mix-30), transparent);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
[data-warm-button][data-variant="apple"] {
|
|
140
|
+
/* `!important` on color/background: legacy's `buttonVariantClasses.apple`
|
|
141
|
+
overlay always applies after any presentation overlay in `cn()` order
|
|
142
|
+
(e.g. promptCategory's own `bg-background`/`text-foreground`,
|
|
143
|
+
sidebarIcon's `text-sidebar-foreground`, tableHeader's
|
|
144
|
+
`text-muted-foreground`), so apple's colors always win regardless of
|
|
145
|
+
presentation. Presentation rules here use `:not([data-explicit-size])`,
|
|
146
|
+
which raises their specificity above a plain variant selector, so
|
|
147
|
+
`!important` is required to reproduce that same always-wins order. Box
|
|
148
|
+
shadow is deliberately NOT `!important`: promptCategory's own
|
|
149
|
+
`shadow-xs` legitimately overrides apple's outline-style shadow (see
|
|
150
|
+
the promptCategory rules above), matching origin exactly. */
|
|
151
|
+
background-color: var(--apple-button-background) !important;
|
|
152
|
+
color: var(--apple-button-foreground) !important;
|
|
153
|
+
box-shadow: var(--button-outline-shadow);
|
|
154
|
+
}
|
|
155
|
+
button[data-warm-button][data-variant="apple"] {
|
|
156
|
+
appearance: button;
|
|
157
|
+
background-clip: padding-box;
|
|
158
|
+
}
|
|
159
|
+
[data-warm-button][data-variant="apple"]:hover {
|
|
160
|
+
background-color: color-mix(in oklab, var(--apple-button-hover) var(--button-mix-90), transparent);
|
|
161
|
+
color: var(--apple-button-foreground);
|
|
162
|
+
}
|
|
163
|
+
[data-warm-button][data-variant="arrow"] {
|
|
164
|
+
border-width: 0;
|
|
165
|
+
text-decoration: underline;
|
|
166
|
+
text-underline-offset: var(--button-underline-offset);
|
|
167
|
+
}
|
|
168
|
+
[data-warm-button][data-variant="arrow"] svg {
|
|
169
|
+
width: var(--button-icon-size);
|
|
170
|
+
height: var(--button-icon-size);
|
|
171
|
+
transition: transform calc(var(--motion-duration-fast) * 2);
|
|
172
|
+
}
|
|
173
|
+
[data-warm-button][data-variant="arrow"]:hover svg {
|
|
174
|
+
transform: translate(calc(var(--motion-distance-sm) / 2), calc(-1 * var(--motion-distance-sm) / 2));
|
|
175
|
+
}
|
|
176
|
+
@media (prefers-reduced-motion: reduce) {
|
|
177
|
+
[data-warm-button][data-variant="arrow"]:hover svg {
|
|
178
|
+
transform: none;
|
|
179
|
+
transition: none;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* Sizes (public only; presentation overrides suppressed by explicit size via data attr, matches old resolveButton) */
|
|
184
|
+
[data-warm-button][data-size="default"] {
|
|
185
|
+
height: var(--button-height-default);
|
|
186
|
+
padding-left: var(--button-padding-x);
|
|
187
|
+
padding-right: var(--button-padding-x);
|
|
188
|
+
gap: var(--button-gap);
|
|
189
|
+
}
|
|
190
|
+
[data-warm-button][data-size="jumbo"] {
|
|
191
|
+
height: var(--button-height-jumbo);
|
|
192
|
+
padding-left: var(--button-padding-x);
|
|
193
|
+
padding-right: var(--button-padding-x);
|
|
194
|
+
font-size: var(--button-font-size-jumbo);
|
|
195
|
+
font-weight: 700;
|
|
196
|
+
line-height: var(--button-line-height-jumbo);
|
|
197
|
+
gap: var(--button-gap);
|
|
198
|
+
}
|
|
199
|
+
[data-warm-button][data-size="icon"] {
|
|
200
|
+
width: var(--button-height-icon);
|
|
201
|
+
height: var(--button-height-icon);
|
|
202
|
+
}
|
|
203
|
+
/* Explicit-size 'default' gets an extra vertical inset (legacy
|
|
204
|
+
buttonInputAlignmentClasses.default = 'bg-clip-border py-2'); this only
|
|
205
|
+
applies when the caller passes size="default" explicitly, never when
|
|
206
|
+
'default' is reached through presentation-driven implicit sizing. */
|
|
207
|
+
[data-warm-button][data-size="default"][data-explicit-size] {
|
|
208
|
+
padding-top: var(--button-padding-y-md);
|
|
209
|
+
padding-bottom: var(--button-padding-y-md);
|
|
210
|
+
background-clip: border-box;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Presentations: private legacy maps drive resolve to public data-size; geometry only when !data-explicit-size. Exact from old buttonPresentationClasses + primitive sizes (icon-round=36px etc) + edge + variants. */
|
|
214
|
+
[data-warm-button][data-presentation="assistantIcon"]:not([data-explicit-size]) {
|
|
215
|
+
width: var(--button-size-xs);
|
|
216
|
+
height: var(--button-size-xs);
|
|
217
|
+
padding: var(--button-padding-y-md);
|
|
218
|
+
}
|
|
219
|
+
[data-warm-button][data-presentation="assistantIcon"]:not([data-explicit-size]):active:not([aria-haspopup]) {
|
|
220
|
+
transform: scale(0.9);
|
|
221
|
+
}
|
|
222
|
+
@media (prefers-reduced-motion: reduce) {
|
|
223
|
+
[data-warm-button][data-presentation="assistantIcon"]:not([data-explicit-size]):active:not(
|
|
224
|
+
[aria-haspopup]
|
|
225
|
+
) {
|
|
226
|
+
transform: none;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
[data-warm-button][data-presentation="assistantRoundIcon"]:not([data-explicit-size]) {
|
|
230
|
+
/* old icon-round 36px */
|
|
231
|
+
width: var(--button-height-icon);
|
|
232
|
+
height: var(--button-height-icon);
|
|
233
|
+
}
|
|
234
|
+
[data-warm-button][data-presentation="composerIcon"]:not([data-explicit-size]) {
|
|
235
|
+
width: var(--button-size-sm);
|
|
236
|
+
height: var(--button-size-sm);
|
|
237
|
+
padding: var(--button-padding-y-md);
|
|
238
|
+
}
|
|
239
|
+
[data-warm-button][data-presentation="editPill"]:not([data-explicit-size]) {
|
|
240
|
+
/* old sm 32px */
|
|
241
|
+
height: var(--button-size-md);
|
|
242
|
+
padding-left: var(--button-padding-x);
|
|
243
|
+
padding-right: var(--button-padding-x);
|
|
244
|
+
}
|
|
245
|
+
[data-warm-button][data-presentation="hero"]:not([data-explicit-size]) {
|
|
246
|
+
height: var(--button-size-lg);
|
|
247
|
+
padding-left: var(--button-padding-x);
|
|
248
|
+
padding-right: var(--button-padding-x);
|
|
249
|
+
padding-top: var(--button-padding-y-md);
|
|
250
|
+
padding-bottom: var(--button-padding-y-md);
|
|
251
|
+
font-size: var(--button-font-size-base);
|
|
252
|
+
font-weight: 700;
|
|
253
|
+
line-height: var(--button-line-height-base);
|
|
254
|
+
background-clip: border-box;
|
|
255
|
+
}
|
|
256
|
+
[data-warm-button][data-presentation="promptCategory"]:not([data-explicit-size]) {
|
|
257
|
+
height: var(--button-height-default);
|
|
258
|
+
gap: var(--button-gap);
|
|
259
|
+
background-color: var(--background);
|
|
260
|
+
padding-left: var(--button-padding-x);
|
|
261
|
+
padding-right: var(--button-padding-x);
|
|
262
|
+
font-size: var(--button-font-size);
|
|
263
|
+
font-weight: 400;
|
|
264
|
+
color: var(--foreground);
|
|
265
|
+
transition-property: var(--button-transition-property-colors);
|
|
266
|
+
}
|
|
267
|
+
/* legacy's `getButtonEdgeClassName` applies `shadow-xs!` (Tailwind's `!`
|
|
268
|
+
important modifier) to every `default`-variant promptCategory button
|
|
269
|
+
regardless of explicit/implicit sizing, overriding the default variant's
|
|
270
|
+
own gradient shadow unconditionally; mirror that with `!important` here. */
|
|
271
|
+
[data-warm-button][data-presentation="promptCategory"][data-variant="default"] {
|
|
272
|
+
box-shadow: var(--shadow-xs) !important;
|
|
273
|
+
}
|
|
274
|
+
/* buttonPresentationClasses.promptCategory's own (non-!important) `shadow-xs`
|
|
275
|
+
utility: only present in implicit mode (resolveLegacyButton's `!size`
|
|
276
|
+
branch), and only survives the cascade for variants without their own
|
|
277
|
+
later shadow override — arrow forces `shadow-none` and must keep winning;
|
|
278
|
+
default is already covered, unconditionally, by the rule above. */
|
|
279
|
+
[data-warm-button][data-presentation="promptCategory"]:not([data-explicit-size]):not([data-variant="arrow"]) {
|
|
280
|
+
box-shadow: var(--shadow-xs) !important;
|
|
281
|
+
}
|
|
282
|
+
[data-warm-button][data-presentation="promptCategory"]:hover {
|
|
283
|
+
background-color: var(--muted);
|
|
284
|
+
}
|
|
285
|
+
[data-warm-button][data-presentation="promptOption"]:not([data-explicit-size]) {
|
|
286
|
+
height: auto;
|
|
287
|
+
min-height: var(--button-min-height-option);
|
|
288
|
+
align-items: flex-start;
|
|
289
|
+
justify-content: flex-start;
|
|
290
|
+
gap: var(--button-gap);
|
|
291
|
+
padding: var(--button-padding-x);
|
|
292
|
+
font-size: var(--button-font-size);
|
|
293
|
+
}
|
|
294
|
+
[data-warm-button][data-presentation="sidebarIcon"]:not([data-explicit-size]) {
|
|
295
|
+
color: var(--sidebar-foreground);
|
|
296
|
+
background-clip: border-box;
|
|
297
|
+
}
|
|
298
|
+
[data-warm-button][data-presentation="sidebarIcon"]:hover {
|
|
299
|
+
background-color: var(--sidebar-accent);
|
|
300
|
+
color: var(--sidebar-accent-foreground);
|
|
301
|
+
}
|
|
302
|
+
[data-warm-button][data-presentation="tableHeader"]:not([data-explicit-size]) {
|
|
303
|
+
height: auto;
|
|
304
|
+
min-height: 0;
|
|
305
|
+
width: var(--button-width-full);
|
|
306
|
+
justify-content: flex-start;
|
|
307
|
+
gap: var(--button-gap);
|
|
308
|
+
padding: 0;
|
|
309
|
+
font-weight: 400;
|
|
310
|
+
/* text-sm's line-height: tableHeader is the only non-arrow presentation
|
|
311
|
+
relying on `height: auto` with real text content, so the base rule's
|
|
312
|
+
`line-height: 1` (matching cva's other fixed-height presentations,
|
|
313
|
+
where line-height is irrelevant) would otherwise under-measure it. */
|
|
314
|
+
line-height: var(--button-line-height);
|
|
315
|
+
color: var(--muted-foreground);
|
|
316
|
+
}
|
|
317
|
+
[data-warm-button][data-presentation="tableHeader"]:hover {
|
|
318
|
+
background-color: transparent;
|
|
319
|
+
color: var(--foreground);
|
|
320
|
+
}
|
|
321
|
+
[data-warm-button][data-presentation="touch"]:not([data-explicit-size]) {
|
|
322
|
+
min-height: var(--button-min-height-touch);
|
|
323
|
+
}
|
|
324
|
+
[data-warm-button][data-presentation="threadMore"]:not([data-explicit-size]) {
|
|
325
|
+
width: var(--button-size-xs);
|
|
326
|
+
height: var(--button-size-xs);
|
|
327
|
+
padding: 0;
|
|
328
|
+
}
|
|
329
|
+
[data-warm-button][data-presentation="threadNew"]:not([data-explicit-size]) {
|
|
330
|
+
height: var(--button-size-md);
|
|
331
|
+
justify-content: flex-start;
|
|
332
|
+
gap: var(--button-gap);
|
|
333
|
+
padding-left: var(--button-padding-x-sm);
|
|
334
|
+
padding-right: var(--button-padding-x-sm);
|
|
335
|
+
font-size: var(--button-font-size);
|
|
336
|
+
font-weight: 400;
|
|
337
|
+
}
|
|
338
|
+
[data-warm-button][data-presentation="threadNew"]:hover {
|
|
339
|
+
background-color: var(--muted);
|
|
340
|
+
}
|
|
341
|
+
[data-warm-button][data-presentation="compact"]:not([data-explicit-size]) {
|
|
342
|
+
height: var(--button-size-md);
|
|
343
|
+
padding-left: var(--button-padding-x-sm);
|
|
344
|
+
padding-right: var(--button-padding-x-sm);
|
|
345
|
+
}
|
|
346
|
+
/* 'default' and 'controlPeer' both resolve to private size 'default' with no
|
|
347
|
+
own `buttonPresentationClasses` entry, so origin's `resolveLegacyButton`
|
|
348
|
+
falls through to `buttonInputAlignmentClasses.default = 'bg-clip-border
|
|
349
|
+
py-2'` even with no explicit size passed. Mirror that here (the explicit
|
|
350
|
+
`[data-size="default"][data-explicit-size]` rule below only fires once a
|
|
351
|
+
caller passes size="default" explicitly, so implicit 'default'/'controlPeer'
|
|
352
|
+
need their own copy of the same two properties). */
|
|
353
|
+
[data-warm-button][data-presentation="default"]:not([data-explicit-size]),
|
|
354
|
+
[data-warm-button][data-presentation="controlPeer"]:not([data-explicit-size]) {
|
|
355
|
+
padding-top: var(--button-padding-y-md);
|
|
356
|
+
padding-bottom: var(--button-padding-y-md);
|
|
357
|
+
background-clip: border-box;
|
|
358
|
+
}
|
|
359
|
+
[data-warm-button][data-presentation="menuOption"]:not([data-explicit-size]) {
|
|
360
|
+
/* old option min-height/auto 64px */
|
|
361
|
+
height: auto;
|
|
362
|
+
min-height: var(--button-min-height-option);
|
|
363
|
+
align-items: flex-start;
|
|
364
|
+
justify-content: flex-start;
|
|
365
|
+
gap: var(--button-gap);
|
|
366
|
+
padding: var(--button-padding-x);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* Arrow overrides any presentation-driven box geometry declared above it:
|
|
370
|
+
legacy's cn(presentationClassName, variantClassName, ...) always merges the
|
|
371
|
+
arrow variant classes last, so arrow's box reset wins whenever it conflicts
|
|
372
|
+
with a presentation, and height is always content-driven (verified against
|
|
373
|
+
the real compiled legacy recipe: every presentation x size combination
|
|
374
|
+
renders the same auto height once variant="arrow"). Width is intentionally
|
|
375
|
+
left alone everywhere: arrow's own recipe never sets it, so an explicit
|
|
376
|
+
public size's fixed width (e.g. `[data-size="icon"]`) or a presentation's
|
|
377
|
+
own private width (e.g. assistantIcon's 1.5rem) still applies underneath
|
|
378
|
+
it. The selector repeats `[data-variant="arrow"]` to match the (0,3,0)
|
|
379
|
+
specificity of the `:not([data-explicit-size])` presentation rules and the
|
|
380
|
+
`[data-size][data-explicit-size]` size rule above, so this later rule wins
|
|
381
|
+
the cascade instead of losing on specificity. */
|
|
382
|
+
[data-warm-button][data-variant="arrow"][data-variant="arrow"] {
|
|
383
|
+
height: auto;
|
|
384
|
+
padding: 0;
|
|
385
|
+
gap: var(--button-gap);
|
|
386
|
+
background-color: transparent;
|
|
387
|
+
background-image: none;
|
|
388
|
+
box-shadow: none;
|
|
389
|
+
border-radius: 0;
|
|
390
|
+
color: var(--foreground);
|
|
391
|
+
font-size: var(--button-font-size-base);
|
|
392
|
+
/* text-base's line-height (verified to win over any competing presentation
|
|
393
|
+
or size text utility, e.g. jumbo's text-lg, in the real compiled legacy
|
|
394
|
+
recipe). Only affects buttons with a real text node: an icon-only arrow
|
|
395
|
+
button has no inline content to lay out a line box for, so this is a
|
|
396
|
+
no-op there and height still resolves from the icon's own box (verified:
|
|
397
|
+
icon-only arrow buttons measure 16/20px tall matching their glyph, never
|
|
398
|
+
the 24px text strut). */
|
|
399
|
+
line-height: var(--button-line-height-base);
|
|
400
|
+
font-weight: 700;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* SVG rules (from cva) */
|
|
404
|
+
[data-warm-button] svg {
|
|
405
|
+
pointer-events: none;
|
|
406
|
+
flex-shrink: 0;
|
|
407
|
+
}
|
|
408
|
+
[data-warm-button] svg:not([class*="size-"]) {
|
|
409
|
+
width: var(--button-icon-size);
|
|
410
|
+
height: var(--button-icon-size);
|
|
411
|
+
}
|
|
412
|
+
[data-warm-button][data-size="jumbo"] svg:not([class*="size-"]) {
|
|
413
|
+
width: var(--button-icon-size-jumbo);
|
|
414
|
+
height: var(--button-icon-size-jumbo);
|
|
415
|
+
}
|
|
416
|
+
/* `hero`'s private size (`lg`) publicly maps to the same `data-size="jumbo"`
|
|
417
|
+
bucket as the genuine cva `jumbo` size (both being the "larger" public
|
|
418
|
+
size per `resolveButton`), but legacy's real `lg` primitive size carries no
|
|
419
|
+
svg-shrink/grow override at all, unlike `jumbo`'s own `size-5`. Restore the
|
|
420
|
+
unscaled glyph for `hero` specifically so it doesn't inherit jumbo's larger
|
|
421
|
+
icon. Gated to implicit sizing only: if the caller explicitly passes
|
|
422
|
+
`size="jumbo"` on a hero-presentation button, that is a real request for
|
|
423
|
+
the genuine cva `jumbo` size (own `size-5` icon included), not hero's
|
|
424
|
+
private `lg` behavior. `:not([data-explicit-size])` also raises this rule's
|
|
425
|
+
specificity above the plain jumbo rule above, so it wins unconditionally
|
|
426
|
+
whenever it matches. */
|
|
427
|
+
[data-warm-button][data-presentation="hero"]:not([data-explicit-size]) svg:not([class*="size-"]) {
|
|
428
|
+
width: var(--button-icon-size);
|
|
429
|
+
height: var(--button-icon-size);
|
|
430
|
+
}
|
|
431
|
+
/* Arrow's own icon size (legacy `[&_svg]:size-4`) wins over the base and
|
|
432
|
+
default-size svg rules above via source order, but intentionally has lower
|
|
433
|
+
specificity than the jumbo icon rule below: legacy's own jumbo svg rule
|
|
434
|
+
(`[&_svg:not([class*='size-'])]:size-5`) carries one more selector
|
|
435
|
+
component than arrow's (`[&_svg]:size-4`), so jumbo's larger glyph size
|
|
436
|
+
wins whenever both apply (verified against the real compiled recipe). */
|
|
437
|
+
[data-warm-button][data-variant="arrow"] svg {
|
|
438
|
+
width: var(--button-icon-size);
|
|
439
|
+
height: var(--button-icon-size);
|
|
440
|
+
}
|
|
441
|
+
/* icon-xs primitive size (assistantIcon, threadMore) shrinks the icon glyph;
|
|
442
|
+
suppressed when the caller passes an explicit public size. */
|
|
443
|
+
[data-warm-button][data-presentation="assistantIcon"]:not([data-explicit-size]) svg:not([class*="size-"]),
|
|
444
|
+
[data-warm-button][data-presentation="threadMore"]:not([data-explicit-size]) svg:not([class*="size-"]) {
|
|
445
|
+
width: var(--button-icon-size-xs);
|
|
446
|
+
height: var(--button-icon-size-xs);
|
|
447
|
+
}
|
|
448
|
+
/* Aria states (from cva aria-invalid + aria-expanded + dark) */
|
|
449
|
+
[data-warm-button][aria-invalid] {
|
|
450
|
+
box-shadow: 0 0 0 var(--button-focus-ring) color-mix(in oklab, var(--destructive) var(--button-mix-20), transparent);
|
|
451
|
+
}
|
|
452
|
+
.dark [data-warm-button][aria-invalid] {
|
|
453
|
+
box-shadow: 0 0 0 var(--button-focus-ring) color-mix(in oklab, var(--destructive) var(--button-mix-40), transparent);
|
|
454
|
+
}
|
|
455
|
+
[data-warm-button][data-variant="ghost"][aria-expanded="true"],
|
|
456
|
+
[data-warm-button][data-variant="outline"][aria-expanded="true"] {
|
|
457
|
+
background-color: var(--muted);
|
|
458
|
+
color: var(--foreground);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* Disabled base + variant overrides (keep full shadow/border for some) */
|
|
462
|
+
[data-warm-button]:disabled {
|
|
463
|
+
pointer-events: none;
|
|
464
|
+
opacity: 0.5;
|
|
465
|
+
}
|
|
466
|
+
[data-warm-button][data-variant="apple"]:disabled,
|
|
467
|
+
[data-warm-button][data-variant="outline"]:disabled {
|
|
468
|
+
opacity: 1;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/* Logo - keep data-warm-logo on wrapper div AND svg; unambiguous element selectors + data to avoid sizing wrapper or constraining width; wrapper inline-flex (not inline-block); preserve gap/text/typography/tone/icon sizes from old */
|
|
472
|
+
[data-warm-logo] {
|
|
473
|
+
display: inline-flex;
|
|
474
|
+
align-items: center;
|
|
475
|
+
gap: var(--logo-gap);
|
|
476
|
+
}
|
|
477
|
+
[data-warm-logo] > span {
|
|
478
|
+
font-size: var(--logo-font-size);
|
|
479
|
+
font-weight: 700;
|
|
480
|
+
letter-spacing: var(--logo-letter-spacing);
|
|
481
|
+
}
|
|
482
|
+
svg[data-warm-logo] {
|
|
483
|
+
display: inline-block;
|
|
484
|
+
vertical-align: middle;
|
|
485
|
+
fill: currentColor;
|
|
486
|
+
flex-shrink: 0;
|
|
487
|
+
}
|
|
488
|
+
[data-warm-logo][data-size="badge"] svg[data-warm-logo] { width: var(--logo-size-badge); height: var(--logo-size-badge); }
|
|
489
|
+
[data-warm-logo][data-size="default"] svg[data-warm-logo] { width: var(--logo-size-default); height: var(--logo-size-default); }
|
|
490
|
+
[data-warm-logo][data-size="feature"] svg[data-warm-logo] { width: var(--logo-size-feature); height: var(--logo-size-feature); }
|
|
491
|
+
[data-warm-logo][data-size="hero"] svg[data-warm-logo] { width: var(--logo-size-hero); height: var(--logo-size-hero); }
|
|
492
|
+
|
|
493
|
+
[data-warm-logo][data-tone="foreground"] { color: var(--foreground); }
|
|
494
|
+
[data-warm-logo][data-tone="sidebar"] { color: var(--sidebar-foreground); }
|
|
495
|
+
|
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type ButtonPresentation = 'assistantIcon' | 'assistantRoundIcon' | 'default' | 'composerIcon' | 'compact' | 'controlPeer' | 'editPill' | 'hero' | 'menuOption' | 'promptCategory' | 'promptOption' | 'sidebarIcon' | 'tableHeader' | 'threadMore' | 'threadNew' | 'touch';
|
|
3
|
+
export type ButtonVariant = 'default' | 'outline' | 'ghost' | 'destructive' | 'apple' | 'arrow';
|
|
4
|
+
export type ButtonSize = 'default' | 'jumbo' | 'icon';
|
|
5
|
+
export type ButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className' | 'style'> & {
|
|
6
|
+
asChild?: never;
|
|
7
|
+
className?: never;
|
|
8
|
+
presentation?: ButtonPresentation;
|
|
9
|
+
render?: never;
|
|
10
|
+
size?: ButtonSize;
|
|
11
|
+
style?: never;
|
|
12
|
+
variant?: ButtonVariant;
|
|
13
|
+
width?: 'full';
|
|
14
|
+
};
|
|
15
|
+
export type ButtonLinkProps = Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'disabled' | 'style'> & {
|
|
16
|
+
href: string;
|
|
17
|
+
presentation?: ButtonPresentation;
|
|
18
|
+
size?: ButtonSize;
|
|
19
|
+
variant?: ButtonVariant;
|
|
20
|
+
width?: 'full';
|
|
21
|
+
asChild?: never;
|
|
22
|
+
className?: never;
|
|
23
|
+
disabled?: never;
|
|
24
|
+
render?: never;
|
|
25
|
+
style?: never;
|
|
26
|
+
};
|
|
27
|
+
declare const Button: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "className" | "style"> & {
|
|
28
|
+
asChild?: never;
|
|
29
|
+
className?: never;
|
|
30
|
+
presentation?: ButtonPresentation;
|
|
31
|
+
render?: never;
|
|
32
|
+
size?: ButtonSize;
|
|
33
|
+
style?: never;
|
|
34
|
+
variant?: ButtonVariant;
|
|
35
|
+
width?: 'full';
|
|
36
|
+
} & React.RefAttributes<HTMLButtonElement>>;
|
|
37
|
+
declare const ButtonLink: React.ForwardRefExoticComponent<Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "className" | "disabled" | "style"> & {
|
|
38
|
+
href: string;
|
|
39
|
+
presentation?: ButtonPresentation;
|
|
40
|
+
size?: ButtonSize;
|
|
41
|
+
variant?: ButtonVariant;
|
|
42
|
+
width?: 'full';
|
|
43
|
+
asChild?: never;
|
|
44
|
+
className?: never;
|
|
45
|
+
disabled?: never;
|
|
46
|
+
render?: never;
|
|
47
|
+
style?: never;
|
|
48
|
+
} & React.RefAttributes<HTMLAnchorElement>>;
|
|
49
|
+
export { Button, ButtonLink };
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
const presentationToPrivateSize = {
|
|
4
|
+
assistantIcon: 'icon-xs',
|
|
5
|
+
assistantRoundIcon: 'icon-round',
|
|
6
|
+
composerIcon: 'icon-sm',
|
|
7
|
+
default: 'default',
|
|
8
|
+
compact: 'sm',
|
|
9
|
+
controlPeer: 'default',
|
|
10
|
+
editPill: 'sm',
|
|
11
|
+
hero: 'lg',
|
|
12
|
+
menuOption: 'option',
|
|
13
|
+
promptCategory: 'default',
|
|
14
|
+
promptOption: 'option',
|
|
15
|
+
sidebarIcon: 'icon',
|
|
16
|
+
tableHeader: 'default',
|
|
17
|
+
threadMore: 'icon-xs',
|
|
18
|
+
threadNew: 'sm',
|
|
19
|
+
touch: 'default',
|
|
20
|
+
};
|
|
21
|
+
function resolveButton(presentation, size) {
|
|
22
|
+
const privateSize = presentationToPrivateSize[presentation];
|
|
23
|
+
const resolvedSize = size ??
|
|
24
|
+
(privateSize === 'lg'
|
|
25
|
+
? 'jumbo'
|
|
26
|
+
: privateSize === 'icon' ||
|
|
27
|
+
privateSize === 'icon-round' ||
|
|
28
|
+
privateSize === 'icon-sm' ||
|
|
29
|
+
privateSize === 'icon-xs'
|
|
30
|
+
? 'icon'
|
|
31
|
+
: 'default');
|
|
32
|
+
return { resolvedSize, explicitSize: size ? resolvedSize : undefined };
|
|
33
|
+
}
|
|
34
|
+
function withArrow(variant, children) {
|
|
35
|
+
if (variant !== 'arrow')
|
|
36
|
+
return children;
|
|
37
|
+
return (_jsxs(_Fragment, { children: [children, _jsxs("svg", { "aria-hidden": "true", "data-icon": "inline-end", width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M7 7h10v10" }), _jsx("path", { d: "M7 17L17 7" })] })] }));
|
|
38
|
+
}
|
|
39
|
+
const Button = React.forwardRef(function Button({ children, className: _className, presentation = 'default', render: _render, size, style: _style, type, variant = 'default', width, asChild: _asChild, ...props }, ref) {
|
|
40
|
+
const { resolvedSize, explicitSize } = resolveButton(presentation, size);
|
|
41
|
+
return (_jsx("button", { ...props, ref: ref, type: type ?? 'button', "data-warm-button": "", "data-slot": "button", "data-presentation": presentation, "data-variant": variant, "data-size": resolvedSize, "data-explicit-size": explicitSize, "data-width": width, children: withArrow(variant, children) }));
|
|
42
|
+
});
|
|
43
|
+
Button.displayName = 'WarmButton';
|
|
44
|
+
const ButtonLink = React.forwardRef(function ButtonLink({ children, className: _className, disabled: _disabled, presentation = 'default', render: _render, size, style: _style, variant = 'default', width, asChild: _asChild, ...props }, ref) {
|
|
45
|
+
const { resolvedSize, explicitSize } = resolveButton(presentation, size);
|
|
46
|
+
return (_jsx("a", { ...props, ref: ref, "data-warm-button": "", "data-slot": "button", "data-presentation": presentation, "data-variant": variant, "data-size": resolvedSize, "data-explicit-size": explicitSize, "data-width": width, children: withArrow(variant, children) }));
|
|
47
|
+
});
|
|
48
|
+
ButtonLink.displayName = 'WarmButtonLink';
|
|
49
|
+
export { Button, ButtonLink };
|
package/dist/logo.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export type WarmLogoSize = 'badge' | 'default' | 'feature' | 'hero';
|
|
3
|
+
export type WarmLogoTone = 'default' | 'foreground' | 'sidebar';
|
|
4
|
+
export type WarmLogoIconProps = {
|
|
5
|
+
size?: WarmLogoSize;
|
|
6
|
+
tone?: WarmLogoTone;
|
|
7
|
+
decorative?: boolean;
|
|
8
|
+
className?: never;
|
|
9
|
+
style?: never;
|
|
10
|
+
asChild?: never;
|
|
11
|
+
render?: never;
|
|
12
|
+
} & Omit<React.SVGProps<SVGSVGElement>, 'className' | 'style'>;
|
|
13
|
+
export type LogoProps = {
|
|
14
|
+
size?: WarmLogoSize;
|
|
15
|
+
tone?: WarmLogoTone;
|
|
16
|
+
showText?: boolean;
|
|
17
|
+
className?: never;
|
|
18
|
+
style?: never;
|
|
19
|
+
textClass?: never;
|
|
20
|
+
asChild?: never;
|
|
21
|
+
render?: never;
|
|
22
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, 'className' | 'style' | 'textClass'>;
|
|
23
|
+
export declare function WarmLogoIcon({ size, tone, decorative, className: _className, style: _style, asChild: _asChild, render: _render, ...props }: WarmLogoIconProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export declare const Logo: React.ForwardRefExoticComponent<{
|
|
25
|
+
size?: WarmLogoSize;
|
|
26
|
+
tone?: WarmLogoTone;
|
|
27
|
+
showText?: boolean;
|
|
28
|
+
className?: never;
|
|
29
|
+
style?: never;
|
|
30
|
+
textClass?: never;
|
|
31
|
+
asChild?: never;
|
|
32
|
+
render?: never;
|
|
33
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "style" | "textClass"> & React.RefAttributes<HTMLDivElement>>;
|
package/dist/logo.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
const WShape = (_jsxs("g", { fill: "currentColor", children: [_jsx("path", { d: "M 33.500 40.660 C 20.206 42.744, 0 48.560, -0 50.302 C -0 51.759, 123.955 348.889, 127.738 356.500 C 142.182 385.561, 169.450 410.275, 198.946 421.040 C 218.922 428.330, 243.683 430.825, 264.526 427.648 C 277.151 425.724, 294.628 420.011, 294.194 417.952 C 293.790 416.040, 172.732 124.599, 168.522 115.405 C 161.779 100.679, 149.168 84.171, 135.093 71.646 C 119.358 57.644, 98.880 47.362, 77 42.478 C 69.645 40.836, 40.220 39.606, 33.500 40.660" }), _jsx("path", { d: "M 229.651 41.597 C 217.916 43.718, 202.443 49.144, 202.744 51.035 C 203.418 55.268, 329.689 356.584, 333.687 363.500 C 340.525 375.327, 345.293 381.483, 355.408 391.538 C 374.603 410.619, 398.878 423.014, 425.907 427.533 C 439.649 429.831, 463.594 429.132, 476 426.072 C 489.892 422.645, 497.188 419.815, 496.703 418.043 C 495.416 413.331, 371.058 116.094, 367.406 109 C 349.467 74.149, 313.008 47.875, 273.752 41.509 C 262.281 39.648, 240.195 39.693, 229.651 41.597" }), _jsx("path", { d: "M 463 40.593 C 451.670 42.545, 447.288 43.569, 439 46.203 C 433.775 47.864, 429.338 49.372, 429.140 49.555 C 428.942 49.737, 434.670 64.021, 441.868 81.297 C 455.574 114.190, 462.215 131.660, 468.447 151.208 C 475.360 172.892, 486.124 186.213, 503.834 194.997 C 530.956 208.450, 564.982 201.679, 583.654 179.112 C 604.399 154.041, 605.407 121.944, 586.291 95.155 C 567.827 69.280, 539.107 50.114, 508 42.907 C 500.680 41.212, 494.713 40.644, 482 40.434 C 472.925 40.284, 464.375 40.356, 463 40.593" })] }));
|
|
4
|
+
export function WarmLogoIcon({ size = 'default', tone = 'default', decorative = false, className: _className, style: _style, asChild: _asChild, render: _render, ...props }) {
|
|
5
|
+
const label = decorative ? undefined : 'Warm logo';
|
|
6
|
+
return (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 600 469", "data-warm-logo": true, "data-size": size, "data-tone": tone, width: size === 'hero' ? 56 : size === 'feature' ? 32 : size === 'default' ? 28 : 12, height: size === 'hero' ? 56 : size === 'feature' ? 32 : size === 'default' ? 28 : 12, "aria-hidden": decorative ? true : undefined, "aria-label": label, ...props, children: WShape }));
|
|
7
|
+
}
|
|
8
|
+
export const Logo = React.forwardRef(function Logo({ size = 'default', tone = 'default', showText = true, className: _className, style: _style, textClass: _textClass, asChild: _asChild, render: _render, ...props }, ref) {
|
|
9
|
+
return (_jsxs("div", { ref: ref, "data-warm-logo": true, "data-size": size, "data-tone": tone, ...props, children: [_jsx(WarmLogoIcon, { size: size, tone: tone, decorative: showText }), showText ? _jsx("span", { children: "Warm" }) : null] }));
|
|
10
|
+
});
|
package/globals.css
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warmio/design",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Warm's public CSS
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Warm's public design contract: CSS tokens and Button/Logo primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"interfaces.css",
|
|
24
24
|
"theme.css",
|
|
25
25
|
"themes.css",
|
|
26
|
+
"components.css",
|
|
26
27
|
"fonts",
|
|
28
|
+
"assets",
|
|
29
|
+
"dist",
|
|
27
30
|
"LICENSES"
|
|
28
31
|
],
|
|
29
32
|
"exports": {
|
|
@@ -32,9 +35,22 @@
|
|
|
32
35
|
"./fonts.css": "./fonts.css",
|
|
33
36
|
"./theme.css": "./theme.css",
|
|
34
37
|
"./themes.css": "./themes.css",
|
|
35
|
-
"./interfaces.css": "./interfaces.css"
|
|
38
|
+
"./interfaces.css": "./interfaces.css",
|
|
39
|
+
"./components.css": "./components.css",
|
|
40
|
+
"./button": {
|
|
41
|
+
"types": "./dist/button.d.ts",
|
|
42
|
+
"import": "./dist/button.js"
|
|
43
|
+
},
|
|
44
|
+
"./logo": {
|
|
45
|
+
"types": "./dist/logo.d.ts",
|
|
46
|
+
"import": "./dist/logo.js"
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": "^19.0.0"
|
|
36
51
|
},
|
|
37
52
|
"scripts": {
|
|
53
|
+
"build": "tsc -p tsconfig.json",
|
|
38
54
|
"pack:check": "node ../../tools/tasks/check-design-package.mjs"
|
|
39
55
|
}
|
|
40
56
|
}
|
package/theme.css
CHANGED
|
@@ -41,6 +41,8 @@
|
|
|
41
41
|
══════════════════════════════════════════════════════════════════════════ */
|
|
42
42
|
--accent: oklch(0.96 0.002 17.2);
|
|
43
43
|
--accent-foreground: oklch(0.214 0.009 43.1);
|
|
44
|
+
--accent-solid: var(--brand-accent-solid);
|
|
45
|
+
--accent-solid-text: oklch(from var(--brand-accent-solid) calc(l - 0.14) calc(c + 0.02) h);
|
|
44
46
|
|
|
45
47
|
/* Interactive hover background - single source of truth for all hover states */
|
|
46
48
|
--hover-bg: color-mix(in oklch, var(--foreground) 4%, transparent);
|
|
@@ -71,6 +73,11 @@
|
|
|
71
73
|
/* Alternating section backgrounds for marketing pages */
|
|
72
74
|
--section-alt: oklch(0.986 0 0);
|
|
73
75
|
|
|
76
|
+
/* Marketing surface and dot patterns (owned by design) */
|
|
77
|
+
--marketing-surface: #f7f7f7;
|
|
78
|
+
--marketing-pattern-dots: url('./assets/subtle-dots.png');
|
|
79
|
+
--marketing-pattern-dots-dark: url('./assets/subtle-dots-dark.svg');
|
|
80
|
+
|
|
74
81
|
/* Charts */
|
|
75
82
|
--chart-1: oklch(0.32 0 none);
|
|
76
83
|
--chart-2: oklch(0.41 0 none);
|
|
@@ -82,7 +89,7 @@
|
|
|
82
89
|
--logo-accent: var(--logo-primary);
|
|
83
90
|
|
|
84
91
|
/* ══════════════════════════════════════════════════════════════════════════
|
|
85
|
-
|
|
92
|
+
SIDEBAR — Transparent to show body texture
|
|
86
93
|
══════════════════════════════════════════════════════════════════════════ */
|
|
87
94
|
--sidebar: oklch(0.986 0 0);
|
|
88
95
|
--sidebar-foreground: var(--foreground);
|
|
@@ -97,7 +104,6 @@
|
|
|
97
104
|
FIXED SEMANTIC COLORS
|
|
98
105
|
These are universal - green=success, amber=warning, red=error, blue=info
|
|
99
106
|
══════════════════════════════════════════════════════════════════════════ */
|
|
100
|
-
|
|
101
107
|
/* Success = Green (H: 145) */
|
|
102
108
|
--success: oklch(0.65 0.16 145);
|
|
103
109
|
--success-text: oklch(0.35 0.16 145);
|
|
@@ -201,13 +207,23 @@
|
|
|
201
207
|
--spacing-xl: 1.5rem;
|
|
202
208
|
--spacing-2xl: 2rem;
|
|
203
209
|
|
|
204
|
-
/*
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
--
|
|
209
|
-
|
|
210
|
-
|
|
210
|
+
/* Motion tokens (single source of truth; package-wide reduced-motion contract)
|
|
211
|
+
* --motion-duration-essential unused by default (requires audited justification + exception)
|
|
212
|
+
* nonessentials (chart uses slow) zero under reduced-motion
|
|
213
|
+
*/
|
|
214
|
+
--motion-duration-fast: 100ms;
|
|
215
|
+
--motion-duration-normal: 300ms;
|
|
216
|
+
--motion-duration-slow: 600ms;
|
|
217
|
+
--motion-duration-essential: 1000ms;
|
|
218
|
+
--motion-distance-sm: 0.25rem;
|
|
219
|
+
--motion-distance-md: 0.5rem;
|
|
220
|
+
--motion-distance-lg: 1rem;
|
|
221
|
+
|
|
222
|
+
/* Aliases keep one scale; legacy names resolve to motion; chart uses slow (nonessential scale) */
|
|
223
|
+
--transition-fast: var(--motion-duration-fast);
|
|
224
|
+
--transition-normal: var(--motion-duration-normal);
|
|
225
|
+
--transition-slow: var(--motion-duration-slow);
|
|
226
|
+
--chart-animation-duration: var(--motion-duration-slow);
|
|
211
227
|
--button-padding-x-sm: 0.5rem;
|
|
212
228
|
--button-padding-y-sm: 0.25rem;
|
|
213
229
|
--button-padding-x-md: 0.75rem;
|
|
@@ -245,6 +261,53 @@
|
|
|
245
261
|
--apple-button-background: var(--foreground);
|
|
246
262
|
--apple-button-foreground: var(--background);
|
|
247
263
|
--apple-button-hover: var(--apple-button-background);
|
|
264
|
+
/* ══════════════════════════════════════════════════════════════════════════
|
|
265
|
+
CANONICAL BUTTON / LOGO / COMPONENT GEOMETRY TOKENS (small coherent set)
|
|
266
|
+
Theme owns ALL; components.css uses ONLY var() / token-derived calc() for
|
|
267
|
+
nonzero lengths/colors/shadows/radii/motion/timings. No local -- decls.
|
|
268
|
+
*/
|
|
269
|
+
--button-height-default: 2.25rem;
|
|
270
|
+
--button-height-jumbo: 3.5rem;
|
|
271
|
+
--button-height-icon: 2.25rem;
|
|
272
|
+
--button-min-height-touch: 2.75rem;
|
|
273
|
+
--button-min-height-option: 4rem;
|
|
274
|
+
--button-padding-x: 1rem;
|
|
275
|
+
--button-font-size-base: 1rem;
|
|
276
|
+
--button-font-size-jumbo: 1.125rem;
|
|
277
|
+
--button-line-height-base: 1.5rem;
|
|
278
|
+
--button-line-height: 1.25rem;
|
|
279
|
+
--button-line-height-jumbo: 1.75rem;
|
|
280
|
+
--button-line-height-hero: 1.5rem;
|
|
281
|
+
--button-icon-size: 1rem;
|
|
282
|
+
--button-icon-size-jumbo: 1.25rem;
|
|
283
|
+
--button-icon-size-xs: 0.75rem;
|
|
284
|
+
--button-size-xs: 1.5rem;
|
|
285
|
+
--button-size-sm: 1.75rem;
|
|
286
|
+
--button-size-md: 2rem;
|
|
287
|
+
--button-size-lg: 3rem;
|
|
288
|
+
--button-underline-offset: 0.25rem;
|
|
289
|
+
--button-focus-ring: 3px;
|
|
290
|
+
--logo-size-badge: 0.75rem;
|
|
291
|
+
--logo-size-default: 1.75rem;
|
|
292
|
+
--logo-size-feature: 2rem;
|
|
293
|
+
--logo-size-hero: 3.5rem;
|
|
294
|
+
--logo-font-size: 0.875rem;
|
|
295
|
+
--logo-letter-spacing: 0.025em;
|
|
296
|
+
--logo-gap: 0.5rem;
|
|
297
|
+
--button-transition-property: color, background-color, box-shadow, border-color, transform;
|
|
298
|
+
--button-transition-property-colors: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to;
|
|
299
|
+
--button-width-full: 100%;
|
|
300
|
+
--warm-surface-raised-dark: var(--input);
|
|
301
|
+
--warm-surface-raised-hover-dark: var(--muted);
|
|
302
|
+
/* canonical mix percents and dark outline inset (resolve dark outline via tokens) */
|
|
303
|
+
--button-mix-10: 10%;
|
|
304
|
+
--button-mix-20: 20%;
|
|
305
|
+
--button-mix-30: 30%;
|
|
306
|
+
--button-mix-40: 40%;
|
|
307
|
+
--button-mix-50: 50%;
|
|
308
|
+
--button-mix-90: 90%;
|
|
309
|
+
--radius-full: 33554400px;
|
|
310
|
+
--button-outline-shadow-inset: inset 0 1px 0 rgb(255 255 255 / 0.1);
|
|
248
311
|
|
|
249
312
|
/* Form Specific */
|
|
250
313
|
--input-height: 2.25rem;
|
|
@@ -305,6 +368,21 @@
|
|
|
305
368
|
--breakpoint-xl: theme('screens.xl');
|
|
306
369
|
--breakpoint-2xl: theme('screens.2xl');
|
|
307
370
|
}
|
|
371
|
+
@media (prefers-reduced-motion: reduce) {
|
|
372
|
+
:root {
|
|
373
|
+
--motion-duration-fast: 0ms;
|
|
374
|
+
--motion-duration-normal: 0ms;
|
|
375
|
+
--motion-duration-slow: 0ms;
|
|
376
|
+
--motion-distance-sm: 0px;
|
|
377
|
+
--motion-distance-md: 0px;
|
|
378
|
+
--motion-distance-lg: 0px;
|
|
379
|
+
--transition-fast: 0ms;
|
|
380
|
+
--transition-normal: 0ms;
|
|
381
|
+
--transition-slow: 0ms;
|
|
382
|
+
--chart-animation-duration: 0ms;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
308
386
|
|
|
309
387
|
/* ════════════════════════════════════════════════════════════════════════════
|
|
310
388
|
DARK MODE
|
|
@@ -324,6 +402,9 @@
|
|
|
324
402
|
/* Alternating section backgrounds - notably darker for visual distinction */
|
|
325
403
|
--section-alt: oklch(0.2 0 0);
|
|
326
404
|
|
|
405
|
+
/* Marketing surface (dark) */
|
|
406
|
+
--marketing-surface: #111;
|
|
407
|
+
/* Date-group header band comment follows */
|
|
327
408
|
/* Date-group header band: a higher-contrast card surface so grouped date rows
|
|
328
409
|
read as clear section dividers in dark mode (light mode uses bg-muted/40). */
|
|
329
410
|
--date-group-surface: color-mix(in oklch, var(--card), white 8%);
|
|
@@ -337,6 +418,8 @@
|
|
|
337
418
|
--secondary-foreground: var(--foreground);
|
|
338
419
|
--accent: oklch(from var(--brand-accent) calc(l + 0.08) calc(c - 0.02) h);
|
|
339
420
|
--accent-foreground: var(--foreground);
|
|
421
|
+
--accent-solid: oklch(from var(--brand-accent-solid) calc(l + 0.08) c h);
|
|
422
|
+
--accent-solid-text: var(--accent-solid);
|
|
340
423
|
|
|
341
424
|
/* Default button gradient (dark) */
|
|
342
425
|
--default-button-gradient-start: oklch(0.4 0 0);
|
|
@@ -367,6 +450,7 @@
|
|
|
367
450
|
--apple-button-background: oklch(0 0 0);
|
|
368
451
|
--apple-button-foreground: oklch(1 0 0);
|
|
369
452
|
--apple-button-hover: var(--apple-button-background);
|
|
453
|
+
--button-outline-shadow-inset: inset 0 1px 0 rgb(255 255 255 / 0.1);
|
|
370
454
|
|
|
371
455
|
/* Sidebar = solid dark surface */
|
|
372
456
|
--sidebar: var(--background);
|
|
@@ -481,6 +565,8 @@
|
|
|
481
565
|
--secondary-foreground: oklch(from var(--brand-neutral) 0.35 0.015 h);
|
|
482
566
|
--accent: var(--brand-accent);
|
|
483
567
|
--accent-foreground: oklch(from var(--brand-neutral) 0.25 0.01 h);
|
|
568
|
+
--accent-solid: var(--brand-accent-solid);
|
|
569
|
+
--accent-solid-text: oklch(from var(--brand-accent-solid) calc(l - 0.14) calc(c + 0.02) h);
|
|
484
570
|
|
|
485
571
|
/* Default button gradient (light) */
|
|
486
572
|
--default-button-gradient-start: oklch(1 0 0);
|
|
@@ -600,6 +686,8 @@
|
|
|
600
686
|
--color-muted-foreground: var(--muted-foreground);
|
|
601
687
|
--color-accent: var(--accent);
|
|
602
688
|
--color-accent-foreground: var(--accent-foreground);
|
|
689
|
+
--color-accent-solid: var(--accent-solid);
|
|
690
|
+
--color-accent-solid-text: var(--accent-solid-text);
|
|
603
691
|
--color-destructive: var(--destructive);
|
|
604
692
|
--color-destructive-foreground: var(--destructive-foreground);
|
|
605
693
|
--color-success: var(--success);
|
package/themes.css
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
OPTIONAL:
|
|
14
14
|
--logo-gradient-start/end Logo gradient colors
|
|
15
|
+
--brand-accent-solid Chromatic accent for links, badges, and highlights
|
|
15
16
|
|
|
16
17
|
════════════════════════════════════════════════════════════════════════════ */
|
|
17
18
|
|
|
@@ -22,6 +23,7 @@
|
|
|
22
23
|
:root {
|
|
23
24
|
--brand-primary: #85807a;
|
|
24
25
|
--brand-accent: #7e7a8a;
|
|
26
|
+
--brand-accent-solid: #8b80a6;
|
|
25
27
|
--brand-muted: #848079;
|
|
26
28
|
--brand-neutral: #837f7c;
|
|
27
29
|
--logo-gradient-start: #85807a;
|
|
@@ -39,6 +41,7 @@
|
|
|
39
41
|
[data-theme='ocean'] {
|
|
40
42
|
--brand-primary: #4a6fa5;
|
|
41
43
|
--brand-accent: #5a8a7a;
|
|
44
|
+
--brand-accent-solid: #8b80a6;
|
|
42
45
|
--brand-muted: #6b7f99;
|
|
43
46
|
--brand-neutral: #6e7a85;
|
|
44
47
|
--logo-gradient-start: #4a6fa5;
|