@zauru-sdk/components 6.18.1 → 6.19.1

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/build/assets/Alerts.stories-Dw0Ct9vA.js +1 -0
  3. package/build/assets/Button-YPU2MaHD.js +1 -0
  4. package/build/assets/Button.stories-CP6jmzaX.js +1 -0
  5. package/build/assets/Card.stories-DbFqLafy.js +1 -0
  6. package/build/assets/Container.stories-BhnM3_6Y.js +1 -0
  7. package/build/assets/Form.stories-BNB6SmSx.js +1 -0
  8. package/build/assets/Icons-BxLN_JWC.js +10 -0
  9. package/build/assets/Labels.stories-Fah6wLSi.js +1 -0
  10. package/build/assets/Matrix.stories-Dc3-VDms.js +1 -0
  11. package/build/assets/Modal.stories-D7kIP3bJ.js +1 -0
  12. package/build/assets/NavBar.stories-DuIgKZkc.js +1 -0
  13. package/build/assets/SidePanel.stories-Xsm8Md4K.js +1 -0
  14. package/build/assets/Tab.stories-RKI0m-pt.js +365 -0
  15. package/build/assets/Table.stories-CGj0WWPk.js +416 -0
  16. package/build/assets/Titles.stories-CPIRDaJs.js +1 -0
  17. package/build/assets/Tooltip.stories-D8M6F7St.js +1 -0
  18. package/build/assets/activestorage.esm-ByWQ3T27.js +1 -0
  19. package/build/assets/axe-sLlwVNub.js +30 -0
  20. package/build/assets/components-FzdPzEyL.js +46 -0
  21. package/build/assets/favicon-gjFwcFbF.svg +22 -0
  22. package/build/assets/index-CraKZ-Fr.css +1 -0
  23. package/build/assets/index-DUSjZECW.js +152 -0
  24. package/build/assets/index-DonOhaBM.js +1 -0
  25. package/build/assets/manifest-B-VxemCC.webmanifest +7 -0
  26. package/build/assets/proxy-BETbFm71.js +9 -0
  27. package/build/assets/touch-icon-ByyUZD7t.png +0 -0
  28. package/build/index.html +28 -0
  29. package/build/meta.json +416 -0
  30. package/dist/Matrix/CheckMatrixCards.d.ts +27 -0
  31. package/dist/Matrix/ResponsiveCheckMatrix.d.ts +7 -0
  32. package/dist/Matrix/index.d.ts +2 -0
  33. package/dist/SidePanel/index.d.ts +4 -2
  34. package/dist/esm/Matrix/CheckMatrixCards.js +26 -0
  35. package/dist/esm/Matrix/ResponsiveCheckMatrix.js +11 -0
  36. package/dist/esm/Matrix/index.js +2 -0
  37. package/dist/esm/SidePanel/index.js +53 -10
  38. package/dist/esm/index.js +1 -0
  39. package/dist/index.d.ts +1 -0
  40. package/package.json +9 -9
  41. package/src/Matrix/CheckMatrixCards.tsx +138 -0
  42. package/src/Matrix/ResponsiveCheckMatrix.tsx +32 -0
  43. package/src/Matrix/index.ts +2 -0
  44. package/src/SidePanel/index.tsx +101 -12
  45. package/src/index.ts +1 -0
  46. package/stories/Matrix.stories.tsx +75 -0
  47. package/stories/SidePanel.stories.tsx +21 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/components",
3
- "version": "6.18.1",
3
+ "version": "6.19.1",
4
4
  "description": "Componentes reutilizables en las WebApps de Zauru.",
5
5
  "main": "./dist/esm/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -27,8 +27,8 @@
27
27
  "@tailwindcss/forms": "^0.5.7",
28
28
  "@types/jsonwebtoken": "^9.0.2",
29
29
  "@types/rails__activestorage": "^7.1.1",
30
- "@types/react": "^19.0.0",
31
- "@types/react-dom": "^19.0.0",
30
+ "@types/react": "^18.2.20",
31
+ "@types/react-dom": "^18.2.7",
32
32
  "@types/styled-components": "^5.1.34",
33
33
  "autoprefixer": "^10.6.0",
34
34
  "postcss": "^8.4.35",
@@ -44,12 +44,12 @@
44
44
  "@reduxjs/toolkit": "^2.2.1",
45
45
  "@remix-run/node": "^2.8.1",
46
46
  "@remix-run/react": "^2.8.1",
47
- "@zauru-sdk/common": "^6.14.1",
48
- "@zauru-sdk/hooks": "^6.4.3",
47
+ "@zauru-sdk/common": "^6.14.2",
48
+ "@zauru-sdk/hooks": "^6.4.4",
49
49
  "@zauru-sdk/icons": "^6.2.0",
50
- "@zauru-sdk/redux": "^6.8.3",
51
- "@zauru-sdk/types": "^6.30.0",
52
- "@zauru-sdk/utils": "^6.30.1",
50
+ "@zauru-sdk/redux": "^6.8.4",
51
+ "@zauru-sdk/types": "^6.30.2",
52
+ "@zauru-sdk/utils": "^6.30.3",
53
53
  "browser-image-compression": "^2.0.2",
54
54
  "framer-motion": "^12.0.0",
55
55
  "jsonwebtoken": "^9.0.2",
@@ -62,5 +62,5 @@
62
62
  "styled-components": "^5.3.5",
63
63
  "zod": "^3.23.8"
64
64
  },
65
- "gitHead": "dc5f6e04a2c4254710cbd86cb7016beefd84b9be"
65
+ "gitHead": "08f14cc3a87fabe1b2dfe678b9fdeb32ed4aaaef"
66
66
  }
@@ -0,0 +1,138 @@
1
+ import React, { ReactNode, useEffect, useState } from "react";
2
+
3
+ export type CheckMatrixColumn = {
4
+ key: string;
5
+ label: ReactNode;
6
+ };
7
+
8
+ export type CheckMatrixRow = {
9
+ key: string;
10
+ label: ReactNode;
11
+ description?: ReactNode;
12
+ };
13
+
14
+ export interface CheckMatrixCardsProps {
15
+ rows: CheckMatrixRow[];
16
+ columns: CheckMatrixColumn[];
17
+ isChecked: (rowKey: string, columnKey: string) => boolean;
18
+ isPending?: (rowKey: string, columnKey: string) => boolean;
19
+ onToggle: (rowKey: string, columnKey: string, checked: boolean) => void;
20
+ disabled?: boolean;
21
+ renderRowAction?: (row: CheckMatrixRow) => ReactNode;
22
+ checkedLabel?: string;
23
+ uncheckedLabel?: string;
24
+ pendingLabel?: string;
25
+ emptyText?: string;
26
+ pageSize?: number;
27
+ showMoreLabel?: string;
28
+ className?: string;
29
+ }
30
+
31
+ export const CheckMatrixCards = ({
32
+ rows,
33
+ columns,
34
+ isChecked,
35
+ isPending = () => false,
36
+ onToggle,
37
+ disabled = false,
38
+ renderRowAction,
39
+ checkedLabel = "Marcado",
40
+ uncheckedLabel = "Sin marcar",
41
+ pendingLabel = "Guardando…",
42
+ emptyText = "No hay filas para mostrar.",
43
+ pageSize = 20,
44
+ showMoreLabel = "Mostrar más",
45
+ className = "",
46
+ }: CheckMatrixCardsProps) => {
47
+ const safePageSize = Math.max(1, pageSize);
48
+ const [visibleCount, setVisibleCount] = useState(safePageSize);
49
+ const rowKeys = rows.map((row) => row.key).join("\u0000");
50
+
51
+ useEffect(() => {
52
+ setVisibleCount(safePageSize);
53
+ }, [rowKeys, safePageSize]);
54
+
55
+ if (rows.length === 0) {
56
+ return <p className={`p-4 text-sm text-gray-600 ${className}`}>{emptyText}</p>;
57
+ }
58
+
59
+ const visibleRows = rows.slice(0, visibleCount);
60
+
61
+ return (
62
+ <div className={`space-y-3 ${className}`}>
63
+ {visibleRows.map((row) => (
64
+ <fieldset
65
+ key={row.key}
66
+ className="min-w-0 rounded-lg border border-gray-200 bg-white p-3 shadow-sm"
67
+ >
68
+ <div className="mb-3 flex min-w-0 items-start justify-between gap-3">
69
+ <legend className="min-w-0 flex-1 px-0 text-sm font-semibold text-gray-900">
70
+ <span className="block break-words">{row.label}</span>
71
+ {row.description ? (
72
+ <span className="mt-0.5 block break-words text-xs font-normal text-gray-600">
73
+ {row.description}
74
+ </span>
75
+ ) : null}
76
+ </legend>
77
+ {renderRowAction ? (
78
+ <div className="shrink-0">{renderRowAction(row)}</div>
79
+ ) : null}
80
+ </div>
81
+ <div className="grid gap-2 sm:grid-cols-2">
82
+ {columns.map((column) => {
83
+ const checked = isChecked(row.key, column.key);
84
+ const pending = isPending(row.key, column.key);
85
+ const inputId = `check-matrix-${encodeURIComponent(row.key)}-${encodeURIComponent(column.key)}`;
86
+ return (
87
+ <label
88
+ key={column.key}
89
+ htmlFor={inputId}
90
+ className={`flex min-h-11 cursor-pointer items-center gap-3 rounded-md border px-3 py-2 text-sm focus-within:ring-2 focus-within:ring-indigo-500 focus-within:ring-offset-1 ${
91
+ checked
92
+ ? "border-indigo-200 bg-indigo-50 text-indigo-950"
93
+ : "border-gray-200 bg-gray-50 text-gray-800"
94
+ } ${pending || disabled ? "cursor-not-allowed opacity-70" : "hover:border-indigo-300"}`}
95
+ >
96
+ <input
97
+ id={inputId}
98
+ type="checkbox"
99
+ checked={checked}
100
+ disabled={disabled || pending}
101
+ onChange={(event) =>
102
+ onToggle(row.key, column.key, event.target.checked)
103
+ }
104
+ className="h-5 w-5 shrink-0 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
105
+ />
106
+ <span className="min-w-0 flex-1 break-words font-medium">
107
+ {column.label}
108
+ </span>
109
+ <span
110
+ className={`shrink-0 text-xs font-medium ${
111
+ checked ? "text-indigo-700" : "text-gray-600"
112
+ }`}
113
+ aria-live={pending ? "polite" : undefined}
114
+ >
115
+ {pending
116
+ ? pendingLabel
117
+ : checked
118
+ ? checkedLabel
119
+ : uncheckedLabel}
120
+ </span>
121
+ </label>
122
+ );
123
+ })}
124
+ </div>
125
+ </fieldset>
126
+ ))}
127
+ {visibleCount < rows.length ? (
128
+ <button
129
+ type="button"
130
+ onClick={() => setVisibleCount((count) => count + safePageSize)}
131
+ className="min-h-11 w-full cursor-pointer rounded-md border border-indigo-300 bg-white px-4 py-2 text-sm font-semibold text-indigo-700 hover:bg-indigo-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2"
132
+ >
133
+ {showMoreLabel} ({rows.length - visibleCount})
134
+ </button>
135
+ ) : null}
136
+ </div>
137
+ );
138
+ };
@@ -0,0 +1,32 @@
1
+ import React, { ReactNode } from "react";
2
+ import {
3
+ CheckMatrixCards,
4
+ CheckMatrixCardsProps,
5
+ } from "./CheckMatrixCards.js";
6
+
7
+ export interface ResponsiveCheckMatrixProps extends CheckMatrixCardsProps {
8
+ desktop: ReactNode;
9
+ desktopBreakpoint?: "sm" | "md" | "lg";
10
+ }
11
+
12
+ const visibilityClasses = {
13
+ sm: { mobile: "sm:hidden", desktop: "hidden sm:block" },
14
+ md: { mobile: "md:hidden", desktop: "hidden md:block" },
15
+ lg: { mobile: "lg:hidden", desktop: "hidden lg:block" },
16
+ } as const;
17
+
18
+ export const ResponsiveCheckMatrix = ({
19
+ desktop,
20
+ desktopBreakpoint = "md",
21
+ ...cardProps
22
+ }: ResponsiveCheckMatrixProps) => {
23
+ const visibility = visibilityClasses[desktopBreakpoint];
24
+ return (
25
+ <>
26
+ <div className={visibility.mobile}>
27
+ <CheckMatrixCards {...cardProps} />
28
+ </div>
29
+ <div className={visibility.desktop}>{desktop}</div>
30
+ </>
31
+ );
32
+ };
@@ -0,0 +1,2 @@
1
+ export * from "./CheckMatrixCards.js";
2
+ export * from "./ResponsiveCheckMatrix.js";
@@ -1,5 +1,11 @@
1
- import React, { useState, ReactNode, useEffect, useRef } from "react";
2
- import { motion, AnimatePresence } from "framer-motion";
1
+ import React, {
2
+ useState,
3
+ ReactNode,
4
+ useEffect,
5
+ useId,
6
+ useRef,
7
+ } from "react";
8
+ import { motion, AnimatePresence, useReducedMotion } from "framer-motion";
3
9
 
4
10
  /**
5
11
  * SidePanel Component
@@ -14,11 +20,14 @@ import { motion, AnimatePresence } from "framer-motion";
14
20
  * @returns A toggleable side panel component.
15
21
  */
16
22
 
17
- interface SidePanelProps {
23
+ export interface SidePanelProps {
18
24
  children: ReactNode;
19
25
  closeOnClickOutside?: boolean;
20
26
  widthPercentage?: number;
21
27
  buttonIcon?: "chevron" | "filter" | ReactNode;
28
+ ariaLabel?: string;
29
+ openButtonLabel?: string;
30
+ closeButtonLabel?: string;
22
31
  }
23
32
 
24
33
  // SVG icon for the left-pointing chevron
@@ -28,6 +37,8 @@ const ChevronLeftIcon = () => (
28
37
  viewBox="0 0 24 24"
29
38
  fill="currentColor"
30
39
  className="h-6 w-6"
40
+ aria-hidden="true"
41
+ focusable="false"
31
42
  >
32
43
  <path
33
44
  fillRule="evenodd"
@@ -44,6 +55,8 @@ const ChevronRightIcon = () => (
44
55
  viewBox="0 0 24 24"
45
56
  fill="currentColor"
46
57
  className="h-6 w-6"
58
+ aria-hidden="true"
59
+ focusable="false"
47
60
  >
48
61
  <path
49
62
  fillRule="evenodd"
@@ -60,6 +73,8 @@ const FilterIcon = () => (
60
73
  viewBox="0 0 24 24"
61
74
  fill="currentColor"
62
75
  className="h-6 w-6"
76
+ aria-hidden="true"
77
+ focusable="false"
63
78
  >
64
79
  <path
65
80
  fillRule="evenodd"
@@ -74,17 +89,26 @@ export const SidePanel: React.FC<SidePanelProps> = ({
74
89
  closeOnClickOutside = true,
75
90
  widthPercentage = 25,
76
91
  buttonIcon = "chevron",
92
+ ariaLabel = "Panel lateral",
93
+ openButtonLabel = "Abrir panel",
94
+ closeButtonLabel = "Cerrar panel",
77
95
  }) => {
78
96
  const [isOpen, setIsOpen] = useState(false);
79
97
  const panelRef = useRef<HTMLDivElement>(null);
98
+ const closeButtonRef = useRef<HTMLButtonElement>(null);
99
+ const triggerButtonRef = useRef<HTMLButtonElement>(null);
100
+ const wasOpenRef = useRef(false);
101
+ const panelId = useId();
102
+ const shouldReduceMotion = useReducedMotion();
80
103
 
81
104
  const togglePanel = () => {
82
- setIsOpen(!isOpen);
105
+ setIsOpen((open) => !open);
83
106
  };
84
107
 
85
108
  useEffect(() => {
86
109
  const handleClickOutside = (event: MouseEvent) => {
87
110
  if (
111
+ isOpen &&
88
112
  closeOnClickOutside &&
89
113
  panelRef.current &&
90
114
  !panelRef.current.contains(event.target as Node)
@@ -97,7 +121,39 @@ export const SidePanel: React.FC<SidePanelProps> = ({
97
121
  return () => {
98
122
  document.removeEventListener("mousedown", handleClickOutside);
99
123
  };
100
- }, [closeOnClickOutside]);
124
+ }, [closeOnClickOutside, isOpen]);
125
+
126
+ useEffect(() => {
127
+ if (!isOpen) return;
128
+ const handleEscape = (event: KeyboardEvent) => {
129
+ if (event.key === "Escape") setIsOpen(false);
130
+ };
131
+ document.addEventListener("keydown", handleEscape);
132
+ return () => document.removeEventListener("keydown", handleEscape);
133
+ }, [isOpen]);
134
+
135
+ useEffect(() => {
136
+ if (isOpen) panelRef.current?.removeAttribute("inert");
137
+ else panelRef.current?.setAttribute("inert", "");
138
+ }, [isOpen]);
139
+
140
+ useEffect(() => {
141
+ if (isOpen) {
142
+ wasOpenRef.current = true;
143
+ closeButtonRef.current?.focus();
144
+ const previousOverflow = document.body.style.overflow;
145
+ if (window.matchMedia("(max-width: 767px)").matches) {
146
+ document.body.style.overflow = "hidden";
147
+ }
148
+ return () => {
149
+ document.body.style.overflow = previousOverflow;
150
+ };
151
+ }
152
+ if (wasOpenRef.current) {
153
+ wasOpenRef.current = false;
154
+ triggerButtonRef.current?.focus();
155
+ }
156
+ }, [isOpen]);
101
157
 
102
158
  const renderIcon = () => {
103
159
  if (typeof buttonIcon === "string") {
@@ -117,30 +173,63 @@ export const SidePanel: React.FC<SidePanelProps> = ({
117
173
  return (
118
174
  <>
119
175
  <motion.div
176
+ id={panelId}
120
177
  ref={panelRef}
178
+ role="dialog"
179
+ aria-modal={isOpen ? "true" : undefined}
180
+ aria-label={ariaLabel}
181
+ aria-hidden={!isOpen}
121
182
  initial={{ x: "100%" }}
122
183
  animate={{ x: isOpen ? 0 : "100%" }}
123
- transition={{ type: "spring", stiffness: 300, damping: 30 }}
124
- className={`fixed top-0 right-0 h-full bg-white shadow-lg z-[9999] overflow-y-auto`}
125
- style={{ width: `${widthPercentage}%` }}
184
+ transition={
185
+ shouldReduceMotion
186
+ ? { duration: 0 }
187
+ : { type: "spring", stiffness: 300, damping: 30 }
188
+ }
189
+ className="fixed right-0 top-0 z-[9999] h-full w-[calc(100%-1rem)] overflow-y-auto bg-white shadow-lg sm:w-[min(28rem,calc(100%-2rem))] md:w-[var(--side-panel-width)]"
190
+ style={
191
+ {
192
+ "--side-panel-width": `${widthPercentage}%`,
193
+ } as React.CSSProperties
194
+ }
126
195
  >
127
196
  <button
197
+ ref={closeButtonRef}
198
+ type="button"
128
199
  onClick={togglePanel}
129
- className="absolute top-4 left-4 p-2 rounded-full bg-gray-200 hover:bg-gray-300 transition-colors"
200
+ aria-label={closeButtonLabel}
201
+ className="absolute left-4 top-4 min-h-11 min-w-11 cursor-pointer rounded-full bg-gray-200 p-2 transition-colors hover:bg-gray-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 md:min-h-0 md:min-w-0"
130
202
  >
131
203
  <ChevronRightIcon />
132
204
  </button>
133
- <div className="p-6 mt-16">{children}</div>
205
+ <div className="mt-16 p-4 sm:p-6">{children}</div>
134
206
  </motion.div>
135
207
  <AnimatePresence>
208
+ {isOpen && closeOnClickOutside ? (
209
+ <motion.button
210
+ type="button"
211
+ aria-label={closeButtonLabel}
212
+ initial={{ opacity: 0 }}
213
+ animate={{ opacity: 1 }}
214
+ exit={{ opacity: 0 }}
215
+ transition={{ duration: shouldReduceMotion ? 0 : 0.15 }}
216
+ onClick={() => setIsOpen(false)}
217
+ className="fixed inset-0 z-[9998] cursor-default bg-black/30 md:hidden"
218
+ />
219
+ ) : null}
136
220
  {!isOpen && (
137
221
  <motion.button
222
+ ref={triggerButtonRef}
223
+ type="button"
224
+ aria-label={openButtonLabel}
225
+ aria-expanded={isOpen}
226
+ aria-controls={panelId}
138
227
  initial={{ opacity: 0, x: 20 }}
139
228
  animate={{ opacity: 1, x: 0 }}
140
229
  exit={{ opacity: 0, x: 20 }}
141
- transition={{ duration: 0.2 }}
230
+ transition={{ duration: shouldReduceMotion ? 0 : 0.2 }}
142
231
  onClick={togglePanel}
143
- className={`fixed top-1/2 right-0 transform -translate-y-1/2 bg-indigo-600 text-white p-3 rounded-l-lg shadow-md hover:bg-indigo-700 transition-colors z-[10000]`}
232
+ className="fixed right-0 top-1/2 z-[10000] min-h-11 min-w-11 -translate-y-1/2 cursor-pointer rounded-l-lg bg-indigo-600 p-3 text-white shadow-md transition-colors hover:bg-indigo-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2"
144
233
  >
145
234
  {renderIcon()}
146
235
  </motion.button>
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./AccessControl/index.js";
3
3
  export * from "./BlockUI/index.js";
4
4
  export * from "./Buttons/index.js";
5
5
  export * from "./Card/index.js";
6
+ export * from "./Matrix/index.js";
6
7
  export * from "./ConnectionState/index.js";
7
8
  export * from "./Chat/index.js";
8
9
  export * from "./Containers/index.js";
@@ -0,0 +1,75 @@
1
+ import { useState } from "react";
2
+ import { ResponsiveCheckMatrix } from "../src/Matrix/ResponsiveCheckMatrix.js";
3
+
4
+ const rows = [
5
+ { key: "129", label: "129 · Cooperativa El Progreso", description: "San Juan" },
6
+ { key: "302", label: "302 · Productor con un nombre largo para validar el ajuste de texto" },
7
+ ];
8
+ const columns = [
9
+ { key: "brocoli", label: "Brócoli certificado" },
10
+ { key: "radicchio", label: "Radicchio" },
11
+ { key: "elotin", label: "Elotín" },
12
+ ];
13
+
14
+ export const Responsive = () => {
15
+ const [blocked, setBlocked] = useState(new Set(["129:radicchio"]));
16
+ const isChecked = (rowKey: string, columnKey: string) =>
17
+ !blocked.has(`${rowKey}:${columnKey}`);
18
+ const onToggle = (rowKey: string, columnKey: string, checked: boolean) => {
19
+ setBlocked((current) => {
20
+ const next = new Set(current);
21
+ const key = `${rowKey}:${columnKey}`;
22
+ if (checked) next.delete(key);
23
+ else next.add(key);
24
+ return next;
25
+ });
26
+ };
27
+
28
+ const desktop = (
29
+ <div className="overflow-x-auto rounded-md border border-gray-200">
30
+ <table className="min-w-full text-sm">
31
+ <thead className="bg-gray-50">
32
+ <tr>
33
+ <th className="px-3 py-2 text-left">Proveedor</th>
34
+ {columns.map((column) => (
35
+ <th key={column.key} className="px-3 py-2 text-center">
36
+ {column.label}
37
+ </th>
38
+ ))}
39
+ </tr>
40
+ </thead>
41
+ <tbody>
42
+ {rows.map((row) => (
43
+ <tr key={row.key} className="border-t border-gray-200">
44
+ <th className="px-3 py-2 text-left">{row.label}</th>
45
+ {columns.map((column) => (
46
+ <td key={column.key} className="px-3 py-2 text-center">
47
+ <input
48
+ type="checkbox"
49
+ aria-label={`${String(row.label)}, ${String(column.label)}`}
50
+ checked={isChecked(row.key, column.key)}
51
+ onChange={(event) =>
52
+ onToggle(row.key, column.key, event.target.checked)
53
+ }
54
+ />
55
+ </td>
56
+ ))}
57
+ </tr>
58
+ ))}
59
+ </tbody>
60
+ </table>
61
+ </div>
62
+ );
63
+
64
+ return (
65
+ <ResponsiveCheckMatrix
66
+ rows={rows}
67
+ columns={columns}
68
+ desktop={desktop}
69
+ isChecked={isChecked}
70
+ onToggle={onToggle}
71
+ checkedLabel="Permitida"
72
+ uncheckedLabel="Bloqueada"
73
+ />
74
+ );
75
+ };
@@ -0,0 +1,21 @@
1
+ import { SidePanel } from "../src/SidePanel/index.js";
2
+
3
+ export const Filters = () => (
4
+ <div className="min-h-[36rem] bg-gray-50 p-6">
5
+ <p className="text-sm text-gray-700">
6
+ Use el botón fijo para abrir los filtros.
7
+ </p>
8
+ <SidePanel buttonIcon="filter" ariaLabel="Filtros de búsqueda">
9
+ <h2 className="text-lg font-semibold text-gray-900">
10
+ Filtros de búsqueda
11
+ </h2>
12
+ <label className="mt-4 block text-sm font-medium text-gray-700">
13
+ Proveedor
14
+ <input
15
+ className="mt-1 block w-full rounded-md border-gray-300"
16
+ placeholder="Nombre o código"
17
+ />
18
+ </label>
19
+ </SidePanel>
20
+ </div>
21
+ );