Skip to content

Commit

Permalink
fix(emptystate): incorporate feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
jonguenther committed Dec 2, 2020
1 parent 8d3b762 commit 1c3f63b
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 107 deletions.
12 changes: 12 additions & 0 deletions .storybook/__snapshots__/Welcome.story.storyshot
Original file line number Diff line number Diff line change
Expand Up @@ -2868,6 +2868,18 @@ exports[`Storybook Snapshot tests and console checks Storyshots 0/Getting Starte
IconDropdown
</div>
</div>
<div
className="bx--structured-list-row"
>
<div
className="bx--structured-list-td"
/>
<div
className="bx--structured-list-td"
>
EmptyState
</div>
</div>
<div
className="bx--structured-list-row"
>
Expand Down
104 changes: 75 additions & 29 deletions src/components/EmptyState/EmptyState.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import {
EmptystateNotauthorizedIcon as NotAuthImage,
} from '../../icons/components';

import './_emptystate.scss';

const { iotPrefix } = settings;

const images = {
const icons = {
error: ErrorImage,
error404: Error404Image,
empty: EmptyImage,
Expand All @@ -26,37 +24,61 @@ const images = {
success: SuccessImage,
};

const actionProp = PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
}),
]);

// TODO: Discuss whether actions can be custom components, e.g. for showing details in error messages.
const props = {
/** Title of empty state */
title: PropTypes.string.isRequired,
/** Description of empty state */
body: PropTypes.string.isRequired,
/** Optional image of state */
image: PropTypes.oneOfType([
icon: PropTypes.oneOfType([
PropTypes.func,
PropTypes.oneOf([...Object.keys(images), '']),
PropTypes.oneOf([
'error',
'error404',
'empty',
'not-authorized',
'no-result',
'success',
'',
]),
]),
/** Optional action for container */
action: actionProp,
action: PropTypes.shape({
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
}),
// action: PropTypes.oneOfType([
// PropTypes.func,
// PropTypes.shape({
// label: PropTypes.string.isRequired,
// onClick: PropTypes.func.isRequired,
// }),
// ]),
/** Optional secondary action for container */
secondaryAction: actionProp,
secondaryAction: PropTypes.shape({
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
}),
// secondaryAction: PropTypes.oneOfType([
// PropTypes.func,
// PropTypes.shape({
// label: PropTypes.string.isRequired,
// onClick: PropTypes.func.isRequired,
// }),
// ]),
/** Specify an optional className to be applied to the container */
className: PropTypes.string,
/** Specify a testid for testing this component */
testID: PropTypes.string,
};

const defaultProps = {
action: null,
secondaryAction: null,
image: '',
icon: '',
className: '',
testID: 'EmptyState',
};

/**
Expand All @@ -65,43 +87,67 @@ const defaultProps = {
*/
const EmptyState = ({
title,
image,
icon,
body,
action,
secondaryAction,
className,
testID,
}) => (
<div className={`${iotPrefix}--empty-state ${className}`}>
<div
className={`${iotPrefix}--empty-state ${className}`}
data-testid={testID}>
<div className={`${iotPrefix}--empty-state--content`}>
{image &&
React.createElement(typeof image === 'string' ? images[image] : image, {
{icon &&
React.createElement(typeof icon === 'string' ? icons[icon] : icon, {
className: `${iotPrefix}--empty-state--icon`,
alt: '',
'data-testid': 'emptystate-icon',
'data-testid': `${testID}-icon`,
})}
<h3 className={`${iotPrefix}--empty-state--title`}>{title}</h3>
<p className={`${iotPrefix}--empty-state--text`}>{body}</p>
<h3
className={`${iotPrefix}--empty-state--title`}
data-testid={`${testID}-title`}>
{title}
</h3>
<p
className={`${iotPrefix}--empty-state--text`}
data-testid={`${testID}-body`}>
{body}
</p>
{action && (
<div className={`${iotPrefix}--empty-state--action`}>
{action.label ? (
<div
className={`${iotPrefix}--empty-state--action`}
data-testid={`${testID}-action`}>
<Button onClick={action.onClick && action.onClick}>
{action.label}
</Button>
{/* {action.label ? (
<Button onClick={action.onClick && action.onClick}>
{action.label}
</Button>
) : (
action
)}
)} */}
</div>
)}
{secondaryAction && (
<div className={`${iotPrefix}--empty-state--link`}>
{secondaryAction.label ? (
<div
className={`${iotPrefix}--empty-state--link`}
data-testid={`${testID}-secondaryAction`}>
{secondaryAction.label && (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<Link onClick={secondaryAction.onClick && secondaryAction.onClick}>
{secondaryAction.label}
</Link>
)}
{/* {secondaryAction.label ? (
// eslint-disable-next-line jsx-a11y/anchor-is-valid
<Link onClick={secondaryAction.onClick && secondaryAction.onClick}>
{secondaryAction.label}
</Link>
) : (
secondaryAction
)}
)} */}
</div>
)}
</div>
Expand Down
55 changes: 24 additions & 31 deletions src/components/EmptyState/EmptyState.story.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@ import { DashboardIcon } from '../../icons/components';

import EmptyState from './EmptyState';

const commonActions = {
action: {
label: text('action.label', 'Optional action'),
onClick: action('action onClick'),
},
secondaryAction: {
label: text('link.label', 'Optional link'),
onClick: action('secondaryAction onClick'),
},
};

export default {
title: 'Watson IoT/EmptyState',

Expand Down Expand Up @@ -49,7 +38,7 @@ export default {

export const FirstTimeUse = () => (
<EmptyState
image="empty"
icon="empty"
title="You don’t have any [variable] yet"
body="Optional extra sentence or sentences to describe the resource and how to create it or the action a first-time user needs to take."
action={{
Expand All @@ -65,23 +54,23 @@ FirstTimeUse.story = {

export const NoSearchResultsFound = () => (
<EmptyState
image="no-result"
icon="no-result"
title="No results found"
body="Subtext is optional because this user experience is common and the user knows how to return to the search mechanism. Use an optional extra sentence or sentences to explain how to adjust search parameters or prompt user action."
/>
);

export const Success = () => (
<EmptyState
image="success"
icon="success"
title="Success"
body="Optional extra sentence or sentences to describe the process or procedure that completed successfully. If needed, describe the next step that the user needs to take."
/>
);

export const Page404 = () => (
<EmptyState
image="error404"
icon="error404"
title="Uh oh. Something’s not right."
body="Optional extra sentence or sentences to describe further details about the error and, if applicable, how the user can fix it."
action={{
Expand All @@ -97,7 +86,7 @@ Page404.story = {

export const DataMissing = () => (
<EmptyState
image="empty"
icon="empty"
title="No [variable] to show"
body="Optional extra sentence or sentences to describe the data and how to create it, the action a user needs to take, or to describe why the data is missing. For example, in a scenario in which no errors occurred, the optional text might describe why no errors are displayed."
action={{
Expand All @@ -109,7 +98,7 @@ export const DataMissing = () => (

export const Error = () => (
<EmptyState
image="error"
icon="error"
title="Oops! We’re having trouble [problem]"
body="Optional extra sentence or sentences to describe further details about the error and, if applicable, how the user can fix it. Can provide information about who to contact if the error persists."
action={{
Expand All @@ -121,7 +110,7 @@ export const Error = () => (

export const NotAuthorized = () => (
<EmptyState
image="not-authorized"
icon="not-authorized"
title="You don’t have permission to [variable]"
body="Optional extra sentence or sentences to describe any action that the user can take or who to contact regarding permissions."
action={{
Expand All @@ -133,7 +122,7 @@ export const NotAuthorized = () => (

export const NotConfigured = () => (
<EmptyState
image="empty"
icon="empty"
title="Configure your [variable]"
body="Optional extra sentence or sentences to describe the [variable] and how to configure it or set it up."
action={{
Expand All @@ -159,7 +148,7 @@ export const WithoutIcon = () => (

export const WithCustomIcon = () => (
<EmptyState
image={DashboardIcon}
icon={DashboardIcon}
title="Empty state with a custom icon"
body="Custom icons can be used in addition to the preconfigured options."
action={{
Expand All @@ -171,26 +160,30 @@ export const WithCustomIcon = () => (

export const Playground = () => (
<EmptyState
image={select('image', [
'error',
'error404',
'empty',
'not-authorized',
'no-result',
'success',
null,
], 'empty')}
icon={select(
'image',
[
'error',
'error404',
'empty',
'not-authorized',
'no-result',
'success',
null,
],
'empty'
)}
title={text('title', 'This is an empty state you can configure via knobs')}
body={text(
'body',
'You can create empty states without images, although it is recommended to always use images. The secondary action should be a text link.'
)}
action={{
label: text('action.label','Primary action'),
label: text('action.label', 'Primary action'),
onClick: action('action onClick'),
}}
secondaryAction={{
label: text('secondaryAction.label','Secondary action'),
label: text('secondaryAction.label', 'Secondary action'),
onClick: action('secondaryAction onClick'),
}}
/>
Expand Down
Loading

0 comments on commit 1c3f63b

Please sign in to comment.