mantine-react-component-dev について
mantine-react-component-dev は、Mantine の規約に沿った React コンポーネントの開発・保守を支援するスキルです。記載された monorepo 構成に基づき、コンポーネントパターン、スタイル、アクセシビリティ、テスト、ドキュメントを扱います。
主な機能
- 01Mantine のポリモーフィックファクトリパターン
- 02Styles API と CSS 変数
- 03コンテキストと複合コンポーネントのパターン
- 04制御状態と非制御状態の指針
- 05アクセシビリティとキーボード操作
- 06テストとドキュメントの規約
活用シーン
- 01Mantine の規約に沿った React コンポーネントの作成
- 02Styles API によるスタイルのカスタマイズ追加
- 03共有状態と複合コンポーネントの実装
- 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 に従ってインポートを自動で並べ替えます。
- CSS のインポート
- React
- Next.js(該当する場合)
- 組み込みモジュール
- サードパーティーモジュール
@mantine/*パッケージ- ローカルインポート(親、次に兄弟)
- 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/ を参照してください。