kaizen-lib 0.2.7 → 0.2.9
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/README.md +19 -2
- package/package.json +1 -1
- package/src/ui/Badge.tsx +77 -0
- package/src/ui/Modal.tsx +8 -1
- package/src/ui/index.ts +2 -0
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ styled-components con `moduleResolution: "bundler"`, así que lo transpilan ello
|
|
|
12
12
|
|---|---|---|
|
|
13
13
|
| `kaizen-lib/tokens` | Referencias `var(--kz-*)` tipadas | listo |
|
|
14
14
|
| `kaizen-lib/tokens.css` | Valores por defecto, tema oscuro y claro | listo |
|
|
15
|
-
| `kaizen-lib/ui` | Componentes (`IconButton`, `ReactionButton`, `Medidor`, `Relieve`, `LangSelector`, `Modal`) | en curso |
|
|
15
|
+
| `kaizen-lib/ui` | Componentes (`IconButton`, `ReactionButton`, `Medidor`, `Relieve`, `LangSelector`, `Modal`, `Badge`) | en curso |
|
|
16
16
|
|
|
17
17
|
## ¿Qué uso para qué?
|
|
18
18
|
|
|
@@ -30,6 +30,7 @@ alcanza, extendé el componente en vez de bypassearlo (ver `implement.md` del ha
|
|
|
30
30
|
| Un ciclador de idioma con banderas en la topbar | `LangSelector` | reconstruir el ciclado a mano — ya lo resuelve, con `Relieve` incluido |
|
|
31
31
|
| Un panel flotante que bloquea el resto de la página hasta que se resuelve (formulario, confirmación, contenido a pantalla completa) | `Modal` | un overlay propio — portal, foco atrapado, `Escape`, scroll bloqueado y anidamiento ya están resueltos ahí |
|
|
32
32
|
| Un arco/medidor que muestra cuánto de un total está ocupado | `Medidor` | un `<progress>` o una barra propia |
|
|
33
|
+
| Una píldora de estado (activo, vencido, un rol, una etiqueta) con fondo lavado y texto del mismo tono | `Badge` | un `styled.span` propio por producto — es exactamente lo que ya había triplicado |
|
|
33
34
|
| Espaciado, radios, color, tipografía, sombra | `kaizen-lib/tokens` (`space`, `radius`, `fg`, `accent`, `font`, `shadow`, `relief`...) | un valor a mano — cada uno es una referencia `var(--kz-*)` |
|
|
34
35
|
|
|
35
36
|
Ningún componente trae íconos ni banderas: los recibe como `children`/`icono`, y el
|
|
@@ -242,12 +243,28 @@ Sin `title` no hay nombre accesible propio — pasá `ariaLabel`. `size` (`sm`|`
|
|
|
242
243
|
fija el ancho máximo del panel; `maxWidth` lo pisa con un valor explícito. `disableClose`
|
|
243
244
|
bloquea la X, el overlay y `Escape` a la vez — para una operación en curso, no para "no
|
|
244
245
|
quiero que cierre nunca". `dismissOnOverlay={false}` sólo desactiva el click afuera, para
|
|
245
|
-
un formulario a medio llenar que sigue queriendo cerrar con la X o `Escape`.
|
|
246
|
+
un formulario a medio llenar que sigue queriendo cerrar con la X o `Escape`. `closeLabel`
|
|
247
|
+
(default `'Cerrar'`) es el nombre accesible de la X — mismo criterio que `etiqueta` de
|
|
248
|
+
`LangSelector`: un consumidor con más de un idioma lo pisa, el resto no lo toca.
|
|
246
249
|
|
|
247
250
|
Lo que **no** trae: un `placement` lateral (drawer) y un padding compacto de cuerpo son
|
|
248
251
|
casos de un solo consumidor cada uno (`components/ui/Dialog` de valle-verde) — se resuelven
|
|
249
252
|
ahí con un wrapper local hasta que un segundo consumidor los pida acá.
|
|
250
253
|
|
|
254
|
+
```tsx
|
|
255
|
+
import { Badge } from 'kaizen-lib/ui';
|
|
256
|
+
|
|
257
|
+
<Badge tone="success">Activo</Badge>
|
|
258
|
+
<Badge tone="danger" bordered>Vencido</Badge>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**`Badge`** es una píldora de estado: fondo lavado + texto del mismo tono (`neutral` |
|
|
262
|
+
`accent` | `success` | `danger` | `warning` | `info`, default `neutral`), mayúsculas
|
|
263
|
+
apretadas. Elicitado de tres implementaciones ya duplicadas (taboo-next, valle-verde,
|
|
264
|
+
kaizen-next) — el look es el de valle-verde. `bordered` agrega un borde de 1px del color
|
|
265
|
+
pleno del tono, para cuando el fondo lavado solo no alcanza de contraste contra la
|
|
266
|
+
superficie de atrás; no es una segunda estética, es el mismo tono con más peso.
|
|
267
|
+
|
|
251
268
|
## Desarrollo
|
|
252
269
|
|
|
253
270
|
```bash
|
package/package.json
CHANGED
package/src/ui/Badge.tsx
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { HTMLAttributes, ReactNode } from 'react';
|
|
4
|
+
import styled from 'styled-components';
|
|
5
|
+
import { accent, danger, fg, font, info, radius, space, success, surface, warning } from '../tokens';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* El tono resuelve un TRÍO por tabla: fondo lavado, texto y el color pleno
|
|
9
|
+
* (para `bordered`) — así no puede salir una píldora con el texto invisible
|
|
10
|
+
* sobre su propio fondo, ni un borde que no combina con ninguno de los dos.
|
|
11
|
+
*/
|
|
12
|
+
export type BadgeTone = 'neutral' | 'accent' | 'success' | 'danger' | 'warning' | 'info';
|
|
13
|
+
|
|
14
|
+
const TONOS: Record<BadgeTone, { fondo: string; texto: string; pleno: string }> = {
|
|
15
|
+
neutral: { fondo: surface.hover, texto: fg.muted, pleno: fg.muted },
|
|
16
|
+
accent: { fondo: accent.soft, texto: accent.fg, pleno: accent.DEFAULT },
|
|
17
|
+
success: { fondo: success.soft, texto: success.fg, pleno: success.DEFAULT },
|
|
18
|
+
danger: { fondo: danger.soft, texto: danger.fg, pleno: danger.DEFAULT },
|
|
19
|
+
warning: { fondo: warning.soft, texto: warning.fg, pleno: warning.DEFAULT },
|
|
20
|
+
info: { fondo: info.soft, texto: info.fg, pleno: info.DEFAULT },
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type Props = {
|
|
24
|
+
tone?: BadgeTone;
|
|
25
|
+
/** Borde de 1px del color pleno del tono. Para una píldora que necesita más peso que el fondo lavado solo. */
|
|
26
|
+
bordered?: boolean;
|
|
27
|
+
children: ReactNode;
|
|
28
|
+
className?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const Raiz = styled.span<{ $tone: BadgeTone; $bordered: boolean }>`
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: ${space[1]};
|
|
35
|
+
padding: 3px ${space[2]};
|
|
36
|
+
border-radius: ${radius.sm};
|
|
37
|
+
border: 1px solid ${({ $tone, $bordered }) => ($bordered ? TONOS[$tone].pleno : 'transparent')};
|
|
38
|
+
font-size: ${font.xs};
|
|
39
|
+
font-weight: 600;
|
|
40
|
+
line-height: 1.35;
|
|
41
|
+
letter-spacing: 0.04em;
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
background: ${({ $tone }) => TONOS[$tone].fondo};
|
|
45
|
+
color: ${({ $tone }) => TONOS[$tone].texto};
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Píldora de estado: fondo lavado + texto del mismo tono, mayúsculas
|
|
50
|
+
* apretadas. Elicitado de las implementaciones ya duplicadas en tres
|
|
51
|
+
* productos (taboo-next, valle-verde, kaizen-next) — el look es el de
|
|
52
|
+
* valle-verde, que era el más resuelto de los tres.
|
|
53
|
+
*
|
|
54
|
+
* ```tsx
|
|
55
|
+
* <Badge tone="success">Activo</Badge>
|
|
56
|
+
* <Badge tone="danger" bordered>Vencido</Badge>
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* `bordered` es opcional: el fondo lavado solo ya es suficiente contraste
|
|
60
|
+
* para la mayoría de los casos (ver `bordered={false}`, el default). Se usa
|
|
61
|
+
* cuando la píldora flota sobre una superficie parecida a su propio fondo y
|
|
62
|
+
* necesita un borde para no perderse — no es una segunda variante estética,
|
|
63
|
+
* es el mismo tono con más peso.
|
|
64
|
+
*/
|
|
65
|
+
export function Badge({
|
|
66
|
+
tone = 'neutral',
|
|
67
|
+
bordered = false,
|
|
68
|
+
children,
|
|
69
|
+
className,
|
|
70
|
+
...rest
|
|
71
|
+
}: Props & Omit<HTMLAttributes<HTMLSpanElement>, keyof Props>) {
|
|
72
|
+
return (
|
|
73
|
+
<Raiz $tone={tone} $bordered={bordered} className={className} {...rest}>
|
|
74
|
+
{children}
|
|
75
|
+
</Raiz>
|
|
76
|
+
);
|
|
77
|
+
}
|
package/src/ui/Modal.tsx
CHANGED
|
@@ -185,6 +185,12 @@ type Props = {
|
|
|
185
185
|
maxWidth?: string;
|
|
186
186
|
/** Botón de cierre en la esquina. Default `true`. */
|
|
187
187
|
showClose?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* Nombre accesible del botón de cierre. Default `'Cerrar'` — mismo criterio
|
|
190
|
+
* que `LangSelector.etiqueta`: un consumidor con más de un idioma lo pisa,
|
|
191
|
+
* el resto no necesita tocarlo.
|
|
192
|
+
*/
|
|
193
|
+
closeLabel?: string;
|
|
188
194
|
/** Click afuera del panel cierra. Default `true`. */
|
|
189
195
|
dismissOnOverlay?: boolean;
|
|
190
196
|
/** Operación en curso: ni la X, ni el overlay, ni Escape cierran. */
|
|
@@ -217,6 +223,7 @@ export function Modal({
|
|
|
217
223
|
size = 'md',
|
|
218
224
|
maxWidth,
|
|
219
225
|
showClose = true,
|
|
226
|
+
closeLabel = 'Cerrar',
|
|
220
227
|
dismissOnOverlay = true,
|
|
221
228
|
disableClose = false,
|
|
222
229
|
ariaLabel,
|
|
@@ -327,7 +334,7 @@ export function Modal({
|
|
|
327
334
|
onKeyDown={manejarTab}
|
|
328
335
|
>
|
|
329
336
|
{showClose ? (
|
|
330
|
-
<Cerrar type="button" onClick={onClose} disabled={disableClose} aria-label=
|
|
337
|
+
<Cerrar type="button" onClick={onClose} disabled={disableClose} aria-label={closeLabel}>
|
|
331
338
|
×
|
|
332
339
|
</Cerrar>
|
|
333
340
|
) : null}
|
package/src/ui/index.ts
CHANGED