├── index.d.ts ├── src ├── index.ts └── Dropdown │ ├── arrows.css │ ├── Dropdown.css │ └── index.tsx ├── .npmignore ├── icons8-kiwi-80.png ├── index.js ├── tsconfig.json ├── Dropdown ├── index.d.ts ├── arrows.css ├── Dropdown.css └── index.js ├── package.json ├── .gitignore └── README.md /index.d.ts: -------------------------------------------------------------------------------- 1 | export * from './Dropdown'; 2 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Dropdown' 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | package-lock.json 4 | tsconfig.json 5 | icons8-kiwi-80.png -------------------------------------------------------------------------------- /icons8-kiwi-80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KiTiVi/react-kiwi-dropdown/HEAD/icons8-kiwi-80.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | function __export(m) { 3 | for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; 4 | } 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | __export(require("./Dropdown")); 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": ".", 4 | "target": "es5", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitAny": false, 8 | "removeComments": true, 9 | "declaration": true, 10 | "jsx": "react", 11 | "lib": ["es2015", "dom", "es2017"], 12 | "types": ["react"], 13 | "esModuleInterop": true 14 | }, 15 | "files": ["src/index.ts"], 16 | "exclude": ["node_modules"] 17 | } 18 | -------------------------------------------------------------------------------- /Dropdown/index.d.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Dropdown.css'; 3 | import './arrows.css'; 4 | interface Option { 5 | value: string; 6 | content: any; 7 | icon?: any; 8 | } 9 | interface Dropdown { 10 | options: Option[]; 11 | selectedOption?: string | string[]; 12 | resetValue?: any; 13 | onChange: (selectedOption: any) => void; 14 | buttonLabel?: string; 15 | buttonIndicator?: boolean; 16 | buttonIndicatorContent?: any; 17 | buttonArrow?: 'single' | 'double'; 18 | selectedOptionIcon?: any; 19 | className?: string; 20 | } 21 | declare const Dropdown: React.FC; 22 | export default Dropdown; 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-kiwi-dropdown", 3 | "version": "1.0.13", 4 | "description": "A minimal, easy-to-use and highly adjustable dropdown component made with ReactJS.", 5 | "main": "index.js", 6 | "scripts": { 7 | "prepare": "tsc && echo 'Finished building NPM package'" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://KiTiVi@github.com/KiTiVi/react-kiwi-dropdown.git" 12 | }, 13 | "author": "Kim Vigren", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/KiTiVi/react-kiwi-dropdown/issues" 17 | }, 18 | "homepage": "https://github.com/KiTiVi/react-kiwi-dropdown#readme", 19 | "types": "index.d.ts", 20 | "peerDependencies": { 21 | "react": "^16.8.6" 22 | }, 23 | "devDependencies": { 24 | "@types/react": "^16.8.23", 25 | "react": "^16.8.6", 26 | "react-dom": "^16.8.6", 27 | "typescript": "^3.5.3" 28 | }, 29 | "dependencies": {}, 30 | "keywords": [ 31 | "react", 32 | "dropdown", 33 | "kiwi" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /Dropdown/arrows.css: -------------------------------------------------------------------------------- 1 | .KIWI-single-arrow { 2 | display: inline-block; 3 | height: 0; 4 | width: 0; 5 | margin-left: 12px; 6 | border-left: 5.5px solid rgba(0, 0, 0, 0); 7 | border-right: 5.5px solid rgba(0, 0, 0, 0); 8 | border-top: 5.5px solid rgba(0, 0, 0, 0.25); 9 | position: relative; 10 | } 11 | 12 | .KIWI-double-arrow { 13 | position: relative; 14 | margin: 0 12px; 15 | } 16 | 17 | .KIWI-double-arrow::before { 18 | content: ''; 19 | display: inline-block; 20 | height: 0; 21 | width: 0; 22 | position: absolute; 23 | top: -8.5px; 24 | border-left: 5.5px solid rgba(0, 0, 0, 0); 25 | border-right: 5.5px solid rgba(0, 0, 0, 0); 26 | border-bottom: 5.5px solid rgba(0, 0, 0, 0.25); 27 | } 28 | 29 | .KIWI-double-arrow::after { 30 | content: ''; 31 | display: inline-block; 32 | height: 0; 33 | width: 0; 34 | position: absolute; 35 | top: 1.5px; 36 | border-left: 5.5px solid rgba(0, 0, 0, 0); 37 | border-right: 5.5px solid rgba(0, 0, 0, 0); 38 | border-top: 5.5px solid rgba(0, 0, 0, 0.25); 39 | } -------------------------------------------------------------------------------- /src/Dropdown/arrows.css: -------------------------------------------------------------------------------- 1 | .KIWI-single-arrow { 2 | display: inline-block; 3 | height: 0; 4 | width: 0; 5 | margin-left: 12px; 6 | border-left: 5.5px solid rgba(0, 0, 0, 0); 7 | border-right: 5.5px solid rgba(0, 0, 0, 0); 8 | border-top: 5.5px solid rgba(0, 0, 0, 0.25); 9 | position: relative; 10 | } 11 | 12 | .KIWI-double-arrow { 13 | position: relative; 14 | margin: 0 12px; 15 | } 16 | 17 | .KIWI-double-arrow::before { 18 | content: ''; 19 | display: inline-block; 20 | height: 0; 21 | width: 0; 22 | position: absolute; 23 | top: -8.5px; 24 | border-left: 5.5px solid rgba(0, 0, 0, 0); 25 | border-right: 5.5px solid rgba(0, 0, 0, 0); 26 | border-bottom: 5.5px solid rgba(0, 0, 0, 0.25); 27 | } 28 | 29 | .KIWI-double-arrow::after { 30 | content: ''; 31 | display: inline-block; 32 | height: 0; 33 | width: 0; 34 | position: absolute; 35 | top: 1.5px; 36 | border-left: 5.5px solid rgba(0, 0, 0, 0); 37 | border-right: 5.5px solid rgba(0, 0, 0, 0); 38 | border-top: 5.5px solid rgba(0, 0, 0, 0.25); 39 | } -------------------------------------------------------------------------------- /Dropdown/Dropdown.css: -------------------------------------------------------------------------------- 1 | .KIWI-button { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | min-height: 40px; 6 | padding: 8px; 7 | background: .fff; 8 | border: 1px solid rgba(0, 0, 0, 0.1); 9 | font-size: 14px; 10 | } 11 | 12 | .KIWI-button:focus { 13 | outline: none; 14 | } 15 | 16 | .KIWI-button-indicator { 17 | display: inline-block; 18 | min-height: 16px; 19 | min-width: 16px; 20 | border: 1px solid rgba(0, 0, 0, 0.25); 21 | margin-right: 7.5px; 22 | } 23 | 24 | .KIWI-option-list { 25 | position: absolute; 26 | list-style: none; 27 | margin: 0; 28 | padding: 0; 29 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 30 | } 31 | 32 | .KIWI-option { 33 | display: flex; 34 | align-items: center; 35 | width: 100%; 36 | padding: 8px; 37 | background: .fff; 38 | border: none; 39 | position: relative; 40 | font-size: 12px; 41 | } 42 | 43 | .option:hover { 44 | text-shadow: 0 0 0.65px .333, 0 0 0.65px .333; 45 | } 46 | 47 | .option .selected { 48 | text-shadow: 0 0 0.65px .333, 0 0 0.65px .333; 49 | } -------------------------------------------------------------------------------- /src/Dropdown/Dropdown.css: -------------------------------------------------------------------------------- 1 | .KIWI-button { 2 | display: flex; 3 | justify-content: space-between; 4 | align-items: center; 5 | min-height: 40px; 6 | padding: 8px; 7 | background: .fff; 8 | border: 1px solid rgba(0, 0, 0, 0.1); 9 | font-size: 14px; 10 | } 11 | 12 | .KIWI-button:focus { 13 | outline: none; 14 | } 15 | 16 | .KIWI-button-indicator { 17 | display: inline-block; 18 | min-height: 16px; 19 | min-width: 16px; 20 | border: 1px solid rgba(0, 0, 0, 0.25); 21 | margin-right: 7.5px; 22 | } 23 | 24 | .KIWI-option-list { 25 | position: absolute; 26 | list-style: none; 27 | margin: 0; 28 | padding: 0; 29 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 30 | } 31 | 32 | .KIWI-option { 33 | display: flex; 34 | align-items: center; 35 | width: 100%; 36 | padding: 8px; 37 | background: .fff; 38 | border: none; 39 | position: relative; 40 | font-size: 12px; 41 | } 42 | 43 | .option:hover { 44 | text-shadow: 0 0 0.65px .333, 0 0 0.65px .333; 45 | } 46 | 47 | .option .selected { 48 | text-shadow: 0 0 0.65px .333, 0 0 0.65px .333; 49 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | test/ 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # TypeScript v1 declaration files 43 | typings/ 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | 63 | # next.js build output 64 | .next 65 | -------------------------------------------------------------------------------- /Dropdown/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importStar = (this && this.__importStar) || function (mod) { 3 | if (mod && mod.__esModule) return mod; 4 | var result = {}; 5 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; 6 | result["default"] = mod; 7 | return result; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | var react_1 = __importStar(require("react")); 11 | require("./Dropdown.css"); 12 | require("./arrows.css"); 13 | var Dropdown = function (_a) { 14 | var options = _a.options, selectedOption = _a.selectedOption, resetValue = _a.resetValue, onChange = _a.onChange, buttonLabel = _a.buttonLabel, buttonIndicator = _a.buttonIndicator, buttonIndicatorContent = _a.buttonIndicatorContent, buttonArrow = _a.buttonArrow, selectedOptionIcon = _a.selectedOptionIcon, className = _a.className; 15 | var _b = react_1.useState(false), showOptions = _b[0], setShowOptions = _b[1]; 16 | var refDropdown = react_1.useRef(null); 17 | var refButtonIndicator = react_1.useRef(null); 18 | react_1.useEffect(function () { 19 | if (showOptions) { 20 | document.addEventListener('click', handleClick); 21 | return function () { 22 | document.removeEventListener('click', handleClick); 23 | }; 24 | } 25 | }, [showOptions]); 26 | var handleClick = function (e) { 27 | if (refDropdown && 28 | refDropdown.current && 29 | refDropdown.current.contains(e.target)) { 30 | return; 31 | } 32 | setShowOptions(false); 33 | }; 34 | var toggleOptions = function (e) { 35 | if (selectedOption && 36 | refButtonIndicator && 37 | refButtonIndicator.current && 38 | refButtonIndicator.current.contains(e.target)) { 39 | return; 40 | } 41 | setShowOptions(function (prevShowOptions) { return !prevShowOptions; }); 42 | }; 43 | return (react_1.default.createElement("div", { className: className }, 44 | react_1.default.createElement("button", { onClick: function (e) { return toggleOptions(e); }, className: "KIWI-button" }, 45 | buttonIndicator && (react_1.default.createElement("span", { ref: refButtonIndicator, className: "KIWI-button-indicator " + (selectedOption && selectedOption.length ? 'selected' : ''), onClick: function () { return selectedOption && onChange(resetValue); } }, buttonIndicatorContent)), 46 | buttonLabel && buttonLabel, 47 | buttonArrow === 'double' && react_1.default.createElement("span", { className: "KIWI-double-arrow" }), 48 | !buttonArrow && react_1.default.createElement("span", { className: "KIWI-single-arrow" })), 49 | showOptions && (react_1.default.createElement("ul", { ref: refDropdown, className: "KIWI-option-list" }, options.map(function (option) { return (react_1.default.createElement("li", { key: option.value }, 50 | react_1.default.createElement("button", { className: "KIWI-option " + (selectedOption === option.value || 51 | (selectedOption && 52 | selectedOption.length && 53 | selectedOption.includes(option.value)) 54 | ? 'selected' 55 | : ''), onClick: function () { return onChange(option); } }, 56 | option.icon, 57 | react_1.default.createElement("span", { style: { 58 | opacity: selectedOption === option.value ? 1 : 0 59 | } }, selectedOptionIcon), 60 | option.content))); }))))); 61 | }; 62 | exports.default = Dropdown; 63 | -------------------------------------------------------------------------------- /src/Dropdown/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react' 2 | import './Dropdown.css' 3 | import './arrows.css' 4 | 5 | interface Option { 6 | value: string 7 | content: any 8 | icon?: any 9 | } 10 | 11 | interface Dropdown { 12 | options: Option[] 13 | selectedOption?: string | string[] 14 | resetValue?: any 15 | onChange: (selectedOption: any) => void 16 | buttonLabel?: string 17 | buttonIndicator?: boolean 18 | buttonIndicatorContent?: any 19 | buttonArrow?: 'single' | 'double' 20 | selectedOptionIcon?: any 21 | className?: string 22 | } 23 | 24 | const Dropdown: React.FC = ({ 25 | options, 26 | selectedOption, 27 | resetValue, 28 | onChange, 29 | buttonLabel, 30 | buttonIndicator, 31 | buttonIndicatorContent, 32 | buttonArrow, 33 | selectedOptionIcon, 34 | className 35 | }) => { 36 | const [showOptions, setShowOptions] = useState(false) 37 | const refDropdown = useRef(null) 38 | const refButtonIndicator = useRef(null) 39 | 40 | useEffect(() => { 41 | if (showOptions) { 42 | document.addEventListener('click', handleClick) 43 | return () => { 44 | document.removeEventListener('click', handleClick) 45 | } 46 | } 47 | }, [showOptions]) 48 | 49 | const handleClick = (e: any) => { 50 | if ( 51 | refDropdown && 52 | refDropdown.current && 53 | refDropdown.current.contains(e.target) 54 | ) { 55 | return 56 | } 57 | 58 | setShowOptions(false) 59 | } 60 | 61 | const toggleOptions = (e: any) => { 62 | if ( 63 | selectedOption && 64 | refButtonIndicator && 65 | refButtonIndicator.current && 66 | refButtonIndicator.current.contains(e.target) 67 | ) { 68 | return 69 | } 70 | setShowOptions(prevShowOptions => !prevShowOptions) 71 | } 72 | 73 | return ( 74 |
75 | 91 | {showOptions && ( 92 |
    93 | {options.map(option => ( 94 |
  • 95 | 117 |
  • 118 | ))} 119 |
120 | )} 121 |
122 | ) 123 | } 124 | 125 | export default Dropdown 126 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![alt text](./icons8-kiwi-80.png) react-kiwi-dropdown 2 | 3 | A minimal, easy-to-use and highly adjustable dropdown component made with ReactJS. 4 | 5 | ## Why? 6 | 7 | Styling \