mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat: make template pages responsive (#3232)
This commit is contained in:
@ -1,16 +0,0 @@
|
||||
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}`,
|
||||
},
|
||||
}))
|
@ -2,6 +2,7 @@ import { makeStyles } 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 { AvatarData } from "components/AvatarData/AvatarData"
|
||||
@ -26,70 +27,72 @@ export const TemplateResourcesTable: FC<TemplateResourcesProps> = ({ resources }
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<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>
|
||||
</TableHeaderRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{resources.map((resource) => {
|
||||
// We need to initialize the agents to display the resource
|
||||
const agents = resource.agents ?? [null]
|
||||
return agents.map((agent, agentIndex) => {
|
||||
// If there is no agent, just display the resource name
|
||||
if (!agent) {
|
||||
<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>
|
||||
</TableHeaderRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{resources.map((resource) => {
|
||||
// We need to initialize the agents to display the resource
|
||||
const agents = resource.agents ?? [null]
|
||||
return agents.map((agent, agentIndex) => {
|
||||
// If there is no agent, just display the resource name
|
||||
if (!agent) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell className={styles.resourceNameCell}>
|
||||
<AvatarData
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
avatar={<ResourceAvatar type={resource.type} />}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell colSpan={3}></TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell className={styles.resourceNameCell}>
|
||||
<AvatarData
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
avatar={<ResourceAvatar type={resource.type} />}
|
||||
/>
|
||||
<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}>
|
||||
<AvatarData
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
avatar={<ResourceAvatar type={resource.type} />}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
<TableCell className={styles.agentColumn}>
|
||||
{agent.name}
|
||||
<span className={styles.operatingSystem}>{agent.operating_system}</span>
|
||||
</TableCell>
|
||||
<TableCell colSpan={3}></TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
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}>
|
||||
<AvatarData
|
||||
title={resource.name}
|
||||
subtitle={resource.type}
|
||||
highlightTitle
|
||||
avatar={<ResourceAvatar type={resource.type} />}
|
||||
/>
|
||||
</TableCell>
|
||||
)}
|
||||
|
||||
<TableCell className={styles.agentColumn}>
|
||||
{agent.name}
|
||||
<span className={styles.operatingSystem}>{agent.operating_system}</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
})
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
||||
@ -98,7 +101,7 @@ const useStyles = makeStyles((theme) => ({
|
||||
margin: 0,
|
||||
},
|
||||
|
||||
table: {
|
||||
tableContainer: {
|
||||
border: 0,
|
||||
},
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
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 { TableContainer } from "components/TableContainer/TableContainer"
|
||||
import { FC } from "react"
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { UsersTableBody } from "./UsersTableBody"
|
||||
|
@ -3,6 +3,7 @@ import { 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"
|
||||
@ -27,48 +28,50 @@ export const VersionsTable: FC<VersionsTableProps> = ({ versions }) => {
|
||||
const theme: Theme = useTheme()
|
||||
|
||||
return (
|
||||
<Table data-testid="versions-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell width="30%">{Language.nameLabel}</TableCell>
|
||||
<TableCell width="30%">{Language.createdAtLabel}</TableCell>
|
||||
<TableCell width="40%">{Language.createdByLabel}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
{versions &&
|
||||
versions
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((version) => {
|
||||
return (
|
||||
<TableRow key={version.id} data-testid={`version-${version.id}`}>
|
||||
<TableCell>{version.name}</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{new Date(version.created_at).toLocaleString()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{version.created_by_name}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
|
||||
{versions && versions.length === 0 && (
|
||||
<TableContainer>
|
||||
<Table data-testid="versions-table">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Box p={4}>
|
||||
<EmptyState message={Language.emptyMessage} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
<TableCell width="30%">{Language.nameLabel}</TableCell>
|
||||
<TableCell width="30%">{Language.createdAtLabel}</TableCell>
|
||||
<TableCell width="40%">{Language.createdByLabel}</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{isLoading && <TableLoader />}
|
||||
{versions &&
|
||||
versions
|
||||
.slice()
|
||||
.reverse()
|
||||
.map((version) => {
|
||||
return (
|
||||
<TableRow key={version.id} data-testid={`version-${version.id}`}>
|
||||
<TableCell>{version.name}</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{new Date(version.created_at).toLocaleString()}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<span style={{ color: theme.palette.text.secondary }}>
|
||||
{version.created_by_name}
|
||||
</span>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
|
||||
{versions && versions.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<Box p={4}>
|
||||
<EmptyState message={Language.emptyMessage} />
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)
|
||||
}
|
||||
|
@ -26,6 +26,14 @@ AllStates.args = {
|
||||
],
|
||||
}
|
||||
|
||||
export const SmallViewport = Template.bind({})
|
||||
SmallViewport.args = {
|
||||
...AllStates.args,
|
||||
}
|
||||
SmallViewport.parameters = {
|
||||
chromatic: { viewports: [600] },
|
||||
}
|
||||
|
||||
export const EmptyCanCreate = Template.bind({})
|
||||
EmptyCanCreate.args = {
|
||||
canCreateTemplate: true,
|
||||
|
@ -3,6 +3,7 @@ import { fade, makeStyles } 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"
|
||||
@ -94,73 +95,77 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
|
||||
)}
|
||||
</PageHeader>
|
||||
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>{Language.nameLabel}</TableCell>
|
||||
<TableCell>{Language.usedByLabel}</TableCell>
|
||||
<TableCell>{Language.lastUpdatedLabel}</TableCell>
|
||||
<TableCell>{Language.createdByLabel}</TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.loading && <TableLoader />}
|
||||
{!props.loading && !props.templates?.length && (
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message={Language.emptyMessage}
|
||||
description={
|
||||
props.canCreateTemplate ? Language.emptyDescription : Language.emptyViewNoPerms
|
||||
}
|
||||
descriptionClassName={styles.emptyDescription}
|
||||
cta={<CodeExample code="coder template init" />}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{Language.nameLabel}</TableCell>
|
||||
<TableCell>{Language.usedByLabel}</TableCell>
|
||||
<TableCell>{Language.lastUpdatedLabel}</TableCell>
|
||||
<TableCell>{Language.createdByLabel}</TableCell>
|
||||
<TableCell width="1%"></TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
{props.templates?.map((template) => {
|
||||
const templatePageLink = `/templates/${template.name}`
|
||||
return (
|
||||
<TableRow
|
||||
key={template.id}
|
||||
hover
|
||||
data-testid={`template-${template.id}`}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
navigate(templatePageLink)
|
||||
}
|
||||
}}
|
||||
className={styles.clickableTableRow}
|
||||
>
|
||||
<TableCellLink to={templatePageLink}>
|
||||
<AvatarData
|
||||
title={template.name}
|
||||
subtitle={template.description}
|
||||
highlightTitle
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.loading && <TableLoader />}
|
||||
{!props.loading && !props.templates?.length && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message={Language.emptyMessage}
|
||||
description={
|
||||
props.canCreateTemplate
|
||||
? Language.emptyDescription
|
||||
: Language.emptyViewNoPerms
|
||||
}
|
||||
descriptionClassName={styles.emptyDescription}
|
||||
cta={<CodeExample code="coder template init" />}
|
||||
/>
|
||||
</TableCellLink>
|
||||
|
||||
<TableCellLink to={templatePageLink}>
|
||||
{Language.developerCount(template.workspace_owner_count)}
|
||||
</TableCellLink>
|
||||
|
||||
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
|
||||
{createDayString(template.updated_at)}
|
||||
</TableCellLink>
|
||||
<TableCellLink to={templatePageLink}>{template.created_by_name}</TableCellLink>
|
||||
<TableCellLink to={templatePageLink}>
|
||||
<div className={styles.arrowCell}>
|
||||
<KeyboardArrowRight className={styles.arrowRight} />
|
||||
</div>
|
||||
</TableCellLink>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
{props.templates?.map((template) => {
|
||||
const templatePageLink = `/templates/${template.name}`
|
||||
return (
|
||||
<TableRow
|
||||
key={template.id}
|
||||
hover
|
||||
data-testid={`template-${template.id}`}
|
||||
tabIndex={0}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
navigate(templatePageLink)
|
||||
}
|
||||
}}
|
||||
className={styles.clickableTableRow}
|
||||
>
|
||||
<TableCellLink to={templatePageLink}>
|
||||
<AvatarData
|
||||
title={template.name}
|
||||
subtitle={template.description}
|
||||
highlightTitle
|
||||
/>
|
||||
</TableCellLink>
|
||||
|
||||
<TableCellLink to={templatePageLink}>
|
||||
{Language.developerCount(template.workspace_owner_count)}
|
||||
</TableCellLink>
|
||||
|
||||
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
|
||||
{createDayString(template.updated_at)}
|
||||
</TableCellLink>
|
||||
<TableCellLink to={templatePageLink}>{template.created_by_name}</TableCellLink>
|
||||
<TableCellLink to={templatePageLink}>
|
||||
<div className={styles.arrowCell}>
|
||||
<KeyboardArrowRight className={styles.arrowRight} />
|
||||
</div>
|
||||
</TableCellLink>
|
||||
</TableRow>
|
||||
)
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Margins>
|
||||
)
|
||||
}
|
||||
|
@ -17,6 +17,14 @@ Admin.args = {
|
||||
canEditUsers: true,
|
||||
}
|
||||
|
||||
export const SmallViewport = Template.bind({})
|
||||
SmallViewport.args = {
|
||||
...Admin.args,
|
||||
}
|
||||
SmallViewport.parameters = {
|
||||
chromatic: { viewports: [600] },
|
||||
}
|
||||
|
||||
export const Member = Template.bind({})
|
||||
Member.args = { ...Admin.args, canCreateUser: false, canEditUsers: false }
|
||||
|
||||
|
Reference in New Issue
Block a user