mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: Responsive for workspaces and workspace page (#3189)
This commit is contained in:
@@ -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<BuildsTableProps> = ({ builds, className }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<Table className={className} data-testid="builds-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="20%">{Language.actionLabel}</TableCell>
|
||||
<TableCell width="20%">{Language.durationLabel}</TableCell>
|
||||
<TableCell width="40%">{Language.startedAtLabel}</TableCell>
|
||||
<TableCell width="20%">{Language.statusLabel}</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
{builds &&
|
||||
builds.map((build) => {
|
||||
const status = getDisplayWorkspaceBuildStatus(theme, build)
|
||||
const buildPageLink = `/@${username}/${workspaceName}/builds/${build.build_number}`
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
key={build.id}
|
||||
data-testid={`build-${build.id}`}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
navigate(buildPageLink)
|
||||
}
|
||||
}}
|
||||
className={styles.clickableTableRow}
|
||||
>
|
||||
<TableCellLink to={buildPageLink}>{build.transition}</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{displayWorkspaceBuildDuration(build)}
|
||||
</span>
|
||||
</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{new Date(build.created_at).toLocaleString()}
|
||||
</span>
|
||||
</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<span style={{ color: status.color }} className={styles.status}>
|
||||
{status.status}
|
||||
</span>
|
||||
</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<div className={styles.arrowCell}>
|
||||
<KeyboardArrowRight className={styles.arrowRight} />
|
||||
</div>
|
||||
</TableCellLink>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
|
||||
{builds && builds.length === 0 && (
|
||||
<TableContainer className={className}>
|
||||
<Table data-testid="builds-table" aria-describedby="builds table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Box p={4}>
|
||||
<EmptyState message={Language.emptyMessage} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell width="20%">{Language.actionLabel}</TableCell>
|
||||
<TableCell width="20%">{Language.durationLabel}</TableCell>
|
||||
<TableCell width="40%">{Language.startedAtLabel}</TableCell>
|
||||
<TableCell width="20%">{Language.statusLabel}</TableCell>
|
||||
<TableCell></TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
{builds &&
|
||||
builds.map((build) => {
|
||||
const status = getDisplayWorkspaceBuildStatus(theme, build)
|
||||
const buildPageLink = `/@${username}/${workspaceName}/builds/${build.build_number}`
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
key={build.id}
|
||||
data-testid={`build-${build.id}`}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
navigate(buildPageLink)
|
||||
}
|
||||
}}
|
||||
className={styles.clickableTableRow}
|
||||
>
|
||||
<TableCellLink to={buildPageLink}>{build.transition}</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{displayWorkspaceBuildDuration(build)}
|
||||
</span>
|
||||
</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{new Date(build.created_at).toLocaleString()}
|
||||
</span>
|
||||
</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<span style={{ color: status.color }} className={styles.status}>
|
||||
{status.status}
|
||||
</span>
|
||||
</TableCellLink>
|
||||
<TableCellLink to={buildPageLink}>
|
||||
<div className={styles.arrowCell}>
|
||||
<KeyboardArrowRight className={styles.arrowRight} />
|
||||
</div>
|
||||
</TableCellLink>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
|
||||
{builds && builds.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Box p={4}>
|
||||
<EmptyState message={Language.emptyMessage} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,11 @@ export const PageHeader: React.FC<PageHeaderProps> = ({ children, actions, class
|
||||
return (
|
||||
<div className={combineClasses([styles.root, className])}>
|
||||
<hgroup>{children}</hgroup>
|
||||
<Stack direction="row" className={styles.actions}>
|
||||
{actions}
|
||||
</Stack>
|
||||
{actions && (
|
||||
<Stack direction="row" className={styles.actions}>
|
||||
{actions}
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -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%",
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -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<ResourcesProps> = ({
|
||||
{getResourcesError ? (
|
||||
{ getResourcesError }
|
||||
) : (
|
||||
<Table className={styles.table}>
|
||||
<TableHead>
|
||||
<TableHeaderRow>
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={0.5} alignItems="center">
|
||||
{Language.resourceLabel}
|
||||
<ResourcesHelpTooltip />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell className={styles.agentColumn}>
|
||||
<Stack direction="row" spacing={0.5} alignItems="center">
|
||||
{Language.agentLabel}
|
||||
<AgentHelpTooltip />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
{canUpdateWorkspace && <TableCell></TableCell>}
|
||||
</TableHeaderRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{resources?.map((resource) => {
|
||||
{
|
||||
/* We need to initialize the agents to display the resource */
|
||||
}
|
||||
const agents = resource.agents ?? [null]
|
||||
const resourceName = (
|
||||
<AvatarData
|
||||
avatar={<ResourceAvatar type={resource.type} />}
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
/>
|
||||
)
|
||||
|
||||
return agents.map((agent, agentIndex) => {
|
||||
<TableContainer className={styles.tableContainer}>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableHeaderRow>
|
||||
<TableCell>
|
||||
<Stack direction="row" spacing={0.5} alignItems="center">
|
||||
{Language.resourceLabel}
|
||||
<ResourcesHelpTooltip />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
<TableCell className={styles.agentColumn}>
|
||||
<Stack direction="row" spacing={0.5} alignItems="center">
|
||||
{Language.agentLabel}
|
||||
<AgentHelpTooltip />
|
||||
</Stack>
|
||||
</TableCell>
|
||||
{canUpdateWorkspace && <TableCell></TableCell>}
|
||||
</TableHeaderRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{resources?.map((resource) => {
|
||||
{
|
||||
/* If there is no agent, just display the resource name */
|
||||
/* We need to initialize the agents to display the resource */
|
||||
}
|
||||
if (!agent) {
|
||||
const agents = resource.agents ?? [null]
|
||||
const resourceName = (
|
||||
<AvatarData
|
||||
avatar={<ResourceAvatar type={resource.type} />}
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
/>
|
||||
)
|
||||
|
||||
return agents.map((agent, agentIndex) => {
|
||||
{
|
||||
/* If there is no agent, just display the resource name */
|
||||
}
|
||||
if (!agent) {
|
||||
return (
|
||||
<TableRow key={`${resource.id}-${agentIndex}`}>
|
||||
<TableCell>{resourceName}</TableCell>
|
||||
<TableCell colSpan={3}></TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const agentStatus = getDisplayAgentStatus(theme, agent)
|
||||
return (
|
||||
<TableRow key={`${resource.id}-${agentIndex}`}>
|
||||
<TableCell>{resourceName}</TableCell>
|
||||
<TableCell colSpan={3}></TableCell>
|
||||
<TableRow key={`${resource.id}-${agent.id}`}>
|
||||
{/* 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 && (
|
||||
<TableCell className={styles.resourceNameCell} rowSpan={agents.length}>
|
||||
{resourceName}
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
<TableCell className={styles.agentColumn}>
|
||||
{agent.name}
|
||||
<div className={styles.agentInfo}>
|
||||
<span className={styles.operatingSystem}>{agent.operating_system}</span>
|
||||
<span style={{ color: agentStatus.color }} className={styles.status}>
|
||||
{agentStatus.status}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<>
|
||||
{canUpdateWorkspace && agent.status === "connected" && (
|
||||
<div className={styles.accessLinks}>
|
||||
<SSHButton workspaceName={workspace.name} agentName={agent.name} />
|
||||
<TerminalLink
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
userName={workspace.owner_name}
|
||||
/>
|
||||
{agent.apps.map((app) => (
|
||||
<AppLink
|
||||
key={app.name}
|
||||
appIcon={app.icon}
|
||||
appName={app.name}
|
||||
userName={workspace.owner_name}
|
||||
workspaceName={workspace.name}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const agentStatus = getDisplayAgentStatus(theme, agent)
|
||||
return (
|
||||
<TableRow key={`${resource.id}-${agent.id}`}>
|
||||
{/* 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 && (
|
||||
<TableCell className={styles.resourceNameCell} rowSpan={agents.length}>
|
||||
{resourceName}
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
<TableCell className={styles.agentColumn}>
|
||||
{agent.name}
|
||||
<div className={styles.agentInfo}>
|
||||
<span className={styles.operatingSystem}>{agent.operating_system}</span>
|
||||
<span style={{ color: agentStatus.color }} className={styles.status}>
|
||||
{agentStatus.status}
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<>
|
||||
{canUpdateWorkspace && agent.status === "connected" && (
|
||||
<div className={styles.accessLinks}>
|
||||
<SSHButton workspaceName={workspace.name} agentName={agent.name} />
|
||||
<TerminalLink
|
||||
workspaceName={workspace.name}
|
||||
agentName={agent.name}
|
||||
userName={workspace.owner_name}
|
||||
/>
|
||||
{agent.apps.map((app) => (
|
||||
<AppLink
|
||||
key={app.name}
|
||||
appIcon={app.icon}
|
||||
appName={app.name}
|
||||
userName={workspace.owner_name}
|
||||
workspaceName={workspace.name}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
})
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -152,7 +155,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
|
||||
table: {
|
||||
tableContainer: {
|
||||
border: 0,
|
||||
},
|
||||
|
||||
|
||||
@@ -166,6 +166,7 @@ const useStyles = makeStyles<Theme, StyleProps>((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,
|
||||
|
||||
@@ -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%",
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
width: 890,
|
||||
},
|
||||
},
|
||||
[theme.breakpoints.down("md")]: {
|
||||
[theme.breakpoints.down("sm")]: {
|
||||
contentPanel: {
|
||||
width: 700,
|
||||
},
|
||||
|
||||
@@ -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<TestData>[] = [
|
||||
{
|
||||
name: "Name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
name: "Description",
|
||||
key: "description",
|
||||
// For description, we'll test out the custom renderer path
|
||||
renderer: (field) => <span>{"!!" + field + "!!"}</span>,
|
||||
},
|
||||
]
|
||||
|
||||
const data: TestData[] = [{ name: "AName", description: "ADescription" }]
|
||||
const emptyData: TestData[] = []
|
||||
|
||||
describe("Table", () => {
|
||||
it("renders empty state if empty", async () => {
|
||||
// Given
|
||||
const emptyState = <div>Empty Table!</div>
|
||||
const tableProps = {
|
||||
title: "TitleTest",
|
||||
data: emptyData,
|
||||
columns,
|
||||
emptyState,
|
||||
}
|
||||
|
||||
// When
|
||||
render(<Table {...tableProps} />)
|
||||
|
||||
// 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(<Table {...tableProps} />)
|
||||
|
||||
// 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(<Table {...tableProps} />)
|
||||
|
||||
// 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()
|
||||
})
|
||||
})
|
||||
@@ -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<T> = {
|
||||
[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<T> {
|
||||
/**
|
||||
* Title of the table
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* A list of columns, including the name and the key
|
||||
*/
|
||||
columns: Column<T>[]
|
||||
/**
|
||||
* 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 = <T,>({
|
||||
columns,
|
||||
data,
|
||||
emptyState,
|
||||
title,
|
||||
rowMenu,
|
||||
}: TableProps<T>): ReactElement => {
|
||||
const columnNames = columns.map(({ name }) => name)
|
||||
const body = renderTableBody(data, columns, emptyState, rowMenu)
|
||||
|
||||
return (
|
||||
<MuiTable>
|
||||
<TableHead>
|
||||
{title && <TableTitle title={title} />}
|
||||
<TableHeaders columns={columnNames} hasMenu={!!rowMenu} />
|
||||
</TableHead>
|
||||
{body}
|
||||
</MuiTable>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to render the table data, falling back to an empty state if available
|
||||
*/
|
||||
const renderTableBody = <T,>(
|
||||
data: T[],
|
||||
columns: Column<T>[],
|
||||
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 (
|
||||
<TableCell key={String(column.key)}>
|
||||
{column.renderer(item[column.key], item)}
|
||||
</TableCell>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<TableCell key={String(column.key)}>{String(item[column.key]).toString()}</TableCell>
|
||||
)
|
||||
}
|
||||
})
|
||||
return (
|
||||
<TableRow key={index}>
|
||||
{cells}
|
||||
{rowMenu && <TableCell>{rowMenu(item)}</TableCell>}
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
return <TableBody>{rows}</TableBody>
|
||||
} else {
|
||||
return (
|
||||
<TableBody>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Box p={4}>{emptyState}</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import { FC } from "react"
|
||||
|
||||
export const TableContainer: FC = ({ children }) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return <div className={styles.wrapper}>{children}</div>
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
wrapper: {
|
||||
overflowX: "auto",
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
border: `1px solid ${theme.palette.divider}`,
|
||||
},
|
||||
}))
|
||||
@@ -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<TableTitleProps> = ({ title, details }) => {
|
||||
const styles = useStyles()
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={9999} className={styles.cell}>
|
||||
<Box className={`${styles.container} ${details ? "-details" : ""}`}>
|
||||
{title && (
|
||||
<Typography variant="h6" className={styles.title}>
|
||||
{title}
|
||||
</Typography>
|
||||
)}
|
||||
{details && <div className={styles.details}>{details}</div>}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
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`,
|
||||
},
|
||||
},
|
||||
}))
|
||||
@@ -60,7 +60,7 @@ export const Workspace: FC<WorkspaceProps> = ({
|
||||
<Margins>
|
||||
<PageHeader
|
||||
actions={
|
||||
<Stack direction="row" spacing={1}>
|
||||
<Stack direction="row" spacing={1} className={styles.actions}>
|
||||
<WorkspaceScheduleButton
|
||||
workspace={workspace}
|
||||
onDeadlineMinus={scheduleProps.onDeadlineMinus}
|
||||
@@ -123,25 +123,33 @@ export const useStyles = makeStyles((theme) => {
|
||||
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,
|
||||
},
|
||||
|
||||
@@ -104,7 +104,9 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
|
||||
return (
|
||||
<span className={styles.buttonContainer}>
|
||||
{/* primary workspace CTA */}
|
||||
<span data-testid="primary-cta">{buttonMapping[actions.primary]}</span>
|
||||
<span data-testid="primary-cta" className={styles.primaryCta}>
|
||||
{buttonMapping[actions.primary]}
|
||||
</span>
|
||||
{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`,
|
||||
},
|
||||
|
||||
@@ -77,7 +77,7 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
|
||||
<span className={styles.label}>
|
||||
<WorkspaceScheduleLabel workspace={workspace} />
|
||||
{canUpdateWorkspace && shouldDisplayPlusMinus(workspace) && (
|
||||
<span>
|
||||
<span className={styles.actions}>
|
||||
<IconButton
|
||||
className={styles.iconButton}
|
||||
size="small"
|
||||
@@ -136,22 +136,32 @@ export const WorkspaceScheduleButton: React.FC<WorkspaceScheduleButtonProps> = (
|
||||
|
||||
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,
|
||||
|
||||
@@ -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<WorkspacesTableProps> = ({ isLoading, workspaceRefs, filter }) => {
|
||||
return (
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="25%">{Language.name}</TableCell>
|
||||
<TableCell width="20%">{Language.template}</TableCell>
|
||||
<TableCell width="25%">{Language.lastBuiltBy}</TableCell>
|
||||
<TableCell width="15%">{Language.version}</TableCell>
|
||||
<TableCell width="15%">{Language.status}</TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<WorkspacesTableBody isLoading={isLoading} workspaceRefs={workspaceRefs} filter={filter} />
|
||||
</TableBody>
|
||||
</Table>
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="25%">{Language.name}</TableCell>
|
||||
<TableCell width="20%">{Language.template}</TableCell>
|
||||
<TableCell width="25%">{Language.lastBuiltBy}</TableCell>
|
||||
<TableCell width="15%">{Language.version}</TableCell>
|
||||
<TableCell width="15%">{Language.status}</TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
<WorkspacesTableBody
|
||||
isLoading={isLoading}
|
||||
workspaceRefs={workspaceRefs}
|
||||
filter={filter}
|
||||
/>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
+21
-10
@@ -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",
|
||||
},
|
||||
},
|
||||
MuiTableContainer: {
|
||||
root: {
|
||||
borderRadius: 2,
|
||||
border: `1px solid ${palette.divider}`,
|
||||
},
|
||||
},
|
||||
MuiTable: {
|
||||
root: {
|
||||
// Gives the appearance of a border!
|
||||
borderRadius: 2,
|
||||
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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user