How can I keep Link and IconButton on the same line? - reactjs

I have a link with a long text and a "copy" icon after it. I don't want the icon to be aware rendered on a separate line alone.
So these should be ok:
But not this:
The basic code is:
<>
<Link>cat dog cow pig owl rabbit hare wolf fox</Link>
<IconButton><EditIcon/></IconButton>
</>
I can easily place the icon to the right with a flexbox, but not sure how I effectively insert a non-breaking space between a text and an icon.

I just ran into the same problem and I managed to solve it using MUI's Grid.
I had to do some string splitting in my case, so I included here as well. Hope this helps!
<Link onClick={() => { alert('test'); }}>
<Grid container direction="row" alignItems="center">
<Grid item>
<Typography variant="body1">
{linkText.split(' ').slice(0, -1).join(' ')}
</Typography>
</Grid>
<Grid item>
<Typography variant="body1" display="flex" alignItems="center">
{linkText.split(' ').slice(-1)}
<IconButton>
<EditIcon
onClick={(event) => {
event?.stopPropagation();
// do stuff
}}
/>
</IconButton>
</Typography>
</Grid>
</Grid>
</Link>

Related

How to style and position Material UI Link component

I'm trying to style and change position of a Link component, I'm trying to have the Link (Forget Password?) at the right end, but now it's centered as shown in the image below
Also I want to change the color of it, I tried color="black" but didn't work
I'm using grids trying to have the texts apart from each other.
The Code
{/* Password */}
<Grid container mt={4}>
<Grid item xs={6} justify={"flex-start"}>
<Typography
variant="h9"
gutterBottom
component="div"
style={{ fontWeight: "bold", textAlign: "left"}}
>
Password
</Typography>
</Grid>
<Grid item xs={6} justify={"flex-end"}>
<Typography
variant="body2"
gutterBottom
component={Link}
// align="right"
to="/register"
>
{/* <Link to="/register">Forget Password?</Link> */}
Forget Password?
</Typography>
</Grid>
</Grid>
If "black" isn't defined in your theme, that won't work. color="primary" should work, but if you can also define black as a color in your theme.
Alternatively, if you want only this link to be black, you can add the attribute sx={{ color: 'black' }} to <Link> and that should work.
If it's not necessary to do so, I wouldn't wrap the link in a typography; just apply all the styles directly to link, as link already uses a typography element under the hood.
Try this to right-align your password link:
<Grid item xs={6}>
<Box display="flex" justifyContent="flex-end">
<Typography
variant="body2"
gutterBottom
component={Link}
// align="right"
to="/register"
>
{/* <Link to="/register">Forget Password?</Link> */}
Forget Password?
</Typography>
</Box>
</Grid>

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.

Box component inserting text to 'start' and 'end'

I am using material UI components and I'm trying to make a card component. I want my text will be in the same row inbox component("phase" and "2" have to be in the same row). However, I couldn't do it. Here is my code;
<Card className={classes.rootMultiple} variant='outlined'>
<CardContent>
<Box>
<Box justifyContent={'start'}>
<Typography
variant='h5'
component='h2'
className={classes.titleMultiple}
>
phase
</Typography>
</Box>
<Box display='flex' justifyContent={'end'}>
<Typography
variant='h7'
component='h2'
className={classes.descriptionMultiple}
>
2
</Typography>
</Box>
</Box>
</CardContent>
</Card>;
Try to specify the display and flexDirection attributes in the parent's Box.
Then use the flex attribute on the children's `Box:
<Card className={classes.rootMultiple} variant='outlined'>
<CardContent>
<Box sx={{
display: 'flex',
flexDirection: 'row',
}}>
<Box flex={1}>
<Typography
variant='h5'
component='h2'
className={classes.titleMultiple}
>
phase
</Typography>
</Box>
<Box flex={0}>
<Typography
variant='h7'
component='h2'
className={classes.descriptionMultiple}
>
2
</Typography>
</Box>
</Box>
</CardContent>
</Card>
You can just write a Grid (that uses flex) with justify-content: space-between.
<Card className={classes.rootMultiple} variant='outlined'>
<CardContent>
<Grid container justifyContent="space-between">
<Grid item>
<Typography
variant='h5'
component='h2'
className={classes.titleMultiple}
>
phase
</Typography>
</Grid>
<Grid item>
<Typography
variant='h7'
component='h2'
className={classes.descriptionMultiple}
>
2
</Typography>
</Grid>
</Grid>
</CardContent>
</Card>
No need to make other components; less is better. Of course you can use Box with flex style, but Grid does it for you.
Note that h7 isn't a valid value for variant inside Typography.
EDIT: I wrote space-between only because your example prints the texts in the margins, but of corse you can use what you want: they will be always in same row.

Space between two buttons in Material Design

I have two buttons and would like to put a space between them. So, I have tried to do that by putting a box around them and then applying a margin to the box. See code below.
render() {
return (
<>
<ThemeProvider theme={theme}>
<CssBaseline />
<BrowserRouter>
<Header />
</BrowserRouter>
</ThemeProvider>
<ThemeProvider theme={theme}>
<Grid container justify="center">
<Box pt={3}>
<Paper>
<Box pl={3} pr={3} pb={3} pt={3}>
<Grid container spacing={0}>
<Grid item xs={6}>
<Typography
variant="h6"
gutterBottom={false}
onClick={this.handleSetDummyText}
>
Keyword Processor
</Typography>
</Grid>
<Grid item xs={6}>
{/* ********* Offending code section ********** */}
<Button
color={
this.state.sorttypeStatus === 'default'
? 'primary'
: 'secondary'
}
onClick={this.handleSortDefault}
>
DEFAULT
</Button>
{/* ********* Offending code section ********** */}
<Button
color={
this.state.sorttypeStatus === 'alpha'
? 'primary'
: 'secondary'
}
onClick={this.handleSortAlpha}
>
A-Z
</Button>
<Button
color={
this.state.sorttypeStatus === 'length'
? 'primary'
: 'secondary'
}
onClick={this.handleSortLength}
>
LENGTH
</Button>
</Grid>
<Grid container spacing={2}>
<Grid item xs={6}>
<TextField
label="Input Keywords"
fullWidth
multiline
rows={10}
variant="outlined"
margin="normal"
onChange={this.handleInputText}
// defaultValue={defaultInputText}
value={this.state.inputText}
/>
<Box m={1}>
<Button
variant="contained"
color="primary"
onClick={this.inputToOutput}
>
Parse
</Button>
<Button
variant="outlined"
color="secondary"
onClick={this.clearInput}
// default={this.state.inputText}
>
Clear
</Button>
</Box>
<br />
<Checkboxes
handleDedupe={this.handleDedupe}
handleRemoveNumbers={this.handleRemoveNumbers}
handleConvertToLowercase={
this.handleConvertToLowercase
}
handleOneWordPerLine={this.handleOneWordPerLine}
handleAddCommas={this.handleAddCommas}
handleAddCommasSpace={this.handleAddCommasSpace}
handleStartWords={this.handleStartWords}
handleEndWords={this.handleEndWords}
/>
</Grid>
<Grid item xs={6}>
<TextField
label="Output Keywords"
fullWidth
multiline
rows={30}
variant="outlined"
margin="normal"
value={this.state.outputText}
/>
<Button
variant="contained"
color="primary"
mt={5}
onClick={() => {
navigator.clipboard.writeText(
this.state.outputText
);
}}
>
Copy
</Button>
<Button
variant="outlined"
color="secondary"
mt={5}
onClick={this.clearOutput}
>
Clear
</Button>
<Button
variant="outlined"
color="secondary"
mt={5}
onClick={this.handleSetDummyText}
>
Dummy Data
</Button>
</Grid>
</Grid>
</Grid>
</Box>
</Paper>
</Box>
</Grid>
</ThemeProvider>
</>
);
}
}
But the boxes seem to stick together like glue! I do have a custom theme but I cannot seem to get that to work with my App.js file because I am using a class component, rather than a functional component. I understand that you are supposed to use withStyles with class components, but I have struggled to get that to work. So, as a fix, I am trying the Box method.
Any idea how to get my box method to work?
you could use Grid component of material ui and as justify content use space-between
<Grid
style={{width:'140px'}}
container
direction="row"
justify="space-between"
alignItems="center">
<Button/>// here are the buttons
<Button/>
</Grid>
read more about the Grid https://material-ui.com/components/grid/
or you could do it with <div/> i used inline style so that you can read it
easier
<div style ={{ width:'200px',
display:'flex',
flexDirection:'row',
justifyContent:'space-between'
}}>
<button>button 1</button>
<button>button 2</button>
</div>
see working examples with div https://codesandbox.io/s/youthful-taussig-jyesc?file=/src/App.js:67-263
Have you tried to put each button in a box?
<div>
<Box m={1}>
<Button variant="contained">one</Button>
</Box>
<Box m={1}>
<Button variant="contained">two</Button>
</Box>
</div>
The <Box> component works like a <div>, it is a block, so your code can not align buttons in a row.
You can use flex-box to style to get what you want. Use Grid item, container and spacing API of Grid fit your need as well.
Below is an example:
<Grid container spacing={4}>
<Grid item xs={2}>
<Button fullWidth variant="contained" color="primary">
button A{" "}
</Button>
</Grid>
<Grid item xs={2}>
<Button fullWidth variant="contained" color="primary">
button B{" "}
</Button>
</Grid>
</Grid>
When using <Grid container>, you create a flex-box container. By default on ReactJS, the flex-items inside will be aligned by row. That's the reason why We do not have to declare props direction="row" on the container.
You can change the spacing value to change the space between two Grid items. By default, 1 = 8px and it can be customized in material ui as well.
xs indicates the size of your screen, in material ui we have 5 of them xs, sm, md, lg and xl, you can read about it in material ui docs.
fullWidth buttons just to make the buttons take the whole width of containers for ease of seeing space between buttons.
Working example on codesandbox: https://codesandbox.io/s/angry-wescoff-lnj71?file=/src/App.js

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