I have following code for creating a grid of cards , but the LINK button at the bottom is not aligned in all the cards. What do I need to change to get all the link buttons aligned in all the card at the bottom right. Please see the image at the bottom, I would like all the select button to be horizontally aligned with other cards in the row.
<Row xs={1} md={4} className="g-4">
{MilitaryFormsType.map((e, idx) => (
<Col>
<Card border="#f7f7f7" style={{ width: '18rem', height: '18rem', whiteSpace: 'pre-wrap' }}>
<Card.Body>
<Card.Title>{e.name}</Card.Title>
<Card.Text >{e.Description}</Card.Text>
<Link to={e.link} >
<Button variant="primary" style={{ backgroundColor: "#aa92df", borderStyle: "none", float: "right" }}>Select</Button>
</Link>
</Card.Body>
</Card>
</Col>
))}
</Row>
What I understand from a problem is you want to move the button on the right bottom of all cards.
You can use position "relative" on the body and for link position "absolute".
<Card.Body style={{ position: "relative" }}>
<Card.Title>{e.name}</Card.Title>
<Card.Text>{e.Description}</Card.Text>
<Link to={e.link} style={{ position: "absolute", bottom: 0, right: 0 }}>
<Button
variant="primary"
style={{
backgroundColor: "#aa92df",
borderStyle: "none",
float: "right",
}}
>
Select
</Button>
</Link>
</Card.Body>
**1. Try this one if using wrap it's possible **
<Card.Body>
<Card.Title>{e.name}</Card.Title>
<Card.Text >{e.Description}</Card.Text>
<Link to={e.link} >
<div style={{ text-align:center }}>
<Button variant="primary" style={{ backgroundColor: "#aa92df", borderStyle: "none"}}>Select</Button>
</div>
</Link>
</Card.Body>
Actually you can dynamically control the height of Card.Title, Card.Text and others using javascript. Below is the code of how you can make all the heights the same so the cards are aligned while containing all the data without overflowing. The principle is basically to set all the cards' heights equal to the height of the longest card. Below is the code:
const [cardHeight, setCardHeight] = useState(null);
const getMaxHeightTitle = (elements) => {
let titleHeights = Array.prototype.map.call(elements, (element, i) => {
return element.offsetHeight;
});
return Math.max(...titleHeights);
}
const setMaxContent = (elements) => {
Array.prototype.map.call(elements, (element, i) => {
element.style.height = 'max-content';
});
}
const setMaxHeightTitle = useCallback((elements, h) => {
Array.prototype.map.call(elements, (element, i) => {
if (element.offsetHeight !== cardHeight) {
element.style.height = cardHeight + 'px'
}
});
}, [cardHeight]);
const resizeCardTitle = useCallback(() => {
setMaxContent(document.getElementsByClassName('card-title h5'));
titleHeight = getMaxHeightTitle(document.getElementsByClassName('card-title h5'));
setCardHeight(titleHeight)
setMaxHeightTitle(document.getElementsByClassName('card-title h5'), titleHeight);
}, [setMaxHeightTitle]);
useEffect(() => {
window.addEventListener('load', resizeCardTitle);
window.addEventListener('resize', resizeCardTitle);
return () => {
window.removeEventListener('load', resizeCardTitle);
window.removeEventListener('resize', resizeCardTitle);
}
}, [cardHeight, resizeCardTitle]);
useEffect(() => {
resizeCardTitle();
}, [resizeCardTitle])
Related
I am using drag and drop in my project for slides. What I done is drag a slide and swap slide position with other.It works perfectly. But the issue is the symbol
comes even if it is allow to drag .I want to remove this icon and show drag icon while draging.Also I need an empty space on dragged slide. My code is
onDragStart = (event, index) => {
this.draggedItem = this.state.slides[index];
event.dataTransfer.setDragImage(event.target.parentNode, 20, 20);
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/html", event.target.parentNode);
};
onDragOver = index => {
const { slides } = this.state;
const hoveredItem = slides[index];
if (this.draggedItem === hoveredItem) {
return;
}
const filteredItems = slides.filter(item => item !== this.draggedItem);
filteredItems.splice(index, 0, this.draggedItem);
this.setState({ slides: filteredItems });
};
onDragEnd = () => {
this.draggedIdx = null;
};
<div className="draggableList">
<div key={data} onDragOver={() => onDragOver(index)}>
<div className="handle"
draggable
onDragStart={event => onDragStart(event, index)}
onDragEnd={onDragEnd}>
<div class="d-flex justify-content-center" style={{ fontWeight: "bold", paddingTop: "20px" }}>{"Slide " + (index + 1)}</div>
<div className={"py-3 d-flex justify-content-center course_slide slide_" + index}>
<div style={{ backgroundColor: 'white', height: 150, width: 120, boxShadow: '4px 4px 5px grey', cursor: 'pointer', overflowY: 'hidden', border: selected ? '2px solid limegreen' : '', position: 'relative' }} onClick={onClick}>
<h1 style={{ fontSize: 7 }}>{data.title}</h1>
<div style={{ fontSize: 4 }} dangerouslySetInnerHTML={{ __html: data.description }}>
</div>
{onDelete && <div onClick={onDelete} style={{ padding: 4, position: 'absolute', bottom: 0, right: 0 }}><DeleteOutline /></div>}
</div>
</div>
</div>
</div>
</div>
Can anyone please help me to sort out this issue?
first of all look at these below screenshots:
There are two tasks which I want to achieve:
There are two questions shown on the page using the array map method, by default I'm showing only one question, and when I press the next part button the second question will appear with the same question and a TextField (multiline). Now I've implemented a word counter in TextField but when I type something in 1st question the counter works properly. But when I go to the next question the here counter shows the previous question's word counter value, I want them to separately work for both questions.
When I click on the next part and again when I click on the previous part then the values from TextField are removed automatically. I want the values there if I navigate to the previous and next part questions. Also, I want to get both TextField values for a form submission when I press the Submit Test button.
Below are my codes for this page. I'm using Next.js and MUI
import { Grid, Typography, Box, NoSsr, TextField } from '#mui/material';
import PersonIcon from '#mui/icons-material/Person';
import Timer from '../../../components/timer';
import Button from '#mui/material/Button';
import { useState } from 'react';
import ArrowForwardIosIcon from '#mui/icons-material/ArrowForwardIos';
import { Scrollbars } from 'react-custom-scrollbars';
import AppBar from '#mui/material/AppBar';
import Toolbar from '#mui/material/Toolbar';
import ArrowBackIosIcon from '#mui/icons-material/ArrowBackIos';
import axios from '../../../lib/axios';
import { decode } from 'html-entities';
import { blueGrey } from '#mui/material/colors';
export default function Writing({ questions }) {
const [show, setShow] = useState(false);
const [show1, setShow1] = useState(true);
const [showQuestionCounter, setShowQuestionCounter] = useState(0);
const [wordsCount, setWordsCount] = useState(0);
return (
<>
<Box sx={{ flexGrow: 1 }}>
<AppBar position="fixed" style={{ background: blueGrey[900] }}>
<Toolbar>
<Grid container spacing={2} alignItems="center">
<Grid item xs={4} display="flex" alignItems="center">
<PersonIcon
sx={{ background: '#f2f2f2', borderRadius: '50px' }}
/>
<Typography variant="h6" color="#f2f2f2" ml={1}>
xxxxx xxxxx-1234
</Typography>
</Grid>
<Grid item xs={4} container justifyContent="center">
<Timer timeValue={2400} />
</Grid>
<Grid item xs={4} container justifyContent={'right'}>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Settings
</Button>
<Button
variant="contained"
style={{
background: 'white',
color: 'black',
margin: '0px 10px',
}}
size="small">
Hide
</Button>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Help
</Button>
</Grid>
</Grid>
</Toolbar>
</AppBar>
</Box>
<Box
sx={{
background: blueGrey[50],
height: '100%',
width: '100%',
position: 'absolute',
}}
pt={{ xs: 13, sm: 11, md: 10, lg: 11, xl: 11 }}>
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Box
key={question.id}
px={3}
sx={{ background: '#f2f2f2', pb: 4 }}
position={{
xs: 'sticky',
sm: 'sticky',
lg: 'initial',
md: 'initial',
xl: 'initial',
}}>
<Box
style={{ background: '#f7fcff', borderRadius: '4px' }}
py={1}
px={2}>
<Box>
<Typography variant="h6" component="h6" ml={1}>
Part {question.id}
</Typography>
<Typography variant="subtitle2" component="div" ml={1} mt={1}>
<NoSsr>
<div
dangerouslySetInnerHTML={{
__html: decode(question.questions[0].question, {
level: 'html5',
}),
}}></div>
</NoSsr>
</Typography>
</Box>
</Box>
<Box
style={{
background: '#f7fcff',
borderRadius: '4px',
marginBottom: '75px',
}}
pt={1}
px={3}
mt={{ xs: 2, sm: 2, md: 2, lg: 0, xl: 3 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={12} lg={6} md={6} xl={6}>
<Box
py={{ lg: 1, md: 1, xl: 1 }}
style={{ height: '50vh' }}>
<Scrollbars universal>
<Typography
variant="body1"
component="div"
style={{ textAlign: 'justify' }}
mr={2}>
<NoSsr>
<div
dangerouslySetInnerHTML={{
__html: decode(question.question_text, {
level: 'html5',
}),
}}></div>
</NoSsr>
</Typography>
</Scrollbars>
</Box>
</Grid>
<Grid
item
xs={12}
sm={12}
lg={6}
md={6}
xl={6}
mt={{ md: 4, lg: 4, xl: 4 }}>
<TextField
id={`${question.id}`}
label="Type your answer here"
multiline
name={`answer_${question.id}`}
rows={12}
variant="outlined"
fullWidth
helperText={`Words Count: ${wordsCount}`}
onChange={(e) => {
setWordsCount(
e.target.value.trim().split(/\s+/).length
);
}}
/>
</Grid>
</Grid>
</Box>
</Box>
) : null
)}
<Box sx={{ position: 'fixed', width: '100%', left: 0, bottom: 0 }}>
<Grid
container
style={{ background: blueGrey[300], display: 'flex' }}
py={2}
px={3}>
<Grid
item
xs={3}
sm={3}
lg={6}
md={6}
xl={6}
container
justifyContent={'start'}>
<Button
variant="contained"
style={{ background: 'white', color: 'black' }}
size="small">
Save Draft
</Button>
</Grid>
<Grid
item
xs={9}
sm={9}
lg={6}
md={6}
xl={6}
container
justifyContent={'end'}>
<Button
variant="contained"
size="small"
style={{
background: 'white',
color: 'black',
visibility: show1 ? 'visible' : 'hidden',
}}
endIcon={<ArrowForwardIosIcon />}
onClick={() => {
setShow((prev) => !prev);
setShowQuestionCounter(showQuestionCounter + 1);
setShow1((s) => !s);
}}>
Next Part
</Button>
{show && (
<>
<Box>
<Button
variant="contained"
style={{
background: 'white',
color: 'black',
margin: '0 10px',
visibility: show ? 'visible' : 'hidden',
}}
startIcon={<ArrowBackIosIcon />}
size="small"
onClick={() => {
setShow1((s) => !s);
setShowQuestionCounter(showQuestionCounter - 1);
setShow((prev) => !prev);
}}>
previous Part
</Button>
<Button variant="contained" color="success">
Submit Test
</Button>
</Box>
</>
)}
</Grid>
</Grid>
</Box>
</Box>
</>
);
}
export async function getServerSideProps(context) {
const { id } = context.query;
const token = context.req.cookies.token;
if (!token) {
context.res.writeHead(302, {
Location: '/',
});
context.res.end();
}
const res = await axios.get(`test/${id}/questions`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (res.data.success) {
return {
props: {
questions: res.data.data.questions,
},
};
}
}
The wordsCount state is shared between both questions, which means that when you go to the next question the state remains unchanged and shows the wordsCount from the first question. To solve it, each question needs to have it's own state which you can do by creating a Question component and mapping over it:
export default function Question({ question }) {
const [wordsCount, setWordsCount] = useState(0)
return (
<Box
// ...
>
{/* ... */}
<TextField
// ...
helperText={`${wordsCount} words`}
onChange={(e) => {
setWordsCount(e.target.value.trim().split(/\s+/).length)
}}
/>
{/* ... */}
</Box>
)
}
Then map over it:
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Question key={question.id} question={question} />
) : null
)}
Currently, the value of TextField gets reset you the component is unmounted (i.e. when you go to the next question). You need to make the TextField component a controlled component, meaning that you store the value of the field in useState. And if you need to submit the value of TextField later, then you probably need to store the values in the parent component:
export default function Writing({ questions }) {
// ...
const [answers, setAnswers] = useState([])
function handleChange(id, answer) {
// copy the current answers
let newAnswers = [...answers]
// find the index of the id of the answer in the current answers
const index = newAnswers.findIndex((item) => item.id === id)
// if the answer does exist, replace the previous answer with the new one, else add the new answer
if (index) {
newAnswers[index] = { id, answer }
setAnswers(newAnswers)
} else {
setAnswers([...answers, { id, answer }])
}
}
return (
<>
{/* ... */}
{questions.map((question, index) =>
index === showQuestionCounter ? (
<Question
key={question.id}
question={question}
value={answers.find((item) => item.id === question.id)?.answer || ''}
handleChange={handleChange}
/>
) : null
)}
</>
}
In the Question component, add the handler.
export default function Question({ question, value, handleInputChange }) {
const [wordsCount, setWordsCount] = useState(0)
return (
<Box>
{/* ... */}
<TextField
helperText={`${wordsCount} words`}
value={value}
onChange={(e) => {
handleInputChange(question.id, e.target.value)
setWordsCount(e.target.value.trim().split(/\s+/).length)
}}
/>
{/* ... */}
</Box>
)
}
In the parent component (Writing) you should be able to use the values for form submission.
I have cards that are mapped over They all have a toggle button menu and Im trying to figure out how to target the menu's individually since they are controlled by a single part of the state Im not even sure this is possible. But I think it should be right? I have created a simple working example here. Im not sure if I can use the ID of them to target them individually but im not sure how to implement that. Any advice would be helpful thanks!
1) First of all you can't control all state of Items with single boolean state, You can create open as an array and then initially set it as false.
const [open, setOpen] = useState(Array.from(Items, () => false));
2) When you want to toggle particular element then you can use index
onClick={() => toggle(idx)}
3) Then you have to handle 2 cases where you update newOpenState
case: 1: when you update respective state after you click on the button. In this case you are just toggling the value which is present at the index idx
<Button
isOpen={open[idx]}
onClick={() => toggle( idx )}
style={{
width: "30px",
height: "30px",
marginTop: "25px",
marginLeft: "10px"
}}
/>
case 2: When you externally providing the value
<Col
md="12"
onClick={() => toggle( idx, false )}
className="editCol"
>
So these two cases are covered by using a single expression as:
newOpenState[index] = value ?? !newOpenState[index];
what above statement means is if the value is provided(case 2) then you just have to assign the value to newOpenState[index]. If you haven't provided the value(case 1) then it will be undefined, and you have to just toggle the value of newOpenState[index]. I've used using Nullish coalescing operator (??) you can assign the right hand side value of ?? if left hand side of value is undefined or null.
CODE
import "./styles.css";
import { Card, Button, Col, Row } from "reactstrap";
import { useState } from "react";
const Items = [
{
name: "Test 1",
ID: 1234
},
{
name: "Test 2",
ID: 4321
},
{
name: "Test 3",
ID: 3421
}
];
export default function App() {
const [open, setOpen] = useState(Array.from(Items, () => false));
const toggle = (index, value) => {
const newOpenState = [...open];
newOpenState[index] = value ?? !newOpenState[index];
setOpen(newOpenState);
};
return (
<>
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
{Items.map((item, idx) => (
<Card
key={idx}
style={{
border: "solid",
borderColor: "#00000",
margin: "5px",
width: "150px",
display: "flex",
justifyContent: "center"
}}
>
<h1>{item.name}</h1>
<span style={{ display: "flex" }}>
{!open[idx] ? (
<Button
isOpen={open[idx]}
onClick={() => toggle(idx)}
style={{
width: "30px",
height: "30px",
marginTop: "25px",
marginLeft: "10px"
}}
>
...
</Button>
) : (
<Card
style={{
border: "solid 1px",
borderColor: "#00000",
margin: "5px"
}}
>
<Row>
<Col md="12" className="closeMenu">
<span className="X" onClick={() => toggle(idx, false)}>
X
</span>
</Col>
</Row>
<Row>
<Col
md="12"
onClick={() => toggle(idx, false)}
className="editCol"
>
<span
className="editName"
onClick={() => setOpen(item.ID)}
>
Edit Name
</span>
</Col>
</Row>
<Row>
<Col md="12">
<span
className="deleteForm"
onClick={() => handleFormDelete(item.ID)}
>
Delete
</span>
</Col>
</Row>
</Card>
)}
</span>
</Card>
))}
</div>
</>
);
}
I have MatrialUiTable if I select any of the table row by Clicking the checkboxes. On the top right hand side of the table delete icon will appear. But I wanted different icon which will do the same deletion operation .Is there any solution to change the Icon in MuiTable. Thank you.
Multi row delete icon can be changed by using Custom Components. The code is mentioned in code sample.`
const options = {
rowsSelected: rowsSelected,
customToolbarSelect: (selectedRows, displayData, setSelectedRows) => (
<div>
<Tooltip title={"Delete"} cursor='pointer' className="mr-6">
<Icon onClick={()=>DeleteRow(selectedRows)} color="error">delete</Icon>
</Tooltip>
</div>
),
}`
If you want to remove the delete icon from top during multiple rows select. use it.
customToolbarSelect: () => {},
Also for other task replacing delete icon. Try it
const [selectedFabrics, setSelectedFabrics] = useState([])
customToolbarSelect: selectedRows => (
<IconButton
onClick={() => {
const indexesToPrint = selectedRows.data.map((row, k) => row.dataIndex);
let temp = [];
for (let i = 0; i < fabricList.length; i++) {
if (indexesToPrint.includes(i)) {
temp.push(fabricList[i]['id']);
}
}
setSelectedFabrics(temp);
}}
style={{
marginRight: "24px",
height: "48px",
top: "50%",
display: "block",
position: "relative",
transform: "translateY(-50%)",
}}
>
{/* <EditIcon /> */}
<span style={{marginTop: "23px"}}>Print QR Code</span>
</IconButton>
),
I'm mapping through data, I want to click on an image and have a modal popup with the data.title and data.info. Each modal is only showing the last data from my array. I've read that all the modals are popping up together and I'm only seeing the last one but I don't quite understand how to solve the problem, particularly with function components in React. TIA
export default function SomeComponent
const [modalIsOpen, setModalIsOpen] =
useState(false);
const customStyles = {
content: {
top: '35%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
width: '60%',
transform: 'translate(-40%, -10%)',
},
}
return (
<div className="container">
{ somedata.map((data) =>
(<div className='item' key={data.id} >
<img src={data.img} alt='' onClick={()=> setModalIsOpen(true)} />
<Modal isOpen={modalIsOpen} onRequestClose={() => setModalIsOpen(false)} style={customStyles}>
<h1>{data.title</h1>
<p>{data.content</p>
<div>
<button onClick={() => setModalIsOpen(false)}>X</button>
</div>
</Modal>
</div>))}
</div>
</div>
);}
You should use only one instance of Modal component and pass on the data of the clicked image to it using a state. Each time you click on an image, the data for modal should be updated with the data of the clicked image. See the example below. I've modified your code to make it work.
export default function SomeComponent() {
const [modalIsOpen, setModalIsOpen] = useState(false);
const [modalData, setModalData] = useState(null);
const customStyles = {
content: {
top: '35%',
left: '50%',
right: 'auto',
bottom: 'auto',
marginRight: '-50%',
width: '60%',
transform: 'translate(-40%, -10%)',
},
}
return (
<>
<div className="container">
{somedata.map(data => (
<div className='item' key={data.id} >
<img
src={data.img}
alt=''
onClick={()=> {
setModalData(data);
setModalIsOpen(true);
}
/>
</div>
))}
</div>
<Modal isOpen={modalIsOpen} onRequestClose={() => setModalIsOpen(false)} style={customStyles}>
<h1>{modalData.title}</h1>
<p>{modalData.content}</p>
<div>
<button onClick={() => setModalIsOpen(false)}>X</button>
</div>
</Modal>
</>
)
}