Skip to content

Commit ecc5579

Browse files
BrunoQuaresmakylecarbs
authored andcommitted
feat: Add user menu on users table (#1222)
1 parent c92b7b5 commit ecc5579

File tree

5 files changed

+116
-7
lines changed

5 files changed

+116
-7
lines changed

site/src/components/Table/Table.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,21 @@ export interface TableProps<T> {
4040
* Optional empty state UI when the data is empty
4141
*/
4242
emptyState?: React.ReactElement
43+
/**
44+
* Optional element to render row actions like delete, update, etc
45+
*/
46+
rowMenu?: (data: T) => React.ReactElement
4347
}
4448

45-
export const Table = <T,>({ columns, data, emptyState, title }: TableProps<T>): React.ReactElement => {
49+
export const Table = <T,>({ columns, data, emptyState, title, rowMenu }: TableProps<T>): React.ReactElement => {
4650
const columnNames = columns.map(({ name }) => name)
47-
const body = renderTableBody(data, columns, emptyState)
51+
const body = renderTableBody(data, columns, emptyState, rowMenu)
4852

4953
return (
5054
<MuiTable>
5155
<TableHead>
5256
{title && <TableTitle title={title} />}
53-
<TableHeaders columns={columnNames} />
57+
<TableHeaders columns={columnNames} hasMenu={!!rowMenu} />
5458
</TableHead>
5559
{body}
5660
</MuiTable>
@@ -60,7 +64,12 @@ export const Table = <T,>({ columns, data, emptyState, title }: TableProps<T>):
6064
/**
6165
* Helper function to render the table data, falling back to an empty state if available
6266
*/
63-
const renderTableBody = <T,>(data: T[], columns: Column<T>[], emptyState?: React.ReactElement) => {
67+
const renderTableBody = <T,>(
68+
data: T[],
69+
columns: Column<T>[],
70+
emptyState?: React.ReactElement,
71+
rowMenu?: (data: T) => React.ReactElement,
72+
) => {
6473
if (data.length > 0) {
6574
const rows = data.map((item: T, index) => {
6675
const cells = columns.map((column) => {
@@ -70,7 +79,12 @@ const renderTableBody = <T,>(data: T[], columns: Column<T>[], emptyState?: React
7079
return <TableCell key={String(column.key)}>{String(item[column.key]).toString()}</TableCell>
7180
}
7281
})
73-
return <TableRow key={index}>{cells}</TableRow>
82+
return (
83+
<TableRow key={index}>
84+
{cells}
85+
{rowMenu && <TableCell>{rowMenu(item)}</TableCell>}
86+
</TableRow>
87+
)
7488
})
7589
return <TableBody>{rows}</TableBody>
7690
} else {

site/src/components/TableHeaders/TableHeaders.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import React from "react"
55

66
export interface TableHeadersProps {
77
columns: string[]
8+
hasMenu?: boolean
89
}
910

10-
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns }) => {
11+
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns, hasMenu }) => {
1112
const styles = useStyles()
1213
return (
1314
<TableRow className={styles.root}>
@@ -16,6 +17,8 @@ export const TableHeaders: React.FC<TableHeadersProps> = ({ columns }) => {
1617
{c}
1718
</TableCell>
1819
))}
20+
{/* 1% is a trick to make the table cell width fit the content */}
21+
{hasMenu && <TableCell width="1%" />}
1922
</TableRow>
2023
)
2124
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ComponentMeta, Story } from "@storybook/react"
2+
import React from "react"
3+
import { TableRowMenu, TableRowMenuProps } from "./TableRowMenu"
4+
5+
export default {
6+
title: "components/TableRowMenu",
7+
component: TableRowMenu,
8+
} as ComponentMeta<typeof TableRowMenu>
9+
10+
type DataType = {
11+
id: string
12+
}
13+
14+
const Template: Story<TableRowMenuProps<DataType>> = (args) => <TableRowMenu {...args} />
15+
16+
export const Example = Template.bind({})
17+
Example.args = {
18+
data: { id: "123" },
19+
menuItems: [
20+
{ label: "Suspend", onClick: (data) => alert(data.id) },
21+
{ label: "Update", onClick: (data) => alert(data.id) },
22+
{ label: "Delete", onClick: (data) => alert(data.id) },
23+
],
24+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import IconButton from "@material-ui/core/IconButton"
2+
import Menu, { MenuProps } from "@material-ui/core/Menu"
3+
import MenuItem from "@material-ui/core/MenuItem"
4+
import MoreVertIcon from "@material-ui/icons/MoreVert"
5+
import React from "react"
6+
7+
export interface TableRowMenuProps<TData> {
8+
data: TData
9+
menuItems: Array<{
10+
label: string
11+
onClick: (data: TData) => void
12+
}>
13+
}
14+
15+
export const TableRowMenu = <T,>({ data, menuItems }: TableRowMenuProps<T>): JSX.Element => {
16+
const [anchorEl, setAnchorEl] = React.useState<MenuProps["anchorEl"]>(null)
17+
18+
const handleClick = (event: React.MouseEvent) => {
19+
setAnchorEl(event.currentTarget)
20+
}
21+
22+
const handleClose = () => {
23+
setAnchorEl(null)
24+
}
25+
26+
return (
27+
<>
28+
<IconButton size="small" aria-label="more" aria-controls="long-menu" aria-haspopup="true" onClick={handleClick}>
29+
<MoreVertIcon />
30+
</IconButton>
31+
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
32+
{menuItems.map((item) => (
33+
<MenuItem
34+
key={item.label}
35+
onClick={() => {
36+
handleClose()
37+
item.onClick(data)
38+
}}
39+
>
40+
{item.label}
41+
</MenuItem>
42+
))}
43+
</Menu>
44+
</>
45+
)
46+
}

site/src/components/UsersTable/UsersTable.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import React from "react"
22
import { UserResponse } from "../../api/types"
33
import { EmptyState } from "../EmptyState/EmptyState"
44
import { Column, Table } from "../Table/Table"
5+
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
56
import { UserCell } from "../UserCell/UserCell"
67

78
const Language = {
89
pageTitle: "Users",
910
usersTitle: "All users",
1011
emptyMessage: "No users found",
1112
usernameLabel: "User",
13+
suspendMenuItem: "Suspend",
1214
}
1315

1416
const emptyState = <EmptyState message={Language.emptyMessage} />
@@ -28,5 +30,25 @@ export interface UsersTableProps {
2830
}
2931

3032
export const UsersTable: React.FC<UsersTableProps> = ({ users }) => {
31-
return <Table columns={columns} data={users} title={Language.usersTitle} emptyState={emptyState} />
33+
return (
34+
<Table
35+
columns={columns}
36+
data={users}
37+
title={Language.usersTitle}
38+
emptyState={emptyState}
39+
rowMenu={(user) => (
40+
<TableRowMenu
41+
data={user}
42+
menuItems={[
43+
{
44+
label: Language.suspendMenuItem,
45+
onClick: () => {
46+
// TO-DO: Add suspend action here
47+
},
48+
},
49+
]}
50+
/>
51+
)}
52+
/>
53+
)
3254
}

0 commit comments

Comments
 (0)