@zauru-sdk/components 6.13.3 → 6.14.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.
- package/CHANGELOG.md +19 -0
- package/dist/esm/Tab/PermissionMatrix.js +14 -10
- package/package.json +7 -7
- package/src/Tab/PermissionMatrix.tsx +57 -42
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,25 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [6.14.1](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.14.0...@zauru-sdk/components@6.14.1) (2026-10-26)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @zauru-sdk/components
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# [6.14.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.13.3...@zauru-sdk/components@6.14.0) (2026-10-26)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
* fijar cabecera y primera columna de PermissionMatrix al hacer scroll ([5b628a9](https://github.com/intuitiva/zauru-typescript-sdk/commit/5b628a9c0bc80169757cbe9ad96106cfe3732458))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
6
25
|
## [6.13.3](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.13.2...@zauru-sdk/components@6.13.3) (2026-10-23)
|
|
7
26
|
|
|
8
27
|
**Note:** Version bump only for package @zauru-sdk/components
|
|
@@ -3,27 +3,31 @@ export function permissionMatrixCellKey(roleId, key) {
|
|
|
3
3
|
return `${roleId}:${key}`;
|
|
4
4
|
}
|
|
5
5
|
const checkboxClass = "form-checkbox h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-50";
|
|
6
|
+
const FIRST_COL_SHADOW = "shadow-[2px_0_4px_-2px_rgba(0,0,0,0.12)]";
|
|
6
7
|
export const PermissionMatrix = ({ groups, roles, disabled = false, pendingCells, onToggle, onBulkRole, onBulkSection, emptyText = "Crea un rol para editar la matriz de permisos.", markAllLabel = "Marcar todo", unmarkAllLabel = "Desmarcar todo", }) => {
|
|
7
8
|
const allKeys = groups.flatMap((group) => group.actions.map((action) => action.key));
|
|
8
9
|
if (roles.length === 0) {
|
|
9
10
|
return (_jsx("p", { className: "text-sm text-gray-600", role: "status", children: emptyText }));
|
|
10
11
|
}
|
|
11
12
|
const roleHasKeys = (role, keys) => keys.length > 0 && keys.every((key) => role.permissionKeys.includes(key));
|
|
12
|
-
return (_jsx("div", { className: "overflow-
|
|
13
|
+
return (_jsx("div", { className: "relative isolate max-h-[70vh] overflow-auto rounded-md border border-gray-200", children: _jsxs("table", { className: "min-w-full border-separate border-spacing-0 text-sm", children: [_jsx("caption", { className: "sr-only", children: "Matriz de permisos por rol. Filas son acciones, columnas son roles." }), _jsx("thead", { children: _jsxs("tr", { className: "bg-gray-50", children: [_jsx("th", { scope: "col", className: `sticky left-0 top-0 z-40 min-w-[16rem] border-b border-gray-200 bg-gray-50 px-3 py-2 text-left font-semibold text-gray-800 ${FIRST_COL_SHADOW}`, children: "Permiso" }), roles.map((role) => {
|
|
13
14
|
const allGranted = roleHasKeys(role, allKeys);
|
|
14
|
-
return (_jsx("th", { scope: "col", className: "min-w-[9rem] border-b border-l border-gray-200 px-3 py-2 text-center font-semibold text-gray-800", children: _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("span", { className: "break-words", children: role.name }), onBulkRole ? (_jsx("button", { type: "button", className: "cursor-pointer text-xs font-medium text-indigo-700 hover:text-indigo-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded", disabled: disabled, onClick: () => onBulkRole(role.id, allGranted ? "none" : "all"), children: allGranted ? unmarkAllLabel : markAllLabel })) : null] }) }, role.id));
|
|
15
|
+
return (_jsx("th", { scope: "col", className: "sticky top-0 z-30 min-w-[9rem] border-b border-l border-gray-200 bg-gray-50 px-3 py-2 text-center font-semibold text-gray-800", children: _jsxs("div", { className: "flex flex-col items-center gap-1", children: [_jsx("span", { className: "break-words", children: role.name }), onBulkRole ? (_jsx("button", { type: "button", className: "cursor-pointer text-xs font-medium text-indigo-700 hover:text-indigo-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded", disabled: disabled, onClick: () => onBulkRole(role.id, allGranted ? "none" : "all"), children: allGranted ? unmarkAllLabel : markAllLabel })) : null] }) }, role.id));
|
|
15
16
|
})] }) }), _jsx("tbody", { children: groups.map((group) => {
|
|
16
17
|
const sectionKeys = group.actions.map((action) => action.key);
|
|
17
|
-
return (_jsx(GroupRows, { group: group, roles: roles, sectionKeys: sectionKeys, disabled: disabled, pendingCells: pendingCells, onToggle: onToggle, onBulkSection: onBulkSection, checkboxClass: checkboxClass }, group.id));
|
|
18
|
+
return (_jsx(GroupRows, { group: group, roles: roles, sectionKeys: sectionKeys, disabled: disabled, pendingCells: pendingCells, onToggle: onToggle, onBulkSection: onBulkSection, checkboxClass: checkboxClass, firstColShadow: FIRST_COL_SHADOW }, group.id));
|
|
18
19
|
}) })] }) }));
|
|
19
20
|
};
|
|
20
|
-
function GroupRows({ group, roles, sectionKeys, disabled, pendingCells, onToggle, onBulkSection, checkboxClass, }) {
|
|
21
|
-
return (_jsxs(_Fragment, { children: [
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
function GroupRows({ group, roles, sectionKeys, disabled, pendingCells, onToggle, onBulkSection, checkboxClass, firstColShadow, }) {
|
|
22
|
+
return (_jsxs(_Fragment, { children: [_jsxs("tr", { className: "bg-slate-100", children: [_jsx("th", { scope: "colgroup", className: `sticky left-0 z-20 border-b border-gray-200 bg-slate-100 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-slate-700 ${firstColShadow}`, children: group.label }), roles.map((role) => (_jsx("td", { className: "border-b border-l border-gray-200 bg-slate-100" }, role.id)))] }), group.actions.map((action, actionIndex) => {
|
|
23
|
+
const rowBg = actionIndex % 2 === 0 ? "bg-white" : "bg-slate-50";
|
|
24
|
+
return (_jsxs("tr", { className: rowBg, children: [_jsx("th", { scope: "row", className: `sticky left-0 z-10 border-b border-gray-100 px-3 py-2 text-left font-normal text-gray-800 ${rowBg} ${firstColShadow}`, children: _jsxs("div", { className: "flex flex-col gap-0.5", children: [_jsx("span", { children: action.label }), _jsx("code", { className: "text-xs text-gray-500", children: action.key })] }) }), roles.map((role) => {
|
|
25
|
+
const granted = role.permissionKeys.includes(action.key);
|
|
26
|
+
const cellKey = permissionMatrixCellKey(role.id, action.key);
|
|
27
|
+
const pending = pendingCells?.has(cellKey) ?? false;
|
|
28
|
+
return (_jsx("td", { className: "border-b border-l border-gray-100 px-3 py-2 text-center", children: _jsx("input", { type: "checkbox", id: `permission-${role.id}-${action.key}`, name: `permission-${role.id}-${action.key}`, className: checkboxClass, checked: granted, disabled: disabled || pending, "aria-label": `${role.name}: ${action.label}`, onChange: () => onToggle(role.id, action.key, !granted) }) }, role.id));
|
|
29
|
+
})] }, action.key));
|
|
30
|
+
}), onBulkSection ? (_jsxs("tr", { children: [_jsx("td", { className: `sticky left-0 z-10 border-b border-gray-200 bg-white px-3 py-1 text-xs text-gray-500 ${firstColShadow}`, children: "Secci\u00F3n" }), roles.map((role) => {
|
|
27
31
|
const sectionGranted = sectionKeys.every((key) => role.permissionKeys.includes(key));
|
|
28
32
|
return (_jsx("td", { className: "border-b border-l border-gray-200 px-3 py-1 text-center", children: _jsx("button", { type: "button", className: "cursor-pointer text-xs font-medium text-indigo-700 hover:text-indigo-900 focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded disabled:cursor-not-allowed disabled:opacity-50", disabled: disabled, onClick: () => onBulkSection(role.id, group.id, sectionGranted ? "remove" : "add"), children: sectionGranted ? "Quitar sección" : "Marcar sección" }) }, role.id));
|
|
29
33
|
})] })) : null] }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zauru-sdk/components",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.14.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",
|
|
@@ -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.
|
|
48
|
-
"@zauru-sdk/hooks": "^6.3.
|
|
47
|
+
"@zauru-sdk/common": "^6.12.0",
|
|
48
|
+
"@zauru-sdk/hooks": "^6.3.23",
|
|
49
49
|
"@zauru-sdk/icons": "^6.1.0",
|
|
50
|
-
"@zauru-sdk/redux": "^6.7.
|
|
51
|
-
"@zauru-sdk/types": "^6.
|
|
52
|
-
"@zauru-sdk/utils": "^6.
|
|
50
|
+
"@zauru-sdk/redux": "^6.7.2",
|
|
51
|
+
"@zauru-sdk/types": "^6.28.0",
|
|
52
|
+
"@zauru-sdk/utils": "^6.26.0",
|
|
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": "
|
|
65
|
+
"gitHead": "2fda4a18e3f62d22c38fbb91052869db9ebc16f4"
|
|
66
66
|
}
|
|
@@ -30,6 +30,8 @@ export function permissionMatrixCellKey(roleId: number, key: string): string {
|
|
|
30
30
|
const checkboxClass =
|
|
31
31
|
"form-checkbox h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:cursor-not-allowed disabled:opacity-50";
|
|
32
32
|
|
|
33
|
+
const FIRST_COL_SHADOW = "shadow-[2px_0_4px_-2px_rgba(0,0,0,0.12)]";
|
|
34
|
+
|
|
33
35
|
export const PermissionMatrix = ({
|
|
34
36
|
groups,
|
|
35
37
|
roles,
|
|
@@ -58,8 +60,8 @@ export const PermissionMatrix = ({
|
|
|
58
60
|
keys.length > 0 && keys.every((key) => role.permissionKeys.includes(key));
|
|
59
61
|
|
|
60
62
|
return (
|
|
61
|
-
<div className="overflow-
|
|
62
|
-
<table className="min-w-full border-
|
|
63
|
+
<div className="relative isolate max-h-[70vh] overflow-auto rounded-md border border-gray-200">
|
|
64
|
+
<table className="min-w-full border-separate border-spacing-0 text-sm">
|
|
63
65
|
<caption className="sr-only">
|
|
64
66
|
Matriz de permisos por rol. Filas son acciones, columnas son roles.
|
|
65
67
|
</caption>
|
|
@@ -67,7 +69,7 @@ export const PermissionMatrix = ({
|
|
|
67
69
|
<tr className="bg-gray-50">
|
|
68
70
|
<th
|
|
69
71
|
scope="col"
|
|
70
|
-
className=
|
|
72
|
+
className={`sticky left-0 top-0 z-40 min-w-[16rem] border-b border-gray-200 bg-gray-50 px-3 py-2 text-left font-semibold text-gray-800 ${FIRST_COL_SHADOW}`}
|
|
71
73
|
>
|
|
72
74
|
Permiso
|
|
73
75
|
</th>
|
|
@@ -77,7 +79,7 @@ export const PermissionMatrix = ({
|
|
|
77
79
|
<th
|
|
78
80
|
key={role.id}
|
|
79
81
|
scope="col"
|
|
80
|
-
className="min-w-[9rem] border-b border-l border-gray-200 px-3 py-2 text-center font-semibold text-gray-800"
|
|
82
|
+
className="sticky top-0 z-30 min-w-[9rem] border-b border-l border-gray-200 bg-gray-50 px-3 py-2 text-center font-semibold text-gray-800"
|
|
81
83
|
>
|
|
82
84
|
<div className="flex flex-col items-center gap-1">
|
|
83
85
|
<span className="break-words">{role.name}</span>
|
|
@@ -113,6 +115,7 @@ export const PermissionMatrix = ({
|
|
|
113
115
|
onToggle={onToggle}
|
|
114
116
|
onBulkSection={onBulkSection}
|
|
115
117
|
checkboxClass={checkboxClass}
|
|
118
|
+
firstColShadow={FIRST_COL_SHADOW}
|
|
116
119
|
/>
|
|
117
120
|
);
|
|
118
121
|
})}
|
|
@@ -131,6 +134,7 @@ function GroupRows({
|
|
|
131
134
|
onToggle,
|
|
132
135
|
onBulkSection,
|
|
133
136
|
checkboxClass,
|
|
137
|
+
firstColShadow,
|
|
134
138
|
}: {
|
|
135
139
|
group: PermissionGroup;
|
|
136
140
|
roles: PermissionMatrixRole[];
|
|
@@ -140,56 +144,67 @@ function GroupRows({
|
|
|
140
144
|
onToggle: Props["onToggle"];
|
|
141
145
|
onBulkSection: Props["onBulkSection"];
|
|
142
146
|
checkboxClass: string;
|
|
147
|
+
firstColShadow: string;
|
|
143
148
|
}) {
|
|
144
149
|
return (
|
|
145
150
|
<>
|
|
146
151
|
<tr className="bg-slate-100">
|
|
147
152
|
<th
|
|
148
153
|
scope="colgroup"
|
|
149
|
-
|
|
150
|
-
className="sticky left-0 border-b border-gray-200 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-slate-700"
|
|
154
|
+
className={`sticky left-0 z-20 border-b border-gray-200 bg-slate-100 px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-slate-700 ${firstColShadow}`}
|
|
151
155
|
>
|
|
152
156
|
{group.label}
|
|
153
157
|
</th>
|
|
158
|
+
{roles.map((role) => (
|
|
159
|
+
<td
|
|
160
|
+
key={role.id}
|
|
161
|
+
className="border-b border-l border-gray-200 bg-slate-100"
|
|
162
|
+
/>
|
|
163
|
+
))}
|
|
154
164
|
</tr>
|
|
155
|
-
{group.actions.map((action) =>
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
165
|
+
{group.actions.map((action, actionIndex) => {
|
|
166
|
+
const rowBg = actionIndex % 2 === 0 ? "bg-white" : "bg-slate-50";
|
|
167
|
+
return (
|
|
168
|
+
<tr key={action.key} className={rowBg}>
|
|
169
|
+
<th
|
|
170
|
+
scope="row"
|
|
171
|
+
className={`sticky left-0 z-10 border-b border-gray-100 px-3 py-2 text-left font-normal text-gray-800 ${rowBg} ${firstColShadow}`}
|
|
172
|
+
>
|
|
173
|
+
<div className="flex flex-col gap-0.5">
|
|
174
|
+
<span>{action.label}</span>
|
|
175
|
+
<code className="text-xs text-gray-500">{action.key}</code>
|
|
176
|
+
</div>
|
|
177
|
+
</th>
|
|
178
|
+
{roles.map((role) => {
|
|
179
|
+
const granted = role.permissionKeys.includes(action.key);
|
|
180
|
+
const cellKey = permissionMatrixCellKey(role.id, action.key);
|
|
181
|
+
const pending = pendingCells?.has(cellKey) ?? false;
|
|
182
|
+
return (
|
|
183
|
+
<td
|
|
184
|
+
key={role.id}
|
|
185
|
+
className="border-b border-l border-gray-100 px-3 py-2 text-center"
|
|
186
|
+
>
|
|
187
|
+
<input
|
|
188
|
+
type="checkbox"
|
|
189
|
+
id={`permission-${role.id}-${action.key}`}
|
|
190
|
+
name={`permission-${role.id}-${action.key}`}
|
|
191
|
+
className={checkboxClass}
|
|
192
|
+
checked={granted}
|
|
193
|
+
disabled={disabled || pending}
|
|
194
|
+
aria-label={`${role.name}: ${action.label}`}
|
|
195
|
+
onChange={() => onToggle(role.id, action.key, !granted)}
|
|
196
|
+
/>
|
|
197
|
+
</td>
|
|
198
|
+
);
|
|
199
|
+
})}
|
|
200
|
+
</tr>
|
|
201
|
+
);
|
|
202
|
+
})}
|
|
190
203
|
{onBulkSection ? (
|
|
191
204
|
<tr>
|
|
192
|
-
<td
|
|
205
|
+
<td
|
|
206
|
+
className={`sticky left-0 z-10 border-b border-gray-200 bg-white px-3 py-1 text-xs text-gray-500 ${firstColShadow}`}
|
|
207
|
+
>
|
|
193
208
|
Sección
|
|
194
209
|
</td>
|
|
195
210
|
{roles.map((role) => {
|