I have a simple section in which I want to change the width dynamically
Here is section
<div className="form-control">
<Formats>
{styles.map(style => <ButtonStyled type="button" active={style.id === styleId} onClick={() => setStyleId(style.id)} key={`button-${style.name}`}>{style.name}</ButtonStyled>)}
{activeStyle && <FormatsContent>
<FormatsDescription>{activeStyle.desciption}</FormatsDescription>
<Proportions>
{activeStyle.proportions.map(x => <Proportion onClick={() => setProportion(x.proportion)} active={proportion === x.proportion} width={x.width} height={x.height} key={`proportion-${x.proportion}`}>{x.proportionLabel}</Proportion>)}
</Proportions>
</FormatsContent>}
</Formats>
</div>
Here is style
const Formats = styled.div`
margin-left: 0px;
padding:12px;
background-color: ${gray.brightest};
border-radius: 20px;
width: 100%;
& select {
height: 31px;
padding: 0;
margin-left: 12px;
}
`;
Now I want to add props to check if width is full or not isFullWidth:true/false
and on styles to use it like this,
const Formats = styled.div`
margin-left: 0px;
padding:12px;
background-color: ${gray.brightest};
border-radius: 20px;
width: ${props => props.isFullWidth ? '100%' : '59%'}
& select {
height: 31px;
padding: 0;
margin-left: 12px;
}
`;
What do I need to change to achieve what I want?
The Format component should have isFullWidth prop holding either true or false as value as shown below:
The example below will give a 100% width
<Format isFullWidth ={true}>
</Format>
And this one will give a 59% width
<Format isFullWidth ={false}>
</Format>
I think it is enough to pass the prop to the component and keep the last style you posted:
So it should be
<Formats isFullWidth>
...
</Formats>
EDIT: wrong prop name
Related
I'm trying to implement like function by using redux and map function to use states in other components. I used redux not using useState([]) because I thought it is better way to manage states. I made the button changed when button is clicked. But they are sharing same state so it is changed at the same time. To solve this problem, I think I should add this state in array. And here comes my question. How can I add state declared in redux to manage state separately? And is this way correct? I'll upload concrete code including redux by using codesandbox. I'd appreciate it if you let me know, thanks.
Clothes
import React, { useState } from "react";
import styled from "styled-components";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faHeart } from "#fortawesome/free-solid-svg-icons";
import { faHome } from "#fortawesome/free-solid-svg-icons";
import { useDispatch, useSelector } from "react-redux";
const array = [
{
id: "1",
name: "vest",
img:
"https://shopimg.kakaofriendsgolf.com/live/images/2022/9/7/10/918997_1662515279620.png",
price: "10000"
},
{
id: "2",
name: "shirts",
img:
"https://shopimg.kakaofriendsgolf.com/live/images/2022/8/23/18/551886_1661246483199.png",
price: "12000"
},
{
id: "3",
name: "pants",
img:
" https://shopimg.kakaofriendsgolf.com/live/images/2022/8/22/18/18783_1661159105201.png",
price: "15000"
}
];
export default function Clothes() {
const isClick = useSelector((state) => state.CheckLike.isClick);
const dispatch = useDispatch();
// change Icon
const setHide = (e) => {
dispatch({ type: "False" });
};
const setShow = (e) => {
dispatch({ type: "True" });
};
return (
<Wrap>
<div className="productCard">
{array.map((content, idx) => {
return (
<div key={idx} className="productCard__wrap">
<img src={content.img} className="productCard__img" />
<div className="productCard__name">
<div>
<h3 className="productCard__title">{content.name}</h3>
<div className="productCard__priceWrap">
{content.price
.toString()
.replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
</div>
</div>
<div className="productCard__likeWrap">
{/* show heart and house icon according to state */}
{isClick ? (
<div
onClick={() => {
setHide();
}}
>
<FontAwesomeIcon icon={faHeart} />
</div>
) : (
<div
onClick={() => {
setShow();
}}
>
<FontAwesomeIcon icon={faHome} />
</div>
)}
</div>
</div>
</div>
);
})}
</div>
</Wrap>
);
}
const Wrap = styled.div`
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: scroll;
.productCard {
display: flex;
position: absolute;
top: calc(50vh - 120px);
left: calc(50vw - 180px);
width: "228px";
height: "351px";
padding-right: 8px;
padding-left: 8px;
border: solid 1px black;
}
.productCard__wrap {
border: 1px solid grey;
line-height: 1.428571429;
background-color: #fff;
}
.productCard__img {
width: 228px;
height: 228px;
}
.productCard__name {
padding-bottom: 30px;
position: relative;
padding: 8px 8px 0 8px;
min-height: 118px;
text-align: left;
}
.productCard__title {
font-size: 18px;
margin-top: 0;
margin-bottom: 2px;
padding-right: 37px;
line-height: 1.3;
margin-block-start: 1em;
margin-block-end: 1em;
}
.productCard__priceWrap {
font-size: 18px;
margin-top: 5px;
vertical-align: middle;
margin: 0 0 9px;
margin-block-start: 1em;
margin-block-end: 1em;
text-align: left;
}
.productCard__likeWrap {
position: absolute;
top: -5px;
right: 0;
padding: 0;
height: 40px;
line-height: 37px;
white-space: normal;
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
}
`;
codesandbox
https://codesandbox.io/s/likepractice-tpcv7l?file=/src/components/Clothes.jsx
There could be many approaches, but assuming that the goal is to make the like button work for individual items and save the liked items in the store, I think the solution of using an array as a state is suitable in the use case.
Forked demo with modification: codesandbox
Configure the state as an array and create matching reducers, here the product id is saved to the state array when liked, but this is an optional approach.
const initailState = {
isClick: [],
};
const CheckLike = (state = initailState, action) => {
switch (action.type) {
case "True":
return {
...state,
isClick: [...state.isClick, action.id],
};
case "False":
return {
...state,
isClick: state.isClick.filter((id) => id !== action.id),
};
default:
return {
...state,
};
}
};
Edit the event handler to dispatch the item id to be added or removed from the state array:
const setHide = (id) => {
dispatch({ type: "False", id });
};
const setShow = (id) => {
dispatch({ type: "True", id });
};
Check if product id content.id is included in the state array as a condition for rendering the icons, and wire up the icons to send content.id to the events:
<div className="productCard__likeWrap">
{isClick.includes(content.id) ? (
<div
onClick={() => {
setHide(content.id);
}}
>
<FontAwesomeIcon icon={faHeart} />
</div>
) : (
<div
onClick={() => {
setShow(content.id);
}}
>
<FontAwesomeIcon icon={faHome} />
</div>
)}
</div>
if I'm guessing correctly your problem is that the button click changes all the buttons.
for this issue, you don't need redux. use redux when you want to manage a global state used in multiple components(ex, session).
solution for your problem is, create a new component for each product card and there manage the state of the button.
and to store which products are on the wishlist you can store that in redux.
I'm following this React tutorial here: https://ibaslogic.com/how-to-edit-todos-items-in-react/ to build a simple TO DO app.
I've also reviewed Why onDoubleClick event is not working in React.js? but there's no onclick event to worry about in my example.
My onDoubleClick event should call a function handleEditing but nothing happens when I double click a list item.
I'm unsure of why it does not work (the web browser does not seem to register a double click event.
Below is my example:
import React from "react";
import styles from "./TodoItem.module.css";
class TodoItem extends React.Component {
state = {
editing: false,
};
handleEditing = () => {
console.log("doubleClick")
this.setState({
editing: true,
});
};
render() {
const completedStyle = {
fontStyle: "italic",
color: "#595959",
opacity: 0.4,
textDecoration: "line-through",
};
const { completed, id, title } = this.props.todo;
let viewMode = {}
let editMode = {}
if (this.state.editing) {
viewMode.display = "none"
} else {
editMode.display = "none"
}
return (
<li className={styles.item}>
<div onDoubleClick={this.handleEditing} style={viewMode}>
<input
type="checkbox"
className={styles.checkbox}
checked={completed}
onChange={() => this.props.handleChangeProps(id)}
/>
<button onClick={() => this.props.deleteTodoProps(id)}>Delete</button>
<span style={completed ? completedStyle : null}>{title}</span>
</div>
<input type="text" style={editMode} className={styles.textInput} />
</li>
);
}
}
export default TodoItem;
I don't think this is relevant, but here is my css:
.item {
font-size: 1.2rem;
list-style-type: none;
padding: 17px 0px;
border-bottom: 1px solid #eaeaea;
}
.checkbox {
margin-right: 15px;
}
.item button {
font-size: 13px;
background: #f1f3f4;
border: none;
cursor: pointer;
float: right;
outline: none;
border-radius: 100px;
height: 50px;
width: 50px;
margin: -10px 0 0 10px;
}
.textInput {
width: 100%;
padding: 10px;
border: 1px solid #dfdfdf;
}
onDoubleClick works when your dev tool is not opened
Updated answer:
As found out in the comments, the problem was a combination of OS and Browser. Windows / Chrome in this example.
Old answer:
I haven't read into much detail, but the first difference I can spot is that in your code the handleEditing is not bound. Which should not prevent the output of your console.log. Does it appear?
onDoubleClick={this.handleEditing.bind(this)}
Hope this helps in your case.
I am facing a weird CSS issue in my React project. A particular part of the JSX <div> has a class applied to it and added some style properties in the main .css file of the project. In local development, everything works fine but as soon as the build is created and uploaded to the production server, that particular part of the JSX <div> CSS class changes and the styling gets distorted.
Example:
Original JSX
import React, { useEffect, useState, useContext } from "react";
import Tooltip from "#material-ui/core/Tooltip";
import { withStyles, makeStyles } from "#material-ui/core/styles";
import Slider from "#material-ui/core/Slider";
const useStyles = makeStyles((theme) => ({
root: {
width: 450,
},
margin: {
height: 100,
},
}));
const PrettoSlider = withStyles({
root: {
color: "red",
height: 8,
},
thumb: {
height: 24,
width: 24,
backgroundColor: "#fff",
border: "2px solid currentColor",
marginTop: -8,
marginLeft: -12,
"&:focus,&:hover,&$active": {
boxShadow: "inherit",
border: "2px solid #fff407 !important",
},
},
active: {
backgroundColor: "#fff407",
},
})(Slider);
const CustomizedSlider = ({
id,
abbr,
type,
minElig,
maxElig,
}) => {
useEffect(() => {
setValue(sliderPreviousValue);
}, [sliderPreviousValue]);
const classes = useStyles();
return (
<>
<div className={classes.root}>
{type === "intervention" ? (
<ProgressBar max={maxElig} value={sliderValue} />
) : null}
{renderSlider}
</div>
</>
);
};
Original DOM:
<div class="diabMetr clearfix">
<span class="diabLabl">Diabetes</span>
<div class="makeStyles-root-1">
<span class="MuiSlider-root WithStyles(ForwardRef(Slider))-root-3 MuiSlider-colorPrimary"><span class="MuiSlider-rail WithStyles(ForwardRef(Slider))-rail-8"></span><span class="MuiSlider-track WithStyles(ForwardRef(Slider))-track-7" style="left: 0%; width: 83.3333%;"></span><input type="hidden" value="200"><span class="MuiSlider-thumb WithStyles(ForwardRef(Slider))-thumb-4 MuiSlider-thumbColorPrimary PrivateValueLabel-open-12 PrivateValueLabel-thumb-11" tabindex="0" role="slider" data-index="0" aria-label="pretto slider" aria-orientation="horizontal" aria-valuemax="240" aria-valuemin="0" aria-valuenow="200" style="left: 83.3333%;"><span class="PrivateValueLabel-offset-13 MuiSlider-valueLabel WithStyles(ForwardRef(Slider))-valueLabel-6"><span class="PrivateValueLabel-circle-14"><span class="PrivateValueLabel-label-15">200</span></span></span></span></span>
<div class="valueOuter clearfix"><label class="valueLeft">0</label><label class="valueRight">240</label></div>
</div>
</div>
The CSS for this JSX is:
.diabMetr {
padding-top: 10px;
span.diabLabl {
display: inline-block;
width: 200px;
text-align: left;
font-size: 12px;
line-height: 30px;
text-align: right;
#include respond-to(media-xl) {
width: 120px;
}
}
span.MuiSlider-root {
width: 100%;
padding: 0;
height: 0px;
.MuiSlider-rail {
height: 30px;
border-radius: 15px;
background: #e8e8e8;
opacity: 1;
}
.MuiSlider-track {
height: 30px;
background: #88d479;
border-radius: 15px;
}
.MuiSlider-thumb {
z-index: 12;
width: 35px;
height: 35px;
border-radius: 50%;
margin-left: -17px;
border: #88d479 solid 2px;
margin-top: -3px;
}
.MuiSlider-markLabel.MuiSlider-markLabelActive:last-child() {
right: 0 !important;
}
}
}
.makeStyles-root-1 {
width: calc(100% - 220px) !important;
float: right;
margin-top: -22px;
}
The DOM changes after build and uploaded to the server:
<div class="diabMetr clearfix">
<span class="diabLabl">Diabetes</span>
<div class="jss16">
<span class="MuiSlider-root jss18 MuiSlider-colorPrimary"><span class="MuiSlider-rail jss23"></span><span class="MuiSlider-track jss22" style="left: 0%; width: 83.3333%;"></span><input type="hidden" value="200"><span class="MuiSlider-thumb jss19 MuiSlider-thumbColorPrimary jss27 jss26" tabindex="0" role="slider" data-index="0" aria-label="pretto slider" aria-orientation="horizontal" aria-valuemax="240" aria-valuemin="0" aria-valuenow="200" style="left: 83.3333%;"><span class="jss28 MuiSlider-valueLabel jss21"><span class="jss29"><span class="jss30">200</span></span></span></span></span>
<div class="valueOuter clearfix"><label class="valueLeft">0</label><label class="valueRight">240</label></div>
</div>
</div>
The CSS for the class .jss16 is:
.jss16 {
width: 450px;
}
Issue to notice
Only the class .makeStyles-root-1 gets replaced with some random class .jss16 when the build gets uploaded to the server and the CSS changes accordingly, the rest of the JSX remains unchanged. I tried searching for the class .jss16 everywhere in the code, but it's not found. Also, everything works fine on localhost.
I tried adding the CSS properties to the .jss16 like this:
.jss16 {
width: 450px;
width: calc(100% - 220px) !important;
margin-top: -22px;
float: right;
}
and then re-initiate the uploading process but then instead of .jss16, another class is replaced something like .jss42. This keeps on repeating and does not work on any new build created.
I also tried the following CSS:
.diabMetr + span + div {
width: 450px;
width: calc(100% - 220px) !important;
margin-top: -22px;
float: right;
},
but this also didn't help. The styling of the app still remains distorted (incorrect, not as on localhost).
I spent several hours searching for this but in vain. If anyone can assist me in understanding this error and resolve the same, will be highly appreciated. Thanks in advance!
there are quite a few issues with this code. First in jsx CSS class is given as className as #Max has mentioned in his/her answer.
Another issue is that #material-ui's makeStyle doesn't work in this way. The classNames inside the makeStyle change to random names in the build stage. This happens to keep the classNames uniques, this is #material-ui's feature. I'd suggest you to read this #matrial-ui's documentation about makeStyles. And here a code example is provided.
To use makeStyles classes you've to hook it into your component.
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles({
root: {
backgroundColor: 'red',
color: props => props.color,
},
});
export default function MyComponent(props) {
const classes = useStyles(props);
return (
<div className={classes.root}>
Lorem iosum poder
</div>
);
}
Update
According to your jsx code, add the styles which you've added in css class .makeStyles-root-1 in the useStyles object. It'll add the styles to the element.
After adding those CSS styles in useStyles this object will look like this:-
const useStyles = makeStyles((theme) => ({
root: {
width: 'calc(100% - 220px) !important',
float: 'right',
marginTop: '-22px'
},
margin: {
height: 100,
},
}));
The root class will contain those styles and it'll be applied without providing the styles separately from the CSS file.
Solution
I am not sure what component creates the div with "jss16" class, assume it is ExternalComponent.
You should add a custom className (assuming ExternalComponent handles this correctly):
<ExternalComponent className="myclass">
...
</ExternalComponent>
this will create a DOM like this:
<div class="jss16 myclass">
...
</div>
Sou you can create css for myclass:
.myclass {
width: calc(100% - 220px) !important;
float: right;
margin-top: -22px;
}
Explanation
ExternalComponent uses jss to dynamically generate css classes, so you cant rely on the name of the dynamically generated class. In most of the cases, components with custom classes should append props.className to the generated jss like this:
return (
<div className={jssClassname + props.className ? ' ' + props.className : ''}>
{children}
</div>
);
I couldn't reproduce the error because I had some syntax issues, so I wonder if fixing these, the build will behave correctly:
Add closing / to input
Write style's using objects, example style={{left: '0%', width: '83.3333%'}}
Update class to className
Update tabindex to tabIndex
Lastly, if that doesn't help, to make your CSS work, ie .diabMetr + span + div, rewrite it to:
.diabMetr > span + div {}
or
.diabMetr > div {}
Right now, it's not selecting the child element.
I am building a carousel, very minimalist, using CSS snap points. It is important for me to have CSS only options, but I'm fine with enhancing a bit with javascript (no framework).
I am trying to add previous and next buttons to scroll programmatically to the next or previous element. If javascript is disabled, buttons will be hidden and carousel still functionnal.
My issue is about how to trigger the scroll to the next snap point ?
All items have different size, and most solution I found require pixel value (like scrollBy used in the exemple). A scrollBy 40px works for page 2, but not for others since they are too big (size based on viewport).
function goPrecious() {
document.getElementById('container').scrollBy({
top: -40,
behavior: 'smooth'
});
}
function goNext() {
document.getElementById('container').scrollBy({
top: 40,
behavior: 'smooth'
});
}
#container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
border: 2px solid var(--gs0);
border-radius: 8px;
height: 60vh;
}
#container div {
scroll-snap-align: start;
display: flex;
justify-content: center;
align-items: center;
font-size: 4rem;
}
#container div:nth-child(1) {
background: hotpink;
color: white;
height: 50vh;
}
#container div:nth-child(2) {
background: azure;
height: 40vh;
}
#container div:nth-child(3) {
background: blanchedalmond;
height: 60vh;
}
#container div:nth-child(4) {
background: lightcoral;
color: white;
height: 40vh;
}
<div id="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<button onClick="goPrecious()">previous</button>
<button onClick="goNext()">next</button>
Nice question! I took this as a challenge.
So, I increased JavaScript for it to work dynamically. Follow my detailed solution (in the end the complete code):
First, add position: relative to the .container, because it need to be reference for scroll and height checkings inside .container.
Then, let's create 3 global auxiliary variables:
1) One to get items scroll positions (top and bottom) as arrays into an array. Example: [[0, 125], [125, 280], [280, 360]] (3 items in this case).
3) One that stores half of .container height (it will be useful later).
2) Another one to store the item index for scroll position
var carouselPositions;
var halfContainer;
var currentItem;
Now, a function called getCarouselPositions that creates the array with items positions (stored in carouselPositions) and calculates the half of .container (stored in halfContainer):
function getCarouselPositions() {
carouselPositions = [];
document.querySelectorAll('#container div').forEach(function(div) {
carouselPositions.push([div.offsetTop, div.offsetTop + div.offsetHeight]); // add to array the positions information
})
halfContainer = document.querySelector('#container').offsetHeight/2;
}
getCarouselPositions(); // call it once
Let's replace the functions on buttons. Now, when you click on them, the same function will be called, but with "next" or "previous" argument:
<button onClick="goCarousel('previous')">previous</button>
<button onClick="goCarousel('next')">next</button>
Here is about the goCarousel function itself:
First, it creates 2 variables that store top scroll position and bottom scroll position of carousel.
Then, there are 2 conditionals to see if the current carousel position is on most top or most bottom.
If it's on top and clicked "next" button, it will go to the second item position. If it's on bottom and clicked "previous" button, it will go the previous one before the last item.
If both conditionals failed, it means the current item is not the first or the last one. So, it checks to see what is the current position, calculating using the half of the container in a loop with the array of positions to see what item is showing. Then, it combines with "previous" or "next" checking to set the correct next position for currentItem variable.
Finally, it goes to the correct position through scrollTo using currentItem new value.
Below, the complete code:
var carouselPositions;
var halfContainer;
var currentItem;
function getCarouselPositions() {
carouselPositions = [];
document.querySelectorAll('#container div').forEach(function(div) {
carouselPositions.push([div.offsetTop, div.offsetTop + div.offsetHeight]); // add to array the positions information
})
halfContainer = document.querySelector('#container').offsetHeight/2;
}
getCarouselPositions(); // call it once
function goCarousel(direction) {
var currentScrollTop = document.querySelector('#container').scrollTop;
var currentScrollBottom = currentScrollTop + document.querySelector('#container').offsetHeight;
if (currentScrollTop === 0 && direction === 'next') {
currentItem = 1;
} else if (currentScrollBottom === document.querySelector('#container').scrollHeight && direction === 'previous') {
console.log('here')
currentItem = carouselPositions.length - 2;
} else {
var currentMiddlePosition = currentScrollTop + halfContainer;
for (var i = 0; i < carouselPositions.length; i++) {
if (currentMiddlePosition > carouselPositions[i][0] && currentMiddlePosition < carouselPositions[i][1]) {
currentItem = i;
if (direction === 'next') {
currentItem++;
} else if (direction === 'previous') {
currentItem--
}
}
}
}
document.getElementById('container').scrollTo({
top: carouselPositions[currentItem][0],
behavior: 'smooth'
});
}
window.addEventListener('resize', getCarouselPositions);
#container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
border: 2px solid var(--gs0);
border-radius: 8px;
height: 60vh;
position: relative;
}
#container div {
scroll-snap-align: start;
display: flex;
justify-content: center;
align-items: center;
font-size: 4rem;
}
#container div:nth-child(1) {
background: hotpink;
color: white;
height: 50vh;
}
#container div:nth-child(2) {
background: azure;
height: 40vh;
}
#container div:nth-child(3) {
background: blanchedalmond;
height: 60vh;
}
#container div:nth-child(4) {
background: lightcoral;
color: white;
height: 40vh;
}
<div id="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<button onClick="goCarousel('previous')">previous</button>
<button onClick="goCarousel('next')">next</button>
Another good detail to add is to call getCarouselPositions function again if the window resizes:
window.addEventListener('resize', getCarouselPositions);
That's it.
That was cool to do. I hope it can help somehow.
I've just done something similar recently. The idea is to use IntersectionObserver to keep track of which item is in view currently and then hook up the previous/next buttons to event handler calling Element.scrollIntoView().
Anyway, Safari does not currently support scroll behavior options. So you might want to polyfill it on demand with polyfill.app service.
let activeIndex = 0;
const container = document.querySelector("#container");
const elements = [...document.querySelectorAll("#container div")];
function handleIntersect(entries){
const entry = entries.find(e => e.isIntersecting);
if (entry) {
const index = elements.findIndex(
e => e === entry.target
);
activeIndex = index;
}
}
const observer = new IntersectionObserver(handleIntersect, {
root: container,
rootMargin: "0px",
threshold: 0.75
});
elements.forEach(el => {
observer.observe(el);
});
function goPrevious() {
if(activeIndex > 0) {
elements[activeIndex - 1].scrollIntoView({
behavior: 'smooth'
})
}
}
function goNext() {
if(activeIndex < elements.length - 1) {
elements[activeIndex + 1].scrollIntoView({
behavior: 'smooth'
})
}
}
#container {
scroll-snap-type: y mandatory;
overflow-y: scroll;
border: 2px solid var(--gs0);
border-radius: 8px;
height: 60vh;
}
#container div {
scroll-snap-align: start;
display: flex;
justify-content: center;
align-items: center;
font-size: 4rem;
}
#container div:nth-child(1) {
background: hotpink;
color: white;
height: 50vh;
}
#container div:nth-child(2) {
background: azure;
height: 40vh;
}
#container div:nth-child(3) {
background: blanchedalmond;
height: 60vh;
}
#container div:nth-child(4) {
background: lightcoral;
color: white;
height: 40vh;
}
<div id="container">
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<button onClick="goPrevious()">previous</button>
<button onClick="goNext()">next</button>
An easier approach done with react.
export const AppCarousel = props => {
const containerRef = useRef(null);
const carouselRef = useRef(null);
const [state, setState] = useState({
scroller: null,
itemWidth: 0,
isPrevHidden: true,
isNextHidden: false
})
const next = () => {
state.scroller.scrollBy({left: state.itemWidth * 3, top: 0, behavior: 'smooth'});
// Hide if is the last item
setState({...state, isNextHidden: true, isPrevHidden: false});
}
const prev = () => {
state.scroller.scrollBy({left: -state.itemWidth * 3, top: 0, behavior: 'smooth'});
setState({...state, isNextHidden: false, isPrevHidden: true});
// Hide if is the last item
// Show remaining
}
useEffect(() => {
const items = containerRef.current.childNodes;
const scroller = containerRef.current;
const itemWidth = containerRef.current.firstElementChild?.clientWidth;
setState({...state, scroller, itemWidth});
return () => {
}
},[props.items])
return (<div className="app-carousel" ref={carouselRef}>
<div className="carousel-items shop-products products-swiper" ref={containerRef}>
{props.children}
</div>
<div className="app-carousel--navigation">
<button className="btn prev" onClick={e => prev()} hidden={state.isPrevHidden}><</button>
<button className="btn next" onClick={e => next()} hidden={state.isNextHidden}>></button>
</div>
</div>)
}
I was struggling with the too while working with a react project and came up with this solution. Here's a super basic example of the code using react and styled-components.
import React, { useState, useRef } from 'react';
import styled from 'styled-components';
const App = () => {
const ref = useRef();
const [scrollX, setScrollX] = useState(0);
const scrollSideways = (px) => {
ref.current.scrollTo({
top: 0,
left: scrollX + px,
behavior: 'smooth'
});
setScrollX(scrollX + px);
};
return (
<div>
<List ref={ref}>
<ListItem color="red">Card 1</ListItem>
<ListItem color="blue">Card 2</ListItem>
<ListItem color="green">Card 3</ListItem>
<ListItem color="yellow">Card 4</ListItem>
</List>
<button onClick={() => scrollSideways(-600)}> Left </button>
<button onClick={() => scrollSideways(600)}> Right </button>
</div>
);
};
const List = styled.ul`
display: flex;
overflow-x: auto;
padding-inline-start: 40px;
scroll-snap-type: x mandatory;
list-style: none;
padding: 40px;
width: 700px;
`;
const ListItem = styled.li`
display: flex;
flex-shrink: 0;
scroll-snap-align: start;
background: ${(p) => p.color};
width: 600px;
margin-left: 15px;
height: 200px;
`;
I am using the foundation 6 xy grid for the first time and am having difficulties aligning three images which have a text overlay at the bottom of each image. I struggling to get the background of the text to fill the full width of the image while making it responsive, 100% width of the newsArticle__pCont class is greater than the parent cell which I don't understand. Below is the closest that I have got (which isn't very close)
class News extends Component {
renderArticlePreview(article) {
if(articleCount <= 2) {
articleCount++;
return (
<div key={article.id} className="cell small-12 medium-4 newsArticle__cont--firstThree">
<img className="newsArticle__img--overlay" src={article.imageUrl} />
<div className="newsArticle__pCont">
{article.title}</p>
</div>
</div>
)
}
}
render() {
const { news } = this.props;
return (
<div>
<Header />
<div className="grid-container">
<div className="grid-x grid-padding-x newsArticle">
{ news.map((article) => this.renderArticlePreview(article)) }
</div>
</div>
</div>
)
}
}
export default News;
.scss
#import './Helpers.scss';
.newsArticle {
.newsArticle__cont--firstThree {
position: relative;
}
.newsArticle__title {
text-align: center;
font-size: 24px;
font-weight: 500;
color: #000000;
}
.newsArticle__title--overlay {
font-size: 16px;
font-weight: 500;
color: #FFFFFF;
text-align: center;
margin-bottom: 0px;
}
.newsArticle__pCont {
background-color: rgba(0, 94, 154, 0.75);
height: 45px;
position: absolute;
bottom: 0px;
z-index: 10;
width: 100%;
}
#include screen(sm-only) {
.newsArticle__img {
margin-top: 10px;
}
}
#include screen(md) {
.newsArticle__img {
margin: 10px;
}
.newsArticle__p {
margin: 10px;
}
}
}
So the reason your text right now is too large is because it is
position absolute (which takes it out of the flow, so it's not being
contained by the padding of the cell itself)
width: 100%, which will take it to the full width of the parent (which includes the padding).
The simplest way to fix this in your case would be to switch from padding-x (gutters are actually inside the cell, cell width includes the gutters) to margin-x (gutters are done by margin, so cell width excludes gutters). This would change your grid to look like:
<div className="grid-x grid-margin-x newsArticle">
A codepen showing this: https://codepen.io/kball/pen/zEGoaR