@zauru-sdk/components 6.13.2 → 6.14.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 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.0](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.13.3...@zauru-sdk/components@6.14.0) (2026-10-26)
7
+
8
+
9
+ ### Features
10
+
11
+ * fijar cabecera y primera columna de PermissionMatrix al hacer scroll ([5b628a9](https://github.com/intuitiva/zauru-typescript-sdk/commit/5b628a9c0bc80169757cbe9ad96106cfe3732458))
12
+
13
+
14
+
15
+
16
+
17
+ ## [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)
18
+
19
+ **Note:** Version bump only for package @zauru-sdk/components
20
+
21
+
22
+
23
+
24
+
6
25
  ## [6.13.2](https://github.com/intuitiva/zauru-typescript-sdk/compare/@zauru-sdk/components@6.13.1...@zauru-sdk/components@6.13.2) (2026-10-16)
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-x-auto rounded-md border border-gray-200", children: _jsxs("table", { className: "min-w-full border-collapse 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 z-10 min-w-[16rem] border-b border-gray-200 bg-gray-50 px-3 py-2 text-left font-semibold text-gray-800", children: "Permiso" }), roles.map((role) => {
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: [_jsx("tr", { className: "bg-slate-100", children: _jsx("th", { scope: "colgroup", colSpan: roles.length + 1, 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", children: group.label }) }), group.actions.map((action) => (_jsxs("tr", { className: "odd:bg-white even:bg-slate-50", children: [_jsx("th", { scope: "row", className: "sticky left-0 z-10 border-b border-gray-100 bg-inherit px-3 py-2 text-left font-normal text-gray-800", 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) => {
22
- const granted = role.permissionKeys.includes(action.key);
23
- const cellKey = permissionMatrixCellKey(role.id, action.key);
24
- const pending = pendingCells?.has(cellKey) ?? false;
25
- 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));
26
- })] }, action.key))), 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", children: "Secci\u00F3n" }), roles.map((role) => {
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.13.2",
3
+ "version": "6.14.0",
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.10.0",
48
- "@zauru-sdk/hooks": "^6.3.21",
47
+ "@zauru-sdk/common": "^6.11.1",
48
+ "@zauru-sdk/hooks": "^6.3.22",
49
49
  "@zauru-sdk/icons": "^6.1.0",
50
- "@zauru-sdk/redux": "^6.7.0",
51
- "@zauru-sdk/types": "^6.24.0",
52
- "@zauru-sdk/utils": "^6.24.1",
50
+ "@zauru-sdk/redux": "^6.7.1",
51
+ "@zauru-sdk/types": "^6.27.0",
52
+ "@zauru-sdk/utils": "^6.25.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": "2daa677e6cd4948b23bfbf862929b4c74655e4e0"
65
+ "gitHead": "27790283dfb4e06580fe08c6bcd8793f702656c0"
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-x-auto rounded-md border border-gray-200">
62
- <table className="min-w-full border-collapse text-sm">
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="sticky left-0 z-10 min-w-[16rem] border-b border-gray-200 bg-gray-50 px-3 py-2 text-left font-semibold text-gray-800"
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
- colSpan={roles.length + 1}
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
- <tr key={action.key} className="odd:bg-white even:bg-slate-50">
157
- <th
158
- scope="row"
159
- className="sticky left-0 z-10 border-b border-gray-100 bg-inherit px-3 py-2 text-left font-normal text-gray-800"
160
- >
161
- <div className="flex flex-col gap-0.5">
162
- <span>{action.label}</span>
163
- <code className="text-xs text-gray-500">{action.key}</code>
164
- </div>
165
- </th>
166
- {roles.map((role) => {
167
- const granted = role.permissionKeys.includes(action.key);
168
- const cellKey = permissionMatrixCellKey(role.id, action.key);
169
- const pending = pendingCells?.has(cellKey) ?? false;
170
- return (
171
- <td
172
- key={role.id}
173
- className="border-b border-l border-gray-100 px-3 py-2 text-center"
174
- >
175
- <input
176
- type="checkbox"
177
- id={`permission-${role.id}-${action.key}`}
178
- name={`permission-${role.id}-${action.key}`}
179
- className={checkboxClass}
180
- checked={granted}
181
- disabled={disabled || pending}
182
- aria-label={`${role.name}: ${action.label}`}
183
- onChange={() => onToggle(role.id, action.key, !granted)}
184
- />
185
- </td>
186
- );
187
- })}
188
- </tr>
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 className="sticky left-0 z-10 border-b border-gray-200 bg-white px-3 py-1 text-xs text-gray-500">
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) => {