@zauru-sdk/components 6.18.0 → 6.19.0
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/CHANGELOG.md +24 -0
- package/build/assets/Alerts.stories-CSBQ8R_t.js +1 -0
- package/build/assets/Button-HdDVNit0.js +1 -0
- package/build/assets/Button.stories-4PbdRo1_.js +1 -0
- package/build/assets/Card.stories-QwjOcKTY.js +1 -0
- package/build/assets/Container.stories-pWk2cF23.js +1 -0
- package/build/assets/Form.stories-lVdn9hAH.js +1 -0
- package/build/assets/Icons-DjqYUpg9.js +10 -0
- package/build/assets/Labels.stories-FX8D89vw.js +1 -0
- package/build/assets/Matrix.stories-Cxd7sZW4.js +1 -0
- package/build/assets/Modal.stories-CgG40zUJ.js +1 -0
- package/build/assets/NavBar.stories-BPC2tPzi.js +1 -0
- package/build/assets/SidePanel.stories-Atykcy9h.js +1 -0
- package/build/assets/Tab.stories-qL5aJzJZ.js +365 -0
- package/build/assets/Table.stories-JhlKDwAx.js +416 -0
- package/build/assets/Titles.stories-BIaWnH29.js +1 -0
- package/build/assets/Tooltip.stories-iBejLWUg.js +1 -0
- package/build/assets/activestorage.esm-ByWQ3T27.js +1 -0
- package/build/assets/axe-VDwKxXpa.js +30 -0
- package/build/assets/components-TO4aWqdC.js +46 -0
- package/build/assets/favicon-gjFwcFbF.svg +22 -0
- package/build/assets/index-CraKZ-Fr.css +1 -0
- package/build/assets/index-DxYjnckq.js +152 -0
- package/build/assets/index-TgOqmrBM.js +1 -0
- package/build/assets/manifest-B-VxemCC.webmanifest +7 -0
- package/build/assets/proxy-DgkUxN7K.js +9 -0
- package/build/assets/touch-icon-ByyUZD7t.png +0 -0
- package/build/index.html +28 -0
- package/build/meta.json +416 -0
- package/dist/Matrix/CheckMatrixCards.d.ts +27 -0
- package/dist/Matrix/ResponsiveCheckMatrix.d.ts +7 -0
- package/dist/Matrix/index.d.ts +2 -0
- package/dist/SidePanel/index.d.ts +4 -2
- package/dist/esm/Matrix/CheckMatrixCards.js +25 -0
- package/dist/esm/Matrix/ResponsiveCheckMatrix.js +11 -0
- package/dist/esm/Matrix/index.js +2 -0
- package/dist/esm/SidePanel/index.js +47 -10
- package/dist/esm/index.js +1 -0
- package/dist/index.d.ts +1 -0
- package/package.json +9 -9
- package/src/Matrix/CheckMatrixCards.tsx +137 -0
- package/src/Matrix/ResponsiveCheckMatrix.tsx +32 -0
- package/src/Matrix/index.ts +2 -0
- package/src/SidePanel/index.tsx +96 -12
- package/src/index.ts +1 -0
- package/stories/Matrix.stories.tsx +75 -0
- package/stories/SidePanel.stories.tsx +21 -0
|
@@ -0,0 +1,137 @@
|
|
|
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
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
setVisibleCount(safePageSize);
|
|
52
|
+
}, [rows, safePageSize]);
|
|
53
|
+
|
|
54
|
+
if (rows.length === 0) {
|
|
55
|
+
return <p className={`p-4 text-sm text-gray-600 ${className}`}>{emptyText}</p>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const visibleRows = rows.slice(0, visibleCount);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div className={`space-y-3 ${className}`}>
|
|
62
|
+
{visibleRows.map((row) => (
|
|
63
|
+
<fieldset
|
|
64
|
+
key={row.key}
|
|
65
|
+
className="min-w-0 rounded-lg border border-gray-200 bg-white p-3 shadow-sm"
|
|
66
|
+
>
|
|
67
|
+
<div className="mb-3 flex min-w-0 items-start justify-between gap-3">
|
|
68
|
+
<legend className="min-w-0 flex-1 px-0 text-sm font-semibold text-gray-900">
|
|
69
|
+
<span className="block break-words">{row.label}</span>
|
|
70
|
+
{row.description ? (
|
|
71
|
+
<span className="mt-0.5 block break-words text-xs font-normal text-gray-600">
|
|
72
|
+
{row.description}
|
|
73
|
+
</span>
|
|
74
|
+
) : null}
|
|
75
|
+
</legend>
|
|
76
|
+
{renderRowAction ? (
|
|
77
|
+
<div className="shrink-0">{renderRowAction(row)}</div>
|
|
78
|
+
) : null}
|
|
79
|
+
</div>
|
|
80
|
+
<div className="grid gap-2 sm:grid-cols-2">
|
|
81
|
+
{columns.map((column) => {
|
|
82
|
+
const checked = isChecked(row.key, column.key);
|
|
83
|
+
const pending = isPending(row.key, column.key);
|
|
84
|
+
const inputId = `check-matrix-${encodeURIComponent(row.key)}-${encodeURIComponent(column.key)}`;
|
|
85
|
+
return (
|
|
86
|
+
<label
|
|
87
|
+
key={column.key}
|
|
88
|
+
htmlFor={inputId}
|
|
89
|
+
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 ${
|
|
90
|
+
checked
|
|
91
|
+
? "border-indigo-200 bg-indigo-50 text-indigo-950"
|
|
92
|
+
: "border-gray-200 bg-gray-50 text-gray-800"
|
|
93
|
+
} ${pending || disabled ? "cursor-not-allowed opacity-70" : "hover:border-indigo-300"}`}
|
|
94
|
+
>
|
|
95
|
+
<input
|
|
96
|
+
id={inputId}
|
|
97
|
+
type="checkbox"
|
|
98
|
+
checked={checked}
|
|
99
|
+
disabled={disabled || pending}
|
|
100
|
+
onChange={(event) =>
|
|
101
|
+
onToggle(row.key, column.key, event.target.checked)
|
|
102
|
+
}
|
|
103
|
+
className="h-5 w-5 shrink-0 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
104
|
+
/>
|
|
105
|
+
<span className="min-w-0 flex-1 break-words font-medium">
|
|
106
|
+
{column.label}
|
|
107
|
+
</span>
|
|
108
|
+
<span
|
|
109
|
+
className={`shrink-0 text-xs font-medium ${
|
|
110
|
+
checked ? "text-indigo-700" : "text-gray-600"
|
|
111
|
+
}`}
|
|
112
|
+
aria-live={pending ? "polite" : undefined}
|
|
113
|
+
>
|
|
114
|
+
{pending
|
|
115
|
+
? pendingLabel
|
|
116
|
+
: checked
|
|
117
|
+
? checkedLabel
|
|
118
|
+
: uncheckedLabel}
|
|
119
|
+
</span>
|
|
120
|
+
</label>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
</div>
|
|
124
|
+
</fieldset>
|
|
125
|
+
))}
|
|
126
|
+
{visibleCount < rows.length ? (
|
|
127
|
+
<button
|
|
128
|
+
type="button"
|
|
129
|
+
onClick={() => setVisibleCount((count) => count + safePageSize)}
|
|
130
|
+
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"
|
|
131
|
+
>
|
|
132
|
+
{showMoreLabel} ({rows.length - visibleCount})
|
|
133
|
+
</button>
|
|
134
|
+
) : null}
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
@@ -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
|
+
};
|
package/src/SidePanel/index.tsx
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
|
|
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(!
|
|
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,34 @@ 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) {
|
|
137
|
+
wasOpenRef.current = true;
|
|
138
|
+
closeButtonRef.current?.focus();
|
|
139
|
+
const previousOverflow = document.body.style.overflow;
|
|
140
|
+
if (window.matchMedia("(max-width: 767px)").matches) {
|
|
141
|
+
document.body.style.overflow = "hidden";
|
|
142
|
+
}
|
|
143
|
+
return () => {
|
|
144
|
+
document.body.style.overflow = previousOverflow;
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
if (wasOpenRef.current) {
|
|
148
|
+
wasOpenRef.current = false;
|
|
149
|
+
triggerButtonRef.current?.focus();
|
|
150
|
+
}
|
|
151
|
+
}, [isOpen]);
|
|
101
152
|
|
|
102
153
|
const renderIcon = () => {
|
|
103
154
|
if (typeof buttonIcon === "string") {
|
|
@@ -117,30 +168,63 @@ export const SidePanel: React.FC<SidePanelProps> = ({
|
|
|
117
168
|
return (
|
|
118
169
|
<>
|
|
119
170
|
<motion.div
|
|
171
|
+
id={panelId}
|
|
120
172
|
ref={panelRef}
|
|
173
|
+
role="dialog"
|
|
174
|
+
aria-modal={isOpen ? "true" : undefined}
|
|
175
|
+
aria-label={ariaLabel}
|
|
176
|
+
aria-hidden={!isOpen}
|
|
121
177
|
initial={{ x: "100%" }}
|
|
122
178
|
animate={{ x: isOpen ? 0 : "100%" }}
|
|
123
|
-
transition={
|
|
124
|
-
|
|
125
|
-
|
|
179
|
+
transition={
|
|
180
|
+
shouldReduceMotion
|
|
181
|
+
? { duration: 0 }
|
|
182
|
+
: { type: "spring", stiffness: 300, damping: 30 }
|
|
183
|
+
}
|
|
184
|
+
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)]"
|
|
185
|
+
style={
|
|
186
|
+
{
|
|
187
|
+
"--side-panel-width": `${widthPercentage}%`,
|
|
188
|
+
} as React.CSSProperties
|
|
189
|
+
}
|
|
126
190
|
>
|
|
127
191
|
<button
|
|
192
|
+
ref={closeButtonRef}
|
|
193
|
+
type="button"
|
|
128
194
|
onClick={togglePanel}
|
|
129
|
-
|
|
195
|
+
aria-label={closeButtonLabel}
|
|
196
|
+
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
197
|
>
|
|
131
198
|
<ChevronRightIcon />
|
|
132
199
|
</button>
|
|
133
|
-
<div className="p-
|
|
200
|
+
<div className="mt-16 p-4 sm:p-6">{children}</div>
|
|
134
201
|
</motion.div>
|
|
135
202
|
<AnimatePresence>
|
|
203
|
+
{isOpen && closeOnClickOutside ? (
|
|
204
|
+
<motion.button
|
|
205
|
+
type="button"
|
|
206
|
+
aria-label={closeButtonLabel}
|
|
207
|
+
initial={{ opacity: 0 }}
|
|
208
|
+
animate={{ opacity: 1 }}
|
|
209
|
+
exit={{ opacity: 0 }}
|
|
210
|
+
transition={{ duration: shouldReduceMotion ? 0 : 0.15 }}
|
|
211
|
+
onClick={() => setIsOpen(false)}
|
|
212
|
+
className="fixed inset-0 z-[9998] cursor-default bg-black/30 md:hidden"
|
|
213
|
+
/>
|
|
214
|
+
) : null}
|
|
136
215
|
{!isOpen && (
|
|
137
216
|
<motion.button
|
|
217
|
+
ref={triggerButtonRef}
|
|
218
|
+
type="button"
|
|
219
|
+
aria-label={openButtonLabel}
|
|
220
|
+
aria-expanded={isOpen}
|
|
221
|
+
aria-controls={panelId}
|
|
138
222
|
initial={{ opacity: 0, x: 20 }}
|
|
139
223
|
animate={{ opacity: 1, x: 0 }}
|
|
140
224
|
exit={{ opacity: 0, x: 20 }}
|
|
141
|
-
transition={{ duration: 0.2 }}
|
|
225
|
+
transition={{ duration: shouldReduceMotion ? 0 : 0.2 }}
|
|
142
226
|
onClick={togglePanel}
|
|
143
|
-
className=
|
|
227
|
+
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
228
|
>
|
|
145
229
|
{renderIcon()}
|
|
146
230
|
</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
|
+
);
|