I'm trying to create the below component using react and tailwind css:
The behavior I am trying to create is how to display the dropdown options on top of the next section items ("Add product images and description") instead of making a scrollbar to appear.
It is vertical 2 step process where the active tab expands, allowing users to fill details.
Each of the 2 steps are placed inside a react component called StepContainer.jsx whose code is mentioned below:
import { useState, useEffect } from 'react';
import { IoIosArrowDown } from 'react-icons/io';
function StepContainer({ children, stepCount, step, setStep, title }) {
// console.log('step', step);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
if (step[stepCount]?.active) {
setIsOpen(true);
}
}, [step, stepCount]);
const handleExpandButtonClick = () => {
setIsOpen(!isOpen);
setStep((prevState) => {
return {
...prevState,
[stepCount]: {
...prevState[stepCount],
active: !isOpen,
},
};
});
};
return (
<div className='bg-white w-full border p-4 relative overflow-hidden'>
{/* heading section */}
<div className='flex items-center justify-between'>
<div className='flex items-center gap-4'>
{/* step counter */}
<div
className={`flex items-center justify-center h-6 w-6 rounded-full ${
step[stepCount]?.completed
? 'bg-emerald-600 text-white'
: step[stepCount]?.active
? 'bg-orange-500 text-white'
: 'bg-gray-300'
}`}
>
<span className='text-sm'>{step[stepCount]?.step}</span>
</div>
<h3 className='m-0'>{title}</h3>
</div>
<button
className='border rounded-full w-6 h-6 flex items-center justify-center'
onClick={handleExpandButtonClick}
>
<IoIosArrowDown
className={`transition-all duration-300 ${
isOpen ? 'transform rotate-180' : ''
}`}
/>
</button>
</div>
{/* content section */}
<div
className={`bg-white transition-all duration-300 max-h-0 ${
isOpen ? 'max-h-[30rem] overflow-y-auto' : 'overflow-hidden'
}`}
>
<div className='py-4'>{children}</div>
</div>
</div>
);
}
export default StepContainer;
The dropdown is handled by a separate component called: DropdownAddWithCheck
import { useEffect, useRef, useState } from 'react';
import { AiFillCaretDown } from 'react-icons/ai';
import { IoAddCircleOutline } from 'react-icons/io5';
const DropdownAddWithCheck = ({
list,
placeholder,
value,
selectedItems,
setSelectedItem,
className,
inputClassname,
borderColor,
borderSelectColor,
}) => {
const [open, setOpen] = useState(false);
const [searchText, setSearchText] = useState('');
const [filteredList, setFilteredList] = useState([]);
// Step 1: populate filtered list with provided list
useEffect(() => {
if (list) {
setFilteredList(list);
}
}, [list]);
// Step 2: Close dropdown on click outside using useRef
const wrapperRef = useRef(null);
useEffect(() => {
const handleClickOutside = (event) => {
if (wrapperRef.current && !wrapperRef.current.contains(event.target)) {
setOpen(false);
}
};
document.addEventListener('click', handleClickOutside, true);
return () => {
document.removeEventListener('click', handleClickOutside, true);
};
}, [wrapperRef]);
// Step 3: Filter list based on search text
const handleSearchTextChange = (event) => {
setSearchText(event.target.value);
let newList = [];
if (searchText.length > 0) {
newList = list.filter((item) =>
item.name.toLowerCase().includes(event.target.value.toLowerCase())
);
} else {
newList = list;
}
setFilteredList(newList);
};
// Step 4: Set selected item on click
const handleListItemClick = (item) => {
setSelectedItem(item);
setSearchText(item.name);
setOpen(false);
};
return (
<div className='w-full flex items-center gap-2 z-20'>
<div ref={wrapperRef} className={`${className} relative`}>
{/* input search box */}
<div
className={`flex ${
open
? borderSelectColor
? borderSelectColor
: 'border-purple-500'
: borderColor
? borderColor
: 'border-gray-300'
}`}
>
<input
onFocus={() => setOpen(true)}
type='text'
placeholder={placeholder}
value={searchText ? searchText : value ? value : ''}
onChange={handleSearchTextChange}
className={`outline-none w-full text-sm py-0.5 ${inputClassname}`}
/>
<button onClick={() => setOpen(!open)} type='button'>
<AiFillCaretDown
className={`text-gray-600 transition-all duration-200 ${
open ? 'transform rotate-180' : ''
}`}
/>
</button>
</div>
<ul
className={`absolute top-9 bg-white w-full shadow-lg rounded max-h-0 transition-all ease-in-out
duration-200 divide-y ${
open
? 'max-h-40 border border-purple-500 overflow-y-scroll'
: 'overflow-hidden'
}`}
>
{filteredList &&
filteredList.map((item, index) => (
<li key={index}>
<button
type='button'
onClick={() => handleListItemClick(item)}
className='w-full hover:bg-blue-50 text-left px-2 py-1 flex items-center gap-2'
>
<input
type='checkbox'
checked={selectedItems?.includes(item) || false}
readOnly
/>
<span className='text-sm'>{item.name}</span>
</button>
</li>
))}
</ul>
</div>
<button className='border border-green-500 text-green-600 hover:bg-green-100 rounded-lg px-1 py-1 tracking-tighter text-sm whitespace-nowrap flex items-center gap-1'>
<IoAddCircleOutline className='scale-125' />
Add
</button>
</div>
);
};
export default DropdownAddWithCheck;
