I have a array of 20 items. In UI, I have two buttons and the 20 items. My UI looks like this.
When Clicking the Prev and Next Buttons, it scrolls correctly. But now my need was.
When the div block reaches 1st item, i want to hide the prev button and show only Next button. Same as like when i reach last 20th item, i want to hide next button and show only Prev Button. In between div, i want to show two buttons.
I tried but don't know how to achieve it. Please Help with some soltions.
Here's the Code i tried:
import { useRef } from "react";
export default function App() {
const cardsContainerRef = useRef();
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
const onPrevorNextBtnClick = (scrollOffset) => {
cardsContainerRef.current.scrollLeft += scrollOffset;
};
return (
<div>
<div style={{ display: "flex", gap: "10px" }}>
<button
onClick={() => {
onPrevorNextBtnClick(-300);
}}
>
Prev
</button>
<div
ref={cardsContainerRef}
style={{ display: "flex", gap: "10px", overflow: "hidden" }}
>
{data.map((item, index) => (
<div
style={{
width: "100px",
height: "100px",
flexShrink: 0,
backgroundColor: "green",
color: "white"
}}
>
{item}
</div>
))}
</div>
<button
onClick={() => {
onPrevorNextBtnClick(300);
}}
>
Next
</button>
</div>
</div>
);
}
Code Sandbox Link: https://codesandbox.io/s/sweet-morning-ziweui?file=/src/App.js
Required Output:
You can use Intersection Observer (IO) for this. With IO you can observe an element and react whenever it comes into view (or leaves the view)
So you set up IO on your first and last element and whenever they are entering or leaving the view you add / remove a class that shows or hides the button.
I created a basic snippet where you can see the approach. You will have to add your logic to show / hide the buttons accordingly. I also only log when element is becoming visible, if you want to react for when it comes out of the view you have to add this too to the callback function.
let options = {
root: document.querySelector('ul'),
threshold: 0.9
}
let callback = (entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
console.log(`${entry.target.className} is intersecting`);
}
});
};
let observer = new IntersectionObserver(callback, options);
let targets = document.querySelectorAll('li:first-child, li:last-child');
targets.forEach(target => {
observer.observe(target);
})
* {
padding: 0;
margin: 0;
}
ul {
display: flex;
overflow: auto;
max-width: 400px;
gap: 10px;
}
li {
list-style-type: none;
flex: 200px;
min-width: 200px;
height: 150px;
background: #b00b15;
}
<ul>
<li class="first">
Test
</li>
<li>
Test
</li>
<li>
Test
</li>
<li class="last">
Test
</li>
</ul>
for the next button you can add a condition on it to be hidden when you are at [data.length -1] .
for the the previous button you can add a condtion on it to be hidden when you are not at data[0]
here my example and it's work for me :
<button type="button" class="cursor-pointer" (click)="carrouselPrevAction()"
*ngIf="!carrouselItems[0].active && carrouselItems.length> carrouselCards">
</button>
<button type="button" class="cursor-pointer" (click)="carrouselNextAction()"
*ngIf="!carrouselItems[carrouselItems.length - 1].active && carrouselItems.length> carrouselCards">
</button>
carrouselPrevAction() {
const index = this.carrouselItems.findIndex((element: any) => element.active == true)
this.carrouselItems[index - 1].active = true;
this.carrouselItems[index + this.carrouselCards - 1].active = false;
}
carrouselNextAction() {
const index = this.carrouselItems.findIndex((element: any) => element.active == true)
this.carrouselItems[index].active = false;
this.carrouselItems[index + this.carrouselCards].active = true;
}
Related
I have a list (ParentBox.tsx) that contains many items (Box.tsx). When clicking the Add button, the ParentBox has one additional unique Box. The animation works fine. However, there are two scenarios where it does not:
When I click on the Box, it removes the item from the list. Framer Motion removes the Box from the user interface without exit animation.
When clicking "Remove All", the whole list of items is removed. There is no exit stagger effect.
I want to have an individual element of the list animated out, and when the whole list is cleared, have them one by one animated out.
Full Repro in CodeSanbox
Parent Box
const variantsBoxContainer: Variants = {
hidden: {
transition: {
staggerChildren: 0.1,
delayChildren: 0.3,
staggerDirection: -1
}
},
show: {
transition: {
staggerChildren: 0.1,
delayChildren: 0.3,
staggerDirection: 1
}
}
};
let id = 3;
export const ParentBox = (props: ParentBoxProps) => {
const [items, setItems] = useState<Item[]>([
{ id: 1, text: "Test #1" },
{ id: 2, text: "Test #2" }
]);
return (
<motion.div
className="parentbox"
>
<button
onClick={() => {
id++;
setItems([...items, { id: id, text: `Click to delete id ${id}` }]);
}}
>
Add
</button>
<button
onClick={() => {
id++;
setItems([]);
}}
>
Remove All
</button>
<motion.ol
variants={variantsBoxContainer}
initial="hidden"
animate="show"
exit="hidden"
>
<AnimatePresence mode="popLayout">
{items
.sort((a, b) => a.id - b.id)
.map((d) => (
<Box
key={d.id}
data={d}
onRemove={(item) => {
const newList = items.filter((i) => i.id !== item.id);
console.log(newList);
setItems(newList);
}}
/>
))}
</AnimatePresence>
</motion.ol>
</motion.div>
);
};
Box
const variantBox: Variants = {
hidden: { opacity: 0, top: -100, transition: { duration: 2 } },
show: { opacity: 1, top: 0, transition: { duration: 2 } }
};
export const Box = (props: BoxProps) => {
return (
<motion.li
className="box"
variants={variantBox}
onClick={() => {
props.onRemove(props.data);
}}
>
{props.data.text}
</motion.li>
);
};
What I have tried so far:
Adding/Removing the explicit mention of initial, animate, exit on the Box component.
Adding/Removing the when option.
Tried all mode in the AnimatedPresence
Try to add a function for the hidden (exit) variant to have a custom delay per index
Ensure all Box all have unique key
Let me know if you have any idea what I am missing to have the animation on Box removal (children).
CodeSanbox
Exit animations will work if you explicitly indicate which variant to use for the animation states:
export const Box = (props: BoxProps) => {
return (
<motion.li
custom={props.index}
className="box"
variants={variantBox}
exit="hidden"
initial="hidden"
animate="show"
onClick={() => {
props.onRemove(props.data);
}}
>
{props.data.text}
</motion.li>
);
};
I believe AnimatePresence is conflicting with the staggerChildren prop since it appears between the parent and children. See this issue on GitHub.
Quickest workaround is probably to use dynamic variants and manually set a delay in the variants for the Box component (based on the index in the items array.
I am using https://www.npmjs.com/package/react-multi-carousel in react js project.
The carousel is working as expected but I am in the need to make the carousel to start from first when it reaches the last slide.
Complete working example: https://codesandbox.io/s/react-multi-carousel-playground-2c6ye
Code:
<Carousel
ssr
deviceType={deviceType}
itemClass="image-item"
responsive={responsive}
>
I have added like this,
<Carousel
infinite={true}
autoPlay={true}
autoPlaySpeed={3000}
ssr
deviceType={deviceType}
itemClass="image-item"
responsive={responsive}
>
But it automatically creates infinite number of slides but that is not my requirement.. Once it reaches the end then it should get back to first slide after 1 second duration because user needs to move backward n number of times to reach the first slide.
Kindly help me to start from beginning slide once the carousel once it reaches last slide(With some delay like 1000ms so that user can see the last slide for 1s and can view the first after that..
You can achieve this by writing your own autoloop and by using custom buttons. Honnestly, maybe you should just pick another library that does what you want. But you educationnal purpose, I did an example of what you should have done. Please note that you need to add the CSS for the new button group.
import React, { useEffect, useRef } from "react";
import { render } from "react-dom";
import Carousel from "react-multi-carousel";
import "react-multi-carousel/lib/styles.css";
const responsive = {
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 1,
paritialVisibilityGutter: 60
},
tablet: {
breakpoint: { max: 1024, min: 464 },
items: 1,
paritialVisibilityGutter: 50
},
mobile: {
breakpoint: { max: 464, min: 0 },
items: 1,
paritialVisibilityGutter: 30
}
};
const images = [
"https://images.unsplash.com/photo-1549989476-69a92fa57c36?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60",
"https://images.unsplash.com/photo-1549396535-c11d5c55b9df?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60",
"https://images.unsplash.com/photo-1550133730-695473e544be?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60"
];
/* ADD THIS LINE */
// Your custom Button group. CSS need to be added
const ButtonGroup = ({ next, previous, goToSlide, ...rest }) => {
const {
carouselState: { currentSlide }
} = rest;
const lastImageIndex = images.length - 1;
return (
<div className="carousel-button-group" style={{ position: "absolute" }}>
<button
onClick={() =>
currentSlide === 0 ? goToSlide(lastImageIndex) : previous()
}
>
Prev
</button>
<button
onClick={() =>
currentSlide === lastImageIndex ? goToSlide(0) : next()
}
>
Next
</button>
</div>
);
};
/* TO THIS LINE */
const Simple = ({ deviceType }) => {
/* ADD THIS LINE */
const carousel = useRef(null);
const lastImageIndex = images.length - 1;
useEffect(() => {
const autoloop = setInterval(() => {
if (carousel.state.currentSlide === lastImageIndex) {
carousel.goToSlide(0);
} else {
carousel.next();
}
}, 3000); // Your custom auto loop delay in ms
return () => clearInterval(autoloop);
}, []);
/* TO THIS LINE */
return (
<Carousel
ssr
deviceType={deviceType}
itemClass="image-item"
responsive={responsive}
/* ADD THIS LINE */
ref={el => (carousel = el)}
arrows={false}
customButtonGroup={<ButtonGroup />}
/* TO THIS LINE */
>
{images.slice(0, 5).map((image, index) => {
return (
<div key={index} style={{ position: "relative" }}>
<img
draggable={false}
alt="text"
style={{ width: "100%", height: "100%" }}
src={image}
/>
<p
style={{
position: "absolute",
left: "50%",
bottom: 0,
color: "white",
transform: " translateX(-50%)"
}}
>
Legend:{index}.
</p>
</div>
);
})}
</Carousel>
);
};
render(<Simple />, document.getElementById("root"));
Hope it helps. Happy coding :)
I believe the best option now is to use this prop:
infiniteLoop: true
Reference: https://github.com/leandrowd/react-responsive-carousel/issues/232
The simplest solution to this problem is to add infiniteloop props as true.
<Carousel infiniteLoop={true} autoPlay={true} interval={1000}>
<div>
<img src={slider1} />
<p className='legend'>Legend 1</p>
</div>
<div>
<img src={slider2} />
<p className='legend'>Legend 2</p>
</div>
</Carousel>
I have a react component that has a state variable:
showEditor
when showEditor is false it is supposed to show only a div with some number inside it (initially showEditor is false). If this state variable is true my react component is supposed to show a textbox and a button with "save" label inside another div -making dissapear the first div with the number-. This textbox will be used to change the number. For the first div (the one that only shows a number) I defined:
<div onClick={this.showEditorProc}>
{ this.state.showEditor ?
<div ....>
{ just the numeric value }
</div>
:
<div ....>
textBox
<button onClick={save and make show Editor false}>
Save
</button>
</div>
</div>
the function this.showEditorProc will modify the state of the showEditor variable to true and the save button and textbox components will appear (inside another div too). I created a function that will executed if the save button is clicked. This function modifies the showEditor variable to false however, I can not see the div with just the numeric value. Instead I still see the textbox with the save button. Is there something else I could be missing? Here it is the code of my component:
import React from 'react';
import ReactDOM from 'react-dom';
import NumberFormat from 'react-number-format';
export class NumericBox extends React.Component {
constructor() {
super();
this.state = {
enteredValue: '',
showNumEditor: false,
index: ''
};
this.showNumericEditor = this.showNumericEditor.bind(this);
this.handle_enteredValue_change = this.handle_enteredValue_change.bind(this);
this.saveCellInfo = this.saveCellInfo.bind(this);
this.loadBasicInformation = this.loadBasicInformation.bind(this);
}
saveCellInfo(e){
alert(this.state.index);
/* cellAuxParams = new Map([['updateCellValue', this.state.updateCellValue]]); */
console.log('numericBox.js>saveCellInfo>cellAuxParams= ------------------------------------------------ ' + 28);
console.log(this.props.cellAuxParams);
var f = this.props.cellAuxParams.get('updateCellValue');
this.setState({showNumEditor: false}, () => f(this.state.Index, '77'));
}
handle_enteredValue_change(values) {
const {formattedValue, value} = values;
// formattedValue = $2,223
// value ie, 2223
this.setState({enteredValue: value});
}
showNumericEditor()
{
this.setState({showNumEditor: true})
}
loadBasicInformation()
{
this.setState({enteredValue: this.props.enteredValue,
index: this.props.index
});
}
componentDidMount(){
this.loadBasicInformation();
}
componentWillReceiveProps(nextProps){
alert(nextProps.enteredValue);
this.setState({enteredValue: nextProps.enteredValue}, () => this.loadBasicInformation());
}
render() {
const table4controls = {
display: 'table',
width: this.props.style.width,
backgroundColor: 'white',
border: '0px solid #666666',
borderSpacing: '0px',
paddingBottom: '0em',
paddingTop: '0em'
};
const table4controls_RowStyle = {
display: 'table-row',
width: 'auto',
clear: 'both',
borderBottom: '5px'
};
const table4controls_ColsStyleA = {
float: 'left',
display: 'table-column',
width: '60px',
backgroundColor: 'white'
};
const table4controls_ColsStyleB = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const table4controls_ColsStyleC = {
float: 'left',
display: 'table-column',
width: '20px',
backgroundColor: 'white'
};
const btnStyle={
};
return (
<div onClick={this.showNumericEditor}>
{ this.state.showNumEditor ?
<div style ={table4controls}>
<div style={table4controls_RowStyle}>
<div style={table4controls_ColsStyleA}>
<NumberFormat style={{width: '60px'}}
value={this.state.enteredValue}
thousandSeparator={true}
prefix={this.props.prefix}
onValueChange={this.handle_enteredValue_change}
/>
</div>
<div style={table4controls_ColsStyleB}>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▲
</button>
<button style={btnStyle} onClick={() => this.saveCellInfo(this.state.index)}>
▼
</button>
</div>
<div style={table4controls_ColsStyleC}>
<button style={btnStyle} onClick={(e) => {this.saveCellInfo(e, this.state.index)}}>
Save
</button>
</div>
</div>
</div>
:
<div syle={table4controls_ColsStyleA}>
{this.state.enteredValue}
</div>
}
</div>
);
}
}
You have an onClick={this.showNumericEditor} handler on the surrounding div, so when you press the save button, the click event bubbles up and invokes a this.setState({showNumEditor: true}).
To fix it, you can either restructure the rendering or call e.stopPropagation(); at the start of saveCellInfo. Also note that some of your this.saveCellInfo calls are not passing the event.
Now, I have component like this:
code of it:
import React from "react";
import {withStyles} from "material-ui/styles";
import Settings from "material-ui-icons/Settings";
import Button from "material-ui/Button";
const styles = {
button: {
color: "primary",
height: 95,
width: 95,
disableRipple: "true",
focusRipple: "true",
},
icon: {
height: 35,
width: 35,
display: "block",
float: "none",
},
text: {
height: 35,
width: 35,
display: "block",
float: "none",
marginTop: 10,
},
};
/* eslint-disable react/prop-types */
const IconedLabel = ({classes}) => (
<section>
<Button className={classes.iconButton} variant="raised" color="primary">
<Settings className={classes.icon}/>
<div className={classes.text}>Message</div>
</Button>
</section>
);
export default withStyles(styles)(IconedLabel);
But need to button, that in top part contains icon and text message in bottom.
I use reactjs and material-ui lib from here https://material-ui-next.com/demos/buttons/
The Button component uses flexbox to control the layout/alignment of content. To align the content vertically (so the icon is above the text), you can simply change the flex-direction to column.
This style needs to be applied to an element inside the button component, not to the root element. You can use the classes property to override all of the styles in a component.
In this case, you want to add flexDirection: column to the label class.
Documentation on class overrides in material ui v1
Here's a working example. Hope it helps.
const [React, ReactDOM, Button, Settings, withStyles] = [window.React, window.ReactDOM, window['material-ui'].Button, ({className}) => <i className={`material-icons ${className}`}>settings</i>, window['material-ui'].withStyles]
// Ignore code above this line
const styles = theme => ({
button: {
height: 95, // setting height/width is optional
},
label: {
// Aligns the content of the button vertically.
flexDirection: 'column'
},
icon: {
fontSize: '32px !important',
marginBottom: theme.spacing.unit
}
})
const CustomButton = ({ classes }) => (
<Button
/* Use classes property to inject custom styles */
classes={{ root: classes.button, label: classes.label }}
variant="raised"
color="primary"
disableRipple={true}
>
<Settings className={classes.icon} />
Message
</Button>
)
const WrappedCustomButton = withStyles(styles)(CustomButton)
ReactDOM.render(<WrappedCustomButton />, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script><script src="https://unpkg.com/material-ui#1.0.0-beta.40/umd/material-ui.production.min.js"></script><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"><div id="root" />
A (potentially bad) solution would simply be:
.MuiIconButton-label {
flex-direction: column
}
I say bad, because you might want to use it in it's standard format elsewhere.
What I opted to do was add a class name nav-bar-icon-wrapper to the IconButton & set the flex direction in it's parent:
.nav-bar-icon-wrapper {
flex-direction: column
}
.MuiIconButton-label {
flex-direction: inherit
}
If I run into instance later where I want the icon/label button to be standard, I'll just add a new class default-icon-wrapper and css that handles that:
.default-icon-wrapper {
flex-direction: row
}
FWIW:
I preach the BEM http://getbem.com/introduction/ convention AND that whenever you make a component, you add an optional modifier prop.
I have functions in a shared dir that looks these:
export function BEMifyThis(modifier) {
return (klass) => BEMify(klass, modifier)
}
export function BEMify(klass, modifier=false) {
if (modifier) {
klass += ` ${klass}-${modifier}`
}
return klass
}
Then I use that everywhere in my component so the user can access the component elements as a group or individually using their modifiers.
import {BEMifyThis} from '../shared/bem'
const BEMify = BEMifyThis(this.props.modifier)
className={"navbar__menu_item")}
becomes
className={BEMify("navbar__menu_item")}
so something like navbar__menu_item becomes navbar__menu_item navbar__menu_item-logout
I'm wondering how to align text in Draft.js just like on the picture below.
I have searched this several days, but I haven't found the solution.
After reading the source code, I found a way for it. Using blockRenderMap, you can add some custom block types like this:
const blockRenderMap: Record<string, DraftBlockRenderConfig> = {
'header-one-right': {
element: 'h1',
wrapper: <StyleHOC style={{ ...blockStylesMap['header-one'], display: 'flex', justifyContent: 'flex-end' }} />,
},
'header-two-right': {
element: 'h2',
wrapper: <StyleHOC style={{ ...blockStylesMap['header-two'], display: 'flex', justifyContent: 'flex-end' }} />,
},
'header-three-right': {
element: 'h3',
wrapper: <StyleHOC style={{ ...blockStylesMap['header-three'], display: 'flex', justifyContent: 'flex-end' }} />,
},
'unstyled-right': {
element: 'div',
wrapper: <StyleHOC style={{ ...blockStylesMap['unstyled'], display: 'flex', justifyContent: 'flex-end' }} />,
},
};
I use flex to avoid wasting time to find a away to override the internal style .public-DraftStyleDefault-ltr.
StyleHOC is quite simple:
const StyleHOC: React.FC<Props> = ({ style, children }) => {
const childrenWithStyle = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return React.cloneElement(child, { style });
}
return child;
});
return <>{childrenWithStyle}</>;
};
And then you can toggle the blockType using RichUtils.toggleBlockType(editorState, blockType).
The Editor component has a div with the class .public-DraftStyleDefault-ltr. This controls the text alignment of each paragraph that you write. As you create more paragraphs, more of these divs are created to contain the text. The text is wrapped in a span element and .public-DraftStyleDefault-ltr has a default alignment of text-align: left.
I created some css classes for text-align: left, text-align: center, text-align: right and text-align: justify and added this basic for loop to my component for creating text alignment buttons.
const textBlock = document.querySelectorAll(".public-DraftStyleDefault-ltr");
for (let i = 0; i < textBlock.length; i++) {
textBlock[i].classList.toggle(this.props.style);
}
this.props.style is the name of the css class that determines the text-alignment I wanted.
It is a pretty basic fix since this way when you click align right say, the whole document is aligned right. I am planning to work on this so only the selected text is aligned so should be able to update this answer soon. Hope it helps in some way
I tried to make almost the same thing. But my trouble was in text-align property, which was correctly set to block span, but .public-DraftStyleDefault-ltr doesn't react to it.
So, I have made next decision, which get the first div child, and copy it's params:
const paragraphs: any = document.querySelectorAll(".public-DraftStyleDefault-ltr");
for (let i = 0; i < paragraphs.length; i++) {
const paragraph = paragraphs.item(i);
if (paragraph) {
const firstItem = paragraph.querySelectorAll('*').item(0);
// Apply to the parent the first child style
paragraph.style.textAlign = firstItem.style.textAlign;
}
}
To change block alignment you can:
1- set alignment data
const modifiedBlockState = Modifier.setBlockData(editorState.getCurrentContent(),
editorState.getSelection(),Map({align:'align-center'}));
setEditorState(EditorState.push(modifiedBlockState,'change-block-data'));
2- use it in styling function
/*
JSX
blockStyleFn={ block => block.getData().get('align')
this will return 'align-center|left|right' which will be assigned as a classname
*/
<Editor blockStyleFn={ block => block.getData().get('align')} .../>
//CSS
.align-center div{
text-align: center;
}
.align-right div{
text-align: right;
}
.....