Skip to content

Instantly share code, notes, and snippets.

@staydecent
Last active October 19, 2017 17:07
Show Gist options
  • Select an option

  • Save staydecent/64acbd69e04e5c78f23f191eff760694 to your computer and use it in GitHub Desktop.

Select an option

Save staydecent/64acbd69e04e5c78f23f191eff760694 to your computer and use it in GitHub Desktop.
Preact Dropdown
import check from 'check-arg-types'
import createAction from '/util/createAction'
export const TOGGLE_DROPDOWN = 'TOGGLE_DROPDOWN'
const toggleDropdownAction = createAction(TOGGLE_DROPDOWN)
export function toggleDropdown (uid) {
check(arguments, ['string'])
return toggleDropdownAction({uid})
}
export const CLOSE_ALL_DROPDOWNS = 'CLOSE_ALL_DROPDOWNS'
const closeAllDropdownsAction = createAction(CLOSE_ALL_DROPDOWNS)
export function closeAllDropdowns () {
return closeAllDropdownsAction({})
}
export default toggleDropdown
import {pathOr, pipe, filter} from 'wasmuth'
import {compose, setNodeName} from '/util/compose'
import guid from '/util/guid'
import {DownArrow} from '/components/elements/arrow'
import Button from '/components/elements/button'
import Level from '/components/elements/level'
import {dispatch, getState, subscribe} from '/store'
import {closeAllDropdowns, toggleDropdown} from './actions'
// DOM event to close all Dropdown's on off-click
const isDropdown = (el) =>
(el.classList && el.classList.contains('dropdown-menu')) ||
(el.classList && el.classList.contains('btn-dropdown'))
document.body.addEventListener('click', (ev) => {
const dds = pipe(
pathOr({}, ['_dropdowns']),
filter((val) => val),
Object.keys
)(getState())
if (!dds.length) {
return
}
let el = ev.target
if (isDropdown(el)) return
while (el.parentNode) {
el = el.parentNode
if (isDropdown(el)) return
}
dispatch(closeAllDropdowns())
})
const Dropdown = compose(
setNodeName('Dropdown'),
function componentWillMount () {
this._uid = guid()
},
function componentDidMount () {
// Adjust position dynamically
// const offset = this.base.offsetWidth < 1000
// ? this.base.offsetWidth / 2
// : this.base.offsetWidth / 4
// const menuEl = this.base.querySelector('.dropdown-menu')
// menuEl.style = `margin-left: -${offset}px`
// Sync state
const syncState = () => {
const open = pathOr(false, ['_dropdowns', this._uid], getState())
if (open !== this.state.open) {
if (open === false && this.state.open === undefined) {
return
}
this.setState({open})
}
}
syncState()
subscribe(() => syncState())
},
function handleClick (ev) {
ev.preventDefault()
dispatch(toggleDropdown(this._uid))
},
function render ({
open,
handleClick,
Trigger,
buttonText = 'Select',
noWrapper = false,
children
}) {
const cls = open
? 'dropdown-menu open'
: open === false
? 'dropdown-menu close'
: 'dropdown-menu'
return <div>
{Trigger === undefined
? <Button className='btn-dropdown black-ghost-btn' to={handleClick}>
<Level noPadding>{buttonText} <DownArrow /></Level>
</Button>
: <Trigger className='btn-dropdown' onClick={handleClick} />}
{noWrapper
? open && children
: <div className={cls}>
<div class='dropdown-arrow' />
{children}
</div>}
</div>
}
)
export default Dropdown
@import (reference) 'variables.less';
.dropdown-menu {
display: block;
z-index: 11;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 0 20px 0 rgba(0,0,0,.1);
min-width: 210px;
padding: 20px 30px;
position: absolute;
margin-top: 20px;
opacity: 0;
&.open {
animation: lower-in .35s;
opacity: 1;
}
&.close {
animation: raise-up .35s;
opacity: 0;
}
.dropdown-arrow {
border-style: solid;
border-color: transparent transparent #fff transparent;
border-width: 0px 8px 8px 8px;
top: -8px;
right: 45%;
position: absolute;
z-index: 999;
&:after {
border-color: transparent transparent #fff transparent;
border-style: solid;
border-width: 0px 7px 7px 7px;
top: 1px;
content: " ";
position: absolute;
left: -7px;
}
}
.down-caret {
display: block;
width: 1rem;
height: 1rem;
transform: rotate(90deg) scaleY(2);
margin-left: 1rem;
margin-top: 0.5rem;
font-weight: 400;
}
li.hr {
background-color: @hr;
width: 100%;
height: 1px;
margin: 20px 0;
}
a {
color: @black;
&.blue {
color: @blue;
}
}
}
@keyframes lower-in {
0% { transform: translateY(-20%); opacity: 0; }
100% { transform: translateY(0); opacity: 1; }
}
@keyframes raise-up {
0% { transform: translateY(0); opacity: 1; }
60% { transform: translateY(-20%); opacity: 0; }
}
import {assoc, path, pathSet} from 'wasmuth'
import {TOGGLE_DROPDOWN, CLOSE_ALL_DROPDOWNS} from './actions'
const dropdownReducer = ({type, payload}, state) => {
switch (type) {
case TOGGLE_DROPDOWN:
const newVal = !path(['_dropdowns', payload.uid], state)
return pathSet(['_dropdowns'], {[payload.uid]: newVal}, state)
case CLOSE_ALL_DROPDOWNS:
return assoc('_dropdowns', {}, state)
default:
return state
}
}
export default dropdownReducer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment