Skip to content

Commit

Permalink
Merge pull request #23441 from storybookjs/norbert/status-in-search-item
Browse files Browse the repository at this point in the history
UI: Show the story status in the search results
  • Loading branch information
ndelangen committed Jul 18, 2023
2 parents e691ba4 + 5da09e9 commit afdc58a
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 28 deletions.
@@ -1,5 +1,7 @@
import { addons } from '@storybook/manager-api';
import { addons, types } from '@storybook/manager-api';
import { IconButton, Icons } from '@storybook/components';
import startCase from 'lodash/startCase.js';
import React, { Fragment } from 'react';

addons.setConfig({
sidebar: {
Expand Down
22 changes: 19 additions & 3 deletions code/ui/manager/src/components/sidebar/Search.tsx
Expand Up @@ -21,6 +21,7 @@ import type {
import { isSearchResult, isExpandType, isClearType, isCloseType } from './types';

import { scrollIntoView, searchItem } from '../../utils/tree';
import { getGroupStatus, getHighestStatus } from '../../utils/status';

const { document } = global;

Expand Down Expand Up @@ -169,17 +170,32 @@ export const Search = React.memo<{

const selectStory = useCallback(
(id: string, refId: string) => {
if (api) api.selectStory(id, undefined, { ref: refId !== DEFAULT_REF_ID && refId });
if (api) {
api.selectStory(id, undefined, { ref: refId !== DEFAULT_REF_ID && refId });
}
inputRef.current.blur();
showAllComponents(false);
},
[api, inputRef, showAllComponents, DEFAULT_REF_ID]
);

const list: SearchItem[] = useMemo(() => {
return dataset.entries.reduce((acc: SearchItem[], [refId, { index }]) => {
return dataset.entries.reduce<SearchItem[]>((acc, [refId, { index, status }]) => {
const groupStatus = getGroupStatus(index || {}, status);

if (index) {
acc.push(...Object.values(index).map((item) => searchItem(item, dataset.hash[refId])));
acc.push(
...Object.values(index).map((item) => {
const statusValue =
status && status[item.id]
? getHighestStatus(Object.values(status[item.id] || {}).map((s) => s.status))
: null;
return {
...searchItem(item, dataset.hash[refId]),
status: statusValue || groupStatus[item.id] || null,
};
})
);
}
return acc;
}, []);
Expand Down
27 changes: 24 additions & 3 deletions code/ui/manager/src/components/sidebar/SearchResults.tsx
Expand Up @@ -13,6 +13,7 @@ import { isCloseType, isClearType, isExpandType } from './types';
// eslint-disable-next-line import/no-cycle
import { getLink } from '../../utils/tree';
import { matchesKeyCode, matchesModifiers } from '../../keybinding';
import { statusMapping } from '../../utils/status';

const { document } = global;

Expand All @@ -25,14 +26,18 @@ const ResultsList = styled.ol({
});

const ResultRow = styled.li<{ isHighlighted: boolean }>(({ theme, isHighlighted }) => ({
display: 'block',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
margin: 0,
padding: 0,
paddingRight: 20,
background: isHighlighted ? theme.background.hoverable : 'transparent',
cursor: 'pointer',
'a:hover, button:hover': {
background: 'transparent',
},
gap: 10,
}));

const NoResults = styled.div(({ theme }) => ({
Expand Down Expand Up @@ -109,7 +114,11 @@ const Highlight: FC<{ match?: Match }> = React.memo(function Highlight({ childre
});

const Result: FC<
SearchResult & { icon: string; isHighlighted: boolean; onClick: MouseEventHandler }
SearchResult & {
icon: string;
isHighlighted: boolean;
onClick: MouseEventHandler;
}
> = React.memo(function Result({ item, matches, icon, onClick, ...props }) {
const click: MouseEventHandler = useCallback(
(event) => {
Expand Down Expand Up @@ -163,7 +172,16 @@ const Result: FC<
node = <DocumentNode href={getLink(item, item.refId)} {...nodeProps} />;
}

return <ResultRow {...props}>{node}</ResultRow>;
const [i, iconColor] = item.status ? statusMapping[item.status] : [];

return (
<ResultRow {...props}>
{node}
{item.status ? (
<Icons width="8px" height="8px" icon={i} style={{ color: iconColor }} />
) : null}
</ResultRow>
);
});

export const SearchResults: FC<{
Expand Down Expand Up @@ -202,6 +220,9 @@ export const SearchResults: FC<{
}, [closeMenu, enableShortcuts, isLoading]);

const mouseOverHandler = useCallback((event: MouseEvent) => {
if (!api) {
return;
}
const currentTarget = event.currentTarget as HTMLElement;
const storyId = currentTarget.getAttribute('data-id');
const refId = currentTarget.getAttribute('data-refid');
Expand Down
20 changes: 19 additions & 1 deletion code/ui/manager/src/components/sidebar/Sidebar.stories.tsx
Expand Up @@ -2,12 +2,18 @@ import React from 'react';

import type { IndexHash, State } from 'lib/manager-api/src';
import type { StoryObj, Meta } from '@storybook/react';
import { within, userEvent } from '@storybook/testing-library';
import { Sidebar, DEFAULT_REF_ID } from './Sidebar';
import { standardData as standardHeaderData } from './Heading.stories';
import * as ExplorerStories from './Explorer.stories';
import { mockDataset } from './mockdata';
import type { RefType } from './types';

const wait = (ms: number) =>
new Promise<void>((resolve) => {
setTimeout(resolve, ms);
});

const meta = {
component: Sidebar,
title: 'Sidebar/Sidebar',
Expand Down Expand Up @@ -184,6 +190,7 @@ export const StatusesCollapsed: Story = {
/>
),
};

export const StatusesOpen: Story = {
...StatusesCollapsed,
args: {
Expand All @@ -200,7 +207,18 @@ export const StatusesOpen: Story = {
addonB: { status: 'error', title: 'Addon B', description: 'This is a big deal!' },
},
};
return acc;
}, {}),
},
};

export const Searching: Story = {
...StatusesOpen,
parameters: { theme: 'light', chromatic: { delay: 2200 } },
play: async ({ canvasElement, step }) => {
await step('wait 2000ms', () => wait(2000));
const canvas = await within(canvasElement);
const search = await canvas.findByPlaceholderText('Find components');
userEvent.clear(search);
userEvent.type(search, 'B2');
},
};
1 change: 0 additions & 1 deletion code/ui/manager/src/components/sidebar/Sidebar.tsx
Expand Up @@ -101,7 +101,6 @@ export const Sidebar = React.memo(function Sidebar({
refs = {},
}: SidebarProps) {
const selected: Selection = useMemo(() => storyId && { storyId, refId }, [storyId, refId]);

const dataset = useCombination({ index, indexError, previewInitialized, status }, refs);
const isLoading = !index && !indexError;
const lastViewedProps = useLastViewed(selected);
Expand Down
6 changes: 4 additions & 2 deletions code/ui/manager/src/components/sidebar/Tree.tsx
Expand Up @@ -258,7 +258,9 @@ const Node = React.memo<NodeProps>(function Node({
)}
closeOnOutsideClick
>
<Icons icon={icon} style={{ color: iconColor }} />
<Action type="button">
<Icons icon={icon} style={{ color: isSelected ? 'white' : iconColor }} />
</Action>
</WithTooltip>
) : null}
</LeafNodeStyleWrapper>
Expand Down Expand Up @@ -528,7 +530,7 @@ export const Tree = React.memo<{
}

const isDisplayed = !item.parent || ancestry[itemId].every((a: string) => expanded[a]);
const color = groupStatus[itemId];
const color = groupStatus[itemId] ? statusMapping[groupStatus[itemId]][2] : null;

return (
<Node
Expand Down
5 changes: 3 additions & 2 deletions code/ui/manager/src/components/sidebar/types.ts
@@ -1,8 +1,9 @@
import type { StoriesHash, State } from '@storybook/manager-api';
import type { ControllerStateAndHelpers } from 'downshift';
import type { API_StatusState, API_StatusValue } from 'lib/types/src';

export type Refs = State['refs'];
export type RefType = Refs[keyof Refs];
export type RefType = Refs[keyof Refs] & { status?: API_StatusState };
export type Item = StoriesHash[keyof StoriesHash];
export type Dataset = Record<string, Item>;

Expand Down Expand Up @@ -56,7 +57,7 @@ export interface ExpandType {
moreCount: number;
}

export type SearchItem = Item & { refId: string; path: string[] };
export type SearchItem = Item & { refId: string; path: string[]; status?: API_StatusValue };

export type SearchResult = Fuse.FuseResultWithMatches<SearchItem> &
Fuse.FuseResultWithScore<SearchItem>;
Expand Down
16 changes: 8 additions & 8 deletions code/ui/manager/src/utils/status.test.ts
Expand Up @@ -23,10 +23,10 @@ describe('getGroupStatus', () => {
})
).toMatchInlineSnapshot(`
Object {
"group-1": "#A15C20",
"root-1-child-a1": null,
"root-1-child-a2": null,
"root-3-child-a2": null,
"group-1": "warn",
"root-1-child-a1": "unknown",
"root-1-child-a2": "unknown",
"root-3-child-a2": "unknown",
}
`);
});
Expand All @@ -40,10 +40,10 @@ describe('getGroupStatus', () => {
})
).toMatchInlineSnapshot(`
Object {
"group-1": "brown",
"root-1-child-a1": null,
"root-1-child-a2": null,
"root-3-child-a2": null,
"group-1": "error",
"root-1-child-a1": "unknown",
"root-1-child-a2": "unknown",
"root-3-child-a2": "unknown",
}
`);
});
Expand Down
13 changes: 6 additions & 7 deletions code/ui/manager/src/utils/status.tsx
Expand Up @@ -11,9 +11,9 @@ export const statusMapping: Record<
> = {
unknown: [null, null, null],
pending: ['watch', 'currentColor', 'currentColor'],
success: ['passed', 'green', 'currentColor'],
warn: ['changed', 'orange', '#A15C20'],
error: ['failed', 'red', 'brown'],
success: ['circle', 'green', 'currentColor'],
warn: ['circle', 'orange', '#A15C20'],
error: ['circle', 'red', 'brown'],
};

export const getHighestStatus = (statuses: API_StatusValue[]): API_StatusValue => {
Expand All @@ -28,8 +28,8 @@ export function getGroupStatus(
[x: string]: Partial<API_HashEntry>;
},
status: API_StatusState
): Record<string, string> {
return Object.values(collapsedData).reduce<Record<string, string>>((acc, item) => {
): Record<string, API_StatusValue> {
return Object.values(collapsedData).reduce<Record<string, API_StatusValue>>((acc, item) => {
if (item.type === 'group' || item.type === 'component') {
const leafs = getDescendantIds(collapsedData as any, item.id, false)
.map((id) => collapsedData[id])
Expand All @@ -40,8 +40,7 @@ export function getGroupStatus(
);

if (combinedStatus) {
// eslint-disable-next-line prefer-destructuring
acc[item.id] = statusMapping[combinedStatus][2];
acc[item.id] = combinedStatus;
}
}
return acc;
Expand Down

0 comments on commit afdc58a

Please sign in to comment.