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
Related
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>
);
}
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
This is a little bit weird situation. I have 2 divs in a FormControl and my input label for Select completely fine. However, when I style it in flexDirection: 'row' The label is outboxed as you can see in below picture.
Edit: It works fine when I comment out justifyContent.
When it is ok.
When it is in flexDirection: row
Below is my code
const useStyles = makeStyles((theme) => ({
submitForm: {
display: 'flex',
flexDirection: 'row',
margin: theme.spacing(1),
justifyContent: 'space-evenly'
},
bacsInputs: {
minWidth: '200px'
}
}))
const DemoInventory = () => {
const bacsInfoInputs = () => {
return(
<>
<p><b>Enter BACS information below and click SAVE</b></p>
<FormControl variant="outlined" className={classes.submitForm}>
<div>
<InputLabel clasName={classes.submitForm} id="demo-simple-select-outlined-label">Select BACS Unit</InputLabel>
<Select
labelId="demo-simple-select-outlined-label"
id="demo-simple-select-outlined"
value={bacsUnit}
onChange={onChangeInput}
label="BACS Unit"
className={classes.bacsInputs}
>
<MenuItem value={'Product-1'}>Product-1</MenuItem>
<MenuItem value={'Product-2'}>Product-2</MenuItem>
<MenuItem value={'Product-3'}>Product-3</MenuItem>
<MenuItem value={'Product-4'}>Product-4</MenuItem>
<MenuItem value={'Product-5'}>Product-5</MenuItem>
<MenuItem value={'Product-6'}>Product-6</MenuItem>
<MenuItem value={'Product-7'}>Product-7</MenuItem>
</Select>
<p>Serial number input</p>
<p>Part number input</p>
<p>Location input</p>
<p>description input</p>
</div>
<div>
<p>deployment input</p>
<p>ship Date input</p>
<p>Expected return date input</p>
</div>
<p>Save Button</p>
</FormControl>
</>
)
}
}
return (
<Fragment>
{bacsInfoInputs()}
</Fragment>
)
it is because you've provided a class classes.submitForm to the FormControl element which is changing its default style and thus you're getting a different style in the selectbox.
FormControl elements are used for wrapping the input elements only to get extra control and features, so don't put any other elements other than input elements inside this element. So put only the Select and InputLabel elements only in this case for better result.
I've made a custom User Confirmation Dialog from Material UI Dialog component like here
I faced a problem to overwrite the Dialog's font. I can overwrite color or background color, but fonts in Dialog's header or buttons are inherited from Material-UI. I successfully overwrote Material-UI fonts in other components, but not in this part with callback:
const UserConfirmation = (
message: string,
callback: (shouldNavigate: boolean) => void
) => {
const container = document.createElement('div')
container.setAttribute('custom-confirmation-navigation', '')
document.body.appendChild(container)
const closeModal = (shouldNavigate: boolean) => {
ReactDOM.unmountComponentAtNode(container)
callback(shouldNavigate)
}
ReactDOM.render(
<>
<Dialog
fullWidth={true}
maxWidth="sm"
open={true}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitleWrapper
style={{fontFamily: `BuenosAires !important`, color: `orange`}}
>
Discard draft?
</DialogTitleWrapper>
<DialogContent>
<p> {message} </p>
</DialogContent>
<DialogActionsWrapper>
<Button
onClick={() => closeModal(true)}
fullWidth={true}
variant="outlined"
label="Discard"
/>
<div style={{ width: '80%' }} />
<Button
onClick={() => closeModal(false)}
fullWidth={true}
variant="contained"
label="Cancel"
/>
</DialogActionsWrapper>
</Dialog>
</>,
container
)
}
export default UserConfirmation
Thank Alex
That works brilliant for me:
<DialogTitle disableTypography="true">
Also, buttons' labels were fixed by that:
label={<h5 style={{ textTransform: 'none' }}>Cancel</h5>}
You can use classes object to Override or extend the styles applied to the component.
here
create custom styles like below
const useStyles = makeStyles({
customDialogTitle: {
fontFamily:'Impact'//sans-serif
}
});
and assign to classes
<DialogTitle disableTypography="true"
classes={{
root: classes.customDialogTitle
}}
>
.....
</DialogTitle>
sample sandbox
i have a main screen that contains 2 Tabs in which every one renders different result.
At the end of the the first tabs, i have two buttons that : First Button --> redirect to First Tab year 1 (the default tab)
Second Button --> redirect to First Tab Version Two year 2
So, basically the buttons redirect to different Tab every time selected . I need to only change the tab not the whole screen.
here is the code that i used and it's working fine for the default Tab but i don't know how to implement the buttons so that it redirects to different tabs with changing the main screen ... Any help ?
Main screen :
<Tabs>
<Tab heading="First Tab">
<FirstTab text={key} />
</Tab>
<Tab heading="Second Tab">
<SecondTab/>
</Tab>
</Tabs>
the First Tab (the default one)
<ScrollView>
...
<View style={{flexDirection: 'row'}}>
<Button active>
<Text>
year 1
</Text>
</Button>
<Button>
<Text> year 2 </Text>
</Button>
</View>
</View>
</ScrollView>
Here is an image that explains what i need to do :
I also tried this method: Implementing Footer Tabs in Native React using Native Base and the code i used is :
<Tabs>
<Tab>
<Content>{this.renderSelectedTab()} </Content>
</Tab>
<Tab>
<SecondTab/>
</Tab>
</Tabs>
<View style={{flexDirection: 'row'}}>
<Button active={this.selectedTab==='2016'}
onPress={() => this.state.setState({selectedTab: '2016'})}>
<Text> 2016 </Text>
</Button>
<Button active={this.state.selectedTab==='2015'}
onPress={() => this.setState({selectedTab: '2015'})} >
<Text> 2015 </Text>
</Button>
</View>
..
renderSelectedTab () {
console.log("this.state.selectedTab", this.selectedTab )
switch (this.state.selectedTab) {
case '2016':
return (<Tab2016 />);
break;
case '2015':
return (<Tab2015 />);
break;
default:
return (<Tab2016 />);
}
}
And i get
If i use this.selectedTab instead of this.state.selectedTab it runs fine but i get in the console : and it runs directly the default value and the buttons don't work
Actually, that is not how it works.
You need to use the page and initialPage props of Tabs Component and then to change tabs you only need to update the page props to the tab index
So that is an example:
import React from 'react';
import { Button, Text } from 'react-native';
import { Container, Header, Content, Tab, Tabs } from 'native-base';
export default class AuthScreenContent extends React.Component {
constructor(props) {
super(props);
this.state = {activeTab: 0, initialTab: 0}
}
render() {
return (
<Container>
<Tabs initialPage={this.state.initialTab} page={this.state.activeTab}>
<Tab heading="Sign up">
<Text>This is the register's content </Text>
<Button
title="Go to login tab"
onPress={() => this.setState({activeTab: 1})}
/>
</Tab>
<Tab heading="Login">
<Text>This is the login's content </Text>
<Button
title="Go to register tab"
onPress={() => this.setState({activeTab: 0})}
/>
</Tab>
</Tabs>
</Container>
);
}
};