framer motion animation not working with element that rendered through map() - javascript

I'm trying to animate something like this :
https://drive.google.com/file/d/1WQCg7j49xd5XfuaYuC2YFQCUU-UXassp/view?usp=sharing
and here's my code :
<motion.div layout className="grid grid-cols-2 md:grid-cols-3 gap-8 py-10">
<motion.div
layout
style={cardOpen && { gridColumn: "1 / -1" }}
className={`card group relative w-full h-72 bg-primary rounded-lg overflow-hidden ${cardOpen ? "flex gap-5 p-10 shadow-inner" : "cursor-pointer"}`}
onClick={() => {
if (cardOpen) return;
setCardOpen(true);
}}
>
<AnimatePresence mode="wait">
{cardOpen && (
<motion.div
className="h-full absolute -bottom-32 -left-4 group-hover:brightness-30"
animate={{ y: [-100, 0] }}
>
<motion.img
className="h-full z-0"
src="/img/highlighted_projects/bg/moladin.svg"
alt=""
/>
</motion.div>
)}
{cardOpen || (
<motion.img
transition={{ ease: "easeIn", duration: 0.1 }}
initial={{ x: -500 }}
animate={{ x: [-500, 0] }}
exit={{ opacity: [1, 0] }}
className="absolute top-0 left-0 rounded-lg duration-300 w-full h-full object-cover brightness-50 group-hover:scale-110 group-hover:brightness-100"
src="/img/highlighted_projects/supra.jpeg"
alt=""
/>
)}
</AnimatePresence>
<motion.div className={`duration-300 text-white z-10 w-full ${cardOpen ? "" : "absolute bottom-3 left-3 group-hover:bottom-5"}`}>
<motion.div layout="position">
{cardOpen && <span className="montserrat font-light text-xs">Dec 2021 - Present</span>}
<div className="flex justify-between">
<h1 className="text-4xl font-bold">Moladin</h1>
{cardOpen && <X className="cursor-pointer" onClick={() => setCardOpen(false)} />}
</div>
<div className="flex gap-2 text-xs">
<span className="border rounded-full py-px px-2">Javascript</span>
<span className="border rounded-full py-px px-2">React</span>
<span className="border rounded-full py-px px-2">SCSS</span>
</div>
</motion.div>
{cardOpen && (
<motion.div className="mt-2" animate={{ opacity: [0, 1] }} transition={{ duration: 1, ease: "easeIn" }}>
<p className="montserrat">Yes. I'm one of many moladians you saw out there. We make selling/buying cars easier, safer, and profitable! My responsibility is mainly on CRM side of Moladin. But I sometimes handle the B2C side as well.</p>
</motion.div>
)}
</motion.div>
</motion.div>
<motion.div layout className="card w-full h-44 bg-blue-400">
moladin
</motion.div>
<motion.div layout className="card w-full h-44 bg-blue-400">
moladin
</motion.div>
</motion.div>
It is working, but then I make a component out of it and provide data for it. Now it's not working : https://drive.google.com/file/d/1XdqN-_BA5ABdNpQThI_KfF6D9UPlQKXO/view?usp=share_link
Here's the code :
{featuredProjects.map(project => <FeaturedProject key={project.projectName} {...project} /> )}
I tried this Framer motion new animation and exit not working with mapping
but it seems like not working to me or I wasn't able to understand it
How do I make it work?
Thanks.

I happen to make it working by using AnimatedSharedLayout :
<AnimateSharedLayout>
{featuredProjects.map(project => (
<FeaturedProject key={project.id} {...project} />
))}
</AnimateSharedLayout>
But I notice that it is deprecated and recommends me to use layoutId instead.
I tried using layoutId but it's not working...
and even removing layout itself :
As for now my question is kind of solved but I don't think its actually solved yet. So I'll just keep this thread open

Related

Flow direction for divs displayed on hovering

I have a following set of images each with its own div and I want to show a popup with information on hover. Refer the following component code-
function Skill({ directionLeft, skill }: Props) {
return (
//group hover filter class in motion img used for special effect
<div className='group relative flex cursor-pointer'>
<motion.div
initial={{
x: directionLeft ? -200 : 200,
opacity: 0
}}
transition={{ duration: 0 }}
whileInView={{ opacity: 1, x: 0 }}
className='w-16 h-16 rounded-full border border-gray-500 object-cover xl:w-20 xl:h-20 filter group-hover:grayscale transition duration-300 ease-in-out'
>
<Image
src={urlFor(skill.skillImage).url()}
height={108} width={192} alt='' className=' w-16 h-16 rounded-full border border-gray-500 object-cover xl:w-20 xl:h-20 filter group-hover:grayscale transition duration-300 ease-in-out' />
</motion.div>
<div className='absolute opacity-0 group-hover:opacity-90 transition duration-300 ease-in-out z-20 group-hover:bg-[#1e313f] h-54 xl:w-72 rounded-lg p-5'>
<div className='flex flex-col items-center justify-center h-full'>
{/*loop over if multiple skill supports*/}
{skill.relevantWorkDone.map((relevantWorkDone, index) => (
<p key={index} className='uppercase tracking-[5px] text-gray-200 text-sm overflow-y-auto opacity-100 z-20= justify-center'>
-{relevantWorkDone}
</p>
))}
</div>
</div>
</div>
)
}
Now I want to adjust the hovering of the popup messages according to the location of the parent div in super parent grid.
Eg- instead of 1
I want something like 2-
The image div being hovered on is the one in the one in the last column if the 2nd last row.
I know I can pass a prop with the grid location like I and create a function depending upon the remainder to make it flow either left or right and if I is in the first half or second half of total elements to make it flow from either top or bottom.
But I don't know what tailwind CSS classes to use to achieve this.
Any help would be appreciated.

Drag and Drop ghost trailing on drop w/React

I am having this problem when I try to reorder elements by dragging them. The reorder works but the functionality of changing places is not performing as desired. When you drag an element/image from a position x to position y it will change the position and it will stay there but on drop handler a "ghost" section it tries to go to the old position and the delete button on the overlay disappears.
I've provided a CodeSandbox link.
This is the component throwing the error.
as oposed to
I tried adding more event handlers like :
onDragOver
onDragDrop
onDragStart
onDragEnter
onDragLeave
and tried to add some logic to metigate the transition of images, but nothing worked.
{imageList.map((image, index, isDragging) => (
<div
key={index}
className="image-item w-1/2 sm:w-40 h-36 sm:p-2 px-1 pt-2"
draggable
onDragStart={(e) => handleDragStart(e, index)}
onDragEnd={(e) => handleDragEnd(e)}
onDragEnter={(e) => handleDragEnter(e, index)}
onDragLeave={(e) => handleDragLeave(e)}
onDragOver={(e) => handleDragOver(e, index)}
onDrop={(e) => handleDrop(e, index)}
>
<div
className={`border relative bg-white h-full transition duration-200 ease-in-out `}
>
{/* //!IMAGE */}
<span
className={` w-full h-full bg-center bg-contain bg-no-repeat `}
style={
{
// backgroundImage: `url(${image.dataURL})`
}
}
>
<img src={image.dataURL} width="200" alt="" />
</span>
{/* OVERLAY */}
<div className="z-10 overlay absolute w-full h-full flex items-center justify-center opacity-0 hover:opacity-100 transition-opacity duration-300 bg-black bg-opacity-50">
<div className="flex">
<button
type="button"
onClick={() => onImageRemove(index)}
title=""
>
<span className="w-5 h-5">
<button>delete</button>
</span>
</button>
</div>
</div>
</div>
</div>
))}

How to design a collapse component in reactjs with tailwind css

I am trying to create a collapse section component design in react JS with TailwindCSS, in the component, there will be an edit button to open and close the info section, and the edit info button will be next to the input field. The design is done(screenshot 1) but some adjustments are needed in the styling, which causes confusion about how to put the edit info button and info section code in a single component so that the design looks like the below screenshot.
<div>
<div
className={[
'flex',
'justify-between',
'relative',
'lg:px-20',
'xl:px-40',
'py-6',
].join(' ')}
>
<div className="flex items-center sm:flex-col lg:flex-row ">
<div className="flex flex-col lg:flex-row md:flex-row items-center">
<StreamTokenInputField />
//EDIT INFO BUTTON
<button
className="button"
onClick={() => setIsCollapseTrue(!isCollapseTrue)}
>
{i18n.t(Edit Info)}
{isCollapseTrue ? (
<IoIosArrowUp className="font-extrabold ml-2 text-lg" />
) : (
<FiChevronDown className="font-extrabold ml-2 text-lg" />
)}
</button>
</div>
</div>
</div>
// INFO SECTION
<div
className={[
'container',
'mx-auto',
'md:w-full',
'w-96',
'py-8',
'my-4',
'lg:py-20',
'lg:px-40',
'bg-skin-card',
'rounded-3xl',
!isCollapseTrue && 'hidden',
].join(' ')}
>
{/* OTHER CODES */}
</div>
</div>
screenshot 1
I have tried like this
<div
className={[
'flex',
'justify-between',
'relative',
'lg:px-20',
'xl:px-40',
'py-6',
].join(' ')}
>
<div className="flex items-center sm:flex-col lg:flex-row ">
<div className="flex flex-col lg:flex-row md:flex-row items-center">
<StreamTokenInputField />
<button
className={[
'flex',
'button',
'button-green',
'xl:px-6',
'md:px-2',
'lg:px-10',
'lg:my-6',
'md:my-6',
'mx-4',
'justify-center',
'uppercase',
'font-semibold',
].join(' ')}
onClick={() => setIsCollapseTrue(!isCollapseTrue)}
>
{i18n.t(buttonName)}
{isCollapseTrue ? (
<IoIosArrowUp className="font-extrabold ml-2 text-lg" />
) : (
<FiChevronDown className="font-extrabold ml-2 text-lg" />
)}
</button>
<div
className={[
'container',
'mx-auto',
'md:w-full',
'w-96',
'py-8',
'my-4',
'lg:py-20',
'lg:px-40',
'bg-skin-card',
'rounded-3xl',
!isCollapseTrue && 'hidden',
].join(' ')}
>
{/* OTHER CODES */}
</div>
</div>
</div>
</div>
However, the output shows like this: I want the info section to appear below the edit info button just like in the screenshot above.
If you want info section appear below button. You don't need lg:flex-row
So your code will be
<div className="flex items-center flex-col">
[...]
</div>
Also tailwind uses mobile-first breakpoint system. So you don't need to add sm: to sm:flex-col.
Same here <div className="flex flex-col lg:flex-row md:flex-row items-center">. You don't need to add lg:flex-row once you added md:flex-row. Any larger size than md will follow same class defined as md:
More information can be found here mobile-first-breakpoint-system
You can make use of the detail tag. Modify the animation anyhow you want or perhaps hide the default icon and specify a custom icon.
<details className="open:bg-white border-b open:ring-1 open:ring-black/5 open:shadow-lg p-6 rounded-lg transform-gpu delay-75 duration-100 ease-in-out ">
<summary className="leading-6 text-slate-900 dark:text-white font-semibold select-none">
Why do they call it Ovaltine?
</summary>
<div className="mt-3 text-sm leading-6 text-slate-600 dark:text-slate-400">
<p>The mug is round. The jar is round. They should call it Roundtine.</p>
</div>
</details>

How to render components with individual keys?

I have a dashboard and I want to use React-Grid-Layout but I render the components only if they have been favorited. To use the gird layout each div needs a key="a" or key="b" depending on the layout defined each key needs to be different.
How can I go about giving each div an individual key? When it renders on-screen in its current form it renders two divs with the same cards in where I need it to render one div for each card.
const layout = [
{ i: "a", x: 0, y: 0, w: 1, h: 2 },
{ i: "b", x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
{ i: "c", x: 4, y: 0, w: 1, h: 2 },
];
render function
const userHasFavorite = (data, favIds, layout) => {
if (favIds && data) {
const filteredData = data.filter((idsArr) =>
favIds.split(",").includes(idsArr.id)
);
const keysMapped = layout.map((ids) => ids.i);
console.log(keysMapped);
return (
<div key={keysMapped}>
<PriceCard data={filteredData} />
</div>
);
} else {
return <p1>No Cards Have Been Faved</p1>;
}
};
Grid Layout
<GridLayout
className="layout"
layout={layout}
cols={12}
rowHeight={30}
width={1200}
>
{isLoading ? (
<LoadingCard />
) : (
userHasFavorite(data, favoritedIds, layout)
)}
</GridLayout>
PRICE CARD
return (
<dl className="mt-5 ml-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{data.map((item) => (
<div
key={item.id}
className="flex-row bg-white hover:bg-gray-100 dark:hover:bg-purple-700 dark:bg-secondaryDark h-24 pt-2 px-4 pb-12 sm:pt-6 sm:px-6 shadow-2xl rounded-lg overflow-hidden"
>
<div className="flex h-2 ">
{/* TODO: ADD TRANSISITON */}
{cardFaved.some((cardFaved) => cardFaved.id === item.id) ? (
<MinusSmIcon
onClick={() => removeCardFavClick(item.id)}
className="ml-auto h-5 w-5 cursor-pointer fill-current text-red-600 "
/>
) : (
<PlusSmIcon
onClick={() => handleCardFavClick(item.id)}
className="ml-auto h-5 w-5 cursor-pointer fill-current text-green-600 "
/>
)}
</div>
<dt>
<div className="absolute rounded-md p-3">
<img className="h-6 w-6 mr-3" src={item.image} />
</div>
<p className="ml-16 text-sm pb-0 font-medium text-gray-500 dark:text-white truncate">
{item.name.toUpperCase()}
</p>
</dt>
<dd className="ml-16 pb-7 flex items-baseline sm:pb-7">
<p className="text-2xl font-semibold text-gray-900 dark:text-white">
{formatDollar(item.current_price)}
</p>
</dd>
</div>
))}
</dl>
);
};
export default PriceCard;
Error I'm having is the cards render 2 in one div
Error 2
Ok, so from the React-Grid-Layout official usage, You need to map() directly within the <GridLayout/> component.
So here a good suggestion:
Within the GridLayout:
WITH map()
<GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
{isLoading
? <LoadingCard />
: layout.map(({ i }) =>
<div key={i}>
// change this from a typical function into a React component
<UserHasFavorite data={data} favIds={favoritedIds} />
</div>
)}
</GridLayout>
WITHOUT map()
<GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
{isLoading
? <LoadingCard />
: <React.Fragment>
<div key="a"><UserHasFavorite data={data} favIds={favoritedIds} /></div>
<div key="b"><UserHasFavorite data={data} favIds={favoritedIds} /></div>
<div key="c"><UserHasFavorite data={data} favIds={favoritedIds} /></div>
</React.Fragment>}
</GridLayout>
Within the child component
const UserHasFavorite = (data, favIds) => {
if (favIds && data) {
const filteredData = data.filter((idsArr) =>
favIds.split(",").includes(idsArr.id)
);
// <div> is moved up into the GridLayout
return <PriceCard data={filteredData} />;
} else {
return <p1>No Cards Have Been Faved</p1>;
}
};
UPDATE: To handle issue of rendering multiple "similar" cards
Within the PriceCard, there is this...
return (
<dl className="mt-5 ml-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{data.map((item) => (
<div
key={item.id}
className="flex-row bg-white hover:bg-gray-100 dark:hover:bg-purple-700 dark:bg-secondaryDark h-24 pt-2 px-4 pb-12 sm:pt-6 sm:px-6 shadow-2xl rounded-lg overflow-hidden"
>
</div>
)}
</dl>
)
You are mapping to display all the cards in a row. So if you updated the className="flex-column..." those two cards should render in a Column but in one <div>.
And then if you do that, you have two options:
Either you delete the <GridLayout/> and render directly like this...
// <GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
return (
isLoading
? <LoadingCard />
: <UserHasFavorite data={data} favIds={favoritedIds} />
)
// </GridLayout>
By removing the <GridLayout/> which is used for display, you will have just two cards rendered in a column (handled with PriceCard component).
2.Or render the <PriceCard/> directly to within the like this...
// Parent component -> GridLayout
export const ParentComponent = props => {
// I'm not sure where "data" and "favoritedIds" are coming from yet,
// but we check like "data &&..." to ensure they are defined
// before we attempt to filter
const filteredData = data && data.filter(
(idsArr) => favoritedIds && favoritedIds.split(",").includes(idsArr.id)
);
return (
<GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
{isLoading
? <LoadingCard />
: (!!data && !!filteredData)
? <React.Fragment>
<div key="a"><PriceCard data={filteredData[0]} /></div>
<div key="b"><PriceCard data={filteredData[1]} /></div>
<div key="c"><PriceCard data={filteredData[2]} /></div>
</React.Fragment>}
: <p1>No Cards Have Been Faved</p1>
</GridLayout>
)
}
// Child component -> PriceCard
export const ChildComponent = props => {
return (
<dl className="mt-5 ml-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{data && /* don't map here -> we moved it to parent */
<div className="flex-row bg-white hover:bg-gray-100 dark:hover:bg-purple-700 dark:bg-secondaryDark h-24 pt-2 px-4 pb-12 sm:pt-6 sm:px-6 shadow-2xl rounded-lg overflow-hidden">
/* make necessary changes */
</div>}
</dl>
)
}
From what I read I think that you need to iterate and assign it to the div. You can do something like this.
return (
{filteredData.map(data => (
<div key={data.i}>
<PriceCard data={filteredData} />
</div>
)
);

Tailwind Flex Box Responsive Grid with Cards issue

Here is where i started with
Preview
Code :
<div class="container my-12 mx-auto">
<div className="flex flex-wrap ">
{error ? <p>{error.message}</p> : null}
{!isLoading ? (
users.map(user => {
const { username, name, email } = user;
return (
<div
key={username}
className="w-full md:w-1/2 lg:w-1/3 my-5"
>
<article class="overflow-hidden rounded-lg shadow-lg">
<img
alt="Placeholder"
className="block h-auto w-full"
src="https://picsum.photos/600/400/?random"
/>
<header class="flex items-center justify-between leading-tight p-2 bg-white invisible lg:visible">
<h1 class="text-lg">{name}</h1>
<p class="text-grey-darker text-sm">
{email}
</p>
</header>
</article>
</div>
Then i tried to make it more spaced out like shown in the codepen example here :
https://codepen.io/codetimeio/pen/RYMEJe
but everytime i try to add some padding and margin it escapes to the next line and i cant figure out why it does that or how i can stop it
Here is the line i updated :
<div key={username} className="w-full md:w-1/2 lg:w-1/3 my-5 mx-5">
Here is my tailwind config file
module.exports = {
theme: {
container: {
center: true
},
screens: {
sm: "640px",
md: "768px",
lg: "1024px",
xl: "1280px"
},
fontFamily: {
display: ["Gilroy", "sans-serif"],
body: ["Graphik", "sans-serif"]
},
extend: {}
},
variants: {},
plugins: []
};
I really want to understand what im doing wrong so that i can use tailwind as my main templating framework
Here is updated code as per the given answer below
<div class="container my-12 mx-auto bg-gray-400">
<div className="flex flex-wrap ">
{error ? <p>{error.message}</p> : null}
{!isLoading ? (
users.map(user => {
const { username, name, email } = user;
return (
<div key={username} className="w-full p-5 md:w-1/2 lg:w-1/3">
<article className="overflow-hidden rounded-lg shadow-lg">
<img alt="Placeholder" className="w-full" src="https://picsum.photos/600/400/?random" />
<header className="flex items-center justify-between leading-tight p-2 bg-white">
<h1 className="text-lg">{name}</h1>
<p className="text-grey-darker text-sm">
{email}
</p>
</header>
</article>
</div>
It's happening because of the extra margins, w-1/3 means ~ width: 33.3333% If you add a margin on top of it, three can't fit in one line.
There are alternative ways (widths taking into account the gutter or gap property), but in exactly this case you could just use padding instead of margins, because you already have a presentational wrapping element around your cards.
Example: https://codepen.io/tlgreg/pen/RmLMOx
Not related, but few notes looking at your code:
Unless you use the old color palette in the config, grey-darker will not work.
img is block by default in v1.
invisible and lg:visible changes visibility, the header will take up space, if that not what you want, it should be hidden and lg:flex.

Categories

Resources