@addev-be/ui 0.14.5 → 0.15.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": "@addev-be/ui",
3
- "version": "0.14.5",
3
+ "version": "0.15.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "watch": "tsc -b --watch",
@@ -1,38 +1,83 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  /* eslint-disable @typescript-eslint/no-unnecessary-type-constraint */
3
3
 
4
+ import { EditableDataGridCellProps, EditableDataGridColumn } from '../types';
4
5
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
6
 
6
- import { DataGridCellProps } from '../types';
7
7
  import { DataGridEditableCellFC } from './types';
8
8
  import { EditableCellContainer } from '../styles';
9
9
  import { useDataGridContext } from '../hooks';
10
10
 
11
- export const DataGridEditableCell = <R,>({
11
+ export const DataGridEditableCell = <R, T>({
12
12
  row,
13
13
  columnKey,
14
14
  column,
15
15
  context,
16
- }: DataGridCellProps<R>) => {
17
- const { onCellEdited, setEditingCell } = useDataGridContext(context);
16
+ }: EditableDataGridCellProps<R, T>) => {
17
+ const { setEditingCell, setRows, editRow, rows, rowKeyGetter } =
18
+ useDataGridContext(context);
18
19
  const initialValue = useMemo(() => column.getter(row) ?? '', [column, row]);
19
20
  const [value, setValue] = useState(initialValue);
20
21
  const inputRef = useRef<HTMLInputElement | null>(null);
22
+ const rowKey = rowKeyGetter(row);
23
+
24
+ const defaultPartialRowGetter = useCallback(
25
+ (row: R, newValue: T): Partial<R> =>
26
+ ({ [columnKey]: newValue } as Partial<R>),
27
+ [columnKey]
28
+ );
29
+ const partialRowGetter: EditableDataGridColumn<R, T>['partialRowGetter'] =
30
+ column.partialRowGetter ?? defaultPartialRowGetter;
31
+
32
+ const save = useCallback(() => {
33
+ const rowIndex = rows.findIndex(
34
+ (r) => rowKeyGetter(r) === rowKeyGetter(row)
35
+ );
36
+
37
+ // If row is found
38
+ if (rowIndex >= 0) {
39
+ // Update the row with the new value
40
+ const partialRow = partialRowGetter(row, value);
41
+ const newRow = {
42
+ ...row,
43
+ ...partialRow,
44
+ };
45
+
46
+ // Update the edited rows state
47
+ editRow(rowKey, partialRow);
48
+
49
+ // Update the rows
50
+ setRows?.((oldRows) => {
51
+ const newRows = [...oldRows];
52
+ newRows[rowIndex] = newRow;
53
+ return newRows;
54
+ });
55
+ }
56
+ }, [
57
+ editRow,
58
+ partialRowGetter,
59
+ row,
60
+ rowKey,
61
+ rowKeyGetter,
62
+ rows,
63
+ setRows,
64
+ value,
65
+ ]);
21
66
 
22
67
  const onClose = useCallback(
23
- (save: boolean, nextEditingCell: [number, number] = [-1, -1]) => {
24
- if (save && value !== initialValue) {
25
- onCellEdited?.(row, columnKey, value);
68
+ (shouldSave: boolean, nextEditingCell: [number, number] = [-1, -1]) => {
69
+ if (shouldSave && value !== initialValue) {
70
+ save();
26
71
  }
27
72
  setEditingCell(nextEditingCell);
28
73
  },
29
- [columnKey, initialValue, onCellEdited, row, setEditingCell, value]
74
+ [initialValue, save, setEditingCell, value]
30
75
  );
31
76
 
32
77
  useEffect(() => {
33
78
  if (inputRef.current) {
34
79
  inputRef.current.focus();
35
- inputRef.current.select();
80
+ inputRef.current.select?.();
36
81
  }
37
82
  }, [inputRef]);
38
83
 
@@ -1,4 +1,4 @@
1
- import { FC, ForwardedRef } from 'react';
1
+ import { ForwardedRef } from 'react';
2
2
 
3
3
  export type DataGridEditableCellProps<R, T> = {
4
4
  ref?: ForwardedRef<HTMLInputElement>;
@@ -9,6 +9,7 @@ export type DataGridEditableCellProps<R, T> = {
9
9
  };
10
10
 
11
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- export type DataGridEditableCellFC<R, T = any> = FC<
13
- DataGridEditableCellProps<R, T>
14
- >;
12
+ export type DataGridEditableCellFC<R, T = any> = (
13
+ props: DataGridEditableCellProps<R, T>,
14
+ ref: ForwardedRef<HTMLInputElement>
15
+ ) => JSX.Element | null;
@@ -351,8 +351,9 @@ export const selectColumn = <R extends Record<string, any>, T>(
351
351
  items,
352
352
  itemKey,
353
353
  itemLabel,
354
- editComponent: ({ value, onChange, onClose }) => (
354
+ editComponent: forwardRef(({ value, onChange, onClose }, ref) => (
355
355
  <Select
356
+ ref={ref}
356
357
  items={items}
357
358
  itemKey={itemKey}
358
359
  itemLabel={itemLabel}
@@ -360,7 +361,7 @@ export const selectColumn = <R extends Record<string, any>, T>(
360
361
  onChange={onChange}
361
362
  onBlur={() => onClose(true)}
362
363
  />
363
- ),
364
+ )),
364
365
  ...otherOptions,
365
366
  });
366
367
 
@@ -151,37 +151,6 @@ export const useDataGrid = <R,>(
151
151
  clearChangedRows,
152
152
  } = useDataGridChangedRows<R>(onRowAdded);
153
153
 
154
- const onCellEdited = useCallback(
155
- (row: R, columnKey: string, value: any) => {
156
- setEditingCell([-1, -1]);
157
- const column = columns.find((col) => col.key === columnKey);
158
- const rowIndex = rows.findIndex(
159
- (r) => rowKeyGetter(r) === rowKeyGetter(row)
160
- );
161
-
162
- // If row and column are valid
163
- if (rowIndex >= 0 && column) {
164
- // Update the row with the new value
165
- const partialRow: Partial<R> = {};
166
- partialRow[columnKey as keyof R] = value;
167
- const newRow = { ...row, ...partialRow };
168
- const rowKey = rowKeyGetter(newRow);
169
-
170
- // Update the edited rows state
171
- editRow(rowKey, partialRow);
172
-
173
- // Update the rows
174
- const newRows = [...rows];
175
- newRows[rowIndex] = newRow;
176
- setRows?.(newRows);
177
-
178
- // Call the onRowEdited callback if provided
179
- // onRowEdited?.(newRow, columnKey, value);
180
- }
181
- },
182
- [columns, editRow, rowKeyGetter, rows, setRows]
183
- );
184
-
185
154
  /** ROWS SORTING **/
186
155
 
187
156
  const [sorts, setSorts] = useState<Record<string, DataGridSort>>(
@@ -301,7 +270,6 @@ export const useDataGrid = <R,>(
301
270
  setFilters,
302
271
  editingCell,
303
272
  setEditingCell,
304
- onCellEdited,
305
273
  copyTable,
306
274
  setColumnWidth,
307
275
  settings,
@@ -340,7 +308,6 @@ export const useDataGrid = <R,>(
340
308
  sorts,
341
309
  filters,
342
310
  editingCell,
343
- onCellEdited,
344
311
  copyTable,
345
312
  setColumnWidth,
346
313
  settings,
@@ -64,30 +64,31 @@ type CommonGridColumnProps<R, T> = {
64
64
  width?: number;
65
65
  };
66
66
 
67
- type EditableColumnProps<R, T> = CommonGridColumnProps<R, T> & {
67
+ export type EditableDataGridColumn<R, T> = CommonGridColumnProps<R, T> & {
68
68
  editable?: boolean;
69
69
  editComponent?: DataGridEditableCellFC<R, T>;
70
+ partialRowGetter?: (row: R, value: T) => Partial<R>;
70
71
  };
71
72
 
72
- export type DataGridTextColumn<R> = EditableColumnProps<R, string> & {
73
+ export type DataGridTextColumn<R> = EditableDataGridColumn<R, string> & {
73
74
  type: 'text';
74
75
  };
75
76
 
76
- export type DataGridNumberColumn<R> = EditableColumnProps<R, number> & {
77
+ export type DataGridNumberColumn<R> = EditableDataGridColumn<R, number> & {
77
78
  type: 'number';
78
79
  decimals?: number;
79
80
  currency?: string | ((row: R) => string);
80
81
  };
81
82
 
82
- export type DataGridDateColumn<R> = EditableColumnProps<R, Date> & {
83
+ export type DataGridDateColumn<R> = EditableDataGridColumn<R, Date> & {
83
84
  type: 'date';
84
85
  };
85
86
 
86
- export type DataGridCheckboxColumn<R> = EditableColumnProps<R, boolean> & {
87
+ export type DataGridCheckboxColumn<R> = EditableDataGridColumn<R, boolean> & {
87
88
  type: 'checkbox';
88
89
  };
89
90
 
90
- export type DataGridSelectColumn<R, T = any> = EditableColumnProps<
91
+ export type DataGridSelectColumn<R, T = any> = EditableDataGridColumn<
91
92
  R,
92
93
  T | null
93
94
  > & {
@@ -97,7 +98,14 @@ export type DataGridSelectColumn<R, T = any> = EditableColumnProps<
97
98
  itemLabel: (item: T) => string;
98
99
  };
99
100
 
100
- export type DataGridColorColumn<R> = EditableColumnProps<R, string> & {
101
+ export type DataGridForeignListColumn<R> = EditableDataGridColumn<
102
+ R,
103
+ string | null
104
+ > & {
105
+ type: 'foreignList';
106
+ };
107
+
108
+ export type DataGridColorColumn<R> = EditableDataGridColumn<R, string> & {
101
109
  type: 'color';
102
110
  };
103
111
 
@@ -107,6 +115,7 @@ export type DataGridColumn<R> =
107
115
  | DataGridDateColumn<R>
108
116
  | DataGridCheckboxColumn<R>
109
117
  | DataGridSelectColumn<R>
118
+ | DataGridForeignListColumn<R>
110
119
  | DataGridColorColumn<R>;
111
120
 
112
121
  export type DataGridColumnWithFilter<R> = DataGridColumn<R> & {
@@ -246,6 +255,9 @@ export type DataGridCellProps<R> = {
246
255
  userSelect?: boolean;
247
256
  color?: ThemeColor;
248
257
  };
258
+ export type EditableDataGridCellProps<R, T> = DataGridCellProps<R> & {
259
+ column: EditableDataGridColumn<R, T>;
260
+ };
249
261
 
250
262
  export type DataGridHeaderCellProps<R> = {
251
263
  columnKey: string;
@@ -0,0 +1,16 @@
1
+ import { ForwardedRef, forwardRef } from 'react';
2
+
3
+ import { SqlRequestForeignList } from '../SqlRequestForeignList';
4
+ import { SqlRequestForeignListProps } from '../SqlRequestForeignList/types';
5
+
6
+ const SqlRequestForeignListEditableCellInner = <
7
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
8
+ R extends Record<string, any>
9
+ >(
10
+ props: SqlRequestForeignListProps<R>,
11
+ ref: ForwardedRef<HTMLDivElement>
12
+ ) => <SqlRequestForeignList<R> {...props} ref={ref} />;
13
+
14
+ export const SqlRequestForeignListEditableCell = forwardRef(
15
+ SqlRequestForeignListEditableCellInner
16
+ );
@@ -3,7 +3,9 @@
3
3
  import { SqlImage, SqlImageWrapper } from '../styles';
4
4
  import {
5
5
  SqlRequestDataGridCheckboxColumn,
6
+ SqlRequestDataGridColumn,
6
7
  SqlRequestDataGridDateColumn,
8
+ SqlRequestDataGridForeignListColumn,
7
9
  SqlRequestDataGridNumberColumn,
8
10
  SqlRequestDataGridTextColumn,
9
11
  } from '../types';
@@ -22,11 +24,18 @@ import {
22
24
  } from '../../../../helpers/numbers';
23
25
 
24
26
  import { CheckboxEditableCell } from '../../DataGrid/DataGridEditableCell/CheckboxEditableCell';
27
+ import { DataGridSettings } from '../../DataGrid/types';
25
28
  import { DateEditableCell } from '../../DataGrid/DataGridEditableCell/DateEditableCell';
26
29
  import { NumberEditableCell } from '../../DataGrid/DataGridEditableCell/NumberEditableCell';
30
+ import { SqlRequestForeignList } from '../../SqlRequestForeignList';
31
+ import { SqlRequestForeignListProps } from '../../SqlRequestForeignList/types';
27
32
  import { TextEditableCell } from '../../DataGrid/DataGridEditableCell/TextEditableCell';
28
33
  import { forwardRef } from 'react';
29
34
 
35
+ export const isColumnVisible = <R,>(
36
+ obj: SqlRequestDataGridColumn<R> | DataGridSettings
37
+ ): boolean => obj?.order !== -1;
38
+
30
39
  export const sqlTextColumn = <R extends Record<string, any>>(
31
40
  key: string,
32
41
  title: React.ReactNode,
@@ -364,3 +373,35 @@ export const sqlColorColumn = <R extends Record<string, any>>(
364
373
  },
365
374
  ...options,
366
375
  });
376
+
377
+ export const sqlForeignListColumn = <
378
+ R extends Record<string, any>,
379
+ T extends Record<string, any>
380
+ >(
381
+ key: string,
382
+ title: string,
383
+ foreignListOptions: SqlRequestForeignListProps<T>,
384
+ options?: Partial<SqlRequestDataGridForeignListColumn<R, T | null>>
385
+ ): SqlRequestDataGridForeignListColumn<R, T | null> => ({
386
+ key,
387
+ name: title,
388
+ type: 'foreignList',
389
+ getter: (row) => row[key] ?? '',
390
+ sortGetter: (row) => row[key] ?? '',
391
+ filter: {
392
+ ...textFilter(key),
393
+ getter: (value) => value[key] ?? '',
394
+ renderer: (value) => value,
395
+ },
396
+ footer: (rows) => `${rows[0][key]} éléments`,
397
+ editComponent: forwardRef(({ row, onChange, onClose }, ref) => (
398
+ <SqlRequestForeignList<T>
399
+ initialId={row[key] ?? null}
400
+ onItemChanged={onChange}
401
+ onBlur={() => onClose(true)}
402
+ {...foreignListOptions}
403
+ ref={ref}
404
+ />
405
+ )),
406
+ ...options,
407
+ });
@@ -1,3 +1,5 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
1
3
  import {
2
4
  ConditionDTO,
3
5
  FieldDTO,
@@ -12,6 +14,7 @@ import {
12
14
  DataGridNumberColumn,
13
15
  DataGridProps,
14
16
  DataGridTextColumn,
17
+ EditableDataGridColumn,
15
18
  } from '../DataGrid/types';
16
19
 
17
20
  export type SqlRequestDataGridFilter<
@@ -41,12 +44,17 @@ export type SqlRequestDataGridCheckboxColumn<R> = DataGridCheckboxColumn<R> &
41
44
  SqlRequestDataGridCommonProps;
42
45
  export type SqlRequestDataGridDateColumn<R> = DataGridDateColumn<R> &
43
46
  SqlRequestDataGridCommonProps;
47
+ export type SqlRequestDataGridForeignListColumn<R, T> = {
48
+ type: 'foreignList';
49
+ } & EditableDataGridColumn<R, T> &
50
+ SqlRequestDataGridCommonProps;
44
51
 
45
52
  export type SqlRequestDataGridColumn<R> =
46
53
  | SqlRequestDataGridTextColumn<R>
47
54
  | SqlRequestDataGridNumberColumn<R>
48
55
  | SqlRequestDataGridCheckboxColumn<R>
49
- | SqlRequestDataGridDateColumn<R>;
56
+ | SqlRequestDataGridDateColumn<R>
57
+ | SqlRequestDataGridForeignListColumn<R, any>;
50
58
 
51
59
  export type SqlRequestDataGridColumns<R> = SqlRequestDataGridColumn<R>[];
52
60
 
@@ -1,11 +1,25 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
 
3
+ import {
4
+ ForwardedRef,
5
+ MouseEvent,
6
+ forwardRef,
7
+ useCallback,
8
+ useEffect,
9
+ useImperativeHandle,
10
+ useMemo,
11
+ useRef,
12
+ useState,
13
+ } from 'react';
3
14
  import {
4
15
  SqlRequestForeignListInput,
5
16
  SqlRequestForeignListItem,
6
17
  SqlRequestForeignListValue,
7
18
  } from './styles';
8
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
19
+ import {
20
+ SqlRequestForeignListItemProps,
21
+ SqlRequestForeignListProps,
22
+ } from './types';
9
23
 
10
24
  import { ChevronDownIcon } from '../../../Icons';
11
25
  import { Dropdown } from '../../layout';
@@ -14,93 +28,105 @@ import { useSqlRequestHandler } from '../../../services';
14
28
 
15
29
  const DEFAULT_ROW_HEIGHT = 24;
16
30
 
17
- type SqlRequestForeignListItemProps<R extends Record<string, any>> = {
18
- onClick?: (item: R | null) => void;
19
- };
20
-
21
31
  const createValueTemplate =
22
32
  <R extends Record<string, any>>(
23
- displayColumn: string,
33
+ ValueTemplate: VirtualScrollerTemplateFC<
34
+ R,
35
+ SqlRequestForeignListItemProps<R>
36
+ >,
24
37
  rowHeight = DEFAULT_ROW_HEIGHT
25
38
  ): VirtualScrollerTemplateFC<R, SqlRequestForeignListItemProps<R>> =>
26
39
  ({ item }) =>
27
40
  (
28
41
  <SqlRequestForeignListValue $rowHeight={rowHeight}>
29
- {item?.[displayColumn]}
42
+ <ValueTemplate item={item} index={0} />
30
43
  </SqlRequestForeignListValue>
31
44
  );
32
45
 
33
46
  const createItemTemplate =
34
47
  <R extends Record<string, any>>(
35
- displayColumn: string,
48
+ ItemTemplate: VirtualScrollerTemplateFC<
49
+ R,
50
+ SqlRequestForeignListItemProps<R>
51
+ >,
36
52
  rowHeight = DEFAULT_ROW_HEIGHT
37
53
  ): VirtualScrollerTemplateFC<R, SqlRequestForeignListItemProps<R>> =>
38
- ({ item, onClick }) =>
39
- (
54
+ ({ item, index, onClick: onItemClick }) => {
55
+ const onClick = useCallback(
56
+ (
57
+ e: MouseEvent<HTMLElement>,
58
+ item: R | null,
59
+ onItemClick?: (item: R | null) => void
60
+ ) => {
61
+ e.preventDefault();
62
+ e.stopPropagation();
63
+ onItemClick?.(item);
64
+ },
65
+ []
66
+ );
67
+
68
+ return (
40
69
  <SqlRequestForeignListItem
41
70
  $rowHeight={rowHeight}
42
- onClick={() => onClick?.(item)}
71
+ onClick={(e) => onClick(e, item, onItemClick)}
43
72
  >
44
- {item?.[displayColumn]} ({item?.Id})
73
+ <ItemTemplate item={item} index={index} />
45
74
  </SqlRequestForeignListItem>
46
75
  );
76
+ };
47
77
 
48
- type SqlRequestForeignListProps<R extends Record<string, any>> = {
49
- requestName: string;
50
- idColumn: string;
51
- columns: string[];
52
- value?: string | null;
53
- onChange?: (value: string | null) => void;
54
- valueTemplate?: VirtualScrollerTemplateFC<
55
- R,
56
- SqlRequestForeignListItemProps<R>
57
- >;
58
- itemTemplate?: VirtualScrollerTemplateFC<
59
- R,
60
- SqlRequestForeignListItemProps<R>
61
- >;
62
- rowHeight?: number;
63
- maxRows?: number;
64
- };
65
-
66
- export const SqlRequestForeignList = <R extends Record<string, any>>({
67
- requestName,
68
- idColumn,
69
- columns = [],
70
- value,
71
- onChange,
72
- itemTemplate,
73
- valueTemplate,
74
- rowHeight = DEFAULT_ROW_HEIGHT,
75
- maxRows = 10,
76
- }: SqlRequestForeignListProps<R>) => {
77
- const inputRef = useRef<HTMLDivElement>(null);
78
+ export const SqlRequestForeignListInner = <T extends Record<string, any>>(
79
+ {
80
+ requestName,
81
+ idColumn,
82
+ columns = [],
83
+ initialId,
84
+ onItemChanged,
85
+ onIdChanged,
86
+ onClosed,
87
+ onBlur,
88
+ itemTemplate,
89
+ valueTemplate,
90
+ rowHeight = DEFAULT_ROW_HEIGHT,
91
+ maxRows = 10,
92
+ }: SqlRequestForeignListProps<T>,
93
+ ref: ForwardedRef<HTMLDivElement>
94
+ ) => {
78
95
  const [opened, setOpened] = useState(false);
79
96
  const [rect, setRect] = useState<DOMRect | null>(null);
80
- const [rows, setRows] = useState<R[]>([]);
97
+ const [rows, setRows] = useState<T[]>([]);
81
98
  const [loaded, setLoaded] = useState(false);
99
+ const [selectedItem, setSelectedItem] = useState<T | null>(null);
100
+
101
+ // Create an internal ref for the component to use
102
+ const internalRef = useRef<HTMLDivElement>(null);
103
+
104
+ // Forward the ref while linking it to our internal ref
105
+ useImperativeHandle(ref, () => internalRef.current!, []);
82
106
 
83
- const [sqlRequest] = useSqlRequestHandler<R>(requestName);
84
- const [selectedItem, setSelectedItem] = useState<R | null>(null);
107
+ const [sqlRequest] = useSqlRequestHandler<T>(requestName);
85
108
 
86
109
  const open = useCallback(() => {
87
- if (inputRef.current) {
88
- setRect(inputRef.current.getBoundingClientRect());
110
+ if (internalRef.current) {
111
+ const parentRect = internalRef.current.getBoundingClientRect();
112
+ setRect(new DOMRect(0, 0, parentRect.width, parentRect.height));
89
113
  setOpened(true);
90
114
  }
91
115
  }, []);
92
116
 
93
117
  const close = useCallback(() => {
94
118
  setOpened(false);
95
- }, []);
119
+ onClosed?.();
120
+ }, [onClosed]);
96
121
 
97
122
  const onItemClick = useCallback(
98
- (item: R | null) => {
123
+ (item: T | null) => {
99
124
  setSelectedItem(item);
100
- onChange?.(item === null ? null : String(item[idColumn]));
125
+ onItemChanged?.(item);
126
+ onIdChanged?.(item?.[idColumn] ?? null);
101
127
  close();
102
128
  },
103
- [close, idColumn, onChange]
129
+ [close, idColumn, onIdChanged, onItemChanged]
104
130
  );
105
131
 
106
132
  useEffect(() => {
@@ -110,48 +136,66 @@ export const SqlRequestForeignList = <R extends Record<string, any>>({
110
136
  // conditions,
111
137
  // orderBy,
112
138
  }).then((response) => {
113
- const data = (response.data ?? []) as R[];
139
+ const data = (response.data ?? []) as T[];
114
140
  setRows(data);
115
141
  setSelectedItem(
116
- data.find((item) => String(item[idColumn]) === value) ?? null
142
+ data.find((item) => String(item[idColumn]) === initialId) ?? null
117
143
  );
118
144
  setLoaded(true);
119
145
  });
120
146
  }
121
- }, [columns, idColumn, loaded, sqlRequest, value]);
147
+ }, [columns, idColumn, initialId, loaded, sqlRequest]);
122
148
 
123
149
  const ItemTemplate = useMemo(
124
- () => itemTemplate ?? createItemTemplate<R>(columns[0], rowHeight),
150
+ () =>
151
+ createItemTemplate<T>(
152
+ itemTemplate ??
153
+ (({ item }) => <span>{item?.[columns[0]] ?? '(Aucun)'}</span>),
154
+ rowHeight
155
+ ),
125
156
  [columns, itemTemplate, rowHeight]
126
157
  );
127
158
  const ValueTemplate = useMemo(
128
- () => valueTemplate ?? createValueTemplate<R>(columns[0], rowHeight),
159
+ () =>
160
+ createValueTemplate<T>(
161
+ valueTemplate ??
162
+ (({ item }) => <span>{item?.[columns[0]] ?? '(Aucun)'}</span>),
163
+ rowHeight
164
+ ),
129
165
  [columns, rowHeight, valueTemplate]
130
166
  );
131
167
 
132
168
  return (
133
169
  <>
134
- <SqlRequestForeignListInput ref={inputRef} onClick={open}>
170
+ <SqlRequestForeignListInput
171
+ tabIndex={0}
172
+ ref={internalRef}
173
+ onClick={open}
174
+ onBlur={onBlur}
175
+ >
135
176
  <ValueTemplate item={selectedItem} index={0} />
136
177
  <ChevronDownIcon />
178
+ {opened && rect && (
179
+ <Dropdown
180
+ $height={[Math.min(maxRows, rows.length) * rowHeight, 600]}
181
+ $width={rect.width}
182
+ $sourceRect={rect}
183
+ onClose={close}
184
+ usePortal={false}
185
+ >
186
+ {rows.map((row, index) => (
187
+ <ItemTemplate
188
+ item={row}
189
+ onClick={() => onItemClick(row)}
190
+ key={index}
191
+ index={index}
192
+ />
193
+ ))}
194
+ </Dropdown>
195
+ )}
137
196
  </SqlRequestForeignListInput>
138
- {opened && rect && (
139
- <Dropdown
140
- $height={[Math.min(maxRows, rows.length) * rowHeight, 600]}
141
- $width={rect.width}
142
- $sourceRect={rect}
143
- onClose={close}
144
- >
145
- {rows.map((row, index) => (
146
- <ItemTemplate
147
- item={row}
148
- onClick={onItemClick}
149
- key={index}
150
- index={index}
151
- />
152
- ))}
153
- </Dropdown>
154
- )}
155
197
  </>
156
198
  );
157
199
  };
200
+
201
+ export const SqlRequestForeignList = forwardRef(SqlRequestForeignListInner);
@@ -0,0 +1,31 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ import { VirtualScrollerTemplateFC } from '../VirtualScroller/types';
4
+
5
+ export type SqlRequestForeignListProps<T extends Record<string, any>> = {
6
+ requestName: string;
7
+ idColumn: string;
8
+ columns: string[];
9
+ initialId?: string | null;
10
+ onIdChanged?: (value: string | null) => void;
11
+ onItemChanged?: (value: T | null) => void;
12
+ onClosed?: () => void;
13
+ onBlur?: () => void;
14
+ valueTemplate?: VirtualScrollerTemplateFC<
15
+ T,
16
+ SqlRequestForeignListItemProps<T>
17
+ >;
18
+ itemTemplate?: VirtualScrollerTemplateFC<
19
+ T,
20
+ SqlRequestForeignListItemProps<T>
21
+ >;
22
+ rowHeight?: number;
23
+ maxRows?: number;
24
+ };
25
+
26
+ export type SqlRequestForeignListItemProps<T extends Record<string, any>> = {
27
+ onClick?: (item: T | null) => void;
28
+ };
29
+
30
+ export type SqlRequestForeignListItemFC<T extends Record<string, any>> =
31
+ VirtualScrollerTemplateFC<T, SqlRequestForeignListItemProps<T>>;
@@ -1,4 +1,4 @@
1
- import { SelectHTMLAttributes, useCallback } from 'react';
1
+ import { SelectHTMLAttributes, forwardRef, useCallback } from 'react';
2
2
 
3
3
  import { InputContainerProps } from './types';
4
4
  import { InputWithLabel } from './InputWithLabel';
@@ -14,7 +14,7 @@ type SelectProps<T> = {
14
14
  };
15
15
 
16
16
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
- export const Select = <T,>({
17
+ const SelectInner = <T,>({
18
18
  icon,
19
19
  label,
20
20
  items,
@@ -63,3 +63,5 @@ export const Select = <T,>({
63
63
  </InputWithLabel>
64
64
  );
65
65
  };
66
+
67
+ export const Select = forwardRef(SelectInner);
@@ -18,6 +18,7 @@ type DropdownProps = PropsWithChildren<
18
18
  $autoPositionX?: boolean;
19
19
  $autoPositionY?: boolean;
20
20
  onClose?: () => void;
21
+ usePortal?: boolean;
21
22
  style?: CSSProperties;
22
23
  }
23
24
  >;
@@ -65,6 +66,7 @@ const getDropdownStyle = (dropdown: DropdownProps): CSSProperties => {
65
66
  export const Dropdown: FC<DropdownProps> = ({
66
67
  children,
67
68
  onClose,
69
+ usePortal = true,
68
70
  ...props
69
71
  }) => {
70
72
  const { createPortal } = usePortals();
@@ -98,15 +100,14 @@ export const Dropdown: FC<DropdownProps> = ({
98
100
  }
99
101
  }, [onDocumentClick]);
100
102
 
101
- const modalPortal = useMemo(
102
- () =>
103
- createPortal(
104
- <styles.DropdownContainer ref={containerRef} {...props} style={style}>
105
- {children}
106
- </styles.DropdownContainer>
107
- ),
108
- [children, createPortal, props, style]
109
- );
103
+ const modalPortal = useMemo(() => {
104
+ const content = (
105
+ <styles.DropdownContainer ref={containerRef} {...props} style={style}>
106
+ {children}
107
+ </styles.DropdownContainer>
108
+ );
109
+ return usePortal ? createPortal(content) : content;
110
+ }, [children, createPortal, props, style, usePortal]);
110
111
 
111
112
  return <>{modalPortal}</>;
112
113
  };