@eduardoalvarez/arrecife 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/README.md +868 -467
- package/dist/brand/index.cjs +112 -95
- package/dist/brand/index.d.cts +40 -39
- package/dist/brand/index.d.ts +40 -39
- package/dist/brand/index.js +5 -4
- package/dist/catalog-D13txprv.d.cts +78 -0
- package/dist/catalog-D13txprv.d.ts +78 -0
- package/dist/chart/index.cjs +100 -83
- package/dist/chart/index.d.cts +66 -66
- package/dist/chart/index.d.ts +66 -66
- package/dist/chart/index.js +14 -12
- package/dist/chunk-2WPWEIMD.js +27 -0
- package/dist/chunk-45HVCTB7.js +70 -0
- package/dist/{chunk-ZEOQKRQ7.js → chunk-727HCBD4.js} +1 -1
- package/dist/chunk-CKRSQPTX.js +36 -0
- package/dist/chunk-E6KFUSKB.js +144 -0
- package/dist/chunk-GCRII2KQ.js +86 -0
- package/dist/{chunk-YZ2SDOVZ.js → chunk-JN3IS5OS.js} +30 -30
- package/dist/chunk-ODBFN44D.js +45 -0
- package/dist/chunk-OMKSESQB.js +300 -0
- package/dist/{chunk-VPT32GPG.js → chunk-TA7TLWW4.js} +2 -2
- package/dist/chunk-WGNIRIN7.js +42 -0
- package/dist/doctor.mjs +166 -0
- package/dist/form/index.cjs +109 -92
- package/dist/form/index.d.cts +43 -42
- package/dist/form/index.d.ts +43 -42
- package/dist/form/index.js +25 -23
- package/dist/icons/index.cjs +149 -0
- package/dist/icons/index.d.cts +94 -0
- package/dist/icons/index.d.ts +94 -0
- package/dist/icons/index.js +28 -0
- package/dist/index-DlAO2JZs.d.cts +47 -0
- package/dist/index-DlAO2JZs.d.ts +47 -0
- package/dist/index.cjs +1292 -983
- package/dist/index.d.cts +927 -806
- package/dist/index.d.ts +927 -806
- package/dist/index.js +809 -778
- package/dist/{label-DuTvJGxD.d.ts → label-MgHFKnFy.d.cts} +3 -3
- package/dist/{label-DuTvJGxD.d.cts → label-MgHFKnFy.d.ts} +3 -3
- package/dist/og/index.cjs +133 -132
- package/dist/og/index.d.cts +93 -89
- package/dist/og/index.d.ts +93 -89
- package/dist/og/index.js +106 -106
- package/dist/shiki/index.cjs +28 -30
- package/dist/shiki/index.d.cts +4 -4
- package/dist/shiki/index.d.ts +4 -4
- package/dist/shiki/index.js +12 -12
- package/dist/social/index.cjs +67 -0
- package/dist/social/index.d.cts +2 -0
- package/dist/social/index.d.ts +2 -0
- package/dist/social/index.js +2 -0
- package/dist/theme/index.cjs +97 -0
- package/dist/theme/index.d.cts +144 -0
- package/dist/theme/index.d.ts +144 -0
- package/dist/theme/index.js +2 -0
- package/dist/tokens/index.cjs +159 -88
- package/dist/tokens/index.d.cts +277 -165
- package/dist/tokens/index.d.ts +277 -165
- package/dist/tokens/index.js +2 -2
- package/dist/tokens/theme.css +165 -100
- package/dist/variants/index.cjs +195 -0
- package/dist/variants/index.d.cts +195 -0
- package/dist/variants/index.d.ts +195 -0
- package/dist/variants/index.js +3 -0
- package/llms.txt +1145 -746
- package/package.json +42 -11
- package/dist/catalogo-Du5ID-Hi.d.cts +0 -77
- package/dist/catalogo-Du5ID-Hi.d.ts +0 -77
- package/dist/chunk-E3OMP2DL.js +0 -36
- package/dist/chunk-KPZNNMV5.js +0 -83
- package/dist/chunk-NHS7ETKJ.js +0 -27
- package/dist/chunk-TSPJOM6K.js +0 -229
- package/dist/chunk-UOWIDFCB.js +0 -81
- package/dist/tema/index.cjs +0 -94
- package/dist/tema/index.d.cts +0 -110
- package/dist/tema/index.d.ts +0 -110
- package/dist/tema/index.js +0 -2
package/llms.txt
CHANGED
|
@@ -1,262 +1,566 @@
|
|
|
1
1
|
# @eduardoalvarez/arrecife
|
|
2
2
|
|
|
3
|
-
>
|
|
4
|
-
> `docs/llms.
|
|
5
|
-
> `pnpm check:llms`
|
|
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
|
-
|
|
8
|
-
TypeScript, Tailwind v4, shadcn/ui
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
`AGENTS.md`,
|
|
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
|
-
##
|
|
15
|
+
## First things first: do not reimplement what is already here
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
##
|
|
26
|
+
## Installation
|
|
26
27
|
|
|
27
28
|
```bash
|
|
28
29
|
pnpm add @eduardoalvarez/arrecife
|
|
29
30
|
```
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
Requirements, and they are not optional:
|
|
32
33
|
|
|
33
34
|
| | |
|
|
34
35
|
| --- | --- |
|
|
35
|
-
| React | `^19.0.0`
|
|
36
|
-
| Tailwind | v4. **
|
|
37
|
-
| Node | `>=22.18.0`
|
|
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`
|
|
40
|
-
`react-day-picker`
|
|
41
|
-
|
|
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
|
-
**
|
|
44
|
-
|
|
44
|
+
**It ships no icon set.** The glyphs the components
|
|
45
|
+
need are inline, inherit `currentColor` and measure 1em.
|
|
45
46
|
|
|
46
|
-
##
|
|
47
|
+
## Tailwind configuration
|
|
47
48
|
|
|
48
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
Tailwind
|
|
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
|
|
60
|
-
|
|
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
|
-
###
|
|
67
|
+
### Light mode and dark mode
|
|
67
68
|
|
|
68
|
-
**
|
|
69
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
###
|
|
80
|
+
### Fonts
|
|
79
81
|
|
|
80
|
-
|
|
81
|
-
Bricolage Grotesque (`font-display`), Geist (`font-sans`)
|
|
82
|
-
(`font-mono`)
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
+
The name has to match **exactly** the one the tokens declare, and this has
|
|
88
|
+
already failed in two projects:
|
|
87
89
|
|
|
88
|
-
|
|
|
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
|
-
|
|
95
|
-
`"Geist Variable"`.
|
|
96
|
-
tokens
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
##
|
|
102
|
+
## What to import from where
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
script
|
|
105
|
-
|
|
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
|
-
|
|
|
109
|
+
| Subpath | Drags React | Also requires | What for |
|
|
108
110
|
| --- | --- | --- | --- |
|
|
109
|
-
| `@eduardoalvarez/arrecife` |
|
|
110
|
-
| `@eduardoalvarez/arrecife/tokens` | **no** | — |
|
|
111
|
-
| `@eduardoalvarez/arrecife/tokens/theme.css` | — | — |
|
|
112
|
-
| `@eduardoalvarez/arrecife/
|
|
113
|
-
| `@eduardoalvarez/arrecife/
|
|
114
|
-
| `@eduardoalvarez/arrecife/
|
|
115
|
-
| `@eduardoalvarez/arrecife/
|
|
116
|
-
| `@eduardoalvarez/arrecife/
|
|
117
|
-
| `@eduardoalvarez/arrecife/
|
|
118
|
-
| `@eduardoalvarez/arrecife/
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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/social` | yes, on the server only | — | The nine social icons, loose. No `"use client"` |
|
|
120
|
+
| `@eduardoalvarez/arrecife/icons` | yes | `@phosphor-icons/react` | `Icon`, which draws a Phosphor icon at the system's size, and at the weight its role asks for |
|
|
121
|
+
| `@eduardoalvarez/arrecife/form` | yes | `react-hook-form` | The form layer: labels, errors and `aria-*` |
|
|
122
|
+
| `@eduardoalvarez/arrecife/chart` | yes | `recharts` | The chart chassis and the series palette |
|
|
123
|
+
| `@eduardoalvarez/arrecife/assets/*` | — | — | The brand PNGs |
|
|
124
|
+
|
|
125
|
+
Importing the root from a build script to get one token is the mistake the
|
|
126
|
+
subpaths exist to prevent: it drags all of React into a worker that never mounts
|
|
127
|
+
it.
|
|
128
|
+
|
|
129
|
+
`./form`, `./chart` and `./icons` sit outside the root for the symmetric reason:
|
|
130
|
+
if they hung off the main index, the projects that draw no charts, use no React
|
|
131
|
+
Hook Form and need no icons would have to install those dependencies anyway so
|
|
132
|
+
their bundler could resolve an import they never execute. Two of the five consume
|
|
133
|
+
zero icons.
|
|
134
|
+
|
|
135
|
+
### Next, Server Components and `"use client"`
|
|
136
|
+
|
|
137
|
+
The root, `./brand`, `./form` and `./chart` ship `"use client"` in the published
|
|
138
|
+
`dist/`, and they are the only four. They render React and their Radix primitives call `createContext` at
|
|
139
|
+
module scope, so without the directive a Next project with the App Router cannot
|
|
140
|
+
import them at all: it fails at build time with
|
|
141
|
+
`TypeError: (0 , r.createContext) is not a function`.
|
|
142
|
+
|
|
143
|
+
You do not add anything: importing `Button` from a Server Component works, and
|
|
144
|
+
the boundary is already where it belongs. What you should NOT do is wrap the
|
|
145
|
+
import in an adapter of your own marked `"use client"` — that was the workaround
|
|
146
|
+
before 0.6.0 and it pulled 272 KB of client chunk in for components that never
|
|
147
|
+
needed it.
|
|
148
|
+
|
|
149
|
+
The five portable subpaths do NOT carry the directive, and that is the half that
|
|
150
|
+
matters in a Server Component: `./tokens`, `./theme`, `./variants`, `./og` and
|
|
151
|
+
`./shiki` stay on the server. Neither does `./social`, which is a third case: it
|
|
152
|
+
renders React — it is nine `<svg>` — so it can never be portable, but it holds no
|
|
153
|
+
state and nothing about it needs a client boundary. It is the only way to put a
|
|
154
|
+
social icon in a Server Component, and § «The social icons come from `./social`»
|
|
155
|
+
below says why the grouped form cannot do it. If all you need are classes — for a `<div>`, an
|
|
156
|
+
`<a>` or an Astro island you do not want to hydrate — import them from
|
|
157
|
+
`./variants` and nothing crosses to the client:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
// A Server Component, or an .astro frontmatter. No React reaches the browser.
|
|
161
|
+
import { buttonVariants, CARD_SURFACE } from '@eduardoalvarez/arrecife/variants';
|
|
162
|
+
|
|
163
|
+
<a className={buttonVariants({ variant: 'tertiary' })} href="/cursos">./ver_cursos →</a>
|
|
164
|
+
<div className={CARD_SURFACE}>…</div>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
In Astro and in plain Vite the directive is inert — a string literal at the top
|
|
168
|
+
of a module. Rollup may warn `Module level directives cause errors when bundled`
|
|
169
|
+
and nothing else happens: one `dist` serves the Next projects and the Astro ones.
|
|
127
170
|
|
|
128
171
|
```ts
|
|
129
|
-
//
|
|
172
|
+
// Good, in an OG generator or in astro.config.mjs
|
|
130
173
|
import { tokens } from '@eduardoalvarez/arrecife/tokens';
|
|
131
174
|
import { arrecife } from '@eduardoalvarez/arrecife/shiki';
|
|
132
175
|
|
|
133
|
-
//
|
|
134
|
-
import {
|
|
176
|
+
// Good, in the <head> of an Astro that mounts no React
|
|
177
|
+
import { themeScript } from '@eduardoalvarez/arrecife/theme';
|
|
135
178
|
|
|
136
|
-
//
|
|
179
|
+
// Bad: mounts React where it is not needed
|
|
137
180
|
import { tokens } from '@eduardoalvarez/arrecife';
|
|
138
181
|
```
|
|
139
182
|
|
|
140
|
-
###
|
|
183
|
+
### The theme, and the first-paint flash
|
|
141
184
|
|
|
142
|
-
`ThemeToggle`
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
185
|
+
`ThemeToggle` is the button; the hard part is in `./theme`. Without `themeScript`
|
|
186
|
+
inline in the `<head>`, the first paint comes out in the default mode and the
|
|
187
|
+
chosen one arrives a frame later: on a dark site the user left in light mode,
|
|
188
|
+
that is a white flash on every load.
|
|
146
189
|
|
|
147
190
|
```astro
|
|
148
191
|
---
|
|
149
|
-
import {
|
|
192
|
+
import { themeScript } from '@eduardoalvarez/arrecife/theme';
|
|
150
193
|
---
|
|
151
194
|
<head>
|
|
152
|
-
|
|
195
|
+
<!-- The site follows the reader's OS, falling back to dark. -->
|
|
196
|
+
<script is:inline set:html={themeScript()} />
|
|
197
|
+
|
|
198
|
+
<!-- Or: this site IS dark, and the OS is not consulted. -->
|
|
199
|
+
<script is:inline set:html={themeScript({ base: 'dark' })} />
|
|
153
200
|
</head>
|
|
154
201
|
```
|
|
155
202
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
203
|
+
`base` is not «the fallback», it is «this site IS this mode». A stored choice
|
|
204
|
+
still wins over it, so the toggle keeps working — it sets what happens when
|
|
205
|
+
nobody has chosen yet. Use it when the project has already decided its mode and
|
|
206
|
+
does not want the OS overruling that.
|
|
207
|
+
|
|
208
|
+
It has to go INLINE. A `<script src>`, even a synchronous one, gets downloaded,
|
|
209
|
+
and the flash comes back. The script re-attaches on `astro:after-swap` because
|
|
210
|
+
view transitions replace the whole `<html>`.
|
|
211
|
+
|
|
212
|
+
### `npx arrecife` — run it once after installing
|
|
213
|
+
|
|
214
|
+
Two things break with **no error at all**, and the command catches both.
|
|
215
|
+
|
|
216
|
+
**Tailwind purges everything the components emit** unless the stylesheet has
|
|
217
|
+
`@source "<path>/node_modules/@eduardoalvarez/arrecife/dist"`. It does not scan
|
|
218
|
+
`node_modules`. There is no console error and no undefined class: the card mounts
|
|
219
|
+
with no padding, no radius and no border. The path is relative to the SHEET, not
|
|
220
|
+
to the project root, and the command computes it.
|
|
221
|
+
|
|
222
|
+
**A `--color-*` of yours silently replaces ours.** A project coming from shadcn
|
|
223
|
+
has `@theme inline { --color-accent: var(--accent); }` — shadcn's `--accent` is
|
|
224
|
+
the hover surface, `#17303E`, and ours is the brand turquoise, `#35D6C0`. That
|
|
225
|
+
one line repainted 88 classes inside the library's own components grey. Five
|
|
226
|
+
names collide in total; four agree on the value and are harmless, and the command
|
|
227
|
+
tells them apart.
|
|
228
|
+
|
|
229
|
+
Do not silence it by removing the `@import`: the fix is the `@source` line, or
|
|
230
|
+
renaming your own token.
|
|
231
|
+
|
|
232
|
+
### `SidebarNav` groups, and the icon replaces the prompt
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
<SidebarNav aria-label="Administración" brand={<>…</>} version="v0.6.0" branch="main">
|
|
236
|
+
<SidebarItem href="/admin" icon={<Icon as={SquaresFour} />} active>Resumen</SidebarItem>
|
|
237
|
+
|
|
238
|
+
<SidebarGroup label="Ventas">
|
|
239
|
+
<SidebarItem href="/admin/ventas" icon={<Icon as={CreditCard} />}>Ventas</SidebarItem>
|
|
240
|
+
<SidebarItem href="/admin/cupones" icon={<Icon as={Ticket} />}>Cupones</SidebarItem>
|
|
241
|
+
</SidebarGroup>
|
|
242
|
+
</SidebarNav>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Past about eight items a flat sidebar stops being readable. Each `SidebarGroup`
|
|
246
|
+
is a nested list named by its label, so a screen reader says «lista Ventas, 3
|
|
247
|
+
elementos» instead of one list of eleven. The label is a paragraph and **not** a
|
|
248
|
+
heading on purpose: a sidebar is navigation, and a heading here would land in the
|
|
249
|
+
page's own outline.
|
|
250
|
+
|
|
251
|
+
**`icon` replaces the `▸`, it does not join it.** Do not pass a glyph and expect
|
|
252
|
+
the prompt as well. A sidebar with no icons keeps the prompt on every item, which
|
|
253
|
+
is what a four-section blog admin wants.
|
|
254
|
+
|
|
255
|
+
**`brand` does not replace `title`.** `title` is the eyebrow and also the `nav`'s
|
|
256
|
+
accessible name when it is a string; a logo is not an accessible name, so pass
|
|
257
|
+
`aria-label` when you use `brand`. See `docs/decisions.md` § 32.
|
|
258
|
+
|
|
259
|
+
**It collapses to a rail, and the toggle is CONTROLLED:**
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
263
|
+
|
|
264
|
+
<SidebarNav
|
|
265
|
+
collapsed={collapsed}
|
|
266
|
+
onCollapsedChange={setCollapsed}
|
|
267
|
+
brand={<Wordmark />}
|
|
268
|
+
mark={<Isotype className="h-6" />}
|
|
269
|
+
user={<Avatar … />}
|
|
270
|
+
>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
There is no uncontrolled mode: this state is almost always persisted, and an
|
|
274
|
+
internal one would fight the cookie you already keep. `onCollapsedChange` is also
|
|
275
|
+
what makes the toggle appear — `collapsed` on its own is a rail with no way out,
|
|
276
|
+
which is a layout and not an accident.
|
|
159
277
|
|
|
160
|
-
|
|
278
|
+
Collapsed, the widths become `w-sidebar-rail` (56) and `w-sidebar` (256), and the
|
|
279
|
+
component owns them only when it can collapse. It does **not** transition, on
|
|
280
|
+
purpose. `brand` is hidden and `mark` takes its place, because a wordmark does
|
|
281
|
+
not fit in a rail. Every label stays in the accessibility tree as `sr-only`, so
|
|
282
|
+
do not «simplify» by dropping the children of a collapsed item. See
|
|
283
|
+
`docs/decisions.md` § 34.
|
|
284
|
+
|
|
285
|
+
### `Nav` is two slots and one height
|
|
286
|
+
|
|
287
|
+
Almost everything an app shell wants from a site bar is already a slot:
|
|
161
288
|
|
|
162
289
|
```tsx
|
|
163
|
-
|
|
290
|
+
<Nav
|
|
291
|
+
size="compact" // 56px, for a shell with a sidebar
|
|
292
|
+
brand={<a href="/"><Logo /><span><span className="text-accent">~/</span>cursos</span></a>}
|
|
293
|
+
actions={session ? <UserMenu /> : <Button size="sm" asChild><Link href="/login">Entrar</Link></Button>}
|
|
294
|
+
>
|
|
295
|
+
<NavItem href="/cursos" active>cursos</NavItem>
|
|
296
|
+
</Nav>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`brand` and `actions` are `ReactNode`, so a wordmark, a user menu, a theme toggle
|
|
300
|
+
or a search box go in without the library knowing anything about sessions.
|
|
301
|
+
**Session state does not get a prop** — it is project infrastructure, and the
|
|
302
|
+
library takes none.
|
|
303
|
+
|
|
304
|
+
`size="compact"` is 56px instead of 64, for a bar that shares the screen with a
|
|
305
|
+
sidebar. It is a prop and not a class because the height lives on `Nav`'s inner
|
|
306
|
+
container: `className` reaches the `<header>` and stops there, so passing `h-14`
|
|
307
|
+
does nothing.
|
|
308
|
+
|
|
309
|
+
**One `Nav` per page.** It renders the site's `banner` landmark, and two banners
|
|
310
|
+
on one page is an accessibility failure — which is also why `PageHeader` goes
|
|
311
|
+
inside `<main>` and is not a landmark. See `docs/decisions.md` § 30.
|
|
312
|
+
|
|
313
|
+
### Icons are yours, the way they are drawn is not
|
|
314
|
+
|
|
315
|
+
The library ships no icon set and `lib/glyphs.tsx` is not exported: it is the
|
|
316
|
+
minimum set the primitives need and it does not grow. What the library does ship
|
|
317
|
+
is the drawing.
|
|
318
|
+
|
|
319
|
+
```tsx
|
|
320
|
+
import { GraduationCap, Trash } from '@phosphor-icons/react';
|
|
321
|
+
import { Icon } from '@eduardoalvarez/arrecife/icons';
|
|
322
|
+
|
|
323
|
+
// 1em, and `tone="action"` by default — `regular`, the stroke the document names
|
|
324
|
+
<Icon as={GraduationCap} />
|
|
325
|
+
|
|
326
|
+
// Inside a control with no text, the name goes on the CONTROL
|
|
327
|
+
<Button size="icon-sm" variant="secondary" aria-label="Borrar la fila">
|
|
328
|
+
<Icon as={Trash} />
|
|
329
|
+
</Button>
|
|
330
|
+
|
|
331
|
+
// Alone and meaning something on its own, it gets a name
|
|
332
|
+
<Icon as={Trophy} label="Curso completado" />
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**Do not size them by hand.** `size-4`, `size-3.5`, `size-6` scattered through a
|
|
336
|
+
codebase is what this replaces: at 1em the icon takes the size of the text it
|
|
337
|
+
sits in — 13px beside `text-label`, 15px beside `text-ui` — and nobody picks a
|
|
338
|
+
number.
|
|
339
|
+
|
|
340
|
+
**The weight is not yours to pick either, but it is not one value.** `tone` names
|
|
341
|
+
what the icon is doing and the weight follows from it. There are three and there
|
|
342
|
+
is no fourth:
|
|
343
|
+
|
|
344
|
+
| `tone` | Weight | What it is |
|
|
345
|
+
| --- | --- | --- |
|
|
346
|
+
| `action` · the default | `regular` | An icon that is a control or names one. It is the system's line: 16 on a 256 grid = 0.0625em, against the document's 1.6 on a 24 grid = 0.0667em. Six per cent apart, which is no pixel on any screen |
|
|
347
|
+
| `current` | `fill` | The one of a set you are on — the sidebar item carrying `aria-current` |
|
|
348
|
+
| `quiet` | `light` | Furniture: a marker in a metadata row, not a control and not a state |
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
<SidebarItem href="/cursos" active icon={<Icon as={GraduationCap} tone="current" />}>
|
|
352
|
+
cursos
|
|
353
|
+
</SidebarItem>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`current` is the one that earns the axis. An active item already paints itself
|
|
357
|
+
biolume, and colour on its own is the channel WCAG 1.4.1 says may not carry
|
|
358
|
+
meaning alone; the fill is the second channel, and it is the one that survives a
|
|
359
|
+
forced-colours mode. `weight` is deliberately **not** a prop: Phosphor ships six
|
|
360
|
+
and this system reads three, because `thin`, `bold` and `duotone` have no role
|
|
361
|
+
behind them here.
|
|
362
|
+
|
|
363
|
+
`@phosphor-icons/react` is an **optional** peer dependency. If your project uses
|
|
364
|
+
no icons you install nothing; two of the five do exactly that.
|
|
365
|
+
|
|
366
|
+
**An icon is not illustration, and the two never substitute for each other.**
|
|
367
|
+
Tiburoncín — the faces, the poses, the fin — is the mascot; it comes from
|
|
368
|
+
`./brand`, and the manual says where a face may appear: empty states,
|
|
369
|
+
confirmations, errors, course progress, celebration, and nowhere else. An icon is
|
|
370
|
+
functional vocabulary and goes wherever a control needs a label it cannot spell.
|
|
371
|
+
Do not put an icon where the system asks for a face, and do not put a face where
|
|
372
|
+
a control wants an icon.
|
|
373
|
+
|
|
374
|
+
**In Next, import from `@phosphor-icons/react/ssr` inside a Server Component.**
|
|
375
|
+
Phosphor's default build reads `IconContext` through `useContext`, and a hook in
|
|
376
|
+
a Server Component throws. It ships no `"use client"` to stop you, so the failure
|
|
377
|
+
arrives at render rather than at build. The `/ssr` entry is the same icons
|
|
378
|
+
without the context read, and `Icon` works with either.
|
|
379
|
+
|
|
380
|
+
See `docs/decisions.md` § 29 and § 35.
|
|
381
|
+
|
|
382
|
+
### `Stat`'s delta says direction, not judgement
|
|
383
|
+
|
|
384
|
+
```tsx
|
|
385
|
+
<Stat
|
|
386
|
+
label="alumnos"
|
|
387
|
+
value="1.284"
|
|
388
|
+
delta={{ value: '+12 esta semana', direction: 'up' }}
|
|
389
|
+
/>
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
`direction` picks the arrow and **never the colour**. «+12 alumnos» and «+12
|
|
393
|
+
errores» point the same way and mean opposite things, so whether a number is good
|
|
394
|
+
news is `tone`'s job and yours: `neutral` for a datum, `alert` when the number IS
|
|
395
|
+
the problem, `achievement` when it is the reward. `alert` and `achievement` paint
|
|
396
|
+
the same sand on purpose — the API is the meaning, the colour is the
|
|
397
|
+
implementation. See `docs/decisions.md` § 28.
|
|
398
|
+
|
|
399
|
+
`delta.value` arrives already formatted, like `value`: the library imposes no
|
|
400
|
+
locale and computes no percentage. `spark` is a `ReactNode` and the library ships
|
|
401
|
+
no sparkline — pass your own, exactly like `icon`.
|
|
402
|
+
|
|
403
|
+
**Do not colour the number.** A neutral `Stat` renders its value in primary ink,
|
|
404
|
+
and biolume goes on the icon badge and the sparkline instead: three accents in
|
|
405
|
+
one card and the figure stops being the loudest thing in it. `alert` and
|
|
406
|
+
`achievement` DO paint the number sand, which is how «this number is not just a
|
|
407
|
+
number» is said. See `docs/decisions.md` § 31.
|
|
408
|
+
|
|
409
|
+
**`icon` is a badge in the corner opposite the title**, in a circle tinted at
|
|
410
|
+
10 % of the tone. You pass the glyph; the circle, the tint and the size are the
|
|
411
|
+
component's.
|
|
412
|
+
|
|
413
|
+
### The two shapes of `EmptyState`
|
|
414
|
+
|
|
415
|
+
```tsx
|
|
416
|
+
// The empty state IS the screen: a search with no results, a 404, a section
|
|
417
|
+
// with nothing in it yet. It carries the face, and `expression` is mandatory.
|
|
418
|
+
<EmptyState expression="waiting" title="Sin resultados" description="…" />
|
|
419
|
+
|
|
420
|
+
// The hole INSIDE something else: a table page, a dashboard widget. No face, no
|
|
421
|
+
// surface, no border — the table or the card already draws the region.
|
|
422
|
+
<EmptyState variant="inline" title="No hay lecciones en esta página" />
|
|
423
|
+
|
|
424
|
+
// ❌ does not compile: the props are a union, and `inline` has no face
|
|
425
|
+
<EmptyState variant="inline" expression="waiting" title="…" />
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
`inline` takes an optional `icon` — a `ReactNode` the project passes and sizes,
|
|
429
|
+
at 1em and in `currentColor`, like `Stat`'s. The library ships no icons.
|
|
430
|
+
|
|
431
|
+
Do not reach for `page` inside a table because the face is «nicer»: an admin
|
|
432
|
+
screen with a dozen empty regions gets a dozen mascots, which is what made every
|
|
433
|
+
consuming project write its own empty state instead of using this one.
|
|
434
|
+
|
|
435
|
+
### The social icons come from `./social`
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
// ❌ does not exist: the root publishes them grouped, not loose
|
|
164
439
|
import { GitHub } from '@eduardoalvarez/arrecife';
|
|
165
440
|
|
|
166
|
-
// ✅
|
|
441
|
+
// ✅ the normal form
|
|
442
|
+
import { GitHub } from '@eduardoalvarez/arrecife/social';
|
|
443
|
+
|
|
444
|
+
// ✅ for iterating the catalogue
|
|
167
445
|
import { social } from '@eduardoalvarez/arrecife';
|
|
168
446
|
<social.GitHub />
|
|
169
447
|
```
|
|
170
448
|
|
|
171
|
-
|
|
172
|
-
`
|
|
449
|
+
All nine: `GitHub`, `LinkedIn`, `X`, `Instagram`, `Discord`, `YouTube`, `Rss`,
|
|
450
|
+
`Email`, `Newsletter`. `Newsletter` is the bell: a way to follow, like `Rss`,
|
|
451
|
+
named for what it means.
|
|
173
452
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
453
|
+
**In a Server Component the subpath is mandatory, not preferred.** The root
|
|
454
|
+
carries `"use client"`, and a client reference crosses the boundary per EXPORT —
|
|
455
|
+
the properties of a plain object are not exports, so `social.LinkedIn` is
|
|
456
|
+
`undefined` on the server and `undefined` as an element type kills the build at
|
|
457
|
+
prerender. `./social` carries no directive: it renders on the server and ships no
|
|
458
|
+
client JS. Use `social` only when mapping a list of names onto icons.
|
|
459
|
+
|
|
460
|
+
The root keeps the group because one of them is called `X`, and loose at the root
|
|
461
|
+
it collides. In the subpath, alias it: `import { X as XIcon }`.
|
|
462
|
+
|
|
463
|
+
The internal glyphs — `Close`, `ChevronDown`, `Sun` — are **not exported** and
|
|
464
|
+
are not going to be: they are the primitives' minimum set. A component that needs
|
|
465
|
+
an icon receives it as a prop (`Stat` has `icon`, each `SocialLink` in `Footer`
|
|
466
|
+
has its own). Do not ask for them to be published: pass your own.
|
|
178
467
|
|
|
179
468
|
## Tokens
|
|
180
469
|
|
|
181
|
-
|
|
182
|
-
|
|
470
|
+
The source is a TypeScript object and the CSS output is generated from it, so the
|
|
471
|
+
same value is available in both places and they cannot disagree.
|
|
183
472
|
|
|
184
|
-
| Token | Custom property |
|
|
473
|
+
| Token | Custom property | Tailwind utility |
|
|
185
474
|
| --- | --- | --- |
|
|
186
|
-
| `colors[
|
|
475
|
+
| `colors[mode].surfaceRaised` | `--color-surface-raised` | `bg-surface-raised` |
|
|
187
476
|
| `brand.hull` | `--color-brand-hull` | `bg-brand-hull` |
|
|
188
|
-
| `typeScale.h1` | `--text-h1` | `text-h1` (
|
|
477
|
+
| `typeScale.h1` | `--text-h1` | `text-h1` (brings line-height, weight and tracking) |
|
|
189
478
|
| `fonts.display` | `--font-display` | `font-display` |
|
|
190
479
|
| `radius.card` | `--radius-card` | `rounded-card` |
|
|
191
480
|
| `spacing.stepLg` | `--spacing-step-lg` | `p-step-lg`, `gap-step-lg`, `mb-step-lg` |
|
|
192
481
|
| `spacing.section` | `--spacing-section` | `py-section`, `mb-section` |
|
|
193
482
|
| `control.md` | `--spacing-control-md` | `px-control-md` |
|
|
194
483
|
| `control.icon` | `--spacing-control-icon` | `size-control-icon` (42×42) |
|
|
195
|
-
| `gradient[
|
|
196
|
-
| `size.nav` | `--spacing-nav` | `h-nav` |
|
|
484
|
+
| `gradient[mode].hero` | `--gradient-hero` | `gradient-hero` |
|
|
485
|
+
| `size.nav` / `size.navCompact` | `--spacing-nav` / `--spacing-nav-compact` | `h-nav` / `h-nav-compact` |
|
|
197
486
|
| `size.content` / `size.wide` | `--container-content` / `--container-wide` | `max-w-content` / `max-w-wide` |
|
|
198
487
|
| `limits.measure` | `--container-measure` | `max-w-measure` |
|
|
199
488
|
| `shadow.standard` | `--shadow-standard` | `shadow-standard` |
|
|
200
489
|
| `motion` | `--duration-standard`, `--ease-standard` | `duration-standard`, `ease-standard` |
|
|
201
490
|
|
|
202
|
-
`transition-standard`
|
|
203
|
-
color
|
|
204
|
-
|
|
205
|
-
**
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<https://github.com/Proskynete/arrecife/blob/main/docs/
|
|
213
|
-
|
|
214
|
-
##
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
1. **
|
|
220
|
-
2. **`Button variant="conversion"`
|
|
221
|
-
runtime;
|
|
222
|
-
3.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
- **
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
491
|
+
`transition-standard` is the system's only transition and it can only animate
|
|
492
|
+
color and border: that is how the utility is written.
|
|
493
|
+
|
|
494
|
+
**The spacing steps carry `step` in the name and it is not optional.** `p-md` is
|
|
495
|
+
not an Arrecife class: in a Tailwind v4 project it lands on the numeric scale and
|
|
496
|
+
does nothing visible. Page rhythm is `p-step-md`, `gap-step-sm`, `py-step-xl`.
|
|
497
|
+
They carry a prefix because `xs, sm, md, lg, xl` are the names of Tailwind's
|
|
498
|
+
`--container-*` scale, and a `--spacing-md` of our own was swallowing `max-w-md`
|
|
499
|
+
across the whole project with nothing warning about it. `max-w-*`, `w-*` and
|
|
500
|
+
`h-*` belong to Tailwind and are used as they are. Migration guide from 0.2.0:
|
|
501
|
+
<https://github.com/Proskynete/arrecife/blob/main/docs/migration-0.3.md>.
|
|
502
|
+
|
|
503
|
+
## System rules the consuming code must not break
|
|
504
|
+
|
|
505
|
+
These are identity decisions, already measured. Breaking them produces code that
|
|
506
|
+
compiles and looks wrong, or that fails the project's accessibility audit.
|
|
507
|
+
|
|
508
|
+
1. **Zero literal hexes.** Every color comes from a token or its custom property.
|
|
509
|
+
2. **`Button variant="conversion"` appears once per screen.** It is not enforced
|
|
510
|
+
at runtime; two on the same page are a design error.
|
|
511
|
+
3. **`Button variant="destructive"` is for the irreversible only.** Never for
|
|
512
|
+
«cancel» on a form, and not inside an `AlertDialog` — there the confirm button
|
|
513
|
+
stays `primary`, because the title, the focus on cancel and the no-click-outside
|
|
514
|
+
already carry the weight. See `docs/decisions.md` § 21.
|
|
515
|
+
4. **`secondary` is never filled.** It is border and text.
|
|
516
|
+
5. **No entrance animations.** Modals, menus, tooltips and toasts appear where
|
|
517
|
+
they will stay. The only exception is the `Button loading` spinner.
|
|
518
|
+
6. **Semantics and scale are independent.** An `h2` that has to look small is
|
|
519
|
+
`<Text as="h2" variant="h3">`, never an `h3` that lies about the hierarchy.
|
|
520
|
+
7. **`textMuted` never goes over `surfaceRaised`**: it gives 4.07 in dark. Over a
|
|
521
|
+
raised surface — menus, active tabs — the token is `textSecondary`.
|
|
522
|
+
8. **A background tinted with a semantic color carries text from a text token**,
|
|
523
|
+
not from the semantic color. The color stays on the border and on the glyph.
|
|
524
|
+
Putting `accent` over its own tint at 8 % gives 4.12 and does not reach AA.
|
|
525
|
+
9. **`Progress` requires `label`.** A bar with no accessible name does not say
|
|
526
|
+
what it is about.
|
|
527
|
+
10. **`Button size="icon"` and `size="icon-sm"` require `aria-label`.** They carry
|
|
528
|
+
no text. `icon` is 42×42 and is a page action; `icon-sm` is 32×32 and is for a
|
|
529
|
+
dense table row.
|
|
530
|
+
11. **The mascot's faces only appear** in empty states, confirmations, errors,
|
|
531
|
+
course progress and celebration. Never in a hero, pricing, services, contact
|
|
532
|
+
or the CV.
|
|
533
|
+
**And not in every empty state either**: `EmptyState variant="inline"` is the
|
|
534
|
+
hole inside a table page or a dashboard widget, and it carries no face — the
|
|
535
|
+
type does not accept one. `page`, the default, is the one that IS the screen,
|
|
536
|
+
and there `expression` stays mandatory. A dozen mascots on one admin screen is
|
|
537
|
+
not the humour contract. See `docs/decisions.md` § 27.
|
|
538
|
+
12. **The fin is not a free parameter**: `foam` on a dark background, `color` on a
|
|
539
|
+
light one. The components already choose it from the background.
|
|
540
|
+
|
|
541
|
+
## What the library does NOT do, on purpose
|
|
542
|
+
|
|
543
|
+
These are the confusions people run into most often when consuming it.
|
|
544
|
+
|
|
545
|
+
- **It does not ship Shiki.** It publishes the *theme*, not the highlighter.
|
|
546
|
+
`CodeBlock` receives the code **already highlighted** by the project's tool.
|
|
547
|
+
- **It does not format dates.** `ArticleCard`, `TalkCard` and company receive the
|
|
548
|
+
date already formatted by the project: the library imposes no locale.
|
|
549
|
+
`dateTime` is separate, in ISO, for the `<time>` attribute.
|
|
550
|
+
- **`NewsletterForm` does not do the POST.** It is presentational: it takes
|
|
551
|
+
`state` and emits `onSubmitEmail`. The call is made by the project with its own
|
|
552
|
+
provider.
|
|
553
|
+
- **It ships no router.** The components with links accept `asChild` to wrap the
|
|
554
|
+
framework's `Link`.
|
|
555
|
+
- **It ships no `data-testid`.** A composed part your test suite has to reach is
|
|
556
|
+
reached with a slot: `ArticleCard`'s `tagAsChild`, `Breadcrumb`'s and
|
|
557
|
+
`TableOfContents`'s `linkAsChild`. They hand you the element and its
|
|
558
|
+
attributes and keep the classes. Do NOT select by structure or by a style
|
|
559
|
+
class — a style class is not a contract and it changes when the style does.
|
|
560
|
+
- **It does not load fonts.** It declares them by name.
|
|
561
|
+
- **There is no Tailwind v3 preset.**
|
|
562
|
+
|
|
563
|
+
## Usage patterns
|
|
260
564
|
|
|
261
565
|
```tsx
|
|
262
566
|
import { Text, Button, Badge, cn } from '@eduardoalvarez/arrecife';
|
|
@@ -264,12 +568,12 @@ import type { TextProps } from '@eduardoalvarez/arrecife';
|
|
|
264
568
|
|
|
265
569
|
<Text variant="eyebrow" tone="muted">charlas</Text>
|
|
266
570
|
<Text as="h2" variant="h1">Escalar con criterio</Text>
|
|
267
|
-
<Text variant="body">
|
|
268
|
-
<Text variant="ui" measure={false}>
|
|
571
|
+
<Text variant="body">Clamps itself to 68ch.</Text>
|
|
572
|
+
<Text variant="ui" measure={false}>No clamp, for a narrow cell.</Text>
|
|
269
573
|
```
|
|
270
574
|
|
|
271
|
-
`asChild`
|
|
272
|
-
|
|
575
|
+
`asChild` renders the child instead of the component's own element. It is how the
|
|
576
|
+
framework's link gets wrapped without losing the styles:
|
|
273
577
|
|
|
274
578
|
```tsx
|
|
275
579
|
<Button asChild>
|
|
@@ -277,23 +581,23 @@ enlace del framework sin perder los estilos:
|
|
|
277
581
|
</Button>
|
|
278
582
|
```
|
|
279
583
|
|
|
280
|
-
`cn`
|
|
281
|
-
|
|
584
|
+
`cn` is `clsx` + `tailwind-merge`. It is used to compose `className` without two
|
|
585
|
+
utilities from the same group fighting each other.
|
|
282
586
|
|
|
283
|
-
Open Graph,
|
|
587
|
+
Open Graph, without React:
|
|
284
588
|
|
|
285
589
|
```ts
|
|
286
590
|
import satori from 'satori';
|
|
287
|
-
import {
|
|
591
|
+
import { articleTemplate, OG } from '@eduardoalvarez/arrecife/og';
|
|
288
592
|
|
|
289
|
-
const svg = await satori(
|
|
593
|
+
const svg = await satori(articleTemplate({ title, date, readingMinutes }), {
|
|
290
594
|
width: OG.width, // 1200
|
|
291
595
|
height: OG.height, // 630
|
|
292
596
|
fonts: [...],
|
|
293
597
|
});
|
|
294
598
|
```
|
|
295
599
|
|
|
296
|
-
|
|
600
|
+
Syntax highlighting, from the site's configuration:
|
|
297
601
|
|
|
298
602
|
```ts
|
|
299
603
|
import { arrecife } from '@eduardoalvarez/arrecife/shiki';
|
|
@@ -303,1253 +607,1348 @@ export default defineConfig({
|
|
|
303
607
|
});
|
|
304
608
|
```
|
|
305
609
|
|
|
306
|
-
#
|
|
610
|
+
# Inventory
|
|
307
611
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
612
|
+
What follows comes out of the TypeScript compiler on every build. Only the props
|
|
613
|
+
**declared by the library** are listed: the ones inherited from an HTML element
|
|
614
|
+
or from a Radix primitive are summarised in the `Extends` line, and they are the
|
|
615
|
+
usual ones.
|
|
311
616
|
|
|
312
|
-
##
|
|
617
|
+
## Primitives
|
|
313
618
|
|
|
314
|
-
|
|
619
|
+
Imported from `@eduardoalvarez/arrecife`. 110 exports.
|
|
315
620
|
|
|
316
621
|
### Accordion, AccordionItem, AccordionTrigger, AccordionContent
|
|
317
622
|
|
|
318
|
-
|
|
623
|
+
Source: `src/primitives/accordion.tsx`
|
|
319
624
|
|
|
320
625
|
**Accordion**
|
|
321
|
-
|
|
626
|
+
The disclosure. Two projects asked for it: the portfolio FAQ and the course syllabus, which is literally a list of sections that open.
|
|
322
627
|
|
|
323
|
-
-
|
|
324
|
-
-
|
|
628
|
+
- Extends: `ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>`
|
|
629
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
325
630
|
|
|
326
631
|
**AccordionItem**
|
|
327
|
-
-
|
|
632
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
328
633
|
|
|
329
634
|
**AccordionTrigger**
|
|
330
|
-
|
|
635
|
+
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
636
|
|
|
332
|
-
-
|
|
637
|
+
- Extends: `ComponentPropsWithoutRef< typeof AccordionPrimitive.Trigger >`
|
|
333
638
|
|
|
334
|
-
| prop |
|
|
639
|
+
| prop | type | req. | default | what it does |
|
|
335
640
|
| --- | --- | --- | --- | --- |
|
|
336
|
-
| `headingLevel` | `4 \| 2 \| 3` | | `3` |
|
|
641
|
+
| `headingLevel` | `4 \| 2 \| 3` | | `3` | The level of the heading wrapping the trigger. |
|
|
337
642
|
|
|
338
643
|
**AccordionContent**
|
|
339
|
-
-
|
|
644
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
340
645
|
|
|
341
646
|
### AlertDialogOverlay, AlertDialogContent, AlertDialogHeader, AlertDialogFooter, AlertDialogTitle, AlertDialogDescription, AlertDialogCancel, AlertDialogAction, AlertDialog, AlertDialogTrigger
|
|
342
647
|
|
|
343
|
-
|
|
648
|
+
Source: `src/primitives/alert-dialog.tsx`
|
|
344
649
|
|
|
345
650
|
**AlertDialogOverlay**
|
|
346
|
-
-
|
|
651
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
347
652
|
|
|
348
653
|
**AlertDialogContent**
|
|
349
|
-
|
|
654
|
+
No entrance animation, same as `Dialog`: it appears where it will stay.
|
|
350
655
|
|
|
351
|
-
-
|
|
656
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
352
657
|
|
|
353
658
|
**AlertDialogHeader**
|
|
354
|
-
-
|
|
659
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
355
660
|
|
|
356
661
|
**AlertDialogFooter**
|
|
357
|
-
|
|
662
|
+
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
663
|
|
|
359
|
-
-
|
|
664
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
360
665
|
|
|
361
666
|
**AlertDialogTitle**
|
|
362
|
-
-
|
|
667
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
363
668
|
|
|
364
669
|
**AlertDialogDescription**
|
|
365
|
-
|
|
670
|
+
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
671
|
|
|
367
|
-
-
|
|
672
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
368
673
|
|
|
369
674
|
**AlertDialogCancel**
|
|
370
|
-
|
|
675
|
+
The one that takes focus on open.
|
|
371
676
|
|
|
372
|
-
-
|
|
677
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
373
678
|
|
|
374
679
|
**AlertDialogAction**
|
|
375
|
-
-
|
|
680
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
376
681
|
|
|
377
682
|
**AlertDialog**
|
|
378
|
-
|
|
683
|
+
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
684
|
|
|
380
|
-
-
|
|
685
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
381
686
|
|
|
382
687
|
**AlertDialogTrigger**
|
|
383
|
-
-
|
|
688
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
384
689
|
|
|
385
690
|
### Alert
|
|
386
691
|
|
|
387
|
-
|
|
692
|
+
Source: `src/primitives/alert.tsx`
|
|
388
693
|
|
|
389
|
-
|
|
694
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'title'> & VariantProps<typeof alert>`
|
|
390
695
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
| prop | tipo | req. | defecto | qué hace |
|
|
696
|
+
| prop | type | req. | default | what it does |
|
|
394
697
|
| --- | --- | --- | --- | --- |
|
|
395
|
-
| `
|
|
396
|
-
| `icon` | `ReactNode` | | |
|
|
698
|
+
| `emphasis` | `"subtle" \| "strong"` | | | |
|
|
699
|
+
| `icon` | `ReactNode` | | | Replaces the variant's mono glyph. Never an emoji: if you need something else, it is an SVG from `glyphs`. |
|
|
397
700
|
| `title` | `ReactNode` | | | |
|
|
398
|
-
| `variant` | `"accent" \| "success" \| "warning" \| "error"` | |
|
|
701
|
+
| `variant` | `"accent" \| "success" \| "warning" \| "error"` | | | |
|
|
399
702
|
|
|
400
703
|
### Avatar, AvatarImage, AvatarFallback, AvatarUpload
|
|
401
704
|
|
|
402
|
-
|
|
705
|
+
Source: `src/primitives/avatar.tsx`
|
|
403
706
|
|
|
404
707
|
**Avatar**
|
|
405
|
-
|
|
708
|
+
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
709
|
|
|
407
|
-
-
|
|
710
|
+
- Extends: `ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & VariantProps<typeof avatar>`
|
|
408
711
|
|
|
409
|
-
| prop |
|
|
712
|
+
| prop | type | req. | default | what it does |
|
|
410
713
|
| --- | --- | --- | --- | --- |
|
|
411
|
-
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | |
|
|
714
|
+
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | | | |
|
|
412
715
|
|
|
413
716
|
**AvatarImage**
|
|
414
|
-
-
|
|
717
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
415
718
|
|
|
416
719
|
**AvatarFallback**
|
|
417
|
-
|
|
720
|
+
Initials while the image loads, or when there is no image.
|
|
418
721
|
|
|
419
|
-
-
|
|
722
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
420
723
|
|
|
421
724
|
**AvatarUpload**
|
|
422
|
-
|
|
725
|
+
The avatar you can change. `Avatar` displays; this one also lets you pick.
|
|
423
726
|
|
|
424
|
-
-
|
|
727
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' \| 'children'> & VariantProps<typeof avatar>`
|
|
425
728
|
|
|
426
|
-
| prop |
|
|
729
|
+
| prop | type | req. | default | what it does |
|
|
427
730
|
| --- | --- | --- | --- | --- |
|
|
428
|
-
| `accept` | `string` | | `image/*` |
|
|
731
|
+
| `accept` | `string` | | `image/*` | What the system dialog accepts. |
|
|
429
732
|
| `disabled` | `boolean \| undefined` | | `false` | |
|
|
430
|
-
| `fallback` | `ReactNode` | | |
|
|
431
|
-
| `label` | `string` | | `Cambiar la foto` |
|
|
432
|
-
| `onSelectFile` | `(
|
|
733
|
+
| `fallback` | `ReactNode` | | | Initials while there is no image. |
|
|
734
|
+
| `label` | `string` | | `Cambiar la foto` | The control's accessible name. It is the only thing naming it: there is no visible text. |
|
|
735
|
+
| `onSelectFile` | `(file: File) => void` | | | Fires with the chosen file. The upload is the project's job. |
|
|
433
736
|
| `size` | `"sm" \| "md" \| "lg" \| "xl"` | | | |
|
|
434
|
-
| `src` | `string` | | |
|
|
737
|
+
| `src` | `string` | | | The current image, already uploaded. The local preview beats it while it lasts. |
|
|
435
738
|
|
|
436
739
|
### Badge, CategoryBadge, MetricBadge
|
|
437
740
|
|
|
438
|
-
|
|
741
|
+
Source: `src/primitives/badge.tsx`
|
|
439
742
|
|
|
440
743
|
**Badge**
|
|
441
|
-
|
|
744
|
+
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
745
|
|
|
443
|
-
-
|
|
746
|
+
- Extends: `ComponentPropsWithoutRef<'span'> & VariantProps<typeof badge>`
|
|
444
747
|
|
|
445
|
-
| prop |
|
|
748
|
+
| prop | type | req. | default | what it does |
|
|
446
749
|
| --- | --- | --- | --- | --- |
|
|
447
|
-
| `variant` | `"
|
|
750
|
+
| `variant` | `"neutral" \| "accent" \| "warm" \| "success" \| "warning" \| "error"` | | | |
|
|
448
751
|
|
|
449
752
|
**CategoryBadge**
|
|
450
|
-
|
|
753
|
+
- Extends: `ComponentPropsWithoutRef<'span'>`
|
|
451
754
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
| prop | tipo | req. | defecto | qué hace |
|
|
755
|
+
| prop | type | req. | default | what it does |
|
|
455
756
|
| --- | --- | --- | --- | --- |
|
|
456
|
-
| `active` | `boolean \| undefined` | | `false` |
|
|
757
|
+
| `active` | `boolean \| undefined` | | `false` | Selected filter: solid sand with ink on top. |
|
|
457
758
|
|
|
458
759
|
**MetricBadge**
|
|
459
|
-
-
|
|
760
|
+
- Extends: `ComponentPropsWithoutRef<'span'>`
|
|
460
761
|
|
|
461
|
-
| prop |
|
|
762
|
+
| prop | type | req. | default | what it does |
|
|
462
763
|
| --- | --- | --- | --- | --- |
|
|
463
|
-
| `boxed` | `boolean \| undefined` | | `false` |
|
|
764
|
+
| `boxed` | `boolean \| undefined` | | `false` | Adds the hairline ring. By default a metric carries no box. |
|
|
464
765
|
|
|
465
766
|
### Button
|
|
466
767
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
Las CUATRO variantes del sistema, y solo esas cuatro.
|
|
768
|
+
Source: `src/primitives/button.tsx`
|
|
470
769
|
|
|
471
|
-
-
|
|
770
|
+
- Extends: `ComponentPropsWithoutRef<'button'> & VariantProps<typeof button>`
|
|
472
771
|
|
|
473
|
-
| prop |
|
|
772
|
+
| prop | type | req. | default | what it does |
|
|
474
773
|
| --- | --- | --- | --- | --- |
|
|
475
|
-
| `asChild` | `boolean` | | `false` |
|
|
476
|
-
| `icon` | `ReactNode` | | |
|
|
477
|
-
| `loading` | `boolean` | | `false` |
|
|
478
|
-
| `size` | `"sm" \| "md" \| "lg" \| "icon"` | |
|
|
479
|
-
| `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary"` | |
|
|
774
|
+
| `asChild` | `boolean` | | `false` | Renders the child instead of a `<button>`, to wrap a link. |
|
|
775
|
+
| `icon` | `ReactNode` | | | SVG glyph before the text. Hidden while loading. |
|
|
776
|
+
| `loading` | `boolean` | | `false` | Disables and announces `aria-busy`. Incompatible with `asChild`. |
|
|
777
|
+
| `size` | `"sm" \| "md" \| "lg" \| "icon" \| "icon-sm"` | | | |
|
|
778
|
+
| `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary" \| "destructive" \| "destructiveOutline"` | | | |
|
|
480
779
|
|
|
481
780
|
### Calendar
|
|
482
781
|
|
|
483
|
-
|
|
782
|
+
Source: `src/primitives/calendar.tsx`
|
|
484
783
|
|
|
485
|
-
|
|
784
|
+
A navigable month calendar, on top of `react-day-picker`.
|
|
486
785
|
|
|
487
|
-
-
|
|
786
|
+
- Extends: `ComponentProps<typeof DayPicker>`
|
|
488
787
|
|
|
489
|
-
| prop |
|
|
788
|
+
| prop | type | req. | default | what it does |
|
|
490
789
|
| --- | --- | --- | --- | --- |
|
|
491
|
-
| `fullWidth` | `boolean \| undefined` | | `false` |
|
|
790
|
+
| `fullWidth` | `boolean \| undefined` | | `false` | Stretches the calendar to fill its container's whole width, with the cells splitting it evenly. |
|
|
492
791
|
|
|
493
792
|
### Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter
|
|
494
793
|
|
|
495
|
-
|
|
794
|
+
Source: `src/primitives/card.tsx`
|
|
496
795
|
|
|
497
796
|
**Card**
|
|
498
|
-
-
|
|
797
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
499
798
|
|
|
500
799
|
**CardHeader**
|
|
501
|
-
-
|
|
800
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
502
801
|
|
|
503
802
|
**CardTitle**
|
|
504
|
-
-
|
|
803
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
505
804
|
|
|
506
805
|
**CardDescription**
|
|
507
|
-
-
|
|
806
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
508
807
|
|
|
509
808
|
**CardContent**
|
|
510
|
-
-
|
|
809
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
511
810
|
|
|
512
811
|
**CardFooter**
|
|
513
|
-
-
|
|
812
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
514
813
|
|
|
515
814
|
### Checkbox
|
|
516
815
|
|
|
517
|
-
|
|
816
|
+
Source: `src/primitives/checkbox.tsx`
|
|
518
817
|
|
|
519
|
-
-
|
|
520
|
-
-
|
|
818
|
+
- Extends: `ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>`
|
|
819
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
521
820
|
|
|
522
821
|
### Code
|
|
523
822
|
|
|
524
|
-
|
|
823
|
+
Source: `src/primitives/code.tsx`
|
|
525
824
|
|
|
526
|
-
|
|
825
|
+
Inline code, inside prose.
|
|
527
826
|
|
|
528
|
-
-
|
|
529
|
-
-
|
|
827
|
+
- Extends: `ComponentPropsWithoutRef<'code'>`
|
|
828
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
530
829
|
|
|
531
830
|
### DateField
|
|
532
831
|
|
|
533
|
-
|
|
832
|
+
Source: `src/primitives/date-field.tsx`
|
|
534
833
|
|
|
535
|
-
|
|
834
|
+
A date field on the native control, not on a calendar of our own.
|
|
536
835
|
|
|
537
|
-
-
|
|
836
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'input'>, 'type'>`
|
|
538
837
|
|
|
539
|
-
| prop |
|
|
838
|
+
| prop | type | req. | default | what it does |
|
|
540
839
|
| --- | --- | --- | --- | --- |
|
|
541
840
|
| `invalid` | `boolean \| undefined` | | `false` | |
|
|
542
|
-
| `withTime` | `boolean \| undefined` | | `false` |
|
|
841
|
+
| `withTime` | `boolean \| undefined` | | `false` | Adds the time to the field. It is the native `datetime-local`. |
|
|
543
842
|
|
|
544
843
|
### DialogOverlay, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, Dialog, DialogTrigger, DialogClose
|
|
545
844
|
|
|
546
|
-
|
|
845
|
+
Source: `src/primitives/dialog.tsx`
|
|
547
846
|
|
|
548
847
|
**DialogOverlay**
|
|
549
|
-
-
|
|
848
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
550
849
|
|
|
551
850
|
**DialogContent**
|
|
552
|
-
|
|
851
|
+
No entrance animation: there is no scale or displacement in the system.
|
|
553
852
|
|
|
554
|
-
-
|
|
853
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
555
854
|
|
|
556
855
|
**DialogHeader**
|
|
557
|
-
-
|
|
856
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
558
857
|
|
|
559
858
|
**DialogFooter**
|
|
560
|
-
-
|
|
859
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
561
860
|
|
|
562
861
|
**DialogTitle**
|
|
563
|
-
-
|
|
862
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
564
863
|
|
|
565
864
|
**DialogDescription**
|
|
566
|
-
-
|
|
865
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
567
866
|
|
|
568
867
|
**Dialog**
|
|
569
|
-
-
|
|
868
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
570
869
|
|
|
571
870
|
**DialogTrigger**
|
|
572
|
-
-
|
|
871
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
573
872
|
|
|
574
873
|
**DialogClose**
|
|
575
|
-
-
|
|
874
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
576
875
|
|
|
577
876
|
### DropdownMenuContent, DropdownMenuItem, DropdownMenuCheckboxItem, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenu, DropdownMenuTrigger, DropdownMenuGroup, DropdownMenuRadioGroup, DropdownMenuSub
|
|
578
877
|
|
|
579
|
-
|
|
878
|
+
Source: `src/primitives/dropdown-menu.tsx`
|
|
580
879
|
|
|
581
880
|
**DropdownMenuContent**
|
|
582
|
-
|
|
881
|
+
No entrance animation: the menu appears, it does not unfold.
|
|
583
882
|
|
|
584
|
-
-
|
|
883
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
585
884
|
|
|
586
885
|
**DropdownMenuItem**
|
|
587
|
-
-
|
|
886
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
588
887
|
|
|
589
888
|
**DropdownMenuCheckboxItem**
|
|
590
|
-
-
|
|
889
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
591
890
|
|
|
592
891
|
**DropdownMenuRadioItem**
|
|
593
|
-
-
|
|
892
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
594
893
|
|
|
595
894
|
**DropdownMenuLabel**
|
|
596
|
-
-
|
|
895
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
597
896
|
|
|
598
897
|
**DropdownMenuSeparator**
|
|
599
|
-
-
|
|
898
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
600
899
|
|
|
601
900
|
**DropdownMenuSubTrigger**
|
|
602
|
-
-
|
|
901
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
603
902
|
|
|
604
903
|
**DropdownMenuSubContent**
|
|
605
|
-
-
|
|
904
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
606
905
|
|
|
607
906
|
**DropdownMenu**
|
|
608
|
-
-
|
|
907
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
609
908
|
|
|
610
909
|
**DropdownMenuTrigger**
|
|
611
|
-
-
|
|
910
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
612
911
|
|
|
613
912
|
**DropdownMenuGroup**
|
|
614
|
-
-
|
|
913
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
615
914
|
|
|
616
915
|
**DropdownMenuRadioGroup**
|
|
617
|
-
-
|
|
916
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
618
917
|
|
|
619
918
|
**DropdownMenuSub**
|
|
620
|
-
-
|
|
919
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
621
920
|
|
|
622
921
|
### Input
|
|
623
922
|
|
|
624
|
-
|
|
923
|
+
Source: `src/primitives/input.tsx`
|
|
625
924
|
|
|
626
|
-
-
|
|
925
|
+
- Extends: `ComponentPropsWithoutRef<'input'>`
|
|
627
926
|
|
|
628
|
-
| prop |
|
|
927
|
+
| prop | type | req. | default | what it does |
|
|
629
928
|
| --- | --- | --- | --- | --- |
|
|
630
|
-
| `invalid` | `boolean` | | `false` |
|
|
929
|
+
| `invalid` | `boolean` | | `false` | Marks the control as invalid and tints the border. |
|
|
631
930
|
|
|
632
931
|
### Label
|
|
633
932
|
|
|
634
|
-
|
|
933
|
+
Source: `src/primitives/label.tsx`
|
|
635
934
|
|
|
636
|
-
|
|
935
|
+
The `label` scale: 13px, which is the system's absolute minimum on screen.
|
|
637
936
|
|
|
638
|
-
-
|
|
639
|
-
-
|
|
937
|
+
- Extends: `ComponentPropsWithoutRef<typeof LabelPrimitive.Root>`
|
|
938
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
640
939
|
|
|
641
940
|
### Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationPrevious, PaginationNext, PaginationEllipsis
|
|
642
941
|
|
|
643
|
-
|
|
942
|
+
Source: `src/primitives/pagination.tsx`
|
|
644
943
|
|
|
645
944
|
**Pagination**
|
|
646
|
-
-
|
|
945
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
647
946
|
|
|
648
947
|
**PaginationContent**
|
|
649
|
-
-
|
|
948
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
650
949
|
|
|
651
950
|
**PaginationItem**
|
|
652
|
-
-
|
|
951
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
653
952
|
|
|
654
953
|
**PaginationLink**
|
|
655
|
-
-
|
|
954
|
+
- Extends: `ComponentPropsWithoutRef<'a'>`
|
|
656
955
|
|
|
657
|
-
| prop |
|
|
956
|
+
| prop | type | req. | default | what it does |
|
|
658
957
|
| --- | --- | --- | --- | --- |
|
|
659
958
|
| `isActive` | `boolean` | | `false` | |
|
|
660
959
|
|
|
661
960
|
**PaginationPrevious**
|
|
662
961
|
|
|
663
|
-
| prop |
|
|
962
|
+
| prop | type | req. | default | what it does |
|
|
664
963
|
| --- | --- | --- | --- | --- |
|
|
665
964
|
| `isActive` | `boolean` | | | |
|
|
666
965
|
|
|
667
966
|
**PaginationNext**
|
|
668
967
|
|
|
669
|
-
| prop |
|
|
968
|
+
| prop | type | req. | default | what it does |
|
|
670
969
|
| --- | --- | --- | --- | --- |
|
|
671
970
|
| `isActive` | `boolean` | | | |
|
|
672
971
|
|
|
673
972
|
**PaginationEllipsis**
|
|
674
|
-
-
|
|
973
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
675
974
|
|
|
676
975
|
### PopoverContent, Popover, PopoverTrigger, PopoverAnchor
|
|
677
976
|
|
|
678
|
-
|
|
977
|
+
Source: `src/primitives/popover.tsx`
|
|
679
978
|
|
|
680
979
|
**PopoverContent**
|
|
681
|
-
|
|
980
|
+
No entrance animation: it appears where it will stay, like the rest.
|
|
682
981
|
|
|
683
|
-
-
|
|
684
|
-
-
|
|
982
|
+
- Extends: `ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & Labelled`
|
|
983
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
685
984
|
|
|
686
985
|
**Popover**
|
|
687
|
-
-
|
|
986
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
688
987
|
|
|
689
988
|
**PopoverTrigger**
|
|
690
|
-
-
|
|
989
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
691
990
|
|
|
692
991
|
**PopoverAnchor**
|
|
693
|
-
-
|
|
992
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
694
993
|
|
|
695
994
|
### Progress
|
|
696
995
|
|
|
697
|
-
|
|
996
|
+
Source: `src/primitives/progress.tsx`
|
|
698
997
|
|
|
699
|
-
|
|
998
|
+
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
999
|
|
|
701
|
-
-
|
|
1000
|
+
- Extends: `ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>`
|
|
702
1001
|
|
|
703
|
-
| prop |
|
|
1002
|
+
| prop | type | req. | default | what it does |
|
|
704
1003
|
| --- | --- | --- | --- | --- |
|
|
705
|
-
| `label` | `string` |
|
|
706
|
-
| `tone` | `"accent" \| "warm"` | | `accent` |
|
|
1004
|
+
| `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. |
|
|
1005
|
+
| `tone` | `"accent" \| "warm"` | | `accent` | Sand instead of biolume, for course progress. |
|
|
707
1006
|
|
|
708
1007
|
### RadioGroup, RadioGroupItem
|
|
709
1008
|
|
|
710
|
-
|
|
1009
|
+
Source: `src/primitives/radio-group.tsx`
|
|
711
1010
|
|
|
712
1011
|
**RadioGroup**
|
|
713
|
-
-
|
|
714
|
-
-
|
|
1012
|
+
- Extends: `ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>`
|
|
1013
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
715
1014
|
|
|
716
1015
|
**RadioGroupItem**
|
|
717
|
-
-
|
|
718
|
-
-
|
|
1016
|
+
- Extends: `ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>`
|
|
1017
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
719
1018
|
|
|
720
1019
|
### SelectTrigger, SelectContent, SelectLabel, SelectItem, SelectSeparator, Select, SelectGroup, SelectValue
|
|
721
1020
|
|
|
722
|
-
|
|
1021
|
+
Source: `src/primitives/select.tsx`
|
|
723
1022
|
|
|
724
1023
|
**SelectTrigger**
|
|
725
|
-
-
|
|
1024
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
726
1025
|
|
|
727
1026
|
**SelectContent**
|
|
728
|
-
|
|
1027
|
+
No entrance animation: the menu appears, it does not unfold.
|
|
729
1028
|
|
|
730
|
-
-
|
|
1029
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
731
1030
|
|
|
732
1031
|
**SelectLabel**
|
|
733
|
-
-
|
|
1032
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
734
1033
|
|
|
735
1034
|
**SelectItem**
|
|
736
|
-
-
|
|
1035
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
737
1036
|
|
|
738
1037
|
**SelectSeparator**
|
|
739
|
-
-
|
|
1038
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
740
1039
|
|
|
741
1040
|
**Select**
|
|
742
|
-
-
|
|
1041
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
743
1042
|
|
|
744
1043
|
**SelectGroup**
|
|
745
|
-
-
|
|
1044
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
746
1045
|
|
|
747
1046
|
**SelectValue**
|
|
748
|
-
-
|
|
1047
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
749
1048
|
|
|
750
1049
|
### Separator
|
|
751
1050
|
|
|
752
|
-
|
|
1051
|
+
Source: `src/primitives/separator.tsx`
|
|
753
1052
|
|
|
754
|
-
`hairline`,
|
|
1053
|
+
`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
1054
|
|
|
756
|
-
-
|
|
757
|
-
-
|
|
1055
|
+
- Extends: `ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>`
|
|
1056
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
758
1057
|
|
|
759
1058
|
### SheetContent, SheetHeader, SheetBody, SheetFooter, SheetTitle, SheetDescription, Sheet, SheetTrigger, SheetClose
|
|
760
1059
|
|
|
761
|
-
|
|
1060
|
+
Source: `src/primitives/sheet.tsx`
|
|
762
1061
|
|
|
763
1062
|
**SheetContent**
|
|
764
|
-
|
|
1063
|
+
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
1064
|
|
|
766
|
-
-
|
|
1065
|
+
- Extends: `ComponentPropsWithoutRef<typeof DialogPrimitive.Content> & VariantProps<typeof panel>`
|
|
767
1066
|
|
|
768
|
-
| prop |
|
|
1067
|
+
| prop | type | req. | default | what it does |
|
|
769
1068
|
| --- | --- | --- | --- | --- |
|
|
770
1069
|
| `side` | `"right" \| "left" \| "top" \| "bottom"` | | `right` | |
|
|
771
1070
|
|
|
772
1071
|
**SheetHeader**
|
|
773
|
-
-
|
|
1072
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
774
1073
|
|
|
775
1074
|
**SheetBody**
|
|
776
|
-
-
|
|
1075
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
777
1076
|
|
|
778
1077
|
**SheetFooter**
|
|
779
|
-
-
|
|
1078
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
780
1079
|
|
|
781
1080
|
**SheetTitle**
|
|
782
|
-
-
|
|
1081
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
783
1082
|
|
|
784
1083
|
**SheetDescription**
|
|
785
|
-
-
|
|
1084
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
786
1085
|
|
|
787
1086
|
**Sheet**
|
|
788
|
-
-
|
|
1087
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
789
1088
|
|
|
790
1089
|
**SheetTrigger**
|
|
791
|
-
-
|
|
1090
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
792
1091
|
|
|
793
1092
|
**SheetClose**
|
|
794
|
-
-
|
|
1093
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
795
1094
|
|
|
796
1095
|
### Skeleton
|
|
797
1096
|
|
|
798
|
-
|
|
1097
|
+
Source: `src/primitives/skeleton.tsx`
|
|
799
1098
|
|
|
800
|
-
|
|
1099
|
+
A 1.4s linear sweep, from the document.
|
|
801
1100
|
|
|
802
|
-
-
|
|
1101
|
+
- Extends: `ComponentPropsWithoutRef<'div'>`
|
|
803
1102
|
|
|
804
|
-
| prop |
|
|
1103
|
+
| prop | type | req. | default | what it does |
|
|
805
1104
|
| --- | --- | --- | --- | --- |
|
|
806
|
-
| `still` | `boolean \| undefined` | | `false` |
|
|
1105
|
+
| `still` | `boolean \| undefined` | | `false` | Turns the sweep off. For long lists, where many at once are dizzying. |
|
|
807
1106
|
|
|
808
1107
|
### Switch
|
|
809
1108
|
|
|
810
|
-
|
|
1109
|
+
Source: `src/primitives/switch.tsx`
|
|
811
1110
|
|
|
812
|
-
|
|
1111
|
+
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
1112
|
|
|
814
|
-
-
|
|
815
|
-
-
|
|
1113
|
+
- Extends: `ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>`
|
|
1114
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
816
1115
|
|
|
817
1116
|
### Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption
|
|
818
1117
|
|
|
819
|
-
|
|
1118
|
+
Source: `src/primitives/table.tsx`
|
|
820
1119
|
|
|
821
1120
|
**Table**
|
|
822
|
-
|
|
1121
|
+
The container scrolls horizontally: the page never does.
|
|
823
1122
|
|
|
824
|
-
-
|
|
1123
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
825
1124
|
|
|
826
1125
|
**TableHeader**
|
|
827
|
-
-
|
|
1126
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
828
1127
|
|
|
829
1128
|
**TableBody**
|
|
830
|
-
-
|
|
1129
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
831
1130
|
|
|
832
1131
|
**TableFooter**
|
|
833
|
-
-
|
|
1132
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
834
1133
|
|
|
835
1134
|
**TableRow**
|
|
836
|
-
-
|
|
1135
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
837
1136
|
|
|
838
1137
|
**TableHead**
|
|
839
|
-
-
|
|
1138
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
840
1139
|
|
|
841
1140
|
**TableCell**
|
|
842
|
-
-
|
|
1141
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
843
1142
|
|
|
844
1143
|
**TableCaption**
|
|
845
|
-
-
|
|
1144
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
846
1145
|
|
|
847
1146
|
### Tabs, TabsList, TabsTrigger, TabsContent
|
|
848
1147
|
|
|
849
|
-
|
|
1148
|
+
Source: `src/primitives/tabs.tsx`
|
|
850
1149
|
|
|
851
1150
|
**Tabs**
|
|
852
|
-
-
|
|
853
|
-
-
|
|
1151
|
+
- Extends: `ComponentPropsWithoutRef<typeof TabsPrimitive.Root>`
|
|
1152
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
854
1153
|
|
|
855
1154
|
**TabsList**
|
|
856
|
-
-
|
|
1155
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
857
1156
|
|
|
858
1157
|
**TabsTrigger**
|
|
859
|
-
-
|
|
1158
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
860
1159
|
|
|
861
1160
|
**TabsContent**
|
|
862
|
-
-
|
|
1161
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
863
1162
|
|
|
864
1163
|
### Textarea
|
|
865
1164
|
|
|
866
|
-
|
|
1165
|
+
Source: `src/primitives/textarea.tsx`
|
|
867
1166
|
|
|
868
|
-
-
|
|
1167
|
+
- Extends: `ComponentPropsWithoutRef<'textarea'>`
|
|
869
1168
|
|
|
870
|
-
| prop |
|
|
1169
|
+
| prop | type | req. | default | what it does |
|
|
871
1170
|
| --- | --- | --- | --- | --- |
|
|
872
1171
|
| `invalid` | `boolean` | | `false` | |
|
|
873
1172
|
|
|
874
1173
|
### Toaster
|
|
875
1174
|
|
|
876
|
-
|
|
1175
|
+
Source: `src/primitives/toaster.tsx`
|
|
877
1176
|
|
|
878
|
-
|
|
1177
|
+
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
1178
|
|
|
880
|
-
-
|
|
1179
|
+
- 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
1180
|
|
|
882
|
-
| prop |
|
|
1181
|
+
| prop | type | req. | default | what it does |
|
|
883
1182
|
| --- | --- | --- | --- | --- |
|
|
884
|
-
| `duration` | `number` | | `5000` |
|
|
885
|
-
| `label` | `string` | | `Avisos` |
|
|
1183
|
+
| `duration` | `number` | | `5000` | How long a notice lasts when it does not say otherwise. |
|
|
1184
|
+
| `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
1185
|
|
|
887
1186
|
### TooltipContent, TooltipProvider, Tooltip, TooltipTrigger
|
|
888
1187
|
|
|
889
|
-
|
|
1188
|
+
Source: `src/primitives/tooltip.tsx`
|
|
890
1189
|
|
|
891
1190
|
**TooltipContent**
|
|
892
|
-
-
|
|
1191
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
893
1192
|
|
|
894
1193
|
**TooltipProvider**
|
|
895
|
-
-
|
|
1194
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
896
1195
|
|
|
897
1196
|
**Tooltip**
|
|
898
|
-
-
|
|
1197
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
899
1198
|
|
|
900
1199
|
**TooltipTrigger**
|
|
901
|
-
-
|
|
1200
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
902
1201
|
|
|
903
1202
|
### Text
|
|
904
1203
|
|
|
905
|
-
|
|
1204
|
+
Source: `src/primitives/typography.tsx`
|
|
906
1205
|
|
|
907
|
-
-
|
|
1206
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'p'>, 'color'> & VariantProps<typeof text>`
|
|
908
1207
|
|
|
909
|
-
| prop |
|
|
1208
|
+
| prop | type | req. | default | what it does |
|
|
910
1209
|
| --- | --- | --- | --- | --- |
|
|
911
|
-
| `as` | `"h2" \| "h3" \| "li" \| "p" \| "span" \| "caption" \| "dd" \| "dt" \| "em" \| "figcaption" \| "
|
|
912
|
-
| `asChild` | `boolean` | | `false` |
|
|
913
|
-
| `measure` | `boolean` | | |
|
|
914
|
-
| `tone` | `"
|
|
915
|
-
| `variant` | `"display" \| "
|
|
1210
|
+
| `as` | `"h1" \| "h2" \| "h3" \| "strong" \| "li" \| "p" \| "span" \| "caption" \| "dd" \| "dt" \| "em" \| "figcaption" \| "h4" \| "legend"` | | | HTML tag. Defaults to whichever one matches the scale. |
|
|
1211
|
+
| `asChild` | `boolean` | | `false` | Renders the child instead of creating an element, to wrap a link. |
|
|
1212
|
+
| `measure` | `boolean` | | | Clamps the line to 68ch. On by default for `body`, the only scale meant to be read in long paragraphs. |
|
|
1213
|
+
| `tone` | `"primary" \| "secondary" \| "accent" \| "warm" \| "success" \| "warning" \| "error" \| "muted"` | | | |
|
|
1214
|
+
| `variant` | `"display" \| "stat" \| "h1" \| "h2" \| "h3" \| "body" \| "lead" \| "ui" \| "label" \| "tag" \| "chip" \| "meta" \| "eyebrow"` | | | |
|
|
916
1215
|
|
|
917
|
-
##
|
|
1216
|
+
## Components
|
|
918
1217
|
|
|
919
|
-
|
|
1218
|
+
Imported from `@eduardoalvarez/arrecife`. 25 exports.
|
|
920
1219
|
|
|
921
1220
|
### ArticleCard
|
|
922
1221
|
|
|
923
|
-
|
|
1222
|
+
Source: `src/components/article-card/index.tsx`
|
|
924
1223
|
|
|
925
|
-
|
|
1224
|
+
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
1225
|
|
|
927
|
-
-
|
|
1226
|
+
- Extends: `Omit<CardShellProps, 'children' \| 'title'>`
|
|
928
1227
|
|
|
929
|
-
| prop |
|
|
1228
|
+
| prop | type | req. | default | what it does |
|
|
930
1229
|
| --- | --- | --- | --- | --- |
|
|
931
|
-
| `asChild` | `boolean \| undefined` | | |
|
|
932
|
-
| `date` | `ReactNode` | | |
|
|
933
|
-
| `dateTime` | `string` | | |
|
|
934
|
-
| `excerpt` | `ReactNode` | | |
|
|
935
|
-
| `headingLevel` | `2 \| 3` | | `3` |
|
|
1230
|
+
| `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. |
|
|
1231
|
+
| `date` | `ReactNode` | | | Date already formatted by the project: the library imposes no locale. |
|
|
1232
|
+
| `dateTime` | `string` | | | The `<time>` element's `datetime` value, in ISO. |
|
|
1233
|
+
| `excerpt` | `ReactNode` | | | Standfirst. Clamped to two lines so the grid does not fall out of line. |
|
|
1234
|
+
| `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
1235
|
| `readingMinutes` | `number` | | | |
|
|
1236
|
+
| `tagAsChild` | `(props: { tag: string; children: ReactNode; }) => ReactNode` | | | Renders each tag through the child, so an E2E suite can reach it. |
|
|
937
1237
|
| `tags` | `readonly string[]` | | | |
|
|
938
|
-
| `title` | `ReactNode` |
|
|
1238
|
+
| `title` | `ReactNode` | yes | | |
|
|
939
1239
|
|
|
940
1240
|
### AudioPlayer
|
|
941
1241
|
|
|
942
|
-
|
|
1242
|
+
Source: `src/components/audio-player/index.tsx`
|
|
943
1243
|
|
|
944
|
-
-
|
|
1244
|
+
- 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
1245
|
|
|
946
|
-
| prop |
|
|
1246
|
+
| prop | type | req. | default | what it does |
|
|
947
1247
|
| --- | --- | --- | --- | --- |
|
|
948
|
-
| `mode` | `"banner" \| "full" \| "compact"` | | `full` | `full`
|
|
949
|
-
| `onFirstPlay` | `(title?: string \| undefined) => void` | | |
|
|
950
|
-
| `src` | `string` |
|
|
1248
|
+
| `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. |
|
|
1249
|
+
| `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. |
|
|
1250
|
+
| `src` | `string` | yes | | |
|
|
951
1251
|
| `title` | `string` | | | |
|
|
952
1252
|
|
|
953
1253
|
### AuthorCard
|
|
954
1254
|
|
|
955
|
-
|
|
1255
|
+
Source: `src/components/author-card/index.tsx`
|
|
956
1256
|
|
|
957
|
-
|
|
1257
|
+
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
1258
|
|
|
959
|
-
-
|
|
1259
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'role'>`
|
|
960
1260
|
|
|
961
|
-
| prop |
|
|
1261
|
+
| prop | type | req. | default | what it does |
|
|
962
1262
|
| --- | --- | --- | --- | --- |
|
|
963
|
-
| `action` | `ReactNode` | | |
|
|
964
|
-
| `bio` | `ReactNode` | | |
|
|
965
|
-
| `name` | `string` |
|
|
966
|
-
| `role` | `ReactNode` | | |
|
|
967
|
-
| `src` | `string` | | |
|
|
1263
|
+
| `action` | `ReactNode` | | | Links or a contact button. |
|
|
1264
|
+
| `bio` | `ReactNode` | | | One or two sentences. It clamps itself to 68ch. |
|
|
1265
|
+
| `name` | `string` | yes | | |
|
|
1266
|
+
| `role` | `ReactNode` | | | The role. It goes in mono: it is a datum, not a sentence. |
|
|
1267
|
+
| `src` | `string` | | | The avatar's URL. Without it the initials are shown. |
|
|
968
1268
|
|
|
969
1269
|
### Blockquote
|
|
970
1270
|
|
|
971
|
-
|
|
1271
|
+
Source: `src/components/blockquote/index.tsx`
|
|
972
1272
|
|
|
973
|
-
|
|
1273
|
+
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
1274
|
|
|
975
|
-
-
|
|
1275
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'blockquote'>, 'cite'>`
|
|
976
1276
|
|
|
977
|
-
| prop |
|
|
1277
|
+
| prop | type | req. | default | what it does |
|
|
978
1278
|
| --- | --- | --- | --- | --- |
|
|
979
|
-
| `author` | `ReactNode` | | |
|
|
980
|
-
| `source` | `ReactNode` | | |
|
|
1279
|
+
| `author` | `ReactNode` | | | Who said it. Marked up as `<cite>`. |
|
|
1280
|
+
| `source` | `ReactNode` | | | Where they said it: a talk, an article, a conversation. |
|
|
981
1281
|
|
|
982
1282
|
### Breadcrumb
|
|
983
1283
|
|
|
984
|
-
|
|
1284
|
+
Source: `src/components/breadcrumb/index.tsx`
|
|
985
1285
|
|
|
986
|
-
-
|
|
1286
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'nav'>, 'children'>`
|
|
987
1287
|
|
|
988
|
-
| prop |
|
|
1288
|
+
| prop | type | req. | default | what it does |
|
|
989
1289
|
| --- | --- | --- | --- | --- |
|
|
990
|
-
| `homeHref` | `string` | | `/` |
|
|
991
|
-
| `homeLabel` | `string` | | `Inicio` |
|
|
992
|
-
| `items` | `readonly
|
|
993
|
-
| `linkAsChild` | `(props: { href: string; children: ReactNode; }) => ReactNode` | | |
|
|
1290
|
+
| `homeHref` | `string` | | `/` | Where the `~` goes. The site root by default. |
|
|
1291
|
+
| `homeLabel` | `string` | | `Inicio` | Accessible label for the `~`, which otherwise reads as a stray tilde. |
|
|
1292
|
+
| `items` | `readonly Crumb[]` | yes | | |
|
|
1293
|
+
| `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
1294
|
|
|
995
1295
|
### CodeBlock
|
|
996
1296
|
|
|
997
|
-
|
|
1297
|
+
Source: `src/components/code-block/index.tsx`
|
|
998
1298
|
|
|
999
|
-
`brand.hull`
|
|
1299
|
+
`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
1300
|
|
|
1001
|
-
-
|
|
1301
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'children'>`
|
|
1002
1302
|
|
|
1003
|
-
| prop |
|
|
1303
|
+
| prop | type | req. | default | what it does |
|
|
1004
1304
|
| --- | --- | --- | --- | --- |
|
|
1005
|
-
| `children` | `ReactNode` |
|
|
1006
|
-
| `copyText` | `string` | | |
|
|
1007
|
-
| `language` | `string` | | |
|
|
1305
|
+
| `children` | `ReactNode` | yes | | The already-highlighted code, or flat text. |
|
|
1306
|
+
| `copyText` | `string` | | | The text copied to the clipboard. Without it, the button is not shown. |
|
|
1307
|
+
| `language` | `string` | | | The language label. Shown in the top bar. |
|
|
1008
1308
|
|
|
1009
1309
|
### CourseCard
|
|
1010
1310
|
|
|
1011
|
-
|
|
1311
|
+
Source: `src/components/course-card/index.tsx`
|
|
1012
1312
|
|
|
1013
|
-
-
|
|
1313
|
+
- Extends: `Omit<CardShellProps, 'children' \| 'title'>`
|
|
1014
1314
|
|
|
1015
|
-
| prop |
|
|
1315
|
+
| prop | type | req. | default | what it does |
|
|
1016
1316
|
| --- | --- | --- | --- | --- |
|
|
1017
|
-
| `asChild` | `boolean \| undefined` | | |
|
|
1018
|
-
| `meta` | `readonly ReactNode[]` | | |
|
|
1019
|
-
| `progress` | `number` | | |
|
|
1020
|
-
| `status` | `ReactNode` | | |
|
|
1317
|
+
| `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. |
|
|
1318
|
+
| `meta` | `readonly ReactNode[]` | | | Level, duration, number of lessons: whatever the project wants to list. |
|
|
1319
|
+
| `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. |
|
|
1320
|
+
| `status` | `ReactNode` | | | Status label: «próximamente», «gratis», «nuevo». |
|
|
1021
1321
|
| `summary` | `ReactNode` | | | |
|
|
1022
|
-
| `title` | `ReactNode` |
|
|
1322
|
+
| `title` | `ReactNode` | yes | | |
|
|
1023
1323
|
|
|
1024
1324
|
### EmptyState
|
|
1025
1325
|
|
|
1026
|
-
|
|
1326
|
+
Source: `src/components/empty-state/index.tsx`
|
|
1027
1327
|
|
|
1028
|
-
|
|
1328
|
+
- Extends: `EmptyStateBase & ( \| { /** `page`, the default: the empty state IS the screen or the section, and it carries the face. */ variant?: 'page' \| undefined; /** * The face. Mandatory on `page` and impossible on `inline` — the props * are a union, so the generated table cannot show a per-variant «req.» * and the sentence has to carry it. Without it, `page` is a centred * paragraph. */ expression: Face; /** Where the brand PNGs are served from. */ basePath?: string \| undefined; icon?: never; } \| { /** `inline`: the hole inside a table or a widget. No face, and no way to pass one. */ variant: 'inline'; /** * A glyph above the line. It measures 1em and inherits `currentColor`, * like `Stat`'s: the project passes its own and sizes it, because the * system has no icon library and is not getting one. */ icon?: ReactNode; expression?: never; basePath?: never; } )`
|
|
1029
1329
|
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
| prop | tipo | req. | defecto | qué hace |
|
|
1330
|
+
| prop | type | req. | default | what it does |
|
|
1033
1331
|
| --- | --- | --- | --- | --- |
|
|
1034
|
-
| `action` | `ReactNode` | | |
|
|
1035
|
-
| `basePath` | `string` | | |
|
|
1036
|
-
| `description` | `ReactNode` | | |
|
|
1037
|
-
| `
|
|
1038
|
-
| `
|
|
1332
|
+
| `action` | `ReactNode` | | | The action that gets you out of the empty state. Usually a tertiary button. |
|
|
1333
|
+
| `basePath` | `string` | | | Where the brand PNGs are served from. |
|
|
1334
|
+
| `description` | `ReactNode` | | | One line explaining what is missing or what to do. |
|
|
1335
|
+
| `expression` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | | | The face. Mandatory on `page` and impossible on `inline` — the props are a union, so the generated table cannot show a per-variant «req.» and the sentence has to carry it. Without it, `page` is a centred paragraph. |
|
|
1336
|
+
| `icon` | `ReactNode` | | | A glyph above the line. It measures 1em and inherits `currentColor`, like `Stat`'s: the project passes its own and sizes it, because the system has no icon library and is not getting one. |
|
|
1337
|
+
| `title` | `ReactNode` | yes | | |
|
|
1338
|
+
| `variant` | `"inline" \| "page"` | | | `page`, the default: the empty state IS the screen or the section, and it carries the face. `inline`: the hole inside a table or a widget. No face, and no way to pass one. |
|
|
1039
1339
|
|
|
1040
1340
|
### EventCalendar
|
|
1041
1341
|
|
|
1042
|
-
|
|
1342
|
+
Source: `src/components/event-calendar/index.tsx`
|
|
1043
1343
|
|
|
1044
|
-
-
|
|
1344
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' \| 'children'>`
|
|
1045
1345
|
|
|
1046
|
-
| prop |
|
|
1346
|
+
| prop | type | req. | default | what it does |
|
|
1047
1347
|
| --- | --- | --- | --- | --- |
|
|
1048
|
-
| `emptyMessage` | `ReactNode` | | `Nada en este día.` |
|
|
1049
|
-
| `events` | `readonly
|
|
1050
|
-
| `formatDay` | `(
|
|
1051
|
-
| `formatTime` | `(
|
|
1052
|
-
| `onCreateEvent` | `(
|
|
1348
|
+
| `emptyMessage` | `ReactNode` | | `Nada en este día.` | The panel's text when the chosen day has nothing on it. |
|
|
1349
|
+
| `events` | `readonly CalendarEvent[]` | yes | | |
|
|
1350
|
+
| `formatDay` | `(day: Date) => string` | | `(day) => format(day, "EEEE d de MMMM", { locale: es })` | The panel's heading. Defaults to date-fns' `es`, like `Calendar`. |
|
|
1351
|
+
| `formatTime` | `(date: Date) => string` | | `(date) => format(date, HH:mm, { locale: es })` | |
|
|
1352
|
+
| `onCreateEvent` | `(event: Omit<CalendarEvent, "id">) => void` | | | Without it, the schedule is read-only and the form is not painted. |
|
|
1053
1353
|
| `onDeleteEvent` | `(id: string) => void` | | | |
|
|
1054
|
-
| `onSelectDay` | `(
|
|
1055
|
-
| `onUpdateEvent` | `(
|
|
1056
|
-
| `selected` | `Date` | | |
|
|
1354
|
+
| `onSelectDay` | `(day: Date) => void` | | | |
|
|
1355
|
+
| `onUpdateEvent` | `(event: CalendarEvent) => void` | | | |
|
|
1356
|
+
| `selected` | `Date` | | | Selected day, if the project controls it. Without it, it starts on today. |
|
|
1057
1357
|
|
|
1058
1358
|
### Footer, FooterLink
|
|
1059
1359
|
|
|
1060
|
-
|
|
1360
|
+
Source: `src/components/footer/index.tsx`
|
|
1061
1361
|
|
|
1062
1362
|
**Footer**
|
|
1063
|
-
-
|
|
1363
|
+
- Extends: `ComponentPropsWithoutRef<'footer'>`
|
|
1064
1364
|
|
|
1065
|
-
| prop |
|
|
1365
|
+
| prop | type | req. | default | what it does |
|
|
1066
1366
|
| --- | --- | --- | --- | --- |
|
|
1067
|
-
| `brand` | `ReactNode` | | |
|
|
1068
|
-
| `social` | `readonly
|
|
1069
|
-
| `year` | `number` | | `new Date().getFullYear()` |
|
|
1367
|
+
| `brand` | `ReactNode` | | | The brand row: the fin and the wordmark, at the very top. |
|
|
1368
|
+
| `social` | `readonly SocialLink[]` | | | |
|
|
1369
|
+
| `year` | `number` | | `new Date().getFullYear()` | The signature's year. |
|
|
1070
1370
|
|
|
1071
1371
|
**FooterLink**
|
|
1072
|
-
-
|
|
1372
|
+
- Extends: `ComponentPropsWithoutRef<'a'>`
|
|
1073
1373
|
|
|
1074
|
-
| prop |
|
|
1374
|
+
| prop | type | req. | default | what it does |
|
|
1075
1375
|
| --- | --- | --- | --- | --- |
|
|
1076
1376
|
| `asChild` | `boolean \| undefined` | | `false` | |
|
|
1077
1377
|
|
|
1078
1378
|
### Hero
|
|
1079
1379
|
|
|
1080
|
-
|
|
1380
|
+
Source: `src/components/hero/index.tsx`
|
|
1081
1381
|
|
|
1082
|
-
|
|
1382
|
+
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
1383
|
|
|
1084
|
-
-
|
|
1384
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'section'>, 'title'>`
|
|
1085
1385
|
|
|
1086
|
-
| prop |
|
|
1386
|
+
| prop | type | req. | default | what it does |
|
|
1087
1387
|
| --- | --- | --- | --- | --- |
|
|
1088
|
-
| `action` | `ReactNode` | | |
|
|
1388
|
+
| `action` | `ReactNode` | | | The buttons. The screen's only `conversion` goes here. |
|
|
1089
1389
|
| `basePath` | `string` | | | |
|
|
1090
1390
|
| `description` | `ReactNode` | | | |
|
|
1091
|
-
| `eyebrow` | `ReactNode` | | | Mono,
|
|
1092
|
-
| `pose` | `"desk" \| "laptop-coffee" \| "peek" \| "surf"` | | |
|
|
1093
|
-
| `title` | `ReactNode` |
|
|
1094
|
-
| `variant` | `"
|
|
1391
|
+
| `eyebrow` | `ReactNode` | | | Mono, small caps, in accent. |
|
|
1392
|
+
| `pose` | `"desk" \| "laptop-coffee" \| "peek" \| "surf"` | | | Tiburoncín's pose. Without it the hero is a panel with text. |
|
|
1393
|
+
| `title` | `ReactNode` | yes | | |
|
|
1394
|
+
| `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
1395
|
|
|
1096
1396
|
### LinkRow
|
|
1097
1397
|
|
|
1098
|
-
|
|
1398
|
+
Source: `src/components/link-row/index.tsx`
|
|
1099
1399
|
|
|
1100
|
-
|
|
1400
|
+
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
1401
|
|
|
1102
|
-
-
|
|
1402
|
+
- Extends: `Omit<CardShellProps, 'children'>`
|
|
1103
1403
|
|
|
1104
|
-
| prop |
|
|
1404
|
+
| prop | type | req. | default | what it does |
|
|
1105
1405
|
| --- | --- | --- | --- | --- |
|
|
1106
|
-
| `asChild` | `boolean \| undefined` | | |
|
|
1406
|
+
| `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
1407
|
| `description` | `ReactNode` | | | |
|
|
1108
|
-
| `external` | `boolean \| undefined` | | `false` |
|
|
1109
|
-
| `icon` | `ReactNode` | | |
|
|
1110
|
-
| `name` | `ReactNode` |
|
|
1408
|
+
| `external` | `boolean \| undefined` | | `false` | Marks the link as external: adds the arrow and the safe `rel`. |
|
|
1409
|
+
| `icon` | `ReactNode` | | | The target's SVG glyph. Never an emoji. |
|
|
1410
|
+
| `name` | `ReactNode` | yes | | |
|
|
1111
1411
|
|
|
1112
1412
|
### Nav, NavItem
|
|
1113
1413
|
|
|
1114
|
-
|
|
1414
|
+
Source: `src/components/nav/index.tsx`
|
|
1115
1415
|
|
|
1116
1416
|
**Nav**
|
|
1117
|
-
|
|
1417
|
+
The site bar: 64px, abyss at 86 % and a 14px blur behind it — 56 when it shares the screen with a sidebar.
|
|
1118
1418
|
|
|
1119
|
-
-
|
|
1419
|
+
- Extends: `ComponentPropsWithoutRef<'header'>`
|
|
1120
1420
|
|
|
1121
|
-
| prop |
|
|
1421
|
+
| prop | type | req. | default | what it does |
|
|
1122
1422
|
| --- | --- | --- | --- | --- |
|
|
1123
|
-
| `actions` | `ReactNode` | | |
|
|
1124
|
-
| `brand` | `ReactNode` | | |
|
|
1423
|
+
| `actions` | `ReactNode` | | | Actions on the right: conversion, theme switch, search. |
|
|
1424
|
+
| `brand` | `ReactNode` | | | The logo, on the left. |
|
|
1425
|
+
| `size` | `"compact" \| "default"` | | `default` | `compact` is 56px instead of 64, for a bar that shares the screen with a sidebar: at 64 the two compete for the same corner and together they eat the top of the content area. |
|
|
1125
1426
|
|
|
1126
1427
|
**NavItem**
|
|
1127
|
-
|
|
1428
|
+
The `./` is put there by the component, not by whoever uses it.
|
|
1128
1429
|
|
|
1129
|
-
-
|
|
1430
|
+
- Extends: `ComponentPropsWithoutRef<'a'>`
|
|
1130
1431
|
|
|
1131
|
-
| prop |
|
|
1432
|
+
| prop | type | req. | default | what it does |
|
|
1132
1433
|
| --- | --- | --- | --- | --- |
|
|
1133
|
-
| `active` | `boolean \| undefined` | | `false` |
|
|
1134
|
-
| `asChild` | `boolean \| undefined` | | `false` |
|
|
1434
|
+
| `active` | `boolean \| undefined` | | `false` | Current section: biolume with a 1px underline. |
|
|
1435
|
+
| `asChild` | `boolean \| undefined` | | `false` | Renders the child instead of an `<a>`, for the router's `Link`. |
|
|
1135
1436
|
|
|
1136
1437
|
### NewsletterForm
|
|
1137
1438
|
|
|
1138
|
-
|
|
1439
|
+
Source: `src/components/newsletter-form/index.tsx`
|
|
1139
1440
|
|
|
1140
|
-
-
|
|
1441
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'section'>, 'title' \| 'onSubmit'>`
|
|
1141
1442
|
|
|
1142
|
-
| prop |
|
|
1443
|
+
| prop | type | req. | default | what it does |
|
|
1143
1444
|
| --- | --- | --- | --- | --- |
|
|
1445
|
+
| `aside` | `ReactNode` | | | The illustration, as a second column inside the panel. |
|
|
1144
1446
|
| `basePath` | `string` | | | |
|
|
1145
1447
|
| `description` | `ReactNode` | | | |
|
|
1146
|
-
| `disclaimer` | `ReactNode` | | |
|
|
1448
|
+
| `disclaimer` | `ReactNode` | | | The small print. It is the «sin spam», which is why it accepts a face. |
|
|
1147
1449
|
| `errorMessage` | `ReactNode` | | `No se pudo suscribir ese correo. Revísalo y vuelve a intentar.` | |
|
|
1148
|
-
| `
|
|
1450
|
+
| `expression` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | | | |
|
|
1451
|
+
| `fieldErrors` | `{ name?: ReactNode; email?: ReactNode; }` | | | A message under one specific field, instead of the single alert. |
|
|
1149
1452
|
| `fieldLabel` | `string` | | `Correo electrónico` | |
|
|
1150
|
-
| `nameField` | `boolean` | | `false` |
|
|
1151
|
-
| `nameInputProps` | `Omit<InputProps, "id" \| "disabled" \| "name">` | | |
|
|
1453
|
+
| `nameField` | `boolean` | | `false` | Adds the name field ahead of the email one. |
|
|
1454
|
+
| `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
1455
|
| `nameLabel` | `string` | | `Nombre` | |
|
|
1153
1456
|
| `namePlaceholder` | `string` | | `Cómo te llamas` | |
|
|
1154
|
-
| `
|
|
1155
|
-
| `
|
|
1156
|
-
| `
|
|
1457
|
+
| `onFieldChange` | `(field: "email" \| "name", value: string) => void` | | | Fires when either field changes. It is where the project clears its error. |
|
|
1458
|
+
| `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. |
|
|
1459
|
+
| `placeholder` | `string` | | `tu@email.dev` | |
|
|
1460
|
+
| `resetOnSuccess` | `boolean` | | `true` | Empties the fields after a successful subscription. On by default. |
|
|
1461
|
+
| `state` | `"success" \| "error" \| "idle" \| "sending"` | | `idle` | |
|
|
1157
1462
|
| `submitLabel` | `string` | | `Suscribirme` | |
|
|
1158
1463
|
| `successMessage` | `ReactNode` | | `Ya estás dentro. Te llega un correo cada dos semanas, y nada más.` | |
|
|
1159
|
-
| `title` | `ReactNode` |
|
|
1464
|
+
| `title` | `ReactNode` | yes | | |
|
|
1160
1465
|
|
|
1161
1466
|
### PageHeader
|
|
1162
1467
|
|
|
1163
|
-
|
|
1468
|
+
Source: `src/components/page-header/index.tsx`
|
|
1164
1469
|
|
|
1165
|
-
|
|
1470
|
+
One header at two scales, not two components.
|
|
1166
1471
|
|
|
1167
|
-
-
|
|
1472
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'header'>, 'title'> & VariantProps<typeof header>`
|
|
1168
1473
|
|
|
1169
|
-
| prop |
|
|
1474
|
+
| prop | type | req. | default | what it does |
|
|
1170
1475
|
| --- | --- | --- | --- | --- |
|
|
1171
|
-
| `action` | `ReactNode` | | |
|
|
1172
|
-
| `as` | `"
|
|
1476
|
+
| `action` | `ReactNode` | | | Slot for the calls to action. If a conversion button goes here, it is the only one on the screen. |
|
|
1477
|
+
| `as` | `"h1" \| "h2"` | | `h1` | The headline's level. `h1` unless the page already has one. |
|
|
1173
1478
|
| `description` | `ReactNode` | | | |
|
|
1174
|
-
| `eyebrow` | `ReactNode` | | | Mono,
|
|
1479
|
+
| `eyebrow` | `ReactNode` | | | Mono, small caps, in accent. It is the section the page belongs to. |
|
|
1175
1480
|
| `size` | `"display" \| "page"` | | `page` | |
|
|
1176
|
-
| `title` | `ReactNode` |
|
|
1481
|
+
| `title` | `ReactNode` | yes | | |
|
|
1177
1482
|
|
|
1178
1483
|
### ScrollingProgressBar
|
|
1179
1484
|
|
|
1180
|
-
|
|
1485
|
+
Source: `src/components/scrolling-progress-bar/index.tsx`
|
|
1181
1486
|
|
|
1182
|
-
|
|
1487
|
+
How much you have read. It is NOT `Progress` under another name.
|
|
1183
1488
|
|
|
1184
|
-
-
|
|
1489
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'children'>`
|
|
1185
1490
|
|
|
1186
|
-
| prop |
|
|
1491
|
+
| prop | type | req. | default | what it does |
|
|
1187
1492
|
| --- | --- | --- | --- | --- |
|
|
1188
|
-
| `sticky` | `boolean` | | `true` |
|
|
1189
|
-
| `target` | `RefObject<HTMLElement \| null>` | | |
|
|
1190
|
-
| `tone` | `"accent" \| "warm"` | | `accent` |
|
|
1493
|
+
| `sticky` | `boolean` | | `true` | Pins the bar to the top edge of the window. |
|
|
1494
|
+
| `target` | `RefObject<HTMLElement \| null>` | | | The element being measured. Without it, the whole document. |
|
|
1495
|
+
| `tone` | `"accent" \| "warm"` | | `accent` | Sand instead of biolume, to match course progress. |
|
|
1191
1496
|
|
|
1192
|
-
### SidebarItem, SidebarNav
|
|
1497
|
+
### SidebarItem, SidebarGroup, SidebarNav
|
|
1193
1498
|
|
|
1194
|
-
|
|
1499
|
+
Source: `src/components/sidebar-nav/index.tsx`
|
|
1195
1500
|
|
|
1196
1501
|
**SidebarItem**
|
|
1197
|
-
|
|
1502
|
+
The blog admin's sidebar.
|
|
1198
1503
|
|
|
1199
|
-
-
|
|
1504
|
+
- Extends: `ComponentPropsWithoutRef<'a'>`
|
|
1200
1505
|
|
|
1201
|
-
| prop |
|
|
1506
|
+
| prop | type | req. | default | what it does |
|
|
1202
1507
|
| --- | --- | --- | --- | --- |
|
|
1203
1508
|
| `active` | `boolean \| undefined` | | `false` | |
|
|
1204
1509
|
| `asChild` | `boolean \| undefined` | | `false` | |
|
|
1205
|
-
| `badge` | `ReactNode` | | |
|
|
1510
|
+
| `badge` | `ReactNode` | | | Counter on the right: pending drafts, unused media. |
|
|
1511
|
+
| `icon` | `ReactNode` | | | The section's glyph, on the left. It REPLACES the `▸` rather than joining it, and it inherits `currentColor`, so it follows the item's state without being tinted separately. |
|
|
1512
|
+
|
|
1513
|
+
**SidebarGroup**
|
|
1514
|
+
A labelled block of items — «Contenido», «Alumnos», «Ventas».
|
|
1515
|
+
|
|
1516
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'li'>, 'title'>`
|
|
1517
|
+
|
|
1518
|
+
| prop | type | req. | default | what it does |
|
|
1519
|
+
| --- | --- | --- | --- | --- |
|
|
1520
|
+
| `label` | `ReactNode` | yes | | The block's name. Sentence case, not a section title. |
|
|
1206
1521
|
|
|
1207
1522
|
**SidebarNav**
|
|
1208
|
-
-
|
|
1523
|
+
- Extends: `ComponentPropsWithoutRef<'nav'>`
|
|
1209
1524
|
|
|
1210
|
-
| prop |
|
|
1525
|
+
| prop | type | req. | default | what it does |
|
|
1211
1526
|
| --- | --- | --- | --- | --- |
|
|
1212
1527
|
| `branch` | `ReactNode` | | | |
|
|
1213
|
-
| `
|
|
1528
|
+
| `brand` | `ReactNode` | | | The row at the top: isotype and wordmark, `cursos · admin`. It is a slot and not a `logo`/`name` pair because every panel spells its own name differently, and the part that IS the system — the rhythm, the hairline under it — is here. |
|
|
1529
|
+
| `collapsed` | `boolean \| undefined` | | `false` | Turns the sidebar into a rail: icons only, and the widths become the library's — `w-sidebar` and `w-sidebar-rail`. It is CONTROLLED and there is no uncontrolled mode, because this state is almost always persisted in a cookie or in `localStorage`, and an internal state would fight the one the project already keeps. |
|
|
1530
|
+
| `collapseLabel` | `string` | | `Plegar el panel` | The toggle's accessible name, in the two directions. |
|
|
1531
|
+
| `expandLabel` | `string` | | `Desplegar el panel` | |
|
|
1532
|
+
| `mark` | `ReactNode` | | | What `brand` becomes in the rail. Usually the isotype with no wordmark. |
|
|
1533
|
+
| `onCollapsedChange` | `(collapsed: boolean) => void` | | | Called with what the state should become. With it, the toggle appears; with `collapsed` alone the sidebar is a rail with no way out of it, which is a legitimate layout and not an accident. |
|
|
1534
|
+
| `user` | `ReactNode` | | | Who is signed in, at the bottom above the version. A slot, because an avatar needs a session and a sign-out route and the library takes no project infrastructure — the same reason `Nav`'s user menu goes in `actions`. |
|
|
1535
|
+
| `version` | `ReactNode` | | | Version and branch, at the bottom. |
|
|
1214
1536
|
|
|
1215
1537
|
### Stat
|
|
1216
1538
|
|
|
1217
|
-
|
|
1539
|
+
Source: `src/components/stat/index.tsx`
|
|
1218
1540
|
|
|
1219
|
-
|
|
1541
|
+
A large metric: the number in the `stat` scale and its name underneath.
|
|
1220
1542
|
|
|
1221
|
-
-
|
|
1543
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'div'>, 'title'>`
|
|
1222
1544
|
|
|
1223
|
-
| prop |
|
|
1545
|
+
| prop | type | req. | default | what it does |
|
|
1224
1546
|
| --- | --- | --- | --- | --- |
|
|
1225
|
-
| `
|
|
1226
|
-
| `
|
|
1227
|
-
| `
|
|
1228
|
-
| `
|
|
1229
|
-
| `
|
|
1230
|
-
| `
|
|
1547
|
+
| `delta` | `StatDelta` | | | How the number moved since last time. |
|
|
1548
|
+
| `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. |
|
|
1549
|
+
| `icon` | `ReactNode` | | | Glyph in a tinted circle, in the corner opposite the title. At 1em, and it inherits `currentColor` from the badge, so it takes the tone without being tinted separately. |
|
|
1550
|
+
| `label` | `ReactNode` | yes | | What is being counted. It goes in mono small caps. |
|
|
1551
|
+
| `progress` | `number` | | | With `progress`, the metric reads as progress and adds the bar. |
|
|
1552
|
+
| `spark` | `ReactNode` | | | The number's shape over time, under it. A `ReactNode` and not a data prop: a sparkline needs a charting library, and this component lives in the barrel that four projects install. The one project that draws them passes its own, exactly like `icon`. |
|
|
1553
|
+
| `tone` | `"neutral" \| "alert" \| "achievement"` | | `neutral` | `alert` ONLY when the number is the problem, and `achievement` when it is the opposite — the diplomas issued, the modules finished. The two paint the same sand today and they are still two names: a system that names by meaning cannot make «this is bad» the only way to say «this stands out». See `docs/decisions.md` § 28. |
|
|
1554
|
+
| `value` | `ReactNode` | yes | | The number, already formatted. The library imposes no locale. |
|
|
1231
1555
|
|
|
1232
1556
|
### TalkCard
|
|
1233
1557
|
|
|
1234
|
-
|
|
1558
|
+
Source: `src/components/talk-card/index.tsx`
|
|
1559
|
+
|
|
1560
|
+
A talk has more than one destination, and that is what shapes this type.
|
|
1235
1561
|
|
|
1236
|
-
-
|
|
1562
|
+
- 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
1563
|
|
|
1238
|
-
| prop |
|
|
1564
|
+
| prop | type | req. | default | what it does |
|
|
1239
1565
|
| --- | --- | --- | --- | --- |
|
|
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
1566
|
| `date` | `ReactNode` | | | |
|
|
1242
1567
|
| `dateTime` | `string` | | | |
|
|
1243
|
-
| `description` | `ReactNode` | | |
|
|
1244
|
-
| `event` | `ReactNode` |
|
|
1568
|
+
| `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. |
|
|
1569
|
+
| `event` | `ReactNode` | yes | | Where it was given: the conference, the meetup, the team. |
|
|
1245
1570
|
| `location` | `ReactNode` | | | |
|
|
1246
|
-
| `
|
|
1247
|
-
| `
|
|
1571
|
+
| `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. |
|
|
1572
|
+
| `status` | `ReactNode` | | | Short status label: «con vídeo», «próxima», «solo audio». |
|
|
1573
|
+
| `title` | `ReactNode` | yes | | |
|
|
1248
1574
|
|
|
1249
1575
|
### ThemeToggle
|
|
1250
1576
|
|
|
1251
|
-
|
|
1577
|
+
Source: `src/components/theme-toggle/index.tsx`
|
|
1252
1578
|
|
|
1253
|
-
|
|
1579
|
+
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
1580
|
|
|
1255
|
-
-
|
|
1581
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'button'>, 'onClick'>`
|
|
1256
1582
|
|
|
1257
|
-
| prop |
|
|
1583
|
+
| prop | type | req. | default | what it does |
|
|
1258
1584
|
| --- | --- | --- | --- | --- |
|
|
1259
|
-
| `label` | `string` | | `Cambiar de tema` |
|
|
1260
|
-
| `onThemeChange` | `(
|
|
1261
|
-
| `size` | `"sm" \| "md" \| "lg" \| "icon"` | | `icon` | |
|
|
1262
|
-
| `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary"` | | `secondary` | |
|
|
1585
|
+
| `label` | `string` | | `Cambiar de tema` | Accessible name. The button has no visible text, so it is the only thing naming it. |
|
|
1586
|
+
| `onThemeChange` | `(theme: Theme) => void` | | | Fires with whichever theme ended up set, in case the project wants to record it. |
|
|
1587
|
+
| `size` | `"sm" \| "md" \| "lg" \| "icon" \| "icon-sm"` | | `icon` | |
|
|
1588
|
+
| `variant` | `"primary" \| "conversion" \| "secondary" \| "tertiary" \| "destructive" \| "destructiveOutline"` | | `secondary` | |
|
|
1263
1589
|
|
|
1264
1590
|
### TableOfContents
|
|
1265
1591
|
|
|
1266
|
-
|
|
1592
|
+
Source: `src/components/toc/index.tsx`
|
|
1267
1593
|
|
|
1268
|
-
-
|
|
1594
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'nav'>, 'children'>`
|
|
1269
1595
|
|
|
1270
|
-
| prop |
|
|
1596
|
+
| prop | type | req. | default | what it does |
|
|
1271
1597
|
| --- | --- | --- | --- | --- |
|
|
1272
|
-
| `activeHref` | `string` | | |
|
|
1273
|
-
| `items` | `readonly
|
|
1598
|
+
| `activeHref` | `string` | | | The anchor of the visible section. |
|
|
1599
|
+
| `items` | `readonly TocEntry[]` | yes | | |
|
|
1274
1600
|
| `linkAsChild` | `(props: { href: string; children: ReactNode; }) => ReactNode` | | | |
|
|
1275
1601
|
|
|
1276
|
-
##
|
|
1602
|
+
## Brand
|
|
1277
1603
|
|
|
1278
|
-
|
|
1604
|
+
Imported from `@eduardoalvarez/arrecife` or `@eduardoalvarez/arrecife/brand`. 4 exports.
|
|
1279
1605
|
|
|
1280
|
-
###
|
|
1606
|
+
### Isotype
|
|
1281
1607
|
|
|
1282
|
-
|
|
1608
|
+
Source: `src/brand/isotype.tsx`
|
|
1283
1609
|
|
|
1284
|
-
-
|
|
1610
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'img'>, 'src' \| 'alt'>`
|
|
1285
1611
|
|
|
1286
|
-
| prop |
|
|
1612
|
+
| prop | type | req. | default | what it does |
|
|
1287
1613
|
| --- | --- | --- | --- | --- |
|
|
1288
|
-
| `alt` | `string` | | |
|
|
1289
|
-
| `
|
|
1290
|
-
| `
|
|
1614
|
+
| `alt` | `string` | | | Alt text. Empty when the isotype accompanies text that already names it. |
|
|
1615
|
+
| `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. |
|
|
1616
|
+
| `basePath` | `string` | | `ASSETS_PATH` | |
|
|
1291
1617
|
|
|
1292
1618
|
### Logo
|
|
1293
1619
|
|
|
1294
|
-
|
|
1620
|
+
Source: `src/brand/logo.tsx`
|
|
1295
1621
|
|
|
1296
|
-
|
|
1622
|
+
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
1623
|
|
|
1298
|
-
-
|
|
1624
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'span'>, 'children'>`
|
|
1299
1625
|
|
|
1300
|
-
| prop |
|
|
1626
|
+
| prop | type | req. | default | what it does |
|
|
1301
1627
|
| --- | --- | --- | --- | --- |
|
|
1302
|
-
| `
|
|
1303
|
-
| `
|
|
1304
|
-
| `
|
|
1305
|
-
| `
|
|
1628
|
+
| `background` | `"dark" \| "light"` | | `dark` | |
|
|
1629
|
+
| `basePath` | `string` | | `ASSETS_PATH` | |
|
|
1630
|
+
| `isotypeOnly` | `boolean \| undefined` | | `false` | Hides the wordmark and leaves only the fin, for very narrow bars. |
|
|
1631
|
+
| `withTagline` | `boolean \| undefined` | | `false` | Adds the tagline under the wordmark, separated from the fin by a divider. |
|
|
1306
1632
|
|
|
1307
|
-
###
|
|
1633
|
+
### Mascot, MascotFace
|
|
1308
1634
|
|
|
1309
|
-
|
|
1635
|
+
Source: `src/brand/mascot.tsx`
|
|
1636
|
+
|
|
1637
|
+
**Mascot**
|
|
1638
|
+
Full-body Tiburoncín.
|
|
1639
|
+
|
|
1640
|
+
- Extends: `Base`
|
|
1641
|
+
|
|
1642
|
+
| prop | type | req. | default | what it does |
|
|
1643
|
+
| --- | --- | --- | --- | --- |
|
|
1644
|
+
| `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. |
|
|
1645
|
+
| `basePath` | `string` | | `ASSETS_PATH` | |
|
|
1646
|
+
| `pose` | `"desk" \| "laptop-coffee" \| "peek" \| "surf"` | yes | | |
|
|
1310
1647
|
|
|
1311
|
-
**
|
|
1312
|
-
Tiburoncín
|
|
1648
|
+
**MascotFace**
|
|
1649
|
+
Tiburoncín's head, with an expression.
|
|
1313
1650
|
|
|
1314
|
-
-
|
|
1651
|
+
- Extends: `Base`
|
|
1315
1652
|
|
|
1316
|
-
| prop |
|
|
1653
|
+
| prop | type | req. | default | what it does |
|
|
1317
1654
|
| --- | --- | --- | --- | --- |
|
|
1318
|
-
| `alt` | `string` | | |
|
|
1319
|
-
| `basePath` | `string` | | `
|
|
1320
|
-
| `
|
|
1655
|
+
| `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. |
|
|
1656
|
+
| `basePath` | `string` | | `ASSETS_PATH` | |
|
|
1657
|
+
| `expression` | `"annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink"` | yes | | |
|
|
1658
|
+
|
|
1659
|
+
## Social icons
|
|
1660
|
+
|
|
1661
|
+
Imported from `@eduardoalvarez/arrecife/social` · or grouped as `social` from the root. 9 exports.
|
|
1662
|
+
|
|
1663
|
+
### GitHub, LinkedIn, X, Instagram, Discord, YouTube, Rss, Email, Newsletter
|
|
1664
|
+
|
|
1665
|
+
Source: `src/social/index.tsx`
|
|
1666
|
+
|
|
1667
|
+
**GitHub**
|
|
1668
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1669
|
+
|
|
1670
|
+
**LinkedIn**
|
|
1671
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1672
|
+
|
|
1673
|
+
**X**
|
|
1674
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1321
1675
|
|
|
1322
|
-
**
|
|
1323
|
-
|
|
1676
|
+
**Instagram**
|
|
1677
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1324
1678
|
|
|
1325
|
-
|
|
1679
|
+
**Discord**
|
|
1680
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1326
1681
|
|
|
1327
|
-
|
|
1682
|
+
**YouTube**
|
|
1683
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1684
|
+
|
|
1685
|
+
**Rss**
|
|
1686
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1687
|
+
|
|
1688
|
+
**Email**
|
|
1689
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1690
|
+
|
|
1691
|
+
**Newsletter**
|
|
1692
|
+
The newsletter. It plays the same role as `Rss` — a way to follow, not a social network — which is why it belongs in this catalogue and does not open the door to an icon library.
|
|
1693
|
+
|
|
1694
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1695
|
+
|
|
1696
|
+
## Icons
|
|
1697
|
+
|
|
1698
|
+
Imported from `@eduardoalvarez/arrecife/icons` · requires `@phosphor-icons/react`. 1 exports.
|
|
1699
|
+
|
|
1700
|
+
### Icon
|
|
1701
|
+
|
|
1702
|
+
Source: `src/icons/index.tsx`
|
|
1703
|
+
|
|
1704
|
+
- Extends: `Omit<PhosphorIconProps, 'size' \| 'weight' \| 'ref'>`
|
|
1705
|
+
|
|
1706
|
+
| prop | type | req. | default | what it does |
|
|
1328
1707
|
| --- | --- | --- | --- | --- |
|
|
1329
|
-
| `
|
|
1330
|
-
| `
|
|
1331
|
-
| `
|
|
1708
|
+
| `as` | `Icon` | yes | | The Phosphor icon itself, passed as a component: `<Icon as={Books} />`. |
|
|
1709
|
+
| `label` | `string` | | | The accessible name. WITHOUT it the icon is decorative and gets `aria-hidden`, which is the right default: most icons sit beside their own label and announcing them twice is noise. |
|
|
1710
|
+
| `tone` | `"action" \| "current" \| "quiet"` | | `action` | WHAT THE ICON IS DOING, which is what picks the weight. Three values, and there is no fourth: `action` is the default and the system's line, `current` is the one of a set you are on, `quiet` is furniture that is not a control. `weight` is deliberately not a prop — see `TONE_WEIGHT`. |
|
|
1332
1711
|
|
|
1333
|
-
##
|
|
1712
|
+
## Forms
|
|
1334
1713
|
|
|
1335
|
-
|
|
1714
|
+
Imported from `@eduardoalvarez/arrecife/form` · requires `react-hook-form`. 7 exports.
|
|
1336
1715
|
|
|
1337
1716
|
### FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage, Form
|
|
1338
1717
|
|
|
1339
|
-
|
|
1718
|
+
Source: `src/form/index.tsx`
|
|
1340
1719
|
|
|
1341
1720
|
**FormField**
|
|
1342
|
-
|
|
1721
|
+
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
1722
|
|
|
1344
|
-
-
|
|
1723
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1345
1724
|
|
|
1346
1725
|
**FormItem**
|
|
1347
|
-
|
|
1726
|
+
The field's box: label, control, help and message, in a column.
|
|
1348
1727
|
|
|
1349
|
-
-
|
|
1728
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1350
1729
|
|
|
1351
1730
|
**FormLabel**
|
|
1352
|
-
|
|
1731
|
+
The label is NOT tinted red when the field fails.
|
|
1353
1732
|
|
|
1354
|
-
-
|
|
1733
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1355
1734
|
|
|
1356
1735
|
**FormControl**
|
|
1357
|
-
|
|
1736
|
+
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
1737
|
|
|
1359
|
-
-
|
|
1738
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1360
1739
|
|
|
1361
1740
|
**FormDescription**
|
|
1362
|
-
|
|
1741
|
+
The field's help text. It is always announced, error or not.
|
|
1363
1742
|
|
|
1364
|
-
-
|
|
1743
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1365
1744
|
|
|
1366
1745
|
**FormMessage**
|
|
1367
|
-
|
|
1746
|
+
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
1747
|
|
|
1369
|
-
-
|
|
1748
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1370
1749
|
|
|
1371
1750
|
**Form**
|
|
1372
|
-
|
|
1751
|
+
The layer that ties the controls to a form with validation and messages.
|
|
1373
1752
|
|
|
1374
|
-
-
|
|
1753
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1375
1754
|
|
|
1376
|
-
##
|
|
1755
|
+
## Charts
|
|
1377
1756
|
|
|
1378
|
-
|
|
1757
|
+
Imported from `@eduardoalvarez/arrecife/chart` · requires `recharts`. 5 exports.
|
|
1379
1758
|
|
|
1380
1759
|
### ChartContainer, ChartTooltip, ChartLegend, ChartTooltipContent, ChartLegendContent
|
|
1381
1760
|
|
|
1382
|
-
|
|
1761
|
+
Source: `src/chart/index.tsx`
|
|
1383
1762
|
|
|
1384
1763
|
**ChartContainer**
|
|
1385
|
-
|
|
1764
|
+
Wraps the chart in a `<figure>` with an accessible name and gives Recharts the concrete height it needs to measure itself.
|
|
1386
1765
|
|
|
1387
|
-
-
|
|
1766
|
+
- Extends: `Omit<ComponentPropsWithoutRef<'figure'>, 'title'>`
|
|
1388
1767
|
|
|
1389
|
-
| prop |
|
|
1768
|
+
| prop | type | req. | default | what it does |
|
|
1390
1769
|
| --- | --- | --- | --- | --- |
|
|
1391
|
-
| `height` | `number` | | `320` |
|
|
1392
|
-
| `label` | `string` |
|
|
1393
|
-
| `summary` | `ReactNode` | | |
|
|
1770
|
+
| `height` | `number` | | `320` | Height in pixels. Recharts needs a concrete one to measure itself. |
|
|
1771
|
+
| `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. |
|
|
1772
|
+
| `summary` | `ReactNode` | | | What the chart says, in words. It goes in a visually hidden `figcaption`. |
|
|
1394
1773
|
|
|
1395
1774
|
**ChartTooltip**
|
|
1396
|
-
|
|
1775
|
+
Recharts' `Tooltip` with the system's defaults: no animation, and the cursor tinted `surfaceRaised`.
|
|
1397
1776
|
|
|
1398
|
-
-
|
|
1777
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1399
1778
|
|
|
1400
1779
|
**ChartLegend**
|
|
1401
|
-
-
|
|
1780
|
+
- No own props: it passes through those of the element or primitive it wraps.
|
|
1402
1781
|
|
|
1403
1782
|
**ChartTooltipContent**
|
|
1404
|
-
|
|
1783
|
+
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
1784
|
|
|
1406
|
-
-
|
|
1785
|
+
- 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
1786
|
|
|
1408
|
-
| prop |
|
|
1787
|
+
| prop | type | req. | default | what it does |
|
|
1409
1788
|
| --- | --- | --- | --- | --- |
|
|
1410
1789
|
| `active` | `boolean \| undefined` | | | |
|
|
1411
1790
|
| `className` | `string` | | | |
|
|
1412
|
-
| `formatter` | `(
|
|
1413
|
-
| `hideLabel` | `boolean` | | `false` |
|
|
1791
|
+
| `formatter` | `(value: unknown, item: ChartPayloadItem) => ReactNode` | | | Formats the value. Without it, it is printed as is: the library imposes no locale. |
|
|
1792
|
+
| `hideLabel` | `boolean` | | `false` | Hides the header, for a single-category chart. |
|
|
1414
1793
|
| `label` | `ReactNode` | | | |
|
|
1415
1794
|
| `payload` | `readonly ChartPayloadItem[]` | | | |
|
|
1416
1795
|
|
|
1417
1796
|
**ChartLegendContent**
|
|
1418
|
-
|
|
1797
|
+
The legend, with the tooltip's same square swatch and the `label` scale.
|
|
1419
1798
|
|
|
1420
|
-
-
|
|
1799
|
+
- Extends: `{ payload?: readonly ChartPayloadItem[] \| undefined; className?: string; }`
|
|
1421
1800
|
|
|
1422
|
-
| prop |
|
|
1801
|
+
| prop | type | req. | default | what it does |
|
|
1423
1802
|
| --- | --- | --- | --- | --- |
|
|
1424
1803
|
| `className` | `string` | | | |
|
|
1425
1804
|
| `payload` | `readonly ChartPayloadItem[]` | | | |
|
|
1426
1805
|
|
|
1427
|
-
##
|
|
1806
|
+
## Exports that are not components
|
|
1428
1807
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1808
|
+
The root re-exports everything from `./tokens` and `./brand` for convenience.
|
|
1809
|
+
Each one appears exactly once, under the most specific subpath that publishes
|
|
1810
|
+
it: if the code does not mount React, that subpath is the one to import.
|
|
1811
|
+
|
|
1812
|
+
### `@eduardoalvarez/arrecife/variants`
|
|
1813
|
+
|
|
1814
|
+
| export | type | what it is |
|
|
1815
|
+
| --- | --- | --- |
|
|
1816
|
+
| `alertVariants` | `(props?: (ConfigVariants<{ variant: { accent: string; success: string; warning: string; error: string; }; emphasis: { subtle: string; strong: string; }; }> & ClassProp) \| undefined): string` | |
|
|
1817
|
+
| `avatarVariants` | `(props?: (ConfigVariants<{ size: { sm: string; md: string; lg: string; xl: string; }; }> & ClassProp) \| undefined): string` | |
|
|
1818
|
+
| `badgeVariants` | `(props?: (ConfigVariants<{ variant: { neutral: string; accent: string; warm: string; success: string; warning: string; error: string; }; }> & ClassProp) \| undefined): string` | |
|
|
1819
|
+
| `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` | |
|
|
1820
|
+
| `CARD` | `string[]` | |
|
|
1821
|
+
| `CARD_HOVER` | `"transition-standard hover:border-hairline-hover"` | |
|
|
1822
|
+
| `CARD_SURFACE` | `"rounded-card border-hairline bg-surface border"` | |
|
|
1823
|
+
| `categoryBadgeVariants` | `(props?: (ConfigVariants<{ active: { false: string; true: string; }; }> & ClassProp) \| undefined): string` | |
|
|
1824
|
+
| `metricBadgeVariants` | `string[]` | |
|
|
1825
|
+
| `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
1826
|
|
|
1433
1827
|
### `@eduardoalvarez/arrecife/tokens`
|
|
1434
1828
|
|
|
1435
|
-
| export |
|
|
1829
|
+
| export | type | what it is |
|
|
1436
1830
|
| --- | --- | --- |
|
|
1437
|
-
| `brand` | `{ readonly body: "#3E7CB1"; readonly spots: "#C2D7E7"; readonly hull: "#0B1524"; }` |
|
|
1831
|
+
| `brand` | `{ readonly body: "#3E7CB1"; readonly spots: "#C2D7E7"; readonly hull: "#0B1524"; }` | Brand — identical in both modes. |
|
|
1438
1832
|
| `colors` | `{ dark, light }` | |
|
|
1439
|
-
| `control` | `{ readonly sm: 14; readonly md: 22; readonly lg: 30; readonly icon: 42; }` |
|
|
1440
|
-
| `dark` | `{ background, surface, surfaceRaised, border, hairline, hairlineHover, textPrimary, textSecondary, textMuted, accent, accentHover, accentOn, warm, warmHover, warmOn, success, warning, error }` |
|
|
1833
|
+
| `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`. |
|
|
1834
|
+
| `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
1835
|
| `fonts` | `{ display, sans, mono }` | |
|
|
1442
1836
|
| `gradient` | `{ dark, light }` | |
|
|
1443
|
-
| `light` | `{ background, surface, surfaceRaised, border, hairline, hairlineHover, textPrimary, textSecondary, textMuted, accent, accentHover, accentOn, warm, warmHover, warmOn, success, warning, error }` |
|
|
1444
|
-
| `limits` | `{ readonly minScreenPx: 13; readonly minPrintPt: 12; readonly measure: "68ch"; }` |
|
|
1445
|
-
| `motion` | `{ readonly duration: "150ms"; readonly easing: "ease-out"; readonly properties: "color, background-color, border-color, fill, stroke"; }` | 150ms ease-out —
|
|
1446
|
-
| `naming` | `{ readonly wordmark: "Eduardo Álvarez"; readonly mascot: "Tiburoncín"; readonly domain: "eduardoalvarez.dev"; }` |
|
|
1837
|
+
| `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. |
|
|
1838
|
+
| `limits` | `{ readonly minScreenPx: 13; readonly minPrintPt: 12; readonly measure: "68ch"; }` | Hard legibility limits. |
|
|
1839
|
+
| `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. |
|
|
1840
|
+
| `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
1841
|
| `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"]; }` |
|
|
1449
|
-
| `shadow` | `{ readonly standard: "0 1px 2px rgba(0, 0, 0, 0.35)"; }` |
|
|
1450
|
-
| `
|
|
1451
|
-
| `
|
|
1452
|
-
| `
|
|
1453
|
-
| `tagline` | `{
|
|
1454
|
-
| `tokens` | `{ colors, brand, fonts, typeScale, limits, radius, control, spacing, size, gradient,
|
|
1842
|
+
| `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. |
|
|
1843
|
+
| `shadow` | `{ readonly standard: "0 1px 2px rgba(0, 0, 0, 0.35)"; }` | A single level. There is no elevation scale. |
|
|
1844
|
+
| `size` | `{ readonly nav: 64; readonly navCompact: 56; readonly sidebar: 256; readonly sidebarRail: 56; readonly content: 760; readonly wide: 1180; }` | |
|
|
1845
|
+
| `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. |
|
|
1846
|
+
| `syntax` | `{ background, identifier, literal, keyword, comment, invalid }` | The syntax highlighting palette. |
|
|
1847
|
+
| `tagline` | `{ long, short, en }` | |
|
|
1848
|
+
| `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
1849
|
| `typeScale` | `{ display, stat, h1, h2, h3, body, lead, ui, label, tag, chip, meta, eyebrow }` | |
|
|
1456
1850
|
|
|
1457
|
-
|
|
1851
|
+
Types (13): `BrandToken`, `ColorMode`, `ColorToken`, `ControlToken`, `FontToken`, `GradientToken`, `RadiusToken`, `SeriesToken`, `SizeToken`, `SpacingToken`, `SyntaxToken`, `Tokens`, `TypeScaleToken`.
|
|
1852
|
+
|
|
1853
|
+
### `@eduardoalvarez/arrecife/social`
|
|
1854
|
+
|
|
1855
|
+
Types (1): `SocialIconProps`.
|
|
1856
|
+
|
|
1857
|
+
### `@eduardoalvarez/arrecife/theme`
|
|
1858
|
+
|
|
1859
|
+
| export | type | what it is |
|
|
1860
|
+
| --- | --- | --- |
|
|
1861
|
+
| `applyTheme` | `(theme: Theme, persist?: boolean): void` | Sets the theme on `<html>` and persists it. |
|
|
1862
|
+
| `currentTheme` | `(): Theme` | The theme currently in place, read from the DOM. |
|
|
1863
|
+
| `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. |
|
|
1864
|
+
| `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. |
|
|
1865
|
+
| `THEME_ATTRIBUTE` | `"data-theme"` | The attribute the `[data-theme]` blocks in `theme.css` read. |
|
|
1866
|
+
| `THEME_EVENT` | `"arrecife:theme"` | The event emitted when the theme changes. |
|
|
1867
|
+
| `THEME_KEY` | `"arrecife-theme"` | The `localStorage` key. |
|
|
1868
|
+
| `themeScript` | `({ base }?: ThemeOptions): string` | The script that goes INLINE in the `<head>`, before any stylesheet. |
|
|
1869
|
+
| `toggleTheme` | `(): Theme` | Switches to the opposite one and returns whichever stuck. |
|
|
1870
|
+
| `watchTheme` | `(onChange: (theme: Theme) => void, options?: ThemeOptions): () => void` | Subscribes to theme changes and returns the function that cancels it. |
|
|
1871
|
+
|
|
1872
|
+
Types (2): `Theme`, `ThemeOptions`.
|
|
1458
1873
|
|
|
1459
1874
|
### `@eduardoalvarez/arrecife/brand`
|
|
1460
1875
|
|
|
1461
|
-
| export |
|
|
1876
|
+
| export | type | what it is |
|
|
1462
1877
|
| --- | --- | --- |
|
|
1463
|
-
| `
|
|
1464
|
-
| `
|
|
1465
|
-
| `
|
|
1466
|
-
| `
|
|
1467
|
-
| `
|
|
1468
|
-
| `
|
|
1469
|
-
| `
|
|
1878
|
+
| `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. |
|
|
1879
|
+
| `faceList` | `readonly ("annoyed" \| "confused" \| "hearts" \| "laughing" \| "shades" \| "waiting" \| "wink")[]` | |
|
|
1880
|
+
| `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. |
|
|
1881
|
+
| `faceUsage` | `{ wink, waiting, laughing, shades, hearts, confused, annoyed }` | The assigned use of each face, from the manual's inventory. |
|
|
1882
|
+
| `fins` | `{ readonly color: "fin.png"; readonly foam: "fin-foam.png"; }` | The fin, in its two variants. |
|
|
1883
|
+
| `poseList` | `readonly ("desk" \| "laptop-coffee" \| "peek" \| "surf")[]` | |
|
|
1884
|
+
| `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
1885
|
|
|
1471
|
-
|
|
1886
|
+
Types (8): `Background`, `Face`, `Fin`, `IsotypeProps`, `LogoProps`, `MascotFaceProps`, `MascotProps`, `Pose`.
|
|
1472
1887
|
|
|
1473
|
-
### `@eduardoalvarez/arrecife/
|
|
1888
|
+
### `@eduardoalvarez/arrecife/icons`
|
|
1474
1889
|
|
|
1475
|
-
| export |
|
|
1890
|
+
| export | type | what it is |
|
|
1476
1891
|
| --- | --- | --- |
|
|
1477
|
-
| `
|
|
1892
|
+
| `ICON_WEIGHT` | `"light" \| "fill" \| "thin" \| "regular" \| "bold" \| "duotone"` | Phosphor's own name for the system's line. It is what `tone="action"` resolves to. |
|
|
1893
|
+
| `TONE_WEIGHT` | `Record<IconTone, IconWeight>` | The three roles, and the weight each one is drawn at. This is the whole of the weight axis: Phosphor ships six and this system reads three, because the other three — `thin`, `bold`, `duotone` — have no role behind them here. |
|
|
1478
1894
|
|
|
1479
|
-
|
|
1895
|
+
Types (2): `IconProps`, `IconTone`.
|
|
1480
1896
|
|
|
1481
|
-
### `@eduardoalvarez/arrecife/
|
|
1897
|
+
### `@eduardoalvarez/arrecife/shiki`
|
|
1482
1898
|
|
|
1483
|
-
| export |
|
|
1899
|
+
| export | type | what it is |
|
|
1484
1900
|
| --- | --- | --- |
|
|
1485
|
-
| `
|
|
1486
|
-
| `COLORES_DE_SERIE` | `string[]` | Las cuatro, en orden, para pasárselas de golpe a un `Pie` con `Cell`. |
|
|
1901
|
+
| `arrecife` | `ShikiTheme` | |
|
|
1487
1902
|
|
|
1488
|
-
|
|
1903
|
+
Types (1): `ShikiTheme`.
|
|
1489
1904
|
|
|
1490
|
-
### `@eduardoalvarez/arrecife/
|
|
1905
|
+
### `@eduardoalvarez/arrecife/chart`
|
|
1491
1906
|
|
|
1492
|
-
| export |
|
|
1907
|
+
| export | type | what it is |
|
|
1493
1908
|
| --- | --- | --- |
|
|
1494
|
-
| `
|
|
1495
|
-
| `
|
|
1496
|
-
|
|
1497
|
-
|
|
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`.
|
|
1909
|
+
| `SERIES_COLORS` | `string[]` | All four, in order, to hand to a `Pie` with `Cell` in one go. |
|
|
1910
|
+
| `seriesColor` | `(index: number): string` | The color of series `index`, as a custom property. |
|
|
1911
|
+
|
|
1912
|
+
Types (4): `ChartContainerProps`, `ChartLegendContentProps`, `ChartPayloadItem`, `ChartTooltipContentProps`.
|
|
1506
1913
|
|
|
1507
1914
|
### `@eduardoalvarez/arrecife/form`
|
|
1508
1915
|
|
|
1509
|
-
| export |
|
|
1916
|
+
| export | type | what it is |
|
|
1510
1917
|
| --- | --- | --- |
|
|
1511
|
-
| `useFormField` | `(): { invalid: boolean; isDirty: boolean; isTouched: boolean; isValidating: boolean; error?: FieldError; name: string; id: string;
|
|
1918
|
+
| `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
1919
|
|
|
1513
1920
|
### `@eduardoalvarez/arrecife/og`
|
|
1514
1921
|
|
|
1515
|
-
| export |
|
|
1922
|
+
| export | type | what it is |
|
|
1516
1923
|
| --- | --- | --- |
|
|
1517
|
-
| `
|
|
1518
|
-
| `
|
|
1519
|
-
| `
|
|
1520
|
-
| `
|
|
1521
|
-
| `
|
|
1522
|
-
| `
|
|
1924
|
+
| `articleTemplate` | `(data: ArticleData): SatoriNode` | Article · 145° gradient over abyss, category and reading time in sand. |
|
|
1925
|
+
| `courseTemplate` | `(data: CourseData): SatoriNode` | Course · THE ONLY LIGHT TEMPLATE. |
|
|
1926
|
+
| `defaultTemplate` | `(data?: DefaultData): SatoriNode` | Default · the document's declared exception. |
|
|
1927
|
+
| `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. |
|
|
1928
|
+
| `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` | |
|
|
1929
|
+
| `talkTemplate` | `(data: TalkData): SatoriNode` | Talk · eyebrow in biolume with the event and year, pose bleeding off the corner. |
|
|
1523
1930
|
|
|
1524
|
-
|
|
1931
|
+
Types (6): `ArticleData`, `BaseData`, `CourseData`, `DefaultData`, `SatoriNode`, `TalkData`.
|
|
1525
1932
|
|
|
1526
1933
|
### `@eduardoalvarez/arrecife`
|
|
1527
1934
|
|
|
1528
|
-
| export |
|
|
1935
|
+
| export | type | what it is |
|
|
1529
1936
|
| --- | --- | --- |
|
|
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
1937
|
| `cn` | `(...inputs: ClassValue[]): string` | |
|
|
1536
|
-
| `
|
|
1537
|
-
| `
|
|
1538
|
-
| `
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
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.
|
|
1938
|
+
| `social` | `typeof import("src/social/index")` | |
|
|
1939
|
+
| `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. |
|
|
1940
|
+
| `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. |
|
|
1941
|
+
|
|
1942
|
+
Types (62): `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`, `SidebarGroupProps`, `SidebarItemProps`, `SidebarNavProps`, `SkeletonProps`, `SocialLink`, `StatDelta`, `StatProps`, `SwitchProps`, `TableOfContentsProps`, `TabsProps`, `TalkCardProps`, `TextProps`, `TextareaProps`, `ThemeToggleProps`, `ToastOptions`, `ToastVariant`, `ToasterProps`, `TocEntry`.
|
|
1943
|
+
|
|
1944
|
+
# Where to look if this is not enough
|
|
1945
|
+
|
|
1946
|
+
- Storybook publishes every component with its stories and the props table
|
|
1947
|
+
generated from the types.
|
|
1948
|
+
- The repo's `README.md`: the reasoning behind each decision, the contrast
|
|
1949
|
+
correction table and the release cycle.
|
|
1950
|
+
- `docs/design-system.md` and `docs/brand-manual.md`: the identity documents,
|
|
1951
|
+
greppable.
|
|
1952
|
+
- `docs/decisions.md`: the points where the code and the document did not say the
|
|
1953
|
+
same thing, each with its resolution.
|
|
1954
|
+
- `AGENTS.md`: for working inside the library's repo.
|