innoboxrr-react-datatable 2.1.0 → 2.3.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "innoboxrr-react-datatable",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Gemelo React de innoboxrr-vue-datatable: el mismo contrato de modelo, la misma tabla.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -37,11 +37,12 @@
|
|
|
37
37
|
"node": ">=20"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
+
"@floating-ui/dom": "^1.8.0",
|
|
40
41
|
"@iconify/react": "^6.0.0",
|
|
41
42
|
"axios": "^1.7.0"
|
|
42
43
|
},
|
|
43
44
|
"peerDependencies": {
|
|
44
|
-
"innoboxrr-form-core": "^2.
|
|
45
|
+
"innoboxrr-form-core": "^2.3.0",
|
|
45
46
|
"react": "^19.0.0",
|
|
46
47
|
"react-dom": "^19.0.0",
|
|
47
48
|
"react-router-dom": "^7.0.0"
|
|
@@ -2,13 +2,12 @@ import DisabledLinkComponent from './DisabledLinkComponent.jsx'
|
|
|
2
2
|
import IconLinkComponent from './IconLinkComponent.jsx'
|
|
3
3
|
import IconRouteComponent from './IconRouteComponent.jsx'
|
|
4
4
|
|
|
5
|
+
// Un popover se cierra solo: no hace falta preguntarle nada a nadie.
|
|
6
|
+
// La llamada es opcional porque hidePopover no existe en un navegador anterior
|
|
7
|
+
// a la Popover API —ni en jsdom—, y un menu que no cierra es mejor que una
|
|
8
|
+
// excepcion.
|
|
5
9
|
const closeDropdown = (event) => {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// UIkit lo aporta la aplicación anfitriona.
|
|
9
|
-
if (dropdown) {
|
|
10
|
-
globalThis.UIkit?.dropdown(dropdown)?.hide(false)
|
|
11
|
-
}
|
|
10
|
+
event.target.closest('[popover]')?.hidePopover?.()
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react'
|
|
2
2
|
import ActionListComponent from './ActionListComponent.jsx'
|
|
3
|
+
import DatatableIcon from './DatatableIcon.jsx'
|
|
3
4
|
import NavDropdownComponent from './NavDropdownComponent.jsx'
|
|
4
5
|
|
|
5
6
|
const cellValue = (head, row) => (
|
|
@@ -96,8 +97,9 @@ export default function DataTableComponent({
|
|
|
96
97
|
type="button"
|
|
97
98
|
aria-label={`Acciones del registro ${row.id}`}
|
|
98
99
|
className="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-2 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 focus:outline-none dark:focus:ring-blue-800"
|
|
100
|
+
popoverTarget={`dropdown_${row.id}`}
|
|
99
101
|
onClick={() => onActionButtonClicked?.(row.actions)}>
|
|
100
|
-
<
|
|
102
|
+
<DatatableIcon icon="actions" />
|
|
101
103
|
</button>
|
|
102
104
|
|
|
103
105
|
<NavDropdownComponent id={`dropdown_${row.id}`} pos="left">
|
|
@@ -13,7 +13,8 @@ export default function DisabledLinkComponent({ icon = '', text }) {
|
|
|
13
13
|
href="#"
|
|
14
14
|
aria-disabled="true"
|
|
15
15
|
className="disabled-link block px-4 py-2 dark:hover:text-white dark:text-slate-400"
|
|
16
|
-
|
|
16
|
+
data-tooltip="This action is not authorized"
|
|
17
|
+
data-tooltip-pos="right"
|
|
17
18
|
onClick={(event) => event.preventDefault()}>
|
|
18
19
|
{showIcon ? <DatatableIcon icon={icon} /> : null}
|
|
19
20
|
<span>{text}</span>
|
|
@@ -1,35 +1,98 @@
|
|
|
1
|
-
import { useEffect, useRef } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useRef } from 'react'
|
|
2
|
+
import { autoUpdate, computePosition, flip, offset as offsetMiddleware, shift } from '@floating-ui/dom'
|
|
3
|
+
import { classFor } from 'innoboxrr-form-core'
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* Gemelo de NavDropdownComponent.vue.
|
|
5
7
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* porque UIkit lo
|
|
8
|
+
* Antes lo movía `uk-dropdown`, y como UIkit sólo procesa el atributo al
|
|
9
|
+
* montarse el nodo, había que avisarle con `UIkit.update()` — con guarda,
|
|
10
|
+
* porque UIkit lo aportaba la aplicación anfitriona sin que nadie lo
|
|
11
|
+
* declarara, y en una prueba no estaba.
|
|
12
|
+
*
|
|
13
|
+
* La mitad de lo que hacía ya lo hace el navegador. La **Popover API** —el
|
|
14
|
+
* atributo `popover` y el `popoverTarget` del botón— aporta de fábrica la capa
|
|
15
|
+
* superior, el cierre al pulsar fuera y el cierre con Escape. Lo único que
|
|
16
|
+
* falta es colocarlo, y de eso se ocupa Floating UI.
|
|
17
|
+
*
|
|
18
|
+
* El botón que lo abre sólo necesita `popoverTarget` con este mismo id:
|
|
19
|
+
*
|
|
20
|
+
* <button popoverTarget={`dropdown_${row.id}`}>…</button>
|
|
21
|
+
* <NavDropdownComponent id={`dropdown_${row.id}`} pos="left" />
|
|
22
|
+
*
|
|
23
|
+
* @param {{ id: string, pos?: string, offset?: number, children?: any }} props
|
|
9
24
|
*/
|
|
10
25
|
export default function NavDropdownComponent({
|
|
11
26
|
id,
|
|
27
|
+
// Se conservan los nombres que usaba UIkit para no romper a quien ya los
|
|
28
|
+
// pasa; `bottom-left` y compañía se traducen a los de Floating UI.
|
|
12
29
|
pos = 'bottom-left',
|
|
13
|
-
|
|
14
|
-
offset = 0,
|
|
15
|
-
// Es el nombre de una animacion de UIkit, no una clase nuestra.
|
|
16
|
-
animation = 'uk-animation-slide-top-small',
|
|
17
|
-
duration = 500,
|
|
30
|
+
offset = 4,
|
|
18
31
|
children,
|
|
19
32
|
}) {
|
|
20
|
-
const
|
|
33
|
+
const panel = useRef(null)
|
|
34
|
+
const stopFollowing = useRef(null)
|
|
35
|
+
|
|
36
|
+
const placement = (() => {
|
|
37
|
+
const [side, align] = pos.split('-')
|
|
38
|
+
|
|
39
|
+
if (! align) {
|
|
40
|
+
return side
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// UIkit dice `bottom-left` para «debajo, alineado a la izquierda»;
|
|
44
|
+
// Floating UI lo llama `bottom-start`.
|
|
45
|
+
return `${side}-${align === 'left' ? 'start' : 'end'}`
|
|
46
|
+
})()
|
|
47
|
+
|
|
48
|
+
const onBeforeToggle = useCallback((event) => {
|
|
49
|
+
if (event.newState !== 'open') {
|
|
50
|
+
stopFollowing.current?.()
|
|
51
|
+
stopFollowing.current = null
|
|
52
|
+
|
|
53
|
+
return
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// El botón es quien apunta a este panel, así que se encuentra por el
|
|
57
|
+
// atributo y no hace falta que nadie lo pase como prop.
|
|
58
|
+
const trigger = document.querySelector(`[popovertarget="${id}"]`)
|
|
59
|
+
|
|
60
|
+
if (! trigger || ! panel.current) {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// autoUpdate recoloca al hacer scroll o redimensionar: un menú abierto
|
|
65
|
+
// que se queda flotando donde estaba es peor que uno mal colocado.
|
|
66
|
+
stopFollowing.current = autoUpdate(trigger, panel.current, () => {
|
|
67
|
+
computePosition(trigger, panel.current, {
|
|
68
|
+
placement,
|
|
69
|
+
middleware: [
|
|
70
|
+
offsetMiddleware(offset),
|
|
71
|
+
// Si no cabe abajo, se va arriba; si se sale por un lado,
|
|
72
|
+
// se desplaza para caber.
|
|
73
|
+
flip(),
|
|
74
|
+
shift({ padding: 8 }),
|
|
75
|
+
],
|
|
76
|
+
}).then(({ x, y }) => {
|
|
77
|
+
Object.assign(panel.current.style, { left: `${x}px`, top: `${y}px` })
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
}, [id, offset, placement])
|
|
21
81
|
|
|
22
82
|
useEffect(() => {
|
|
23
|
-
|
|
24
|
-
|
|
83
|
+
const node = panel.current
|
|
84
|
+
|
|
85
|
+
node?.addEventListener('beforetoggle', onBeforeToggle)
|
|
86
|
+
|
|
87
|
+
return () => {
|
|
88
|
+
node?.removeEventListener('beforetoggle', onBeforeToggle)
|
|
89
|
+
stopFollowing.current?.()
|
|
90
|
+
}
|
|
91
|
+
}, [onBeforeToggle])
|
|
25
92
|
|
|
26
93
|
return (
|
|
27
|
-
<div
|
|
28
|
-
|
|
29
|
-
id={id}
|
|
30
|
-
uk-dropdown={`pos: ${pos}; mode: ${mode}; offset: ${offset}; animation: ${animation}; duration: ${duration};`}
|
|
31
|
-
className="fe-p-0 z-10 hidden text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-slate-800 p-2">
|
|
32
|
-
<ul className="fe-menu">{children}</ul>
|
|
94
|
+
<div ref={panel} id={id} popover="" className={classFor('menu')}>
|
|
95
|
+
<ul className="fe-menu-list">{children}</ul>
|
|
33
96
|
</div>
|
|
34
97
|
)
|
|
35
98
|
}
|