mirror of
https://github.com/coder/coder.git
synced 2026-09-22 13:10:21 +08:00
refactor: Improve empty views (#5134)
This commit is contained in:
@@ -9,9 +9,9 @@ export interface EmptyStateProps {
|
||||
message: string
|
||||
/** Longer optional description to display below the message */
|
||||
description?: string | React.ReactNode
|
||||
descriptionClassName?: string
|
||||
cta?: ReactNode
|
||||
className?: string
|
||||
image?: ReactNode
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -25,14 +25,7 @@ export interface EmptyStateProps {
|
||||
export const EmptyState: FC<React.PropsWithChildren<EmptyStateProps>> = (
|
||||
props,
|
||||
) => {
|
||||
const {
|
||||
message,
|
||||
description,
|
||||
cta,
|
||||
descriptionClassName,
|
||||
className,
|
||||
...boxProps
|
||||
} = props
|
||||
const { message, description, cta, className, image, ...boxProps } = props
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
@@ -44,38 +37,42 @@ export const EmptyState: FC<React.PropsWithChildren<EmptyStateProps>> = (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="textSecondary"
|
||||
className={combineClasses([styles.description, descriptionClassName])}
|
||||
className={styles.description}
|
||||
>
|
||||
{description}
|
||||
</Typography>
|
||||
)}
|
||||
{cta && <div className={styles.cta}>{cta}</div>}
|
||||
{image}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(
|
||||
(theme) => ({
|
||||
root: {
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
minHeight: 300,
|
||||
padding: theme.spacing(3),
|
||||
},
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
textAlign: "center",
|
||||
minHeight: 360,
|
||||
padding: theme.spacing(10, 5),
|
||||
position: "relative",
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: theme.spacing(3),
|
||||
},
|
||||
description: {
|
||||
marginTop: theme.spacing(1.5),
|
||||
fontSize: theme.spacing(2),
|
||||
},
|
||||
cta: {
|
||||
marginTop: theme.spacing(4),
|
||||
},
|
||||
}),
|
||||
{ name: "EmptyState" },
|
||||
)
|
||||
title: {
|
||||
fontSize: theme.spacing(3),
|
||||
},
|
||||
|
||||
description: {
|
||||
marginTop: theme.spacing(1.5),
|
||||
fontSize: theme.spacing(2),
|
||||
lineHeight: "140%",
|
||||
maxWidth: theme.spacing(60),
|
||||
},
|
||||
|
||||
cta: {
|
||||
marginTop: theme.spacing(4),
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TableCell from "@material-ui/core/TableCell"
|
||||
import TableRow from "@material-ui/core/TableRow"
|
||||
import { FC } from "react"
|
||||
import {
|
||||
EmptyState,
|
||||
EmptyStateProps,
|
||||
} from "../../components/EmptyState/EmptyState"
|
||||
|
||||
export type TableEmptyProps = EmptyStateProps
|
||||
|
||||
export const TableEmpty: FC<TableEmptyProps> = (props) => {
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={999} className={styles.tableCell}>
|
||||
<EmptyState {...props} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(() => ({
|
||||
tableCell: {
|
||||
padding: "0 !important",
|
||||
},
|
||||
}))
|
||||
@@ -1,8 +1,9 @@
|
||||
import Button from "@material-ui/core/Button"
|
||||
import Link from "@material-ui/core/Link"
|
||||
import { makeStyles } from "@material-ui/core/styles"
|
||||
import TableCell from "@material-ui/core/TableCell"
|
||||
import TableRow from "@material-ui/core/TableRow"
|
||||
import AddCircleOutline from "@material-ui/icons/AddCircleOutline"
|
||||
import AddOutlined from "@material-ui/icons/AddOutlined"
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
|
||||
import { FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
@@ -24,6 +25,7 @@ export const WorkspacesTableBody: FC<
|
||||
React.PropsWithChildren<TableBodyProps>
|
||||
> = ({ isLoading, workspaceRefs, filter, isNonInitialPage }) => {
|
||||
const { t } = useTranslation("workspacesPage")
|
||||
const styles = useStyles()
|
||||
|
||||
return (
|
||||
<ChooseOne>
|
||||
@@ -32,7 +34,7 @@ export const WorkspacesTableBody: FC<
|
||||
</Cond>
|
||||
<Cond condition={!workspaceRefs || workspaceRefs.length === 0}>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<TableCell colSpan={999} className={styles.emptyTableCell}>
|
||||
<ChooseOne>
|
||||
<Cond condition={isNonInitialPage}>
|
||||
<EmptyState message={t("emptyPageMessage")} />
|
||||
@@ -44,6 +46,7 @@ export const WorkspacesTableBody: FC<
|
||||
}
|
||||
>
|
||||
<EmptyState
|
||||
className={styles.empty}
|
||||
message={t("emptyCreateWorkspaceMessage")}
|
||||
description={t("emptyCreateWorkspaceDescription")}
|
||||
cta={
|
||||
@@ -52,11 +55,16 @@ export const WorkspacesTableBody: FC<
|
||||
component={RouterLink}
|
||||
to="/templates"
|
||||
>
|
||||
<Button startIcon={<AddCircleOutline />}>
|
||||
<Button startIcon={<AddOutlined />}>
|
||||
{t("createFromTemplateButton")}
|
||||
</Button>
|
||||
</Link>
|
||||
}
|
||||
image={
|
||||
<div className={styles.emptyImage}>
|
||||
<img src="/empty/workspaces.webp" alt="" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Cond>
|
||||
<Cond>
|
||||
@@ -75,3 +83,25 @@ export const WorkspacesTableBody: FC<
|
||||
</ChooseOne>
|
||||
)
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyTableCell: {
|
||||
padding: "0 !important",
|
||||
},
|
||||
|
||||
empty: {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
|
||||
emptyImage: {
|
||||
maxWidth: "50%",
|
||||
height: theme.spacing(34),
|
||||
overflow: "hidden",
|
||||
marginTop: theme.spacing(6),
|
||||
opacity: 0.85,
|
||||
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -11,6 +11,7 @@ import useTheme from "@material-ui/styles/useTheme"
|
||||
import { AlertBanner } from "components/AlertBanner/AlertBanner"
|
||||
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
|
||||
import { Maybe } from "components/Conditionals/Maybe"
|
||||
import { TableEmpty } from "components/TableEmpty/TableEmpty"
|
||||
import { FC } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { useNavigate } from "react-router-dom"
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
import * as TypesGen from "../../api/typesGenerated"
|
||||
import { AvatarData } from "../../components/AvatarData/AvatarData"
|
||||
import { CodeExample } from "../../components/CodeExample/CodeExample"
|
||||
import { EmptyState } from "../../components/EmptyState/EmptyState"
|
||||
import { Margins } from "../../components/Margins/Margins"
|
||||
import {
|
||||
PageHeader,
|
||||
@@ -174,20 +174,21 @@ export const TemplatesPageView: FC<
|
||||
|
||||
<ChooseOne>
|
||||
<Cond condition={empty}>
|
||||
<TableRow>
|
||||
<TableCell colSpan={999}>
|
||||
<EmptyState
|
||||
message={Language.emptyMessage}
|
||||
description={
|
||||
props.canCreateTemplate
|
||||
? Language.emptyDescription
|
||||
: Language.emptyViewNoPerms
|
||||
}
|
||||
descriptionClassName={styles.emptyDescription}
|
||||
cta={<CodeExample code="coder templates init" />}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableEmpty
|
||||
className={styles.empty}
|
||||
message={Language.emptyMessage}
|
||||
description={
|
||||
props.canCreateTemplate
|
||||
? Language.emptyDescription
|
||||
: Language.emptyViewNoPerms
|
||||
}
|
||||
cta={<CodeExample code="coder templates init" />}
|
||||
image={
|
||||
<div className={styles.emptyImage}>
|
||||
<img src="/empty/templates.webp" alt="" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Cond>
|
||||
<Cond>
|
||||
{props.templates?.map((template) => {
|
||||
@@ -287,9 +288,6 @@ export const TemplatesPageView: FC<
|
||||
}
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
emptyDescription: {
|
||||
maxWidth: theme.spacing(62),
|
||||
},
|
||||
clickableTableRow: {
|
||||
"&:hover td": {
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
@@ -321,4 +319,18 @@ const useStyles = makeStyles((theme) => ({
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
empty: {
|
||||
paddingBottom: 0,
|
||||
},
|
||||
|
||||
emptyImage: {
|
||||
maxWidth: "50%",
|
||||
height: theme.spacing(40),
|
||||
overflow: "hidden",
|
||||
opacity: 0.85,
|
||||
|
||||
"& img": {
|
||||
maxWidth: "100%",
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
Reference in New Issue
Block a user