diff --git a/components/Carousel/Carousel.jsx b/components/Carousel/Carousel.jsx
new file mode 100644
index 000000000..538398382
--- /dev/null
+++ b/components/Carousel/Carousel.jsx
@@ -0,0 +1,121 @@
+require('./Carousel.scss')
+import classNames from 'classnames'
+
+import React, { Component } from 'react'
+import ReactDOM from 'react-dom'
+
+import LeftArrowIcon from '../Icons/LeftArrowIcon'
+import RightArrowIcon from '../Icons/RightArrowIcon'
+
+export default class Carousel extends Component {
+ componentWillMount() {
+ this.handleResize = this.handleResize.bind(this)
+ window.addEventListener('resize', this.handleResize)
+ this.handlePageUp = this.handlePageUp.bind(this)
+ this.handlePageDown = this.handlePageDown.bind(this)
+ this.setState({firstVisibleItem: this.props.firstVisibleItem || 0})
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('resize', this.handleResize)
+ }
+
+ handleResize() {
+ this.validatePagers()
+ }
+
+ componentDidMount() {
+ this.validatePagers()
+ }
+
+ componentDidUpdate() {
+ this.validatePagers()
+ }
+
+ validatePagers() {
+ const pageDownClass = classNames(
+ 'page-down',
+ { hidden: this.state.firstVisibleItem === 0 }
+ )
+ const pageUpClass = classNames(
+ 'page-up',
+ { hidden: this.lastElementVisible(this.state.firstVisibleItem) }
+ )
+ const node = ReactDOM.findDOMNode(this)
+ const pageDownNode = node.querySelector('.page-down')
+ const pageUpNode = node.querySelector('.page-up')
+ pageDownNode.className = pageDownClass
+ pageUpNode.className = pageUpClass
+ }
+
+
+ lastElementVisible(firstVisibleItem) {
+ const node = ReactDOM.findDOMNode(this)
+ const parentNode = node.parentNode
+ const maxWidth = parentNode.getBoundingClientRect().width
+ const visibleAreaNode = node.querySelector('.visible-area')
+ visibleAreaNode.style.width = maxWidth + 'px'
+ const itemNodes = visibleAreaNode.children
+ let width = 0
+ if (firstVisibleItem > 0) {
+ // if first item is not visible, account 20px for page-down element
+ width += 20
+ // account the right margin for page-down (see Carousel.scss)
+ width += 15
+ }
+ for (let i = 0; i < itemNodes.length; i++) {
+ const itemNode = itemNodes[i]
+ width += itemNode.getBoundingClientRect().width
+ if (i < itemNodes.length - 1) {
+ // account 30px for every carousel-item (see Carousel.scss)
+ width += 30
+ }
+ if (width > maxWidth) {
+ return false
+ }
+ }
+ return true
+ }
+
+ handlePageUp() {
+ if (!this.lastElementVisible(this.state.firstVisibleItem + 1)) {
+ const nextFirstVisibleItem = this.state.firstVisibleItem + 1
+ this.setState({firstVisibleItem: nextFirstVisibleItem})
+ }
+ }
+
+ handlePageDown() {
+ if (this.state.firstVisibleItem > 0) {
+ const nextFirstVisibleItem = this.state.firstVisibleItem - 1
+ this.setState({firstVisibleItem: nextFirstVisibleItem})
+ }
+ }
+
+ render() {
+ const carouselItem = (item, idx) => {
+ if (idx < this.state.firstVisibleItem) {
+ return
+ }
+
+ return (
+
+ {item}
+
+ )
+ }
+
+ return (
+
+
+
+
+
+ { this.props.children.map(carouselItem) }
+
+
+
+
+
+ )
+ }
+}
\ No newline at end of file
diff --git a/components/Carousel/Carousel.scss b/components/Carousel/Carousel.scss
new file mode 100644
index 000000000..cb699725a
--- /dev/null
+++ b/components/Carousel/Carousel.scss
@@ -0,0 +1,45 @@
+@import 'topcoder/tc-includes';
+
+$pager-bg-color: #737380;
+
+.Carousel {
+ display: flex;
+ flex-direction: row;
+
+ .page-down {
+ width: 20px;
+ margin-right: 15px;
+ background-color: $pager-bg-color;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+
+ &.hidden {
+ display: none;
+ }
+ }
+
+ .page-up {
+ width: 20px;
+ background-color: $pager-bg-color;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+
+ &.hidden {
+ display: none;
+ }
+ }
+
+ .visible-area {
+ display: flex;
+ flex-direction: row;
+ overflow: hidden;
+
+ .carousel-item:not(:first-child) {
+ margin-left: 30px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/Carousel/CarouselExamples.jsx b/components/Carousel/CarouselExamples.jsx
new file mode 100644
index 000000000..54597e001
--- /dev/null
+++ b/components/Carousel/CarouselExamples.jsx
@@ -0,0 +1,39 @@
+import React from 'react'
+import Carousel from './Carousel'
+
+import StandardListItem from '../StandardListItem/StandardListItem'
+
+require('./CarouselExamples.scss')
+
+
+const CarouselExamples = () => (
+
+
+
With limited width
+
+
+
+
+
+
+
+
With full width
+
+
+
+
+
+
+
+
With limited width and custom first visible element
+
+
+
+
+
+
+
+
+)
+
+module.exports = CarouselExamples
diff --git a/components/Carousel/CarouselExamples.scss b/components/Carousel/CarouselExamples.scss
new file mode 100644
index 000000000..d6d4c8b34
--- /dev/null
+++ b/components/Carousel/CarouselExamples.scss
@@ -0,0 +1,17 @@
+@import 'topcoder/tc-includes';
+
+.Carousel {
+ .StandardListItem {
+ padding: 0px;
+ }
+}
+
+.CarouselExamples {
+ > p {
+ border: 1px solid $accent-gray;
+ margin: 20px 0px;
+ }
+ .limited-width {
+ width: 200px;
+ }
+}
\ No newline at end of file
diff --git a/components/Carousel/placeholder.svg b/components/Carousel/placeholder.svg
new file mode 100644
index 000000000..d3e7220e5
--- /dev/null
+++ b/components/Carousel/placeholder.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/components/Icons/LeftArrowIcon.jsx b/components/Icons/LeftArrowIcon.jsx
new file mode 100644
index 000000000..a4e5b13fe
--- /dev/null
+++ b/components/Icons/LeftArrowIcon.jsx
@@ -0,0 +1,19 @@
+import React from 'react'
+
+const LeftArrowIcon = ({ width, height, fill }) => {
+ const f = (fill || '#A3A3AE')
+ return (
+
+ )
+}
+
+export default LeftArrowIcon
diff --git a/components/Icons/PlaceholderIcon.jsx b/components/Icons/PlaceholderIcon.jsx
new file mode 100644
index 000000000..66beca645
--- /dev/null
+++ b/components/Icons/PlaceholderIcon.jsx
@@ -0,0 +1,12 @@
+import React from 'react'
+
+const PlaceholderIcon = ({ width, height, fill }) => {
+ const f = (fill || '#B47DD6')
+ return (
+
+ )
+}
+
+export default PlaceholderIcon
\ No newline at end of file
diff --git a/components/Icons/RightArrowIcon.jsx b/components/Icons/RightArrowIcon.jsx
new file mode 100644
index 000000000..73c438db9
--- /dev/null
+++ b/components/Icons/RightArrowIcon.jsx
@@ -0,0 +1,17 @@
+import React from 'react'
+
+const RightArrowIcon = ({ width, height, fill }) => {
+ const f = (fill || '#A3A3AE')
+ return (
+
+ )
+}
+
+export default RightArrowIcon
diff --git a/components/Router/Router.cjsx b/components/Router/Router.cjsx
index 5c097682b..1bd920a27 100644
--- a/components/Router/Router.cjsx
+++ b/components/Router/Router.cjsx
@@ -27,6 +27,8 @@ SearchSuggestionsExamples = require '../SearchSuggestions/SearchSuggestionsE
SearchBarExample = require '../SearchBar/SearchBarExamples.jsx'
NavbarExample = require '../Navbar/NavbarExample.jsx'
TCFooterExamples = require '../TCFooter/TCFooterExamples.jsx'
+CarouselExamples = require '../Carousel/CarouselExamples.jsx'
+SubNavExamples = require '../SubNav/SubNavExamples.jsx'
{ Router, Route, Link, IndexRoute, browserHistory } = require 'react-router'
@@ -75,6 +77,10 @@ component = ->
+
+
+
+
diff --git a/components/SubNav/SubNav.jsx b/components/SubNav/SubNav.jsx
new file mode 100644
index 000000000..68234a9d0
--- /dev/null
+++ b/components/SubNav/SubNav.jsx
@@ -0,0 +1,48 @@
+require('./SubNav.scss')
+
+import React from 'react'
+import Carousel from '../Carousel/Carousel'
+import StandardListItem from '../StandardListItem/StandardListItem'
+
+const tcSubNav = {
+ compete : [
+ {img: require('./placeholder.svg'), text: 'Design Challenges', link: '/challenges/design/active'},
+ {img: require('./placeholder.svg'), text: 'Development Challenges', link: '/challenges/develop/active'},
+ {img: require('./placeholder.svg'), text: 'Data Science Challenges', link: '/challenges/data/active'},
+ {img: require('./placeholder.svg'), text: 'Competitive Programming', link: process.env.ARENA_URL}
+ ],
+ learn : [
+ {img: require('./placeholder.svg'), text: 'Getting Started', link: '/getting-started'},
+ {img: require('./placeholder.svg'), text: 'Design Challenges', link: '/community/design'},
+ {img: require('./placeholder.svg'), text: 'Development Challenges', link: '/community/develop'},
+ {img: require('./placeholder.svg'), text: 'Data Science Challenges', link: '/community/data-science'},
+ {img: require('./placeholder.svg'), text: 'Competitive Programming', link: '/community/competitive programming/'}
+ ],
+ community : [
+ {img: require('./placeholder.svg'), text: 'Overview', link: '/community/members'},
+ {img: require('./placeholder.svg'), text: 'TCO16', link: process.env.TCO16_URL},
+ {img: require('./placeholder.svg'), text: 'Programs', link: '/community/member-overview'},
+ {img: require('./placeholder.svg'), text: 'Forums', link: process.env.FORUMS_APP_URL},
+ {img: require('./placeholder.svg'), text: 'Statistics', link: '/community/statistics'},
+ {img: require('./placeholder.svg'), text: 'Events', link: '/community/events'},
+ {img: require('./placeholder.svg'), text: 'Blog', link: '/blog'}
+ ]
+}
+
+const SubNav = ({ primaryMenu = 'compete' }) => {
+ const subNav = tcSubNav[primaryMenu]
+ const subNavMap = (item, idx) => {
+ return (
+
+ )
+ }
+ return (
+
+
+ { subNav.map(subNavMap) }
+
+
+ )
+}
+
+export default SubNav
\ No newline at end of file
diff --git a/components/SubNav/SubNav.scss b/components/SubNav/SubNav.scss
new file mode 100644
index 000000000..78fe29e69
--- /dev/null
+++ b/components/SubNav/SubNav.scss
@@ -0,0 +1,21 @@
+@import 'topcoder/tc-includes';
+
+$subnav-item-bg-color: #B47DD6;
+$subnav-item-text-color: #7A7F83;
+
+.SubNav {
+ background-color: $accent-gray-dark;
+
+ .StandardListItem {
+ padding: 20px 0px;
+
+ .label {
+ color: $subnav-item-text-color;
+ }
+
+ .label:active,
+ .label:hover {
+ color: $white;
+ }
+ }
+}
\ No newline at end of file
diff --git a/components/SubNav/SubNavExamples.jsx b/components/SubNav/SubNavExamples.jsx
new file mode 100644
index 000000000..ebd0c60ab
--- /dev/null
+++ b/components/SubNav/SubNavExamples.jsx
@@ -0,0 +1,24 @@
+import React from 'react'
+import SubNav from './SubNav'
+
+require('./SubNavExamples.scss')
+
+const SubNavExamples = () => (
+
+
+
Compete Sub Navigation
+
+
+
+
Learn Sub Navigation
+
+
+
+
Community Sub Navigation
+
+
+
+
+)
+
+module.exports = SubNavExamples
diff --git a/components/SubNav/SubNavExamples.scss b/components/SubNav/SubNavExamples.scss
new file mode 100644
index 000000000..81bc13175
--- /dev/null
+++ b/components/SubNav/SubNavExamples.scss
@@ -0,0 +1,8 @@
+@import 'topcoder/tc-includes';
+
+.SubNavExamples {
+ > p {
+ border: 1px solid $accent-gray;
+ margin: 20px 0px;
+ }
+}
\ No newline at end of file
diff --git a/components/SubNav/placeholder.svg b/components/SubNav/placeholder.svg
new file mode 100644
index 000000000..d3e7220e5
--- /dev/null
+++ b/components/SubNav/placeholder.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
index 72acaeb80..8314df414 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
},
"scripts": {
"example": "webpack-dev-server -d --progress --inline --colors",
- "dev": "webpack-dev-server -d --progress --inline --colors --dev",
+ "dev": "webpack-dev-server -d --progress --inline --colors --dev --tc",
"clean": "rm -r dist",
"build": "webpack --config webpack.config.js; cp example/index.html dist/",
"lint": "eslint --ext .js,.jsx .",