@patternfly/react-table 6.0.0-alpha.96 → 6.0.0-alpha.97

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": "@patternfly/react-table",
3
- "version": "6.0.0-alpha.96",
3
+ "version": "6.0.0-alpha.97",
4
4
  "description": "This library provides a set of React table components for use with the PatternFly 4",
5
5
  "main": "dist/js/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -40,10 +40,10 @@
40
40
  "subpaths": "node ../../scripts/exportSubpaths.js --config subpaths.config.json"
41
41
  },
42
42
  "dependencies": {
43
- "@patternfly/react-core": "^6.0.0-alpha.95",
44
- "@patternfly/react-icons": "^6.0.0-alpha.34",
45
- "@patternfly/react-styles": "^6.0.0-alpha.33",
46
- "@patternfly/react-tokens": "^6.0.0-alpha.33",
43
+ "@patternfly/react-core": "^6.0.0-alpha.96",
44
+ "@patternfly/react-icons": "^6.0.0-alpha.35",
45
+ "@patternfly/react-styles": "^6.0.0-alpha.34",
46
+ "@patternfly/react-tokens": "^6.0.0-alpha.34",
47
47
  "lodash": "^4.17.21",
48
48
  "tslib": "^2.6.3"
49
49
  },
@@ -51,5 +51,5 @@
51
51
  "react": "^17 || ^18",
52
52
  "react-dom": "^17 || ^18"
53
53
  },
54
- "gitHead": "431a35ea6f91a416710e1a8584792c0098b057e7"
54
+ "gitHead": "19631bb68902f7cd7b0c3c20d08088b94eab321d"
55
55
  }
@@ -81,7 +81,6 @@ export type OnCheckChange = (
81
81
  // Todo: Update type with next breaking change release
82
82
  // export type IHeaderRow = ColumnType;
83
83
 
84
- // eslint-disable-next-line @typescript-eslint/no-empty-interface
85
84
  export interface IHeaderRow extends ColumnType {}
86
85
 
87
86
  export interface IRowData extends IRow {
@@ -51,11 +51,15 @@ import FolderOpenIcon from '@patternfly/react-icons/dist/esm/icons/folder-open-i
51
51
  import SortAmountDownIcon from '@patternfly/react-icons/dist/esm/icons/sort-amount-down-icon';
52
52
  import BlueprintIcon from '@patternfly/react-icons/dist/esm/icons/blueprint-icon';
53
53
  import EllipsisVIcon from '@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon';
54
+ import PencilAltIcon from '@patternfly/react-icons/dist/esm/icons/pencil-alt-icon';
55
+ import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon';
56
+ import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
54
57
 
55
58
  import { css } from '@patternfly/react-styles';
56
59
  import styles from '@patternfly/react-styles/css/components/Table/table';
57
60
  import spacing from '@patternfly/react-styles/css/utilities/Spacing/spacing';
58
61
  import textStyles from '@patternfly/react-styles/css/utilities/Text/text';
62
+ import inlineEditStyles from '@patternfly/react-styles/css/components/InlineEdit/inline-edit';
59
63
  import global_background_color_secondary_default from '@patternfly/react-tokens/dist/esm/global_background_color_secondary_default';
60
64
 
61
65
  ## Table examples
@@ -163,6 +167,13 @@ This selectable rows feature is intended for use when a table is used to present
163
167
 
164
168
  ```
165
169
 
170
+ ### Editable rows
171
+
172
+ This example shows a table with editable rows. Cells in a row can be edited after clicking on the edit icon.
173
+
174
+ ```ts file="TableEditable.tsx"
175
+ ```
176
+
166
177
  ### Actions
167
178
 
168
179
  This example demonstrates adding actions as the last column. The header's last cell is an empty cell, and each body row's last cell is an action cell.
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  import React from 'react';
3
2
  import {
4
3
  Button,
@@ -0,0 +1,313 @@
1
+ import React from 'react';
2
+ import { Table, Thead, Tr, Th, Tbody, Td } from '@patternfly/react-table';
3
+ import { Button, Checkbox, Radio, TextInput, KeyTypes, getUniqueId } from '@patternfly/react-core';
4
+ import PencilAltIcon from '@patternfly/react-icons/dist/esm/icons/pencil-alt-icon';
5
+ import CheckIcon from '@patternfly/react-icons/dist/esm/icons/check-icon';
6
+ import TimesIcon from '@patternfly/react-icons/dist/esm/icons/times-icon';
7
+ import inlineEditStyles from '@patternfly/react-styles/css/components/InlineEdit/inline-edit';
8
+ import { css } from '@patternfly/react-styles';
9
+
10
+ interface EditButtonsCellProps {
11
+ onClick: (type: 'save' | 'cancel' | 'edit') => void;
12
+ elementToFocusOnEditRef?: React.MutableRefObject<HTMLElement>;
13
+ rowAriaLabel: string;
14
+ }
15
+
16
+ const EditButtonsCell: React.FunctionComponent<EditButtonsCellProps> = ({
17
+ onClick,
18
+ elementToFocusOnEditRef,
19
+ rowAriaLabel = 'row'
20
+ }) => {
21
+ const editButtonRef = React.useRef<HTMLButtonElement>();
22
+
23
+ const onKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>, button: 'edit' | 'stopEditing') => {
24
+ const focusRef = button === 'edit' ? elementToFocusOnEditRef : editButtonRef;
25
+
26
+ if (event.key === KeyTypes.Enter || event.key === KeyTypes.Space) {
27
+ // because space key triggers click event before keyDown, we have to prevent default behaviour and trigger click manually
28
+ event.preventDefault();
29
+ (event.target as HTMLButtonElement).click();
30
+ setTimeout(() => {
31
+ focusRef?.current?.focus();
32
+ }, 0);
33
+ }
34
+ };
35
+
36
+ return (
37
+ <>
38
+ <Td
39
+ dataLabel="Save and cancel buttons"
40
+ className={css(
41
+ inlineEditStyles.inlineEditGroup,
42
+ inlineEditStyles.modifiers.iconGroup,
43
+ inlineEditStyles.modifiers.actionGroup
44
+ )}
45
+ >
46
+ <div className={css(inlineEditStyles.inlineEditAction, inlineEditStyles.modifiers.valid)}>
47
+ <Button
48
+ aria-label={`Save edits of ${rowAriaLabel}`}
49
+ onClick={() => onClick('save')}
50
+ onKeyDown={(event) => onKeyDown(event, 'stopEditing')}
51
+ variant="plain"
52
+ >
53
+ <CheckIcon />
54
+ </Button>
55
+ </div>
56
+ <div className={css(inlineEditStyles.inlineEditAction)}>
57
+ <Button
58
+ aria-label={`Discard edits of ${rowAriaLabel}`}
59
+ onClick={() => onClick('cancel')}
60
+ onKeyDown={(event) => onKeyDown(event, 'stopEditing')}
61
+ variant="plain"
62
+ >
63
+ <TimesIcon />
64
+ </Button>
65
+ </div>
66
+ </Td>
67
+ <Td
68
+ dataLabel="Edit button"
69
+ className={css(inlineEditStyles.inlineEditAction, inlineEditStyles.modifiers.enableEditable)}
70
+ >
71
+ <Button
72
+ ref={editButtonRef}
73
+ aria-label={`Edit ${rowAriaLabel}`}
74
+ onClick={() => onClick('edit')}
75
+ onKeyDown={(event) => onKeyDown(event, 'edit')}
76
+ variant="plain"
77
+ >
78
+ <PencilAltIcon />
79
+ </Button>
80
+ </Td>
81
+ </>
82
+ );
83
+ };
84
+
85
+ interface EditableCellProps {
86
+ dataLabel: string;
87
+ staticValue: React.ReactNode;
88
+ editingValue: React.ReactNode;
89
+ role?: string;
90
+ ariaLabel?: string;
91
+ }
92
+
93
+ const EditableCell: React.FunctionComponent<EditableCellProps> = ({
94
+ dataLabel,
95
+ staticValue,
96
+ editingValue,
97
+ role,
98
+ ariaLabel
99
+ }) => {
100
+ const hasMultipleInputs = Array.isArray(editingValue) && editingValue.every((elem) => React.isValidElement(elem));
101
+
102
+ return (
103
+ <Td dataLabel={dataLabel}>
104
+ <div className={css(inlineEditStyles.inlineEditValue)}>{staticValue}</div>
105
+ {hasMultipleInputs ? (
106
+ <div className={css(inlineEditStyles.inlineEditGroup, 'pf-m-column')} role={role} aria-label={ariaLabel}>
107
+ {(editingValue as React.ReactElement[]).map((elem, index) => (
108
+ <div key={index} className={css(inlineEditStyles.inlineEditInput)}>
109
+ {elem}
110
+ </div>
111
+ ))}
112
+ </div>
113
+ ) : (
114
+ <div className={css(inlineEditStyles.inlineEditInput)}>{editingValue}</div>
115
+ )}
116
+ </Td>
117
+ );
118
+ };
119
+
120
+ interface EditableRow {
121
+ data: CustomData;
122
+ columnNames: ColumnNames<CustomData>;
123
+ dataOptions?: CustomDataOptions;
124
+ saveChanges: (editedData: CustomData) => void;
125
+ ariaLabel: string;
126
+ rowIndex?: number;
127
+ }
128
+
129
+ const EditableRow: React.FunctionComponent<EditableRow> = ({
130
+ data,
131
+ columnNames,
132
+ dataOptions,
133
+ saveChanges,
134
+ ariaLabel,
135
+ rowIndex
136
+ }) => {
137
+ const [editable, setEditable] = React.useState(false);
138
+ const [editedData, setEditedData] = React.useState(data);
139
+
140
+ const inputRef = React.useRef();
141
+
142
+ return (
143
+ <Tr className={css(inlineEditStyles.inlineEdit, editable ? inlineEditStyles.modifiers.inlineEditable : '')}>
144
+ <EditableCell
145
+ dataLabel={columnNames.textInput}
146
+ staticValue={data.textInput}
147
+ editingValue={
148
+ <TextInput
149
+ aria-label={`${columnNames.textInput} ${ariaLabel}`}
150
+ ref={inputRef}
151
+ value={editedData.textInput}
152
+ onChange={(e) => setEditedData((data) => ({ ...data, textInput: (e.target as HTMLInputElement).value }))}
153
+ />
154
+ }
155
+ />
156
+ <EditableCell
157
+ dataLabel={columnNames.textInputDisabled}
158
+ staticValue={data.textInputDisabled}
159
+ editingValue={
160
+ <TextInput
161
+ aria-label={`${columnNames.textInputDisabled} ${ariaLabel}`}
162
+ isDisabled={true}
163
+ value={editedData.textInputDisabled ?? ''}
164
+ />
165
+ }
166
+ />
167
+ <EditableCell
168
+ dataLabel={columnNames.checkboxes}
169
+ staticValue={data.checkboxes.join(', ')}
170
+ role="group"
171
+ ariaLabel={`Checkbox group row ${rowIndex}`}
172
+ editingValue={dataOptions?.checkboxes.map((option) => {
173
+ const id = getUniqueId('checkbox');
174
+ return (
175
+ <Checkbox
176
+ key={id}
177
+ name={id}
178
+ id={id}
179
+ label={option}
180
+ isChecked={editedData.checkboxes.includes(option)}
181
+ onChange={(_e, checked) =>
182
+ setEditedData((data) => ({
183
+ ...data,
184
+ checkboxes: checked ? [...data.checkboxes, option] : data.checkboxes.filter((item) => item !== option)
185
+ }))
186
+ }
187
+ />
188
+ );
189
+ })}
190
+ />
191
+ <EditableCell
192
+ dataLabel={columnNames.radios}
193
+ staticValue={data.radios}
194
+ ariaLabel={`Radio button group row ${rowIndex}`}
195
+ role="radiogroup"
196
+ editingValue={dataOptions?.radios.map((option) => {
197
+ const id = getUniqueId('radio');
198
+ return (
199
+ <Radio
200
+ key={id}
201
+ name={id}
202
+ id={id}
203
+ label={option}
204
+ isChecked={editedData.radios === option}
205
+ onChange={() => setEditedData((data) => ({ ...data, radios: option }))}
206
+ />
207
+ );
208
+ })}
209
+ />
210
+ <EditButtonsCell
211
+ onClick={(type) => {
212
+ type === 'edit' ? setEditable(true) : setEditable(false);
213
+ type === 'save' && saveChanges(editedData);
214
+ type === 'cancel' && setEditedData(data);
215
+ }}
216
+ rowAriaLabel={ariaLabel}
217
+ elementToFocusOnEditRef={inputRef}
218
+ />
219
+ </Tr>
220
+ );
221
+ };
222
+
223
+ interface CustomData {
224
+ textInput: string;
225
+ textInputDisabled: string | null;
226
+ checkboxes: string[];
227
+ radios: string;
228
+ }
229
+
230
+ interface CustomDataOptions {
231
+ checkboxes: string[];
232
+ radios: string[];
233
+ }
234
+
235
+ type ColumnNames<T> = { [K in keyof T]: string };
236
+
237
+ export const TableEditable: React.FunctionComponent = () => {
238
+ // In real usage, this data would come from some external source like an API via props.
239
+ const initialRows: CustomData[] = [
240
+ {
241
+ textInput: 'Editable text 1',
242
+ textInputDisabled: 'Non-editable text 1',
243
+ checkboxes: ['Option A'],
244
+ radios: 'Option A'
245
+ },
246
+ {
247
+ textInput: 'Editable text 2',
248
+ textInputDisabled: null,
249
+ checkboxes: [],
250
+ radios: 'Option B'
251
+ },
252
+ {
253
+ textInput: 'Editable text 3',
254
+ textInputDisabled: 'Non-editable text 3',
255
+ checkboxes: ['Option A', 'Option B'],
256
+ radios: 'Option A'
257
+ }
258
+ ];
259
+
260
+ // List of all selectable options for some cells of initialRows
261
+ const initialRowsOptions: CustomDataOptions[] = [
262
+ {
263
+ checkboxes: ['Option A', 'Option B', 'Option C'],
264
+ radios: ['Option A', 'Option B']
265
+ },
266
+ {
267
+ checkboxes: ['Option A', 'Option B'],
268
+ radios: ['Option A', 'Option B', 'Option C']
269
+ },
270
+ {
271
+ checkboxes: ['Option A', 'Option B'],
272
+ radios: ['Option A', 'Option B']
273
+ }
274
+ ];
275
+
276
+ const [rows, setRows] = React.useState(initialRows);
277
+
278
+ const columnNames: ColumnNames<CustomData> = {
279
+ textInput: 'Text input',
280
+ textInputDisabled: 'Disabled text input',
281
+ checkboxes: 'Checkboxes',
282
+ radios: 'Radios'
283
+ };
284
+
285
+ return (
286
+ <Table aria-label="Editable table">
287
+ <Thead>
288
+ <Tr>
289
+ <Th>{columnNames.textInput}</Th>
290
+ <Th>{columnNames.textInputDisabled}</Th>
291
+ <Th>{columnNames.checkboxes}</Th>
292
+ <Th>{columnNames.radios}</Th>
293
+ <Th screenReaderText="Row edit actions" />
294
+ </Tr>
295
+ </Thead>
296
+ <Tbody>
297
+ {rows.map((data, index) => (
298
+ <EditableRow
299
+ key={index}
300
+ data={data}
301
+ rowIndex={index}
302
+ dataOptions={initialRowsOptions[index]}
303
+ columnNames={columnNames}
304
+ saveChanges={(editedRow) => {
305
+ setRows((rows) => rows.map((row, i) => (i === index ? editedRow : row)));
306
+ }}
307
+ ariaLabel={`row ${index + 1}`}
308
+ ></EditableRow>
309
+ ))}
310
+ </Tbody>
311
+ </Table>
312
+ );
313
+ };
@@ -16,7 +16,6 @@ interface Repository {
16
16
  };
17
17
  }
18
18
 
19
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
19
  export const TableExpandable: React.FunctionComponent = () => {
21
20
  // In real usage, this data would come from some external source like an API via props.
22
21
  const repositories: Repository[] = [
@@ -46,7 +46,7 @@ export const HeaderRow: React.FunctionComponent<HeaderRowProps> = ({
46
46
 
47
47
  if (!transformedProps) {
48
48
  // tslint:disable-next-line:no-console
49
- console.warn('Table.Header - Failed to receive a transformed result'); // eslint-disable-line max-len, no-console
49
+ console.warn('Table.Header - Failed to receive a transformed result'); // eslint-disable-line no-console
50
50
  }
51
51
 
52
52
  let cellNode;