Skip to content

Commit 5a58b24

Browse files
authored
Merge pull request #2600 from devtron-labs/feat/table-storybook
feat: table component storybook
2 parents 4e0e005 + c57cade commit 5a58b24

File tree

3 files changed

+262
-1
lines changed

3 files changed

+262
-1
lines changed

.storybook/preview.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ const preview: Preview = {
5959
<div id="animated-dialog-backdrop" />
6060
<div id="visible-modal" />
6161

62+
<div id="visible-modal" />
63+
6264
<BaseConfirmationModal />
6365
</ConfirmationModalProvider>
6466
</ThemeProvider>

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
6363
"test:ci": "jest --watchAll=false --ci --json --coverage --testLocationInResults --outputFile=report.json",
6464
"jest": "jest",
6565
"lint-staged": "lint-staged",
66-
"storybook": "IS_STORYBOOK=true storybook dev -p 6006",
66+
"storybook": "IS_STORYBOOK=true GENERATE_SOURCEMAP=true storybook dev -p 6006",
6767
"build-storybook": "IS_STORYBOOK=true storybook build",
6868
"postinstall": "patch-package"
6969
},

src/stories/Table.stories.tsx

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
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

Comments
 (0)