Skip to content

Commit e4380d1

Browse files
frozenheliumAdityaKhatri
authored andcommitted
Add ScrollTabs component
1 parent 86b58ba commit e4380d1

File tree

4 files changed

+196
-70
lines changed

4 files changed

+196
-70
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@import '../../../stylesheets/base';
22

33
.formatted-date {
4+
white-space: nowrap;
45
font-family: $font-family-monospace;
56
}

components/View/ScrollTabs/index.js

Lines changed: 139 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,84 @@
11
import PropTypes from 'prop-types';
22
import React from 'react';
33

4-
import List from '../List';
54
import Button from '../../Action/Button';
5+
import HashManager from '../../General/HashManager';
6+
import List from '../List';
67
import iconNames from '../../../constants/iconNames';
8+
import { addClassName } from '../../../utils/common';
9+
710
import styles from './styles.scss';
811

912
const propTypes = {
13+
active: PropTypes.string,
14+
children: PropTypes.node,
1015
className: PropTypes.string,
16+
itemClassName: PropTypes.string,
17+
defaultHash: PropTypes.string,
18+
onClick: PropTypes.func,
19+
replaceHistory: PropTypes.bool,
1120
tabs: PropTypes.shape({
1221
dummy: PropTypes.string,
1322
}),
14-
onClick: PropTypes.func,
15-
active: PropTypes.string,
23+
useHash: PropTypes.bool,
24+
modifier: PropTypes.func,
1625
};
1726

1827
const defaultProps = {
1928
active: undefined,
29+
children: null,
2030
className: '',
21-
tabs: [],
31+
itemClassName: '',
32+
defaultHash: undefined,
2233
onClick: () => {},
34+
replaceHistory: false,
35+
tabs: {},
36+
useHash: false,
37+
modifier: undefined,
2338
};
2439

25-
export default class ScrollTabs extends React.Component {
40+
41+
export default class FixedTabs extends React.Component {
2642
static propTypes = propTypes;
2743
static defaultProps = defaultProps;
2844

45+
constructor(props) {
46+
super(props);
47+
this.state = {
48+
hash: undefined,
49+
};
50+
this.tabsContainerRef = React.createRef();
51+
this.mainContainerRef = React.createRef();
52+
}
53+
54+
componentDidMount() {
55+
const { current: tabsContainer } = this.tabsContainerRef;
56+
const { current: mainContainer } = this.mainContainerRef;
57+
58+
if (tabsContainer.scrollWidth > tabsContainer.clientWidth) {
59+
addClassName(mainContainer, styles.scroll);
60+
}
61+
}
62+
2963
getClassName = () => {
3064
const { className } = this.props;
3165

3266
const classNames = [
3367
className,
3468
'scroll-tabs',
69+
styles.scrollTabs,
3570
];
3671

3772
return classNames.join(' ');
3873
}
3974

4075
getTabClassName = (isActive) => {
76+
const { itemClassName } = this.props;
77+
4178
const classNames = [
79+
itemClassName,
4280
styles.tab,
43-
'scroll-tab',
81+
'fixed-tab',
4482
];
4583

4684
if (isActive) {
@@ -51,69 +89,129 @@ export default class ScrollTabs extends React.Component {
5189
return classNames.join(' ');
5290
}
5391

54-
handleTabClick = (key) => {
55-
const { onClick } = this.props;
92+
handleHashChange = (hash) => {
93+
this.setState({ hash });
94+
}
95+
96+
handleTabClick = (key, e) => {
97+
const {
98+
onClick,
99+
useHash,
100+
replaceHistory,
101+
} = this.props;
102+
103+
if (useHash && replaceHistory) {
104+
window.location.replace(`#/${key}`);
105+
e.preventDefault();
106+
}
107+
56108
onClick(key);
57109
}
58110

59-
renderTab = (key, data) => {
111+
handleLeftButtonClick = () => {
112+
const { current: tabsContainer } = this.tabsContainerRef;
113+
114+
tabsContainer.scrollLeft -= 48;
115+
}
116+
117+
handleRightButtonClick = () => {
118+
const { current: tabsContainer } = this.tabsContainerRef;
119+
120+
tabsContainer.scrollLeft += 48;
121+
}
122+
123+
renderTab = (_, data) => {
60124
const {
61125
active,
62126
tabs,
127+
useHash,
128+
modifier,
63129
} = this.props;
64-
const isActive = data === active;
130+
131+
if (!tabs[data]) {
132+
return null;
133+
}
134+
135+
const onClick = (e) => { this.handleTabClick(data, e); };
136+
const content = modifier ? modifier(data) : tabs[data];
137+
138+
if (!useHash) {
139+
const isActive = data === active;
140+
const className = this.getTabClassName(isActive);
141+
142+
return (
143+
<button
144+
onClick={onClick}
145+
className={className}
146+
key={data}
147+
type="button"
148+
>
149+
{ content }
150+
</button>
151+
);
152+
}
153+
154+
const { hash } = this.state;
155+
156+
const isActive = hash === data;
65157
const className = this.getTabClassName(isActive);
66-
const onClick = () => { this.handleTabClick(data); };
67158

68159
return (
69-
<button
160+
<a
161+
onClick={onClick}
162+
href={`#/${data}`}
70163
className={className}
71164
key={data}
72-
onClick={onClick}
73-
type="button"
74165
>
75-
{ tabs[data] }
76-
</button>
166+
{ content }
167+
</a>
77168
);
78169
}
79170

80171
render() {
81172
const {
82173
tabs,
174+
useHash,
175+
defaultHash,
83176
} = this.props;
84177

178+
// FIXME: generate tabList when tabs change
85179
const tabList = Object.keys(tabs);
86-
const leftButtonClassNames = [
87-
styles.scrollButton,
88-
styles.scrollButtonLeft,
89-
];
90-
const rightButtonClassNames = [
91-
styles.scrollButton,
92-
styles.scrollButtonRight,
93-
];
94-
180+
const className = this.getClassName();
95181
return (
96-
<div className={styles.scrollTabs}>
182+
<div
183+
ref={this.mainContainerRef}
184+
className={className}
185+
>
186+
<HashManager
187+
tabs={tabs}
188+
useHash={useHash}
189+
defaultHash={defaultHash}
190+
onHashChange={this.handleHashChange}
191+
/>
97192
<Button
98-
className={leftButtonClassNames.join(' ')}
99-
transparent
100-
smallVerticalPadding
101-
smallHorizontalPadding
102-
onClick={this.handleScrollLeftButtonClick}
103193
iconName={iconNames.chevronLeft}
194+
transparent
195+
className={styles.leftButton}
196+
onClick={this.handleLeftButtonClick}
104197
/>
105-
<List
106-
data={tabList}
107-
modifier={this.renderTab}
108-
/>
109-
<div className={styles.void} />
198+
<div
199+
ref={this.tabsContainerRef}
200+
className={styles.tabsContainer}
201+
>
202+
<List
203+
data={tabList}
204+
modifier={this.renderTab}
205+
/>
206+
<div className={styles.blank}>
207+
{ this.props.children }
208+
</div>
209+
</div>
110210
<Button
111-
className={rightButtonClassNames.join(' ')}
112-
transparent
113-
smallVerticalPadding
114-
smallHorizontalPadding
115-
onClick={this.handleScrollLeftButtonClick}
116211
iconName={iconNames.chevronRight}
212+
transparent
213+
className={styles.rightButton}
214+
onClick={this.handleRightButtonClick}
117215
/>
118216
</div>
119217
);

components/View/ScrollTabs/styles.scss

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,44 +2,69 @@
22

33
.scroll-tabs {
44
display: flex;
5-
position: relative;
6-
width: 200px;
7-
overflow: hidden;
5+
justify-content: center;
6+
background-color: $color-foreground;
87

9-
.tab {
8+
.left-button {
9+
display: none;
10+
align-self: center;
1011
flex-shrink: 0;
11-
outline: 0;
12-
border: 0;
13-
border-bottom: $width-separator-medium solid $color-separator;
14-
background-color: transparent;
15-
cursor: pointer;
16-
padding: $spacing-small $spacing-medium;
17-
color: $color-text-label-on-light;
18-
font-family: inherit;
19-
font-size: inherit;
20-
21-
&.active {
22-
border-bottom-color: $color-accent;
23-
color: $color-text;
24-
}
12+
padding: $spacing-small-alt;
2513
}
2614

27-
.scroll-button {
28-
position: absolute;
15+
.right-button {
16+
display: none;
2917
align-self: center;
18+
flex-shrink: 0;
19+
padding: $spacing-small-alt;
3020
}
3121

32-
.scroll-button-left {
33-
left: 0;
34-
}
22+
.tabs-container {
23+
display: flex;
24+
flex-grow: 1;
25+
overflow-x: auto;
26+
27+
&::-webkit-scrollbar {
28+
display: none;
29+
}
30+
31+
.tab {
32+
user-select: none;
33+
flex-shrink: 0;
34+
outline: 0;
35+
border: 0;
36+
border-bottom: $width-separator-medium solid $color-separator;
37+
background-color: transparent;
38+
cursor: pointer;
39+
padding: $spacing-small-alt $spacing-medium;
40+
text-transform: uppercase;
41+
color: $color-text-label;
42+
font-family: inherit;
43+
font-size: inherit;
3544

36-
.scroll-button-right {
37-
right: 0;
45+
&.active {
46+
border-bottom-color: $color-accent;
47+
color: $color-text;
48+
}
49+
}
50+
51+
.blank {
52+
display: flex;
53+
align-items: center;
54+
align-self: stretch;
55+
flex-grow: 1;
56+
justify-content: flex-end;
57+
border-bottom: $width-separator-medium solid $color-separator;
58+
}
3859
}
3960

40-
.void {
41-
align-self: stretch;
42-
flex-grow: 1;
43-
border-bottom: $width-separator-medium solid $color-separator;
61+
&.scroll {
62+
.left-button {
63+
display: initial;
64+
}
65+
66+
.right-button {
67+
display: initial;
68+
}
4469
}
4570
}

stylesheets/_base.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
@import 'dimens';
55
@import 'utils';
66

7+
@import url("https://cdnjs.cloudflare.com/ajax/libs/ionicons/2.0.1/css/ionicons.min.css");
8+
79
$font-family-sans-serif: sans-serif !default;
810
$font-family-monospace: monospace !default;
911
$font-family-icons: 'Ionicons' !default;

0 commit comments

Comments
 (0)