Skip to content

Commit

Permalink
UI: Improve empty state of addon panel
Browse files Browse the repository at this point in the history
  • Loading branch information
yannbf committed Mar 13, 2024
1 parent cfb2fbe commit a3d75bb
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 32 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
import React, { Fragment } from 'react';
import type { Meta, StoryFn } from '@storybook/react';

import { Placeholder } from './placeholder';
import { Link } from '../typography/link/link';

export default {
component: Placeholder,
};
} as Meta<typeof Placeholder>;

export const SingleChild = () => (
type Story = StoryFn<typeof Placeholder>;

export const SingleChild: Story = () => (
<Placeholder>This is a placeholder with single child, it's bolded</Placeholder>
);
export const TwoChildren = () => (

export const TwoChildren: Story = () => (
<Placeholder>
<Fragment key="title">This has two children, the first bold</Fragment>
<Fragment key="desc">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ const Desc = styled.div();

const Message = styled.div(({ theme }) => ({
padding: 30,
textAlign: 'center',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
display: 'flex',
flexDirection: 'column',
color: theme.color.defaultText,
fontSize: theme.typography.size.s2 - 1,
}));
Expand Down
69 changes: 52 additions & 17 deletions code/ui/components/src/components/tabs/tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { expect } from '@storybook/test';
import React, { Fragment } from 'react';
import React from 'react';
import { action } from '@storybook/addon-actions';
import type { Meta, StoryObj } from '@storybook/react';
import { within, fireEvent, waitFor, screen, userEvent, findByText } from '@storybook/test';
import { CPUIcon, MemoryIcon } from '@storybook/icons';
import { BottomBarIcon, CloseIcon } from '@storybook/icons';
import { Tabs, TabsState, TabWrapper } from './tabs';
import type { ChildrenList } from './tabs.helpers';
import { IconButton } from '../IconButton/IconButton';
Expand Down Expand Up @@ -260,7 +260,27 @@ export const StatelessBordered = {
),
} satisfies Story;

const AddonTools = () => (
<div
style={{
display: 'flex',
alignItems: 'center',
gap: 6,
}}
>
<IconButton title="Tool 1">
<BottomBarIcon />
</IconButton>
<IconButton title="Tool 2">
<CloseIcon />
</IconButton>
</div>
);

export const StatelessWithTools = {
args: {
tools: <AddonTools />,
},
render: (args) => (
<Tabs
bordered
Expand All @@ -269,22 +289,12 @@ export const StatelessWithTools = {
actions={{
onSelect,
}}
tools={
<Fragment>
<IconButton title="Tool 1">
<MemoryIcon />
</IconButton>
<IconButton title="Tool 2">
<CPUIcon />
</IconButton>
</Fragment>
}
{...args}
>
{content}
</Tabs>
),
} satisfies Story;
} satisfies StoryObj<typeof Tabs>;

export const StatelessAbsolute = {
parameters: {
Expand All @@ -303,7 +313,7 @@ export const StatelessAbsolute = {
{content}
</Tabs>
),
} satisfies Story;
} satisfies StoryObj<typeof Tabs>;

export const StatelessAbsoluteBordered = {
parameters: {
Expand All @@ -323,9 +333,13 @@ export const StatelessAbsoluteBordered = {
{content}
</Tabs>
),
} satisfies Story;
} satisfies StoryObj<typeof Tabs>;

export const StatelessEmpty = {
export const StatelessEmptyWithTools = {
args: {
...StatelessWithTools.args,
showToolsWhenEmpty: true,
},
parameters: {
layout: 'fullscreen',
},
Expand All @@ -340,4 +354,25 @@ export const StatelessEmpty = {
{...args}
/>
),
} satisfies Story;
} satisfies StoryObj<typeof Tabs>;

export const StatelessWithCustomEmpty = {
args: {
...StatelessEmptyWithTools.args,
customEmptyContent: <div>I am custom!</div>,
},
parameters: {
layout: 'fullscreen',
},
render: (args) => (
<Tabs
actions={{
onSelect,
}}
bordered
menuName="Addons"
absolute
{...args}
/>
),
} satisfies StoryObj<typeof Tabs>;
28 changes: 20 additions & 8 deletions code/ui/components/src/components/tabs/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export interface TabsProps {
}>[];
id?: string;
tools?: ReactNode;
showToolsWhenEmpty?: boolean;
customEmptyContent?: ReactNode;
selected?: string;
actions?: {
onSelect: (id: string) => void;
Expand All @@ -140,6 +142,8 @@ export const Tabs: FC<TabsProps> = memo(
backgroundColor,
id: htmlId,
menuName,
customEmptyContent,
showToolsWhenEmpty,
}) => {
const idList = childrenToList(children)
.map((i) => i.id)
Expand All @@ -157,7 +161,17 @@ export const Tabs: FC<TabsProps> = memo(

const { visibleList, tabBarRef, tabRefs, AddonTab } = useList(list);

return list.length ? (
const EmptyState = customEmptyContent ?? (
<Placeholder>
<Fragment key="title">Nothing found</Fragment>
</Placeholder>
);

if (!showToolsWhenEmpty && list.length === 0) {
return EmptyState;
}

return (
<Wrapper absolute={absolute} bordered={bordered} id={htmlId}>
<FlexBar scrollable={false} border backgroundColor={backgroundColor}>
<TabBar style={{ whiteSpace: 'normal' }} ref={tabBarRef} role="tablist">
Expand Down Expand Up @@ -190,15 +204,13 @@ export const Tabs: FC<TabsProps> = memo(
{tools}
</FlexBar>
<Content id="panel-tab-content" bordered={bordered} absolute={absolute}>
{list.map(({ id, active, render }) => {
return React.createElement(render, { key: id, active }, null);
})}
{list.length
? list.map(({ id, active, render }) => {
return React.createElement(render, { key: id, active }, null);
})
: EmptyState}
</Content>
</Wrapper>
) : (
<Placeholder>
<Fragment key="title">Nothing found</Fragment>
</Placeholder>
);
}
);
Expand Down
29 changes: 26 additions & 3 deletions code/ui/manager/src/components/panel/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { Component } from 'react';
import { Tabs, IconButton } from '@storybook/components';
import React, { Component, Fragment } from 'react';
import { Tabs, IconButton, Placeholder, P, Link } from '@storybook/components';
import type { State } from '@storybook/manager-api';
import { shortcutToHumanString } from '@storybook/manager-api';
import type { Addon_BaseType } from '@storybook/types';
import { styled } from '@storybook/theming';
import { BottomBarIcon, CloseIcon, SidebarAltIcon } from '@storybook/icons';
import { BottomBarIcon, CloseIcon, DocumentIcon, SidebarAltIcon } from '@storybook/icons';
import { useLayout } from '../layout/LayoutProvider';

export interface SafeTabProps {
Expand Down Expand Up @@ -60,6 +60,23 @@ export const AddonPanel = React.memo<{
{...(selectedPanel ? { selected: selectedPanel } : {})}
menuName="Addons"
actions={actions}
showToolsWhenEmpty
customEmptyContent={
<Placeholder>
<Fragment key="title">
<P>Storybook add-ons</P>
</Fragment>
<EmptyStateDescription key="content">
<P>
Integrate your tools with Storybook to connect workflows and unlock advanced
features.
</P>
<Link href={'https://storybook.js.org/integrations'} target="_blank" withArrow>
<DocumentIcon /> Explore integrations catalog
</Link>
</EmptyStateDescription>
</Placeholder>
}
tools={
<Actions>
{isDesktop ? (
Expand Down Expand Up @@ -108,3 +125,9 @@ const Actions = styled.div({
alignItems: 'center',
gap: 6,
});

const EmptyStateDescription = styled.div({
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
});

0 comments on commit a3d75bb

Please sign in to comment.