Skip to content

Instantly share code, notes, and snippets.

@steevehook
Last active October 4, 2018 14:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steevehook/add3c36565674ba0513e6ec8f0ea7aee to your computer and use it in GitHub Desktop.
Save steevehook/add3c36565674ba0513e6ec8f0ea7aee to your computer and use it in GitHub Desktop.
GHD React initial components
@import "../core/fonts";
@import "../core/colors";
@import "../core/mixins";
.dropdown-label {
font: 12px $helvetica-light;
text-transform: none;
padding-right: 15px;
@include breakpoint(xp) {
display: inline;
font: 14px $helvetica-light;
}
}
.dropdown-list {
margin: 0;
width: 100%;
@include breakpoint(xp) {
width: 300px;
}
* {
list-style: none;
}
&__item {
position: relative;
padding: 30px 20px 10px 0;
font: 12px $avantgarde-gothic;
text-transform: uppercase;
cursor: pointer;
z-index: 1;
background-color: $white;
border-top: 1px solid $white-dark;
text-align: center;
@include breakpoint(sm) {
text-align: right;
}
@include breakpoint(xp) {
border-right: 1px solid $white-dark;
padding: 20px 20px 20px 0;
font: 14px $avantgarde-gothic;
}
&:first-child {
border-top: none;
}
&:last-child {
border-bottom: 1px solid $white-dark;
}
& > .dropdown-list {
position: absolute;
margin: 10px 0;
@include breakpoint(xp) {
margin: 20px 0;
}
& > .dropdown-list__item {
padding: 20px;
border-right: none;
&:hover {
background-color: $white-dark;
}
}
}
&--selected {
background-color: $white-dark;
}
}
&__item-header {
white-space: nowrap;
}
.dropdown-arrow {
margin-left: 10px;
font-size: 15px;
font-weight: bold;
}
}
import React from 'react';
import Dropdown, {CLOSE_ALL, TOGGLE} from '../Core/Dropdown';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
sortOptions: {
label: '',
items: []
},
subCategories: {
label: '',
items: []
},
dropdownList: []
};
}
com = {
sortLabel: document.querySelector('.sort-label'),
sortOptions: document.getElementById('sort'),
selectedSortOption: document.querySelector('#sort > option[selected]'),
subCategoriesLabel: document.querySelector('.subcategories-label'),
subCategories: document.getElementById('subcategories'),
selectedSubCategory: document.querySelector('#subcategories > option[selected]')
};
componentWillMount() {
let {
sortLabel,
sortOptions,
selectedSortOption,
subCategoriesLabel,
subCategories,
selectedSubCategory
} = this.com;
sortLabel = sortLabel && sortLabel.innerText || '';
sortOptions = Array
.prototype
.slice
.call(sortOptions || [])
.map(({value, innerText, selected}) => ({
value,
text: innerText,
selected
}));
subCategoriesLabel = subCategoriesLabel && subCategoriesLabel.innerText || '';
subCategories = Array
.prototype
.slice
.call(subCategories || [])
.map(({value, innerText, selected}) => ({
value,
text: innerText,
selected
}));
this.setState(() => ({
sortOptions: {
label: sortLabel.trim(),
items: sortOptions,
selected: {
value: selectedSortOption.value,
text: selectedSortOption.innerText,
selected: selectedSortOption.selected,
}
},
subCategories: {
label: subCategoriesLabel.trim(),
items: subCategories,
selected: {
value: selectedSubCategory.value,
text: selectedSubCategory.innerText,
selected: selectedSubCategory.selected,
}
}
}));
}
onInit = (dropdown) => {
this.setState(prevState => ({
dropdownList: prevState.dropdownList.concat(dropdown)
}));
};
onChange = ({selected, label}) => {
let items = [];
const unselect = option => {
option.selected = false;
return option;
};
switch (label.toLowerCase()) {
case 'sort by':
items = this.state.sortOptions.items.map(unselect);
break;
case 'shop by':
items = this.state.subCategories.items.map(unselect);
break;
}
const index = items.indexOf(selected);
items[index].selected = true;
};
onTransition = (ACTION, dropdown) => {
switch (ACTION) {
case TOGGLE:
dropdown.toggle();
break;
case CLOSE_ALL:
this.state.dropdownList.forEach(dropdownItem => {
if (!dropdown || dropdownItem !== dropdown) {
dropdownItem.close();
}
});
break;
}
};
onSubmit = (label) => {
let selectedItem;
let selectBox;
const form = document.getElementById('refineForm');
switch (label.toLowerCase()) {
case 'sort by':
selectedItem = this.state.sortOptions.items.find(item => item.selected);
selectBox = document.getElementById('sort');
break;
case 'shop by':
selectedItem = this.state.subCategories.items.find(item => item.selected);
selectBox = document.getElementById('subcategories');
break;
}
const option = Array
.from(selectBox.children)
.map(option => {
option.removeAttribute('selected');
return option
})
.find(option => option.value === selectedItem.value);
option.setAttribute('selected', true);
form.submit();
};
render() {
return (
<div className="product-menu">
<div className="product-menu__wrapper">
<Dropdown
label={this.state.subCategories.label}
items={this.state.subCategories.items}
selected={this.state.subCategories.selected}
tabIndex={0}
onInit={this.onInit}
onSubmit={this.onSubmit}
onChange={this.onChange}
onTransition={this.onTransition}
/>
<Dropdown
label={this.state.sortOptions.label}
items={this.state.sortOptions.items}
selected={this.state.sortOptions.selected}
tabIndex={1}
onInit={this.onInit}
onSubmit={this.onSubmit}
onChange={this.onChange}
onTransition={this.onTransition}
/>
</div>
</div>
);
}
}
export default App;
import React from 'react';
export const TOGGLE = 'TOGGLE';
export const CLOSE_ALL = 'CLOSE_ALL';
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
isOpen: false,
selected: props.items.find(item => item.selected)
}
}
componentWillMount() {
this.props.onInit(this);
document.addEventListener('click', e => {
if (!e.target.closest('.dropdown-list')) {
this.props.onTransition(CLOSE_ALL)
}
});
}
close = () => this.setState(() => ({isOpen: false}));
toggle = () => this.setState(prevState => ({isOpen: !prevState.isOpen}));
handleTransition = () => {
this.props.onTransition(CLOSE_ALL, this);
this.props.onTransition(TOGGLE, this);
window.onkeydown = !this.state.isOpen ? e => e.preventDefault() : null;
};
handleSwitch = e => {
const {items, label} = this.props;
const index = this.props.items.indexOf(this.state.selected);
let selected;
switch (e.which) {
// Enter or space bar
case 13:
case 32:
this.props.onSubmit(this.props.label);
break;
// Up arrow
case 38:
selected = items[index - 1 < 0 ? items.length - 1 : index - 1];
this.setState(() => ({selected}));
this.props.onChange({selected, label});
break;
// Down arrow
case 40:
selected = items[index + 1 > items.length - 1 ? 0 : index + 1];
this.setState(() => ({selected}));
this.props.onChange({selected, label});
break;
// Any other non matching keys
default:
this.props.onTransition(CLOSE_ALL);
window.onkeydown = null;
}
};
handleClick = (selected) => {
const {label, onChange, onSubmit} = this.props;
this.setState(() => ({selected}));
onChange({selected, label});
onSubmit(label);
};
render() {
const {
listClass = '',
listItemClass = '',
listItemHeaderClass = '',
iconClass = ''
} = this.props;
return this.props.items.length > 0 && (
<ul
className={`dropdown-list ${listClass}`}
tabIndex={this.props.tabIndex}
onKeyDown={this.handleSwitch}
>
<li className={`dropdown-list__item ${listItemClass}`} onClick={this.handleTransition}>
<span className="dropdown-label">
{this.props.label}
</span>
<span className={`dropdown-list__item-header ${listItemHeaderClass}`}>
{this.props.selected.text}
</span>
<span className={`icon-chevron-down dropdown-arrow ${iconClass}`}
/>
<ul className={`dropdown-list ${this.state.isOpen ? 'visible' : 'hidden'}`}>
{this.props.items.map(item => (
<li
key={item.value}
className={`dropdown-list__item ${item.selected ? 'dropdown-list__item--selected' : ''}`}
onClick={() => this.handleClick(item)}
>
{item.text}
</li>
))}
</ul>
</li>
</ul>
);
}
}
export default Dropdown;
import React from 'react';
import ReactDOM from 'react-dom';
const appContainer = document.getElementById('refresh-product-menu-app');
if (appContainer) {
(async () => {
const {default: App} = await import ('./App');
ReactDOM.render(<App/>, appContainer);
})();
}
<%@ taglib prefix="dropdown" tagdir="/WEB-INF/tags/refresh-dropdown" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="s" uri="http://www.springframework.org/tags" %>
<%@ attribute name="category" required="true" type="com.ghdhair.app.data.CategoryData" %>
<form
action="<c:url value="${requestScope['javax.servlet.forward.request_uri']}"/>"
id="refineForm"
name="refineForm"
method="post"
class="hidden"
>
<c:if test="${!empty category.categories}">
<s:message code="category.select.label" var="shopLabel"/>
<dropdown:menu
label="${shopLabel}"
items="${category.categories}"
currentValue="${category.seoName}"
input_name="subcategories"
valuePropertyName="seoName"
/>
</c:if>
<c:if test="${!empty category.products}">
<s:message code="category.sort.label" var="sortLabel"/>
<dropdown:menu
label="${sortLabel}"
items="${sortOptions}"
currentValue="${sortOption}"
input_name="sort"
/>
</c:if>
</form>
<div id="refresh-product-menu-app">
</div>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment