@doscientos/ui 0.1.15 → 0.1.17

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,124 @@ 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",
2601
+ idleWidth: "0.75rem",
2602
+ activeWidth: "1rem",
2603
+ selectedOffset: "0.875rem",
2604
+ activeSelectedOffset: "0.625rem"
2605
+ },
2606
+ md: {
2607
+ control: "h-5 w-9 p-0.5",
2608
+ thumb: "h-4",
2609
+ idleWidth: "1rem",
2610
+ activeWidth: "1.25rem",
2611
+ selectedOffset: "1rem",
2612
+ activeSelectedOffset: "0.75rem"
2613
+ },
2614
+ lg: {
2615
+ control: "h-6 w-11 p-0.5",
2616
+ thumb: "h-5",
2617
+ idleWidth: "1.25rem",
2618
+ activeWidth: "1.5rem",
2619
+ selectedOffset: "1.25rem",
2620
+ activeSelectedOffset: "1rem"
2621
+ }
2594
2622
  };
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,
2623
+ function Switch({
2624
+ className,
2625
+ children,
2626
+ description,
2627
+ icon,
2628
+ inputRef,
2629
+ isDisabled,
2630
+ isReadOnly,
2631
+ selectedIcon,
2632
+ size = "sm",
2633
+ ...props
2634
+ }) {
2635
+ const styles = switchSizes[size];
2636
+ const fallbackInputRef = (0, import_react12.useRef)(null);
2637
+ const resolvedInputRef = inputRef ?? fallbackInputRef;
2638
+ (0, import_react12.useEffect)(() => {
2639
+ const input = resolvedInputRef.current;
2640
+ if (!input) return;
2641
+ const handleDirectionalKey = (event) => {
2642
+ if (event.target !== input || event.defaultPrevented || isDisabled || isReadOnly || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
2643
+ const nextSelected = event.key === "ArrowRight" ? true : event.key === "ArrowLeft" ? false : null;
2644
+ if (nextSelected === null) return;
2645
+ event.preventDefault();
2646
+ if (input.checked !== nextSelected) input.click();
2647
+ };
2648
+ const ownerDocument = input.ownerDocument;
2649
+ ownerDocument.addEventListener("keydown", handleDirectionalKey);
2650
+ return () => ownerDocument.removeEventListener("keydown", handleDirectionalKey);
2651
+ }, [isDisabled, isReadOnly, resolvedInputRef]);
2652
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2653
+ import_react_aria_components25.Switch,
2599
2654
  {
2600
2655
  "data-slot": "switch",
2601
2656
  "data-size": size,
2602
- className: (state) => cn(
2603
- "group/switch inline-flex w-max items-center gap-2 text-sm text-foreground outline-none",
2657
+ inputRef: resolvedInputRef,
2658
+ isDisabled,
2659
+ isReadOnly,
2660
+ className: cn(
2661
+ "group/switch inline-flex cursor-pointer items-center gap-3 text-sm text-foreground outline-none select-none",
2604
2662
  "data-disabled:cursor-not-allowed data-disabled:opacity-50",
2605
- typeof className === "function" ? className(state) : className
2663
+ className
2606
2664
  ),
2607
- children: (state) => /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
2608
- /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2609
- "span",
2610
- {
2611
- "aria-hidden": "true",
2612
- 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
2622
- ),
2623
- children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2624
- "span",
2625
- {
2626
- 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,
2632
- styles.thumb
2633
- )
2634
- }
2635
- )
2636
- }
2637
- ),
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 })
2639
- ] })
2665
+ ...props,
2666
+ children: (state) => {
2667
+ const isThumbActive = state.isHovered || state.isPressed;
2668
+ const thumbOffset = state.isSelected ? isThumbActive ? styles.activeSelectedOffset : styles.selectedOffset : "0";
2669
+ return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)(import_jsx_runtime42.Fragment, { children: [
2670
+ /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2671
+ "span",
2672
+ {
2673
+ "aria-hidden": "true",
2674
+ "data-slot": "switch-control",
2675
+ className: cn(
2676
+ "relative inline-flex shrink-0 items-center overflow-hidden rounded-full border border-border bg-input shadow-inner",
2677
+ "transition-[background-color,border-color,box-shadow] duration-200 ease-out motion-reduce:transition-none",
2678
+ "group-data-hovered/switch:bg-muted-foreground/25",
2679
+ "group-data-selected/switch:border-primary group-data-selected/switch:bg-primary",
2680
+ "group-data-selected/switch:group-data-hovered/switch:bg-primary/85",
2681
+ "group-data-focus-visible/switch:ring-3 group-data-focus-visible/switch:ring-ring/50",
2682
+ styles.control
2683
+ ),
2684
+ children: /* @__PURE__ */ (0, import_jsx_runtime42.jsx)(
2685
+ "span",
2686
+ {
2687
+ "data-slot": "switch-thumb",
2688
+ style: {
2689
+ transform: `translate3d(${thumbOffset}, 0, 0)`,
2690
+ width: isThumbActive ? styles.activeWidth : styles.idleWidth
2691
+ },
2692
+ className: cn(
2693
+ "grid shrink-0 place-items-center rounded-full border border-border/60 bg-background text-[0.625rem] text-muted-foreground shadow-sm",
2694
+ "will-change-[transform,width] transition-[transform,width,background-color,color,border-color] duration-[240ms] ease-[cubic-bezier(0.2,0.8,0.2,1)] motion-reduce:transition-none",
2695
+ "group-data-selected/switch:border-primary-foreground/20",
2696
+ "group-data-selected/switch:bg-primary-foreground group-data-selected/switch:text-primary",
2697
+ styles.thumb
2698
+ ),
2699
+ children: state.isSelected ? selectedIcon ?? icon : icon
2700
+ }
2701
+ )
2702
+ }
2703
+ ),
2704
+ children || description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("span", { className: "grid gap-0.5 leading-tight", children: [
2705
+ children ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-slot": "switch-label", className: "font-medium", children: typeof children === "function" ? children(state) : children }) : null,
2706
+ description ? /* @__PURE__ */ (0, import_jsx_runtime42.jsx)("span", { "data-slot": "switch-description", className: "text-xs font-normal text-muted-foreground", children: description }) : null
2707
+ ] }) : null
2708
+ ] });
2709
+ }
2640
2710
  }
2641
- ) });
2711
+ );
2642
2712
  }
2643
2713
 
2644
2714
  // src/ui/table/table.tsx
@@ -2743,7 +2813,7 @@ function TabsContent({ className, ...props }) {
2743
2813
  }
2744
2814
 
2745
2815
  // src/ui/toast/toast.tsx
2746
- var import_react12 = require("react");
2816
+ var import_react13 = require("react");
2747
2817
  var import_sileo = require("sileo");
2748
2818
  var import_jsx_runtime45 = require("react/jsx-runtime");
2749
2819
  function toSileoOptions({ title, description, duration, position, action }) {
@@ -2798,7 +2868,7 @@ function ToastViewport({ position, visiblePosition, className }) {
2798
2868
  return /* @__PURE__ */ (0, import_jsx_runtime45.jsx)(Toaster, { position });
2799
2869
  }
2800
2870
  function Toast({ id, title, description, variant = "default", action, state = "open", duration = 0, position, onDismiss }) {
2801
- (0, import_react12.useEffect)(() => {
2871
+ (0, import_react13.useEffect)(() => {
2802
2872
  if (state !== "open") return;
2803
2873
  const toastId = create({ title, description, variant, action, duration, position });
2804
2874
  return () => {