index.html

Material UI Browser Style Tab

Word count: 710Reading time: 4 min
2020/03/07 Share

In case someone needs to build a browser style (and behavior) tab bar using material UI, like the one in the image.

There is an add button after the right most tab, and each tab has a close button.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import React from "react";
import {withStyles} from "@material-ui/core/styles";
import PropTypes from "prop-types";
import Container from "@material-ui/core/Container";
import AppBar from "@material-ui/core/AppBar";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import AddIcon from '@material-ui/icons/Add';
import IconButton from "@material-ui/core/IconButton";
import CloseIcon from '@material-ui/icons/Close';
import {v4 as uuidV4} from "uuid";
import Typography from "@material-ui/core/Typography";

// This class stores all fields required for a tab
class DefaultTab {
constructor() {
this.id = uuidV4(); // use UUID as key
this.name = "New tab"; // Title displayed on the tab
}
}

const styles = theme => ({
Container: {
padding: theme.spacing(3)
},
TabsBar: {
width: "100%",
backgroundColor: theme.palette.background.paper
},
Tab: { // Separator for each tab
borderRight: "1px solid #ddd"
},
TabLabel: { // Ellipsis for long tab title
textOverflow: "ellipsis",
overflow: "hidden",
whiteSpace: "nowrap"
},
NewTabButton: { // Make new tab button narrower than default
minWidth: 0
},
// Pull right the tab close button
CustomTabLabel: {
width: "100%",
display: "flex"
},
CloseTabButton: {
marginLeft: "auto",
padding: 0
}
});

class DemoApp extends React.PureComponent {
static propTypes = {
title: PropTypes.string
};

static defaultProps = {
title: "Alan please add details"
}

constructor(props) {
super(props);
this.state = {
tabIndex: "", // Point to the current tab
tabs: [] // Stores all tabs and content
}
}

changeTab = (event, newValue) => {
this.setState({tabIndex: newValue})
};

newTab = () => {
const newTab = new DefaultTab();
this.setState(prevState => ({
tabs: [...prevState.tabs, newTab],
tabIndex: newTab.id
}));
};

closeThisTab = event => {
event.stopPropagation();
const indexToRemove = event.currentTarget.getAttribute('data-index');
let newTabIndex = this.state.tabIndex; // Default no tab switch
if (this.state.tabIndex === indexToRemove) {
const indexOfElement = this.state.tabs.findIndex(({id}) => id === indexToRemove);
if (indexOfElement < this.state.tabs.length - 1) { // Switch to the right tab
newTabIndex = this.state.tabs[indexOfElement + 1].id;
} else if (indexOfElement > 0) { // Switch to the left tab
newTabIndex = this.state.tabs[indexOfElement - 1].id;
} else { // Last tab
newTabIndex = 0
}
}
this.setState(prevState => {
return {
tabs: prevState.tabs.filter(({id}) => (id !== indexToRemove)),
tabIndex: newTabIndex
}
});
};

closeAllTab = () => {
this.setState({
tabs: [new DefaultTab()]
})
};

render() {
const {classes} = this.props;
return (
<>
{/** App bar */}
<AppBar position="static">
<ToolBar>
<Typography variant="h6" noWrap>
{this.props.title}
</Typography>
</ToolBar>
</AppBar>
{/** Tabs bar */}
<AppBar className={classes.TabsBar} position="static" color="default">
<Tabs
value={this.state.tabIndex || 0}
onChange={this.changeTab}
indicatorColor="primary"
textColor="primary"
variant="scrollable"
scrollButtons="auto"
aria-label="opened working tabs"
>
{this.state.tabs.map((tab) => (
<Tab key={tab.id} value={tab.id}
className={classes.Tab}
component={"div"}
label={
<div className={classes.CustomTabLabel}>
<div className={classes.TabLabel}>
{tab.name}
</div>
<IconButton className={classes.CloseTabButton} size={"small"}
onClick={this.closeThisTab} data-index={tab.id}>
<CloseIcon/>
</IconButton>
</div>
}
/>
))}
<Tab className={classes.NewTabButton}
component={"div"}
label={
<IconButton size={"small"}>
<AddIcon/>
</IconButton>
} onClick={this.newTab}/>
</Tabs>
</AppBar>
{/** Main view */}
<Container className={classes.Container} maxWidth={"xl"}>
{this.state.tabs.map(tab => (
<Typography
key={tab.id}
component="div"
role="tabpanel"
hidden={this.state.tabIndex !== tab.id}
>
{tab.name + ': ' + tab.id}
</Typography>
))}
</Container>
</>
);
}
}

export default withStyles(styles)(DemoApp);
CATALOG