|
| 1 | +import { useEffect } from 'react' |
| 2 | +import { action } from '@storybook/addon-actions' |
| 3 | +import { Meta, StoryObj } from '@storybook/react' |
| 4 | + |
| 5 | +import { |
| 6 | + Button, |
| 7 | + ButtonComponentType, |
| 8 | + ButtonStyleType, |
| 9 | + ButtonVariantType, |
| 10 | + ComponentSizeType, |
| 11 | + FiltersTypeEnum, |
| 12 | + PaginationEnum, |
| 13 | + SearchBar, |
| 14 | + SelectAllDialogStatus, |
| 15 | + Table, |
| 16 | + TableBulkOperationModalProps, |
| 17 | + TableCellComponentProps, |
| 18 | + TableProps, |
| 19 | + TableSignalEnum, |
| 20 | + TableViewWrapperProps, |
| 21 | +} from '@devtron-labs/devtron-fe-common-lib' |
| 22 | + |
| 23 | +import { ReactComponent as ICPause } from '@Icons/ic-pause.svg' |
| 24 | +import { ReactComponent as ICPlay } from '@Icons/ic-play-outline.svg' |
| 25 | +import { ReactComponent as ICWarning } from '@Icons/ic-warning-y6.svg' |
| 26 | + |
| 27 | +const CellComponent = ({ field, value, signals, row, isRowActive }: TableCellComponentProps) => { |
| 28 | + const handleButtonClick = () => { |
| 29 | + action(`Row ${value} clicked`) |
| 30 | + } |
| 31 | + |
| 32 | + useEffect(() => { |
| 33 | + const rowEnterPressedCallback = ({ |
| 34 | + detail: { |
| 35 | + activeRowData: { id }, |
| 36 | + }, |
| 37 | + }) => { |
| 38 | + if (id === row.id && field === 'name') { |
| 39 | + handleButtonClick() |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + const getCallback = |
| 44 | + (text: string) => |
| 45 | + ({ |
| 46 | + detail: { |
| 47 | + activeRowData: { id }, |
| 48 | + }, |
| 49 | + }) => { |
| 50 | + if (id === row.id && field === 'name') { |
| 51 | + action(text) |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + const deletePressedCallback = getCallback(`Delete pressed for ${value}`) |
| 56 | + const openContextMenuCallback = getCallback(`Open context menu for ${value}`) |
| 57 | + |
| 58 | + signals.addEventListener(TableSignalEnum.ENTER_PRESSED, rowEnterPressedCallback) |
| 59 | + signals.addEventListener(TableSignalEnum.DELETE_PRESSED, deletePressedCallback) |
| 60 | + signals.addEventListener(TableSignalEnum.OPEN_CONTEXT_MENU, openContextMenuCallback) |
| 61 | + |
| 62 | + return () => { |
| 63 | + signals.removeEventListener(TableSignalEnum.ENTER_PRESSED, rowEnterPressedCallback) |
| 64 | + signals.removeEventListener(TableSignalEnum.DELETE_PRESSED, deletePressedCallback) |
| 65 | + signals.removeEventListener(TableSignalEnum.OPEN_CONTEXT_MENU, openContextMenuCallback) |
| 66 | + } |
| 67 | + }, []) |
| 68 | + |
| 69 | + if (field === 'name') { |
| 70 | + return ( |
| 71 | + <div className="flexbox dc__align-items-center"> |
| 72 | + <Button |
| 73 | + variant={ButtonVariantType.text} |
| 74 | + text={value as string} |
| 75 | + dataTestId={`${field}-${row.id}`} |
| 76 | + style={isRowActive ? ButtonStyleType.default : ButtonStyleType.neutral} |
| 77 | + onClick={handleButtonClick} |
| 78 | + /> |
| 79 | + </div> |
| 80 | + ) |
| 81 | + } |
| 82 | + |
| 83 | + return ( |
| 84 | + <div className="flexbox dc__gap-6 dc__align-items-center"> |
| 85 | + <ICWarning className="dc__no-shrink icon-dim-18" /> |
| 86 | + |
| 87 | + <span>{value}</span> |
| 88 | + </div> |
| 89 | + ) |
| 90 | +} |
| 91 | + |
| 92 | +const COLUMNS: TableProps['columns'] = [ |
| 93 | + { |
| 94 | + field: 'name', |
| 95 | + size: { fixed: 300 }, |
| 96 | + label: 'Name', |
| 97 | + comparator: (a: string, b: string) => a.localeCompare(b), |
| 98 | + isSortable: true, |
| 99 | + CellComponent, |
| 100 | + }, |
| 101 | + { |
| 102 | + field: 'value', |
| 103 | + size: { |
| 104 | + range: { |
| 105 | + startWidth: 180, |
| 106 | + minWidth: 100, |
| 107 | + maxWidth: 600, |
| 108 | + }, |
| 109 | + }, |
| 110 | + label: 'Value', |
| 111 | + }, |
| 112 | + { |
| 113 | + field: 'message', |
| 114 | + size: { |
| 115 | + fixed: 200, |
| 116 | + }, |
| 117 | + label: 'Message', |
| 118 | + CellComponent, |
| 119 | + }, |
| 120 | +] |
| 121 | + |
| 122 | +type RowDataType = { |
| 123 | + name: string |
| 124 | + value: string |
| 125 | + message: string |
| 126 | +} |
| 127 | + |
| 128 | +const ROWS: TableProps['rows'] = [ |
| 129 | + { id: '1', data: { name: 'Alice', value: '123', message: 'Something new' } }, |
| 130 | + { id: '2', data: { name: 'Bob', value: '456', message: 'Another message' } }, |
| 131 | + { id: '3', data: { name: 'Charlie', value: '789', message: 'Yet another one' } }, |
| 132 | + { id: '4', data: { name: 'Diana', value: '101', message: 'Message here' } }, |
| 133 | + { id: '5', data: { name: 'Eve', value: '202', message: 'Something else' } }, |
| 134 | + { id: '6', data: { name: 'Frank', value: '303', message: 'New message' } }, |
| 135 | + { id: '7', data: { name: 'Grace', value: '404', message: 'Important note' } }, |
| 136 | + { id: '8', data: { name: 'Hank', value: '505', message: 'Final message' } }, |
| 137 | + { id: '9', data: { name: 'Ivy', value: '606', message: 'Additional info' } }, |
| 138 | + { id: '10', data: { name: 'Jack', value: '707', message: 'Critical update' } }, |
| 139 | + { id: '11', data: { name: 'Karen', value: '808', message: 'New feature' } }, |
| 140 | + { id: '12', data: { name: 'Leo', value: '909', message: 'Bug fix' } }, |
| 141 | + { id: '13', data: { name: 'Mona', value: '1010', message: 'Performance improvement' } }, |
| 142 | + { id: '14', data: { name: 'Nina', value: '1111', message: 'Security patch' } }, |
| 143 | + { id: '15', data: { name: 'Oscar', value: '1212', message: 'UI enhancement' } }, |
| 144 | + { id: '16', data: { name: 'Paul', value: '1313', message: 'Backend update' } }, |
| 145 | + { id: '17', data: { name: 'Quinn', value: '1414', message: 'Database migration' } }, |
| 146 | + { id: '18', data: { name: 'Rachel', value: '1515', message: 'API change' } }, |
| 147 | + { id: '19', data: { name: 'Steve', value: '1616', message: 'Documentation update' } }, |
| 148 | + { id: '20', data: { name: 'Tina', value: '1717', message: 'New integration' } }, |
| 149 | + { id: '21', data: { name: 'Uma', value: '1818', message: 'Deprecated feature' } }, |
| 150 | + { id: '22', data: { name: 'Victor', value: '1919', message: 'Hotfix applied' } }, |
| 151 | + { id: '23', data: { name: 'Wendy', value: '2020', message: 'Code refactor' } }, |
| 152 | + { id: '24', data: { name: 'Xander', value: '2121', message: 'New dependency' } }, |
| 153 | + { id: '25', data: { name: 'Yara', value: '2222', message: 'Improved logging' } }, |
| 154 | + { id: '26', data: { name: 'Zane', value: '2323', message: 'Monitoring added' } }, |
| 155 | + { id: '27', data: { name: 'Amy', value: '2424', message: 'Analytics update' } }, |
| 156 | + { id: '28', data: { name: 'Brian', value: '2525', message: 'Localization added' } }, |
| 157 | + { id: '29', data: { name: 'Cathy', value: '2626', message: 'Accessibility fix' } }, |
| 158 | + { id: '30', data: { name: 'David', value: '2727', message: 'New dashboard' } }, |
| 159 | + { id: '31', data: { name: 'Ella', value: '2828', message: 'Improved UX' } }, |
| 160 | + { id: '32', data: { name: 'Fred', value: '2929', message: 'Updated icons' } }, |
| 161 | + { id: '33', data: { name: 'Gina', value: '3030', message: 'Enhanced security' } }, |
| 162 | + { id: '34', data: { name: 'Harry', value: '3131', message: 'New theme' } }, |
| 163 | + { id: '35', data: { name: 'Iris', value: '3232', message: 'Updated dependencies' } }, |
| 164 | + { id: '36', data: { name: 'Jake', value: '3333', message: 'Improved performance' } }, |
| 165 | + { id: '37', data: { name: 'Kara', value: '3434', message: 'New API endpoint' } }, |
| 166 | + { id: '38', data: { name: 'Liam', value: '3535', message: 'Updated README' } }, |
| 167 | +] |
| 168 | + |
| 169 | +const meta = { |
| 170 | + component: Table, |
| 171 | +} satisfies Meta<TableProps> |
| 172 | + |
| 173 | +export default meta |
| 174 | + |
| 175 | +type Story = StoryObj<typeof meta> |
| 176 | + |
| 177 | +const BulkActionsComponent = () => ( |
| 178 | + <div className="flexbox dc__gap-4"> |
| 179 | + <Button |
| 180 | + icon={<ICPause />} |
| 181 | + dataTestId="rb-bulk-action__action-widget--cordon" |
| 182 | + component={ButtonComponentType.button} |
| 183 | + style={ButtonStyleType.negativeGrey} |
| 184 | + variant={ButtonVariantType.borderLess} |
| 185 | + ariaLabel="Pause" |
| 186 | + size={ComponentSizeType.small} |
| 187 | + onClick={() => action('Pause clicked')} |
| 188 | + showAriaLabelInTippy |
| 189 | + /> |
| 190 | + |
| 191 | + <Button |
| 192 | + icon={<ICPlay />} |
| 193 | + dataTestId="rb-bulk-action__action-widget--uncordon" |
| 194 | + component={ButtonComponentType.button} |
| 195 | + style={ButtonStyleType.neutral} |
| 196 | + variant={ButtonVariantType.borderLess} |
| 197 | + ariaLabel="Play" |
| 198 | + size={ComponentSizeType.small} |
| 199 | + onClick={() => action('Play clicked!')} |
| 200 | + showAriaLabelInTippy |
| 201 | + /> |
| 202 | + </div> |
| 203 | +) |
| 204 | + |
| 205 | +const ViewWrapper = ({ children, handleSearch, searchKey }: TableViewWrapperProps) => ( |
| 206 | + <div |
| 207 | + style={{ height: '800px' }} |
| 208 | + className="w-100 flexbox-col flex-grow-1 bg__primary dc__overflow-hidden dc__gap-16 py-12" |
| 209 | + > |
| 210 | + <div className="flexbox w-100 dc__align-start px-20"> |
| 211 | + <SearchBar handleSearchChange={handleSearch} initialSearchText={searchKey} containerClassName="w-300" /> |
| 212 | + </div> |
| 213 | + |
| 214 | + {children} |
| 215 | + </div> |
| 216 | +) |
| 217 | + |
| 218 | +const BulkOperationModal = ({ action: bulkAction, selections, onClose }: TableBulkOperationModalProps) => { |
| 219 | + useEffect(() => { |
| 220 | + action(`Applying bulk action: ${bulkAction} on ${selections?.length} selections`) |
| 221 | + setTimeout(onClose, 1000) // Simulate a delay for the action |
| 222 | + }, []) |
| 223 | + |
| 224 | + return null |
| 225 | +} |
| 226 | + |
| 227 | +export const TableTemplate: Story = { |
| 228 | + args: { |
| 229 | + columns: COLUMNS, |
| 230 | + rows: ROWS, |
| 231 | + filtersVariant: FiltersTypeEnum.STATE, |
| 232 | + id: 'table__story', |
| 233 | + paginationVariant: PaginationEnum.PAGINATED, |
| 234 | + emptyStateConfig: { |
| 235 | + noRowsConfig: { |
| 236 | + title: 'No rows to display', |
| 237 | + description: 'There are no rows to display.', |
| 238 | + }, |
| 239 | + }, |
| 240 | + filter: (row, filterData) => { |
| 241 | + const lowerCasedSearchKey = filterData.searchKey.toLowerCase() |
| 242 | + return (row.data as RowDataType).name.toLowerCase().includes(lowerCasedSearchKey) |
| 243 | + }, |
| 244 | + bulkSelectionConfig: { |
| 245 | + bulkActionsData: null, |
| 246 | + bulkOperationModalData: null, |
| 247 | + BulkOperationModal, |
| 248 | + BulkActionsComponent, |
| 249 | + getSelectAllDialogStatus: () => SelectAllDialogStatus.CLOSED, |
| 250 | + }, |
| 251 | + stylesConfig: { |
| 252 | + showSeparatorBetweenRows: true, |
| 253 | + }, |
| 254 | + ViewWrapper, |
| 255 | + additionalFilterProps: { |
| 256 | + initialSortKey: 'name', |
| 257 | + }, |
| 258 | + } as TableProps, |
| 259 | +} |
0 commit comments