From 6e24da4ca8f612d055846f90f9bbf9468d840977 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Jul 2024 16:26:29 +0200 Subject: [PATCH 1/6] Replace experimental_SIDEBAR_BOTTOM API with a core story status filter UI --- code/core/src/core-events/index.ts | 3 + code/core/src/manager-api/modules/stories.ts | 3 + .../sidebar/FilterToggle.stories.ts | 40 +++++++ .../components/sidebar/FilterToggle.tsx | 59 ++++++++++ .../components/sidebar/Sidebar.stories.tsx | 103 ++++++++---------- .../manager/components/sidebar/Sidebar.tsx | 7 +- .../sidebar/SidebarBottom.stories.tsx | 42 +++++++ .../components/sidebar/SidebarBottom.tsx | 91 ++++++++++++++++ code/core/src/manager/container/Sidebar.tsx | 4 - code/core/src/manager/globals/exports.ts | 3 + 10 files changed, 288 insertions(+), 67 deletions(-) create mode 100644 code/core/src/manager/components/sidebar/FilterToggle.stories.ts create mode 100644 code/core/src/manager/components/sidebar/FilterToggle.tsx create mode 100644 code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx create mode 100644 code/core/src/manager/components/sidebar/SidebarBottom.tsx diff --git a/code/core/src/core-events/index.ts b/code/core/src/core-events/index.ts index cbb1bef3c6af..90eed7b0d5b6 100644 --- a/code/core/src/core-events/index.ts +++ b/code/core/src/core-events/index.ts @@ -47,6 +47,8 @@ enum events { STORY_ARGS_UPDATED = 'storyArgsUpdated', // Reset either a single arg of a story all args of a story RESET_STORY_ARGS = 'resetStoryArgs', + // Emitted after a filter is set + SET_FILTER = 'setFilter', // Emitted by the preview at startup once it knows the initial set of globals+globalTypes SET_GLOBALS = 'setGlobals', // Tell the preview to update the value of a global @@ -114,6 +116,7 @@ export const { SELECT_STORY, SET_CONFIG, SET_CURRENT_STORY, + SET_FILTER, SET_GLOBALS, SET_INDEX, SET_STORIES, diff --git a/code/core/src/manager-api/modules/stories.ts b/code/core/src/manager-api/modules/stories.ts index 23edae60ad95..80511847149b 100644 --- a/code/core/src/manager-api/modules/stories.ts +++ b/code/core/src/manager-api/modules/stories.ts @@ -41,6 +41,7 @@ import { DOCS_PREPARED, SET_CURRENT_STORY, SET_CONFIG, + SET_FILTER, } from '@storybook/core/core-events'; import { logger } from '@storybook/core/client-logger'; @@ -662,6 +663,8 @@ export const init: ModuleFn = ({ Object.entries(refs).forEach(([refId, { internal_index, ...ref }]) => { fullAPI.setRef(refId, { ...ref, storyIndex: internal_index }, true); }); + + provider.channel?.emit(SET_FILTER, { id }); }, }; diff --git a/code/core/src/manager/components/sidebar/FilterToggle.stories.ts b/code/core/src/manager/components/sidebar/FilterToggle.stories.ts new file mode 100644 index 000000000000..1c40ea9d775d --- /dev/null +++ b/code/core/src/manager/components/sidebar/FilterToggle.stories.ts @@ -0,0 +1,40 @@ +import { fn } from '@storybook/test'; +import { FilterToggle } from './FilterToggle'; + +export default { + component: FilterToggle, + args: { + active: false, + onClick: fn(), + }, +}; + +export const Changes = { + args: { + count: 12, + label: 'Change', + status: 'warning', + }, +}; + +export const ChangesActive = { + args: { + ...Changes.args, + active: true, + }, +}; + +export const Errors = { + args: { + count: 2, + label: 'Error', + status: 'critical', + }, +}; + +export const ErrorsActive = { + args: { + ...Errors.args, + active: true, + }, +}; diff --git a/code/core/src/manager/components/sidebar/FilterToggle.tsx b/code/core/src/manager/components/sidebar/FilterToggle.tsx new file mode 100644 index 000000000000..93d056fa59ac --- /dev/null +++ b/code/core/src/manager/components/sidebar/FilterToggle.tsx @@ -0,0 +1,59 @@ +import { Badge as BaseBadge, IconButton } from '@storybook/components'; +import { css, styled } from '@storybook/theming'; +import React, { type ComponentProps } from 'react'; + +const Badge = styled(BaseBadge)(({ theme }) => ({ + padding: '4px 8px', + fontSize: theme.typography.size.s1, +})); + +const Button = styled(IconButton)( + ({ theme }) => ({ + fontSize: theme.typography.size.s2, + '&:hover [data-badge][data-status=warning], [data-badge=true][data-status=warning]': { + background: '#E3F3FF', + borderColor: 'rgba(2, 113, 182, 0.1)', + color: '#0271B6', + }, + '&:hover [data-badge][data-status=critical], [data-badge=true][data-status=critical]': { + background: theme.background.negative, + boxShadow: `inset 0 0 0 1px rgba(182, 2, 2, 0.1)`, + color: theme.color.negativeText, + }, + }), + ({ active, theme }) => + !active && + css({ + '&:hover': { + color: theme.base === 'light' ? theme.color.defaultText : theme.color.light, + }, + }) +); + +const Label = styled.span(({ theme }) => ({ + color: theme.base === 'light' ? theme.color.defaultText : theme.color.light, +})); + +interface FilterToggleProps { + active: boolean; + count: number; + label: string; + status: ComponentProps['status']; +} + +export const FilterToggle = ({ + active, + count, + label, + status, + ...props +}: FilterToggleProps & Omit, 'status'>) => { + return ( + + ); +}; diff --git a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx index b954cefc84c2..cb13169d9365 100644 --- a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx +++ b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx @@ -4,7 +4,7 @@ import type { IndexHash, State } from '@storybook/core/manager-api'; import { ManagerContext, types } from '@storybook/core/manager-api'; import type { StoryObj, Meta } from '@storybook/react'; import { within, userEvent, expect, fn } from '@storybook/test'; -import type { Addon_SidebarTopType } from '@storybook/core/types'; +import type { Addon_SidebarTopType, API_StatusState } from '@storybook/core/types'; import { Button, IconButton } from '@storybook/core/components'; import { FaceHappyIcon } from '@storybook/icons'; import { Sidebar, DEFAULT_REF_ID } from './Sidebar'; @@ -26,6 +26,26 @@ const storyId = 'root-1-child-a2--grandchild-a1-1'; export const simpleData = { menu, index, storyId }; export const loadingData = { menu }; +const managerContext: any = { + state: { + docsOptions: { + defaultName: 'Docs', + autodocs: 'tag', + docsMode: false, + }, + }, + api: { + emit: fn().mockName('api::emit'), + on: fn().mockName('api::on'), + off: fn().mockName('api::off'), + getShortcutKeys: fn(() => ({ search: ['control', 'shift', 's'] })).mockName( + 'api::getShortcutKeys' + ), + selectStory: fn().mockName('api::selectStory'), + experimental_setFilter: fn().mockName('api::experimental_setFilter'), + }, +}; + const meta = { component: Sidebar, title: 'Sidebar/Sidebar', @@ -44,28 +64,7 @@ const meta = { }, decorators: [ (storyFn) => ( - ({ search: ['control', 'shift', 's'] })).mockName( - 'api::getShortcutKeys' - ), - selectStory: fn().mockName('api::selectStory'), - }, - } as any - } - > + {storyFn()} @@ -218,41 +217,29 @@ export const Searching: Story = { }; export const Bottom: Story = { - args: { - bottom: [ - { - id: '1', - type: types.experimental_SIDEBAR_BOTTOM, - render: () => ( - - ), - }, - { - id: '2', - type: types.experimental_SIDEBAR_BOTTOM, - render: () => ( - - ), - }, - { - id: '3', - type: types.experimental_SIDEBAR_BOTTOM, - render: () => ( - - {' '} - - - ), - }, - ], - }, + decorators: [ + (storyFn) => ( + + {storyFn()} + + ), + ], }; /** diff --git a/code/core/src/manager/components/sidebar/Sidebar.tsx b/code/core/src/manager/components/sidebar/Sidebar.tsx index 583c11518fc9..55b5af5f0b42 100644 --- a/code/core/src/manager/components/sidebar/Sidebar.tsx +++ b/code/core/src/manager/components/sidebar/Sidebar.tsx @@ -20,6 +20,7 @@ import { SearchResults } from './SearchResults'; import type { CombinedDataset, Selection } from './types'; import { useLastViewed } from './useLastViewed'; import { MEDIA_DESKTOP_BREAKPOINT } from '../../constants'; +import { SidebarBottom } from './SidebarBottom'; export const DEFAULT_REF_ID = 'storybook_internal'; @@ -109,7 +110,6 @@ export interface SidebarProps extends API_LoadedRefData { status: State['status']; menu: any[]; extra: Addon_SidebarTopType[]; - bottom?: Addon_SidebarBottomType[]; storyId?: string; refId?: string; menuHighlighted?: boolean; @@ -128,7 +128,6 @@ export const Sidebar = React.memo(function Sidebar({ previewInitialized, menu, extra, - bottom = [], menuHighlighted = false, enableShortcuts = true, refs = {}, @@ -194,9 +193,7 @@ export const Sidebar = React.memo(function Sidebar({ {isLoading ? null : ( - {bottom.map(({ id, render: Render }) => ( - - ))} + )} diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx new file mode 100644 index 000000000000..f6de8f13b787 --- /dev/null +++ b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx @@ -0,0 +1,42 @@ +import { fn } from '@storybook/test'; + +import { SidebarBottomBase } from './SidebarBottom'; + +export default { + component: SidebarBottomBase, + args: { + api: { + experimental_setFilter: fn(), + emit: fn(), + }, + }, +}; + +export const Changes = { + args: { + status: { + one: { 'sidebar-bottom-filter': { status: 'warn' } }, + two: { 'sidebar-bottom-filter': { status: 'warn' } }, + }, + }, +}; + +export const Errors = { + args: { + status: { + one: { 'sidebar-bottom-filter': { status: 'error' } }, + two: { 'sidebar-bottom-filter': { status: 'error' } }, + }, + }, +}; + +export const Both = { + args: { + status: { + one: { 'sidebar-bottom-filter': { status: 'warn' } }, + two: { 'sidebar-bottom-filter': { status: 'warn' } }, + three: { 'sidebar-bottom-filter': { status: 'error' } }, + four: { 'sidebar-bottom-filter': { status: 'error' } }, + }, + }, +}; diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.tsx new file mode 100644 index 000000000000..f85c5eaacc8c --- /dev/null +++ b/code/core/src/manager/components/sidebar/SidebarBottom.tsx @@ -0,0 +1,91 @@ +import { + useStorybookApi, + useStorybookState, + type API, + type State, +} from '@storybook/core/manager-api'; +import { styled } from '@storybook/core/theming'; +import type { API_FilterFunction } from '@storybook/types'; +import React, { useCallback, useEffect } from 'react'; + +import { FilterToggle } from './FilterToggle'; + +const filterNone: API_FilterFunction = () => true; +const filterWarn: API_FilterFunction = ({ status = {} }) => + Object.values(status).some((value) => value?.status === 'warn'); +const filterError: API_FilterFunction = ({ status = {} }) => + Object.values(status).some((value) => value?.status === 'error'); +const filterBoth: API_FilterFunction = ({ status = {} }) => + Object.values(status).some((value) => value?.status === 'warn' || value?.status === 'error'); + +const getFilter = (showWarnings = false, showErrors = false) => { + if (showWarnings && showErrors) return filterBoth; + if (showWarnings) return filterWarn; + if (showErrors) return filterError; + return filterNone; +}; + +const Wrapper = styled.div({ + display: 'flex', + gap: 5, +}); + +interface SidebarBottomProps { + api: API; + status: State['status']; +} + +export const SidebarBottomBase = ({ api, status = {} }: SidebarBottomProps) => { + const [showWarnings, setShowWarnings] = React.useState(false); + const [showErrors, setShowErrors] = React.useState(false); + + const warnings = Object.values(status).filter((statusByAddonId) => + Object.values(statusByAddonId).some((value) => value.status === 'warn') + ); + const errors = Object.values(status).filter((statusByAddonId) => + Object.values(statusByAddonId).some((value) => value.status === 'error') + ); + const hasWarnings = warnings.length > 0; + const hasErrors = errors.length > 0; + + const toggleWarnings = useCallback(() => setShowWarnings((shown) => !shown), []); + const toggleErrors = useCallback(() => setShowErrors((shown) => !shown), []); + + useEffect(() => { + const filter = getFilter(hasWarnings && showWarnings, hasErrors && showErrors); + api.experimental_setFilter('sidebar-bottom-filter', filter); + }, [api, hasWarnings, hasErrors, showWarnings, showErrors]); + + if (!hasWarnings && !hasErrors) return null; + + return ( + + {hasErrors && ( + + )} + {hasWarnings && ( + + )} + + ); +}; + +export const SidebarBottom = () => { + const api = useStorybookApi(); + const { status } = useStorybookState(); + return ; +}; diff --git a/code/core/src/manager/container/Sidebar.tsx b/code/core/src/manager/container/Sidebar.tsx index 058361de9afb..9bc6c156b0ba 100755 --- a/code/core/src/manager/container/Sidebar.tsx +++ b/code/core/src/manager/container/Sidebar.tsx @@ -42,11 +42,8 @@ const Sidebar = React.memo(function Sideber({ onMenuClick }: SidebarProps) { const whatsNewNotificationsEnabled = state.whatsNewData?.status === 'SUCCESS' && !state.disableWhatsNewNotifications; - const bottomItems = api.getElements(Addon_TypesEnum.experimental_SIDEBAR_BOTTOM); const topItems = api.getElements(Addon_TypesEnum.experimental_SIDEBAR_TOP); // eslint-disable-next-line react-hooks/exhaustive-deps - const bottom = useMemo(() => Object.values(bottomItems), [Object.keys(bottomItems).join('')]); - // eslint-disable-next-line react-hooks/exhaustive-deps const top = useMemo(() => Object.values(topItems), [Object.keys(topItems).join('')]); return { @@ -63,7 +60,6 @@ const Sidebar = React.memo(function Sideber({ onMenuClick }: SidebarProps) { menu, menuHighlighted: whatsNewNotificationsEnabled && api.isWhatsNewUnread(), enableShortcuts, - bottom, extra: top, }; }; diff --git a/code/core/src/manager/globals/exports.ts b/code/core/src/manager/globals/exports.ts index f83d30717328..74ac509e61e5 100644 --- a/code/core/src/manager/globals/exports.ts +++ b/code/core/src/manager/globals/exports.ts @@ -777,6 +777,7 @@ export default { 'SELECT_STORY', 'SET_CONFIG', 'SET_CURRENT_STORY', + 'SET_FILTER', 'SET_GLOBALS', 'SET_INDEX', 'SET_STORIES', @@ -833,6 +834,7 @@ export default { 'SELECT_STORY', 'SET_CONFIG', 'SET_CURRENT_STORY', + 'SET_FILTER', 'SET_GLOBALS', 'SET_INDEX', 'SET_STORIES', @@ -889,6 +891,7 @@ export default { 'SELECT_STORY', 'SET_CONFIG', 'SET_CURRENT_STORY', + 'SET_FILTER', 'SET_GLOBALS', 'SET_INDEX', 'SET_STORIES', From 5a60cf59322005c7c7f3d3cea039288ad26947d1 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Jul 2024 17:49:24 +0200 Subject: [PATCH 2/6] Mark experimental_SIDEBAR_BOTTOM as deprecated --- code/core/src/types/modules/addons.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/types/modules/addons.ts b/code/core/src/types/modules/addons.ts index cc9d90251c2c..6a6fc417a5f4 100644 --- a/code/core/src/types/modules/addons.ts +++ b/code/core/src/types/modules/addons.ts @@ -523,7 +523,7 @@ export enum Addon_TypesEnum { experimental_PAGE = 'page', /** * This adds items in the bottom of the sidebar. - * @unstable + * @deprecated This doesn't do anything anymore and will be removed in Storybook 9.0. */ experimental_SIDEBAR_BOTTOM = 'sidebar-bottom', /** From 61a02237124a906063555faf9ddb574ac56fe46d Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Tue, 23 Jul 2024 20:21:20 +0200 Subject: [PATCH 3/6] Update stories --- .../sidebar/FilterToggle.stories.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/code/core/src/manager/components/sidebar/FilterToggle.stories.ts b/code/core/src/manager/components/sidebar/FilterToggle.stories.ts index 1c40ea9d775d..8177cbbe9713 100644 --- a/code/core/src/manager/components/sidebar/FilterToggle.stories.ts +++ b/code/core/src/manager/components/sidebar/FilterToggle.stories.ts @@ -9,32 +9,32 @@ export default { }, }; -export const Changes = { +export const Errors = { args: { - count: 12, - label: 'Change', - status: 'warning', + count: 2, + label: 'Error', + status: 'critical', }, }; -export const ChangesActive = { +export const ErrorsActive = { args: { - ...Changes.args, + ...Errors.args, active: true, }, }; -export const Errors = { +export const Warning = { args: { - count: 2, - label: 'Error', - status: 'critical', + count: 12, + label: 'Warning', + status: 'warning', }, }; -export const ErrorsActive = { +export const WarningActive = { args: { - ...Errors.args, + ...Warning.args, active: true, }, }; From 04bd79a096eae5a6a39f4a4f795b4096a238b991 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 24 Jul 2024 13:25:00 +0200 Subject: [PATCH 4/6] Mark experimental_SIDEBAR_TOP as deprecated --- code/core/src/types/modules/addons.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/code/core/src/types/modules/addons.ts b/code/core/src/types/modules/addons.ts index 6a6fc417a5f4..708261d2626f 100644 --- a/code/core/src/types/modules/addons.ts +++ b/code/core/src/types/modules/addons.ts @@ -431,6 +431,10 @@ export interface Addon_WrapperType { }> >; } + +/** + * @deprecated This doesn't do anything anymore and will be removed in Storybook 9.0. + */ export interface Addon_SidebarBottomType { type: Addon_TypesEnum.experimental_SIDEBAR_BOTTOM; /** @@ -443,6 +447,9 @@ export interface Addon_SidebarBottomType { render: FC; } +/** + * @deprecated This will be removed in Storybook 9.0. + */ export interface Addon_SidebarTopType { type: Addon_TypesEnum.experimental_SIDEBAR_TOP; /** @@ -528,7 +535,7 @@ export enum Addon_TypesEnum { experimental_SIDEBAR_BOTTOM = 'sidebar-bottom', /** * This adds items in the top of the sidebar. - * @unstable This will get replaced with a new API in 8.0, use at your own risk. + * @deprecated This will be removed in Storybook 9.0. */ experimental_SIDEBAR_TOP = 'sidebar-top', } From ed560db4cf518ad43f18e106d1f8b098163abe58 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 24 Jul 2024 13:25:26 +0200 Subject: [PATCH 5/6] Add migration notes for deprecated APIs --- MIGRATION.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 3dd1311dd620..6a9f7d85ddc5 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,7 @@

Migration

+- [From version 8.2.x to 8.3.x](#from-version-82x-to-83x) + - [Removed `experimental_SIDEBAR_BOTTOM` and deprecated `experimental_SIDEBAR_TOP` addon types](#removed-experimental_sidebar_bottom-and-deprecated-experimental_sidebar_top-addon-types) - [From version 8.1.x to 8.2.x](#from-version-81x-to-82x) - [Failed to resolve import "@storybook/X" error](#failed-to-resolve-import-storybookx-error) - [Preview.js globals renamed to initialGlobals](#previewjs-globals-renamed-to-initialglobals) @@ -414,6 +416,14 @@ - [Packages renaming](#packages-renaming) - [Deprecated embedded addons](#deprecated-embedded-addons) +## From version 8.2.x to 8.3.x + +### Removed `experimental_SIDEBAR_BOTTOM` and deprecated `experimental_SIDEBAR_TOP` addon types + +The experimental SIDEBAR_BOTTOM addon type was removed in favor of a built-in filter UI. The enum type definition will remain available until Storybook 9.0 but will be ignored. Similarly the experimental SIDEBAR_TOP addon type is deprecated and will be removed in a future version. + +These APIs allowed addons to render arbitrary content in the Storybook sidebar. Due to potential conflicts between addons and challenges regarding styling, these APIs are/will be removed. In the future, Storybook will provide declarative API hooks to allow addons to add content to the sidebar without risk of conflicts or UI inconsistencies. One such API is `experimental_updateStatus` which allow addons to set a status for stories. The SIDEBAR_BOTTOM slot is now used to allow filtering stories with a given status. + ## From version 8.1.x to 8.2.x ### Failed to resolve import "@storybook/X" error @@ -2324,8 +2334,8 @@ export default config; #### Vite builder uses Vite config automatically -When using a [Vite-based framework](#framework-field-mandatory), Storybook will automatically use your `vite.config.(ctm)js` config file starting in 7.0. -Some settings will be overridden by Storybook so that it can function properly, and the merged settings can be modified using `viteFinal` in `.storybook/main.js` (see the [Storybook Vite configuration docs](https://storybook.js.org/docs/react/builders/vite#configuration)). +When using a [Vite-based framework](#framework-field-mandatory), Storybook will automatically use your `vite.config.(ctm)js` config file starting in 7.0. +Some settings will be overridden by Storybook so that it can function properly, and the merged settings can be modified using `viteFinal` in `.storybook/main.js` (see the [Storybook Vite configuration docs](https://storybook.js.org/docs/react/builders/vite#configuration)). If you were using `viteFinal` in 6.5 to simply merge in your project's standard Vite config, you can now remove it. For Svelte projects this means that the `svelteOptions` property in the `main.js` config should be omitted, as it will be loaded automatically via the project's `vite.config.js`. From 257f53df5eb02111deb1c1e567baa64c793da201 Mon Sep 17 00:00:00 2001 From: Gert Hengeveld Date: Wed, 24 Jul 2024 13:46:56 +0200 Subject: [PATCH 6/6] Rename story for consistency --- .../components/sidebar/SidebarBottom.stories.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx index f6de8f13b787..498750fd82e0 100644 --- a/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx +++ b/code/core/src/manager/components/sidebar/SidebarBottom.stories.tsx @@ -12,20 +12,20 @@ export default { }, }; -export const Changes = { +export const Errors = { args: { status: { - one: { 'sidebar-bottom-filter': { status: 'warn' } }, - two: { 'sidebar-bottom-filter': { status: 'warn' } }, + one: { 'sidebar-bottom-filter': { status: 'error' } }, + two: { 'sidebar-bottom-filter': { status: 'error' } }, }, }, }; -export const Errors = { +export const Warnings = { args: { status: { - one: { 'sidebar-bottom-filter': { status: 'error' } }, - two: { 'sidebar-bottom-filter': { status: 'error' } }, + one: { 'sidebar-bottom-filter': { status: 'warn' } }, + two: { 'sidebar-bottom-filter': { status: 'warn' } }, }, }, };