|
| 1 | +import type { FunctionComponent } from 'react'; |
| 2 | +import { useState, useEffect } from 'react'; |
| 3 | +import { |
| 4 | + DataListItem, |
| 5 | + DataList, |
| 6 | + DataListItemRow, |
| 7 | + DataListCheck, |
| 8 | + DataListCell, |
| 9 | + DataListItemCells, |
| 10 | + DataListControl, |
| 11 | + DataListDragButton, |
| 12 | + Button, |
| 13 | + ButtonVariant, |
| 14 | + Title, |
| 15 | + Checkbox, |
| 16 | + Dropdown, |
| 17 | + DropdownItem, |
| 18 | + MenuToggle |
| 19 | +} from '@patternfly/react-core'; |
| 20 | +import { |
| 21 | + DragDrop, |
| 22 | + Droppable, |
| 23 | + Draggable |
| 24 | +} from '@patternfly/react-core/deprecated'; |
| 25 | + |
| 26 | +export interface ColumnColumn { |
| 27 | + /** Internal identifier of a column by which table displayed columns are filtered. */ |
| 28 | + key: string; |
| 29 | + /** The actual display name of the column possibly with a tooltip or icon. */ |
| 30 | + title: React.ReactNode; |
| 31 | + /** If user changes checkboxes, the component will send back column array with this property altered. */ |
| 32 | + isShown?: boolean; |
| 33 | + /** Set to false if the column should be hidden initially */ |
| 34 | + isShownByDefault: boolean; |
| 35 | + /** The checkbox will be disabled, this is applicable to columns which should not be toggleable by user */ |
| 36 | + isUntoggleable?: boolean; |
| 37 | +} |
| 38 | + |
| 39 | +export interface ColumnProps { |
| 40 | + /** Current column state */ |
| 41 | + columns: ColumnColumn[]; |
| 42 | + /* Column description text */ |
| 43 | + description?: string; |
| 44 | + /* Column title text */ |
| 45 | + title?: string; |
| 46 | + /** Custom OUIA ID */ |
| 47 | + ouiaId?: string | number; |
| 48 | + /** Callback when a column is selected or deselected */ |
| 49 | + onSelect?: (column: ColumnColumn) => void; |
| 50 | + /** Callback when the column order changes */ |
| 51 | + onOrderChange?: (columns: ColumnColumn[]) => void; |
| 52 | + /** Callback to save the column state */ |
| 53 | + onSave?: (columns: ColumnColumn[]) => void; |
| 54 | + /** Callback to close the modal */ |
| 55 | + onCancel?: () => void; |
| 56 | +} |
| 57 | + |
| 58 | +const Column: FunctionComponent<ColumnProps> = ( |
| 59 | + { columns, |
| 60 | + description, |
| 61 | + title, |
| 62 | + ouiaId = 'Column', |
| 63 | + onSelect, |
| 64 | + onOrderChange, |
| 65 | + onSave, |
| 66 | + onCancel }: ColumnProps) => { |
| 67 | + |
| 68 | + const [ isDropdownOpen, setIsDropdownOpen ] = useState(false); |
| 69 | + const [ currentColumns, setCurrentColumns ] = useState( |
| 70 | + () => columns.map(column => ({ ...column, isShown: column.isShown ?? column.isShownByDefault, id: column.key })) |
| 71 | + ); |
| 72 | + |
| 73 | + useEffect(() => { |
| 74 | + setCurrentColumns(columns.map(column => ({ ...column, isShown: column.isShown ?? column.isShownByDefault, id: column.key }))); |
| 75 | + }, [ columns ]); |
| 76 | + |
| 77 | + const handleChange = index => { |
| 78 | + const newColumns = [ ...currentColumns ]; |
| 79 | + const changedColumn = { ...newColumns[index] }; |
| 80 | + |
| 81 | + changedColumn.isShown = !changedColumn.isShown; |
| 82 | + newColumns[index] = changedColumn; |
| 83 | + |
| 84 | + setCurrentColumns(newColumns); |
| 85 | + onSelect?.(changedColumn); |
| 86 | + }; |
| 87 | + |
| 88 | + const onDrag = (source, dest) => { |
| 89 | + if (dest) { |
| 90 | + const newColumns = [ ...currentColumns ]; |
| 91 | + const [ removed ] = newColumns.splice(source.index, 1); |
| 92 | + newColumns.splice(dest.index, 0, removed); |
| 93 | + setCurrentColumns(newColumns); |
| 94 | + onOrderChange?.(newColumns); |
| 95 | + return true; |
| 96 | + } |
| 97 | + return false; |
| 98 | + }; |
| 99 | + |
| 100 | + const handleSave = () => { |
| 101 | + onSave?.(currentColumns); |
| 102 | + onCancel?.(); |
| 103 | + } |
| 104 | + |
| 105 | + const onSelectAll = (select = true) => { |
| 106 | + const newColumns = currentColumns.map(c => ({ ...c, isShown: c.isUntoggleable ? c.isShown : select })); |
| 107 | + setCurrentColumns(newColumns); |
| 108 | + onOrderChange?.(newColumns); |
| 109 | + } |
| 110 | + |
| 111 | + const isAllSelected = () => currentColumns.every(c => c.isShown || c.isUntoggleable); |
| 112 | + const isSomeSelected = () => currentColumns.some(c => c.isShown); |
| 113 | + |
| 114 | + const dropdownItems = [ |
| 115 | + <DropdownItem key="select-all" onClick={() => onSelectAll(true)}>Select all</DropdownItem>, |
| 116 | + <DropdownItem key="deselect-all" onClick={() => onSelectAll(false)}>Select none</DropdownItem> |
| 117 | + ]; |
| 118 | + |
| 119 | + const content = ( |
| 120 | + <> |
| 121 | + <Title headingLevel="h3">{title}</Title> |
| 122 | + {description && <div style={{ paddingBottom: '1rem' }}><p>{description}</p></div>} |
| 123 | + <div style={{ paddingBottom: '1rem' }}> |
| 124 | + <Dropdown |
| 125 | + onSelect={() => setIsDropdownOpen(false)} |
| 126 | + toggle={(toggleRef) => ( |
| 127 | + <MenuToggle |
| 128 | + ref={toggleRef} |
| 129 | + onClick={() => setIsDropdownOpen(!isDropdownOpen)} |
| 130 | + isExpanded={isDropdownOpen} |
| 131 | + > |
| 132 | + <Checkbox |
| 133 | + aria-label="Select all" |
| 134 | + isChecked={isAllSelected() ? true : isSomeSelected() ? null : false} |
| 135 | + id={`${ouiaId}-select-all-checkbox`} |
| 136 | + /> |
| 137 | + </MenuToggle> |
| 138 | + )} |
| 139 | + isOpen={isDropdownOpen} |
| 140 | + > |
| 141 | + {dropdownItems} |
| 142 | + </Dropdown> |
| 143 | + </div> |
| 144 | + <DragDrop onDrop={onDrag}> |
| 145 | + <Droppable droppableId="draggable-datalist"> |
| 146 | + <DataList aria-label="Selected columns" isCompact data-ouia-component-id={`${ouiaId}-column-list`}> |
| 147 | + {currentColumns.map((column, index) => |
| 148 | + <Draggable key={column.key} id={column.key}> |
| 149 | + <DataListItem key={column.key}> |
| 150 | + <DataListItemRow> |
| 151 | + <DataListControl> |
| 152 | + <DataListDragButton |
| 153 | + aria-label="Reorder" |
| 154 | + aria-labelledby={`${ouiaId}-column-${index}-label`} |
| 155 | + /> |
| 156 | + </DataListControl> |
| 157 | + <DataListCheck |
| 158 | + isChecked={column.isShown} |
| 159 | + onChange={() => handleChange(index)} |
| 160 | + isDisabled={column.isUntoggleable} |
| 161 | + aria-labelledby={`${ouiaId}-column-${index}-label`} |
| 162 | + ouiaId={`${ouiaId}-column-${index}-checkbox`} |
| 163 | + id={`${ouiaId}-column-${index}-checkbox`} |
| 164 | + /> |
| 165 | + <DataListItemCells |
| 166 | + dataListCells={[ |
| 167 | + <DataListCell key={column.key} data-ouia-component-id={`${ouiaId}-column-${index}-label`}> |
| 168 | + <label htmlFor={`${ouiaId}-column-${index}-checkbox`} id={`${ouiaId}-column-${index}-label`}> |
| 169 | + {column.title} |
| 170 | + </label> |
| 171 | + </DataListCell> |
| 172 | + ]} |
| 173 | + /> |
| 174 | + </DataListItemRow> |
| 175 | + </DataListItem> |
| 176 | + </Draggable> |
| 177 | + )} |
| 178 | + </DataList> |
| 179 | + </Droppable> |
| 180 | + </DragDrop> |
| 181 | + <div style={{ display: 'flex', justifyContent: 'normal', paddingTop: '1rem' }}> |
| 182 | + <Button key="save" variant={ButtonVariant.primary} onClick={handleSave} ouiaId={`${ouiaId}-save-button`}> |
| 183 | + Save |
| 184 | + </Button> |
| 185 | + <Button key="cancel" variant={ButtonVariant.link} onClick={onCancel} ouiaId={`${ouiaId}-cancel-button`}> |
| 186 | + Cancel |
| 187 | + </Button> |
| 188 | + </div> |
| 189 | + </> |
| 190 | + ); |
| 191 | + |
| 192 | + return content; |
| 193 | +} |
| 194 | + |
| 195 | +export default Column; |
0 commit comments