Skip to content

Commit

Permalink
Merge pull request #29333 from storybookjs/shilman/simple-tag-filter
Browse files Browse the repository at this point in the history
UI: Simple tag filtering
  • Loading branch information
shilman authored Oct 21, 2024
2 parents 387ca48 + 816e595 commit f75cf71
Show file tree
Hide file tree
Showing 13 changed files with 498 additions and 100 deletions.
7 changes: 4 additions & 3 deletions code/core/src/core-server/presets/common-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { global } from '@storybook/global';

import { addons } from '@storybook/core/manager-api';

const TAG_FILTERS = 'tag-filters';
const STATIC_FILTER = 'static-filter';

addons.register(STATIC_FILTER, (api) => {
addons.register(TAG_FILTERS, (api) => {
// FIXME: this ensures the filter is applied after the first render
// to avoid a strange race condition in Webkit only.
const excludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce(
const staticExcludeTags = Object.entries(global.TAGS_OPTIONS ?? {}).reduce(
(acc, entry) => {
const [tag, option] = entry;
if ((option as any).excludeFromSidebar) {
Expand All @@ -23,7 +24,7 @@ addons.register(STATIC_FILTER, (api) => {
return (
// we can filter out the primary story, but we still want to show autodocs
(tags.includes('dev') || item.type === 'docs') &&
tags.filter((tag) => excludeTags[tag]).length === 0
tags.filter((tag) => staticExcludeTags[tag]).length === 0
);
});
});
7 changes: 4 additions & 3 deletions code/core/src/manager-api/modules/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,10 @@ export interface SubAPI {
* Set the query parameters for the current URL & navigates.
*
* @param {QueryParams} input - An object containing the query parameters to set.
* @param {NavigateOptions} options - Options for the navigation.
* @returns {void}
*/
applyQueryParams: (input: QueryParams) => void;
applyQueryParams: (input: QueryParams, options?: NavigateOptions) => void;
}

export const init: ModuleFn<SubAPI, SubState> = (moduleArgs) => {
Expand Down Expand Up @@ -206,10 +207,10 @@ export const init: ModuleFn<SubAPI, SubState> = (moduleArgs) => {
provider.channel?.emit(UPDATE_QUERY_PARAMS, update);
}
},
applyQueryParams(input) {
applyQueryParams(input, options) {
const { path, queryParams } = api.getUrlState();

navigateTo(path, { ...queryParams, ...input } as any);
navigateTo(path, { ...queryParams, ...input } as any, options);
api.setQueryParams(input);
},
navigateUrl(url, options) {
Expand Down
6 changes: 1 addition & 5 deletions code/core/src/manager/components/sidebar/Search.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,7 @@ const baseProps = {

export const Simple: StoryFn = () => <Search {...baseProps}>{() => null}</Search>;

export const SimpleWithCreateButton: StoryFn = () => (
<Search {...baseProps} showCreateStoryButton={true}>
{() => null}
</Search>
);
export const SimpleWithCreateButton: StoryFn = () => <Search {...baseProps}>{() => null}</Search>;

export const FilledIn: StoryFn = () => (
<Search {...baseProps} initialQuery="Search query">
Expand Down
130 changes: 48 additions & 82 deletions code/core/src/manager/components/sidebar/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React, { useCallback, useRef, useState } from 'react';
import React, { type ReactNode, useCallback, useRef, useState } from 'react';

import { IconButton, TooltipNote, WithTooltip } from '@storybook/core/components';
import { IconButton } from '@storybook/core/components';
import { styled } from '@storybook/core/theming';
import { global } from '@storybook/global';
import { CloseIcon, PlusIcon, SearchIcon } from '@storybook/icons';
import { CloseIcon, SearchIcon } from '@storybook/icons';

import { shortcutToHumanString, useStorybookApi } from '@storybook/core/manager-api';

Expand All @@ -15,7 +15,6 @@ import Fuse from 'fuse.js';
import { getGroupStatus, getHighestStatus } from '../../utils/status';
import { scrollIntoView, searchItem } from '../../utils/tree';
import { useLayout } from '../layout/LayoutProvider';
import { CreateNewStoryFileModal } from './CreateNewStoryFileModal';
import { DEFAULT_REF_ID } from './Sidebar';
import type {
CombinedDataset,
Expand Down Expand Up @@ -54,10 +53,6 @@ const SearchBar = styled.div({
columnGap: 6,
});

const TooltipNoteWrapper = styled(TooltipNote)({
margin: 0,
});

const ScreenReaderLabel = styled.label({
position: 'absolute',
left: -10000,
Expand All @@ -67,49 +62,47 @@ const ScreenReaderLabel = styled.label({
overflow: 'hidden',
});

const CreateNewStoryButton = styled(IconButton)(({ theme }) => ({
color: theme.color.mediumdark,
const SearchField = styled.div(({ theme }) => ({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
padding: 2,
flexGrow: 1,
height: 32,
width: '100%',
boxShadow: `${theme.button.border} 0 0 0 1px inset`,
borderRadius: theme.appBorderRadius + 2,

'&:has(input:focus), &:has(input:active)': {
boxShadow: `${theme.color.secondary} 0 0 0 1px inset`,
background: theme.background.app,
},
}));

const SearchIconWrapper = styled.div(({ theme }) => ({
position: 'absolute',
top: 0,
left: 8,
zIndex: 1,
pointerEvents: 'none',
const IconWrapper = styled.div(({ theme, onClick }) => ({
cursor: onClick ? 'pointer' : 'default',
flex: '0 0 28px',
height: '100%',
pointerEvents: onClick ? 'auto' : 'none',
color: theme.textMutedColor,
display: 'flex',
alignItems: 'center',
height: '100%',
justifyContent: 'center',
}));

const SearchField = styled.div({
display: 'flex',
flexDirection: 'column',
flexGrow: 1,
position: 'relative',
});

const Input = styled.input(({ theme }) => ({
appearance: 'none',
height: 28,
paddingLeft: 28,
paddingRight: 28,
width: '100%',
padding: 0,
border: 0,
boxShadow: `${theme.button.border} 0 0 0 1px inset`,
background: 'transparent',
borderRadius: 4,
fontSize: `${theme.typography.size.s1 + 1}px`,
fontFamily: 'inherit',
transition: 'all 150ms',
color: theme.color.defaultText,
width: '100%',
outline: 0,

'&:focus, &:active': {
outline: 0,
borderColor: theme.color.secondary,
background: theme.background.app,
},
'&::placeholder': {
color: theme.textMutedColor,
opacity: 1,
Expand All @@ -133,11 +126,9 @@ const Input = styled.input(({ theme }) => ({
}));

const FocusKey = styled.code(({ theme }) => ({
position: 'absolute',
top: 6,
right: 9,
margin: 5,
marginTop: 6,
height: 16,
zIndex: 1,
lineHeight: '16px',
textAlign: 'center',
fontSize: '11px',
Expand All @@ -147,50 +138,43 @@ const FocusKey = styled.code(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: 4,
flexShrink: 0,
}));

const FocusKeyCmd = styled.span({
fontSize: '14px',
});

const ClearIcon = styled.div(({ theme }) => ({
position: 'absolute',
top: 0,
right: 8,
zIndex: 1,
color: theme.textMutedColor,
cursor: 'pointer',
const Actions = styled.div({
display: 'flex',
alignItems: 'center',
height: '100%',
}));
gap: 2,
});

const FocusContainer = styled.div({ outline: 0 });

const isDevelopment = global.CONFIG_TYPE === 'DEVELOPMENT';
const isRendererReact = global.STORYBOOK_RENDERER === 'react';

export const Search = React.memo<{
children: SearchChildrenFn;
dataset: CombinedDataset;
enableShortcuts?: boolean;
getLastViewed: () => Selection[];
initialQuery?: string;
showCreateStoryButton?: boolean;
searchBarContent?: ReactNode;
searchFieldContent?: ReactNode;
}>(function Search({
children,
dataset,
enableShortcuts = true,
getLastViewed,
initialQuery = '',
showCreateStoryButton = isDevelopment && isRendererReact,
searchBarContent,
searchFieldContent,
}) {
const api = useStorybookApi();
const inputRef = useRef<HTMLInputElement>(null);
const [inputPlaceholder, setPlaceholder] = useState('Find components');
const [allComponents, showAllComponents] = useState(false);
const searchShortcut = api ? shortcutToHumanString(api.getShortcutKeys().search) : '/';
const [isFileSearchModalOpen, setIsFileSearchModalOpen] = useState(false);

const makeFuse = useCallback(() => {
const list = dataset.entries.reduce<SearchItem[]>((acc, [refId, { index, status }]) => {
Expand Down Expand Up @@ -406,9 +390,9 @@ export const Search = React.memo<{
{...getRootProps({ refKey: '' }, { suppressRefError: true })}
className="search-field"
>
<SearchIconWrapper>
<IconWrapper>
<SearchIcon />
</SearchIconWrapper>
</IconWrapper>
<Input {...inputProps} />
{!isMobile && enableShortcuts && !isOpen && (
<FocusKey>
Expand All @@ -421,34 +405,16 @@ export const Search = React.memo<{
)}
</FocusKey>
)}
{isOpen && (
<ClearIcon onClick={() => clearSelection()}>
<CloseIcon />
</ClearIcon>
)}
<Actions>
{isOpen && (
<IconButton onClick={() => clearSelection()}>
<CloseIcon />
</IconButton>
)}
{searchFieldContent}
</Actions>
</SearchField>
{showCreateStoryButton && (
<>
<WithTooltip
trigger="hover"
hasChrome={false}
tooltip={<TooltipNoteWrapper note="Create a new story" />}
>
<CreateNewStoryButton
onClick={() => {
setIsFileSearchModalOpen(true);
}}
variant="outline"
>
<PlusIcon />
</CreateNewStoryButton>
</WithTooltip>
<CreateNewStoryFileModal
open={isFileSearchModalOpen}
onOpenChange={setIsFileSearchModalOpen}
/>
</>
)}
{searchBarContent}
</SearchBar>
<FocusContainer tabIndex={0} id="storybook-explorer-menu">
{children({
Expand Down
22 changes: 22 additions & 0 deletions code/core/src/manager/components/sidebar/Sidebar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ const managerContext: any = {
),
selectStory: fn().mockName('api::selectStory'),
experimental_setFilter: fn().mockName('api::experimental_setFilter'),
getDocsUrl: () => 'https://storybook.js.org/docs/',
getUrlState: () => ({
queryParams: {},
path: '',
viewMode: 'story',
url: 'http://localhost:6006/',
}),
applyQueryParams: fn().mockName('api::applyQueryParams'),
},
};

Expand All @@ -56,6 +64,20 @@ const meta = {
menu,
extra: [] as Addon_SidebarTopType[],
index: index,
indexJson: {
entries: {
// force the tags filter menu to show in production
['dummy--dummyId']: {
id: 'dummy--dummyId',
name: 'Dummy story',
title: 'dummy',
importPath: './dummy.stories.js',
type: 'story',
tags: ['A', 'B', 'C', 'dev'],
},
},
v: 6,
},
storyId,
refId: DEFAULT_REF_ID,
refs: {},
Expand Down
Loading

0 comments on commit f75cf71

Please sign in to comment.