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: Show the story status in the search results #23441

Merged
merged 9 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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