Skip to content

Commit 6a943bf

Browse files
authored
Merge pull request #72 from topcoder-platform/search
Search
2 parents f284bd4 + a143ca9 commit 6a943bf

File tree

10 files changed

+319
-100
lines changed

10 files changed

+319
-100
lines changed

dist/dev/index.js

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/prod/index.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
+14
Loading

src/components/AccountMenu/index.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class AccountMenu extends React.Component {
4141

4242
render () {
4343
const {
44-
onClose, onClickNotifications, numNotifications, open, menu, switchText, onSwitch, profile
44+
onClose, open, menu, switchText, onSwitch, profile
4545
} = this.props
4646

4747
return (
@@ -82,7 +82,7 @@ class AccountMenu extends React.Component {
8282
onClick={onSwitch}
8383
>
8484
<IconSwitchBusiness className={styles['switch-icon']} />
85-
<Link to={hasAccess(profile.roles)? _.isEmpty(switchText.href) ? switchText.link : switchText.href : 'http://www.topcoder.com'} onClick={onClose}>
85+
<Link to={hasAccess(profile.roles) ? _.isEmpty(switchText.href) ? switchText.link : switchText.href : 'http://www.topcoder.com'} onClick={onClose}>
8686
<span className={styles['switch-to-busniness']}>{switchText.title}</span>
8787
</Link>
8888
</div>
@@ -112,8 +112,6 @@ AccountMenu.propTypes = {
112112
onClose: PropTypes.func,
113113
menu: PropTypes.array,
114114
switchText: PropTypes.shape(),
115-
numNotifications: PropTypes.number,
116-
onClickNotifications: PropTypes.func,
117115
onSwitch: PropTypes.func,
118116
profile: PropTypes.shape()
119117
}

src/components/TopNav/MobileNav.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import PropTypes from 'prop-types'
33
import styles from './MobileNav.module.scss'
44
import IconClose from '../../assets/images/icon-close.svg'
55
import IconMenu from '../../assets/images/icon-menu.svg'
6+
import IconMagnifyingGlass from '../../assets/images/magnifying_glass.svg'
7+
import { config } from 'topcoder-react-utils'
68

7-
const MobileNav = ({ showLeftMenu, onClickLeftMenu, logo, rightMenu }) => (
9+
const MobileNav = ({ showLeftMenu, onClickLeftMenu, logo, rightMenu }) => (<div>
810
<div className={styles.mobileNav}>
911
<div className={styles.leftMenuContainer}>
1012
<button className={styles.menuBtn} onClick={onClickLeftMenu}>
@@ -24,6 +26,23 @@ const MobileNav = ({ showLeftMenu, onClickLeftMenu, logo, rightMenu }) => (
2426
</div>
2527
)}
2628
</div>
29+
{showLeftMenu && (
30+
<div className={styles.search}>
31+
<IconMagnifyingGlass className={styles.icon} />
32+
<input
33+
onKeyPress={(event) => {
34+
if (event.key === 'Enter') {
35+
window.location = `${config.URL.BASE}/search/members?q=${
36+
encodeURIComponent(event.target.value)
37+
}`
38+
}
39+
}}
40+
placeholder='Find members by username or skill'
41+
aria-label='Find members by username or skill'
42+
/>
43+
</div>
44+
)}
45+
</div>
2746
)
2847

2948
MobileNav.propTypes = {

src/components/TopNav/MobileNav.module.scss

+43
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,46 @@
4343
right: 0;
4444
}
4545
}
46+
47+
.icon {
48+
@include mobile-only;
49+
left: 18px;
50+
position: absolute;
51+
top: 16px;
52+
}
53+
54+
.search {
55+
@include mobile-only;
56+
padding: 6px 12px 0;
57+
position: relative;
58+
59+
input {
60+
background: none;
61+
border: 1px solid $gray-50;
62+
box-shadow: none;
63+
color: $white;
64+
font: 16px "Roboto", Helvetica, Arial, sans-serif !important;
65+
height: 32px;
66+
outline: none;
67+
padding-left: 30px !important;
68+
69+
&::placeholder {
70+
color: $gray-50;
71+
}
72+
73+
&:hover {
74+
border: 1px solid $gray-50;
75+
}
76+
77+
&:focus {
78+
border: 1px solid $blue;
79+
}
80+
}
81+
82+
input:active,
83+
input:focus,
84+
input:hover {
85+
box-shadow: none;
86+
outline: none;
87+
}
88+
}

src/components/TopNav/PrimaryNav.js

+136-77
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ import cn from 'classnames'
44
import ResizeDetector from 'react-resize-detector'
55
import ChosenArrow from '../ChosenArrow'
66
import IconArrowSmalldown from '../../assets/images/arrow-small-down.svg'
7+
import MagnifyingGlass from '../../assets/images/magnifying_glass.svg'
78
import styles from './PrimaryNav.module.scss'
9+
import { config } from 'topcoder-react-utils'
10+
11+
const BASE_URL = config.URL.BASE
812

913
const PrimaryNav = ({
1014
collapsed,
@@ -26,93 +30,146 @@ const PrimaryNav = ({
2630
createHandleClickMoreItem,
2731
createSetRef,
2832
showChosenArrow,
29-
chosenArrowX
33+
chosenArrowX,
34+
searchOpened,
35+
toggleSearchOpen
3036
}) => {
3137
const filterNotInMore = menu => !(moreMenu || []).find(x => x.id === menu.id)
32-
38+
const activeTrigger = {
39+
bottom: 50 // The main nav head bottom Y
40+
}
3341
return (
34-
<div className={cn(styles.primaryNavContainer, showLeftMenu && styles.primaryNavContainerOpen)}>
35-
<div className={styles.primaryNav} ref={createSetRef('primaryNav')}>
36-
<a
37-
className={cn(styles.tcLogo, collapsed && styles.tcLogoPush)}
38-
onClick={onClickLogo}
39-
href='/'
40-
>
41-
{logo}
42-
</a>
43-
{menu.map((level1, i) => ([
44-
<span className={styles.primaryLevel1Separator} key={`separator-${i}`} />,
45-
/* Level 1 menu item */
42+
<div>
43+
<div className={cn(styles.primaryNavContainer, showLeftMenu && styles.primaryNavContainerOpen)}>
44+
<div className={styles.primaryNav} ref={createSetRef('primaryNav')}>
4645
<a
47-
className={cn(styles.primaryLevel1, !activeLevel2Id && level1.id === activeLevel1Id && styles.primaryLevel1Open, level1.mobileOnly && styles.mobileOnly)}
48-
href={level1.href}
49-
key={`level1-${i}`}
50-
onClick={createHandleClickLevel1(level1.id, true)}
51-
ref={createSetRef(level1.id)}
46+
className={cn(styles.tcLogo, collapsed && styles.tcLogoPush)}
47+
onClick={onClickLogo}
48+
href='/'
5249
>
53-
{level1.title}
54-
</a>,
55-
/* Level 2 menu */
56-
level1.subMenu && (
57-
<div
58-
className={cn(styles.primaryLevel2Container, level1.id === activeLevel1Id && styles.primaryLevel2ContainerOpen)}
59-
key={`level2-${i}-container`}
60-
ref={createSetRef(`level2Container${i}`)}
50+
{logo}
51+
</a>
52+
{menu.map((level1, i) => ([
53+
<span className={styles.primaryLevel1Separator} key={`separator-${i}`} />,
54+
/* Level 1 menu item */
55+
<a
56+
className={cn(styles.primaryLevel1, !activeLevel2Id && level1.id === activeLevel1Id && styles.primaryLevel1Open, level1.mobileOnly && styles.mobileOnly)}
57+
href={level1.href}
58+
key={`level1-${i}`}
59+
onClick={createHandleClickLevel1(level1.id, true)}
60+
ref={createSetRef(level1.id)}
6161
>
62-
{level1.subMenu.filter(filterNotInMore).map((level2, i) => (
63-
<a
64-
className={cn(styles.primaryLevel2, level2.id === activeLevel2Id && styles.primaryLevel2Open)}
65-
href={level2.href}
66-
key={`level2-${i}`}
67-
onClick={createHandleClickLevel2(level2.id, true)}
68-
ref={createSetRef(level2.id)}
69-
>
70-
{level2.title}
71-
</a>
72-
))}
73-
{/* The More menu */}
74-
{level1.id === activeLevel1Id && moreMenu && moreMenu.length > 0 && (
75-
<div className={cn(styles.moreBtnContainer, openMore && styles.moreOpen)}>
76-
<div className={styles.backdrop} onClick={onCloseMore} />
77-
<button
78-
className={cn(styles.primaryLevel2, styles.moreBtn)}
79-
onClick={handleClickMore}
80-
ref={createSetRef(moreId)}
62+
{level1.title}
63+
</a>,
64+
/* Level 2 menu */
65+
level1.subMenu && (
66+
<div
67+
className={cn(styles.primaryLevel2Container, level1.id === activeLevel1Id && styles.primaryLevel2ContainerOpen)}
68+
key={`level2-${i}-container`}
69+
ref={createSetRef(`level2Container${i}`)}
70+
>
71+
{level1.subMenu.filter(filterNotInMore).map((level2, i) => (
72+
<a
73+
className={cn(styles.primaryLevel2, level2.id === activeLevel2Id && styles.primaryLevel2Open)}
74+
href={level2.href}
75+
key={`level2-${i}`}
76+
onClick={createHandleClickLevel2(level2.id, true)}
77+
ref={createSetRef(level2.id)}
8178
>
82-
<div className={styles.moreBtnMask} />
83-
<span>More</span>
84-
<IconArrowSmalldown />
85-
</button>
86-
<div className={styles.moreContentContainer}>
87-
{moreMenu.map((menu, i) => (
88-
<a
89-
className={cn(styles.primaryLevel2, menu.id === activeLevel2Id && styles.primaryLevel2Open)}
90-
href={menu.href}
91-
key={`more-item-${i}`}
92-
onClick={createHandleClickMoreItem(menu.id)}
93-
>
94-
{menu.title}
95-
</a>
96-
))}
79+
{level2.title}
80+
</a>
81+
))}
82+
{/* The More menu */}
83+
{level1.id === activeLevel1Id && moreMenu && moreMenu.length > 0 && (
84+
<div className={cn(styles.moreBtnContainer, openMore && styles.moreOpen)}>
85+
<div className={styles.backdrop} onClick={onCloseMore} />
86+
<button
87+
className={cn(styles.primaryLevel2, styles.moreBtn)}
88+
onClick={handleClickMore}
89+
ref={createSetRef(moreId)}
90+
>
91+
<div className={styles.moreBtnMask} />
92+
<span>More</span>
93+
<IconArrowSmalldown />
94+
</button>
95+
<div className={styles.moreContentContainer}>
96+
{moreMenu.map((menu, i) => (
97+
<a
98+
className={cn(styles.primaryLevel2, menu.id === activeLevel2Id && styles.primaryLevel2Open)}
99+
href={menu.href}
100+
key={`more-item-${i}`}
101+
onClick={createHandleClickMoreItem(menu.id)}
102+
>
103+
{menu.title}
104+
</a>
105+
))}
106+
</div>
97107
</div>
98-
</div>
99-
)}
108+
)}
109+
</div>
110+
)
111+
]))}
112+
<ChosenArrow show={showChosenArrow} x={chosenArrowX} />
113+
</div>
114+
<div className={styles.primaryNavRight}>
115+
<ResizeDetector
116+
handleWidth
117+
onResize={onRightMenuResize}
118+
/>
119+
{rightMenu && (
120+
<div className={styles.primaryLevel1}>
121+
{rightMenu}
100122
</div>
101-
)
102-
]))}
103-
<ChosenArrow show={showChosenArrow} x={chosenArrowX} />
123+
)}
124+
<div
125+
aria-label='Find members by username or skill'
126+
role='button'
127+
tabIndex={0}
128+
data-menu='search'
129+
className={cn(styles.searchIcon, { opened: searchOpened })}
130+
onFocus={() => toggleSearchOpen(true)}
131+
onBlur={(event) => {
132+
if (event.pageY < activeTrigger.bottom) {
133+
toggleSearchOpen(false)
134+
}
135+
}}
136+
onMouseEnter={(event) => toggleSearchOpen(true)}
137+
onMouseLeave={(event) => {
138+
console.log(`${event.clientX} - ${event.clientY}`)
139+
if (event.pageY < activeTrigger.bottom) {
140+
toggleSearchOpen(false)
141+
}
142+
}}
143+
onTouchStart={(event) => {
144+
if (searchOpened) {
145+
toggleSearchOpen(false)
146+
} else {
147+
toggleSearchOpen(true)
148+
}
149+
}}
150+
>
151+
<MagnifyingGlass />
152+
</div>
153+
</div>
104154
</div>
105-
106-
<div className={styles.primaryNavRight}>
107-
<ResizeDetector
108-
handleWidth
109-
onResize={onRightMenuResize}
155+
<div
156+
role='search'
157+
className={cn(styles.searchField, { opened: searchOpened, closed: !searchOpened })}
158+
onMouseLeave={(event) => { toggleSearchOpen(false) }}
159+
>
160+
<input
161+
ref={createSetRef('searchInputBox')}
162+
onKeyPress={(event) => {
163+
if (event.key === 'Enter') {
164+
window.location = `${BASE_URL}/search/members?q=${
165+
encodeURIComponent(event.target.value)
166+
}`
167+
}
168+
}}
169+
onBlur={() => toggleSearchOpen(false)}
170+
aria-label='Find members by username or skill'
171+
placeholder='Find members by username or skill'
110172
/>
111-
{rightMenu && (
112-
<div className={styles.primaryLevel1}>
113-
{rightMenu}
114-
</div>
115-
)}
116173
</div>
117174
</div>
118175
)
@@ -138,7 +195,9 @@ PrimaryNav.propTypes = {
138195
createHandleClickMoreItem: PropTypes.func,
139196
createSetRef: PropTypes.func,
140197
showChosenArrow: PropTypes.bool,
141-
chosenArrowX: PropTypes.number
198+
chosenArrowX: PropTypes.number,
199+
searchOpened: PropTypes.bool,
200+
toggleSearchOpen: PropTypes.func
142201
}
143202

144203
export default PrimaryNav

0 commit comments

Comments
 (0)