@claralight-design/react 0.0.1
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/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/lib/anchored.d.ts +152 -0
- package/dist/lib/anchored.js +821 -0
- package/dist/lib/anchored.js.map +1 -0
- package/dist/lib/morph.js +484 -0
- package/dist/lib/morph.js.map +1 -0
- package/dist/lib/squircle.d.ts +95 -0
- package/dist/lib/squircle.js +220 -0
- package/dist/lib/squircle.js.map +1 -0
- package/dist/lib/utils.d.ts +30 -0
- package/dist/lib/utils.js +76 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/ui/button.d.ts +40 -0
- package/dist/ui/button.js +104 -0
- package/dist/ui/button.js.map +1 -0
- package/dist/ui/card.d.ts +54 -0
- package/dist/ui/card.js +82 -0
- package/dist/ui/card.js.map +1 -0
- package/dist/ui/dialog.d.ts +82 -0
- package/dist/ui/dialog.js +129 -0
- package/dist/ui/dialog.js.map +1 -0
- package/dist/ui/input.d.ts +34 -0
- package/dist/ui/input.js +60 -0
- package/dist/ui/input.js.map +1 -0
- package/dist/ui/popover.d.ts +67 -0
- package/dist/ui/popover.js +93 -0
- package/dist/ui/popover.js.map +1 -0
- package/dist/ui/scroll-area.d.ts +110 -0
- package/dist/ui/scroll-area.js +125 -0
- package/dist/ui/scroll-area.js.map +1 -0
- package/dist/ui/select.d.ts +113 -0
- package/dist/ui/select.js +213 -0
- package/dist/ui/select.js.map +1 -0
- package/dist/ui/tooltip.d.ts +108 -0
- package/dist/ui/tooltip.js +142 -0
- package/dist/ui/tooltip.js.map +1 -0
- package/package.json +72 -0
- package/styles/anchored.css +115 -0
- package/styles/base.css +309 -0
- package/styles/fonts/README.md +63 -0
- package/styles/index.css +28 -0
- package/styles/scroll-area.css +299 -0
- package/styles/theme.css +540 -0
- package/styles/tooltip.css +156 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaraLight Design — the anchored overlay surface.
|
|
3
|
+
*
|
|
4
|
+
* Popover and Tooltip only. Split out of `base.css` because it is the one part
|
|
5
|
+
* of the language a project can decline: a build with neither component has no
|
|
6
|
+
* use for the tail padding, the probe or the grow-from-the-tail entrance.
|
|
7
|
+
*
|
|
8
|
+
* Tokens live in `theme.css`; the tail itself is constructed in
|
|
9
|
+
* `src/lib/anchored.tsx`, which is the other half of this file.
|
|
10
|
+
*
|
|
11
|
+
* @import "tailwindcss";
|
|
12
|
+
* @import "@claralight-design/react/styles.css";
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
@layer components {
|
|
16
|
+
/**
|
|
17
|
+
* Popovers and tooltips.
|
|
18
|
+
*
|
|
19
|
+
* The pointing arrow is *not* a second element. It is spliced into the
|
|
20
|
+
* surface's own smooth-corner path, so fill, backdrop blur and the 1px
|
|
21
|
+
* outline cross the join as one shape — see `src/lib/anchored.tsx`. A
|
|
22
|
+
* separate arrow element cannot do that here: ClaraLight's frost fill is
|
|
23
|
+
* translucent, so an overlap would composite into a visible dark seam and
|
|
24
|
+
* the body's outline would run straight through the arrow's mouth.
|
|
25
|
+
*
|
|
26
|
+
* The consequence for CSS is this padding. The tail lives *inside* the
|
|
27
|
+
* element's box, occupying `--cl-arrow-extent` on whichever side faces the
|
|
28
|
+
* anchor, so content has to be inset by that much again or it would sit in
|
|
29
|
+
* the tail. `[data-side]` is Base UI's, and names the side of the *anchor*
|
|
30
|
+
* the surface was placed on — so the tail is on the opposite edge.
|
|
31
|
+
*/
|
|
32
|
+
.cl-anchored {
|
|
33
|
+
padding: var(--cl-anchored-padding-y) var(--cl-anchored-padding-x);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.cl-anchored-arrow[data-side="top"] {
|
|
37
|
+
padding-bottom: calc(var(--cl-anchored-padding-y) + var(--cl-arrow-extent));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.cl-anchored-arrow[data-side="bottom"] {
|
|
41
|
+
padding-top: calc(var(--cl-anchored-padding-y) + var(--cl-arrow-extent));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.cl-anchored-arrow[data-side="left"] {
|
|
45
|
+
padding-right: calc(var(--cl-anchored-padding-x) + var(--cl-arrow-extent));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.cl-anchored-arrow[data-side="right"] {
|
|
49
|
+
padding-left: calc(var(--cl-anchored-padding-x) + var(--cl-arrow-extent));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Base UI positions this as its arrow element; ClaraLight never paints it.
|
|
54
|
+
* It is a measurement probe: Floating UI writes the already-clamped arrow
|
|
55
|
+
* offset onto it as inline `left`/`top`, and `AnchoredSurface` reads its box
|
|
56
|
+
* to place the tail. Sized so that clamp accounts for the real tail width.
|
|
57
|
+
*/
|
|
58
|
+
.cl-anchor-probe {
|
|
59
|
+
width: var(--cl-arrow-width);
|
|
60
|
+
height: var(--cl-arrow-extent);
|
|
61
|
+
pointer-events: none;
|
|
62
|
+
visibility: hidden;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The entrance and exit: the surface grows out of its own tail, then returns
|
|
68
|
+
* to that same point.
|
|
69
|
+
*
|
|
70
|
+
* A dialog nudges into place — `--cl-enter-scale` is 0.95 for it, because a
|
|
71
|
+
* modal has no fixed point to grow from. An anchored overlay does: the tail's
|
|
72
|
+
* tip sits against the anchor and does not move while the surface opens, so
|
|
73
|
+
* the surface starts at **zero** and unfolds from that point. That is what
|
|
74
|
+
* makes the overlay read as pointing at its anchor instead of appearing near
|
|
75
|
+
* it, and it is why this is a second entrance rather than a value change to
|
|
76
|
+
* `.cl-enter-root`.
|
|
77
|
+
*
|
|
78
|
+
* No opacity leg on the way in, unlike `.cl-enter-root`: at scale 0 there is
|
|
79
|
+
* nothing on screen to fade, and the surface ClaraLight draws arrives opaque
|
|
80
|
+
* and grows. Opacity is what the reduced-motion fallback uses instead.
|
|
81
|
+
*
|
|
82
|
+
* The origin is not here. It is a measured length that only the component
|
|
83
|
+
* knows — where on the edge the tail actually landed — so `AnchoredSurface`
|
|
84
|
+
* writes it inline; see `src/lib/anchored.tsx`.
|
|
85
|
+
*/
|
|
86
|
+
.cl-anchored-root {
|
|
87
|
+
transition: scale var(--cl-duration-enter) var(--ease-cl-spring-overlay);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.cl-anchored-root:has(> [data-ending-style]) {
|
|
91
|
+
transition-duration: var(--cl-duration-exit);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.cl-anchored-root:has(> [data-starting-style]),
|
|
95
|
+
.cl-anchored-root:has(> [data-ending-style]) {
|
|
96
|
+
scale: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Reduced motion drops the growth — the one part that is movement — and keeps
|
|
101
|
+
* the overlay legible with a short fade at the reduced duration. Same split as
|
|
102
|
+
* the Flutter overlay, which reserves opacity for exactly this case.
|
|
103
|
+
*/
|
|
104
|
+
@media (prefers-reduced-motion: reduce) {
|
|
105
|
+
.cl-anchored-root {
|
|
106
|
+
transition: opacity var(--cl-duration-reduced) var(--ease-cl-out);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.cl-anchored-root:has(> [data-starting-style]),
|
|
110
|
+
.cl-anchored-root:has(> [data-ending-style]) {
|
|
111
|
+
scale: none;
|
|
112
|
+
opacity: 0;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
package/styles/base.css
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaraLight Design — component primitives.
|
|
3
|
+
*
|
|
4
|
+
* Tokens live in `theme.css`. This file holds the handful of behaviours that
|
|
5
|
+
* are part of the design language itself and are too structural to express as
|
|
6
|
+
* a Tailwind class string: the signature press interaction, the frosted
|
|
7
|
+
* floating surface, and the focus ring.
|
|
8
|
+
*
|
|
9
|
+
* Kept as CSS rather than utilities so a component's class list stays
|
|
10
|
+
* readable and the physics stay in one place.
|
|
11
|
+
*
|
|
12
|
+
* Everything here is used by every build. What only one or two components need
|
|
13
|
+
* ships beside them instead — `anchored.css` for Popover and Tooltip,
|
|
14
|
+
* `tooltip.css` for the shared-tooltip morph, `scroll-area.css` for the scroll
|
|
15
|
+
* area's edges and scrollbars — so a copy-in project that skips those
|
|
16
|
+
* components does not carry their CSS. The npm entry point pulls in all four.
|
|
17
|
+
*
|
|
18
|
+
* @import "tailwindcss";
|
|
19
|
+
* @import "@claralight-design/react/styles.css";
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/* Tailwind's built-in shadow compiler expands token values into literal
|
|
23
|
+
* --tw-shadow declarations. Keep these public utilities runtime-bound so a
|
|
24
|
+
* scoped --shadow-* override also reaches Squircle's SVG effect reader. */
|
|
25
|
+
@utility shadow-frost {
|
|
26
|
+
box-shadow: var(--shadow-frost);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@utility shadow-panel {
|
|
30
|
+
box-shadow: var(--shadow-panel);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@utility shadow-dialog {
|
|
34
|
+
box-shadow: var(--shadow-dialog);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@layer components {
|
|
38
|
+
/* -------------------------------------------------------------------------
|
|
39
|
+
* Press
|
|
40
|
+
* ---------------------------------------------------------------------- */
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The signature ClaraLight press:
|
|
44
|
+
* the control scales *up* while held, then springs back with overshoot on
|
|
45
|
+
* release.
|
|
46
|
+
*
|
|
47
|
+
* CSS picks transition values from the state being entered, which is exactly
|
|
48
|
+
* the behaviour we want and needs no JS:
|
|
49
|
+
*
|
|
50
|
+
* press -> element becomes :active -> 170ms easeOutQuart
|
|
51
|
+
* release -> element stops being :active -> 550ms spring
|
|
52
|
+
*
|
|
53
|
+
* The two legs therefore live in different rules. Overriding the duration
|
|
54
|
+
* through custom properties keeps a single `transition` shorthand valid
|
|
55
|
+
* instead of fighting `transition-property` ordering.
|
|
56
|
+
*
|
|
57
|
+
* Set `--cl-press-scale` using the size tokens so shorter controls move
|
|
58
|
+
* further in relative terms and every size feels equally responsive.
|
|
59
|
+
*/
|
|
60
|
+
.cl-press {
|
|
61
|
+
--cl-press-scale: var(--cl-press-scale-lg);
|
|
62
|
+
--cl-press-duration: var(--cl-duration-release);
|
|
63
|
+
--cl-press-ease: var(--ease-cl-spring-press);
|
|
64
|
+
|
|
65
|
+
transition:
|
|
66
|
+
color var(--cl-duration-press) var(--ease-cl-out),
|
|
67
|
+
background-color var(--cl-duration-press) var(--ease-cl-out),
|
|
68
|
+
border-color var(--cl-duration-press) var(--ease-cl-out),
|
|
69
|
+
box-shadow var(--cl-duration-press) var(--ease-cl-out),
|
|
70
|
+
opacity var(--cl-duration-press) var(--ease-cl-out),
|
|
71
|
+
outline-color var(--cl-duration-fast) var(--ease-cl-out),
|
|
72
|
+
transform var(--cl-press-duration) var(--cl-press-ease);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Disabled shaped children can still leave their wrapper :active when
|
|
76
|
+
* pointer-events are suppressed. Guard both wrapped and standalone controls. */
|
|
77
|
+
.cl-press:active:not(:where(:disabled, [disabled], [data-disabled], [aria-disabled="true"])):not(:has(> :where(:disabled, [disabled], [data-disabled], [aria-disabled="true"]))) {
|
|
78
|
+
--cl-press-duration: var(--cl-duration-press);
|
|
79
|
+
--cl-press-ease: var(--ease-cl-press-in);
|
|
80
|
+
transform: scale(var(--cl-press-scale));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* ClaraLight drops the scale entirely under reduced motion rather than
|
|
85
|
+
* shrinking it, and keeps the opacity/surface feedback.
|
|
86
|
+
*/
|
|
87
|
+
@media (prefers-reduced-motion: reduce) {
|
|
88
|
+
.cl-press:active {
|
|
89
|
+
transform: none;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* -------------------------------------------------------------------------
|
|
94
|
+
* Frosted floating surface
|
|
95
|
+
* ---------------------------------------------------------------------- */
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Menus, popovers, dialogs and sheets. The blur, outline and shadow all
|
|
99
|
+
* consume theme tokens; Squircle handles their smooth-corner geometry.
|
|
100
|
+
*/
|
|
101
|
+
.cl-frost {
|
|
102
|
+
background-color: var(--cl-frost);
|
|
103
|
+
-webkit-backdrop-filter: blur(var(--blur-frost));
|
|
104
|
+
backdrop-filter: blur(var(--blur-frost));
|
|
105
|
+
border: 1px solid var(--cl-outline-strong);
|
|
106
|
+
box-shadow: var(--shadow-frost);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Same layer, without the shadow — for surfaces stacked inside a panel. */
|
|
110
|
+
.cl-panel {
|
|
111
|
+
background-color: var(--cl-panel);
|
|
112
|
+
border: 1px solid var(--cl-outline);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* -------------------------------------------------------------------------
|
|
116
|
+
* Smooth-corner root
|
|
117
|
+
* ---------------------------------------------------------------------- */
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The wrapper `Squircle` renders around a clipped element.
|
|
121
|
+
*
|
|
122
|
+
* It exists because Lisse re-renders borders and shadows as SVG overlays,
|
|
123
|
+
* which have to be appended to a positioned element that tightly wraps the
|
|
124
|
+
* shape. Two consequences worth knowing:
|
|
125
|
+
*
|
|
126
|
+
* 1. **Transforms belong here, not on the shape.** The overlays are siblings
|
|
127
|
+
* of the clipped element, so anything that moves the shape without moving
|
|
128
|
+
* the wrapper tears the 1px border away from the edge. The press scale and
|
|
129
|
+
* the dialog entrance therefore live on this element, which also reads
|
|
130
|
+
* better: fill, border and shadow scale as one object.
|
|
131
|
+
*
|
|
132
|
+
* 2. **The focus ring lives here too**, because `clip-path` crops `outline` to
|
|
133
|
+
* nothing on the shape itself — measured, a 2px ring at 2px offset goes
|
|
134
|
+
* from 436 lit pixels to 0. `:has()` lets the browser's own
|
|
135
|
+
* `:focus-visible` heuristic drive it, so there is no JS focus state and
|
|
136
|
+
* clicks still do not flash a ring.
|
|
137
|
+
*
|
|
138
|
+
* The radius is carried in a custom property so the ring clears the shape at
|
|
139
|
+
* every radius token; `Squircle` sets it.
|
|
140
|
+
*/
|
|
141
|
+
.cl-squircle-root {
|
|
142
|
+
border-radius: calc(var(--cl-squircle-radius, 0px) + var(--cl-focus-offset));
|
|
143
|
+
outline: var(--cl-focus-width) solid transparent;
|
|
144
|
+
outline-offset: var(--cl-focus-offset);
|
|
145
|
+
/*
|
|
146
|
+
* Deliberately no `transition` here. `.cl-press` and `.cl-squircle-root`
|
|
147
|
+
* very often sit on the same element, and a second `transition` declaration
|
|
148
|
+
* wins by source order — which silently replaced the press spring with an
|
|
149
|
+
* outline fade. `.cl-press` owns the transition and lists `outline-color`
|
|
150
|
+
* alongside its own properties.
|
|
151
|
+
*/
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.cl-squircle-root:has(> [data-cl-squircle]:focus-visible) {
|
|
155
|
+
outline-color: var(--cl-accent);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Text fields and selects take focus on click, and the user needs to see which
|
|
160
|
+
* field got it, so they cannot use `:focus-visible`.
|
|
161
|
+
*/
|
|
162
|
+
.cl-squircle-field:has(> [data-cl-squircle]:focus) {
|
|
163
|
+
outline-color: var(--cl-accent);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* -------------------------------------------------------------------------
|
|
167
|
+
* Floating-layer entrance
|
|
168
|
+
* ---------------------------------------------------------------------- */
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Base UI observes animations on the popup element when deciding when to
|
|
172
|
+
* unmount. ClaraLight deliberately animates the wrapper so the fill, SVG
|
|
173
|
+
* outline and shadow stay together, which puts the visible transition one
|
|
174
|
+
* level above that observer.
|
|
175
|
+
*
|
|
176
|
+
* This non-visual animation is the bridge between the two contracts. It runs
|
|
177
|
+
* only while Base UI marks the popup as ending and animates a custom property,
|
|
178
|
+
* never opacity, transform or layout. The wrapper remains the only thing that
|
|
179
|
+
* moves; Base UI now has a real animation on the popup whose completion it
|
|
180
|
+
* can await before `forceUnmount`.
|
|
181
|
+
*/
|
|
182
|
+
@keyframes cl-overlay-exit-sentinel {
|
|
183
|
+
from {
|
|
184
|
+
--cl-overlay-exit-sentinel: 0;
|
|
185
|
+
}
|
|
186
|
+
to {
|
|
187
|
+
--cl-overlay-exit-sentinel: 1;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.cl-exit-sentinel[data-ending-style] {
|
|
192
|
+
animation: cl-overlay-exit-sentinel var(--cl-duration-exit) linear both;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Menus, popovers, dialogs and sheets.
|
|
197
|
+
*
|
|
198
|
+
* The transition runs on the **wrapper**, not on the popup, because the popup
|
|
199
|
+
* is the clipped element and the SVG border and shadow overlays are its
|
|
200
|
+
* siblings on the wrapper. Scaling the popup alone would leave the 1px border
|
|
201
|
+
* and the shadow at their final size — 5% of a 512px dialog is 25px, which is
|
|
202
|
+
* very visible over a 250ms entrance.
|
|
203
|
+
*
|
|
204
|
+
* Base UI drives this from the popup's `data-starting-style` /
|
|
205
|
+
* `data-ending-style`; `:has()` lifts that state onto the wrapper so the whole
|
|
206
|
+
* assembly — fill, border and shadow — moves as one object.
|
|
207
|
+
*
|
|
208
|
+
* Two durations, because a scrim that takes as long as the spring makes the
|
|
209
|
+
* page behind it feel stuck. The ClaraLight springs are `Curve` equivalents,
|
|
210
|
+
* so `linear()` encodes the shape and the component owns the timing.
|
|
211
|
+
*/
|
|
212
|
+
.cl-enter-root {
|
|
213
|
+
transition:
|
|
214
|
+
opacity var(--cl-duration-surface) var(--ease-cl-out),
|
|
215
|
+
scale var(--cl-duration-enter) var(--ease-cl-spring-overlay);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.cl-enter-root:has(> [data-starting-style]),
|
|
219
|
+
.cl-enter-root:has(> [data-ending-style]) {
|
|
220
|
+
opacity: 0;
|
|
221
|
+
scale: var(--cl-enter-scale);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.cl-enter-root:has(> [data-ending-style]) {
|
|
225
|
+
transition:
|
|
226
|
+
opacity var(--cl-duration-surface) var(--ease-cl-out),
|
|
227
|
+
scale var(--cl-duration-exit) var(--ease-cl-spring-overlay);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A layer whose geometry belongs to `useSurfaceMorph`.
|
|
232
|
+
*
|
|
233
|
+
* The 5% nudge belongs to a layer that has no fixed point to grow from. A layer
|
|
234
|
+
* that has one is carried by a projective quad, and the nudge would be a second,
|
|
235
|
+
* coarser claim on the same geometry — measured through it, the morph's own
|
|
236
|
+
* resting box lands 5% small, and therefore that far off the trigger.
|
|
237
|
+
*
|
|
238
|
+
* Opacity moves here for the same reason: the morph reveals the content from
|
|
239
|
+
* the same progress that carries the geometry, so the two legs cannot disagree.
|
|
240
|
+
* Left to a transition, the `ease-out` fade would empty the layer in the first
|
|
241
|
+
* half of a dismissal and leave the collapse back to the trigger unwatchable.
|
|
242
|
+
*
|
|
243
|
+
* Declared in the markup rather than set from the effect that drives the morph,
|
|
244
|
+
* because *changing* a computed `scale` is itself what starts the transition
|
|
245
|
+
* above: a component that reported itself as morphed one frame late would
|
|
246
|
+
* animate the nudge it was replacing. Present from the first style resolution,
|
|
247
|
+
* neither value changes and neither transition starts. This block has to stay
|
|
248
|
+
* after the two above it — it replaces their `transition` at equal specificity.
|
|
249
|
+
*
|
|
250
|
+
* Scoped to `no-preference` so reduced motion keeps the fade it is asking for,
|
|
251
|
+
* which is the CSS leg with no geometry underneath it.
|
|
252
|
+
*/
|
|
253
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
254
|
+
.cl-enter-root.cl-morph,
|
|
255
|
+
.cl-enter-root.cl-morph:has(> [data-starting-style]),
|
|
256
|
+
.cl-enter-root.cl-morph:has(> [data-ending-style]) {
|
|
257
|
+
transition-property: scale;
|
|
258
|
+
transition-duration: var(--cl-duration-enter);
|
|
259
|
+
transition-timing-function: var(--ease-cl-spring-overlay);
|
|
260
|
+
opacity: 1;
|
|
261
|
+
scale: none;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/* The dialog/select wrapper also fades for `--cl-duration-surface`; its
|
|
266
|
+
* sentinel has to cover that longer leg, not just the 110ms scale return. */
|
|
267
|
+
.cl-enter-root > .cl-exit-sentinel[data-ending-style] {
|
|
268
|
+
animation-duration: var(--cl-duration-surface);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
@media (prefers-reduced-motion: reduce) {
|
|
272
|
+
.cl-enter-root {
|
|
273
|
+
transition: opacity var(--cl-duration-reduced) var(--ease-cl-out);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.cl-enter-root:has(> [data-starting-style]),
|
|
277
|
+
.cl-enter-root:has(> [data-ending-style]) {
|
|
278
|
+
scale: none;
|
|
279
|
+
opacity: 0;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* -------------------------------------------------------------------------
|
|
284
|
+
* Focus
|
|
285
|
+
* ---------------------------------------------------------------------- */
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* ClaraLight marks focus with the accent color. Uses `outline` rather than a
|
|
289
|
+
* ring so the indicator never shifts layout and survives `overflow: hidden`
|
|
290
|
+
* ancestors, and stays visible on both schemes because accent is the same
|
|
291
|
+
* blue in each.
|
|
292
|
+
*
|
|
293
|
+
* Two variants, because the trigger differs:
|
|
294
|
+
* cl-focus buttons and other pointer-activated controls, which
|
|
295
|
+
* should not flash an indicator on mouse click
|
|
296
|
+
* cl-focus-field text fields and selects, where focus follows the click
|
|
297
|
+
* and the user needs to see which field took it
|
|
298
|
+
*/
|
|
299
|
+
.cl-focus,
|
|
300
|
+
.cl-focus-field {
|
|
301
|
+
outline: var(--cl-focus-width) solid transparent;
|
|
302
|
+
outline-offset: var(--cl-focus-offset);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.cl-focus:focus-visible,
|
|
306
|
+
.cl-focus-field:focus {
|
|
307
|
+
outline-color: var(--cl-accent);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Fonts
|
|
2
|
+
|
|
3
|
+
`theme.css` names four preferred families. This package currently ships
|
|
4
|
+
**no font files** — the token layer supplies platform fallbacks. Rendering and
|
|
5
|
+
metrics depend on the fonts installed by the consumer; they are not guaranteed
|
|
6
|
+
to match the preferred faces.
|
|
7
|
+
|
|
8
|
+
## What the design language expects
|
|
9
|
+
|
|
10
|
+
| Family | Role | Token | Source / license |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| MiSans | UI text, Chinese + Latin (variable) | `--font-sans` | [hyperos.mi.com/font](https://hyperos.mi.com/font) — MiSans 字体知识产权许可协议, free commercial use & redistribution |
|
|
13
|
+
| Sarasa Mono SC | Numeric values and units | `--font-mono` | [be5invis/Sarasa-Gothic](https://github.com/be5invis/Sarasa-Gothic) v1.0.40 — SIL OFL 1.1 |
|
|
14
|
+
| ChillDINGothic | Large display headings | `--font-display` | [Warren2060/ChillDIN-ChillDINGothic](https://github.com/Warren2060/ChillDIN-ChillDINGothic) v1.300 — SIL OFL 1.1 |
|
|
15
|
+
| Clara Serif Pro | Optional serif, not in the default ramp | `--font-serif` | Obtain the font and verify its license before redistribution |
|
|
16
|
+
|
|
17
|
+
Check the license accompanying each actual font file before shipping it.
|
|
18
|
+
|
|
19
|
+
## To ship them
|
|
20
|
+
|
|
21
|
+
1. Obtain licensed font files from their upstream sources and convert to
|
|
22
|
+
woff2 for web delivery. Keep a variable version when the required weight
|
|
23
|
+
range and download size justify it.
|
|
24
|
+
|
|
25
|
+
2. Subset for the languages the application supports. Preserve required CJK
|
|
26
|
+
coverage; use separate `@font-face` declarations with `unicode-range`
|
|
27
|
+
splits where useful. Do not assume an upstream file is already subset.
|
|
28
|
+
|
|
29
|
+
3. Add the files in this directory (or a CDN), create the corresponding
|
|
30
|
+
`@font-face` stylesheet, and import it from `index.css`.
|
|
31
|
+
|
|
32
|
+
## MiSans' wght axis is not standard
|
|
33
|
+
|
|
34
|
+
Verify the named instances in the exact MiSans VF file you ship. The following
|
|
35
|
+
mapping has been used for MiSans files with non-standard weight positions;
|
|
36
|
+
it is not a guarantee for every release:
|
|
37
|
+
|
|
38
|
+
| CSS `font-weight` | MiSans `wght` axis | Named instance |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| 400 | **330** | Regular |
|
|
41
|
+
| 500 | **380** | Medium |
|
|
42
|
+
| 600 | **450** | Demibold |
|
|
43
|
+
| 700 | **520** | Semibold |
|
|
44
|
+
|
|
45
|
+
For a file with this mapping, axis position 600 is above Semibold, not
|
|
46
|
+
Demibold. If explicit axis settings are needed, define the mapping as tokens
|
|
47
|
+
in `theme.css`, then consume them rather than repeating axis values:
|
|
48
|
+
|
|
49
|
+
```css
|
|
50
|
+
/* Instead of relying on font-weight alone. */
|
|
51
|
+
.cl-weight-title {
|
|
52
|
+
font-variation-settings: "wght" var(--cl-font-axis-title);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Because `font-variation-settings` is a single property, the token ramp's
|
|
57
|
+
`--text-*--font-weight` values are the *intent* (700/600/500/400). They land
|
|
58
|
+
according to the loaded font's metadata; on fallback fonts the browser uses
|
|
59
|
+
the available weights. The example axis token above is not shipped yet.
|
|
60
|
+
|
|
61
|
+
Rather than hand-maintaining a utility per step, add a
|
|
62
|
+
`@utility cl-wght-{400,500,600,700}` set in `theme.css` that maps to
|
|
63
|
+
330/380/450/520 during the font drop-in.
|
package/styles/index.css
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClaraLight Design — full stylesheet for npm consumers.
|
|
3
|
+
*
|
|
4
|
+
* Registry (copy-in) consumers import these files directly instead, and take
|
|
5
|
+
* only the ones their components need, so the files stay the single source of
|
|
6
|
+
* truth for both paths. The npm package cannot make that choice per consumer,
|
|
7
|
+
* so it imports all of them.
|
|
8
|
+
*
|
|
9
|
+
* @import "tailwindcss";
|
|
10
|
+
* @import "@claralight-design/react/styles.css";
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
@import "./theme.css";
|
|
14
|
+
@import "./base.css";
|
|
15
|
+
@import "./anchored.css";
|
|
16
|
+
@import "./tooltip.css";
|
|
17
|
+
@import "./scroll-area.css";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tailwind v4 does not scan `node_modules`, so a consumer importing the built
|
|
21
|
+
* package would otherwise get components with every utility class stripped.
|
|
22
|
+
* `@source` is resolved relative to this file, which puts it at the package
|
|
23
|
+
* root next to `dist/`.
|
|
24
|
+
*
|
|
25
|
+
* Registry consumers do not need this: their copied components live inside the
|
|
26
|
+
* project and are scanned automatically.
|
|
27
|
+
*/
|
|
28
|
+
@source "../dist";
|