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 ( + + ico-arrow-big-left + Created with Sketch. + + + + + + + + ) +} + +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 ( + + ico-arrow-big-right + Created with Sketch. + + + + + + ) +} + +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 .",