商品作成ブロック
CreateProductコンポーネントは、ReactとTypeScriptで構築された完全にカスタマイズ可能でアクセシブルな商品作成フォームです。画像アップロード、カテゴリ選択、位置情報、モダンなデザインパターンを備えた商品登録のための完全なインターフェースを提供します。
🚀 インストール
npm install @nodeblocks/frontend-create-product-block@0.5.0
📖 使用法
import {CreateProduct} from '@nodeblocks/frontend-create-product-block';
- 基本的な使用法
- 高度な使用法
function BasicCreateProduct() { const categoryOptions = [ {value: 'electronics', label: '電子機器'}, {value: 'clothing', label: '衣類'}, {value: 'home', label: 'ホーム・ガーデン'}, ]; return ( <CreateProduct categoryOptions={categoryOptions} onSubmit={formData => { console.log('商品が作成されました:', formData); }} onChange={(_setError, getValues) => { const values = getValues(); console.log('フォーム値:', values); }} onAcceptAttachment={files => { console.log('ファイルが承認されました:', files); }} onClearAttachment={() => { console.log('添付ファイルがクリアされました'); }} > <CreateProduct.MainInfo> <CreateProduct.MainInfo.SectionTitle>商品情報</CreateProduct.MainInfo.SectionTitle> <CreateProduct.MainInfo.Dropzone /> <CreateProduct.MainInfo.NameField /> </CreateProduct.MainInfo> <CreateProduct.BasicInfo> <CreateProduct.BasicInfo.Title /> <CreateProduct.BasicInfo.CategoryField /> <CreateProduct.BasicInfo.DescriptionField /> </CreateProduct.BasicInfo> <CreateProduct.AdditionalInfo> <CreateProduct.AdditionalInfo.Title /> <CreateProduct.AdditionalInfo.Subtitle /> <CreateProduct.AdditionalInfo.Address1Field /> <CreateProduct.AdditionalInfo.Address2Field /> </CreateProduct.AdditionalInfo> <CreateProduct.Actions> <CreateProduct.Actions.SubmitButton>商品を作成</CreateProduct.Actions.SubmitButton> </CreateProduct.Actions> </CreateProduct> ); }
function AdvancedCreateProduct() { const [currentStep, setCurrentStep] = useState(1); const [selectedTags, setSelectedTags] = useState(['注目']); const categoryOptions = [ {value: 'electronics', label: '電子機器'}, {value: 'clothing', label: '衣類'}, {value: 'home', label: 'ホーム・ガーデン'}, {value: 'sports', label: 'スポーツ・アウトドア'}, ]; const availableTags = ['注目', 'セール', '新着', '限定', 'ベストセラー']; const StepIndicator = ({step, label, isActive, isComplete}) => ( <div style={{ display: 'flex', alignItems: 'center', gap: '8px', opacity: isActive || isComplete ? 1 : 0.4, }} > <div style={{ width: '28px', height: '28px', borderRadius: '50%', background: isComplete ? '#10b981' : isActive ? '#6366f1' : '#e5e7eb', color: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', fontWeight: '600', }} > {isComplete ? '✓' : step} </div> <span style={{ fontSize: '13px', fontWeight: isActive ? '600' : '400', color: isActive ? '#4f46e5' : '#6b7280', }} > {label} </span> </div> ); const TagChip = ({label, selected, onClick}) => ( <button type="button" onClick={onClick} style={{ padding: '6px 14px', borderRadius: '20px', border: selected ? '2px solid #8b5cf6' : '1px solid #d1d5db', background: selected ? '#ede9fe' : 'white', color: selected ? '#6d28d9' : '#4b5563', fontSize: '12px', fontWeight: '500', cursor: 'pointer', transition: 'all 0.15s ease', }} > {selected && '✓ '} {label} </button> ); return ( <CreateProduct categoryOptions={categoryOptions} onSubmit={formData => { console.log('タグ付き商品:', {...formData, tags: selectedTags}); }} onChange={(_setError, getValues) => { const values = getValues(); if (values.name) setCurrentStep(2); if (values.categoryId) setCurrentStep(3); }} onAcceptAttachment={files => console.log('ファイル:', files)} onClearAttachment={() => console.log('クリア')} sx={{ background: 'linear-gradient(180deg, #fafafa 0%, #f5f5f5 100%)', borderRadius: '20px', border: '1px solid #e5e7eb', overflow: 'hidden', }} > {({defaultBlocks}) => ({ blocks: { ...defaultBlocks, stepHeader: ( <div style={{ background: 'white', padding: '20px 24px', borderBottom: '1px solid #e5e7eb', display: 'flex', justifyContent: 'space-between', alignItems: 'center', }} > <StepIndicator step={1} label="基本情報" isActive={currentStep === 1} isComplete={currentStep > 1} /> <div style={{flex: 1, height: '2px', background: currentStep > 1 ? '#10b981' : '#e5e7eb', margin: '0 12px'}} /> <StepIndicator step={2} label="詳細" isActive={currentStep === 2} isComplete={currentStep > 2} /> <div style={{flex: 1, height: '2px', background: currentStep > 2 ? '#10b981' : '#e5e7eb', margin: '0 12px'}} /> <StepIndicator step={3} label="配送" isActive={currentStep === 3} isComplete={currentStep > 3} /> </div> ), mainInfo: ( <div style={{ padding: '24px', background: 'white', margin: '16px', borderRadius: '12px', border: '1px solid #e5e7eb', }} > <div style={{display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '16px'}}> <span style={{ background: '#dbeafe', color: '#1d4ed8', padding: '4px 10px', borderRadius: '6px', fontSize: '12px', fontWeight: '600', }} > ステップ 1 </span> <h3 style={{fontSize: '16px', fontWeight: '600', color: '#1f2937', margin: 0}}>商品基本</h3> </div> <div style={{display: 'grid', gridTemplateColumns: '180px 1fr', gap: '20px'}}> <div> <p style={{fontSize: '12px', color: '#6b7280', marginBottom: '8px'}}>商品画像</p> <CreateProduct.MainInfo.Dropzone beforeUploadMessage="アップロード" beforeUploadSubtitle="PNG, JPG 最大2MB" /> </div> <div style={{display: 'flex', flexDirection: 'column', gap: '12px'}}> <CreateProduct.MainInfo.NameField label="商品名" placeholder="商品名を入力..." /> <CreateProduct.TextField name="sku" label="SKUコード" placeholder="ABC-12345" /> </div> </div> </div> ), tagsSection: ( <div style={{ padding: '24px', background: 'white', margin: '0 16px', borderRadius: '12px', border: '1px solid #e5e7eb', }} > <h4 style={{fontSize: '14px', fontWeight: '600', color: '#374151', marginBottom: '12px'}}> 商品タグ </h4> <div style={{display: 'flex', flexWrap: 'wrap', gap: '8px'}}> {availableTags.map(tag => ( <TagChip key={tag} label={tag} selected={selectedTags.includes(tag)} onClick={() => setSelectedTags(prev => (prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag])) } /> ))} </div> </div> ), actions: ( <div style={{ padding: '20px 24px', background: 'white', borderTop: '1px solid #e5e7eb', display: 'flex', justifyContent: 'space-between', alignItems: 'center', }} > <div style={{fontSize: '13px', color: '#6b7280'}}> {selectedTags.length > 0 && ( <span> タグ: <strong style={{color: '#4f46e5'}}>{selectedTags.join(', ')}</strong> </span> )} </div> <div style={{display: 'flex', gap: '10px'}}> <button type="button" style={{ padding: '10px 20px', border: '1px solid #d1d5db', borderRadius: '8px', background: 'white', color: '#374151', fontSize: '14px', cursor: 'pointer', }} > 下書き保存 </button> <CreateProduct.Actions.SubmitButton sx={{borderRadius: '8px', px: 3, background: '#4f46e5', '&:hover': {background: '#4338ca'}}} > 商品を公開 </CreateProduct.Actions.SubmitButton> </div> </div> ), }, blockOrder: ['stepHeader', 'mainInfo', 'tagsSection', 'basicInfo', 'additionalInfo', 'actions'], })} </CreateProduct> ); }
完全なフォームオーバーライドの例:
function CustomCreateProduct() { const [priceType, setPriceType] = useState('fixed'); const [visibility, setVisibility] = useState('public'); const [features, setFeatures] = useState({ warranty: false, returns: true, freeShipping: false }); const categoryOptions = [ {value: 'handmade', label: 'ハンドメイドクラフト'}, {value: 'vintage', label: 'ヴィンテージアイテム'}, {value: 'art', label: 'アート・コレクティブル'}, ]; const FeatureToggle = ({label, checked, onChange, icon}) => ( <label style={{ display: 'flex', alignItems: 'center', gap: '10px', padding: '12px 16px', borderRadius: '10px', border: checked ? '2px solid #10b981' : '1px solid #e5e7eb', background: checked ? '#ecfdf5' : 'white', cursor: 'pointer', transition: 'all 0.15s ease' }}> <input type="checkbox" checked={checked} onChange={onChange} style={{ display: 'none' }} /> <span style={{ fontSize: '18px' }}>{icon}</span> <span style={{ fontSize: '13px', fontWeight: '500', color: checked ? '#059669' : '#6b7280' }}>{label}</span> <span style={{ marginLeft: 'auto', width: '20px', height: '20px', borderRadius: '50%', background: checked ? '#10b981' : '#e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'white', fontSize: '12px' }}> {checked ? '✓' : ''} </span> </label> ); const PriceOption = ({type, label, desc, selected}) => ( <button type="button" onClick={() => setPriceType(type)} style={{ flex: 1, padding: '16px', borderRadius: '12px', border: selected ? '2px solid #8b5cf6' : '1px solid #e5e7eb', background: selected ? 'linear-gradient(135deg, #f5f3ff 0%, #ede9fe 100%)' : 'white', cursor: 'pointer', textAlign: 'center' }} > <div style={{ fontSize: '14px', fontWeight: '600', color: selected ? '#6d28d9' : '#374151' }}>{label}</div> <div style={{ fontSize: '11px', color: '#9ca3af', marginTop: '2px' }}>{desc}</div> </button> ); return ( <CreateProduct categoryOptions={categoryOptions} onSubmit={formData => { console.log('商品:', {...formData, priceType, visibility, features}); }} onChange={(setError, getValues, clearErrors) => console.log(getValues())} onAcceptAttachment={files => console.log('ファイル:', files)} onClearAttachment={() => console.log('クリア')} sx={{ background: 'linear-gradient(180deg, #fefce8 0%, #fef9c3 100%)', borderRadius: '24px', border: '1px solid #fde047', overflow: 'hidden', }} > {({defaultBlocks, defaultBlockOrder}) => ({ blocks: { // イラスト付きヒーローヘッダー hero: ( <div style={{ background: 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)', padding: '32px', textAlign: 'center', borderBottom: '1px solid #fcd34d', }} > <div style={{fontSize: '48px', marginBottom: '8px'}}>🛍️</div> <h1 style={{fontSize: '24px', fontWeight: '700', color: '#92400e', margin: '0 0 4px 0'}}> リスティングを作成 </h1> <p style={{fontSize: '14px', color: '#b45309', margin: 0}}> あなたの商品を数千人のバイヤーにアピール </p> </div> ), // 写真とタイトルカード mainInfo: ( <div style={{ padding: '24px' }}> <div style={{ background: 'white', borderRadius: '16px', padding: '20px', boxShadow: '0 4px 12px rgba(0,0,0,0.05)', }} > <div style={{display: 'flex', gap: '20px'}}> <div style={{width: '160px'}}> <CreateProduct.MainInfo.Dropzone beforeUploadMessage="写真を追加" beforeUploadSubtitle="ドラッグまたはクリック" /> </div> <div style={{flex: 1, display: 'flex', flexDirection: 'column', gap: '12px'}}> <CreateProduct.MainInfo.NameField label="商品タイトル" placeholder="キャッチーな商品名をつけましょう" /> <CreateProduct.TextField name="brand" label="ブランド(任意)" placeholder="Nike, Apple, ハンドメイド..." /> </div> </div> </div> </div> ), // 価格オプションカード pricingCard: ( <div style={{ padding: '0 24px' }}> <div style={{ background: 'white', borderRadius: '16px', padding: '20px', boxShadow: '0 4px 12px rgba(0,0,0,0.05)', }} > <h3 style={{fontSize: '15px', fontWeight: '600', color: '#374151', marginBottom: '12px'}}> 価格タイプ </h3> <div style={{display: 'flex', gap: '12px', marginBottom: '16px'}}> <PriceOption type="fixed" label="固定価格" desc="価格を設定" selected={priceType === 'fixed'} /> <PriceOption type="auction" label="オークション" desc="入札を受付" selected={priceType === 'auction'} /> <PriceOption type="offer" label="ベストオファー" desc="オファーを受付" selected={priceType === 'offer'} /> </div> <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px'}}> <CreateProduct.TextField name="price" label={priceType === 'auction' ? '開始価格' : '価格'} placeholder="¥0" /> {priceType === 'offer' && ( <CreateProduct.TextField name="minOffer" label="最低オファー" placeholder="¥0" /> )} </div> </div> </div> ), // 詳細セクション basicInfo: ( <div style={{ padding: '24px' }}> <div style={{ background: 'white', borderRadius: '16px', padding: '20px', boxShadow: '0 4px 12px rgba(0,0,0,0.05)', }} > <h3 style={{fontSize: '15px', fontWeight: '600', color: '#374151', marginBottom: '12px'}}> 商品詳細 </h3> <div style={{display: 'flex', flexDirection: 'column', gap: '12px'}}> <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px'}}> <CreateProduct.BasicInfo.CategoryField /> <CreateProduct.SelectField name="condition" label="状態" placeholder="状態を選択" options={[ {value: 'new', label: '新品'}, {value: 'like_new', label: 'ほぼ新品'}, {value: 'good', label: '良好'}, {value: 'fair', label: '普通'}, ]} /> </div> <CreateProduct.BasicInfo.DescriptionField placeholder="商品の詳細を説明してください。サイズ、素材、状態のメモなど..." /> </div> </div> </div> ), // 機能トグル featuresSection: ( <div style={{ padding: '0 24px' }}> <div style={{ background: 'white', borderRadius: '16px', padding: '20px', boxShadow: '0 4px 12px rgba(0,0,0,0.05)', }} > <h3 style={{fontSize: '15px', fontWeight: '600', color: '#374151', marginBottom: '12px'}}> セラー保証 </h3> <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '12px'}}> <FeatureToggle label="保証" icon="🛡️" checked={features.warranty} onChange={() => setFeatures(f => ({...f, warranty: !f.warranty}))} /> <FeatureToggle label="返品対応" icon="↩️" checked={features.returns} onChange={() => setFeatures(f => ({...f, returns: !f.returns}))} /> <FeatureToggle label="送料無料" icon="📦" checked={features.freeShipping} onChange={() => setFeatures(f => ({...f, freeShipping: !f.freeShipping}))} /> </div> </div> </div> ), // 位置 additionalInfo: ( <div style={{ padding: '24px' }}> <div style={{ background: 'white', borderRadius: '16px', padding: '20px', boxShadow: '0 4px 12px rgba(0,0,0,0.05)', }} > <h3 style={{fontSize: '15px', fontWeight: '600', color: '#374151', marginBottom: '12px'}}> 発送元 </h3> <div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px'}}> <CreateProduct.AdditionalInfo.Address1Field /> <CreateProduct.AdditionalInfo.Address2Field /> </div> </div> </div> ), // 公開設定セレクター visibilitySection: ( <div style={{ padding: '0 24px' }}> <div style={{ background: 'white', borderRadius: '16px', padding: '20px', boxShadow: '0 4px 12px rgba(0,0,0,0.05)', }} > <h3 style={{fontSize: '15px', fontWeight: '600', color: '#374151', marginBottom: '12px'}}> リスティングの公開設定 </h3> <div style={{display: 'flex', gap: '12px'}}> {[ {id: 'public', icon: '🌍', label: '公開', desc: '誰でも閲覧可能'}, {id: 'followers', icon: '👥', label: 'フォロワー', desc: 'フォロワーのみ'}, {id: 'private', icon: '🔒', label: '非公開', desc: 'リンクのみ'}, ].map(opt => ( <button key={opt.id} type="button" onClick={() => setVisibility(opt.id)} style={{ flex: 1, padding: '14px', borderRadius: '12px', border: visibility === opt.id ? '2px solid #f59e0b' : '1px solid #e5e7eb', background: visibility === opt.id ? '#fffbeb' : 'white', cursor: 'pointer', textAlign: 'center', }} > <div style={{fontSize: '24px', marginBottom: '4px'}}>{opt.icon}</div> <div style={{fontSize: '13px', fontWeight: '600', color: '#374151'}}>{opt.label}</div> <div style={{fontSize: '11px', color: '#9ca3af'}}>{opt.desc}</div> </button> ))} </div> </div> </div> ), // 送信アクション actions: ( <div style={{ padding: '24px', display: 'flex', gap: '12px', justifyContent: 'flex-end', }} > <button type="button" style={{ padding: '12px 24px', border: '1px solid #d1d5db', borderRadius: '12px', background: 'white', color: '#6b7280', fontSize: '14px', fontWeight: '500', cursor: 'pointer', }} > プレビュー </button> <button type="button" style={{ padding: '12px 24px', border: '1px solid #d1d5db', borderRadius: '12px', background: 'white', color: '#374151', fontSize: '14px', fontWeight: '500', cursor: 'pointer', }} > 下書き保存 </button> <CreateProduct.Actions.SubmitButton sx={{ background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', borderRadius: '12px', px: 4, fontWeight: 600, boxShadow: '0 4px 12px rgba(245,158,11,0.3)', '&:hover': {background: 'linear-gradient(135deg, #d97706 0%, #b45309 100%)'}, }} > リスティングを公開 </CreateProduct.Actions.SubmitButton> </div> ), }, blockOrder: ['hero', 'mainInfo', 'pricingCard', 'basicInfo', 'featuresSection', 'additionalInfo', 'visibilitySection', 'actions'], })} </CreateProduct> ); }
🔧 プロパティリファレンス
メインコンポーネントのプロパティ
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
onSubmit | (data: T, event?: React.BaseSyntheticEvent) => void | 必須 | 有効なデータでフォームが送信されたときにトリガーされるコールバック関数 |
onChange | (setError, getValues, clearErrors) => void | 必須 | フォーム値が変更されたときにトリガーされるコールバック関数 |
categoryOptions | Array<{value: string, label: string}> | undefined | カテゴリ選択フィールド用のカテゴリオプションの配列 |
onAcceptAttachment | (files: File[]) => void | undefined | ファイルがアップロード用に承認されたときに発生するコールバック |
onRejectAttachment | (file: File, message: string) => void | undefined | ファイルがエラーメッセージ付きで拒否されたときに発生するコールバック |
onClearAttachment | () => void | undefined | 添付ファイルがクリアされたときに発生するコールバック |
defaultData | DefaultValues<T> | undefined | 初期レンダリング時にフィールドに設定するデフォルトフォーム値 |
data | T | undefined | 制御されたフォーム値 - 外部フォーム状態管理に使用 |
className | string | undefined | フォームコンテナスタイリング用の追加CSSクラス名 |
children | BlocksOverride | undefined | デフォルトレンダリングをオーバーライドするカスタムブロックコンポーネント |
注意: メインコンポーネントはMUI StackProps を拡張し、component="form" を使用してフォーム要素としてレンダリングされます。デフォルトスタイリングには spacing={4}、direction="column"、p: 3 のパディングが含まれます。
サブコンポーネント
CreateProductコンポーネントは複数のサブコンポーネントを提供します。すべてのサブコンポーネントは、メインコンポーネントのコンテキストからデフォルト値を受け取り、プロパティを通じてこれらの値をオーバーライドできます。
CreateProduct.MainInfo
画像アップロードと商品名を含むメイン商品情報のコンテナ。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | BlocksOverride | undefined | デフォルトメイン情報フィールドをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。デフォルトスタイリングには spacing={4} が含まれます。
CreateProduct.MainInfo.SectionTitle
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "Create a Product" | メイン情報セクションのタイトルコンテンツ |
variant | TypographyProps['variant'] | "h6" | MUI Typographyバリアント |
component | ElementType | "h3" | レンダリングするHTML要素 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。
CreateProduct.MainInfo.Dropzone
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
isDraggingMessage | string | "Drop the image here..." | ドラッグ中に表示されるメッセージ |
beforeUploadMessage | string | "Upload product image" | ファイルアップロード前に表示されるメッセージ |
beforeUploadSubtitle | string | "PNG, JPG up to 2MB" | ファイルアップロード前に表示されるサブタイトル |
afterUploadOptionsButton | string | "Options" | アップロード後のオプションボタンのテキスト |
replaceFileButton | string | "Select a new file" | ファイル置換ボタンのテキスト |
deleteFileButton | string | "Delete" | ファイル削除ボタンのテキスト |
required | boolean | undefined | ファイルアップロードが必須かどうか |
maxSize | number | 2000000 | 最大ファイルサイズ(バイト単位、2MB) |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Box プロパティと react-dropzone プロパティを継承します。image/* ファイルのみ受け付け、単一ファイルアップロード。
CreateProduct.MainInfo.NameField
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "name" | フォームデータとバリデーションに使用されるフィールド名 |
label | string | "Name" | 入力の上に表示されるフィールドラベル |
placeholder | string | "Enter product name" | プレースホルダーテキスト |
required | boolean | true | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI TextField プロパティを継承します。
CreateProduct.BasicInfo
カテゴリと説明を含む基本商品情報のコンテナ。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | BlocksOverride | undefined | デフォルト基本情報フィールドをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。デフォルトスタイリングには spacing={4} が含まれます。
CreateProduct.BasicInfo.Title
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "Basic Information" | 基本情報セクションのタイトルコンテンツ |
variant | TypographyProps['variant'] | "h6" | MUI Typographyバリアント |
component | ElementType | "h3" | レンダリングするHTML要素 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。
CreateProduct.BasicInfo.CategoryField
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "categoryId" | フォームデータのフィールド名 |
label | string | "Categories" | フィールドラベル |
placeholder | string | "Select product category" | プレースホルダーテキスト |
options | Array<{value: string, label: string}> | コンテキストから | カテゴリオプションの配列 |
required | boolean | true | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI TextField プロパティを継承します(selectモード)。オプションはメインコンポーネントの categoryOptions プロパティ経由で提供されます。
CreateProduct.BasicInfo.DescriptionField
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "description" | フォームデータのフィールド名 |
label | string | "Description" | 入力フィールドのラベル |
placeholder | string | "Describe the product" | プレースホルダーテキスト |
inputType | string | "multiline" | 入力タイプ(テキストエリアにはmultiline) |
required | boolean | true | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI TextField プロパティを継承します。4行のマルチラインテキストエリアとしてレンダリングされます。
CreateProduct.AdditionalInfo
位置詳細を含む追加商品情報のコンテナ。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | BlocksOverride | undefined | デフォルト追加情報フィールドをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。デフォルトスタイリングには spacing={4} が含まれます。
CreateProduct.AdditionalInfo.Title
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "Additional Infomation" | 追加情報セクションのタイトルコンテンツ |
variant | TypographyProps['variant'] | "h6" | MUI Typographyバリアント |
component | ElementType | "h3" | レンダリングするHTML要素 |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。
CreateProduct.AdditionalInfo.Subtitle
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "Location" | 位置セクションのサブタイトルコンテンツ |
variant | TypographyProps['variant'] | "body1" | MUI Typographyバリアント |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Typography プロパティを継承します。デフォルトスタイリングには fontWeight: 'bold' が含まれます。
CreateProduct.AdditionalInfo.Address1Field
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "address1" | フォームデータのフィールド名 |
label | string | "Prefecture" | フィールドラベル |
placeholder | string | "Select prefecture" | プレースホルダーテキスト |
options | Array<{value: string, label: string}> | prefectures | 都道府県オプションの配列(日本の都道府県) |
required | boolean | true | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI TextField プロパティを継承します(selectモード)。
CreateProduct.AdditionalInfo.Address2Field
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | string | "address2" | フォームデータとバリデーションに使用されるフィールド名 |
label | string | "Address" | 入力の上に表示されるフィールドラベル |
placeholder | string | "Enter location address" | プレースホルダーテキスト |
required | boolean | true | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI TextField プロパティを継承します。
CreateProduct.Actions
フォームアクションボタンのコンテナ。
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | BlocksOverride | undefined | デフォルトアクションボタンをオーバーライドするカスタムコンテンツ |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Stack プロパティを継承します。デフォルトスタイリングには direction="row"、justifyContent: 'center'、alignItems: 'center' が含まれます。
CreateProduct.Actions.SubmitButton
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
children | ReactNode | "Submit" | ボタン内に配置するテキスト |
variant | ButtonProps['variant'] | "contained" | MUI Buttonバリアント |
size | ButtonProps['size'] | "large" | MUI Buttonサイズ |
type | "submit" | "button" | "reset" | "submit" | HTMLボタンタイプ |
disabled | boolean | !formState.isValid | フォームが無効なときに無効化 |
startIcon | ReactNode | <TaskAlt /> | ボタンテキストの前に表示されるアイコン |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI Button プロパティを継承します。フォームバリデーションが失敗すると自動的に無効化されます。
追加フィールドコンポーネント
コンポーネントは、カスタムフォームで使用できる追加のフィールドタイプも提供します:
CreateProduct.TextField
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | Path<T> | 必須 | フォームデータのフィールド名 |
label | string | undefined | 入力フィールドのラベル |
placeholder | string | undefined | プレースホルダーテキスト |
required | boolean | undefined | フィールドが必須かどうか |
inputType | "multiline" | string | undefined | 入力タイプ(テキストエリアには "multiline" を使用) |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI TextField プロパティを継承します。フォーム統合に react-hook-form Controllerを使用します。
CreateProduct.TimeField
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | Path<T> | 必須 | フォームデータのフィールド名 |
label | string | undefined | 入力フィールドのラベル |
required | boolean | undefined | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントは type="time" を持つMUI TextField プロパティを継承します。フォーム統合に react-hook-form Controllerを使用します。
CreateProduct.CheckboxField
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | Path<T> | 必須 | フォームデータのフィールド名 |
label | ReactNode | 必須 | チェックボックスラベル |
required | boolean | undefined | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI FormControlLabel プロパティを継承します。フォーム統合に react-hook-form Controllerを使用します。
CreateProduct.SelectField
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
name | Path<T> | 必須 | フォームデータのフィールド名 |
label | string | undefined | フィールドラベル |
placeholder | string | undefined | オプションが選択されていないときのプレースホルダーテキスト |
options | Array<{value: string, label: string}> | undefined | オプションの配列 |
required | boolean | undefined | フィールドが必須かどうか |
className | string | undefined | スタイリング用の追加CSSクラス名 |
注意: このコンポーネントはMUI TextField プロパティを継承します(selectモード)。フォーム統合に react-hook-form Controllerを使用します。
🎨 設定例
カスタムセクションタイトルスタイリング
<CreateProduct.MainInfo.SectionTitle
variant="h5"
sx={{
color: 'primary.main',
fontWeight: 700,
borderBottom: '2px solid',
borderColor: 'primary.main',
pb: 1,
}}
>
商品を作成
</CreateProduct.MainInfo.SectionTitle>
カスタムドロップゾーンスタイリング
<CreateProduct.MainInfo.Dropzone
beforeUploadMessage="商品画像をドロップ"
beforeUploadSubtitle="PNG, JPG 最大2MBをサポート"
sx={{
borderColor: 'primary.main',
bgcolor: 'primary.50',
'&:hover': {bgcolor: 'primary.100'},
}}
/>
カスタムフィールドスタイリング
<CreateProduct.TextField
name="customField"
label="カスタムフィールド"
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: '12px',
},
}}
/>
ブロックオーバーライドパターンの使用
<CreateProduct {...props}>
{({defaultBlocks, defaultBlockOrder}) => ({
blocks: {
...defaultBlocks,
mainInfo: {
...defaultBlocks.mainInfo,
props: {
...defaultBlocks.mainInfo.props,
sx: {
background: 'linear-gradient(135deg, #0f172a 0%, #1e293b 100%)',
borderRadius: '14px',
p: 3,
},
},
},
},
blockOrder: defaultBlockOrder,
})}
</CreateProduct>
🔧 TypeScriptサポート
包括的な型定義による完全なTypeScriptサポート:
import {CreateProduct} from '@nodeblocks/frontend-create-product-block';
import {UseFormClearErrors, UseFormGetValues, UseFormSetError} from 'react-hook-form';
interface CustomProductFormData extends Record<string, unknown> {
name: string;
description: string;
address1: string;
address2: string;
categoryId: string;
image: {url: string; type?: string; id?: string} | File;
customField?: string;
price?: string;
stock?: string;
sku?: string;
}
interface CategoryOption {
value: string;
label: string;
}
// 完全な型付けの使用例
function TypedCreateProductExample() {
const categoryOptions: CategoryOption[] = [
{value: 'electronics', label: '電子機器'},
{value: 'clothing', label: '衣類'},
{value: 'home', label: 'ホーム・ガーデン'},
];
const handleSubmit = (data: CustomProductFormData, event?: React.BaseSyntheticEvent): void => {
console.log('商品データ:', data);
console.log('イベント:', event);
};
const handleChange = (
setError: UseFormSetError<CustomProductFormData>,
getValues: UseFormGetValues<CustomProductFormData>,
clearErrors: UseFormClearErrors<CustomProductFormData>,
): void => {
const values = getValues();
// フォーム値をバリデーションし、必要に応じてエラーを設定
if (!values.name) {
setError('name', {message: '商品名は必須です'});
} else {
clearErrors('name');
}
};
return (
<CreateProduct<CustomProductFormData>
onSubmit={handleSubmit}
onChange={handleChange}
categoryOptions={categoryOptions}
onAcceptAttachment={files => console.log('ファイルが承認されました:', files)}
onRejectAttachment={(file, message) => console.log('ファイルが拒否されました:', file, message)}
onClearAttachment={() => console.log('添付ファイルがクリアされました')}
>
<CreateProduct.MainInfo>
<CreateProduct.MainInfo.SectionTitle>商品詳細</CreateProduct.MainInfo.SectionTitle>
<CreateProduct.MainInfo.Dropzone />
<CreateProduct.MainInfo.NameField name="name" />
</CreateProduct.MainInfo>
<CreateProduct.BasicInfo>
<CreateProduct.BasicInfo.Title />
<CreateProduct.BasicInfo.CategoryField />
<CreateProduct.BasicInfo.DescriptionField />
</CreateProduct.BasicInfo>
<CreateProduct.AdditionalInfo>
<CreateProduct.AdditionalInfo.Title />
<CreateProduct.AdditionalInfo.Subtitle />
<CreateProduct.AdditionalInfo.Address1Field />
<CreateProduct.AdditionalInfo.Address2Field />
</CreateProduct.AdditionalInfo>
<CreateProduct.Actions>
<CreateProduct.Actions.SubmitButton>商品を作成</CreateProduct.Actions.SubmitButton>
</CreateProduct.Actions>
</CreateProduct>
);
}
📝 注意事項
- ルートコンポーネントは
component="form"を持つMUIのStackを使用してセマンティックなフォーム要素を作成します - フォーム状態管理はバリデーション用の
mode: 'onBlur'を持つreact-hook-formによって処理されます onChangeコールバックはフォーム値が変更されるたびにトリガーされ、setError、getValues、clearErrors関数を提供しますSubmitButtonはformState.isValidがfalseの場合、自動的に無効化されます- デフォルトブロック順序:
mainInfo、basicInfo、additionalInfo、actions - すべてのサブコンポーネントはインラインスタイリング用のMUI
sxプロパティをサポートします - フォームフィールドはフォーム状態統合に
react-hook-formControllerを使用します - コンポーネントはカスタムフォームデータインターフェース用のジェネリック型付けをサポートします(
DefaultCreateProductFormDataを拡張する必要があります) - CSSクラスはBEM命名規則に従います:
nbb-create-product、nbb-create-product-main-infoなど - 画像ドロップゾーンは最大ファイルサイズ2MBと
image/*acceptフィルターを持つreact-dropzoneを使用します - Address1Fieldの都道府県オプションは日本の都道府県で事前設定されています
dataプロパティは制御されたフォーム状態に使用でき、defaultDataは初期値を設定します
React、TypeScript、MUIを使用して❤️で構築されました。