From c1d612f93d4a26a6d55be3d6a592f16fe0f9aefd Mon Sep 17 00:00:00 2001 From: Bruno Date: Fri, 29 Apr 2022 16:58:46 +0000 Subject: [PATCH 1/3] feat: Add user menu on users table --- site/src/components/Table/Table.tsx | 24 +++++++++++---- .../components/TableHeaders/TableHeaders.tsx | 5 +++- site/src/components/UsersTable/UserMenu.tsx | 29 +++++++++++++++++++ site/src/components/UsersTable/UsersTable.tsx | 11 ++++++- 4 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 site/src/components/UsersTable/UserMenu.tsx diff --git a/site/src/components/Table/Table.tsx b/site/src/components/Table/Table.tsx index a854d1ecdb313..e8445464c9378 100644 --- a/site/src/components/Table/Table.tsx +++ b/site/src/components/Table/Table.tsx @@ -40,17 +40,21 @@ export interface TableProps { * 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 = ({ columns, data, emptyState, title }: TableProps): React.ReactElement => { +export const Table = ({ columns, data, emptyState, title, rowMenu }: TableProps): React.ReactElement => { const columnNames = columns.map(({ name }) => name) - const body = renderTableBody(data, columns, emptyState) + const body = renderTableBody(data, columns, emptyState, rowMenu) return ( {title && } - + {body} @@ -60,7 +64,12 @@ export const Table = ({ columns, data, emptyState, title }: TableProps): /** * Helper function to render the table data, falling back to an empty state if available */ -const renderTableBody = (data: T[], columns: Column[], emptyState?: React.ReactElement) => { +const renderTableBody = ( + data: T[], + columns: Column[], + 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 = (data: T[], columns: Column[], emptyState?: React return {String(item[column.key]).toString()} } }) - return {cells} + return ( + + {cells} + {rowMenu && {rowMenu(item)}} + + ) }) return {rows} } else { diff --git a/site/src/components/TableHeaders/TableHeaders.tsx b/site/src/components/TableHeaders/TableHeaders.tsx index f91aa578df37a..462132a9c0a4a 100644 --- a/site/src/components/TableHeaders/TableHeaders.tsx +++ b/site/src/components/TableHeaders/TableHeaders.tsx @@ -5,9 +5,10 @@ import React from "react" export interface TableHeadersProps { columns: string[] + hasMenu?: boolean } -export const TableHeaders: React.FC = ({ columns }) => { +export const TableHeaders: React.FC = ({ columns, hasMenu }) => { const styles = useStyles() return ( @@ -16,6 +17,8 @@ export const TableHeaders: React.FC = ({ columns }) => { {c} ))} + {/* 1% is a hack to make the table cell width fits the content */} + {hasMenu && } ) } diff --git a/site/src/components/UsersTable/UserMenu.tsx b/site/src/components/UsersTable/UserMenu.tsx new file mode 100644 index 0000000000000..163e8248aafaa --- /dev/null +++ b/site/src/components/UsersTable/UserMenu.tsx @@ -0,0 +1,29 @@ +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" +import { UserResponse } from "../../api/types" + +export const UserMenu: React.FC<{ user: UserResponse }> = () => { + const [anchorEl, setAnchorEl] = React.useState(null) + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget) + } + + const handleClose = () => { + setAnchorEl(null) + } + + return ( + <> + + + + + Suspend + + + ) +} diff --git a/site/src/components/UsersTable/UsersTable.tsx b/site/src/components/UsersTable/UsersTable.tsx index 764f90dad63d5..10aac8182cea8 100644 --- a/site/src/components/UsersTable/UsersTable.tsx +++ b/site/src/components/UsersTable/UsersTable.tsx @@ -3,6 +3,7 @@ import { UserResponse } from "../../api/types" import { EmptyState } from "../EmptyState/EmptyState" import { Column, Table } from "../Table/Table" import { UserCell } from "../UserCell/UserCell" +import { UserMenu } from "./UserMenu" const Language = { pageTitle: "Users", @@ -28,5 +29,13 @@ export interface UsersTableProps { } export const UsersTable: React.FC = ({ users }) => { - return + return ( +
} + /> + ) } From ef73ad92e49d76c6480cf1da034709dd020febdb Mon Sep 17 00:00:00 2001 From: Bruno Quaresma Date: Fri, 29 Apr 2022 13:51:00 -0500 Subject: [PATCH 2/3] Update site/src/components/TableHeaders/TableHeaders.tsx Co-authored-by: G r e y --- site/src/components/TableHeaders/TableHeaders.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/src/components/TableHeaders/TableHeaders.tsx b/site/src/components/TableHeaders/TableHeaders.tsx index 462132a9c0a4a..6004939e449ba 100644 --- a/site/src/components/TableHeaders/TableHeaders.tsx +++ b/site/src/components/TableHeaders/TableHeaders.tsx @@ -17,7 +17,7 @@ export const TableHeaders: React.FC = ({ columns, hasMenu }) {c} ))} - {/* 1% is a hack to make the table cell width fits the content */} + {/* 1% is a trick to make the table cell width fit the content */} {hasMenu && } ) From 1f562a339e05776d33a4bfe75469e3d3d591f63c Mon Sep 17 00:00:00 2001 From: Bruno Date: Mon, 2 May 2022 14:49:21 +0000 Subject: [PATCH 3/3] refactor: extract user menu to be TableRowMenu --- .../TableRowMenu/TableRowMenu.stories.tsx | 24 +++++++++++++++++++ .../TableRowMenu.tsx} | 23 +++++++++++++++--- site/src/components/UsersTable/UsersTable.tsx | 17 +++++++++++-- 3 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 site/src/components/TableRowMenu/TableRowMenu.stories.tsx rename site/src/components/{UsersTable/UserMenu.tsx => TableRowMenu/TableRowMenu.tsx} (61%) diff --git a/site/src/components/TableRowMenu/TableRowMenu.stories.tsx b/site/src/components/TableRowMenu/TableRowMenu.stories.tsx new file mode 100644 index 0000000000000..39b2aec38c300 --- /dev/null +++ b/site/src/components/TableRowMenu/TableRowMenu.stories.tsx @@ -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 + +type DataType = { + id: string +} + +const Template: Story> = (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) }, + ], +} diff --git a/site/src/components/UsersTable/UserMenu.tsx b/site/src/components/TableRowMenu/TableRowMenu.tsx similarity index 61% rename from site/src/components/UsersTable/UserMenu.tsx rename to site/src/components/TableRowMenu/TableRowMenu.tsx index 163e8248aafaa..8e10df4e88ba7 100644 --- a/site/src/components/UsersTable/UserMenu.tsx +++ b/site/src/components/TableRowMenu/TableRowMenu.tsx @@ -3,9 +3,16 @@ 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" -import { UserResponse } from "../../api/types" -export const UserMenu: React.FC<{ user: UserResponse }> = () => { +export interface TableRowMenuProps { + data: TData + menuItems: Array<{ + label: string + onClick: (data: TData) => void + }> +} + +export const TableRowMenu = ({ data, menuItems }: TableRowMenuProps): JSX.Element => { const [anchorEl, setAnchorEl] = React.useState(null) const handleClick = (event: React.MouseEvent) => { @@ -22,7 +29,17 @@ export const UserMenu: React.FC<{ user: UserResponse }> = () => { - Suspend + {menuItems.map((item) => ( + { + handleClose() + item.onClick(data) + }} + > + {item.label} + + ))} ) diff --git a/site/src/components/UsersTable/UsersTable.tsx b/site/src/components/UsersTable/UsersTable.tsx index 10aac8182cea8..3b497e161243a 100644 --- a/site/src/components/UsersTable/UsersTable.tsx +++ b/site/src/components/UsersTable/UsersTable.tsx @@ -2,14 +2,15 @@ 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" -import { UserMenu } from "./UserMenu" const Language = { pageTitle: "Users", usersTitle: "All users", emptyMessage: "No users found", usernameLabel: "User", + suspendMenuItem: "Suspend", } const emptyState = @@ -35,7 +36,19 @@ export const UsersTable: React.FC = ({ users }) => { data={users} title={Language.usersTitle} emptyState={emptyState} - rowMenu={(user) => } + rowMenu={(user) => ( + { + // TO-DO: Add suspend action here + }, + }, + ]} + /> + )} /> ) }