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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kaizen-lib",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Tokens y componentes compartidos entre los fronts de Kaizen.",
5
5
  "license": "MIT",
6
6
  "author": "Nicolás Sandobal",
@@ -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="Cerrar">
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
@@ -9,3 +9,5 @@ export { LangSelector } from './LangSelector';
9
9
  export type { OpcionDeIdioma } from './LangSelector';
10
10
  export { Modal } from './Modal';
11
11
  export type { ModalSize } from './Modal';
12
+ export { Badge } from './Badge';
13
+ export type { BadgeTone } from './Badge';