Skip to content

Commit b045734

Browse files
feat: Add use template button to template row (#5811)
1 parent 0e58772 commit b045734

File tree

3 files changed

+131
-120
lines changed

3 files changed

+131
-120
lines changed

site/src/hooks/useClickable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { KeyboardEvent } from "react"
22

3-
interface UseClickableResult {
3+
export interface UseClickableResult {
44
tabIndex: 0
55
role: "button"
66
onClick: () => void
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { makeStyles } from "@material-ui/core/styles"
2+
import { useClickable, UseClickableResult } from "./useClickable"
3+
4+
interface UseClickableTableRowResult extends UseClickableResult {
5+
className: string
6+
hover: true
7+
}
8+
9+
export const useClickableTableRow = (
10+
onClick: () => void,
11+
): UseClickableTableRowResult => {
12+
const styles = useStyles()
13+
const clickable = useClickable(onClick)
14+
15+
return {
16+
...clickable,
17+
className: styles.row,
18+
hover: true,
19+
}
20+
}
21+
22+
const useStyles = makeStyles((theme) => ({
23+
row: {
24+
cursor: "pointer",
25+
26+
"&:focus": {
27+
outline: `1px solid ${theme.palette.secondary.dark}`,
28+
outlineOffset: -1,
29+
},
30+
},
31+
}))
Lines changed: 99 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import Button from "@material-ui/core/Button"
22
import Link from "@material-ui/core/Link"
3-
import { makeStyles, Theme } from "@material-ui/core/styles"
3+
import { makeStyles } from "@material-ui/core/styles"
44
import Table from "@material-ui/core/Table"
55
import TableBody from "@material-ui/core/TableBody"
66
import TableCell from "@material-ui/core/TableCell"
77
import TableContainer from "@material-ui/core/TableContainer"
88
import TableHead from "@material-ui/core/TableHead"
99
import TableRow from "@material-ui/core/TableRow"
10-
import KeyboardArrowRight from "@material-ui/icons/KeyboardArrowRight"
1110
import AddIcon from "@material-ui/icons/AddOutlined"
12-
import useTheme from "@material-ui/styles/useTheme"
1311
import { AlertBanner } from "components/AlertBanner/AlertBanner"
1412
import { ChooseOne, Cond } from "components/Conditionals/ChooseOne"
1513
import { Maybe } from "components/Conditionals/Maybe"
@@ -28,7 +26,6 @@ import {
2826
PageHeaderTitle,
2927
} from "../../components/PageHeader/PageHeader"
3028
import { Stack } from "../../components/Stack/Stack"
31-
import { TableCellLink } from "../../components/TableCellLink/TableCellLink"
3229
import { TableLoader } from "../../components/TableLoader/TableLoader"
3330
import {
3431
HelpTooltip,
@@ -39,6 +36,11 @@ import {
3936
} from "../../components/Tooltips/HelpTooltip/HelpTooltip"
4037
import { EmptyTemplates } from "./EmptyTemplates"
4138
import { TemplatesContext } from "xServices/templates/templatesXService"
39+
import { useClickableTableRow } from "hooks/useClickableTableRow"
40+
import { Template } from "api/typesGenerated"
41+
import { combineClasses } from "util/combineClasses"
42+
import { colors } from "theme/colors"
43+
import ArrowForwardOutlined from "@material-ui/icons/ArrowForwardOutlined"
4244

4345
export const Language = {
4446
developerCount: (activeCount: number): string => {
@@ -54,7 +56,6 @@ export const Language = {
5456
templateTooltipText:
5557
"With templates you can create a common configuration for your workspaces using Terraform.",
5658
templateTooltipLink: "Manage templates",
57-
createdByLabel: "Created by",
5859
}
5960

6061
const TemplateHelpTooltip: React.FC = () => {
@@ -71,16 +72,80 @@ const TemplateHelpTooltip: React.FC = () => {
7172
)
7273
}
7374

75+
const TemplateRow: FC<{ template: Template }> = ({ template }) => {
76+
const templatePageLink = `/templates/${template.name}`
77+
const hasIcon = template.icon && template.icon !== ""
78+
const navigate = useNavigate()
79+
const styles = useStyles()
80+
const { className: clickableClassName, ...clickableRow } =
81+
useClickableTableRow(() => {
82+
navigate(templatePageLink)
83+
})
84+
85+
return (
86+
<TableRow
87+
key={template.id}
88+
data-testid={`template-${template.id}`}
89+
{...clickableRow}
90+
className={combineClasses([clickableClassName, styles.tableRow])}
91+
>
92+
<TableCell>
93+
<AvatarData
94+
title={
95+
template.display_name.length > 0
96+
? template.display_name
97+
: template.name
98+
}
99+
subtitle={template.description}
100+
highlightTitle
101+
avatar={
102+
hasIcon && (
103+
<div className={styles.templateIconWrapper}>
104+
<img alt="" src={template.icon} />
105+
</div>
106+
)
107+
}
108+
/>
109+
</TableCell>
110+
111+
<TableCell className={styles.secondary}>
112+
{Language.developerCount(template.active_user_count)}
113+
</TableCell>
114+
115+
<TableCell className={styles.secondary}>
116+
{formatTemplateBuildTime(template.build_time_stats.start.P50)}
117+
</TableCell>
118+
119+
<TableCell data-chromatic="ignore" className={styles.secondary}>
120+
{createDayString(template.updated_at)}
121+
</TableCell>
122+
123+
<TableCell className={styles.actionCell}>
124+
<Button
125+
variant="outlined"
126+
size="small"
127+
className={styles.actionButton}
128+
startIcon={<ArrowForwardOutlined />}
129+
title={`Create a workspace using the ${template.display_name} template`}
130+
onClick={(e) => {
131+
e.stopPropagation()
132+
navigate(`/templates/${template.name}/workspace`)
133+
}}
134+
>
135+
Use template
136+
</Button>
137+
</TableCell>
138+
</TableRow>
139+
)
140+
}
141+
74142
export interface TemplatesPageViewProps {
75143
context: TemplatesContext
76144
}
77145

78146
export const TemplatesPageView: FC<
79147
React.PropsWithChildren<TemplatesPageViewProps>
80148
> = ({ context }) => {
81-
const styles = useStyles()
82-
const navigate = useNavigate()
83-
const theme: Theme = useTheme()
84149
const { templates, error, examples, permissions } = context
85150
const isLoading = !templates
86151
const isEmpty = Boolean(templates && templates.length === 0)
@@ -136,11 +201,10 @@ export const TemplatesPageView: FC<
136201
<Table>
137202
<TableHead>
138203
<TableRow>
139-
<TableCell width="34%">{Language.nameLabel}</TableCell>
140-
<TableCell width="16%">{Language.usedByLabel}</TableCell>
141-
<TableCell width="16%">{Language.buildTimeLabel}</TableCell>
142-
<TableCell width="16%">{Language.lastUpdatedLabel}</TableCell>
143-
<TableCell width="16%">{Language.createdByLabel}</TableCell>
204+
<TableCell width="35%">{Language.nameLabel}</TableCell>
205+
<TableCell width="15%">{Language.usedByLabel}</TableCell>
206+
<TableCell width="10%">{Language.buildTimeLabel}</TableCell>
207+
<TableCell width="15%">{Language.lastUpdatedLabel}</TableCell>
144208
<TableCell width="1%"></TableCell>
145209
</TableRow>
146210
</TableHead>
@@ -158,91 +222,9 @@ export const TemplatesPageView: FC<
158222
</Cond>
159223

160224
<Cond>
161-
{templates?.map((template) => {
162-
const templatePageLink = `/templates/${template.name}`
163-
const hasIcon = template.icon && template.icon !== ""
164-
165-
return (
166-
<TableRow
167-
key={template.id}
168-
hover
169-
data-testid={`template-${template.id}`}
170-
tabIndex={0}
171-
onKeyDown={(event) => {
172-
if (event.key === "Enter") {
173-
navigate(templatePageLink)
174-
}
175-
}}
176-
className={styles.clickableTableRow}
177-
>
178-
<TableCellLink to={templatePageLink}>
179-
<AvatarData
180-
title={
181-
template.display_name.length > 0
182-
? template.display_name
183-
: template.name
184-
}
185-
subtitle={template.description}
186-
highlightTitle
187-
avatar={
188-
hasIcon && (
189-
<div className={styles.templateIconWrapper}>
190-
<img alt="" src={template.icon} />
191-
</div>
192-
)
193-
}
194-
/>
195-
</TableCellLink>
196-
197-
<TableCellLink to={templatePageLink}>
198-
<span
199-
style={{ color: theme.palette.text.secondary }}
200-
>
201-
{Language.developerCount(
202-
template.active_user_count,
203-
)}
204-
</span>
205-
</TableCellLink>
206-
207-
<TableCellLink to={templatePageLink}>
208-
<span
209-
style={{ color: theme.palette.text.secondary }}
210-
>
211-
{formatTemplateBuildTime(
212-
template.build_time_stats.start.P50,
213-
)}
214-
</span>
215-
</TableCellLink>
216-
217-
<TableCellLink
218-
data-chromatic="ignore"
219-
to={templatePageLink}
220-
>
221-
<span
222-
style={{ color: theme.palette.text.secondary }}
223-
>
224-
{createDayString(template.updated_at)}
225-
</span>
226-
</TableCellLink>
227-
228-
<TableCellLink to={templatePageLink}>
229-
<span
230-
style={{ color: theme.palette.text.secondary }}
231-
>
232-
{template.created_by_name}
233-
</span>
234-
</TableCellLink>
235-
236-
<TableCellLink to={templatePageLink}>
237-
<div className={styles.arrowCell}>
238-
<KeyboardArrowRight
239-
className={styles.arrowRight}
240-
/>
241-
</div>
242-
</TableCellLink>
243-
</TableRow>
244-
)
245-
})}
225+
{templates?.map((template) => (
226+
<TemplateRow key={template.id} template={template} />
227+
))}
246228
</Cond>
247229
</ChooseOne>
248230
</TableBody>
@@ -255,27 +237,6 @@ export const TemplatesPageView: FC<
255237
}
256238

257239
const useStyles = makeStyles((theme) => ({
258-
clickableTableRow: {
259-
"&:hover td": {
260-
backgroundColor: theme.palette.action.hover,
261-
},
262-
263-
"&:focus": {
264-
outline: `1px solid ${theme.palette.secondary.dark}`,
265-
},
266-
267-
"& .MuiTableCell-root:last-child": {
268-
paddingRight: theme.spacing(2),
269-
},
270-
},
271-
arrowRight: {
272-
color: theme.palette.text.secondary,
273-
width: 20,
274-
height: 20,
275-
},
276-
arrowCell: {
277-
display: "flex",
278-
},
279240
templateIconWrapper: {
280241
// Same size then the avatar component
281242
width: 36,
@@ -286,4 +247,23 @@ const useStyles = makeStyles((theme) => ({
286247
width: "100%",
287248
},
288249
},
250+
actionCell: {
251+
whiteSpace: "nowrap",
252+
},
253+
secondary: {
254+
color: theme.palette.text.secondary,
255+
},
256+
tableRow: {
257+
"&:hover $actionButton": {
258+
color: theme.palette.text.primary,
259+
borderColor: colors.gray[11],
260+
"&:hover": {
261+
borderColor: theme.palette.text.primary,
262+
},
263+
},
264+
},
265+
actionButton: {
266+
color: theme.palette.text.secondary,
267+
transition: "none",
268+
},
289269
}))

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy