Skip to content

Commit

Permalink
Merge pull request #23778 from storybookjs/norbert/add-sidebar-footer…
Browse files Browse the repository at this point in the history
…-slot

UI: Add an experimental API for adding sidebar bottom toolbar
  • Loading branch information
ndelangen authored Aug 24, 2023
2 parents dfe41f7 + 0302088 commit 1f18e29
Show file tree
Hide file tree
Showing 8 changed files with 140 additions and 18 deletions.
11 changes: 8 additions & 3 deletions code/lib/manager-api/src/lib/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
Addon_Types,
Addon_TypesMapping,
Addon_WrapperType,
Addon_SidebarBottomType,
} from '@storybook/types';
import { Addon_TypesEnum } from '@storybook/types';
import { logger } from '@storybook/client-logger';
Expand Down Expand Up @@ -97,9 +98,12 @@ export class AddonStore {
this.serverChannel = channel;
};

getElements<T extends Addon_Types | Addon_TypesEnum.experimental_PAGE>(
type: T
): Addon_Collection<Addon_TypesMapping[T]> {
getElements<
T extends
| Addon_Types
| Addon_TypesEnum.experimental_PAGE
| Addon_TypesEnum.experimental_SIDEBAR_BOTTOM
>(type: T): Addon_Collection<Addon_TypesMapping[T]> {
if (!this.elements[type]) {
this.elements[type] = {};
}
Expand Down Expand Up @@ -141,6 +145,7 @@ export class AddonStore {
id: string,
addon:
| Addon_BaseType
| (Omit<Addon_SidebarBottomType, 'id'> & DeprecatedAddonWithId)
| (Omit<Addon_PageType, 'id'> & DeprecatedAddonWithId)
| (Omit<Addon_WrapperType, 'id'> & DeprecatedAddonWithId)
): void {
Expand Down
7 changes: 6 additions & 1 deletion code/lib/manager-api/src/modules/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ export interface SubAPI {
* @param {Addon_Types | Addon_TypesEnum.experimental_PAGE} type - The type of the elements to retrieve.
* @returns {API_Collection<T>} - A collection of elements of the specified type.
*/
getElements: <T extends Addon_Types | Addon_TypesEnum.experimental_PAGE = Addon_Types>(
getElements: <
T extends
| Addon_Types
| Addon_TypesEnum.experimental_PAGE
| Addon_TypesEnum.experimental_SIDEBAR_BOTTOM = Addon_Types
>(
type: T
) => Addon_Collection<Addon_TypesMapping[T]>;
/**
Expand Down
40 changes: 35 additions & 5 deletions code/lib/types/src/modules/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ import type {
} from './csf';
import type { IndexEntry } from './indexer';

export type Addon_Types = Exclude<Addon_TypesEnum, Addon_TypesEnum.experimental_PAGE>;

export type Addon_Types = Exclude<
Addon_TypesEnum,
Addon_TypesEnum.experimental_PAGE | Addon_TypesEnum.experimental_SIDEBAR_BOTTOM
>;
export interface Addon_ArgType<TArg = unknown> extends InputType {
defaultValue?: TArg;
}
Expand Down Expand Up @@ -324,7 +326,11 @@ export type ReactJSXElement = {
key: any;
};

export type Addon_Type = Addon_BaseType | Addon_PageType | Addon_WrapperType;
export type Addon_Type =
| Addon_BaseType
| Addon_PageType
| Addon_WrapperType
| Addon_SidebarBottomType;
export interface Addon_BaseType {
/**
* The title of the addon.
Expand All @@ -335,7 +341,12 @@ export interface Addon_BaseType {
* The type of the addon.
* @example Addon_TypesEnum.PANEL
*/
type: Exclude<Addon_Types, Addon_TypesEnum.PREVIEW>;
type: Exclude<
Addon_Types,
| Addon_TypesEnum.PREVIEW
| Addon_TypesEnum.experimental_PAGE
| Addon_TypesEnum.experimental_SIDEBAR_BOTTOM
>;
/**
* The unique id of the addon.
* @warn This will become non-optional in 8.0
Expand Down Expand Up @@ -448,15 +459,29 @@ export interface Addon_WrapperType {
}>
>;
}
export interface Addon_SidebarBottomType {
type: Addon_TypesEnum.experimental_SIDEBAR_BOTTOM;
/**
* The unique id of the tool.
*/
id: string;
/**
* A React.FunctionComponent.
*/
render: FCWithoutChildren;
}

type Addon_TypeBaseNames = Exclude<
Addon_TypesEnum,
Addon_TypesEnum.PREVIEW | Addon_TypesEnum.experimental_PAGE
| Addon_TypesEnum.PREVIEW
| Addon_TypesEnum.experimental_PAGE
| Addon_TypesEnum.experimental_SIDEBAR_BOTTOM
>;

export interface Addon_TypesMapping extends Record<Addon_TypeBaseNames, Addon_BaseType> {
[Addon_TypesEnum.PREVIEW]: Addon_WrapperType;
[Addon_TypesEnum.experimental_PAGE]: Addon_PageType;
[Addon_TypesEnum.experimental_SIDEBAR_BOTTOM]: Addon_SidebarBottomType;
}

export type Addon_Loader<API> = (api: API) => void;
Expand Down Expand Up @@ -510,6 +535,11 @@ export enum Addon_TypesEnum {
* @unstable
*/
experimental_PAGE = 'page',
/**
* This adds items in the bottom of the sidebar.
* @unstable
*/
experimental_SIDEBAR_BOTTOM = 'sidebar-bottom',

/**
* @deprecated This property does nothing, and will be removed in Storybook 8.0.
Expand Down
1 change: 1 addition & 0 deletions code/ui/.storybook/manager.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import { addons, types } from '@storybook/manager-api';
import startCase from 'lodash/startCase.js';

Expand Down
1 change: 1 addition & 0 deletions code/ui/manager/src/components/sidebar/RefBlocks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export const EmptyBlock: FC<any> = ({ isMain }) => (
The glob specified in <code>main.js</code> isn't correct.
</li>
<li>No stories are defined in your story files.</li>
<li>You're using filter-functions, and all stories are filtered away.</li>
</ul>{' '}
</>
) : (
Expand Down
55 changes: 54 additions & 1 deletion code/ui/manager/src/components/sidebar/Sidebar.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';

import type { IndexHash, State } from 'lib/manager-api/src';
import type { IndexHash, State } from '@storybook/manager-api';
import { types } from '@storybook/manager-api';
import type { StoryObj, Meta } from '@storybook/react';
import { within, userEvent } from '@storybook/testing-library';
import { Button, IconButton, Icons } from '@storybook/components';
import { Sidebar, DEFAULT_REF_ID } from './Sidebar';
import { standardData as standardHeaderData } from './Heading.stories';
import * as ExplorerStories from './Explorer.stories';
Expand Down Expand Up @@ -222,3 +224,54 @@ export const Searching: Story = {
userEvent.type(search, 'B2');
},
};

export const Bottom: Story = {
args: {
previewInitialized: true,
},
parameters: { theme: 'light' },
render: (args) => (
<Sidebar
{...args}
menu={menu}
index={index as any}
storyId={storyId}
refId={DEFAULT_REF_ID}
refs={{}}
status={{}}
bottom={[
{
id: '1',
type: types.experimental_SIDEBAR_BOTTOM,
render: () => (
<Button>
<Icons icon="facehappy" />
Custom addon A
</Button>
),
},
{
id: '2',
type: types.experimental_SIDEBAR_BOTTOM,
render: () => (
<Button>
{' '}
<Icons icon="facehappy" />
Custom addon B
</Button>
),
},
{
id: '3',
type: types.experimental_SIDEBAR_BOTTOM,
render: () => (
<IconButton>
{' '}
<Icons icon="facehappy" />
</IconButton>
),
},
]}
/>
),
};
36 changes: 30 additions & 6 deletions code/ui/manager/src/components/sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { styled } from '@storybook/theming';
import { ScrollArea, Spaced } from '@storybook/components';
import type { State } from '@storybook/manager-api';

import type { API_LoadedRefData } from 'lib/types/src';
import type { Addon_SidebarBottomType, API_LoadedRefData } from '@storybook/types';
import { Heading } from './Heading';

// eslint-disable-next-line import/no-cycle
Expand All @@ -27,20 +27,35 @@ const Container = styled.nav({
right: 0,
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
});

const StyledSpaced = styled(Spaced)({
paddingBottom: '2.5rem',
const Top = styled(Spaced)({
padding: 20,
flex: 1,
});

const Bottom = styled.div(({ theme }) => ({
borderTop: `1px solid ${theme.appBorderColor}`,
padding: theme.layoutMargin / 2,
display: 'flex',
flexWrap: 'wrap',
gap: theme.layoutMargin / 2,
backgroundColor: theme.barBg,

'&:empty': {
display: 'none',
},
}));

const CustomScrollArea = styled(ScrollArea)({
'&&&&& .os-scrollbar-handle:before': {
left: -12,
},
'&&&&& .os-scrollbar-vertical': {
right: 5,
},
padding: 20,
});

const Swap = React.memo(function Swap({
Expand Down Expand Up @@ -82,6 +97,7 @@ export interface SidebarProps extends API_LoadedRefData {
refs: State['refs'];
status: State['status'];
menu: any[];
bottom?: Addon_SidebarBottomType[];
storyId?: string;
refId?: string;
menuHighlighted?: boolean;
Expand All @@ -96,6 +112,7 @@ export const Sidebar = React.memo(function Sidebar({
status,
previewInitialized,
menu,
bottom = [],
menuHighlighted = false,
enableShortcuts = true,
refs = {},
Expand All @@ -108,7 +125,7 @@ export const Sidebar = React.memo(function Sidebar({
return (
<Container className="container sidebar-container">
<CustomScrollArea vertical>
<StyledSpaced row={1.6}>
<Top row={1.6}>
<Heading
className="sidebar-header"
menuHighlighted={menuHighlighted}
Expand Down Expand Up @@ -151,8 +168,15 @@ export const Sidebar = React.memo(function Sidebar({
</Swap>
)}
</Search>
</StyledSpaced>
</Top>
</CustomScrollArea>
{isLoading ? null : (
<Bottom>
{bottom.map(({ id, render: Render }) => (
<Render key={id} />
))}
</Bottom>
)}
</Container>
);
});
7 changes: 5 additions & 2 deletions code/ui/manager/src/containers/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import React, { useMemo } from 'react';

import type { Combo, StoriesHash } from '@storybook/manager-api';
import { Consumer } from '@storybook/manager-api';
import { types, Consumer } from '@storybook/manager-api';

import { Sidebar as SidebarComponent } from '../components/sidebar/Sidebar';
import { useMenu } from './menu';
Expand Down Expand Up @@ -36,6 +36,8 @@ const Sidebar = React.memo(function Sideber() {
const whatsNewNotificationsEnabled =
state.whatsNewData?.status === 'SUCCESS' && !state.disableWhatsNewNotifications;

const items = api.getElements(types.experimental_SIDEBAR_BOTTOM);
const bottom = useMemo(() => Object.values(items), [items]);
return {
title: name,
url,
Expand All @@ -50,6 +52,7 @@ const Sidebar = React.memo(function Sideber() {
menu,
menuHighlighted: whatsNewNotificationsEnabled && api.isWhatsNewUnread(),
enableShortcuts,
bottom,
};
};
return (
Expand Down

0 comments on commit 1f18e29

Please sign in to comment.