加入直播会话: 周四,美国东部时间上午11点,Storybook 9 发布及 AMA
文档
Storybook 文档

高亮

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();
    },
  ],
};

启用后,当你点击与你提供的选择器匹配的选中元素时,将显示菜单。但是,如果你不想显示任何信息,可以省略这些项目或将 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 Hook 派生的 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 属性,其值为你想滚动到视图中的元素的选择器。此外,你可以提供一个 options 对象来自定义滚动行为。请参见上面的使用示例

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,则可以通过在元数据(组件)或故事级别将其设置为 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 属性,其值为你想滚动到视图中的元素的选择器。此外,你可以提供一个 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
);