@eduardoalvarez/arrecife 0.3.0 → 0.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +121 -1
  3. package/dist/brand/index.cjs +29 -10
  4. package/dist/brand/index.d.cts +9 -1
  5. package/dist/brand/index.d.ts +9 -1
  6. package/dist/brand/index.js +4 -2
  7. package/dist/chart/index.cjs +287 -0
  8. package/dist/chart/index.d.cts +127 -0
  9. package/dist/chart/index.d.ts +127 -0
  10. package/dist/chart/index.js +103 -0
  11. package/dist/chunk-KPZNNMV5.js +83 -0
  12. package/dist/chunk-MLKGABMK.js +7 -0
  13. package/dist/chunk-NHS7ETKJ.js +27 -0
  14. package/dist/{chunk-I7GYZD2A.js → chunk-TSPJOM6K.js} +5 -7
  15. package/dist/chunk-UOWIDFCB.js +81 -0
  16. package/dist/{chunk-VSMW2CT4.js → chunk-VPT32GPG.js} +2 -1
  17. package/dist/{chunk-ND45OQOC.js → chunk-YZ2SDOVZ.js} +29 -35
  18. package/dist/chunk-ZEOQKRQ7.js +19 -0
  19. package/dist/form/index.cjs +301 -0
  20. package/dist/form/index.d.cts +86 -0
  21. package/dist/form/index.d.ts +86 -0
  22. package/dist/form/index.js +84 -0
  23. package/dist/index.cjs +1094 -293
  24. package/dist/index.d.cts +452 -37
  25. package/dist/index.d.ts +452 -37
  26. package/dist/index.js +963 -373
  27. package/dist/label-DuTvJGxD.d.cts +14 -0
  28. package/dist/label-DuTvJGxD.d.ts +14 -0
  29. package/dist/og/index.js +2 -1
  30. package/dist/shiki/index.js +2 -1
  31. package/dist/tema/index.cjs +94 -0
  32. package/dist/tema/index.d.cts +110 -0
  33. package/dist/tema/index.d.ts +110 -0
  34. package/dist/tema/index.js +2 -0
  35. package/dist/tokens/index.cjs +6 -0
  36. package/dist/tokens/index.d.cts +33 -1
  37. package/dist/tokens/index.d.ts +33 -1
  38. package/dist/tokens/index.js +3 -2
  39. package/dist/tokens/theme.css +43 -0
  40. package/llms.txt +351 -44
  41. package/package.json +31 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/Proskynete/arrecife/compare/v0.4.0...v0.5.0) (2026-08-31)
4
+
5
+
6
+ ### ⚠ BREAKING CHANGES
7
+
8
+ * **primitives:** `Toast`, `ToastProvider`, `ToastViewport`, `ToastTitle` y `ToastDescription` dejan de exportarse; se usa `<Toaster />` una vez y `toast()` desde donde haga falta. `ToastAction` no cambia. Migración en docs/migracion-0.5.md.
9
+
10
+ ### 🚀 Novedades
11
+
12
+ * **components:** EventCalendar, la agenda con crear, editar y borrar ([6190700](https://github.com/Proskynete/arrecife/commit/61907003eeef777ba86c594791b2e95d225eda1f))
13
+ * **components:** la barra, el pie y las métricas como en el documento ([203b669](https://github.com/Proskynete/arrecife/commit/203b669379f63048ce4b34fce9a9411aebec3279))
14
+ * **primitives:** AvatarUpload, el avatar que se puede cambiar ([f888b4c](https://github.com/Proskynete/arrecife/commit/f888b4c0fadeddfb885f0b4651bf56000da7d430))
15
+ * **primitives:** el plegable anima la altura, como cuarta excepción ([b9dd44c](https://github.com/Proskynete/arrecife/commit/b9dd44ccb99ccb2c0f00ec99fefa17bc838cca52))
16
+ * **primitives:** Toaster es la única forma de mostrar un aviso ([2ec1d23](https://github.com/Proskynete/arrecife/commit/2ec1d2383ecd87c58a88c9e32543dc2112df2321))
17
+
18
+
19
+ ### 📚 Documentación
20
+
21
+ * los iconos de redes van agrupados, y por qué ([1939455](https://github.com/Proskynete/arrecife/commit/19394554185a54e4e752a86e27e60f1a60acec26))
22
+
23
+ ## [0.4.0](https://github.com/Proskynete/arrecife/compare/v0.3.0...v0.4.0) (2026-08-31)
24
+
25
+
26
+ ### 🚀 Novedades
27
+
28
+ * **components:** ScrollingProgressBar y las cinco ranuras que faltaban ([52325cc](https://github.com/Proskynete/arrecife/commit/52325ccff7eedbda39d449c98386668df7a518bf))
29
+ * **form:** la capa de formulario y el chasis de gráficas, en su subruta ([4a40857](https://github.com/Proskynete/arrecife/commit/4a40857e9e9efc8bbe4011bf72f6313347c97541))
30
+ * **primitives:** Accordion, AlertDialog y el Toast imperativo ([239120d](https://github.com/Proskynete/arrecife/commit/239120d510ea395159719581ef69c26734d82441))
31
+ * **tema:** el modo claro y oscuro, sin parpadeo y sin React ([c99995c](https://github.com/Proskynete/arrecife/commit/c99995c8eae6313ece6ffdcf927d9120f6050866))
32
+ * **tokens:** la paleta de series de gráfica, cuatro colores por tono ([b660a30](https://github.com/Proskynete/arrecife/commit/b660a30376c794be82f6a11808d9de175a31d5d9))
33
+
34
+
35
+ ### 🐛 Correcciones
36
+
37
+ * **ci:** check:exports no veía React dentro de un chunk ([3354ab1](https://github.com/Proskynete/arrecife/commit/3354ab12feb867a81480e1c3b612280c40f765ad))
38
+
39
+
40
+ ### 📚 Documentación
41
+
42
+ * los dos requisitos de consumo que no dan ningún error ([0a3a7fd](https://github.com/Proskynete/arrecife/commit/0a3a7fd71124aa2dafa3b70df4a90f8fd080544d))
43
+ * main está protegida y así se trabaja con ella ([a625a49](https://github.com/Proskynete/arrecife/commit/a625a499ba871ebf7f13a8f7f0cc9f7fbfff17fc))
44
+ * mergear saltándose la regla se pide, no pasa solo ([c3aca08](https://github.com/Proskynete/arrecife/commit/c3aca08d3926885edea1f500200f0a7496ab13bb))
45
+
3
46
  ## [0.3.0](https://github.com/Proskynete/arrecife/compare/v0.2.0...v0.3.0) (2026-08-31)
4
47
 
5
48
 
package/README.md CHANGED
@@ -64,6 +64,30 @@ mismo objeto `tokens`: la fuente no cambia.
64
64
  El modo oscuro es el primario y es el default. Un proyecto en modo claro declara
65
65
  `data-theme="light"` en `<html>`; uno oscuro no necesita declarar nada.
66
66
 
67
+ **Si además vas a usar componentes, falta una línea, y sin ella no falla nada.**
68
+ Tailwind no escanea `node_modules`, así que purga todas las clases que emiten los
69
+ componentes: `border-hairline`, `rounded-pill` y `p-step-lg` resuelven a la nada.
70
+ No hay error en consola, no hay aviso en el build, no hay clase sin definir — la
71
+ tarjeta simplemente sale con borde `currentColor` y la píldora, cuadrada.
72
+
73
+ ```css
74
+ @import "tailwindcss";
75
+ @import "@eduardoalvarez/arrecife/tokens/theme.css";
76
+
77
+ /* Sin esto, los componentes se montan sin ningún estilo del sistema. */
78
+ @source "../node_modules/@eduardoalvarez/arrecife/dist";
79
+ ```
80
+
81
+ La ruta es relativa al archivo CSS donde va la directiva, así que en un proyecto
82
+ con la hoja en `src/styles/` sube dos niveles y no uno. Lo detectaron los tests
83
+ E2E del blog, no el build, y hasta la 0.3.0 esto solo estaba escrito en
84
+ `llms.txt` — el archivo que lee un agente y no una persona.
85
+
86
+ > **Vienes de la 0.4.0 o anterior.** `Toast`, `ToastProvider`, `ToastViewport`,
87
+ > `ToastTitle` y `ToastDescription` dejaron de ser API pública en la 0.5.0: se
88
+ > usa `Toaster` y `toast()`. `ToastAction` se queda. La migración, con el porqué
89
+ > y los ejemplos, está en [`docs/migracion-0.5.md`](docs/migracion-0.5.md).
90
+
67
91
  > **Vienes de la 0.2.0 o anterior.** Los cinco escalones de espaciado se
68
92
  > renombraron: `p-md` es ahora `p-step-md`, `gap-sm` es `gap-step-sm`. Es un
69
93
  > cambio incompatible, y si tu proyecto usa `max-w-sm`, `max-w-md` o `max-w-lg`,
@@ -101,6 +125,49 @@ solución es declararlo con el nombre que pide el token:
101
125
  }
102
126
  ```
103
127
 
128
+ #### En Next, con `next/font`
129
+
130
+ Es el mismo fallo por otra puerta, y muerde a los dos proyectos Next. `next/font`
131
+ registra cada familia bajo un nombre GENERADO —`__Geist_a1b2c3`— y la expone como
132
+ una custom property; el nombre literal `"Geist"` que declaran los tokens no
133
+ existe en ningún `@font-face` de la página.
134
+
135
+ Importar `theme.css` sobrescribe `--font-sans` con ese literal, y las tres
136
+ familias caen a la fuente del sistema. En silencio: no hay 404, porque la fuente
137
+ sí se cargó — con otro nombre.
138
+
139
+ La solución es reafirmar las tres DESPUÉS del import, apuntando a las variables
140
+ que genera `next/font`:
141
+
142
+ ```ts
143
+ // app/fuentes.ts
144
+ import { Geist, Bricolage_Grotesque, JetBrains_Mono } from 'next/font/google';
145
+
146
+ export const sans = Geist({ subsets: ['latin'], variable: '--fuente-sans' });
147
+ export const display = Bricolage_Grotesque({ subsets: ['latin'], variable: '--fuente-display' });
148
+ export const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--fuente-mono' });
149
+ ```
150
+
151
+ ```css
152
+ @import "tailwindcss";
153
+ @import "@eduardoalvarez/arrecife/tokens/theme.css";
154
+ @source "../node_modules/@eduardoalvarez/arrecife/dist";
155
+
156
+ /* Después del import, o gana el literal que no está cargado. */
157
+ @theme {
158
+ --font-sans: var(--fuente-sans), ui-sans-serif, system-ui, sans-serif;
159
+ --font-display: var(--fuente-display), ui-sans-serif, system-ui, sans-serif;
160
+ --font-mono: var(--fuente-mono), ui-monospace, SFMono-Regular, Menlo, monospace;
161
+ }
162
+ ```
163
+
164
+ Las variables se llaman `--fuente-*` y no `--font-*` a propósito: `--font-sans`
165
+ es el nombre que Tailwind usa para SU token, y dárselo a `next/font` deja las dos
166
+ capas peleando por la misma propiedad.
167
+
168
+ El `variable` de cada familia va en la clase del `<html>`, como pide Next:
169
+ `className={`${sans.variable} ${display.variable} ${mono.variable}`}`.
170
+
104
171
  ### Mapa de tokens a utilidades
105
172
 
106
173
  | Token | Custom property | Utilidad |
@@ -191,6 +258,59 @@ Comprobado empaquetando la librería con `pnpm pack` e instalándola en un proye
191
258
  aparte: los tipos resuelven desde `dist/`, `./tokens` carga sin arrastrar React y
192
259
  `./tokens/theme.css` se resuelve por subruta.
193
260
 
261
+ ### Los iconos de redes van agrupados
262
+
263
+ Es lo primero con lo que tropieza quien consume la librería, porque la forma
264
+ natural no funciona:
265
+
266
+ ```tsx
267
+ // ❌ no existe
268
+ import { GitHub, LinkedIn } from '@eduardoalvarez/arrecife';
269
+
270
+ // ✅
271
+ import { social } from '@eduardoalvarez/arrecife';
272
+
273
+ <social.GitHub />
274
+ <social.LinkedIn />
275
+ ```
276
+
277
+ Los ocho son `GitHub`, `LinkedIn`, `X`, `Instagram`, `Discord`, `YouTube`, `Rss`
278
+ y `Correo`. Van bajo un namespace por un motivo concreto: **uno se llama `X`**.
279
+ Un `export const X` en la raíz de una librería de componentes colisiona con
280
+ cualquier cosa —una variable de un genérico, un `import { X }` de otro sitio— y
281
+ el fallo aparece lejos de aquí.
282
+
283
+ **Los glifos internos NO se exportan.** `Close`, `ChevronDown`, `Copy`, `Sol` y
284
+ compañía son el juego mínimo que necesitan los primitivos y se quedan dentro.
285
+ Publicarlos convertiría `lib/glyphs.tsx` en la librería de iconos que el sistema
286
+ decidió no tener, y a partir de ahí crece sola. Un proyecto que necesite un icono
287
+ pasa el suyo: `Stat` recibe `icon`, `Footer` recibe el `icon` de cada red.
288
+
289
+ ### Las dos subrutas que piden una dependencia
290
+
291
+ `./form` y `./chart` no cuelgan de la raíz, y es a propósito. Cada una pide una
292
+ dependencia de pares **opcional** —`react-hook-form` y `recharts`—, y colgarlas
293
+ del índice principal obligaría a los cinco proyectos a instalarlas para que su
294
+ bundler resolviera un import que cuatro de ellos nunca ejecutan.
295
+
296
+ Es la misma decisión que `./og` y `./shiki`, mirada desde el otro lado: allí se
297
+ saca React del camino de quien no lo monta; aquí se saca Recharts del camino de
298
+ quien no dibuja.
299
+
300
+ ```tsx
301
+ import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage }
302
+ from '@eduardoalvarez/arrecife/form';
303
+
304
+ import { ChartContainer, ChartTooltip, ChartTooltipContent, colorDeSerie }
305
+ from '@eduardoalvarez/arrecife/chart';
306
+ ```
307
+
308
+ `check:exports` verifica que las cuatro portables —`./tokens`, `./tema`, `./og` y
309
+ `./shiki`— no traen React en el `dist/` publicado, **siguiendo los imports
310
+ relativos**. Sin eso el check era papel mojado: con `treeshake` activo, cada
311
+ entrada portable queda en dos líneas que reexportan de un `chunk-XXXX.js`, y un
312
+ grep sobre esas dos líneas no encuentra React ni aunque el chunk lo importe.
313
+
194
314
  ## Scripts
195
315
 
196
316
  | | |
@@ -199,7 +319,7 @@ aparte: los tipos resuelven desde `dist/`, `./tokens` carga sin arrastrar React
199
319
  | `pnpm typecheck` | `tsc --noEmit` |
200
320
  | `pnpm lint` | ESLint, incluido el veto a hex literales fuera de `tokens.ts` |
201
321
  | `pnpm check:tokens` | falla si `src/tokens/` importa algo de fuera |
202
- | `pnpm test` | corre axe sobre las 161 stories, en los dos modos |
322
+ | `pnpm test` | compila Tailwind y corre axe sobre las 206 stories, en los dos modos |
203
323
  | `pnpm check:exports` | verifica que `dist/` tiene lo que `exports` promete |
204
324
  | `pnpm check:release` | valida `release-please-config.json` contra el esquema oficial |
205
325
  | `pnpm storybook` | genera los tokens y levanta Storybook en el 6006 |
@@ -116,6 +116,9 @@ var spacing = {
116
116
  stepXl: 40,
117
117
  section: 96
118
118
  };
119
+ var tagline = {
120
+ /** Header, una línea. */
121
+ corto: "Ayudo a equipos a escalar con criterio"};
119
122
  var naming = {
120
123
  wordmark: "Eduardo \xC1lvarez"};
121
124
 
@@ -165,20 +168,36 @@ function Logo({
165
168
  sobre = "oscuro",
166
169
  basePath = RUTA_ASSETS,
167
170
  soloIsotipo = false,
171
+ conLema = false,
168
172
  className,
169
173
  ...props
170
174
  }) {
175
+ const conAleta = /* @__PURE__ */ jsxRuntime.jsx(
176
+ Isotipo,
177
+ {
178
+ sobre,
179
+ basePath,
180
+ alt: soloIsotipo ? naming.wordmark : "",
181
+ className: conLema ? "h-9 w-auto" : "h-7 w-auto"
182
+ }
183
+ );
184
+ const wordmark = /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui font-display text-text-primary font-bold tracking-[-0.02em]", children: naming.wordmark });
185
+ if (soloIsotipo) {
186
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children: conAleta });
187
+ }
188
+ if (conLema) {
189
+ return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("gap-step-sm inline-flex items-center", className), ...props, children: [
190
+ conAleta,
191
+ /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", className: "bg-hairline h-8 w-px shrink-0" }),
192
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-col", children: [
193
+ wordmark,
194
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-eyebrow font-mono text-text-muted uppercase", children: tagline.corto })
195
+ ] })
196
+ ] });
197
+ }
171
198
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children: [
172
- /* @__PURE__ */ jsxRuntime.jsx(
173
- Isotipo,
174
- {
175
- sobre,
176
- basePath,
177
- alt: soloIsotipo ? naming.wordmark : "",
178
- className: "h-7 w-auto"
179
- }
180
- ),
181
- soloIsotipo ? null : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-ui font-display text-text-primary font-bold tracking-[-0.02em]", children: naming.wordmark })
199
+ conAleta,
200
+ wordmark
182
201
  ] });
183
202
  }
184
203
  function Mascota({
@@ -22,6 +22,14 @@ type LogoProps = Omit<ComponentPropsWithoutRef<'span'>, 'children'> & {
22
22
  basePath?: string | undefined;
23
23
  /** Oculta el wordmark y deja solo la aleta, para barras muy estrechas. */
24
24
  soloIsotipo?: boolean | undefined;
25
+ /**
26
+ * Añade el lema bajo el wordmark, separado de la aleta por una divisoria.
27
+ *
28
+ * Es la forma que toma la marca en la barra del sitio. El texto sale de
29
+ * `tagline.corto` y no se puede pasar por prop, por lo mismo que el wordmark:
30
+ * un lema escrito a mano en cinco proyectos son cinco lemas en seis meses.
31
+ */
32
+ conLema?: boolean | undefined;
25
33
  };
26
34
  /**
27
35
  * El wordmark sale de `naming.wordmark`, no de una cadena escrita a mano, y
@@ -31,7 +39,7 @@ type LogoProps = Omit<ComponentPropsWithoutRef<'span'>, 'children'> & {
31
39
  * Cuando se oculta el wordmark, el nombre sigue llegando al lector de pantalla
32
40
  * por el `alt` del isotipo. Un logo sin nombre accesible es un logo invisible.
33
41
  */
34
- declare function Logo({ sobre, basePath, soloIsotipo, className, ...props }: LogoProps): react.JSX.Element;
42
+ declare function Logo({ sobre, basePath, soloIsotipo, conLema, className, ...props }: LogoProps): react.JSX.Element;
35
43
 
36
44
  type Base = Omit<ComponentPropsWithoutRef<'img'>, 'src' | 'alt'> & {
37
45
  basePath?: string | undefined;
@@ -22,6 +22,14 @@ type LogoProps = Omit<ComponentPropsWithoutRef<'span'>, 'children'> & {
22
22
  basePath?: string | undefined;
23
23
  /** Oculta el wordmark y deja solo la aleta, para barras muy estrechas. */
24
24
  soloIsotipo?: boolean | undefined;
25
+ /**
26
+ * Añade el lema bajo el wordmark, separado de la aleta por una divisoria.
27
+ *
28
+ * Es la forma que toma la marca en la barra del sitio. El texto sale de
29
+ * `tagline.corto` y no se puede pasar por prop, por lo mismo que el wordmark:
30
+ * un lema escrito a mano en cinco proyectos son cinco lemas en seis meses.
31
+ */
32
+ conLema?: boolean | undefined;
25
33
  };
26
34
  /**
27
35
  * El wordmark sale de `naming.wordmark`, no de una cadena escrita a mano, y
@@ -31,7 +39,7 @@ type LogoProps = Omit<ComponentPropsWithoutRef<'span'>, 'children'> & {
31
39
  * Cuando se oculta el wordmark, el nombre sigue llegando al lector de pantalla
32
40
  * por el `alt` del isotipo. Un logo sin nombre accesible es un logo invisible.
33
41
  */
34
- declare function Logo({ sobre, basePath, soloIsotipo, className, ...props }: LogoProps): react.JSX.Element;
42
+ declare function Logo({ sobre, basePath, soloIsotipo, conLema, className, ...props }: LogoProps): react.JSX.Element;
35
43
 
36
44
  type Base = Omit<ComponentPropsWithoutRef<'img'>, 'src' | 'alt'> & {
37
45
  basePath?: string | undefined;
@@ -1,3 +1,5 @@
1
- export { CaraDeMascota, Isotipo, Logo, Mascota } from '../chunk-ND45OQOC.js';
1
+ export { CaraDeMascota, Isotipo, Logo, Mascota } from '../chunk-YZ2SDOVZ.js';
2
2
  export { RUTA_ASSETS, aletas, caras, listaCaras, listaPoses, poses, usoDeCara } from '../chunk-E3OMP2DL.js';
3
- import '../chunk-I7GYZD2A.js';
3
+ import '../chunk-NHS7ETKJ.js';
4
+ import '../chunk-TSPJOM6K.js';
5
+ import '../chunk-MLKGABMK.js';
@@ -0,0 +1,287 @@
1
+ 'use strict';
2
+
3
+ var recharts = require('recharts');
4
+ var clsx = require('clsx');
5
+ var tailwindMerge = require('tailwind-merge');
6
+ var reactSlot = require('@radix-ui/react-slot');
7
+ var classVarianceAuthority = require('class-variance-authority');
8
+ var jsxRuntime = require('react/jsx-runtime');
9
+
10
+ // src/chart/index.tsx
11
+ var typeScale = {
12
+ display: { family: "display", size: 76, lineHeight: 0.96, weight: 800, tracking: "-0.035em" },
13
+ /** Métricas grandes. Números, no prosa: interlineado 1 y sin descendentes. */
14
+ stat: { family: "display", size: 46, lineHeight: 1, weight: 800, tracking: "-0.035em" },
15
+ h1: { family: "display", size: 44, lineHeight: 1.05, weight: 700, tracking: "-0.03em" },
16
+ h2: { family: "display", size: 30, lineHeight: 1.1, weight: 600, tracking: "-0.02em" },
17
+ h3: { family: "display", size: 25, lineHeight: 1.15, weight: 600, tracking: "-0.02em" },
18
+ body: { family: "sans", size: 18, lineHeight: 1.75, weight: 400 },
19
+ /**
20
+ * 17px. El botón grande y la bajada de las páginas internas.
21
+ *
22
+ * Entra como escalón propio porque el documento lo usa DOS veces y en piezas
23
+ * distintas: «lg 15px 30px r12» con texto de 17, y «h1 44/700 · bajada 17px ·
24
+ * párrafo de contexto 15px». Un solo uso no habría justificado partir la
25
+ * distancia entre `ui` (15) y `body` (18); dos sí.
26
+ */
27
+ lead: { family: "sans", size: 17, lineHeight: 1.5, weight: 400 },
28
+ ui: { family: "sans", size: 15, lineHeight: 1.6, weight: 400 },
29
+ label: { family: "sans", size: 13, lineHeight: 1.5, weight: 500 },
30
+ /**
31
+ * La escala de las etiquetas de estado: sans 12.5/500, cuadrada.
32
+ *
33
+ * Es medio píxel por debajo de `label`, y esta vez el medio píxel sí importa:
34
+ * una etiqueta de estado va dentro de una tabla o al lado de un título, y a 13
35
+ * competía con el texto que acompaña. El documento la da en 12.5 y se ve.
36
+ */
37
+ tag: { family: "sans", size: 12.5, lineHeight: 1.4, weight: 500 },
38
+ /**
39
+ * La escala de categoría y métrica: mono 11.5.
40
+ *
41
+ * Rompe el suelo de `limits.minScreenPx`, y es a propósito. Ese suelo protege
42
+ * al TEXTO —lo que se lee en una frase—, y una píldora de una palabra no es
43
+ * texto corrido: es una marca. El contraste sigue medido y pasa AA (plancton
44
+ * 5.57:1 sobre abismo), que es la parte que no se negocia.
45
+ *
46
+ * A 13 las tres familias de etiqueta salían del tamaño de un botón pequeño y
47
+ * pesaban más que el título que acompañan. Ver `docs/decisiones.md`.
48
+ */
49
+ chip: { family: "mono", size: 11.5, lineHeight: 1.4, weight: 400 },
50
+ /**
51
+ * Mono SIN transformar: fechas, rutas, versiones, nombres de archivo, la firma
52
+ * del footer y las métricas de etiqueta. Es la mayoría del mono del sistema.
53
+ *
54
+ * Existe porque `eyebrow` lleva `uppercase` de fábrica y no es una preferencia
55
+ * que se pueda apagar: es la escala del eyebrow. Un `18 ago 2026` en versalitas
56
+ * o un `pose-laptop-coffee.png` en mayúsculas están mal, y sin este escalón la
57
+ * única salida era un `normal-case` en cada sitio de uso.
58
+ *
59
+ * El documento dice 12.5. Son 13 por la misma razón por la que `textMuted` se
60
+ * corrigió: `limits.minScreenPx` es 13 y plancton «nunca bajo 13px». Esta
61
+ * escala es justo donde se escriben las metas en muted, así que ponerla en
62
+ * 12.5 habría dejado ilegible su uso más común por medio píxel.
63
+ */
64
+ meta: { family: "mono", size: 13, lineHeight: 1.6, weight: 400 },
65
+ eyebrow: { family: "mono", size: 12, tracking: "0.12em", transform: "uppercase" }
66
+ };
67
+ var radius = {
68
+ chip: 6,
69
+ control: 10,
70
+ card: 14,
71
+ panel: 16,
72
+ pill: 999
73
+ };
74
+ var control = {
75
+ sm: 14,
76
+ md: 22,
77
+ lg: 30,
78
+ /** Botón de icono: cuadrado, sin texto. */
79
+ icon: 42
80
+ };
81
+ var spacing = {
82
+ stepXs: 8,
83
+ stepSm: 12,
84
+ stepMd: 16,
85
+ stepLg: 26,
86
+ stepXl: 40,
87
+ section: 96
88
+ };
89
+
90
+ // src/lib/cn.ts
91
+ var kebab = (nombre) => nombre.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
92
+ var escalas = Object.keys(typeScale);
93
+ var radios = Object.keys(radius);
94
+ var espacios = Object.keys(spacing).map(kebab);
95
+ var controles = Object.keys(control).map((nombre) => `control-${kebab(nombre)}`);
96
+ var twMerge = tailwindMerge.extendTailwindMerge({
97
+ extend: {
98
+ classGroups: {
99
+ "font-size": [{ text: escalas }],
100
+ rounded: [{ rounded: radios }],
101
+ p: [{ p: espacios }],
102
+ px: [{ px: [...espacios, ...controles] }],
103
+ py: [{ py: espacios }],
104
+ gap: [{ gap: espacios }],
105
+ size: [{ size: controles }]
106
+ }
107
+ }
108
+ });
109
+ function cn(...inputs) {
110
+ return twMerge(clsx.clsx(inputs));
111
+ }
112
+ var ETIQUETA = {
113
+ display: "h1",
114
+ stat: "p",
115
+ h1: "h1",
116
+ h2: "h2",
117
+ h3: "h3",
118
+ body: "p",
119
+ lead: "p",
120
+ ui: "p",
121
+ label: "span",
122
+ tag: "span",
123
+ meta: "p",
124
+ chip: "span",
125
+ eyebrow: "p"
126
+ };
127
+ var texto = classVarianceAuthority.cva("", {
128
+ variants: {
129
+ variant: {
130
+ display: "text-display font-display",
131
+ // Métricas grandes: 46px de display. Es la única escala pensada para un
132
+ // número, y por eso el interlineado es 1.
133
+ stat: "text-stat font-display",
134
+ h1: "text-h1 font-display",
135
+ h2: "text-h2 font-display",
136
+ h3: "text-h3 font-display",
137
+ body: "text-body font-sans",
138
+ // 17px: la bajada de las páginas internas y el botón grande.
139
+ lead: "text-lead font-sans",
140
+ ui: "text-ui font-sans",
141
+ label: "text-label font-sans",
142
+ // Las dos escalas de etiqueta. `tag` es el estado (sans 12.5/500) y `chip`
143
+ // la categoría y la métrica (mono 11.5). Ninguna transforma el texto.
144
+ tag: "text-tag font-sans",
145
+ chip: "text-chip font-mono",
146
+ // Mono SIN transformar: fechas, rutas, versiones, nombres de archivo y la
147
+ // firma del footer. Es el escalón que faltaba, y por el que cada sitio de
148
+ // uso terminaba escribiendo `variant="eyebrow" className="normal-case"`.
149
+ meta: "text-meta font-mono",
150
+ // `uppercase` no cabe en un token de tamaño: text-transform no es un
151
+ // modificador de --text-*. Por eso lo pone la variante y no theme.css.
152
+ eyebrow: "text-eyebrow font-mono uppercase"
153
+ },
154
+ tone: {
155
+ primary: "text-text-primary",
156
+ secondary: "text-text-secondary",
157
+ muted: "text-text-muted",
158
+ accent: "text-accent",
159
+ warm: "text-warm",
160
+ success: "text-success",
161
+ warning: "text-warning",
162
+ error: "text-error"
163
+ }
164
+ },
165
+ defaultVariants: { variant: "body", tone: "primary" }
166
+ });
167
+ function Text({
168
+ className,
169
+ variant,
170
+ tone,
171
+ as,
172
+ asChild = false,
173
+ measure,
174
+ children,
175
+ ...props
176
+ }) {
177
+ const escala = variant ?? "body";
178
+ const limitar = measure ?? escala === "body";
179
+ const clases = cn(texto({ variant, tone }), limitar && "max-w-measure", className);
180
+ if (asChild) {
181
+ return /* @__PURE__ */ jsxRuntime.jsx(reactSlot.Slot, { className: clases, ...props, children });
182
+ }
183
+ const Etiqueta = as ?? ETIQUETA[escala];
184
+ return /* @__PURE__ */ jsxRuntime.jsx(Etiqueta, { className: clases, ...props, children });
185
+ }
186
+ function colorDeSerie(indice) {
187
+ return `var(--color-serie-${Math.abs(indice) % 4 + 1})`;
188
+ }
189
+ var COLORES_DE_SERIE = [0, 1, 2, 3].map(colorDeSerie);
190
+ function ChartContainer({
191
+ label,
192
+ summary,
193
+ height = 320,
194
+ className,
195
+ children,
196
+ ...props
197
+ }) {
198
+ return /* @__PURE__ */ jsxRuntime.jsxs(
199
+ "figure",
200
+ {
201
+ "aria-label": label,
202
+ className: cn(
203
+ "w-full",
204
+ // La rejilla y los ejes, con los tokens del sistema en vez de los grises
205
+ // por defecto de Recharts. Van como variantes de descendiente porque los
206
+ // nodos los pinta la librería y no hay dónde ponerles una clase.
207
+ "[&_.recharts-cartesian-grid_line]:stroke-hairline",
208
+ "[&_.recharts-cartesian-axis-line]:stroke-hairline",
209
+ "[&_.recharts-cartesian-axis-tick_text]:fill-text-muted",
210
+ "[&_.recharts-cartesian-axis-tick_text]:font-mono",
211
+ "[&_.recharts-cartesian-axis-tick_text]:text-chip",
212
+ "[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-surface-raised",
213
+ "[&_.recharts-curve.recharts-tooltip-cursor]:stroke-hairline-hover",
214
+ "[&_.recharts-sector]:outline-none",
215
+ "[&_.recharts-surface]:overflow-visible",
216
+ className
217
+ ),
218
+ ...props,
219
+ children: [
220
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { height }, children: /* @__PURE__ */ jsxRuntime.jsx(recharts.ResponsiveContainer, { width: "100%", height: "100%", children }) }),
221
+ summary ? /* @__PURE__ */ jsxRuntime.jsx("figcaption", { className: "sr-only", children: summary }) : null
222
+ ]
223
+ }
224
+ );
225
+ }
226
+ function ChartTooltip(props) {
227
+ return /* @__PURE__ */ jsxRuntime.jsx(recharts.Tooltip, { isAnimationActive: false, ...props });
228
+ }
229
+ function ChartLegend(props) {
230
+ return /* @__PURE__ */ jsxRuntime.jsx(recharts.Legend, { ...props });
231
+ }
232
+ function ChartTooltipContent({
233
+ active,
234
+ payload,
235
+ label,
236
+ formatter,
237
+ hideLabel = false,
238
+ className
239
+ }) {
240
+ if (!active || !payload || payload.length === 0) return null;
241
+ return /* @__PURE__ */ jsxRuntime.jsxs(
242
+ "div",
243
+ {
244
+ className: cn(
245
+ "rounded-card border-border bg-surface shadow-standard p-step-sm gap-step-xs flex flex-col border",
246
+ className
247
+ ),
248
+ children: [
249
+ hideLabel || label === void 0 || label === null ? null : /* @__PURE__ */ jsxRuntime.jsx(Text, { as: "p", variant: "meta", tone: "muted", children: label }),
250
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "gap-step-xs flex flex-col", children: payload.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "gap-step-xs flex items-center", children: [
251
+ /* @__PURE__ */ jsxRuntime.jsx(
252
+ "span",
253
+ {
254
+ "aria-hidden": "true",
255
+ className: "rounded-chip size-2 shrink-0",
256
+ style: { backgroundColor: item.color ?? colorDeSerie(i) }
257
+ }
258
+ ),
259
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { as: "span", variant: "label", tone: "secondary", className: "font-normal", children: item.name }),
260
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { as: "span", variant: "label", className: "ml-auto font-mono tabular-nums", children: formatter ? formatter(item.value, item) : String(item.value ?? "") })
261
+ ] }, `${String(item.dataKey ?? item.name ?? i)}`)) })
262
+ ]
263
+ }
264
+ );
265
+ }
266
+ function ChartLegendContent({ payload, className }) {
267
+ if (!payload || payload.length === 0) return null;
268
+ return /* @__PURE__ */ jsxRuntime.jsx("ul", { className: cn("gap-step-md pt-step-sm flex flex-wrap items-center justify-center", className), children: payload.map((item, i) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "gap-step-xs flex items-center", children: [
269
+ /* @__PURE__ */ jsxRuntime.jsx(
270
+ "span",
271
+ {
272
+ "aria-hidden": "true",
273
+ className: "rounded-chip size-2 shrink-0",
274
+ style: { backgroundColor: item.color ?? colorDeSerie(i) }
275
+ }
276
+ ),
277
+ /* @__PURE__ */ jsxRuntime.jsx(Text, { as: "span", variant: "label", tone: "secondary", className: "font-normal", children: item.value })
278
+ ] }, `${String(item.dataKey ?? item.value ?? i)}`)) });
279
+ }
280
+
281
+ exports.COLORES_DE_SERIE = COLORES_DE_SERIE;
282
+ exports.ChartContainer = ChartContainer;
283
+ exports.ChartLegend = ChartLegend;
284
+ exports.ChartLegendContent = ChartLegendContent;
285
+ exports.ChartTooltip = ChartTooltip;
286
+ exports.ChartTooltipContent = ChartTooltipContent;
287
+ exports.colorDeSerie = colorDeSerie;