Create Product Block
The CreateProduct Component is a fully customizable and accessible product creation form built with React and TypeScript. It provides a complete interface for product registration with image upload, category selection, location information, and modern design patterns.
π Installationβ
npm install @nodeblocks/frontend-create-product-block@0.5.0
π Usageβ
import {CreateProduct} from '@nodeblocks/frontend-create-product-block';
- Basic Usage
- Advanced Usage
function BasicCreateProduct() { const categoryOptions = [ {value: 'electronics', label: 'Electronics'}, {value: 'clothing', label: 'Clothing'}, {value: 'home', label: 'Home & Garden'}, ]; return ( <CreateProduct categoryOptions={categoryOptions} onSubmit={formData => { console.log('Product created:', formData); }} onChange={(_setError, getValues) => { const values = getValues(); console.log('Form values:', values); }} onAcceptAttachment={files => { console.log('File accepted:', files); }} onClearAttachment={() => { console.log('Attachment cleared'); }} > <CreateProduct.MainInfo> <CreateProduct.MainInfo.SectionTitle>Product Information</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>Create Product</CreateProduct.Actions.SubmitButton> </CreateProduct.Actions> </CreateProduct> ); }
function AdvancedCreateProduct() { const [currentStep, setCurrentStep] = useState(1); const [selectedTags, setSelectedTags] = useState(['Featured']); const categoryOptions = [ {value: 'electronics', label: 'Electronics'}, {value: 'clothing', label: 'Clothing'}, {value: 'home', label: 'Home & Garden'}, {value: 'sports', label: 'Sports & Outdoor'}, ]; const availableTags = ['Featured', 'Sale', 'New Arrival', 'Limited', 'Bestseller']; 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('Product with tags:', {...formData, tags: selectedTags}); }} onChange={(_setError, getValues) => { const values = getValues(); if (values.name) setCurrentStep(2); if (values.categoryId) setCurrentStep(3); }} onAcceptAttachment={files => console.log('Files:', files)} onClearAttachment={() => console.log('Cleared')} 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="Basic Info" isActive={currentStep === 1} isComplete={currentStep > 1} /> <div style={{flex: 1, height: '2px', background: currentStep > 1 ? '#10b981' : '#e5e7eb', margin: '0 12px'}} /> <StepIndicator step={2} label="Details" isActive={currentStep === 2} isComplete={currentStep > 2} /> <div style={{flex: 1, height: '2px', background: currentStep > 2 ? '#10b981' : '#e5e7eb', margin: '0 12px'}} /> <StepIndicator step={3} label="Shipping" 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', }} > Step 1 </span> <h3 style={{fontSize: '16px', fontWeight: '600', color: '#1f2937', margin: 0}}>Product Basics</h3> </div> <div style={{display: 'grid', gridTemplateColumns: '180px 1fr', gap: '20px'}}> <div> <p style={{fontSize: '12px', color: '#6b7280', marginBottom: '8px'}}>Product Image</p> <CreateProduct.MainInfo.Dropzone beforeUploadMessage="Upload" beforeUploadSubtitle="PNG, JPG up to 2MB" /> </div> <div style={{display: 'flex', flexDirection: 'column', gap: '12px'}}> <CreateProduct.MainInfo.NameField label="Product Name" placeholder="Enter product name..." /> <CreateProduct.TextField name="sku" label="SKU Code" 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'}}> Product Tags </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> Tags: <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', }} > Save Draft </button> <CreateProduct.Actions.SubmitButton sx={{borderRadius: '8px', px: 3, background: '#4f46e5', '&:hover': {background: '#4338ca'}}} > Publish Product </CreateProduct.Actions.SubmitButton> </div> </div> ), }, blockOrder: ['stepHeader', 'mainInfo', 'tagsSection', 'basicInfo', 'additionalInfo', 'actions'], })} </CreateProduct> ); }
Example with Complete Form Override:
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: 'Handmade Crafts' }, { value: 'vintage', label: 'Vintage Items' }, { value: 'art', label: 'Art & Collectibles' } ]; 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('Product:', { ...formData, priceType, visibility, features }); }} onChange={(setError, getValues, clearErrors) => console.log(getValues())} onAcceptAttachment={(files) => console.log('Files:', files)} onClearAttachment={() => console.log('Cleared')} sx={{ background: 'linear-gradient(180deg, #fefce8 0%, #fef9c3 100%)', borderRadius: '24px', border: '1px solid #fde047', overflow: 'hidden' }} > {({ defaultBlocks, defaultBlockOrder }) => ({ blocks: { // Hero header with illustration 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' }}> Create Your Listing </h1> <p style={{ fontSize: '14px', color: '#b45309', margin: 0 }}> Showcase your product to thousands of buyers </p> </div> ), // Photo and title card 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="Add Photos" beforeUploadSubtitle="Drag or click" /> </div> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: '12px' }}> <CreateProduct.MainInfo.NameField label="Product Title" placeholder="Give your product a catchy name" /> <CreateProduct.TextField name="brand" label="Brand (optional)" placeholder="Nike, Apple, Handmade..." /> </div> </div> </div> </div> ), // Pricing options card 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' }}> Pricing Type </h3> <div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}> <PriceOption type="fixed" label="Fixed Price" desc="Set your price" selected={priceType === 'fixed'} /> <PriceOption type="auction" label="Auction" desc="Let buyers bid" selected={priceType === 'auction'} /> <PriceOption type="offer" label="Best Offer" desc="Accept offers" selected={priceType === 'offer'} /> </div> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}> <CreateProduct.TextField name="price" label={priceType === 'auction' ? 'Starting Bid' : 'Price'} placeholder="$0.00" /> {priceType === 'offer' && ( <CreateProduct.TextField name="minOffer" label="Minimum Offer" placeholder="$0.00" /> )} </div> </div> </div> ), // Details section 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' }}> Product Details </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="Condition" placeholder="Select condition" options={[ { value: 'new', label: 'Brand New' }, { value: 'like_new', label: 'Like New' }, { value: 'good', label: 'Good' }, { value: 'fair', label: 'Fair' } ]} /> </div> <CreateProduct.BasicInfo.DescriptionField placeholder="Describe your product in detail. Include dimensions, materials, condition notes..." /> </div> </div> </div> ), // Features toggles 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' }}> Seller Guarantees </h3> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '12px' }}> <FeatureToggle label="Warranty" icon="π‘οΈ" checked={features.warranty} onChange={() => setFeatures(f => ({ ...f, warranty: !f.warranty }))} /> <FeatureToggle label="Easy Returns" icon="β©οΈ" checked={features.returns} onChange={() => setFeatures(f => ({ ...f, returns: !f.returns }))} /> <FeatureToggle label="Free Shipping" icon="π¦" checked={features.freeShipping} onChange={() => setFeatures(f => ({ ...f, freeShipping: !f.freeShipping }))} /> </div> </div> </div> ), // Location 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' }}> Shipping From </h3> <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}> <CreateProduct.AdditionalInfo.Address1Field /> <CreateProduct.AdditionalInfo.Address2Field /> </div> </div> </div> ), // Visibility selector 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' }}> Listing Visibility </h3> <div style={{ display: 'flex', gap: '12px' }}> {[ { id: 'public', icon: 'π', label: 'Public', desc: 'Anyone can see' }, { id: 'followers', icon: 'π₯', label: 'Followers', desc: 'Followers only' }, { id: 'private', icon: 'π', label: 'Private', desc: 'Link only' } ].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> ), // Submit actions 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' }}> Preview </button> <button type="button" style={{ padding: '12px 24px', border: '1px solid #d1d5db', borderRadius: '12px', background: 'white', color: '#374151', fontSize: '14px', fontWeight: '500', cursor: 'pointer' }}> Save Draft </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%)' } }} > Publish Listing </CreateProduct.Actions.SubmitButton> </div> ) }, blockOrder: ['hero', 'mainInfo', 'pricingCard', 'basicInfo', 'featuresSection', 'additionalInfo', 'visibilitySection', 'actions'] })} </CreateProduct> ); }
π§ Props Referenceβ
Main Component Propsβ
| Prop | Type | Default | Description |
|---|---|---|---|
onSubmit | (data: T, event?: React.BaseSyntheticEvent) => void | Required | Callback function triggered when form is submitted with valid data |
onChange | (setError, getValues, clearErrors) => void | Required | Callback function triggered when form values change |
categoryOptions | Array<{value: string, label: string}> | undefined | Array of category options for the category select field |
onAcceptAttachment | (files: File[]) => void | undefined | Callback fired when file is accepted for upload |
onRejectAttachment | (file: File, message: string) => void | undefined | Callback fired when file is rejected with error message |
onClearAttachment | () => void | undefined | Callback fired when attachment is cleared |
defaultData | DefaultValues<T> | undefined | Default form values to populate fields on initial render |
data | T | undefined | Controlled form values - use this for external form state management |
className | string | undefined | Additional CSS class name for styling the form container |
children | BlocksOverride | undefined | Custom block components to override default rendering |
Note: The main component extends MUI StackProps and renders as a form element using component="form". Default styling includes spacing={4}, direction="column", and p: 3 padding.
Sub-Componentsβ
The CreateProduct component provides several sub-components. All sub-components receive their default values from the main component's context and can override these values through props.
CreateProduct.MainInfoβ
Container for main product information including image upload and product name.
| Prop | Type | Default | Description |
|---|---|---|---|
children | BlocksOverride | undefined | Custom content to override default main info fields |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props. Default styling includes spacing={4}.
CreateProduct.MainInfo.SectionTitleβ
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | "Create a Product" | Title content for the main info section |
variant | TypographyProps['variant'] | "h6" | MUI Typography variant |
component | ElementType | "h3" | HTML element to render |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Typography props.
CreateProduct.MainInfo.Dropzoneβ
| Prop | Type | Default | Description |
|---|---|---|---|
isDraggingMessage | string | "Drop the image here..." | Message displayed while dragging |
beforeUploadMessage | string | "Upload product image" | Message displayed before file upload |
beforeUploadSubtitle | string | "PNG, JPG up to 2MB" | Subtitle displayed before file upload |
afterUploadOptionsButton | string | "Options" | Text for options button after upload |
replaceFileButton | string | "Select a new file" | Text for replace file button |
deleteFileButton | string | "Delete" | Text for delete file button |
required | boolean | undefined | Whether file upload is required |
maxSize | number | 2000000 | Maximum file size in bytes (2MB) |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Box props and react-dropzone props. Accepts image/* files only, single file upload.
CreateProduct.MainInfo.NameFieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | "name" | Field name used for form data and validation |
label | string | "Name" | Field label displayed above the input |
placeholder | string | "Enter product name" | Placeholder text |
required | boolean | true | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props.
CreateProduct.BasicInfoβ
Container for basic product information including category and description.
| Prop | Type | Default | Description |
|---|---|---|---|
children | BlocksOverride | undefined | Custom content to override default basic info fields |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props. Default styling includes spacing={4}.
CreateProduct.BasicInfo.Titleβ
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | "Basic Information" | Title content for the basic info section |
variant | TypographyProps['variant'] | "h6" | MUI Typography variant |
component | ElementType | "h3" | HTML element to render |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Typography props.
CreateProduct.BasicInfo.CategoryFieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | "categoryId" | Field name for form data |
label | string | "Categories" | Field label |
placeholder | string | "Select product category" | Placeholder text |
options | Array<{value: string, label: string}> | From context | Array of category options |
required | boolean | true | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props (select mode). Options are provided via categoryOptions prop on main component.
CreateProduct.BasicInfo.DescriptionFieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | "description" | Field name for form data |
label | string | "Description" | Label of the input field |
placeholder | string | "Describe the product" | Placeholder text |
inputType | string | "multiline" | Input type (multiline for textarea) |
required | boolean | true | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props. Renders as multiline textarea with 4 rows.
CreateProduct.AdditionalInfoβ
Container for additional product information including location details.
| Prop | Type | Default | Description |
|---|---|---|---|
children | BlocksOverride | undefined | Custom content to override default additional info fields |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props. Default styling includes spacing={4}.
CreateProduct.AdditionalInfo.Titleβ
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | "Additional Infomation" | Title content for the additional info section |
variant | TypographyProps['variant'] | "h6" | MUI Typography variant |
component | ElementType | "h3" | HTML element to render |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Typography props.
CreateProduct.AdditionalInfo.Subtitleβ
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | "Location" | Subtitle content for the location section |
variant | TypographyProps['variant'] | "body1" | MUI Typography variant |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Typography props. Default styling includes fontWeight: 'bold'.
CreateProduct.AdditionalInfo.Address1Fieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | "address1" | Field name for form data |
label | string | "Prefecture" | Field label |
placeholder | string | "Select prefecture" | Placeholder text |
options | Array<{value: string, label: string}> | prefectures | Array of prefecture options (Japanese prefectures) |
required | boolean | true | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props (select mode).
CreateProduct.AdditionalInfo.Address2Fieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | string | "address2" | Field name used for form data and validation |
label | string | "Address" | Field label displayed above the input |
placeholder | string | "Enter location address" | Placeholder text |
required | boolean | true | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props.
CreateProduct.Actionsβ
Container for form action buttons.
| Prop | Type | Default | Description |
|---|---|---|---|
children | BlocksOverride | undefined | Custom content to override default action buttons |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Stack props. Default styling includes direction="row", justifyContent: 'center', alignItems: 'center'.
CreateProduct.Actions.SubmitButtonβ
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | "Submit" | Text to place inside the button |
variant | ButtonProps['variant'] | "contained" | MUI Button variant |
size | ButtonProps['size'] | "large" | MUI Button size |
type | "submit" | "button" | "reset" | "submit" | HTML button type |
disabled | boolean | !formState.isValid | Disabled when form is invalid |
startIcon | ReactNode | <TaskAlt /> | Icon displayed before button text |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI Button props. Automatically disabled when form validation fails.
Additional Field Componentsβ
The component also provides additional field types that can be used in custom forms:
CreateProduct.TextFieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | Path<T> | Required | Field name for form data |
label | string | undefined | Label of the input field |
placeholder | string | undefined | Placeholder text |
required | boolean | undefined | Whether the field is required |
inputType | "multiline" | string | undefined | Input type (use "multiline" for textarea) |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props. Uses react-hook-form Controller for form integration.
CreateProduct.TimeFieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | Path<T> | Required | Field name for form data |
label | string | undefined | Label of the input field |
required | boolean | undefined | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props with type="time". Uses react-hook-form Controller for form integration.
CreateProduct.CheckboxFieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | Path<T> | Required | Field name for form data |
label | ReactNode | Required | Checkbox label |
required | boolean | undefined | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI FormControlLabel props. Uses react-hook-form Controller for form integration.
CreateProduct.SelectFieldβ
| Prop | Type | Default | Description |
|---|---|---|---|
name | Path<T> | Required | Field name for form data |
label | string | undefined | Field label |
placeholder | string | undefined | Placeholder text when no option selected |
options | Array<{value: string, label: string}> | undefined | Array of options |
required | boolean | undefined | Whether the field is required |
className | string | undefined | Additional CSS class name for styling |
Note: This component inherits MUI TextField props (select mode). Uses react-hook-form Controller for form integration.
π¨ Configuration examplesβ
Custom Section Title Stylingβ
<CreateProduct.MainInfo.SectionTitle
variant="h5"
sx={{
color: 'primary.main',
fontWeight: 700,
borderBottom: '2px solid',
borderColor: 'primary.main',
pb: 1,
}}
>
Create Your Product
</CreateProduct.MainInfo.SectionTitle>
Custom Dropzone Stylingβ
<CreateProduct.MainInfo.Dropzone
beforeUploadMessage="Drop your product image here"
beforeUploadSubtitle="Supports PNG, JPG up to 2MB"
sx={{
borderColor: 'primary.main',
bgcolor: 'primary.50',
'&:hover': {bgcolor: 'primary.100'},
}}
/>
Custom Field Stylingβ
<CreateProduct.TextField
name="customField"
label="Custom Field"
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: '12px',
},
}}
/>
Using Block Override Patternβ
<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 Supportβ
Full TypeScript support with comprehensive type definitions:
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;
}
// Complete typed example
function TypedCreateProductExample() {
const categoryOptions: CategoryOption[] = [
{value: 'electronics', label: 'Electronics'},
{value: 'clothing', label: 'Clothing'},
{value: 'home', label: 'Home & Garden'},
];
const handleSubmit = (data: CustomProductFormData, event?: React.BaseSyntheticEvent): void => {
console.log('Product data:', data);
console.log('Event:', event);
};
const handleChange = (
setError: UseFormSetError<CustomProductFormData>,
getValues: UseFormGetValues<CustomProductFormData>,
clearErrors: UseFormClearErrors<CustomProductFormData>,
): void => {
const values = getValues();
// Validate form values and set errors if needed
if (!values.name) {
setError('name', {message: 'Product name is required'});
} else {
clearErrors('name');
}
};
return (
<CreateProduct<CustomProductFormData>
onSubmit={handleSubmit}
onChange={handleChange}
categoryOptions={categoryOptions}
onAcceptAttachment={files => console.log('Files accepted:', files)}
onRejectAttachment={(file, message) => console.log('File rejected:', file, message)}
onClearAttachment={() => console.log('Attachment cleared')}
>
<CreateProduct.MainInfo>
<CreateProduct.MainInfo.SectionTitle>Product Details</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>Create Product</CreateProduct.Actions.SubmitButton>
</CreateProduct.Actions>
</CreateProduct>
);
}
π Notesβ
- The root component uses MUI's
Stackwithcomponent="form"to create a semantic form element - Form state management is handled by
react-hook-formwithmode: 'onBlur'for validation - The
onChangecallback is triggered whenever form values change, providingsetError,getValues, andclearErrorsfunctions SubmitButtonis automatically disabled whenformState.isValidisfalse- Default block order:
mainInfo,basicInfo,additionalInfo,actions - All sub-components support the MUI
sxprop for inline styling - Form fields use
react-hook-formController for form state integration - The component supports generic typing for custom form data interfaces (must extend
DefaultCreateProductFormData) - CSS classes follow BEM naming:
nbb-create-product,nbb-create-product-main-info, etc. - Image dropzone uses
react-dropzonewith 2MB max file size andimage/*accept filter - Prefecture options for Address1Field are pre-populated with Japanese prefectures
- The
dataprop can be used for controlled form state, whiledefaultDatasets initial values
Built with β€οΈ using React, TypeScript, and MUI.