mcpField
M
mantine-react-component-dev

mantine-react-component-dev

github.com/gfazioli开发452 次安装1 天前更新
代码仓库

关于 mantine-react-component-dev

mantine-react-component-dev 是用于开发和维护符合 Mantine 规范的 React 组件的技能,涵盖组件模式、样式、无障碍、测试和文档,并遵循所述 monorepo 结构。

主要功能

  1. 01Mantine 多态工厂模式
  2. 02Styles API 与 CSS 变量
  3. 03上下文和复合组件模式
  4. 04受控与非受控状态指南
  5. 05无障碍和键盘导航
  6. 06测试与文档规范

适用场景

  1. 01构建 Mantine 风格的 React 组件
  2. 02通过 Styles API 添加样式定制
  3. 03实现共享状态和复合组件
  4. 04编写组件测试、演示和文档

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/gfazioli/mantine-select-stepper --skill mantine-react-component-dev

安装后在 Claude Code 中输入/mantine-react-component-dev确认已加载。

详细介绍

Mantine React 组件开发技能

使用此技能的时机

在以下情况下应用此技能:

  • 组件开发:基于 Mantine 的工厂模式创建或编辑 React 组件
  • TypeScript 模式:实现多态组件、类型安全的属性和 CSS 变量
  • Styles API:通过 Mantine 的 Styles API 系统(选择器、变量、修饰符)配置组件样式
  • 组件组合:构建带有静态子组件的复合组件(例如,Component.Target)
  • 上下文管理:实现安全的上下文模式以共享组件状态
  • 无障碍性:确保符合 ARIA 规范、支持键盘导航并管理焦点
  • 代码审查:确保符合既定模式和约定
  • 文档:编写组件演示、MDX 文档和 API 参考

项目结构

组件采用单体仓库工作区结构组织:

  • /package/src/:组件主要源代码

    • ComponentName.tsx:组件主要实现
    • ComponentName.module.css:组件作用域样式
    • ComponentName.context.ts:上下文提供程序(如有需要)
    • ComponentName.errors.ts:错误消息
    • ComponentName.test.tsx:Jest + Testing Library 测试
    • ComponentName.story.tsx:Storybook 故事
    • SubComponent/SubComponent.tsx:位于独立文件夹中的子组件
    • index.ts:公共导出
  • /docs/:Next.js 文档站点

    • demos/ComponentName.demo.*.tsx:交互式演示
    • styles-api/ComponentName.styles-api.ts:Styles API 元数据
    • docs.mdx:主要文档页面

请参阅 ./package/src/ 中的现有组件,了解实现示例。

TypeScript 模式

组件工厂模式

所有组件都使用 Mantine 的多态工厂模式。

import { polymorphicFactory, PolymorphicFactory, useProps, useStyles, createVarsResolver } from '@mantine/core';

export type ComponentStylesNames = 'root' | 'element';
export type ComponentCssVariables = {
  root: '--custom-var' | '--another-var';
};

export interface ComponentProps extends BoxProps, StylesApiProps<ComponentFactory> {
  /** Prop description */
  customProp?: string;
}

export type ComponentFactory = PolymorphicFactory<{
  props: ComponentProps;
  defaultComponent: 'div';
  defaultRef: HTMLDivElement;
  stylesNames: ComponentStylesNames;
  vars: ComponentCssVariables;
  staticComponents: {
    SubComponent: typeof SubComponent;
  };
}>;

const defaultProps: Partial<ComponentProps> = {
  customProp: 'default',
};

const varsResolver = createVarsResolver<ComponentFactory>((_, { customProp }) => ({
  root: {
    '--custom-var': customProp,
  },
}));

export const Component = polymorphicFactory<ComponentFactory>((_props, ref) => {
  const props = useProps('Component', defaultProps, _props);
  const { classNames, className, style, styles, unstyled, vars, customProp, ...others } = props;
  
  const getStyles = useStyles<ComponentFactory>({
    name: 'Component',
    classes,
    props,
    className,
    style,
    classNames,
    styles,
    unstyled,
    vars,
    varsResolver,
  });

  return (
    <Box ref={ref} {...getStyles('root')} {...others}>
      {/* component content */}
    </Box>
  );
});

Component.displayName = '@your-scope/Component';
Component.SubComponent = SubComponent;

主要要求:

  • 对不受约束的泛型使用 unknown;通过类型守卫缩小类型范围
  • 避免使用 any;子元素使用 React.ReactNode
  • 为样式名称和 CSS 变量定义显式类型别名
  • 使用 useProps Hook 合并默认属性
  • 实现 varsResolver 以处理 CSS 自定义属性
上下文模式

对于需要共享状态的组件,请使用 Mantine 的安全上下文模式。

import { createSafeContext } from '@mantine/core';
import { COMPONENT_ERRORS } from './Component.errors';

interface ComponentContext {
  state: boolean;
  setState: (value: boolean) => void;
}

export const [ComponentContextProvider, useComponentContext] = createSafeContext<ComponentContext>(
  COMPONENT_ERRORS.context
);

Component.errors.ts 中的错误定义:

export const COMPONENT_ERRORS = {
  context: 'Component was not found in the tree',
  validation: 'Specific validation message',
};
子组件模式

子组件通过上下文访问父组件,并强制执行约束。

import { forwardRef, useProps, isElement, createEventHandler } from '@mantine/core';
import { useComponentContext } from '../Component.context';

export interface SubComponentProps {
  children: React.ReactNode;
  refProp?: string;
}

export const SubComponent = forwardRef<HTMLElement, SubComponentProps>((props, ref) => {
  const { children, ...others } = useProps('SubComponent', {}, props);
  
  if (!isElement(children)) {
    throw new Error(COMPONENT_ERRORS.children);
  }
  
  const ctx = useComponentContext();
  const onClick = createEventHandler(children.props.onClick, () => ctx.toggle());
  
  return cloneElement(children, { onClick, ref });
});

Styles API

每个组件都提供 Styles API 以供自定义。在 ./docs/styles-api/Component.styles-api.ts 中定义:

import type { ComponentFactory } from '@your-scope/component';
import type { StylesApiData } from '../components/styles-api.types';

export const ComponentStylesApi: StylesApiData<ComponentFactory> = {
  selectors: {
    root: 'Root element',
    element: 'Specific child element',
  },
  vars: {
    root: {
      '--custom-var': 'Controls custom behavior',
      '--another-var': 'Controls another aspect',
    },
  },
  modifiers: [
    { modifier: 'data-active', selector: 'root', condition: '`active` prop is set' }
  ],
};

CSS 模块(Component.module.css):

.root {
  /* Use CSS custom properties from varsResolver */
  property: var(--custom-var, fallback);
}

.element {
  /* Scoped class name */
}

/* Data attribute modifiers */
.root[data-active] {
  /* Active state */
}

组件指南

受控与非受控状态

使用 @mantine/hooks 中的 useUncontrolled 支持双模式状态:

import { useUncontrolled } from '@mantine/hooks';

const [value, setValue] = useUncontrolled({
  value: props.value,
  defaultValue: props.defaultValue,
  finalValue: undefined,
  onChange: props.onChange,
});
生命周期 Hook

使用 @mantine/hooks 中的 useDidUpdate 实现更新时触发副作用的模式:

import { useDidUpdate } from '@mantine/hooks';

useDidUpdate(() => {
  props.onStateChange?.(state);
}, [state]);
属性验证

当类型系统不足以确保正确性时,在运行时验证属性:

if (React.Children.count(children) !== 2) {
  throw new Error('Component requires exactly two children');
}
Ref 转发

始终支持 Ref 转发,以便组合组件:

export const Component = polymorphicFactory<ComponentFactory>((_props, ref) => {
  return <Box ref={ref} {...others} />;
});

编码标准

导入顺序

Prettier 会根据 ./.prettierrc.mjs 自动排序导入:

  1. CSS 导入
  2. React
  3. Next.js(如适用)
  4. 内置模块
  5. 第三方模块
  6. @mantine/* 包
  7. 本地导入(先父级,再同级)
  8. CSS 模块置于最后
格式
  • 打印宽度:100 个字符
  • 引号:单引号
  • 尾随逗号:ES5 风格
  • MDX:打印宽度为 70 个字符

提交前运行 npm run prettier:write。

代码检查

ESLint 配置继承自 eslint-config-mantine:

import mantine from 'eslint-config-mantine';
import tseslint from 'typescript-eslint';

export default tseslint.config(...mantine, { 
  ignores: ['**/.next/**', '**/*.{mjs,cjs,js,d.ts,d.mts}'] 
});

运行 npm run lint 检查所有规则。

TypeScript 配置

主要配置(./tsconfig.json):

  • 目标:ES2015
  • 模块:ESNext,使用 Node 解析
  • JSX:React(经典运行时)
  • 严格模式:已启用(通过 mantine 配置)
  • 跳过库检查:true(加快构建速度)

构建配置(tsconfig.build.json)用于隔离编译范围。

测试

使用 @mantine-tests/core 渲染器和 Testing Library。

import React from 'react';
import { render } from '@mantine-tests/core';
import { Component } from './Component';

describe('Component', () => {
  it('renders without crashing', () => {
    const { container } = render(<Component>Content</Component>);
    expect(container).toBeTruthy();
  });

  it('applies custom className', () => {
    const { container } = render(<Component className="custom" />);
    expect(container.querySelector('.custom')).toBeTruthy();
  });

  it('forwards ref', () => {
    const ref = React.createRef<HTMLDivElement>();
    render(<Component ref={ref} />);
    expect(ref.current).toBeTruthy();
  });
});

使用 npm run jest 运行测试。

文档

演示

在 ./docs/demos/ 中创建交互式演示:

// Component.demo.basic.tsx
import { Component } from '@your-scope/component';
import { MantineDemo } from '@docs/components';

const code = `
import { Component } from '@your-scope/component';

function Demo() {
  return <Component>Content</Component>;
}
`;

export const basic: MantineDemo = {
  type: 'code',
  component: Demo,
  code,
};

配置器演示使用 MantineDemo 类型 'configurator' 和 controls 对象。

MDX 页面

主要文档页面位于 ./docs/docs.mdx:

import { InstallScript } from './components/InstallScript/InstallScript';
import * as demos from './demos';

## Installation

<InstallScript packages="@your-scope/component" />

## Usage

<Demo data={demos.basic} />

## Props

<PropsTable component="Component" />

## Styles API

<StylesApiTable component="Component" />

请参照 /docs 中现有的文档结构,保持一致。

无障碍

ARIA 模式
键盘导航
  • 确保所有交互元素都可通过键盘访问
  • 使用 tabIndex={-1} 进行程序化焦点管理
  • 为复合控件实现方向键导航
焦点管理
import { useFocusTrap } from '@mantine/hooks';

const focusTrapRef = useFocusTrap(active);
用于状态的数据属性

通过数据属性公开组件状态,以供 CSS 和屏幕阅读器使用:

<div data-active={isActive} data-disabled={disabled} />

安全与维护

禁止硬编码机密信息
  • 切勿提交 API 密钥、令牌或凭据
  • 使用环境变量配置敏感信息
  • 在 .gitignore 中排除 .env 文件
依赖项管理
  • 保持 @mantine/core 和 @mantine/hooks 版本同步(参见 ./package/package.json)
  • 对 React 和 Mantine 包使用 peerDependencies
  • 运行 npm run syncpack 验证版本一致性
最小权限
  • 尽量缩小 React Context 提供程序的作用范围
  • 避免使用全局状态;优先使用组件级状态
  • 使用 createSafeContext 强制执行提供程序边界

边缘情况与故障排除

模块解析
  • 确保本地模块的所有导入都使用相对路径
  • 检查 package.json 的 exports 字段,确保入口点正确
  • 使用别名时,检查 tsconfig.json 中的 paths
CSS Modules
  • 类名通过 hash-css-selector 自动限定作用域(参见 ./rollup.config.mjs)
  • 使用 import classes from './Component.module.css' 导入 CSS 模块
  • 使用 getStyles 辅助函数合并 className
多态组件
  • 默认组件为 div;可通过 component 属性覆盖
  • Ref 类型必须与工厂定义中的 defaultRef 匹配
  • 属性会合并:BoxProps & ComponentProps & { component?: any }
Testing Library 查询
  • 优先使用 getByRole,而不是 getByTestId
  • 全局查询使用 screen,限定范围的查询使用 container
  • 需要时模拟 window.matchMedia(参见 ./jsdom.mocks.cjs)
Storybook 集成
  • Story 文件与组件放在一起,命名为 Component.story.tsx
  • 使用包含 Meta 和 StoryObj 类型的 CSF3 格式
  • Story 会根据 TypeScript 类型自动生成文档

文件组织

为每个组件保持一致的文件结构:

package/src/
├── Component.tsx              # Main component
├── Component.module.css       # Scoped styles
├── Component.context.ts       # Context (if needed)
├── Component.errors.ts        # Error constants
├── Component.test.tsx         # Tests
├── Component.story.tsx        # Storybook
├── SubComponent/
│   └── SubComponent.tsx       # Sub-component
└── index.ts                   # Public API

公共 API(index.ts):

export { Component } from './Component';
export type { ComponentProps, ComponentFactory } from './Component';

提交规范

  • 提交前运行 npm run test(包括 prettier、typecheck、lint、jest)
  • 编写清晰的提交信息:fix: resolve prop merging issue 或 feat: add keyboard navigation
  • 避免提交生成的文件(dist/、.next/、out/)
  • 使用 .gitignore 排除构建产物

参考资料

请参阅 ./references/ 中索引的文档指引。

相关文件

评论

支持 Markdown