Material-UI Responsive Cards - reactjs

I'm in the process of testing out Material-UI. I've been using Bootstrap for a long time, but am interested in adapting some React projects to Material-UI. Something I've been trying to figure out is how to create responsive cards in Material-UI. I've leaned pretty heavily on Bootstraps responsive containers in the past, so I can't understand why my cards expand with the page but don't shrink as the window is condensed...If we're meant to write custom css for this I'm cool with that, just need to be pointed in the right direction.
Questions:
Are responsive cards in Material-UI out of the gate a thing?
Or are we meant to write the css to make the cards responsive?
If so, where can I learn to do that? (leaned on Bootstrap a lot in the past)
Thanks in advance for your help!
...
useStyles = () => makeStyles(theme => ({
root: {
flexGrow: 1,
},
paper: {
padding: theme.spacing.unit,
textAlign: "center",
color: theme.palette.text.secondary,
marginBottom: theme.spacing.unit
},
}));
render() {
const {baseData} = this.state;
const {hfcMetrics} = this.state;
const {stateMember} = this.state;
const {stateName} = this.state;
const {states} = this.state;
const {lineVizData} = this.state;
const classes = this.useStyles();
return (this.state.doneLoading === false ? (
<div className={classes.root}>
<Grid container>
<Grid item xs={12}>
<ReactLoading type={"spinningBubbles"} color={"black"} height={'10%'}
width={'10%'} id='spinner'/>
</Grid>
</Grid>
</div>
)
:
(stateMember === true ?
<div className={classes.root}>
<Grid container spacing={3}>
<Grid item xs={12}>
<Card>
<CardHeader
title="Options"
/>
<CardContent style={{ width: '100%', height: 300 }}>
<LineViz
data={lineVizData}
state={stateName}
source='compareTool'
states={states}
vizKey={this.state.vizKey}
/>
</CardContent>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
</Grid>
<Grid item xs={6}>
<Card ref={this.elRef}>
<CardHeader
title="Comparison Analysis"
action={
<ButtonGroup variant="text" color="primary" aria-label="text primary button group">
<YearDropDown2
year={this.state.year}
handleChange={this.toggleYear}
/>
<IconButton color='default' component="span"
onClick={() => this.resetStateToggle()}><AutorenewRoundedIcon/></IconButton>
</ButtonGroup>
}
/>
<CardContent style={{padding: 0}}>
<DataCompareTable
data={this.state.compareData}
metric={this.state.metric}
stateName={this.state.stateName}
compareCount={this.state.compareCount}
handleChange={this.toggleStateName}
toggleOne={this.state.toggleOne}
toggleTwo={this.state.toggleTwo}
toggleThree={this.state.toggleThree}
/>
</CardContent>
</Card>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>xs=6</Paper>
</Grid>
</Grid>
</div>
: ''
)
)
}
}
export default CompareTool

Thank you for your question
To answer your questions:
1) not that I know of
2) no you dont have to write alot of css, but yes some css in the usestyles
3) Below I explained in detail the css you have to write, in your code. You are using inline styles and useStyles at the same time, try putting all your styles in the usestyle hook API instead and make it responsive. Hope this helps let me know if I need to make it clearer. Thank you
as far as I know, you can use the "useMediaQuery" hook to make your design responsive.
Here is the component from the material UI Card component page, I only added the useTheme and useMediaQuery imports, and added a medium breakpoint inside useStyle under classes.root Here is a useful link
on "useMediaQuery"
https://material-ui.com/components/use-media-query/#usemediaquery
import { useTheme } from "#material-ui/styles";
import useMediaQuery from "#material-ui/core/useMediaQuery";
const useStyles = makeStyles(theme => ({
root: {
maxWidth: 345,
[theme.breakpoints.down("md")] : {
maxWidth: 200
}
},
media: {
height: 140
}
}));
const Card = () => {
const classes = useStyles();
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.up("sm"));
return (
<Card className={classes.root}>
<CardActionArea>
<CardMedia
className={classes.media}
title="Contemplative Reptile"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
Lizard
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
Lizards are a widespread group of squamate reptiles, with over 6,000
species, ranging across all continents except Antarctica
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Card>
);
}
Hope this helps

You can wrap your card in a container with responsive options or use grids, which is very convenient.

Related

How to remove MUI 5 Card Action Area Default Hover State

This question has probably been answered but i cant seem to find a solution.
I want to remove the default hover state on the CardActionArea component from mui 5. When i hover over the action area, there is a light grey background that i want to remove. Any help is much appreciated.
<Grid container spacing={2}>
{todos.map((todo) => (
<Grid key={todo.db_id} item xs={12} md={4}>
<Card variant='outlined' sx={{ minWidth: 200 }}>
<CardActionArea onClick={() => handleRedirect(todo.db_id)}>
<CardContent>
<Typography variant='h4' color='text.secondary' gutterBottom>
{todo.title}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button
onClick={() => deleteHandler(todo.db_id)}
variant='contained'
size='small'
>
Delete
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
I'm using the styled-components engine for MUI 5, so my answer will be based on that, but I was able to remove the grey hover by accessing the .MuiCardActionArea-focusHighlight, as described on this page:
https://mui.com/material-ui/api/card-action-area/
I was able to remove it with the following code:
const StyledCardActionArea = styled(CardActionArea)(({theme}) => `
.MuiCardActionArea-focusHighlight {
background: transparent;
}
`);
Then just put that component in place of CardActionArea within your code.

How to properly set the background of Container or other MUI Components?

Hi I'm migrating my code to MUI components and I'm having quite the difficulty to style it, I haven't fine any references for Container MUI component for example but I have found for Box, Cards, Paper, etc etc... and I wanted to know how to do it for the Container
This is my attempt of styling lol
and this is what I wanna achieve (I did my best to edit that lol)
My code, I do not know if I'm "boxing" things correctly but I think I do in this particular case, also I'm using pl to add some padding to the left because wasn't able to center it properly neither but that at least fixed the issue temporally. (I did try alignitems and all that stuff but it just ignores it)
<Container fixed bgcolor = "#f2f6fc" >
<Box bgcolor = "#f2f6fc" mb={2} pt={2} sx={{textAlign:'center'}}>
<Button
startIcon = {<HouseSharpIcon />}
variant = "contained"
color = "secondary"
size = "medium"
onClick={goHome} >
Regresar
</Button>
</Box>
<Box bgcolor = "#f2f6fc" pl={45} sx={{textAlign:'left', width: "500px"}}>
<Card >
<CardContent>
<Typography variant = "h5" gutterBottom>
Listado de libros por estudiante
</Typography>
<Typography variant="h6" component="div">
Acudiente: {user.displayName}
</Typography>
<Typography variant="h6" component="div">
Estudiante: {nombre}
</Typography>
<Typography variant="h6" component="div">
Datos del estudiante:
</Typography>
<Typography >
Colegio: {colegio}
<br />
Grado: {grado}
</Typography>
</CardContent>
</Card>
</Box>
<Box bgcolor = "#f2f6fc" pt={2} mb={2} pl={30} sx={{height: '650px', width: "780px", textAlign:'center'}}>
<DataGrid
rows={librosNuevos}
columns={columns}
pageSize={20}
rowsPerPageOptions={[20]}
checkboxSelection
//Store Data from the row in another variable
onSelectionModelChange = {(id) => {
setSelectionModel(id);
const selectedIDs = new Set(id);
const selectedRowData = librosNuevos.filter((row) =>
selectedIDs.has(row.id)
);
setLibrosID1(selectedRowData)
}
}
{...librosNuevos}
/>
</Box>
</Container>
I hope you already found the answer, but still if someone is looking for this here's what I use :
<Container style={{ background: '#f2f6fc' }} >

How to contain images in react material ui CardMedia component

Certain images are much bigger and parts of them are hidden:
The closest I get in getting it right was by playing around with width and height:
const useStyles = makeStyles((theme) => ({
...
media: {
height: 100,
width: 100,
margin: 'auto',
},
...
}));
const Brands = (props) => {
...
return <div style={{ marginTop: props._marginTop }}>
<Grid container justify='center'>
<Grid item xs={10}>
<Grid container spacing={2}>
{brands.map((brand, i)=> {
return <Grid item key={i} lg={3} xs={12}>
<Card>
<CardMedia
className={classes.media}
image={brand.image.length > 0 ? brand.image : knightdemon}
title={brand.name}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{brand.name.toUpperCase()}
</Typography>
<Typography
onClick={()=>setFlip(true)}
className={classes.description}
gutterBottom variant="body2"
component="p"
>
DESCRIPTION
</Typography>
</CardContent>
</Card>
</Grid>
})}
</Grid>
</Grid>
</Grid>
</div>
}
export default Brands;
Height looks good on all of them, the problem is with width and if I increase it it affects height as well.
How do I contain them in the given space so they look something like this:
To fit image in CardMedia, add props component="img" like:
<CardMedia
className={classes.media}
image={brand.image.length > 0 ? brand.image : knightdemon}
title={brand.name}
component="img"
/>
This should solve your problem.

How to put different content for each cards

I am using material ui album template for my react app
I want to put different image and different text for each cards for this when I remove this array it create the problems in responsiveness of the cards
I tried to put separte grid for each cards but thats some how solve the issue but that responsiveness of the template does not remain same
here is my demo code
https://codesandbox.io/s/material-demo-pz8df
Make sure you're updating the right cards array. Use an array of objects as well, where each object has a key for an image-link and one for the description. In the .map() we'll use these values to render the content. Here's a working sandbox: https://codesandbox.io/s/material-demo-3v44c
The responsiveness will work like expected.
Working code:
import React from "react";
import AppBar from "#material-ui/core/AppBar";
import Button from "#material-ui/core/Button";
import CameraIcon from "#material-ui/icons/PhotoCamera";
import Card from "#material-ui/core/Card";
import CardActions from "#material-ui/core/CardActions";
import CardContent from "#material-ui/core/CardContent";
import CardMedia from "#material-ui/core/CardMedia";
import CssBaseline from "#material-ui/core/CssBaseline";
import Grid from "#material-ui/core/Grid";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import { makeStyles } from "#material-ui/core/styles";
import Container from "#material-ui/core/Container";
import Link from "#material-ui/core/Link";
function MadeWithLove() {
return (
<Typography variant="body2" color="textSecondary" align="center">
{"Built with love by the "}
<Link color="inherit" href="https://material-ui.com/">
Material-UI
</Link>
{" team."}
</Typography>
);
}
const useStyles = makeStyles(theme => ({
icon: {
marginRight: theme.spacing(2)
},
heroContent: {
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(8, 0, 6)
},
heroButtons: {
marginTop: theme.spacing(4)
},
cardGrid: {
paddingTop: theme.spacing(8),
paddingBottom: theme.spacing(8)
},
card: {
height: "100%",
display: "flex",
flexDirection: "column"
},
cardMedia: {
paddingTop: "56.25%" // 16:9
},
cardContent: {
flexGrow: 1
},
footer: {
backgroundColor: theme.palette.background.paper,
padding: theme.spacing(6)
}
}));
const cards = [
{
img:
"https://images.unsplash.com/photo-1564135624576-c5c88640f235?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3300&q=80",
desc: "Campsite"
},
{
img:
"https://images.unsplash.com/photo-1564198879220-63f2734f7cec?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2072&q=80",
desc: "Space"
}
];
export default function Album() {
const classes = useStyles();
return (
<React.Fragment>
<CssBaseline />
<AppBar position="relative">
<Toolbar>
<CameraIcon className={classes.icon} />
<Typography variant="h6" color="inherit" noWrap>
Album layout
</Typography>
</Toolbar>
</AppBar>
<main>
{/* Hero unit */}
<div className={classes.heroContent}>
<Container maxWidth="sm">
<Typography
component="h1"
variant="h2"
align="center"
color="textPrimary"
gutterBottom
>
Album layout
</Typography>
<Typography
variant="h5"
align="center"
color="textSecondary"
paragraph
>
Something short and leading about the collection below—its
contents, the creator, etc. Make it short and sweet, but not too
short so folks don&apos;t simply skip over it entirely.
</Typography>
<div className={classes.heroButtons}>
<Grid container spacing={2} justify="center">
<Grid item>
<Button variant="contained" color="primary">
Main call to action
</Button>
</Grid>
<Grid item>
<Button variant="outlined" color="primary">
Secondary action
</Button>
</Grid>
</Grid>
</div>
</Container>
</div>
<Container className={classes.cardGrid} maxWidth="md">
{/* End hero unit */}
<Grid container spacing={4}>
{cards.map(card => (
<Grid item key={card} xs={12} sm={6} md={4}>
<Card className={classes.card}>
<CardMedia
className={classes.cardMedia}
image={card.img}
title="Image title"
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
Heading
</Typography>
<Typography>{card.desc}</Typography>
</CardContent>
<CardActions>
<Button size="small" color="primary">
View
</Button>
<Button size="small" color="primary">
Edit
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
</Container>
</main>
{/* Footer */}
<footer className={classes.footer}>
<Typography variant="h6" align="center" gutterBottom>
Footer
</Typography>
<Typography
variant="subtitle1"
align="center"
color="textSecondary"
component="p"
>
Something here to give the footer a purpose!
</Typography>
<MadeWithLove />
</footer>
{/* End footer */}
</React.Fragment>
);
}

How to dynamically create button for calling specific action using map array in react

I tried to map an array in react and tried to generate a button that will perform a specific action ,that is referencing another object generated by the same array using map() function.I'm using material-ui to speed up my development process.
I am very new to react (actually this is my first project with react), so maybe this is just simple question to implement 'state' in react, but i'm a little bit confusing to use this and bind syntax properly.
P.S -So excuse me for my stupidity :>
Follow this link to reproduce the code
and this is the code i got trouble with:
const products = [
{
id: 1,
img: "https://image.flaticon.com/icons/png/512/676/676434.png",
title: "Pineaple",
price: "Rp. 14.000",
desc: "Pineaple is one of nutritious food"
},
{
id: 2,
img: "https://image.flaticon.com/icons/png/512/676/676433.png",
title: "Banana",
price: "Rp. 14.000",
desc: "Banana is one of nutritious food"
},
{
id: 3,
img: "https://image.flaticon.com/icons/png/512/676/676441.png",
title: "Dragonfruit",
price: "Rp. 14.000",
desc: "Dragonfruit is one of nutritious food"
},
];
export default function Posts(props) {
const [open, setOpen] = React.useState(false);
function handleClickOpen() {
setOpen(true);
}
function handleClose() {
setOpen(false);
}
return (
<div>
<Grid container spacing={1} justify="center">
{products.map(product => (
<Grid item xs={6} sm={3} md={2} key={product.id}>
<Card>
<CardActionArea>
<CardMedia
component="img"
width="auto"
height="auto"
image={product.img}
/>
<CardContent>
<Typography component="h2"> {product.title} </Typography>
<Typography variant="body2" color="primary" component="p">
{" "}{product.price}{" "}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button onClick={handleClickOpen}>
Buy
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
{products.map(product => (
<Dialog
key={product.id}
fullScreen
open={open}
onClose={handleClose}
>
<AppBar position="sticky">
<Toolbar>
<IconButton onClick={handleClose}>
<CloseIcon />
</IconButton>
<Typography> {product.title} </Typography>
<Button onClick={handleClose}> buy </Button>
</Toolbar>
</AppBar>
<List className={classes.dialogue}>
<img src={product.img} alt={product.title} />
<ListItem button>
<ListItemText primary={product.title} secondary={product.desc}/>
</ListItem>
</List>
</Dialog>
))}
</div>
);
}
I want to make onclick button generated by mapped array to reference to specific action (show specific dialog within array list). I also want to implement same method for onSubmit on 'buy' button in the Dialog.
Screenshoot: https://imgur.com/a/M4v5LOu
(I click buy on 'pineaple' but react render all list and show the latest object in a the list which is 'dragonfruit'.)
I guess i'll use redux but maybe not right now.
Anyway that's it, I really appreciate any response and helps :)
Thanks!
There are several ways you can solve this but I will show you one. You are making use of React Hooks and you have a hook for setting the open/close state. In my solution, I make slight modification by adding another hook to set the selected product and then checking if both open and the product are set.
export default function Posts(props) {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const [product, setProduct] = React.useState(null);
function handleClickOpen(event, item) {
event.persist();
setProduct(item);
setOpen(true);
}
function handleClose() {
setOpen(false);
}
return (
<div style={{ margin: 0, padding: 0 }}>
<Grid container spacing={1} justify="center">
{products.map(product => (
<Grid item xs={6} sm={3} md={2} key={product.id}>
<Card elevation={0}>
<CardActionArea>
<CardMedia
component="img"
width="auto"
height="auto"
image={product.img}
/>
<CardContent>
<Typography component="h2"> {product.title} </Typography>
<Typography variant="body2" color="primary" component="p">
{' '}
{product.price}{' '}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button
variant={'outlined'}
size="small"
color="primary"
onClick={event => handleClickOpen(event, product)}
>
Buy
</Button>
</CardActions>
</Card>
</Grid>
))}
</Grid>
{open && product && (
<Dialog
key={product.id}
className={classes.dialogue}
fullScreen
open={open}
onClose={handleClose}
BackdropProps={{ classes: { root: classes.root } }}
PaperProps={{ classes: { root: classes.paper } }}
>
<AppBar position="sticky">
<Toolbar>
<IconButton
edge="start"
color="inherit"
onClick={handleClose}
aria-label="Close"
>
<CloseIcon />
</IconButton>
<Typography variant="h6" className={classes.title}>
{product.title}
</Typography>
<Button color="inherit" onClick={handleClose}>
buy
</Button>
</Toolbar>
</AppBar>
<List className={classes.dialogue}>
<Image
className={classes.images}
src={product.img}
alt={product.title}
/>
<ListItem button>
<ListItemText primary={product.title} secondary={product.desc} />
</ListItem>
</List>
</Dialog>
)}
</div>
);
}
In your code, you didn't have a way to track the currently selected product hence you always get the last item in the loop. By using another hook for the selected product, I can track the selected product. I hope this helps you and good luck in your mastery of React.
You are having two states open and close.
You are using map on array and showing the dialog box.
The dialog box will open when open state is true.
This will be true for all elements in the array. Dialog box will be shown for all elements.
Now, they will overlap on each other and you can only see the last one.
When you click on close dialog your open state set to false and all the dialogs are closed.
Hint :-
Maintain a state that will contain the id of element for which dialog is to be shown. Show dialog only when id state matches with the element's id

Resources