You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Storybook中测试全局CSS定制的Ionic组件?

Ionic自定义组件的Storybook测试方案

问题背景

我希望用Storybook隔离测试自定义Ionic组件,把大部分定制逻辑放在全局CSS里,不用创建大量新组件,但不知道怎么在Storybook里测试。现有可运行的Story代码和全局SCSS如下:

现有Story代码

import { centered, in_header } from '~/.storybook/utils';
import { componentWrapperDecorator } from '@storybook/angular';

import type { Meta, StoryObj } from '@storybook/angular';

import { IonButton } from '@ionic/angular';
type Story = StoryObj<IonButton>;
const meta: Meta<IonButton> = {
  title: 'Ionic/Monkey Buttons',
  component: IonButton,
  tags: ['autodocs'],
  decorators: [
    centered({ horizontalMarginPx: 20 }),
    componentWrapperDecorator(() => `
      <ion-button> My Button </ion-button>
  `),
  ]
};
export default meta;

export const PrimaryFull: Story = {

};

export const PrimarySecondary: Story = {
      args: {
        // label: 'My Button',
        color: 'tertiary'
      }
};

全局SCSS定制

ion-button {
  // Rounded Corners
  --border-radius: var(--button-radius);

  // Defaults
  display: inline-flex; // Make sure button doesn't take full width by default
  align-items: center;
  justify-content: center;
  text-transform: none !important; // Disable uppercase globally for ion-buttons (need to force it!)
  padding: 0; // Ionic adds 5px padding top & bottom
  margin: 0; // remove outer margin (beyond the border-box)
  box-sizing: border-box !important;

  // Icon
  &.icon-present {
    .button-text {
      margin-left: 8px;
      text-align: left;
      text-transform: none !important;
    }
    ion-icon {
      margin-left: auto;
    }
  }

  &.icon-absent {
    .button-text {
      text-align: center;
      margin: 0 auto;
      text-transform: none !important;
    }
  }
}

具体问题解答

1. 如何在Story中为ion-button添加自定义类(如icon-present)

有两种常用实现方式:

  • 方式一:通过Story的args传递class属性
    Ionic组件支持直接绑定class属性,在Story的args中定义即可:
    export const ButtonWithIconClass: Story = {
      args: {
        class: 'icon-present',
        color: 'primary'
      }
    };
    
  • 方式二:修改componentWrapperDecorator的模板
    直接在装饰器的HTML模板中给ion-button添加类名,单个Story可单独设置装饰器覆盖全局配置:
    export const ButtonWithIconClass: Story = {
      decorators: [
        componentWrapperDecorator(() => `
          <ion-button class="icon-present"> My Button </ion-button>
        `)
      ]
    };
    

2. 如何在Story层面修改按钮文本

三种灵活实现方式:

  • 方式一:修改componentWrapperDecorator的模板文本
    直接替换装饰器HTML中的硬编码文本:
    export const CustomTextButton: Story = {
      decorators: [
        componentWrapperDecorator(() => `
          <ion-button> 自定义按钮文本 </ion-button>
        `)
      ]
    };
    
  • 方式二:利用Ionic按钮的label输入属性
    IonButton原生支持label输入属性,直接在args中传递即可,需先移除全局装饰器中的硬编码文本避免冲突:
    // 全局装饰器修改为无文本模板
    componentWrapperDecorator(() => `<ion-button></ion-button>`),
    
    // 单个Story设置文本
    export const CustomTextButton: Story = {
      args: {
        label: '自定义按钮文本'
      }
    };
    
  • 方式三:通过template字段自定义按钮结构
    给单个Story设置专属模板,完全自定义内容:
    export const CustomTextButton: Story = {
      template: `<ion-button> {{ label }} </ion-button>`,
      args: {
        label: '自定义按钮文本'
      }
    };
    

额外需求:通过CSS自动判断是否含ion-icon并添加类

纯CSS没有父选择器,无法直接实现“检测子元素后给父元素加类”的逻辑,但有两种替代方案:

方案一:用CSS直接匹配含ion-icon的按钮

不需要额外类名,用:has()选择器直接匹配含ion-icon的按钮,现代浏览器和Storybook预览环境均支持:

// 替换原有.icon-present和.icon-absent逻辑
ion-button:has(ion-icon) {
  .button-text {
    margin-left: 8px;
    text-align: left;
    text-transform: none !important;
  }
  ion-icon {
    margin-left: auto;
  }
}

ion-button:not(:has(ion-icon)) {
  .button-text {
    text-align: center;
    margin: 0 auto;
    text-transform: none !important;
  }
}

方案二:给不同场景的Story设置专属包裹模板

如果坚持要用类名,可给每个场景的Story单独设置componentWrapperDecorator,完全可行:

// 带图标的按钮Story
export const ButtonWithIcon: Story = {
  decorators: [
    componentWrapperDecorator(() => `
      <ion-button class="icon-present">
        <ion-icon name="star"></ion-icon>
        <span class="button-text">带图标按钮</span>
      </ion-button>
    `)
  ]
};

// 不带图标的按钮Story
export const ButtonWithoutIcon: Story = {
  decorators: [
    componentWrapperDecorator(() => `
      <ion-button class="icon-absent">
        <span class="button-text">纯文本按钮</span>
      </ion-button>
    `)
  ]
};

内容的提问来源于stack exchange,提问作者monkey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 08:40:56