mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
feat: Add user menu on users table (#1222)
This commit is contained in:
@@ -40,17 +40,21 @@ export interface TableProps<T> {
|
||||
* Optional empty state UI when the data is empty
|
||||
*/
|
||||
emptyState?: React.ReactElement
|
||||
/**
|
||||
* Optional element to render row actions like delete, update, etc
|
||||
*/
|
||||
rowMenu?: (data: T) => React.ReactElement
|
||||
}
|
||||
|
||||
export const Table = <T,>({ columns, data, emptyState, title }: TableProps<T>): React.ReactElement => {
|
||||
export const Table = <T,>({ columns, data, emptyState, title, rowMenu }: TableProps<T>): React.ReactElement => {
|
||||
const columnNames = columns.map(({ name }) => name)
|
||||
const body = renderTableBody(data, columns, emptyState)
|
||||
const body = renderTableBody(data, columns, emptyState, rowMenu)
|
||||
|
||||
return (
|
||||
<MuiTable>
|
||||
<TableHead>
|
||||
{title && <TableTitle title={title} />}
|
||||
<TableHeaders columns={columnNames} />
|
||||
<TableHeaders columns={columnNames} hasMenu={!!rowMenu} />
|
||||
</TableHead>
|
||||
{body}
|
||||
</MuiTable>
|
||||
@@ -60,7 +64,12 @@ export const Table = <T,>({ columns, data, emptyState, title }: TableProps<T>):
|
||||
/**
|
||||
* Helper function to render the table data, falling back to an empty state if available
|
||||
*/
|
||||
const renderTableBody = <T,>(data: T[], columns: Column<T>[], emptyState?: React.ReactElement) => {
|
||||
const renderTableBody = <T,>(
|
||||
data: T[],
|
||||
columns: Column<T>[],
|
||||
emptyState?: React.ReactElement,
|
||||
rowMenu?: (data: T) => React.ReactElement,
|
||||
) => {
|
||||
if (data.length > 0) {
|
||||
const rows = data.map((item: T, index) => {
|
||||
const cells = columns.map((column) => {
|
||||
@@ -70,7 +79,12 @@ const renderTableBody = <T,>(data: T[], columns: Column<T>[], emptyState?: React
|
||||
return <TableCell key={String(column.key)}>{String(item[column.key]).toString()}</TableCell>
|
||||
}
|
||||
})
|
||||
return <TableRow key={index}>{cells}</TableRow>
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
{cells}
|
||||
{rowMenu && <TableCell>{rowMenu(item)}</TableCell>}
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
return <TableBody>{rows}</TableBody>
|
||||
} else {
|
||||
|
||||
@@ -5,9 +5,10 @@ import React from "react"
|
||||
|
||||
export interface TableHeadersProps {
|
||||
columns: string[]
|
||||
hasMenu?: boolean
|
||||
}
|
||||
|
||||
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns }) => {
|
||||
export const TableHeaders: React.FC<TableHeadersProps> = ({ columns, hasMenu }) => {
|
||||
const styles = useStyles()
|
||||
return (
|
||||
<TableRow className={styles.root}>
|
||||
@@ -16,6 +17,8 @@ export const TableHeaders: React.FC<TableHeadersProps> = ({ columns }) => {
|
||||
{c}
|
||||
</TableCell>
|
||||
))}
|
||||
{/* 1% is a trick to make the table cell width fit the content */}
|
||||
{hasMenu && <TableCell width="1%" />}
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ComponentMeta, Story } from "@storybook/react"
|
||||
import React from "react"
|
||||
import { TableRowMenu, TableRowMenuProps } from "./TableRowMenu"
|
||||
|
||||
export default {
|
||||
title: "components/TableRowMenu",
|
||||
component: TableRowMenu,
|
||||
} as ComponentMeta<typeof TableRowMenu>
|
||||
|
||||
type DataType = {
|
||||
id: string
|
||||
}
|
||||
|
||||
const Template: Story<TableRowMenuProps<DataType>> = (args) => <TableRowMenu {...args} />
|
||||
|
||||
export const Example = Template.bind({})
|
||||
Example.args = {
|
||||
data: { id: "123" },
|
||||
menuItems: [
|
||||
{ label: "Suspend", onClick: (data) => alert(data.id) },
|
||||
{ label: "Update", onClick: (data) => alert(data.id) },
|
||||
{ label: "Delete", onClick: (data) => alert(data.id) },
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import IconButton from "@material-ui/core/IconButton"
|
||||
import Menu, { MenuProps } from "@material-ui/core/Menu"
|
||||
import MenuItem from "@material-ui/core/MenuItem"
|
||||
import MoreVertIcon from "@material-ui/icons/MoreVert"
|
||||
import React from "react"
|
||||
|
||||
export interface TableRowMenuProps<TData> {
|
||||
data: TData
|
||||
menuItems: Array<{
|
||||
label: string
|
||||
onClick: (data: TData) => void
|
||||
}>
|
||||
}
|
||||
|
||||
export const TableRowMenu = <T,>({ data, menuItems }: TableRowMenuProps<T>): JSX.Element => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<MenuProps["anchorEl"]>(null)
|
||||
|
||||
const handleClick = (event: React.MouseEvent) => {
|
||||
setAnchorEl(event.currentTarget)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IconButton size="small" aria-label="more" aria-controls="long-menu" aria-haspopup="true" onClick={handleClick}>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
|
||||
{menuItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onClick={() => {
|
||||
handleClose()
|
||||
item.onClick(data)
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import React from "react"
|
||||
import { UserResponse } from "../../api/types"
|
||||
import { EmptyState } from "../EmptyState/EmptyState"
|
||||
import { Column, Table } from "../Table/Table"
|
||||
import { TableRowMenu } from "../TableRowMenu/TableRowMenu"
|
||||
import { UserCell } from "../UserCell/UserCell"
|
||||
|
||||
const Language = {
|
||||
@@ -9,6 +10,7 @@ const Language = {
|
||||
usersTitle: "All users",
|
||||
emptyMessage: "No users found",
|
||||
usernameLabel: "User",
|
||||
suspendMenuItem: "Suspend",
|
||||
}
|
||||
|
||||
const emptyState = <EmptyState message={Language.emptyMessage} />
|
||||
@@ -28,5 +30,25 @@ export interface UsersTableProps {
|
||||
}
|
||||
|
||||
export const UsersTable: React.FC<UsersTableProps> = ({ users }) => {
|
||||
return <Table columns={columns} data={users} title={Language.usersTitle} emptyState={emptyState} />
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
data={users}
|
||||
title={Language.usersTitle}
|
||||
emptyState={emptyState}
|
||||
rowMenu={(user) => (
|
||||
<TableRowMenu
|
||||
data={user}
|
||||
menuItems={[
|
||||
{
|
||||
label: Language.suspendMenuItem,
|
||||
onClick: () => {
|
||||
// TO-DO: Add suspend action here
|
||||
},
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user