@arbol-ai/ui 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/theme.css ADDED
@@ -0,0 +1,533 @@
1
+ /**
2
+ * @arbol-ai/ui theme: the design tokens, base styles and Tailwind CSS v4 setup every component reads.
3
+ *
4
+ * Import it once, right after Tailwind, in the application's entry stylesheet:
5
+ *
6
+ * @import 'tailwindcss';
7
+ * @import '@arbol-ai/ui/theme.css';
8
+ *
9
+ * What it brings:
10
+ * - `@source`: Tailwind scans the library's own files, so the classes its components use are generated.
11
+ * - tw-animate-css's enter and exit animations, which the components play on React Aria's
12
+ * `data-entering` / `data-exiting` states.
13
+ * - Tokens as CSS variables on `:root`, remapped under `.dark` (dark mode is the `dark` class on an
14
+ * ancestor, usually `<html>`) and under `[data-theme='mono']` (the grayscale appearance; `brand`, the
15
+ * surgical teal, is the default). Tailwind exposes them as utilities through `@theme inline`:
16
+ * `bg-primary`, `text-muted-foreground`, `border-border`, `rounded-field`, `shadow-md`, `bg-gray-a2`…
17
+ *
18
+ * Fonts: the families are named, never shipped. Each stack tries the application's own faces first,
19
+ * then the brand's open faces (Schibsted Grotesk and Martian Mono, SIL Open Font License, which an app
20
+ * can load from Google Fonts or Fontsource), then the system's. Override `--ui-font-sans`,
21
+ * `--ui-font-heading` or `--ui-font-mono` to use other faces.
22
+ */
23
+ @import 'tw-animate-css';
24
+ @source './';
25
+
26
+ @custom-variant dark (&:is(.dark *));
27
+
28
+ :root {
29
+ --ui-font-sans: 'Arbol Sans', 'Schibsted Grotesk', ui-sans-serif, system-ui, sans-serif;
30
+ --ui-font-heading:
31
+ 'Arbol Sans SemiMono', 'Arbol Sans', 'Schibsted Grotesk', ui-sans-serif, system-ui, sans-serif;
32
+ --ui-font-mono: 'Arbol Sans Mono', 'Martian Mono', ui-monospace, monospace;
33
+ }
34
+
35
+ @theme inline {
36
+ /* Fonts: the families are named, never shipped (see the header). */
37
+ --font-sans: var(--ui-font-sans);
38
+ --font-heading: var(--ui-font-heading);
39
+ --font-display: var(--ui-font-heading);
40
+ --font-mono: var(--ui-font-mono);
41
+ --font-serif: ui-serif, serif;
42
+
43
+ /* Elevation — the whole `shadow-*` scale routes through the tokens below
44
+ (:root / .dark), so depth is theme-aware like every color. Recipe: layered
45
+ single-digit-opacity stacks; from `md` up, a 1px hairline ring drawn from
46
+ the gray alpha ramp, which the dark theme re-points to low-alpha white —
47
+ the edge inverts with the theme for free. Rings stay OFF `xs`/`sm` on
48
+ purpose: those sit on filled controls (the primary button), where a gray
49
+ ring reads as a dirty edge. */
50
+ --shadow-2xs: var(--elevation-2xs);
51
+ --shadow-xs: var(--elevation-xs);
52
+ --shadow-sm: var(--elevation-sm);
53
+ --shadow-md: var(--elevation-md);
54
+ --shadow-lg: var(--elevation-lg);
55
+ --shadow-xl: var(--elevation-xl);
56
+ --shadow-2xl: var(--elevation-2xl);
57
+
58
+ /* Colors */
59
+ --color-background: var(--background);
60
+ --color-foreground: var(--foreground);
61
+ --color-card: var(--card);
62
+ --color-card-foreground: var(--card-foreground);
63
+ --color-popover: var(--popover);
64
+ --color-popover-foreground: var(--popover-foreground);
65
+ --color-primary: var(--primary);
66
+ --color-primary-foreground: var(--primary-foreground);
67
+ --color-secondary: var(--secondary);
68
+ --color-secondary-foreground: var(--secondary-foreground);
69
+ --color-muted: var(--muted);
70
+ --color-muted-foreground: var(--muted-foreground);
71
+ --color-accent: var(--accent);
72
+ --color-accent-foreground: var(--accent-foreground);
73
+ --color-destructive: var(--destructive);
74
+ --color-success: var(--success);
75
+ --color-warning: var(--warning);
76
+ --color-border: var(--border);
77
+ --color-input: var(--input);
78
+ --color-ring: var(--ring);
79
+ --color-info: var(--info);
80
+ --color-destructive-foreground: var(--destructive-foreground);
81
+
82
+ /* Status scales */
83
+ --color-red-3: var(--red-3);
84
+ --color-red-6: var(--red-6);
85
+ --color-red-9: var(--red-9);
86
+ --color-red-10: var(--red-10);
87
+ --color-red-11: var(--red-11);
88
+ --color-red-a2: var(--red-a2);
89
+ --color-red-a3: var(--red-a3);
90
+ --color-red-a4: var(--red-a4);
91
+ --color-green-3: var(--green-3);
92
+ --color-green-6: var(--green-6);
93
+ --color-green-9: var(--green-9);
94
+ --color-green-10: var(--green-10);
95
+ --color-green-11: var(--green-11);
96
+ --color-green-a2: var(--green-a2);
97
+ --color-green-a3: var(--green-a3);
98
+ --color-green-a4: var(--green-a4);
99
+ --color-amber-3: var(--amber-3);
100
+ --color-amber-6: var(--amber-6);
101
+ --color-amber-9: var(--amber-9);
102
+ --color-amber-10: var(--amber-10);
103
+ --color-amber-11: var(--amber-11);
104
+ --color-amber-a2: var(--amber-a2);
105
+ --color-amber-a3: var(--amber-a3);
106
+ --color-amber-a4: var(--amber-a4);
107
+ --color-blue-3: var(--blue-3);
108
+ --color-blue-6: var(--blue-6);
109
+ --color-blue-9: var(--blue-9);
110
+ --color-blue-10: var(--blue-10);
111
+ --color-blue-11: var(--blue-11);
112
+ --color-blue-a2: var(--blue-a2);
113
+ --color-blue-a3: var(--blue-a3);
114
+ --color-blue-a4: var(--blue-a4);
115
+
116
+ /* Gray scale */
117
+ --color-gray-1: var(--gray-1);
118
+ --color-gray-2: var(--gray-2);
119
+ --color-gray-3: var(--gray-3);
120
+ --color-gray-4: var(--gray-4);
121
+ --color-gray-5: var(--gray-5);
122
+ --color-gray-6: var(--gray-6);
123
+ --color-gray-7: var(--gray-7);
124
+ --color-gray-8: var(--gray-8);
125
+ --color-gray-9: var(--gray-9);
126
+ --color-gray-10: var(--gray-10);
127
+ --color-gray-11: var(--gray-11);
128
+ --color-gray-12: var(--gray-12);
129
+
130
+ /* Gray alpha */
131
+ --color-gray-a1: var(--gray-a1);
132
+ --color-gray-a2: var(--gray-a2);
133
+ --color-gray-a3: var(--gray-a3);
134
+ --color-gray-a4: var(--gray-a4);
135
+ --color-gray-a5: var(--gray-a5);
136
+ --color-gray-a6: var(--gray-a6);
137
+ --color-gray-a7: var(--gray-a7);
138
+ --color-gray-a8: var(--gray-a8);
139
+ --color-gray-a9: var(--gray-a9);
140
+ --color-gray-a10: var(--gray-a10);
141
+
142
+ /* Brand semantic surfaces — theme-aware (see :root / .dark) */
143
+ --color-brand-tint: var(--brand-tint);
144
+ --color-brand-hairline: var(--brand-hairline);
145
+ --color-brand-accent: var(--brand-accent);
146
+
147
+ /* Charts */
148
+ --color-chart-1: var(--chart-1);
149
+ --color-chart-2: var(--chart-2);
150
+ --color-chart-3: var(--chart-3);
151
+ --color-chart-4: var(--chart-4);
152
+ --color-chart-5: var(--chart-5);
153
+
154
+ /* Chart — semantic outcome tokens */
155
+ --color-chart-positive: var(--chart-positive);
156
+ --color-chart-negative: var(--chart-negative);
157
+ --color-chart-caution: var(--chart-caution);
158
+ --color-chart-neutral: var(--chart-neutral);
159
+
160
+ /* Radius */
161
+ --radius: 0.625rem;
162
+ --radius-sm: calc(var(--radius) - 4px);
163
+ --radius-md: calc(var(--radius) - 2px);
164
+ --radius-lg: var(--radius);
165
+ --radius-xl: calc(var(--radius) + 4px);
166
+ --radius-2xl: calc(var(--radius) + 8px);
167
+ --radius-3xl: calc(var(--radius) + 14px);
168
+
169
+ /* Semantic radius (themeable) */
170
+ --radius-field: var(--radius-field);
171
+ --radius-menu: var(--radius-menu);
172
+ --radius-card: var(--radius-card);
173
+ --radius-dialog: var(--radius-dialog);
174
+
175
+ /* Typography */
176
+ --text-2xs: 0.6875rem;
177
+ --text-2xs--line-height: 1rem;
178
+ --text-3xs: 0.625rem;
179
+ --text-3xs--line-height: 0.875rem;
180
+ }
181
+
182
+ :root {
183
+ --background: #ffffff;
184
+ --foreground: #1c1d1e;
185
+
186
+ /* Elevation (see the @theme mapping). Light: soft layered umbra, hairline
187
+ ring from the gray alpha ramp on overlay sizes only. */
188
+ --elevation-2xs: 0 1px 2px rgba(0, 0, 0, 0.04);
189
+ --elevation-xs: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.03);
190
+ --elevation-sm:
191
+ 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 6px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.03);
192
+ --elevation-md:
193
+ 0 0 0 1px var(--gray-a2), 0 2px 4px rgba(0, 0, 0, 0.05), 0 6px 14px rgba(0, 0, 0, 0.05),
194
+ 0 12px 28px rgba(0, 0, 0, 0.04);
195
+ --elevation-lg:
196
+ 0 0 0 1px var(--gray-a3), 0 4px 8px rgba(0, 0, 0, 0.05), 0 10px 22px rgba(0, 0, 0, 0.06),
197
+ 0 20px 44px rgba(0, 0, 0, 0.07);
198
+ --elevation-xl:
199
+ 0 0 0 1px var(--gray-a3), 0 8px 16px rgba(0, 0, 0, 0.06), 0 16px 36px rgba(0, 0, 0, 0.08),
200
+ 0 28px 64px rgba(0, 0, 0, 0.09);
201
+ --elevation-2xl:
202
+ 0 0 0 1px var(--gray-a3), 0 12px 24px rgba(0, 0, 0, 0.08), 0 24px 56px rgba(0, 0, 0, 0.11),
203
+ 0 40px 96px rgba(0, 0, 0, 0.12);
204
+
205
+ /* Gray scale */
206
+ --gray-1: #ffffff;
207
+ --gray-2: #f5f5f5;
208
+ --gray-3: #e6e6e6;
209
+ --gray-4: #d4d4d4;
210
+ --gray-5: #a3a3a3;
211
+ --gray-6: #656a6e;
212
+ --gray-7: #525252;
213
+ --gray-8: #4c5154;
214
+ --gray-9: #343739;
215
+ --gray-10: #26282a;
216
+ --gray-11: #1c1d1e;
217
+ --gray-12: #000000;
218
+
219
+ /* Gray alpha */
220
+ --gray-a1: rgba(0, 0, 0, 0.05);
221
+ --gray-a2: rgba(0, 0, 0, 0.08);
222
+ --gray-a3: rgba(0, 0, 0, 0.15);
223
+ --gray-a4: rgba(0, 0, 0, 0.35);
224
+ --gray-a5: rgba(0, 0, 0, 0.4);
225
+ --gray-a6: rgba(0, 0, 0, 0.5);
226
+ --gray-a7: rgba(0, 0, 0, 0.6);
227
+ --gray-a8: rgba(0, 0, 0, 0.7);
228
+ --gray-a9: rgba(0, 0, 0, 0.8);
229
+ --gray-a10: rgba(0, 0, 0, 0.95);
230
+
231
+ /* Status scales (theme-independent) */
232
+ --red-3: #fee2e2;
233
+ --red-6: #fca5a5;
234
+ --red-9: #ef4444;
235
+ --red-10: #dc2626;
236
+ --red-11: #b91c1c;
237
+ --green-3: #dcfce7;
238
+ --green-6: #86efac;
239
+ --green-9: #22c55e;
240
+ --green-10: #16a34a;
241
+ --green-11: #15803d;
242
+ --amber-3: #fef3c7;
243
+ --amber-6: #fcd34d;
244
+ --amber-9: #f59e0b;
245
+ --amber-10: #d97706;
246
+ --amber-11: #b45309;
247
+ --blue-3: #dbeafe;
248
+ --blue-6: #93c5fd;
249
+ --blue-9: #3b82f6;
250
+ --blue-10: #2563eb;
251
+ --blue-11: #1d4ed8;
252
+ --red-a2: rgba(239, 68, 68, 0.08);
253
+ --red-a3: rgba(239, 68, 68, 0.15);
254
+ --red-a4: rgba(239, 68, 68, 0.25);
255
+ --green-a2: rgba(34, 197, 94, 0.08);
256
+ --green-a3: rgba(34, 197, 94, 0.15);
257
+ --green-a4: rgba(34, 197, 94, 0.25);
258
+ --amber-a2: rgba(245, 158, 11, 0.08);
259
+ --amber-a3: rgba(245, 158, 11, 0.15);
260
+ --amber-a4: rgba(245, 158, 11, 0.25);
261
+ --blue-a2: rgba(59, 130, 246, 0.08);
262
+ --blue-a3: rgba(59, 130, 246, 0.15);
263
+ --blue-a4: rgba(59, 130, 246, 0.25);
264
+
265
+ /* Semantic radius */
266
+ --radius-field: 0.875rem;
267
+ --radius-menu: 1.125rem;
268
+ --radius-card: 1.125rem;
269
+ --radius-dialog: 1.5rem;
270
+
271
+ /* Brand semantic surfaces (light) */
272
+ --brand-tint: #f2faf8;
273
+ --brand-hairline: #ddf0ea;
274
+ --brand-accent: #0b6d61;
275
+
276
+ /* Semantic colors */
277
+ --card: #ffffff;
278
+ --card-foreground: #1c1d1e;
279
+ --popover: #ffffff;
280
+ --popover-foreground: #1c1d1e;
281
+ --primary: #0d8273;
282
+ --primary-foreground: #ffffff;
283
+ --secondary: #e6e6e6;
284
+ --secondary-foreground: #1c1d1e;
285
+ --muted: #f5f5f5;
286
+ --muted-foreground: #656a6e;
287
+ --accent: var(--gray-a2);
288
+ --accent-foreground: var(--foreground);
289
+ /* Solid red that carries white text at WCAG AA (4.8:1) in both modes; red text uses red-11. */
290
+ --destructive: #dc2626;
291
+ --destructive-foreground: #ffffff;
292
+ --success: var(--green-9);
293
+ --warning: var(--amber-9);
294
+ --info: var(--blue-9);
295
+ --border: var(--gray-a3);
296
+ --input: var(--gray-a3);
297
+ --ring: #14a08c;
298
+
299
+ /* Charts — categorical (brand-aligned) */
300
+ --chart-1: #14a08c;
301
+ --chart-2: #3b82f6;
302
+ --chart-3: #f59e0b;
303
+ --chart-4: #8b5cf6;
304
+ --chart-5: #ef4444;
305
+
306
+ /* Charts — semantic outcome (derived from status scales) */
307
+ --chart-positive: var(--green-10);
308
+ --chart-negative: var(--red-10);
309
+ --chart-caution: var(--amber-10);
310
+ --chart-neutral: var(--gray-6);
311
+ }
312
+
313
+ .dark {
314
+ --background: #1c1d1e;
315
+ --foreground: #f5f5f5;
316
+
317
+ /* Elevation. Dark: black gains depth while the ring — the same gray alpha
318
+ vars, now low-alpha white — becomes the edge that separates a floating
319
+ surface from the surface behind it. */
320
+ --elevation-2xs: 0 1px 2px rgba(0, 0, 0, 0.3);
321
+ --elevation-xs: 0 1px 2px rgba(0, 0, 0, 0.32), 0 2px 4px rgba(0, 0, 0, 0.24);
322
+ --elevation-sm:
323
+ 0 1px 2px rgba(0, 0, 0, 0.34), 0 2px 6px rgba(0, 0, 0, 0.28), 0 4px 12px rgba(0, 0, 0, 0.22);
324
+ --elevation-md:
325
+ 0 0 0 1px var(--gray-a3), 0 2px 4px rgba(0, 0, 0, 0.34), 0 6px 14px rgba(0, 0, 0, 0.3),
326
+ 0 12px 28px rgba(0, 0, 0, 0.26);
327
+ --elevation-lg:
328
+ 0 0 0 1px var(--gray-a3), 0 4px 8px rgba(0, 0, 0, 0.36), 0 10px 22px rgba(0, 0, 0, 0.32),
329
+ 0 20px 44px rgba(0, 0, 0, 0.3);
330
+ --elevation-xl:
331
+ 0 0 0 1px var(--gray-a4), 0 8px 16px rgba(0, 0, 0, 0.38), 0 16px 36px rgba(0, 0, 0, 0.36),
332
+ 0 28px 64px rgba(0, 0, 0, 0.34);
333
+ --elevation-2xl:
334
+ 0 0 0 1px var(--gray-a4), 0 12px 24px rgba(0, 0, 0, 0.42), 0 24px 56px rgba(0, 0, 0, 0.44),
335
+ 0 40px 96px rgba(0, 0, 0, 0.46);
336
+
337
+ /* Gray scale */
338
+ --gray-1: #1c1d1e;
339
+ --gray-2: #262829;
340
+ --gray-3: #343739;
341
+ --gray-4: #4c5154;
342
+ --gray-5: #656a6e;
343
+ --gray-6: #8b9095;
344
+ --gray-7: #a3a8ad;
345
+ --gray-8: #c4c8cc;
346
+ --gray-9: #e6e6e6;
347
+ --gray-10: #f5f5f5;
348
+ --gray-11: #fafafa;
349
+ --gray-12: #ffffff;
350
+
351
+ /* Gray alpha */
352
+ --gray-a1: rgba(255, 255, 255, 0.03);
353
+ --gray-a2: rgba(255, 255, 255, 0.06);
354
+ --gray-a3: rgba(255, 255, 255, 0.1);
355
+ --gray-a4: rgba(255, 255, 255, 0.15);
356
+ --gray-a5: rgba(255, 255, 255, 0.2);
357
+ --gray-a6: rgba(255, 255, 255, 0.3);
358
+ --gray-a7: rgba(255, 255, 255, 0.4);
359
+ --gray-a8: rgba(255, 255, 255, 0.55);
360
+ --gray-a9: rgba(255, 255, 255, 0.7);
361
+ --gray-a10: rgba(255, 255, 255, 0.9);
362
+
363
+ /* Status scale overrides (dark) */
364
+ --red-3: var(--red-a3);
365
+ --red-6: rgba(239, 68, 68, 0.35);
366
+ --red-10: #f87171;
367
+ --red-11: #fca5a5;
368
+ --green-3: var(--green-a3);
369
+ --green-6: rgba(34, 197, 94, 0.35);
370
+ --green-10: #4ade80;
371
+ --green-11: #86efac;
372
+ --amber-3: var(--amber-a3);
373
+ --amber-6: rgba(245, 158, 11, 0.35);
374
+ --amber-10: #fbbf24;
375
+ --amber-11: #fcd34d;
376
+ --blue-3: var(--blue-a3);
377
+ --blue-6: rgba(59, 130, 246, 0.35);
378
+ --blue-10: #60a5fa;
379
+ --blue-11: #93c5fd;
380
+
381
+ /* Brand semantic surfaces (dark) */
382
+ --brand-tint: rgba(76, 189, 167, 0.08);
383
+ --brand-hairline: rgba(76, 189, 167, 0.2);
384
+ --brand-accent: #82d5c4;
385
+
386
+ /* Semantic colors */
387
+ --card: #1c1d1e;
388
+ --card-foreground: #f5f5f5;
389
+ --popover: #262829;
390
+ --popover-foreground: #f5f5f5;
391
+ --primary: #4cbda7;
392
+ --primary-foreground: #052b26;
393
+ --secondary: #343739;
394
+ --secondary-foreground: #f5f5f5;
395
+ --muted: #262829;
396
+ --muted-foreground: #8b9095;
397
+ --accent: var(--gray-a2);
398
+ --accent-foreground: var(--foreground);
399
+ --destructive: #dc2626;
400
+ --destructive-foreground: #ffffff;
401
+ --success: var(--green-9);
402
+ --warning: var(--amber-9);
403
+ --info: var(--blue-9);
404
+ --border: var(--gray-a3);
405
+ --input: var(--gray-a3);
406
+ --ring: rgba(76, 189, 167, 0.5);
407
+
408
+ /* Charts — categorical (brand-aligned) */
409
+ --chart-1: #4cbda7;
410
+ --chart-2: #60a5fa;
411
+ --chart-3: #fbbf24;
412
+ --chart-4: #a78bfa;
413
+ --chart-5: #f87171;
414
+
415
+ color-scheme: dark;
416
+ }
417
+
418
+ /*
419
+ * Appearances: `brand` (surgical teal, the default the tokens above already carry) and `mono` (grayscale).
420
+ * Set `data-theme` on <html>. The selectors include `:root`/`.dark` so they outrank the base blocks.
421
+ */
422
+ :root[data-theme='brand'] {
423
+ --primary: #0d8273;
424
+ --primary-foreground: #ffffff;
425
+ --ring: #14a08c;
426
+ --brand-tint: #f2faf8;
427
+ --brand-hairline: #ddf0ea;
428
+ --brand-accent: #0b6d61;
429
+ --chart-1: #14a08c;
430
+ }
431
+
432
+ .dark[data-theme='brand'] {
433
+ --primary: #4cbda7;
434
+ --primary-foreground: #052b26;
435
+ --ring: rgba(76, 189, 167, 0.5);
436
+ /* Without these the light tint wins over `.dark`: white rows, invisible text. */
437
+ --brand-tint: rgba(76, 189, 167, 0.08);
438
+ --brand-hairline: rgba(76, 189, 167, 0.2);
439
+ --brand-accent: #82d5c4;
440
+ --chart-1: #4cbda7;
441
+ }
442
+
443
+ :root[data-theme='mono'] {
444
+ --primary: #1c1d1e;
445
+ --primary-foreground: #ffffff;
446
+ --ring: rgba(0, 0, 0, 0.35);
447
+ --brand-tint: var(--gray-a1);
448
+ --brand-hairline: var(--gray-a3);
449
+ --brand-accent: var(--gray-11);
450
+ --chart-1: #52525b;
451
+ }
452
+
453
+ .dark[data-theme='mono'] {
454
+ --primary: #f5f5f5;
455
+ --primary-foreground: #1c1d1e;
456
+ --ring: rgba(255, 255, 255, 0.3);
457
+ --brand-accent: var(--gray-11);
458
+ --chart-1: #a1a1aa;
459
+ }
460
+
461
+ @layer base {
462
+ * {
463
+ @apply border-border outline-ring/50;
464
+ }
465
+ body {
466
+ @apply bg-background text-foreground min-h-dvh;
467
+ }
468
+ html {
469
+ text-rendering: optimizelegibility;
470
+ }
471
+ input::placeholder,
472
+ textarea::placeholder {
473
+ @apply text-muted-foreground;
474
+ }
475
+ /* Everything that acts like a button reads like one. React Aria renders
476
+ options/menu items/tabs as non-button elements, so role selectors carry
477
+ the same promise there. */
478
+ button:not(:disabled),
479
+ [role='button']:not(:disabled),
480
+ [role='option']:not([aria-disabled='true']),
481
+ [role='menuitem']:not([aria-disabled='true']),
482
+ [role='menuitemcheckbox']:not([aria-disabled='true']),
483
+ [role='menuitemradio']:not([aria-disabled='true']),
484
+ [role='tab']:not([aria-disabled='true']),
485
+ [role='radio']:not([aria-disabled='true']),
486
+ [role='checkbox']:not([aria-disabled='true']) {
487
+ @apply cursor-pointer;
488
+ }
489
+ }
490
+
491
+ @utility scrollable {
492
+ scrollbar-color: var(--color-gray-a6) transparent;
493
+ scrollbar-width: thin;
494
+
495
+ &::-webkit-scrollbar {
496
+ width: 10px;
497
+ height: 10px;
498
+ }
499
+
500
+ &::-webkit-scrollbar-track {
501
+ background: transparent;
502
+ }
503
+
504
+ &::-webkit-scrollbar-thumb {
505
+ background-color: var(--color-gray-a6);
506
+ background-clip: content-box;
507
+ border: 2px solid transparent;
508
+ border-radius: 9999px;
509
+ }
510
+
511
+ &::-webkit-scrollbar-thumb:hover {
512
+ background-color: var(--color-gray-a8);
513
+ }
514
+ }
515
+
516
+ /*
517
+ * A control smaller than a fingertip keeps its look, but on a touch screen it takes taps across at
518
+ * least 44 × 44 px (WCAG 2.5.5). The area is drawn from the control, so the control must be positioned
519
+ * (`relative`); a box larger than 44 px keeps its own size.
520
+ */
521
+ @utility touch-target {
522
+ @media (pointer: coarse) {
523
+ &::after {
524
+ position: absolute;
525
+ top: 50%;
526
+ left: 50%;
527
+ width: max(100%, 2.75rem);
528
+ height: max(100%, 2.75rem);
529
+ content: '';
530
+ translate: -50% -50%;
531
+ }
532
+ }
533
+ }
package/package.json CHANGED
@@ -1,6 +1,120 @@
1
1
  {
2
2
  "name": "@arbol-ai/ui",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Arbol's React component library: accessible React Aria Components styled with Tailwind CSS v4, localized in Spanish and English, for building dashboards fast.",
5
+ "keywords": [
6
+ "accessibility",
7
+ "components",
8
+ "dashboard",
9
+ "design-system",
10
+ "i18n",
11
+ "react",
12
+ "react-aria",
13
+ "react-aria-components",
14
+ "tailwindcss",
15
+ "ui"
16
+ ],
17
+ "homepage": "https://github.com/getarbol/project-arbol-ui#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/getarbol/project-arbol-ui/issues"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Arbol",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/getarbol/project-arbol-ui.git"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "skills",
30
+ "README.md",
31
+ "CHANGELOG.md",
32
+ "LICENSE"
33
+ ],
34
+ "type": "module",
35
+ "sideEffects": [
36
+ "**/*.css"
37
+ ],
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/index.js"
42
+ },
43
+ "./theme.css": "./dist/theme.css",
44
+ "./package.json": "./package.json"
45
+ },
46
+ "scripts": {
47
+ "dev": "vp dev site",
48
+ "build": "vp pack",
49
+ "site:build": "vp build site",
50
+ "typecheck": "tsc -p tsconfig.json && tsc -p tsconfig.node.json",
51
+ "check": "vp check",
52
+ "fix": "vp check --fix",
53
+ "test": "vp test run",
54
+ "test:coverage": "vp test run --coverage",
55
+ "verify": "vp run typecheck && vp check && vp pack && vp build site && vp run verify:package",
56
+ "verify:package": "publint --strict && attw --pack . --profile esm-only --exclude-entrypoints ./theme.css",
57
+ "prepare": "vp config"
58
+ },
59
+ "dependencies": {
60
+ "@internationalized/date": "^3.12.4",
61
+ "@phosphor-icons/react": "^2.1.10",
62
+ "@tanstack/react-table": "^9.3.1",
63
+ "class-variance-authority": "^0.7.1",
64
+ "clsx": "^2.1.1",
65
+ "tailwind-merge": "^3.7.0",
66
+ "tw-animate-css": "^1.4.0"
67
+ },
68
+ "devDependencies": {
69
+ "@arethetypeswrong/cli": "0.18.5",
70
+ "@tailwindcss/vite": "4.3.5",
71
+ "@tanstack/react-form": "1.33.5",
72
+ "@types/node": "26.6.5",
73
+ "@types/react": "19.3.0",
74
+ "@types/react-dom": "19.3.0",
75
+ "@vitejs/plugin-react": "6.2.1",
76
+ "@vitest/browser-playwright": "5.1.0",
77
+ "@vitest/coverage-istanbul": "5.1.0",
78
+ "axe-core": "4.14.0",
79
+ "echarts": "6.1.0",
80
+ "playwright": "1.65.0",
81
+ "publint": "0.3.25",
82
+ "react": "19.3.1",
83
+ "react-aria": "3.53.1",
84
+ "react-aria-components": "1.22.1",
85
+ "react-dom": "19.3.1",
86
+ "tailwindcss": "4.3.5",
87
+ "typescript": "7.0.2",
88
+ "ultracite": "8.1.1",
89
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.5.0",
90
+ "vite-plus": "1.5.0",
91
+ "vitest": "5.1.0",
92
+ "vitest-browser-react": "2.3.0",
93
+ "wrangler": "4.158.0"
94
+ },
95
+ "peerDependencies": {
96
+ "@tanstack/react-form": "^1.30.0",
97
+ "echarts": "^6.0.0",
98
+ "react": "^19.0.0",
99
+ "react-aria": "^3.52.0",
100
+ "react-aria-components": "^1.21.0",
101
+ "react-dom": "^19.0.0",
102
+ "tailwindcss": "^4.1.0"
103
+ },
104
+ "peerDependenciesMeta": {
105
+ "echarts": {
106
+ "optional": true
107
+ },
108
+ "@tanstack/react-form": {
109
+ "optional": true
110
+ }
111
+ },
112
+ "overrides": {
113
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.5.0",
114
+ "vitest": "5.1.0"
115
+ },
116
+ "engines": {
117
+ "node": ">=22.18"
118
+ },
119
+ "packageManager": "bun@1.4.4"
120
+ }
@@ -0,0 +1,41 @@
1
+ # Text and languages
2
+
3
+ There are exactly two kinds of text on a screen built with @arbol-ai/ui.
4
+
5
+ ## App copy: always yours
6
+
7
+ Titles, labels, descriptions, column headers, button labels, empty-state sentences, item names: pass them
8
+ through props and children, already translated by the app's own system (Paraglide's `m.*()`, i18next's
9
+ `t()`, or plain Spanish). The library never defaults app copy.
10
+
11
+ ```tsx
12
+ <PageHeader>
13
+ <PageTitle>{m.contacts_title()}</PageTitle>
14
+ </PageHeader>
15
+ <SearchField aria-label={m.contacts_search()} placeholder={m.contacts_searchPlaceholder()} />
16
+ ```
17
+
18
+ ## Library strings: built in, Spanish and English
19
+
20
+ What a component says by itself (a close button's name, "Previous page", "1–15 of 120", "No results",
21
+ screen-reader instructions, "Copied") ships in English (`en-US`) and Latin American Spanish (`es-419`), and
22
+ follows `UIProvider locale`: any `es-*` locale reads Spanish, everything else English. Numbers inside them
23
+ are formatted for the locale (`1.234` in `es-CO`).
24
+
25
+ Replace any of them, in whichever locale is active, with `UIProvider messages`. Values are strings or
26
+ functions of the message's variables, so Paraglide functions plug in directly:
27
+
28
+ ```tsx
29
+ <UIProvider locale={locale} messages={{ 'overlay.close': m.ui_close, 'pagination.range': m.ui_pagination_range }}>
30
+ ```
31
+
32
+ Variables keep the names Arbol's catalogs use: `first`, `last`, `total`, `count`, `page`, `label`,
33
+ `value`. Many components also take a prop for a single instance (`closeLabel`, `clearLabel`).
34
+
35
+ ## Dates, times and numbers
36
+
37
+ Components format with React Aria and `@internationalized/date` in the provider's locale. Spanish
38
+ conventions differ by region (`es-CO`: 12-hour clock, weeks start on Sunday; `es`: 24-hour, Monday): pick
39
+ the locale tag deliberately, and pass `firstDayOfWeek` / `hourCycle` to the date components when the
40
+ product needs another convention. Pass numbers as numbers and dates as `CalendarDate`/`ZonedDateTime`
41
+ values, never pre-formatted strings, so plural rules and formats stay correct.