Skip to content

Commit

Permalink
Merge pull request #26481 from storybookjs/feat/addon-panel-empty-state
Browse files Browse the repository at this point in the history
UI: Improve empty state of addon panel
  • Loading branch information
yannbf authored Mar 19, 2024
2 parents 718ae4a + 5152b7c commit ccc04e5
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 138 deletions.
4 changes: 2 additions & 2 deletions code/addons/a11y/src/components/Report/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FC } from 'react';
import React, { Fragment } from 'react';
import { Placeholder } from '@storybook/components';
import { EmptyTabContent } from '@storybook/components';
import type { Result } from 'axe-core';

import { Item } from './Item';
Expand All @@ -18,7 +18,7 @@ export const Report: FC<ReportProps> = ({ items, empty, type }) => (
{items && items.length ? (
items.map((item) => <Item item={item} key={`${type}:${item.id}`} type={type} />)
) : (
<Placeholder key="placeholder">{empty}</Placeholder>
<EmptyTabContent title={empty} />
)}
</Fragment>
);
74 changes: 20 additions & 54 deletions code/addons/interactions/src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,11 @@
import React, { useEffect, useState } from 'react';
import { Link } from '@storybook/components';
import { Link, EmptyTabContent } from '@storybook/components';
import { DocumentIcon, VideoIcon } from '@storybook/icons';
import { Consumer, useStorybookApi } from '@storybook/manager-api';
import { useStorybookApi } from '@storybook/manager-api';
import { styled } from '@storybook/theming';

import { DOCUMENTATION_LINK, TUTORIAL_VIDEO_LINK } from '../constants';

const Wrapper = styled.div(({ theme }) => ({
height: '100%',
display: 'flex',
padding: 0,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 15,
background: theme.background.content,
}));

const Content = styled.div({
display: 'flex',
flexDirection: 'column',
gap: 4,
maxWidth: 415,
});

const Title = styled.div(({ theme }) => ({
fontWeight: theme.typography.weight.bold,
fontSize: theme.typography.size.s2 - 1,
textAlign: 'center',
color: theme.textColor,
}));

const Description = styled.div(({ theme }) => ({
fontWeight: theme.typography.weight.regular,
fontSize: theme.typography.size.s2 - 1,
textAlign: 'center',
color: theme.textMutedColor,
}));

const Links = styled.div(({ theme }) => ({
display: 'flex',
fontSize: theme.typography.size.s2 - 1,
Expand Down Expand Up @@ -73,27 +41,25 @@ export const Empty = () => {
if (isLoading) return null;

return (
<Wrapper>
<Content>
<Title>Interaction testing</Title>
<Description>
<EmptyTabContent
title="Interaction testing"
description={
<>
Interaction tests allow you to verify the functional aspects of UIs. Write a play function
for your story and you&apos;ll see it run here.
</Description>
</Content>
<Links>
<Link href={TUTORIAL_VIDEO_LINK} target="_blank" withArrow>
<VideoIcon /> Watch 8m video
</Link>
<Divider />
<Consumer>
{({ state }) => (
<Link href={docsUrl} target="_blank" withArrow>
<DocumentIcon /> Read docs
</Link>
)}
</Consumer>
</Links>
</Wrapper>
</>
}
footer={
<Links>
<Link href={TUTORIAL_VIDEO_LINK} target="_blank" withArrow>
<VideoIcon /> Watch 8m video
</Link>
<Divider />
<Link href={docsUrl} target="_blank" withArrow>
<DocumentIcon /> Read docs
</Link>
</Links>
}
/>
);
};
1 change: 1 addition & 0 deletions code/ui/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const config: StorybookConfig = {
'@storybook/addon-interactions',
'@storybook/addon-storysource',
'@storybook/addon-designs',
'@storybook/addon-a11y',
'@chromatic-com/storybook',
],
build: {
Expand Down
93 changes: 40 additions & 53 deletions code/ui/blocks/src/components/ArgsTable/Empty.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
import { styled } from '@storybook/theming';
import { Link } from '@storybook/components';
import { DocumentIcon, SupportIcon, VideoIcon } from '@storybook/icons';
import { Link, EmptyTabContent } from '@storybook/components';
import { DocumentIcon, VideoIcon } from '@storybook/icons';

interface EmptyProps {
inAddonPanel?: boolean;
Expand All @@ -22,27 +22,6 @@ const Wrapper = styled.div<{ inAddonPanel?: boolean }>(({ inAddonPanel, theme })
boxShadow: 'rgba(0, 0, 0, 0.10) 0 1px 3px 0',
}));

const Content = styled.div({
display: 'flex',
flexDirection: 'column',
gap: 4,
maxWidth: 415,
});

const Title = styled.div(({ theme }) => ({
fontWeight: theme.typography.weight.bold,
fontSize: theme.typography.size.s2 - 1,
textAlign: 'center',
color: theme.textColor,
}));

const Description = styled.div(({ theme }) => ({
fontWeight: theme.typography.weight.regular,
fontSize: theme.typography.size.s2 - 1,
textAlign: 'center',
color: theme.textMutedColor,
}));

const Links = styled.div(({ theme }) => ({
display: 'flex',
fontSize: theme.typography.size.s2 - 1,
Expand Down Expand Up @@ -73,39 +52,47 @@ export const Empty: FC<EmptyProps> = ({ inAddonPanel }) => {

return (
<Wrapper inAddonPanel={inAddonPanel}>
<Content>
<Title>
{inAddonPanel
<EmptyTabContent
title={
inAddonPanel
? 'Interactive story playground'
: "Args table with interactive controls couldn't be auto-generated"}
</Title>
<Description>
Controls give you an easy to use interface to test your components. Set your story args
and you&apos;ll see controls appearing here automatically.
</Description>
</Content>
<Links>
{inAddonPanel && (
: "Args table with interactive controls couldn't be auto-generated"
}
description={
<>
<Link href="https://youtu.be/0gOfS6K0x0E" target="_blank" withArrow>
<VideoIcon /> Watch 5m video
</Link>
<Divider />
<Link
href="https://storybook.js.org/docs/essentials/controls"
target="_blank"
withArrow
>
<DocumentIcon /> Read docs
</Link>
Controls give you an easy to use interface to test your components. Set your story args
and you&apos;ll see controls appearing here automatically.
</>
)}
{!inAddonPanel && (
<Link href="https://storybook.js.org/docs/essentials/controls" target="_blank" withArrow>
<SupportIcon /> Learn how to set that up
</Link>
)}
</Links>
}
footer={
<Links>
{inAddonPanel && (
<>
<Link href="https://youtu.be/0gOfS6K0x0E" target="_blank" withArrow>
<VideoIcon /> Watch 5m video
</Link>
<Divider />
<Link
href="https://storybook.js.org/docs/essentials/controls"
target="_blank"
withArrow
>
<DocumentIcon /> Read docs
</Link>
</>
)}
{!inAddonPanel && (
<Link
href="https://storybook.js.org/docs/essentials/controls"
target="_blank"
withArrow
>
<DocumentIcon /> Learn how to set that up
</Link>
)}
</Links>
}
/>
</Wrapper>
);
};
51 changes: 51 additions & 0 deletions code/ui/components/src/components/tabs/EmptyTabContent.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { EmptyTabContent } from './EmptyTabContent';
import { DocumentIcon } from '@storybook/icons';
import { Link } from '@storybook/components';
import type { Meta, StoryObj } from '@storybook/react';

export default {
component: EmptyTabContent,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof EmptyTabContent>;

type Story = StoryObj<typeof EmptyTabContent>;

export const OnlyTitle: Story = {
args: {
title: 'Nothing found',
},
};

export const TitleAndDescription: Story = {
args: {
title: 'Nothing found',
description: 'Sorry, there is nothing to display here.',
},
};

export const TitleAndFooter: Story = {
args: {
title: 'Nothing found',
footer: (
<Link href="foo" withArrow>
<DocumentIcon /> See the docs
</Link>
),
},
};

export const TitleDescriptionAndFooter: Story = {
args: {
title: 'Nothing found',
description: 'Sorry, there is nothing to display here.',
footer: (
<Link href="foo" withArrow>
<DocumentIcon /> See the docs
</Link>
),
},
};
52 changes: 52 additions & 0 deletions code/ui/components/src/components/tabs/EmptyTabContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { styled } from '@storybook/theming';

const Wrapper = styled.div(({ theme }) => ({
height: '100%',
display: 'flex',
padding: 30,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
gap: 15,
background: theme.background.content,
}));

const Content = styled.div({
display: 'flex',
flexDirection: 'column',
gap: 4,
maxWidth: 415,
});

const Title = styled.div(({ theme }) => ({
fontWeight: theme.typography.weight.bold,
fontSize: theme.typography.size.s2 - 1,
textAlign: 'center',
color: theme.textColor,
}));

const Description = styled.div(({ theme }) => ({
fontWeight: theme.typography.weight.regular,
fontSize: theme.typography.size.s2 - 1,
textAlign: 'center',
color: theme.textMutedColor,
}));

interface Props {
title: React.ReactNode;
description?: React.ReactNode;
footer?: React.ReactNode;
}

export const EmptyTabContent = ({ title, description, footer }: Props) => {
return (
<Wrapper>
<Content>
<Title>{title}</Title>
{description && <Description>{description}</Description>}
</Content>
{footer}
</Wrapper>
);
};
Loading

0 comments on commit ccc04e5

Please sign in to comment.