@eduardoalvarez/arrecife 0.2.0 → 0.4.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/CHANGELOG.md +40 -0
- package/README.md +124 -2
- package/dist/brand/index.cjs +9 -8
- package/dist/brand/index.js +4 -2
- package/dist/chart/index.cjs +287 -0
- package/dist/chart/index.d.cts +127 -0
- package/dist/chart/index.d.ts +127 -0
- package/dist/chart/index.js +103 -0
- package/dist/chunk-KPZNNMV5.js +83 -0
- package/dist/chunk-MLKGABMK.js +7 -0
- package/dist/chunk-NHS7ETKJ.js +27 -0
- package/dist/{chunk-ZL5X6XO7.js → chunk-TB3IYPOD.js} +4 -25
- package/dist/{chunk-DHF63QNM.js → chunk-TSPJOM6K.js} +10 -12
- package/dist/chunk-UOWIDFCB.js +81 -0
- package/dist/{chunk-22OXRTAL.js → chunk-VPT32GPG.js} +2 -1
- package/dist/chunk-ZEOQKRQ7.js +19 -0
- package/dist/form/index.cjs +301 -0
- package/dist/form/index.d.cts +86 -0
- package/dist/form/index.d.ts +86 -0
- package/dist/form/index.js +84 -0
- package/dist/index.cjs +832 -332
- package/dist/index.d.cts +332 -29
- package/dist/index.d.ts +332 -29
- package/dist/index.js +746 -445
- package/dist/label-DuTvJGxD.d.cts +14 -0
- package/dist/label-DuTvJGxD.d.ts +14 -0
- package/dist/og/index.js +2 -1
- package/dist/shiki/index.js +2 -1
- package/dist/tema/index.cjs +94 -0
- package/dist/tema/index.d.cts +110 -0
- package/dist/tema/index.d.ts +110 -0
- package/dist/tema/index.js +2 -0
- package/dist/tokens/index.cjs +11 -5
- package/dist/tokens/index.d.cts +65 -11
- package/dist/tokens/index.d.ts +65 -11
- package/dist/tokens/index.js +3 -2
- package/dist/tokens/theme.css +21 -5
- package/llms.txt +329 -24
- package/package.json +35 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0](https://github.com/Proskynete/arrecife/compare/v0.3.0...v0.4.0) (2026-08-31)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### 🚀 Novedades
|
|
7
|
+
|
|
8
|
+
* **components:** ScrollingProgressBar y las cinco ranuras que faltaban ([52325cc](https://github.com/Proskynete/arrecife/commit/52325ccff7eedbda39d449c98386668df7a518bf))
|
|
9
|
+
* **form:** la capa de formulario y el chasis de gráficas, en su subruta ([4a40857](https://github.com/Proskynete/arrecife/commit/4a40857e9e9efc8bbe4011bf72f6313347c97541))
|
|
10
|
+
* **primitives:** Accordion, AlertDialog y el Toast imperativo ([239120d](https://github.com/Proskynete/arrecife/commit/239120d510ea395159719581ef69c26734d82441))
|
|
11
|
+
* **tema:** el modo claro y oscuro, sin parpadeo y sin React ([c99995c](https://github.com/Proskynete/arrecife/commit/c99995c8eae6313ece6ffdcf927d9120f6050866))
|
|
12
|
+
* **tokens:** la paleta de series de gráfica, cuatro colores por tono ([b660a30](https://github.com/Proskynete/arrecife/commit/b660a30376c794be82f6a11808d9de175a31d5d9))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🐛 Correcciones
|
|
16
|
+
|
|
17
|
+
* **ci:** check:exports no veía React dentro de un chunk ([3354ab1](https://github.com/Proskynete/arrecife/commit/3354ab12feb867a81480e1c3b612280c40f765ad))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### 📚 Documentación
|
|
21
|
+
|
|
22
|
+
* los dos requisitos de consumo que no dan ningún error ([0a3a7fd](https://github.com/Proskynete/arrecife/commit/0a3a7fd71124aa2dafa3b70df4a90f8fd080544d))
|
|
23
|
+
* main está protegida y así se trabaja con ella ([a625a49](https://github.com/Proskynete/arrecife/commit/a625a499ba871ebf7f13a8f7f0cc9f7fbfff17fc))
|
|
24
|
+
* mergear saltándose la regla se pide, no pasa solo ([c3aca08](https://github.com/Proskynete/arrecife/commit/c3aca08d3926885edea1f500200f0a7496ab13bb))
|
|
25
|
+
|
|
26
|
+
## [0.3.0](https://github.com/Proskynete/arrecife/compare/v0.2.0...v0.3.0) (2026-08-31)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### ⚠ BREAKING CHANGES
|
|
30
|
+
|
|
31
|
+
* **tokens:** los cinco escalones de espaciado se renombran. Los valores no cambian ni un píxel.
|
|
32
|
+
|
|
33
|
+
### 🚀 Novedades
|
|
34
|
+
|
|
35
|
+
* **tokens:** el ritmo de página lleva step, para no comerse max-w-* ([1f8b79d](https://github.com/Proskynete/arrecife/commit/1f8b79d74b6e56b9c716ce2a1fdcee06042d78a0))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### 📚 Documentación
|
|
39
|
+
|
|
40
|
+
* **agents:** cómo se sube la versión, para no editar el CHANGELOG a mano ([b6e3997](https://github.com/Proskynete/arrecife/commit/b6e3997cd8ab20e7e8e28383c1995dd449919cf8))
|
|
41
|
+
* del footer BREAKING CHANGE solo llega el primer párrafo ([807d891](https://github.com/Proskynete/arrecife/commit/807d891d018245a1050d135eb1688449d8ef2c2e))
|
|
42
|
+
|
|
3
43
|
## [0.2.0](https://github.com/Proskynete/arrecife/compare/v0.1.1...v0.2.0) (2026-08-27)
|
|
4
44
|
|
|
5
45
|
|
package/README.md
CHANGED
|
@@ -64,9 +64,105 @@ 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.2.0 o anterior.** Los cinco escalones de espaciado se
|
|
87
|
+
> renombraron: `p-md` es ahora `p-step-md`, `gap-sm` es `gap-step-sm`. Es un
|
|
88
|
+
> cambio incompatible, y si tu proyecto usa `max-w-sm`, `max-w-md` o `max-w-lg`,
|
|
89
|
+
> además te estaban valiendo 12, 16 y 26px sin que nada lo dijera. El porqué, el
|
|
90
|
+
> patrón de migración y qué revisar después están en
|
|
91
|
+
> [`docs/migracion-0.3.md`](docs/migracion-0.3.md).
|
|
92
|
+
|
|
67
93
|
Las familias tipográficas se declaran por nombre. Cada proyecto carga Bricolage
|
|
68
94
|
Grotesque, Geist y JetBrains Mono como prefiera: la librería no impone cómo.
|
|
69
95
|
|
|
96
|
+
**Los nombres tienen que coincidir EXACTAMENTE**, y esto ya ha mordido dos veces.
|
|
97
|
+
Los tokens declaran las familias así:
|
|
98
|
+
|
|
99
|
+
| Token | `font-family` que declara | Utilidad |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `fonts.display` | `"Bricolage Grotesque"` | `font-display` |
|
|
102
|
+
| `fonts.sans` | `"Geist"` | `font-sans` |
|
|
103
|
+
| `fonts.mono` | `"JetBrains Mono"` | `font-mono` |
|
|
104
|
+
|
|
105
|
+
Un proyecto que registre su `@font-face` como `"Bricolage Grotesque Variable"` o
|
|
106
|
+
`"Geist Variable"` —el nombre con el que las publican varios paquetes de fuentes—
|
|
107
|
+
**no** está cargando lo que los tokens piden: la display y la mono caen a la
|
|
108
|
+
fuente del sistema, en silencio y sin un aviso en consola. Es exactamente lo que
|
|
109
|
+
pasó en dos de los cinco proyectos.
|
|
110
|
+
|
|
111
|
+
El `family` del `@font-face` es un alias que elige el proyecto, así que la
|
|
112
|
+
solución es declararlo con el nombre que pide el token:
|
|
113
|
+
|
|
114
|
+
```css
|
|
115
|
+
@font-face {
|
|
116
|
+
font-family: "Bricolage Grotesque"; /* NO "Bricolage Grotesque Variable" */
|
|
117
|
+
src: url("/fuentes/bricolage-grotesque.woff2") format("woff2-variations");
|
|
118
|
+
font-weight: 200 800;
|
|
119
|
+
font-display: swap;
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### En Next, con `next/font`
|
|
124
|
+
|
|
125
|
+
Es el mismo fallo por otra puerta, y muerde a los dos proyectos Next. `next/font`
|
|
126
|
+
registra cada familia bajo un nombre GENERADO —`__Geist_a1b2c3`— y la expone como
|
|
127
|
+
una custom property; el nombre literal `"Geist"` que declaran los tokens no
|
|
128
|
+
existe en ningún `@font-face` de la página.
|
|
129
|
+
|
|
130
|
+
Importar `theme.css` sobrescribe `--font-sans` con ese literal, y las tres
|
|
131
|
+
familias caen a la fuente del sistema. En silencio: no hay 404, porque la fuente
|
|
132
|
+
sí se cargó — con otro nombre.
|
|
133
|
+
|
|
134
|
+
La solución es reafirmar las tres DESPUÉS del import, apuntando a las variables
|
|
135
|
+
que genera `next/font`:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
// app/fuentes.ts
|
|
139
|
+
import { Geist, Bricolage_Grotesque, JetBrains_Mono } from 'next/font/google';
|
|
140
|
+
|
|
141
|
+
export const sans = Geist({ subsets: ['latin'], variable: '--fuente-sans' });
|
|
142
|
+
export const display = Bricolage_Grotesque({ subsets: ['latin'], variable: '--fuente-display' });
|
|
143
|
+
export const mono = JetBrains_Mono({ subsets: ['latin'], variable: '--fuente-mono' });
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```css
|
|
147
|
+
@import "tailwindcss";
|
|
148
|
+
@import "@eduardoalvarez/arrecife/tokens/theme.css";
|
|
149
|
+
@source "../node_modules/@eduardoalvarez/arrecife/dist";
|
|
150
|
+
|
|
151
|
+
/* Después del import, o gana el literal que no está cargado. */
|
|
152
|
+
@theme {
|
|
153
|
+
--font-sans: var(--fuente-sans), ui-sans-serif, system-ui, sans-serif;
|
|
154
|
+
--font-display: var(--fuente-display), ui-sans-serif, system-ui, sans-serif;
|
|
155
|
+
--font-mono: var(--fuente-mono), ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Las variables se llaman `--fuente-*` y no `--font-*` a propósito: `--font-sans`
|
|
160
|
+
es el nombre que Tailwind usa para SU token, y dárselo a `next/font` deja las dos
|
|
161
|
+
capas peleando por la misma propiedad.
|
|
162
|
+
|
|
163
|
+
El `variable` de cada familia va en la clase del `<html>`, como pide Next:
|
|
164
|
+
`className={`${sans.variable} ${display.variable} ${mono.variable}`}`.
|
|
165
|
+
|
|
70
166
|
### Mapa de tokens a utilidades
|
|
71
167
|
|
|
72
168
|
| Token | Custom property | Utilidad |
|
|
@@ -76,7 +172,8 @@ Grotesque, Geist y JetBrains Mono como prefiera: la librería no impone cómo.
|
|
|
76
172
|
| `typeScale.h1` | `--text-h1` | `text-h1` (arrastra line-height, weight y tracking) |
|
|
77
173
|
| `fonts.display` | `--font-display` | `font-display` |
|
|
78
174
|
| `radius.card` | `--radius-card` | `rounded-card` |
|
|
79
|
-
| `spacing.
|
|
175
|
+
| `spacing.stepLg` | `--spacing-step-lg` | `p-step-lg`, `gap-step-lg`, `mb-step-lg` |
|
|
176
|
+
| `spacing.section` | `--spacing-section` | `py-section`, `mb-section` |
|
|
80
177
|
| `control.md` | `--spacing-control-md` | `px-control-md` (padding de botón) |
|
|
81
178
|
| `control.icon` | `--spacing-control-icon` | `size-control-icon` (botón de icono 42×42) |
|
|
82
179
|
| `gradient[modo].hero` | `--gradient-hero` | `degradado-hero` (utilidad, sigue el modo) |
|
|
@@ -156,6 +253,31 @@ Comprobado empaquetando la librería con `pnpm pack` e instalándola en un proye
|
|
|
156
253
|
aparte: los tipos resuelven desde `dist/`, `./tokens` carga sin arrastrar React y
|
|
157
254
|
`./tokens/theme.css` se resuelve por subruta.
|
|
158
255
|
|
|
256
|
+
### Las dos subrutas que piden una dependencia
|
|
257
|
+
|
|
258
|
+
`./form` y `./chart` no cuelgan de la raíz, y es a propósito. Cada una pide una
|
|
259
|
+
dependencia de pares **opcional** —`react-hook-form` y `recharts`—, y colgarlas
|
|
260
|
+
del índice principal obligaría a los cinco proyectos a instalarlas para que su
|
|
261
|
+
bundler resolviera un import que cuatro de ellos nunca ejecutan.
|
|
262
|
+
|
|
263
|
+
Es la misma decisión que `./og` y `./shiki`, mirada desde el otro lado: allí se
|
|
264
|
+
saca React del camino de quien no lo monta; aquí se saca Recharts del camino de
|
|
265
|
+
quien no dibuja.
|
|
266
|
+
|
|
267
|
+
```tsx
|
|
268
|
+
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage }
|
|
269
|
+
from '@eduardoalvarez/arrecife/form';
|
|
270
|
+
|
|
271
|
+
import { ChartContainer, ChartTooltip, ChartTooltipContent, colorDeSerie }
|
|
272
|
+
from '@eduardoalvarez/arrecife/chart';
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
`check:exports` verifica que las cuatro portables —`./tokens`, `./tema`, `./og` y
|
|
276
|
+
`./shiki`— no traen React en el `dist/` publicado, **siguiendo los imports
|
|
277
|
+
relativos**. Sin eso el check era papel mojado: con `treeshake` activo, cada
|
|
278
|
+
entrada portable queda en dos líneas que reexportan de un `chunk-XXXX.js`, y un
|
|
279
|
+
grep sobre esas dos líneas no encuentra React ni aunque el chunk lo importe.
|
|
280
|
+
|
|
159
281
|
## Scripts
|
|
160
282
|
|
|
161
283
|
| | |
|
|
@@ -164,7 +286,7 @@ aparte: los tipos resuelven desde `dist/`, `./tokens` carga sin arrastrar React
|
|
|
164
286
|
| `pnpm typecheck` | `tsc --noEmit` |
|
|
165
287
|
| `pnpm lint` | ESLint, incluido el veto a hex literales fuera de `tokens.ts` |
|
|
166
288
|
| `pnpm check:tokens` | falla si `src/tokens/` importa algo de fuera |
|
|
167
|
-
| `pnpm test` | corre axe sobre las
|
|
289
|
+
| `pnpm test` | compila Tailwind y corre axe sobre las 193 stories, en los dos modos |
|
|
168
290
|
| `pnpm check:exports` | verifica que `dist/` tiene lo que `exports` promete |
|
|
169
291
|
| `pnpm check:release` | valida `release-please-config.json` contra el esquema oficial |
|
|
170
292
|
| `pnpm storybook` | genera los tokens y levanta Storybook en el 6006 |
|
package/dist/brand/index.cjs
CHANGED
|
@@ -109,21 +109,22 @@ var control = {
|
|
|
109
109
|
icon: 42
|
|
110
110
|
};
|
|
111
111
|
var spacing = {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
stepXs: 8,
|
|
113
|
+
stepSm: 12,
|
|
114
|
+
stepMd: 16,
|
|
115
|
+
stepLg: 26,
|
|
116
|
+
stepXl: 40,
|
|
117
117
|
section: 96
|
|
118
118
|
};
|
|
119
119
|
var naming = {
|
|
120
120
|
wordmark: "Eduardo \xC1lvarez"};
|
|
121
121
|
|
|
122
122
|
// src/lib/cn.ts
|
|
123
|
+
var kebab = (nombre) => nombre.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
123
124
|
var escalas = Object.keys(typeScale);
|
|
124
125
|
var radios = Object.keys(radius);
|
|
125
|
-
var espacios = Object.keys(spacing);
|
|
126
|
-
var controles = Object.keys(control).map((nombre) => `control-${nombre}`);
|
|
126
|
+
var espacios = Object.keys(spacing).map(kebab);
|
|
127
|
+
var controles = Object.keys(control).map((nombre) => `control-${kebab(nombre)}`);
|
|
127
128
|
var twMerge = tailwindMerge.extendTailwindMerge({
|
|
128
129
|
extend: {
|
|
129
130
|
classGroups: {
|
|
@@ -167,7 +168,7 @@ function Logo({
|
|
|
167
168
|
className,
|
|
168
169
|
...props
|
|
169
170
|
}) {
|
|
170
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("gap-xs inline-flex items-center", className), ...props, children: [
|
|
171
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children: [
|
|
171
172
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
172
173
|
Isotipo,
|
|
173
174
|
{
|
package/dist/brand/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export { CaraDeMascota, Isotipo, Logo, Mascota } from '../chunk-
|
|
1
|
+
export { CaraDeMascota, Isotipo, Logo, Mascota } from '../chunk-TB3IYPOD.js';
|
|
2
2
|
export { RUTA_ASSETS, aletas, caras, listaCaras, listaPoses, poses, usoDeCara } from '../chunk-E3OMP2DL.js';
|
|
3
|
-
import '../chunk-
|
|
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;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef, ReactNode } from 'react';
|
|
3
|
+
import { Legend, Tooltip } from 'recharts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* El chasis de las gráficas: contenedor, tooltip y leyenda con el vocabulario
|
|
7
|
+
* del sistema. Recharts pinta; esto decide cómo se ve.
|
|
8
|
+
*
|
|
9
|
+
* Se publica en `@eduardoalvarez/arrecife/chart` y NO en la raíz. Recharts pesa,
|
|
10
|
+
* y solo uno de los cinco proyectos dibuja métricas: colgarlo del índice
|
|
11
|
+
* principal obligaría a los otros cuatro a instalarlo para que su bundler
|
|
12
|
+
* resolviera un import que nunca ejecutan. Es dependencia de pares OPCIONAL, por
|
|
13
|
+
* el mismo criterio con el que `./og` y `./shiki` se mantienen sin React.
|
|
14
|
+
*
|
|
15
|
+
* Lo que esta capa aporta —y lo que se perdía copiando el `chart.tsx` de shadcn
|
|
16
|
+
* en cada proyecto— son tres cosas que se desincronizan solas:
|
|
17
|
+
*
|
|
18
|
+
* 1. La paleta de series sale de `tokens.series` y sigue el modo. Son cuatro
|
|
19
|
+
* y se distinguen por tono, no por luminosidad.
|
|
20
|
+
* 2. La rejilla, los ejes y el cursor usan `hairline` y `textMuted`, no los
|
|
21
|
+
* grises por defecto de Recharts, que no son de esta identidad.
|
|
22
|
+
* 3. El tooltip aparece donde va a quedarse: `isAnimationActive` apagado en
|
|
23
|
+
* todas partes, porque el sistema no anima posición.
|
|
24
|
+
*
|
|
25
|
+
* Las piezas de datos —`BarChart`, `Line`, `XAxis`— NO se reexportan. Son la API
|
|
26
|
+
* de Recharts, el proyecto ya la conoce y envolverlas sería una capa de nombres
|
|
27
|
+
* que hay que mantener sincronizada con la versión de la librería.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* El color de la serie `indice`, como custom property.
|
|
31
|
+
*
|
|
32
|
+
* Devuelve `var(--color-serie-N)` y no el hexadecimal: leído en JS, el hex sería
|
|
33
|
+
* el del modo que había cuando se montó el componente y no cambiaría al alternar
|
|
34
|
+
* el tema. La variable la resuelve el navegador en cada pintura.
|
|
35
|
+
*
|
|
36
|
+
* Da la vuelta pasadas las cuatro. Que dos series compartan color es un fallo
|
|
37
|
+
* visible, y es la señal correcta: la gráfica tiene más categorías de las que
|
|
38
|
+
* esta identidad sabe distinguir, y lo que toca es agrupar en «otros».
|
|
39
|
+
*/
|
|
40
|
+
declare function colorDeSerie(indice: number): string;
|
|
41
|
+
/** Las cuatro, en orden, para pasárselas de golpe a un `Pie` con `Cell`. */
|
|
42
|
+
declare const COLORES_DE_SERIE: string[];
|
|
43
|
+
type ChartContainerProps = Omit<ComponentPropsWithoutRef<'figure'>, 'title'> & {
|
|
44
|
+
/**
|
|
45
|
+
* Qué muestra la gráfica, en una frase. Obligatorio, como el `label` de
|
|
46
|
+
* `Progress`: un `<svg>` de barras sin nombre accesible no es «una gráfica sin
|
|
47
|
+
* etiqueta», es una región vacía.
|
|
48
|
+
*/
|
|
49
|
+
label: string;
|
|
50
|
+
/**
|
|
51
|
+
* Lo que la gráfica dice, en palabras. Va en un `figcaption` oculto
|
|
52
|
+
* visualmente.
|
|
53
|
+
*
|
|
54
|
+
* No sustituye a la gráfica: la capa de accesibilidad de Recharts ya permite
|
|
55
|
+
* recorrer los puntos con el teclado. Es el titular —«sube de 24 a 52 con una
|
|
56
|
+
* caída en mayo»— que ningún recorrido punto a punto da.
|
|
57
|
+
*/
|
|
58
|
+
summary?: ReactNode;
|
|
59
|
+
/** Alto en píxeles. Recharts necesita uno concreto para medir. */
|
|
60
|
+
height?: number;
|
|
61
|
+
children: ReactNode;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Envuelve la gráfica en un `<figure>` con nombre accesible y le da a Recharts
|
|
65
|
+
* el alto concreto que necesita para medirse.
|
|
66
|
+
*
|
|
67
|
+
* El `<figure>` NO lleva `role`: ya tiene el suyo implícito, y ponerle `group`
|
|
68
|
+
* encima es un rol no permitido para el elemento —axe lo señala—. Lo que sí
|
|
69
|
+
* lleva es `aria-label`, que es lo que lo nombra.
|
|
70
|
+
*
|
|
71
|
+
* Y el contenido NO va `aria-hidden`. Fue lo primero que se intentó, con el
|
|
72
|
+
* argumento de que anunciar cada tick no cuenta lo que la gráfica cuenta, y está
|
|
73
|
+
* mal por dos motivos: la capa de accesibilidad de Recharts hace la gráfica
|
|
74
|
+
* recorrible con el teclado, y esconder un subárbol que contiene elementos
|
|
75
|
+
* enfocables es una violación por sí misma —el foco entra en algo que no existe
|
|
76
|
+
* para quien escucha—. El resumen se suma a eso, no lo reemplaza.
|
|
77
|
+
*/
|
|
78
|
+
declare function ChartContainer({ label, summary, height, className, children, ...props }: ChartContainerProps): react.JSX.Element;
|
|
79
|
+
/**
|
|
80
|
+
* El `Tooltip` de Recharts con los defectos del sistema: sin animación y con el
|
|
81
|
+
* cursor teñido de `surfaceRaised`.
|
|
82
|
+
*
|
|
83
|
+
* Se pasa `content={<ChartTooltipContent />}` para la caja.
|
|
84
|
+
*/
|
|
85
|
+
declare function ChartTooltip(props: ComponentPropsWithoutRef<typeof Tooltip>): react.JSX.Element;
|
|
86
|
+
declare function ChartLegend(props: ComponentPropsWithoutRef<typeof Legend>): react.JSX.Element;
|
|
87
|
+
/**
|
|
88
|
+
* Lo que Recharts le pasa a un `content`, declarado aquí en corto.
|
|
89
|
+
*
|
|
90
|
+
* Los tipos de Recharts para esto son abiertos y arrastran genéricos que no
|
|
91
|
+
* aportan nada al sitio de uso; `any` está prohibido en el repo, así que se
|
|
92
|
+
* declara lo que de verdad se lee y el valor se trata como `unknown`.
|
|
93
|
+
*/
|
|
94
|
+
type ChartPayloadItem = {
|
|
95
|
+
name?: string | number | undefined;
|
|
96
|
+
value?: unknown;
|
|
97
|
+
color?: string | undefined;
|
|
98
|
+
dataKey?: string | number | undefined;
|
|
99
|
+
};
|
|
100
|
+
type ChartTooltipContentProps = {
|
|
101
|
+
active?: boolean | undefined;
|
|
102
|
+
payload?: readonly ChartPayloadItem[] | undefined;
|
|
103
|
+
label?: ReactNode;
|
|
104
|
+
/** Formatea el valor. Sin ella se imprime tal cual: la librería no impone locale. */
|
|
105
|
+
formatter?: ((valor: unknown, item: ChartPayloadItem) => ReactNode) | undefined;
|
|
106
|
+
/** Oculta el encabezado, para una gráfica de una sola categoría. */
|
|
107
|
+
hideLabel?: boolean;
|
|
108
|
+
className?: string;
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* La caja del tooltip. Es una tarjeta del sistema —`surface`, borde de control,
|
|
112
|
+
* sombra estándar— y no la caja blanca de Recharts, que en modo oscuro es un
|
|
113
|
+
* rectángulo blanco encima de un panel oscuro.
|
|
114
|
+
*
|
|
115
|
+
* El punto de color es un cuadrado de 8px, no un círculo: es la misma marca que
|
|
116
|
+
* usa la leyenda, y en 8 píxeles un círculo y un cuadrado se distinguen peor
|
|
117
|
+
* entre sí que dos colores de la paleta.
|
|
118
|
+
*/
|
|
119
|
+
declare function ChartTooltipContent({ active, payload, label, formatter, hideLabel, className, }: ChartTooltipContentProps): react.JSX.Element | null;
|
|
120
|
+
type ChartLegendContentProps = {
|
|
121
|
+
payload?: readonly ChartPayloadItem[] | undefined;
|
|
122
|
+
className?: string;
|
|
123
|
+
};
|
|
124
|
+
/** La leyenda con la misma marca cuadrada del tooltip y la escala `label`. */
|
|
125
|
+
declare function ChartLegendContent({ payload, className }: ChartLegendContentProps): react.JSX.Element | null;
|
|
126
|
+
|
|
127
|
+
export { COLORES_DE_SERIE, ChartContainer, type ChartContainerProps, ChartLegend, ChartLegendContent, type ChartLegendContentProps, type ChartPayloadItem, ChartTooltip, ChartTooltipContent, type ChartTooltipContentProps, colorDeSerie };
|