Skip to content

Commit

Permalink
feat(ErrorState): add some customization capabilities (#448)
Browse files Browse the repository at this point in the history
* feat(ErrorState): add some customization capabilities

* fix linting and add headingLevel overrides to relevant components
  • Loading branch information
nicolethoen authored Nov 18, 2024
1 parent 374d47d commit 65f296b
Show file tree
Hide file tree
Showing 11 changed files with 4,456 additions and 3,442 deletions.
7,799 changes: 4,372 additions & 3,427 deletions package-lock.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packa
---

import ErrorState from "@patternfly/react-component-groups/dist/dynamic/ErrorState";
import { PathMissingIcon } from '@patternfly/react-icons/dist/dynamic/icons/path-missing-icon';

The **error state** component repurposes the `EmptyState` component to display an error to users. To further customize this component, you can also utilize all properties of the [empty state component](/components/empty-state), with the `exception` of `children`.

## Examples

### Basic error state

To provide users with error details, a basic error state should contain an appropriate and informative `titleText` and `bodyText`.
To provide users with error details, a basic error state should contain an appropriate and informative `titleText` and `bodyText`. Error state provides a default action to navigate back to the previous page, or the home page in the empty state's footer.

```js file="./ErrorStateExample.tsx"

Expand All @@ -35,3 +36,11 @@ To override the default action button, specify your own using `customFooter`.
```js file="./ErrorStateFooterExample.tsx"

```

### Customizations using EmptyState props

All properties of the [empty state component](/components/empty-state) are spread to the error state component group. Passing `status='none'` to the error state will cause the icon color to be grey.

```js file="./ErrorStateExtraProps.tsx"

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import ErrorState from "@patternfly/react-component-groups/dist/dynamic/ErrorState";
import { PathMissingIcon } from '@patternfly/react-icons/dist/dynamic/icons/path-missing-icon';

export const BasicExample: React.FunctionComponent = () => (
<ErrorState
titleText='Sample error title'
bodyText='Sample error description'
headingLevel='h2'
icon={PathMissingIcon}
status="none"
customFooter="Any other details in a custom footer."
/>
);
24 changes: 20 additions & 4 deletions packages/module/src/ErrorBoundary/ErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ export interface ErrorBoundaryProps {
children?: React.ReactNode;
/** Custom OUIA ID */
ouiaId?: string | number;
/** The heading level to use on the header title, default is h1 */
headerTitleHeadingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
/** The heading level to use on the error title, default is h2 */
errorTitleHeadingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
}

export interface ErrorBoundaryState {
Expand All @@ -45,9 +49,20 @@ interface ErrorPageProps extends ErrorBoundaryProps {
classes: Record<string | number | symbol, string>;
}

export const ErrorBoundary: React.FunctionComponent<ErrorBoundaryProps> = (props: ErrorBoundaryProps) => {
export const ErrorBoundary: React.FunctionComponent<ErrorBoundaryProps> = ({
headerTitleHeadingLevel = "h1",
errorTitleHeadingLevel = "h2",
...props
}: ErrorBoundaryProps) => {
const classes = useStyles();
return <ErrorBoundaryContent classes={classes} {...props} />
return (
<ErrorBoundaryContent
classes={classes}
headerTitleHeadingLevel={headerTitleHeadingLevel}
errorTitleHeadingLevel={errorTitleHeadingLevel}
{...props}
/>
);
}

// As of time of writing, React only supports error boundaries in class components
Expand Down Expand Up @@ -82,7 +97,7 @@ class ErrorBoundaryContent extends React.Component<ErrorPageProps, ErrorBoundary
}

render() {
const { ouiaId = 'ErrorBoundary', ...props } = this.props;
const { ouiaId = 'ErrorBoundary', errorTitleHeadingLevel, headerTitleHeadingLevel, ...props } = this.props;

if (this.state.hasError) {
if (this.props.silent) {
Expand All @@ -91,9 +106,10 @@ class ErrorBoundaryContent extends React.Component<ErrorPageProps, ErrorBoundary

return (
<div data-ouia-component-id={ouiaId}>
{props?.headerTitle && <Title headingLevel="h1" size="2xl" ouiaId={`${ouiaId}-title`}>{props.headerTitle}</Title>}
{props?.headerTitle && <Title headingLevel={headerTitleHeadingLevel || 'h1'} size="2xl" ouiaId={`${ouiaId}-title`}>{props.headerTitle}</Title>}
<ErrorState
titleText={props.errorTitle}
headingLevel={errorTitleHeadingLevel}
bodyText={
<>
<span>{props.errorDescription}</span>
Expand Down
7 changes: 7 additions & 0 deletions packages/module/src/ErrorState/ErrorState.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,11 @@ describe('ErrorState component', () => {
expect(screen.getByText('Go to home page')).toBeVisible();
});

it('should spread empty state props', () => {
render(<ErrorState headingLevel="h2" variant="xs" data-testid="test"/>);

expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument();
expect(screen.getByTestId('test')).toHaveClass('pf-m-xs');
});

});
28 changes: 22 additions & 6 deletions packages/module/src/ErrorState/ErrorState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@ import { createUseStyles } from 'react-jss'
import React from 'react';

const useStyles = createUseStyles({
errorIcon: {
fill: 'var(--pf-t--global--color--status--danger--default)',
},
errorDescription: {
margin: 'auto'
}
})

/** extends EmptyStateProps */
export interface ErrorStateProps extends Omit<EmptyStateProps, 'children' | 'titleText'> {
export interface ErrorStateProps extends Omit<EmptyStateProps, 'children' | 'titleText' | 'status'> {
/** Title of the error. */
titleText?: string;
/** A description of the error, if no body text is provided then it will be set to the defaultBodyText. */
Expand All @@ -33,12 +30,31 @@ export interface ErrorStateProps extends Omit<EmptyStateProps, 'children' | 'tit
customFooter?: React.ReactNode;
/** ErrorState OUIA ID */
ouiaId?: string | number;
/** Status of the error message. */
status?: 'danger' | 'warning' | 'success' | 'info' | 'custom' | 'none';
}

const ErrorState: React.FunctionComponent<ErrorStateProps> = ({ titleText = 'Something went wrong', bodyText, defaultBodyText, customFooter, ouiaId = "ErrorState", ...props }: ErrorStateProps) => {
const ErrorState: React.FunctionComponent<ErrorStateProps> = ({
titleText = 'Something went wrong',
bodyText,
defaultBodyText,
customFooter,
ouiaId = "ErrorState",
headingLevel = "h4",
status = EmptyStateStatus.danger,
variant = EmptyStateVariant.lg,
...props
}: ErrorStateProps) => {
const classes = useStyles();
return (
<EmptyState headingLevel="h4" status={EmptyStateStatus.danger} variant={EmptyStateVariant.lg} titleText={titleText} data-ouia-component-id={ouiaId} {...props}>
<EmptyState
headingLevel={headingLevel}
{...(status !== 'none' && { status } )}
variant={variant}
titleText={titleText}
data-ouia-component-id={ouiaId}
{...props}
>
<EmptyStateBody data-ouia-component-id={`${ouiaId}-body`}>
<Stack>
{bodyText ? <StackItem className={classes.errorDescription}>{bodyText}</StackItem> : defaultBodyText}
Expand Down
3 changes: 2 additions & 1 deletion packages/module/src/Maintenance/Maintenance.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const Maintenance: React.FunctionComponent<MaintenanceProps> = ({
redirectLinkText = 'status.redhat.com.',
customFooter = 'For more information please visit',
ouiaId = 'Maintenance',
headingLevel = 'h5',
...props
}: MaintenanceProps) => {
let formattedBodyText = bodyText;
Expand All @@ -44,7 +45,7 @@ const Maintenance: React.FunctionComponent<MaintenanceProps> = ({
}

return (
<EmptyState headingLevel="h5" status={EmptyStateStatus.danger} icon={HourglassHalfIcon} titleText={titleText} variant={EmptyStateVariant.lg} data-ouia-component-id={ouiaId} {...props}>
<EmptyState headingLevel={headingLevel} status={EmptyStateStatus.danger} icon={HourglassHalfIcon} titleText={titleText} variant={EmptyStateVariant.lg} data-ouia-component-id={ouiaId} {...props}>
<EmptyStateBody data-ouia-component-id={`${ouiaId}-body`}>
{bodyText ? formattedBodyText : defaultBodyText}
</EmptyStateBody>
Expand Down
3 changes: 2 additions & 1 deletion packages/module/src/MissingPage/MissingPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ export const MissingPage: React.FunctionComponent<MissingPageProps> = ({
titleText = 'We lost that page',
bodyText = "Let's find you a new one. Try a new search or return home.",
ouiaId = "MissingPage",
headingLevel = 'h1',
...props
}: MissingPageProps) => (
<EmptyState headingLevel='h1' icon={NotFoundIcon} variant={EmptyStateVariant.full} data-ouia-component-id={ouiaId} {...props} titleText={titleText}>
<EmptyState headingLevel={headingLevel} icon={NotFoundIcon} variant={EmptyStateVariant.full} data-ouia-component-id={ouiaId} {...props} titleText={titleText}>
<EmptyStateBody data-ouia-component-id={`${ouiaId}-body`}>{bodyText}</EmptyStateBody>
<EmptyStateFooter data-ouia-component-id={`${ouiaId}-footer`}>
<Button variant="link" component="a" href={toHomePageUrl} ouiaId={`${ouiaId}-home-button`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,10 @@ const UnauthorizedAccess: React.FunctionComponent<UnauthorizedAccessProps> = ({
showReturnButton = true,
className,
ouiaId = 'UnauthorizedAccess',
headingLevel = 'h5',
...props
}: UnauthorizedAccessProps) => (
<EmptyState headingLevel="h5" icon={Icon} variant={EmptyStateVariant.full} className={className} data-ouia-component-id={ouiaId} {...props} titleText={titleText}>
<EmptyState headingLevel={headingLevel} icon={Icon} variant={EmptyStateVariant.full} className={className} data-ouia-component-id={ouiaId} {...props} titleText={titleText}>
<EmptyStateBody data-ouia-component-id={`${ouiaId}-body`}>{bodyText}</EmptyStateBody>
<EmptyStateFooter data-ouia-component-id={`${ouiaId}-footer`}>
{primaryAction ? <EmptyStateActions>{primaryAction}</EmptyStateActions> : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ const UnavailableContent: React.FunctionComponent<UnavailableContentProps> = ({
titleText = 'This page is temporarily unavailable',
bodyText = 'Try refreshing the page. If the problem persists, contact your organization administrator or visit our status page for known outages.',
ouiaId = 'UnavailableContent',
headingLevel = "h5",
...props
}: UnavailableContentProps) => (
<EmptyState headingLevel="h5" status={EmptyStateStatus.danger} icon={ExclamationCircleIcon} titleText={titleText} variant={EmptyStateVariant.lg} data-ouia-component-id={ouiaId} {...props}>
<EmptyState headingLevel={headingLevel} status={EmptyStateStatus.danger} icon={ExclamationCircleIcon} titleText={titleText} variant={EmptyStateVariant.lg} data-ouia-component-id={ouiaId} {...props}>
<EmptyStateBody data-ouia-component-id={`${ouiaId}-body`}>
{bodyText}
</EmptyStateBody>
Expand Down
3 changes: 3 additions & 0 deletions packages/module/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ export * from './MultiContentCard';
export { default as MissingPage } from './MissingPage';
export * from './MissingPage';

export { default as Maintenance } from './Maintenance';
export * from './Maintenance';

export { default as LogSnippet } from './LogSnippet';
export * from './LogSnippet';

Expand Down

0 comments on commit 65f296b

Please sign in to comment.