diff --git a/site/src/components/BuildsTable/BuildsTable.tsx b/site/src/components/BuildsTable/BuildsTable.tsx index 92229a1a3be16..b45421b06262d 100644 --- a/site/src/components/BuildsTable/BuildsTable.tsx +++ b/site/src/components/BuildsTable/BuildsTable.tsx @@ -3,6 +3,7 @@ import { fade, makeStyles, Theme } from "@material-ui/core/styles" import Table from "@material-ui/core/Table" import TableBody from "@material-ui/core/TableBody" import TableCell from "@material-ui/core/TableCell" +import TableContainer from "@material-ui/core/TableContainer" import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight" @@ -37,72 +38,74 @@ export const BuildsTable: FC = ({ builds, className }) => { const styles = useStyles() return ( - - - - {Language.actionLabel} - {Language.durationLabel} - {Language.startedAtLabel} - {Language.statusLabel} - - - - - {isLoading && } - {builds && - builds.map((build) => { - const status = getDisplayWorkspaceBuildStatus(theme, build) - const buildPageLink = `/@${username}/${workspaceName}/builds/${build.build_number}` - - return ( - { - if (event.key === "Enter") { - navigate(buildPageLink) - } - }} - className={styles.clickableTableRow} - > - {build.transition} - - - {displayWorkspaceBuildDuration(build)} - - - - - {new Date(build.created_at).toLocaleString()} - - - - - {status.status} - - - -
- -
-
-
- ) - })} - - {builds && builds.length === 0 && ( + +
+ - - - - - + {Language.actionLabel} + {Language.durationLabel} + {Language.startedAtLabel} + {Language.statusLabel} + - )} - -
+ + + {isLoading && } + {builds && + builds.map((build) => { + const status = getDisplayWorkspaceBuildStatus(theme, build) + const buildPageLink = `/@${username}/${workspaceName}/builds/${build.build_number}` + + return ( + { + if (event.key === "Enter") { + navigate(buildPageLink) + } + }} + className={styles.clickableTableRow} + > + {build.transition} + + + {displayWorkspaceBuildDuration(build)} + + + + + {new Date(build.created_at).toLocaleString()} + + + + + {status.status} + + + +
+ +
+
+
+ ) + })} + + {builds && builds.length === 0 && ( + + + + + + + + )} +
+ + ) } diff --git a/site/src/components/PageHeader/PageHeader.tsx b/site/src/components/PageHeader/PageHeader.tsx index 752ab55e40f34..445816478b90e 100644 --- a/site/src/components/PageHeader/PageHeader.tsx +++ b/site/src/components/PageHeader/PageHeader.tsx @@ -13,9 +13,11 @@ export const PageHeader: React.FC = ({ children, actions, class return (
{children}
- - {actions} - + {actions && ( + + {actions} + + )}
) } @@ -38,6 +40,11 @@ const useStyles = makeStyles((theme) => ({ alignItems: "center", paddingTop: theme.spacing(6), paddingBottom: theme.spacing(5), + + [theme.breakpoints.down("sm")]: { + flexDirection: "column", + alignItems: "flex-start", + }, }, title: { @@ -60,5 +67,11 @@ const useStyles = makeStyles((theme) => ({ actions: { marginLeft: "auto", + + [theme.breakpoints.down("sm")]: { + marginTop: theme.spacing(3), + marginLeft: "initial", + width: "100%", + }, }, })) diff --git a/site/src/components/Resources/Resources.tsx b/site/src/components/Resources/Resources.tsx index bb3ba6cf3c6db..debbf2bfe45d7 100644 --- a/site/src/components/Resources/Resources.tsx +++ b/site/src/components/Resources/Resources.tsx @@ -2,6 +2,7 @@ import { makeStyles, Theme } from "@material-ui/core/styles" import Table from "@material-ui/core/Table" import TableBody from "@material-ui/core/TableBody" import TableCell from "@material-ui/core/TableCell" +import TableContainer from "@material-ui/core/TableContainer" import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" import useTheme from "@material-ui/styles/useTheme" @@ -46,101 +47,103 @@ export const Resources: FC = ({ {getResourcesError ? ( { getResourcesError } ) : ( - - - - - - {Language.resourceLabel} - - - - - - {Language.agentLabel} - - - - {canUpdateWorkspace && } - - - - {resources?.map((resource) => { - { - /* We need to initialize the agents to display the resource */ - } - const agents = resource.agents ?? [null] - const resourceName = ( - } - title={resource.name} - subtitle={resource.type} - highlightTitle - /> - ) - - return agents.map((agent, agentIndex) => { + +
+ + + + + {Language.resourceLabel} + + + + + + {Language.agentLabel} + + + + {canUpdateWorkspace && } + + + + {resources?.map((resource) => { { - /* If there is no agent, just display the resource name */ - } - if (!agent) { - return ( - - {resourceName} - - - ) + /* We need to initialize the agents to display the resource */ } + const agents = resource.agents ?? [null] + const resourceName = ( + } + title={resource.name} + subtitle={resource.type} + highlightTitle + /> + ) - const agentStatus = getDisplayAgentStatus(theme, agent) - return ( - - {/* We only want to display the name in the first row because we are using rowSpan */} - {/* The rowspan should be the same than the number of agents */} - {agentIndex === 0 && ( - - {resourceName} + return agents.map((agent, agentIndex) => { + { + /* If there is no agent, just display the resource name */ + } + if (!agent) { + return ( + + {resourceName} + + + ) + } + + const agentStatus = getDisplayAgentStatus(theme, agent) + return ( + + {/* We only want to display the name in the first row because we are using rowSpan */} + {/* The rowspan should be the same than the number of agents */} + {agentIndex === 0 && ( + + {resourceName} + + )} + + + {agent.name} +
+ {agent.operating_system} + + {agentStatus.status} + +
- )} - - - {agent.name} -
- {agent.operating_system} - - {agentStatus.status} - -
-
- - <> - {canUpdateWorkspace && agent.status === "connected" && ( -
- - - {agent.apps.map((app) => ( - + <> + {canUpdateWorkspace && agent.status === "connected" && ( +
+ + - ))} -
- )} - - - - ) - }) - })} - -
+ {agent.apps.map((app) => ( + + ))} + + )} + + + + ) + }) + })} + + + )} ) @@ -152,7 +155,7 @@ const useStyles = makeStyles((theme) => ({ border: `1px solid ${theme.palette.divider}`, }, - table: { + tableContainer: { border: 0, }, diff --git a/site/src/components/SearchBarWithFilter/SearchBarWithFilter.tsx b/site/src/components/SearchBarWithFilter/SearchBarWithFilter.tsx index 668ec0e9bda8e..841d97bea47f7 100644 --- a/site/src/components/SearchBarWithFilter/SearchBarWithFilter.tsx +++ b/site/src/components/SearchBarWithFilter/SearchBarWithFilter.tsx @@ -166,6 +166,7 @@ const useStyles = makeStyles((theme) => ({ border: `1px solid ${theme.palette.divider}`, borderRight: "0px", borderRadius: `${theme.shape.borderRadius}px 0px 0px ${theme.shape.borderRadius}px`, + flexShrink: 0, }, errorRoot: { color: theme.palette.error.main, diff --git a/site/src/components/Stack/Stack.tsx b/site/src/components/Stack/Stack.tsx index 5d2c859f9125a..a6614f1243e5f 100644 --- a/site/src/components/Stack/Stack.tsx +++ b/site/src/components/Stack/Stack.tsx @@ -17,6 +17,10 @@ const useStyles = makeStyles((theme) => ({ flexDirection: ({ direction }: StyleProps) => direction, gap: ({ spacing }: StyleProps) => theme.spacing(spacing), alignItems: ({ alignItems }: StyleProps) => alignItems, + + [theme.breakpoints.down("sm")]: { + width: "100%", + }, }, })) diff --git a/site/src/components/TabPanel/TabPanel.tsx b/site/src/components/TabPanel/TabPanel.tsx index 25eddc7f508c1..3b031517666f4 100644 --- a/site/src/components/TabPanel/TabPanel.tsx +++ b/site/src/components/TabPanel/TabPanel.tsx @@ -82,7 +82,7 @@ const useStyles = makeStyles((theme) => ({ width: 890, }, }, - [theme.breakpoints.down("md")]: { + [theme.breakpoints.down("sm")]: { contentPanel: { width: 700, }, diff --git a/site/src/components/Table/Table.test.tsx b/site/src/components/Table/Table.test.tsx deleted file mode 100644 index eb9f20e29dc28..0000000000000 --- a/site/src/components/Table/Table.test.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { screen } from "@testing-library/react" -import { render } from "../../testHelpers/renderHelpers" -import { Column, Table } from "./Table" - -interface TestData { - name: string - description: string -} - -const columns: Column[] = [ - { - name: "Name", - key: "name", - }, - { - name: "Description", - key: "description", - // For description, we'll test out the custom renderer path - renderer: (field) => {"!!" + field + "!!"}, - }, -] - -const data: TestData[] = [{ name: "AName", description: "ADescription" }] -const emptyData: TestData[] = [] - -describe("Table", () => { - it("renders empty state if empty", async () => { - // Given - const emptyState =
Empty Table!
- const tableProps = { - title: "TitleTest", - data: emptyData, - columns, - emptyState, - } - - // When - render() - - // Then - // Since there are no items, our empty state should've rendered - const emptyTextElement = await screen.findByText("Empty Table!") - expect(emptyTextElement).toBeDefined() - }) - - it("renders title", async () => { - // Given - const tableProps = { - title: "TitleTest", - data: emptyData, - columns, - } - - // When - render(
) - - // Then - const titleElement = await screen.findByText("TitleTest") - expect(titleElement).toBeDefined() - }) - - it("renders data fields with default renderer if none provided", async () => { - // Given - const tableProps = { - title: "TitleTest", - data, - columns, - } - - // When - render(
) - - // Then - // Check that the 'name' was rendered, with the default renderer - const nameElement = await screen.findByText("AName") - expect(nameElement).toBeDefined() - // ...and the description used our custom rendered - const descriptionElement = await screen.findByText("!!ADescription!!") - expect(descriptionElement).toBeDefined() - }) -}) diff --git a/site/src/components/Table/Table.tsx b/site/src/components/Table/Table.tsx deleted file mode 100644 index 5b2c2b83d8ab5..0000000000000 --- a/site/src/components/Table/Table.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import Box from "@material-ui/core/Box" -import MuiTable from "@material-ui/core/Table" -import TableBody from "@material-ui/core/TableBody" -import TableCell from "@material-ui/core/TableCell" -import TableHead from "@material-ui/core/TableHead" -import TableRow from "@material-ui/core/TableRow" -import { ReactElement } from "react" -import { TableHeaders } from "../TableHeaders/TableHeaders" -import { TableTitle } from "../TableTitle/TableTitle" - -export type Column = { - [K in keyof T]: { - /** - * The field of type T that this column is associated with - */ - key: K - /** - * Friendly name of the field, shown in headers - */ - name: string - /** - * Custom render for the field inside the table - */ - renderer?: (field: T[K], data: T) => ReactElement - } -}[keyof T] - -export interface TableProps { - /** - * Title of the table - */ - title?: string - /** - * A list of columns, including the name and the key - */ - columns: Column[] - /** - * The actual data to show in the table - */ - data: T[] - /** - * Optional empty state UI when the data is empty - */ - emptyState?: ReactElement - /** - * Optional element to render row actions like delete, update, etc - */ - rowMenu?: (data: T) => ReactElement -} - -export const Table = ({ - columns, - data, - emptyState, - title, - rowMenu, -}: TableProps): ReactElement => { - const columnNames = columns.map(({ name }) => name) - const body = renderTableBody(data, columns, emptyState, rowMenu) - - return ( - - - {title && } - - - {body} - - ) -} - -/** - * Helper function to render the table data, falling back to an empty state if available - */ -const renderTableBody = ( - data: T[], - columns: Column[], - emptyState?: ReactElement, - rowMenu?: (data: T) => ReactElement, -) => { - if (data.length > 0) { - const rows = data.map((item: T, index) => { - const cells = columns.map((column) => { - if (column.renderer) { - return ( - - {column.renderer(item[column.key], item)} - - ) - } else { - return ( - {String(item[column.key]).toString()} - ) - } - }) - return ( - - {cells} - {rowMenu && {rowMenu(item)}} - - ) - }) - return {rows} - } else { - return ( - - - - {emptyState} - - - - ) - } -} diff --git a/site/src/components/TableContainer/TableContainer.tsx b/site/src/components/TableContainer/TableContainer.tsx new file mode 100644 index 0000000000000..989b26490b60a --- /dev/null +++ b/site/src/components/TableContainer/TableContainer.tsx @@ -0,0 +1,16 @@ +import { makeStyles } from "@material-ui/core/styles" +import { FC } from "react" + +export const TableContainer: FC = ({ children }) => { + const styles = useStyles() + + return
{children}
+} + +const useStyles = makeStyles((theme) => ({ + wrapper: { + overflowX: "auto", + borderRadius: theme.shape.borderRadius, + border: `1px solid ${theme.palette.divider}`, + }, +})) diff --git a/site/src/components/TableTitle/TableTitle.tsx b/site/src/components/TableTitle/TableTitle.tsx deleted file mode 100644 index d098919b9c07f..0000000000000 --- a/site/src/components/TableTitle/TableTitle.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import Box from "@material-ui/core/Box" -import { makeStyles } from "@material-ui/core/styles" -import TableCell from "@material-ui/core/TableCell" -import TableRow from "@material-ui/core/TableRow" -import Typography from "@material-ui/core/Typography" -import * as React from "react" - -export interface TableTitleProps { - /** A title to display */ - readonly title?: React.ReactNode - /** Arbitrary node to display to the right of the title. */ - readonly details?: React.ReactNode -} - -/** - * Component that encapsulates all of the pieces that sit on the top of a table. - */ -export const TableTitle: React.FC = ({ title, details }) => { - const styles = useStyles() - return ( - - - - {title && ( - - {title} - - )} - {details &&
{details}
} -
-
-
- ) -} - -const useStyles = makeStyles((theme) => ({ - cell: { - background: "none", - paddingTop: theme.spacing(2), - paddingBottom: theme.spacing(2), - }, - container: { - display: "flex", - alignItems: "center", - justifyContent: "space-between", - }, - title: { - fontSize: theme.typography.h5.fontSize, - fontWeight: 500, - color: theme.palette.text.primary, - textTransform: "none", - letterSpacing: "normal", - }, - details: { - alignItems: "center", - display: "flex", - justifyContent: "flex-end", - letterSpacing: "normal", - margin: `0 ${theme.spacing(2)}px`, - - [theme.breakpoints.down("sm")]: { - margin: `${theme.spacing(1)}px 0 0 0`, - }, - }, -})) diff --git a/site/src/components/Workspace/Workspace.tsx b/site/src/components/Workspace/Workspace.tsx index 07319da6a94d9..edddace4c5ed6 100644 --- a/site/src/components/Workspace/Workspace.tsx +++ b/site/src/components/Workspace/Workspace.tsx @@ -60,7 +60,7 @@ export const Workspace: FC = ({ + { statusBadge: { marginBottom: theme.spacing(3), }, + + actions: { + [theme.breakpoints.down("sm")]: { + flexDirection: "column", + }, + }, + firstColumnSpacer: { flex: 2, }, + secondColumnSpacer: { flex: `0 0 ${spacerWidth}px`, }, + layout: { alignItems: "flex-start", }, + main: { width: "100%", }, - sidebar: { - width: theme.spacing(32), - flexShrink: 0, - }, + timelineContents: { margin: 0, }, + timelineTable: { border: 0, }, diff --git a/site/src/components/WorkspaceActions/WorkspaceActions.tsx b/site/src/components/WorkspaceActions/WorkspaceActions.tsx index e4c2339194f8e..b800e35a790fc 100644 --- a/site/src/components/WorkspaceActions/WorkspaceActions.tsx +++ b/site/src/components/WorkspaceActions/WorkspaceActions.tsx @@ -104,7 +104,9 @@ export const WorkspaceActions: FC = ({ return ( {/* primary workspace CTA */} - {buttonMapping[actions.primary]} + + {buttonMapping[actions.primary]} + {actions.canCancel ? ( // cancel CTA <>{buttonMapping[ButtonTypesEnum.cancel]} @@ -152,7 +154,7 @@ const useStyles = makeStyles((theme) => ({ buttonContainer: { border: `1px solid ${theme.palette.divider}`, borderRadius: `${theme.shape.borderRadius}px`, - display: "inline-block", + display: "inline-flex", }, dropdownButton: { border: "none", @@ -164,6 +166,15 @@ const useStyles = makeStyles((theme) => ({ marginRight: "8px", }, }, + primaryCta: { + [theme.breakpoints.down("sm")]: { + width: "100%", + + "& > *": { + width: "100%", + }, + }, + }, popoverPaper: { padding: `${theme.spacing(2)}px ${theme.spacing(3)}px ${theme.spacing(3)}px`, }, diff --git a/site/src/components/WorkspaceScheduleButton/WorkspaceScheduleButton.tsx b/site/src/components/WorkspaceScheduleButton/WorkspaceScheduleButton.tsx index d99bdef3366ae..f643b5b4acd05 100644 --- a/site/src/components/WorkspaceScheduleButton/WorkspaceScheduleButton.tsx +++ b/site/src/components/WorkspaceScheduleButton/WorkspaceScheduleButton.tsx @@ -77,7 +77,7 @@ export const WorkspaceScheduleButton: React.FC = ( {canUpdateWorkspace && shouldDisplayPlusMinus(workspace) && ( - + = ( const useStyles = makeStyles((theme) => ({ wrapper: { - display: "inline-block", + display: "inline-flex", + alignItems: "center", border: `1px solid ${theme.palette.divider}`, borderRadius: `${theme.shape.borderRadius}px`, }, label: { borderRight: 0, - height: "100%", padding: "0 8px 0 16px", color: theme.palette.text.secondary, - // It is from the button props - minHeight: 42, + + [theme.breakpoints.down("sm")]: { + width: "100%", + display: "flex", + alignItems: "center", + }, + }, + actions: { + [theme.breakpoints.down("sm")]: { + marginLeft: "auto", + }, }, scheduleButton: { border: "none", borderLeft: `1px solid ${theme.palette.divider}`, borderRadius: `0px ${theme.shape.borderRadius}px ${theme.shape.borderRadius}px 0px`, + flexShrink: 0, }, iconButton: { borderRadius: 2, diff --git a/site/src/components/WorkspacesTable/WorkspacesTable.tsx b/site/src/components/WorkspacesTable/WorkspacesTable.tsx index c99559d2c6219..43fd8699821e9 100644 --- a/site/src/components/WorkspacesTable/WorkspacesTable.tsx +++ b/site/src/components/WorkspacesTable/WorkspacesTable.tsx @@ -1,6 +1,7 @@ import Table from "@material-ui/core/Table" import TableBody from "@material-ui/core/TableBody" import TableCell from "@material-ui/core/TableCell" +import TableContainer from "@material-ui/core/TableContainer" import TableHead from "@material-ui/core/TableHead" import TableRow from "@material-ui/core/TableRow" import { FC } from "react" @@ -23,20 +24,26 @@ export interface WorkspacesTableProps { export const WorkspacesTable: FC = ({ isLoading, workspaceRefs, filter }) => { return ( -
- - - {Language.name} - {Language.template} - {Language.lastBuiltBy} - {Language.version} - {Language.status} - - - - - - -
+ + + + + {Language.name} + {Language.template} + {Language.lastBuiltBy} + {Language.version} + {Language.status} + + + + + + +
+
) } diff --git a/site/src/theme/overrides.ts b/site/src/theme/overrides.ts index 9deac14e03fa0..265fad2a76d47 100644 --- a/site/src/theme/overrides.ts +++ b/site/src/theme/overrides.ts @@ -1,8 +1,9 @@ -import { PaletteOptions, SimplePaletteColorOptions } from "@material-ui/core/styles/createPalette" +import { Theme } from "@material-ui/core/styles" +import { SimplePaletteColorOptions } from "@material-ui/core/styles/createPalette" +import { Overrides } from "@material-ui/core/styles/overrides" import { MONOSPACE_FONT_FAMILY } from "./constants" -// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types -export const getOverrides = (palette: PaletteOptions) => { +export const getOverrides = ({ palette, breakpoints }: Theme): Overrides => { return { MuiCssBaseline: { "@global": { @@ -29,7 +30,7 @@ export const getOverrides = (palette: PaletteOptions) => { root: { // Prevents a loading button from collapsing! minHeight: 42, - fontWeight: "regular", + fontWeight: "normal", fontFamily: MONOSPACE_FONT_FAMILY, fontSize: 16, textTransform: "none", @@ -38,7 +39,7 @@ export const getOverrides = (palette: PaletteOptions) => { }, contained: { boxShadow: "none", - color: palette.text?.primary, + color: palette.text.primary, backgroundColor: "hsl(223, 27%, 3%)", "&:hover": { boxShadow: "none", @@ -71,10 +72,14 @@ export const getOverrides = (palette: PaletteOptions) => { textTransform: "uppercase", }, }, - MuiTable: { + MuiTableContainer: { root: { - // Gives the appearance of a border! borderRadius: 2, + border: `1px solid ${palette.divider}`, + }, + }, + MuiTable: { + root: { background: "hsla(222, 31%, 19%, .5)", "& td": { @@ -82,16 +87,22 @@ export const getOverrides = (palette: PaletteOptions) => { paddingBottom: 16, background: "transparent", }, + + [breakpoints.down("sm")]: { + // Random value based on visual adjustments. + // This is used to avoid line breaking on columns + minWidth: 1000, + }, }, }, MuiTableCell: { head: { - color: palette.text?.secondary, + color: palette.text.secondary, }, root: { fontFamily: MONOSPACE_FONT_FAMILY, fontSize: 16, - background: palette.background?.paper, + background: palette.background.paper, borderBottom: `1px solid ${palette.divider}`, padding: 8, // This targets the first+last td elements, and also the first+last elements @@ -116,7 +127,7 @@ export const getOverrides = (palette: PaletteOptions) => { }, "& input:-webkit-autofill": { - WebkitBoxShadow: `0 0 0 1000px ${palette.background?.paper} inset`, + WebkitBoxShadow: `0 0 0 1000px ${palette.background.paper} inset`, }, "&:hover .MuiOutlinedInput-notchedOutline": { borderColor: palette.divider, diff --git a/site/src/theme/theme.ts b/site/src/theme/theme.ts index 54d93cdc16edd..523dfd47031c6 100644 --- a/site/src/theme/theme.ts +++ b/site/src/theme/theme.ts @@ -1,6 +1,5 @@ import { createMuiTheme } from "@material-ui/core/styles" import { PaletteOptions } from "@material-ui/core/styles/createPalette" -import { Overrides } from "@material-ui/core/styles/overrides" import { borderRadius } from "./constants" import { getOverrides } from "./overrides" import { darkPalette } from "./palettes" @@ -8,15 +7,18 @@ import { props } from "./props" import { typography } from "./typography" const makeTheme = (palette: PaletteOptions) => { - return createMuiTheme({ + const theme = createMuiTheme({ palette, typography, shape: { borderRadius, }, props, - overrides: getOverrides(palette) as Overrides, }) + + theme.overrides = getOverrides(theme) + + return theme } export const dark = makeTheme(darkPalette)