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

Commite8e095e

Browse files
authored
feat: redesign error alert (#4403)
* added a warning summary component* added warning to workspace page* consolidated warnings* prettier* updated design* added color scheme* updated expander component* cleanup* fixed tests* fixed height issue* prettier* use theme constants* increased icon margin
1 parent3cc77d9 commite8e095e

File tree

12 files changed

+347
-43
lines changed

12 files changed

+347
-43
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import{Story}from"@storybook/react"
2+
import{AlertBanner,AlertBannerProps}from"./AlertBanner"
3+
importButtonfrom"@material-ui/core/Button"
4+
import{makeMockApiError}from"testHelpers/entities"
5+
6+
exportdefault{
7+
title:"components/AlertBanner",
8+
component:AlertBanner,
9+
}
10+
11+
constExampleAction=(
12+
<ButtononClick={()=>null}size="small">
13+
Button
14+
</Button>
15+
)
16+
17+
constmockError=makeMockApiError({
18+
message:"Email or password was invalid",
19+
detail:"Password is invalid",
20+
})
21+
22+
constTemplate:Story<AlertBannerProps>=(args)=><AlertBanner{...args}/>
23+
24+
exportconstWarning=Template.bind({})
25+
Warning.args={
26+
text:"This is a warning",
27+
severity:"warning",
28+
}
29+
30+
exportconstErrorWithDefaultMessage=Template.bind({})
31+
ErrorWithDefaultMessage.args={
32+
text:"This is an error",
33+
severity:"error",
34+
}
35+
36+
exportconstErrorWithErrorMessage=Template.bind({})
37+
ErrorWithErrorMessage.args={
38+
error:mockError,
39+
severity:"error",
40+
}
41+
42+
exportconstWarningWithDismiss=Template.bind({})
43+
WarningWithDismiss.args={
44+
text:"This is a warning",
45+
dismissible:true,
46+
severity:"warning",
47+
}
48+
49+
exportconstErrorWithDismiss=Template.bind({})
50+
ErrorWithDismiss.args={
51+
error:mockError,
52+
dismissible:true,
53+
severity:"error",
54+
}
55+
56+
exportconstWarningWithAction=Template.bind({})
57+
WarningWithAction.args={
58+
text:"This is a warning",
59+
actions:[ExampleAction],
60+
severity:"warning",
61+
}
62+
63+
exportconstErrorWithAction=Template.bind({})
64+
ErrorWithAction.args={
65+
error:mockError,
66+
actions:[ExampleAction],
67+
severity:"error",
68+
}
69+
70+
exportconstWarningWithActionAndDismiss=Template.bind({})
71+
WarningWithActionAndDismiss.args={
72+
text:"This is a warning",
73+
actions:[ExampleAction],
74+
dismissible:true,
75+
severity:"warning",
76+
}
77+
78+
exportconstErrorWithActionAndDismiss=Template.bind({})
79+
ErrorWithActionAndDismiss.args={
80+
error:mockError,
81+
actions:[ExampleAction],
82+
dismissible:true,
83+
severity:"error",
84+
}
85+
86+
exportconstErrorWithRetry=Template.bind({})
87+
ErrorWithRetry.args={
88+
error:mockError,
89+
retry:()=>null,
90+
dismissible:true,
91+
severity:"error",
92+
}
93+
94+
exportconstErrorWithActionRetryAndDismiss=Template.bind({})
95+
ErrorWithActionRetryAndDismiss.args={
96+
error:mockError,
97+
actions:[ExampleAction],
98+
retry:()=>null,
99+
dismissible:true,
100+
severity:"error",
101+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import{useState,FC}from"react"
2+
importCollapsefrom"@material-ui/core/Collapse"
3+
import{Stack}from"components/Stack/Stack"
4+
import{makeStyles,Theme}from"@material-ui/core/styles"
5+
import{colors}from"theme/colors"
6+
import{useTranslation}from"react-i18next"
7+
import{getErrorDetail,getErrorMessage}from"api/errors"
8+
import{Expander}from"components/Expander/Expander"
9+
import{Severity,AlertBannerProps}from"./alertTypes"
10+
import{severityConstants}from"./severityConstants"
11+
import{AlertBannerCtas}from"./AlertBannerCtas"
12+
13+
/**
14+
* severity: the level of alert severity (see ./severityTypes.ts)
15+
* text: default text to be displayed to the user; useful for warnings or as a fallback error message
16+
* error: should be passed in if the severity is 'Error'; warnings can use 'text' instead
17+
* actions: an array of CTAs passed in by the consumer
18+
* dismissible: determines whether or not the banner should have a `Dismiss` CTA
19+
* retry: a handler to retry the action that spawned the error
20+
*/
21+
exportconstAlertBanner:FC<AlertBannerProps>=({
22+
severity,
23+
text,
24+
error,
25+
actions=[],
26+
retry,
27+
dismissible=false,
28+
})=>{
29+
const{ t}=useTranslation("common")
30+
31+
const[open,setOpen]=useState(true)
32+
33+
// if an error is passed in, display that error, otherwise
34+
// display the text passed in, e.g. warning text
35+
constalertMessage=getErrorMessage(error,text??t("warningsAndErrors.somethingWentWrong"))
36+
37+
// if we have an error, check if there's detail to display
38+
constdetail=error ?getErrorDetail(error) :undefined
39+
constclasses=useStyles({ severity,hasDetail:Boolean(detail)})
40+
41+
const[showDetails,setShowDetails]=useState(false)
42+
43+
return(
44+
<Collapsein={open}>
45+
<Stack
46+
className={classes.alertContainer}
47+
direction="row"
48+
alignItems="center"
49+
spacing={0}
50+
justifyContent="space-between"
51+
>
52+
<Stackdirection="row"alignItems="center"spacing={1}>
53+
{severityConstants[severity].icon}
54+
<Stackspacing={0}>
55+
{alertMessage}
56+
{detail&&(
57+
<Expanderexpanded={showDetails}setExpanded={setShowDetails}>
58+
<div>{detail}</div>
59+
</Expander>
60+
)}
61+
</Stack>
62+
</Stack>
63+
64+
<AlertBannerCtas
65+
actions={actions}
66+
dismissible={dismissible}
67+
retry={retry}
68+
setOpen={setOpen}
69+
/>
70+
</Stack>
71+
</Collapse>
72+
)
73+
}
74+
75+
interfaceStyleProps{
76+
severity:Severity
77+
hasDetail:boolean
78+
}
79+
80+
constuseStyles=makeStyles<Theme,StyleProps>((theme)=>({
81+
alertContainer:(props)=>({
82+
borderColor:severityConstants[props.severity].color,
83+
border:`1px solid${colors.orange[7]}`,
84+
borderRadius:theme.shape.borderRadius,
85+
padding:`${theme.spacing(1)}px${theme.spacing(2)}px`,
86+
backgroundColor:`${colors.gray[16]}`,
87+
88+
"& svg":{
89+
marginTop:props.hasDetail ?`${theme.spacing(1)}px` :"inherit",
90+
marginRight:`${theme.spacing(1)}px`,
91+
},
92+
}),
93+
}))
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import{FC}from"react"
2+
import{AlertBannerProps}from"./alertTypes"
3+
import{Stack}from"components/Stack/Stack"
4+
importButtonfrom"@material-ui/core/Button"
5+
importRefreshIconfrom"@material-ui/icons/Refresh"
6+
import{useTranslation}from"react-i18next"
7+
8+
typeAlertBannerCtasProps=Pick<AlertBannerProps,"actions"|"dismissible"|"retry">&{
9+
setOpen:(arg0:boolean)=>void
10+
}
11+
12+
exportconstAlertBannerCtas:FC<AlertBannerCtasProps>=({
13+
actions=[],
14+
dismissible,
15+
retry,
16+
setOpen,
17+
})=>{
18+
const{ t}=useTranslation("common")
19+
20+
return(
21+
<Stackdirection="row">
22+
{/* CTAs passed in by the consumer */}
23+
{actions.length>0&&actions.map((action)=><divkey={String(action)}>{action}</div>)}
24+
25+
{/* retry CTA */}
26+
{retry&&(
27+
<div>
28+
<Buttonsize="small"onClick={retry}startIcon={<RefreshIcon/>}variant="outlined">
29+
{t("ctas.retry")}
30+
</Button>
31+
</div>
32+
)}
33+
34+
{/* close CTA */}
35+
{dismissible&&(
36+
<Buttonsize="small"onClick={()=>setOpen(false)}variant="outlined">
37+
{t("ctas.dismissCta")}
38+
</Button>
39+
)}
40+
</Stack>
41+
)
42+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import{ApiError}from"api/errors"
2+
import{ReactElement}from"react"
3+
4+
exporttypeSeverity="warning"|"error"
5+
6+
exportinterfaceAlertBannerProps{
7+
severity:Severity
8+
text?:string
9+
error?:ApiError|Error|unknown
10+
actions?:ReactElement[]
11+
dismissible?:boolean
12+
retry?:()=>void
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
importReportProblemOutlinedIconfrom"@material-ui/icons/ReportProblemOutlined"
2+
importErrorOutlineOutlinedIconfrom"@material-ui/icons/ErrorOutlineOutlined"
3+
import{colors}from"theme/colors"
4+
import{Severity}from"./alertTypes"
5+
import{ReactElement}from"react"
6+
7+
exportconstseverityConstants:Record<Severity,{color:string;icon:ReactElement}>={
8+
warning:{
9+
color:colors.orange[7],
10+
icon:<ReportProblemOutlinedIconfontSize="small"style={{color:colors.orange[7]}}/>,
11+
},
12+
error:{
13+
color:colors.red[7],
14+
icon:<ErrorOutlineOutlinedIconfontSize="small"style={{color:colors.red[7]}}/>,
15+
},
16+
}

‎site/src/components/ErrorSummary/ErrorSummary.test.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import{fireEvent,render,screen}from"@testing-library/react"
22
import{ErrorSummary}from"./ErrorSummary"
3+
import{i18n}from"i18n"
4+
5+
const{ t}=i18n
36

47
describe("ErrorSummary",()=>{
58
it("renders",async()=>{
@@ -26,7 +29,8 @@ describe("ErrorSummary", () => {
2629
render(<ErrorSummaryerror={error}/>)
2730

2831
// Then
29-
fireEvent.click(screen.getByText("More"))
32+
constexpandText=t("ctas.expand",{ns:"common"})
33+
fireEvent.click(screen.getByText(expandText))
3034
constelement=awaitscreen.findByText(
3135
"The resource you requested does not exist in the database.",
3236
{exact:false},
@@ -48,8 +52,11 @@ describe("ErrorSummary", () => {
4852
render(<ErrorSummaryerror={error}/>)
4953

5054
// Then
51-
fireEvent.click(screen.getByText("More"))
52-
fireEvent.click(screen.getByText("Less"))
55+
constexpandText=t("ctas.expand",{ns:"common"})
56+
constcollapseText=t("ctas.collapse",{ns:"common"})
57+
58+
fireEvent.click(screen.getByText(expandText))
59+
fireEvent.click(screen.getByText(collapseText))
5360
constelement=awaitscreen.findByText(
5461
"The resource you requested does not exist in the database.",
5562
{exact:false},
Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,66 @@
11
importLinkfrom"@material-ui/core/Link"
22
importmakeStylesfrom"@material-ui/core/styles/makeStyles"
33
import{CloseDropdown,OpenDropdown}from"components/DropdownArrows/DropdownArrows"
4-
5-
constLanguage={
6-
expand:"More",
7-
collapse:"Less",
8-
}
4+
import{PropsWithChildren,FC}from"react"
5+
importCollapsefrom"@material-ui/core/Collapse"
6+
import{useTranslation}from"react-i18next"
7+
import{combineClasses}from"util/combineClasses"
98

109
exportinterfaceExpanderProps{
1110
expanded:boolean
1211
setExpanded:(val:boolean)=>void
1312
}
1413

15-
exportconstExpander:React.FC<ExpanderProps>=({ expanded, setExpanded})=>{
16-
consttoggleExpanded=()=>setExpanded(!expanded)
14+
exportconstExpander:FC<PropsWithChildren<ExpanderProps>>=({
15+
expanded,
16+
setExpanded,
17+
children,
18+
})=>{
1719
conststyles=useStyles()
20+
const{ t}=useTranslation("common")
21+
22+
consttoggleExpanded=()=>setExpanded(!expanded)
23+
1824
return(
19-
<Linkaria-expanded={expanded}onClick={toggleExpanded}className={styles.expandLink}>
20-
{expanded ?(
21-
<spanclassName={styles.text}>
22-
{Language.collapse}
23-
<CloseDropdownmargin={false}/>{" "}
24-
</span>
25-
) :(
26-
<spanclassName={styles.text}>
27-
{Language.expand}
28-
<OpenDropdownmargin={false}/>
29-
</span>
25+
<>
26+
{!expanded&&(
27+
<LinkonClick={toggleExpanded}className={styles.expandLink}>
28+
<spanclassName={styles.text}>
29+
{t("ctas.expand")}
30+
<OpenDropdownmargin={false}/>
31+
</span>
32+
</Link>
33+
)}
34+
<Collapsein={expanded}>
35+
<divclassName={styles.text}>{children}</div>
36+
</Collapse>
37+
{expanded&&(
38+
<Link
39+
onClick={toggleExpanded}
40+
className={combineClasses([styles.expandLink,styles.collapseLink])}
41+
>
42+
<spanclassName={styles.text}>
43+
{t("ctas.collapse")}
44+
<CloseDropdownmargin={false}/>
45+
</span>
46+
</Link>
3047
)}
31-
</Link>
48+
</>
3249
)
3350
}
3451

3552
constuseStyles=makeStyles((theme)=>({
3653
expandLink:{
3754
cursor:"pointer",
38-
color:theme.palette.text.primary,
39-
display:"flex",
55+
color:theme.palette.text.secondary,
56+
},
57+
collapseLink:{
58+
marginTop:`${theme.spacing(2)}px`,
4059
},
4160
text:{
4261
display:"flex",
4362
alignItems:"center",
63+
color:theme.palette.text.secondary,
64+
fontSize:theme.typography.caption.fontSize,
4465
},
4566
}))

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp