In a react app, I dynamically create new components inside of a container. When the height of the container is exceeded by the newly created items, I want the container to become scrollable in the Y direction. I could not achieve the behavior by using overflow: 'auto'. Any suggestions?
sass:
.v-timeline{ // parent component
flex: 1;
height: 100%;
width: 100%;
flex-wrap: nowrap;
.v-timeline-container { // this is our container
.v-timeline-indicator {
z-index: 2;
background-color: rgb(0, 105, 185);
width: 3px;
position: relative;
}
.v-timeline-items { // these are the new items
.v-timeline-item {
height: 60px;
}
}
}
}
The component:
const [gridItems, setGridItems] = useState([{ id: 'firstItemID', info: 'firstItemInfo'}]);
const addRow = () => {
setGridItems([
...gridItems, {
id: uuidv4() ,
info: 'doesnt matter'
}
]);
}
return (
<>
<button onClick={addRow}>Add Row</button>
<div className='v-timeline-container' style={{ overflow: 'auto'}} >
{
gridItems.map(item => (
<div id={`thisTimeline${item.id}`} style={{ display: 'flex', overflow: 'scroll'}} key={`thisItem${item.id}`} className="v-timeline-items v-border">
<Indicator id={item.id} height={60}/>
<div>
<CustomItemContainer id={item.id} info={item.info}/>
</div>
</div>
))
}
</div>
</>
)
This is how it starts:
This is how it goes:
It seems like you didn't set height to the container.
Try to set a fixed height to container.
.v-timeline-container{
height: 100%;
}
Related
I am trying to build a react component with a large fixed div and a navbar below. Initially, the div should remain fixed at the top left and scroll through a number of slides as the user scrolls, before its display property reverts to absolute and it scrolls out of view. Meanwhile, the nav's position becomes sticky and scrolls to the top of the window where it remains.
I've managed to build something hacky using react-intersection-observer and a number of hidden 'ghost' elements which allow the slides to cycle between opacity 0 or 1, but the outcome is not great and I feel there must be an easier way to achieve this effect. In particular it is problematic having to account for the page position when the main component becomes absolute positioned and requires a 'top' value.
Any advice is appreciated thanks. Sample code is below:
import * as React from 'react';
import { useInView } from 'react-intersection-observer';
import styled from 'styled-components';
const OuterContainer = styled.div`
height: 500vh;
`;
const Card = styled.div`
box-sizing: border-box;
position: fixed;
top: 0;
width: 100%;
height:64vh;
transition: 1s;
z-index: 5;
&.hidden {
opacity: 0;
}
&.show {
opacity: 1;
}
&.border {
position: absolute;
top: 0;
}
`;
const AltCard = styled(Card)`
position: static;
`;
const CardContainer = styled.div`
height: 64vh;
`;
const FirstCard = styled(Card)`
opacity: 1;
`;
const CardGhost = styled.div`
height:64vh;
`;
const LastGhost = styled.div`
box-sizing: border-box;
`;
const GhostContainer = styled.div`
width: 100%;
position: absolute;
top: 0;
`;
const MainContainer = styled.div`
position: fixed;
width: 100%;
top: 0;
&.moving {
position: absolute;
top: 156.5vh;
}
`;
const HeaderBar = styled.div`
top: 64vh;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 8rem;
background-color: #44ff44;
`;
export default function IntroSection() {
const options = { threshold: 0.66 };
const [ref0, visible0] = useInView({ threshold: 0 });
const [ref1, visible1] = useInView(options);
const [ref2, visible2] = useInView(options);
const [ref3, visible3] = useInView(options);
const [ref4, visible4] = useInView({ threshold: 0.33 });
return (
<>
<OuterContainer>
<GhostContainer>
<CardGhost ref={ref0} />
<CardGhost ref={ref1} />
<CardGhost ref={ref2} />
<CardGhost ref={ref3} />
<LastGhost ref={ref4} />
</GhostContainer>
<CardContainer>
<FirstCard
className={visible0 ? 'show' : 'hidden'}
style={{ backgroundColor: 'lightBlue' }}
/>
<Card
className={visible1 ? 'show' : 'hidden'}
style={{ backgroundColor: 'lightGreen' }}
/>
<Card
className={visible2 ? 'show' : 'hidden'}
style={{ backgroundColor: 'lightYellow' }}
/>
</CardContainer>
</OuterContainer>
<MainContainer
className={visible4 && 'moving'}
>
<AltCard
className={visible3 ? 'show' : 'hidden'}
style={{ backgroundColor: '#ffBBFF' }}
/>
<HeaderBar><h1>THIS IS THE NAVBAR</h1></HeaderBar>
</MainContainer>
</>
);
}
please how do I make it work the images in the carousel am trying to map don't render or show?
how do I pass props to styled components and map through an array of image objects?
I don't want to create static data of the images in my carousel that is why I created an array of objects to map over the images, but I don't know how to go about it in styled components?
export const data = [
{
id: 1,
imgUrl: "../../utils/images/slide-1.jpg",
},
{
id: 2,
imgUrl: "../../utils/images/slide-2.jpg",
},
{
id: 3,
imgUrl: "../../utils/images/slide-3.jpg",
},
];
import { data } from "../data/data";
const BannerWrapper = styled.div`
min-height: 500px;
/* position: relative;
right: 0;
left: 0; */
margin-top: -140px;
/* bottom: 0; */
z-index: 1;
.caro-bg,
.caro-bg-2,
.caro-bg-1 {
/* background: url(${(props) => props.imgUrl}); */
background-blend-mode: multiply;
#media (max-width: 768px) {**strong text**
background-position: 55% 30%;
object-fit: fill;
text-align: center;
height: 790px;
padding-bottom: 80px;
}
}`enter code here`
<Carousel effect="fade" autoplay>
{data &&
data.map((slide) => (
<div
className="caro-bg"
key={slide.id}
style={{ background: `${slide.imgUrl}` }}
>
<div className="banner-content">
<div className="date-wrap">
{" "}
<div className="save">save</div>
<div className="the-date">the date</div>
<div className="date">
- 19<span className="pink-dot">.</span>12
<span className="pink-dot">.</span>21 -
</div>
</div>
<div>
<div className="banner-text medium light">
<h3>*** WE ARE GETTING MARRIED ***</h3>
</div>
<div className="heart-divider">
<span className="white-line"></span>
<HeartFilled
className="pink-dot"
style={{ fontSize: 18, zIndex: 20 }}
/>
<HeartFilled style={{ fontSize: 20, marginLeft: -5 }} />
<span className="white-line"></span>
</div>
</div>
</div>
</div>
))}
</Carousel>
you can use this data.map((element)=>{ return element.imgUrl this will return the specifique value of the object key .you can use it as a props or creat <img src={imgUrl}/> inside the data.map so every time it iterate ,it will create an img with that specifique url like this:
data.map((el)=>{
return <img src={el.imgUrl}/>})
you can create a component does that.
I have a specific requirement which is to dynamically determine the number of rows & columns needed to be spanned based on image height. I have tried couple of ways, latest one of them is at the end of the question.
Here is the current code that needs suggestions
HTML
<div className="grid">
{
pictures.map((pic,i)=>{
return(
<div key={pic.name+i} className="pic-container"
style={{
gridRowStart: pic.row, gridColumnStart:pic.col,
// Need to put the height & width of each image in the array
// in the following place to span dynamically
gridRowEnd: HEIGHT/50 , gridColumnEnd:8*(WIDTH/1400)}}>
<div>
<img src={`/pictures/${pic.name}.png`} className="pic"/>
<div className="pic-name">{pic.name}</div>
</div>
</div>
);
}
</div>
CSS
.grid{
display: grid;
grid-template-rows: repeat(99, 50px);
grid-template-columns: repeat(8, 1fr);
max-width: 1400px;
gap: 0px;
position: relative;
z-index: 2;
}
.pic-container{
width: 100%;
text-align: center;
display: flex;
align-items: center;
justify-content: center;
}
.pic-container > div{
display: block;
width:100%;
height:100%;
}
.pic{
max-width: 100%;
max-height: 100%;
height: 100%;
object-fit: contain;
}
Latest that I have tried:
const [width,setWidth] = useState(null);
const [height,setHeight] = useState(null);
.
..
...
pictures.map((pic,i)=>{
const getDim = (e) =>{
setWidth(e.target.offsetWidth);
setHeight(e.target.offsetHeight);
}
return(
<div key={''+pic.name+i} className="pic-container"
style={{gridArea:`${pic.row} / ${pic.col} / span ${Math.ceil(height/50)} / span ${Math.ceil(8*(width/1400))`}}>
<div>
<img onLoad={getDim} className="pic" src={`/pictures/${pic.name}.png`}/>
<div className="pic-name">{pic.name}</div>
</div>
</div>
)
})
The idea behind the previous solution is that everytime we get a new image the state of width and height will get updated with the new image dimensions and then will be applied on the span. Next image will update the width and height and then have them applied on its span and so on so forth. But that didn't work.
I hope this maybe help you.
Sandbox demo here
Image List Component:
const ImageList = (props) => {
const images = props.images.map((image) => {
return <ImageCard key={image.id} image={image} />
});
return <div className="image-list">{images}</div>
Image List Css
.image-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-gap:0 10px;
grid-auto-rows: 10px;
}
.image-list img {
width: 250px;
}
ImageCard component:
class ImageCard extends React.Component {
constructor(props) {
super(props);
this.state = {
spans : 0
}
this.imageRef = React.createRef();
}
componentDidMount() {
this.imageRef.current.addEventListener('load', this.setSpans);
}
setSpans = () => {
const height = this.imageRef.current.clientHeight;
const spans = Math.ceil(height / 10);
this.setState({spans});
}
render() {
const {description, urls} = this.props.image;
return (
<div style={{gridRowEnd: `span ${this.state.spans}`}}>
<img ref={this.imageRef} src={urls.regular} alt={description} />
</div>
);
}
I am building a "flash card" component that reveals the backplate on hover. The frontplate will hold an image. I am trying to adapt the class on the image to adapt it for a landscape/portrait type container size so the image always fills the space. Although when the page loads for the first time - the width is coming back as 0. On redirect - the image class doesn't feel correct and fails to fill the space - with the grey bit at the bottom on the right. Looking to make sure the code logic is correct and a way to fix the issue.
css
.flash-card{
width: 100%;
height: 300px;
overflow:hidden;
background: pink;
.show{
display:block;
}
.hide{
display:none;
}
.flash-card-contents{
width: 100%;
height: 100%;
min-height: 100vh;
font-size: 12px;
&.frontcard{
background: $grey;
position: relative;
.flash-img{
width: 100%;
}
}
&.backcard{
background: $white;
position: relative;
padding: 25px;
}
}
&.portrait {
.flash-card-contents.frontcard{
.flash-img{
width: auto;
height: 100%;
}
}
}
&.landscape {
.flash-card-contents.frontcard{
.flash-img{
width: 100%;
height: auto;
}
}
}
}
js
import React, { Component } from 'react';
import Grid from '#material-ui/core/Grid';
import Button from '#material-ui/core/Button';
import './FlashCard.scss';
class FlashCard extends Component {
constructor() {
super();
this.myRef = React.createRef();
this.state = {
isFlashed: false,
orientation: ""
};
this.handleToggle = this.handleToggle.bind(this);
this.updateDimensions = this.updateDimensions.bind(this);
}
handleToggle(e) {
e.preventDefault();
this.setState(prevState => ({ isFlashed: !prevState.isFlashed }));
}
componentDidMount() {
window.addEventListener("resize", this.updateDimensions);
this.updateDimensions();
}
componentWillUnmount() {
window.removeEventListener("resize", this.updateDimensions);
}
updateDimensions(){
console.log("----->>flash width", this.myRef.current.getBoundingClientRect().width);
console.log("----->>flash height", this.myRef.current.getBoundingClientRect().height);
//update the dimensions of the slide for responsiveness
if (this.myRef.current.getBoundingClientRect().width > this.myRef.current.getBoundingClientRect().height){
//it's a landscape
this.setState({
orientation: "landscape"
});
} else if (this.myRef.current.getBoundingClientRect().width < this.myRef.current.getBoundingClientRect().height){
//it's a portrait
this.setState({
orientation: "portrait"
});
} else {
//image width and height are equal, therefore it is square.
this.setState({
orientation: "square"
});
}
}
render() {
return (
<div
ref={this.myRef}
className={"flash-card " + this.state.orientation}
style={{
height: (this.props.height? this.props.height: "auto")
}}
>
<div className={this.state.isFlashed? 'hide': 'show'} onMouseOver={this.handleToggle}>
<div className="flash-card-contents frontcard">
<img className="flash-img" src={this.props.image} alt="" />
</div>
</div>
<div className={this.state.isFlashed? 'show': 'hide'} onMouseLeave={this.handleToggle}>
<div className="flash-card-contents backcard">
<Grid container spacing={1}>
<Grid item xs={12} sm={12}>
<h3>{this.props.header}</h3>
<h4>{this.props.subheader}</h4>
</Grid>
<Grid item xs={12} sm={12}>
{this.props.body}
</Grid>
<Grid item xs={12} sm={12}>
<Button
className="flash-card-button"
variant="contained"
color="primary"
href={this.props.button.link}
>
{this.props.button.label}
</Button>
</Grid>
</Grid>
</div>
</div>
</div>
);
}
}
export default FlashCard;
i want to place a popup based on the position of leftnav element. basically place this popup next to the lefnav element such that it is vertically center to leftnav element.
below is the picture of how the popup should be placed
below is my code,
function LeftNav() {
return (
<Wrapper id="left_nav">
</Wrapper>
);
}
const Wrapper = styled.div`
position: absolute;
top: 50%;
right: 16px;
display: flex;
flex-direction: column;
flex: 1;
`;
function PopUp() {
const left_nav_el = document.getElementById('left_nav');
return (
<PopupWrapper />
);
}
const PopupWrapper = styled.div`
width: 400px;
position: relative;
display: none;
`;
i am not sure how to get the top-left corner of the leftnav and use it as coordinates to place the popup in center and 16px away to the leftnav.
could someone help me with this. thanks.
const App = () => {
function LeftNav() {
return (
<div
style={{
height: "50px",
background: "black",
width: "50px"
}}
id="left_nav"
/>
);
}
function PopUp() {
return (
<div
style={{
marginRight: "16px",
height: "100px",
background: "green",
width: "50px"
}}
className="popup"
/>
);
}
return (
<div
style={{ width: "100px", display: "flex", alignItems: "center"}}
className="App"
>
<LeftNav />
<PopUp />
</div>
);
}