Last active
October 19, 2017 17:07
-
-
Save staydecent/64acbd69e04e5c78f23f191eff760694 to your computer and use it in GitHub Desktop.
Preact Dropdown
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| @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; } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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