文档
Storybook Docs

Highlight

Storybook 的高亮功能是一个用于直观调试组件的有用工具。它允许您在直接使用或增强插件(如无障碍功能插件)时,高亮显示故事中的特定 DOM 节点,以告知您组件中存在的无障碍问题。

Story with highlighted elements

高亮 DOM 元素

要高亮 DOM 元素,您需要从故事或插件中发出 HIGHLIGHT 事件。事件负载必须包含一个 selectors 属性,该属性被分配给一个选择器数组,匹配您想要高亮的元素。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { useChannel } from 'storybook/preview-api';
import { HIGHLIGHT } from 'storybook/highlight';
 
import { MyComponent } from './MyComponent';
 
const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const Highlighted: Story = {
  decorators: [
    (storyFn) => {
      const emit = useChannel({});
      emit(HIGHLIGHT, {
        selectors: ['h2', 'a', '.storybook-button'],
      });
      return storyFn();
    },
  ],
};

我们建议选择最具体可能的选择器,以避免高亮其他插件使用的元素。这是因为该功能会尝试将选择器与整个 DOM 树进行匹配。

自定义样式

默认情况下,高亮元素会包含应用于所选元素的标准轮廓样式。但是,您可以通过扩展负载对象并添加其他属性来定制高亮元素的显示方式,从而启用自定义样式。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { useChannel } from 'storybook/preview-api';
import { HIGHLIGHT } from 'storybook/highlight';
 
import { MyComponent } from './MyComponent';
 
const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const StyledHighlight: Story = {
  decorators: [
    (storyFn) => {
      const emit = useChannel({});
      emit(HIGHLIGHT, {
        selectors: ['h2', 'a', '.storybook-button'],
        styles: {
          backgroundColor: `color-mix(in srgb, hotpink, transparent 90%)`,
          outline: '3px solid hotpink',
          animation: 'pulse 3s linear infinite',
          transition: 'outline-offset 0.2s ease-in-out',
        },
        hoverStyles: {
          outlineOffset: '3px',
        },
        focusStyles: {
          backgroundColor: 'transparent',
        },
        keyframes: `@keyframes pulse {
          0% { outline-color: rgba(255, 105, 180, 1); }
          50% { outline-color: rgba(255, 105, 180, 0.2); }
          100% { outline-color: rgba(255, 105, 180, 1); }
        }`,
      });
      return storyFn();
    },
  ],
};

这些属性是可选的,您可以使用它们来定制高亮元素的显示方式。hoverStylesfocusStyles 属性建议与 menu 属性一起使用。不支持伪类和伪元素。

高亮菜单

高亮功能包含一个内置的调试选项,允许您在点击高亮元素时选择它们。这对于检查受该功能影响的元素特别有用,因为它允许您预览匹配您提供的选择器的元素列表。要启用它,请在负载对象中添加一个 menu 属性,其中包含有关元素或其他触发操作的附加信息。每个项目必须包含一个 id 和一个 title,您还可以提供一个可选的 selectors 属性来将菜单项限制为特定的高亮元素。

Menu with custom items

MyComponent.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { useChannel } from 'storybook/preview-api';
import { HIGHLIGHT } from 'storybook/highlight';
 
import { MyComponent } from './MyComponent';
 
const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const StyledHighlight: Story = {
  decorators: [
    (storyFn) => {
      const emit = useChannel({});
      emit(HIGHLIGHT, {
        selectors: ['h2', 'a', '.storybook-button'],
        menu: [
          [
            {
              id: 'button-name',
              title: 'Login',
              description: 'Navigate to the login page',
              clickEvent: 'my-menu-click-event',
            },
            {
              id: 'h2-home',
              title: 'Acme',
              description: 'Navigate to the home page',
            },
          ],
        ],
      });
      return storyFn();
    },
  ],
};

启用后,当您点击匹配您提供的选择器的所选元素时,将显示菜单。但是,如果您不想显示任何信息,可以省略 items 或将 menu 属性设置为一个空数组以显示默认菜单。

Menu of selectable targets

移除高亮

如果您需要从特定元素中移除高亮,可以通过发出 REMOVE_HIGHLIGHT 事件并提供您想移除的高亮 id 来实现。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { useChannel } from 'storybook/preview-api';
import { HIGHLIGHT, REMOVE_HIGHLIGHT } from 'storybook/highlight';
 
import { MyComponent } from './MyComponent';
 
const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const RemoveHighlight: Story = {
  decorators: [
    (storyFn) => {
      const emit = useChannel({});
      emit(HIGHLIGHT, {
        id: 'my-unique-id',
        selectors: ['header', 'section', 'footer'],
      });
      emit(REMOVE_HIGHLIGHT, 'my-unique-id');
      return storyFn();
    },
  ],
};

useChannel API 钩子派生的 emit 函数会在 Storybook 的 UI 中创建一个通信通道,用于监听事件并相应地更新 UI。高亮功能使用此通道来监听自定义事件并相应地更新高亮元素(如果有)。

重置高亮元素

默认情况下,Storybook 会在切换故事时自动移除高亮元素。但是,如果您需要手动清除它们,可以从故事或插件中发出 RESET_HIGHLIGHT 事件。这将移除所有高亮,包括其他插件创建的高亮。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { useChannel } from 'storybook/preview-api';
import { HIGHLIGHT, RESET_HIGHLIGHT } from 'storybook/highlight';
 
import { MyComponent } from './MyComponent';
 
const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const ResetHighlight: Story = {
  decorators: [
    (storyFn) => {
      const emit = useChannel({});
      emit(RESET_HIGHLIGHT); //👈 Remove previously highlighted elements
      emit(HIGHLIGHT, {
        selectors: ['header', 'section', 'footer'],
      });
      return storyFn();
    },
  ],
};

滚动元素至视图

高亮功能允许您滚动元素至视图并高亮它。要启用它,请从故事或插件中发出 SCROLL_INTO_VIEW 事件。事件负载必须包含一个 selector 属性,用于定位您想要滚动至视图的元素。当元素可见时,它会被短暂高亮。

MyComponent.stories.ts|tsx
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { useChannel } from 'storybook/preview-api';
import { SCROLL_INTO_VIEW } from 'storybook/highlight';
 
import { MyComponent } from './MyComponent';
 
const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const ScrollIntoView: Story = {
  decorators: [
    (storyFn) => {
      const emit = useChannel({});
      emit(SCROLL_INTO_VIEW, '#footer');
      return storyFn();
    },
  ],
};

API

参数

此功能为 Storybook 贡献了以下 参数,位于 highlight 命名空间下

disable

类型:boolean

禁用此功能。如果您希望在整个 Storybook 中关闭此功能,您应该在您的主配置文件中进行操作

此参数最适合用于允许在更具体的级别进行覆盖。例如,如果此参数在项目级别设置为 true,可以通过在 meta(组件)或故事级别将其设置为 false 来重新启用它。

导出

此功能为 Storybook 贡献了以下导出

import { HIGHLIGHT, REMOVE_HIGHLIGHT, RESET_HIGHLIGHT, SCROLL_INTO_VIEW } from 'storybook/highlight';

HIGHLIGHT

用于高亮 DOM 元素。事件负载必须包含一个 selectors 属性,该属性被分配给一个选择器数组,匹配您想要高亮的元素。它还可以扩展一个可选对象,其中包含额外的配置选项。请参阅上面的用法示例

import { HIGHLIGHT, type HighlightOptions } from 'storybook/highlight';
 
channel.emit(
  HIGHLIGHT,
  options // The available configuration options inheriting from the HighlightOptions API
);

options 对象包含以下属性:

interface HighlightOptions {
  /** Unique identifier for the highlight, required if you want to remove the highlight later */
  id?: string;
  /** HTML selectors of the elements */
  selectors: string[];
  /** Priority of the highlight, higher takes precedence, defaults to 0 */
  priority?: number;
  /** CSS styles to apply to the highlight */
  styles?: Record<string, string>;
  /** CSS styles to apply to the highlight when it is hovered */
  hoverStyles?: Record<string, string>;
  /** CSS styles to apply to the highlight when it is focused or selected */
  focusStyles?: Record<string, string>;
  /** Keyframes required for animations */
  keyframes?: string;
  /** Groups of menu items to show when the highlight is selected */
  menu?: HighlightMenuItem[][];
}
 
interface HighlightMenuItem {
  /** Unique identifier for the menu item */
  id: string;
  /** Title of the menu item */
  title: string;
  /** Description of the menu item */
  description?: string;
  /** Icon for the menu item, left side */
  iconLeft?: "chevronLeft" | "chevronRight" | "info" | "shareAlt";
  /** Icon for the menu item, right side */
  iconRight?: "chevronLeft" | "chevronRight" | "info" | "shareAlt";
  /** Name for a channel event to trigger when the menu item is clicked */
  clickEvent?: string;
  /** HTML selectors for which this menu item should show (subset of HighlightOptions['selectors']) */
  selectors?: HighlightOptions['selectors'];
}

菜单项可以指定一个 clickEvent,当项目被点击时,该事件将通过通道发出。通道事件将接收两个参数:菜单项的 id 和一个 ClickEventDetails 对象,包含以下属性:

interface ClickEventDetails {
  // Position and dimensions of the element on the page
  top: number;
  left: number;
  width: number;
  height: number;
  // Selector(s) which matched the element
  selectors: string[];
  // DOM element details
  element: {
    attributes: Record<string, string>;
    localName: string;
    tagName: string;
    outerHTML: string;
  };
}

要监听此事件(假设 clickEvent: 'MY_CLICK_EVENT'):

import type { ClickEventDetails } from 'storybook/highlight';
 
const handleClickEvent = (itemId: string, details: ClickEventDetails) => {
  // Handle the menu item click event
}
 
// When you have a channel instance:
channel.on('MY_CLICK_EVENT', handleClickEvent)
 
// Or from a decorator:
useChannel({
  MY_CLICK_EVENT: handleClickEvent,
}, [handleClickEvent])

REMOVE_HIGHLIGHT

用于移除先前创建的高亮的事件。事件负载必须包含一个 id 属性,该属性被分配给您想移除的高亮的 id。请参阅上面的用法示例

import { REMOVE_HIGHLIGHT } from 'storybook/highlight';
 
channel.emit(
  REMOVE_HIGHLIGHT,
  id // The id of the previously created highlight to be removed
);

RESET_HIGHLIGHT

用于清除所有高亮元素高亮的事件。请参阅上面的用法示例

import { RESET_HIGHLIGHT } from 'storybook/highlight';
 
channel.emit(RESET_HIGHLIGHT);

SCROLL_INTO_VIEW

用于滚动 DOM 元素至视图并短暂高亮它。事件负载必须包含一个 selector 属性,该属性被分配给您想滚动至视图的元素的 selector。您还可以选择提供一个 options 对象来定制滚动行为。请参阅上面的用法示例

import { SCROLL_INTO_VIEW } from 'storybook/highlight';
 
channel.emit(
  SCROLL_INTO_VIEW,
  selector // Element selector to scroll into view
  options // An object inheriting from ScrollIntoViewOptions API to customize the scroll behavior
);