@eduardoalvarez/arrecife 0.5.1 → 0.6.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 (66) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +706 -470
  3. package/dist/brand/index.cjs +112 -95
  4. package/dist/brand/index.d.cts +40 -39
  5. package/dist/brand/index.d.ts +40 -39
  6. package/dist/brand/index.js +5 -4
  7. package/dist/catalog-D13txprv.d.cts +78 -0
  8. package/dist/catalog-D13txprv.d.ts +78 -0
  9. package/dist/chart/index.cjs +100 -83
  10. package/dist/chart/index.d.cts +66 -66
  11. package/dist/chart/index.d.ts +66 -66
  12. package/dist/chart/index.js +14 -12
  13. package/dist/chunk-25YNFCIF.js +141 -0
  14. package/dist/{chunk-YZ2SDOVZ.js → chunk-6O3KWB6P.js} +30 -30
  15. package/dist/chunk-CKRSQPTX.js +36 -0
  16. package/dist/{chunk-ZEOQKRQ7.js → chunk-DKCN7BAL.js} +1 -1
  17. package/dist/chunk-GCRII2KQ.js +86 -0
  18. package/dist/chunk-JMOOFZ3B.js +42 -0
  19. package/dist/chunk-O4TAH7YJ.js +276 -0
  20. package/dist/chunk-ODBFN44D.js +45 -0
  21. package/dist/{chunk-VPT32GPG.js → chunk-PMN7NR3G.js} +2 -2
  22. package/dist/chunk-XKYHTOUJ.js +27 -0
  23. package/dist/form/index.cjs +109 -92
  24. package/dist/form/index.d.cts +43 -42
  25. package/dist/form/index.d.ts +43 -42
  26. package/dist/form/index.js +25 -23
  27. package/dist/index.cjs +1047 -914
  28. package/dist/index.d.cts +769 -777
  29. package/dist/index.d.ts +769 -777
  30. package/dist/index.js +608 -660
  31. package/dist/{label-DuTvJGxD.d.ts → label-MgHFKnFy.d.cts} +3 -3
  32. package/dist/{label-DuTvJGxD.d.cts → label-MgHFKnFy.d.ts} +3 -3
  33. package/dist/og/index.cjs +130 -130
  34. package/dist/og/index.d.cts +93 -89
  35. package/dist/og/index.d.ts +93 -89
  36. package/dist/og/index.js +106 -106
  37. package/dist/shiki/index.cjs +28 -30
  38. package/dist/shiki/index.d.cts +4 -4
  39. package/dist/shiki/index.d.ts +4 -4
  40. package/dist/shiki/index.js +12 -12
  41. package/dist/theme/index.cjs +97 -0
  42. package/dist/theme/index.d.cts +144 -0
  43. package/dist/theme/index.d.ts +144 -0
  44. package/dist/theme/index.js +2 -0
  45. package/dist/tokens/index.cjs +133 -86
  46. package/dist/tokens/index.d.cts +246 -161
  47. package/dist/tokens/index.d.ts +246 -161
  48. package/dist/tokens/index.js +2 -2
  49. package/dist/tokens/theme.css +133 -98
  50. package/dist/variants/index.cjs +192 -0
  51. package/dist/variants/index.d.cts +192 -0
  52. package/dist/variants/index.d.ts +192 -0
  53. package/dist/variants/index.js +3 -0
  54. package/llms.txt +810 -744
  55. package/package.json +20 -11
  56. package/dist/catalogo-Du5ID-Hi.d.cts +0 -77
  57. package/dist/catalogo-Du5ID-Hi.d.ts +0 -77
  58. package/dist/chunk-E3OMP2DL.js +0 -36
  59. package/dist/chunk-KPZNNMV5.js +0 -83
  60. package/dist/chunk-NHS7ETKJ.js +0 -27
  61. package/dist/chunk-TSPJOM6K.js +0 -229
  62. package/dist/chunk-UOWIDFCB.js +0 -81
  63. package/dist/tema/index.cjs +0 -94
  64. package/dist/tema/index.d.cts +0 -110
  65. package/dist/tema/index.d.ts +0 -110
  66. package/dist/tema/index.js +0 -2
package/llms.txt CHANGED
@@ -1,166 +1,211 @@
1
1
  # @eduardoalvarez/arrecife
2
2
 
3
- > GENERADO por `scripts/build-llms.mjs`. No lo edites a mano: la prosa está en
4
- > `docs/llms.plantilla.md` y el inventario sale de los tipos en cada build.
5
- > `pnpm check:llms` falla si este archivo y el código dejan de decir lo mismo.
3
+ > GENERATED by `scripts/build-llms.mjs`. Do not edit by hand: the prose lives in
4
+ > `docs/llms.template.md` and the inventory comes out of the types on every
5
+ > build. `pnpm check:llms` fails if this file and the code stop saying the same
6
+ > thing.
6
7
 
7
- Librería de componentes de la identidad visual de Eduardo Álvarez. React 19,
8
- TypeScript, Tailwind v4, shadcn/ui sobre Radix.
8
+ The component library of Eduardo Álvarez's visual identity. React 19,
9
+ TypeScript, Tailwind v4, shadcn/ui on top of Radix.
9
10
 
10
- Este documento es para un agente que escribe código en un proyecto que consume la
11
- librería. Si estás trabajando **dentro** del repo de Arrecife, el documento es
12
- `AGENTS.md`, no este.
11
+ This document is for an agent writing code in a project that consumes the
12
+ library. If you are working **inside** the Arrecife repo, the document is
13
+ `AGENTS.md`, not this one.
13
14
 
14
- ## Lo primero: no reimplementes lo que ya está aquí
15
+ ## First things first: do not reimplement what is already here
15
16
 
16
- Antes de escribir una tarjeta, un botón, una cabecera o un pie, busca en el
17
- inventario de más abajo. La librería existe porque cinco proyectos escribían las
18
- mismas piezas cada uno por su cuenta y se desincronizaban. Un componente nuevo
19
- escrito a mano en el proyecto consumidor reintroduce exactamente ese problema.
17
+ Before writing a card, a button, a header or a footer, look through the inventory
18
+ below. The library exists because five projects were each writing the same pieces
19
+ on their own and drifting apart. A new component hand-written in the consuming
20
+ project reintroduces exactly that problem.
20
21
 
21
- Tampoco escribas colores, tamaños ni espaciados a mano. Todo valor del sistema
22
- tiene un token y una utilidad de Tailwind; un `#hex` o un `p-[13px]` en el
23
- proyecto consumidor es la señal de que se eligió el camino equivocado.
22
+ Do not hand-write colors, sizes or spacing either. Every value in the system has
23
+ a token and a Tailwind utility; a `#hex` or a `p-[13px]` in the consuming project
24
+ is the sign that the wrong path was taken.
24
25
 
25
- ## Instalación
26
+ ## Installation
26
27
 
27
28
  ```bash
28
29
  pnpm add @eduardoalvarez/arrecife
29
30
  ```
30
31
 
31
- Requisitos, y no son opcionales:
32
+ Requirements, and they are not optional:
32
33
 
33
34
  | | |
34
35
  | --- | --- |
35
- | React | `^19.0.0` y `react-dom` `^19.0.0`, como peer dependencies |
36
- | Tailwind | v4. **No hay preset de v3**: la salida es `@theme`, que v3 no entiende |
37
- | Node | `>=22.18.0` para las subrutas que corren en build (`./og`, `./tokens`) |
36
+ | React | `^19.0.0` and `react-dom` `^19.0.0`, as peer dependencies |
37
+ | Tailwind | v4. **There is no v3 preset**: the output is `@theme`, which v3 does not understand |
38
+ | Node | `>=22.18.0` for the subpaths that run at build time (`./og`, `./tokens`) |
38
39
 
39
- Radix, `clsx`, `tailwind-merge`, `class-variance-authority`, `date-fns` y
40
- `react-day-picker` vienen como dependencias de la librería. No hace falta
41
- instalarlos ni declararlos.
40
+ Radix, `clsx`, `tailwind-merge`, `class-variance-authority`, `date-fns` and
41
+ `react-day-picker` come as dependencies of the library. You do not need to
42
+ install or declare them.
42
43
 
43
- **No lleva `lucide-react` ni ninguna librería de iconos.** Los glifos que los
44
- componentes necesitan van inline, heredan `currentColor` y miden 1em.
44
+ **It ships no `lucide-react` and no icon library.** The glyphs the components
45
+ need are inline, inherit `currentColor` and measure 1em.
45
46
 
46
- ## Configuración de Tailwind
47
+ ## Tailwind configuration
47
48
 
48
- Dos líneas, en este orden, en la hoja de estilos de entrada del proyecto:
49
+ Two lines, in this order, in the project's entry stylesheet:
49
50
 
50
51
  ```css
51
52
  @import "tailwindcss";
52
53
  @import "@eduardoalvarez/arrecife/tokens/theme.css";
53
54
  ```
54
55
 
55
- Sin la segunda, los componentes se montan sin ningún estilo del sistema: las
56
- clases que usan (`bg-surface-raised`, `text-h1`, `rounded-card`) no existen en un
57
- Tailwind pelado.
56
+ Without the second one, the components mount with none of the system's styles:
57
+ the classes they use (`bg-surface-raised`, `text-h1`, `rounded-card`) do not
58
+ exist in a bare Tailwind.
58
59
 
59
- Tailwind tiene que escanear la librería para no purgar esas clases. Si el
60
- proyecto declara `@source`, incluye el paquete:
60
+ Tailwind has to scan the library so it does not purge those classes. If the
61
+ project declares `@source`, include the package:
61
62
 
62
63
  ```css
63
64
  @source "../node_modules/@eduardoalvarez/arrecife/dist";
64
65
  ```
65
66
 
66
- ### Modo claro y modo oscuro
67
+ ### Light mode and dark mode
67
68
 
68
- **El modo oscuro es el primario y es el default.** Un proyecto oscuro no declara
69
- nada. Un proyecto en modo claro declara el atributo en `<html>`:
69
+ **Dark mode is primary and it is the default.** A dark project declares nothing.
70
+ A project in light mode declares the attribute on `<html>`:
70
71
 
71
72
  ```html
72
73
  <html data-theme="light">
73
74
  ```
74
75
 
75
- No hay clase `dark:`. La variante disponible es `light:`, para los casos del modo
76
- claro invertido, y casi nunca hace falta: los tokens ya cambian solos.
76
+ There is no `dark:` class. The variant available is `light:`, for the cases of
77
+ inverted light mode, and it is almost never needed: the tokens already switch on
78
+ their own.
77
79
 
78
- ### Fuentes
80
+ ### Fonts
79
81
 
80
- La librería declara las familias **por nombre** y no las carga. El proyecto carga
81
- Bricolage Grotesque (`font-display`), Geist (`font-sans`) y JetBrains Mono
82
- (`font-mono`) como prefiera —`next/font`, `@fontsource`, un `<link>`—. Si no las
83
- carga, el navegador cae al fallback y la tipografía se ve mal.
82
+ The library declares the families **by name** and does not load them. The project
83
+ loads Bricolage Grotesque (`font-display`), Geist (`font-sans`) and JetBrains
84
+ Mono (`font-mono`) however it prefers — `next/font`, `@fontsource`, a `<link>`.
85
+ If it does not load them, the browser falls back and the typography looks wrong.
84
86
 
85
- El nombre tiene que coincidir **exactamente** con el que declaran los tokens, y
86
- esto ya ha fallado en dos proyectos:
87
+ The name has to match **exactly** the one the tokens declare, and this has
88
+ already failed in two projects:
87
89
 
88
- | Utilidad | Nombre que pide el token |
90
+ | Utility | Name the token asks for |
89
91
  | --- | --- |
90
92
  | `font-display` | `"Bricolage Grotesque"` |
91
93
  | `font-sans` | `"Geist"` |
92
94
  | `font-mono` | `"JetBrains Mono"` |
93
95
 
94
- Varios paquetes de fuentes las publican como `"Bricolage Grotesque Variable"` o
95
- `"Geist Variable"`. Registrar el `@font-face` con ese nombre NO carga lo que los
96
- tokens piden: la familia cae al sistema en silencio, sin error en consola. El
97
- `font-family` del `@font-face` es un alias que elige el proyecto, así que se
98
- escribe con el nombre de la tabla.
96
+ Several font packages publish them as `"Bricolage Grotesque Variable"` or
97
+ `"Geist Variable"`. Registering the `@font-face` under that name does NOT load
98
+ what the tokens ask for: the family falls back to the system silently, with no
99
+ console error. The `@font-face`'s `font-family` is an alias the project chooses,
100
+ so write it with the name from the table.
99
101
 
100
- ## Qué importar de dónde
102
+ ## What to import from where
101
103
 
102
- La elección importa, y por dos motivos distintos. Cuatro subrutas **no arrastran
103
- React**, así que pueden consumirse desde un worker, un `astro.config.mjs`, un
104
- script de build o un generador con Satori. Otras dos piden una **dependencia de
105
- pares opcional** que solo instala quien las use.
104
+ The choice matters, for two different reasons. Four subpaths **do not drag React
105
+ in**, so they can be consumed from a worker, an `astro.config.mjs`, a build
106
+ script or a Satori generator. Another two ask for an **optional peer dependency**
107
+ that only whoever uses them installs.
106
108
 
107
- | Subruta | Arrastra React | Pide además | Para qué |
109
+ | Subpath | Drags React | Also requires | What for |
108
110
  | --- | --- | --- | --- |
109
- | `@eduardoalvarez/arrecife` | sí | — | Componentes, primitivos, marca, `cn`. Reexporta tokens y tema |
110
- | `@eduardoalvarez/arrecife/tokens` | **no** | — | El objeto `tokens` en JS. Satori, Astro, scripts |
111
- | `@eduardoalvarez/arrecife/tokens/theme.css` | — | — | El `@theme` de Tailwind v4 |
112
- | `@eduardoalvarez/arrecife/tema` | **no** | — | `scriptTema` para el `<head>`, y leer o cambiar el modo |
113
- | `@eduardoalvarez/arrecife/og` | **no** | — | Las plantillas de Open Graph para Satori |
114
- | `@eduardoalvarez/arrecife/shiki` | **no** | — | El tema de resaltado de sintaxis |
115
- | `@eduardoalvarez/arrecife/brand` | sí | — | Logo, isotipo y mascota como componentes |
116
- | `@eduardoalvarez/arrecife/form` | sí | `react-hook-form` | La capa de formulario: etiquetas, errores y `aria-*` |
117
- | `@eduardoalvarez/arrecife/chart` | sí | `recharts` | El chasis de las gráficas y la paleta de series |
118
- | `@eduardoalvarez/arrecife/assets/*` | — | — | Los PNG de la marca |
119
-
120
- Importar la raíz desde un script de build para sacar un token es el error que las
121
- subrutas existen para evitar: arrastra React entero a un worker que no lo monta.
122
-
123
- `./form` y `./chart` están fuera de la raíz por el motivo simétrico: si colgaran
124
- del índice principal, los proyectos que no dibujan gráficas ni usan React Hook
125
- Form tendrían que instalar esas dependencias igualmente para que su bundler
126
- resolviera un import que nunca ejecutan.
111
+ | `@eduardoalvarez/arrecife` | yes | — | Components, primitives, brand, `cn`. Re-exports tokens and theme |
112
+ | `@eduardoalvarez/arrecife/tokens` | **no** | — | The `tokens` object in JS. Satori, Astro, scripts |
113
+ | `@eduardoalvarez/arrecife/tokens/theme.css` | — | — | Tailwind v4's `@theme` |
114
+ | `@eduardoalvarez/arrecife/theme` | **no** | — | `themeScript` for the `<head>`, and reading or changing the mode |
115
+ | `@eduardoalvarez/arrecife/variants` | **no** | — | The class vocabulary: `buttonVariants`, `badgeVariants`, `CARD_SURFACE` |
116
+ | `@eduardoalvarez/arrecife/og` | **no** | — | The Open Graph templates for Satori |
117
+ | `@eduardoalvarez/arrecife/shiki` | **no** | — | The syntax highlighting theme |
118
+ | `@eduardoalvarez/arrecife/brand` | yes | — | Logo, isotype and mascot as components |
119
+ | `@eduardoalvarez/arrecife/form` | yes | `react-hook-form` | The form layer: labels, errors and `aria-*` |
120
+ | `@eduardoalvarez/arrecife/chart` | yes | `recharts` | The chart chassis and the series palette |
121
+ | `@eduardoalvarez/arrecife/assets/*` | — | — | The brand PNGs |
122
+
123
+ Importing the root from a build script to get one token is the mistake the
124
+ subpaths exist to prevent: it drags all of React into a worker that never mounts
125
+ it.
126
+
127
+ `./form` and `./chart` sit outside the root for the symmetric reason: if they
128
+ hung off the main index, the projects that draw no charts and use no React Hook
129
+ Form would have to install those dependencies anyway so their bundler could
130
+ resolve an import they never execute.
131
+
132
+ ### Next, Server Components and `"use client"`
133
+
134
+ The root, `./brand`, `./form` and `./chart` ship `"use client"` in the published
135
+ `dist/`. They render React and their Radix primitives call `createContext` at
136
+ module scope, so without the directive a Next project with the App Router cannot
137
+ import them at all: it fails at build time with
138
+ `TypeError: (0 , r.createContext) is not a function`.
139
+
140
+ You do not add anything: importing `Button` from a Server Component works, and
141
+ the boundary is already where it belongs. What you should NOT do is wrap the
142
+ import in an adapter of your own marked `"use client"` — that was the workaround
143
+ before 0.6.0 and it pulled 272 KB of client chunk in for components that never
144
+ needed it.
145
+
146
+ The five portable subpaths do NOT carry the directive, and that is the half that
147
+ matters in a Server Component: `./tokens`, `./theme`, `./variants`, `./og` and
148
+ `./shiki` stay on the server. If all you need are classes — for a `<div>`, an
149
+ `<a>` or an Astro island you do not want to hydrate — import them from
150
+ `./variants` and nothing crosses to the client:
151
+
152
+ ```tsx
153
+ // A Server Component, or an .astro frontmatter. No React reaches the browser.
154
+ import { buttonVariants, CARD_SURFACE } from '@eduardoalvarez/arrecife/variants';
155
+
156
+ <a className={buttonVariants({ variant: 'tertiary' })} href="/cursos">./ver_cursos →</a>
157
+ <div className={CARD_SURFACE}>…</div>
158
+ ```
159
+
160
+ In Astro and in plain Vite the directive is inert — a string literal at the top
161
+ of a module. Rollup may warn `Module level directives cause errors when bundled`
162
+ and nothing else happens: one `dist` serves the Next projects and the Astro ones.
127
163
 
128
164
  ```ts
129
- // Bien, en un generador de OG o en astro.config.mjs
165
+ // Good, in an OG generator or in astro.config.mjs
130
166
  import { tokens } from '@eduardoalvarez/arrecife/tokens';
131
167
  import { arrecife } from '@eduardoalvarez/arrecife/shiki';
132
168
 
133
- // Bien, en el <head> de un Astro que no monta React
134
- import { scriptTema } from '@eduardoalvarez/arrecife/tema';
169
+ // Good, in the <head> of an Astro that mounts no React
170
+ import { themeScript } from '@eduardoalvarez/arrecife/theme';
135
171
 
136
- // Mal: monta React donde no hace falta
172
+ // Bad: mounts React where it is not needed
137
173
  import { tokens } from '@eduardoalvarez/arrecife';
138
174
  ```
139
175
 
140
- ### El tema, y el parpadeo de la primera pintura
176
+ ### The theme, and the first-paint flash
141
177
 
142
- `ThemeToggle` es el botón; lo difícil está en `./tema`. Sin `scriptTema` inline en
143
- el `<head>`, la primera pintura sale con el modo por defecto y el elegido entra
144
- un frame después: en un sitio oscuro que el usuario dejó en claro, eso es un
145
- fogonazo blanco en cada carga.
178
+ `ThemeToggle` is the button; the hard part is in `./theme`. Without `themeScript`
179
+ inline in the `<head>`, the first paint comes out in the default mode and the
180
+ chosen one arrives a frame later: on a dark site the user left in light mode,
181
+ that is a white flash on every load.
146
182
 
147
183
  ```astro
148
184
  ---
149
- import { scriptTema } from '@eduardoalvarez/arrecife/tema';
185
+ import { themeScript } from '@eduardoalvarez/arrecife/theme';
150
186
  ---
151
187
  <head>
152
- <script is:inline set:html={scriptTema} />
188
+ <!-- The site follows the reader's OS, falling back to dark. -->
189
+ <script is:inline set:html={themeScript()} />
190
+
191
+ <!-- Or: this site IS dark, and the OS is not consulted. -->
192
+ <script is:inline set:html={themeScript({ base: 'dark' })} />
153
193
  </head>
154
194
  ```
155
195
 
156
- Tiene que ir INLINE. Un `<script src>`, aunque sea síncrono, se descarga, y el
157
- parpadeo vuelve. El script reengancha en `astro:after-swap` porque las
158
- transiciones de vista reemplazan el `<html>` entero.
196
+ `base` is not «the fallback», it is «this site IS this mode». A stored choice
197
+ still wins over it, so the toggle keeps working — it sets what happens when
198
+ nobody has chosen yet. Use it when the project has already decided its mode and
199
+ does not want the OS overruling that.
159
200
 
160
- ### Los iconos de redes van agrupados
201
+ It has to go INLINE. A `<script src>`, even a synchronous one, gets downloaded,
202
+ and the flash comes back. The script re-attaches on `astro:after-swap` because
203
+ view transitions replace the whole `<html>`.
204
+
205
+ ### The social icons are namespaced
161
206
 
162
207
  ```tsx
163
- // ❌ no existe
208
+ // ❌ does not exist
164
209
  import { GitHub } from '@eduardoalvarez/arrecife';
165
210
 
166
211
  // ✅
@@ -168,95 +213,107 @@ import { social } from '@eduardoalvarez/arrecife';
168
213
  <social.GitHub />
169
214
  ```
170
215
 
171
- Los ocho: `GitHub`, `LinkedIn`, `X`, `Instagram`, `Discord`, `YouTube`, `Rss`,
172
- `Correo`. Van bajo namespace porque uno se llama `X` y sueltos colisiona.
216
+ All nine: `GitHub`, `LinkedIn`, `X`, `Instagram`, `Discord`, `YouTube`, `Rss`,
217
+ `Email`, `Newsletter`. They live under a namespace because one of them is called
218
+ `X`, and loose it collides. `Newsletter` is the bell: a way to follow, like
219
+ `Rss`, named for what it means.
173
220
 
174
- Los glifos internos —`Close`, `ChevronDown`, `Sol`— **no se exportan** y no van a
175
- exportarse: son el juego mínimo de los primitivos. Un componente que necesita un
176
- icono lo recibe por prop (`Stat` tiene `icon`, cada `Red` de `Footer` tiene el
177
- suyo). No pidas que se publiquen: pasa el tuyo.
221
+ The internal glyphs — `Close`, `ChevronDown`, `Sun` — are **not exported** and
222
+ are not going to be: they are the primitives' minimum set. A component that needs
223
+ an icon receives it as a prop (`Stat` has `icon`, each `SocialLink` in `Footer`
224
+ has its own). Do not ask for them to be published: pass your own.
178
225
 
179
226
  ## Tokens
180
227
 
181
- La fuente es un objeto de TypeScript y la salida CSS se genera de él, así que el
182
- mismo valor está disponible en los dos sitios y no pueden discrepar.
228
+ The source is a TypeScript object and the CSS output is generated from it, so the
229
+ same value is available in both places and they cannot disagree.
183
230
 
184
- | Token | Custom property | Utilidad de Tailwind |
231
+ | Token | Custom property | Tailwind utility |
185
232
  | --- | --- | --- |
186
- | `colors[modo].surfaceRaised` | `--color-surface-raised` | `bg-surface-raised` |
233
+ | `colors[mode].surfaceRaised` | `--color-surface-raised` | `bg-surface-raised` |
187
234
  | `brand.hull` | `--color-brand-hull` | `bg-brand-hull` |
188
- | `typeScale.h1` | `--text-h1` | `text-h1` (arrastra line-height, weight y tracking) |
235
+ | `typeScale.h1` | `--text-h1` | `text-h1` (brings line-height, weight and tracking) |
189
236
  | `fonts.display` | `--font-display` | `font-display` |
190
237
  | `radius.card` | `--radius-card` | `rounded-card` |
191
238
  | `spacing.stepLg` | `--spacing-step-lg` | `p-step-lg`, `gap-step-lg`, `mb-step-lg` |
192
239
  | `spacing.section` | `--spacing-section` | `py-section`, `mb-section` |
193
240
  | `control.md` | `--spacing-control-md` | `px-control-md` |
194
241
  | `control.icon` | `--spacing-control-icon` | `size-control-icon` (42×42) |
195
- | `gradient[modo].hero` | `--gradient-hero` | `degradado-hero` |
242
+ | `gradient[mode].hero` | `--gradient-hero` | `gradient-hero` |
196
243
  | `size.nav` | `--spacing-nav` | `h-nav` |
197
244
  | `size.content` / `size.wide` | `--container-content` / `--container-wide` | `max-w-content` / `max-w-wide` |
198
245
  | `limits.measure` | `--container-measure` | `max-w-measure` |
199
246
  | `shadow.standard` | `--shadow-standard` | `shadow-standard` |
200
247
  | `motion` | `--duration-standard`, `--ease-standard` | `duration-standard`, `ease-standard` |
201
248
 
202
- `transition-standard` es la única transición del sistema y solo puede animar
203
- color y borde: así está escrita la utilidad.
204
-
205
- **Los escalones de espaciado llevan `step` en el nombre y no es opcional.**
206
- `p-md` no es una clase de Arrecife: en un proyecto con Tailwind v4 cae en la
207
- escala numérica y no hace nada visible. El ritmo de página es `p-step-md`,
208
- `gap-step-sm`, `py-step-xl`. Llevan prefijo porque `xs, sm, md, lg, xl` son los
209
- nombres de la escala `--container-*` de Tailwind, y un `--spacing-md` propio se
210
- comía `max-w-md` en todo el proyecto sin avisar de nada. `max-w-*`, `w-*` y `h-*`
211
- son de Tailwind y se usan tal cual. Guía de migración desde la 0.2.0:
212
- <https://github.com/Proskynete/arrecife/blob/main/docs/migracion-0.3.md>.
213
-
214
- ## Reglas del sistema que el código consumidor no debe romper
215
-
216
- Son decisiones de identidad, ya medidas. Romperlas produce código que compila y
217
- se ve mal, o que falla la auditoría de accesibilidad del proyecto.
218
-
219
- 1. **Cero hex literales.** Todo color sale de un token o de su custom property.
220
- 2. **`Button variant="conversion"` va una sola vez por pantalla.** No se fuerza en
221
- runtime; dos en la misma página son un error de diseño.
222
- 3. **No hay variante de peligro en `Button`.** El error del sistema vive en los
223
- avisos y en la validación de campo, no en un botón rojo.
224
- 4. **`secondary` nunca se rellena el fondo.** Es borde y texto.
225
- 5. **Sin animaciones de entrada.** Modales, menús, tooltips y toasts aparecen
226
- donde van a quedarse. La única excepción es el spinner de `Button loading`.
227
- 6. **La semántica y la escala son independientes.** Un `h2` que debe verse
228
- pequeño es `<Text as="h2" variant="h3">`, nunca un `h3` que miente sobre la
229
- jerarquía.
230
- 7. **`textMuted` no va nunca sobre `surfaceRaised`**: da 4.07 en oscuro. Sobre
231
- superficie elevada —menús, tabs activos— el token es `textSecondary`.
232
- 8. **Un fondo teñido con un color semántico lleva texto de token de texto**, no
233
- del color semántico. El color se queda en el borde y en el glifo. Poner
234
- `accent` sobre su propio tinte al 8 % da 4.12 y no llega a AA.
235
- 9. **`Progress` exige `label`.** Una barra sin nombre accesible no dice de qué es.
236
- 10. **`Button size="icon"` exige `aria-label`.** No lleva texto.
237
- 11. **Las caras de la mascota solo aparecen** en estados vacíos, confirmaciones,
238
- errores, progreso de curso y celebración. Nunca en hero, precios, servicios,
239
- contacto ni CV.
240
- 12. **La aleta no es un parámetro libre**: `espuma` sobre fondo oscuro, `color`
241
- sobre fondo claro. Los componentes ya la eligen por el fondo.
242
-
243
- ## Lo que la librería NO hace, a propósito
244
-
245
- Estas son las confusiones que más veces se cometen al consumirla.
246
-
247
- - **No trae Shiki.** Publica el *tema*, no el resaltador. `CodeBlock` recibe el
248
- código **ya resaltado** por la herramienta del proyecto.
249
- - **No formatea fechas.** `ArticleCard`, `TalkCard` y compañía reciben la fecha ya
250
- formateada por el proyecto: la librería no impone locale. `dateTime` es aparte,
251
- en ISO, para el atributo del `<time>`.
252
- - **`NewsletterForm` no hace el POST.** Es presentacional: recibe `state` y emite
253
- `onSubmitEmail`. La llamada la hace el proyecto con su proveedor.
254
- - **No trae enrutador.** Los componentes con enlaces aceptan `asChild` para
255
- envolver el `Link` del framework.
256
- - **No carga fuentes.** Las declara por nombre.
257
- - **No hay preset de Tailwind v3.**
258
-
259
- ## Patrones de uso
249
+ `transition-standard` is the system's only transition and it can only animate
250
+ color and border: that is how the utility is written.
251
+
252
+ **The spacing steps carry `step` in the name and it is not optional.** `p-md` is
253
+ not an Arrecife class: in a Tailwind v4 project it lands on the numeric scale and
254
+ does nothing visible. Page rhythm is `p-step-md`, `gap-step-sm`, `py-step-xl`.
255
+ They carry a prefix because `xs, sm, md, lg, xl` are the names of Tailwind's
256
+ `--container-*` scale, and a `--spacing-md` of our own was swallowing `max-w-md`
257
+ across the whole project with nothing warning about it. `max-w-*`, `w-*` and
258
+ `h-*` belong to Tailwind and are used as they are. Migration guide from 0.2.0:
259
+ <https://github.com/Proskynete/arrecife/blob/main/docs/migration-0.3.md>.
260
+
261
+ ## System rules the consuming code must not break
262
+
263
+ These are identity decisions, already measured. Breaking them produces code that
264
+ compiles and looks wrong, or that fails the project's accessibility audit.
265
+
266
+ 1. **Zero literal hexes.** Every color comes from a token or its custom property.
267
+ 2. **`Button variant="conversion"` appears once per screen.** It is not enforced
268
+ at runtime; two on the same page are a design error.
269
+ 3. **`Button variant="destructive"` is for the irreversible only.** Never for
270
+ «cancel» on a form, and not inside an `AlertDialog` — there the confirm button
271
+ stays `primary`, because the title, the focus on cancel and the no-click-outside
272
+ already carry the weight. See `docs/decisions.md` § 21.
273
+ 4. **`secondary` is never filled.** It is border and text.
274
+ 5. **No entrance animations.** Modals, menus, tooltips and toasts appear where
275
+ they will stay. The only exception is the `Button loading` spinner.
276
+ 6. **Semantics and scale are independent.** An `h2` that has to look small is
277
+ `<Text as="h2" variant="h3">`, never an `h3` that lies about the hierarchy.
278
+ 7. **`textMuted` never goes over `surfaceRaised`**: it gives 4.07 in dark. Over a
279
+ raised surface — menus, active tabs — the token is `textSecondary`.
280
+ 8. **A background tinted with a semantic color carries text from a text token**,
281
+ not from the semantic color. The color stays on the border and on the glyph.
282
+ Putting `accent` over its own tint at 8 % gives 4.12 and does not reach AA.
283
+ 9. **`Progress` requires `label`.** A bar with no accessible name does not say
284
+ what it is about.
285
+ 10. **`Button size="icon"` and `size="icon-sm"` require `aria-label`.** They carry
286
+ no text. `icon` is 42×42 and is a page action; `icon-sm` is 32×32 and is for a
287
+ dense table row.
288
+ 11. **The mascot's faces only appear** in empty states, confirmations, errors,
289
+ course progress and celebration. Never in a hero, pricing, services, contact
290
+ or the CV.
291
+ 12. **The fin is not a free parameter**: `foam` on a dark background, `color` on a
292
+ light one. The components already choose it from the background.
293
+
294
+ ## What the library does NOT do, on purpose
295
+
296
+ These are the confusions people run into most often when consuming it.
297
+
298
+ - **It does not ship Shiki.** It publishes the *theme*, not the highlighter.
299
+ `CodeBlock` receives the code **already highlighted** by the project's tool.
300
+ - **It does not format dates.** `ArticleCard`, `TalkCard` and company receive the
301
+ date already formatted by the project: the library imposes no locale.
302
+ `dateTime` is separate, in ISO, for the `<time>` attribute.
303
+ - **`NewsletterForm` does not do the POST.** It is presentational: it takes
304
+ `state` and emits `onSubmitEmail`. The call is made by the project with its own
305
+ provider.
306
+ - **It ships no router.** The components with links accept `asChild` to wrap the
307
+ framework's `Link`.
308
+ - **It ships no `data-testid`.** A composed part your test suite has to reach is
309
+ reached with a slot: `ArticleCard`'s `tagAsChild`, `Breadcrumb`'s and
310
+ `TableOfContents`'s `linkAsChild`. They hand you the element and its
311
+ attributes and keep the classes. Do NOT select by structure or by a style
312
+ class — a style class is not a contract and it changes when the style does.
313
+ - **It does not load fonts.** It declares them by name.
314
+ - **There is no Tailwind v3 preset.**
315
+
316
+ ## Usage patterns
260
317
 
261
318
  ```tsx
262
319
  import { Text, Button, Badge, cn } from '@eduardoalvarez/arrecife';
@@ -264,12 +321,12 @@ import type { TextProps } from '@eduardoalvarez/arrecife';
264
321
 
265
322
  <Text variant="eyebrow" tone="muted">charlas</Text>
266
323
  <Text as="h2" variant="h1">Escalar con criterio</Text>
267
- <Text variant="body">Se corta solo a 68ch.</Text>
268
- <Text variant="ui" measure={false}>Sin corte, para una celda estrecha.</Text>
324
+ <Text variant="body">Clamps itself to 68ch.</Text>
325
+ <Text variant="ui" measure={false}>No clamp, for a narrow cell.</Text>
269
326
  ```
270
327
 
271
- `asChild` renderiza el hijo en vez del elemento propio. Es como se envuelve el
272
- enlace del framework sin perder los estilos:
328
+ `asChild` renders the child instead of the component's own element. It is how the
329
+ framework's link gets wrapped without losing the styles:
273
330
 
274
331
  ```tsx
275
332
  <Button asChild>
@@ -277,23 +334,23 @@ enlace del framework sin perder los estilos:
277
334
  </Button>
278
335
  ```
279
336
 
280
- `cn` es `clsx` + `tailwind-merge`. Se usa para componer `className` sin que dos
281
- utilidades del mismo grupo peleen.
337
+ `cn` is `clsx` + `tailwind-merge`. It is used to compose `className` without two
338
+ utilities from the same group fighting each other.
282
339
 
283
- Open Graph, sin React:
340
+ Open Graph, without React:
284
341
 
285
342
  ```ts
286
343
  import satori from 'satori';
287
- import { plantillaArticulo, OG } from '@eduardoalvarez/arrecife/og';
344
+ import { articleTemplate, OG } from '@eduardoalvarez/arrecife/og';
288
345
 
289
- const svg = await satori(plantillaArticulo({ title, date, readingMinutes }), {
346
+ const svg = await satori(articleTemplate({ title, date, readingMinutes }), {
290
347
  width: OG.width, // 1200
291
348
  height: OG.height, // 630
292
349
  fonts: [...],
293
350
  });
294
351
  ```
295
352
 
296
- Resaltado de sintaxis, desde la configuración del sitio:
353
+ Syntax highlighting, from the site's configuration:
297
354
 
298
355
  ```ts
299
356
  import { arrecife } from '@eduardoalvarez/arrecife/shiki';
@@ -303,1253 +360,1262 @@ export default defineConfig({
303
360
  });
304
361
  ```
305
362
 
306
- # Inventario
363
+ # Inventory
307
364
 
308
- Lo que sigue sale del compilador de TypeScript en cada build. Solo se listan los
309
- props **declarados por la librería**: los heredados de un elemento HTML o de una
310
- primitiva de Radix se resumen en la línea `Extiende`, y son los de siempre.
365
+ What follows comes out of the TypeScript compiler on every build. Only the props
366
+ **declared by the library** are listed: the ones inherited from an HTML element
367
+ or from a Radix primitive are summarised in the `Extends` line, and they are the
368
+ usual ones.
311
369
 
312
- ## Primitivos
370
+ ## Primitives
313
371
 
314
- Se importan de `@eduardoalvarez/arrecife`. 110 exportaciones.
372
+ Imported from `@eduardoalvarez/arrecife`. 110 exports.
315
373
 
316
374
  ### Accordion, AccordionItem, AccordionTrigger, AccordionContent
317
375
 
318
- Fuente: `src/primitives/accordion.tsx`
376
+ Source: `src/primitives/accordion.tsx`
319
377
 
320
378
  **Accordion**
321
- El plegable. Lo pedían dos proyectos: el FAQ del portafolio y el temario de cursos, que es literalmente una lista de secciones que se abren.
379
+ The disclosure. Two projects asked for it: the portfolio FAQ and the course syllabus, which is literally a list of sections that open.
322
380
 
323
- - Extiende: `ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>`
324
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
381
+ - Extends: `ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>`
382
+ - No own props: it passes through those of the element or primitive it wraps.
325
383
 
326
384
  **AccordionItem**
327
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
385
+ - No own props: it passes through those of the element or primitive it wraps.
328
386
 
329
387
  **AccordionTrigger**
330
- El disparador es el encabezado, así que va DENTRO de un `<h3>`: Radix envuelve el botón en `AccordionPrimitive.Header`, que renderiza el elemento que se le pida. Sin eso, un lector de pantalla ve una lista de botones sueltos y pierde la estructura de la página, que es justo lo que un FAQ necesita conservar.
388
+ The trigger IS the heading, so it goes INSIDE an `<h3>`: Radix wraps the button in `AccordionPrimitive.Header`, which renders whichever element you ask of it. Without that, a screen reader sees a list of loose buttons and loses the page structure, which is precisely what a FAQ needs to keep.
331
389
 
332
- - Extiende: `ComponentPropsWithoutRef< typeof AccordionPrimitive.Trigger >`
390
+ - Extends: `ComponentPropsWithoutRef< typeof AccordionPrimitive.Trigger >`
333
391
 
334
- | prop | tipo | req. | defecto | qué hace |
392
+ | prop | type | req. | default | what it does |
335
393
  | --- | --- | --- | --- | --- |
336
- | `headingLevel` | `4 \| 2 \| 3` | | `3` | Nivel del encabezado que envuelve al disparador. |
394
+ | `headingLevel` | `4 \| 2 \| 3` | | `3` | The level of the heading wrapping the trigger. |
337
395
 
338
396
  **AccordionContent**
339
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
397
+ - No own props: it passes through those of the element or primitive it wraps.
340
398
 
341
399
  ### AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogCancel, AlertDialogAction, AlertDialog, AlertDialogTrigger
342
400
 
343
- Fuente: `src/primitives/alert-dialog.tsx`
401
+ Source: `src/primitives/alert-dialog.tsx`
344
402
 
345
403
  **AlertDialogOverlay**
346
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
404
+ - No own props: it passes through those of the element or primitive it wraps.
347
405
 
348
406
  **AlertDialogContent**
349
- Sin entrada animada, igual que `Dialog`: aparece donde va a quedarse.
407
+ No entrance animation, same as `Dialog`: it appears where it will stay.
350
408
 
351
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
409
+ - No own props: it passes through those of the element or primitive it wraps.
352
410
 
353
411
  **AlertDialogHeader**
354
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
412
+ - No own props: it passes through those of the element or primitive it wraps.
355
413
 
356
414
  **AlertDialogFooter**
357
- Cancelar a la IZQUIERDA de confirmar en escritorio y ABAJO en móvil, que es lo que da `flex-col-reverse`: el orden del DOM pone cancelar primero —es donde va el foco— y en columna el dedo lo encuentra donde toca sin cambiar la tabulación.
415
+ Cancel to the LEFT of confirm on desktop and BELOW it on mobile, which is what `flex-col-reverse` gives: the DOM order puts cancel first — that is where focus goes — and in a column the thumb finds it where it should be without changing the tab order.
358
416
 
359
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
417
+ - No own props: it passes through those of the element or primitive it wraps.
360
418
 
361
419
  **AlertDialogTitle**
362
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
420
+ - No own props: it passes through those of the element or primitive it wraps.
363
421
 
364
422
  **AlertDialogDescription**
365
- Lo que se pierde, dicho entero. Es lo que el rol `alertdialog` hace que se anuncie de entrada, así que aquí no va «esta acción no se puede deshacer» suelto: va qué se borra y qué se lleva por delante.
423
+ What is lost, spelled out. The `alertdialog` role makes this get announced up front, so «this action cannot be undone» does not go here on its own: what goes here is what gets deleted and what it takes down with it.
366
424
 
367
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
425
+ - No own props: it passes through those of the element or primitive it wraps.
368
426
 
369
427
  **AlertDialogCancel**
370
- El que se lleva el foco al abrir.
428
+ The one that takes focus on open.
371
429
 
372
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
430
+ - No own props: it passes through those of the element or primitive it wraps.
373
431
 
374
432
  **AlertDialogAction**
375
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
433
+ - No own props: it passes through those of the element or primitive it wraps.
376
434
 
377
435
  **AlertDialog**
378
- La confirmación destructiva. NO es un `Dialog` con otro texto, y por eso está en su propio archivo y sobre su propia primitiva de Radix.
436
+ The destructive confirmation. It is NOT a `Dialog` with different text, which is why it lives in its own file and on its own Radix primitive.
379
437
 
380
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
438
+ - No own props: it passes through those of the element or primitive it wraps.
381
439
 
382
440
  **AlertDialogTrigger**
383
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
441
+ - No own props: it passes through those of the element or primitive it wraps.
384
442
 
385
443
  ### Alert
386
444
 
387
- Fuente: `src/primitives/alert.tsx`
388
-
389
- El aviso lleva el color en el fondo, no solo en el borde.
445
+ Source: `src/primitives/alert.tsx`
390
446
 
391
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'title'> & VariantProps<typeof alert>`
447
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'title'> & VariantProps<typeof alert>`
392
448
 
393
- | prop | tipo | req. | defecto | qué hace |
449
+ | prop | type | req. | default | what it does |
394
450
  | --- | --- | --- | --- | --- |
395
- | `enfasis` | `"sutil" \| "fuerte"` | | `sutil` | |
396
- | `icon` | `ReactNode` | | | Sustituye el glifo mono de la variante. Nunca un emoji: si necesitas otra cosa, es un SVG de `glyphs`. |
451
+ | `emphasis` | `"subtle" \| "strong"` | | | |
452
+ | `icon` | `ReactNode` | | | Replaces the variant's mono glyph. Never an emoji: if you need something else, it is an SVG from `glyphs`. |
397
453
  | `title` | `ReactNode` | | | |
398
- | `variant` | `"accent" \| "success" \| "warning" \| "error"` | | `accent` | |
454
+ | `variant` | `"accent" \| "success" \| "warning" \| "error"` | | | |
399
455
 
400
456
  ### Avatar, AvatarImage, AvatarFallback, AvatarUpload
401
457
 
402
- Fuente: `src/primitives/avatar.tsx`
458
+ Source: `src/primitives/avatar.tsx`
403
459
 
404
460
  **Avatar**
405
- Uno solo para todo: la foto del autor y la de cualquier persona del sistema. No hay un `brand/Avatar` aparte — una foto de perfil con la piel de la marca es exactamente esto con un `src` distinto.
461
+ One for everything: the author's photo and anyone else's in the system. There is no separate `brand/Avatar` — a profile photo wearing the brand's skin is exactly this with a different `src`.
406
462
 
407
- - Extiende: `ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & VariantProps<typeof avatar>`
463
+ - Extends: `ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & VariantProps<typeof avatar>`
408
464
 
409
- | prop | tipo | req. | defecto | qué hace |
465
+ | prop | type | req. | default | what it does |
410
466
  | --- | --- | --- | --- | --- |
411
- | `size` | `"sm" \| "md" \| "lg" \| "xl"` | | `md` | |
467
+ | `size` | `"sm" \| "md" \| "lg" \| "xl"` | | | |
412
468
 
413
469
  **AvatarImage**
414
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
470
+ - No own props: it passes through those of the element or primitive it wraps.
415
471
 
416
472
  **AvatarFallback**
417
- Iniciales mientras la imagen carga, o cuando no hay imagen.
473
+ Initials while the image loads, or when there is no image.
418
474
 
419
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
475
+ - No own props: it passes through those of the element or primitive it wraps.
420
476
 
421
477
  **AvatarUpload**
422
- El avatar que se puede cambiar. `Avatar` muestra; este además deja elegir.
478
+ The avatar you can change. `Avatar` displays; this one also lets you pick.
423
479
 
424
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' \| 'children'> & VariantProps<typeof avatar>`
480
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' \| 'children'> & VariantProps<typeof avatar>`
425
481
 
426
- | prop | tipo | req. | defecto | qué hace |
482
+ | prop | type | req. | default | what it does |
427
483
  | --- | --- | --- | --- | --- |
428
- | `accept` | `string` | | `image/*` | Qué acepta el diálogo del sistema. |
484
+ | `accept` | `string` | | `image/*` | What the system dialog accepts. |
429
485
  | `disabled` | `boolean \| undefined` | | `false` | |
430
- | `fallback` | `ReactNode` | | | Iniciales mientras no hay imagen. |
431
- | `label` | `string` | | `Cambiar la foto` | Nombre accesible del control. Es lo único que lo nombra: no hay texto visible. |
432
- | `onSelectFile` | `(archivo: File) => void` | | | Se dispara con el archivo elegido. La subida la hace el proyecto. |
486
+ | `fallback` | `ReactNode` | | | Initials while there is no image. |
487
+ | `label` | `string` | | `Cambiar la foto` | The control's accessible name. It is the only thing naming it: there is no visible text. |
488
+ | `onSelectFile` | `(file: File) => void` | | | Fires with the chosen file. The upload is the project's job. |
433
489
  | `size` | `"sm" \| "md" \| "lg" \| "xl"` | | | |
434
- | `src` | `string` | | | La imagen actual, ya subida. La previsualización local la gana mientras dure. |
490
+ | `src` | `string` | | | The current image, already uploaded. The local preview beats it while it lasts. |
435
491
 
436
492
  ### Badge, CategoryBadge, MetricBadge
437
493
 
438
- Fuente: `src/primitives/badge.tsx`
494
+ Source: `src/primitives/badge.tsx`
439
495
 
440
496
  **Badge**
441
- Semáforo. Cuadrada r6, sans 12.5/500 y fondo al 8 % del semántico — la receta del aviso en tamaño de palabra: un estado es un aviso de una sola palabra.
497
+ Three badge families, three components. Why there are three shapes and not one is in `variants/badge.ts`, next to the classes that make them.
442
498
 
443
- - Extiende: `ComponentPropsWithoutRef<'span'> & VariantProps<typeof badge>`
499
+ - Extends: `ComponentPropsWithoutRef<'span'> & VariantProps<typeof badge>`
444
500
 
445
- | prop | tipo | req. | defecto | qué hace |
501
+ | prop | type | req. | default | what it does |
446
502
  | --- | --- | --- | --- | --- |
447
- | `variant` | `"accent" \| "success" \| "warning" \| "error" \| "neutral" \| "warm"` | | `neutral` | |
503
+ | `variant` | `"neutral" \| "accent" \| "warm" \| "success" \| "warning" \| "error"` | | | |
448
504
 
449
505
  **CategoryBadge**
450
- Un slug, en arena. Sin transformar: los slugs ya vienen en minúscula y forzarla sería el mismo error que forzaba el `uppercase`.
451
-
452
- - Extiende: `ComponentPropsWithoutRef<'span'>`
506
+ - Extends: `ComponentPropsWithoutRef<'span'>`
453
507
 
454
- | prop | tipo | req. | defecto | qué hace |
508
+ | prop | type | req. | default | what it does |
455
509
  | --- | --- | --- | --- | --- |
456
- | `active` | `boolean \| undefined` | | `false` | Filtro seleccionado: arena sólido con tinta encima. |
510
+ | `active` | `boolean \| undefined` | | `false` | Selected filter: solid sand with ink on top. |
457
511
 
458
512
  **MetricBadge**
459
- - Extiende: `ComponentPropsWithoutRef<'span'>`
513
+ - Extends: `ComponentPropsWithoutRef<'span'>`
460
514
 
461
- | prop | tipo | req. | defecto | qué hace |
515
+ | prop | type | req. | default | what it does |
462
516
  | --- | --- | --- | --- | --- |
463
- | `boxed` | `boolean \| undefined` | | `false` | Añade el aro de hairline. Por defecto la métrica va sin caja. |
517
+ | `boxed` | `boolean \| undefined` | | `false` | Adds the hairline ring. By default a metric carries no box. |
464
518
 
465
519
  ### Button
466
520
 
467
- Fuente: `src/primitives/button.tsx`
521
+ Source: `src/primitives/button.tsx`
468
522
 
469
- Las CUATRO variantes del sistema, y solo esas cuatro.
523
+ - Extends: `ComponentPropsWithoutRef<'button'> & VariantProps<typeof button>`
470
524
 
471
- - Extiende: `ComponentPropsWithoutRef<'button'> & VariantProps<typeof button>`
472
-
473
- | prop | tipo | req. | defecto | qué hace |
525
+ | prop | type | req. | default | what it does |
474
526
  | --- | --- | --- | --- | --- |
475
- | `asChild` | `boolean` | | `false` | Renderiza el hijo en vez de un `<button>`, para envolver un enlace. |
476
- | `icon` | `ReactNode` | | | Glifo SVG antes del texto. Se oculta mientras carga. |
477
- | `loading` | `boolean` | | `false` | Deshabilita y anuncia `aria-busy`. Incompatible con `asChild`. |
478
- | `size` | `"sm" \| "md" \| "lg" \| "icon"` | | `md` | |
479
- | `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary"` | | `primary` | |
527
+ | `asChild` | `boolean` | | `false` | Renders the child instead of a `<button>`, to wrap a link. |
528
+ | `icon` | `ReactNode` | | | SVG glyph before the text. Hidden while loading. |
529
+ | `loading` | `boolean` | | `false` | Disables and announces `aria-busy`. Incompatible with `asChild`. |
530
+ | `size` | `"sm" \| "md" \| "lg" \| "icon" \| "icon-sm"` | | | |
531
+ | `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary" \| "destructive" \| "destructiveOutline"` | | | |
480
532
 
481
533
  ### Calendar
482
534
 
483
- Fuente: `src/primitives/calendar.tsx`
535
+ Source: `src/primitives/calendar.tsx`
484
536
 
485
- Calendario mensual navegable, sobre `react-day-picker`.
537
+ A navigable month calendar, on top of `react-day-picker`.
486
538
 
487
- - Extiende: `ComponentProps<typeof DayPicker>`
539
+ - Extends: `ComponentProps<typeof DayPicker>`
488
540
 
489
- | prop | tipo | req. | defecto | qué hace |
541
+ | prop | type | req. | default | what it does |
490
542
  | --- | --- | --- | --- | --- |
491
- | `fullWidth` | `boolean \| undefined` | | `false` | Estira el calendario hasta ocupar todo el ancho de su contenedor, con las celdas repartiéndoselo a partes iguales. |
543
+ | `fullWidth` | `boolean \| undefined` | | `false` | Stretches the calendar to fill its container's whole width, with the cells splitting it evenly. |
492
544
 
493
545
  ### Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter
494
546
 
495
- Fuente: `src/primitives/card.tsx`
547
+ Source: `src/primitives/card.tsx`
496
548
 
497
549
  **Card**
498
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
550
+ - No own props: it passes through those of the element or primitive it wraps.
499
551
 
500
552
  **CardHeader**
501
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
553
+ - No own props: it passes through those of the element or primitive it wraps.
502
554
 
503
555
  **CardTitle**
504
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
556
+ - No own props: it passes through those of the element or primitive it wraps.
505
557
 
506
558
  **CardDescription**
507
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
559
+ - No own props: it passes through those of the element or primitive it wraps.
508
560
 
509
561
  **CardContent**
510
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
562
+ - No own props: it passes through those of the element or primitive it wraps.
511
563
 
512
564
  **CardFooter**
513
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
565
+ - No own props: it passes through those of the element or primitive it wraps.
514
566
 
515
567
  ### Checkbox
516
568
 
517
- Fuente: `src/primitives/checkbox.tsx`
569
+ Source: `src/primitives/checkbox.tsx`
518
570
 
519
- - Extiende: `ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>`
520
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
571
+ - Extends: `ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>`
572
+ - No own props: it passes through those of the element or primitive it wraps.
521
573
 
522
574
  ### Code
523
575
 
524
- Fuente: `src/primitives/code.tsx`
576
+ Source: `src/primitives/code.tsx`
525
577
 
526
- Código en línea, dentro de prosa.
578
+ Inline code, inside prose.
527
579
 
528
- - Extiende: `ComponentPropsWithoutRef<'code'>`
529
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
580
+ - Extends: `ComponentPropsWithoutRef<'code'>`
581
+ - No own props: it passes through those of the element or primitive it wraps.
530
582
 
531
583
  ### DateField
532
584
 
533
- Fuente: `src/primitives/date-field.tsx`
585
+ Source: `src/primitives/date-field.tsx`
534
586
 
535
- Un campo de fecha sobre el control nativo, no sobre un calendario propio.
587
+ A date field on the native control, not on a calendar of our own.
536
588
 
537
- - Extiende: `Omit<ComponentPropsWithoutRef<'input'>, 'type'>`
589
+ - Extends: `Omit<ComponentPropsWithoutRef<'input'>, 'type'>`
538
590
 
539
- | prop | tipo | req. | defecto | qué hace |
591
+ | prop | type | req. | default | what it does |
540
592
  | --- | --- | --- | --- | --- |
541
593
  | `invalid` | `boolean \| undefined` | | `false` | |
542
- | `withTime` | `boolean \| undefined` | | `false` | Añade la hora al campo. Es el `datetime-local` nativo. |
594
+ | `withTime` | `boolean \| undefined` | | `false` | Adds the time to the field. It is the native `datetime-local`. |
543
595
 
544
596
  ### DialogOverlay, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, Dialog, DialogTrigger, DialogClose
545
597
 
546
- Fuente: `src/primitives/dialog.tsx`
598
+ Source: `src/primitives/dialog.tsx`
547
599
 
548
600
  **DialogOverlay**
549
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
601
+ - No own props: it passes through those of the element or primitive it wraps.
550
602
 
551
603
  **DialogContent**
552
- Sin entrada animada: no hay escala ni desplazamiento en el sistema.
604
+ No entrance animation: there is no scale or displacement in the system.
553
605
 
554
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
606
+ - No own props: it passes through those of the element or primitive it wraps.
555
607
 
556
608
  **DialogHeader**
557
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
609
+ - No own props: it passes through those of the element or primitive it wraps.
558
610
 
559
611
  **DialogFooter**
560
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
612
+ - No own props: it passes through those of the element or primitive it wraps.
561
613
 
562
614
  **DialogTitle**
563
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
615
+ - No own props: it passes through those of the element or primitive it wraps.
564
616
 
565
617
  **DialogDescription**
566
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
618
+ - No own props: it passes through those of the element or primitive it wraps.
567
619
 
568
620
  **Dialog**
569
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
621
+ - No own props: it passes through those of the element or primitive it wraps.
570
622
 
571
623
  **DialogTrigger**
572
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
624
+ - No own props: it passes through those of the element or primitive it wraps.
573
625
 
574
626
  **DialogClose**
575
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
627
+ - No own props: it passes through those of the element or primitive it wraps.
576
628
 
577
629
  ### DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenu, DropdownMenuTrigger, DropdownMenuGroup, DropdownMenuRadioGroup, DropdownMenuSub
578
630
 
579
- Fuente: `src/primitives/dropdown-menu.tsx`
631
+ Source: `src/primitives/dropdown-menu.tsx`
580
632
 
581
633
  **DropdownMenuContent**
582
- Sin animación de entrada: el menú aparece, no se despliega.
634
+ No entrance animation: the menu appears, it does not unfold.
583
635
 
584
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
636
+ - No own props: it passes through those of the element or primitive it wraps.
585
637
 
586
638
  **DropdownMenuItem**
587
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
639
+ - No own props: it passes through those of the element or primitive it wraps.
588
640
 
589
641
  **DropdownMenuCheckboxItem**
590
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
642
+ - No own props: it passes through those of the element or primitive it wraps.
591
643
 
592
644
  **DropdownMenuRadioItem**
593
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
645
+ - No own props: it passes through those of the element or primitive it wraps.
594
646
 
595
647
  **DropdownMenuLabel**
596
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
648
+ - No own props: it passes through those of the element or primitive it wraps.
597
649
 
598
650
  **DropdownMenuSeparator**
599
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
651
+ - No own props: it passes through those of the element or primitive it wraps.
600
652
 
601
653
  **DropdownMenuSubTrigger**
602
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
654
+ - No own props: it passes through those of the element or primitive it wraps.
603
655
 
604
656
  **DropdownMenuSubContent**
605
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
657
+ - No own props: it passes through those of the element or primitive it wraps.
606
658
 
607
659
  **DropdownMenu**
608
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
660
+ - No own props: it passes through those of the element or primitive it wraps.
609
661
 
610
662
  **DropdownMenuTrigger**
611
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
663
+ - No own props: it passes through those of the element or primitive it wraps.
612
664
 
613
665
  **DropdownMenuGroup**
614
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
666
+ - No own props: it passes through those of the element or primitive it wraps.
615
667
 
616
668
  **DropdownMenuRadioGroup**
617
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
669
+ - No own props: it passes through those of the element or primitive it wraps.
618
670
 
619
671
  **DropdownMenuSub**
620
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
672
+ - No own props: it passes through those of the element or primitive it wraps.
621
673
 
622
674
  ### Input
623
675
 
624
- Fuente: `src/primitives/input.tsx`
676
+ Source: `src/primitives/input.tsx`
625
677
 
626
- - Extiende: `ComponentPropsWithoutRef<'input'>`
678
+ - Extends: `ComponentPropsWithoutRef<'input'>`
627
679
 
628
- | prop | tipo | req. | defecto | qué hace |
680
+ | prop | type | req. | default | what it does |
629
681
  | --- | --- | --- | --- | --- |
630
- | `invalid` | `boolean` | | `false` | Marca el control como inválido y tiñe el borde. |
682
+ | `invalid` | `boolean` | | `false` | Marks the control as invalid and tints the border. |
631
683
 
632
684
  ### Label
633
685
 
634
- Fuente: `src/primitives/label.tsx`
686
+ Source: `src/primitives/label.tsx`
635
687
 
636
- La escala `label`: 13px, que es el mínimo absoluto en pantalla del sistema.
688
+ The `label` scale: 13px, which is the system's absolute minimum on screen.
637
689
 
638
- - Extiende: `ComponentPropsWithoutRef<typeof LabelPrimitive.Root>`
639
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
690
+ - Extends: `ComponentPropsWithoutRef<typeof LabelPrimitive.Root>`
691
+ - No own props: it passes through those of the element or primitive it wraps.
640
692
 
641
693
  ### Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis
642
694
 
643
- Fuente: `src/primitives/pagination.tsx`
695
+ Source: `src/primitives/pagination.tsx`
644
696
 
645
697
  **Pagination**
646
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
698
+ - No own props: it passes through those of the element or primitive it wraps.
647
699
 
648
700
  **PaginationContent**
649
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
701
+ - No own props: it passes through those of the element or primitive it wraps.
650
702
 
651
703
  **PaginationItem**
652
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
704
+ - No own props: it passes through those of the element or primitive it wraps.
653
705
 
654
706
  **PaginationLink**
655
- - Extiende: `ComponentPropsWithoutRef<'a'>`
707
+ - Extends: `ComponentPropsWithoutRef<'a'>`
656
708
 
657
- | prop | tipo | req. | defecto | qué hace |
709
+ | prop | type | req. | default | what it does |
658
710
  | --- | --- | --- | --- | --- |
659
711
  | `isActive` | `boolean` | | `false` | |
660
712
 
661
713
  **PaginationPrevious**
662
714
 
663
- | prop | tipo | req. | defecto | qué hace |
715
+ | prop | type | req. | default | what it does |
664
716
  | --- | --- | --- | --- | --- |
665
717
  | `isActive` | `boolean` | | | |
666
718
 
667
719
  **PaginationNext**
668
720
 
669
- | prop | tipo | req. | defecto | qué hace |
721
+ | prop | type | req. | default | what it does |
670
722
  | --- | --- | --- | --- | --- |
671
723
  | `isActive` | `boolean` | | | |
672
724
 
673
725
  **PaginationEllipsis**
674
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
726
+ - No own props: it passes through those of the element or primitive it wraps.
675
727
 
676
728
  ### PopoverContent, Popover, PopoverTrigger, PopoverAnchor
677
729
 
678
- Fuente: `src/primitives/popover.tsx`
730
+ Source: `src/primitives/popover.tsx`
679
731
 
680
732
  **PopoverContent**
681
- Sin animación de entrada: aparece donde va a quedarse, como el resto.
733
+ No entrance animation: it appears where it will stay, like the rest.
682
734
 
683
- - Extiende: `ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & Etiquetado`
684
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
735
+ - Extends: `ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & Labelled`
736
+ - No own props: it passes through those of the element or primitive it wraps.
685
737
 
686
738
  **Popover**
687
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
739
+ - No own props: it passes through those of the element or primitive it wraps.
688
740
 
689
741
  **PopoverTrigger**
690
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
742
+ - No own props: it passes through those of the element or primitive it wraps.
691
743
 
692
744
  **PopoverAnchor**
693
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
745
+ - No own props: it passes through those of the element or primitive it wraps.
694
746
 
695
747
  ### Progress
696
748
 
697
- Fuente: `src/primitives/progress.tsx`
749
+ Source: `src/primitives/progress.tsx`
698
750
 
699
- El ancho del indicador cambia, no se anima: el sistema no anima escala ni desplazamiento. `transition-standard` solo cubre color y borde, así que el salto de ancho es inmediato aunque la clase esté puesta.
751
+ The indicator's width changes, it is not animated: the system animates neither scale nor displacement. `transition-standard` only covers color and border, so the width jump is immediate even with the class in place.
700
752
 
701
- - Extiende: `ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>`
753
+ - Extends: `ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>`
702
754
 
703
- | prop | tipo | req. | defecto | qué hace |
755
+ | prop | type | req. | default | what it does |
704
756
  | --- | --- | --- | --- | --- |
705
- | `label` | `string` | sí | | Nombre accesible de la barra. Es obligatorio a propósito: una barra de progreso sin nombre no dice de qué es el progreso, y ninguna otra parte del componente puede deducirlo. |
706
- | `tone` | `"accent" \| "warm"` | | `accent` | Arena en vez de bioluz, para progreso de curso. |
757
+ | `label` | `string` | yes | | The bar's accessible name. It is mandatory on purpose: a progress bar with no name does not say what the progress is about, and no other part of the component can deduce it. |
758
+ | `tone` | `"accent" \| "warm"` | | `accent` | Sand instead of biolume, for course progress. |
707
759
 
708
760
  ### RadioGroup, RadioGroupItem
709
761
 
710
- Fuente: `src/primitives/radio-group.tsx`
762
+ Source: `src/primitives/radio-group.tsx`
711
763
 
712
764
  **RadioGroup**
713
- - Extiende: `ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>`
714
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
765
+ - Extends: `ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>`
766
+ - No own props: it passes through those of the element or primitive it wraps.
715
767
 
716
768
  **RadioGroupItem**
717
- - Extiende: `ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>`
718
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
769
+ - Extends: `ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>`
770
+ - No own props: it passes through those of the element or primitive it wraps.
719
771
 
720
772
  ### SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, Select, SelectGroup, SelectValue
721
773
 
722
- Fuente: `src/primitives/select.tsx`
774
+ Source: `src/primitives/select.tsx`
723
775
 
724
776
  **SelectTrigger**
725
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
777
+ - No own props: it passes through those of the element or primitive it wraps.
726
778
 
727
779
  **SelectContent**
728
- Sin animación de entrada: el menú aparece, no se despliega.
780
+ No entrance animation: the menu appears, it does not unfold.
729
781
 
730
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
782
+ - No own props: it passes through those of the element or primitive it wraps.
731
783
 
732
784
  **SelectLabel**
733
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
785
+ - No own props: it passes through those of the element or primitive it wraps.
734
786
 
735
787
  **SelectItem**
736
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
788
+ - No own props: it passes through those of the element or primitive it wraps.
737
789
 
738
790
  **SelectSeparator**
739
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
791
+ - No own props: it passes through those of the element or primitive it wraps.
740
792
 
741
793
  **Select**
742
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
794
+ - No own props: it passes through those of the element or primitive it wraps.
743
795
 
744
796
  **SelectGroup**
745
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
797
+ - No own props: it passes through those of the element or primitive it wraps.
746
798
 
747
799
  **SelectValue**
748
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
800
+ - No own props: it passes through those of the element or primitive it wraps.
749
801
 
750
802
  ### Separator
751
803
 
752
- Fuente: `src/primitives/separator.tsx`
804
+ Source: `src/primitives/separator.tsx`
753
805
 
754
- `hairline`, no `border`: una división entre contenidos es sutil por definición. Para delimitar un control existe `border`, que es otro token.
806
+ `hairline`, not `border`: a division between pieces of content is subtle by definition. To delimit a control there is `border`, which is another token.
755
807
 
756
- - Extiende: `ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>`
757
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
808
+ - Extends: `ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>`
809
+ - No own props: it passes through those of the element or primitive it wraps.
758
810
 
759
811
  ### SheetContent, SheetHeader, SheetBody, SheetFooter, SheetTitle, SheetDescription, Sheet, SheetTrigger, SheetClose
760
812
 
761
- Fuente: `src/primitives/sheet.tsx`
813
+ Source: `src/primitives/sheet.tsx`
762
814
 
763
815
  **SheetContent**
764
- La segunda y última excepción a «nada de desplazamiento», aprobada a sabiendas: un panel que entra desde un borde se desliza por definición, y quieto sería un modal descentrado.
816
+ The second and last exception to «no displacement», approved knowingly: a panel entering from an edge slides by definition, and held still it would be an off-centre modal.
765
817
 
766
- - Extiende: `ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & VariantProps<typeof panel>`
818
+ - Extends: `ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & VariantProps<typeof panel>`
767
819
 
768
- | prop | tipo | req. | defecto | qué hace |
820
+ | prop | type | req. | default | what it does |
769
821
  | --- | --- | --- | --- | --- |
770
822
  | `side` | `"right" \| "left" \| "top" \| "bottom"` | | `right` | |
771
823
 
772
824
  **SheetHeader**
773
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
825
+ - No own props: it passes through those of the element or primitive it wraps.
774
826
 
775
827
  **SheetBody**
776
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
828
+ - No own props: it passes through those of the element or primitive it wraps.
777
829
 
778
830
  **SheetFooter**
779
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
831
+ - No own props: it passes through those of the element or primitive it wraps.
780
832
 
781
833
  **SheetTitle**
782
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
834
+ - No own props: it passes through those of the element or primitive it wraps.
783
835
 
784
836
  **SheetDescription**
785
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
837
+ - No own props: it passes through those of the element or primitive it wraps.
786
838
 
787
839
  **Sheet**
788
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
840
+ - No own props: it passes through those of the element or primitive it wraps.
789
841
 
790
842
  **SheetTrigger**
791
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
843
+ - No own props: it passes through those of the element or primitive it wraps.
792
844
 
793
845
  **SheetClose**
794
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
846
+ - No own props: it passes through those of the element or primitive it wraps.
795
847
 
796
848
  ### Skeleton
797
849
 
798
- Fuente: `src/primitives/skeleton.tsx`
850
+ Source: `src/primitives/skeleton.tsx`
799
851
 
800
- Barrido de 1.4s lineal, del documento.
852
+ A 1.4s linear sweep, from the document.
801
853
 
802
- - Extiende: `ComponentPropsWithoutRef<'div'>`
854
+ - Extends: `ComponentPropsWithoutRef<'div'>`
803
855
 
804
- | prop | tipo | req. | defecto | qué hace |
856
+ | prop | type | req. | default | what it does |
805
857
  | --- | --- | --- | --- | --- |
806
- | `still` | `boolean \| undefined` | | `false` | Apaga el barrido. Para listas largas, donde muchas a la vez marean. |
858
+ | `still` | `boolean \| undefined` | | `false` | Turns the sweep off. For long lists, where many at once are dizzying. |
807
859
 
808
860
  ### Switch
809
861
 
810
- Fuente: `src/primitives/switch.tsx`
862
+ Source: `src/primitives/switch.tsx`
811
863
 
812
- La perilla cambia de posición, pero no se anima al hacerlo: la posición es el estado, no una transición. Lo único que transiciona es el color de la vía.
864
+ The knob changes position, but is not animated while doing so: the position IS the state, not a transition. The only thing that transitions is the track's color.
813
865
 
814
- - Extiende: `ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>`
815
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
866
+ - Extends: `ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>`
867
+ - No own props: it passes through those of the element or primitive it wraps.
816
868
 
817
869
  ### Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption
818
870
 
819
- Fuente: `src/primitives/table.tsx`
871
+ Source: `src/primitives/table.tsx`
820
872
 
821
873
  **Table**
822
- El contenedor scrollea en horizontal: la página nunca lo hace.
874
+ The container scrolls horizontally: the page never does.
823
875
 
824
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
876
+ - No own props: it passes through those of the element or primitive it wraps.
825
877
 
826
878
  **TableHeader**
827
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
879
+ - No own props: it passes through those of the element or primitive it wraps.
828
880
 
829
881
  **TableBody**
830
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
882
+ - No own props: it passes through those of the element or primitive it wraps.
831
883
 
832
884
  **TableFooter**
833
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
885
+ - No own props: it passes through those of the element or primitive it wraps.
834
886
 
835
887
  **TableRow**
836
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
888
+ - No own props: it passes through those of the element or primitive it wraps.
837
889
 
838
890
  **TableHead**
839
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
891
+ - No own props: it passes through those of the element or primitive it wraps.
840
892
 
841
893
  **TableCell**
842
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
894
+ - No own props: it passes through those of the element or primitive it wraps.
843
895
 
844
896
  **TableCaption**
845
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
897
+ - No own props: it passes through those of the element or primitive it wraps.
846
898
 
847
899
  ### Tabs, TabsList, TabsTrigger, TabsContent
848
900
 
849
- Fuente: `src/primitives/tabs.tsx`
901
+ Source: `src/primitives/tabs.tsx`
850
902
 
851
903
  **Tabs**
852
- - Extiende: `ComponentPropsWithoutRef<typeof TabsPrimitive.Root>`
853
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
904
+ - Extends: `ComponentPropsWithoutRef<typeof TabsPrimitive.Root>`
905
+ - No own props: it passes through those of the element or primitive it wraps.
854
906
 
855
907
  **TabsList**
856
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
908
+ - No own props: it passes through those of the element or primitive it wraps.
857
909
 
858
910
  **TabsTrigger**
859
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
911
+ - No own props: it passes through those of the element or primitive it wraps.
860
912
 
861
913
  **TabsContent**
862
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
914
+ - No own props: it passes through those of the element or primitive it wraps.
863
915
 
864
916
  ### Textarea
865
917
 
866
- Fuente: `src/primitives/textarea.tsx`
918
+ Source: `src/primitives/textarea.tsx`
867
919
 
868
- - Extiende: `ComponentPropsWithoutRef<'textarea'>`
920
+ - Extends: `ComponentPropsWithoutRef<'textarea'>`
869
921
 
870
- | prop | tipo | req. | defecto | qué hace |
922
+ | prop | type | req. | default | what it does |
871
923
  | --- | --- | --- | --- | --- |
872
924
  | `invalid` | `boolean` | | `false` | |
873
925
 
874
926
  ### Toaster
875
927
 
876
- Fuente: `src/primitives/toaster.tsx`
928
+ Source: `src/primitives/toaster.tsx`
877
929
 
878
- Va UNA vez, lo más arriba posible del árbol. Dos `Toaster` montados pintan cada aviso dos veces: la lista es del módulo, no de la instancia.
930
+ Mount it ONCE, as high in the tree as possible. Two mounted `Toaster`s paint every notice twice: the list belongs to the module, not to the instance.
879
931
 
880
- - Extiende: `{ /** Cuánto dura un aviso que no dice lo contrario. */ duration?: number; /** * Nombre del landmark que Radix crea para la región de avisos. Se traduce * porque lo lee un lector de pantalla, y el default de Radix está en inglés. */ label?: string; }`
932
+ - Extends: `{ /** How long a notice lasts when it does not say otherwise. */ duration?: number; /** * The name of the landmark Radix creates for the notices region. It is * translated because a screen reader reads it, and Radix's default is in * English. */ label?: string; }`
881
933
 
882
- | prop | tipo | req. | defecto | qué hace |
934
+ | prop | type | req. | default | what it does |
883
935
  | --- | --- | --- | --- | --- |
884
- | `duration` | `number` | | `5000` | Cuánto dura un aviso que no dice lo contrario. |
885
- | `label` | `string` | | `Avisos` | Nombre del landmark que Radix crea para la región de avisos. Se traduce porque lo lee un lector de pantalla, y el default de Radix está en inglés. |
936
+ | `duration` | `number` | | `5000` | How long a notice lasts when it does not say otherwise. |
937
+ | `label` | `string` | | `Avisos` | The name of the landmark Radix creates for the notices region. It is translated because a screen reader reads it, and Radix's default is in English. |
886
938
 
887
939
  ### TooltipContent, TooltipProvider, Tooltip, TooltipTrigger
888
940
 
889
- Fuente: `src/primitives/tooltip.tsx`
941
+ Source: `src/primitives/tooltip.tsx`
890
942
 
891
943
  **TooltipContent**
892
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
944
+ - No own props: it passes through those of the element or primitive it wraps.
893
945
 
894
946
  **TooltipProvider**
895
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
947
+ - No own props: it passes through those of the element or primitive it wraps.
896
948
 
897
949
  **Tooltip**
898
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
950
+ - No own props: it passes through those of the element or primitive it wraps.
899
951
 
900
952
  **TooltipTrigger**
901
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
953
+ - No own props: it passes through those of the element or primitive it wraps.
902
954
 
903
955
  ### Text
904
956
 
905
- Fuente: `src/primitives/typography.tsx`
957
+ Source: `src/primitives/typography.tsx`
906
958
 
907
- - Extiende: `Omit<ComponentPropsWithoutRef<'p'>, 'color'> & VariantProps<typeof texto>`
959
+ - Extends: `Omit<ComponentPropsWithoutRef<'p'>, 'color'> & VariantProps<typeof text>`
908
960
 
909
- | prop | tipo | req. | defecto | qué hace |
961
+ | prop | type | req. | default | what it does |
910
962
  | --- | --- | --- | --- | --- |
911
- | `as` | `"h2" \| "h3" \| "li" \| "p" \| "span" \| "caption" \| "dd" \| "dt" \| "em" \| "figcaption" \| "h1" \| "h4" \| "legend" \| "strong"` | | | Etiqueta HTML. Por defecto, la que corresponde a la escala. |
912
- | `asChild` | `boolean` | | `false` | Renderiza el hijo en vez de crear un elemento, para envolver un enlace. |
913
- | `measure` | `boolean` | | | Corta la línea a 68ch. Activo por defecto en `body`, que es la única escala pensada para leerse en párrafos largos. |
914
- | `tone` | `"accent" \| "success" \| "warning" \| "error" \| "warm" \| "primary" \| "secondary" \| "muted"` | | `primary` | |
915
- | `variant` | `"display" \| "h2" \| "h3" \| "label" \| "body" \| "h1" \| "meta" \| "stat" \| "lead" \| "ui" \| "tag" \| "chip" \| "eyebrow"` | | `body` | |
963
+ | `as` | `"h1" \| "h2" \| "h3" \| "strong" \| "li" \| "p" \| "span" \| "caption" \| "dd" \| "dt" \| "em" \| "figcaption" \| "h4" \| "legend"` | | | HTML tag. Defaults to whichever one matches the scale. |
964
+ | `asChild` | `boolean` | | `false` | Renders the child instead of creating an element, to wrap a link. |
965
+ | `measure` | `boolean` | | | Clamps the line to 68ch. On by default for `body`, the only scale meant to be read in long paragraphs. |
966
+ | `tone` | `"primary" \| "secondary" \| "accent" \| "warm" \| "success" \| "warning" \| "error" \| "muted"` | | | |
967
+ | `variant` | `"display" \| "stat" \| "h1" \| "h2" \| "h3" \| "body" \| "lead" \| "ui" \| "label" \| "tag" \| "chip" \| "meta" \| "eyebrow"` | | | |
916
968
 
917
- ## Componentes
969
+ ## Components
918
970
 
919
- Se importan de `@eduardoalvarez/arrecife`. 24 exportaciones.
971
+ Imported from `@eduardoalvarez/arrecife`. 24 exports.
920
972
 
921
973
  ### ArticleCard
922
974
 
923
- Fuente: `src/components/article-card/index.tsx`
975
+ Source: `src/components/article-card/index.tsx`
924
976
 
925
- La línea de metadatos va en `meta` y no en `eyebrow`: `18 ago 2026 · 8 min de lectura` es un dato, no un antetítulo, y en versalitas no era ninguna de las dos cosas.
977
+ The metadata line uses `meta` and not `eyebrow`: `18 ago 2026 · 8 min de lectura` is a datum, not an overline, and in small caps it was neither.
926
978
 
927
- - Extiende: `Omit<TarjetaProps, 'children' \| 'title'>`
979
+ - Extends: `Omit<CardShellProps, 'children' \| 'title'>`
928
980
 
929
- | prop | tipo | req. | defecto | qué hace |
981
+ | prop | type | req. | default | what it does |
930
982
  | --- | --- | --- | --- | --- |
931
- | `asChild` | `boolean \| undefined` | | | Renderiza el hijo en vez de un `<a>`. Es como se enchufa el `Link` de Next o de Astro sin que la librería dependa de ningún enrutador. |
932
- | `date` | `ReactNode` | | | Fecha ya formateada por el proyecto: la librería no impone locale. |
933
- | `dateTime` | `string` | | | Valor de `datetime` del `<time>`, en ISO. |
934
- | `excerpt` | `ReactNode` | | | Entradilla. Se corta a dos líneas para que la rejilla no se desalinee. |
935
- | `headingLevel` | `2 \| 3` | | `3` | Nivel del titular. `h3` por defecto: una tarjeta suelta en una rejilla no gana el nivel que su posición no le da. |
983
+ | `asChild` | `boolean \| undefined` | | | Renders the child instead of an `<a>`. It is how Next's or Astro's `Link` plugs in without the library depending on any router. |
984
+ | `date` | `ReactNode` | | | Date already formatted by the project: the library imposes no locale. |
985
+ | `dateTime` | `string` | | | The `<time>` element's `datetime` value, in ISO. |
986
+ | `excerpt` | `ReactNode` | | | Standfirst. Clamped to two lines so the grid does not fall out of line. |
987
+ | `headingLevel` | `2 \| 3` | | `3` | The headline level. `h3` by default: a lone card in a grid does not earn a level its position does not give it. |
936
988
  | `readingMinutes` | `number` | | | |
989
+ | `tagAsChild` | `(props: { tag: string; children: ReactNode; }) => ReactNode` | | | Renders each tag through the child, so an E2E suite can reach it. |
937
990
  | `tags` | `readonly string[]` | | | |
938
- | `title` | `ReactNode` | sí | | |
991
+ | `title` | `ReactNode` | yes | | |
939
992
 
940
993
  ### AudioPlayer
941
994
 
942
- Fuente: `src/components/audio-player/index.tsx`
995
+ Source: `src/components/audio-player/index.tsx`
943
996
 
944
- - Extiende: `{ src: string; title?: string; /** * `full` para páginas de podcast, `compact` para barras laterales y `banner` * para artículos con narración. `compact` y `banner` traen además el * reproductor flotante cuando el estático sale de vista. */ mode?: AudioPlayerMode \| undefined; /** * Se llama una sola vez por carga, la primera vez que el audio arranca. * Aquí es donde el proyecto engancha su analítica; la librería no la trae. */ onFirstPlay?: ((title?: string) => void) \| undefined; }`
997
+ - Extends: `{ src: string; title?: string; /** * `full` for podcast pages, `compact` for sidebars and `banner` for articles * with narration. `compact` and `banner` also bring the floating player when * the static one leaves the viewport. */ mode?: AudioPlayerMode \| undefined; /** * Called once per load, the first time the audio starts. * This is where the project hooks up its analytics; the library ships none. */ onFirstPlay?: ((title?: string) => void) \| undefined; }`
945
998
 
946
- | prop | tipo | req. | defecto | qué hace |
999
+ | prop | type | req. | default | what it does |
947
1000
  | --- | --- | --- | --- | --- |
948
- | `mode` | `"banner" \| "full" \| "compact"` | | `full` | `full` para páginas de podcast, `compact` para barras laterales y `banner` para artículos con narración. `compact` y `banner` traen además el reproductor flotante cuando el estático sale de vista. |
949
- | `onFirstPlay` | `(title?: string \| undefined) => void` | | | Se llama una sola vez por carga, la primera vez que el audio arranca. Aquí es donde el proyecto engancha su analítica; la librería no la trae. |
950
- | `src` | `string` | sí | | |
1001
+ | `mode` | `"banner" \| "full" \| "compact"` | | `full` | `full` for podcast pages, `compact` for sidebars and `banner` for articles with narration. `compact` and `banner` also bring the floating player when the static one leaves the viewport. |
1002
+ | `onFirstPlay` | `(title?: string \| undefined) => void` | | | Called once per load, the first time the audio starts. This is where the project hooks up its analytics; the library ships none. |
1003
+ | `src` | `string` | yes | | |
951
1004
  | `title` | `string` | | | |
952
1005
 
953
1006
  ### AuthorCard
954
1007
 
955
- Fuente: `src/components/author-card/index.tsx`
1008
+ Source: `src/components/author-card/index.tsx`
956
1009
 
957
- La firma al pie del artículo: avatar 52px, nombre 15/500 y el rol en mono muted. Tres datos, ni uno más.
1010
+ The byline at the foot of the article: 52px avatar, 15/500 name and the role in muted mono. Three data points, not one more.
958
1011
 
959
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'role'>`
1012
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'role'>`
960
1013
 
961
- | prop | tipo | req. | defecto | qué hace |
1014
+ | prop | type | req. | default | what it does |
962
1015
  | --- | --- | --- | --- | --- |
963
- | `action` | `ReactNode` | | | Enlaces o botón de contacto. |
964
- | `bio` | `ReactNode` | | | Una o dos frases. Se corta a 68ch sola. |
965
- | `name` | `string` | sí | | |
966
- | `role` | `ReactNode` | | | El rol. Va en mono: es un dato, no una frase. |
967
- | `src` | `string` | | | URL del avatar. Sin ella se muestran las iniciales. |
1016
+ | `action` | `ReactNode` | | | Links or a contact button. |
1017
+ | `bio` | `ReactNode` | | | One or two sentences. It clamps itself to 68ch. |
1018
+ | `name` | `string` | yes | | |
1019
+ | `role` | `ReactNode` | | | The role. It goes in mono: it is a datum, not a sentence. |
1020
+ | `src` | `string` | | | The avatar's URL. Without it the initials are shown. |
968
1021
 
969
1022
  ### Blockquote
970
1023
 
971
- Fuente: `src/components/blockquote/index.tsx`
1024
+ Source: `src/components/blockquote/index.tsx`
972
1025
 
973
- La barra lateral es `accent`, que es el color interactivo, porque una cita es la voz de otro entrando en el texto. No lleva comillas decorativas: los glifos del sistema son SVG y una comilla de adorno no aporta nada que el borde y la sangría no digan ya.
1026
+ The side bar is `accent`, the interactive color, because a quotation is somebody else's voice entering the text. It carries no decorative quote marks: the system's glyphs are SVG, and an ornamental quote adds nothing the border and the indent do not already say.
974
1027
 
975
- - Extiende: `Omit<ComponentPropsWithoutRef<'blockquote'>, 'cite'>`
1028
+ - Extends: `Omit<ComponentPropsWithoutRef<'blockquote'>, 'cite'>`
976
1029
 
977
- | prop | tipo | req. | defecto | qué hace |
1030
+ | prop | type | req. | default | what it does |
978
1031
  | --- | --- | --- | --- | --- |
979
- | `author` | `ReactNode` | | | Quién lo dijo. Se marca como `<cite>`. |
980
- | `source` | `ReactNode` | | | Dónde lo dijo: charla, artículo, conversación. |
1032
+ | `author` | `ReactNode` | | | Who said it. Marked up as `<cite>`. |
1033
+ | `source` | `ReactNode` | | | Where they said it: a talk, an article, a conversation. |
981
1034
 
982
1035
  ### Breadcrumb
983
1036
 
984
- Fuente: `src/components/breadcrumb/index.tsx`
1037
+ Source: `src/components/breadcrumb/index.tsx`
985
1038
 
986
- - Extiende: `Omit<ComponentPropsWithoutRef<'nav'>, 'children'>`
1039
+ - Extends: `Omit<ComponentPropsWithoutRef<'nav'>, 'children'>`
987
1040
 
988
- | prop | tipo | req. | defecto | qué hace |
1041
+ | prop | type | req. | default | what it does |
989
1042
  | --- | --- | --- | --- | --- |
990
- | `homeHref` | `string` | | `/` | Destino del `~`. Por defecto, la raíz del sitio. |
991
- | `homeLabel` | `string` | | `Inicio` | Etiqueta accesible del `~`, que si no se lee como una tilde suelta. |
992
- | `items` | `readonly Migaja[]` | sí | | |
993
- | `linkAsChild` | `(props: { href: string; children: ReactNode; }) => ReactNode` | | | Renderiza los enlaces con el hijo, para enchufar el `Link` de Next o Astro. Recibe cada `href` en el `props` del Slot. |
1043
+ | `homeHref` | `string` | | `/` | Where the `~` goes. The site root by default. |
1044
+ | `homeLabel` | `string` | | `Inicio` | Accessible label for the `~`, which otherwise reads as a stray tilde. |
1045
+ | `items` | `readonly Crumb[]` | yes | | |
1046
+ | `linkAsChild` | `(props: { href: string; children: ReactNode; }) => ReactNode` | | | Renders the links through the child, to plug in Next's or Astro's `Link`. It receives each `href` in the Slot's `props`. |
994
1047
 
995
1048
  ### CodeBlock
996
1049
 
997
- Fuente: `src/components/code-block/index.tsx`
1050
+ Source: `src/components/code-block/index.tsx`
998
1051
 
999
- `brand.hull` es «casco · contorno y fondo de bloques de código», así que un bloque de código es oscuro también en modo claro. Por eso la raíz declara `data-theme="dark"`: todo lo de dentro — tinta, hairline, acento — pasa a la paleta oscura sin importar el tema de la página. Es la única isla de tema invertido del sistema, y es deliberada.
1052
+ `brand.hull` is «hull · outline and the background of code blocks», so a code block is dark in light mode too. That is why the root declares `data-theme="dark"`: everything inside — ink, hairline, accent — switches to the dark palette regardless of the page's theme. It is the system's only island of inverted theme, and it is deliberate.
1000
1053
 
1001
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'children'>`
1054
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'children'>`
1002
1055
 
1003
- | prop | tipo | req. | defecto | qué hace |
1056
+ | prop | type | req. | default | what it does |
1004
1057
  | --- | --- | --- | --- | --- |
1005
- | `children` | `ReactNode` | sí | | El código ya resaltado, o texto plano. |
1006
- | `copyText` | `string` | | | Texto que se copia al portapapeles. Sin esto, no se muestra el botón. |
1007
- | `language` | `string` | | | Etiqueta del lenguaje. Se muestra en la barra superior. |
1058
+ | `children` | `ReactNode` | yes | | The already-highlighted code, or flat text. |
1059
+ | `copyText` | `string` | | | The text copied to the clipboard. Without it, the button is not shown. |
1060
+ | `language` | `string` | | | The language label. Shown in the top bar. |
1008
1061
 
1009
1062
  ### CourseCard
1010
1063
 
1011
- Fuente: `src/components/course-card/index.tsx`
1064
+ Source: `src/components/course-card/index.tsx`
1012
1065
 
1013
- - Extiende: `Omit<TarjetaProps, 'children' \| 'title'>`
1066
+ - Extends: `Omit<CardShellProps, 'children' \| 'title'>`
1014
1067
 
1015
- | prop | tipo | req. | defecto | qué hace |
1068
+ | prop | type | req. | default | what it does |
1016
1069
  | --- | --- | --- | --- | --- |
1017
- | `asChild` | `boolean \| undefined` | | | Renderiza el hijo en vez de un `<a>`. Es como se enchufa el `Link` de Next o de Astro sin que la librería dependa de ningún enrutador. |
1018
- | `meta` | `readonly ReactNode[]` | | | Nivel, duración, número de lecciones: lo que el proyecto quiera listar. |
1019
- | `progress` | `number` | | | Porcentaje cursado. Solo tiene sentido para quien ya está inscrito; cuando se pasa, la barra va en arena, que es el color del progreso de curso. |
1020
- | `status` | `ReactNode` | | | Etiqueta de estado: «próximamente», «gratis», «nuevo». |
1070
+ | `asChild` | `boolean \| undefined` | | | Renders the child instead of an `<a>`. It is how Next's or Astro's `Link` plugs in without the library depending on any router. |
1071
+ | `meta` | `readonly ReactNode[]` | | | Level, duration, number of lessons: whatever the project wants to list. |
1072
+ | `progress` | `number` | | | Percentage completed. It only makes sense for someone already enrolled; when passed, the bar goes in sand, which is the color of course progress. |
1073
+ | `status` | `ReactNode` | | | Status label: «próximamente», «gratis», «nuevo». |
1021
1074
  | `summary` | `ReactNode` | | | |
1022
- | `title` | `ReactNode` | sí | | |
1075
+ | `title` | `ReactNode` | yes | | |
1023
1076
 
1024
1077
  ### EmptyState
1025
1078
 
1026
- Fuente: `src/components/empty-state/index.tsx`
1079
+ Source: `src/components/empty-state/index.tsx`
1027
1080
 
1028
- La regla más importante de la mascota, por fin como código.
1081
+ The mascot's most important rule, finally as code.
1029
1082
 
1030
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'title'>`
1083
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'title'>`
1031
1084
 
1032
- | prop | tipo | req. | defecto | qué hace |
1085
+ | prop | type | req. | default | what it does |
1033
1086
  | --- | --- | --- | --- | --- |
1034
- | `action` | `ReactNode` | | | La acción que saca del estado vacío. Normalmente un botón terciario. |
1035
- | `basePath` | `string` | | | Dónde se sirven los PNG de la marca. |
1036
- | `description` | `ReactNode` | | | Una línea explicando qué falta o qué hacer. |
1037
- | `expresion` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | sí | | La cara. Obligatoria: sin ella esto es un párrafo centrado. |
1038
- | `title` | `ReactNode` | sí | | |
1087
+ | `action` | `ReactNode` | | | The action that gets you out of the empty state. Usually a tertiary button. |
1088
+ | `basePath` | `string` | | | Where the brand PNGs are served from. |
1089
+ | `description` | `ReactNode` | | | One line explaining what is missing or what to do. |
1090
+ | `expression` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | yes | | The face. Mandatory: without it this is a centred paragraph. |
1091
+ | `title` | `ReactNode` | yes | | |
1039
1092
 
1040
1093
  ### EventCalendar
1041
1094
 
1042
- Fuente: `src/components/event-calendar/index.tsx`
1095
+ Source: `src/components/event-calendar/index.tsx`
1043
1096
 
1044
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' \| 'children'>`
1097
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' \| 'children'>`
1045
1098
 
1046
- | prop | tipo | req. | defecto | qué hace |
1099
+ | prop | type | req. | default | what it does |
1047
1100
  | --- | --- | --- | --- | --- |
1048
- | `emptyMessage` | `ReactNode` | | `Nada en este día.` | Texto del panel cuando el día elegido no tiene nada. |
1049
- | `events` | `readonly EventoCalendario[]` | sí | | |
1050
- | `formatDay` | `(dia: Date) => string` | | `(dia) => format(dia, "EEEE d de MMMM", { locale: es })` | Encabezado del panel. Por defecto, `es` de date-fns, como `Calendar`. |
1051
- | `formatTime` | `(fecha: Date) => string` | | `(fecha) => format(fecha, HH:mm, { locale: es })` | |
1052
- | `onCreateEvent` | `(evento: Omit<EventoCalendario, "id">) => void` | | | Sin él, la agenda es de solo lectura y no se pinta el formulario. |
1101
+ | `emptyMessage` | `ReactNode` | | `Nada en este día.` | The panel's text when the chosen day has nothing on it. |
1102
+ | `events` | `readonly CalendarEvent[]` | yes | | |
1103
+ | `formatDay` | `(day: Date) => string` | | `(day) => format(day, "EEEE d de MMMM", { locale: es })` | The panel's heading. Defaults to date-fns' `es`, like `Calendar`. |
1104
+ | `formatTime` | `(date: Date) => string` | | `(date) => format(date, HH:mm, { locale: es })` | |
1105
+ | `onCreateEvent` | `(event: Omit<CalendarEvent, "id">) => void` | | | Without it, the schedule is read-only and the form is not painted. |
1053
1106
  | `onDeleteEvent` | `(id: string) => void` | | | |
1054
- | `onSelectDay` | `(dia: Date) => void` | | | |
1055
- | `onUpdateEvent` | `(evento: EventoCalendario) => void` | | | |
1056
- | `selected` | `Date` | | | Día seleccionado, si el proyecto lo controla. Sin él, empieza en hoy. |
1107
+ | `onSelectDay` | `(day: Date) => void` | | | |
1108
+ | `onUpdateEvent` | `(event: CalendarEvent) => void` | | | |
1109
+ | `selected` | `Date` | | | Selected day, if the project controls it. Without it, it starts on today. |
1057
1110
 
1058
1111
  ### Footer, FooterLink
1059
1112
 
1060
- Fuente: `src/components/footer/index.tsx`
1113
+ Source: `src/components/footer/index.tsx`
1061
1114
 
1062
1115
  **Footer**
1063
- - Extiende: `ComponentPropsWithoutRef<'footer'>`
1116
+ - Extends: `ComponentPropsWithoutRef<'footer'>`
1064
1117
 
1065
- | prop | tipo | req. | defecto | qué hace |
1118
+ | prop | type | req. | default | what it does |
1066
1119
  | --- | --- | --- | --- | --- |
1067
- | `brand` | `ReactNode` | | | La fila de marca: la aleta y el wordmark, arriba del todo. |
1068
- | `social` | `readonly Red[]` | | | |
1069
- | `year` | `number` | | `new Date().getFullYear()` | Año de la firma. |
1120
+ | `brand` | `ReactNode` | | | The brand row: the fin and the wordmark, at the very top. |
1121
+ | `social` | `readonly SocialLink[]` | | | |
1122
+ | `year` | `number` | | `new Date().getFullYear()` | The signature's year. |
1070
1123
 
1071
1124
  **FooterLink**
1072
- - Extiende: `ComponentPropsWithoutRef<'a'>`
1125
+ - Extends: `ComponentPropsWithoutRef<'a'>`
1073
1126
 
1074
- | prop | tipo | req. | defecto | qué hace |
1127
+ | prop | type | req. | default | what it does |
1075
1128
  | --- | --- | --- | --- | --- |
1076
1129
  | `asChild` | `boolean \| undefined` | | `false` | |
1077
1130
 
1078
1131
  ### Hero
1079
1132
 
1080
- Fuente: `src/components/hero/index.tsx`
1133
+ Source: `src/components/hero/index.tsx`
1081
1134
 
1082
- UNO por sitio. Es la única pieza del sistema que se gasta como el botón de conversión, y por la misma razón: si hay dos, no hay ninguno.
1135
+ ONE per site. It is the only piece in the system that is spent like the conversion button, and for the same reason: if there are two, there are none.
1083
1136
 
1084
- - Extiende: `Omit<ComponentPropsWithoutRef<'section'>, 'title'>`
1137
+ - Extends: `Omit<ComponentPropsWithoutRef<'section'>, 'title'>`
1085
1138
 
1086
- | prop | tipo | req. | defecto | qué hace |
1139
+ | prop | type | req. | default | what it does |
1087
1140
  | --- | --- | --- | --- | --- |
1088
- | `action` | `ReactNode` | | | Los botones. Aquí va el único `conversion` de la pantalla. |
1141
+ | `action` | `ReactNode` | | | The buttons. The screen's only `conversion` goes here. |
1089
1142
  | `basePath` | `string` | | | |
1090
1143
  | `description` | `ReactNode` | | | |
1091
- | `eyebrow` | `ReactNode` | | | Mono, versalitas, en acento. |
1092
- | `pose` | `"desk" \| "laptop-coffee" \| "peek" \| "surf"` | | | La pose de Tiburoncín. Sin ella el hero es un panel con texto. |
1093
- | `title` | `ReactNode` | sí | | |
1094
- | `variant` | `"cabecera" \| "centrado"` | | `cabecera` | `cabecera` sangra la pose por la esquina; `centrado` la pone arriba y centra el texto, para una página que es solo esto. |
1144
+ | `eyebrow` | `ReactNode` | | | Mono, small caps, in accent. |
1145
+ | `pose` | `"desk" \| "laptop-coffee" \| "peek" \| "surf"` | | | Tiburoncín's pose. Without it the hero is a panel with text. |
1146
+ | `title` | `ReactNode` | yes | | |
1147
+ | `variant` | `"header" \| "centered"` | | `header` | `header` bleeds the pose off the corner; `centered` puts it on top and centres the text, for a page that is only this. |
1095
1148
 
1096
1149
  ### LinkRow
1097
1150
 
1098
- Fuente: `src/components/link-row/index.tsx`
1151
+ Source: `src/components/link-row/index.tsx`
1099
1152
 
1100
- Migrado desde `links/src/components/Card.astro`. El original escalaba la tarjeta al 102 %, subía el título un píxel y giraba y agrandaba el icono en hover — cuatro movimientos que el sistema no permite. Aquí el hover cambia el borde y el color del icono, y nada más.
1153
+ Migrated from `links/src/components/Card.astro`. The original scaled the card to 102 %, lifted the title by a pixel and rotated and enlarged the icon on hover — four movements the system does not allow. Here the hover changes the border and the icon's color, and nothing else.
1101
1154
 
1102
- - Extiende: `Omit<TarjetaProps, 'children'>`
1155
+ - Extends: `Omit<CardShellProps, 'children'>`
1103
1156
 
1104
- | prop | tipo | req. | defecto | qué hace |
1157
+ | prop | type | req. | default | what it does |
1105
1158
  | --- | --- | --- | --- | --- |
1106
- | `asChild` | `boolean \| undefined` | | | Renderiza el hijo en vez de un `<a>`. Es como se enchufa el `Link` de Next o de Astro sin que la librería dependa de ningún enrutador. |
1159
+ | `asChild` | `boolean \| undefined` | | | Renders the child instead of an `<a>`. It is how Next's or Astro's `Link` plugs in without the library depending on any router. |
1107
1160
  | `description` | `ReactNode` | | | |
1108
- | `external` | `boolean \| undefined` | | `false` | Marca el enlace como externo: añade la flecha y el `rel` seguro. |
1109
- | `icon` | `ReactNode` | | | Glifo SVG del destino. Nunca un emoji. |
1110
- | `name` | `ReactNode` | sí | | |
1161
+ | `external` | `boolean \| undefined` | | `false` | Marks the link as external: adds the arrow and the safe `rel`. |
1162
+ | `icon` | `ReactNode` | | | The target's SVG glyph. Never an emoji. |
1163
+ | `name` | `ReactNode` | yes | | |
1111
1164
 
1112
1165
  ### Nav, NavItem
1113
1166
 
1114
- Fuente: `src/components/nav/index.tsx`
1167
+ Source: `src/components/nav/index.tsx`
1115
1168
 
1116
1169
  **Nav**
1117
- La barra del sitio: 64px, abismo al 86 % y desenfoque de 14px detrás.
1170
+ The site bar: 64px, abyss at 86 % and a 14px blur behind it.
1118
1171
 
1119
- - Extiende: `ComponentPropsWithoutRef<'header'>`
1172
+ - Extends: `ComponentPropsWithoutRef<'header'>`
1120
1173
 
1121
- | prop | tipo | req. | defecto | qué hace |
1174
+ | prop | type | req. | default | what it does |
1122
1175
  | --- | --- | --- | --- | --- |
1123
- | `actions` | `ReactNode` | | | Acciones a la derecha: conversión, cambio de tema, buscar. |
1124
- | `brand` | `ReactNode` | | | El logo, a la izquierda. |
1176
+ | `actions` | `ReactNode` | | | Actions on the right: conversion, theme switch, search. |
1177
+ | `brand` | `ReactNode` | | | The logo, on the left. |
1125
1178
 
1126
1179
  **NavItem**
1127
- El `./` lo pone el componente, no quien lo usa.
1180
+ The `./` is put there by the component, not by whoever uses it.
1128
1181
 
1129
- - Extiende: `ComponentPropsWithoutRef<'a'>`
1182
+ - Extends: `ComponentPropsWithoutRef<'a'>`
1130
1183
 
1131
- | prop | tipo | req. | defecto | qué hace |
1184
+ | prop | type | req. | default | what it does |
1132
1185
  | --- | --- | --- | --- | --- |
1133
- | `active` | `boolean \| undefined` | | `false` | Sección actual: bioluz con subrayado de 1px. |
1134
- | `asChild` | `boolean \| undefined` | | `false` | Renderiza el hijo en vez de un `<a>`, para el `Link` del enrutador. |
1186
+ | `active` | `boolean \| undefined` | | `false` | Current section: biolume with a 1px underline. |
1187
+ | `asChild` | `boolean \| undefined` | | `false` | Renders the child instead of an `<a>`, for the router's `Link`. |
1135
1188
 
1136
1189
  ### NewsletterForm
1137
1190
 
1138
- Fuente: `src/components/newsletter-form/index.tsx`
1191
+ Source: `src/components/newsletter-form/index.tsx`
1139
1192
 
1140
- - Extiende: `Omit<ComponentPropsWithoutRef<'section'>, 'title' \| 'onSubmit'>`
1193
+ - Extends: `Omit<ComponentPropsWithoutRef<'section'>, 'title' \| 'onSubmit'>`
1141
1194
 
1142
- | prop | tipo | req. | defecto | qué hace |
1195
+ | prop | type | req. | default | what it does |
1143
1196
  | --- | --- | --- | --- | --- |
1197
+ | `aside` | `ReactNode` | | | The illustration, as a second column inside the panel. |
1144
1198
  | `basePath` | `string` | | | |
1145
1199
  | `description` | `ReactNode` | | | |
1146
- | `disclaimer` | `ReactNode` | | | La letra pequeña. Es el «sin spam», y por eso admite cara. |
1147
- | `errorMessage` | `ReactNode` | | `No se pudo suscribir ese correo. Revísalo y vuelve a intentar.` | |
1148
- | `expresion` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | | | |
1149
- | `fieldLabel` | `string` | | `Correo electrónico` | |
1150
- | `nameField` | `boolean` | | `false` | Añade el campo de nombre delante del correo. |
1151
- | `nameInputProps` | `Omit<InputProps, "id" \| "disabled" \| "name">` | | | Lo que el proyecto necesite colgar del campo de nombre: `minLength`, `maxLength`, `pattern`. La librería no impone ninguna de las tres. |
1200
+ | `disclaimer` | `ReactNode` | | | The small print. It is the «sin spam», which is why it accepts a face. |
1201
+ | `errorMessage` | `ReactNode` | | `No se pudo suscribir ese email. Revísalo y vuelve a intentar.` | |
1202
+ | `expression` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | | | |
1203
+ | `fieldErrors` | `{ name?: ReactNode; email?: ReactNode; }` | | | A message under one specific field, instead of the single alert. |
1204
+ | `fieldLabel` | `string` | | `Email electrónico` | |
1205
+ | `nameField` | `boolean` | | `false` | Adds the name field ahead of the email one. |
1206
+ | `nameInputProps` | `Omit<InputProps, "id" \| "disabled" \| "name">` | | | Whatever the project needs to hang off the name field: `minLength`, `maxLength`, `pattern`. The library imposes none of the three. |
1152
1207
  | `nameLabel` | `string` | | `Nombre` | |
1153
1208
  | `namePlaceholder` | `string` | | `Cómo te llamas` | |
1154
- | `onSubmitEmail` | `(email: string, name?: string \| undefined) => void` | | | Se dispara con el correo ya leído del campo, y con el nombre si el campo está puesto. |
1155
- | `placeholder` | `string` | | `tu@correo.dev` | |
1156
- | `state` | `"error" \| "reposo" \| "enviando" \| "exito"` | | `reposo` | |
1209
+ | `onFieldChange` | `(field: "email" \| "name", value: string) => void` | | | Fires when either field changes. It is where the project clears its error. |
1210
+ | `onSubmitEmail` | `(email: string, name?: string \| undefined) => void` | | | Fires with the email already read from the field, and with the name if that field is enabled. |
1211
+ | `placeholder` | `string` | | `tu@email.dev` | |
1212
+ | `resetOnSuccess` | `boolean` | | `true` | Empties the fields after a successful subscription. On by default. |
1213
+ | `state` | `"success" \| "error" \| "idle" \| "sending"` | | `idle` | |
1157
1214
  | `submitLabel` | `string` | | `Suscribirme` | |
1158
- | `successMessage` | `ReactNode` | | `Ya estás dentro. Te llega un correo cada dos semanas, y nada más.` | |
1159
- | `title` | `ReactNode` | sí | | |
1215
+ | `successMessage` | `ReactNode` | | `Ya estás dentro. Te llega un email cada dos semanas, y nada más.` | |
1216
+ | `title` | `ReactNode` | yes | | |
1160
1217
 
1161
1218
  ### PageHeader
1162
1219
 
1163
- Fuente: `src/components/page-header/index.tsx`
1220
+ Source: `src/components/page-header/index.tsx`
1164
1221
 
1165
- Una sola cabecera en dos escalas, no dos componentes.
1222
+ One header at two scales, not two components.
1166
1223
 
1167
- - Extiende: `Omit<ComponentPropsWithoutRef<'header'>, 'title'> & VariantProps<typeof cabecera>`
1224
+ - Extends: `Omit<ComponentPropsWithoutRef<'header'>, 'title'> & VariantProps<typeof header>`
1168
1225
 
1169
- | prop | tipo | req. | defecto | qué hace |
1226
+ | prop | type | req. | default | what it does |
1170
1227
  | --- | --- | --- | --- | --- |
1171
- | `action` | `ReactNode` | | | Ranura para las llamadas a la acción. Si aquí va un botón de conversión, es el único de la pantalla. |
1172
- | `as` | `"h2" \| "h1"` | | `h1` | Nivel del titular. `h1` salvo que la página ya tenga uno. |
1228
+ | `action` | `ReactNode` | | | Slot for the calls to action. If a conversion button goes here, it is the only one on the screen. |
1229
+ | `as` | `"h1" \| "h2"` | | `h1` | The headline's level. `h1` unless the page already has one. |
1173
1230
  | `description` | `ReactNode` | | | |
1174
- | `eyebrow` | `ReactNode` | | | Mono, versalitas, en acento. Es la sección a la que pertenece la página. |
1231
+ | `eyebrow` | `ReactNode` | | | Mono, small caps, in accent. It is the section the page belongs to. |
1175
1232
  | `size` | `"display" \| "page"` | | `page` | |
1176
- | `title` | `ReactNode` | sí | | |
1233
+ | `title` | `ReactNode` | yes | | |
1177
1234
 
1178
1235
  ### ScrollingProgressBar
1179
1236
 
1180
- Fuente: `src/components/scrolling-progress-bar/index.tsx`
1237
+ Source: `src/components/scrolling-progress-bar/index.tsx`
1181
1238
 
1182
- Cuánto llevas leído. NO es `Progress` con otro nombre.
1239
+ How much you have read. It is NOT `Progress` under another name.
1183
1240
 
1184
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'children'>`
1241
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'children'>`
1185
1242
 
1186
- | prop | tipo | req. | defecto | qué hace |
1243
+ | prop | type | req. | default | what it does |
1187
1244
  | --- | --- | --- | --- | --- |
1188
- | `sticky` | `boolean` | | `true` | Pega la barra al borde superior de la ventana. |
1189
- | `target` | `RefObject<HTMLElement \| null>` | | | El elemento que se mide. Sin él, el documento entero. |
1190
- | `tone` | `"accent" \| "warm"` | | `accent` | Arena en vez de bioluz, para igualar el progreso de curso. |
1245
+ | `sticky` | `boolean` | | `true` | Pins the bar to the top edge of the window. |
1246
+ | `target` | `RefObject<HTMLElement \| null>` | | | The element being measured. Without it, the whole document. |
1247
+ | `tone` | `"accent" \| "warm"` | | `accent` | Sand instead of biolume, to match course progress. |
1191
1248
 
1192
1249
  ### SidebarItem, SidebarNav
1193
1250
 
1194
- Fuente: `src/components/sidebar-nav/index.tsx`
1251
+ Source: `src/components/sidebar-nav/index.tsx`
1195
1252
 
1196
1253
  **SidebarItem**
1197
- La barra lateral del admin del blog.
1254
+ The blog admin's sidebar.
1198
1255
 
1199
- - Extiende: `ComponentPropsWithoutRef<'a'>`
1256
+ - Extends: `ComponentPropsWithoutRef<'a'>`
1200
1257
 
1201
- | prop | tipo | req. | defecto | qué hace |
1258
+ | prop | type | req. | default | what it does |
1202
1259
  | --- | --- | --- | --- | --- |
1203
1260
  | `active` | `boolean \| undefined` | | `false` | |
1204
1261
  | `asChild` | `boolean \| undefined` | | `false` | |
1205
- | `badge` | `ReactNode` | | | Contador a la derecha: borradores pendientes, media sin usar. |
1262
+ | `badge` | `ReactNode` | | | Counter on the right: pending drafts, unused media. |
1206
1263
 
1207
1264
  **SidebarNav**
1208
- - Extiende: `ComponentPropsWithoutRef<'nav'>`
1265
+ - Extends: `ComponentPropsWithoutRef<'nav'>`
1209
1266
 
1210
- | prop | tipo | req. | defecto | qué hace |
1267
+ | prop | type | req. | default | what it does |
1211
1268
  | --- | --- | --- | --- | --- |
1212
1269
  | `branch` | `ReactNode` | | | |
1213
- | `version` | `ReactNode` | | | Versión y rama, al pie. |
1270
+ | `version` | `ReactNode` | | | Version and branch, at the bottom. |
1214
1271
 
1215
1272
  ### Stat
1216
1273
 
1217
- Fuente: `src/components/stat/index.tsx`
1274
+ Source: `src/components/stat/index.tsx`
1218
1275
 
1219
- Una métrica grande: el número en la escala `stat` y su nombre debajo.
1276
+ A large metric: the number in the `stat` scale and its name underneath.
1220
1277
 
1221
- - Extiende: `Omit<ComponentPropsWithoutRef<'div'>, 'title'>`
1278
+ - Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'title'>`
1222
1279
 
1223
- | prop | tipo | req. | defecto | qué hace |
1280
+ | prop | type | req. | default | what it does |
1224
1281
  | --- | --- | --- | --- | --- |
1225
- | `description` | `ReactNode` | | | La bajada: el matiz que el número solo no da. «12 aplicaciones» no dice si son muchas, y aquí es donde se dice. |
1226
- | `icon` | `ReactNode` | | | Glifo al lado del título, a 1em. Hereda `currentColor`, así que sigue al tono del título y no hay que teñirlo aparte. |
1227
- | `label` | `ReactNode` | sí | | Qué se está contando. Va en mono versalitas. |
1228
- | `progress` | `number` | | | Con `progress`, la métrica se lee como avance y añade la barra. |
1229
- | `tone` | `"neutral" \| "alerta"` | | `neutral` | `alerta` solo cuando el número ES el problema. |
1230
- | `value` | `ReactNode` | sí | | El número, ya formateado. La librería no impone locale. |
1282
+ | `description` | `ReactNode` | | | The standfirst: the nuance the number alone does not give. «12 aplicaciones» does not say whether that is a lot, and this is where that gets said. |
1283
+ | `icon` | `ReactNode` | | | Glyph beside the title, at 1em. It inherits `currentColor`, so it follows the title's tone and does not have to be tinted separately. |
1284
+ | `label` | `ReactNode` | yes | | What is being counted. It goes in mono small caps. |
1285
+ | `progress` | `number` | | | With `progress`, the metric reads as progress and adds the bar. |
1286
+ | `tone` | `"neutral" \| "alerta"` | | `neutral` | `alerta` only when the number IS the problem. |
1287
+ | `value` | `ReactNode` | yes | | The number, already formatted. The library imposes no locale. |
1231
1288
 
1232
1289
  ### TalkCard
1233
1290
 
1234
- Fuente: `src/components/talk-card/index.tsx`
1291
+ Source: `src/components/talk-card/index.tsx`
1292
+
1293
+ A talk has more than one destination, and that is what shapes this type.
1235
1294
 
1236
- - Extiende: `Omit<TarjetaProps, 'children' \| 'title'>`
1295
+ - Extends: `\| (Omit<CardShellProps, 'children' \| 'title'> & TalkContent & { /** The card is the link. Do not pass resources with this. */ resources?: never; }) \| (Omit<ComponentPropsWithoutRef<'article'>, 'title'> & TalkContent & { /** * Slides, repo, recording. They are the links, so the card stops being * one. */ resources: ReactNode; })`
1237
1296
 
1238
- | prop | tipo | req. | defecto | qué hace |
1297
+ | prop | type | req. | default | what it does |
1239
1298
  | --- | --- | --- | --- | --- |
1240
- | `asChild` | `boolean \| undefined` | | | Renderiza el hijo en vez de un `<a>`. Es como se enchufa el `Link` de Next o de Astro sin que la librería dependa de ningún enrutador. |
1241
1299
  | `date` | `ReactNode` | | | |
1242
1300
  | `dateTime` | `string` | | | |
1243
- | `description` | `ReactNode` | | | De qué iba la charla. Se corta a dos líneas, igual que el `excerpt` de `ArticleCard`, para que la rejilla no se desalinee. |
1244
- | `event` | `ReactNode` | sí | | Dónde se dio: la conferencia, el meetup, el equipo. |
1301
+ | `description` | `ReactNode` | | | What the talk was about. Clamped to two lines, same as `ArticleCard`'s `excerpt`, so the grid does not fall out of line. |
1302
+ | `event` | `ReactNode` | yes | | Where it was given: the conference, the meetup, the team. |
1245
1303
  | `location` | `ReactNode` | | | |
1246
- | `status` | `ReactNode` | | | Etiqueta corta de estado: «con vídeo», «próxima», «solo audio». |
1247
- | `title` | `ReactNode` | sí | | |
1304
+ | `resources` | `ReactNode` | | | The card is the link. Do not pass resources with this. Slides, repo, recording. They are the links, so the card stops being one. |
1305
+ | `status` | `ReactNode` | | | Short status label: «con vídeo», «próxima», «solo audio». |
1306
+ | `title` | `ReactNode` | yes | | |
1248
1307
 
1249
1308
  ### ThemeToggle
1250
1309
 
1251
- Fuente: `src/components/theme-toggle/index.tsx`
1310
+ Source: `src/components/theme-toggle/index.tsx`
1252
1311
 
1253
- El control que faltaba. La librería definía todo el sistema de temas y no exponía lo que lo cambia, así que dos proyectos lo reimplementaban.
1312
+ The control that was missing. The library defined the whole theming system and exposed nothing that changes it, so two projects were reimplementing it.
1254
1313
 
1255
- - Extiende: `Omit<ComponentPropsWithoutRef<'button'>, 'onClick'>`
1314
+ - Extends: `Omit<ComponentPropsWithoutRef<'button'>, 'onClick'>`
1256
1315
 
1257
- | prop | tipo | req. | defecto | qué hace |
1316
+ | prop | type | req. | default | what it does |
1258
1317
  | --- | --- | --- | --- | --- |
1259
- | `label` | `string` | | `Cambiar de tema` | Nombre accesible. El botón no tiene texto visible, así que es lo único que lo nombra. |
1260
- | `onThemeChange` | `(tema: Tema) => void` | | | Se dispara con el tema que quedó puesto, por si el proyecto quiere anotarlo. |
1261
- | `size` | `"sm" \| "md" \| "lg" \| "icon"` | | `icon` | |
1262
- | `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary"` | | `secondary` | |
1318
+ | `label` | `string` | | `Cambiar de theme` | Accessible name. The button has no visible text, so it is the only thing naming it. |
1319
+ | `onThemeChange` | `(theme: Theme) => void` | | | Fires with whichever theme ended up set, in case the project wants to record it. |
1320
+ | `size` | `"sm" \| "md" \| "lg" \| "icon" \| "icon-sm"` | | `icon` | |
1321
+ | `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary" \| "destructive" \| "destructiveOutline"` | | `secondary` | |
1263
1322
 
1264
1323
  ### TableOfContents
1265
1324
 
1266
- Fuente: `src/components/toc/index.tsx`
1325
+ Source: `src/components/toc/index.tsx`
1267
1326
 
1268
- - Extiende: `Omit<ComponentPropsWithoutRef<'nav'>, 'children'>`
1327
+ - Extends: `Omit<ComponentPropsWithoutRef<'nav'>, 'children'>`
1269
1328
 
1270
- | prop | tipo | req. | defecto | qué hace |
1329
+ | prop | type | req. | default | what it does |
1271
1330
  | --- | --- | --- | --- | --- |
1272
- | `activeHref` | `string` | | | Ancla de la sección visible. |
1273
- | `items` | `readonly Entrada[]` | sí | | |
1331
+ | `activeHref` | `string` | | | The anchor of the visible section. |
1332
+ | `items` | `readonly TocEntry[]` | yes | | |
1274
1333
  | `linkAsChild` | `(props: { href: string; children: ReactNode; }) => ReactNode` | | | |
1275
1334
 
1276
- ## Marca
1335
+ ## Brand
1277
1336
 
1278
- Se importan de `@eduardoalvarez/arrecife` o `@eduardoalvarez/arrecife/brand`. 4 exportaciones.
1337
+ Imported from `@eduardoalvarez/arrecife` or `@eduardoalvarez/arrecife/brand`. 4 exports.
1279
1338
 
1280
- ### Isotipo
1339
+ ### Isotype
1281
1340
 
1282
- Fuente: `src/brand/isotipo.tsx`
1341
+ Source: `src/brand/isotype.tsx`
1283
1342
 
1284
- - Extiende: `Omit<ComponentPropsWithoutRef<'img'>, 'src' \| 'alt'>`
1343
+ - Extends: `Omit<ComponentPropsWithoutRef<'img'>, 'src' \| 'alt'>`
1285
1344
 
1286
- | prop | tipo | req. | defecto | qué hace |
1345
+ | prop | type | req. | default | what it does |
1287
1346
  | --- | --- | --- | --- | --- |
1288
- | `alt` | `string` | | | Texto alternativo. Vacío cuando el isotipo acompaña a un texto que ya lo nombra. |
1289
- | `basePath` | `string` | | `RUTA_ASSETS` | |
1290
- | `sobre` | `"oscuro" \| "claro"` | | `oscuro` | Sobre qué fondo se monta. Es obligatorio decidirlo, aunque tenga default: el cuerpo de la aleta es casi negro, así que la variante de dos azules desaparece sobre abismo. Al ser una prop, la regla deja de ser algo que recordar. |
1347
+ | `alt` | `string` | | | Alt text. Empty when the isotype accompanies text that already names it. |
1348
+ | `background` | `"dark" \| "light"` | | `dark` | Which background it sits on. Deciding is mandatory even though it has a default: the fin's body is nearly black, so the two-blue variant disappears over abyss. Being a prop, the rule stops being something to remember. |
1349
+ | `basePath` | `string` | | `ASSETS_PATH` | |
1291
1350
 
1292
1351
  ### Logo
1293
1352
 
1294
- Fuente: `src/brand/logo.tsx`
1353
+ Source: `src/brand/logo.tsx`
1295
1354
 
1296
- El wordmark sale de `naming.wordmark`, no de una cadena escrita a mano, y siempre dice «Eduardo Álvarez». La mascota se llama Tiburoncín y no aparece escrita dentro del logo: no hay ninguna prop que permita cambiar el texto.
1355
+ The wordmark comes from `naming.wordmark`, not from a hand-written string, and it always reads «Eduardo Álvarez». The mascot is called Tiburoncín and its name never appears inside the logo: there is no prop that changes the text.
1297
1356
 
1298
- - Extiende: `Omit<ComponentPropsWithoutRef<'span'>, 'children'>`
1357
+ - Extends: `Omit<ComponentPropsWithoutRef<'span'>, 'children'>`
1299
1358
 
1300
- | prop | tipo | req. | defecto | qué hace |
1359
+ | prop | type | req. | default | what it does |
1301
1360
  | --- | --- | --- | --- | --- |
1302
- | `basePath` | `string` | | `RUTA_ASSETS` | |
1303
- | `conLema` | `boolean \| undefined` | | `false` | Añade el lema bajo el wordmark, separado de la aleta por una divisoria. |
1304
- | `sobre` | `"oscuro" \| "claro"` | | `oscuro` | |
1305
- | `soloIsotipo` | `boolean \| undefined` | | `false` | Oculta el wordmark y deja solo la aleta, para barras muy estrechas. |
1361
+ | `background` | `"dark" \| "light"` | | `dark` | |
1362
+ | `basePath` | `string` | | `ASSETS_PATH` | |
1363
+ | `isotypeOnly` | `boolean \| undefined` | | `false` | Hides the wordmark and leaves only the fin, for very narrow bars. |
1364
+ | `withTagline` | `boolean \| undefined` | | `false` | Adds the tagline under the wordmark, separated from the fin by a divider. |
1306
1365
 
1307
- ### Mascota, CaraDeMascota
1366
+ ### Mascot, MascotFace
1308
1367
 
1309
- Fuente: `src/brand/mascota.tsx`
1368
+ Source: `src/brand/mascot.tsx`
1310
1369
 
1311
- **Mascota**
1312
- Tiburoncín de cuerpo entero.
1370
+ **Mascot**
1371
+ Full-body Tiburoncín.
1313
1372
 
1314
- - Extiende: `Base`
1373
+ - Extends: `Base`
1315
1374
 
1316
- | prop | tipo | req. | defecto | qué hace |
1375
+ | prop | type | req. | default | what it does |
1317
1376
  | --- | --- | --- | --- | --- |
1318
- | `alt` | `string` | | | Texto alternativo. Vacío por defecto: la mascota es ilustración y el texto que la acompaña ya dice lo que hay que saber. Se rellena solo cuando la imagen aporta información que no está escrita al lado. |
1319
- | `basePath` | `string` | | `RUTA_ASSETS` | |
1320
- | `pose` | `"desk" \| "laptop-coffee" \| "peek" \| "surf"` | sí | | |
1377
+ | `alt` | `string` | | | Alt text. Empty by default: the mascot is illustration and the text beside it already says what there is to know. Fill it in only when the image carries information that is not written next to it. |
1378
+ | `basePath` | `string` | | `ASSETS_PATH` | |
1379
+ | `pose` | `"desk" \| "laptop-coffee" \| "peek" \| "surf"` | yes | | |
1321
1380
 
1322
- **CaraDeMascota**
1323
- La cabeza de Tiburoncín, con expresión.
1381
+ **MascotFace**
1382
+ Tiburoncín's head, with an expression.
1324
1383
 
1325
- - Extiende: `Base`
1384
+ - Extends: `Base`
1326
1385
 
1327
- | prop | tipo | req. | defecto | qué hace |
1386
+ | prop | type | req. | default | what it does |
1328
1387
  | --- | --- | --- | --- | --- |
1329
- | `alt` | `string` | | | Texto alternativo. Vacío por defecto: la mascota es ilustración y el texto que la acompaña ya dice lo que hay que saber. Se rellena solo cuando la imagen aporta información que no está escrita al lado. |
1330
- | `basePath` | `string` | | `RUTA_ASSETS` | |
1331
- | `expresion` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | sí | | |
1388
+ | `alt` | `string` | | | Alt text. Empty by default: the mascot is illustration and the text beside it already says what there is to know. Fill it in only when the image carries information that is not written next to it. |
1389
+ | `basePath` | `string` | | `ASSETS_PATH` | |
1390
+ | `expression` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | yes | | |
1332
1391
 
1333
- ## Formularios
1392
+ ## Forms
1334
1393
 
1335
- Se importan de `@eduardoalvarez/arrecife/form` · pide `react-hook-form`. 7 exportaciones.
1394
+ Imported from `@eduardoalvarez/arrecife/form` · requires `react-hook-form`. 7 exports.
1336
1395
 
1337
1396
  ### FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage, Form
1338
1397
 
1339
- Fuente: `src/form/index.tsx`
1398
+ Source: `src/form/index.tsx`
1340
1399
 
1341
1400
  **FormField**
1342
- Un campo controlado. Envuelve el `Controller` de RHF y además publica el nombre en contexto, que es de donde lo leen la etiqueta y el mensaje sin que haya que repetirlo tres veces.
1401
+ A controlled field. It wraps RHF's `Controller` and also publishes the name into context, which is where the label and the message read it from without having to repeat it three times.
1343
1402
 
1344
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1403
+ - No own props: it passes through those of the element or primitive it wraps.
1345
1404
 
1346
1405
  **FormItem**
1347
- La caja del campo: etiqueta, control, ayuda y mensaje, en columna.
1406
+ The field's box: label, control, help and message, in a column.
1348
1407
 
1349
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1408
+ - No own props: it passes through those of the element or primitive it wraps.
1350
1409
 
1351
1410
  **FormLabel**
1352
- La etiqueta NO se tiñe de rojo cuando el campo falla.
1411
+ The label is NOT tinted red when the field fails.
1353
1412
 
1354
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1413
+ - No own props: it passes through those of the element or primitive it wraps.
1355
1414
 
1356
1415
  **FormControl**
1357
- Envuelve al control y le cablea los atributos: el `id` que la etiqueta apunta, el `aria-describedby` con la ayuda y el mensaje, y el `aria-invalid`.
1416
+ Wraps the control and wires its attributes: the `id` the label points at, the `aria-describedby` with the help and the message, and the `aria-invalid`.
1358
1417
 
1359
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1418
+ - No own props: it passes through those of the element or primitive it wraps.
1360
1419
 
1361
1420
  **FormDescription**
1362
- La ayuda del campo. Se anuncia siempre, haya error o no.
1421
+ The field's help text. It is always announced, error or not.
1363
1422
 
1364
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1423
+ - No own props: it passes through those of the element or primitive it wraps.
1365
1424
 
1366
1425
  **FormMessage**
1367
- El mensaje de validación. Sin error no renderiza nada: un hueco reservado para el fallo desplaza el resto del formulario cada vez que aparece.
1426
+ The validation message. With no error it renders nothing: a gap reserved for the failure shifts the rest of the form every time it appears.
1368
1427
 
1369
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1428
+ - No own props: it passes through those of the element or primitive it wraps.
1370
1429
 
1371
1430
  **Form**
1372
- La capa que ata los controles a un formulario con validación y mensajes.
1431
+ The layer that ties the controls to a form with validation and messages.
1373
1432
 
1374
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1433
+ - No own props: it passes through those of the element or primitive it wraps.
1375
1434
 
1376
- ## Gráficas
1435
+ ## Charts
1377
1436
 
1378
- Se importan de `@eduardoalvarez/arrecife/chart` · pide `recharts`. 5 exportaciones.
1437
+ Imported from `@eduardoalvarez/arrecife/chart` · requires `recharts`. 5 exports.
1379
1438
 
1380
1439
  ### ChartContainer, ChartTooltip, ChartLegend, ChartTooltipContent, ChartLegendContent
1381
1440
 
1382
- Fuente: `src/chart/index.tsx`
1441
+ Source: `src/chart/index.tsx`
1383
1442
 
1384
1443
  **ChartContainer**
1385
- Envuelve la gráfica en un `<figure>` con nombre accesible y le da a Recharts el alto concreto que necesita para medirse.
1444
+ Wraps the chart in a `<figure>` with an accessible name and gives Recharts the concrete height it needs to measure itself.
1386
1445
 
1387
- - Extiende: `Omit<ComponentPropsWithoutRef<'figure'>, 'title'>`
1446
+ - Extends: `Omit<ComponentPropsWithoutRef<'figure'>, 'title'>`
1388
1447
 
1389
- | prop | tipo | req. | defecto | qué hace |
1448
+ | prop | type | req. | default | what it does |
1390
1449
  | --- | --- | --- | --- | --- |
1391
- | `height` | `number` | | `320` | Alto en píxeles. Recharts necesita uno concreto para medir. |
1392
- | `label` | `string` | sí | | Qué muestra la gráfica, en una frase. Obligatorio, como el `label` de `Progress`: un `<svg>` de barras sin nombre accesible no es «una gráfica sin etiqueta», es una región vacía. |
1393
- | `summary` | `ReactNode` | | | Lo que la gráfica dice, en palabras. Va en un `figcaption` oculto visualmente. |
1450
+ | `height` | `number` | | `320` | Height in pixels. Recharts needs a concrete one to measure itself. |
1451
+ | `label` | `string` | yes | | What the chart shows, in one sentence. Mandatory, like `Progress`'s `label`: a bar `<svg>` with no accessible name is not «a chart without a label», it is an empty region. |
1452
+ | `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
1394
1453
 
1395
1454
  **ChartTooltip**
1396
- El `Tooltip` de Recharts con los defectos del sistema: sin animación y con el cursor teñido de `surfaceRaised`.
1455
+ Recharts' `Tooltip` with the system's defaults: no animation, and the cursor tinted `surfaceRaised`.
1397
1456
 
1398
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1457
+ - No own props: it passes through those of the element or primitive it wraps.
1399
1458
 
1400
1459
  **ChartLegend**
1401
- - Sin props propios: pasa los del elemento o la primitiva que envuelve.
1460
+ - No own props: it passes through those of the element or primitive it wraps.
1402
1461
 
1403
1462
  **ChartTooltipContent**
1404
- La caja del tooltip. Es una tarjeta del sistema —`surface`, borde de control, sombra estándar— y no la caja blanca de Recharts, que en modo oscuro es un rectángulo blanco encima de un panel oscuro.
1463
+ The tooltip's box. It is a system card — `surface`, control border, standard shadow — and not Recharts' white box, which in dark mode is a white rectangle on top of a dark panel.
1405
1464
 
1406
- - Extiende: `{ active?: boolean \| undefined; payload?: readonly ChartPayloadItem[] \| undefined; label?: ReactNode; /** Formatea el valor. Sin ella se imprime tal cual: la librería no impone locale. */ formatter?: ((valor: unknown, item: ChartPayloadItem) => ReactNode) \| undefined; /** Oculta el encabezado, para una gráfica de una sola categoría. */ hideLabel?: boolean; className?: string; }`
1465
+ - Extends: `{ active?: boolean \| undefined; payload?: readonly ChartPayloadItem[] \| undefined; label?: ReactNode; /** Formats the value. Without it, it is printed as is: the library imposes no locale. */ formatter?: ((value: unknown, item: ChartPayloadItem) => ReactNode) \| undefined; /** Hides the header, for a single-category chart. */ hideLabel?: boolean; className?: string; }`
1407
1466
 
1408
- | prop | tipo | req. | defecto | qué hace |
1467
+ | prop | type | req. | default | what it does |
1409
1468
  | --- | --- | --- | --- | --- |
1410
1469
  | `active` | `boolean \| undefined` | | | |
1411
1470
  | `className` | `string` | | | |
1412
- | `formatter` | `(valor: unknown, item: ChartPayloadItem) => ReactNode` | | | Formatea el valor. Sin ella se imprime tal cual: la librería no impone locale. |
1413
- | `hideLabel` | `boolean` | | `false` | Oculta el encabezado, para una gráfica de una sola categoría. |
1471
+ | `formatter` | `(value: unknown, item: ChartPayloadItem) => ReactNode` | | | Formats the value. Without it, it is printed as is: the library imposes no locale. |
1472
+ | `hideLabel` | `boolean` | | `false` | Hides the header, for a single-category chart. |
1414
1473
  | `label` | `ReactNode` | | | |
1415
1474
  | `payload` | `readonly ChartPayloadItem[]` | | | |
1416
1475
 
1417
1476
  **ChartLegendContent**
1418
- La leyenda con la misma marca cuadrada del tooltip y la escala `label`.
1477
+ The legend, with the tooltip's same square swatch and the `label` scale.
1419
1478
 
1420
- - Extiende: `{ payload?: readonly ChartPayloadItem[] \| undefined; className?: string; }`
1479
+ - Extends: `{ payload?: readonly ChartPayloadItem[] \| undefined; className?: string; }`
1421
1480
 
1422
- | prop | tipo | req. | defecto | qué hace |
1481
+ | prop | type | req. | default | what it does |
1423
1482
  | --- | --- | --- | --- | --- |
1424
1483
  | `className` | `string` | | | |
1425
1484
  | `payload` | `readonly ChartPayloadItem[]` | | | |
1426
1485
 
1427
- ## Exportaciones que no son componentes
1486
+ ## Exports that are not components
1428
1487
 
1429
- La raíz reexporta todo lo de `./tokens` y `./brand` por conveniencia. Cada uno
1430
- aparece una sola vez, en la subruta más específica que lo publica: si el código
1431
- no monta React, esa subruta es la que hay que importar.
1488
+ The root re-exports everything from `./tokens` and `./brand` for convenience.
1489
+ Each one appears exactly once, under the most specific subpath that publishes
1490
+ it: if the code does not mount React, that subpath is the one to import.
1491
+
1492
+ ### `@eduardoalvarez/arrecife/variants`
1493
+
1494
+ | export | type | what it is |
1495
+ | --- | --- | --- |
1496
+ | `alertVariants` | `(props?: (ConfigVariants<{ variant: { accent: string; success: string; warning: string; error: string; }; emphasis: { subtle: string; strong: string; }; }> & ClassProp) \| undefined): string` | |
1497
+ | `avatarVariants` | `(props?: (ConfigVariants<{ size: { sm: string; md: string; lg: string; xl: string; }; }> & ClassProp) \| undefined): string` | |
1498
+ | `badgeVariants` | `(props?: (ConfigVariants<{ variant: { neutral: string; accent: string; warm: string; success: string; warning: string; error: string; }; }> & ClassProp) \| undefined): string` | |
1499
+ | `buttonVariants` | `(props?: (ConfigVariants<{ variant: { primary: string[]; conversion: string; secondary: string[]; tertiary: string[]; destructive: string; destructiveOutline: string[]; }; size: { sm: string; md: string; lg: string; icon: string; 'icon-sm': string; }; }> & ClassProp) \| undefined): string` | |
1500
+ | `CARD` | `string[]` | |
1501
+ | `CARD_HOVER` | `"transition-standard hover:border-hairline-hover"` | |
1502
+ | `CARD_SURFACE` | `"rounded-card border-hairline bg-surface border"` | |
1503
+ | `categoryBadgeVariants` | `(props?: (ConfigVariants<{ active: { false: string; true: string; }; }> & ClassProp) \| undefined): string` | |
1504
+ | `metricBadgeVariants` | `string[]` | |
1505
+ | `textVariants` | `(props?: (ConfigVariants<{ variant: { display: string; stat: string; h1: string; h2: string; h3: string; body: string; lead: string; ui: string; label: string; tag: string; chip: string; meta: string; eyebrow: string; }; tone: { ...; }; }> & ClassProp) \| undefined): string` | |
1432
1506
 
1433
1507
  ### `@eduardoalvarez/arrecife/tokens`
1434
1508
 
1435
- | export | tipo | qué es |
1509
+ | export | type | what it is |
1436
1510
  | --- | --- | --- |
1437
- | `brand` | `{ readonly body: "#3E7CB1"; readonly spots: "#C2D7E7"; readonly hull: "#0B1524"; }` | Marca — iguales en los dos modos. |
1511
+ | `brand` | `{ readonly body: "#3E7CB1"; readonly spots: "#C2D7E7"; readonly hull: "#0B1524"; }` | Brand — identical in both modes. |
1438
1512
  | `colors` | `{ dark, light }` | |
1439
- | `control` | `{ readonly sm: 14; readonly md: 22; readonly lg: 30; readonly icon: 42; }` | Controles, del documento: `sm 8/14 · md 12/22 · lg 15/30 · icono 42×42`. |
1440
- | `dark` | `{ background, surface, surfaceRaised, border, hairline, hairlineHover, textPrimary, textSecondary, textMuted, accent, accentHover, accentOn, warm, warmHover, warmOn, success, warning, error }` | Modo oscuro (primario). Contrastes medidos sobre `background` #091319. |
1513
+ | `control` | `{ readonly sm: 14; readonly md: 22; readonly lg: 30; readonly icon: 42; readonly iconSm: 32; }` | Controls, from the document: `sm 8/14 · md 12/22 · lg 15/30 · icon 42×42`. |
1514
+ | `dark` | `{ background, surface, surfaceRaised, border, hairline, hairlineHover, textPrimary, textSecondary, textMuted, accent, accentHover, accentOn, warm, warmHover, warmOn, success, warning, error, danger, dangerHover, dangerOn }` | Dark mode (primary). Contrast measured against `background` #091319. |
1441
1515
  | `fonts` | `{ display, sans, mono }` | |
1442
1516
  | `gradient` | `{ dark, light }` | |
1443
- | `light` | `{ background, surface, surfaceRaised, border, hairline, hairlineHover, textPrimary, textSecondary, textMuted, accent, accentHover, accentOn, warm, warmHover, warmOn, success, warning, error }` | Modo claro. Contrastes medidos sobre `background` #F6F2EA. `background` es blanco CÁLIDO: nunca #FFF como fondo de página. |
1444
- | `limits` | `{ readonly minScreenPx: 13; readonly minPrintPt: 12; readonly measure: "68ch"; }` | Límites duros de legibilidad. |
1445
- | `motion` | `{ readonly duration: "150ms"; readonly easing: "ease-out"; readonly properties: "color, background-color, border-color, fill, stroke"; }` | 150ms ease-out — solo color y borde. El sistema no anima posición ni escala: los estados se comunican con borde y color, no con movimiento. |
1446
- | `naming` | `{ readonly wordmark: "Eduardo Álvarez"; readonly mascot: "Tiburoncín"; readonly domain: "eduardoalvarez.dev"; }` | El wordmark siempre dice «Eduardo Álvarez». La mascota se llama Tiburoncín y nunca aparece escrita dentro del logo. |
1517
+ | `light` | `{ background, surface, surfaceRaised, border, hairline, hairlineHover, textPrimary, textSecondary, textMuted, accent, accentHover, accentOn, warm, warmHover, warmOn, success, warning, error, danger, dangerHover, dangerOn }` | Light mode. Contrast measured against `background` #F6F2EA. `background` is WARM white: never #FFF as the page background. |
1518
+ | `limits` | `{ readonly minScreenPx: 13; readonly minPrintPt: 12; readonly measure: "68ch"; }` | Hard legibility limits. |
1519
+ | `motion` | `{ readonly duration: "150ms"; readonly easing: "ease-out"; readonly properties: "color, background-color, border-color, fill, stroke"; }` | 150ms ease-out — color and border only. The system animates neither position nor scale: states are communicated with border and color, not with movement. |
1520
+ | `naming` | `{ readonly wordmark: "Eduardo Álvarez"; readonly mascot: "Tiburoncín"; readonly domain: "eduardoalvarez.dev"; }` | The wordmark always reads «Eduardo Álvarez». The mascot is called Tiburoncín and its name never appears inside the logo. |
1447
1521
  | `radius` | `{ readonly chip: 6; readonly control: 10; readonly card: 14; readonly panel: 16; readonly pill: 999; }` | |
1448
- | `series` | `{ readonly dark: readonly ["#35D6C0", "#F2A65A", "#3E7CB1", "#71919C"]; readonly light: readonly ["#0D7C6F", "#A65B27", "#3E7CB1", "#626A75"]; }` | La paleta de series de las gráficas. CUATRO, por el mismo motivo que la de sintaxis: el sistema se comunica con color y borde, no con ruido cromático. |
1449
- | `shadow` | `{ readonly standard: "0 1px 2px rgba(0, 0, 0, 0.35)"; }` | Un solo nivel. No hay escala de elevación. |
1450
- | `sintaxis` | `{ fondo, identificador, literal, palabraClave, comentario, invalido }` | La paleta del resaltado de sintaxis. |
1522
+ | `series` | `{ readonly dark: readonly ["#35D6C0", "#F2A65A", "#3E7CB1", "#71919C"]; readonly light: readonly ["#0D7C6F", "#A65B27", "#3E7CB1", "#626A75"]; }` | The chart series palette. FOUR, for the same reason as the syntax palette: the system communicates with color and border, not with chromatic noise. |
1523
+ | `shadow` | `{ readonly standard: "0 1px 2px rgba(0, 0, 0, 0.35)"; }` | A single level. There is no elevation scale. |
1451
1524
  | `size` | `{ readonly nav: 64; readonly content: 760; readonly wide: 1180; }` | |
1452
- | `spacing` | `{ readonly stepXs: 8; readonly stepSm: 12; readonly stepMd: 16; readonly stepLg: 26; readonly stepXl: 40; readonly section: 96; }` | El ritmo de página. Los cinco escalones llevan `step` en el nombre, y no es decoración: es la corrección de un bug que no dio la cara en ningún sitio. |
1453
- | `tagline` | `{ largo, corto, en }` | |
1454
- | `tokens` | `{ colors, brand, fonts, typeScale, limits, radius, control, spacing, size, gradient, sintaxis, series, shadow, motion, tagline, naming }` | Todos los tokens en un solo objeto, para plantillas Satori y generadores. |
1525
+ | `spacing` | `{ readonly stepXs: 8; readonly stepSm: 12; readonly stepMd: 16; readonly stepLg: 26; readonly stepXl: 40; readonly section: 96; }` | Page rhythm. All five steps carry `step` in the name, and that is not decoration: it is the fix for a bug that never surfaced anywhere. |
1526
+ | `syntax` | `{ background, identifier, literal, keyword, comment, invalid }` | The syntax highlighting palette. |
1527
+ | `tagline` | `{ long, short, en }` | |
1528
+ | `tokens` | `{ colors, brand, fonts, typeScale, limits, radius, control, spacing, size, gradient, syntax, series, shadow, motion, tagline, naming }` | Every token in a single object, for Satori templates and generators. |
1455
1529
  | `typeScale` | `{ display, stat, h1, h2, h3, body, lead, ui, label, tag, chip, meta, eyebrow }` | |
1456
1530
 
1457
- Tipos (13): `BrandToken`, `ColorMode`, `ColorToken`, `ControlToken`, `FontToken`, `GradientToken`, `RadiusToken`, `SeriesToken`, `SintaxisToken`, `SizeToken`, `SpacingToken`, `Tokens`, `TypeScaleToken`.
1531
+ Types (13): `BrandToken`, `ColorMode`, `ColorToken`, `ControlToken`, `FontToken`, `GradientToken`, `RadiusToken`, `SeriesToken`, `SizeToken`, `SpacingToken`, `SyntaxToken`, `Tokens`, `TypeScaleToken`.
1532
+
1533
+ ### `@eduardoalvarez/arrecife/theme`
1534
+
1535
+ | export | type | what it is |
1536
+ | --- | --- | --- |
1537
+ | `applyTheme` | `(theme: Theme, persist?: boolean): void` | Sets the theme on `<html>` and persists it. |
1538
+ | `currentTheme` | `(): Theme` | The theme currently in place, read from the DOM. |
1539
+ | `preferredTheme` | `({ base }?: ThemeOptions): Theme` | The theme that applies: whatever was chosen, and with no choice, `base` if the site declared one, else whatever the system asks for. With no `prefers-color-scheme` declared, dark, which is primary. |
1540
+ | `storedTheme` | `(): Theme \| null` | The stored preference, if any. `null` means «nobody has chosen», which is not the same as «chose dark»: with no choice, the system decides. |
1541
+ | `THEME_ATTRIBUTE` | `"data-theme"` | The attribute the `[data-theme]` blocks in `theme.css` read. |
1542
+ | `THEME_EVENT` | `"arrecife:theme"` | The event emitted when the theme changes. |
1543
+ | `THEME_KEY` | `"arrecife-theme"` | The `localStorage` key. |
1544
+ | `themeScript` | `({ base }?: ThemeOptions): string` | The script that goes INLINE in the `<head>`, before any stylesheet. |
1545
+ | `toggleTheme` | `(): Theme` | Switches to the opposite one and returns whichever stuck. |
1546
+ | `watchTheme` | `(onChange: (theme: Theme) => void, options?: ThemeOptions): () => void` | Subscribes to theme changes and returns the function that cancels it. |
1547
+
1548
+ Types (2): `Theme`, `ThemeOptions`.
1458
1549
 
1459
1550
  ### `@eduardoalvarez/arrecife/brand`
1460
1551
 
1461
- | export | tipo | qué es |
1552
+ | export | type | what it is |
1462
1553
  | --- | --- | --- |
1463
- | `aletas` | `{ readonly color: "fin.png"; readonly espuma: "fin-foam.png"; }` | La aleta, en sus dos variantes. |
1464
- | `caras` | `{ annoyed, confused, hearts, laughing, shades, waiting, wink }` | Las caras. Solo se usan en estados vacíos, confirmaciones, errores, progreso de curso y celebración — nunca en hero, precios, servicios, contacto ni CV. Por eso `EmptyState` recibe una cara y `PageHeader` no. |
1465
- | `listaCaras` | `readonly ("annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink")[]` | |
1466
- | `listaPoses` | `readonly ("desk" \| "laptop-coffee" \| "peek" \| "surf")[]` | |
1467
- | `poses` | `{ readonly desk: "pose-desk.png"; readonly 'laptop-coffee': "pose-laptop-coffee.png"; readonly peek: "pose-peek.png"; readonly surf: "pose-surf.png"; }` | Poses de cuerpo entero. |
1468
- | `RUTA_ASSETS` | `"/brand"` | Dónde se sirven los PNG. Por defecto `/brand`, que es donde ya viven en los cinco proyectos (`public/brand/`), así que no hay nada que configurar. |
1469
- | `usoDeCara` | `{ wink, waiting, laughing, shades, hearts, confused, annoyed }` | El uso asignado de cada cara, del inventario del manual. |
1554
+ | `ASSETS_PATH` | `"/brand"` | Where the PNGs are served from. `/brand` by default, which is where they already live in all five projects (`public/brand/`), so there is nothing to configure. |
1555
+ | `faceList` | `readonly ("annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink")[]` | |
1556
+ | `faces` | `{ annoyed, confused, hearts, laughing, shades, waiting, wink }` | The faces. They are used only in empty states, confirmations, errors, course progress and celebration — never in a hero, pricing, services, contact or CV. That is why `EmptyState` takes a face and `PageHeader` does not. |
1557
+ | `faceUsage` | `{ wink, waiting, laughing, shades, hearts, confused, annoyed }` | The assigned use of each face, from the manual's inventory. |
1558
+ | `fins` | `{ readonly color: "fin.png"; readonly foam: "fin-foam.png"; }` | The fin, in its two variants. |
1559
+ | `poseList` | `readonly ("desk" \| "laptop-coffee" \| "peek" \| "surf")[]` | |
1560
+ | `poses` | `{ readonly desk: "pose-desk.png"; readonly 'laptop-coffee': "pose-laptop-coffee.png"; readonly peek: "pose-peek.png"; readonly surf: "pose-surf.png"; }` | Full-body poses. |
1470
1561
 
1471
- Tipos (8): `Aleta`, `Cara`, `CaraDeMascotaProps`, `Fondo`, `IsotipoProps`, `LogoProps`, `MascotaProps`, `Pose`.
1562
+ Types (8): `Background`, `Face`, `Fin`, `IsotypeProps`, `LogoProps`, `MascotFaceProps`, `MascotProps`, `Pose`.
1472
1563
 
1473
1564
  ### `@eduardoalvarez/arrecife/shiki`
1474
1565
 
1475
- | export | tipo | qué es |
1566
+ | export | type | what it is |
1476
1567
  | --- | --- | --- |
1477
- | `arrecife` | `TemaShiki` | |
1568
+ | `arrecife` | `ShikiTheme` | |
1478
1569
 
1479
- Tipos (1): `TemaShiki`.
1570
+ Types (1): `ShikiTheme`.
1480
1571
 
1481
1572
  ### `@eduardoalvarez/arrecife/chart`
1482
1573
 
1483
- | export | tipo | qué es |
1574
+ | export | type | what it is |
1484
1575
  | --- | --- | --- |
1485
- | `colorDeSerie` | `(indice: number): string` | El color de la serie `indice`, como custom property. |
1486
- | `COLORES_DE_SERIE` | `string[]` | Las cuatro, en orden, para pasárselas de golpe a un `Pie` con `Cell`. |
1576
+ | `SERIES_COLORS` | `string[]` | All four, in order, to hand to a `Pie` with `Cell` in one go. |
1577
+ | `seriesColor` | `(index: number): string` | The color of series `index`, as a custom property. |
1487
1578
 
1488
- Tipos (4): `ChartContainerProps`, `ChartLegendContentProps`, `ChartPayloadItem`, `ChartTooltipContentProps`.
1489
-
1490
- ### `@eduardoalvarez/arrecife/tema`
1491
-
1492
- | export | tipo | qué es |
1493
- | --- | --- | --- |
1494
- | `alternarTema` | `(): Tema` | Cambia al contrario y devuelve el que quedó. |
1495
- | `aplicarTema` | `(tema: Tema, persistir?: boolean): void` | Pone el tema en el `<html>` y lo persiste. |
1496
- | `escucharTema` | `(alCambiar: (tema: Tema) => void): () => void` | Se suscribe a los cambios de tema y devuelve la función que cancela. |
1497
- | `scriptTema` | `string` | El script que va INLINE en el `<head>`, antes de cualquier hoja de estilo. |
1498
- | `TEMA_ATRIBUTO` | `"data-theme"` | El atributo que leen los bloques `[data-theme]` de `theme.css`. |
1499
- | `TEMA_CLAVE` | `"arrecife-tema"` | La clave de `localStorage`. |
1500
- | `TEMA_EVENTO` | `"arrecife:tema"` | El evento que se emite cuando el tema cambia. |
1501
- | `temaActual` | `(): Tema` | El tema que hay puesto ahora mismo, leído del DOM. |
1502
- | `temaGuardado` | `(): Tema \| null` | La preferencia guardada, si la hay. `null` significa «nadie ha elegido», que no es lo mismo que «eligió oscuro»: sin elección manda el sistema. |
1503
- | `temaPreferido` | `(): Tema` | El tema que corresponde: lo elegido, y si no hay elección, lo que pida el sistema. Sin `prefers-color-scheme` declarado, oscuro, que es el primario. |
1504
-
1505
- Tipos (1): `Tema`.
1579
+ Types (4): `ChartContainerProps`, `ChartLegendContentProps`, `ChartPayloadItem`, `ChartTooltipContentProps`.
1506
1580
 
1507
1581
  ### `@eduardoalvarez/arrecife/form`
1508
1582
 
1509
- | export | tipo | qué es |
1583
+ | export | type | what it is |
1510
1584
  | --- | --- | --- |
1511
- | `useFormField` | `(): { invalid: boolean; isDirty: boolean; isTouched: boolean; isValidating: boolean; error?: FieldError; name: string; id: string; idDescripcion: string; idMensaje: string; }` | Lo que necesita cualquier pieza del campo: el nombre, los tres ids y el estado de validación. |
1585
+ | `useFormField` | `(): { invalid: boolean; isDirty: boolean; isTouched: boolean; isValidating: boolean; error?: FieldError; name: string; id: string; descriptionId: string; messageId: string; }` | What any piece of the field needs: the name, the three ids and the validation state. |
1512
1586
 
1513
1587
  ### `@eduardoalvarez/arrecife/og`
1514
1588
 
1515
- | export | tipo | qué es |
1589
+ | export | type | what it is |
1516
1590
  | --- | --- | --- |
1517
- | `OG` | `{ readonly width: 1200; readonly height: 630; readonly margen: 64; readonly mascota: 430; readonly aletaFirma: 34; readonly reservaMascota: 560; }` | El lienzo y la retícula. Son las medidas de producción. |
1518
- | `plantillaArticulo` | `(datos: DatosArticulo): NodoSatori` | Artículo · degradado 145° sobre abismo, categoría y lectura en arena. |
1519
- | `plantillaBase` | `(opciones: { modo: "oscuro" \| "claro"; fondo: string; eyebrow: { texto: string; color: string; }; title: string; bajada?: string \| undefined; firma: string; firmaColor: string; base: string; mascota?: NodoSatori \| null \| undefined; mascotaIzquierda?: boolean \| undefined; tinta: string; tintaSecundaria: string; }): NodoSatori` | |
1520
- | `plantillaCharla` | `(datos: DatosCharla): NodoSatori` | Charla · eyebrow en bioluz con evento y año, pose sangrando por la esquina. |
1521
- | `plantillaCurso` | `(datos: DatosCurso): NodoSatori` | Curso · LA ÚNICA PLANTILLA EN CLARO. |
1522
- | `plantillaDefecto` | `(datos?: DatosDefecto): NodoSatori` | Por defecto · la excepción declarada del documento. |
1591
+ | `articleTemplate` | `(data: ArticleData): SatoriNode` | Article · 145° gradient over abyss, category and reading time in sand. |
1592
+ | `courseTemplate` | `(data: CourseData): SatoriNode` | Course · THE ONLY LIGHT TEMPLATE. |
1593
+ | `defaultTemplate` | `(data?: DefaultData): SatoriNode` | Default · the document's declared exception. |
1594
+ | `OG` | `{ readonly width: 1200; readonly height: 630; readonly margin: 64; readonly mascot: 430; readonly signatureFin: 34; readonly mascotReserve: 560; }` | The canvas and the grid. These are the production measurements. |
1595
+ | `plantillaBase` | `(options: { mode: "dark" \| "light"; background: string; eyebrow: { text: string; color: string; }; title: string; bajada?: string \| undefined; signature: string; firmaColor: string; base: string; mascot?: SatoriNode \| ... 1 more ... \| undefined; mascotaIzquierda?: boolean \| undefined; tinta: string; tintaSecundaria: string; }): SatoriNode` | |
1596
+ | `talkTemplate` | `(data: TalkData): SatoriNode` | Talk · eyebrow in biolume with the event and year, pose bleeding off the corner. |
1523
1597
 
1524
- Tipos (6): `DatosArticulo`, `DatosBase`, `DatosCharla`, `DatosCurso`, `DatosDefecto`, `NodoSatori`.
1598
+ Types (6): `ArticleData`, `BaseData`, `CourseData`, `DefaultData`, `SatoriNode`, `TalkData`.
1525
1599
 
1526
1600
  ### `@eduardoalvarez/arrecife`
1527
1601
 
1528
- | export | tipo | qué es |
1602
+ | export | type | what it is |
1529
1603
  | --- | --- | --- |
1530
- | `alertVariants` | `(props?: (ConfigVariants<{ variant: { accent: string; success: string; warning: string; error: string; }; enfasis: { sutil: string; fuerte: string; }; }> & ClassProp) \| undefined): string` | |
1531
- | `avatarVariants` | `(props?: (ConfigVariants<{ size: { sm: string; md: string; lg: string; xl: string; }; }> & ClassProp) \| undefined): string` | |
1532
- | `badgeVariants` | `(props?: (ConfigVariants<{ variant: { neutral: string; accent: string; warm: string; success: string; warning: string; error: string; }; }> & ClassProp) \| undefined): string` | |
1533
- | `buttonVariants` | `(props?: (ConfigVariants<{ variant: { primary: string[]; conversion: string; secondary: string[]; tertiary: string[]; }; size: { sm: string; md: string; lg: string; icon: string; }; }> & ClassProp) \| undefined): string` | |
1534
- | `categoryBadgeVariants` | `(props?: (ConfigVariants<{ active: { false: string; true: string; }; }> & ClassProp) \| undefined): string` | |
1535
1604
  | `cn` | `(...inputs: ClassValue[]): string` | |
1536
- | `HOVER_TARJETA` | `"transition-standard hover:border-hairline-hover"` | El hover de la regla 6: solo el borde. Se aplica donde la tarjeta es pulsable. |
1537
1605
  | `social` | `typeof import("src/lib/social")` | |
1538
- | `SUPERFICIE_TARJETA` | `"rounded-card border-hairline bg-surface border"` | El contenedor de superficie del sistema, y la única definición de lo que es una tarjeta: `surface`, borde `hairline`, radio de tarjeta. |
1539
- | `textVariants` | `(props?: (ConfigVariants<{ variant: { display: string; stat: string; h1: string; h2: string; h3: string; body: string; lead: string; ui: string; label: string; tag: string; chip: string; meta: string; eyebrow: string; }; tone: { ...; }; }> & ClassProp) \| undefined): string` | |
1540
- | `toast` | `(mensaje: ReactNode, opciones?: ToastOptions \| undefined): string` | Lanza un aviso. Devuelve su id, que es lo que hay que guardar para cerrarlo a mano —el caso de «guardando…» que se reemplaza cuando termina la petición. |
1541
- | `useTema` | `(): Tema` | El tema puesto ahora mismo, para un proyecto que necesite ramificar en React —un logo distinto por modo, una imagen que no tiene versión clara—. |
1542
-
1543
- Tipos (60): `AccordionProps`, `AccordionTriggerProps`, `AlertProps`, `ArticleCardProps`, `AudioPlayerMode`, `AudioPlayerProps`, `AuthorCardProps`, `AvatarProps`, `AvatarUploadProps`, `BadgeProps`, `BlockquoteProps`, `BreadcrumbProps`, `ButtonProps`, `CalendarProps`, `CategoryBadgeProps`, `CheckboxProps`, `CodeBlockProps`, `CodeProps`, `CourseCardProps`, `DateFieldProps`, `EmptyStateProps`, `Entrada`, `EventCalendarProps`, `EventoCalendario`, `FooterLinkProps`, `FooterProps`, `HeroProps`, `InputProps`, `LabelProps`, `LinkRowProps`, `MetricBadgeProps`, `Migaja`, `NavItemProps`, `NavProps`, `NewsletterFormProps`, `NewsletterState`, `PageHeaderProps`, `PaginationLinkProps`, `PopoverContentProps`, `ProgressProps`, `RadioGroupItemProps`, `RadioGroupProps`, `Red`, `ScrollingProgressBarProps`, `SeparatorProps`, `SheetContentProps`, `SidebarItemProps`, `SidebarNavProps`, `SkeletonProps`, `StatProps`, `SwitchProps`, `TableOfContentsProps`, `TabsProps`, `TalkCardProps`, `TextProps`, `TextareaProps`, `ThemeToggleProps`, `ToastOptions`, `ToastVariant`, `ToasterProps`.
1544
-
1545
- # Dónde mirar si esto no basta
1546
-
1547
- - Storybook publica cada componente con sus stories y la tabla de props generada
1548
- desde los tipos.
1549
- - `README.md` del repo: el porqué de cada decisión, la tabla de correcciones de
1550
- contraste y el ciclo de publicación.
1551
- - `docs/design-system.md` y `docs/manual-de-marca.md`: los documentos de
1552
- identidad, consultables con `grep`.
1553
- - `docs/decisiones.md`: los quince puntos donde el código y el documento no
1554
- decían lo mismo, con la resolución de cada uno.
1555
- - `AGENTS.md`: para trabajar dentro del repo de la librería.
1606
+ | `toast` | `(message: ReactNode, options?: ToastOptions \| undefined): string` | Fires a notice. It returns its id, which is what you keep in order to close it by hand — the «guardando…» case that gets replaced when the request finishes. |
1607
+ | `useTheme` | `(): Theme` | The theme set right now, for a project that needs to branch in React — a different logo per mode, an image with no light version. |
1608
+
1609
+ Types (60): `AccordionProps`, `AccordionTriggerProps`, `AlertProps`, `ArticleCardProps`, `AudioPlayerMode`, `AudioPlayerProps`, `AuthorCardProps`, `AvatarProps`, `AvatarUploadProps`, `BadgeProps`, `BlockquoteProps`, `BreadcrumbProps`, `ButtonProps`, `CalendarEvent`, `CalendarProps`, `CategoryBadgeProps`, `CheckboxProps`, `CodeBlockProps`, `CodeProps`, `CourseCardProps`, `Crumb`, `DateFieldProps`, `EmptyStateProps`, `EventCalendarProps`, `FooterLinkProps`, `FooterProps`, `HeroProps`, `InputProps`, `LabelProps`, `LinkRowProps`, `MetricBadgeProps`, `NavItemProps`, `NavProps`, `NewsletterFormProps`, `NewsletterState`, `PageHeaderProps`, `PaginationLinkProps`, `PopoverContentProps`, `ProgressProps`, `RadioGroupItemProps`, `RadioGroupProps`, `ScrollingProgressBarProps`, `SeparatorProps`, `SheetContentProps`, `SidebarItemProps`, `SidebarNavProps`, `SkeletonProps`, `SocialLink`, `StatProps`, `SwitchProps`, `TableOfContentsProps`, `TabsProps`, `TalkCardProps`, `TextProps`, `TextareaProps`, `ThemeToggleProps`, `ToastOptions`, `ToastVariant`, `ToasterProps`, `TocEntry`.
1610
+
1611
+ # Where to look if this is not enough
1612
+
1613
+ - Storybook publishes every component with its stories and the props table
1614
+ generated from the types.
1615
+ - The repo's `README.md`: the reasoning behind each decision, the contrast
1616
+ correction table and the release cycle.
1617
+ - `docs/design-system.md` and `docs/brand-manual.md`: the identity documents,
1618
+ greppable.
1619
+ - `docs/decisions.md`: the points where the code and the document did not say the
1620
+ same thing, each with its resolution.
1621
+ - `AGENTS.md`: for working inside the library's repo.