├── .github
└── FUNDING.yml
├── .gitignore
├── LICENSE
├── README.md
├── example
├── README.md
├── package-lock.json
├── package.json
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
│ ├── App.js
│ ├── App.test.js
│ ├── index.css
│ ├── index.js
│ └── static
│ └── demo_image.png
├── package-lock.json
├── package.json
└── src
├── .eslintrc
├── AutocompleteCommandField.js
├── CommandRow.js
├── CommandsList.js
└── index.js
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: saharmor
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/
2 | /example/node_modules/
3 | /node_modules/
4 | /dist/
5 | /example/build/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Sahar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
React Super Command ⚡
4 |
5 |
6 | [](https://www.npmjs.com/package/react-super-cmd) [](https://standardjs.com)
7 |
8 | The command-line experience for the no-mouse generation. A blazing fast command line for your users to seamlessly interact with your React app.
9 |
10 | [Live demo](https://saharmor.github.io/react-super-cmd/)
11 |
12 | ## Installation
13 |
14 | ### npm
15 |
16 | ```bash
17 | npm install --save react-super-cmd
18 | ```
19 |
20 | ### yarn
21 |
22 | ```bash
23 | yarn add react-super-cmd
24 | ```
25 |
26 | ## Usage
27 |
28 | ```jsx
29 | import React from 'react';
30 | import CommandLineModal from "react-super-cmd";
31 |
32 | import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined';
33 | import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline';
34 | import RemoveCircleOutlineOutlinedIcon from '@material-ui/icons/RemoveCircleOutlineOutlined';
35 | import OfflineBoltOutlined from "@material-ui/icons/OfflineBoltOutlined";
36 |
37 |
38 | const App = () => {
39 | const [cmdLineModal, setCmdLineModal] = useState(true);
40 |
41 | const commands = {
42 | SEARCH_CONTACT: {
43 | name: 'Search', logo: , shortcut: 'S', callback: () => console.log('search')
44 | },
45 | ADD_CONTACT: {
46 | name: 'Add', logo: , shortcut: '⌘ A', callback: () => console.log('add')
47 | },
48 | };
49 |
50 | function toggleIsOpen() {
51 | setCmdLineModal(previousState => !previousState);
52 | };
53 |
54 | return (
55 | }
60 | noOptionsText = "No commands found. Try a different search term."
61 | />
62 | );
63 | };
64 |
65 | export default App;
66 | ```
67 |
68 | ## Props
69 | ### commands
70 | Object representing the different commands to list. The key is command's name and value is another object containing command details. Example:
71 | ```
72 | const commands = {
73 | SEARCH_CONTACT: {
74 | name: 'Search', logo: , shortcut: 'S', callback: () => console.log('search')
75 | },
76 | ADD_CONTACT: {
77 | name: 'Add', logo: , shortcut: '⌘ A', callback: () => console.log('add')
78 | },
79 | };
80 | ```
81 | Command details varibales
82 |
83 | | Parameter | Type | Description | Example |
84 | | :--------- | :-------- | :---------- | :----- |
85 | | name | `string` | The text to be displayed for this command| Search
86 | | logo | `component` | Component that will be next to command's name |` ` from Material UI|
87 | | shortcut | `string` | Shortcut text to display next to command name |⌘ S|
88 | | callback | `func` | A function callback text to be displayed for this command|function searchCallback() { console.log("search called") }|
89 |
90 | ### isOpen
91 | If true, command line modal will be visible.
92 |
93 | ### toggleIsModalOpen
94 | A function to be called to toggle modal state. Used to control isOpen state within the external component (e.g. `Super Command` in above example).
95 |
96 | ### title
97 | The title to be displayed for the command line modal (e.g. `` in above example)
98 |
99 | ### logo
100 | Optional
101 | A logo component to display as part of the title
102 |
103 | ### noOptionsTest
104 | Optional
105 | Text to show when no commands were found based on input search term
106 |
107 | ## Development
108 | Follow create-react-library's [development guide](https://www.npmjs.com/package/create-react-library#development)
109 |
110 | ## License
111 | `react-super-cmd` is released under MIT license © [saharmor](https://github.com/saharmor).
112 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # react-super-cmd example ⚡
2 |
3 | ## Getting started
4 |
5 | ### Clone
6 | If you want to run the example project locally, you first need to clone the repository:
7 |
8 | ```
9 | git clone git@github.com:saharmor/react-super-cmd.git
10 | ```
11 |
12 | ### Build
13 |
14 | ```
15 | yarn install && yarn run prepublish
16 | ```
17 |
18 | ### Start
19 | Start the example app:
20 |
21 | ```
22 | cd example && yarn start
23 | ```
24 |
25 | The example app will automatically open in your default browser.
26 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-super-cmd-example",
3 | "homepage": ".",
4 | "version": "0.0.0",
5 | "private": true,
6 | "scripts": {
7 | "start": "node ../node_modules/react-scripts/bin/react-scripts.js start",
8 | "build": "node ../node_modules/react-scripts/bin/react-scripts.js build",
9 | "test": "node ../node_modules/react-scripts/bin/react-scripts.js test",
10 | "eject": "node ../node_modules/react-scripts/bin/react-scripts.js eject"
11 | },
12 | "dependencies": {
13 | "@material-ui/icons": "^4.11.2",
14 | "react": "file:../node_modules/react",
15 | "react-dom": "file:../node_modules/react-dom",
16 | "react-hotkeys": "^2.0.0",
17 | "react-scripts": "file:../node_modules/react-scripts",
18 | "react-super-cmd": "file:.."
19 | },
20 | "devDependencies": {
21 | "@babel/plugin-syntax-object-rest-spread": "^7.8.3"
22 | },
23 | "eslintConfig": {
24 | "extends": "react-app"
25 | },
26 | "browserslist": [
27 | ">0.2%",
28 | "not dead",
29 | "not ie <= 11",
30 | "not op_mini all"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saharmor/react-super-cmd/b617375b28479f9d2516cb4899213b0f9edfe669/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
17 |
18 |
19 |
28 | react-super-cmd
29 |
30 |
31 |
32 |
33 | You need to enable JavaScript to run this app.
34 |
35 |
36 |
37 |
38 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "react-super-cmd",
3 | "name": "react-super-cmd",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/example/src/App.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from 'react';
2 | import withStyles from "@material-ui/core/styles/withStyles";
3 | import {configure, GlobalHotKeys} from "react-hotkeys";
4 | import AddCircleOutlineIcon from '@material-ui/icons/AddCircleOutline';
5 | import RemoveCircleOutlineOutlinedIcon from '@material-ui/icons/RemoveCircleOutlineOutlined';
6 | import SearchOutlinedIcon from '@material-ui/icons/SearchOutlined';
7 | import UpdateOutlinedIcon from '@material-ui/icons/UpdateOutlined';
8 | import OfflineBoltOutlined from "@material-ui/icons/OfflineBoltOutlined";
9 | import CommandLineModal from "react-super-cmd";
10 | import Grid from "@material-ui/core/Grid";
11 | import Typography from "@material-ui/core/Typography";
12 | import {Link, List, ListItem, ListItemText} from "@material-ui/core";
13 | import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord';
14 | import GitHubIcon from '@material-ui/icons/GitHub';
15 | import LinkedInIcon from "@material-ui/icons/LinkedIn";
16 |
17 | const useStyles = (theme) => ({
18 | root: {
19 | backgroundColor: '#fbfafa',
20 | height: '100vh',
21 | display: 'flex',
22 | flexDirection: 'column',
23 | flex: '1',
24 | padding: '30px 30px 10px 30px',
25 | },
26 | headline: {
27 | marginBottom: '10px',
28 | },
29 | ctaText: {
30 | fontWeight: 'bold',
31 | },
32 | cmd: {
33 | backgroundColor: '#dedede',
34 | },
35 | listBullet: {
36 | fontSize: '10px',
37 | marginRight: '3px',
38 | },
39 | listItem: {
40 | paddingBottom: '0px',
41 | paddingTop: '0px',
42 | },
43 | footer: {
44 | marginTop: '20px',
45 | }
46 | });
47 |
48 | const App = ({classes}) => {
49 | const [cmdLineModal, setCmdLineModal] = useState(false);
50 | const [lastRunCmd, setLastRunCmd] = useState(null);
51 |
52 | const commands = {
53 | SEARCH_CONTACT: {
54 | name: 'Search', logo: , shortcut: 'S', callback: () => setLastRunCmd('SEARCH_CONTACT')
55 | },
56 | ADD_CONTACT: {
57 | name: 'Add', logo: , shortcut: '⌘ A', callback: () => setLastRunCmd('ADD_CONTACT')
58 | },
59 | DELETE_CONTACT: {
60 | name: 'Delete', logo: , shortcut: '⌘ D', callback: () => setLastRunCmd('DELETE_CONTACT')
61 | },
62 | UPDATE_CONTACT: {
63 | name: 'Update', logo: , shortcut: null, callback: () => setLastRunCmd('UPDATE_CONTACT')
64 | },
65 | REACH_OUT_CONTACT: {
66 | name: 'Reach out', logo: , shortcut: 'ctrl R', callback: () => setLastRunCmd('REACH_OUT_CONTACT')
67 | }
68 | }
69 |
70 | function toggleIsOpen() {
71 | setCmdLineModal(previousState => !previousState);
72 | }
73 |
74 | const keyMap = {TOGGLE_MODAL: "cmd+k"};
75 | const handlers = {
76 | TOGGLE_MODAL: () => {
77 | toggleIsOpen();
78 | },
79 | };
80 |
81 | configure({
82 | ignoreTags: ['input', 'select', 'textarea'],
83 | ignoreEventsCondition: function () {
84 | }
85 | });
86 |
87 | return (
88 |
89 |
90 |
91 |
92 | react-super-cmd ⚡
93 |
94 | The command line experience for the no-mouse generation.
95 | A blazing fast command line for your users to seamlessly interact with your React app.
96 |
97 | {!lastRunCmd &&
98 |
99 | Give it a spin 👉 Press cmd+k
100 |
101 | }
102 | {lastRunCmd &&
103 | Command selected {lastRunCmd}
104 | }
105 |
106 |
107 |
108 |
109 | Possible applications
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 | By Sahar Mor
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 | }/>
148 |
149 |
150 | )
151 | }
152 |
153 | export default withStyles(useStyles)(App);
--------------------------------------------------------------------------------
/example/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div')
7 | ReactDOM.render( , div)
8 | ReactDOM.unmountComponentAtNode(div)
9 | })
10 |
--------------------------------------------------------------------------------
/example/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
5 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
6 | sans-serif;
7 | -webkit-font-smoothing: antialiased;
8 | -moz-osx-font-smoothing: grayscale;
9 | }
10 |
11 | code {
12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
13 | monospace;
14 | }
15 |
16 | #root {
17 | height: 100vh;
18 | }
19 |
20 | html {
21 | height: 100%;
22 | }
23 |
24 | body {
25 | min-height: 100%;
26 | }
27 |
28 | /* MaterialUI overrides */
29 | a:-webkit-any-link {
30 | text-decoration: none !important;
31 | }
32 |
33 | a, a:visited, a:hover, a:active {
34 | color: inherit;
35 | }
36 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | import './index.css'
2 |
3 | import React from 'react'
4 | import ReactDOM from 'react-dom'
5 | import App from './App'
6 |
7 | ReactDOM.render( , document.getElementById('root'))
8 |
--------------------------------------------------------------------------------
/example/src/static/demo_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/saharmor/react-super-cmd/b617375b28479f9d2516cb4899213b0f9edfe669/example/src/static/demo_image.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-super-cmd",
3 | "version": "1.0.6",
4 | "description": "A sleek command line modal for React apps",
5 | "author": "saharmor",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/saharmor/react-super-cmd.git"
10 | },
11 | "main": "dist/index.js",
12 | "module": "dist/index.modern.js",
13 | "source": "src/index.js",
14 | "engines": {
15 | "node": ">=10"
16 | },
17 | "keywords": [
18 | "react-component",
19 | "command-line",
20 | "command",
21 | "superhuman",
22 | "shortcuts",
23 | "react"
24 | ],
25 | "bugs": {
26 | "url": "https://github.com/saharmor/react-super-cmd/issues"
27 | },
28 | "scripts": {
29 | "build": "microbundle-crl --no-compress --format modern,cjs",
30 | "start": "microbundle-crl watch --no-compress --format modern,cjs",
31 | "prepare": "run-s build",
32 | "test": "run-s test:unit test:lint test:build",
33 | "test:build": "run-s build",
34 | "test:lint": "eslint react-super-cmd",
35 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
36 | "test:watch": "react-scripts test --env=jsdom",
37 | "predeploy": "cd example && npm install && npm run build",
38 | "deploy": "gh-pages -d example/build"
39 | },
40 | "peerDependencies": {
41 | "react": "^16.13.0",
42 | "react-dom": "^16.13.0",
43 | "@material-ui/core": "^4.11.2",
44 | "@material-ui/icons": "^4.11.2"
45 | },
46 | "devDependencies": {
47 | "microbundle-crl": "^0.13.10",
48 | "babel-eslint": "^10.0.3",
49 | "cross-env": "^7.0.2",
50 | "eslint": "^6.8.0",
51 | "eslint-config-prettier": "^6.7.0",
52 | "eslint-config-standard": "^14.1.0",
53 | "eslint-config-standard-react": "^9.2.0",
54 | "eslint-plugin-import": "^2.18.2",
55 | "eslint-plugin-node": "^11.0.0",
56 | "eslint-plugin-prettier": "^3.1.1",
57 | "eslint-plugin-promise": "^4.2.1",
58 | "eslint-plugin-react": "^7.17.0",
59 | "eslint-plugin-standard": "^4.0.1",
60 | "gh-pages": "^2.2.0",
61 | "npm-run-all": "^4.1.5",
62 | "prettier": "^2.0.4",
63 | "react": "^16.13.0",
64 | "react-dom": "^16.13.0",
65 | "react-scripts": "^3.4.1",
66 | "@material-ui/core": "^4.11.2",
67 | "@material-ui/icons": "^4.11.2"
68 | },
69 | "files": [
70 | "dist"
71 | ],
72 | "homepage": "https://github.com/saharmor/react-super-cmd#readme",
73 | "directories": {
74 | "example": "example"
75 | },
76 | "dependencies": {}
77 | }
78 |
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/AutocompleteCommandField.js:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import withStyles from "@material-ui/core/styles/withStyles";
3 | import TextField from '@material-ui/core/TextField';
4 |
5 |
6 | const useStyles = (theme) => ({
7 | root: {
8 | backgroundColor: '#212121',
9 | display: 'flex',
10 | width: '100%',
11 | margin: '24px 0',
12 | },
13 | inputField: {
14 | flex: '1',
15 | border: 'none',
16 | boxShadow: 'inset 3px 0 0 #dcb865',
17 | backgroundColor: 'inherit',
18 | color: '#b7babe',
19 | },
20 | input: {
21 | color: '#f1f2f2',
22 | paddingLeft: '30px',
23 | fontSize: '21px',
24 | lineHeight: '28px',
25 | }
26 | });
27 |
28 | const AutocompleteCommandField = ({classes, fieldValue, onChange, onKeyPress}) => {
29 | return (
30 |
31 |
33 |
34 | )
35 | }
36 |
37 | export default withStyles(useStyles)(AutocompleteCommandField);
38 |
--------------------------------------------------------------------------------
/src/CommandRow.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import withStyles from "@material-ui/core/styles/withStyles";
3 | import Grid from "@material-ui/core/Grid";
4 | import ButtonBase from '@material-ui/core/ButtonBase';
5 | import Typography from "@material-ui/core/Typography";
6 |
7 | const useStyles = (theme) => ({
8 | root: {
9 | color: '#b7babe',
10 | minHeight: '60px',
11 | padding: '20px 36px',
12 | width: '100%',
13 | textAlign: 'left',
14 | },
15 | rootHighlighted: {
16 | minHeight: '60px',
17 | padding: '20px 36px',
18 | width: '100%',
19 | textAlign: 'left',
20 | backgroundColor: 'rgba(255, 255, 255, 0.1)',
21 | color: '#ffffffe6',
22 | },
23 | button: {
24 | flexGrow: '1',
25 | },
26 | cmdIcon: {
27 | width: '20px',
28 | height: '20px',
29 | },
30 | shortcut: {
31 | background: 'rgba(255, 255, 255, 0.15)',
32 | borderRadius: '3px',
33 | maxHeight: '18px',
34 | minWidth: '19px',
35 | marginLeft: '4px',
36 | padding: "1px 5px 5px 5px",
37 | marginTop: "3px",
38 | }
39 | });
40 |
41 | const CommandRow = ({classes, command, commandName, isHighlighted, onHover, onKeyPress, onClick, ref}) => {
42 | const rootStyle = isHighlighted ? classes.rootHighlighted : classes.root;
43 |
44 | function shortcutToKeys(shortcut) {
45 | return (
46 |
47 | {
48 | shortcut.split(" ").map((keyboardKey) => {
49 | return {keyboardKey}
50 | })
51 | }
52 |
53 | )
54 | }
55 |
56 | return (
57 | onHover(commandName)} onKeyPress={onKeyPress}>
58 |
59 |
60 |
61 |
62 | {command['logo']}
63 |
64 |
65 | {command['name']}
66 |
67 |
68 |
69 |
70 | {command['shortcut'] && shortcutToKeys(command['shortcut'])}
71 |
72 |
73 |
74 | )
75 | }
76 |
77 | export default withStyles(useStyles)(CommandRow);
78 |
--------------------------------------------------------------------------------
/src/CommandsList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CommandRow from "./CommandRow";
3 | import withStyles from "@material-ui/core/styles/withStyles";
4 |
5 | const useStyles = (theme) => ({
6 | root: {
7 | maxHeight: '220px',
8 | overflow: 'auto',
9 |
10 | "&::-webkit-scrollbar": {
11 | width: '8px',
12 | borderRadius: '5px',
13 | paddingRight: '3px',
14 | },
15 | "&::-webkit-scrollbar-track": {
16 | borderRadius: '5px',
17 | },
18 | "&::-webkit-scrollbar-thumb": {
19 | borderRadius: '5px',
20 | backgroundColor: "#757474",
21 | },
22 | },
23 | child: {
24 | height: '100%',
25 | margin: '0 auto',
26 | }
27 | })
28 |
29 | const CommandsList = ({classes, commands, highlightedCmdName, setHighlightedCallback, handleEnter, ignoreHover, toggleIgnoreHover}) => {
30 | function onHoverCallback(hoveredCmdName) {
31 | if (!ignoreHover) {
32 | setHighlightedCallback(hoveredCmdName);
33 | } else {
34 | toggleIgnoreHover();
35 | }
36 | }
37 |
38 | return (
39 |
40 |
41 | {Object.keys(commands).map((command, _) => {
42 | return
45 | }
46 | )}
47 |
48 |
49 | )
50 | };
51 |
52 | export default withStyles(useStyles)(CommandsList);
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useRef} from "react";
2 | import withStyles from "@material-ui/core/styles/withStyles";
3 | import Dialog from "@material-ui/core/Dialog";
4 | import DialogTitle from "@material-ui/core/DialogTitle";
5 | import DialogContent from "@material-ui/core/DialogContent";
6 | import Grid from "@material-ui/core/Grid";
7 | import Divider from "@material-ui/core/Divider";
8 | import Typography from "@material-ui/core/Typography";
9 | import AutocompleteCommandField from "./AutocompleteCommandField";
10 | import CommandsList from "./CommandsList";
11 | import OfflineBoltOutlined from "@material-ui/icons/OfflineBoltOutlined";
12 |
13 | const useStyles = (theme) => ({
14 | paperRoot: {
15 | padding: '10px 0 10px 0',
16 | minWidth: '760px',
17 | maxHeight: '380px',
18 | backgroundColor: '#212121',
19 | borderRadius: '4px',
20 | boxShadow: '0 5px 10px 0 rgba(0, 0, 0, 0.7), 0 0px 10px 0 #000, 0.15px 0.5px 0 0 rgba(255, 255, 255, 0.1) inset'
21 | },
22 | backdropRoot: {
23 | backgroundColor: 'transparent',
24 | },
25 | dialogTitle: {
26 | margin: '12px 36px',
27 | padding: '0px',
28 | },
29 | titleSection: {
30 | color: '#d4d6d8',
31 | display: 'flex',
32 | flexDirection: 'row',
33 | marginBottom: '20px',
34 | },
35 | title: {
36 | marginLeft: '8px',
37 | },
38 | dialogBody: {
39 | padding: '0px',
40 | overflow: 'hidden',
41 | },
42 | divider: {
43 | backgroundColor: 'rgba(255, 255, 255, 0.1)',
44 | },
45 | noOptionsText: {
46 | color: '#d4d6d8',
47 | padding: '12px 36px',
48 | },
49 | commandsList:{
50 | paddingRight: '3px',
51 | }
52 | });
53 |
54 |
55 | const CommandLineModal = ({
56 | classes, commands, isOpen, toggleIsModalOpen, title,
57 | noOptionsText = "No commands found. Try a different search term.",
58 | logo =
59 | }) => {
60 | let commandsInternal = {};
61 | Object.entries(commands).map(([commandName, properties]) => {
62 | commandsInternal[commandName] = {...properties, ref: useRef(null)}
63 | });
64 |
65 | const [inputValue, setInputValue] = useState('');
66 | const [possibleCommands, setPossibleCommands] = useState(commandsInternal);
67 | const [highlightedCmdName, setHighlightedCmdName] = useState(Object.keys(possibleCommands)[0]);
68 | const [ignoreHover, setIgnoreHover] = useState(false);
69 |
70 | function changeHighlightedCmd(commandName) {
71 | setHighlightedCmdName(commandName);
72 | }
73 |
74 | function toggleIgnoreHover() {
75 | setIgnoreHover(previousState => !previousState);
76 | }
77 |
78 | function setPossibleCommandsWithSearchTerm(searchTerm) {
79 | const inputToSearch = searchTerm.toLowerCase();
80 | let tempPossibleCommands = {};
81 | Object.entries(commandsInternal).map(([commandName, properties]) => {
82 | if (properties.name.toLowerCase().includes(inputToSearch)) {
83 | tempPossibleCommands[commandName] = properties
84 | }
85 | });
86 | setPossibleCommands(tempPossibleCommands);
87 | changeHighlightedCmd(Object.keys(tempPossibleCommands)[0]);
88 | }
89 |
90 | function handleInputChange(event) {
91 | const newSearchTerm = event.target.value;
92 | setPossibleCommandsWithSearchTerm(newSearchTerm);
93 | setInputValue(newSearchTerm);
94 | }
95 |
96 | function reset() {
97 | setInputValue('');
98 | setPossibleCommands(commandsInternal);
99 | setHighlightedCmdName(Object.keys(commandsInternal)[0]);
100 | }
101 |
102 | function handleCommandSelected() {
103 | if (highlightedCmdName) {
104 | possibleCommands[highlightedCmdName]['callback']();
105 | toggleIsModalOpen();
106 | reset();
107 | }
108 | }
109 |
110 | function onArrowsPress(direction) {
111 | setIgnoreHover(true);
112 | const keysArray = Object.keys(possibleCommands);
113 | const currSelectedIndex = keysArray.indexOf(highlightedCmdName);
114 |
115 | let selectedCommandName = null;
116 | if (direction === "down") {
117 | if (currSelectedIndex + 1 === keysArray.length) {
118 | selectedCommandName = keysArray[0];
119 | } else {
120 | selectedCommandName = keysArray[currSelectedIndex + 1];
121 | }
122 | } else if (direction === "up") {
123 | if (currSelectedIndex - 1 < 0) {
124 | selectedCommandName = keysArray[keysArray.length - 1];
125 | } else {
126 | selectedCommandName = keysArray[currSelectedIndex - 1];
127 | }
128 | }
129 |
130 | changeHighlightedCmd(selectedCommandName);
131 | commandsInternal[selectedCommandName].ref.current.scrollIntoView();
132 | }
133 |
134 | function onKeyPress(event) {
135 | if (event.which === 13 /* Enter */) {
136 | handleCommandSelected();
137 | } else if (event.which === 38 /* Arrow Up */) {
138 | onArrowsPress('up');
139 | } else if (event.which === 40 /* Arrow Down*/) {
140 | onArrowsPress('down');
141 | }
142 | }
143 |
144 | return (
145 |
147 |
148 |
149 | {logo}
150 | {title}
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | {Object.keys(possibleCommands).length > 0 &&
162 |
165 | }
166 | {Object.keys(possibleCommands).length === 0 &&
167 | {noOptionsText}
168 | }
169 |
170 |
171 |
172 |
173 | )
174 | }
175 |
176 | export default withStyles(useStyles)(CommandLineModal);
177 |
--------------------------------------------------------------------------------