Material UI button moves down when textField error message shown - css

I have added the material ui error message display to my text field, if I click on the button without any text typed on the text field, an error message will be shown, this message will push down the button, I want to stop that, I am unable to find the way to do that
This is my form code.
<Grid container justify='center' alignContent='center' >
<Grid item xs={12} >
<TextField
id="outlined-full-width"
label="Input"
style={{ width:'100%',marginTop:30 }}
placeholder="Add A Todo Item "
margin="normal"
InputLabelProps={{
shrink: true,
}}
error={this.state.errorState }
helperText={
this.state.errorState && "Item name can't be blank"
}
size="large"
variant="outlined"
value={newItem}
onChange={handleInput}
/>
</Grid>
</Grid>
<CardActions>
<Grid container justify='center' alignContent='center' >
<Grid item xs={12} md={6} >
<Button
type="submit"
variant="contained"
color='inherit'
fontsize='inherit'
style={styles.add}
startIcon={<AddIcon/>}
>
Add Item
</Button>
</Grid>
</Grid>
</CardActions>
</form>

In your main theme, add these rules so that the helperText / error text stays within the margin of your field. This will prevent the text from pushing down the content underneath.
const theme = createTheme({
// ...
MuiFormHelperText: {
root: {
// Use existing space / prevents shifting content below field
marginTop: 0,
height: 0,
},
}
})

Related

Prevent MUI multi-select from expanding parent flexbox past screen size

I have a multi-select MUI element, and when I select multiple items on a small screen, the elements make the parent larger, so you would have to scroll to the right to view everything.
How can I fixe this?
<Box
display={"flex"}
flexDirection={"column"}
rowGap={3}
sx={{
mb: 1,
px: 2,
}}
>
<FormControl>
<InputLabel id="simple-select-label">
Job completed by
</InputLabel>
<Select
label="Job completed by"
name="employeeID"
multiple={true}
id="simple-select"
sx={{
}}
required
value={values.employeeID}
onChange={(e) => {
setValues({
...values,
employeeID: e.target.value,
})
}}
>
{employeesLists?.map((employee) => (
<MenuItem value={employee.id} key={employee.id}>
{employee.firstName} {employee.lastName}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
To fix this, I had to use the override nested component strategy: https://mui.com/material-ui/customization/how-to-customize/#1-one-off-customization
I added this to fix the problem, to override the default styles. It can be placed on <Select>.
sx={{
'& #simple-select': {
whiteSpace: 'normal',
},
}}
``

MUI Grid - align to end

I'm relatively new to using MUI Grid, and am not trying to do anything too complicated, yet I haven't been able to find a solution on other posts or in the Flexbox/MUI docs. Essentially I just want to align my 'Cancel' and 'Save' buttons all the way to the right on the screen. Docs seem to make this look extremely simple, yet my page layout isn't affected. I've been trying alignItems, alignContent, justifyContent, etc. just to try and make it work - to no avail.
<Grid container xs={12} justify-content='flex-end'>
<Grid item xs={6}/>
<Grid item xs={3}>
<button
onClick={() => doSomething()}
Click here to cancel
</button>
</Grid>
<Grid item xs={3}>
<button
onClick={() => doSomething()}
Click here to Save
</button>
</Grid>
</Grid>
If you are using the Material-UI styled component. You could use the following styles definition below:
.myClass {
display: flex;
flex-direction: row;
justify-content: end;
align-items: flex-end;
}
It seems like the typing mistake with justify-content='flex-end' since it should be justifyContent="flex-end":
<Grid spacing={2} container xs={12} justifyContent="flex-end">
<Grid item xs={3}>
<Item>xs=3</Item>
</Grid>
<Grid item xs={3}>
<Item>xs=3</Item>
</Grid>
</Grid>
Working example
Also since you want to align items along just one horizontal direction you may want to use Stack rather than Grid. Stack is made for aligning items along the vertical or horizontal axis and won't create redundant for these cases DOM-elements(grid-item containers) :
<Stack direction="row" spacing={2} justifyContent="flex-end">
<Item>Item 1</Item>
<Item>Item 2</Item>
<Item>Item 3</Item>
</Stack>
The example of UI generated by this snippet can be seen at the link above.

Adding margin around button components in Material UI

I have a centered Toolbar in Material UI that has 3 components. Each component is a button. I want to add a margin around each button. I tried adding the {mt} option to the component button as below, but nothing changed. I've been experimenting with makeStyles, but haven't figured it out.
<Box display="flex">
<Box m="auto">
<Toolbar>
<SeasonComponent>
<WeekComponent>
<GameComponent>
</Toolbar>
</Box>
</Box>
Season component:
return (
<div>
<Button
variant="outlined"
color="primary"
onClick={handleClickOpen}
mt={2}
>
Button text
</Button>
</div>
Here is a picture of the buttons:
You can wrap buttons in a horizontal <Stack>:
<Toolbar>
<Stack spacing={2} direction="row">
<SeasonComponent>
<WeekComponent>
<GameComponent>
</Stack>
</Toolbar>
Here's a simple example: https://codesandbox.io/s/basicbuttons-material-demo-forked-0gpgz?file=/demo.js:234-269
Rather than upgrade my repo to version 5 right now, I just added an invisible button between the buttons. Not a perfect solution, but it solved the problem in the short term.
// SpacerButton.js
import React from 'react';
import Button from '#material-ui/core/Button';
const style = {
minWidth: 1
}
export default function SpacerButton(props) {
return (
<Button variant="text" style={style}>
</Button>
);
}

Material-UI Autocomplete/Popper - How to prevent x-scroll

I am currently working on implementing the Material-UI Autocomplete component, and I want each option label to display an icon and some text. However, I only want the popper to be the full width of the text input, and then add an ellipsis to any text that would normally overflow on the Popper width.
In my renderOption method, if I return <Typography noWrap>"Text"</Typography> it succesfully forces the text to have an ellipsis, but if I put it in a Grid or Flex box in order to also include the icon, then the Popper component will be able to scroll horizontally. Is there a way to fix the Popper viewport to the width of Autocomplete, so that way the text in the renderOption method will wrap?
import React from 'react';
import InputAdornment from '#material-ui/core/InputAdornment';
import TextField from '#material-ui/core/TextField';
import SearchIcon from '#material-ui/icons/Search';
import PersonIcon from '#material-ui/icons/Person';
import GraphicEqIcon from '#material-ui/icons/GraphicEq';
import { Grid } from '#material-ui/core';
import { Autocomplete } from '#material-ui/lab';
import Typography from '#material-ui/core/Typography';
function SearchBarDisplay({ options = [], onChange, onSelectValue, value = '' }) {
function getOptionLabel(option) {
if (option.name) {
return option.name;
} else if (option.username) {
return option.username;
} else if (option.type === 'advanced') {
return option.value;
} else {
return null;
}
}
function renderOption(name, username, type) {
if (name) {
return (
<Grid container alignItems="center" spacing={1} wrap={'nowrap'}>
<GraphicEqIcon />
<Grid item>
<Typography noWrap>{name}</Typography>
</Grid>
</Grid>
);
} else if (username) {
return (
<Grid container alignItems="center" spacing={1} wrap={'nowrap'}>
<PersonIcon />
<Grid item>
<Typography noWrap>{username}</Typography>
</Grid>
</Grid>
);
} else if (type === 'advanced') {
return (
<Grid container alignItems="center" spacing={1}>
<SearchIcon />
<Grid item>
<Typography
noWrap={true}
color="textSecondary">{`See more results for "${value}"`}</Typography>
</Grid>
</Grid>
);
} else {
return null;
}
}
return (
<Autocomplete
id="autocomplete"
options={options}
getOptionSelected={(option, value) => option._id === value._id}
getOptionLabel={(option) => getOptionLabel(option)}
onChange={(event, value) => {
onSelectValue(value);
}}
onInputChange={(event, value) => onChange(value)}
renderOption={({ name, username, type }) => renderOption(name, username, type)}
renderInput={(params) => (
<TextField
{...params}
placeholder="Search for podcasts or users"
margin="normal"
variant="outlined"
InputProps={{
...params.InputProps,
startAdornment: (
<>
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
</>
)
}}
/>
)}
/>
);
}
export default SearchBarDisplay;
To change each option label to have just one line with ellipsis (... - tree dots) instead two or more lines, do you need to change your renderOption property to the follow code:
<Autocomplete
...
getOptionLabel={(option: any) => `${option.label} (${option.code})`}
renderOption={(option) => (
<React.Fragment>
<div style={{ textOverflow: 'ellipsis', overflow: "hidden", whiteSpace: "nowrap" }}>
{option.label} ({option.code})
</div>
</React.Fragment>
)}
...
/>
See an example with the countries list at CodeSandbox that I did: https://codesandbox.io/s/autocomplete-with-ellipsis-i8hnw

How to have multiple row tabs in Material UI Tabs

I have almost 30 tabs inside Material UI Tabs, the user has to scroll two times to see all the tabs, I would like to show the tabs in two rows with scroll instead of one row with scroll, this will help the user to see most of the tabs in one glance.
How can I do something like this ?, I looked over Material UI document but i couldn't find anything useful, I tried manually giving it CSS style but I wasn't able to achieve my goal (my CSS skills are mediocre).
To show what I mean by multiple row tabs, here is a sample image :
Any help is much appreciated.
I did a little hack:
Use 2 different Tabs components and adjust indexes:
<Box sx={{ display: 'flex',justifyContent: 'center', flexWrap: 'wrap'}}>
<Tabs value={value} onChange={handleChange}>
<Tab label='Precios'/>
<Tab label='Usuarios'/>
<Tab label='Plan'/>
</Tabs>
<Tabs value={value - 3 } onChange={handleChange2}>
<Tab label='Empleados'/>
</Tabs>
</Box>
And manage change for this adjustment:
const handleChange = (event, newValue) => {
setValue(newValue);
};
const handleChange2 = (event, newValue) => {
setValue(newValue + 3);
};
You just change the number 3 for the number of tabs in your first component.
Saludos
I am struggling with similar problem. I have gone throw the documentation and looks like this is not possible using Tabs/Tab features. For now I can see two options:
Use properties variant="scrollable" and scrollButtons="auto" like mentioned above. This will not give you what you expected but at least it is working.
Implement your own tabs. You can use Grid for it. Below example is just to show an approach. It is not redy solution, but it can be easily adjusted.
const useStyles = makeStyles((theme) => ({
navigationLinkContainer: {
// up to you
},
navigationLinkButtonActive: {
color: '#ffffff',
// up to you
},
}));
const NavigationLink = (props) => {
const classes = useStyles();
return (
<Grid item className={classes.navigationLinkContainer}>
<Button
component={Link}
onClick={props.onClick}
>
{props.children}
</Button>
</Grid>
);
};
const NavigationHeaders = (props) => {
const classes = useStyles();
const { headers, className } = props;
const [activeTab, setActiveTab] = React.useState('');
const isActive = (headerId) => headerId === activeTab;
return (
<>
<Grid container >
{headers.map((header) => (
<NavigationLink
className={classnames(isActive(header.id) && classes.navigationLinkButtonActive)}
key={header.id}
onClick={() => setActiveTab(header.id)}
>
{header.title}
</NavigationLink>
))}
</Grid>
{/* some content here shown base on activeTab */}
</>
);
};
I also came to the conclusion that this is not currently possible with Tabs/Tab. I tried using <br />, <hr />, <Divider />, functions to insert breaks, making multiple rows of tabs (which messed up selection), wrapping Tabs in span with max-width (also messed up selection), you name it. I ultimately decided to use scroll on small screens.
I figured out the smallest screen size that would show my tabs properly, then used scroll for any smaller.
const mql = window.matchMedia('(max-width: 2000px)');
const smallScreen = mql.matches;
<Tabs
value={tabValue}
onChange={handleTabChange}
orientation="horizontal"
variant={smallScreen ? 'scrollable' : 'standard'}
centered={!smallScreen}
>
<Tab label="1" />
<Tab label="1" />
<Tab label="3" />
<Tab label="4" />
<Tab label="5" />
</Tabs>
You could add an event handler to change on resize, but was not necessary for my use case
You can set flexWrap: 'wrap' in the tab container component which is a flexbox:
<Tabs
// disable the tab indicator because it doesn't work well with wrapped container
TabIndicatorProps={{ sx: { display: 'none' } }}
sx={{
'& .MuiTabs-flexContainer': {
flexWrap: 'wrap',
},
}}
{...}
>
https://codesandbox.io/s/69733826-material-ui-responsive-tabs-5q57p?file=/demo.js
Try going through the doc of any stuff you use to safe unnecessary problems in future
visit this for more details and full code https://material-ui.com/components/tabs/
<div className={classes.root}>
<AppBar position="static">
<Tabs value={value}
onChange={handleChange}
aria-label="simple tabs example"
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="scrollable auto tabs example"
>
<Tab label="Item One" {...a11yProps(0)} />
<Tab label="Item Two" {...a11yProps(1)} />
<Tab label="Item Three" {...a11yProps(2)} />
</Tabs>
</AppBar>
<TabPanel value={value} index={0}>
Item One
</TabPanel>
<TabPanel value={value} index={1}>
Item Two
</TabPanel>
<TabPanel value={value} index={2}>
Item Three
</TabPanel>
</div>
Edit: please notice the variant in Tabs

Resources