@eduardoalvarez/arrecife 0.3.0 → 0.5.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 +43 -0
- package/README.md +121 -1
- package/dist/brand/index.cjs +29 -10
- package/dist/brand/index.d.cts +9 -1
- package/dist/brand/index.d.ts +9 -1
- package/dist/brand/index.js +4 -2
- package/dist/chart/index.cjs +287 -0
- package/dist/chart/index.d.cts +127 -0
- package/dist/chart/index.d.ts +127 -0
- package/dist/chart/index.js +103 -0
- package/dist/chunk-KPZNNMV5.js +83 -0
- package/dist/chunk-MLKGABMK.js +7 -0
- package/dist/chunk-NHS7ETKJ.js +27 -0
- package/dist/{chunk-I7GYZD2A.js → chunk-TSPJOM6K.js} +5 -7
- package/dist/chunk-UOWIDFCB.js +81 -0
- package/dist/{chunk-VSMW2CT4.js → chunk-VPT32GPG.js} +2 -1
- package/dist/{chunk-ND45OQOC.js → chunk-YZ2SDOVZ.js} +29 -35
- package/dist/chunk-ZEOQKRQ7.js +19 -0
- package/dist/form/index.cjs +301 -0
- package/dist/form/index.d.cts +86 -0
- package/dist/form/index.d.ts +86 -0
- package/dist/form/index.js +84 -0
- package/dist/index.cjs +1094 -293
- package/dist/index.d.cts +452 -37
- package/dist/index.d.ts +452 -37
- package/dist/index.js +963 -373
- package/dist/label-DuTvJGxD.d.cts +14 -0
- package/dist/label-DuTvJGxD.d.ts +14 -0
- package/dist/og/index.js +2 -1
- package/dist/shiki/index.js +2 -1
- package/dist/tema/index.cjs +94 -0
- package/dist/tema/index.d.cts +110 -0
- package/dist/tema/index.d.ts +110 -0
- package/dist/tema/index.js +2 -0
- package/dist/tokens/index.cjs +6 -0
- package/dist/tokens/index.d.cts +33 -1
- package/dist/tokens/index.d.ts +33 -1
- package/dist/tokens/index.js +3 -2
- package/dist/tokens/theme.css +43 -0
- package/llms.txt +351 -44
- package/package.json +31 -2
|
@@ -1,30 +1,8 @@
|
|
|
1
1
|
import { aletas, RUTA_ASSETS, poses, caras } from './chunk-E3OMP2DL.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { extendTailwindMerge } from 'tailwind-merge';
|
|
2
|
+
import { cn } from './chunk-NHS7ETKJ.js';
|
|
3
|
+
import { naming, tagline } from './chunk-TSPJOM6K.js';
|
|
5
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
6
5
|
|
|
7
|
-
var kebab = (nombre) => nombre.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
8
|
-
var escalas = Object.keys(typeScale);
|
|
9
|
-
var radios = Object.keys(radius);
|
|
10
|
-
var espacios = Object.keys(spacing).map(kebab);
|
|
11
|
-
var controles = Object.keys(control).map((nombre) => `control-${kebab(nombre)}`);
|
|
12
|
-
var twMerge = extendTailwindMerge({
|
|
13
|
-
extend: {
|
|
14
|
-
classGroups: {
|
|
15
|
-
"font-size": [{ text: escalas }],
|
|
16
|
-
rounded: [{ rounded: radios }],
|
|
17
|
-
p: [{ p: espacios }],
|
|
18
|
-
px: [{ px: [...espacios, ...controles] }],
|
|
19
|
-
py: [{ py: espacios }],
|
|
20
|
-
gap: [{ gap: espacios }],
|
|
21
|
-
size: [{ size: controles }]
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
function cn(...inputs) {
|
|
26
|
-
return twMerge(clsx(inputs));
|
|
27
|
-
}
|
|
28
6
|
function Isotipo({
|
|
29
7
|
sobre = "oscuro",
|
|
30
8
|
basePath = RUTA_ASSETS,
|
|
@@ -49,20 +27,36 @@ function Logo({
|
|
|
49
27
|
sobre = "oscuro",
|
|
50
28
|
basePath = RUTA_ASSETS,
|
|
51
29
|
soloIsotipo = false,
|
|
30
|
+
conLema = false,
|
|
52
31
|
className,
|
|
53
32
|
...props
|
|
54
33
|
}) {
|
|
34
|
+
const conAleta = /* @__PURE__ */ jsx(
|
|
35
|
+
Isotipo,
|
|
36
|
+
{
|
|
37
|
+
sobre,
|
|
38
|
+
basePath,
|
|
39
|
+
alt: soloIsotipo ? naming.wordmark : "",
|
|
40
|
+
className: conLema ? "h-9 w-auto" : "h-7 w-auto"
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
const wordmark = /* @__PURE__ */ jsx("span", { className: "text-ui font-display text-text-primary font-bold tracking-[-0.02em]", children: naming.wordmark });
|
|
44
|
+
if (soloIsotipo) {
|
|
45
|
+
return /* @__PURE__ */ jsx("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children: conAleta });
|
|
46
|
+
}
|
|
47
|
+
if (conLema) {
|
|
48
|
+
return /* @__PURE__ */ jsxs("span", { className: cn("gap-step-sm inline-flex items-center", className), ...props, children: [
|
|
49
|
+
conAleta,
|
|
50
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "bg-hairline h-8 w-px shrink-0" }),
|
|
51
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-col", children: [
|
|
52
|
+
wordmark,
|
|
53
|
+
/* @__PURE__ */ jsx("span", { className: "text-eyebrow font-mono text-text-muted uppercase", children: tagline.corto })
|
|
54
|
+
] })
|
|
55
|
+
] });
|
|
56
|
+
}
|
|
55
57
|
return /* @__PURE__ */ jsxs("span", { className: cn("gap-step-xs inline-flex items-center", className), ...props, children: [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
sobre,
|
|
60
|
-
basePath,
|
|
61
|
-
alt: soloIsotipo ? naming.wordmark : "",
|
|
62
|
-
className: "h-7 w-auto"
|
|
63
|
-
}
|
|
64
|
-
),
|
|
65
|
-
soloIsotipo ? null : /* @__PURE__ */ jsx("span", { className: "text-ui font-display text-text-primary font-bold tracking-[-0.02em]", children: naming.wordmark })
|
|
58
|
+
conAleta,
|
|
59
|
+
wordmark
|
|
66
60
|
] });
|
|
67
61
|
}
|
|
68
62
|
function Mascota({
|
|
@@ -100,4 +94,4 @@ function CaraDeMascota({
|
|
|
100
94
|
);
|
|
101
95
|
}
|
|
102
96
|
|
|
103
|
-
export { CaraDeMascota, Isotipo, Logo, Mascota
|
|
97
|
+
export { CaraDeMascota, Isotipo, Logo, Mascota };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cn } from './chunk-NHS7ETKJ.js';
|
|
2
|
+
import * as LabelPrimitive from '@radix-ui/react-label';
|
|
3
|
+
import { jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
function Label({ className, ...props }) {
|
|
6
|
+
return /* @__PURE__ */ jsx(
|
|
7
|
+
LabelPrimitive.Root,
|
|
8
|
+
{
|
|
9
|
+
className: cn(
|
|
10
|
+
"text-label font-sans text-text-secondary select-none",
|
|
11
|
+
"peer-disabled:opacity-50 group-data-[disabled]:opacity-50",
|
|
12
|
+
className
|
|
13
|
+
),
|
|
14
|
+
...props
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Label };
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var reactHookForm = require('react-hook-form');
|
|
5
|
+
var reactSlot = require('@radix-ui/react-slot');
|
|
6
|
+
var clsx = require('clsx');
|
|
7
|
+
var tailwindMerge = require('tailwind-merge');
|
|
8
|
+
var LabelPrimitive = require('@radix-ui/react-label');
|
|
9
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
10
|
+
var classVarianceAuthority = require('class-variance-authority');
|
|
11
|
+
|
|
12
|
+
function _interopNamespace(e) {
|
|
13
|
+
if (e && e.__esModule) return e;
|
|
14
|
+
var n = Object.create(null);
|
|
15
|
+
if (e) {
|
|
16
|
+
Object.keys(e).forEach(function (k) {
|
|
17
|
+
if (k !== 'default') {
|
|
18
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
19
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
get: function () { return e[k]; }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
n.default = e;
|
|
27
|
+
return Object.freeze(n);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
var LabelPrimitive__namespace = /*#__PURE__*/_interopNamespace(LabelPrimitive);
|
|
31
|
+
|
|
32
|
+
// src/form/index.tsx
|
|
33
|
+
var typeScale = {
|
|
34
|
+
display: { family: "display", size: 76, lineHeight: 0.96, weight: 800, tracking: "-0.035em" },
|
|
35
|
+
/** Métricas grandes. Números, no prosa: interlineado 1 y sin descendentes. */
|
|
36
|
+
stat: { family: "display", size: 46, lineHeight: 1, weight: 800, tracking: "-0.035em" },
|
|
37
|
+
h1: { family: "display", size: 44, lineHeight: 1.05, weight: 700, tracking: "-0.03em" },
|
|
38
|
+
h2: { family: "display", size: 30, lineHeight: 1.1, weight: 600, tracking: "-0.02em" },
|
|
39
|
+
h3: { family: "display", size: 25, lineHeight: 1.15, weight: 600, tracking: "-0.02em" },
|
|
40
|
+
body: { family: "sans", size: 18, lineHeight: 1.75, weight: 400 },
|
|
41
|
+
/**
|
|
42
|
+
* 17px. El botón grande y la bajada de las páginas internas.
|
|
43
|
+
*
|
|
44
|
+
* Entra como escalón propio porque el documento lo usa DOS veces y en piezas
|
|
45
|
+
* distintas: «lg 15px 30px r12» con texto de 17, y «h1 44/700 · bajada 17px ·
|
|
46
|
+
* párrafo de contexto 15px». Un solo uso no habría justificado partir la
|
|
47
|
+
* distancia entre `ui` (15) y `body` (18); dos sí.
|
|
48
|
+
*/
|
|
49
|
+
lead: { family: "sans", size: 17, lineHeight: 1.5, weight: 400 },
|
|
50
|
+
ui: { family: "sans", size: 15, lineHeight: 1.6, weight: 400 },
|
|
51
|
+
label: { family: "sans", size: 13, lineHeight: 1.5, weight: 500 },
|
|
52
|
+
/**
|
|
53
|
+
* La escala de las etiquetas de estado: sans 12.5/500, cuadrada.
|
|
54
|
+
*
|
|
55
|
+
* Es medio píxel por debajo de `label`, y esta vez el medio píxel sí importa:
|
|
56
|
+
* una etiqueta de estado va dentro de una tabla o al lado de un título, y a 13
|
|
57
|
+
* competía con el texto que acompaña. El documento la da en 12.5 y se ve.
|
|
58
|
+
*/
|
|
59
|
+
tag: { family: "sans", size: 12.5, lineHeight: 1.4, weight: 500 },
|
|
60
|
+
/**
|
|
61
|
+
* La escala de categoría y métrica: mono 11.5.
|
|
62
|
+
*
|
|
63
|
+
* Rompe el suelo de `limits.minScreenPx`, y es a propósito. Ese suelo protege
|
|
64
|
+
* al TEXTO —lo que se lee en una frase—, y una píldora de una palabra no es
|
|
65
|
+
* texto corrido: es una marca. El contraste sigue medido y pasa AA (plancton
|
|
66
|
+
* 5.57:1 sobre abismo), que es la parte que no se negocia.
|
|
67
|
+
*
|
|
68
|
+
* A 13 las tres familias de etiqueta salían del tamaño de un botón pequeño y
|
|
69
|
+
* pesaban más que el título que acompañan. Ver `docs/decisiones.md`.
|
|
70
|
+
*/
|
|
71
|
+
chip: { family: "mono", size: 11.5, lineHeight: 1.4, weight: 400 },
|
|
72
|
+
/**
|
|
73
|
+
* Mono SIN transformar: fechas, rutas, versiones, nombres de archivo, la firma
|
|
74
|
+
* del footer y las métricas de etiqueta. Es la mayoría del mono del sistema.
|
|
75
|
+
*
|
|
76
|
+
* Existe porque `eyebrow` lleva `uppercase` de fábrica y no es una preferencia
|
|
77
|
+
* que se pueda apagar: es la escala del eyebrow. Un `18 ago 2026` en versalitas
|
|
78
|
+
* o un `pose-laptop-coffee.png` en mayúsculas están mal, y sin este escalón la
|
|
79
|
+
* única salida era un `normal-case` en cada sitio de uso.
|
|
80
|
+
*
|
|
81
|
+
* El documento dice 12.5. Son 13 por la misma razón por la que `textMuted` se
|
|
82
|
+
* corrigió: `limits.minScreenPx` es 13 y plancton «nunca bajo 13px». Esta
|
|
83
|
+
* escala es justo donde se escriben las metas en muted, así que ponerla en
|
|
84
|
+
* 12.5 habría dejado ilegible su uso más común por medio píxel.
|
|
85
|
+
*/
|
|
86
|
+
meta: { family: "mono", size: 13, lineHeight: 1.6, weight: 400 },
|
|
87
|
+
eyebrow: { family: "mono", size: 12, tracking: "0.12em", transform: "uppercase" }
|
|
88
|
+
};
|
|
89
|
+
var radius = {
|
|
90
|
+
chip: 6,
|
|
91
|
+
control: 10,
|
|
92
|
+
card: 14,
|
|
93
|
+
panel: 16,
|
|
94
|
+
pill: 999
|
|
95
|
+
};
|
|
96
|
+
var control = {
|
|
97
|
+
sm: 14,
|
|
98
|
+
md: 22,
|
|
99
|
+
lg: 30,
|
|
100
|
+
/** Botón de icono: cuadrado, sin texto. */
|
|
101
|
+
icon: 42
|
|
102
|
+
};
|
|
103
|
+
var spacing = {
|
|
104
|
+
stepXs: 8,
|
|
105
|
+
stepSm: 12,
|
|
106
|
+
stepMd: 16,
|
|
107
|
+
stepLg: 26,
|
|
108
|
+
stepXl: 40,
|
|
109
|
+
section: 96
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/lib/cn.ts
|
|
113
|
+
var kebab = (nombre) => nombre.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
|
|
114
|
+
var escalas = Object.keys(typeScale);
|
|
115
|
+
var radios = Object.keys(radius);
|
|
116
|
+
var espacios = Object.keys(spacing).map(kebab);
|
|
117
|
+
var controles = Object.keys(control).map((nombre) => `control-${kebab(nombre)}`);
|
|
118
|
+
var twMerge = tailwindMerge.extendTailwindMerge({
|
|
119
|
+
extend: {
|
|
120
|
+
classGroups: {
|
|
121
|
+
"font-size": [{ text: escalas }],
|
|
122
|
+
rounded: [{ rounded: radios }],
|
|
123
|
+
p: [{ p: espacios }],
|
|
124
|
+
px: [{ px: [...espacios, ...controles] }],
|
|
125
|
+
py: [{ py: espacios }],
|
|
126
|
+
gap: [{ gap: espacios }],
|
|
127
|
+
size: [{ size: controles }]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
function cn(...inputs) {
|
|
132
|
+
return twMerge(clsx.clsx(inputs));
|
|
133
|
+
}
|
|
134
|
+
function Label({ className, ...props }) {
|
|
135
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
136
|
+
LabelPrimitive__namespace.Root,
|
|
137
|
+
{
|
|
138
|
+
className: cn(
|
|
139
|
+
"text-label font-sans text-text-secondary select-none",
|
|
140
|
+
"peer-disabled:opacity-50 group-data-[disabled]:opacity-50",
|
|
141
|
+
className
|
|
142
|
+
),
|
|
143
|
+
...props
|
|
144
|
+
}
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
var ETIQUETA = {
|
|
148
|
+
display: "h1",
|
|
149
|
+
stat: "p",
|
|
150
|
+
h1: "h1",
|
|
151
|
+
h2: "h2",
|
|
152
|
+
h3: "h3",
|
|
153
|
+
body: "p",
|
|
154
|
+
lead: "p",
|
|
155
|
+
ui: "p",
|
|
156
|
+
label: "span",
|
|
157
|
+
tag: "span",
|
|
158
|
+
meta: "p",
|
|
159
|
+
chip: "span",
|
|
160
|
+
eyebrow: "p"
|
|
161
|
+
};
|
|
162
|
+
var texto = classVarianceAuthority.cva("", {
|
|
163
|
+
variants: {
|
|
164
|
+
variant: {
|
|
165
|
+
display: "text-display font-display",
|
|
166
|
+
// Métricas grandes: 46px de display. Es la única escala pensada para un
|
|
167
|
+
// número, y por eso el interlineado es 1.
|
|
168
|
+
stat: "text-stat font-display",
|
|
169
|
+
h1: "text-h1 font-display",
|
|
170
|
+
h2: "text-h2 font-display",
|
|
171
|
+
h3: "text-h3 font-display",
|
|
172
|
+
body: "text-body font-sans",
|
|
173
|
+
// 17px: la bajada de las páginas internas y el botón grande.
|
|
174
|
+
lead: "text-lead font-sans",
|
|
175
|
+
ui: "text-ui font-sans",
|
|
176
|
+
label: "text-label font-sans",
|
|
177
|
+
// Las dos escalas de etiqueta. `tag` es el estado (sans 12.5/500) y `chip`
|
|
178
|
+
// la categoría y la métrica (mono 11.5). Ninguna transforma el texto.
|
|
179
|
+
tag: "text-tag font-sans",
|
|
180
|
+
chip: "text-chip font-mono",
|
|
181
|
+
// Mono SIN transformar: fechas, rutas, versiones, nombres de archivo y la
|
|
182
|
+
// firma del footer. Es el escalón que faltaba, y por el que cada sitio de
|
|
183
|
+
// uso terminaba escribiendo `variant="eyebrow" className="normal-case"`.
|
|
184
|
+
meta: "text-meta font-mono",
|
|
185
|
+
// `uppercase` no cabe en un token de tamaño: text-transform no es un
|
|
186
|
+
// modificador de --text-*. Por eso lo pone la variante y no theme.css.
|
|
187
|
+
eyebrow: "text-eyebrow font-mono uppercase"
|
|
188
|
+
},
|
|
189
|
+
tone: {
|
|
190
|
+
primary: "text-text-primary",
|
|
191
|
+
secondary: "text-text-secondary",
|
|
192
|
+
muted: "text-text-muted",
|
|
193
|
+
accent: "text-accent",
|
|
194
|
+
warm: "text-warm",
|
|
195
|
+
success: "text-success",
|
|
196
|
+
warning: "text-warning",
|
|
197
|
+
error: "text-error"
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
defaultVariants: { variant: "body", tone: "primary" }
|
|
201
|
+
});
|
|
202
|
+
function Text({
|
|
203
|
+
className,
|
|
204
|
+
variant,
|
|
205
|
+
tone,
|
|
206
|
+
as,
|
|
207
|
+
asChild = false,
|
|
208
|
+
measure,
|
|
209
|
+
children,
|
|
210
|
+
...props
|
|
211
|
+
}) {
|
|
212
|
+
const escala = variant ?? "body";
|
|
213
|
+
const limitar = measure ?? escala === "body";
|
|
214
|
+
const clases = cn(texto({ variant, tone }), limitar && "max-w-measure", className);
|
|
215
|
+
if (asChild) {
|
|
216
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactSlot.Slot, { className: clases, ...props, children });
|
|
217
|
+
}
|
|
218
|
+
const Etiqueta = as ?? ETIQUETA[escala];
|
|
219
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Etiqueta, { className: clases, ...props, children });
|
|
220
|
+
}
|
|
221
|
+
var Form = reactHookForm.FormProvider;
|
|
222
|
+
var Campo = react.createContext(null);
|
|
223
|
+
var Item = react.createContext(null);
|
|
224
|
+
function FormField(props) {
|
|
225
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Campo.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsxRuntime.jsx(reactHookForm.Controller, { ...props }) });
|
|
226
|
+
}
|
|
227
|
+
function useFormField() {
|
|
228
|
+
const campo = react.useContext(Campo);
|
|
229
|
+
const item = react.useContext(Item);
|
|
230
|
+
const { getFieldState } = reactHookForm.useFormContext();
|
|
231
|
+
const estado = reactHookForm.useFormState({ name: campo?.name ?? "" });
|
|
232
|
+
if (!campo) throw new Error("useFormField se usa dentro de un <FormField>.");
|
|
233
|
+
if (!item) throw new Error("useFormField se usa dentro de un <FormItem>.");
|
|
234
|
+
return {
|
|
235
|
+
name: campo.name,
|
|
236
|
+
id: item.id,
|
|
237
|
+
idDescripcion: `${item.id}-descripcion`,
|
|
238
|
+
idMensaje: `${item.id}-mensaje`,
|
|
239
|
+
...getFieldState(campo.name, estado)
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function FormItem({ className, ...props }) {
|
|
243
|
+
const id = react.useId();
|
|
244
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Item.Provider, { value: { id }, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("gap-step-xs flex flex-col", className), ...props }) });
|
|
245
|
+
}
|
|
246
|
+
function FormLabel({ className, ...props }) {
|
|
247
|
+
const { id } = useFormField();
|
|
248
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: id, className, ...props });
|
|
249
|
+
}
|
|
250
|
+
function FormControl({ ...props }) {
|
|
251
|
+
const { error, id, idDescripcion, idMensaje } = useFormField();
|
|
252
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
253
|
+
reactSlot.Slot,
|
|
254
|
+
{
|
|
255
|
+
id,
|
|
256
|
+
"aria-describedby": error ? `${idDescripcion} ${idMensaje}` : idDescripcion,
|
|
257
|
+
"aria-invalid": error ? true : void 0,
|
|
258
|
+
...props
|
|
259
|
+
}
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
function FormDescription({ className, ...props }) {
|
|
263
|
+
const { idDescripcion } = useFormField();
|
|
264
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
265
|
+
Text,
|
|
266
|
+
{
|
|
267
|
+
as: "p",
|
|
268
|
+
variant: "label",
|
|
269
|
+
tone: "muted",
|
|
270
|
+
id: idDescripcion,
|
|
271
|
+
className: cn("font-normal", className),
|
|
272
|
+
...props
|
|
273
|
+
}
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
function FormMessage({ className, children, ...props }) {
|
|
277
|
+
const { error, idMensaje } = useFormField();
|
|
278
|
+
const cuerpo = error?.message ? String(error.message) : children;
|
|
279
|
+
if (!cuerpo) return null;
|
|
280
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
281
|
+
Text,
|
|
282
|
+
{
|
|
283
|
+
as: "p",
|
|
284
|
+
variant: "label",
|
|
285
|
+
tone: "error",
|
|
286
|
+
id: idMensaje,
|
|
287
|
+
className,
|
|
288
|
+
...props,
|
|
289
|
+
children: cuerpo
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
exports.Form = Form;
|
|
295
|
+
exports.FormControl = FormControl;
|
|
296
|
+
exports.FormDescription = FormDescription;
|
|
297
|
+
exports.FormField = FormField;
|
|
298
|
+
exports.FormItem = FormItem;
|
|
299
|
+
exports.FormLabel = FormLabel;
|
|
300
|
+
exports.FormMessage = FormMessage;
|
|
301
|
+
exports.useFormField = useFormField;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import * as react_hook_form from 'react-hook-form';
|
|
4
|
+
import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
|
|
5
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
6
|
+
import { L as Label } from '../label-DuTvJGxD.cjs';
|
|
7
|
+
import '@radix-ui/react-label';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* La capa que ata los controles a un formulario con validación y mensajes.
|
|
11
|
+
*
|
|
12
|
+
* La librería ya traía `Input`, `Label`, `Checkbox`, `RadioGroup`, `Select` y
|
|
13
|
+
* `Textarea`, y ninguno sabía nada del otro: el `htmlFor`, el `aria-describedby`
|
|
14
|
+
* del mensaje de error y el `aria-invalid` había que cablearlos a mano en cada
|
|
15
|
+
* campo de cada proyecto. Eso se olvida, y cuando se olvida el fallo es que un
|
|
16
|
+
* lector de pantalla no anuncia por qué el campo está en rojo.
|
|
17
|
+
*
|
|
18
|
+
* Se publica en `@eduardoalvarez/arrecife/form`, NO en la raíz, y eso es
|
|
19
|
+
* deliberado. React Hook Form es una dependencia de pares opcional: solo uno de
|
|
20
|
+
* los cinco proyectos la usa, y si esto colgara del índice principal, los otros
|
|
21
|
+
* cuatro tendrían que instalarla para que su bundler resolviera un import que
|
|
22
|
+
* nunca ejecutan. Es la misma razón por la que `./og` y `./shiki` viven aparte,
|
|
23
|
+
* mirada desde el otro lado: allí se saca React del camino, aquí se saca RHF.
|
|
24
|
+
*
|
|
25
|
+
* La forma es la de shadcn —`FormField` sobre `Controller`, contexto de campo y
|
|
26
|
+
* contexto de item— porque el proyecto que la consume ya está escrito contra
|
|
27
|
+
* ella y reinventarla solo le costaría una migración. Lo que cambia es el
|
|
28
|
+
* vocabulario visual: los ids, la escala y el tono salen del sistema.
|
|
29
|
+
*/
|
|
30
|
+
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getErrors, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
|
|
31
|
+
/**
|
|
32
|
+
* Un campo controlado. Envuelve el `Controller` de RHF y además publica el
|
|
33
|
+
* nombre en contexto, que es de donde lo leen la etiqueta y el mensaje sin que
|
|
34
|
+
* haya que repetirlo tres veces.
|
|
35
|
+
*/
|
|
36
|
+
declare function FormField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>(props: ControllerProps<TFieldValues, TName>): react.JSX.Element;
|
|
37
|
+
/**
|
|
38
|
+
* Lo que necesita cualquier pieza del campo: el nombre, los tres ids y el estado
|
|
39
|
+
* de validación.
|
|
40
|
+
*
|
|
41
|
+
* Tira si se usa fuera de un `FormField` o de un `FormItem`, con el mensaje que
|
|
42
|
+
* dice cuál de los dos falta. Devolver algo a medias sería peor: el campo se
|
|
43
|
+
* renderizaría sin `aria-describedby` y nada avisaría.
|
|
44
|
+
*/
|
|
45
|
+
declare function useFormField(): {
|
|
46
|
+
invalid: boolean;
|
|
47
|
+
isDirty: boolean;
|
|
48
|
+
isTouched: boolean;
|
|
49
|
+
isValidating: boolean;
|
|
50
|
+
error?: react_hook_form.FieldError;
|
|
51
|
+
name: string;
|
|
52
|
+
id: string;
|
|
53
|
+
idDescripcion: string;
|
|
54
|
+
idMensaje: string;
|
|
55
|
+
};
|
|
56
|
+
/** La caja del campo: etiqueta, control, ayuda y mensaje, en columna. */
|
|
57
|
+
declare function FormItem({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
58
|
+
/**
|
|
59
|
+
* La etiqueta NO se tiñe de rojo cuando el campo falla.
|
|
60
|
+
*
|
|
61
|
+
* El sistema deja el color semántico en el borde y en el glifo, y el texto en un
|
|
62
|
+
* token de texto: el borde del control ya está en `error` y el mensaje de abajo
|
|
63
|
+
* también, así que teñir además la etiqueta son tres rojos para un solo fallo.
|
|
64
|
+
*/
|
|
65
|
+
declare function FormLabel({ className, ...props }: ComponentPropsWithoutRef<typeof Label>): react.JSX.Element;
|
|
66
|
+
/**
|
|
67
|
+
* Envuelve al control y le cablea los atributos: el `id` que la etiqueta apunta,
|
|
68
|
+
* el `aria-describedby` con la ayuda y el mensaje, y el `aria-invalid`.
|
|
69
|
+
*
|
|
70
|
+
* Es un `Slot`, así que el hijo puede ser cualquiera de los controles del
|
|
71
|
+
* sistema —`Input`, `Textarea`, `SelectTrigger`— sin que esto sepa cuál.
|
|
72
|
+
*/
|
|
73
|
+
declare function FormControl({ ...props }: ComponentPropsWithoutRef<typeof Slot>): react.JSX.Element;
|
|
74
|
+
/** La ayuda del campo. Se anuncia siempre, haya error o no. */
|
|
75
|
+
declare function FormDescription({ className, ...props }: ComponentPropsWithoutRef<'p'>): react.JSX.Element;
|
|
76
|
+
/**
|
|
77
|
+
* El mensaje de validación. Sin error no renderiza nada: un hueco reservado
|
|
78
|
+
* para el fallo desplaza el resto del formulario cada vez que aparece.
|
|
79
|
+
*
|
|
80
|
+
* Toma el texto del error de RHF; `children` sirve para un mensaje que no venga
|
|
81
|
+
* del esquema —el 409 que devuelve el servidor y que ningún validador de
|
|
82
|
+
* cliente puede anticipar—.
|
|
83
|
+
*/
|
|
84
|
+
declare function FormMessage({ className, children, ...props }: ComponentPropsWithoutRef<'p'>): react.JSX.Element | null;
|
|
85
|
+
|
|
86
|
+
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentPropsWithoutRef } from 'react';
|
|
3
|
+
import * as react_hook_form from 'react-hook-form';
|
|
4
|
+
import { FieldValues, FieldPath, ControllerProps } from 'react-hook-form';
|
|
5
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
6
|
+
import { L as Label } from '../label-DuTvJGxD.js';
|
|
7
|
+
import '@radix-ui/react-label';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* La capa que ata los controles a un formulario con validación y mensajes.
|
|
11
|
+
*
|
|
12
|
+
* La librería ya traía `Input`, `Label`, `Checkbox`, `RadioGroup`, `Select` y
|
|
13
|
+
* `Textarea`, y ninguno sabía nada del otro: el `htmlFor`, el `aria-describedby`
|
|
14
|
+
* del mensaje de error y el `aria-invalid` había que cablearlos a mano en cada
|
|
15
|
+
* campo de cada proyecto. Eso se olvida, y cuando se olvida el fallo es que un
|
|
16
|
+
* lector de pantalla no anuncia por qué el campo está en rojo.
|
|
17
|
+
*
|
|
18
|
+
* Se publica en `@eduardoalvarez/arrecife/form`, NO en la raíz, y eso es
|
|
19
|
+
* deliberado. React Hook Form es una dependencia de pares opcional: solo uno de
|
|
20
|
+
* los cinco proyectos la usa, y si esto colgara del índice principal, los otros
|
|
21
|
+
* cuatro tendrían que instalarla para que su bundler resolviera un import que
|
|
22
|
+
* nunca ejecutan. Es la misma razón por la que `./og` y `./shiki` viven aparte,
|
|
23
|
+
* mirada desde el otro lado: allí se saca React del camino, aquí se saca RHF.
|
|
24
|
+
*
|
|
25
|
+
* La forma es la de shadcn —`FormField` sobre `Controller`, contexto de campo y
|
|
26
|
+
* contexto de item— porque el proyecto que la consume ya está escrito contra
|
|
27
|
+
* ella y reinventarla solo le costaría una migración. Lo que cambia es el
|
|
28
|
+
* vocabulario visual: los ids, la escala y el tono salen del sistema.
|
|
29
|
+
*/
|
|
30
|
+
declare const Form: <TFieldValues extends FieldValues, TContext = any, TTransformedValues = TFieldValues>({ children, watch, getValues, getErrors, getFieldState, setError, clearErrors, setValue, setValues, trigger, formState, resetField, reset, resetDefaultValues, handleSubmit, unregister, control, register, setFocus, subscribe, }: react_hook_form.FormProviderProps<TFieldValues, TContext, TTransformedValues>) => React.JSX.Element;
|
|
31
|
+
/**
|
|
32
|
+
* Un campo controlado. Envuelve el `Controller` de RHF y además publica el
|
|
33
|
+
* nombre en contexto, que es de donde lo leen la etiqueta y el mensaje sin que
|
|
34
|
+
* haya que repetirlo tres veces.
|
|
35
|
+
*/
|
|
36
|
+
declare function FormField<TFieldValues extends FieldValues = FieldValues, TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>>(props: ControllerProps<TFieldValues, TName>): react.JSX.Element;
|
|
37
|
+
/**
|
|
38
|
+
* Lo que necesita cualquier pieza del campo: el nombre, los tres ids y el estado
|
|
39
|
+
* de validación.
|
|
40
|
+
*
|
|
41
|
+
* Tira si se usa fuera de un `FormField` o de un `FormItem`, con el mensaje que
|
|
42
|
+
* dice cuál de los dos falta. Devolver algo a medias sería peor: el campo se
|
|
43
|
+
* renderizaría sin `aria-describedby` y nada avisaría.
|
|
44
|
+
*/
|
|
45
|
+
declare function useFormField(): {
|
|
46
|
+
invalid: boolean;
|
|
47
|
+
isDirty: boolean;
|
|
48
|
+
isTouched: boolean;
|
|
49
|
+
isValidating: boolean;
|
|
50
|
+
error?: react_hook_form.FieldError;
|
|
51
|
+
name: string;
|
|
52
|
+
id: string;
|
|
53
|
+
idDescripcion: string;
|
|
54
|
+
idMensaje: string;
|
|
55
|
+
};
|
|
56
|
+
/** La caja del campo: etiqueta, control, ayuda y mensaje, en columna. */
|
|
57
|
+
declare function FormItem({ className, ...props }: ComponentPropsWithoutRef<'div'>): react.JSX.Element;
|
|
58
|
+
/**
|
|
59
|
+
* La etiqueta NO se tiñe de rojo cuando el campo falla.
|
|
60
|
+
*
|
|
61
|
+
* El sistema deja el color semántico en el borde y en el glifo, y el texto en un
|
|
62
|
+
* token de texto: el borde del control ya está en `error` y el mensaje de abajo
|
|
63
|
+
* también, así que teñir además la etiqueta son tres rojos para un solo fallo.
|
|
64
|
+
*/
|
|
65
|
+
declare function FormLabel({ className, ...props }: ComponentPropsWithoutRef<typeof Label>): react.JSX.Element;
|
|
66
|
+
/**
|
|
67
|
+
* Envuelve al control y le cablea los atributos: el `id` que la etiqueta apunta,
|
|
68
|
+
* el `aria-describedby` con la ayuda y el mensaje, y el `aria-invalid`.
|
|
69
|
+
*
|
|
70
|
+
* Es un `Slot`, así que el hijo puede ser cualquiera de los controles del
|
|
71
|
+
* sistema —`Input`, `Textarea`, `SelectTrigger`— sin que esto sepa cuál.
|
|
72
|
+
*/
|
|
73
|
+
declare function FormControl({ ...props }: ComponentPropsWithoutRef<typeof Slot>): react.JSX.Element;
|
|
74
|
+
/** La ayuda del campo. Se anuncia siempre, haya error o no. */
|
|
75
|
+
declare function FormDescription({ className, ...props }: ComponentPropsWithoutRef<'p'>): react.JSX.Element;
|
|
76
|
+
/**
|
|
77
|
+
* El mensaje de validación. Sin error no renderiza nada: un hueco reservado
|
|
78
|
+
* para el fallo desplaza el resto del formulario cada vez que aparece.
|
|
79
|
+
*
|
|
80
|
+
* Toma el texto del error de RHF; `children` sirve para un mensaje que no venga
|
|
81
|
+
* del esquema —el 409 que devuelve el servidor y que ningún validador de
|
|
82
|
+
* cliente puede anticipar—.
|
|
83
|
+
*/
|
|
84
|
+
declare function FormMessage({ className, children, ...props }: ComponentPropsWithoutRef<'p'>): react.JSX.Element | null;
|
|
85
|
+
|
|
86
|
+
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Label } from '../chunk-ZEOQKRQ7.js';
|
|
2
|
+
import { Text } from '../chunk-UOWIDFCB.js';
|
|
3
|
+
import { cn } from '../chunk-NHS7ETKJ.js';
|
|
4
|
+
import '../chunk-TSPJOM6K.js';
|
|
5
|
+
import '../chunk-MLKGABMK.js';
|
|
6
|
+
import { createContext, useContext, useId } from 'react';
|
|
7
|
+
import { FormProvider, Controller, useFormContext, useFormState } from 'react-hook-form';
|
|
8
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
9
|
+
import { jsx } from 'react/jsx-runtime';
|
|
10
|
+
|
|
11
|
+
var Form = FormProvider;
|
|
12
|
+
var Campo = createContext(null);
|
|
13
|
+
var Item = createContext(null);
|
|
14
|
+
function FormField(props) {
|
|
15
|
+
return /* @__PURE__ */ jsx(Campo.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx(Controller, { ...props }) });
|
|
16
|
+
}
|
|
17
|
+
function useFormField() {
|
|
18
|
+
const campo = useContext(Campo);
|
|
19
|
+
const item = useContext(Item);
|
|
20
|
+
const { getFieldState } = useFormContext();
|
|
21
|
+
const estado = useFormState({ name: campo?.name ?? "" });
|
|
22
|
+
if (!campo) throw new Error("useFormField se usa dentro de un <FormField>.");
|
|
23
|
+
if (!item) throw new Error("useFormField se usa dentro de un <FormItem>.");
|
|
24
|
+
return {
|
|
25
|
+
name: campo.name,
|
|
26
|
+
id: item.id,
|
|
27
|
+
idDescripcion: `${item.id}-descripcion`,
|
|
28
|
+
idMensaje: `${item.id}-mensaje`,
|
|
29
|
+
...getFieldState(campo.name, estado)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function FormItem({ className, ...props }) {
|
|
33
|
+
const id = useId();
|
|
34
|
+
return /* @__PURE__ */ jsx(Item.Provider, { value: { id }, children: /* @__PURE__ */ jsx("div", { className: cn("gap-step-xs flex flex-col", className), ...props }) });
|
|
35
|
+
}
|
|
36
|
+
function FormLabel({ className, ...props }) {
|
|
37
|
+
const { id } = useFormField();
|
|
38
|
+
return /* @__PURE__ */ jsx(Label, { htmlFor: id, className, ...props });
|
|
39
|
+
}
|
|
40
|
+
function FormControl({ ...props }) {
|
|
41
|
+
const { error, id, idDescripcion, idMensaje } = useFormField();
|
|
42
|
+
return /* @__PURE__ */ jsx(
|
|
43
|
+
Slot,
|
|
44
|
+
{
|
|
45
|
+
id,
|
|
46
|
+
"aria-describedby": error ? `${idDescripcion} ${idMensaje}` : idDescripcion,
|
|
47
|
+
"aria-invalid": error ? true : void 0,
|
|
48
|
+
...props
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
function FormDescription({ className, ...props }) {
|
|
53
|
+
const { idDescripcion } = useFormField();
|
|
54
|
+
return /* @__PURE__ */ jsx(
|
|
55
|
+
Text,
|
|
56
|
+
{
|
|
57
|
+
as: "p",
|
|
58
|
+
variant: "label",
|
|
59
|
+
tone: "muted",
|
|
60
|
+
id: idDescripcion,
|
|
61
|
+
className: cn("font-normal", className),
|
|
62
|
+
...props
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function FormMessage({ className, children, ...props }) {
|
|
67
|
+
const { error, idMensaje } = useFormField();
|
|
68
|
+
const cuerpo = error?.message ? String(error.message) : children;
|
|
69
|
+
if (!cuerpo) return null;
|
|
70
|
+
return /* @__PURE__ */ jsx(
|
|
71
|
+
Text,
|
|
72
|
+
{
|
|
73
|
+
as: "p",
|
|
74
|
+
variant: "label",
|
|
75
|
+
tone: "error",
|
|
76
|
+
id: idMensaje,
|
|
77
|
+
className,
|
|
78
|
+
...props,
|
|
79
|
+
children: cuerpo
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, useFormField };
|