Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Add collapse roots to sidebar navigation #13685

Merged
merged 11 commits into from
Feb 25, 2021
1 change: 1 addition & 0 deletions examples/official-storybook/manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ addons.setConfig({
hidden: true,
},
},
collapsedRoots: ['other'],
});
4 changes: 3 additions & 1 deletion lib/api/src/lib/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface Root {
isComponent: false;
isRoot: true;
isLeaf: false;
startCollapsed?: boolean;
}

export interface Group {
Expand Down Expand Up @@ -143,7 +144,7 @@ export const transformStoriesRawToStoriesHash = (

const storiesHashOutOfOrder = values.reduce((acc, item) => {
const { kind, parameters } = item;
const { showRoots } = provider.getConfig();
const { showRoots, collapsedRoots = [] } = provider.getConfig();

const setShowRoots = typeof showRoots !== 'undefined';
if (usesOldHierarchySeparator && !setShowRoots) {
Expand Down Expand Up @@ -176,6 +177,7 @@ export const transformStoriesRawToStoriesHash = (
isComponent: false,
isLeaf: false,
isRoot: true,
startCollapsed: collapsedRoots.includes(id),
});
} else {
list.push({
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/src/components/sidebar/RefIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export const RefIndicator = React.memo(
</MessageWrapper>
}
>
<IndicatorClickTarget>
<IndicatorClickTarget data-action="toggle-indicator">
<Icons icon="globe" />
</IndicatorClickTarget>
</WithTooltip>
Expand Down
2 changes: 1 addition & 1 deletion lib/ui/src/components/sidebar/Refs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export const Ref: FunctionComponent<RefType & RefProps> = React.memo((props) =>
aria-label={`${isExpanded ? 'Hide' : 'Show'} ${title} stories`}
aria-expanded={isExpanded}
>
<CollapseButton onClick={handleClick}>
<CollapseButton data-action="collapse-ref" onClick={handleClick}>
<CollapseIcon isExpanded={isExpanded} />
<RefTitle title={title}>{title}</RefTitle>
</CollapseButton>
Expand Down
87 changes: 74 additions & 13 deletions lib/ui/src/components/sidebar/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ import { Icons } from '@storybook/components';
import { transparentize } from 'polished';
import React, { MutableRefObject, useCallback, useMemo, useRef } from 'react';

import { ComponentNode, DocumentNode, GroupNode, RootNode, StoryNode } from './TreeNode';
import { useExpanded, ExpandAction } from './useExpanded';
import {
ComponentNode,
DocumentNode,
GroupNode,
RootNode,
StoryNode,
CollapseIcon,
} from './TreeNode';
import { useExpanded, ExpandAction, ExpandedState } from './useExpanded';
import { Highlight, Item } from './types';
import { createId, getAncestorIds, getDescendantIds, getLink } from './utils';

Expand All @@ -16,6 +23,7 @@ export const Action = styled.button(({ theme }) => ({
width: 20,
height: 20,
margin: 0,
marginLeft: 'auto',
padding: 0,
outline: 0,
lineHeight: 'normal',
Expand All @@ -42,6 +50,44 @@ export const Action = styled.button(({ theme }) => ({
},
}));

const CollapseButton = styled.button(({ theme }) => ({
// Reset button
background: 'transparent',
border: 'none',
outline: 'none',
boxSizing: 'content-box',
cursor: 'pointer',
position: 'relative',
textAlign: 'left',
lineHeight: 'normal',
font: 'inherit',
color: 'inherit',
letterSpacing: 'inherit',
textTransform: 'inherit',

display: 'flex',
flex: '0 1 auto',
padding: '3px 10px 1px 1px',
margin: 0,
marginLeft: -19,
overflow: 'hidden',
borderRadius: 26,
transition: 'color 150ms, box-shadow 150ms',

'span:first-of-type': {
marginTop: 4,
marginRight: 7,
},

'&:focus': {
boxShadow: `0 0 0 1px ${theme.color.secondary}`,
color: theme.color.secondary,
'span:first-of-type': {
color: theme.color.secondary,
},
},
}));

interface NodeProps {
item: Item;
refId: string;
Expand Down Expand Up @@ -105,20 +151,33 @@ const Node = React.memo<NodeProps>(
data-ref-id={refId}
data-item-id={item.id}
data-nodetype="root"
aria-expanded={isExpanded}
>
{item.name}
<Action
<CollapseButton
type="button"
className="sidebar-subheading-action"
data-action="expand-all"
data-expanded={isFullyExpanded}
data-action="collapse-root"
onClick={(event) => {
event.preventDefault();
setFullyExpanded();
setExpanded({ ids: [item.id], value: !isExpanded });
}}
>
<Icons icon={isFullyExpanded ? 'collapse' : 'expandalt'} />
</Action>
<CollapseIcon isExpanded={isExpanded} />
{item.name}
</CollapseButton>
{isExpanded && (
<Action
type="button"
className="sidebar-subheading-action"
data-action="expand-all"
data-expanded={isFullyExpanded}
onClick={(event) => {
event.preventDefault();
setFullyExpanded();
}}
>
<Icons icon={isFullyExpanded ? 'collapse' : 'expandalt'} />
</Action>
)}
</RootNode>
);
}
Expand Down Expand Up @@ -197,16 +256,17 @@ export const Tree = React.memo<{
const containerRef = useRef<HTMLDivElement>(null);

// Find top-level nodes and group them so we can hoist any orphans and expand any roots.
const [rootIds, orphanIds] = useMemo(
const [rootIds, orphanIds, initialExpanded] = useMemo(
() =>
Object.keys(data).reduce<[string[], string[]]>(
Object.keys(data).reduce<[string[], string[], ExpandedState]>(
(acc, id) => {
const item = data[id];
if (isRoot(item)) acc[0].push(id);
else if (!item.parent) acc[1].push(id);
if (isRoot(item) && item.startCollapsed) acc[2][id] = false;
return acc;
},
[[], []]
[[], [], {}]
),
[data]
);
Expand Down Expand Up @@ -277,6 +337,7 @@ export const Tree = React.memo<{
isBrowsing, // only enable keyboard shortcuts when tree is visible
refId,
data: collapsedData,
initialExpanded,
rootIds,
highlightedRef,
setHighlightedItemId,
Expand Down
9 changes: 6 additions & 3 deletions lib/ui/src/components/sidebar/TreeNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ export const CollapseIcon = styled.span<{ isExpanded: boolean }>(({ theme, isExp
marginTop: 6,
marginLeft: 8,
marginRight: 5,
color: transparentize(0.4, theme.color.mediumdark),
borderTop: '3px solid transparent',
borderBottom: '3px solid transparent',
borderLeft: `3px solid ${transparentize(0.4, theme.color.mediumdark)}`,
borderLeft: `3px solid`,
transform: isExpanded ? 'rotateZ(90deg)' : 'none',
transition: 'transform .1s ease-out',
}));
Expand Down Expand Up @@ -121,11 +122,13 @@ export const Path = styled.span(({ theme }) => ({
},
}));

export const RootNode = styled.span(({ theme }) => ({
export const RootNode = styled.div(({ theme }) => ({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
margin: '16px 20px 4px 20px',
padding: '0 20px',
marginTop: 16,
marginBottom: 4,
fontSize: `${theme.typography.size.s1 - 1}px`,
fontWeight: theme.typography.weight.black,
lineHeight: '16px',
Expand Down
13 changes: 9 additions & 4 deletions lib/ui/src/components/sidebar/useExpanded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface ExpandedProps {
isBrowsing: boolean;
refId: string;
data: StoriesHash;
initialExpanded?: ExpandedState;
rootIds: string[];
highlightedRef: MutableRefObject<Highlight>;
setHighlightedItemId: (storyId: string) => void;
Expand All @@ -30,11 +31,13 @@ export interface ExpandedProps {
const initializeExpanded = ({
refId,
data,
initialExpanded,
highlightedRef,
rootIds,
}: {
refId: string;
data: StoriesHash;
initialExpanded?: ExpandedState;
highlightedRef: MutableRefObject<Highlight>;
rootIds: string[];
}) => {
Expand All @@ -43,7 +46,7 @@ const initializeExpanded = ({
? getAncestorIds(data, highlightedRef.current?.itemId)
: [];
return [...rootIds, ...highlightedAncestors].reduce<ExpandedState>(
(acc, id) => Object.assign(acc, { [id]: true }),
(acc, id) => Object.assign(acc, { [id]: id in initialExpanded ? initialExpanded[id] : true }),
{}
);
};
Expand All @@ -55,28 +58,30 @@ export const useExpanded = ({
isBrowsing,
refId,
data,
initialExpanded,
rootIds,
highlightedRef,
setHighlightedItemId,
selectedStoryId,
onSelectStoryId,
}: ExpandedProps): [Record<string, boolean>, Dispatch<ExpandAction>] => {
}: ExpandedProps): [ExpandedState, Dispatch<ExpandAction>] => {
const api = useStorybookApi();

// Track the set of currently expanded nodes within this tree.
// Root nodes are expanded by default (and cannot be collapsed).
// Root nodes are expanded by default.
const [expanded, setExpanded] = useReducer<
React.Reducer<ExpandedState, ExpandAction>,
{
refId: string;
data: StoriesHash;
highlightedRef: MutableRefObject<Highlight>;
rootIds: string[];
initialExpanded: ExpandedState;
}
>(
(state, { ids, value }) =>
ids.reduce((acc, id) => Object.assign(acc, { [id]: value }), { ...state }),
{ refId, data, highlightedRef, rootIds },
{ refId, data, highlightedRef, rootIds, initialExpanded },
initializeExpanded
);

Expand Down