nextjs + framer-motion: "exit" animation not working on child - next.js

i have a problem with framer-motion and the "exit"-animation.
After some search on the Internet i found out that the children of the <AnimatePresence> need a key prop and should be the direct child.
My simplified structure:
// manager of the sites
const SiteManager = () => {
return (
<AnimatePresence mode="wait" ...>
{
{
0: <Page1 />
1: <Page2 />
...
}[page]
}
</AnimatePresence>
)
}
// this component should be animated with the slide effect
const Fade = ({ children }) => {
return (
<motion.div key={page} ...>
{ children }
</motino.div>
)
}
// a page has content and a footer -> footer shouldnt be animated, thats the reason why i had to seperate it to the <Fade /> Component
const Page1 = () => {
const [value, setValue] = useState("")
return (
<>
<Fade>
<input value={value} onChange={e => setValue(e.target.value)} />
</Fade>
<Footer value={value} ... />
</>
)
}
Maybe the codesandbox helps a bit:
Codesandbox
I gave the <motion.div> a key, but it doenst change anything.

Related

Make react swiper pagination as a separate component and plug it in swiper

I am working on a project with a setup combination of react, tailwind and react icons. In my project multiple sliders are required (some has navigation, some has pagination etc…)
So, I created a slider factory component Carousel and controlling variation through props
// Carousel/index.jsx
import { Children, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import { HiOutlineArrowLeft, HiOutlineArrowRight } from "react-icons/hi";
import { Swiper, SwiperSlide } from "swiper/react";
import { Autoplay, Keyboard, Mousewheel, Navigation, Pagination } from "swiper";
import "swiper/css";
import "swiper/css/pagination";
import "swiper/css/navigation";
import NextSlideBtn from "./NextSlideBtn";
import PrevSlideBtn from "./PrevSlideBtn";
const Carousel = ({
children,
loop,
pagination,
navigation,
autoplay,
breakpoints,
}) => {
const childrenArray = Children.toArray(children);
const [swiperRef, setSwiperRef] = useState();
return (
<div className="group relative">
<Swiper
className="rounded overflow-hidden"
onSwiper={setSwiperRef}
modules={[
Autoplay,
Keyboard,
Mousewheel,
Pagination,
Navigation,
]}
mousewheel
keyboard
grabCursor
loop={loop || false}
autoplay={
autoplay && {
delay: 5000,
disableOnInteraction: true,
}
}
spaceBetween={breakpoints && 20}
breakpoints={
breakpoints && {
450: {
slidesPerView: 2,
},
768: {
slidesPerView: 3,
},
1536: {
slidesPerView: 4,
},
}
}
pagination={
pagination && {
clickable: true,
}
}
>
{childrenArray.map((item) => (
<SwiperSlide key={uuidv4()}>{item}</SwiperSlide>
))}
</Swiper>
{navigation && (
<div className="hidden group-hover:block">
<PrevSlideBtn swiperRef={swiperRef}>
<HiOutlineArrowLeft className="text-violet-600" />
</PrevSlideBtn>
<NextSlideBtn swiperRef={swiperRef}>
<HiOutlineArrowRight className="text-violet-600" />
</NextSlideBtn>
</div>
)}
</div>
);
};
export default Carousel;
// Carousel/NextSlideBtn.jsx
const NextSlideBtn = ({ children, swiperRef }) => {
const handleNextSlide = () => swiperRef.slideNext();
return (
<button
onClick={handleNextSlide}
className="absolute z-50 top-1/2 -translate-y-1/2 -right-2 translate-x-2 bg-white shadow-md rounded-full p-3"
>
{children}
</button>
);
};
export default NextSlideBtn;
// Carousel/PrevSlideBtn.jsx
const PrevSlideBtn = ({ children, swiperRef }) => {
const handlePrevSlide = () => swiperRef.slidePrev();
return (
<button
onClick={handlePrevSlide}
className="absolute z-50 top-1/2 -translate-y-1/2 -left-2 -translate-x-2 bg-white shadow-md rounded-full p-3"
>
{children}
</button>
);
};
export default PrevSlideBtn;
One of the requirement was to create custom navigation button, which I managed to accomplish as you can see in the code above, but for more visibility I am marking the flow in short
// Carousel/index.jsx
const [swiperRef, setSwiperRef] = useState();
<div>
<Swiper
onSwiper={setSwiperRef}
...
>
...
</Swiper>
<PrevSlideBtn swiperRef={swiperRef} />
<NextSlideBtn swiperRef={swiperRef} />
</div>
// Carousel/NextSlideBtn.jsx
const NextSlideBtn = ({ children, swiperRef }) => {
const handleNextSlide = () => swiperRef.slideNext();
return (
<button
onClick={handleNextSlide}
...
>
{children}
</button>
);
};
export default NextSlideBtn;
// Carousel/PrevSlideBtn.jsx
const PrevSlideBtn = ({ children, swiperRef }) => {
const handlePrevSlide = () => swiperRef.slidePrev();
return (
<button
onClick={handlePrevSlide}
...
>
{children}
</button>
);
};
export default PrevSlideBtn;
Couple of github issues, stackoverflow questions and swiper docs helped me out to accomplish this, and it was pretty straight forward to understand.
Now my question is how can I customize swiper react pagination (like radio buttons which some websites use, or any other styles…) using tailwind and I want to make it as a separate component like I did for navigation buttons.
I’ve tried many solutions eg:
const paginationBullets = {
type: "custom",
clickable: true,
renderCustom: (_, current, total) => {
return <MyCustomSwiperPaginationComponent current={current} total={total} />;
},
};
But nothing seems to work as expected.
Please help me out.
Component separation is my goal.

Possible to style button according to if the state value is true or false?

I have two buttons that show two different components when toggling them. For UX reasons (to know which component is showing) I would like to style the buttons according to if the value of the state is true or false (give them an underline and a darker color if the state is true). Is this possible in any way?
This is my GitHub repo: https://github.com/uohman/Portfolio2022
And this is the component where I handle the buttons:
`
import React, { useState } from 'react'
import ReactDOM from 'react-dom';
import { Subheading } from 'GlobalStyles';
import { FrontendProjects } from './FrontendProjects'
import { GraphicDesignProjects } from './GraphicDesignProjects';
import 'index.css'
export const FeaturedProjects = () => {
const [buttons, setButtons] = useState([
{ label: 'Development', value: true },
{ label: 'Graphic design', value: false }
]);
const handleButtonsChange = () => (label) => {
const newButtonsState = buttons.map((button) => {
if (button.label === label) {
return (button = { label: button.label, value: true });
}
return {
label: button.label,
value: false
};
});
setButtons(newButtonsState);
};
return (
<>
<Subheading><span>Featured projects</span></Subheading>
<SpecialButton {...{ buttons, setButtons, handleButtonsChange }} />
{buttons[0].value && <FrontendProjects />}
{buttons[1].value && <GraphicDesignProjects />}
</>
);
};
const SpecialButton = ({ buttons, setButtons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
onClick={() => handleButtonsChange({ buttons, setButtons })(button.label)}>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
const rootElement = document.getElementById('root');
ReactDOM.render(<FeaturedProjects />, rootElement);
`
I've given the buttons the pseudo element :focus and that nearly solves my problem, but still as a default the buttons are the same color although it is one of the components that is showing. Thankful for suggestions on how to solve this!
You can provide a style props to any html component.
You should pass an object where attributes are camelcased.
<button
style={{ // double bracket to pass an object
backgroundColor: yourVariable ? 'red' : undefined // notice css background-color became backgroundColor
}}
>
{button.label.toUpperCase()}
</button>
You can do the same with classes
<button
className={yourVariable && "yourClass"}
>
{button.label.toUpperCase()}
</button>
You can set styles for button based on a condition.
In this use case, you already have the state button.value which can be used as a condition to set inline styles (or classes) for the mapped button.
Example:
const SpecialButton = ({ buttons, setButtons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
// 👇 This property is added
style={{
backgroundColor: button.value ? "#aaa" : "#eee",
textDecoration: button.value ? "underline" : "none",
}}
onClick={() =>
handleButtonsChange({ buttons, setButtons })(button.label)
}
>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
The buttons are set to become darker when selected in the above example, but you can further customize the styles for the desired result.
More about inline styles
On a side note, it is not necessary to pass state values to the the event by onClick={() => handleButtonsChange({ buttons, setButtons })(button.label)}.
The parent component always have these values, so you do not need to pass it down to SpecialButton and pass it back.
Hope this will help!
Full example:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Subheading } from "GlobalStyles";
import { FrontendProjects } from "./FrontendProjects";
import { GraphicDesignProjects } from "./GraphicDesignProjects";
import "index.css";
export const FeaturedProjects = () => {
const [buttons, setButtons] = useState([
{ label: "Development", value: true },
{ label: "Graphic design", value: false },
]);
const handleButtonsChange = (label) => {
const newButtonsState = buttons.map((button) => {
if (button.label === label) {
return (button = { label: button.label, value: true });
}
return {
label: button.label,
value: false,
};
});
setButtons(newButtonsState);
};
return (
<>
<Subheading>
<span>Featured projects</span>
</Subheading>
<SpecialButton {...{ buttons, handleButtonsChange }} />
{buttons[0].value && <FrontendProjects />}
{buttons[1].value && <GraphicDesignProjects />}
</>
);
};
const SpecialButton = ({ buttons, handleButtonsChange }) => {
return (
<div className="button-container">
{buttons.map((button, index) => (
<button
key={`${button.label}-${index}`}
// 👇 This property is added
style={{
backgroundColor: button.value ? "#aaa" : "#eee",
textDecoration: button.value ? "underline" : "none",
}}
onClick={() =>
handleButtonsChange(button.label)
}
>
{button.label.toUpperCase()}
</button>
))}
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<FeaturedProjects />, rootElement);

Change the style of the item in rectjs and remove it if I clicked another item

I wanted to change the style of an item with one click in a list and remove it if I click another item
I did like this but when I click on the second it doesn’t change to the first
const ref = useRef();
const handleClick = () => {
if (ref.current.style.backgroundColor) {
ref.current.style.backgroundColor = '';
ref.current.style.color = '';
} else {
ref.current.style.backgroundColor = 'green';
ref.current.style.color = 'white';
}
};
<Card ref={ref} elevation={6} style={{ marginBottom: "5px"}} onClick={()=>{ handleClick()}} >
<CardContent style={{ height: "10px" }}>
<Typography >
{user}
</Typography>
</CardContent>
</Card>
);
};
any help please!
I like using a package like classnames ( yarn add classnames or npm i classnames) to apply conditional styling through classes rather than having to inline the element's styling directly.
You could pass the selected attribute to your Card component using React's useState (or Redux) and then apply conditional styling to selected cards (i.e. <Card selected />).
Component.js
import { useState } from 'react';
import { useSelector } from 'react-redux';
import Card from './Card';
const Component = () => {
const [selectedId, setSelectedId] = useState(null);
const items = useSelector(state => state.items);
const handleClick = (id) => {
setSelectedId(id);
};
return items.map(({id}) =>
<Card
key={id}
onClick={() => handleClick(id)}
selected={id === selectedId}
>
...
</Card>
);
};
export default Component;
Card.js
import { classNames } from 'classnames';
const Card = ({ children, onClick, selected = false }) => (
<div
className={
classNames('Card', {
'Card--Selected': selected
})
}
onClick={onClick}
>
{ children }
</div>
);
export default Card;
Card.scss
.Card {
// Card styling...
&--Selected {
// Selected card styling...
}
}

React-beautiful-dnd: how to fix list items sliding down on isDraggingOver

I have a react-beautiful-dnd app where the user can drag cards from one column to another. When I drag a card to a column with existing cards, the existing cards jump down. This makes sense if I'm trying to drag the card to the top of the column. However, the first card slides down even if I drag the new card to the middle or the list. And if I drag the new card to the bottom of the list everything above it slides down. Once I release the mouse button and the dragging is complete, the cards snap back to where they should be. Therefore, it's something to do with isDraggingOver. I checked my styled components, but the only styles that change on isDraggingOver are background colors and border colors. Has anyone else encountered this? If so, how did yo fix it. Thanks in advance!
Update 9/6:
After unsuccessfully trying #Jordan solution I looked at the GitHub issues for the project and found this. Sure enough, if I do not render provided.placeholder the problem goes away. Of course, now I get a warning in the console about not rendering that placeholder. I then tried rendering it again but wrapping it in a call to React.cloneElement() and passing {style: {height: '0'}} as props. This does not work. So for now, I am simply not rendering the placeholder. Does anyone know another way around this? Here's a screenshot of the latest diff:
Update 8/25
Trying the solution of #jordan but I"m still seeing the issue. Here is my updated component file for the draggable component called Task
Draggable file:
import React, { memo, useState } from 'react'
import { Draggable } from 'react-beautiful-dnd'
import TaskForm from '../TaskForm/TaskForm'
import Task from '../Task/Task'
import { useStyletron } from 'baseui'
import useDraggableInPortal from '../../Hooks/useDraggableInPortal'
const TaskCard = ({ description, id, index, column }) => {
const renderDraggable = useDraggableInPortal()
const [isEditing, setIsEditing] = useState(false)
const [css, theme] = useStyletron()
const handleEdit = id => {
setIsEditing(true)
}
return (
<Draggable draggableId={id} index={index}>
{renderDraggable((provided, snapshot) => (
<div
className={css({
boxSizing: 'border-box',
':focus': {
outline: `${theme.colors.hotPink} 3px solid`,
borderRadius: '6px',
margin: '3px',
}
})}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}>
{!isEditing &&
<Task
column={column}
description={description}
handleEdit={handleEdit}
id={id}
snapshot={snapshot}
/>
}
{isEditing &&
<TaskForm
column={column}
handleOnCancel={() => setIsEditing(false)}
initialValues={{ description, id }} isEditing={true}
/>
}
</div>
))}
</Draggable>
)
}
export default memo(TaskCard)
Hook from #jordan :
import { createPortal } from 'react-dom'
import { useEffect, useRef } from 'react'
const useDraggableInPortal = () => {
const self = useRef({}).current
useEffect(() => {
const div = document.createElement('div')
div.style.position = 'absolute'
div.style.pointerEvents = 'none'
div.style.top = '0'
div.style.width = '100%'
div.style.height = '100%'
self.elt = div
document.body.appendChild(div)
return () => {
document.body.removeChild(div)
}
}, [self])
return (render) => (provided, ...args) => {
const element = render(provided, ...args)
if (provided.draggableProps.style.position === 'fixed') {
return createPortal(element, self.elt)
}
return element
}
}
export default useDraggableInPortal
I had this issue, and I found a solution to it here.
Basically when the library is using position: fixed, there are are some unintended consequences - and in those cases you need to use portal to get around them:
I had the same issue. Following the great solution of #kasperpihl, I created a simple hook do do the trick:
const useDraggableInPortal = () => {
const self = useRef({}).current;
useEffect(() => {
const div = document.createElement('div');
div.style.position = 'absolute';
div.style.pointerEvents = 'none';
div.style.top = '0';
div.style.width = '100%';
div.style.height = '100%';
self.elt = div;
document.body.appendChild(div);
return () => {
document.body.removeChild(div);
};
}, [self]);
return (render) => (provided, ...args) => {
const element = render(provided, ...args);
if (provided.draggableProps.style.position === 'fixed') {
return createPortal(element, self.elt);
}
return element;
};
};
Usage: Considering the following component:
const MyComponent = (props) => {
return (
<DragDropContext onDragEnd={/* ... */}>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div ref={innerRef} {...droppableProps}>
{props.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.title}
</div>
)}
</Draggable>
)}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
Just call the hook and use the returned function to wrap the children callback of component:
const MyComponent = (props) => {
const renderDraggable = useDraggableInPortal();
return (
<DragDropContext onDragEnd={/* ... */}>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div ref={innerRef} {...droppableProps}>
{props.items.map((item, index) => (
<Draggable key={item.id} draggableId={item.id} index={index}>
{renderDraggable((provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
{item.title}
</div>
))}
</Draggable>
))}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
};
Another potential fix is to add a css rule to override whatever behaviour you've unintentionally set for your draggable:
HTML:
<div className="draggableClassName"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div>
<input
//...etc.
index.css
.draggableClassName {
left: auto !important;
top: auto !important;
}
Okay, I finally figured this out. I feel rather silly. I had originally rendered the Droppable part of my React component tree like so:
<Droppable droppableId={column}>
{(provided, snapShot) => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapShot.isDraggingOver}
tasks={tasks}>
{provided.placeholder} // incorrect
</TaskList>
)}
</Droppable>
This makes no sense as my TaskList component does not take children as a prop. I think I was trying to imitate some sample code that I had seen and I got confused.
The correct way to mark it up for my specific context is:
<Droppable droppableId={column}>
{(provided, snapShot) => (
<>
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapShot.isDraggingOver}
tasks={tasks}
/>
{provided.placeholder} // correct
</>
)}
</Droppable>
The Eureka moment was when I read the warning in the console which said that the provided.placeholder must be a child of the Droppable.
Note
I have to put the provided.placeholder after my TaskList. If it is the first child of the fragment I still have the original problem (the entire list slides down when you drag a new item onto it)

How to remove drop down menu in Transfer ant-design?

I want to use Transfer Component from antd but I don't need the drop down menu as you can see in the below picture . How can I remove it?
It looks like the only way is through css selection since the component API doesn't have any control over it via props. Put below code in your css file:
span.ant-transfer-list-header-dropdown {
display: none;
}
DEMO
EDIT
It's possible to "change" the menu options by setting the selectAllLabels props of the component but you'd have to build the dropdown menu yourself. You'll still have to hide their header dropdown in CSS since you're replacing their menu with your own menu.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Transfer, Dropdown, Menu, Space, Button } from "antd";
import { DownOutlined } from "#ant-design/icons";
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`
});
}
const initialTargetKeys = mockData
.filter((item) => +item.key > 10)
.map((item) => item.key);
const App = () => {
const [targetKeys, setTargetKeys] = useState(initialTargetKeys);
const [selectedKeys, setSelectedKeys] = useState([]);
const onChange = (nextTargetKeys, direction, moveKeys) => {
console.log("targetKeys:", nextTargetKeys);
console.log("direction:", direction);
console.log("moveKeys:", moveKeys);
setTargetKeys(nextTargetKeys);
};
const onSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
console.log("sourceSelectedKeys:", sourceSelectedKeys);
console.log("targetSelectedKeys:", targetSelectedKeys);
setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys]);
};
const onScroll = (direction, e) => {
console.log("direction:", direction);
console.log("target:", e.target);
};
const leftLabel = ({ selectedCount, totalCount }) => (
<Space size={5}>
<Dropdown
placement="bottomLeft"
overlay={
<Menu>
<Menu.Item>
<a>Option 1</a>
</Menu.Item>
<Menu.Item>
<a>Option 2</a>
</Menu.Item>
<Menu.Item>
<a>Option 3</a>
</Menu.Item>
</Menu>
}
>
<DownOutlined style={{ fontSize: 11 }} />
</Dropdown>
{selectedCount > 0 ? `${selectedCount}/${totalCount}` : totalCount} items
</Space>
);
const rightLabel = ({ selectedCount, totalCount }) => (
<Space size={5}>
<Dropdown
placement="bottomLeft"
overlay={
<Menu>
<Menu.Item>
<a>Option A</a>
</Menu.Item>
<Menu.Item>
<a>Option B</a>
</Menu.Item>
<Menu.Item>
<a>Option C</a>
</Menu.Item>
</Menu>
}
>
<DownOutlined style={{ fontSize: 11 }} />
</Dropdown>
{selectedCount > 0 ? `${selectedCount}/${totalCount}` : totalCount} items
</Space>
);
return (
<Transfer
dataSource={mockData}
titles={["Source", "Target"]}
targetKeys={targetKeys}
selectedKeys={selectedKeys}
onChange={onChange}
onSelectChange={onSelectChange}
onScroll={onScroll}
render={(item) => item.title}
selectAllLabels={[leftLabel, rightLabel]}
/>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
DEMO

Resources