Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commited2207f

Browse files
BrunoQuaresmakylecarbs
authored andcommitted
feat: Improve empty states for workspaces and templates (#1950)
1 parentb41750d commited2207f

File tree

11 files changed

+196
-89
lines changed

11 files changed

+196
-89
lines changed
Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
import{makeStyles}from"@material-ui/core/styles"
22
import{FC}from"react"
33
import{MONOSPACE_FONT_FAMILY}from"../../theme/constants"
4+
import{combineClasses}from"../../util/combineClasses"
45
import{CopyButton}from"../CopyButton/CopyButton"
56

67
exportinterfaceCodeExampleProps{
78
code:string
9+
className?:string
10+
buttonClassName?:string
811
}
912

1013
/**
1114
* Component to show single-line code examples, with a copy button
1215
*/
13-
exportconstCodeExample:FC<CodeExampleProps>=({ code})=>{
16+
exportconstCodeExample:FC<CodeExampleProps>=({ code, className, buttonClassName})=>{
1417
conststyles=useStyles()
1518

1619
return(
17-
<divclassName={styles.root}>
18-
<code>{code}</code>
19-
<CopyButtontext={code}/>
20+
<divclassName={combineClasses([styles.root,className])}>
21+
<codeclassName={styles.code}>{code}</code>
22+
<CopyButtontext={code}buttonClassName={combineClasses([styles.button,buttonClassName])}/>
2023
</div>
2124
)
2225
}
@@ -30,8 +33,17 @@ const useStyles = makeStyles((theme) => ({
3033
background:theme.palette.background.default,
3134
color:theme.palette.primary.contrastText,
3235
fontFamily:MONOSPACE_FONT_FAMILY,
33-
fontSize:13,
34-
padding:theme.spacing(2),
36+
fontSize:14,
37+
borderRadius:theme.shape.borderRadius,
38+
padding:theme.spacing(0.5),
39+
},
40+
code:{
41+
padding:`${theme.spacing(0.5)}px${theme.spacing(0.75)}px${theme.spacing(0.5)}px${theme.spacing(2)}px`,
42+
},
43+
button:{
44+
border:0,
45+
minWidth:42,
46+
minHeight:42,
3547
borderRadius:theme.shape.borderRadius,
3648
},
3749
}))

‎site/src/components/EmptyState/EmptyState.tsx

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import Box from "@material-ui/core/Box"
22
import{makeStyles}from"@material-ui/core/styles"
33
importTypographyfrom"@material-ui/core/Typography"
44
import{FC,ReactNode}from"react"
5+
import{MONOSPACE_FONT_FAMILY}from"../../theme/constants"
6+
import{combineClasses}from"../../util/combineClasses"
57

68
exportinterfaceEmptyStateProps{
79
/** Text Message to display, placed inside Typography component */
810
message:string
911
/** Longer optional description to display below the message */
10-
description?:string
12+
description?:string|React.ReactNode
13+
descriptionClassName?:string
1114
cta?:ReactNode
15+
className?:string
1216
}
1317

1418
/**
@@ -20,17 +24,21 @@ export interface EmptyStateProps {
2024
* that you can directly pass props through to to customize the shape and layout of it.
2125
*/
2226
exportconstEmptyState:FC<EmptyStateProps>=(props)=>{
23-
const{ message, description, cta, ...boxProps}=props
27+
const{ message, description, cta,descriptionClassName, className,...boxProps}=props
2428
conststyles=useStyles()
2529

2630
return(
27-
<BoxclassName={styles.root}{...boxProps}>
31+
<BoxclassName={combineClasses([styles.root,className])}{...boxProps}>
2832
<divclassName={styles.header}>
2933
<Typographyvariant="h5"className={styles.title}>
3034
{message}
3135
</Typography>
3236
{description&&(
33-
<Typographyvariant="body2"color="textSecondary"className={styles.description}>
37+
<Typography
38+
variant="body2"
39+
color="textSecondary"
40+
className={combineClasses([styles.description,descriptionClassName])}
41+
>
3442
{description}
3543
</Typography>
3644
)}
@@ -48,17 +56,20 @@ const useStyles = makeStyles(
4856
justifyContent:"center",
4957
alignItems:"center",
5058
textAlign:"center",
51-
minHeight:120,
59+
minHeight:300,
5260
padding:theme.spacing(3),
61+
fontFamily:MONOSPACE_FONT_FAMILY,
5362
},
5463
header:{
5564
marginBottom:theme.spacing(3),
5665
},
5766
title:{
58-
fontWeight:400,
67+
fontWeight:600,
68+
fontFamily:"inherit",
5969
},
6070
description:{
6171
marginTop:theme.spacing(1),
72+
fontFamily:"inherit",
6273
},
6374
}),
6475
{name:"EmptyState"},

‎site/src/components/Footer/Footer.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ export const Footer: React.FC = ({ children }) => {
2727
</div>
2828
{buildInfoState.context.buildInfo&&(
2929
<divclassName={styles.buildInfo}>
30-
<Linkvariant="caption"target="_blank"href={buildInfoState.context.buildInfo.external_url}>
30+
<Link
31+
className={styles.link}
32+
variant="caption"
33+
target="_blank"
34+
href={buildInfoState.context.buildInfo.external_url}
35+
>
3136
{Language.buildInfoText(buildInfoState.context.buildInfo)}
3237
</Link>
3338
</div>
@@ -38,6 +43,7 @@ export const Footer: React.FC = ({ children }) => {
3843

3944
constuseFooterStyles=makeStyles((theme)=>({
4045
root:{
46+
opacity:0.6,
4147
textAlign:"center",
4248
flex:"0",
4349
paddingTop:theme.spacing(2),
@@ -50,4 +56,8 @@ const useFooterStyles = makeStyles((theme) => ({
5056
buildInfo:{
5157
margin:theme.spacing(0.25),
5258
},
59+
link:{
60+
color:theme.palette.text.secondary,
61+
fontWeight:600,
62+
},
5363
}))

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.stories.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ export default {
3333

3434
constTemplate:Story<CreateWorkspacePageViewProps>=(args)=><CreateWorkspacePageView{...args}/>
3535

36+
exportconstNoTemplates=Template.bind({})
37+
NoTemplates.args={
38+
templates:[],
39+
}
40+
3641
exportconstNoParameters=Template.bind({})
3742
NoParameters.args={
3843
templates:[MockTemplate],

‎site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { FC, useState } from "react"
88
import{LinkasRouterLink}from"react-router-dom"
99
import*asYupfrom"yup"
1010
import*asTypesGenfrom"../../api/typesGenerated"
11+
import{CodeExample}from"../../components/CodeExample/CodeExample"
12+
import{EmptyState}from"../../components/EmptyState/EmptyState"
1113
import{FormFooter}from"../../components/FormFooter/FormFooter"
1214
import{FullPageForm}from"../../components/FullPageForm/FullPageForm"
1315
import{Loader}from"../../components/Loader/Loader"
@@ -18,6 +20,17 @@ import { getFormHelpers, nameValidator, onChangeTrimmed } from "../../util/formU
1820
exportconstLanguage={
1921
templateLabel:"Template",
2022
nameLabel:"Name",
23+
emptyMessage:"Let's create your first template",
24+
emptyDescription:(
25+
<>
26+
To create a workspace you need to have a template. You can{" "}
27+
<Linktarget="_blank"href="https://github.com/coder/coder/blob/main/docs/templates.md">
28+
create one from scratch
29+
</Link>{" "}
30+
or use a built-in template by typing the following Coder CLI command:
31+
</>
32+
),
33+
templateLink:"Read more about this template",
2134
}
2235

2336
exportinterfaceCreateWorkspacePageViewProps{
@@ -98,7 +111,18 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
98111
{props.loadingTemplates&&<Loader/>}
99112

100113
<Stack>
101-
{props.templates&&(
114+
{props.templates&&props.templates.length===0&&(
115+
<EmptyState
116+
className={styles.emptyState}
117+
message={Language.emptyMessage}
118+
description={Language.emptyDescription}
119+
descriptionClassName={styles.emptyStateDescription}
120+
cta={
121+
<CodeExampleclassName={styles.code}buttonClassName={styles.codeButton}code="coder template init"/>
122+
}
123+
/>
124+
)}
125+
{props.templates&&props.templates.length>0&&(
102126
<TextField
103127
{...getFieldHelpers("template_id")}
104128
disabled={form.isSubmitting}
@@ -116,7 +140,7 @@ export const CreateWorkspacePageView: FC<CreateWorkspacePageViewProps> = (props)
116140
to={`/templates/${selectedTemplate.name}`}
117141
target="_blank"
118142
>
119-
Read more about this template<OpenInNewIcon/>
143+
{Language.templateLink}<OpenInNewIcon/>
120144
</Link>
121145
)
122146
}
@@ -179,4 +203,21 @@ const useStyles = makeStyles((theme) => ({
179203
marginLeft:theme.spacing(0.5),
180204
},
181205
},
206+
emptyState:{
207+
padding:0,
208+
fontFamily:"inherit",
209+
textAlign:"left",
210+
minHeight:"auto",
211+
alignItems:"flex-start",
212+
},
213+
emptyStateDescription:{
214+
lineHeight:"160%",
215+
},
216+
code:{
217+
background:theme.palette.background.paper,
218+
width:"100%",
219+
},
220+
codeButton:{
221+
background:theme.palette.background.paper,
222+
},
182223
}))

‎site/src/pages/TemplatesPage/TemplatesPage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ describe("TemplatesPage", () => {
3131
render(<TemplatesPage/>)
3232

3333
// Then
34-
awaitscreen.findByText(Language.emptyViewCreate)
34+
awaitscreen.findByText(Language.emptyMessage)
3535
})
3636

3737
it("renders a filled templates page",async()=>{

‎site/src/pages/TemplatesPage/TemplatesPageView.tsx

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import TableRow from "@material-ui/core/TableRow"
88
importdayjsfrom"dayjs"
99
importrelativeTimefrom"dayjs/plugin/relativeTime"
1010
import{FC}from"react"
11-
import{LinkasRouterLink}from"react-router-dom"
1211
import*asTypesGenfrom"../../api/typesGenerated"
1312
import{AvatarData}from"../../components/AvatarData/AvatarData"
13+
import{CodeExample}from"../../components/CodeExample/CodeExample"
14+
import{EmptyState}from"../../components/EmptyState/EmptyState"
1415
import{Margins}from"../../components/Margins/Margins"
1516
import{Stack}from"../../components/Stack/Stack"
1617
import{TableLoader}from"../../components/TableLoader/TableLoader"
@@ -24,9 +25,17 @@ export const Language = {
2425
nameLabel:"Name",
2526
usedByLabel:"Used by",
2627
lastUpdatedLabel:"Last updated",
27-
emptyViewCreateCTA:"Create a template",
28-
emptyViewCreate:"to standardize development workspaces for your team.",
29-
emptyViewNoPerms:"No templates have been created! Contact your Coder administrator.",
28+
emptyViewNoPerms:"Contact your Coder administrator to create a template. You can share the code below.",
29+
emptyMessage:"Create your first template",
30+
emptyDescription:(
31+
<>
32+
To create a workspace you need to have a template. You can{" "}
33+
<Linktarget="_blank"href="https://github.com/coder/coder/blob/main/docs/templates.md">
34+
create one from scratch
35+
</Link>{" "}
36+
or use a built-in template using the following Coder CLI command:
37+
</>
38+
),
3039
}
3140

3241
exportinterfaceTemplatesPageViewProps{
@@ -53,18 +62,12 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
5362
{!props.loading&&!props.templates?.length&&(
5463
<TableRow>
5564
<TableCellcolSpan={999}>
56-
<divclassName={styles.welcome}>
57-
{props.canCreateTemplate ?(
58-
<span>
59-
<Linkcomponent={RouterLink}to="/templates/new">
60-
{Language.emptyViewCreateCTA}
61-
</Link>
62-
&nbsp;{Language.emptyViewCreate}
63-
</span>
64-
) :(
65-
<span>{Language.emptyViewNoPerms}</span>
66-
)}
67-
</div>
65+
<EmptyState
66+
message={Language.emptyMessage}
67+
description={props.canCreateTemplate ?Language.emptyDescription :Language.emptyViewNoPerms}
68+
descriptionClassName={styles.emptyDescription}
69+
cta={<CodeExamplecode="coder template init"/>}
70+
/>
6871
</TableCell>
6972
</TableRow>
7073
)}
@@ -92,20 +95,9 @@ export const TemplatesPageView: FC<TemplatesPageViewProps> = (props) => {
9295

9396
constuseStyles=makeStyles((theme)=>({
9497
root:{
95-
marginTop:theme.spacing(3),
98+
marginTop:theme.spacing(10),
9699
},
97-
welcome:{
98-
paddingTop:theme.spacing(12),
99-
paddingBottom:theme.spacing(12),
100-
display:"flex",
101-
flexDirection:"column",
102-
alignItems:"center",
103-
justifyContent:"center",
104-
"& span":{
105-
maxWidth:600,
106-
textAlign:"center",
107-
fontSize:theme.spacing(2),
108-
lineHeight:`${theme.spacing(3)}px`,
109-
},
100+
emptyDescription:{
101+
maxWidth:theme.spacing(62),
110102
},
111103
}))

‎site/src/pages/WorkspacesPage/WorkspacesPage.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe("WorkspacesPage", () => {
2323
render(<WorkspacesPage/>)
2424

2525
// Then
26-
awaitscreen.findByText(Language.emptyView)
26+
awaitscreen.findByText(Language.emptyMessage)
2727
})
2828

2929
it("renders a filled workspaces page",async()=>{

‎site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import{ComponentMeta,Story}from"@storybook/react"
2-
import{ProvisionerJobStatus,Workspace}from"../../api/typesGenerated"
2+
import{ProvisionerJobStatus,Workspace,WorkspaceTransition}from"../../api/typesGenerated"
33
import{MockWorkspace}from"../../testHelpers/entities"
44
import{WorkspacesPageView,WorkspacesPageViewProps}from"./WorkspacesPageView"
55

@@ -10,7 +10,10 @@ export default {
1010

1111
constTemplate:Story<WorkspacesPageViewProps>=(args)=><WorkspacesPageView{...args}/>
1212

13-
constcreateWorkspaceWithStatus=(status:ProvisionerJobStatus,transition="start"):Workspace=>{
13+
constcreateWorkspaceWithStatus=(
14+
status:ProvisionerJobStatus,
15+
transition:WorkspaceTransition="start",
16+
):Workspace=>{
1417
return{
1518
...MockWorkspace,
1619
latest_build:{
@@ -46,4 +49,6 @@ AllStates.args = {
4649
}
4750

4851
exportconstEmpty=Template.bind({})
49-
Empty.args={}
52+
Empty.args={
53+
workspaces:[],
54+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp