@serenis /图标
用于Serenis设计系统的图标组件库。包装自定义SVG图标并从iconoir-react重新导出。
安装
npm install @serenis/icons
快速启动
# Build
yarn workspace @serenis/icons build
# Dev (watch mode)
yarn workspace @serenis/icons dev
# Typecheck
yarn workspace @serenis/icons typecheck
import { Icon, Home, IconHome } from '@serenis/icons'
import { type IconElement, type IconCommonProps, type IconSize } from '@serenis/icons'
// Option A: Icon wrapper with raw SVG component (dynamic / slotted usage)
<Icon Svg={Home} colorName="primary" size={24} />
// Option B: Pre-wrapped shorthand (fixed icon usage)
<IconHome colorName="primary" size={24} />
目录结构
libraries/icons/
├── src/
│ ├── components/ # One .tsx file per icon + Icon.tsx wrapper
│ │ ├── Icon.tsx # Design-token-aware wrapper
│ │ ├── Home.tsx # Example: exports Home + IconHome
│ │ ├── Agenda.tsx
│ │ └── ... # custom icon files
│ ├── stories/
│ │ ├── Overview.mdx # Storybook documentation page
│ │ ├── Playground.stories.tsx # Storybook playground for all icons
│ │ └── ...
│ ├── types.ts # Shared TypeScript types
│ └── index.ts # Public API (re-exports everything)
├── dist/ # Built output (gitignored)
├── tsup.config.ts # Bundle config
└── package.json
体系结构
两个图标源
该库通过一个公共API从两个来源发送图标:
| 来源 | 他们住在哪里 | Icon* 速记? |
|---|---|---|
| 自定义塞雷尼 | src/components/*.tsx (内联SVG) | 是的。 Home + IconHome |
| Iconoir | 再出口的 iconoir-react | 无使用通道 <Icon Svg={...} /> |
自定义图标是手工维护的内联SVG React组件,专门为Serenis产品设计。Iconoir图标填补了不需要自定义艺术品的通用UI符号(箭头,导航chrome,实用程序操作)的空白。
这个 Icon 包装器
Icon 是原始SVG组件和设计令牌系统之间的桥梁。它接受任何 IconElement 并应用基于设计标记的颜色和大小:
const Icon = ({ Svg, colorName, fillColorName, size }: IconProps): JSX.Element | null => {
if (Svg == null) {
return null
}
return (
<Svg
color={colorName ? cssvarColor(colorName) : 'currentColor'}
fill={fillColorName ? cssvarColor(fillColorName) : 'none'}
height={size}
width={size}
/>
)
}
colorName-映射到color(中风)通过cssvarColor()从@serenis/design-tokens。省略默认为currentColor,从父文本颜色继承。fillColorName-映射到fill通过cssvarColor()。省略默认为none。很少使用(星号,一些导航图标)。size-适用于两者width和height.
双重出口模式
每个自定义图标文件导出两件事:
// Home.tsx
export const Home = ({ color, height, width }: IconElementProps) => (
<svg ...>...</svg>
)
export const IconHome = (props: IconCommonProps) => <Icon Svg={Home} {...props} />
Home-原始SVG组件。的引用传递时使用Icon封装器或类型为的组件道具IconElement(例如按钮的IconStart)。IconHome-预包装简写。当你不需要传递图标作为参考时,使用直接渲染。
Iconoir图标只能导出为原始组件(没有 Icon* 速记)。
类型系统
所有类型都是从包的根目录导出的:
import {
type IconElement,
type IconElementProps,
type IconCommonProps,
type IconProps,
type IconSize,
} from '@serenis/icons'
| 类型 | 目的 |
|---|---|
IconSize | 12 | 16 | 20 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 120 |
IconElementProps | SVG组件的底层契约: { color, fill, height, width } |
IconElement | 任何接受 IconElementProps -用于接受图标引用的道具 |
IconCommonProps | 基于符号的API: { colorName?, fillColorName?, size } |
IconProps | IconCommonProps & { Svg?: IconElement } - - - Icon 包装道具 |
尺度
| 大小 |
|---|
| 12 |
| 16 |
| 20. |
| 24 (默认) |
| 32 |
| 40 |
| 48 |
| 56 |
| 64 |
| 72 |
| 80 |
| 120 |
使用模式
直接渲染
// Pre-wrapped shorthand (custom icons only)
<IconHome colorName="primary" size={24} />
// Icon wrapper (both custom and Iconoir)
<Icon Svg={Home} colorName="primary" size={24} />
<Icon Svg={ArrowLeft} colorName="neutral-80" size={24} />
将图标作为道具传递
许多 @serenis/ui 组件接受 IconElement 作为道具:
// Button with start icon
<Button IconStart={Home} kind="primary">Go home</Button>
// Tile with required icon
<Tile Icon={Shield} title="Security" action={<Button>Configure</Button>} />
// SideBarItem with active/inactive variants
<SideBarItem Icon={Agenda} IconActive={AgendaSolid} label="Agenda" active={isActive} />
活动/不活动模式
许多自定义图标对于活动状态都有Solid变体:
<Icon Svg={active ? AgendaSolid : Agenda} colorName={active ? 'primary-50' : 'neutral-110'} size={24} />
可用双: Agenda/AgendaSolid, Calendar/CalendarSolid, Chat/ChatSolid, Clock/ClockSolid, Codes/CodesSolid, Delete/DeleteSolid, Diary/DiarySolid, Home/HomeSolid, Menu/MenuSolid, Patients/PatientsSolid, Paths/PathsSolid等等 Path*/Path*Solid 治疗路径图标。
名称冲突时的别名
一些图标名称与通用标识符冲突。输入时的别名:
import { Card as IconCard, Link as IconLink } from '@serenis/icons'
从父代继承颜色
省略 colorName 使用 currentColor -图标继承父元素的文本颜色:
<span className={cx('text-primary')}>
<Icon Svg={Home} size={24} /> {/* stroke is primary */}
</span>
可访问性
控件呈现的图标 Icon 包装是 默认是装饰性的 -它们没有可访问的名称或角色。含义应该来自相邻的文本或父控件的标签。
带有文本的图标(不需要额外的ARIA)
当图标出现在可见文本旁边时,文本提供了可访问的名称。图标上不需要额外的属性:
<Button IconStart={Home} kind="primary">Go home</Button>
<div className={cx('flex', 'items-center', 'gap-xs')}>
<Icon Svg={Info} size={24} />
<span>Important information</span>
</div>
只需要图标控件 aria-label
当一个图标是 唯一的内容 对于可单击控件,父元素必须提供一个可访问的名称 aria-label 或者 aria-labelledby:
<Button IconStart={Delete} kind="tertiary" aria-label="Delete item" />
<Pressable aria-label="Close" onClick={onClose}>
<Icon Svg={ActionCloseLine} size={24} />
</Pressable>
为什么 Icon 包装器没有ARIA属性
这个 Icon 组件故意不设置 aria-hidden, role,或任何ARIA属性。图标是装饰性的还是有意义的取决于使用上下文,只有调用者知道。调用者应该:
- 添加
aria-label当图标是唯一的内容时,调用父控件 - 当相邻的文本已经传达了意思时什么都不做
- 避免增加
aria-hidden="true"显示在文本旁边的图标(文本已经提供了名称)
ESLint执法
自定义ESLint规则 serenis/no-icon-inside-button (活跃 warn 在 libraries/ui, apps/web, apps/blog, apps/website, apps/nutrition-blog)防止嵌套 Icon 或者 Icon* 的子组件 Button。使用 IconStart / IconEnd Props——这确保了一致的大小、加载状态处理和可访问的标记:
// Correct
<Button IconStart={Home} kind="primary">Go home</Button>
// Incorrect — caught by ESLint
<Button><Icon Svg={Home} size={24} />Go home</Button>
如何添加新图标
没有代码生成管道-图标是手工维护的TSX文件。
- 创建组件文件 在
src/components/MyIcon.tsx:
import { type IconCommonProps, type IconElementProps } from '../types'
import Icon from './Icon'
type Props = IconElementProps
export const MyIcon = ({ color, height, width }: Props) => (
<svg fill="none" height={height} viewBox="0 0 24 24" width={width} xmlns="http://www.w3.org/2000/svg">
{/* Paste SVG paths here, use color={color} for strokes */}
</svg>
)
export const IconMyIcon = (props: IconCommonProps) => <Icon Svg={MyIcon} {...props} />
- 出口的
src/index.ts(按字母顺序排列):
export { IconMyIcon, MyIcon } from './components/MyIcon'
- 构建和类型检查:
yarn workspace @serenis/icons build
yarn workspace @serenis/icons typecheck
新图标将自动出现在Storybook Playground故事中。
构建
这个包裹是捆绑的 tsup 转化为ESM + CJS + TypeScript声明。工作区依赖项(@serenis/design-tokens, iconoir-react, react)是外化的。
消费者
- 应用程序:
apps/web(主要客户)apps/blog,apps/nutrition-blog,apps/website - 库:
libraries/ui(@serenis/ui-按钮,模态,手风琴,面包屑,FileUploadField等),libraries/shared-components(@serenis/shared-components- Nav)
文档
- 故事书:概览页面和交互式游乐场中
apps/design-system - AGENTS.md:面向代理的API参考和规则-参见 AGENTS.md
许可证
自定义Serenis SVG图标:CC BY-NC-ND 4.0 -见 许可证 查看全文。Iconoir再出口保留其上游MIT许可证。