React: Overlay on hover without outer container for element - javascript

I am developing no-code platform where you can drag and drop HTML elements.
2 things are required:
user can SELECT each element
user can style each element through right-side panel (width, length, font-size, ...)
In order for 1) I have used this solution:
Selector.js
export default Selector = ({children}) => {
const [isHovered, setIsHovered] = useState(false);
const mouseOverHandler = (ev) => {
ev.stopPropagation();
setIsHovered(true);
}
const mouseOutHandler = (ev) => {
ev.stopPropagation();
setIsHovered(false);
}
return (
<div className="container" onMouseOver={mouseOverHandler} onMouseOut={mouseOutHandler}>
{children}
{ isHovered && <div className="overlay"></div> }
</div>
);
}
Selector.css
.container {
position: relative; // For .overlay to be absolute
max-width: fit-content; // To fit children/content size
}
.overlay {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(122, 122, 122, 0.2);
}
App.js
export default App = () => [
return (
<div className="workspace">
<Selector><Input style={{width: "100%"}} /></Selector>
<Selector><Block style={{width: "100px"}} /></Selector>
<Selector><Block style={{width: "100%"}} /></Selector>
</div>
);
}
THE PROBLEM is when I try to set width:100% on Block component, for example. I want Block to stretch full width in .workspace, but because Block is in Selector, it will have .container as parent so it will fill only that element, not .workspace. With this, I am unable to fulfill 2) requirement.
You can check it here on Fiddle: https://jsfiddle.net/49cmok2a/2/.
If you set width to 100px on Block it's gonna be block with 100px width, but if you put 100% width, it will just shrink to 1px.
Do you know how to create Selector, so it doesn't create parent div on elements. Thanks!

Your problem is that you are trying to overwrite the relative style in the child. You can easy control your Selector's style width just in 2 actions:
First of all, you have to change your max-width: object-fit, this property is used primarily for img or video tags. Change it's property like i did
.container{
position: relative;
max-width: 100%;
cursor: pointer;
}
Next time you can easy change you Selector's width.
I hope i've got your question correctly, have a good day!

If you want only the the square you can use 100vw insted of 100% :
const App = () => {
return (
<div className="workspace">
<Selector><Input style={{width: "100%"}} /></Selector>
<Selector><Block style={{width: "100vw"}} /></Selector>
</div>
);
}
Or make the .workspace 100% of the his parent, make the selector to take 100% of his parent (.workspace) and make the Block or the Input the sizess you want them to be :
<div className="workspace" style={{width: "100%"}} >
<Selector style={{width: "100%"}} ><Input /></Selector>
<Selector style={{width: "100%"}} ><Block/></Selector>
</div>
but you need max width to be 100% o n the css file.
max-width: 100%;

Related

How do I expand div smoothly when new content is added

How do I make a div expand smoothly when content is added?
const [render, setRender] = useState(false)
const showHide= () => {
setRender(!render)
}
return (
<div className="container">
<h1>TEST CONTAINER</h1>
{render && <Paragprah />}
<button type="button" class="btn btn-primary" onClick={showHide}>Primary</button>
</div>
);
}
CSS
.container {
margin-left: 30%;
margin-top: 10%;
width: 500px;
height: auto;
background-color: whitesmoke;
}
In the above, a component is being rendered when a button is clicked, when the new content is added to the div, it expands instantly. basically, I would like it to expand smoothly when the content is added. Here is a video as well by what I mean when it expands instantly.
Setting a fixed height wont work in my situation because the content that's being loaded is dynamic, its coming from an API and the length is different everytime. So setting a fixed height will make the content overflow sometimes. I need a way where the transition can occur smoothly and the height still be large enough to fit the content, which would probably require ```height: auto;`` to be present?
Link to Imgur video
you can use the transition css property for a smooth transition.
transition: height 2s;
Follow this link for reference.
Here I updated the code. Since the content that's being loaded has dynamic size then you have to track that content size using useRef hook and store it on a variable and export it with your component.
import { useRef, useEffect} from "react";
var ParagraphHeight;
function Paragraph() {
const ref = useRef(null);
useEffect(() => {
//when you content is loaded store its height
ParagraphHeight = ref.current.offsetHeight;
}, []);
return (
<div ref={ref}>
<p>Your content here for exemple...</p>
</div>
);
}
export { Paragraph, ParagraphHeight };
Now import your Paragraph component and put it inside a .wrapper div that has a default height of 0px (that means your Paragraph will be hidden) and when you click on the button the height of it will change to the ParagraphHeight
import { useState } from "react";
import { Paragraph, ParagraphHeight } from "./Paragraph.js";
export default function App() {
const [isopned, setisopned] = useState(false);
const [wrapperHeight, setWH] = useState(0);
const showHide = () => {
if (!isopned) setWH(ParagraphHeight);
else setWH(0);
setisopned(!isopned);
};
return (
<div className="container">
<h1>TEST CONTAINER</h1>
<div className="wrapper" style={{ height: wrapperHeight }}>
<Paragraph />
</div>
<button type="button" className="btn btn-primary" onClick={showHide}>
Primary
</button>
</div>
);
}
Finally don't forget to update your css, add overflow-y hidden and css transition to to expand your wrapper div smoothly.
.container {
margin-left: 30%;
margin-top: 10%;
width: 500px;
background-color: whitesmoke;
}
.wrapper {
display: grid; /*to prevent some problem caused by overflow hidden*/
transition: height 1s linear;
background-color: wheat;
overflow-y: hidden;
}
This is a live exemple demo

How to make a box expand and transition from origin to center of screen without affecting DOM?

I'm trying to make it so that a box would expand (in width and height) and transition from its origin to the center of a screen upon being clicked. Here's what I have so far:
I'm running into two problems here -- when I click on the box, the DOM automatically shifts, because the clicked element has its position changed to 'absolute'. The other problem is that the box doesn't transition from its origin, it transitions from the bottom right corner (it also doesn't return to its position from the center of the screen, when make inactive is clicked).
What am I doing wrong here?
import React from "react";
import styled from "styled-components";
import "./styles.css";
export default function App() {
const [clickedBox, setClickedBox] = React.useState(undefined);
const handleClick = React.useCallback((index) => () => {
console.log(index);
setClickedBox(index);
});
return (
<Container>
{Array.from({ length: 5 }, (_, index) => (
<Box
key={index}
active={clickedBox === index}
onClick={handleClick(index)}
>
box {index}
{clickedBox === index && (
<div>
<button
onClick={(e) => {
e.stopPropagation();
handleClick(undefined)();
}}
>
make inactive
</button>
</div>
)}
</Box>
))}
</Container>
);
}
const Container = styled.div`
display: flex;
flex-wrap: wrap;
align-items: center;
height: 100vh;
`;
const Box = styled.div`
flex: 1 0 32%;
padding: 0.5rem;
cursor: pointer;
margin: 1rem;
border: 1px solid red;
transition: 2s;
background-color: white;
${({ active }) => `
${
active
? `
position: absolute;
width: 50vw;
height: 50vh;
background-color: tomato;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`
: ""
}
`}
`;
With CSS
Wery unlikely you can achieve that with plain css. And for sure impossible to achieve a versatile solution.
You have a dynamic size and position to adopt to (starting div)
You have to adapt to the current scrolling position
If you remove the div from the layout is almost impossible to avoid screwing up the layout (even if you can, there will always be some edge case).
transition from a relative to a fixed position.
With the current css standard is impossible to perform these things.
With JS
The solution is to do some javascript magic. Since you are using React i developed you a solution using react-spring (an animation framework). Here you have a wrapping component that will do what you want:
The complete SandBox
import React, { useEffect, useRef, useState } from "react";
import { useSpring, animated } from "react-spring";
export default function Popping(props) {
const cont = useRef(null);
const [oriSize, setOriSize] = useState(null);
const [finalSize, setFinalSize] = useState(null);
useEffect(() => {
if (props.open && cont.current) {
const b = cont.current.getBoundingClientRect();
setOriSize({
diz: 0,
opacity: 0,
top: b.top,
left: b.left,
width: b.width,
height: b.height
});
const w = window.innerWidth,
h = window.innerHeight;
setFinalSize({
diz: 1,
opacity: 1,
top: h * 0.25,
left: w * 0.25,
width: w * 0.5,
height: h * 0.5
});
}
}, [props.open]);
const styles = useSpring({
from: props.open ? oriSize : finalSize,
to: props.open ? finalSize : oriSize,
config: { duration: 300 }
});
return (
<>
<animated.div
style={{
background: "orange",
position: "fixed",
display:
styles.diz?.interpolate((d) => (d === 0 ? "none" : "initial")) ||
"none",
...styles
}}
>
{props.popup}
</animated.div>
<div ref={cont} style={{ border: "2px solid green" }}>
{props.children}
</div>
</>
);
}
Note: This code uses two <div>, one to wrap your content, and the second one is always fixed but hidden. When you toggle the popup visibility, the wrapping div gets measured (we obtain its size and position on the screen) and the fixed div is animated from that position to its final position. You can achieve the illusion you are looking for by rendering the same content in both <div>, but there is always the risk of minor misalignment.
The idea is similar to what newbie did in their post but without any extra libraries. I might have done some things a bit non-standard to avoid using any libraries.
CodeSandbox
import React from "react";
import { StyledBox } from "./App.styles";
export const Box = (props) => {
const boxRef = React.useRef(null);
const { index, active, handleClick } = props;
const handleBoxClick = () => {
handleClick(index);
};
React.useEffect(() => {
const b = boxRef.current;
const a = b.querySelector(".active-class");
a.style.left = b.offsetLeft + "px";
a.style.top = b.offsetTop + "px";
a.style.width = b.offsetWidth + "px";
a.style.height = b.offsetHeight + "px";
});
return (
<StyledBox active={active} onClick={handleBoxClick} ref={boxRef}>
box {index}
<div className="active-class">
box {index}
<div>
<button
onClick={(e) => {
e.stopPropagation();
handleClick(undefined);
}}
>
make inactive
</button>
</div>
</div>
</StyledBox>
);
};
import styled from "styled-components";
export const StyledContainer = styled.div`
display: flex;
flex-wrap: wrap;
align-items: center;
height: 100vh;
`;
export const StyledBox = styled.div`
flex: 1 0 32%;
padding: 0.5rem;
cursor: pointer;
margin: 1rem;
border: 1px solid red;
background-color: white;
.active-class {
position: absolute;
transition: 0.3s all ease-in;
background-color: tomato;
z-index: -1;
${({ active }) =>
active
? `
width: 50vw !important;
height: 50vh !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%);
z-index: 1;
opacity: 1;
`
: `
z-index: -1;
transform: translate(0, 0);
opacity: 0;
`}
}
`;
first, transition with position or display don't work on css(it can work but without transition).
here you have:
flex: 1 0 32%;
that is equivalent to :
flex-grow: 1;
flex-shrink: 1;
flex-basis: 32%;
so, when active is true, width would jump to 50vw and height to 50vh but roughly without transition. so the solution is to use scale like this:
z-index: 99;
transform: scaleY(5) scaleX(2) translate(20%, 20%);
background-tomato: tomato
and you need to tweak the values of scaleY, scaleX and translate (for each Box) until you get it to work.
you can take a look at what i did in this codesandbox: https://codesandbox.io/s/peaceful-payne-ewmxi?file=/src/App.js:1362-1432
here is also a link if you want master flex: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Controlling_Ratios_of_Flex_Items_Along_the_Main_Ax
be sure that all your items have the following css properties : transform: translateX(0) translateY(0); transition: transform Xms timing-function-of-your-choice, left Xms timing, top Xms timing;
Until your page is completly loaded, let all the item in your page have the css property : position: static.
When page loads, retrive the items' properties : x offset from left of screen, y offset from top of document, width and height.
Use javascript to change the items properties : set position to fixed and affect the left, top, width and height css properties from the values we just retrieved. this way, the items will keep their exact position after their position property changes.
with javascript, when the box is clicked on, to center it inside your page, just apply the following css properties via javascript : left: 50%; top: 50%; transform: translateX(-50%) translateY(-50%); width: the width of your choice; height: the height of your choice;
This way, your item will move in the center of your screen no matter what their width and origin offset were. Also, the transition will be very smooth.
You might also want to change the z-index of an item when it is clicked.

How to override one page over other in Reactjs?

I want to create pages which can override over another pages in React. Like see for an example here in Youtube: https://youtu.be/n5kr99DAjDk?t=441. The 'Post' page is opened with full height and a 'Back' button. This was easily possible in React Native. How could it be done in React for web.
Below is the code for reference:
App.js
const Routing = () => {
return (
<Router>
<TopNavbar />
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/newPage" component={NewPage} />
</Switch>
<BottomNavbar />
</Router>
);
};
Home.js
const Home = () => {
return (
<div style={{ marginTop: "80px", position:'relative' }}>
<h2>Home Page</h2>
<Link to="/newPage">
<button>Click Here</button>
</Link>
</div>
);
};
export default Home;
NewPage.js
const NewPage = () => {
return (
<div className="newPageCSS">
<h2>New Page</h2>
</div>
);
};
export default NewPage;
NewPage.css
.newPageCSS {
margin-top: 80px;
position: absolute;
top:0;
left: 0;
}
I have tried position:absolute in NewPage but, no use. What could be best possible solution?
Please have a look at the given below codesandbox link for clarity of code.
Here is the codesandbox link: https://codesandbox.io/s/react-material-forked-9dodd
It sounds like you'd like your new page component to take up all the room of the page except for top and bottom navigation.
The solution is entirely CSS, however you'll need to tinker with styles to get margins/padding correct. It looks like you're using a UI library which can clash with your style sheet.
Here are the changes:
Update your main container to use flexbox instead of trying to control for top/bottom navs with pixels
#root {
display: flex;
min-height: 100vh;
flex-direction: column;
}
Then, get your new page component to expand by adding a flex 1. Added red border to easily visualize change.
.newPageCSS {
margin-top: 80px;
border: 2px solid red;
flex: 1;
}
Update your home page so bottom nav sticks to bottom and you can scroll home content
.homePageCSS {
margin-top: 80px;
max-height: 50%;
/* position: relative; */
flex: 1;
max-height: 70vh;
overflow: auto;
}
Position fixed is now unnecessary for bottom nav
.makeStyles-gridList-2 {
width: 100%;
border: 1px solid grey;
/* bottom: 0px; */
/* position: fixed; */
padding: 0;
flex-wrap: nowrap;
background: white;
}

How to disable click events when execution is going on in React?

I have a screen
where I want to disable all the events when execution is going on.
When I click on the Execute button, an API is called which probably takes 4-5 minutes to respond. During that time, I don't want the user to click on the calendar cells or Month navigation arrows.
In short, I want to disable all the events on the center screen but not the left menu.
Is it possible to do that?
Yes sure, you can add a class with css pointer-events rule. Set it on the whole table and it will disable all events. Just add it when request starts and remove it when it ends. You can achieve that, by having a boolean variable isLoading in your state, or redux store and based on that add or remove the no-click class.
.no-click {
pointer-events: none;
}
You can use classic loading overlay box over your content when some flag (i.e. loading) is true.
Other way to do it is to use pointer-event: none in CSS. Use same flag to set class to your content block.
Here is a working example in codesanbox:
https://codesandbox.io/s/determined-dirac-fj0lv?file=/src/App.js
Here is code:
export default function App() {
const [loadingState, setLoadingState] = useState(false);
const [pointerEvent, setPointerEvent] = useState(false);
return (
<div className="App">
<div className="sidebar">Sidebar</div>
<div
className={classnames("content", {
"content-pointer-event-none": pointerEvent
})}
>
<button onClick={() => setLoadingState(true)}>
Show loading overlay
</button>
<button onClick={() => setPointerEvent(true)}>
Set pointer event in css
</button>
{loadingState && <div className="loading-overlay"></div>}
</div>
</div>
);
}
.App {
font-family: sans-serif;
text-align: center;
display: flex;
position: absolute;
width: 100%;
height: 100%;
}
.content {
flex: 1;
position: relative;
}
.loading-overlay {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.4);
z-index: 1;
}
.content-pointer-event-none {
pointer-events: none;
}

Changing elements (like fixed logo) color, depending on the section in React

I want to achieve an effect like this one
in a React webpage but without using jQuery. I've looked for alternatives to that library, but without results. I've seen a lot of similar questions, but each of them are answered using jQuery.
The effect is basically changing the color of the logo (and other elements in the page) as I scroll down through different sections.
Does anyone know a way to achieve this?
A way this could be done is by centering the logo's to their own containers dynamically, kinda like simulating position fixed, but using position absolute, so each logo is contained in their own section, and not globally like position fixed would do.
This way when you scroll to the next section, the second section covers the first section making it look like its transitioning.
I created a proof of concept here:
https://codesandbox.io/s/9k4o3zoo
NOTE: this demo is a proof of concept, it could be improved in performance by using something like request animation frame, and throttling.
Code:
class App extends React.Component {
state = {};
handleScroll = e => {
if (!this.logo1) return;
const pageY = e.pageY;
// 600 is the height of each section
this.setState(prevState => ({
y: Math.abs(pageY),
y2: Math.abs(pageY) - 600
}));
};
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
render() {
const { y, y2 } = this.state;
return (
<div>
<section className="first">
<h1
className="logo"
style={{ transform: `translateY(${y}px)` }}
ref={logo => {
this.logo1 = logo;
}}
>
YOUR LOGO
</h1>
</section>
<section className="second">
<h1
className="logo"
style={{ transform: `translateY(${y2}px)` }}
ref={logo => {
this.logo2 = logo;
}}
>
YOUR LOGO
</h1>
</section>
</div>
);
}
}
CSS would be:
section {
height: 600px;
width: 100%;
position: relative;
font-family: helvetica, arial;
font-size: 25px;
overflow: hidden;
}
.first {
background: salmon;
z-index: 1;
}
.first .logo {
color: black;
}
.second {
background: royalBlue;
z-index: 2;
}
.second .logo {
color: red;
}
.logo {
position: absolute;
margin: auto;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 230px;
height: 30px;
}

Categories