属性選択ブロック
AttributeSelectionコンポーネントは、ReactとTypeScriptで構築された完全にカスタマイズ可能でアクセシブルな属性選択フォームです。フォームバリデーション、エラーハンドリング、モダンなデザインパターンを備えた属性選択のための完全なインターフェースを提供します。
🚀 インストール
npm install @nodeblocks/frontend-attribute-selection-block@0.2.0
📖 使用法
import {AttributeSelection} from '@nodeblocks/frontend-attribute-selection-block';
- 基本的な使用法
- 高度な使用法
function BasicAttributeSelection() { const [submittedData, setSubmittedData] = useState(null); const handleSubmit = (data) => { console.log('フォームが送信されました:', data); setSubmittedData(data); }; const handleCancel = (reset) => { console.log('フォームがキャンセルされました'); reset(); setSubmittedData(null); }; const handleChange = (_setError, getValues) => { console.log('フォームが変更されました:', getValues()); }; return ( <div> <AttributeSelection onSubmit={handleSubmit} onCancel={handleCancel} onChange={handleChange} defaultValues={{admin: false, editor: true, viewer: false}} > <AttributeSelection.Title>ユーザーロールを選択</AttributeSelection.Title> <AttributeSelection.Subtitle>割り当てるロールを選択してください</AttributeSelection.Subtitle> <AttributeSelection.FieldName>ロール</AttributeSelection.FieldName> <AttributeSelection.Checkbox name="admin" label="管理者" value="admin" /> <AttributeSelection.Checkbox name="editor" label="編集者" value="editor" /> <AttributeSelection.Checkbox name="viewer" label="閲覧者" value="viewer" /> <AttributeSelection.SubmitButton>ロールを保存</AttributeSelection.SubmitButton> <AttributeSelection.CancelButton>キャンセル</AttributeSelection.CancelButton> </AttributeSelection> {submittedData && ( <div style={{marginTop: '16px', padding: '12px', background: '#f0f0f0', borderRadius: '8px'}}> <strong>送信されたデータ:</strong> <pre>{JSON.stringify(submittedData, null, 2)}</pre> </div> )} </div> ); }
interface SkillFormData { [key: string]: unknown; javascript?: boolean; typescript?: boolean; react?: boolean; nodejs?: boolean; python?: boolean; } function AdvancedAttributeSelection() { const [submittedData, setSubmittedData] = useState<SkillFormData | null>(null); const [formError, setFormError] = useState<string | null>(null); const handleSubmit = (data: SkillFormData) => { const selectedSkills = Object.entries(data).filter(([, value]) => value); if (selectedSkills.length < 2) { setFormError('少なくとも2つのスキルを選択してください'); return; } setFormError(null); setSubmittedData(data); console.log('スキルが保存されました:', data); }; const handleCancel = (reset) => { reset(); setSubmittedData(null); setFormError(null); }; const handleChange = (_setError, getValues) => { const values = getValues(); const selectedCount = Object.values(values).filter(Boolean).length; console.log('選択されたスキル数:', selectedCount); }; return ( <AttributeSelection onSubmit={handleSubmit} onCancel={handleCancel} onChange={handleChange} defaultValues={{ javascript: true, typescript: true, react: false, nodejs: false, python: false, }} sx={{ maxWidth: 500, mx: 'auto', p: 3, bgcolor: '#ffffff', borderRadius: 3, boxShadow: '0 4px 20px rgba(0,0,0,0.08)', }} > {({defaultBlocks}) => { return { blocks: { ...defaultBlocks, title: ( <div> <div style={{ padding: '16px', background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', borderRadius: '12px 12px 0 0', marginBottom: '16px', }} > <div style={{fontSize: '20px', fontWeight: '700', color: '#ffffff', marginBottom: '4px'}}> 開発者スキル評価 </div> <div style={{fontSize: '14px', color: 'rgba(255,255,255,0.85)'}}> 習熟しているすべてのスキルを選択してください(最低2つ) </div> </div> </div> ), subtitle: ( <AttributeSelection.Subtitle sx={{display: 'none'}}>スキルを選択</AttributeSelection.Subtitle> ), fieldName: ( <AttributeSelection.FieldName sx={{fontWeight: '600', color: '#374151', mb: 2}}> プログラミング言語 & フレームワーク </AttributeSelection.FieldName> ), // Each checkbox should be its own block with the name as the key javascript: ( <AttributeSelection.Checkbox name="javascript" label="JavaScript" value="javascript" sx={{ '& .MuiFormControlLabel-label': {fontWeight: '500'}, '& .Mui-checked': {color: '#6366f1'}, mb: 1, }} /> ), typescript: ( <AttributeSelection.Checkbox name="typescript" label="TypeScript" value="typescript" sx={{ '& .MuiFormControlLabel-label': {fontWeight: '500'}, '& .Mui-checked': {color: '#6366f1'}, mb: 1, }} /> ), react: ( <AttributeSelection.Checkbox name="react" label="React" value="react" sx={{ '& .MuiFormControlLabel-label': {fontWeight: '500'}, '& .Mui-checked': {color: '#6366f1'}, mb: 1, }} /> ), nodejs: ( <AttributeSelection.Checkbox name="nodejs" label="Node.js" value="nodejs" sx={{ '& .MuiFormControlLabel-label': {fontWeight: '500'}, '& .Mui-checked': {color: '#6366f1'}, mb: 1, }} /> ), python: ( <AttributeSelection.Checkbox name="python" label="Python" value="python" sx={{ '& .MuiFormControlLabel-label': {fontWeight: '500'}, '& .Mui-checked': {color: '#6366f1'}, mb: 1, }} /> ), submitButton: ( <AttributeSelection.SubmitButton sx={{ mt: 2, background: 'linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%)', '&:hover': { background: 'linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%)', }, }} > スキルを保存 </AttributeSelection.SubmitButton> ), cancelButton: ( <div> <AttributeSelection.CancelButton sx={{ mt: 1, color: '#6b7280', }} > リセット </AttributeSelection.CancelButton> <div style={{marginTop: '16px', textAlign: 'center'}}> {formError && ( <div style={{ padding: '8px 16px', background: '#fee2e2', color: '#dc2626', borderRadius: '8px', marginBottom: '12px', fontSize: '14px', }} > {formError} </div> )} {submittedData && ( <div style={{ padding: '8px 16px', background: '#dcfce7', color: '#16a34a', borderRadius: '8px', fontSize: '14px', }} > スキルが正常に保存されました! </div> )} </div> </div> ), }, // Update blockOrder to include individual checkbox blocks instead of 'checkbox' blockOrder: [ 'title', 'subtitle', 'fieldName', 'javascript', 'typescript', 'react', 'nodejs', 'python', 'submitButton', 'cancelButton', ], }; }} </AttributeSelection> ); }
🔧 プロパティリファレンス
メインコンポーネントのプロパティ
コンポーネントは2つの型パラメータを受け入れます: AttributeSelection<T, B> ここで:
T- フォームデータ型(インデックスシグネチャを持つRecord<string, unknown>を拡張する必要があります)B- ブロック型(シンプルな使用の場合はRecord<string, never>、ブロックオーバーライドの場合はRecord<string, ReactNode>)
| Prop | Type | Default | Description |
|---|---|---|---|
onSubmit | (data: T) => void | Required | Callback function triggered when form is submitted with valid data |
onChange | (setError: UseFormSetError<T>, getValues: UseFormGetValues<T>) => void | Required | Callback function triggered when form values change |
onCancel | (reset: UseFormReset<T>) => void | Required | Callback function triggered when cancel button is clicked |
defaultValues | DefaultValues<T> | undefined | Default form values to populate fields on initial render |
className | string | undefined | Additional CSS class name for styling the form container |
children | BlocksOverride | undefined | Custom blocks override for form components |
注意: メインコンポーネントはMUI StackProps を拡張し、component="form" を使用してフォーム要素としてレンダリングします。デフォルトスタイリングにはStackレイアウトが含まれます。
サブコンポーネント
AttributeSelectionコンポーネントは複数のサブコンポーネントを提供します。すべてのサブコンポーネントは、メインコンポーネントのコンテキストからデフォルト値を受け取り、プロパティを通じてこれらの値をオーバーライドできます。
AttributeSelection.Title
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "職種" | フォームヘッダーのタイトルテキストコンテンツ |
variant | TypographyProps['variant'] | "h4" | MUI Typographyバリアント |
component | ElementType | "h2" | レンダリングするHTML要素 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。デフォルトスタイリングには textAlign: 'center' が含まれます。
AttributeSelection.Subtitle
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "ご自身の職種を選択してください" | フォーム説明を提供するサブタイトルテキストコンテンツ |
variant | TypographyProps['variant'] | "body2" | MUI Typographyバリアント |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。デフォルトスタイリングには fontWeight: 600 が含まれます。
AttributeSelection.FieldName
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "役割" | フォーム入力の上に表示されるフィールド名テキストコンテンツ |
variant | TypographyProps['variant'] | "body2" | MUI Typographyバリアント |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。デフォルトスタイリングには fontWeight: 600 が含まれます。
AttributeSelection.Checkbox
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "" | フォームデータとバリデーションに使用されるフィールド名 |
label | string | "" | チェックボックスの横に表示されるラベルテキスト |
value | string | "" | チェックボックスオプションの値識別子 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI FormControlLabel プロパティを継承します。フォーム統合には react-hook-form Controllerを使用します。デフォルトスタイリングには角丸の境界線付きコンテナが含まれます。デフォルトのチェック状態は、メインコンポーネントの defaultValues プロパティで制御されます。
AttributeSelection.SubmitButton
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "次へ" | ボタンテキストコンテンツ |
variant | ButtonProps['variant'] | "contained" | MUI Buttonバリアント |
size | ButtonProps['size'] | "large" | MUI Buttonサイズ |
fullWidth | boolean | true | ボタンが全幅に広がるかどうか |
type | "submit" | "button" | "reset" | "submit" | HTMLボタンタイプ |
disabled | boolean | !formState.isValid | フォームが無効なときに無効化 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Button プロパティを継承します。フォームバリデーションが失敗すると自動的に無効化されます。
AttributeSelection.CancelButton
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "戻る" | ボタンテキストコンテンツ |
variant | ButtonProps['variant'] | "outlined" | MUI Buttonバリアント |
size | ButtonProps['size'] | "large" | MUI Buttonサイズ |
fullWidth | boolean | true | ボタンが全幅に広がるかどうか |
type | "submit" | "button" | "reset" | "button" | HTMLボタンタイプ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Button プロパティを継承します(onClick を除く)。クリック時に onCancel(reset) を呼び出します。
🎨 設定例
カスタムタイトルスタイリング
<AttributeSelection.Title
variant="h3"
sx={{
color: 'primary.main',
fontWeight: 700,
textAlign: 'left'
}}
/>
カスタムチェックボックススタイリング
<AttributeSelection.Checkbox
name="option1"
label="カスタムオプション"
value="option1"
sx={{
border: '2px solid #3b82f6',
borderRadius: '12px',
'&:hover': { backgroundColor: '#f0f9ff' }
}}
/>
カスタムボタンスタイリング
<AttributeSelection.SubmitButton
variant="contained"
sx={{
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
borderRadius: '12px',
py: 1.5
}}
>
続行
</AttributeSelection.SubmitButton>
ブロックオーバーライドパターンの使用
<AttributeSelection {...props}>
{({defaultBlocks, defaultBlockOrder}) => {
return {
blocks: {
...defaultBlocks,
customInfo: <Alert severity="info">少なくとも1つのオプションを選択してください</Alert>,
},
blockOrder: ['title', 'subtitle', 'customInfo', 'fieldName', 'checkbox', 'submitButton', 'cancelButton']
};
}}
</AttributeSelection>
🔧 TypeScriptサポート
包括的な型定義による完全なTypeScriptサポート:
import {AttributeSelection} from '@nodeblocks/frontend-attribute-selection-block';
import {StackProps} from '@mui/material';
import {DefaultValues, UseFormGetValues, UseFormReset, UseFormSetError} from 'react-hook-form';
import {ReactNode} from 'react';
// Form data interface must include index signature for react-hook-form compatibility
interface CustomAttributeFormData {
[key: string]: unknown;
role?: boolean;
department?: boolean;
experience?: boolean;
skills?: boolean;
}
// Main component props interface with two type parameters:
// T - Form data type
// B - Blocks type (Record<string, never> for simple usage, Record<string, ReactNode> for block override)
interface AttributeSelectionProps<T extends Record<string, unknown>, B extends Record<string, ReactNode>>
extends Omit<StackProps, 'onSubmit' | 'onChange' | 'children'> {
onSubmit: (data: T) => void;
onCancel: (reset: UseFormReset<T>) => void;
onChange: (setError: UseFormSetError<T>, getValues: UseFormGetValues<T>) => void;
defaultValues?: DefaultValues<T>;
children?: BlocksOverride;
}
// Type-safe submit handler
const handleSubmit = (formData: CustomAttributeFormData): void => {
console.log('フォームが送信されました:', formData.role, formData.department, formData.experience);
};
// Type-safe change handler with validation
const handleChange = (
_setError: UseFormSetError<CustomAttributeFormData>,
getValues: UseFormGetValues<CustomAttributeFormData>,
): void => {
const values = getValues();
const hasSelection = values.role || values.department || values.experience || values.skills;
if (!hasSelection) {
_setError('role', {message: '少なくとも1つのオプションを選択してください'});
}
};
// Type-safe cancel handler
const handleCancel = (reset: UseFormReset<CustomAttributeFormData>): void => {
reset();
console.log('フォームがキャンセルされました');
};
// Simple usage with two type parameters
const SimpleAttributeSelectionForm = () => {
return (
<AttributeSelection<CustomAttributeFormData, Record<string, never>>
onSubmit={handleSubmit}
onChange={handleChange}
onCancel={handleCancel}
defaultValues={{role: false, department: false, experience: false, skills: false}}
>
<AttributeSelection.Title>属性を選択</AttributeSelection.Title>
<AttributeSelection.Subtitle>該当するオプションを選択してください</AttributeSelection.Subtitle>
<AttributeSelection.FieldName>職業情報</AttributeSelection.FieldName>
<AttributeSelection.Checkbox name="role" label="管理職" value="role" />
<AttributeSelection.Checkbox name="department" label="技術部門" value="department" />
<AttributeSelection.Checkbox name="experience" label="5年以上の経験" value="experience" />
<AttributeSelection.Checkbox name="skills" label="リーダーシップスキル" value="skills" />
<AttributeSelection.SubmitButton>続行</AttributeSelection.SubmitButton>
<AttributeSelection.CancelButton>戻る</AttributeSelection.CancelButton>
</AttributeSelection>
);
};
// Block override pattern with ReactNode blocks type
const AdvancedAttributeSelectionForm = () => {
return (
<AttributeSelection<CustomAttributeFormData, Record<string, ReactNode>>
onSubmit={handleSubmit}
onChange={handleChange}
onCancel={handleCancel}
defaultValues={{role: false, department: false, experience: false, skills: false}}
>
{({defaultBlocks, defaultBlockOrder}) => {
return {
blocks: {
...defaultBlocks,
title: (
<AttributeSelection.Title sx={{color: 'primary.main'}}>
カスタムタイトル
</AttributeSelection.Title>
),
},
blockOrder: defaultBlockOrder,
};
}}
</AttributeSelection>
);
};
📝 注意事項
- ルートコンポーネントはMUIの
Stackをcomponent="form"で使用してセマンティックなフォーム要素を作成します - フォーム状態管理は
react-hook-formによって処理され、バリデーションにはmode: 'onBlur'が使用されます onChangeコールバックはフォーム値が変更されるたびにトリガーされ、リアルタイムバリデーションを可能にしますSubmitButtonはformState.isValidがfalseの場合に自動的に無効化されますCancelButtonはフォームのreset関数を引数としてonCancelコールバックを呼び出します- デフォルトブロック順序:
title,subtitle,fieldName,checkbox,submitButton,cancelButton - すべてのサブコンポーネントはインラインスタイリング用のMUI
sxプロパティをサポートします - チェックボックスはフォーム状態統合に
react-hook-formControllerを使用します - コンポーネントは2つのジェネリック型パラメータをサポートします:
<FormData, BlocksType>ここでFormDataはフォーム値を定義し、BlocksTypeはブロック構造を制御します - フォームデータインターフェースには、react-hook-form互換性のために
[key: string]: unknownインデックスシグネチャを含める必要があります - シンプルな使用の場合は
Record<string, never>を、ブロックオーバーライドパターンの場合はRecord<string, ReactNode>を2番目の型パラメータとして使用します - CSSクラスはBEM命名規則に従います:
nbb-attribute-selection,nbb-attribute-selection-titleなど
React、TypeScript、MUIを使用して❤️で構築されました。