Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b6712ff

Browse files
authored
chore: add wrapper components for conditional rendering (#4047)
* Add conditional wrappers * Use wrappers in TemplatesPageView
1 parent 4f0417c commit b6712ff

File tree

5 files changed

+209
-97
lines changed

5 files changed

+209
-97
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { Story } from "@storybook/react"
2+
import { ChooseOne, Cond } from "./ChooseOne"
3+
4+
export default {
5+
title: "components/Conditionals/ChooseOne",
6+
component: ChooseOne,
7+
subcomponents: { Cond },
8+
}
9+
10+
export const FirstIsTrue: Story = () => (
11+
<ChooseOne>
12+
<Cond condition>The first one shows.</Cond>
13+
<Cond condition={false}>The second one does not show.</Cond>
14+
</ChooseOne>
15+
)
16+
17+
export const SecondIsTrue: Story = () => (
18+
<ChooseOne>
19+
<Cond condition={false}>The first one does not show.</Cond>
20+
<Cond condition>The second one shows.</Cond>
21+
</ChooseOne>
22+
)
23+
24+
export const AllAreTrue: Story = () => (
25+
<ChooseOne>
26+
<Cond condition>Only the first one shows.</Cond>
27+
<Cond condition>The second one does not show.</Cond>
28+
</ChooseOne>
29+
)
30+
31+
export const NoneAreTrue: Story = () => (
32+
<ChooseOne>
33+
<Cond condition={false}>The first one does not show.</Cond>
34+
<Cond condition={false}>The second shows because it is the fallback.</Cond>
35+
</ChooseOne>
36+
)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { Children, PropsWithChildren } from "react"
2+
3+
export interface CondProps {
4+
condition: boolean
5+
}
6+
7+
/**
8+
* Wrapper component that attaches a condition to a child component so that ChooseOne can
9+
* determine which child to render. The last Cond in a ChooseOne is the fallback case; set
10+
* its `condition` to `true` to avoid confusion.
11+
* @param condition boolean expression indicating whether the child should be rendered
12+
* @returns child. Note that Cond alone does not enforce the condition; it should be used inside ChooseOne.
13+
*/
14+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
15+
export const Cond = ({ children, condition }: PropsWithChildren<CondProps>): JSX.Element => {
16+
return <>{children}</>
17+
}
18+
19+
/**
20+
* Wrapper component for rendering exactly one of its children. Wrap each child in Cond to associate it
21+
* with a condition under which it should be rendered. If no conditions are met, the final child
22+
* will be rendered.
23+
* @returns one of its children
24+
*/
25+
export const ChooseOne = ({ children }: PropsWithChildren): JSX.Element => {
26+
const childArray = Children.toArray(children) as JSX.Element[]
27+
const chosen = childArray.find((child) => child.props.condition)
28+
return chosen ?? childArray[childArray.length - 1]
29+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Story } from "@storybook/react"
2+
import { Maybe, MaybeProps } from "./Maybe"
3+
4+
export default {
5+
title: "components/Conditionals/Maybe",
6+
component: Maybe,
7+
}
8+
9+
const Template: Story<MaybeProps> = (args: MaybeProps) => <Maybe {...args}>Now you see me</Maybe>
10+
11+
export const ConditionIsTrue = Template.bind({})
12+
ConditionIsTrue.args = {
13+
condition: true,
14+
}
15+
16+
export const ConditionIsFalse = Template.bind({})
17+
ConditionIsFalse.args = {
18+
condition: false,
19+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { PropsWithChildren } from "react"
2+
3+
export interface MaybeProps {
4+
condition: boolean
5+
}
6+
7+
/**
8+
* Wrapper component for conditionally rendering a child component without using "curly brace mode."
9+
* @param condition boolean expression that determines whether the child will be rendered
10+
* @returns the child or null
11+
*/
12+
export const Maybe = ({
13+
children,
14+
condition,
15+
}: PropsWithChildren<MaybeProps>): JSX.Element | null => {
16+
return condition ? <>{children}</> : null
17+
}

site/src/pages/TemplatesPage/TemplatesPageView.tsx

Lines changed: 108 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import TableHead from "@material-ui/core/TableHead"
88
import TableRow from "@material-ui/core/TableRow"
99
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
1010
import useTheme from "@material-ui/styles/useTheme"
11+
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
12+
import { Maybe } from "components/Conditionals/Maybe"
1113
import { ErrorSummary } from "components/ErrorSummary/ErrorSummary"
1214
import { FC } from "react"
1315
import { useTranslation } from "react-i18next"
@@ -103,7 +105,7 @@ export const TemplatesPageView: FC<React.PropsWithChildren<TemplatesPageViewProp
103105
<TemplateHelpTooltip />
104106
</Stack>
105107
</PageHeaderTitle>
106-
{props.templates && props.templates.length > 0 && (
108+
<Maybe condition={Boolean(props.templates && props.templates.length > 0)}>
107109
<PageHeaderSubtitle>
108110
Choose a template to create a new workspace
109111
{props.canCreateTemplate ? (
@@ -121,113 +123,122 @@ export const TemplatesPageView: FC<React.PropsWithChildren<TemplatesPageViewProp
121123
"."
122124
)}
123125
</PageHeaderSubtitle>
124-
)}
126+
</Maybe>
125127
</PageHeader>
126128

127-
{props.getOrganizationsError ? (
128-
<ErrorSummary
129-
error={props.getOrganizationsError}
130-
defaultMessage={t("errors.getOrganizationsError")}
131-
/>
132-
) : props.getTemplatesError ? (
133-
<ErrorSummary
134-
error={props.getTemplatesError}
135-
defaultMessage={t("errors.getTemplatesError")}
136-
/>
137-
) : (
138-
<TableContainer>
139-
<Table>
140-
<TableHead>
141-
<TableRow>
142-
<TableCell width="50%">{Language.nameLabel}</TableCell>
143-
<TableCell width="16%">{Language.usedByLabel}</TableCell>
144-
<TableCell width="16%">{Language.lastUpdatedLabel}</TableCell>
145-
<TableCell width="16%">{Language.createdByLabel}</TableCell>
146-
<TableCell width="1%"></TableCell>
147-
</TableRow>
148-
</TableHead>
149-
<TableBody>
150-
{props.loading && <TableLoader />}
151-
152-
{empty ? (
129+
<ChooseOne>
130+
<Cond condition={Boolean(props.getOrganizationsError)}>
131+
<ErrorSummary
132+
error={props.getOrganizationsError}
133+
defaultMessage={t("errors.getOrganizationsError")}
134+
/>
135+
</Cond>
136+
<Cond condition={Boolean(props.getTemplatesError)}>
137+
<ErrorSummary
138+
error={props.getTemplatesError}
139+
defaultMessage={t("errors.getTemplatesError")}
140+
/>
141+
</Cond>
142+
<Cond condition>
143+
<TableContainer>
144+
<Table>
145+
<TableHead>
153146
<TableRow>
154-
<TableCell colSpan={999}>
155-
<EmptyState
156-
message={Language.emptyMessage}
157-
description={
158-
props.canCreateTemplate
159-
? Language.emptyDescription
160-
: Language.emptyViewNoPerms
161-
}
162-
descriptionClassName={styles.emptyDescription}
163-
cta={<CodeExample code="coder templates init" />}
164-
/>
165-
</TableCell>
147+
<TableCell width="50%">{Language.nameLabel}</TableCell>
148+
<TableCell width="16%">{Language.usedByLabel}</TableCell>
149+
<TableCell width="16%">{Language.lastUpdatedLabel}</TableCell>
150+
<TableCell width="16%">{Language.createdByLabel}</TableCell>
151+
<TableCell width="1%"></TableCell>
166152
</TableRow>
167-
) : (
168-
props.templates?.map((template) => {
169-
const templatePageLink = `/templates/${template.name}`
170-
const hasIcon = template.icon && template.icon !== ""
153+
</TableHead>
154+
<TableBody>
155+
<Maybe condition={Boolean(props.loading)}>
156+
<TableLoader />
157+
</Maybe>
171158

172-
return (
173-
<TableRow
174-
key={template.id}
175-
hover
176-
data-testid={`template-${template.id}`}
177-
tabIndex={0}
178-
onKeyDown={(event) => {
179-
if (event.key === "Enter") {
180-
navigate(templatePageLink)
181-
}
182-
}}
183-
className={styles.clickableTableRow}
184-
>
185-
<TableCellLink to={templatePageLink}>
186-
<AvatarData
187-
title={template.name}
188-
subtitle={template.description}
189-
highlightTitle
190-
avatar={
191-
hasIcon && (
192-
<div className={styles.templateIconWrapper}>
193-
<img alt="" src={template.icon} />
194-
</div>
195-
)
159+
<ChooseOne>
160+
<Cond condition={empty}>
161+
<TableRow>
162+
<TableCell colSpan={999}>
163+
<EmptyState
164+
message={Language.emptyMessage}
165+
description={
166+
props.canCreateTemplate
167+
? Language.emptyDescription
168+
: Language.emptyViewNoPerms
196169
}
170+
descriptionClassName={styles.emptyDescription}
171+
cta={<CodeExample code="coder templates init" />}
197172
/>
198-
</TableCellLink>
173+
</TableCell>
174+
</TableRow>
175+
</Cond>
176+
<Cond condition>
177+
{props.templates?.map((template) => {
178+
const templatePageLink = `/templates/${template.name}`
179+
const hasIcon = template.icon && template.icon !== ""
199180

200-
<TableCellLink to={templatePageLink}>
201-
<span style={{ color: theme.palette.text.secondary }}>
202-
{Language.developerCount(template.active_user_count)}
203-
</span>
204-
</TableCellLink>
181+
return (
182+
<TableRow
183+
key={template.id}
184+
hover
185+
data-testid={`template-${template.id}`}
186+
tabIndex={0}
187+
onKeyDown={(event) => {
188+
if (event.key === "Enter") {
189+
navigate(templatePageLink)
190+
}
191+
}}
192+
className={styles.clickableTableRow}
193+
>
194+
<TableCellLink to={templatePageLink}>
195+
<AvatarData
196+
title={template.name}
197+
subtitle={template.description}
198+
highlightTitle
199+
avatar={
200+
hasIcon && (
201+
<div className={styles.templateIconWrapper}>
202+
<img alt="" src={template.icon} />
203+
</div>
204+
)
205+
}
206+
/>
207+
</TableCellLink>
205208

206-
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
207-
<span style={{ color: theme.palette.text.secondary }}>
208-
{createDayString(template.updated_at)}
209-
</span>
210-
</TableCellLink>
209+
<TableCellLink to={templatePageLink}>
210+
<span style={{ color: theme.palette.text.secondary }}>
211+
{Language.developerCount(template.active_user_count)}
212+
</span>
213+
</TableCellLink>
211214

212-
<TableCellLink to={templatePageLink}>
213-
<span style={{ color: theme.palette.text.secondary }}>
214-
{template.created_by_name}
215-
</span>
216-
</TableCellLink>
215+
<TableCellLink data-chromatic="ignore" to={templatePageLink}>
216+
<span style={{ color: theme.palette.text.secondary }}>
217+
{createDayString(template.updated_at)}
218+
</span>
219+
</TableCellLink>
217220

218-
<TableCellLink to={templatePageLink}>
219-
<div className={styles.arrowCell}>
220-
<KeyboardArrowRight className={styles.arrowRight} />
221-
</div>
222-
</TableCellLink>
223-
</TableRow>
224-
)
225-
})
226-
)}
227-
</TableBody>
228-
</Table>
229-
</TableContainer>
230-
)}
221+
<TableCellLink to={templatePageLink}>
222+
<span style={{ color: theme.palette.text.secondary }}>
223+
{template.created_by_name}
224+
</span>
225+
</TableCellLink>
226+
227+
<TableCellLink to={templatePageLink}>
228+
<div className={styles.arrowCell}>
229+
<KeyboardArrowRight className={styles.arrowRight} />
230+
</div>
231+
</TableCellLink>
232+
</TableRow>
233+
)
234+
})}
235+
</Cond>
236+
</ChooseOne>
237+
</TableBody>
238+
</Table>
239+
</TableContainer>
240+
</Cond>
241+
</ChooseOne>
231242
</Margins>
232243
)
233244
}

0 commit comments

Comments
 (0)