@doscientos/ui 0.1.14 → 0.1.16

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
@@ -55,11 +55,28 @@ El combobox es composable: la aplicación controla datos, peticiones y caché; l
55
55
  ## Desarrollo
56
56
 
57
57
  - `pnpm test`: pruebas unitarias y de renderizado.
58
+ - `pnpm test:storybook`: renderizado, accesibilidad e interacciones de todas las stories en Chromium.
58
59
  - `pnpm typecheck`: contrato TypeScript.
59
60
  - `pnpm build`: distribución JS, tipos y CSS Tailwind compilado.
60
61
  - `pnpm storybook`: catálogo local en el puerto 6006.
61
62
  - `pnpm build-storybook`: sitio estático en `storybook-static/`, listo para publicar en `ui.doscientos.es`.
62
63
 
64
+ Storybook genera documentación automática para todo el catálogo y ofrece temas
65
+ claro/oscuro y viewports móvil, tablet y escritorio. Chromatic captura cada
66
+ story en claro escritorio, oscuro escritorio y claro móvil. Para activar el job
67
+ visual de CI, configura `CHROMATIC_PROJECT_TOKEN` como secreto del repositorio;
68
+ el valor se consume mediante el entorno y nunca se pasa como argumento del CLI.
69
+
70
+ ### Criterio de stories
71
+
72
+ Cada componente público debe tener una story propia. Cuando apliquen, incluye
73
+ estado por defecto, variantes, disabled o read-only, error, carga o vacío,
74
+ contenido largo y viewport estrecho. Los controles interactivos deben añadir
75
+ un flujo `play` que cubra su comportamiento principal con teclado y foco.
76
+
77
+ Las stories deben usar datos ficticios y permanecer independientes de rutas,
78
+ APIs, autenticación y estado de producto.
79
+
63
80
  ## Criterio de crecimiento
64
81
 
65
82
  Un componente entra cuando ya resuelve dos contextos reales o una necesidad transversal de accesibilidad. No se incorporan entidades de negocio, consultas, rutas ni variaciones visuales exclusivas de un cliente.
package/dist/index.cjs CHANGED
@@ -364,6 +364,7 @@ function getTextMatchParts(text, query) {
364
364
  }
365
365
 
366
366
  // src/ui/accordion/accordion.tsx
367
+ var React = require("react");
367
368
  var import_lucide_react = require("lucide-react");
368
369
  var import_react_aria_components = require("react-aria-components");
369
370
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -372,7 +373,7 @@ function Accordion({ className, ...props }) {
372
373
  import_react_aria_components.DisclosureGroup,
373
374
  {
374
375
  "data-slot": "accordion",
375
- className: cn("flex w-full flex-col", className),
376
+ className: cn("flex w-full flex-col text-foreground", className),
376
377
  ...props
377
378
  }
378
379
  );
@@ -398,7 +399,7 @@ function AccordionTrigger({
398
399
  slot: "trigger",
399
400
  "data-slot": "accordion-trigger",
400
401
  className: cn(
401
- "group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
402
+ "group/accordion-trigger relative flex flex-1 items-start justify-between gap-3 rounded-lg border border-transparent px-2.5 py-2.5 text-left text-sm font-medium text-foreground transition-colors outline-none hover:bg-muted/50 aria-expanded:bg-muted/50 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground",
402
403
  className
403
404
  ),
404
405
  ...props,
@@ -422,18 +423,22 @@ function AccordionTrigger({
422
423
  }
423
424
  ) });
424
425
  }
425
- function AccordionContent({ className, children, ...props }) {
426
+ function AccordionContent({
427
+ className,
428
+ children,
429
+ ...props
430
+ }) {
426
431
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
427
432
  import_react_aria_components.DisclosurePanel,
428
433
  {
429
434
  "data-slot": "accordion-content",
430
- className: "h-(--disclosure-panel-height) overflow-clip text-sm transition-[height] data-open:animate-accordion-down data-closed:animate-accordion-up",
435
+ className: "h-(--disclosure-panel-height) overflow-clip text-sm text-muted-foreground transition-[height] data-open:animate-accordion-down data-closed:animate-accordion-up",
431
436
  ...props,
432
437
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
433
438
  "div",
434
439
  {
435
440
  className: cn(
436
- "pt-0 pb-2.5 [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-foreground [&_p:not(:last-child)]:mb-4",
441
+ "px-2.5 pt-0 pb-3 [&_a]:text-foreground [&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-primary [&_p:not(:last-child)]:mb-4",
437
442
  className
438
443
  ),
439
444
  children
@@ -1171,7 +1176,7 @@ function DrawerDescription({ className, ...props }) {
1171
1176
  }
1172
1177
 
1173
1178
  // src/ui/dropdown-menu/dropdown-menu.tsx
1174
- var React = require("react");
1179
+ var React2 = require("react");
1175
1180
  var import_class_variance_authority5 = require("class-variance-authority");
1176
1181
  var import_lucide_react4 = require("lucide-react");
1177
1182
  var import_react_aria_components11 = require("react-aria-components");
@@ -1674,7 +1679,7 @@ function FormRow({ label, htmlFor, required, hint, error, className, children })
1674
1679
  var import_react_aria_components14 = require("react-aria-components");
1675
1680
 
1676
1681
  // src/ui/tooltip/tooltip.tsx
1677
- var React2 = __toESM(require("react"), 1);
1682
+ var React3 = __toESM(require("react"), 1);
1678
1683
  var import_react_aria_components13 = require("react-aria-components");
1679
1684
  var import_jsx_runtime23 = require("react/jsx-runtime");
1680
1685
  function TooltipTrigger({
@@ -1682,7 +1687,7 @@ function TooltipTrigger({
1682
1687
  children,
1683
1688
  ...props
1684
1689
  }) {
1685
- const [trigger, tooltip] = React2.Children.toArray(children);
1690
+ const [trigger, tooltip] = React3.Children.toArray(children);
1686
1691
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
1687
1692
  import_react_aria_components13.TooltipTrigger,
1688
1693
  {
@@ -2586,59 +2591,108 @@ function SubmitButton({ pendingLabel = "Guardando\u2026", loading = false, child
2586
2591
  }
2587
2592
 
2588
2593
  // src/ui/switch/switch.tsx
2594
+ var import_react12 = require("react");
2589
2595
  var import_react_aria_components25 = require("react-aria-components");
2590
2596
  var import_jsx_runtime42 = require("react/jsx-runtime");
2591
- var sizeStyles = {
2592
- sm: { track: "h-5 w-9 p-0.5", thumb: "size-4", travel: "group-data-selected/switch:translate-x-4" },
2593
- md: { track: "h-6 w-11 p-0.5", thumb: "size-5", travel: "group-data-selected/switch:translate-x-5" }
2597
+ var switchSizes = {
2598
+ sm: {
2599
+ control: "h-4 w-[1.875rem] p-0.5",
2600
+ thumb: "h-3 w-4",
2601
+ offset: "0.625rem"
2602
+ },
2603
+ md: {
2604
+ control: "h-5 w-9 p-0.5",
2605
+ thumb: "h-4 w-5",
2606
+ offset: "0.75rem"
2607
+ },
2608
+ lg: {
2609
+ control: "h-6 w-11 p-0.5",
2610
+ thumb: "h-5 w-6",
2611
+ offset: "1rem"
2612
+ }
2594
2613
  };
2595
- function Switch({ className, children, size = "sm", ...props }) {
2596
- const styles = sizeStyles[size];
2597
- return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(import_react_aria_components25.SwitchField, { ...props, children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2598
- import_react_aria_components25.SwitchButton,
2614
+ function Switch({
2615
+ className,
2616
+ children,
2617
+ description,
2618
+ icon,
2619
+ inputRef,
2620
+ isDisabled,
2621
+ isReadOnly,
2622
+ selectedIcon,
2623
+ size = "sm",
2624
+ ...props
2625
+ }) {
2626
+ const styles = switchSizes[size];
2627
+ const fallbackInputRef = (0, import_react12.useRef)(null);
2628
+ const resolvedInputRef = inputRef ?? fallbackInputRef;
2629
+ (0, import_react12.useEffect)(() => {
2630
+ const input = resolvedInputRef.current;
2631
+ if (!input) return;
2632
+ const handleDirectionalKey = (event) => {
2633
+ if (event.target !== input || event.defaultPrevented || isDisabled || isReadOnly || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
2634
+ const nextSelected = event.key === "ArrowRight" ? true : event.key === "ArrowLeft" ? false : null;
2635
+ if (nextSelected === null) return;
2636
+ event.preventDefault();
2637
+ if (input.checked !== nextSelected) input.click();
2638
+ };
2639
+ const ownerDocument = input.ownerDocument;
2640
+ ownerDocument.addEventListener("keydown", handleDirectionalKey);
2641
+ return () => ownerDocument.removeEventListener("keydown", handleDirectionalKey);
2642
+ }, [isDisabled, isReadOnly, resolvedInputRef]);
2643
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2644
+ import_react_aria_components25.Switch,
2599
2645
  {
2600
2646
  "data-slot": "switch",
2601
2647
  "data-size": size,
2602
- className: (state) => cn(
2603
- "group/switch inline-flex w-max items-center gap-2 text-sm text-foreground outline-none",
2648
+ inputRef: resolvedInputRef,
2649
+ isDisabled,
2650
+ isReadOnly,
2651
+ className: cn(
2652
+ "group/switch inline-flex cursor-pointer items-center gap-3 text-sm text-foreground outline-none select-none",
2604
2653
  "data-disabled:cursor-not-allowed data-disabled:opacity-50",
2605
- typeof className === "function" ? className(state) : className
2654
+ className
2606
2655
  ),
2656
+ ...props,
2607
2657
  children: (state) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
2608
2658
  /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2609
2659
  "span",
2610
2660
  {
2611
2661
  "aria-hidden": "true",
2662
+ "data-slot": "switch-control",
2612
2663
  className: cn(
2613
- "relative inline-flex shrink-0 items-center rounded-full border border-transparent bg-secondary",
2614
- "shadow-[inset_0_1px_2px_rgb(0_0_0/0.08)]",
2615
- "transition-[background-color,box-shadow] duration-200 ease-out",
2616
- "group-data-selected/switch:bg-primary group-data-hovered/switch:bg-secondary/80",
2617
- "group-data-selected/switch:group-data-hovered/switch:bg-primary/90",
2618
- "group-data-focus-visible/switch:ring-3 group-data-focus-visible/switch:ring-ring/40",
2619
- "group-data-pressed/switch:ring-4 group-data-pressed/switch:ring-primary/10",
2620
- "motion-reduce:transition-none",
2621
- styles.track
2664
+ "relative inline-flex shrink-0 items-center overflow-hidden rounded-full border border-border bg-input shadow-inner",
2665
+ "transition-[background-color,border-color,box-shadow] duration-200 ease-out motion-reduce:transition-none",
2666
+ "group-data-hovered/switch:bg-muted-foreground/25",
2667
+ "group-data-selected/switch:border-primary group-data-selected/switch:bg-primary",
2668
+ "group-data-selected/switch:group-data-hovered/switch:bg-primary/85",
2669
+ "group-data-focus-visible/switch:ring-3 group-data-focus-visible/switch:ring-ring/50",
2670
+ styles.control
2622
2671
  ),
2623
2672
  children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2624
2673
  "span",
2625
2674
  {
2675
+ "data-slot": "switch-thumb",
2676
+ style: { transform: state.isSelected ? `translate3d(${styles.offset}, 0, 0)` : "translate3d(0, 0, 0)" },
2626
2677
  className: cn(
2627
- "pointer-events-none block rounded-full bg-background shadow-[0_1px_2px_rgb(0_0_0/0.18)]",
2628
- "transition-[transform,background-color,box-shadow] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)]",
2629
- "group-data-selected/switch:bg-primary-foreground group-data-selected/switch:shadow-[0_1px_3px_rgb(0_0_0/0.22)]",
2630
- "group-data-pressed/switch:scale-[0.94] motion-reduce:transition-none",
2631
- styles.travel,
2678
+ "grid shrink-0 place-items-center rounded-full border border-border/60 bg-background text-[0.625rem] text-muted-foreground shadow-sm",
2679
+ "will-change-transform transition-[transform,background-color,color,border-color] duration-[240ms] ease-[cubic-bezier(0.2,0.8,0.2,1)] motion-reduce:transition-none",
2680
+ "group-data-selected/switch:border-primary-foreground/20",
2681
+ "group-data-selected/switch:bg-primary-foreground group-data-selected/switch:text-primary",
2632
2682
  styles.thumb
2633
- )
2683
+ ),
2684
+ children: state.isSelected ? selectedIcon ?? icon : icon
2634
2685
  }
2635
2686
  )
2636
2687
  }
2637
2688
  ),
2638
- children != null && /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { className: "select-none font-medium leading-5 text-foreground", children: typeof children === "function" ? children(state) : children })
2689
+ children || description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "grid gap-0.5 leading-tight", children: [
2690
+ children ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-slot": "switch-label", className: "font-medium", children: typeof children === "function" ? children(state) : children }) : null,
2691
+ description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-slot": "switch-description", className: "text-xs font-normal text-muted-foreground", children: description }) : null
2692
+ ] }) : null
2639
2693
  ] })
2640
2694
  }
2641
- ) });
2695
+ );
2642
2696
  }
2643
2697
 
2644
2698
  // src/ui/table/table.tsx
@@ -2743,7 +2797,7 @@ function TabsContent({ className, ...props }) {
2743
2797
  }
2744
2798
 
2745
2799
  // src/ui/toast/toast.tsx
2746
- var import_react12 = require("react");
2800
+ var import_react13 = require("react");
2747
2801
  var import_sileo = require("sileo");
2748
2802
  var import_jsx_runtime45 = require("react/jsx-runtime");
2749
2803
  function toSileoOptions({ title, description, duration, position, action }) {
@@ -2798,7 +2852,7 @@ function ToastViewport({ position, visiblePosition, className }) {
2798
2852
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Toaster, { position });
2799
2853
  }
2800
2854
  function Toast({ id, title, description, variant = "default", action, state = "open", duration = 0, position, onDismiss }) {
2801
- (0, import_react12.useEffect)(() => {
2855
+ (0, import_react13.useEffect)(() => {
2802
2856
  if (state !== "open") return;
2803
2857
  const toastId = create({ title, description, variant, action, duration, position });
2804
2858
  return () => {