@zevra/ui 0.43.1 → 0.44.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.
@@ -15,11 +15,12 @@
15
15
  // variables. Aucune classe nouvelle n'entre par cette porte : ce sont les
16
16
  // mêmes .zv-table / .zv-row / .zv-cell que le tableau écrit à la main.
17
17
 
18
- import { Fragment, type CSSProperties, type ReactNode } from 'react'
18
+ import { Fragment, type CSSProperties, type DragEvent, type ReactNode } from 'react'
19
19
  import {
20
20
  flexRender,
21
21
  type Column,
22
22
  type Header,
23
+ type Row,
23
24
  type RowData,
24
25
  type Table as TableTanstack,
25
26
  } from '@tanstack/react-table'
@@ -85,6 +86,32 @@ export interface DataTableProps<T> {
85
86
  * liste). Elle porte un filet d'accent à gauche, distinct de la ligne
86
87
  * COCHÉE, qui est une sélection pour agir. */
87
88
  ligneActive?: (ligne: T) => boolean
89
+ /** Clic droit sur une ligne : l'appelant ouvre son menu contextuel aux
90
+ * coordonnées données. Le menu du navigateur est alors retenu. */
91
+ onRowContextMenu?: (ligne: T, x: number, y: number) => void
92
+ /** Double clic : « ouvrir pour de bon », quand le simple clic ne fait que
93
+ * montrer. La sélection de texte qu'il provoque est effacée. */
94
+ onRowDoubleClick?: (ligne: T) => void
95
+ /** La ligne se saisit (glisser-déposer NATIF) : l'appelant remplit le
96
+ * `dataTransfer`, pour ranger une pièce dans un dossier par exemple. */
97
+ onRowDragStart?: (ligne: T, e: DragEvent<HTMLDivElement>) => void
98
+ /** Les lignes à rendre, dans leur ordre, à la place du modèle de TanStack :
99
+ * pour un ordre que le tri ne sait pas dire (des lignes épinglées en tête
100
+ * quel que soit le tri). */
101
+ lignes?: Row<T>[]
102
+ /** Rangée posée en tête du corps, avant les lignes : une ligne de SAISIE. */
103
+ avantLignes?: ReactNode
104
+ /** Rangée intercalée AVANT une ligne : l'intitulé d'un groupe (une année,
105
+ * un mois) quand la clé change. Rien pour ne rien intercaler. */
106
+ intercalaire?: (ligne: Row<T>, precedente: Row<T> | undefined) => ReactNode
107
+ /** Sous les lignes : un pied de totaux, le déclencheur d'une liste infinie. */
108
+ apresLignes?: ReactNode
109
+ /** Rangées resserrées, pour un tableau secondaire posé dans une fiche. */
110
+ dense?: boolean
111
+ /** Le tableau REMPLIT la hauteur de son parent (une colonne flex bornée) :
112
+ * seules les lignes défilent, les intitulés et les filtres restent en
113
+ * haut. */
114
+ remplir?: boolean
88
115
  className?: string
89
116
  }
90
117
 
@@ -144,6 +171,15 @@ export function DataTable<T>({
144
171
  celluleEntete,
145
172
  sousEntete,
146
173
  ligneActive,
174
+ onRowContextMenu,
175
+ onRowDoubleClick,
176
+ onRowDragStart,
177
+ lignes: lignesImposees,
178
+ avantLignes,
179
+ intercalaire,
180
+ apresLignes,
181
+ dense,
182
+ remplir,
147
183
  className,
148
184
  }: DataTableProps<T>) {
149
185
  const grille = table.options.enableColumnResizing === true
@@ -154,7 +190,7 @@ export function DataTable<T>({
154
190
  grille ? piste(c, reglages[c.id] != null) : (c.columnDef.meta?.largeur ?? 'minmax(0, 1fr)'),
155
191
  )
156
192
  .join(' ')
157
- const lignes = table.getRowModel().rows
193
+ const lignes = lignesImposees ?? table.getRowModel().rows
158
194
  const style: Record<string, string> = { '--zv-cols': gabarit }
159
195
  if (grille) {
160
196
  style['--zv-cols-somme'] = `${colonnes.reduce((s, c) => s + c.getSize(), 0)}px`
@@ -163,9 +199,10 @@ export function DataTable<T>({
163
199
 
164
200
  return (
165
201
  <div
166
- className={cx('zv-table', className)}
202
+ className={cx('zv-table', dense && 'zv-table--dense', remplir && 'zv-table--remplir', className)}
167
203
  style={style as CSSProperties}
168
204
  >
205
+ <Haut actif={remplir}>
169
206
  {table.getHeaderGroups().map((groupe) => (
170
207
  <div className="zv-table__head" key={groupe.id}>
171
208
  {groupe.headers.map((entete) => {
@@ -218,12 +255,16 @@ export function DataTable<T>({
218
255
  ))}
219
256
 
220
257
  {sousEntete}
258
+ </Haut>
259
+
260
+ {avantLignes}
221
261
 
222
262
  {lignes.length === 0
223
263
  ? vide
224
- : lignes.map((ligne) => (
264
+ : lignes.map((ligne, i) => (
265
+ <Fragment key={ligne.id}>
266
+ {intercalaire?.(ligne, lignes[i - 1])}
225
267
  <div
226
- key={ligne.id}
227
268
  className={cx(
228
269
  'zv-row',
229
270
  ligne.getIsSelected() && 'zv-row--selected',
@@ -236,6 +277,25 @@ export function DataTable<T>({
236
277
  tabIndex={onRowClick ? 0 : undefined}
237
278
  style={onRowClick ? undefined : { cursor: 'default' }}
238
279
  onClick={onRowClick ? () => onRowClick(ligne.original) : undefined}
280
+ onContextMenu={
281
+ onRowContextMenu
282
+ ? (e) => {
283
+ e.preventDefault()
284
+ onRowContextMenu(ligne.original, e.clientX, e.clientY)
285
+ }
286
+ : undefined
287
+ }
288
+ onDoubleClick={
289
+ onRowDoubleClick
290
+ ? (e) => {
291
+ e.preventDefault()
292
+ e.currentTarget.ownerDocument.defaultView?.getSelection()?.removeAllRanges()
293
+ onRowDoubleClick(ligne.original)
294
+ }
295
+ : undefined
296
+ }
297
+ draggable={onRowDragStart ? true : undefined}
298
+ onDragStart={onRowDragStart ? (e) => onRowDragStart(ligne.original, e) : undefined}
239
299
  onKeyDown={
240
300
  onRowClick
241
301
  ? (e) => {
@@ -269,7 +329,18 @@ export function DataTable<T>({
269
329
  )
270
330
  })}
271
331
  </div>
332
+ </Fragment>
272
333
  ))}
334
+
335
+ {apresLignes}
273
336
  </div>
274
337
  )
275
338
  }
339
+
340
+ /** L'en-tête et les filtres, groupés dans un bloc COLLANT quand le tableau
341
+ * remplit son parent : sans ce bloc, chaque rangée collerait au même
342
+ * endroit et la seconde recouvrirait la première. Sinon, rien n'enveloppe :
343
+ * le balisage reste celui d'avant. */
344
+ function Haut({ actif, children }: { actif?: boolean; children: ReactNode }) {
345
+ return actif ? <div className="zv-table__haut">{children}</div> : <>{children}</>
346
+ }