Gutenberg Testimonial Block

Posted on: October 8th, 2024
By: Tadeo Martinez

Block.json

{
	"$schema": "https://schemas.wp.org/trunk/block.json",
	"apiVersion": 3,
	"name": "create-block/content-testimonials",
	"version": "0.1.0",
	"title": "Content Testimonials",
	"category": "widgets",
	"icon": "smiley",
	"description": "Example block scaffolded with Create Block tool.",
	"example": {},
	"supports": {
		"html": false
	},
	"attributes": {
		"section_style": {
			"type": "string",
			"default": ""
		},
		"section_class": {
			"type": "string",
			"default": ""
		},
		"section_id": {
			"type": "string",
			"default": ""
		},
		"section_image": {
			"type": "string",
			"default": null
		},
		"section_image_class": {
			"type": "string"
		},
		"section_image_style": {
			"type": "string"
		},
		"section_block": {
			"type": "string",
			"default": ""
		},
		"container_style": {
			"type": "string",
			"default": ""
		},
		"container_class": {
			"type": "string",
			"default": "container"
		},
		"container_id": {
			"type": "string",
			"default": ""
		},
		"row_style": {
			"type": "string",
			"default": ""
		},
		"row_class": {
			"type": "string",
			"default": "row justify-content-center testimonial-carousel owl-carousel owl-theme"
		},
		"row_id": {
			"type": "string",
			"default": ""
		},
		"col_style": {
			"type": "string",
			"default": "padding-bottom: 25px;"
		},
		"col_class": {
			"type": "string",
			"default": "col-lg-9 text-center"
		},
		"col_id": {
			"type": "string",
			"default": ""
		},
		"col_data_aos": {
			"type": "string",
			"default": "fade-up"
		},
		"col_data_aos_delay": {
			"type": "string",
			"default": ""
		},
		"col_data_aos_offset": {
			"type": "string",
			"default": ""
		},
		"testimonial_style": {
			"type": "string",
			"default": ""
		},
		"testimonial_class": {
			"type": "string",
			"default": "col-lg-10 testimonial-carousel owl-carousel owl-theme"
		},
		"testimonial_id": {
			"type": "string",
			"default": ""
		},
		"testimonial_data_aos": {
			"type": "string",
			"default": "fade-up"
		},
		"testimonial_data_aos_delay": {
			"type": "string",
			"default": ""
		},
		"testimonial_data_aos_offset": {
			"type": "string",
			"default": ""
		},
		"testimonials": {
			"type": "array",
			"items": {
				"type": "object"
			},
			"default": [
				{
					"col_class": "",
					"col_style": "",
					"col_id": "",
					"data_aos":"",
					"data_aos_delay": "",
					"name": "",
					"content": ""
				}
			]
		}
	},
	"textdomain": "content-testimonials",
	"editorScript": "file:./index.js",
	"editorStyle": "file:./index.css",
	"style": "file:./style-index.css",
	"viewScript": "file:./view.js"
}

Edit.js

/**
 * Retrieves the translation of text.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
 */
import { __ } from '@wordpress/i18n';

/**
 * React hook that is used to mark the block wrapper element.
 * It provides all the necessary props like the class name.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
 */
import {
	InspectorControls,
	useBlockProps,
	InnerBlocks,
	MediaUpload,
	MediaUploadCheck,
	RichText,
} from '@wordpress/block-editor';
import {
	Button,
	PanelBody,
	__experimentalInputControl as InputControl,
	TextControl,
} from '@wordpress/components';
import { useState, useEffect } from '@wordpress/element';
// import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';


/**
 * Lets webpack process CSS, SASS or SCSS files referenced in JavaScript files.
 * Those files can contain any CSS code that gets applied to the editor.
 *
 * @see https://www.npmjs.com/package/@wordpress/scripts#using-css
 */
import './editor.scss';

/**
 * The edit function describes the structure of your block in the context of the
 * editor. This represents what the editor will render when the block is used.
 *
 * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
 *
 * @return {Element} Element to render.
 */
export default function Edit( { attributes, setAttributes } ) {
	const {
		section_style,
		section_class,
		section_id,
		section_image,
		section_image_class,
		section_image_style,
		section_block,
		container_style,
		container_class,
		container_id,
		row_style,
		row_class,
		row_id,
		col_style,
		col_class,
		col_id,
		col_data_aos,
		col_data_aos_delay,
		col_data_aos_offset,
		testimonial_style,
		testimonial_class,
		testimonial_id,
		testimonial_data_aos,
		testimonial_data_aos_delay,
		testimonial_data_aos_offset,
		testimonials,
	} = attributes;

	const [ value, setValue ] = useState( '' );

	const addTestimonial = () => {
		setAttributes( {
			testimonials: [
				...testimonials,
				{
					col_class: '',
					col_style: '',
					col_id: '',
					data_aos: 'fade-up',
					data_aos_delay: '',
					img: '',
					alt:'',
					img_style:'',
					img_class:'',
					title: '',
					content: '',
					code_block: ''
				},
			],
		} );
	};

	// const updateTestimonial = ( testimonialIndex, field, value ) => {
	// 	setAttributes( {
	// 		testimonials: testimonials.map( ( testimonial, index ) => {
	// 			if ( index === testimonialIndex ) {
	// 				return {
	// 					...testimonial,
	// 					[ field ]: value,
	// 				};
	// 			}
	// 			return testimonial;
	// 		} ),
	// 	} );
	// };

	const updateTestimonial = (testimonialIndex, field, value) => {
		setAttributes({
			testimonials: testimonials.map((testimonial, index) => {
				if (index === testimonialIndex) {
					// Check if value is an object (for handling multiple fields like img and alt)
					if (typeof value === 'object' && value !== null) {
						return {
							...testimonial,
							// Spread the value object to update multiple fields
							...value,
						};
					}
	
					// Default case: single value for a single field
					return {
						...testimonial,
						[field]: value,
					};
				}
				return testimonial;
			}),
		});
	};
	

	return (
		<>
			<InspectorControls>
				<PanelBody title={ __( 'Section' ) } initialOpen={ false }>
					<InputControl
						label="Section Style"
						value={ section_style }
						onChange={ ( nextValue ) =>
							setAttributes( { section_style: nextValue } )
						}
					/>
					<InputControl
						label="Section Class"
						value={ section_class }
						onChange={ ( nextValue ) =>
							setAttributes( { section_class: nextValue } )
						}
					/>
					<InputControl
						label="Section ID"
						value={ section_id }
						onChange={ ( nextValue ) =>
							setAttributes( { section_id: nextValue } )
						}
					/>
				</PanelBody>
				<PanelBody
					title={ __( 'Background Image' ) }
					initialOpen={ false }
				>
					<MediaUploadCheck>
						<MediaUpload
							onSelect={ ( media ) =>
								setAttributes( { section_image: media.url } )
							}
							type="image"
							allowedTypes={ [ 'image' ] }
							value={ section_image }
							render={ ( { open } ) => (
								<div>
									{ section_image && (
										<Button
											isLink
											isDestructive
											onClick={ () =>
												setAttributes( {
													section_image: '',
												} )
											}
										>
											{ __( 'Remove Section Image' ) }
										</Button>
									) }
									<Button
										onClick={ open }
										icon="upload"
										className="editor-media-placeholder__button is-button is-default is-large"
									>
										{ __( 'Select Section Image' ) }
									</Button>
								</div>
							) }
						/>
					</MediaUploadCheck>

					<InputControl
						label="Background Image Class"
						value={ section_image_class }
						onChange={ ( nextValue ) =>
							setAttributes( { section_image_class: nextValue } )
						}
					/>
					<InputControl
						label="Background Image Style"
						value={ section_image_style }
						onChange={ ( nextValue ) =>
							setAttributes( { section_image_style: nextValue } )
						}
					/>
				</PanelBody>
				<PanelBody title={ __( 'Code Block' ) } initialOpen={ false }>
					<label style={ { lineHeight: '2' } }>Code Block</label>
					<textarea
						id="sectionStyleTextarea"
						value={ attributes.section_block }
						onChange={ ( event ) =>
							setAttributes( {
								section_block: event.target.value,
							} )
						}
						placeholder="Enter section block here"
						style={ { width: '100%', height: '100px' } }
					/>
				</PanelBody>
				<PanelBody title={ __( 'Container' ) } initialOpen={ false }>
					<InputControl
						label="Container Section Style"
						value={ container_style }
						onChange={ ( nextValue ) =>
							setAttributes( { container_style: nextValue } )
						}
					/>
					<InputControl
						label="Container Section Class"
						value={ container_class }
						onChange={ ( nextValue ) =>
							setAttributes( { container_class: nextValue } )
						}
					/>
					<InputControl
						label="Container Section ID"
						value={ container_id }
						onChange={ ( nextValue ) =>
							setAttributes( { container_id: nextValue } )
						}
					/>
				</PanelBody>
				<PanelBody title={ __( 'Row' ) } initialOpen={ false }>
					<InputControl
						label="Row Style"
						value={ row_style }
						onChange={ ( nextValue ) =>
							setAttributes( { row_style: nextValue } )
						}
					/>
					<InputControl
						label="Row Class"
						value={ row_class }
						onChange={ ( nextValue ) =>
							setAttributes( { row_class: nextValue } )
						}
					/>
					<InputControl
						label="Row ID"
						value={ row_id }
						onChange={ ( nextValue ) =>
							setAttributes( { row_id: nextValue } )
						}
					/>
				</PanelBody>
				
				<PanelBody
					title={ __( 'Testimonial Settings' ) }
					initialOpen={ false }
				>
					<InputControl
						label="Testimonial Style"
						value={ testimonial_style }
						onChange={ ( nextValue ) =>
							setAttributes( { testimonial_style: nextValue } )
						}
					/>
					<InputControl
						label="Testimonial Class"
						value={ testimonial_class }
						onChange={ ( nextValue ) =>
							setAttributes( { testimonial_class: nextValue } )
						}
					/>
					<InputControl
						label="Testimonial ID"
						value={ testimonial_id }
						onChange={ ( nextValue ) =>
							setAttributes( { testimonial_id: nextValue } )
						}
					/>
					<InputControl
						label="Testimonial Data AOS"
						value={ testimonial_data_aos }
						onChange={ ( nextValue ) =>
							setAttributes( { testimonial_data_aos: nextValue } )
						}
					/>
					<InputControl
						label="Testimonial Data AOS Delay"
						value={ testimonial_data_aos_delay }
						onChange={ ( nextValue ) =>
							setAttributes( { testimonial_data_aos_delay: nextValue } )
						}
					/>
					<InputControl
						label="Testimonial Data AOS Offset"
						value={ testimonial_data_aos_offset }
						onChange={ ( nextValue ) =>
							setAttributes( { testimonial_data_aos_offset: nextValue } )
						}
					/>
					<button onClick={ () => addTestimonial() }>
						Add New Testimonial
					</button>
				</PanelBody>
				<PanelBody title={ __( 'Column' ) } initialOpen={ false }>
					<InputControl
						label="Column Style"
						value={ col_style }
						onChange={ ( nextValue ) =>
							setAttributes( { col_style: nextValue } )
						}
					/>
					<InputControl
						label="Column Class"
						value={ col_class }
						onChange={ ( nextValue ) =>
							setAttributes( { col_class: nextValue } )
						}
					/>
					<InputControl
						label="Column ID"
						value={ col_id }
						onChange={ ( nextValue ) =>
							setAttributes( { col_id: nextValue } )
						}
					/>
					<InputControl
						label="Column Data AOS"
						value={ col_data_aos }
						onChange={ ( nextValue ) =>
							setAttributes( { col_data_aos: nextValue } )
						}
					/>
					<InputControl
						label="Column Data AOS Delay"
						value={ col_data_aos_delay }
						onChange={ ( nextValue ) =>
							setAttributes( { col_data_aos_delay: nextValue } )
						}
					/>
					<InputControl
						label="Column Data AOS Offset"
						value={ col_data_aos_offset }
						onChange={ ( nextValue ) =>
							setAttributes( { col_data_aos_offset: nextValue } )
						}
					/>
				</PanelBody>
			</InspectorControls>
			<section { ...useBlockProps() }>
				<img src={ section_image } alt="" />

<p style={{fontSize:'24px'}}>Inner Blocks Below</p>
<InnerBlocks />
{console.log('hello')}

<p style={{fontSize:'24px'}}>Testimonials Below</p>
				<div className="column-wrapper">
					{ testimonials.map( ( testimonial, index ) => {
						return (
							<div
								className={ `column ${ testimonial.col_class }` }
								style={ {
									padding: '25px',
									borderBottom: '1px solid',
									marginBottom: '25px',
								} }
							>
								<div style={{display:'flex'}}>
								<div style={{paddingRight:'25px'}}>
								<p style={ { marginBottom: '0px' } }>
									Testimonial Class
								</p>
								<input
									type="text"
									value={ testimonial.col_class }
									onChange={ ( content ) =>
										updateTestimonial(
											index,
											'col_class',
											content.target.value
										)
									}
								/>
								</div>
								<div style={{paddingRight:'25px'}}>
								<p style={ { marginBottom: '0px' } }>
									Testimonial Style
								</p>
								<input
									type="text"
									value={ testimonial.col_style }
									onChange={ ( content ) =>
										updateTestimonial(
											index,
											'col_style',
											content.target.value
										)
									}
								/>
								</div>
								<div>
								<p style={ { marginBottom: '0px' } }>
									Testimonial ID
								</p>
								<input
									type="text"
									value={ testimonial.col_id }
									onChange={ ( content ) =>
										updateTestimonial(
											index,
											'col_id',
											content.target.value
										)
									}
								/>
								</div>
								</div>
								<div style={{display:'flex'}}>
								<div style={{paddingRight:'25px'}}>
								<p style={ { marginBottom: '0px' } }>
									Data AOS
								</p>
								<input
									type="text"
									value={ testimonial.data_aos }
									onChange={ ( content ) =>
										updateTestimonial(
											index,
											'data_aos',
											content.target.value
										)
									}
								/>
								</div>
								<div style={{paddingRight:'25px'}}>
								<p style={ { marginBottom: '0px' } }>
									Data AOS Delay
								</p>
								<input
									type="text"
									value={ testimonial.data_aos_delay }
									onChange={ ( content ) =>
										updateTestimonial(
											index,
											'data_aos_delay',
											content.target.value
										)
									}
								/>
								</div>
								</div>
								<div style={{display:'flex',justifyContent:'space-between',marginTop:'25px'}}>
								<div style={{width:'49%'}}>
								<img 
									src={testimonial.img}
									style={{width:'200px',height:'auto'}}
								/>

<MediaUploadCheck>
    <MediaUpload
        onSelect={(media) =>
            updateTestimonial(index, 'img', { img: media.url, alt: media.alt })
        }
        type="image"
        allowedTypes={['image']}
        value={testimonial.img}
        render={({ open }) => (
            <div>
				{testimonial.img && (
				<p className={``} style={{fontSize:'80%',lineHeight:'1.2'}}>{__('Alt Text:')} {testimonial.alt}</p>
            )}
                {testimonial.img && (
                    <Button
                        isLink
                        isDestructive
                        onClick={() => updateTestimonial(index, 'img', '')}
                    >
                        {__('Remove Col Image')}
                    </Button>
                )}
                <Button
                    onClick={open}
                    icon="upload"
                    className="editor-media-placeholder__button is-button is-default is-large"
                >
                    {__('Select Col Image')}
                </Button>
            </div>
        )}
    />
</MediaUploadCheck>


					<div style={{display:'flex'}}>
								<div style={{paddingRight:'25px',width:'49%'}}>
								<p style={ { marginBottom: '0px' } }>Img Class</p>
								<input
									type="text"
									style={{}}
									value={ testimonial.img_class }
									onChange={ ( content ) =>
										updateTestimonial(
											index,
											'img_class',
											content.target.value
										)
									}
								/>
								
								</div>
								<div style={{width:'49%'}}>
								<p style={ { marginBottom: '0px' } }>Img Style</p>
								<input
									type="text"
									style={{}}
									value={ testimonial.img_style }
									onChange={ ( content ) =>
										updateTestimonial(
											index,
											'img_style',
											content.target.value
										)
									}
								/>
								
								</div>
								</div> {/** end of display flex */}

					</div>
								<div style={{width:'49%'}}>
								<p>Code Block</p>
								<textarea 
								value={ testimonial.code_block }
								onChange={ ( content ) =>
									updateTestimonial( index, 'code_block', content.target.value )
								}
								style={{width:'100%',height:'200px'}}
								placeholder={ __( 'Code Block' ) }
								/>
								</div>
								</div>

								<div
									style={ {
										display: 'flex',
										paddingTop: '25px',
									} }
								>
									<div style={ {  } }>
										<p style={{marginBottom:'0px'}}><strong>Testimonial Content</strong></p>
										<RichText
											value={ testimonial.content }
											onChange={ ( content ) =>
												updateTestimonial(
													index,
													'content',
													content
												)
											}
											placeholder={ __( '' ) }
										/>
										<p style={{marginBottom:'0px'}}><strong>Testimonial Name</strong></p>
										<RichText
											value={ testimonial.title }
											onChange={ ( content ) =>
												updateTestimonial(
													index,
													'title',
													content
												)
											}
											placeholder={ __( '' ) }
										/>
									</div>
								</div>
<Button
    style={{border:'1px solid'}}
    onClick={() => {
        const newTestimonials = [...testimonials]; // Create a copy of the columns array
        const newColumn = { // Define a new column object
            col_class: '',
            col_style: '',
            col_id: '',
			data_aos: 'fade-up',
			data_aos_delay: '',
			img: '',
			alt:'',
			img_style:'',
			img_class:'',
			title: '',
			content: '',
			code_block: ''
        };
        newTestimonials.splice(index, 0, newColumn); // Insert the new column at the current index
        setAttributes({ testimonials: newTestimonials }); // Update the columns attribute with the new array
    }}
>
    {__('Add Testimonial Above')}
</Button>
<Button
    style={{border:'1px solid'}}
    onClick={() => {
        const newTestimonials = [...testimonials]; // Create a copy of the columns array
        const newColumn = { // Define a new column object
            col_class: '',
            col_style: '',
            col_id: '',
			data_aos: 'fade-up',
			data_aos_delay: '',
			img: '',
			alt:'',
			img_style:'',
			img_class:'',
			title: '',
			content: '',
			code_block: ''
        };
        newTestimonials.splice(index + 1, 0, newColumn); // Insert the new column at the current index
        setAttributes({ testimonials: newTestimonials }); // Update the columns attribute with the new array
    }}
>
    {__('Add Testimonial Below')}
</Button>
{/* Duplicate Button */}
<Button
style={{ border: '1px solid', marginTop: '10px' }}
onClick={() => {
const newTestimonials = [...testimonials];
const duplicateFeature = { ...testimonial }; // Copy the testimonial object
newTestimonials.splice(index + 1, 0, duplicateFeature); // Insert the copy after the current testimonial
setAttributes({ testimonials: newTestimonials });
}}
>
{__('Duplicate Feature')}
</Button>
<Button
style={{border:'1px solid'}}
isDestructive
onClick={() => {
const newTestimonials = [...testimonials];
newTestimonials.splice(index, 1);
setAttributes({ testimonials: newTestimonials });
}}
>
{__('Remove Testimonial')}
</Button>
{/* Move Up Button */}
<Button
style={{ border: '1px solid', marginTop: '10px' }}
onClick={() => {
	if (index === 0) return; // Prevent moving the first item up
	const newTestimonials = [...testimonials];
	const temp = newTestimonials[index - 1];
	newTestimonials[index - 1] = newTestimonials[index];
	newTestimonials[index] = temp;
	setAttributes({ testimonials: newTestimonials });
}}
disabled={index === 0} // Disable if it's the first item
>
{__('Move Up')}
</Button>

{/* Move Down Button */}
<Button
style={{ border: '1px solid', marginTop: '10px' }}
onClick={() => {
	if (index === testimonials.length - 1) return; // Prevent moving the last item down
	const newTestimonials = [...testimonials];
	const temp = newTestimonials[index + 1];
	newTestimonials[index + 1] = newTestimonials[index];
	newTestimonials[index] = temp;
	setAttributes({ testimonials: newTestimonials });
}}
disabled={index === testimonials.length - 1} // Disable if it's the last item
>
{__('Move Down')}
</Button>
							</div>
						);
					} ) }
				</div>
			</section>
		</>
	);
}

Save.js

/**

* React hook that is used to mark the block wrapper element.

* It provides all the necessary props like the class name.

*

* @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops

*/

import { useBlockProps, InnerBlocks, RichText } from ‘@wordpress/block-editor’;

import { RawHTML } from ‘@wordpress/element’;

/**

* The save function defines the way in which the different attributes should

* be combined into the final markup, which is then serialized by the block

* editor into `post_content`.

*

* @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save

*

* @return {Element} Element to render.

*/

export default function save( { attributes } ) {

const blockProps = useBlockProps.save();

return (

<div { …blockProps }>

<section

className={ `position-relative ${ attributes.section_class }` }

style={ `padding:50px 0;${ attributes.section_style }` }

id={ attributes.section_id }

>

{/* <div className=”column-wrapper”> */}

{ attributes.section_image && (

<img

src={ attributes.section_image }

alt=””

className={ `w-100 h-100 position-absolute bg-img ${ attributes.section_image_class }` }

style={ `top:0;left:0;object-fit:cover;pointer-events:none;${ attributes.section_image_style }` }

/>

) }

<RawHTML>{ attributes.section_block }</RawHTML>

<div

className={ attributes.container_class }

style={ attributes.container_style }

id={ attributes.container_id }

>

<div

className={ attributes.row_class }

style={ attributes.row_style }

id={ attributes.row_id }

>

<div className={attributes.col_class} style={attributes.col_style} id={attributes.col_id} data-aos={attributes.col_data_aos} data-aos-delay={attributes.col_data_aos_delay} data-aos-offset={attributes.col_data_aos_delay_offset}>

<InnerBlocks.Content />

</div>

<div className={attributes.testimonial_class} style={attributes.testimonial_style} id={attributes.testimonial_id} data-aos={attributes.testimonial_data_aos} data-aos-delay={attributes.testimonial_data_aos_delay} data-aos-offset={attributes.testimonial_data_aos_delay_offset}>

{

attributes.testimonials.map((testimonial, index) => {

return (

<div className={`position-relative text-center ${testimonial.col_class}`} style={`${testimonial.col_style}`}>

<div className={“} data-aos={testimonial.data_aos} data-aos-delay={testimonial.data_aos_delay}>

<div className={“}>

<RawHTML>{testimonial.code_block}</RawHTML>

{ testimonial.img && (

<div className={”} style={{display: ‘inline-block’}}>

<img

src={ testimonial.img }

alt={ testimonial.alt }

className={testimonial.img_class}

style={`border-radius: 50%;height: 100px;width: 100px;object-fit: cover;object-position: top;${testimonial.img_style}`}

/>

</div>

)}

<div className={`position-relative`} style={{paddingLeft:’25px’}}>

<img src=”https://resources.latinowebstudio.com/wp-content/uploads/2024/10/Quotes-Gray.png” className={`position-absolute`} style=”object-fit:contain;top:5px;left:0px;width:auto;” width=”auto” height=”15px” alt=”quotes for testimonials” />

<p style={{ margin: ‘0px’ }} className={`text-left`}><RichText.Content value={testimonial.content} /></p>

<img src=”https://resources.latinowebstudio.com/wp-content/uploads/2024/10/Five-Stars.png” style=”width:75px;margin:5px auto;” alt=”happy customers” />

</div>

</div>

<p className={`bold`} style={{ cursor: ‘pointer’ }}><RichText.Content value={testimonial.title} /></p>

</div>

</div>

);

})

}

</div> {/* end of testimonial */}

</div>

</div>

</section>

</div>

);

}

Style.scss

/**
 * The following styles get applied both on the front of your site
 * and in the editor.
 *
 * Replace them with your own styles or remove the file completely.
 */

.wp-block-create-block-content-testimonials {
	@media only screen and (min-width:991px) {
		button.owl-prev,
		button.owl-next {
			position: absolute;
			top: 20%;
		}
		button.owl-prev {
			left:-4%;
		}
		button.owl-next {
			right:-4%;
		}
	}
	.owl-nav img {
		width: 35px;
	}
    p {
        color: #acacac;
    }
}

// Import CSS files from owl-carousel directory
// @import 'owl-carousel/owl.carousel.min.css';
// .testimonial-carousel {
//     background:red;
// }
// @import './owl-carousel/owl-carousel.css';
// @import 'owl-carousel/owl-carousel';
// @import 'owl-carousel/owl.carousel.min.css';
// @import 'owl-carousel/owl.theme.default.min.css';

// .testimonial-carousel {
//     background:red;
// }

Have any questions or comments? Write them below!


Leave a Reply

Your email address will not be published. Required fields are marked *