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

feat: redesign error alert#4403

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Kira-Pilot merged 14 commits intomainfromerror-message-redesign/kira-pilot
Oct 7, 2022
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
14 commits
Select commitHold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletionssite/src/components/AlertBanner/AlertBanner.stories.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
import { Story } from "@storybook/react"
import { AlertBanner, AlertBannerProps } from "./AlertBanner"
import Button from "@material-ui/core/Button"
import { makeMockApiError } from "testHelpers/entities"

export default {
title: "components/AlertBanner",
component: AlertBanner,
}

const ExampleAction = (
<Button onClick={() => null} size="small">
Button
</Button>
)

const mockError = makeMockApiError({
message: "Email or password was invalid",
detail: "Password is invalid",
})

const Template: Story<AlertBannerProps> = (args) => <AlertBanner {...args} />

export const Warning = Template.bind({})
Warning.args = {
text: "This is a warning",
severity: "warning",
}

export const ErrorWithDefaultMessage = Template.bind({})
ErrorWithDefaultMessage.args = {
text: "This is an error",
severity: "error",
}

export const ErrorWithErrorMessage = Template.bind({})
ErrorWithErrorMessage.args = {
error: mockError,
severity: "error",
}

export const WarningWithDismiss = Template.bind({})
WarningWithDismiss.args = {
text: "This is a warning",
dismissible: true,
severity: "warning",
}

export const ErrorWithDismiss = Template.bind({})
ErrorWithDismiss.args = {
error: mockError,
dismissible: true,
severity: "error",
}

export const WarningWithAction = Template.bind({})
WarningWithAction.args = {
text: "This is a warning",
actions: [ExampleAction],
severity: "warning",
}

export const ErrorWithAction = Template.bind({})
ErrorWithAction.args = {
error: mockError,
actions: [ExampleAction],
severity: "error",
}

export const WarningWithActionAndDismiss = Template.bind({})
WarningWithActionAndDismiss.args = {
text: "This is a warning",
actions: [ExampleAction],
dismissible: true,
severity: "warning",
}

export const ErrorWithActionAndDismiss = Template.bind({})
ErrorWithActionAndDismiss.args = {
error: mockError,
actions: [ExampleAction],
dismissible: true,
severity: "error",
}

export const ErrorWithRetry = Template.bind({})
ErrorWithRetry.args = {
error: mockError,
retry: () => null,
dismissible: true,
severity: "error",
}

export const ErrorWithActionRetryAndDismiss = Template.bind({})
ErrorWithActionRetryAndDismiss.args = {
error: mockError,
actions: [ExampleAction],
retry: () => null,
dismissible: true,
severity: "error",
}
93 changes: 93 additions & 0 deletionssite/src/components/AlertBanner/AlertBanner.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
import { useState, FC } from "react"
import Collapse from "@material-ui/core/Collapse"
import { Stack } from "components/Stack/Stack"
import { makeStyles, Theme } from "@material-ui/core/styles"
import { colors } from "theme/colors"
import { useTranslation } from "react-i18next"
import { getErrorDetail, getErrorMessage } from "api/errors"
import { Expander } from "components/Expander/Expander"
import { Severity, AlertBannerProps } from "./alertTypes"
import { severityConstants } from "./severityConstants"
import { AlertBannerCtas } from "./AlertBannerCtas"

/**
* severity: the level of alert severity (see ./severityTypes.ts)
* text: default text to be displayed to the user; useful for warnings or as a fallback error message
* error: should be passed in if the severity is 'Error'; warnings can use 'text' instead
* actions: an array of CTAs passed in by the consumer
* dismissible: determines whether or not the banner should have a `Dismiss` CTA
* retry: a handler to retry the action that spawned the error
*/
export const AlertBanner: FC<AlertBannerProps> = ({
severity,
text,
error,
actions = [],
retry,
dismissible = false,
}) => {
const { t } = useTranslation("common")

const [open, setOpen] = useState(true)

// if an error is passed in, display that error, otherwise
// display the text passed in, e.g. warning text
const alertMessage = getErrorMessage(error, text ?? t("warningsAndErrors.somethingWentWrong"))

// if we have an error, check if there's detail to display
const detail = error ? getErrorDetail(error) : undefined
const classes = useStyles({ severity, hasDetail: Boolean(detail) })

const [showDetails, setShowDetails] = useState(false)

return (
<Collapse in={open}>
<Stack
className={classes.alertContainer}
direction="row"
alignItems="center"
spacing={0}
justifyContent="space-between"
>
<Stack direction="row" alignItems="center" spacing={1}>
{severityConstants[severity].icon}
<Stack spacing={0}>
{alertMessage}
{detail && (
<Expander expanded={showDetails} setExpanded={setShowDetails}>
<div>{detail}</div>
</Expander>
)}
</Stack>
</Stack>

<AlertBannerCtas
actions={actions}
dismissible={dismissible}
retry={retry}
setOpen={setOpen}
/>
</Stack>
</Collapse>
)
}

interface StyleProps {
severity: Severity
hasDetail: boolean
}

const useStyles = makeStyles<Theme, StyleProps>((theme) => ({
alertContainer: (props) => ({
borderColor: severityConstants[props.severity].color,
border: `1px solid ${colors.orange[7]}`,
borderRadius: theme.shape.borderRadius,
padding: `${theme.spacing(1)}px ${theme.spacing(2)}px`,
backgroundColor: `${colors.gray[16]}`,

"& svg": {
marginTop: props.hasDetail ? `${theme.spacing(1)}px` : "inherit",
marginRight: `${theme.spacing(1)}px`,
},
}),
}))
42 changes: 42 additions & 0 deletionssite/src/components/AlertBanner/AlertBannerCtas.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
import { FC } from "react"
import { AlertBannerProps } from "./alertTypes"
import { Stack } from "components/Stack/Stack"
import Button from "@material-ui/core/Button"
import RefreshIcon from "@material-ui/icons/Refresh"
import { useTranslation } from "react-i18next"

type AlertBannerCtasProps = Pick<AlertBannerProps, "actions" | "dismissible" | "retry"> & {
setOpen: (arg0: boolean) => void
}

export const AlertBannerCtas: FC<AlertBannerCtasProps> = ({
actions = [],
dismissible,
retry,
setOpen,
}) => {
const { t } = useTranslation("common")

return (
<Stack direction="row">
{/* CTAs passed in by the consumer */}
{actions.length > 0 && actions.map((action) => <div key={String(action)}>{action}</div>)}

{/* retry CTA */}
{retry && (
<div>
<Button size="small" onClick={retry} startIcon={<RefreshIcon />} variant="outlined">
{t("ctas.retry")}
</Button>
</div>
)}

{/* close CTA */}
{dismissible && (
<Button size="small" onClick={() => setOpen(false)} variant="outlined">
{t("ctas.dismissCta")}
</Button>
)}
</Stack>
)
}
13 changes: 13 additions & 0 deletionssite/src/components/AlertBanner/alertTypes.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
import { ApiError } from "api/errors"
import { ReactElement } from "react"

export type Severity = "warning" | "error"

export interface AlertBannerProps {
severity: Severity
text?: string
error?: ApiError | Error | unknown
actions?: ReactElement[]
dismissible?: boolean
retry?: () => void
}
16 changes: 16 additions & 0 deletionssite/src/components/AlertBanner/severityConstants.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
import ReportProblemOutlinedIcon from "@material-ui/icons/ReportProblemOutlined"
import ErrorOutlineOutlinedIcon from "@material-ui/icons/ErrorOutlineOutlined"
import { colors } from "theme/colors"
import { Severity } from "./alertTypes"
import { ReactElement } from "react"

export const severityConstants: Record<Severity, { color: string; icon: ReactElement }> = {
warning: {
color: colors.orange[7],
icon: <ReportProblemOutlinedIcon fontSize="small" style={{ color: colors.orange[7] }} />,
},
error: {
color: colors.red[7],
icon: <ErrorOutlineOutlinedIcon fontSize="small" style={{ color: colors.red[7] }} />,
},
}
13 changes: 10 additions & 3 deletionssite/src/components/ErrorSummary/ErrorSummary.test.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
import { fireEvent, render, screen } from "@testing-library/react"
import { ErrorSummary } from "./ErrorSummary"
import { i18n } from "i18n"

const { t } = i18n

describe("ErrorSummary", () => {
it("renders", async () => {
Expand All@@ -26,7 +29,8 @@ describe("ErrorSummary", () => {
render(<ErrorSummary error={error} />)

// Then
fireEvent.click(screen.getByText("More"))
const expandText = t("ctas.expand", { ns: "common" })
fireEvent.click(screen.getByText(expandText))
const element = await screen.findByText(
"The resource you requested does not exist in the database.",
{ exact: false },
Expand All@@ -48,8 +52,11 @@ describe("ErrorSummary", () => {
render(<ErrorSummary error={error} />)

// Then
fireEvent.click(screen.getByText("More"))
fireEvent.click(screen.getByText("Less"))
const expandText = t("ctas.expand", { ns: "common" })
const collapseText = t("ctas.collapse", { ns: "common" })

fireEvent.click(screen.getByText(expandText))
fireEvent.click(screen.getByText(collapseText))
const element = await screen.findByText(
"The resource you requested does not exist in the database.",
{ exact: false },
Expand Down
63 changes: 42 additions & 21 deletionssite/src/components/Expander/Expander.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,66 @@
import Link from "@material-ui/core/Link"
import makeStyles from "@material-ui/core/styles/makeStyles"
import { CloseDropdown, OpenDropdown } from "components/DropdownArrows/DropdownArrows"

const Language = {
expand: "More",
collapse: "Less",
}
import { PropsWithChildren, FC } from "react"
import Collapse from "@material-ui/core/Collapse"
import { useTranslation } from "react-i18next"
import { combineClasses } from "util/combineClasses"

export interface ExpanderProps {
expanded: boolean
setExpanded: (val: boolean) => void
}

export const Expander: React.FC<ExpanderProps> = ({ expanded, setExpanded }) => {
const toggleExpanded = () => setExpanded(!expanded)
export const Expander: FC<PropsWithChildren<ExpanderProps>> = ({
expanded,
setExpanded,
children,
}) => {
const styles = useStyles()
const { t } = useTranslation("common")

const toggleExpanded = () => setExpanded(!expanded)

return (
<Link aria-expanded={expanded} onClick={toggleExpanded} className={styles.expandLink}>
{expanded ? (
<span className={styles.text}>
{Language.collapse}
<CloseDropdown margin={false} />{" "}
</span>
) : (
<span className={styles.text}>
{Language.expand}
<OpenDropdown margin={false} />
</span>
<>
{!expanded && (
<Link onClick={toggleExpanded} className={styles.expandLink}>
<span className={styles.text}>
{t("ctas.expand")}
<OpenDropdown margin={false} />
</span>
</Link>
)}
<Collapse in={expanded}>
<div className={styles.text}>{children}</div>
</Collapse>
{expanded && (
<Link
onClick={toggleExpanded}
className={combineClasses([styles.expandLink, styles.collapseLink])}
>
<span className={styles.text}>
{t("ctas.collapse")}
<CloseDropdown margin={false} />
</span>
</Link>
)}
</Link>
</>
)
}

const useStyles = makeStyles((theme) => ({
expandLink: {
cursor: "pointer",
color: theme.palette.text.primary,
display: "flex",
color: theme.palette.text.secondary,
},
collapseLink: {
marginTop: `${theme.spacing(2)}px`,
},
text: {
display: "flex",
alignItems: "center",
color: theme.palette.text.secondary,
fontSize: theme.typography.caption.fontSize,
},
}))
Loading

[8]ページ先頭

©2009-2025 Movatter.jp