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. 01Mantine の規約に沿った React コンポーネントの作成
  2. 02Styles 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 のパターン:ポリモーフィックなコンポーネント、型安全な props、CSS 変数の実装
  • Styles API:Mantine の Styles API システム(セレクター、vars、モディファイア)を使ったコンポーネントのスタイル設定
  • コンポーネントの合成:静的サブコンポーネント(例: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 変数に明示的な型エイリアスを定義する
  • デフォルトの props のマージには useProps フックを使用する
  • CSS カスタムプロパティには varsResolver を実装する
コンテキストパターン

状態の共有が必要なコンポーネントには、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,
});
ライフサイクルフック

更新時に実行するエフェクトのパターンには、@mantine/hooks の useDidUpdate を使用します。

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

useDidUpdate(() => {
  props.onStateChange?.(state);
}, [state]);
props の検証

型システムだけでは不十分な場合、実行時に props を検証します。

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
  • モジュール:Node の解決方式を使用する ESNext
  • JSX:React(クラシックランタイム)
  • Strict:有効(mantine の設定による)
  • Skip Lib Check:true(ビルドを高速化するため)

ビルド設定(tsconfig.build.json)では、コンパイル対象を分離します。

テスト

Testing Library と @mantine-tests/core のレンダラーを使用します。

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,
};

コンフィギュレーターのデモでは、controls オブジェクトを指定した MantineDemo 型の 'configurator' を使用します。

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 パターン
  • <div> とロールの組み合わせより、<button> や <nav> などのセマンティック HTML 要素を使用します
  • セマンティック HTML では不十分な場合にのみ、ARIA 属性を追加します
  • MDN の ARIA プラクティスを参照してください:https://developer.mozilla.org/en-US/docs/Web/Accessibility/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 を参照)
  • CSS モジュールは import classes from './Component.module.css' のようにインポートします
  • クラス名の結合には getStyles ヘルパーを使用します
多態性コンポーネント
  • デフォルトのコンポーネントは div です。component プロパティで上書きできます
  • ref の型は、ファクトリー定義の defaultRef と一致する必要があります
  • プロパティはマージされます:BoxProps & ComponentProps & { component?: any }
Testing Library のクエリ
  • getByTestId よりも getByRole を優先します
  • グローバルなクエリには screen を、スコープを限定したクエリには container を使用します
  • 必要に応じて window.matchMedia をモックします(./jsdom.mocks.cjs を参照)
Storybook の統合
  • ストーリーはコンポーネントと同じ場所に Component.story.tsx として配置します
  • Meta と StoryObj 型を使用する CSF3 形式を使用します
  • ストーリーのドキュメントは 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/ を参照してください。

関連ファイル

コメント