@linzjs/step-ag-grid 1.1.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.
Files changed (96) hide show
  1. package/README.md +4 -0
  2. package/dist/index.d.ts +32 -0
  3. package/dist/index.js +89342 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/components/ComponentLoadingWrapper.d.ts +9 -0
  6. package/dist/src/components/GenericCellClass.d.ts +2 -0
  7. package/dist/src/components/Grid.d.ts +24 -0
  8. package/dist/src/components/GridCell.d.ts +31 -0
  9. package/dist/src/components/GridIcon.d.ts +6 -0
  10. package/dist/src/components/GridLoadableCell.d.ts +6 -0
  11. package/dist/src/components/GridPopoutHook.d.ts +7 -0
  12. package/dist/src/components/GridSubComponentTextArea.d.ts +5 -0
  13. package/dist/src/components/PostSortRowsHook.d.ts +11 -0
  14. package/dist/src/components/gridForm/GridFormDropDown.d.ts +26 -0
  15. package/dist/src/components/gridForm/GridFormEditBearing.d.ts +9 -0
  16. package/dist/src/components/gridForm/GridFormMessage.d.ts +8 -0
  17. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +26 -0
  18. package/dist/src/components/gridForm/GridFormPopoutMenu.d.ts +24 -0
  19. package/dist/src/components/gridForm/GridFormTextArea.d.ts +11 -0
  20. package/dist/src/components/gridForm/GridFormTextInput.d.ts +11 -0
  21. package/dist/src/components/gridHeader/GridHeaderSelect.d.ts +8 -0
  22. package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.d.ts +4 -0
  23. package/dist/src/components/gridPopoverEdit/GridPopoutMenu.d.ts +10 -0
  24. package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +4 -0
  25. package/dist/src/components/gridPopoverEdit/GridPopoverEditDropDown.d.ts +4 -0
  26. package/dist/src/components/gridPopoverEdit/GridPopoverMessage.d.ts +4 -0
  27. package/dist/src/components/gridRender/GridRenderGenericCell.d.ts +15 -0
  28. package/dist/src/components/gridRender/GridRenderPopoutMenuCell.d.ts +3 -0
  29. package/dist/src/contexts/GridContext.d.ts +25 -0
  30. package/dist/src/contexts/GridContextProvider.d.ts +11 -0
  31. package/dist/src/contexts/UpdatingContext.d.ts +6 -0
  32. package/dist/src/contexts/UpdatingContextProvider.d.ts +7 -0
  33. package/dist/src/lui/TextAreaInput.d.ts +11 -0
  34. package/dist/src/lui/TextInputFormatted.d.ts +14 -0
  35. package/dist/src/setupTests.d.ts +1 -0
  36. package/dist/src/stories/components/FormTest.d.ts +12 -0
  37. package/dist/src/stories/components/GridPopoutBearing.stories.d.ts +9 -0
  38. package/dist/src/stories/components/GridPopoutEditDropDown.stories.d.ts +9 -0
  39. package/dist/src/stories/components/GridPopoutEditGeneric.stories.d.ts +9 -0
  40. package/dist/src/stories/components/GridPopoutEditGenericTextArea.stories.d.ts +9 -0
  41. package/dist/src/stories/components/GridPopoutEditMultiSelect.stories.d.ts +9 -0
  42. package/dist/src/stories/components/GridReadOnly.stories.d.ts +9 -0
  43. package/dist/src/utils/bearing.d.ts +5 -0
  44. package/dist/src/utils/util.d.ts +4 -0
  45. package/dist/step-ag-grid.esm.js +89297 -0
  46. package/dist/step-ag-grid.esm.js.map +1 -0
  47. package/package.json +126 -0
  48. package/src/components/ComponentLoadingWrapper.tsx +31 -0
  49. package/src/components/GenericCellClass.tsx +15 -0
  50. package/src/components/Grid.scss +38 -0
  51. package/src/components/Grid.tsx +262 -0
  52. package/src/components/GridCell.tsx +101 -0
  53. package/src/components/GridIcon.tsx +12 -0
  54. package/src/components/GridLoadableCell.tsx +22 -0
  55. package/src/components/GridPopoutHook.tsx +118 -0
  56. package/src/components/GridSubComponentTextArea.tsx +26 -0
  57. package/src/components/GridTheme.scss +94 -0
  58. package/src/components/PostSortRowsHook.ts +131 -0
  59. package/src/components/gridForm/GridFormDropDown.tsx +192 -0
  60. package/src/components/gridForm/GridFormEditBearing.scss +3 -0
  61. package/src/components/gridForm/GridFormEditBearing.tsx +62 -0
  62. package/src/components/gridForm/GridFormMessage.tsx +34 -0
  63. package/src/components/gridForm/GridFormMultiSelect.tsx +180 -0
  64. package/src/components/gridForm/GridFormPopoutMenu.tsx +90 -0
  65. package/src/components/gridForm/GridFormTextArea.tsx +61 -0
  66. package/src/components/gridForm/GridFormTextInput.tsx +66 -0
  67. package/src/components/gridHeader/GridHeaderSelect.tsx +54 -0
  68. package/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.ts +22 -0
  69. package/src/components/gridPopoverEdit/GridPopoutMenu.scss +9 -0
  70. package/src/components/gridPopoverEdit/GridPopoutMenu.tsx +35 -0
  71. package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +24 -0
  72. package/src/components/gridPopoverEdit/GridPopoverEditDropDown.ts +22 -0
  73. package/src/components/gridPopoverEdit/GridPopoverMessage.ts +22 -0
  74. package/src/components/gridRender/GridRenderGenericCell.scss +15 -0
  75. package/src/components/gridRender/GridRenderGenericCell.tsx +49 -0
  76. package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +22 -0
  77. package/src/contexts/GridContext.tsx +82 -0
  78. package/src/contexts/GridContextProvider.tsx +292 -0
  79. package/src/contexts/UpdatingContext.tsx +16 -0
  80. package/src/contexts/UpdatingContextProvider.tsx +43 -0
  81. package/src/lui/TextAreaInput.tsx +50 -0
  82. package/src/lui/TextInputFormatted.scss +11 -0
  83. package/src/lui/TextInputFormatted.tsx +60 -0
  84. package/src/lui-overrides.scss +163 -0
  85. package/src/react-app-env.d.ts +1 -0
  86. package/src/setupTests.ts +5 -0
  87. package/src/stories/components/FormTest.scss +10 -0
  88. package/src/stories/components/FormTest.tsx +51 -0
  89. package/src/stories/components/GridPopoutBearing.stories.tsx +100 -0
  90. package/src/stories/components/GridPopoutEditDropDown.stories.tsx +174 -0
  91. package/src/stories/components/GridPopoutEditGeneric.stories.tsx +76 -0
  92. package/src/stories/components/GridPopoutEditGenericTextArea.stories.tsx +106 -0
  93. package/src/stories/components/GridPopoutEditMultiSelect.stories.tsx +111 -0
  94. package/src/stories/components/GridReadOnly.stories.tsx +151 -0
  95. package/src/utils/bearing.ts +72 -0
  96. package/src/utils/util.ts +26 -0
@@ -0,0 +1,22 @@
1
+ import { ICellRendererParams } from "ag-grid-community";
2
+ import { useContext } from "react";
3
+ import { UpdatingContext } from "../../contexts/UpdatingContext";
4
+ import { GridLoadableCell } from "../GridLoadableCell";
5
+ import { LuiIcon } from "@linzjs/lui";
6
+
7
+ export const GridRenderPopoutMenuCell = (props: ICellRendererParams) => {
8
+ const { checkUpdating } = useContext(UpdatingContext);
9
+ const isLoading = checkUpdating(props.colDef?.field ?? "", props.data.id);
10
+ const disabled = !props.colDef?.editable;
11
+
12
+ return (
13
+ <GridLoadableCell isLoading={isLoading}>
14
+ <LuiIcon
15
+ name={"ic_more_vert"}
16
+ alt={"More actions"}
17
+ size={"md"}
18
+ className={disabled ? `GridPopoutMenu-burgerDisabled` : `GridPopoutMenu-burger`}
19
+ />
20
+ </GridLoadableCell>
21
+ );
22
+ };
@@ -0,0 +1,82 @@
1
+ import { createContext } from "react";
2
+ import { GridApi } from "ag-grid-community";
3
+ import { GridBaseRow } from "../components/Grid";
4
+
5
+ export interface GridContextType {
6
+ gridReady: () => boolean;
7
+ setGridApi: (gridApi: GridApi | undefined) => void;
8
+ setQuickFilter: (quickFilter: string) => void;
9
+ editingCells: () => boolean;
10
+ getSelectedRows: <T extends unknown>() => T[];
11
+ getSelectedRowIds: () => number[];
12
+ selectRowsDiff: (updateFn: () => Promise<any>) => Promise<void>;
13
+ selectRowsWithFlashDiff: (updateFn: () => Promise<any>) => Promise<void>;
14
+ selectRowsById: (rowIds?: number[]) => void;
15
+ selectRowsByIdWithFlash: (rowIds?: number[]) => void;
16
+ flashRows: (rowIds?: number[]) => void;
17
+ flashRowsDiff: (updateFn: () => Promise<any>) => Promise<void>;
18
+ ensureRowVisible: (id: number) => void;
19
+ ensureSelectedRowIsVisible: () => void;
20
+ stopEditing: () => void;
21
+ updatingCells: (
22
+ props: { selectedRows: GridBaseRow[]; field?: string },
23
+ fnUpdate: (selectedRows: any[]) => Promise<boolean>,
24
+ setSaving?: (saving: boolean) => void,
25
+ ) => Promise<boolean>;
26
+ }
27
+
28
+ export const GridContext = createContext<GridContextType>({
29
+ gridReady: () => {
30
+ console.error("no context provider for gridReady");
31
+ return false;
32
+ },
33
+ setGridApi: () => {
34
+ console.error("no context provider for setGridApi");
35
+ },
36
+ setQuickFilter: () => {
37
+ console.error("no context provider for setQuickFilter");
38
+ },
39
+ selectRowsById: () => {
40
+ console.error("no context provider for selectRows");
41
+ },
42
+ getSelectedRows: <T extends unknown>(): T[] => {
43
+ console.error("no context provider for getSelectedRows");
44
+ return [];
45
+ },
46
+ getSelectedRowIds: () => {
47
+ console.error("no context provider for getSelectedRowIds");
48
+ return [];
49
+ },
50
+ selectRowsDiff: async () => {
51
+ console.error("no context provider for selectRowsDiff");
52
+ },
53
+ selectRowsByIdWithFlash: () => {
54
+ console.error("no context provider for selectRowsWithFlash");
55
+ },
56
+ selectRowsWithFlashDiff: async () => {
57
+ console.error("no context provider for selectRowsWithFlashDiff");
58
+ },
59
+ flashRows: () => {
60
+ console.error("no context provider for flashRows");
61
+ },
62
+ flashRowsDiff: async () => {
63
+ console.error("no context provider for flashRows");
64
+ },
65
+ ensureRowVisible: () => {
66
+ console.error("no context provider for ensureRowVisible");
67
+ },
68
+ ensureSelectedRowIsVisible: () => {
69
+ console.error("no context provider for ensureSelectedRowIsVisible");
70
+ },
71
+ editingCells: () => {
72
+ console.error("no context provider for editingCells");
73
+ return false;
74
+ },
75
+ stopEditing: () => {
76
+ console.error("no context provider for stopEditing");
77
+ },
78
+ updatingCells: async () => {
79
+ console.error("no context provider for modifyUpdating");
80
+ return false;
81
+ },
82
+ });
@@ -0,0 +1,292 @@
1
+ import { ReactElement, ReactNode, useContext, useRef } from "react";
2
+ import { GridApi, RowNode } from "ag-grid-community";
3
+ import { GridContext } from "./GridContext";
4
+ import { delay, difference, isEmpty, last, sortBy } from "lodash-es";
5
+ import { isNotEmpty } from "../utils/util";
6
+ import { UpdatingContext } from "./UpdatingContext";
7
+ import { GridBaseRow } from "../components/Grid";
8
+
9
+ interface GridContextProps {
10
+ children: ReactNode;
11
+ }
12
+
13
+ /**
14
+ * Context for AgGrid operations.
15
+ * Make sure you wrap AgGrid in this.
16
+ * Also, make sure the provider is created in a separate component, otherwise it won't be found.
17
+ */
18
+ export const GridContextProvider = (props: GridContextProps): ReactElement => {
19
+ const { modifyUpdating } = useContext(UpdatingContext);
20
+ const gridApiRef = useRef<GridApi>();
21
+ const idsBeforeUpdate = useRef<number[]>([]);
22
+
23
+ /**
24
+ * Has Grid api has been set?
25
+ * Some ops in components may occur before onGridReady has been called,
26
+ * and thus will need to check grid is ready before calling.
27
+ */
28
+ const gridReady = () => gridApiRef.current != null;
29
+
30
+ /**
31
+ * Set current ref to grid api.
32
+ */
33
+ const setGridApi = (_gridApi: GridApi | undefined) => {
34
+ gridApiRef.current = _gridApi;
35
+ };
36
+
37
+ /**
38
+ * Wraps things that require gridApi in common handling, for when gridApi not present.
39
+ *
40
+ * @param hasApiFn Execute when api is ready.
41
+ * @param noApiFn Execute if api is not ready.
42
+ */
43
+ const gridApiOp = <T extends unknown, R extends unknown>(
44
+ hasApiFn: (gridApi: GridApi) => T,
45
+ noApiFn?: () => R,
46
+ ): T | R => {
47
+ if (!noApiFn) {
48
+ noApiFn = (() => {}) as () => R;
49
+ }
50
+ const gridApi = gridApiRef.current;
51
+ if (gridApi) return hasApiFn(gridApi);
52
+ console.error("GridApi not ready");
53
+ return noApiFn();
54
+ };
55
+
56
+ /**
57
+ * Set the quick filter value to grid.
58
+ */
59
+ const setQuickFilter = (quickFilter: string) => {
60
+ gridApiOp((gridApi) => gridApi.setQuickFilter(quickFilter));
61
+ };
62
+
63
+ /**
64
+ * Get all row id's in grid.
65
+ */
66
+ const _getAllRowIds = () => {
67
+ const result: number[] = [];
68
+ return gridApiOp(
69
+ (gridApi) => {
70
+ gridApi.forEachNode((node) => result.push(node.data.id));
71
+ return result;
72
+ },
73
+ () => result,
74
+ );
75
+ };
76
+
77
+ /**
78
+ * Record all row id's before update so that we can select/flash the new rows after update.
79
+ */
80
+ const beforeUpdate = () => {
81
+ idsBeforeUpdate.current = _getAllRowIds();
82
+ };
83
+
84
+ /**
85
+ * Find new row ids
86
+ * Uses beforeUpdate ids to find new nodes.
87
+ */
88
+ const _getNewNodes = (): RowNode[] => {
89
+ return gridApiOp(
90
+ (gridApi) =>
91
+ difference(_getAllRowIds(), idsBeforeUpdate.current)
92
+ .map((rowId) => gridApi.getRowNode("" + rowId)) //
93
+ .filter((r) => r) as RowNode[],
94
+ () => [],
95
+ );
96
+ };
97
+
98
+ /**
99
+ * Get grid nodes via rowIds. If rowIds has no matching node the result may be smaller than expected.
100
+ * This can happen if the grid has not re-rendered yet.
101
+ *
102
+ * @param rowIds Row ids to get from grid.
103
+ */
104
+ const _rowIdsToNodes = (rowIds: number[]): RowNode[] => {
105
+ return gridApiOp(
106
+ (gridApi) =>
107
+ rowIds
108
+ .map((rowId) => gridApi.getRowNode("" + rowId)) //
109
+ .filter((r) => r) as RowNode[],
110
+ () => [] as RowNode[],
111
+ );
112
+ };
113
+
114
+ /**
115
+ * Internal method for selecting and flashing rows.
116
+ *
117
+ * @param rowIds RowIds to select and flash. If undefined a diff is done with beforeUpdate and current state to select
118
+ * @param select Whether to select rows
119
+ * @param flash Whether to flash rows
120
+ * @param retryCount Table updates may not be present when this is called, so retry is needed.
121
+ */
122
+ const _selectRowsWithOptionalFlash = (
123
+ rowIds: number[] | undefined,
124
+ select: boolean,
125
+ flash: boolean,
126
+ retryCount = 5, // We retry for approximately 5x200ms=1s
127
+ ) => {
128
+ return gridApiOp((gridApi) => {
129
+ const rowNodes = rowIds ? _rowIdsToNodes(rowIds) : _getNewNodes();
130
+ const gridRowIdsNotUpdatedYet = rowIds && rowNodes.length !== rowIds.length; // rowIds are specified
131
+ const gridRowIdsNotChangedYet = !rowIds && isEmpty(rowNodes); // rowIds are from beforeUpdate
132
+ const gridHasNotUpdated = gridRowIdsNotUpdatedYet || gridRowIdsNotChangedYet;
133
+ // After retry count expires we give-up and deselect all rows, then select any subset of rows that have updated
134
+ if (gridHasNotUpdated && retryCount > 0) {
135
+ delay(() => _selectRowsWithOptionalFlash(rowIds, select, flash, retryCount - 1), 250);
136
+ return;
137
+ }
138
+
139
+ const rowsThatNeedSelecting = sortBy(
140
+ rowNodes.filter((node) => !node.isSelected()),
141
+ (node) => node.data.id,
142
+ );
143
+ const firstNode = rowsThatNeedSelecting[0];
144
+ firstNode && gridApi.ensureNodeVisible(firstNode);
145
+ if (select) {
146
+ // Select rows that shouldn't be selected
147
+ rowsThatNeedSelecting.forEach((node) => node.setSelected(true));
148
+ // Unselect rows that shouldn't be selected
149
+ gridApi.getSelectedNodes()?.forEach((node) => {
150
+ if (node && !rowNodes.includes(node)) {
151
+ node.setSelected(false);
152
+ }
153
+ });
154
+ }
155
+ if (flash) {
156
+ delay(() => {
157
+ try {
158
+ gridApi.flashCells({ rowNodes });
159
+ } catch {
160
+ // ignore, flash cells sometimes throws errors as nodes have gone out of scope
161
+ }
162
+ }, 250);
163
+ }
164
+ });
165
+ };
166
+
167
+ const selectRowsById = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, false);
168
+ const selectRowsByIdWithFlash = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, true);
169
+ const flashRows = (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, false, true);
170
+
171
+ const selectRowsDiff = async (fn: () => Promise<any>) => {
172
+ beforeUpdate();
173
+ await fn();
174
+ _selectRowsWithOptionalFlash(undefined, true, false);
175
+ };
176
+
177
+ const selectRowsWithFlashDiff = async (fn: () => Promise<any>) => {
178
+ beforeUpdate();
179
+ await fn();
180
+ _selectRowsWithOptionalFlash(undefined, true, true);
181
+ };
182
+
183
+ const flashRowsDiff = async (fn: () => Promise<any>) => {
184
+ beforeUpdate();
185
+ await fn();
186
+ _selectRowsWithOptionalFlash(undefined, false, true);
187
+ };
188
+
189
+ const getSelectedRows = <T extends unknown>(): T[] => {
190
+ return gridApiOp(
191
+ (gridApi) => gridApi.getSelectedRows(),
192
+ () => [],
193
+ );
194
+ };
195
+
196
+ const getSelectedRowIds = (): number[] => getSelectedRows().map((row) => (row as any).id as number);
197
+
198
+ const editingCells = (): boolean => {
199
+ return gridApiOp(
200
+ (gridApi) => isNotEmpty(gridApi.getEditingCells()),
201
+ () => false,
202
+ );
203
+ };
204
+
205
+ const ensureRowVisible = (id: number): void => {
206
+ gridApiOp((gridApi) => {
207
+ const node = gridApi.getRowNode(`${id}`);
208
+ node && gridApi.ensureNodeVisible(node);
209
+ });
210
+ };
211
+
212
+ /**
213
+ * Scroll last selected row into view on grid sort change
214
+ */
215
+ const ensureSelectedRowIsVisible = (): void => {
216
+ gridApiOp((gridApi) => {
217
+ const selectedNodes = gridApi.getSelectedNodes();
218
+ if (isEmpty(selectedNodes)) return;
219
+ gridApi.ensureNodeVisible(last(selectedNodes));
220
+ });
221
+ };
222
+
223
+ const stopEditing = (): void => gridApiOp((gridApi) => gridApi.stopEditing());
224
+
225
+ const updatingCells = async (
226
+ props: { selectedRows: GridBaseRow[]; field?: string },
227
+ fnUpdate: (selectedRows: any[]) => Promise<boolean>,
228
+ setSaving?: (saving: boolean) => void,
229
+ ): Promise<boolean> => {
230
+ setSaving && setSaving(true);
231
+
232
+ return await gridApiOp(async (gridApi) => {
233
+ const selectedRows = props.selectedRows;
234
+ //TODO do I need to do this for all rows?
235
+ /*if (!props.multiEdit) {
236
+ // You can't use data as it could be an orphaned reference due to updates
237
+ selectedRows = selectedRows.filter((row) => row.id === props.data.id);
238
+ }
239
+ */
240
+ let ok = false;
241
+ await modifyUpdating(
242
+ props.field ?? "",
243
+ selectedRows.map((data) => data.id),
244
+ async () => {
245
+ // Need to refresh to get spinners to work on all rows
246
+ gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
247
+ ok = await fnUpdate(selectedRows);
248
+ },
249
+ );
250
+
251
+ // async processes need to refresh their own rows
252
+ gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
253
+
254
+ if (ok) {
255
+ const cell = gridApi.getFocusedCell();
256
+ if (cell && gridApi.getFocusedCell() == null) {
257
+ gridApi.setFocusedCell(cell.rowIndex, cell.column);
258
+ }
259
+ // This is needed to trigger postSortRowsHook
260
+ gridApi.refreshClientSideRowModel();
261
+ stopEditing();
262
+ }
263
+ setSaving && setSaving(false);
264
+ return ok;
265
+ });
266
+ };
267
+
268
+ return (
269
+ <GridContext.Provider
270
+ value={{
271
+ gridReady,
272
+ setGridApi,
273
+ setQuickFilter,
274
+ selectRowsById,
275
+ selectRowsDiff,
276
+ selectRowsByIdWithFlash,
277
+ selectRowsWithFlashDiff,
278
+ flashRows,
279
+ flashRowsDiff,
280
+ getSelectedRows,
281
+ getSelectedRowIds,
282
+ editingCells,
283
+ ensureRowVisible,
284
+ ensureSelectedRowIsVisible,
285
+ stopEditing,
286
+ updatingCells,
287
+ }}
288
+ >
289
+ {props.children}
290
+ </GridContext.Provider>
291
+ );
292
+ };
@@ -0,0 +1,16 @@
1
+ import { createContext } from "react";
2
+
3
+ export type UpdatingContextType = {
4
+ checkUpdating: (fields: string | string[], id: number | string) => boolean;
5
+ modifyUpdating: (field: string, ids: (number | string)[], fn: () => void | Promise<void>) => Promise<void>;
6
+ };
7
+
8
+ export const UpdatingContext = createContext<UpdatingContextType>({
9
+ checkUpdating: () => {
10
+ console.error("Missing UpdatingContext");
11
+ return false;
12
+ },
13
+ modifyUpdating: async () => {
14
+ console.error("Missing UpdatingContext");
15
+ },
16
+ });
@@ -0,0 +1,43 @@
1
+ import { ReactNode, useRef } from "react";
2
+ import { castArray, flatten, remove } from "lodash-es";
3
+ import { UpdatingContext } from "./UpdatingContext";
4
+
5
+ interface UpdatingContextProviderProps {
6
+ children: ReactNode;
7
+ }
8
+
9
+ export type UpdatingContextStatus = Record<string, (number | string)[] | undefined>;
10
+
11
+ type FieldName = string;
12
+ type IdList = (number | string)[];
13
+ type UpdatingBlock = Record<FieldName, IdList[]>;
14
+
15
+ export const UpdatingContextProvider = (props: UpdatingContextProviderProps) => {
16
+ const updatingBlocks = useRef<UpdatingBlock>({});
17
+ const updating = useRef<UpdatingContextStatus>({});
18
+
19
+ const resetUpdating = () => {
20
+ const mergedUpdatingBlocks: UpdatingContextStatus = {};
21
+ for (const key in updatingBlocks.current) {
22
+ mergedUpdatingBlocks[key] = flatten(updatingBlocks.current[key]);
23
+ }
24
+ updating.current = mergedUpdatingBlocks;
25
+ };
26
+
27
+ const modifyUpdating = async (field: string, ids: (number | string)[], fn: () => void | Promise<void>) => {
28
+ const fieldUpdatingIds = updatingBlocks.current[field] ?? (updatingBlocks.current[field] = []);
29
+ const idRef = [...ids];
30
+ fieldUpdatingIds.push(idRef);
31
+ resetUpdating();
32
+ await fn();
33
+ remove(fieldUpdatingIds, (idList) => idList === idRef);
34
+ resetUpdating();
35
+ };
36
+
37
+ const checkUpdating = (fields: string | string[], id: number | string): boolean =>
38
+ castArray(fields).some((f) => updating.current[f]?.includes(id));
39
+
40
+ return (
41
+ <UpdatingContext.Provider value={{ modifyUpdating, checkUpdating }}>{props.children}</UpdatingContext.Provider>
42
+ );
43
+ };
@@ -0,0 +1,50 @@
1
+ import { ChangeEventHandler, InputHTMLAttributes, useState } from "react";
2
+ import clsx from "clsx";
3
+ import { LuiIcon } from "@linzjs/lui";
4
+ import { v4 as uuidv4 } from "uuid";
5
+
6
+ export const useGenerateOrDefaultId = (idFromProps?: string) => {
7
+ const [id] = useState(idFromProps ? idFromProps : uuidv4());
8
+ return id;
9
+ };
10
+
11
+ export interface LuiTextAreaInputProps {
12
+ label?: JSX.Element | string;
13
+ mandatory?: boolean;
14
+ inputProps?: InputHTMLAttributes<HTMLTextAreaElement>;
15
+ onChange?: ChangeEventHandler<HTMLTextAreaElement>;
16
+ value: string;
17
+ error?: string | boolean | null;
18
+ }
19
+
20
+ export const TextAreaInput = (props: LuiTextAreaInputProps) => {
21
+ const id = useGenerateOrDefaultId(props.inputProps?.id);
22
+
23
+ return (
24
+ <div
25
+ className={clsx(
26
+ "LuiTextAreaInput Grid-popoverContainer",
27
+ props.inputProps?.disabled ? "isDisabled" : "",
28
+ props?.error ? "hasError" : "",
29
+ )}
30
+ >
31
+ <label htmlFor={id}>
32
+ {props.mandatory && <span className="LuiTextAreaInput-mandatory">*</span>}
33
+ {props.label && <span className="LuiTextAreaInput-label">{props.label}</span>}
34
+ <div className="LuiTextAreaInput-wrapper">
35
+ {" "}
36
+ {/* wrapper div used for error styling */}
37
+ <textarea id={id} value={props.value} onChange={props.onChange} rows={5} {...props.inputProps} />
38
+ </div>
39
+ </label>
40
+
41
+ {/* Error message */}
42
+ {props.error && (
43
+ <span className="LuiTextAreaInput-error">
44
+ <LuiIcon alt="error" name="ic_error" className="LuiTextAreaInput-error-icon" size="sm" status="error" />
45
+ {props.error}
46
+ </span>
47
+ )}
48
+ </div>
49
+ );
50
+ };
@@ -0,0 +1,11 @@
1
+ .LuiTextInput {
2
+ margin-bottom: 0px;
3
+ }
4
+
5
+ .LuiTextInput-formatted {
6
+ position: absolute;
7
+ right: 14px;
8
+ top: 0;
9
+ line-height: 48px;
10
+ color: #beb9b4;
11
+ }
@@ -0,0 +1,60 @@
1
+ import "./TextInputFormatted.scss";
2
+
3
+ import { ChangeEventHandler, DetailedHTMLProps, InputHTMLAttributes } from "react";
4
+
5
+ import clsx from "clsx";
6
+ import { LuiIcon } from "@linzjs/lui";
7
+
8
+ export interface LuiTextInputProps {
9
+ onChange?: ChangeEventHandler<HTMLInputElement>;
10
+ inputProps?: DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>;
11
+ error?: string | boolean | null;
12
+ warning?: string | boolean | null;
13
+
14
+ className?: string;
15
+ value: string;
16
+
17
+ icon?: JSX.Element;
18
+ placeholder?: string;
19
+ formatted: string;
20
+ }
21
+
22
+ export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
23
+ return (
24
+ <div
25
+ className={clsx(
26
+ "LuiTextInput Grid-popoverContainer",
27
+ props.error && "hasError",
28
+ props.warning && "hasWarning",
29
+ props.className,
30
+ )}
31
+ >
32
+ <span className="LuiTextInput-inputWrapper">
33
+ <input
34
+ type={"text"}
35
+ className={"LuiTextInput-input"}
36
+ min="0"
37
+ value={props.value}
38
+ onChange={props.onChange}
39
+ {...props.inputProps}
40
+ />
41
+ <span className={"LuiTextInput-formatted"}>{props.formatted}</span>
42
+ {props.icon}
43
+ </span>
44
+
45
+ {props.error && (
46
+ <span className="LuiTextInput-error">
47
+ <LuiIcon alt="error" name="ic_error" className="LuiTextInput-error-icon" size="sm" status="error" />
48
+ {props.error}
49
+ </span>
50
+ )}
51
+
52
+ {props.warning && (
53
+ <span className="LuiTextInput-warning">
54
+ <LuiIcon alt="warning" name="ic_warning" className="LuiTextInput-warning-icon" size="sm" status="warning" />
55
+ {props.warning}
56
+ </span>
57
+ )}
58
+ </div>
59
+ );
60
+ };