Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fixed hotkeys are triggered within the ninja-keys search input for issue #35 #39

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 100 additions & 58 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Keyboard shortcut interface for your website that works with Vanilla JS, Vue, an
![Demo](./docs/demo-min.gif)

## Motivation

A lot of applications support a common pattern where the user hits <kbd>⌘</kbd>+<kbd>k</kbd> (or <kbd>ctrl</kbd>+<kbd>k</kbd>) and a search UI dialog appears.
I've recently seen this in Notion, Slack, Linear, Vercel and Algolia, but I'm sure there are plenty more.
Apple Spotlight, Alfred and the Raycast app also have a similar pattern, but with different shortcuts.
Expand All @@ -24,6 +25,7 @@ so I decided to give it a try for [Web Components](https://open-wc.org/) and [Li


## Integrations

- [Vue 3 Example - Codesandbox](https://codesandbox.io/s/ninja-keys-demo-vue3-bps5w?file=/public/index.html)
- [Vue 2 Example - Codesandbox](https://codesandbox.io/s/ninja-keys-demo-vue2-b129z)
- [React Example - Codesandbox](https://codesandbox.io/s/ninja-keys-demo-reactjs-rlv4b?file=/src/App.js)
Expand All @@ -33,6 +35,7 @@ so I decided to give it a try for [Web Components](https://open-wc.org/) and [Li
- [SolidJS wrapper](https://github.com/wobsoriano/solid-ninja-keys)

## Features

- Keyboard navigation
- Light and dark theme built in
- Built-in icon support from Material font and custom svg icons
Expand All @@ -44,24 +47,32 @@ so I decided to give it a try for [Web Components](https://open-wc.org/) and [Li


## Why the "Ninja" name?

Because it appears from nowhere and executes any actions quickly...
Or because it allows your users to become keyboard ninjas 🙃

## Install from NPM

```bash
npm i ninja-keys
```

Import if you are using webpack, rollup, vite or other build system.

```js
import 'ninja-keys';
```

## Install from CDN

Mostly for usage in HTML/JS without a build system.

```html
<script type="module" src="https://unpkg.com/ninja-keys?module"></script>
```

or inside your module scripts

```html
<script type="module">
import {NinjaKeys} from 'https://unpkg.com/ninja-keys?module';
Expand All @@ -74,9 +85,6 @@ Add the tag to your HTML.

```html
<ninja-keys> </ninja-keys>
```

```html
<script>
const ninja = document.querySelector('ninja-keys');
ninja.data = [
Expand Down Expand Up @@ -124,10 +132,22 @@ Add the tag to your HTML.
document.documentElement.classList.add('dark');
},
},
{
id: 'MoveRight',
title: 'Right',
hotkey: 'right',
mdIcon: 'keyboard_double_arrow_right',
keep: false, // in this way, the hotkeys will not be monitored when the search box is opened.
handler: () => {
console.log('Moving to the right!');
},
},
];
</script>
```

Library using flat data structure inside, as in the example above. But you can also use a tree structure as below:

```js
{
id: 'Theme',
Expand All @@ -142,47 +162,57 @@ Library using flat data structure inside, as in the example above. But you can a
]
}
```

## Attributes

| Field | Default | Description |
|----------------------|-----------------------------|-------------------------------------------------------------|
| -------------------- | --------------------------- | ----------------------------------------------------------- |
| placeholder | Type a command or search... | Placeholder for search |
| disableHotkeys | false | If attribute exist will register all hotkey for all actions |
| hideBreadcrumbs | false | Hide breadcrumbs on header if true |
| openHotkey | cmd+k,ctrl+k | Open or close shortcut |
| navigationUpHotkey | up,shift+tab | Navigation up shortcuts |
| navigationDownHotkey | down,tab | Navigation down shortcuts |
| closeHotkey | esc | Close shortcut |
| closeHotkey | esc | Close shortcut |
| goBackHotkey | backspace | Go back on one level if has parent menu |
| selectHotkey | enter | Select action and execute handler or open submenu |
| hotKeysJoinedView | false | If exist/true will display hotkeys inside one element |
| noAutoLoadMdIcons | false | If exist it disable load material icons font on connect |

#### Example

```html
<ninja-keys placeholder="Must app is awesome" openHotkey="cmd+l" hideBreadcrumbs></ninja-keys>
```

## Data

Array of `INinjaAction` - interface properties below
| Name | Type | Description |
|----------|-------------------------|----------------------------------------------------------------------------------------|
| id | string | Unique id/text. Will be displayed as breadcrumb in multimenu |
| title | string | Title of action |
| hotkey | string(optional) | Shortcut to display and register |
| handler | Function(optional) | Function to execute on select |
| mdIcon | string(optional) | Material Design font icon name |
| icon | string(optional) | Html to render as custom icon |
| parent | string(optional) | If using flat structure use id of actions to make a multilevel menu |
| keywords | string(optional) | Keywords to use for search |

| Name | Type | Description |
| -------- | ----------------------- | ------------------------------------------------------------ |
| id | string | Unique id/text. Will be displayed as breadcrumb in multimenu |
| title | string | Title of action |
| hotkey | string(optional) | Shortcut to display and register |
| handler | Function(optional) | Function to execute on select |
| mdIcon | string(optional) | Material Design font icon name |
| icon | string(optional) | Html to render as custom icon |
| parent | string(optional) | If using flat structure use id of actions to make a multilevel menu |
| keywords | string(optional) | Keywords to use for search |
| children | Array<string>(optional) | If using flat structure then ids of child menu actions. Not required on tree structure |
| section | string(optional) | Section text. Like a header will be group with other same sections |
| section | string(optional) | Section text. Like a header will be group with other same sections |
| keep | boolean(optional) | Whether to keep hotkey monitoring when opening the ninja-keys search input.The default is false |

## Methods
| Name | Arg | Description |
|-----------|---------------------|-----------------------------------------------------|
| `open` | { parent?: string } | Open menu with parent, if null then open root menu |
| `close` | | Close menu |
| `setParent` | parent?: string | Navigate to parent menu |

| Name | Arg | Description |
| ----------- | ------------------- | -------------------------------------------------- |
| `open` | { parent?: string } | Open menu with parent, if null then open root menu |
| `close` | | Close menu |
| `setParent` | parent?: string | Navigate to parent menu |

#### Example

```js
const ninja = document.querySelector('ninja-keys');
ninja.open()
Expand All @@ -191,12 +221,13 @@ ninja.open({ parent: 'Theme' })
```

### Events

Component wide events

| Name | Description | Payload |
|------------------------------------|-------------------------------------| ------- |
| `change` | Emitted when on each change of search input | `{ detail: { search: string, actions: Array<NinjaAction> } }` |
| `selected` | Emitted when on user selected action or on submit of input | `{ detail: { search: string, action: NinjaAction or undefined }}` |
| Name | Description | Payload |
| ---------- | ---------------------------------------------------------- | ------------------------------------------------------------ |
| `change` | Emitted when on each change of search input | `{ detail: { search: string, actions: Array<NinjaAction> } }` |
| `selected` | Emitted when on user selected action or on submit of input | `{ detail: { search: string, action: NinjaAction or undefined }}` |

Both `handler` of action and component event `selected` emitted when user submit form or select item.

Expand All @@ -220,59 +251,66 @@ ninja.addEventListener('selected', (event) => {
```

## Themes

Component supports a dark theme out-of-box. You just need to add a class.

```html
<ninja-keys class="dark"></ninja-keys>
```

If you need more style control, use any of the CSS variables below.

### CSS variables
| Name | Default |
|------------------------------------|------------------------------------|
| --ninja-width | 640px; |
| --ninja-backdrop-filter | none; |
| --ninja-overflow-background | rgba(255, 255, 255, 0.5); |
| --ninja-text-color | rgb(60, 65, 73); |
| --ninja-font-size | 16px; |
| --ninja-top | 20%; |
| --ninja-key-border-radius | 0.25em |
| --ninja-accent-color | rgb(110, 94, 210); |
| --ninja-secondary-background-color | rgb(239, 241, 244); |
| --ninja-secondary-text-color | rgb(107, 111, 118); |
| --ninja-selected-background | rgb(248, 249, 251); |
| --ninja-icon-color | var(--ninja-secondary-text-color); |
| --ninja-icon-size | 1.2em; |
| --ninja-separate-border | 1px solid var(--ninja-secondary-background-color); |
| --ninja-modal-background | #fff; |
| --ninja-modal-shadow | rgb(0 0 0 / 50%) 0px 16px 70px; |
| --ninja-actions-height | 300px; |
| --ninja-group-text-color | rgb(144, 149, 157); |
| --ninja-footer-background | rgba(242, 242, 242, 0.4); |
| --ninja-placeholder-color | #8e8e8e |
| --ninja-z-index | 1 |

| Name | Default |
| ---------------------------------- | -------------------------------------------------- |
| --ninja-width | 640px; |
| --ninja-backdrop-filter | none; |
| --ninja-overflow-background | rgba(255, 255, 255, 0.5); |
| --ninja-text-color | rgb(60, 65, 73); |
| --ninja-font-size | 16px; |
| --ninja-top | 20%; |
| --ninja-key-border-radius | 0.25em |
| --ninja-accent-color | rgb(110, 94, 210); |
| --ninja-secondary-background-color | rgb(239, 241, 244); |
| --ninja-secondary-text-color | rgb(107, 111, 118); |
| --ninja-selected-background | rgb(248, 249, 251); |
| --ninja-icon-color | var(--ninja-secondary-text-color); |
| --ninja-icon-size | 1.2em; |
| --ninja-separate-border | 1px solid var(--ninja-secondary-background-color); |
| --ninja-modal-background | #fff; |
| --ninja-modal-shadow | rgb(0 0 0 / 50%) 0px 16px 70px; |
| --ninja-actions-height | 300px; |
| --ninja-group-text-color | rgb(144, 149, 157); |
| --ninja-footer-background | rgba(242, 242, 242, 0.4); |
| --ninja-placeholder-color | #8e8e8e |
| --ninja-z-index | 1 |

#### Example

```css
ninja-keys {
--ninja-width: 400px;
}
```

### CSS Shadow Parts

Allowing you to style specific elements from your style.
Because styles are encapsulated by Shadow DOM, it will be annoying to create css variables for all properties.
That's why you can use `::part` to make a custom look for the component.
It's supported by [all modern browsers](https://caniuse.com/mdn-css_selectors_part)

| Name | Description |
|------------------------------------|-------------------------------------|
| actions-list | Element that wraps all child elements. |
| ninja-action | Single action |
| ninja-selected | Selected action |
| ninja-input | Input element |
| ninja-input-wrapper | Wrapper element around div, useful for advanced styles|
| Name | Description |
| ------------------- | ------------------------------------------------------ |
| actions-list | Element that wraps all child elements. |
| ninja-action | Single action |
| ninja-selected | Selected action |
| ninja-input | Input element |
| ninja-input-wrapper | Wrapper element around div, useful for advanced styles |

#### Example style using parts

```css
ninja-keys::part(actions-list) {
padding: 8px;
Expand Down Expand Up @@ -301,17 +339,20 @@ ninja-keys::part(ninja-input-wrapper) {
```

### Icons

By default, components use icons from [https://fonts.google.com/icons](https://fonts.google.com/icons)

For example, you can just set `mdIcon` to `light_mode` to render a sun icon.

To add Material icons for your website, you need to add them to your HTML, for example

```html
<link href="https://fonts.googleapis.com/css?family=Material+Icons&display=block" rel="stylesheet">
```

If you want custom icons, you can use `svg` or `img` to insert it with an `icon` property for action with `ninja-icon` class.
Example:

```js
{
title: 'Search projects...',
Expand All @@ -321,7 +362,9 @@ Example:
section: 'Projects',
},
```

You can also change the width and font using CSS variables

```css
ninja-keys {
--ninja-icon-size: 1em;
Expand All @@ -330,6 +373,7 @@ ninja-keys {


### Change or hide footer

```html
<ninja-keys>
<div slot="footer">You can use a custom footer or empty div to hide it</div>
Expand Down Expand Up @@ -361,6 +405,4 @@ Copyright (c) [Sergei Sleptsov](https://sergei.ws) <hey@sergei.ws>

Licensed under [the MIT license](./LICENSE).

[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fssleptsov%2Fninja-keys.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fssleptsov%2Fninja-keys?ref=badge_large)


[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fssleptsov%2Fninja-keys.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fssleptsov%2Fninja-keys?ref=badge_large)
Loading