Skip to content

Commit

Permalink
Feat(web-react): Introduce EmptyState component #DS-1309
Browse files Browse the repository at this point in the history
  • Loading branch information
curdaj committed Jun 24, 2024
1 parent 6c84429 commit b88eb64
Show file tree
Hide file tree
Showing 16 changed files with 288 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/web-react/scripts/entryPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const entryPoints = [
{ dirs: ['components', 'UNSTABLE_ActionLayout'] },
{ dirs: ['components', 'UNSTABLE_Avatar'] },
{ dirs: ['components', 'UNSTABLE_Divider'] },
{ dirs: ['components', 'UNSTABLE_EmptyState'] },
{ dirs: ['components', 'UNSTABLE_Slider'] },
{ dirs: ['components', 'VisuallyHidden'] },
];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# UNSTABLE EmptyState

⚠️ This component is UNSTABLE. It may significantly change at any point in the future.
Please use it with caution.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import classNames from 'classnames';
import React, { ReactElement } from 'react';
import { useStyleProps } from '../../hooks';
import { SpiritEmptyStateProps } from '../../types/emptyState';
import { Stack } from '../Stack';
import { useEmptyStateStyleProps } from './useEmptyStateStyleProps';

export const UNSTABLE_EmptyState = (props: SpiritEmptyStateProps): ReactElement => {
const { children, ...restProps } = props;
const { classProps, props: modifiedProps } = useEmptyStateStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

return (
<Stack
{...otherProps}
UNSAFE_className={classNames(classProps.root, styleProps.className)}
UNSAFE_style={styleProps.style}
>
{children}
</Stack>
);
};

export default UNSTABLE_EmptyState;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import classNames from 'classnames';
import React, { ReactElement } from 'react';
import { useStyleProps } from '../../hooks';
import { SpiritEmptyStateProps } from '../../types/emptyState';
import { Stack } from '../Stack';
import { useEmptyStateStyleProps } from './useEmptyStateStyleProps';

export const UNSTABLE_EmptyStateSection = (props: SpiritEmptyStateProps): ReactElement => {
const { children, ...restProps } = props;
const { classProps, props: modifiedProps } = useEmptyStateStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

return (
<Stack
{...otherProps}
UNSAFE_className={classNames(classProps.section, styleProps.className)}
UNSAFE_style={styleProps.style}
>
{children}
</Stack>
);
};

export default UNSTABLE_EmptyStateSection;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import UNSTABLE_EmptyState from '../UNSTABLE_EmptyState';

describe('UNSTABLE_EmptyState', () => {
classNamePrefixProviderTest(UNSTABLE_EmptyState, 'UNSTABLE_EmptyState');

stylePropsTest(UNSTABLE_EmptyState);

restPropsTest(UNSTABLE_EmptyState, 'div');

beforeEach(() => {
render(<UNSTABLE_EmptyState>Content</UNSTABLE_EmptyState>);
});

it('should have default classname', () => {
expect(screen.getByText('Content')).toHaveClass('UNSTABLE_EmptyState');
});

it('should render children', () => {
expect(screen.getByText('Content')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import { UNSTABLE_EmptyStateSection } from '..';

describe('UNSTABLE_EmptyStateSection', () => {
classNamePrefixProviderTest(UNSTABLE_EmptyStateSection, 'UNSTABLE_EmptyState__section');

stylePropsTest(UNSTABLE_EmptyStateSection);

restPropsTest(UNSTABLE_EmptyStateSection, 'div');

beforeEach(() => {
render(<UNSTABLE_EmptyStateSection>Content</UNSTABLE_EmptyStateSection>);
});

it('should have default classname', () => {
expect(screen.getByText('Content')).toHaveClass('UNSTABLE_EmptyState__section');
});

it('should render children', () => {
expect(screen.getByText('Content')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { renderHook } from '@testing-library/react';
import { useEmptyStateStyleProps } from '../useEmptyStateStyleProps';

describe('useEmptyStateStyleProps', () => {
it('should return defaults', () => {
const props = {};
const { result } = renderHook(() => useEmptyStateStyleProps(props));

expect(result.current.classProps.root).toBe('UNSTABLE_EmptyState');
expect(result.current.classProps.section).toBe('UNSTABLE_EmptyState__section');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { ButtonLink } from '../../Button';
import { Heading } from '../../Heading';
import { Link } from '../../Link';
import { Text } from '../../Text';
import { UNSTABLE_ActionLayout } from '../../UNSTABLE_ActionLayout';
import UNSTABLE_EmptyState from '../UNSTABLE_EmptyState';
import UNSTABLE_EmptyStateSection from '../UNSTABLE_EmptyStateSection';

const EmptyStateDefault = () => (
<UNSTABLE_EmptyState spacing="space-700">
<UNSTABLE_EmptyStateSection>
<div
style={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
width: '400px',
padding: '48px 24px',
textAlign: 'center',
gap: '32px',
border: '1px dashed #3EAC98',
background: '#FFF',
}}
>
<svg xmlns="http://www.w3.org/2000/svg" width="82" height="40" viewBox="0 0 82 40" fill="none">
<rect x="1.5" y="0.5" width="79" height="39" stroke="#3EAC98" strokeDasharray="4 4" />
<path
d="M1 29L24.1884 15.814L35.7826 24.1163L64.7681 8L81 24.1163"
stroke="#3EAC98"
strokeWidth="0.8"
strokeDasharray="4 4"
/>
</svg>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', textAlign: 'center', color: '#3EAC98' }}>
<Heading size="xsmall">Placeholder</Heading>
<Text>Replace me with your own component</Text>
</div>
</div>
</UNSTABLE_EmptyStateSection>
<UNSTABLE_EmptyStateSection spacing="space-500">
<Heading elementType="h2" size="xsmall">
Headline
</Heading>
<Text color="secondary">
In publishing and graphic design, lorem ipsum is common placeholder text used to demonstrate the graphic
elements
</Text>
</UNSTABLE_EmptyStateSection>
<UNSTABLE_EmptyStateSection>
<UNSTABLE_ActionLayout>
<ButtonLink color="primary" href="#">
Action
</ButtonLink>
<ButtonLink color="secondary" href="#">
Action
</ButtonLink>
</UNSTABLE_ActionLayout>
</UNSTABLE_EmptyStateSection>
<UNSTABLE_EmptyStateSection>
<Link href="#">Link to something</Link>
</UNSTABLE_EmptyStateSection>
</UNSTABLE_EmptyState>
);

export default EmptyStateDefault;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import DocsSection from '../../../../docs/DocsSections';
import EmptyStateDefault from './EmptyStateDefault';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<DocsSection title="Default" stackAlignment="stretch">
<EmptyStateDefault />
</DocsSection>
</React.StrictMode>,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{> web-react/demo}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { default as UNSTABLE_EmptyState } from './UNSTABLE_EmptyState';
export { default as UNSTABLE_EmptyStateSection } from './UNSTABLE_EmptyStateSection';
export * from './UNSTABLE_EmptyState';
export * from './UNSTABLE_EmptyStateSection';
export * from './useEmptyStateStyleProps';
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { Markdown } from '@storybook/blocks';
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { ButtonLink } from '../../Button';
import { Heading } from '../../Heading';
import { Link } from '../../Link';
import { Text } from '../../Text';
import { UNSTABLE_ActionLayout } from '../../UNSTABLE_ActionLayout';
import ReadMe from '../README.md';
import { UNSTABLE_EmptyStateSection, UNSTABLE_EmptyState } from '..';

const meta: Meta<typeof UNSTABLE_EmptyState> = {
title: 'Experimental/UNSTABLE_EmptyState',
component: UNSTABLE_EmptyState,
parameters: {
docs: {
page: () => <Markdown>{ReadMe}</Markdown>,
},
},
argTypes: {},
args: {
spacing: 'space-700',
children: (
<>
<UNSTABLE_EmptyStateSection spacing="space-500">
<Heading elementType="h2" size="xsmall">
Headline
</Heading>
<Text color="secondary">
In publishing and graphic design, lorem ipsum is common placeholder text used to demonstrate the graphic
elements
</Text>
</UNSTABLE_EmptyStateSection>
<UNSTABLE_EmptyStateSection>
<UNSTABLE_ActionLayout>
<ButtonLink color="primary" href="#">
Action
</ButtonLink>
<ButtonLink color="secondary" href="#">
Action
</ButtonLink>
</UNSTABLE_ActionLayout>
</UNSTABLE_EmptyStateSection>
<UNSTABLE_EmptyStateSection>
<Link href="/">Link to something</Link>
</UNSTABLE_EmptyStateSection>
</>
),
},
};

export default meta;
type Story = StoryObj<typeof UNSTABLE_EmptyState>;

export const Playground: Story = {
name: 'UNSTABLE_EmptyState',
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { useClassNamePrefix } from '../../hooks';
import { SpiritEmptyStateProps } from '../../types/emptyState';

export interface EmptyStateStyles<T> {
classProps: {
root: string;
section: string;
};
props: T;
}

export function useEmptyStateStyleProps(props: SpiritEmptyStateProps): EmptyStateStyles<SpiritEmptyStateProps> {
const emptyStateClass = useClassNamePrefix('UNSTABLE_EmptyState');
const sectionClass = `${emptyStateClass}__section`;

return {
classProps: {
root: emptyStateClass,
section: sectionClass,
},
props,
};
}
4 changes: 4 additions & 0 deletions packages/web-react/src/types/emptyState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ChildrenProps, StyleProps } from './shared';
import { SpiritStackProps } from './stack';

export interface SpiritEmptyStateProps extends ChildrenProps, SpiritStackProps, StyleProps {}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit b88eb64

Please sign in to comment.