Skip to content

Commit

Permalink
feat(EmptyState): update EmptyState with updates for penta
Browse files Browse the repository at this point in the history
  • Loading branch information
wise-king-sullyman committed Jan 8, 2024
1 parent e911735 commit 1a9302e
Show file tree
Hide file tree
Showing 45 changed files with 311 additions and 405 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import {
ButtonVariant,
EmptyState,
EmptyStateBody,
EmptyStateIcon,
EmptyStateActions,
EmptyStateVariant,
EmptyStateFooter,
getResizeObserver,
Popover,
PopoverProps,
TooltipPosition,
EmptyStateHeader
TooltipPosition
} from '@patternfly/react-core';
import MonacoEditor, { ChangeHandler, EditorDidMount } from 'react-monaco-editor';
import { editor } from 'monaco-editor/esm/vs/editor/editor.api';
Expand Down Expand Up @@ -520,12 +518,7 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {
const emptyState =
providedEmptyState ||
(isUploadEnabled ? (
<EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateHeader
titleText={emptyStateTitle}
icon={<EmptyStateIcon icon={CodeIcon} />}
headingLevel="h4"
/>
<EmptyState variant={EmptyStateVariant.sm} titleText={emptyStateTitle} icon={CodeIcon} headingLevel="h4">
<EmptyStateBody>{emptyStateBody}</EmptyStateBody>
{!isReadOnly && (
<EmptyStateFooter>
Expand All @@ -543,12 +536,7 @@ class CodeEditor extends React.Component<CodeEditorProps, CodeEditorState> {
)}
</EmptyState>
) : (
<EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateHeader
titleText={emptyStateTitle}
icon={<EmptyStateIcon icon={CodeIcon} />}
headingLevel="h4"
/>
<EmptyState variant={EmptyStateVariant.sm} titleText={emptyStateTitle} icon={CodeIcon} headingLevel="h4">
{!isReadOnly && (
<EmptyStateFooter>
<EmptyStateActions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import {
SearchInput,
EmptyState,
EmptyStateVariant,
EmptyStateHeader,
EmptyStateFooter,
EmptyStateBody,
EmptyStateIcon,
EmptyStateActions
} from '@patternfly/react-core';
import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
Expand Down Expand Up @@ -139,8 +137,7 @@ export const DualListSelectorComposable: React.FunctionComponent = () => {
};

const buildEmptyState = (isAvailable: boolean) => (
<EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateHeader headingLevel="h4" titleText="No results found" icon={<EmptyStateIcon icon={SearchIcon} />} />
<EmptyState headingLevel="h4" titleText="No results found" icon={SearchIcon} variant={EmptyStateVariant.sm}>
<EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
<EmptyStateFooter>
<EmptyStateActions>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,9 @@ import {
Button,
EmptyState,
EmptyStateVariant,
EmptyStateHeader,
EmptyStateFooter,
EmptyStateBody,
EmptyStateActions,
EmptyStateIcon
EmptyStateActions
} from '@patternfly/react-core';
import AngleDoubleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-double-left-icon';
import AngleLeftIcon from '@patternfly/react-icons/dist/esm/icons/angle-left-icon';
Expand Down Expand Up @@ -258,12 +256,7 @@ export const DualListSelectorComposableTree: React.FunctionComponent<ExampleProp
listMinHeight="300px"
>
{filterApplied && options.length === 0 && (
<EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateHeader
headingLevel="h4"
titleText="No results found"
icon={<EmptyStateIcon icon={SearchIcon} />}
/>
<EmptyState headingLevel="h4" titleText="No results found" icon={SearchIcon} variant={EmptyStateVariant.sm}>
<EmptyStateBody>No results match the filter criteria. Clear all filters and try again.</EmptyStateBody>
<EmptyStateFooter>
<EmptyStateActions>
Expand Down
76 changes: 59 additions & 17 deletions packages/react-core/src/components/EmptyState/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/EmptyState/empty-state';
import { EmptyStateHeader, EmptyStateHeadingLevel, EmptyStateIconProps } from './index';

export enum EmptyStateVariant {
'xs' = 'xs',
Expand All @@ -10,37 +11,78 @@ export enum EmptyStateVariant {
full = 'full'
}

export type EmptyStateStatus = 'danger' | 'warning' | 'success' | 'info' | 'custom';
export type EmptyStateVariantType = 'xs' | 'sm' | 'lg' | 'xl' | 'full';

export interface EmptyStateProps extends React.HTMLProps<HTMLDivElement> {
/** Additional classes added to the empty state */
className?: string;
/** Content rendered inside the empty state */
children: React.ReactNode;
children?: React.ReactNode;
/** Modifies empty state max-width and sizes of icon, title and body */
variant?: 'xs' | 'sm' | 'lg' | 'xl' | 'full';
variant?: EmptyStateVariantType;
/** Cause component to consume the available height of its container */
isFullHeight?: boolean;
/** Status style options added to the empty state icon */
status?: EmptyStateStatus;
/** Additional class names to apply to the empty state header */
headerClassName?: string;
/** Additional props passed to the empty state header container */
headerProps?: React.HTMLProps<HTMLDivElement>;
/** Additional classes added to the title inside empty state header */
titleClassName?: string;
/** Text of the title inside empty state header, will be wrapped in headingLevel */
titleText: React.ReactNode;
/** Empty state icon element to be rendered. Can also be a spinner component */
icon?: React.ComponentType<any>;
/** Additional props passed to the icon element */
iconProps?: EmptyStateIconProps;
/** The heading level to use, default is h1 */
headingLevel?: EmptyStateHeadingLevel;
}

export const EmptyStateContext = React.createContext<{ status: EmptyStateProps['status'] }>({ status: undefined });

export const EmptyState: React.FunctionComponent<EmptyStateProps> = ({
children,
className,
variant = EmptyStateVariant.full,
variant = 'full',
isFullHeight,
status,
icon,
titleText,
titleClassName,
headerClassName,
headingLevel,
headerProps,
...props
}: EmptyStateProps) => (
<div
className={css(
styles.emptyState,
variant === 'xs' && styles.modifiers.xs,
variant === 'sm' && styles.modifiers.sm,
variant === 'lg' && styles.modifiers.lg,
variant === 'xl' && styles.modifiers.xl,
isFullHeight && styles.modifiers.fullHeight,
className
)}
{...props}
>
<div className={css(styles.emptyStateContent)}>{children}</div>
</div>
<EmptyStateContext.Provider value={{ status }}>
<div
className={css(
styles.emptyState,
variant === 'xs' && styles.modifiers.xs,
variant === 'sm' && styles.modifiers.sm,
variant === 'lg' && styles.modifiers.lg,
variant === 'xl' && styles.modifiers.xl,
isFullHeight && styles.modifiers.fullHeight,
status && styles.modifiers[status],
className
)}
{...props}
>
<div className={css(styles.emptyStateContent)}>
<EmptyStateHeader
icon={icon}
titleText={titleText}
titleClassName={titleClassName}
className={headerClassName}
headingLevel={headingLevel}
{...headerProps}
/>
{children}
</div>
</div>
</EmptyStateContext.Provider>
);
EmptyState.displayName = 'EmptyState';
45 changes: 25 additions & 20 deletions packages/react-core/src/components/EmptyState/EmptyStateHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,47 @@
import * as React from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/EmptyState/empty-state';
import { EmptyStateIconProps } from './EmptyStateIcon';
import { EmptyStateContext } from './EmptyState';
import { EmptyStateIcon, EmptyStateIconProps } from './EmptyStateIcon';
import { variantIcons } from '../Alert/AlertIcon';

export type EmptyStateHeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';

export interface EmptyStateHeaderProps extends React.HTMLProps<HTMLDivElement> {
/** Content rendered inside the empty state header, either in addition to or instead of the titleText prop */
children?: React.ReactNode;
/** Additional classes added to the empty state header */
className?: string;
/** Additional classes added to the title inside empty state header */
titleClassName?: string;
/** Text of the title inside empty state header, will be wrapped in headingLevel */
titleText?: React.ReactNode;
/** Empty state icon element to be rendered */
icon?: React.ReactElement<EmptyStateIconProps>;
titleText: React.ReactNode;
/** Empty state icon element to be rendered. Can also be a spinner component */
icon?: React.ComponentType<any>;
/** Additional props passed to the icon element */
iconProps?: EmptyStateIconProps;
/** The heading level to use, default is h1 */
headingLevel?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6';
headingLevel?: EmptyStateHeadingLevel;
}

export const EmptyStateHeader: React.FunctionComponent<EmptyStateHeaderProps> = ({
children,
className,
titleClassName,
titleText,
headingLevel: HeadingLevel = 'h1',
icon,
icon: CustomIcon,
iconProps,
...props
}: EmptyStateHeaderProps) => (
<div className={css(`${styles.emptyState}__header`, className)} {...props}>
{icon}
{(titleText || children) && (
}: EmptyStateHeaderProps) => {
const { status } = React.useContext(EmptyStateContext);
const StatusIcon = status && variantIcons[status];
const Icon = CustomIcon || StatusIcon;

return (
<div className={css(`${styles.emptyState}__header`, className)} {...props}>
{Icon && <EmptyStateIcon icon={Icon} {...iconProps} />}
<div className={css(`${styles.emptyState}__title`)}>
{titleText && (
<HeadingLevel className={css(styles.emptyStateTitleText, titleClassName)}>{titleText}</HeadingLevel>
)}
{children}
<HeadingLevel className={css(styles.emptyStateTitleText, titleClassName)}>{titleText}</HeadingLevel>
</div>
)}
</div>
);
</div>
);
};
EmptyStateHeader.displayName = 'EmptyStateHeader';
16 changes: 3 additions & 13 deletions packages/react-core/src/components/EmptyState/EmptyStateIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,7 @@ import * as React from 'react';
import { css } from '@patternfly/react-styles';
import styles from '@patternfly/react-styles/css/components/EmptyState/empty-state';
import { Spinner } from '../Spinner';
import cssIconColor from '@patternfly/react-tokens/dist/esm/c_empty_state__icon_Color';

export interface IconProps extends Omit<React.HTMLProps<SVGElement>, 'size'> {
/** Changes the color of the icon. */
color?: string;
}

export interface EmptyStateIconProps extends IconProps {
export interface EmptyStateIconProps {
/** Additional classes added to the empty state icon */
className?: string;
/** Icon component to be rendered. Can also be a spinner component */
Expand All @@ -21,15 +14,12 @@ const isSpinner = (icon: React.ReactElement<any>) => icon.type === Spinner;
export const EmptyStateIcon: React.FunctionComponent<EmptyStateIconProps> = ({
className,
icon: IconComponent,
color,
...props
}: EmptyStateIconProps) => {
const iconIsSpinner = isSpinner(<IconComponent />);

return (
<div
className={css(styles.emptyStateIcon)}
{...(color && !iconIsSpinner && { style: { [cssIconColor.name]: color } as React.CSSProperties })}
>
<div className={css(styles.emptyStateIcon)}>
<IconComponent className={className} aria-hidden={!iconIsSpinner} {...props} />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ import { EmptyStateActions } from '../EmptyStateActions';
import { Button } from '../../Button';
import { EmptyStateHeader } from '../EmptyStateHeader';
import { EmptyStateFooter } from '../EmptyStateFooter';
import { EmptyStateIcon } from '../../../../dist/esm';
import styles from '@patternfly/react-styles/css/components/EmptyState/empty-state';

describe('EmptyState', () => {
test('Main', () => {
const { asFragment } = render(
<EmptyState>
<EmptyStateHeader titleText="HTTP Proxies" />
<EmptyState titleText="HTTP Proxies">
<EmptyStateBody>
Defining HTTP Proxies that exist on your network allows you to perform various actions through those proxies.
</EmptyStateBody>
Expand All @@ -38,27 +36,21 @@ describe('EmptyState', () => {

test('Main variant large', () => {
const { asFragment } = render(
<EmptyState variant={EmptyStateVariant.lg}>
<EmptyStateHeader titleText="EmptyState large" />
</EmptyState>
<EmptyState titleText="EmptyState large" variant={EmptyStateVariant.lg}></EmptyState>
);
expect(asFragment()).toMatchSnapshot();
});

test('Main variant small', () => {
const { asFragment } = render(
<EmptyState variant={EmptyStateVariant.sm}>
<EmptyStateHeader titleText="EmptyState small" />
</EmptyState>
<EmptyState titleText="EmptyState small" variant={EmptyStateVariant.sm}></EmptyState>
);
expect(asFragment()).toMatchSnapshot();
});

test('Main variant xs', () => {
const { asFragment } = render(
<EmptyState variant={EmptyStateVariant.xs}>
<EmptyStateHeader titleText="EmptyState extra small" />
</EmptyState>
<EmptyState titleText="EmptyState extra small" variant={EmptyStateVariant.xs}></EmptyState>
);
expect(asFragment()).toMatchSnapshot();
});
Expand All @@ -75,15 +67,13 @@ describe('EmptyState', () => {

test('Full height', () => {
const { asFragment } = render(
<EmptyState isFullHeight variant={EmptyStateVariant.lg}>
<EmptyStateHeader titleText="EmptyState large" />
</EmptyState>
<EmptyState titleText="EmptyState large" isFullHeight variant={EmptyStateVariant.lg}></EmptyState>
);
expect(asFragment()).toMatchSnapshot();
});

test('Header with icon', () => {
const { asFragment } = render(<EmptyStateHeader icon={<EmptyStateIcon icon={AddressBookIcon} />} />);
const { asFragment } = render(<EmptyStateHeader titleText="Empty state" icon={AddressBookIcon} />);
expect(asFragment()).toMatchSnapshot();
});

Expand All @@ -102,11 +92,6 @@ describe('EmptyState', () => {
expect(screen.getByRole('heading', { level: 3, name: 'Empty state' })).toHaveClass(styles.emptyStateTitleText);
});

test('Headers render children', () => {
render(<EmptyStateHeader>Title text</EmptyStateHeader>);
expect(screen.getByText('Title text')).toBeVisible();
});

test('Footer', () => {
render(<EmptyStateFooter className="custom-empty-state-footer" data-testid="actions-test-id" />);
expect(screen.getByTestId('actions-test-id')).toHaveClass('custom-empty-state-footer');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import { Spinner } from '../../../Spinner/Spinner';
it('EmptyStateIcon should match snapshot (auto-generated)', () => {
const { asFragment } = render(
<EmptyStateIcon
color={'string'}
title={'string'}
className={"''"}
icon={UserIcon}
/>
Expand All @@ -23,8 +21,6 @@ it('EmptyStateIcon should match snapshot (auto-generated)', () => {
it('EmptyStateIcon should match snapshot for variant container', () => {
const { asFragment } = render(
<EmptyStateIcon
color={'string'}
title={'string'}
className={"''"}
icon={Spinner}
/>
Expand Down
Loading

0 comments on commit 1a9302e

Please sign in to comment.