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