@eduardoalvarez/arrecife 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/README.md +706 -470
- 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-25YNFCIF.js +141 -0
- package/dist/{chunk-YZ2SDOVZ.js → chunk-6O3KWB6P.js} +30 -30
- package/dist/chunk-CKRSQPTX.js +36 -0
- package/dist/{chunk-ZEOQKRQ7.js → chunk-DKCN7BAL.js} +1 -1
- package/dist/chunk-GCRII2KQ.js +86 -0
- package/dist/chunk-JMOOFZ3B.js +42 -0
- package/dist/chunk-O4TAH7YJ.js +276 -0
- package/dist/chunk-ODBFN44D.js +45 -0
- package/dist/{chunk-VPT32GPG.js → chunk-PMN7NR3G.js} +2 -2
- package/dist/chunk-XKYHTOUJ.js +27 -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/index.cjs +1068 -929
- package/dist/index.d.cts +770 -773
- package/dist/index.d.ts +770 -773
- package/dist/index.js +629 -675
- 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 +130 -130
- 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/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 +133 -86
- package/dist/tokens/index.d.cts +246 -161
- package/dist/tokens/index.d.ts +246 -161
- package/dist/tokens/index.js +2 -2
- package/dist/tokens/theme.css +133 -98
- package/dist/variants/index.cjs +192 -0
- package/dist/variants/index.d.cts +192 -0
- package/dist/variants/index.d.ts +192 -0
- package/dist/variants/index.js +3 -0
- package/llms.txt +810 -744
- package/package.json +20 -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/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export { BrandToken, ColorMode, ColorToken, ControlToken, FontToken, GradientToken, RadiusToken, SeriesToken,
|
|
2
|
-
import {
|
|
3
|
-
export {
|
|
1
|
+
export { BrandToken, ColorMode, ColorToken, ControlToken, FontToken, GradientToken, RadiusToken, SeriesToken, SizeToken, SpacingToken, SyntaxToken, Tokens, TypeScaleToken, brand, colors, control, dark, fonts, gradient, light, limits, motion, naming, radius, series, shadow, size, spacing, syntax, tagline, tokens, typeScale } from './tokens/index.js';
|
|
2
|
+
import { Theme } from './theme/index.js';
|
|
3
|
+
export { THEME_ATTRIBUTE, THEME_EVENT, THEME_KEY, ThemeOptions, applyTheme, currentTheme, preferredTheme, storedTheme, themeScript, toggleTheme, watchTheme } from './theme/index.js';
|
|
4
|
+
import { alertVariants as alert, avatarVariants as avatar, badgeVariants as badge, buttonVariants as button, textVariants as text } from './variants/index.js';
|
|
5
|
+
export { CARD, CARD_HOVER, CARD_SURFACE, categoryBadgeVariants, metricBadgeVariants } from './variants/index.js';
|
|
4
6
|
import * as react from 'react';
|
|
5
7
|
import { ComponentPropsWithoutRef, ReactNode, ComponentProps, RefObject, SVGProps } from 'react';
|
|
6
8
|
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
7
|
-
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
8
9
|
import { VariantProps } from 'class-variance-authority';
|
|
9
10
|
import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog';
|
|
10
11
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
@@ -12,372 +13,234 @@ import { DayPicker } from 'react-day-picker';
|
|
|
12
13
|
import * as CheckboxPrimitive from '@radix-ui/react-checkbox';
|
|
13
14
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
14
15
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
15
|
-
export { L as Label, a as LabelProps } from './label-
|
|
16
|
+
export { L as Label, a as LabelProps } from './label-MgHFKnFy.js';
|
|
16
17
|
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
17
18
|
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
|
18
19
|
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
19
20
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
|
20
21
|
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
22
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
21
23
|
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
|
22
24
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
|
23
25
|
import * as ToastPrimitive from '@radix-ui/react-toast';
|
|
24
26
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
|
25
|
-
import {
|
|
26
|
-
export { A as
|
|
27
|
-
export {
|
|
27
|
+
import { F as Face, P as Pose } from './catalog-D13txprv.js';
|
|
28
|
+
export { A as ASSETS_PATH, B as Background, a as Fin, f as faceList, b as faceUsage, c as faces, d as fins, p as poseList, e as poses } from './catalog-D13txprv.js';
|
|
29
|
+
export { Isotype, IsotypeProps, Logo, LogoProps, Mascot, MascotFace, MascotFaceProps, MascotProps } from './brand/index.js';
|
|
28
30
|
import { ClassValue } from 'clsx';
|
|
29
31
|
import '@radix-ui/react-label';
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
+
* The disclosure. Two projects asked for it: the portfolio FAQ and the course
|
|
35
|
+
* syllabus, which is literally a list of sections that open.
|
|
34
36
|
*
|
|
35
|
-
*
|
|
37
|
+
* The height IS animated, and it is the system's fourth declared exception.
|
|
36
38
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
39
|
+
* It deserves explaining, because the general rule says the opposite and this
|
|
40
|
+
* component was born unanimated citing it. The difference is that nothing
|
|
41
|
+
* APPEARS here: a gap opens, and everything below the accordion shifts. Without
|
|
42
|
+
* a transition that shift is a jump, and whoever just clicked loses their place
|
|
43
|
+
* on the page — which is exactly the harm the «no movement» rule exists to
|
|
44
|
+
* prevent. It is the same category as the side panel, the second exception, and
|
|
45
|
+
* not that of an entrance animation.
|
|
44
46
|
*
|
|
45
|
-
*
|
|
46
|
-
* `--ease-standard`,
|
|
47
|
-
*
|
|
47
|
+
* It sits behind `motion-safe`, lasts `--duration-standard` and uses
|
|
48
|
+
* `--ease-standard`, so it introduces neither a new timing nor a new curve.
|
|
49
|
+
* Whoever asked for less motion still sees the panel appear where it will stay.
|
|
48
50
|
*
|
|
49
|
-
*
|
|
51
|
+
* See `docs/decisions.md` § 20.
|
|
50
52
|
*
|
|
51
|
-
*
|
|
52
|
-
* color
|
|
53
|
-
*
|
|
53
|
+
* The chevron, by contrast, rotates with no transition: `transition-standard`
|
|
54
|
+
* only covers color and border, so `rotate` snaps even with the class in place.
|
|
55
|
+
* It is the same treatment `Progress` gives its width.
|
|
54
56
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
+
* The divider between items is `hairline`, not `border`: it is a reading
|
|
58
|
+
* separation, not the border of a control.
|
|
57
59
|
*/
|
|
58
60
|
type AccordionProps = ComponentPropsWithoutRef<typeof AccordionPrimitive.Root>;
|
|
59
61
|
declare function Accordion({ className, ...props }: AccordionProps): react.JSX.Element;
|
|
60
62
|
declare function AccordionItem({ className, ...props }: ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>): react.JSX.Element;
|
|
61
63
|
/**
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* `headingLevel`
|
|
68
|
-
*
|
|
69
|
-
*
|
|
64
|
+
* The trigger IS the heading, so it goes INSIDE an `<h3>`: Radix wraps the
|
|
65
|
+
* button in `AccordionPrimitive.Header`, which renders whichever element you ask
|
|
66
|
+
* of it. Without that, a screen reader sees a list of loose buttons and loses
|
|
67
|
+
* the page structure, which is precisely what a FAQ needs to keep.
|
|
68
|
+
*
|
|
69
|
+
* `headingLevel` exists because the correct level depends on where it is
|
|
70
|
+
* mounted: on a FAQ page the block hangs off a section `<h2>`, and in a syllabus
|
|
71
|
+
* it may hang off an `<h3>`. Pinning it here would be guessing.
|
|
70
72
|
*/
|
|
71
73
|
type AccordionTriggerProps = ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger> & {
|
|
72
|
-
/**
|
|
74
|
+
/** The level of the heading wrapping the trigger. */
|
|
73
75
|
headingLevel?: 2 | 3 | 4;
|
|
74
76
|
};
|
|
75
77
|
declare function AccordionTrigger({ className, children, headingLevel, ...props }: AccordionTriggerProps): react.JSX.Element;
|
|
76
78
|
declare function AccordionContent({ className, children, ...props }: ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>): react.JSX.Element;
|
|
77
79
|
|
|
78
|
-
/**
|
|
79
|
-
* El aviso lleva el color en el fondo, no solo en el borde.
|
|
80
|
-
*
|
|
81
|
-
* La receta del sistema es fondo al 8 % del color semántico y borde al 22 %.
|
|
82
|
-
* Este archivo daba `bg-surface` a las cuatro variantes, así que el tono vivía
|
|
83
|
-
* entero en un borde de 1px: cuatro avisos que se distinguían entre sí por una
|
|
84
|
-
* línea.
|
|
85
|
-
*
|
|
86
|
-
* Los cuatro tonos empiezan en ACENTO, que es el informativo del sistema (✦).
|
|
87
|
-
* No hay `neutral`: un aviso sin color es un párrafo.
|
|
88
|
-
*
|
|
89
|
-
* MEDIDO en los dos modos, porque el 8 % del documento está calculado sobre
|
|
90
|
-
* abismo y había que comprobar que sobrevive sobre papel. Contraste del tinte
|
|
91
|
-
* contra el fondo de página:
|
|
92
|
-
*
|
|
93
|
-
* 8 % oscuro 8 % claro
|
|
94
|
-
* accent 1.149 1.106
|
|
95
|
-
* success 1.116 1.121
|
|
96
|
-
* warning 1.126 1.109
|
|
97
|
-
* error 1.067 1.120
|
|
98
|
-
*
|
|
99
|
-
* El modo claro NO necesita una segunda tabla: aguanta igual o mejor que el
|
|
100
|
-
* oscuro. El único punto flojo del sistema es `error` sobre abismo, 1.067, que
|
|
101
|
-
* es el tinte más tenue de los ocho y se apoya entero en el borde al 22 %.
|
|
102
|
-
*
|
|
103
|
-
* Hay una SEGUNDA receta, a propósito: el aviso bajo el formulario de
|
|
104
|
-
* newsletter va al 10 % con el borde sólido para leerse bajo el campo. Es
|
|
105
|
-
* `enfasis="fuerte"`, y no se unifica con la sutil porque la diferencia está
|
|
106
|
-
* documentada.
|
|
107
|
-
*
|
|
108
|
-
* TERCERA corrección de contraste, en la línea de las tres que ya tenía
|
|
109
|
-
* `tokens.ts`. El título iba en el color semántico, y en modo claro eso no puede
|
|
110
|
-
* pasar AA: los semánticos claros están calibrados para pasar JUSTO sobre papel
|
|
111
|
-
* (4.54–4.88), así que sobre su propio tinte al 8 % caen a 4.11–4.40. No hay
|
|
112
|
-
* alfa que lo arregle — el problema es poner el color encima de sí mismo.
|
|
113
|
-
*
|
|
114
|
-
* El tinte es una SUPERFICIE, así que el texto que lleva encima es un token de
|
|
115
|
-
* texto: `textPrimary` da 14.6–14.9 sobre los cuatro tintes. El color semántico
|
|
116
|
-
* se queda donde no es texto — el borde y el glifo —, que es lo único que el
|
|
117
|
-
* documento pedía de él. El glifo es decorativo y va `aria-hidden`, así que le
|
|
118
|
-
* aplica el umbral de 3:1 y no el de 4.5: su peor caso claro es 4.11.
|
|
119
|
-
*
|
|
120
|
-
* El radio: el documento dice 12, que no es ninguno de los cinco radios del
|
|
121
|
-
* sistema. Usa el de tarjeta antes que estrenar un sexto — ver
|
|
122
|
-
* `docs/decisiones.md`.
|
|
123
|
-
*/
|
|
124
|
-
declare const alert: (props?: ({
|
|
125
|
-
variant?: "error" | "accent" | "success" | "warning" | null | undefined;
|
|
126
|
-
enfasis?: "sutil" | "fuerte" | null | undefined;
|
|
127
|
-
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
128
80
|
type AlertProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & VariantProps<typeof alert> & {
|
|
129
81
|
title?: ReactNode;
|
|
130
82
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
83
|
+
* Replaces the variant's mono glyph. Never an emoji: if you need something
|
|
84
|
+
* else, it is an SVG from `glyphs`.
|
|
133
85
|
*/
|
|
134
86
|
icon?: ReactNode;
|
|
135
87
|
};
|
|
136
|
-
declare function Alert({ className, variant,
|
|
88
|
+
declare function Alert({ className, variant, emphasis, title, icon, children, ...props }: AlertProps): react.JSX.Element;
|
|
137
89
|
|
|
138
90
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* 1.
|
|
146
|
-
*
|
|
147
|
-
* 2.
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
91
|
+
* The destructive confirmation. It is NOT a `Dialog` with different text, which
|
|
92
|
+
* is why it lives in its own file and on its own Radix primitive.
|
|
93
|
+
*
|
|
94
|
+
* Three differences, and all three matter at the moment somebody is about to
|
|
95
|
+
* delete an article:
|
|
96
|
+
*
|
|
97
|
+
* 1. The role is `alertdialog`, not `dialog`. A screen reader announces it
|
|
98
|
+
* with the description included, without waiting for you to navigate to it.
|
|
99
|
+
* 2. Initial focus goes to CANCEL, not to the first element. Whoever hits
|
|
100
|
+
* Enter out of inertia deletes nothing. Radix does this on its own only if
|
|
101
|
+
* the cancel exists, which is why `AlertDialogCancel` is not optional in
|
|
102
|
+
* practice.
|
|
103
|
+
* 3. It does NOT close on outside click and has no X. Leaving a confirmation
|
|
104
|
+
* is a decision, not a slip: you have to say no.
|
|
105
|
+
*
|
|
106
|
+
* The confirm button is NOT red, and since 0.6.0 that is a choice and no longer
|
|
107
|
+
* the absence of an option: `Button` has `destructive`, and here it is still not
|
|
108
|
+
* used. Everything above already carries the gravity — a title that says what is
|
|
109
|
+
* about to happen, focus on cancel, no closing by clicking outside — and a red
|
|
110
|
+
* button on top of that is shouting. What communicates the gravity is the text:
|
|
111
|
+
* «Borrar el artículo», not «Aceptar».
|
|
112
|
+
*
|
|
113
|
+
* `destructive` is for the destructive button that has none of that around it:
|
|
114
|
+
* a table row, a toolbar. See `docs/decisions.md` § 21.
|
|
157
115
|
*/
|
|
158
116
|
declare const AlertDialog: react.FC<AlertDialogPrimitive.AlertDialogProps>;
|
|
159
117
|
declare const AlertDialogTrigger: react.ForwardRefExoticComponent<AlertDialogPrimitive.AlertDialogTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
160
118
|
declare function AlertDialogOverlay({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>): react.JSX.Element;
|
|
161
|
-
/**
|
|
119
|
+
/** No entrance animation, same as `Dialog`: it appears where it will stay. */
|
|
162
120
|
declare function AlertDialogContent({ className, children, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>): react.JSX.Element;
|
|
163
121
|
declare function AlertDialogHeader({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
164
122
|
/**
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
123
|
+
* Cancel to the LEFT of confirm on desktop and BELOW it on mobile, which is what
|
|
124
|
+
* `flex-col-reverse` gives: the DOM order puts cancel first — that is where focus
|
|
125
|
+
* goes — and in a column the thumb finds it where it should be without changing
|
|
126
|
+
* the tab order.
|
|
169
127
|
*/
|
|
170
128
|
declare function AlertDialogFooter({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
171
129
|
declare function AlertDialogTitle({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>): react.JSX.Element;
|
|
172
130
|
/**
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
131
|
+
* What is lost, spelled out. The `alertdialog` role makes this get announced up
|
|
132
|
+
* front, so «this action cannot be undone» does not go here on its own: what
|
|
133
|
+
* goes here is what gets deleted and what it takes down with it.
|
|
176
134
|
*/
|
|
177
135
|
declare function AlertDialogDescription({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>): react.JSX.Element;
|
|
178
|
-
/**
|
|
136
|
+
/** The one that takes focus on open. */
|
|
179
137
|
declare function AlertDialogCancel({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>): react.JSX.Element;
|
|
180
138
|
declare function AlertDialogAction({ className, ...props }: ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>): react.JSX.Element;
|
|
181
139
|
|
|
182
|
-
declare const avatar: (props?: ({
|
|
183
|
-
size?: "sm" | "md" | "lg" | "xl" | null | undefined;
|
|
184
|
-
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
185
140
|
type AvatarProps = ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> & VariantProps<typeof avatar>;
|
|
186
141
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
142
|
+
* One for everything: the author's photo and anyone else's in the system. There
|
|
143
|
+
* is no separate `brand/Avatar` — a profile photo wearing the brand's skin is
|
|
144
|
+
* exactly this with a different `src`.
|
|
190
145
|
*/
|
|
191
146
|
declare function Avatar({ className, size, ...props }: AvatarProps): react.JSX.Element;
|
|
192
147
|
declare function AvatarImage({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>): react.JSX.Element;
|
|
193
|
-
/**
|
|
148
|
+
/** Initials while the image loads, or when there is no image. */
|
|
194
149
|
declare function AvatarFallback({ className, ...props }: ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>): react.JSX.Element;
|
|
195
150
|
type AvatarUploadProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & VariantProps<typeof avatar> & {
|
|
196
|
-
/**
|
|
151
|
+
/** The current image, already uploaded. The local preview beats it while it lasts. */
|
|
197
152
|
src?: string | undefined;
|
|
198
|
-
/**
|
|
153
|
+
/** Initials while there is no image. */
|
|
199
154
|
fallback?: ReactNode;
|
|
200
|
-
/**
|
|
201
|
-
onSelectFile?: ((
|
|
202
|
-
/**
|
|
155
|
+
/** Fires with the chosen file. The upload is the project's job. */
|
|
156
|
+
onSelectFile?: ((file: File) => void) | undefined;
|
|
157
|
+
/** What the system dialog accepts. */
|
|
203
158
|
accept?: string;
|
|
204
|
-
/**
|
|
159
|
+
/** The control's accessible name. It is the only thing naming it: there is no visible text. */
|
|
205
160
|
label?: string;
|
|
206
161
|
disabled?: boolean | undefined;
|
|
207
162
|
};
|
|
208
163
|
/**
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
164
|
+
* The avatar you can change. `Avatar` displays; this one also lets you pick.
|
|
165
|
+
*
|
|
166
|
+
* It is presentational, like `NewsletterForm`: it emits `onSelectFile` with the
|
|
167
|
+
* `File` and its job ends there. The upload does not belong here — every project
|
|
168
|
+
* has its own storage and its own endpoint — and a component doing the `POST`
|
|
169
|
+
* would be infrastructure, which is the third entry criterion and the one most
|
|
170
|
+
* often skipped.
|
|
171
|
+
*
|
|
172
|
+
* The preview is LOCAL and does not wait for the upload to finish. It is the
|
|
173
|
+
* difference between a control that responds and one that looks broken: seconds
|
|
174
|
+
* can pass between picking the file and the server returning the URL, and with
|
|
175
|
+
* no preview the avatar keeps the old photo as though nothing had happened. The
|
|
176
|
+
* `objectURL` is revoked on change and on unmount; not revoking it is a memory
|
|
177
|
+
* leak that does not show its face until the tenth photo.
|
|
178
|
+
*
|
|
179
|
+
* The control is a `<label>` with a hidden `<input type="file">` inside, not a
|
|
180
|
+
* `<button>` firing a synthetic click. The real input brings the system dialog,
|
|
181
|
+
* the browser's drag-and-drop and keyboard focus; the fake button has to be
|
|
182
|
+
* rebuilt from scratch and something is always missing.
|
|
227
183
|
*/
|
|
228
184
|
declare function AvatarUpload({ src, fallback, onSelectFile, accept, label, size, disabled, className, ...props }: AvatarUploadProps): react.JSX.Element;
|
|
229
185
|
|
|
230
186
|
/**
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* El sistema define tres formas distintas y este archivo las servía todas como
|
|
234
|
-
* píldora mono en versalitas. Cada una tiene su forma porque cada una dice otra
|
|
235
|
-
* cosa:
|
|
236
|
-
*
|
|
237
|
-
* categoría · píldora r999, mono 11.5, arena → un slug: `engineering-culture`
|
|
238
|
-
* estado · cuadrada r6, sans 12.5/500, tinte → un semáforo: publicado, borrador
|
|
239
|
-
* métrica · píldora, mono 11.5 muted → un dato: `8 min de lectura`
|
|
240
|
-
*
|
|
241
|
-
* NINGUNA va en versalitas. El `uppercase tracking-[0.12em]` que tenían las tres
|
|
242
|
-
* venía de `text-eyebrow`, que es la escala del eyebrow y no la de las
|
|
243
|
-
* etiquetas: convertía `engineering-culture` en `ENGINEERING-CULTURE` y
|
|
244
|
-
* `pose-laptop-coffee.png` en un nombre de archivo que no existe.
|
|
187
|
+
* Three badge families, three components. Why there are three shapes and not one
|
|
188
|
+
* is in `variants/badge.ts`, next to the classes that make them.
|
|
245
189
|
*/
|
|
246
|
-
/**
|
|
247
|
-
* Semáforo. Cuadrada r6, sans 12.5/500 y fondo al 8 % del semántico — la receta
|
|
248
|
-
* del aviso en tamaño de palabra: un estado es un aviso de una sola palabra.
|
|
249
|
-
*
|
|
250
|
-
* SIN borde. Lo llevó un tiempo, puesto para reforzar el tono, y pesaba: una
|
|
251
|
-
* caja con borde al lado de un título se lee como un control y no como un dato.
|
|
252
|
-
* El tinte solo es lo que pide el documento y es lo que se ve más limpio.
|
|
253
|
-
*
|
|
254
|
-
* El texto va en `textPrimary`, no en el color del tono. En modo claro los
|
|
255
|
-
* semánticos están calibrados para pasar JUSTO sobre papel, así que sobre su
|
|
256
|
-
* propio tinte caen a 4.10–4.40 y no pasan AA. El tinte es una superficie;
|
|
257
|
-
* encima va un token de texto. Medido en `alert.tsx`.
|
|
258
|
-
*
|
|
259
|
-
* El tono nunca es el único portador del significado: la etiqueta dice
|
|
260
|
-
* «Publicado» o «Borrador» con todas sus letras.
|
|
261
|
-
*/
|
|
262
|
-
declare const badge: (props?: ({
|
|
263
|
-
variant?: "error" | "accent" | "success" | "warning" | "neutral" | "warm" | null | undefined;
|
|
264
|
-
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
265
190
|
type BadgeProps = ComponentPropsWithoutRef<'span'> & VariantProps<typeof badge>;
|
|
266
191
|
declare function Badge({ className, variant, ...props }: BadgeProps): react.JSX.Element;
|
|
267
|
-
/**
|
|
268
|
-
* Un slug, en arena. Sin transformar: los slugs ya vienen en minúscula y
|
|
269
|
-
* forzarla sería el mismo error que forzaba el `uppercase`.
|
|
270
|
-
*
|
|
271
|
-
* El borde del documento es `#4A3A25`, que es arena al 28 % sobre abismo: sale
|
|
272
|
-
* de la paleta, así que no entra como token nuevo. En modo claro la misma regla
|
|
273
|
-
* da arena oscura al 28 % sobre papel, que es lo que se quiere.
|
|
274
|
-
*
|
|
275
|
-
* La variante rellena NO es decorativa: es el único indicador de filtro activo
|
|
276
|
-
* del listado de artículos. Por eso `active` es una prop y no un `className`.
|
|
277
|
-
*/
|
|
278
|
-
declare const categoria: (props?: ({
|
|
279
|
-
active?: boolean | null | undefined;
|
|
280
|
-
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
281
192
|
type CategoryBadgeProps = ComponentPropsWithoutRef<'span'> & {
|
|
282
|
-
/**
|
|
193
|
+
/** Selected filter: solid sand with ink on top. */
|
|
283
194
|
active?: boolean | undefined;
|
|
284
195
|
};
|
|
285
196
|
declare function CategoryBadge({ className, active, ...props }: CategoryBadgeProps): react.JSX.Element;
|
|
286
197
|
type MetricBadgeProps = ComponentPropsWithoutRef<'span'> & {
|
|
287
|
-
/**
|
|
198
|
+
/** Adds the hairline ring. By default a metric carries no box. */
|
|
288
199
|
boxed?: boolean | undefined;
|
|
289
200
|
};
|
|
290
201
|
declare function MetricBadge({ className, boxed, ...props }: MetricBadgeProps): react.JSX.Element;
|
|
291
202
|
|
|
292
|
-
/**
|
|
293
|
-
* Las CUATRO variantes del sistema, y solo esas cuatro.
|
|
294
|
-
*
|
|
295
|
-
* Regla de marca 2, como código y no como documentación: en modo claro el botón
|
|
296
|
-
* primario no puede ser bioluz ni arena, así que pasa a casco sólido. No hay un
|
|
297
|
-
* hex literal en ningún lado — `brand.hull` es un token, y el hover reusa
|
|
298
|
-
* `textSecondary` en vez de inventar un `hullHover`.
|
|
299
|
-
*
|
|
300
|
-
* Regla de marca 3: `conversion` va UNA sola vez por pantalla. Está documentado
|
|
301
|
-
* en la story y no se fuerza en runtime: dos botones de conversión en una misma
|
|
302
|
-
* página son un problema de diseño, no un error que deba tirar el render.
|
|
303
|
-
*
|
|
304
|
-
* `secondary` NUNCA se rellena el fondo. Es borde y texto: en reposo, hairline
|
|
305
|
-
* de hover y espuma; en hover, los dos pasan a bioluz. Un secundario relleno es
|
|
306
|
-
* un primario mal teñido, y era lo que hacía este archivo.
|
|
307
|
-
*
|
|
308
|
-
* `tertiary` es la estética CLI del sistema: mono, formato `./acción →`, sin
|
|
309
|
-
* caja. Aparece en cada tarjeta, así que no es un `ghost` genérico con otro
|
|
310
|
-
* nombre — el formato del texto es parte de la variante.
|
|
311
|
-
*
|
|
312
|
-
* No hay variante de peligro. El error del sistema vive en los avisos y en la
|
|
313
|
-
* validación de campo, no en un botón rojo. Si alguna vez hace falta uno de
|
|
314
|
-
* verdad, entra primero en el documento y luego aquí.
|
|
315
|
-
*/
|
|
316
|
-
declare const button: (props?: ({
|
|
317
|
-
variant?: "primary" | "conversion" | "secondary" | "tertiary" | null | undefined;
|
|
318
|
-
size?: "icon" | "sm" | "md" | "lg" | null | undefined;
|
|
319
|
-
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
320
203
|
type ButtonProps = ComponentPropsWithoutRef<'button'> & VariantProps<typeof button> & {
|
|
321
|
-
/**
|
|
204
|
+
/** Renders the child instead of a `<button>`, to wrap a link. */
|
|
322
205
|
asChild?: boolean;
|
|
323
|
-
/**
|
|
206
|
+
/** Disables and announces `aria-busy`. Incompatible with `asChild`. */
|
|
324
207
|
loading?: boolean;
|
|
325
|
-
/**
|
|
208
|
+
/** SVG glyph before the text. Hidden while loading. */
|
|
326
209
|
icon?: ReactNode;
|
|
327
210
|
};
|
|
328
211
|
declare function Button({ className, variant, size, asChild, loading, icon, children, disabled, ...props }: ButtonProps): react.JSX.Element;
|
|
329
212
|
|
|
330
213
|
type CalendarProps = ComponentProps<typeof DayPicker> & {
|
|
331
214
|
/**
|
|
332
|
-
*
|
|
333
|
-
*
|
|
215
|
+
* Stretches the calendar to fill its container's whole width, with the cells
|
|
216
|
+
* splitting it evenly.
|
|
334
217
|
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
218
|
+
* Off, the calendar measures whatever its cells measure — 36px each — and that
|
|
219
|
+
* is what you want inside a `Popover`, where stretching it would leave a huge
|
|
220
|
+
* bubble. On, it is a planner's month view, which fills the page.
|
|
338
221
|
*
|
|
339
|
-
*
|
|
222
|
+
* With several months, each takes an equal fraction of the width.
|
|
340
223
|
*/
|
|
341
224
|
fullWidth?: boolean | undefined;
|
|
342
225
|
};
|
|
343
226
|
/**
|
|
344
|
-
*
|
|
227
|
+
* A navigable month calendar, on top of `react-day-picker`.
|
|
345
228
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
* no arrastra nada.
|
|
229
|
+
* It is the library's only heavy dependency and it was let in knowingly: the
|
|
230
|
+
* content planner's calendar cannot be solved with the native control. For
|
|
231
|
+
* picking a date inside a form there is `DateField`, which drags nothing along.
|
|
350
232
|
*
|
|
351
|
-
* `animate`
|
|
352
|
-
*
|
|
233
|
+
* `animate` stays off — that is its default — so the month change does not
|
|
234
|
+
* slide. Days are marked with color and border, like everything else.
|
|
353
235
|
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
236
|
+
* The language defaults to Spanish because all five projects are; it is changed
|
|
237
|
+
* by passing another date-fns `locale`.
|
|
356
238
|
*
|
|
357
|
-
*
|
|
358
|
-
*
|
|
359
|
-
* especificidad.
|
|
239
|
+
* It does not ship the library's `style.css`: every class comes from here, so
|
|
240
|
+
* the consumer need not import third-party CSS or fight its specificity.
|
|
360
241
|
*/
|
|
361
242
|
declare function Calendar({ className, classNames, showOutsideDays, fullWidth, ...props }: CalendarProps): react.JSX.Element;
|
|
362
243
|
|
|
363
|
-
/**
|
|
364
|
-
* El contenedor de superficie del sistema, y la única definición de lo que es
|
|
365
|
-
* una tarjeta: `surface`, borde `hairline`, radio de tarjeta.
|
|
366
|
-
*
|
|
367
|
-
* Las tarjetas con dominio — `ArticleCard`, `TalkCard`, `CourseCard`,
|
|
368
|
-
* `LinkRow` — reutilizan estas mismas clases, así que si el radio o el borde
|
|
369
|
-
* cambian, cambian en todas a la vez.
|
|
370
|
-
*
|
|
371
|
-
* El documento daba a la tarjeta un fondo propio, `#0B1620`, un cuarto nivel de
|
|
372
|
-
* superficie entre abismo y fosa. No entra: no tiene par en modo claro, y una
|
|
373
|
-
* superficie sin par es un token que miente en la mitad de los proyectos. La
|
|
374
|
-
* tarjeta es `surface`, y el documento se corrige — ver `docs/decisiones.md`.
|
|
375
|
-
*
|
|
376
|
-
* El padding sí estaba mal: el documento pide 26 (`lg`) y aquí había 16 (`md`).
|
|
377
|
-
*/
|
|
378
|
-
declare const SUPERFICIE_TARJETA = "rounded-card border-hairline bg-surface border";
|
|
379
|
-
/** El hover de la regla 6: solo el borde. Se aplica donde la tarjeta es pulsable. */
|
|
380
|
-
declare const HOVER_TARJETA = "transition-standard hover:border-hairline-hover";
|
|
381
244
|
declare function Card({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
382
245
|
declare function CardHeader({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
383
246
|
declare function CardTitle({ className, ...props }: ComponentPropsWithoutRef<'h3'>): react.JSX.Element;
|
|
@@ -389,41 +252,40 @@ type CheckboxProps = ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>;
|
|
|
389
252
|
declare function Checkbox({ className, ...props }: CheckboxProps): react.JSX.Element;
|
|
390
253
|
|
|
391
254
|
/**
|
|
392
|
-
*
|
|
255
|
+
* Inline code, inside prose.
|
|
393
256
|
*
|
|
394
|
-
*
|
|
395
|
-
*
|
|
396
|
-
* `<code>`
|
|
397
|
-
*
|
|
257
|
+
* It exists because it did not: every consumer wrote `<code className="font-mono">`
|
|
258
|
+
* by hand, and the brand story did it four times in a single paragraph. A bare
|
|
259
|
+
* `<code>` inherits the paragraph's size, so inside `body` (18px) you saw an
|
|
260
|
+
* 18px mono that the document has nowhere.
|
|
398
261
|
*
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
262
|
+
* It is not `CodeBlock`. The block is an island of dark theme over hull with a
|
|
263
|
+
* bar and a copy button; this is one word inside a sentence, which is why it
|
|
264
|
+
* stays on the page surface instead of inverting the theme.
|
|
402
265
|
*/
|
|
403
266
|
type CodeProps = ComponentPropsWithoutRef<'code'>;
|
|
404
267
|
declare function Code({ className, ...props }: CodeProps): react.JSX.Element;
|
|
405
268
|
|
|
406
269
|
type DateFieldProps = Omit<ComponentPropsWithoutRef<'input'>, 'type'> & {
|
|
407
270
|
invalid?: boolean | undefined;
|
|
408
|
-
/**
|
|
271
|
+
/** Adds the time to the field. It is the native `datetime-local`. */
|
|
409
272
|
withTime?: boolean | undefined;
|
|
410
273
|
};
|
|
411
274
|
/**
|
|
412
|
-
*
|
|
275
|
+
* A date field on the native control, not on a calendar of our own.
|
|
413
276
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
418
|
-
*
|
|
419
|
-
* sin trabajo.
|
|
277
|
+
* It is a deliberate decision: `react-day-picker` would have been the library's
|
|
278
|
+
* first heavy dependency, with its own CSS and its own month-change animation —
|
|
279
|
+
* which the system does not allow. The native control brings the OS keyboard,
|
|
280
|
+
* the format matching the user's language and screen-reader support for free,
|
|
281
|
+
* which is more than a bespoke calendar gives without work.
|
|
420
282
|
*
|
|
421
|
-
*
|
|
422
|
-
*
|
|
283
|
+
* It covers picking a date inside a form. A navigable month calendar is a
|
|
284
|
+
* different thing and lives in the project that needs it.
|
|
423
285
|
*
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
286
|
+
* The native picker icon is tinted through `color-scheme`, which is the only
|
|
287
|
+
* thing the browser lets you control: it is tied to the active mode so no little
|
|
288
|
+
* white square shows up over abyss.
|
|
427
289
|
*/
|
|
428
290
|
declare function DateField({ className, invalid, withTime, ...props }: DateFieldProps): react.JSX.Element;
|
|
429
291
|
|
|
@@ -431,7 +293,7 @@ declare const Dialog: react.FC<DialogPrimitive.DialogProps>;
|
|
|
431
293
|
declare const DialogTrigger: react.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
432
294
|
declare const DialogClose: react.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & react.RefAttributes<HTMLButtonElement>>;
|
|
433
295
|
declare function DialogOverlay({ className, ...props }: ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>): react.JSX.Element;
|
|
434
|
-
/**
|
|
296
|
+
/** No entrance animation: there is no scale or displacement in the system. */
|
|
435
297
|
declare function DialogContent({ className, children, ...props }: ComponentPropsWithoutRef<typeof DialogPrimitive.Content>): react.JSX.Element;
|
|
436
298
|
declare function DialogHeader({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
437
299
|
declare function DialogFooter({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
@@ -443,7 +305,7 @@ declare const DropdownMenuTrigger: react.ForwardRefExoticComponent<DropdownMenuP
|
|
|
443
305
|
declare const DropdownMenuGroup: react.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuGroupProps & react.RefAttributes<HTMLDivElement>>;
|
|
444
306
|
declare const DropdownMenuRadioGroup: react.ForwardRefExoticComponent<DropdownMenuPrimitive.DropdownMenuRadioGroupProps & react.RefAttributes<HTMLDivElement>>;
|
|
445
307
|
declare const DropdownMenuSub: react.FC<DropdownMenuPrimitive.DropdownMenuSubProps>;
|
|
446
|
-
/**
|
|
308
|
+
/** No entrance animation: the menu appears, it does not unfold. */
|
|
447
309
|
declare function DropdownMenuContent({ className, sideOffset, ...props }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>): react.JSX.Element;
|
|
448
310
|
declare function DropdownMenuItem({ className, ...props }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item>): react.JSX.Element;
|
|
449
311
|
declare function DropdownMenuCheckboxItem({ className, children, ...props }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>): react.JSX.Element;
|
|
@@ -454,7 +316,7 @@ declare function DropdownMenuSubTrigger({ className, children, ...props }: Compo
|
|
|
454
316
|
declare function DropdownMenuSubContent({ className, ...props }: ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>): react.JSX.Element;
|
|
455
317
|
|
|
456
318
|
type InputProps = ComponentPropsWithoutRef<'input'> & {
|
|
457
|
-
/**
|
|
319
|
+
/** Marks the control as invalid and tints the border. */
|
|
458
320
|
invalid?: boolean;
|
|
459
321
|
};
|
|
460
322
|
declare function Input({ className, invalid, ...props }: InputProps): react.JSX.Element;
|
|
@@ -474,36 +336,37 @@ declare const Popover: react.FC<PopoverPrimitive.PopoverProps>;
|
|
|
474
336
|
declare const PopoverTrigger: react.ForwardRefExoticComponent<PopoverPrimitive.PopoverTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
475
337
|
declare const PopoverAnchor: react.ForwardRefExoticComponent<PopoverPrimitive.PopoverAnchorProps & react.RefAttributes<HTMLDivElement>>;
|
|
476
338
|
/**
|
|
477
|
-
* Radix
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
-
*
|
|
339
|
+
* Radix puts `role="dialog"` on the content, and a dialog with no accessible
|
|
340
|
+
* name says nothing to someone navigating with a screen reader. That is why the
|
|
341
|
+
* type demands one of the two: `aria-label` with the text, or `aria-labelledby`
|
|
342
|
+
* pointing at the title already visible on screen. It cannot be forgotten
|
|
343
|
+
* because it does not compile.
|
|
481
344
|
*/
|
|
482
|
-
type
|
|
345
|
+
type Labelled = {
|
|
483
346
|
'aria-label': string;
|
|
484
347
|
'aria-labelledby'?: never;
|
|
485
348
|
} | {
|
|
486
349
|
'aria-labelledby': string;
|
|
487
350
|
'aria-label'?: never;
|
|
488
351
|
};
|
|
489
|
-
type PopoverContentProps = ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> &
|
|
490
|
-
/**
|
|
352
|
+
type PopoverContentProps = ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> & Labelled;
|
|
353
|
+
/** No entrance animation: it appears where it will stay, like the rest. */
|
|
491
354
|
declare function PopoverContent({ className, align, sideOffset, ...props }: PopoverContentProps): react.JSX.Element;
|
|
492
355
|
|
|
493
356
|
type ProgressProps = ComponentPropsWithoutRef<typeof ProgressPrimitive.Root> & {
|
|
494
357
|
/**
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
358
|
+
* The bar's accessible name. It is mandatory on purpose: a progress bar with
|
|
359
|
+
* no name does not say what the progress is about, and no other part of the
|
|
360
|
+
* component can deduce it.
|
|
498
361
|
*/
|
|
499
362
|
label: string;
|
|
500
|
-
/**
|
|
363
|
+
/** Sand instead of biolume, for course progress. */
|
|
501
364
|
tone?: 'accent' | 'warm';
|
|
502
365
|
};
|
|
503
366
|
/**
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
367
|
+
* The indicator's width changes, it is not animated: the system animates neither
|
|
368
|
+
* scale nor displacement. `transition-standard` only covers color and border, so
|
|
369
|
+
* the width jump is immediate even with the class in place.
|
|
507
370
|
*/
|
|
508
371
|
declare function Progress({ className, value, label, tone, ...props }: ProgressProps): react.JSX.Element;
|
|
509
372
|
|
|
@@ -516,7 +379,7 @@ declare const Select: react.FC<SelectPrimitive.SelectProps>;
|
|
|
516
379
|
declare const SelectGroup: react.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & react.RefAttributes<HTMLDivElement>>;
|
|
517
380
|
declare const SelectValue: react.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & react.RefAttributes<HTMLSpanElement>>;
|
|
518
381
|
declare function SelectTrigger({ className, children, ...props }: ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>): react.JSX.Element;
|
|
519
|
-
/**
|
|
382
|
+
/** No entrance animation: the menu appears, it does not unfold. */
|
|
520
383
|
declare function SelectContent({ className, children, position, ...props }: ComponentPropsWithoutRef<typeof SelectPrimitive.Content>): react.JSX.Element;
|
|
521
384
|
declare function SelectLabel({ className, ...props }: ComponentPropsWithoutRef<typeof SelectPrimitive.Label>): react.JSX.Element;
|
|
522
385
|
declare function SelectItem({ className, children, ...props }: ComponentPropsWithoutRef<typeof SelectPrimitive.Item>): react.JSX.Element;
|
|
@@ -524,8 +387,8 @@ declare function SelectSeparator({ className, ...props }: ComponentPropsWithoutR
|
|
|
524
387
|
|
|
525
388
|
type SeparatorProps = ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>;
|
|
526
389
|
/**
|
|
527
|
-
* `hairline`,
|
|
528
|
-
*
|
|
390
|
+
* `hairline`, not `border`: a division between pieces of content is subtle by
|
|
391
|
+
* definition. To delimit a control there is `border`, which is another token.
|
|
529
392
|
*/
|
|
530
393
|
declare function Separator({ className, orientation, decorative, ...props }: SeparatorProps): react.JSX.Element;
|
|
531
394
|
|
|
@@ -533,14 +396,14 @@ declare const Sheet: react.FC<DialogPrimitive.DialogProps>;
|
|
|
533
396
|
declare const SheetTrigger: react.ForwardRefExoticComponent<DialogPrimitive.DialogTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
534
397
|
declare const SheetClose: react.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & react.RefAttributes<HTMLButtonElement>>;
|
|
535
398
|
/**
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
*
|
|
542
|
-
*
|
|
543
|
-
*
|
|
399
|
+
* The second and last exception to «no displacement», approved knowingly: a
|
|
400
|
+
* panel entering from an edge slides by definition, and held still it would be
|
|
401
|
+
* an off-centre modal.
|
|
402
|
+
*
|
|
403
|
+
* It lasts `--duration-standard` with `--ease-standard`, that is, the same time
|
|
404
|
+
* and the same curve as any color change in the system, so it introduces no new
|
|
405
|
+
* timing. It sits behind `motion-safe`: whoever asked for less motion sees it
|
|
406
|
+
* appear without sliding.
|
|
544
407
|
*/
|
|
545
408
|
declare const panel: (props?: ({
|
|
546
409
|
side?: "bottom" | "left" | "right" | "top" | null | undefined;
|
|
@@ -554,35 +417,36 @@ declare function SheetTitle({ className, ...props }: ComponentPropsWithoutRef<ty
|
|
|
554
417
|
declare function SheetDescription({ className, ...props }: ComponentPropsWithoutRef<typeof DialogPrimitive.Description>): react.JSX.Element;
|
|
555
418
|
|
|
556
419
|
/**
|
|
557
|
-
*
|
|
420
|
+
* A 1.4s linear sweep, from the document.
|
|
558
421
|
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
563
|
-
*
|
|
422
|
+
* It is the THIRD and last exception to «the system does not animate», alongside
|
|
423
|
+
* the button spinner and the side panel. All three are feedback about PROGRESS
|
|
424
|
+
* and not about state, which is the criterion: a block that is still and a block
|
|
425
|
+
* that will never load look exactly the same, and the skeleton exists to say
|
|
426
|
+
* «this is on its way», not «this is empty».
|
|
564
427
|
*
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
428
|
+
* It sits behind `motion-safe`, so it switches itself off for anyone who asked
|
|
429
|
+
* for less motion — and what is left is the block on `surfaceRaised`, which
|
|
430
|
+
* still communicates the shape of what is coming.
|
|
568
431
|
*
|
|
569
|
-
* `still`
|
|
570
|
-
*
|
|
432
|
+
* `still` turns it off by hand, for long tables: twenty rows sweeping at once is
|
|
433
|
+
* a strobe, not a load.
|
|
571
434
|
*/
|
|
572
435
|
type SkeletonProps = ComponentPropsWithoutRef<'div'> & {
|
|
573
|
-
/**
|
|
436
|
+
/** Turns the sweep off. For long lists, where many at once are dizzying. */
|
|
574
437
|
still?: boolean | undefined;
|
|
575
438
|
};
|
|
576
439
|
declare function Skeleton({ className, still, ...props }: SkeletonProps): react.JSX.Element;
|
|
577
440
|
|
|
578
441
|
type SwitchProps = ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>;
|
|
579
442
|
/**
|
|
580
|
-
*
|
|
581
|
-
*
|
|
443
|
+
* The knob changes position, but is not animated while doing so: the position IS
|
|
444
|
+
* the state, not a transition. The only thing that transitions is the track's
|
|
445
|
+
* color.
|
|
582
446
|
*/
|
|
583
447
|
declare function Switch({ className, ...props }: SwitchProps): react.JSX.Element;
|
|
584
448
|
|
|
585
|
-
/**
|
|
449
|
+
/** The container scrolls horizontally: the page never does. */
|
|
586
450
|
declare function Table({ className, ...props }: ComponentPropsWithoutRef<'table'>): react.JSX.Element;
|
|
587
451
|
declare function TableHeader({ className, ...props }: ComponentPropsWithoutRef<'thead'>): react.JSX.Element;
|
|
588
452
|
declare function TableBody({ className, ...props }: ComponentPropsWithoutRef<'tbody'>): react.JSX.Element;
|
|
@@ -605,69 +469,72 @@ declare function Textarea({ className, invalid, ...props }: TextareaProps): reac
|
|
|
605
469
|
|
|
606
470
|
declare const ToastAction: react.ForwardRefExoticComponent<ToastPrimitive.ToastActionProps & react.RefAttributes<HTMLButtonElement>>;
|
|
607
471
|
declare const toast$1: (props?: ({
|
|
608
|
-
variant?: "error" | "
|
|
472
|
+
variant?: "error" | "neutral" | "success" | null | undefined;
|
|
609
473
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
610
474
|
type ToastProps = ComponentPropsWithoutRef<typeof ToastPrimitive.Root> & VariantProps<typeof toast$1>;
|
|
611
475
|
|
|
612
476
|
/**
|
|
613
|
-
*
|
|
614
|
-
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
*
|
|
619
|
-
*
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
626
|
-
*
|
|
627
|
-
*
|
|
628
|
-
*
|
|
629
|
-
*
|
|
630
|
-
*
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
477
|
+
* The imperative face of Radix's `Toast`: `toast('Guardado')` from anywhere,
|
|
478
|
+
* without threading the notice through props down to the component that fires
|
|
479
|
+
* it.
|
|
480
|
+
*
|
|
481
|
+
* It exists because two projects were pulling in `sonner` for this. `Toast`
|
|
482
|
+
* covers the same role and has a different shape: Radix is declarative with a
|
|
483
|
+
* provider, and to show a notice from a `fetch`'s `catch` you have to lift state
|
|
484
|
+
* up to where the provider lives. That is exactly what `sonner` avoids, and it
|
|
485
|
+
* is a real need, not an API preference.
|
|
486
|
+
*
|
|
487
|
+
* The alternative was for the two projects to adapt. It was ruled out: the
|
|
488
|
+
* notice is fired by the data layer, which has no component nearby to hang a
|
|
489
|
+
* `useState` on — nor should it.
|
|
490
|
+
*
|
|
491
|
+
* What was NOT copied from `sonner` is the whole catalogue. There is no
|
|
492
|
+
* `toast.promise`, no `toast.custom`, no configurable positions, no stacking
|
|
493
|
+
* with perspective: they are four variations on the same thing and each one is
|
|
494
|
+
* public surface that has to be maintained. What is here are the three shapes
|
|
495
|
+
* the projects actually use — neutral, success, error — plus `dismiss`, and
|
|
496
|
+
* nothing else.
|
|
497
|
+
*
|
|
498
|
+
* State lives in a module, not in a context, because the whole point is being
|
|
499
|
+
* callable from outside the tree. `Toaster` subscribes with
|
|
500
|
+
* `useSyncExternalStore`, which is React 19's way of reading external state
|
|
501
|
+
* without effects or cascading renders.
|
|
636
502
|
*/
|
|
637
503
|
type ToastVariant = NonNullable<ToastProps['variant']>;
|
|
638
504
|
type ToastOptions = {
|
|
639
|
-
/**
|
|
505
|
+
/** The first line, in bold. Without it the notice is a single sentence. */
|
|
640
506
|
title?: ReactNode;
|
|
641
507
|
description?: ReactNode;
|
|
642
508
|
variant?: ToastVariant;
|
|
643
|
-
/**
|
|
509
|
+
/** Milliseconds on screen. `Infinity` leaves it until it is closed by hand. */
|
|
644
510
|
duration?: number;
|
|
645
|
-
/**
|
|
511
|
+
/** A `ToastAction`, if the notice offers an undo. */
|
|
646
512
|
action?: ReactNode;
|
|
647
513
|
};
|
|
648
|
-
type
|
|
649
|
-
(
|
|
650
|
-
success: (
|
|
651
|
-
error: (
|
|
514
|
+
type Trigger = {
|
|
515
|
+
(message: ReactNode, options?: ToastOptions): string;
|
|
516
|
+
success: (message: ReactNode, options?: ToastOptions) => string;
|
|
517
|
+
error: (message: ReactNode, options?: ToastOptions) => string;
|
|
652
518
|
dismiss: (id?: string) => void;
|
|
653
519
|
};
|
|
654
520
|
/**
|
|
655
|
-
*
|
|
656
|
-
*
|
|
521
|
+
* Fires a notice. It returns its id, which is what you keep in order to close it
|
|
522
|
+
* by hand — the «guardando…» case that gets replaced when the request finishes.
|
|
657
523
|
*/
|
|
658
|
-
declare const toast:
|
|
524
|
+
declare const toast: Trigger;
|
|
659
525
|
type ToasterProps = {
|
|
660
|
-
/**
|
|
526
|
+
/** How long a notice lasts when it does not say otherwise. */
|
|
661
527
|
duration?: number;
|
|
662
528
|
/**
|
|
663
|
-
*
|
|
664
|
-
*
|
|
529
|
+
* The name of the landmark Radix creates for the notices region. It is
|
|
530
|
+
* translated because a screen reader reads it, and Radix's default is in
|
|
531
|
+
* English.
|
|
665
532
|
*/
|
|
666
533
|
label?: string;
|
|
667
534
|
};
|
|
668
535
|
/**
|
|
669
|
-
*
|
|
670
|
-
*
|
|
536
|
+
* Mount it ONCE, as high in the tree as possible. Two mounted `Toaster`s paint
|
|
537
|
+
* every notice twice: the list belongs to the module, not to the instance.
|
|
671
538
|
*/
|
|
672
539
|
declare function Toaster({ duration, label }: ToasterProps): react.JSX.Element;
|
|
673
540
|
|
|
@@ -676,126 +543,148 @@ declare const Tooltip: react.FC<TooltipPrimitive.TooltipProps>;
|
|
|
676
543
|
declare const TooltipTrigger: react.ForwardRefExoticComponent<TooltipPrimitive.TooltipTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
677
544
|
declare function TooltipContent({ className, sideOffset, ...props }: ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>): react.JSX.Element;
|
|
678
545
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
type TextProps = Omit<ComponentPropsWithoutRef<'p'>, 'color'> & VariantProps<typeof texto> & {
|
|
686
|
-
/** Etiqueta HTML. Por defecto, la que corresponde a la escala. */
|
|
687
|
-
as?: Etiqueta;
|
|
688
|
-
/** Renderiza el hijo en vez de crear un elemento, para envolver un enlace. */
|
|
546
|
+
/** Allowed tags. The list is short on purpose: this is not a styled `div`. */
|
|
547
|
+
type Label = 'h1' | 'h2' | 'h3' | 'h4' | 'p' | 'span' | 'strong' | 'em' | 'figcaption' | 'caption' | 'legend' | 'dt' | 'dd' | 'li';
|
|
548
|
+
type TextProps = Omit<ComponentPropsWithoutRef<'p'>, 'color'> & VariantProps<typeof text> & {
|
|
549
|
+
/** HTML tag. Defaults to whichever one matches the scale. */
|
|
550
|
+
as?: Label;
|
|
551
|
+
/** Renders the child instead of creating an element, to wrap a link. */
|
|
689
552
|
asChild?: boolean;
|
|
690
553
|
/**
|
|
691
|
-
*
|
|
692
|
-
*
|
|
554
|
+
* Clamps the line to 68ch. On by default for `body`, the only scale meant
|
|
555
|
+
* to be read in long paragraphs.
|
|
693
556
|
*/
|
|
694
557
|
measure?: boolean;
|
|
695
558
|
};
|
|
696
559
|
declare function Text({ className, variant, tone, as, asChild, measure, children, ...props }: TextProps): react.JSX.Element;
|
|
697
560
|
|
|
698
|
-
|
|
561
|
+
/**
|
|
562
|
+
* The shared shell of the cards. The class list lives in `variants/card.ts`,
|
|
563
|
+
* which brings no React, so `links` can read it instead of copying it.
|
|
564
|
+
*/
|
|
565
|
+
type CardShellProps = ComponentPropsWithoutRef<'a'> & {
|
|
699
566
|
/**
|
|
700
|
-
*
|
|
701
|
-
*
|
|
567
|
+
* Renders the child instead of an `<a>`. It is how Next's or Astro's `Link`
|
|
568
|
+
* plugs in without the library depending on any router.
|
|
702
569
|
*/
|
|
703
570
|
asChild?: boolean | undefined;
|
|
704
571
|
children: ReactNode;
|
|
705
572
|
};
|
|
706
573
|
|
|
707
|
-
type ArticleCardProps = Omit<
|
|
574
|
+
type ArticleCardProps = Omit<CardShellProps, 'children' | 'title'> & {
|
|
708
575
|
title: ReactNode;
|
|
709
|
-
/**
|
|
576
|
+
/** Standfirst. Clamped to two lines so the grid does not fall out of line. */
|
|
710
577
|
excerpt?: ReactNode;
|
|
711
|
-
/**
|
|
578
|
+
/** Date already formatted by the project: the library imposes no locale. */
|
|
712
579
|
date?: ReactNode;
|
|
713
|
-
/**
|
|
580
|
+
/** The `<time>` element's `datetime` value, in ISO. */
|
|
714
581
|
dateTime?: string | undefined;
|
|
715
582
|
readingMinutes?: number | undefined;
|
|
716
583
|
tags?: readonly string[] | undefined;
|
|
717
584
|
/**
|
|
718
|
-
*
|
|
719
|
-
*
|
|
585
|
+
* The headline level. `h3` by default: a lone card in a grid does not earn a
|
|
586
|
+
* level its position does not give it.
|
|
720
587
|
*
|
|
721
|
-
*
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
588
|
+
* Restricted to two values on purpose. The listing page — where the cards ARE
|
|
589
|
+
* the section's main heading — needs `h2`, and that intent was lost with a
|
|
590
|
+
* constant; opening it up to `h4` or `h5`, on the other hand, is an invitation
|
|
591
|
+
* to skip levels, which is the failure the constant was preventing.
|
|
725
592
|
*/
|
|
726
593
|
headingLevel?: 2 | 3;
|
|
594
|
+
/**
|
|
595
|
+
* Renders each tag through the child, so an E2E suite can reach it.
|
|
596
|
+
*
|
|
597
|
+
* The tags are the one composed part a project could not get at: they arrive
|
|
598
|
+
* as strings and the component turns them into badges, so a test had to select
|
|
599
|
+
* them by structure — `article > div > span` — or by a style class. The second
|
|
600
|
+
* one already broke the blog's suite once, because a style class is not a
|
|
601
|
+
* contract: it changes when the style changes.
|
|
602
|
+
*
|
|
603
|
+
* The library keeps the classes and the rule — a tag is a category pill, and
|
|
604
|
+
* that does not become negotiable — and hands over only the element and its
|
|
605
|
+
* attributes:
|
|
606
|
+
*
|
|
607
|
+
* tagAsChild={({ tag }) => <span data-testid={`tag-${tag}`}>{tag}</span>}
|
|
608
|
+
*
|
|
609
|
+
* It is the shape `linkAsChild` already has in `Breadcrumb` and
|
|
610
|
+
* `TableOfContents`, and it is deliberately the same: one idiom for «the
|
|
611
|
+
* project supplies the element, the library supplies the styling».
|
|
612
|
+
*/
|
|
613
|
+
tagAsChild?: ((props: {
|
|
614
|
+
tag: string;
|
|
615
|
+
children: ReactNode;
|
|
616
|
+
}) => ReactNode) | undefined;
|
|
727
617
|
};
|
|
728
618
|
/**
|
|
729
|
-
*
|
|
730
|
-
* lectura`
|
|
731
|
-
* dos cosas.
|
|
619
|
+
* The metadata line uses `meta` and not `eyebrow`: `18 ago 2026 · 8 min de
|
|
620
|
+
* lectura` is a datum, not an overline, and in small caps it was neither.
|
|
732
621
|
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
622
|
+
* The tags are the CATEGORY family — a lowercase sand pill — not the status one.
|
|
623
|
+
* A slug is something you read as `engineering-culture`.
|
|
735
624
|
*/
|
|
736
|
-
declare function ArticleCard({ title, excerpt, date, dateTime, readingMinutes, tags, headingLevel, className, ...props }: ArticleCardProps): react.JSX.Element;
|
|
625
|
+
declare function ArticleCard({ title, excerpt, date, dateTime, readingMinutes, tags, headingLevel, tagAsChild, className, ...props }: ArticleCardProps): react.JSX.Element;
|
|
737
626
|
|
|
738
627
|
/**
|
|
739
|
-
*
|
|
740
|
-
* muted.
|
|
628
|
+
* The byline at the foot of the article: 52px avatar, 15/500 name and the role
|
|
629
|
+
* in muted mono. Three data points, not one more.
|
|
741
630
|
*
|
|
742
|
-
*
|
|
743
|
-
* `PageHeader`.
|
|
744
|
-
*
|
|
745
|
-
* manual
|
|
631
|
+
* It does NOT take a mascot face, and that is not an oversight: it is the same
|
|
632
|
+
* rule as `PageHeader`. A face here would be humour in the exact place where the
|
|
633
|
+
* reader is deciding whether the author knows what they are talking about —
|
|
634
|
+
* precisely what the manual's contract excludes.
|
|
746
635
|
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
636
|
+
* The manual's avatar is the head inside a solid shark-blue circle, never the
|
|
637
|
+
* expressive face on its own.
|
|
749
638
|
*/
|
|
750
639
|
type AuthorCardProps = Omit<ComponentPropsWithoutRef<'div'>, 'role'> & {
|
|
751
640
|
name: string;
|
|
752
|
-
/**
|
|
641
|
+
/** The role. It goes in mono: it is a datum, not a sentence. */
|
|
753
642
|
role?: ReactNode;
|
|
754
|
-
/**
|
|
643
|
+
/** The avatar's URL. Without it the initials are shown. */
|
|
755
644
|
src?: string | undefined;
|
|
756
|
-
/**
|
|
645
|
+
/** One or two sentences. It clamps itself to 68ch. */
|
|
757
646
|
bio?: ReactNode;
|
|
758
|
-
/**
|
|
647
|
+
/** Links or a contact button. */
|
|
759
648
|
action?: ReactNode;
|
|
760
649
|
};
|
|
761
650
|
declare function AuthorCard({ name, role, src, bio, action, className, ...props }: AuthorCardProps): react.JSX.Element;
|
|
762
651
|
|
|
763
652
|
/**
|
|
764
|
-
*
|
|
653
|
+
* Migrated from `eduardoalvarez.dev/src/components/audio-player/index.tsx`.
|
|
765
654
|
*
|
|
766
|
-
*
|
|
767
|
-
*
|
|
768
|
-
*
|
|
769
|
-
*
|
|
655
|
+
* The logic was not rewritten: the three modes, the floating player, the ±15s
|
|
656
|
+
* skips, the 1 → 1.25 → 1.5 → 1.75 → 2 speed cycle and the volume with mute are
|
|
657
|
+
* the same. What changed is the skin and the two dependencies a package cannot
|
|
658
|
+
* have:
|
|
770
659
|
*
|
|
771
|
-
* - `Icon`
|
|
772
|
-
*
|
|
773
|
-
* - `trackEvent`
|
|
774
|
-
*
|
|
660
|
+
* - the portfolio's `Icon` → the glyphs now live in `src/lib/glyphs.tsx`, with
|
|
661
|
+
* the same paths.
|
|
662
|
+
* - analytics' `trackEvent` → the `onFirstPlay` prop, which the consumer wires
|
|
663
|
+
* to whatever they use. It still fires exactly once per load.
|
|
775
664
|
*
|
|
776
|
-
*
|
|
665
|
+
* And three things the system does not allow:
|
|
777
666
|
*
|
|
778
|
-
* -
|
|
779
|
-
*
|
|
780
|
-
* -
|
|
781
|
-
* -
|
|
667
|
+
* - The waveform no longer animates `scaleY`. The bars are still there and still
|
|
668
|
+
* tell playback from pause by opacity, but they do not scale.
|
|
669
|
+
* - The floating player appears and disappears instead of sliding.
|
|
670
|
+
* - The progress bar no longer interpolates its width.
|
|
782
671
|
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
672
|
+
* The loading spinner's spin stays, with the same justification as in `Button`:
|
|
673
|
+
* it is feedback about progress, not about state, and it sits in `motion-safe`.
|
|
785
674
|
*/
|
|
786
675
|
type AudioPlayerMode = 'full' | 'compact' | 'banner';
|
|
787
676
|
type AudioPlayerProps = {
|
|
788
677
|
src: string;
|
|
789
678
|
title?: string;
|
|
790
679
|
/**
|
|
791
|
-
* `full`
|
|
792
|
-
*
|
|
793
|
-
*
|
|
680
|
+
* `full` for podcast pages, `compact` for sidebars and `banner` for articles
|
|
681
|
+
* with narration. `compact` and `banner` also bring the floating player when
|
|
682
|
+
* the static one leaves the viewport.
|
|
794
683
|
*/
|
|
795
684
|
mode?: AudioPlayerMode | undefined;
|
|
796
685
|
/**
|
|
797
|
-
*
|
|
798
|
-
*
|
|
686
|
+
* Called once per load, the first time the audio starts.
|
|
687
|
+
* This is where the project hooks up its analytics; the library ships none.
|
|
799
688
|
*/
|
|
800
689
|
onFirstPlay?: ((title?: string) => void) | undefined;
|
|
801
690
|
};
|
|
@@ -803,44 +692,44 @@ declare function AudioPlayer({ src, title, mode, onFirstPlay }: AudioPlayerProps
|
|
|
803
692
|
|
|
804
693
|
type BlockquoteProps = Omit<ComponentPropsWithoutRef<'blockquote'>, 'cite'> & {
|
|
805
694
|
children: ReactNode;
|
|
806
|
-
/**
|
|
695
|
+
/** Who said it. Marked up as `<cite>`. */
|
|
807
696
|
author?: ReactNode;
|
|
808
|
-
/**
|
|
697
|
+
/** Where they said it: a talk, an article, a conversation. */
|
|
809
698
|
source?: ReactNode;
|
|
810
699
|
};
|
|
811
700
|
/**
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
701
|
+
* The side bar is `accent`, the interactive color, because a quotation is
|
|
702
|
+
* somebody else's voice entering the text. It carries no decorative quote marks:
|
|
703
|
+
* the system's glyphs are SVG, and an ornamental quote adds nothing the border
|
|
704
|
+
* and the indent do not already say.
|
|
816
705
|
*/
|
|
817
706
|
declare function Blockquote({ children, author, source, className, ...props }: BlockquoteProps): react.JSX.Element;
|
|
818
707
|
|
|
819
708
|
/**
|
|
820
|
-
*
|
|
709
|
+
* The path as a path: `~ / artículos / cómo-escalar-un-equipo`.
|
|
821
710
|
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
711
|
+
* The `~` is neither decoration nor a house icon: it is the filesystem's home,
|
|
712
|
+
* which is why the breadcrumb is mono and not sans. The separators use `border`
|
|
713
|
+
* (#22414F), the faintest token that still reads as a line.
|
|
825
714
|
*
|
|
826
|
-
*
|
|
827
|
-
* `aria-current="page"`.
|
|
828
|
-
*
|
|
715
|
+
* The last crumb is the current page, so it is not a link and carries
|
|
716
|
+
* `aria-current="page"`. That is the difference between an accessible breadcrumb
|
|
717
|
+
* and four consecutive links, one of which goes nowhere.
|
|
829
718
|
*/
|
|
830
|
-
type
|
|
719
|
+
type Crumb = {
|
|
831
720
|
label: ReactNode;
|
|
832
|
-
/**
|
|
721
|
+
/** With no `href`, the crumb is text. The last one should never carry it. */
|
|
833
722
|
href?: string | undefined;
|
|
834
723
|
};
|
|
835
724
|
type BreadcrumbProps = Omit<ComponentPropsWithoutRef<'nav'>, 'children'> & {
|
|
836
|
-
items: readonly
|
|
837
|
-
/**
|
|
725
|
+
items: readonly Crumb[];
|
|
726
|
+
/** Where the `~` goes. The site root by default. */
|
|
838
727
|
homeHref?: string;
|
|
839
|
-
/**
|
|
728
|
+
/** Accessible label for the `~`, which otherwise reads as a stray tilde. */
|
|
840
729
|
homeLabel?: string;
|
|
841
730
|
/**
|
|
842
|
-
*
|
|
843
|
-
*
|
|
731
|
+
* Renders the links through the child, to plug in Next's or Astro's `Link`.
|
|
732
|
+
* It receives each `href` in the Slot's `props`.
|
|
844
733
|
*/
|
|
845
734
|
linkAsChild?: ((props: {
|
|
846
735
|
href: string;
|
|
@@ -850,143 +739,149 @@ type BreadcrumbProps = Omit<ComponentPropsWithoutRef<'nav'>, 'children'> & {
|
|
|
850
739
|
declare function Breadcrumb({ items, homeHref, homeLabel, linkAsChild, className, ...props }: BreadcrumbProps): react.JSX.Element;
|
|
851
740
|
|
|
852
741
|
/**
|
|
853
|
-
* `brand.hull`
|
|
854
|
-
*
|
|
855
|
-
* `data-theme="dark"`:
|
|
856
|
-
*
|
|
857
|
-
*
|
|
742
|
+
* `brand.hull` is «hull · outline and the background of code blocks», so a code
|
|
743
|
+
* block is dark in light mode too. That is why the root declares
|
|
744
|
+
* `data-theme="dark"`: everything inside — ink, hairline, accent — switches to
|
|
745
|
+
* the dark palette regardless of the page's theme. It is the system's only
|
|
746
|
+
* island of inverted theme, and it is deliberate.
|
|
858
747
|
*/
|
|
859
748
|
type CodeBlockProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
|
|
860
|
-
/**
|
|
749
|
+
/** The already-highlighted code, or flat text. */
|
|
861
750
|
children: ReactNode;
|
|
862
|
-
/**
|
|
751
|
+
/** The language label. Shown in the top bar. */
|
|
863
752
|
language?: string | undefined;
|
|
864
|
-
/**
|
|
753
|
+
/** The text copied to the clipboard. Without it, the button is not shown. */
|
|
865
754
|
copyText?: string | undefined;
|
|
866
755
|
};
|
|
867
756
|
declare function CodeBlock({ children, language, copyText, className, ...props }: CodeBlockProps): react.JSX.Element;
|
|
868
757
|
|
|
869
|
-
type CourseCardProps = Omit<
|
|
758
|
+
type CourseCardProps = Omit<CardShellProps, 'children' | 'title'> & {
|
|
870
759
|
title: ReactNode;
|
|
871
760
|
summary?: ReactNode;
|
|
872
|
-
/**
|
|
761
|
+
/** Level, duration, number of lessons: whatever the project wants to list. */
|
|
873
762
|
meta?: readonly ReactNode[] | undefined;
|
|
874
|
-
/**
|
|
763
|
+
/** Status label: «próximamente», «gratis», «nuevo». */
|
|
875
764
|
status?: ReactNode;
|
|
876
765
|
/**
|
|
877
|
-
*
|
|
878
|
-
*
|
|
766
|
+
* Percentage completed. It only makes sense for someone already enrolled;
|
|
767
|
+
* when passed, the bar goes in sand, which is the color of course progress.
|
|
879
768
|
*/
|
|
880
769
|
progress?: number | undefined;
|
|
881
770
|
};
|
|
882
771
|
declare function CourseCard({ title, summary, meta, status, progress, className, ...props }: CourseCardProps): react.JSX.Element;
|
|
883
772
|
|
|
884
773
|
/**
|
|
885
|
-
*
|
|
774
|
+
* The mascot's most important rule, finally as code.
|
|
886
775
|
*
|
|
887
|
-
* `
|
|
888
|
-
* `EmptyState`
|
|
889
|
-
*
|
|
890
|
-
*
|
|
776
|
+
* `catalog.ts` and `page-header/index.tsx` both cited it — «that is why
|
|
777
|
+
* `EmptyState` takes a face and `PageHeader` does not» — and the component did
|
|
778
|
+
* not exist, so the rule lived in a comment about a ghost component. Once the
|
|
779
|
+
* brand story started repeating the sentence, it was also a published promise.
|
|
891
780
|
*
|
|
892
|
-
* `
|
|
893
|
-
*
|
|
894
|
-
*
|
|
895
|
-
*
|
|
781
|
+
* `expression` is MANDATORY and not optional: an empty state without a face is
|
|
782
|
+
* half the component. It is the only place, alongside the 404, the server error,
|
|
783
|
+
* course progress, celebration, the toast and the newsletter's «sin spam», where
|
|
784
|
+
* a face may appear.
|
|
896
785
|
*/
|
|
897
786
|
type EmptyStateProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
|
|
898
|
-
/**
|
|
899
|
-
|
|
787
|
+
/** The face. Mandatory: without it this is a centred paragraph. */
|
|
788
|
+
expression: Face;
|
|
900
789
|
title: ReactNode;
|
|
901
|
-
/**
|
|
790
|
+
/** One line explaining what is missing or what to do. */
|
|
902
791
|
description?: ReactNode;
|
|
903
|
-
/**
|
|
792
|
+
/** The action that gets you out of the empty state. Usually a tertiary button. */
|
|
904
793
|
action?: ReactNode;
|
|
905
|
-
/**
|
|
794
|
+
/** Where the brand PNGs are served from. */
|
|
906
795
|
basePath?: string | undefined;
|
|
907
796
|
};
|
|
908
|
-
declare function EmptyState({
|
|
797
|
+
declare function EmptyState({ expression, title, description, action, basePath, className, ...props }: EmptyStateProps): react.JSX.Element;
|
|
909
798
|
|
|
910
799
|
/**
|
|
911
|
-
*
|
|
800
|
+
* The calendar with events: seeing them, creating, editing and deleting them.
|
|
912
801
|
*
|
|
913
|
-
* `Calendar`
|
|
914
|
-
*
|
|
915
|
-
*
|
|
916
|
-
*
|
|
802
|
+
* `Calendar` is the primitive and remains a grid of dates: it serves to PICK a
|
|
803
|
+
* day and knows nothing about content. This is the schedule, which is why it
|
|
804
|
+
* lives in `components/` and not next to that one — it has state, it has a form
|
|
805
|
+
* and it encodes what a day with things in it looks like.
|
|
917
806
|
*
|
|
918
|
-
*
|
|
919
|
-
* `onCreateEvent`, `onUpdateEvent`
|
|
920
|
-
*
|
|
921
|
-
*
|
|
807
|
+
* It is presentational, like the rest of the library: it takes `events` and
|
|
808
|
+
* emits `onCreateEvent`, `onUpdateEvent` and `onDeleteEvent`. It stores nothing,
|
|
809
|
+
* calls no API and generates no ids — the id is set by whoever persists,
|
|
810
|
+
* because they are the one who knows whether it comes from a database or a file.
|
|
922
811
|
*
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
812
|
+
* TWO COLUMNS and not a popover over the day. The popover is what everyone does
|
|
813
|
+
* and it hides the content behind a click: with the list beside it, a month with
|
|
814
|
+
* fifteen events reads at a glance and the selected day does not cover the grid.
|
|
815
|
+
* On a narrow screen they stack.
|
|
927
816
|
*
|
|
928
|
-
*
|
|
929
|
-
*
|
|
817
|
+
* Deleting goes through `AlertDialog` and not through a direct button. It is
|
|
818
|
+
* precisely the case it exists for: a destructive action with no undo.
|
|
930
819
|
*/
|
|
931
|
-
type
|
|
932
|
-
/**
|
|
820
|
+
type CalendarEvent = {
|
|
821
|
+
/** Set by the project. The library never invents it. */
|
|
933
822
|
id: string;
|
|
934
|
-
/**
|
|
823
|
+
/** When it starts, with a time. */
|
|
935
824
|
start: Date;
|
|
936
825
|
title: string;
|
|
937
|
-
/** `warm`
|
|
826
|
+
/** `warm` when the event is the problem, same as in `Stat`. */
|
|
938
827
|
tone?: 'accent' | 'warm' | undefined;
|
|
939
828
|
};
|
|
940
829
|
type EventCalendarProps = Omit<ComponentPropsWithoutRef<'div'>, 'onSelect' | 'children'> & {
|
|
941
|
-
events: readonly
|
|
942
|
-
/**
|
|
943
|
-
onCreateEvent?: ((
|
|
944
|
-
onUpdateEvent?: ((
|
|
830
|
+
events: readonly CalendarEvent[];
|
|
831
|
+
/** Without it, the schedule is read-only and the form is not painted. */
|
|
832
|
+
onCreateEvent?: ((event: Omit<CalendarEvent, 'id'>) => void) | undefined;
|
|
833
|
+
onUpdateEvent?: ((event: CalendarEvent) => void) | undefined;
|
|
945
834
|
onDeleteEvent?: ((id: string) => void) | undefined;
|
|
946
|
-
/**
|
|
835
|
+
/** Selected day, if the project controls it. Without it, it starts on today. */
|
|
947
836
|
selected?: Date | undefined;
|
|
948
|
-
onSelectDay?: ((
|
|
949
|
-
/**
|
|
950
|
-
formatDay?: ((
|
|
951
|
-
formatTime?: ((
|
|
952
|
-
/**
|
|
837
|
+
onSelectDay?: ((day: Date) => void) | undefined;
|
|
838
|
+
/** The panel's heading. Defaults to date-fns' `es`, like `Calendar`. */
|
|
839
|
+
formatDay?: ((day: Date) => string) | undefined;
|
|
840
|
+
formatTime?: ((date: Date) => string) | undefined;
|
|
841
|
+
/** The panel's text when the chosen day has nothing on it. */
|
|
953
842
|
emptyMessage?: ReactNode;
|
|
954
843
|
};
|
|
955
844
|
declare function EventCalendar({ events, onCreateEvent, onUpdateEvent, onDeleteEvent, selected, onSelectDay, formatDay, formatTime, emptyMessage, className, ...props }: EventCalendarProps): react.JSX.Element;
|
|
956
845
|
|
|
957
846
|
/**
|
|
958
|
-
*
|
|
847
|
+
* The footer, and the site's CLI signature: `$ cd ~/eduardoalvarez.dev/2026`.
|
|
848
|
+
*
|
|
849
|
+
* The domain comes from `naming.domain` and not from a hand-written string, for
|
|
850
|
+
* the same reason as the wordmark: if it changes, it changes in all five
|
|
851
|
+
* projects at once.
|
|
959
852
|
*
|
|
960
|
-
*
|
|
961
|
-
*
|
|
962
|
-
*
|
|
853
|
+
* The social links are icons with NO visible text, so `aria-label` is not an
|
|
854
|
+
* improvement: it is the only thing that makes them legible. Which is why it is
|
|
855
|
+
* mandatory in the type and not an optional prop that gets forgotten.
|
|
963
856
|
*
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
857
|
+
* The signature sits top right, level with the FIRST row that exists, not at the
|
|
858
|
+
* end of the block. That is a composition decision and not a styling one: the
|
|
859
|
+
* footer can carry brand, links and social icons, and hanging the signature off
|
|
860
|
+
* one specific row sinks it the moment that row stops being the first.
|
|
967
861
|
*/
|
|
968
|
-
type
|
|
969
|
-
/**
|
|
862
|
+
type SocialLink = {
|
|
863
|
+
/** What replaces the visible text. Mandatory. */
|
|
970
864
|
label: string;
|
|
971
865
|
href: string;
|
|
972
866
|
/**
|
|
973
|
-
*
|
|
974
|
-
*
|
|
867
|
+
* The glyph, at 19px. Brands are SOLID (`fill`) and functional icons use a 1.6
|
|
868
|
+
* stroke. Never an emoji.
|
|
975
869
|
*/
|
|
976
870
|
icon: ReactNode;
|
|
977
871
|
};
|
|
978
872
|
type FooterProps = ComponentPropsWithoutRef<'footer'> & {
|
|
979
|
-
social?: readonly
|
|
980
|
-
/**
|
|
873
|
+
social?: readonly SocialLink[];
|
|
874
|
+
/** The signature's year. */
|
|
981
875
|
year?: number;
|
|
982
|
-
/**
|
|
876
|
+
/** Text links: legal notice, RSS, sitemap. */
|
|
983
877
|
children?: ReactNode;
|
|
984
878
|
/**
|
|
985
|
-
*
|
|
879
|
+
* The brand row: the fin and the wordmark, at the very top.
|
|
986
880
|
*
|
|
987
|
-
*
|
|
988
|
-
*
|
|
989
|
-
*
|
|
881
|
+
* It exists because without it that row ended up inside `children` with a
|
|
882
|
+
* `w-full` so it would take a line of its own. It worked and it was a patch:
|
|
883
|
+
* the brand is not one more text link, and a slot of its own says so in the
|
|
884
|
+
* type.
|
|
990
885
|
*/
|
|
991
886
|
brand?: ReactNode;
|
|
992
887
|
};
|
|
@@ -997,413 +892,503 @@ type FooterLinkProps = ComponentPropsWithoutRef<'a'> & {
|
|
|
997
892
|
declare function FooterLink({ asChild, className, ...props }: FooterLinkProps): react.JSX.Element;
|
|
998
893
|
|
|
999
894
|
/**
|
|
1000
|
-
*
|
|
1001
|
-
*
|
|
1002
|
-
*
|
|
1003
|
-
*
|
|
1004
|
-
*
|
|
1005
|
-
*
|
|
1006
|
-
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
*
|
|
1012
|
-
*
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
*
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
*
|
|
1019
|
-
*
|
|
1020
|
-
*
|
|
1021
|
-
*
|
|
895
|
+
* ONE per site. It is the only piece in the system that is spent like the
|
|
896
|
+
* conversion button, and for the same reason: if there are two, there are none.
|
|
897
|
+
*
|
|
898
|
+
* Gradient, panel radius, text at 62 % of the width and the pose bleeding off
|
|
899
|
+
* the bottom-right corner. That is the `header` variant, which is the default.
|
|
900
|
+
*
|
|
901
|
+
* On mobile there is no edge to bleed off, so the pose drops into the flow,
|
|
902
|
+
* below the buttons. It is not a `hidden` on small screens: the pose is 40 % of
|
|
903
|
+
* the hero's personality.
|
|
904
|
+
*
|
|
905
|
+
* The other variant is `centered`, and it exists because the rule above has a
|
|
906
|
+
* case where it does not apply. «Never centred» was written against the hero of
|
|
907
|
+
* a page with more content below it: there, a centred mascot under the headline
|
|
908
|
+
* is a cover illustration, not a header. But a links page is centred end to end
|
|
909
|
+
* and the mascot is the protagonist, not the flourish. That project skipped
|
|
910
|
+
* `Hero` entirely over this, which is worse: a declared rule with a name can be
|
|
911
|
+
* argued with; a copy of the gradient in another repo just drifts. The pose goes
|
|
912
|
+
* ABOVE the headline, not below it, so it still does not read as the
|
|
913
|
+
* illustration closing a block of text.
|
|
914
|
+
*
|
|
915
|
+
* The gradient comes from `--gradient-hero`, so it follows the mode. There is no
|
|
916
|
+
* hand-written angle in any project.
|
|
1022
917
|
*/
|
|
1023
918
|
type HeroProps = Omit<ComponentPropsWithoutRef<'section'>, 'title'> & {
|
|
1024
919
|
title: ReactNode;
|
|
1025
|
-
/** Mono,
|
|
920
|
+
/** Mono, small caps, in accent. */
|
|
1026
921
|
eyebrow?: ReactNode;
|
|
1027
922
|
description?: ReactNode;
|
|
1028
|
-
/**
|
|
923
|
+
/** The buttons. The screen's only `conversion` goes here. */
|
|
1029
924
|
action?: ReactNode;
|
|
1030
|
-
/**
|
|
925
|
+
/** Tiburoncín's pose. Without it the hero is a panel with text. */
|
|
1031
926
|
pose?: Pose | undefined;
|
|
1032
927
|
basePath?: string | undefined;
|
|
1033
928
|
/**
|
|
1034
|
-
* `
|
|
1035
|
-
*
|
|
929
|
+
* `header` bleeds the pose off the corner; `centered` puts it on top and
|
|
930
|
+
* centres the text, for a page that is only this.
|
|
1036
931
|
*/
|
|
1037
|
-
variant?: '
|
|
932
|
+
variant?: 'header' | 'centered';
|
|
1038
933
|
};
|
|
1039
934
|
declare function Hero({ title, eyebrow, description, action, pose, basePath, variant, className, ...props }: HeroProps): react.JSX.Element;
|
|
1040
935
|
|
|
1041
|
-
type LinkRowProps = Omit<
|
|
936
|
+
type LinkRowProps = Omit<CardShellProps, 'children'> & {
|
|
1042
937
|
name: ReactNode;
|
|
1043
938
|
description?: ReactNode;
|
|
1044
|
-
/**
|
|
939
|
+
/** The target's SVG glyph. Never an emoji. */
|
|
1045
940
|
icon?: ReactNode;
|
|
1046
|
-
/**
|
|
941
|
+
/** Marks the link as external: adds the arrow and the safe `rel`. */
|
|
1047
942
|
external?: boolean | undefined;
|
|
1048
943
|
};
|
|
1049
944
|
/**
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
* hover —
|
|
1053
|
-
*
|
|
945
|
+
* Migrated from `links/src/components/Card.astro`. The original scaled the card
|
|
946
|
+
* to 102 %, lifted the title by a pixel and rotated and enlarged the icon on
|
|
947
|
+
* hover — four movements the system does not allow. Here the hover changes the
|
|
948
|
+
* border and the icon's color, and nothing else.
|
|
1054
949
|
*/
|
|
1055
950
|
declare function LinkRow({ name, description, icon, external, className, ...props }: LinkRowProps): react.JSX.Element;
|
|
1056
951
|
|
|
1057
952
|
/**
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
* `banner`
|
|
1067
|
-
* landmark:
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1071
|
-
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
953
|
+
* The site bar: 64px, abyss at 86 % and a 14px blur behind it.
|
|
954
|
+
*
|
|
955
|
+
* It is page composition and not a primitive, but it lives in the library for a
|
|
956
|
+
* concrete reason: the CLI aesthetic of the items — mono, `./section` format —
|
|
957
|
+
* is the first thing that drifts when five projects each rewrite it on their
|
|
958
|
+
* own.
|
|
959
|
+
*
|
|
960
|
+
* It renders a `<header>` hanging directly off the `body`, so it IS the site's
|
|
961
|
+
* `banner` landmark. That is why `PageHeader` goes inside `<main>` and is not a
|
|
962
|
+
* landmark: two banners on one page is an accessibility failure.
|
|
963
|
+
*
|
|
964
|
+
* The items go on the RIGHT, next to the actions, not straight after the brand.
|
|
965
|
+
* With the brand on the left and the items right behind it, the navigation block
|
|
966
|
+
* floats in the middle of the bar and the eye has to cross the gap twice: once
|
|
967
|
+
* to read the brand and once to come back and find the section. Grouped on the
|
|
968
|
+
* right, brand and navigation are two anchors instead of three.
|
|
1074
969
|
*/
|
|
1075
970
|
type NavProps = ComponentPropsWithoutRef<'header'> & {
|
|
1076
|
-
/**
|
|
971
|
+
/** The logo, on the left. */
|
|
1077
972
|
brand?: ReactNode;
|
|
1078
|
-
/**
|
|
973
|
+
/** The section items. They go on the right, next to `actions`. */
|
|
1079
974
|
children?: ReactNode;
|
|
1080
|
-
/**
|
|
975
|
+
/** Actions on the right: conversion, theme switch, search. */
|
|
1081
976
|
actions?: ReactNode;
|
|
1082
977
|
};
|
|
1083
978
|
declare function Nav({ brand, children, actions, className, ...props }: NavProps): react.JSX.Element;
|
|
1084
979
|
type NavItemProps = ComponentPropsWithoutRef<'a'> & {
|
|
1085
|
-
/**
|
|
980
|
+
/** Current section: biolume with a 1px underline. */
|
|
1086
981
|
active?: boolean | undefined;
|
|
1087
|
-
/**
|
|
982
|
+
/** Renders the child instead of an `<a>`, for the router's `Link`. */
|
|
1088
983
|
asChild?: boolean | undefined;
|
|
1089
984
|
};
|
|
1090
985
|
/**
|
|
1091
|
-
*
|
|
986
|
+
* The `./` is put there by the component, not by whoever uses it.
|
|
1092
987
|
*
|
|
1093
|
-
*
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
988
|
+
* It is the same decision as the tertiary button: the format is part of the
|
|
989
|
+
* piece, not a convention to be remembered across five projects. It is
|
|
990
|
+
* `aria-hidden`, so a screen reader announces «artículos» and not «dot slash
|
|
1096
991
|
* artículos».
|
|
1097
992
|
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1101
|
-
*
|
|
1102
|
-
*
|
|
1103
|
-
*
|
|
993
|
+
* The current section goes in BRACKETS as well as in biolume and underlined.
|
|
994
|
+
* That is not decoration: the underline and the color are the same signal —
|
|
995
|
+
* «this stands out» — and in a six-item mono bar at 13px that signal reads worse
|
|
996
|
+
* than it looks in a mockup. Brackets are how a terminal marks the active path,
|
|
997
|
+
* so they say «you are here» without relying on the color being told apart. They
|
|
998
|
+
* are `aria-hidden`, because whoever is listening already has `aria-current`.
|
|
1104
999
|
*/
|
|
1105
1000
|
declare function NavItem({ active, asChild, className, children, ...props }: NavItemProps): react.JSX.Element;
|
|
1106
1001
|
|
|
1107
1002
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1111
|
-
*
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
*
|
|
1117
|
-
*
|
|
1118
|
-
*
|
|
1119
|
-
*
|
|
1120
|
-
*
|
|
1121
|
-
*
|
|
1122
|
-
*
|
|
1123
|
-
*
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1126
|
-
*
|
|
1127
|
-
*
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1132
|
-
*
|
|
1133
|
-
*
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1003
|
+
* Four states, and the notice goes UNDER the form.
|
|
1004
|
+
*
|
|
1005
|
+
* Replacing the form with the success message is what almost everyone does and
|
|
1006
|
+
* it is what breaks the real case: somebody subscribes with the wrong email and
|
|
1007
|
+
* then has nowhere to type it again. The field stays.
|
|
1008
|
+
*
|
|
1009
|
+
* The component is presentational: it takes `state` and emits `onSubmitEmail`.
|
|
1010
|
+
* The network call is the project's job, because each has its own provider and
|
|
1011
|
+
* the library is not going to choose one for them.
|
|
1012
|
+
*
|
|
1013
|
+
* The notice uses the system's SECOND recipe — `emphasis="strong"`, background
|
|
1014
|
+
* at 10 % and a solid border — because it sits directly under a field that
|
|
1015
|
+
* already has a border: with the subtle recipe, the two lines read as a single
|
|
1016
|
+
* box.
|
|
1017
|
+
*
|
|
1018
|
+
* It is one of the places the mascot may appear: the «sin spam».
|
|
1019
|
+
*
|
|
1020
|
+
* The name field is OPTIONAL and off by default. It is not a styling prop: one
|
|
1021
|
+
* project's endpoint validates name and email and answers 400 if the first is
|
|
1022
|
+
* missing, so a one-field form there is not a poorer form — it is one that sends
|
|
1023
|
+
* something the server rejects. The name travels as the SECOND argument of
|
|
1024
|
+
* `onSubmitEmail`, so the calls that already exist — the ones only declaring
|
|
1025
|
+
* `(email)` — keep compiling.
|
|
1026
|
+
*
|
|
1027
|
+
* What the library does NOT do is validate the name. The project asking for it
|
|
1028
|
+
* bounds it between 2 and 50 characters and to letters and accents only; that
|
|
1029
|
+
* rule is theirs and the server's that actually checks it, and copying it here
|
|
1030
|
+
* would mean two sources drifting apart in silence. `nameInputProps` is there
|
|
1031
|
+
* for the project to put its own in.
|
|
1032
|
+
*
|
|
1033
|
+
* Four of the props are here because the blog had already built each one by
|
|
1034
|
+
* hand, and each workaround leaned on something nobody had promised: `aside`
|
|
1035
|
+
* replaces an absolutely positioned pose and a `md:pr-[330px]` measured off the
|
|
1036
|
+
* image; `resetOnSuccess` replaces finding the `<form>` with a `ref` on the
|
|
1037
|
+
* container; `onFieldChange` replaces an `onInput` on the `<section>` that
|
|
1038
|
+
* worked because the event bubbles; and `fieldErrors` replaces losing the second
|
|
1039
|
+
* message whenever two fields failed at once. A workaround that works by an
|
|
1040
|
+
* implementation detail is a bug with a delay.
|
|
1136
1041
|
*/
|
|
1137
|
-
type NewsletterState = '
|
|
1042
|
+
type NewsletterState = 'idle' | 'sending' | 'success' | 'error';
|
|
1138
1043
|
type NewsletterFormProps = Omit<ComponentPropsWithoutRef<'section'>, 'title' | 'onSubmit'> & {
|
|
1139
1044
|
title: ReactNode;
|
|
1140
1045
|
description?: ReactNode;
|
|
1141
1046
|
state?: NewsletterState;
|
|
1142
1047
|
/**
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1048
|
+
* Fires with the email already read from the field, and with the name if that
|
|
1049
|
+
* field is enabled.
|
|
1145
1050
|
*/
|
|
1146
1051
|
onSubmitEmail?: ((email: string, name?: string) => void) | undefined;
|
|
1147
1052
|
successMessage?: ReactNode;
|
|
1148
1053
|
errorMessage?: ReactNode;
|
|
1149
|
-
/**
|
|
1054
|
+
/** The small print. It is the «sin spam», which is why it accepts a face. */
|
|
1150
1055
|
disclaimer?: ReactNode;
|
|
1151
|
-
|
|
1056
|
+
expression?: Face | undefined;
|
|
1152
1057
|
basePath?: string | undefined;
|
|
1153
1058
|
submitLabel?: string;
|
|
1154
1059
|
placeholder?: string;
|
|
1155
1060
|
fieldLabel?: string;
|
|
1156
|
-
/**
|
|
1061
|
+
/** Adds the name field ahead of the email one. */
|
|
1157
1062
|
nameField?: boolean;
|
|
1158
1063
|
nameLabel?: string;
|
|
1159
1064
|
namePlaceholder?: string;
|
|
1160
1065
|
/**
|
|
1161
|
-
*
|
|
1162
|
-
* `maxLength`, `pattern`.
|
|
1066
|
+
* Whatever the project needs to hang off the name field: `minLength`,
|
|
1067
|
+
* `maxLength`, `pattern`. The library imposes none of the three.
|
|
1163
1068
|
*/
|
|
1164
1069
|
nameInputProps?: Omit<InputProps, 'id' | 'name' | 'disabled'> | undefined;
|
|
1070
|
+
/**
|
|
1071
|
+
* The illustration, as a second column inside the panel.
|
|
1072
|
+
*
|
|
1073
|
+
* It exists because the component builds its own children, so neither
|
|
1074
|
+
* `children` nor an extra `ReactNode` had anywhere to go: the blog ended up
|
|
1075
|
+
* positioning the desk pose absolutely over the panel and reserving room for
|
|
1076
|
+
* it with a hand-written `md:pr-[330px]`. That number depends on the image's
|
|
1077
|
+
* width and nothing keeps the two in step.
|
|
1078
|
+
*
|
|
1079
|
+
* It only becomes a column from `md` up. Below that it goes back into the
|
|
1080
|
+
* flow under the form, for the same reason `Hero`'s pose does: on a narrow
|
|
1081
|
+
* screen there is no second column to put it in.
|
|
1082
|
+
*/
|
|
1083
|
+
aside?: ReactNode;
|
|
1084
|
+
/**
|
|
1085
|
+
* Empties the fields after a successful subscription. On by default.
|
|
1086
|
+
*
|
|
1087
|
+
* With the fields still full, the same email invites a second submission. The
|
|
1088
|
+
* blog worked around it by finding the `<form>` with a `ref` on the container
|
|
1089
|
+
* and calling `reset()`, because the component exposed no form — a trick that
|
|
1090
|
+
* works by an implementation detail and not by contract.
|
|
1091
|
+
*/
|
|
1092
|
+
resetOnSuccess?: boolean;
|
|
1093
|
+
/**
|
|
1094
|
+
* Fires when either field changes. It is where the project clears its error.
|
|
1095
|
+
*
|
|
1096
|
+
* The blog was doing it by hanging an `onInput` off the `<section>` and
|
|
1097
|
+
* relying on the event bubbling up. That works, and it works by accident: it
|
|
1098
|
+
* depends on the spare props landing on the section, which is an
|
|
1099
|
+
* implementation detail and not something anybody promised.
|
|
1100
|
+
*/
|
|
1101
|
+
onFieldChange?: ((field: 'name' | 'email', value: string) => void) | undefined;
|
|
1102
|
+
/**
|
|
1103
|
+
* A message under one specific field, instead of the single alert.
|
|
1104
|
+
*
|
|
1105
|
+
* With one bad field the general alert already names it, because the API
|
|
1106
|
+
* sends Zod's first message. With two bad at once only one of them is ever
|
|
1107
|
+
* seen. `fieldErrors` marks each field and puts its message underneath;
|
|
1108
|
+
* `errorMessage` still covers what belongs to the form as a whole — the 409,
|
|
1109
|
+
* the network failure — and both can show at the same time.
|
|
1110
|
+
*/
|
|
1111
|
+
fieldErrors?: {
|
|
1112
|
+
name?: ReactNode;
|
|
1113
|
+
email?: ReactNode;
|
|
1114
|
+
} | undefined;
|
|
1165
1115
|
};
|
|
1166
|
-
declare function NewsletterForm({ title, description, state, onSubmitEmail, successMessage, errorMessage, disclaimer,
|
|
1116
|
+
declare function NewsletterForm({ title, description, state, onSubmitEmail, successMessage, errorMessage, disclaimer, expression, basePath, submitLabel, placeholder, fieldLabel, nameField, nameLabel, namePlaceholder, nameInputProps, aside, resetOnSuccess, onFieldChange, fieldErrors, className, ...props }: NewsletterFormProps): react.JSX.Element;
|
|
1167
1117
|
|
|
1168
1118
|
/**
|
|
1169
|
-
*
|
|
1119
|
+
* One header at two scales, not two components.
|
|
1170
1120
|
*
|
|
1171
|
-
*
|
|
1172
|
-
* eyebrow
|
|
1173
|
-
*
|
|
1174
|
-
*
|
|
1121
|
+
* The portfolio's hero and the courses one turned out to be the same skeleton —
|
|
1122
|
+
* eyebrow in accent, headline, clamped paragraph — at different sizes. Splitting
|
|
1123
|
+
* them into `Hero` and `PageHeader` would have duplicated the same rule in two
|
|
1124
|
+
* places and left the door open for them to drift apart over time.
|
|
1175
1125
|
*
|
|
1176
|
-
* `display`
|
|
1126
|
+
* `display` for covers, `page` for section headers.
|
|
1177
1127
|
*
|
|
1178
|
-
*
|
|
1179
|
-
*
|
|
1128
|
+
* It takes no mascot face, at either scale: faces go in empty states,
|
|
1129
|
+
* confirmations, errors, course progress and celebration.
|
|
1180
1130
|
*
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1131
|
+
* It renders a `<header>`, and it goes INSIDE `<main>`. A `<header>` hanging
|
|
1132
|
+
* directly off `<body>` becomes a `banner` landmark, and then it competes with
|
|
1133
|
+
* the site header: two banners on one page is an accessibility failure. Inside
|
|
1134
|
+
* `<main>` it is not a landmark and it is the content's header, which is what
|
|
1135
|
+
* this component is.
|
|
1186
1136
|
*/
|
|
1187
|
-
declare const
|
|
1137
|
+
declare const header: (props?: ({
|
|
1188
1138
|
size?: "display" | "page" | null | undefined;
|
|
1189
1139
|
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
1190
|
-
type PageHeaderProps = Omit<ComponentPropsWithoutRef<'header'>, 'title'> & VariantProps<typeof
|
|
1140
|
+
type PageHeaderProps = Omit<ComponentPropsWithoutRef<'header'>, 'title'> & VariantProps<typeof header> & {
|
|
1191
1141
|
title: ReactNode;
|
|
1192
|
-
/** Mono,
|
|
1142
|
+
/** Mono, small caps, in accent. It is the section the page belongs to. */
|
|
1193
1143
|
eyebrow?: ReactNode | undefined;
|
|
1194
1144
|
description?: ReactNode | undefined;
|
|
1195
1145
|
/**
|
|
1196
|
-
*
|
|
1197
|
-
*
|
|
1146
|
+
* Slot for the calls to action. If a conversion button goes here, it is the
|
|
1147
|
+
* only one on the screen.
|
|
1198
1148
|
*/
|
|
1199
1149
|
action?: ReactNode | undefined;
|
|
1200
|
-
/**
|
|
1150
|
+
/** The headline's level. `h1` unless the page already has one. */
|
|
1201
1151
|
as?: 'h1' | 'h2' | undefined;
|
|
1202
1152
|
};
|
|
1203
1153
|
declare function PageHeader({ title, eyebrow, description, action, size, as, className, ...props }: PageHeaderProps): react.JSX.Element;
|
|
1204
1154
|
|
|
1205
1155
|
/**
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
1208
|
-
* `Progress`
|
|
1209
|
-
*
|
|
1210
|
-
*
|
|
1211
|
-
* `role="progressbar"`
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1214
|
-
*
|
|
1215
|
-
*
|
|
1216
|
-
*
|
|
1217
|
-
*
|
|
1218
|
-
*
|
|
1219
|
-
*
|
|
1220
|
-
*
|
|
1221
|
-
*
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1156
|
+
* How much you have read. It is NOT `Progress` under another name.
|
|
1157
|
+
*
|
|
1158
|
+
* `Progress` measures a task: there is a known total, somebody started it and it
|
|
1159
|
+
* is going to finish. This measures a POSITION in a document, which can be
|
|
1160
|
+
* travelled in both directions and of which there is nothing to complete. That
|
|
1161
|
+
* is why it carries neither `role="progressbar"` nor an accessible value: it is
|
|
1162
|
+
* `aria-hidden`.
|
|
1163
|
+
*
|
|
1164
|
+
* That last part is deliberate and it is the decision worth defending. A screen
|
|
1165
|
+
* reader already knows where it is in the document, and announcing «37 %» every
|
|
1166
|
+
* time it moves is noise, not information. The bar is visual orientation, and
|
|
1167
|
+
* what is purely visual is declared as such.
|
|
1168
|
+
*
|
|
1169
|
+
* The width is written directly, with no transition: `transition-standard` only
|
|
1170
|
+
* covers color and border, so the bar follows the scroll instead of chasing it.
|
|
1171
|
+
*
|
|
1172
|
+
* The measurement happens inside `requestAnimationFrame`. Reading `scrollTop` in
|
|
1173
|
+
* the scroll handler forces a synchronous reflow on every event, and in a long
|
|
1174
|
+
* article that shows up in the scroll itself — the opposite of what this piece
|
|
1175
|
+
* is for.
|
|
1225
1176
|
*/
|
|
1226
1177
|
type ScrollingProgressBarProps = Omit<ComponentPropsWithoutRef<'div'>, 'children'> & {
|
|
1227
1178
|
/**
|
|
1228
|
-
*
|
|
1179
|
+
* The element being measured. Without it, the whole document.
|
|
1229
1180
|
*
|
|
1230
|
-
*
|
|
1231
|
-
*
|
|
1232
|
-
*
|
|
1181
|
+
* It is passed when the bar should follow ONLY the article: if the page has a
|
|
1182
|
+
* tall header and a footer full of links, measuring the document hits 100 %
|
|
1183
|
+
* while two paragraphs are still left.
|
|
1233
1184
|
*/
|
|
1234
1185
|
target?: RefObject<HTMLElement | null> | undefined;
|
|
1235
|
-
/**
|
|
1186
|
+
/** Sand instead of biolume, to match course progress. */
|
|
1236
1187
|
tone?: 'accent' | 'warm';
|
|
1237
|
-
/**
|
|
1188
|
+
/** Pins the bar to the top edge of the window. */
|
|
1238
1189
|
sticky?: boolean;
|
|
1239
1190
|
};
|
|
1240
1191
|
declare function ScrollingProgressBar({ target, tone, sticky, className, ...props }: ScrollingProgressBarProps): react.JSX.Element;
|
|
1241
1192
|
|
|
1242
1193
|
/**
|
|
1243
|
-
*
|
|
1194
|
+
* The blog admin's sidebar.
|
|
1244
1195
|
*
|
|
1245
|
-
*
|
|
1246
|
-
* breadcrumb
|
|
1247
|
-
*
|
|
1196
|
+
* The `▸` is put there by the component, same as `NavItem`'s `./` and the
|
|
1197
|
+
* breadcrumb's `~`: it is the same CLI aesthetic and the same decision — the
|
|
1198
|
+
* format is part of the piece, not a convention to be remembered. It is
|
|
1199
|
+
* `aria-hidden`.
|
|
1248
1200
|
*
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1201
|
+
* The footer carries the version and the branch (`v5.0.1 · main`) in the `meta`
|
|
1202
|
+
* scale. It is not decoration: in an admin it is the first thing anyone asks
|
|
1203
|
+
* when something looks off.
|
|
1251
1204
|
*/
|
|
1252
1205
|
type SidebarItemProps = ComponentPropsWithoutRef<'a'> & {
|
|
1253
1206
|
active?: boolean | undefined;
|
|
1254
1207
|
asChild?: boolean | undefined;
|
|
1255
|
-
/**
|
|
1208
|
+
/** Counter on the right: pending drafts, unused media. */
|
|
1256
1209
|
badge?: ReactNode;
|
|
1257
1210
|
};
|
|
1258
1211
|
declare function SidebarItem({ active, asChild, badge, className, children, ...props }: SidebarItemProps): react.JSX.Element;
|
|
1259
1212
|
type SidebarNavProps = ComponentPropsWithoutRef<'nav'> & {
|
|
1260
|
-
/**
|
|
1213
|
+
/** The panel's heading. */
|
|
1261
1214
|
title?: ReactNode;
|
|
1262
|
-
/**
|
|
1215
|
+
/** Version and branch, at the bottom. */
|
|
1263
1216
|
version?: ReactNode;
|
|
1264
1217
|
branch?: ReactNode;
|
|
1265
1218
|
};
|
|
1266
1219
|
declare function SidebarNav({ title, version, branch, children, className, ...props }: SidebarNavProps): react.JSX.Element;
|
|
1267
1220
|
|
|
1268
1221
|
/**
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
*
|
|
1275
|
-
*
|
|
1276
|
-
*
|
|
1277
|
-
*
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1280
|
-
*
|
|
1222
|
+
* A large metric: the number in the `stat` scale and its name underneath.
|
|
1223
|
+
*
|
|
1224
|
+
* The document's rule is not one of style, it is one of semantics: «biolume for
|
|
1225
|
+
* the neutral and sand ONLY when the number is the problem». A 12 of
|
|
1226
|
+
* applications is a datum; a 0 of design systems is the problem the talk is
|
|
1227
|
+
* about. That is why `tone` is not an open palette — there are two values and
|
|
1228
|
+
* they mean different things.
|
|
1229
|
+
*
|
|
1230
|
+
* The reading order is icon + title, the big number, and the standfirst below.
|
|
1231
|
+
* The number goes in the MIDDLE and not at the end on purpose: it is what people
|
|
1232
|
+
* came to read, and a two-line standfirst between the title and the figure
|
|
1233
|
+
* buries it. The top says what it is about, the middle says how much, and the
|
|
1234
|
+
* bottom holds the nuance only someone who stops will read.
|
|
1281
1235
|
*/
|
|
1282
1236
|
type StatProps = Omit<ComponentPropsWithoutRef<'div'>, 'title'> & {
|
|
1283
|
-
/**
|
|
1237
|
+
/** The number, already formatted. The library imposes no locale. */
|
|
1284
1238
|
value: ReactNode;
|
|
1285
|
-
/**
|
|
1239
|
+
/** What is being counted. It goes in mono small caps. */
|
|
1286
1240
|
label: ReactNode;
|
|
1287
|
-
/** `alerta`
|
|
1241
|
+
/** `alerta` only when the number IS the problem. */
|
|
1288
1242
|
tone?: 'neutral' | 'alerta';
|
|
1289
|
-
/**
|
|
1243
|
+
/** With `progress`, the metric reads as progress and adds the bar. */
|
|
1290
1244
|
progress?: number | undefined;
|
|
1291
1245
|
/**
|
|
1292
|
-
*
|
|
1293
|
-
*
|
|
1246
|
+
* Glyph beside the title, at 1em. It inherits `currentColor`, so it follows
|
|
1247
|
+
* the title's tone and does not have to be tinted separately.
|
|
1294
1248
|
*/
|
|
1295
1249
|
icon?: ReactNode;
|
|
1296
1250
|
/**
|
|
1297
|
-
*
|
|
1298
|
-
*
|
|
1251
|
+
* The standfirst: the nuance the number alone does not give. «12 aplicaciones»
|
|
1252
|
+
* does not say whether that is a lot, and this is where that gets said.
|
|
1299
1253
|
*/
|
|
1300
1254
|
description?: ReactNode;
|
|
1301
1255
|
};
|
|
1302
1256
|
declare function Stat({ value, label, tone, progress, icon, description, className, ...props }: StatProps): react.JSX.Element;
|
|
1303
1257
|
|
|
1304
|
-
type
|
|
1258
|
+
type TalkContent = {
|
|
1305
1259
|
title: ReactNode;
|
|
1306
|
-
/**
|
|
1260
|
+
/** Where it was given: the conference, the meetup, the team. */
|
|
1307
1261
|
event: ReactNode;
|
|
1308
1262
|
date?: ReactNode;
|
|
1309
1263
|
dateTime?: string | undefined;
|
|
1310
1264
|
location?: ReactNode;
|
|
1311
|
-
/**
|
|
1265
|
+
/** Short status label: «con vídeo», «próxima», «solo audio». */
|
|
1312
1266
|
status?: ReactNode;
|
|
1313
1267
|
/**
|
|
1314
|
-
*
|
|
1315
|
-
* `
|
|
1268
|
+
* What the talk was about. Clamped to two lines, same as `ArticleCard`'s
|
|
1269
|
+
* `excerpt`, so the grid does not fall out of line.
|
|
1316
1270
|
*/
|
|
1317
1271
|
description?: ReactNode;
|
|
1318
1272
|
};
|
|
1319
|
-
|
|
1273
|
+
/**
|
|
1274
|
+
* A talk has more than one destination, and that is what shapes this type.
|
|
1275
|
+
*
|
|
1276
|
+
* Slides, repo, the recording, the event's own page: the listing used to show
|
|
1277
|
+
* them as loose links under each talk, and migrating to this component made them
|
|
1278
|
+
* DISAPPEAR — there was nowhere to put them, so the blog pointed the whole card
|
|
1279
|
+
* at the first one that existed and the rest were lost.
|
|
1280
|
+
*
|
|
1281
|
+
* So `resources` is a slot, and it forces a choice in the type: a card with
|
|
1282
|
+
* resources is NOT a link. An `<a>` inside an `<a>` is invalid HTML and the
|
|
1283
|
+
* browser un-nests it, so «the card links to the slides AND the slides link to
|
|
1284
|
+
* the slides» is not a thing that can render. With resources the card is an
|
|
1285
|
+
* `<article>` on the card surface and the links are the resources; without them
|
|
1286
|
+
* it stays the single-destination card it was.
|
|
1287
|
+
*
|
|
1288
|
+
* That is why this is a union and not one more optional prop: the combination
|
|
1289
|
+
* that cannot work does not compile.
|
|
1290
|
+
*/
|
|
1291
|
+
type TalkCardProps = (Omit<CardShellProps, 'children' | 'title'> & TalkContent & {
|
|
1292
|
+
/** The card is the link. Do not pass resources with this. */
|
|
1293
|
+
resources?: never;
|
|
1294
|
+
}) | (Omit<ComponentPropsWithoutRef<'article'>, 'title'> & TalkContent & {
|
|
1295
|
+
/**
|
|
1296
|
+
* Slides, repo, recording. They are the links, so the card stops being
|
|
1297
|
+
* one.
|
|
1298
|
+
*/
|
|
1299
|
+
resources: ReactNode;
|
|
1300
|
+
});
|
|
1301
|
+
declare function TalkCard({ title, event, date, dateTime, location, status, description, resources, className, ...props }: TalkCardProps): react.JSX.Element;
|
|
1320
1302
|
|
|
1321
1303
|
/**
|
|
1322
|
-
*
|
|
1323
|
-
*
|
|
1324
|
-
*
|
|
1325
|
-
*
|
|
1326
|
-
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1329
|
-
*
|
|
1330
|
-
*
|
|
1331
|
-
*
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
*
|
|
1339
|
-
*
|
|
1304
|
+
* The control that was missing. The library defined the whole theming system and
|
|
1305
|
+
* exposed nothing that changes it, so two projects were reimplementing it.
|
|
1306
|
+
*
|
|
1307
|
+
* The hard part is not the button: it is that the first paint does not flash and
|
|
1308
|
+
* that the choice survives navigation. That lives in
|
|
1309
|
+
* `@eduardoalvarez/arrecife/theme`, which does not import React — an Astro that
|
|
1310
|
+
* mounts none consumes it — and out of it comes `themeScript`, which goes inline
|
|
1311
|
+
* in the `<head>`. Without that script this button works and you still get the
|
|
1312
|
+
* flash on every load.
|
|
1313
|
+
*
|
|
1314
|
+
* BOTH icons are always rendered and CSS hides the spare one with the `light:`
|
|
1315
|
+
* variant. It is not an optimisation: it is what stops the server and the client
|
|
1316
|
+
* from disagreeing. The server does not know which theme the reader chose, so
|
|
1317
|
+
* whichever icon it picks in the HTML has a fifty-fifty chance of being wrong,
|
|
1318
|
+
* and correcting it on hydration is the flash all over again.
|
|
1319
|
+
*
|
|
1320
|
+
* The accessible name does NOT say which mode you are going to. That would be
|
|
1321
|
+
* more informative and would be a lie half the time, for the same reason as
|
|
1322
|
+
* above: the server's HTML fixes it before the theme is known. «Cambiar de
|
|
1323
|
+
* tema» is always true.
|
|
1340
1324
|
*/
|
|
1341
1325
|
type ThemeToggleProps = Omit<ComponentPropsWithoutRef<'button'>, 'onClick'> & {
|
|
1342
|
-
/**
|
|
1326
|
+
/** Accessible name. The button has no visible text, so it is the only thing naming it. */
|
|
1343
1327
|
label?: string;
|
|
1344
|
-
/**
|
|
1345
|
-
onThemeChange?: ((
|
|
1328
|
+
/** Fires with whichever theme ended up set, in case the project wants to record it. */
|
|
1329
|
+
onThemeChange?: ((theme: Theme) => void) | undefined;
|
|
1346
1330
|
variant?: ButtonProps['variant'];
|
|
1347
1331
|
size?: ButtonProps['size'];
|
|
1348
1332
|
};
|
|
1349
1333
|
declare function ThemeToggle({ label, onThemeChange, variant, size, className, ...props }: ThemeToggleProps): react.JSX.Element;
|
|
1350
1334
|
/**
|
|
1351
|
-
*
|
|
1352
|
-
*
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
* `<html>`
|
|
1357
|
-
*
|
|
1358
|
-
*
|
|
1359
|
-
*
|
|
1360
|
-
* `getServerSnapshot`
|
|
1361
|
-
*
|
|
1362
|
-
*
|
|
1363
|
-
*
|
|
1364
|
-
*
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
1335
|
+
* The theme set right now, for a project that needs to branch in React — a
|
|
1336
|
+
* different logo per mode, an image with no light version.
|
|
1337
|
+
*
|
|
1338
|
+
* It is `useSyncExternalStore` and not a `useState` with an effect behind it
|
|
1339
|
+
* because the theme is exactly that: state living outside React, in an attribute
|
|
1340
|
+
* on `<html>` that can change without React finding out. Writing it as an effect
|
|
1341
|
+
* calling `setState` on mount is the pattern that triggers a cascading render
|
|
1342
|
+
* and that the `set-state-in-effect` rule rightly flags.
|
|
1343
|
+
*
|
|
1344
|
+
* `getServerSnapshot` returns `'dark'` because there is no `document` on the
|
|
1345
|
+
* server. The client's first render matches the server's and the real value
|
|
1346
|
+
* arrives afterwards, which is the same hydration mismatch `ThemeToggle` avoids
|
|
1347
|
+
* by rendering both icons.
|
|
1348
|
+
*
|
|
1349
|
+
* Hence the usage rule: if what branches is ONLY style, this is not needed and
|
|
1350
|
+
* the `light:` variant is better — it re-renders nothing. This is for when the
|
|
1351
|
+
* content changes.
|
|
1368
1352
|
*/
|
|
1369
|
-
declare function
|
|
1353
|
+
declare function useTheme(): Theme;
|
|
1370
1354
|
|
|
1371
1355
|
/**
|
|
1372
|
-
* «En esta página».
|
|
1356
|
+
* «En esta página». The long article's table of contents.
|
|
1373
1357
|
*
|
|
1374
|
-
*
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1358
|
+
* It is a `<nav>` with an accessible name of its own, not a loose list: on a
|
|
1359
|
+
* page that already has the site bar and the breadcrumb, a third group of links
|
|
1360
|
+
* with no name is indistinguishable from the other two for anyone navigating by
|
|
1361
|
+
* landmarks.
|
|
1377
1362
|
*
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1363
|
+
* The active entry is marked with `aria-current`, not with color alone — the
|
|
1364
|
+
* section you are in cannot be communicated purely in biolume.
|
|
1380
1365
|
*
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1366
|
+
* And that attribute is also THE HOOK: the active classes are applied with the
|
|
1367
|
+
* `aria-[current]:` variant, not with a ternary in the render. The difference is
|
|
1368
|
+
* the one between serving controlled only and also serving uncontrolled.
|
|
1384
1369
|
*
|
|
1385
|
-
*
|
|
1386
|
-
* `aria-current`
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1370
|
+
* An Astro site solves scroll-spy with fifteen lines of script that set
|
|
1371
|
+
* `aria-current` on the visible link and remove it from the previous one. With
|
|
1372
|
+
* the state computed in the render, that script could do nothing: the table of
|
|
1373
|
+
* contents had to be hydrated as a React island on every article for something
|
|
1374
|
+
* that costs zero framework JavaScript. Now the CSS reacts to the attribute and
|
|
1375
|
+
* both ways of using it give the same result.
|
|
1391
1376
|
*
|
|
1392
|
-
*
|
|
1393
|
-
*
|
|
1377
|
+
* The hook is the PRESENCE of the attribute, so it is removed to unmark; you do
|
|
1378
|
+
* not set `aria-current="false"`.
|
|
1394
1379
|
*/
|
|
1395
|
-
type
|
|
1396
|
-
/**
|
|
1380
|
+
type TocEntry = {
|
|
1381
|
+
/** The anchor, with its `#`. */
|
|
1397
1382
|
href: string;
|
|
1398
1383
|
label: ReactNode;
|
|
1399
|
-
/**
|
|
1384
|
+
/** Indents the entry. Only two levels: h2 and h3. */
|
|
1400
1385
|
nested?: boolean | undefined;
|
|
1401
1386
|
};
|
|
1402
1387
|
type TableOfContentsProps = Omit<ComponentPropsWithoutRef<'nav'>, 'children'> & {
|
|
1403
|
-
items: readonly
|
|
1404
|
-
/**
|
|
1388
|
+
items: readonly TocEntry[];
|
|
1389
|
+
/** The block's title. */
|
|
1405
1390
|
title?: ReactNode;
|
|
1406
|
-
/**
|
|
1391
|
+
/** The anchor of the visible section. */
|
|
1407
1392
|
activeHref?: string | undefined;
|
|
1408
1393
|
linkAsChild?: ((props: {
|
|
1409
1394
|
href: string;
|
|
@@ -1422,18 +1407,30 @@ declare const Instagram: (props: IconProps) => react.JSX.Element;
|
|
|
1422
1407
|
declare const Discord: (props: IconProps) => react.JSX.Element;
|
|
1423
1408
|
declare const YouTube: (props: IconProps) => react.JSX.Element;
|
|
1424
1409
|
declare const Rss: (props: IconProps) => react.JSX.Element;
|
|
1425
|
-
declare const
|
|
1410
|
+
declare const Email: (props: IconProps) => react.JSX.Element;
|
|
1411
|
+
/**
|
|
1412
|
+
* The newsletter. It plays the same role as `Rss` — a way to follow, not a
|
|
1413
|
+
* social network — which is why it belongs in this catalogue and does not open
|
|
1414
|
+
* the door to an icon library.
|
|
1415
|
+
*
|
|
1416
|
+
* It is named for what it means and not for what it draws, like everything else
|
|
1417
|
+
* in the system: it is a bell, and it is called `Newsletter`. `eduardoalvarez.dev`
|
|
1418
|
+
* had it drawn in the project, following the contract by hand so it would not
|
|
1419
|
+
* clash while it waited.
|
|
1420
|
+
*/
|
|
1421
|
+
declare const Newsletter: (props: IconProps) => react.JSX.Element;
|
|
1426
1422
|
|
|
1427
|
-
declare const social_Correo: typeof Correo;
|
|
1428
1423
|
declare const social_Discord: typeof Discord;
|
|
1424
|
+
declare const social_Email: typeof Email;
|
|
1429
1425
|
declare const social_GitHub: typeof GitHub;
|
|
1430
1426
|
declare const social_Instagram: typeof Instagram;
|
|
1431
1427
|
declare const social_LinkedIn: typeof LinkedIn;
|
|
1428
|
+
declare const social_Newsletter: typeof Newsletter;
|
|
1432
1429
|
declare const social_Rss: typeof Rss;
|
|
1433
1430
|
declare const social_X: typeof X;
|
|
1434
1431
|
declare const social_YouTube: typeof YouTube;
|
|
1435
1432
|
declare namespace social {
|
|
1436
|
-
export {
|
|
1433
|
+
export { social_Discord as Discord, social_Email as Email, social_GitHub as GitHub, social_Instagram as Instagram, social_LinkedIn as LinkedIn, social_Newsletter as Newsletter, social_Rss as Rss, social_X as X, social_YouTube as YouTube };
|
|
1437
1434
|
}
|
|
1438
1435
|
|
|
1439
|
-
export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, AvatarUpload, type AvatarUploadProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type
|
|
1436
|
+
export { Accordion, AccordionContent, AccordionItem, type AccordionProps, AccordionTrigger, type AccordionTriggerProps, Alert, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogOverlay, AlertDialogTitle, AlertDialogTrigger, type AlertProps, ArticleCard, type ArticleCardProps, AudioPlayer, type AudioPlayerMode, type AudioPlayerProps, AuthorCard, type AuthorCardProps, Avatar, AvatarFallback, AvatarImage, type AvatarProps, AvatarUpload, type AvatarUploadProps, Badge, type BadgeProps, Blockquote, type BlockquoteProps, Breadcrumb, type BreadcrumbProps, Button, type ButtonProps, Calendar, type CalendarEvent, type CalendarProps, Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, CategoryBadge, type CategoryBadgeProps, Checkbox, type CheckboxProps, Code, CodeBlock, type CodeBlockProps, type CodeProps, CourseCard, type CourseCardProps, type Crumb, DateField, type DateFieldProps, Dialog, DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogOverlay, DialogTitle, DialogTrigger, DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EmptyState, type EmptyStateProps, EventCalendar, type EventCalendarProps, Face, Footer, FooterLink, type FooterLinkProps, type FooterProps, Hero, type HeroProps, Input, type InputProps, LinkRow, type LinkRowProps, MetricBadge, type MetricBadgeProps, Nav, NavItem, type NavItemProps, type NavProps, NewsletterForm, type NewsletterFormProps, type NewsletterState, PageHeader, type PageHeaderProps, Pagination, PaginationContent, PaginationEllipsis, PaginationItem, PaginationLink, type PaginationLinkProps, PaginationNext, PaginationPrevious, Popover, PopoverAnchor, PopoverContent, type PopoverContentProps, PopoverTrigger, Pose, Progress, type ProgressProps, RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, ScrollingProgressBar, type ScrollingProgressBarProps, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectSeparator, SelectTrigger, SelectValue, Separator, type SeparatorProps, Sheet, SheetBody, SheetClose, SheetContent, type SheetContentProps, SheetDescription, SheetFooter, SheetHeader, SheetTitle, SheetTrigger, SidebarItem, type SidebarItemProps, SidebarNav, type SidebarNavProps, Skeleton, type SkeletonProps, type SocialLink, Stat, type StatProps, Switch, type SwitchProps, Table, TableBody, TableCaption, TableCell, TableFooter, TableHead, TableHeader, TableOfContents, type TableOfContentsProps, TableRow, Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, TalkCard, type TalkCardProps, Text, type TextProps, Textarea, type TextareaProps, Theme, ThemeToggle, type ThemeToggleProps, ToastAction, type ToastOptions, type ToastVariant, Toaster, type ToasterProps, type TocEntry, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, alert as alertVariants, avatar as avatarVariants, badge as badgeVariants, button as buttonVariants, cn, social, text as textVariants, toast, useTheme };
|