<Dropdown />

Toggle contextual overlays for displaying lists of links and more with Dropdowns.

Component

1import React, { useRef, useState, useEffect } from "react";
2
3import "./Styles/_dropdown.scss";
4
5const Dropdown = ({ children }) => {
6 const [visible, setVisible] = useState(false);
7
8 const ref = useRef();
9 useOnClickOutside(ref, () => setVisible(false));
10
11
12 return (
13 <div ref={ref} className={`dropdown ${visible ? 'is-active' : ''}`}>
14 <div className="dropdown-btn">
15 <button onClick={() => setVisible(!visible)} className="button">
16 <span>Dropdown button</span>
17 <span className="icon is-small">
18 <i className="fas fa-angle-down" aria-hidden="true" />
19 </span>
20 </button>
21 </div>
22 <div className="dropdown-menu">
23 <div className="dropdown-content">
24 {children}
25 </div>
26 </div>
27 </div>
28 );
29};
30export default Dropdown;
31
32export { DropdownItem } from './Components/DropdownItem'
33export { DropdownDivider } from './Components/DropdownDivider'
34
35
36
37// Hooks For Adding Functionalities.
38const useOnClickOutside = (ref, handler) => {
39 useEffect(() => {
40 const listener = (event) => {
41 // Do nothing if clicking ref's element or descendent elements
42 if (!ref.current || ref.current.contains(event.target)) {
43 return;
44 }
45 handler(event);
46 };
47 document.addEventListener("mousedown", listener);
48 document.addEventListener("touchstart", listener);
49 return () => {
50 document.removeEventListener("mousedown", listener);
51 document.removeEventListener("touchstart", listener);
52 };
53 }, [ref, handler]);
54};

Styles

1$properties: (
2 position: "",
3 width: 12rem,
4 bgclr: white,
5);
6
7.dropdown {
8 display: inline-flex;
9 position: relative;
10 vertical-align: top;
11
12 &.is-active,
13 &.is-hoverable:hover {
14 .dropdown-menu {
15 opacity: 1;
16 transform: translateY(0);
17 pointer-events: auto;
18 }
19 }
20
21 @if map-get($properties, position) == "right" {
22 .dropdown-menu {
23 left: auto;
24 right: 0;
25 }
26 } @else if map-get($properties, position) == "up" {
27 .dropdown-menu {
28 bottom: 100%;
29 padding-bottom: 4px;
30 padding-top: initial;
31 top: auto;
32 }
33 }
34
35 &-menu {
36 position: absolute;
37 left: 0;
38 min-width: map-get($properties, width);
39 padding-top: 5px;
40 top: 100%;
41 z-index: 20;
42 opacity: 0;
43 pointer-events: none;
44 transform: translateY(-10px);
45 transition: opacity 100ms ease-in-out, transform 100ms ease-in-out;
46 }
47
48 &-content {
49 background-color: map-get($properties, bgclr);
50 border-radius: 0.375em;
51 box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
52 padding-bottom: 0.5rem;
53 padding-top: 0.5rem;
54 }
55
56 &-item {
57 display: block;
58 color: grey;
59 font-size: 0.875rem;
60 line-height: 1.5;
61 padding: 0.375rem 1rem;
62 position: relative;
63 }
64
65 &-divider {
66 background-color: #ededed;
67 border: none;
68 display: block;
69 height: 1.5px;
70 margin: 0.5rem 0;
71 }
72}
73
74a.dropdown-item,
75button.dropdown-item {
76 padding-right: 3rem;
77 text-align: inherit;
78 white-space: nowrap;
79 width: 100%;
80 text-decoration: none;
81
82 &:hover {
83 background-color: hsl(0, 0%, 96%);
84 color: hsl(0, 0%, 4%);
85 }
86
87 &.is-active {
88 background-color: #485fc7;
89 color: #fff;
90 }
91}

Usage

1