@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.
Files changed (45) hide show
  1. package/dist/index.d.ts +12 -0
  2. package/dist/index.js +12 -0
  3. package/dist/lib/anchored.d.ts +152 -0
  4. package/dist/lib/anchored.js +821 -0
  5. package/dist/lib/anchored.js.map +1 -0
  6. package/dist/lib/morph.js +484 -0
  7. package/dist/lib/morph.js.map +1 -0
  8. package/dist/lib/squircle.d.ts +95 -0
  9. package/dist/lib/squircle.js +220 -0
  10. package/dist/lib/squircle.js.map +1 -0
  11. package/dist/lib/utils.d.ts +30 -0
  12. package/dist/lib/utils.js +76 -0
  13. package/dist/lib/utils.js.map +1 -0
  14. package/dist/ui/button.d.ts +40 -0
  15. package/dist/ui/button.js +104 -0
  16. package/dist/ui/button.js.map +1 -0
  17. package/dist/ui/card.d.ts +54 -0
  18. package/dist/ui/card.js +82 -0
  19. package/dist/ui/card.js.map +1 -0
  20. package/dist/ui/dialog.d.ts +82 -0
  21. package/dist/ui/dialog.js +129 -0
  22. package/dist/ui/dialog.js.map +1 -0
  23. package/dist/ui/input.d.ts +34 -0
  24. package/dist/ui/input.js +60 -0
  25. package/dist/ui/input.js.map +1 -0
  26. package/dist/ui/popover.d.ts +67 -0
  27. package/dist/ui/popover.js +93 -0
  28. package/dist/ui/popover.js.map +1 -0
  29. package/dist/ui/scroll-area.d.ts +110 -0
  30. package/dist/ui/scroll-area.js +125 -0
  31. package/dist/ui/scroll-area.js.map +1 -0
  32. package/dist/ui/select.d.ts +113 -0
  33. package/dist/ui/select.js +213 -0
  34. package/dist/ui/select.js.map +1 -0
  35. package/dist/ui/tooltip.d.ts +108 -0
  36. package/dist/ui/tooltip.js +142 -0
  37. package/dist/ui/tooltip.js.map +1 -0
  38. package/package.json +72 -0
  39. package/styles/anchored.css +115 -0
  40. package/styles/base.css +309 -0
  41. package/styles/fonts/README.md +63 -0
  42. package/styles/index.css +28 -0
  43. package/styles/scroll-area.css +299 -0
  44. package/styles/theme.css +540 -0
  45. package/styles/tooltip.css +156 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * ClaraLight Design — the scroll area's edge effects and overlay scrollbars.
3
+ *
4
+ * Base UI's Scroll Area owns the viewport, the thumb geometry and the overflow
5
+ * signals; everything here is the ClaraLight treatment painted on top. Two of
6
+ * those signals do the real work, and they are deliberately used for different
7
+ * halves of the effect:
8
+ *
9
+ * `--scroll-area-overflow-*` the distance in px from each logical edge,
10
+ * rewritten on the viewport every scroll frame.
11
+ * `data-overflow-*` the same four edges as booleans, which change
12
+ * only when an edge is actually crossed.
13
+ *
14
+ * The alpha mask reads the continuous distance, so the band grows out of the
15
+ * first `--cl-scroll-edge` pixels of scroll and there is no animation to run —
16
+ * no transition, no timer, no re-render, and nothing to get out of step with
17
+ * the scroll position. The blur reads the boolean and cross-fades instead,
18
+ * because a `backdrop-filter` layer is worth compositing only while its edge is
19
+ * live, and opacity is the one channel that costs nothing to animate.
20
+ *
21
+ * Requires `theme.css` for the tokens.
22
+ *
23
+ * @import "tailwindcss";
24
+ * @import "@claralight-design/react/styles.css";
25
+ */
26
+
27
+ @layer components {
28
+ /* -------------------------------------------------------------------------
29
+ * The alpha mask
30
+ * ---------------------------------------------------------------------- */
31
+
32
+ /**
33
+ * Four gradients composited together, one per logical edge. A band of zero
34
+ * collapses its gradient's two stops onto the same position, which resolves
35
+ * to fully opaque — so a disabled side, or an axis that is not scrollable,
36
+ * costs nothing and needs no separate rule.
37
+ *
38
+ * `min()` is what clamps the band: Base UI reports the true distance from
39
+ * the edge, which keeps growing as you scroll, and the band stops widening
40
+ * once it reaches the token.
41
+ *
42
+ * `black` in these gradients is a stencil, not a colour. A mask reads only
43
+ * alpha, so the hue never reaches the screen and there is no token for it
44
+ * to come from — every opaque value would render identically.
45
+ */
46
+ .cl-scroll-viewport {
47
+ --cl-scroll-band-block-start: min(
48
+ var(--scroll-area-overflow-y-start, 0px),
49
+ var(--cl-scroll-edge-block-start)
50
+ );
51
+ --cl-scroll-band-block-end: min(
52
+ var(--scroll-area-overflow-y-end, 0px),
53
+ var(--cl-scroll-edge-block-end)
54
+ );
55
+ --cl-scroll-band-inline-start: min(
56
+ var(--scroll-area-overflow-x-start, 0px),
57
+ var(--cl-scroll-edge-inline-start)
58
+ );
59
+ --cl-scroll-band-inline-end: min(
60
+ var(--scroll-area-overflow-x-end, 0px),
61
+ var(--cl-scroll-edge-inline-end)
62
+ );
63
+
64
+ /* Gradients take a physical direction, so the inline pair is the only
65
+ * thing in this file that has to know about writing direction. */
66
+ --cl-scroll-to-inline-start: right;
67
+ --cl-scroll-to-inline-end: left;
68
+ }
69
+
70
+ .cl-scroll-viewport:dir(rtl) {
71
+ --cl-scroll-to-inline-start: left;
72
+ --cl-scroll-to-inline-end: right;
73
+ }
74
+
75
+ /* Applied only once an axis actually overflows, so a scroll area that fits
76
+ * its content is never promoted to a masked layer. */
77
+ .cl-scroll-viewport:is([data-has-overflow-x], [data-has-overflow-y]) {
78
+ mask-image: linear-gradient(
79
+ to bottom,
80
+ transparent,
81
+ black var(--cl-scroll-band-block-start)
82
+ ),
83
+ linear-gradient(to top, transparent, black var(--cl-scroll-band-block-end)),
84
+ linear-gradient(
85
+ to var(--cl-scroll-to-inline-start),
86
+ transparent,
87
+ black var(--cl-scroll-band-inline-start)
88
+ ),
89
+ linear-gradient(
90
+ to var(--cl-scroll-to-inline-end),
91
+ transparent,
92
+ black var(--cl-scroll-band-inline-end)
93
+ );
94
+ mask-composite: intersect;
95
+ }
96
+
97
+ /* -------------------------------------------------------------------------
98
+ * The progressive blur
99
+ * ---------------------------------------------------------------------- */
100
+
101
+ /**
102
+ * One element per edge, each holding three stacked `backdrop-filter` layers
103
+ * whose sigma doubles and whose masks nest, so the blur ramps from full
104
+ * strength at the edge to nothing at the far side of the band. This is the
105
+ * web's stand-in for the single spatially-varying sigma a shader would use.
106
+ *
107
+ * These have to be **descendants of the clipped element**. `clip-path`
108
+ * establishes a backdrop root, so an edge placed as a sibling of the clipped
109
+ * surface cannot see the scrolled content behind it and renders no blur at
110
+ * all — silently, with no error and no warning.
111
+ *
112
+ * That same backdrop root is why **the surface's fill has to be opaque**.
113
+ * Inside it the backdrop is only the surface's own fill plus the scrolled
114
+ * content; everything painted behind the scroll area is excluded. Blur a
115
+ * backdrop that is mostly transparent and the engine lifts what little
116
+ * colour is there instead of preserving it — measured on a 10%-white fill,
117
+ * the band's mean brightness goes from 49 to 87 out of 255, once per
118
+ * overlapping layer, which reads as a white halo around the frame. An
119
+ * opaque fill holds the mean to within 1.3.
120
+ *
121
+ * There is no way to have all three of a squircle-clipped surface, a
122
+ * translucent fill and this blur: clipping the layers themselves instead
123
+ * fixes the halo but then nothing shapes the content, and moving the clip
124
+ * anywhere else stops the blur from seeing the content at all. So a
125
+ * translucent surface takes `edge="mask"`, which has none of this coupling.
126
+ */
127
+ .cl-scroll-edge {
128
+ position: absolute;
129
+ pointer-events: none;
130
+ opacity: 0;
131
+ transition: opacity var(--cl-duration-surface) var(--ease-cl-in);
132
+ }
133
+
134
+ .cl-scroll-edge[data-edge="block-start"] {
135
+ inset-inline: 0;
136
+ inset-block-start: 0;
137
+ block-size: var(--cl-scroll-edge-block-start);
138
+ --cl-scroll-edge-to: bottom;
139
+ }
140
+
141
+ .cl-scroll-edge[data-edge="block-end"] {
142
+ inset-inline: 0;
143
+ inset-block-end: 0;
144
+ block-size: var(--cl-scroll-edge-block-end);
145
+ --cl-scroll-edge-to: top;
146
+ }
147
+
148
+ .cl-scroll-edge[data-edge="inline-start"] {
149
+ inset-block: 0;
150
+ inset-inline-start: 0;
151
+ inline-size: var(--cl-scroll-edge-inline-start);
152
+ --cl-scroll-edge-to: right;
153
+ }
154
+
155
+ .cl-scroll-edge[data-edge="inline-end"] {
156
+ inset-block: 0;
157
+ inset-inline-end: 0;
158
+ inline-size: var(--cl-scroll-edge-inline-end);
159
+ --cl-scroll-edge-to: left;
160
+ }
161
+
162
+ .cl-scroll-edge[data-edge="inline-start"]:dir(rtl) {
163
+ --cl-scroll-edge-to: left;
164
+ }
165
+
166
+ .cl-scroll-edge[data-edge="inline-end"]:dir(rtl) {
167
+ --cl-scroll-edge-to: right;
168
+ }
169
+
170
+ /* Arriving and leaving get the two halves of the ClaraLight motion pair, the
171
+ * same way the Flutter edge controllers animate in and out. */
172
+ .cl-scroll-area[data-overflow-y-start] > .cl-scroll-edge[data-edge="block-start"],
173
+ .cl-scroll-area[data-overflow-y-end] > .cl-scroll-edge[data-edge="block-end"],
174
+ .cl-scroll-area[data-overflow-x-start] > .cl-scroll-edge[data-edge="inline-start"],
175
+ .cl-scroll-area[data-overflow-x-end] > .cl-scroll-edge[data-edge="inline-end"] {
176
+ opacity: 1;
177
+ transition-timing-function: var(--ease-cl-out);
178
+ }
179
+
180
+ .cl-scroll-edge-layer {
181
+ position: absolute;
182
+ inset: 0;
183
+ }
184
+
185
+ /* Weakest and widest first, strongest and narrowest last, so the layers
186
+ * paint in falloff order. Per-edge sigma is reachable without another
187
+ * token: scope `--cl-scroll-blur` to one `[data-edge]` and only that side
188
+ * changes. */
189
+ .cl-scroll-edge-layer:nth-child(1) {
190
+ backdrop-filter: blur(calc(var(--cl-scroll-blur) * var(--cl-scroll-blur-far)));
191
+ mask-image: linear-gradient(to var(--cl-scroll-edge-to), black, transparent);
192
+ }
193
+
194
+ .cl-scroll-edge-layer:nth-child(2) {
195
+ backdrop-filter: blur(calc(var(--cl-scroll-blur) * var(--cl-scroll-blur-mid)));
196
+ mask-image: linear-gradient(
197
+ to var(--cl-scroll-edge-to),
198
+ black,
199
+ transparent var(--cl-scroll-reach-mid)
200
+ );
201
+ }
202
+
203
+ .cl-scroll-edge-layer:nth-child(3) {
204
+ backdrop-filter: blur(var(--cl-scroll-blur));
205
+ mask-image: linear-gradient(
206
+ to var(--cl-scroll-edge-to),
207
+ black,
208
+ transparent var(--cl-scroll-reach-near)
209
+ );
210
+ }
211
+
212
+ /**
213
+ * Dropping to the mask alone is the honest degradation: the edge keeps
214
+ * saying "there is more behind this" at a fraction of the cost, which is
215
+ * also why `edge="mask"` exists as a deliberate choice.
216
+ */
217
+ @media (prefers-reduced-transparency: reduce) {
218
+ .cl-scroll-edge-layer {
219
+ backdrop-filter: none;
220
+ }
221
+ }
222
+
223
+ /* -------------------------------------------------------------------------
224
+ * Overlay scrollbars
225
+ * ---------------------------------------------------------------------- */
226
+
227
+ /**
228
+ * The visibility policy is two transitions rather than a timer: the hidden
229
+ * state carries the fade-out delay, and the revealed state overrides the
230
+ * delay away. Hovering the area or scrolling either axis reveals both bars,
231
+ * and letting go schedules the fade — which is exactly what the Flutter
232
+ * overlay's hide timer does, with nothing to cancel.
233
+ */
234
+ /* No z-index anywhere here: the viewport is static and both the edges and
235
+ * the bars are absolutely positioned, so paint order already runs
236
+ * viewport, edges, bars — exactly the order the DOM is written in. */
237
+ .cl-scrollbar {
238
+ opacity: 0;
239
+ pointer-events: none;
240
+ transition: opacity var(--cl-scrollbar-fade-duration) var(--ease-cl-in)
241
+ var(--cl-scrollbar-fade-delay);
242
+ }
243
+
244
+ .cl-scrollbar:is([data-hovering], [data-scrolling]) {
245
+ opacity: 1;
246
+ pointer-events: auto;
247
+ transition-duration: var(--cl-duration-surface);
248
+ transition-timing-function: var(--ease-cl-out);
249
+ transition-delay: 0s;
250
+ }
251
+
252
+ /* Base UI unmounts a scrollbar whose axis cannot scroll, so `always` still
253
+ * means "whenever this axis has somewhere to go". */
254
+ .cl-scrollbar[data-cl-visibility="always"] {
255
+ opacity: 1;
256
+ pointer-events: auto;
257
+ transition: none;
258
+ }
259
+
260
+ /**
261
+ * The inset is a margin, not an offset: Base UI writes the scrollbar's
262
+ * `inset-*` inline, and the thumb maths already reads margins back out.
263
+ *
264
+ * Each track is a flex line along its own axis, which is what gives the
265
+ * thumb its thickness. Base UI sizes the thumb along the scrolling axis
266
+ * only — the other axis is left to the track, and `stretch` is the flex
267
+ * default, so the cross size follows `--cl-scrollbar-thickness` without
268
+ * either element restating it.
269
+ */
270
+ .cl-scrollbar[data-orientation="vertical"] {
271
+ display: flex;
272
+ flex-direction: column;
273
+ inline-size: var(--cl-scrollbar-thickness);
274
+ margin-block: var(--cl-scrollbar-inset);
275
+ margin-inline-end: var(--cl-scrollbar-inset);
276
+ }
277
+
278
+ .cl-scrollbar[data-orientation="horizontal"] {
279
+ display: flex;
280
+ block-size: var(--cl-scrollbar-thickness);
281
+ margin-inline: var(--cl-scrollbar-inset);
282
+ margin-block-end: var(--cl-scrollbar-inset);
283
+ }
284
+
285
+ /* A 4px bar makes the design's 17px corner a pill, so there is no squircle
286
+ * to draw here — the two shapes coincide below twice the thickness. */
287
+ .cl-scroll-thumb {
288
+ border-radius: var(--radius-capsule);
289
+ background-color: var(--cl-selection);
290
+ }
291
+
292
+ .cl-scroll-thumb[data-orientation="vertical"] {
293
+ margin-block: var(--cl-scrollbar-margin);
294
+ }
295
+
296
+ .cl-scroll-thumb[data-orientation="horizontal"] {
297
+ margin-inline: var(--cl-scrollbar-margin);
298
+ }
299
+ }