chore: add wrapper components for conditional rendering (#4047)

* Add conditional wrappers

* Use wrappers in TemplatesPageView
This commit is contained in:
Presley Pizzo
2022-09-14 09:55:00 -04:00
committed by GitHub
parent 4f0417c6ad
commit b6712ffbee
5 changed files with 212 additions and 100 deletions
@@ -0,0 +1,36 @@
import { Story } from "@storybook/react"
import { ChooseOne, Cond } from "./ChooseOne"
export default {
title: "components/Conditionals/ChooseOne",
component: ChooseOne,
subcomponents: { Cond },
}
export const FirstIsTrue: Story = () => (
<ChooseOne>
<Cond condition>The first one shows.</Cond>
<Cond condition={false}>The second one does not show.</Cond>
</ChooseOne>
)
export const SecondIsTrue: Story = () => (
<ChooseOne>
<Cond condition={false}>The first one does not show.</Cond>
<Cond condition>The second one shows.</Cond>
</ChooseOne>
)
export const AllAreTrue: Story = () => (
<ChooseOne>
<Cond condition>Only the first one shows.</Cond>
<Cond condition>The second one does not show.</Cond>
</ChooseOne>
)
export const NoneAreTrue: Story = () => (
<ChooseOne>
<Cond condition={false}>The first one does not show.</Cond>
<Cond condition={false}>The second shows because it is the fallback.</Cond>
</ChooseOne>
)
@@ -0,0 +1,29 @@
import { Children, PropsWithChildren } from "react"
export interface CondProps {
condition: boolean
}
/**
* Wrapper component that attaches a condition to a child component so that ChooseOne can
* determine which child to render. The last Cond in a ChooseOne is the fallback case; set
* its `condition` to `true` to avoid confusion.
* @param condition boolean expression indicating whether the child should be rendered
* @returns child. Note that Cond alone does not enforce the condition; it should be used inside ChooseOne.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const Cond = ({ children, condition }: PropsWithChildren<CondProps>): JSX.Element => {
return <>{children}</>
}
/**
* Wrapper component for rendering exactly one of its children. Wrap each child in Cond to associate it
* with a condition under which it should be rendered. If no conditions are met, the final child
* will be rendered.
* @returns one of its children
*/
export const ChooseOne = ({ children }: PropsWithChildren): JSX.Element => {
const childArray = Children.toArray(children) as JSX.Element[]
const chosen = childArray.find((child) => child.props.condition)
return chosen ?? childArray[childArray.length - 1]
}
@@ -0,0 +1,19 @@
import { Story } from "@storybook/react"
import { Maybe, MaybeProps } from "./Maybe"
export default {
title: "components/Conditionals/Maybe",
component: Maybe,
}
const Template: Story<MaybeProps> = (args: MaybeProps) => <Maybe {...args}>Now you see me</Maybe>
export const ConditionIsTrue = Template.bind({})
ConditionIsTrue.args = {
condition: true,
}
export const ConditionIsFalse = Template.bind({})
ConditionIsFalse.args = {
condition: false,
}
@@ -0,0 +1,17 @@
import { PropsWithChildren } from "react"
export interface MaybeProps {
condition: boolean
}
/**
* Wrapper component for conditionally rendering a child component without using "curly brace mode."
* @param condition boolean expression that determines whether the child will be rendered
* @returns the child or null
*/
export const Maybe = ({
children,
condition,
}: PropsWithChildren<MaybeProps>): JSX.Element | null => {
return condition ? <>{children}</> : null
}
+111 -100
View File
@@ -8,6 +8,8 @@ import TableHead from "@material-ui/core/TableHead"
import TableRow from "@material-ui/core/TableRow"
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
import useTheme from "@material-ui/styles/useTheme"
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
import { Maybe } from "components/Conditionals/Maybe"
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
import { FC } from "react"
import { useTranslation } from "react-i18next"
@@ -103,7 +105,7 @@ export const TemplatesPageView: FC<React.PropsWithChildren<TemplatesPageViewProp
<TemplateHelpTooltip />
</Stack>
</PageHeaderTitle>
{props.templates && props.templates.length > 0 && (
<Maybe condition={Boolean(props.templates && props.templates.length > 0)}>
<PageHeaderSubtitle>
Choose a template to create a new workspace
{props.canCreateTemplate ? (
@@ -121,113 +123,122 @@ export const TemplatesPageView: FC<React.PropsWithChildren<TemplatesPageViewProp
"."
)}
</PageHeaderSubtitle>
)}
</Maybe>
</PageHeader>
{props.getOrganizationsError ? (
<ErrorSummary
error={props.getOrganizationsError}
defaultMessage={t("errors.getOrganizationsError")}
/>
) : props.getTemplatesError ? (
<ErrorSummary
error={props.getTemplatesError}
defaultMessage={t("errors.getTemplatesError")}
/>
) : (
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell width="50%">{Language.nameLabel}</TableCell>
<TableCell width="16%">{Language.usedByLabel}</TableCell>
<TableCell width="16%">{Language.lastUpdatedLabel}</TableCell>
<TableCell width="16%">{Language.createdByLabel}</TableCell>
<TableCell width="1%"></TableCell>
</TableRow>
</TableHead>
<TableBody>
{props.loading && <TableLoader />}
{empty ? (
<ChooseOne>
<Cond condition={Boolean(props.getOrganizationsError)}>
<ErrorSummary
error={props.getOrganizationsError}
defaultMessage={t("errors.getOrganizationsError")}
/>
</Cond>
<Cond condition={Boolean(props.getTemplatesError)}>
<ErrorSummary
error={props.getTemplatesError}
defaultMessage={t("errors.getTemplatesError")}
/>
</Cond>
<Cond condition>
<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 templates init" />}
/>
</TableCell>
<TableCell width="50%">{Language.nameLabel}</TableCell>
<TableCell width="16%">{Language.usedByLabel}</TableCell>
<TableCell width="16%">{Language.lastUpdatedLabel}</TableCell>
<TableCell width="16%">{Language.createdByLabel}</TableCell>
<TableCell width="1%"></TableCell>
</TableRow>
) : (
props.templates?.map((template) => {
const templatePageLink = `/templates/${template.name}`
const hasIcon = template.icon && template.icon !== ""
</TableHead>
<TableBody>
<Maybe condition={Boolean(props.loading)}>
<TableLoader />
</Maybe>
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
avatar={
hasIcon && (
<div className={styles.templateIconWrapper}>
<img alt="" src={template.icon} />
</div>
)
<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" />}
/>
</TableCellLink>
<TableCellLink to={templatePageLink}>
<span style={{ color: theme.palette.text.secondary }}>
{Language.developerCount(template.active_user_count)}
</span>
</TableCellLink>
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
<span style={{ color: theme.palette.text.secondary }}>
{createDayString(template.updated_at)}
</span>
</TableCellLink>
<TableCellLink to={templatePageLink}>
<span style={{ color: theme.palette.text.secondary }}>
{template.created_by_name}
</span>
</TableCellLink>
<TableCellLink to={templatePageLink}>
<div className={styles.arrowCell}>
<KeyboardArrowRight className={styles.arrowRight} />
</div>
</TableCellLink>
</TableCell>
</TableRow>
)
})
)}
</TableBody>
</Table>
</TableContainer>
)}
</Cond>
<Cond condition>
{props.templates?.map((template) => {
const templatePageLink = `/templates/${template.name}`
const hasIcon = template.icon && template.icon !== ""
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
avatar={
hasIcon && (
<div className={styles.templateIconWrapper}>
<img alt="" src={template.icon} />
</div>
)
}
/>
</TableCellLink>
<TableCellLink to={templatePageLink}>
<span style={{ color: theme.palette.text.secondary }}>
{Language.developerCount(template.active_user_count)}
</span>
</TableCellLink>
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
<span style={{ color: theme.palette.text.secondary }}>
{createDayString(template.updated_at)}
</span>
</TableCellLink>
<TableCellLink to={templatePageLink}>
<span style={{ color: theme.palette.text.secondary }}>
{template.created_by_name}
</span>
</TableCellLink>
<TableCellLink to={templatePageLink}>
<div className={styles.arrowCell}>
<KeyboardArrowRight className={styles.arrowRight} />
</div>
</TableCellLink>
</TableRow>
)
})}
</Cond>
</ChooseOne>
</TableBody>
</Table>
</TableContainer>
</Cond>
</ChooseOne>
</Margins>
)
}