Skip to content

Commit

Permalink
feat: rework styles (#35)
Browse files Browse the repository at this point in the history
* feat: rework components to better support styling

* fix: cleanup buffer

* feat: add bump animation on action transition

* chore: cleanup

* chore: rename content -> portal

* fix: import

* chore: update readme

* chore: add gh link to demo

* chore: lowercase

* chore: cleanup

* chore: add initial docs

* chore: add docs to search

* chore: keep default styles
  • Loading branch information
timc1 authored Sep 22, 2021
1 parent 0db3c0b commit c1de58a
Show file tree
Hide file tree
Showing 25 changed files with 802 additions and 265 deletions.
63 changes: 45 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
## kbar

kbar is a simple plug-n-play React component to add a fast,
portable, and extensible command+k interface to your site.
kbar is a simple plug-n-play React component to add a fast, portable, and extensible command+k interface to your site.

![demo](https://user-images.githubusercontent.com/12195101/132958919-7a525cab-e191-4642-ae9a-5f22a3ba7845.gif)
![demo](https://user-images.githubusercontent.com/12195101/134022553-af4a29e9-0a3d-40f1-9254-3bd9673f3401.gif)

## Background
### Background

Command+k interfaces are used to create a web experience where any type of action users would be able to do via clicking can be done through a command menu.

With macOS's Spotlight and Linear's command+k experience in mind, kbar aims to be a simple abstraction to add a fast and extensible command+k menu to your site.
With macOS's Spotlight and Linear's command+k experience in mind, kbar aims to be a simple
abstraction to add a fast and extensible command+k menu to your site.

### Features

- Built in animations, fully customizable
- Built in animations and fully customizable components
- Keyboard navigation support; e.g. ctrl n / ctrl p for the navigation wizards
- Keyboard shortcuts support for registering keystrokes to specific actions; e.g. hit t for Twitter
- Navigate between nested actions with backspace
- A simple data structure which enables anyone to easily build their custom components
- Nested actions enable creation of rich navigation experiences; e.g. hit backspace to navigate to
the previous action
- A simple data structure which enables anyone to easily build their own custom components

Usage
Have a fully functioning command menu for your site in minutes. Let's start with a basic example. First, install kbar.
### Usage

Have a fully functioning command menu for your site in minutes. First, install kbar.

```
npm install kbar
Expand All @@ -41,7 +43,8 @@ return (

kbar is built on top of `actions`. Actions define what to execute when a user selects it. Actions can have children which are just other actions.

Let's create a few static actions. Static actions are actions with no external dependencies; they don't rely on a method from some other hook, for instance. We'll talk about dynamic actions later.
We'll create a few static actions first. Static actions are actions with no external dependencies. Our example below sets the `window.location.pathname`, which does not rely on any
external hook, for instance.

```tsx
const actions = [
Expand All @@ -68,19 +71,43 @@ return (
);
```

kbar exposes a few components which handle animations, keyboard events, etc. You can compose them together like so:
kbar exposes a few components which handle animations, keyboard events, default styles, etc. You can use them together like so:

```tsx
import { KBarProvider, KBarContent, KBarSearch } from "kbar";

<KBarProvider actions={actions}>
<KBarContent>
<KBarSearch />
<KBarResults />
</KBarContent>
<KBarPortal> // Renders the content outside the root node
<KBarPositioner> // Centers the content
<KBarAnimator> // Handles the show/hide and height animations
<KBarSearch /> // Search input
<KBarResults /> // Results renderer
</KBarAnimator>
</KBarPositioner>
</KBarPortal>
<MyApp />
</KBarProvider>;
```

Hit cmd+k and you should see a primitive command menu. kbar allows you to have full control over all
aspects of your command menu – refer to the <a href="https://kbar.vercel.app/docs">docs</a> to get an understanding of further capabilities.
Hit cmd+k (or ctrl+k) and you should see a primitive command menu. kbar allows you to have full control over all
aspects of your command menu – refer to the <a href="https://kbar.vercel.app/docs">docs</a> to get
an understanding of further capabilities. Excited to see what you build.

### Contributing to kbar

Contributions are welcome!

#### New features

Please [open a new issue](https://github.com/timc1/kbar/issues) so we can discuss prior to moving
forward.

#### Bug fixes

Please [open a new Pull Request](https://github.com/timc1/kbar/pulls) for the given bug fix.

#### Nits and spelling mistakes

Please [open a new issue](https://github.com/timc1/kbar/issues) for things like spelling mistakes
and README tweaks – we will group the issues together and tackle them as a group. Please do not
create a PR for it!
10 changes: 5 additions & 5 deletions example/dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,28 @@
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>⌘</text></svg>">

<!-- Primary Meta Tags -->
<title>KBar – command+k interface for your site</title>
<meta name="title" content="KBar – command+k interface for your site">
<title>kbar – command+k interface for your site</title>
<meta name="title" content="kbar – command+k interface for your site">
<meta name="description" content="Add a fast, portable, and extensible command+k interface to your site">

<!-- Open Graph / Facebook -->
<meta property="og:type" content="website">
<meta property="og:url" content="https://kbar.vercel.app/">
<meta property="og:title" content="KBar – command+k interface for your site">
<meta property="og:title" content="kbar – command+k interface for your site">
<meta property="og:description" content="Add a fast, portable, and extensible command+k interface to your site">
<meta property="og:image" content="">

<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:url" content="https://kbar.vercel.app/">
<meta property="twitter:title" content="KBar – command+k interface for your site">
<meta property="twitter:title" content="kbar – command+k interface for your site">
<meta property="twitter:description" content="Add a fast, portable, and extensible command+k interface to your site">
<meta property="twitter:image" content="">
</head>

<body>
<div id="root"></div>
<script src="index.js"></script>
<script src="/index.js"></script>
</body>

</html>
123 changes: 59 additions & 64 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import "./index.scss";
import * as React from "react";
import { KBarContent } from "../../src/KBarContent";
import { KBarAnimator } from "../../src/KBarAnimator";
import { KBarProvider } from "../../src/KBarContextProvider";
import KBarResults from "../../src/KBarResults";
import KBarPortal from "../../src/KBarPortal";
import KBarPositioner from "../../src/KBarPositioner";
import KBarSearch from "../../src/KBarSearch";
import { Switch, Route, useHistory } from "react-router-dom";
import { Switch, Route, useHistory, Redirect } from "react-router-dom";
import Layout from "./Layout";
import Home from "./Home";
import Docs from "./Docs";
import SearchDocsActions from "./SearchDocsActions";

const searchStyles = {
const searchStyle = {
padding: "12px 16px",
fontSize: "16px",
width: "100%",
Expand All @@ -20,19 +23,26 @@ const searchStyles = {
color: "var(--foreground)",
};

const resultsStyle = {
maxHeight: 400,
overflow: "auto",
};

const animatorStyle = {
maxWidth: "500px",
width: "100%",
background: "var(--background)",
color: "var(--foreground)",
borderRadius: "8px",
overflow: "hidden",
boxShadow: "var(--shadow)",
};

const App = () => {
const history = useHistory();
return (
<KBarProvider
actions={[
{
id: "searchDocsAction",
name: "Search docs…",
shortcut: [],
keywords: "find",
section: "",
children: ["docs1", "docs2"],
},
{
id: "homeAction",
name: "Home",
Expand All @@ -44,7 +54,7 @@ const App = () => {
{
id: "docsAction",
name: "Docs",
shortcut: ["d"],
shortcut: ["g", "d"],
keywords: "help",
section: "Navigation",
perform: () => history.push("/docs"),
Expand All @@ -65,24 +75,6 @@ const App = () => {
section: "Navigation",
perform: () => window.open("https://twitter.com/timcchang", "_blank"),
},
{
id: "docs1",
name: "Docs 1 (Coming soon)",
shortcut: [],
keywords: "Docs 1",
section: "",
perform: () => window.alert("nav -> Docs 1"),
parent: "searchBlogAction",
},
{
id: "docs2",
name: "Docs 2 (Coming soon)",
shortcut: [],
keywords: "Docs 2",
section: "",
perform: () => window.alert("nav -> Docs 2"),
parent: "searchBlogAction",
},
{
id: "theme",
name: "Change theme…",
Expand Down Expand Up @@ -116,37 +108,35 @@ const App = () => {
animations: {
enterMs: 200,
exitMs: 100,
maxContentHeight: 400,
},
}}
>
<KBarContent
contentStyle={{
maxWidth: "400px",
width: "100%",
background: "var(--background)",
color: "var(--foreground)",
borderRadius: "8px",
overflow: "hidden",
boxShadow: "var(--shadow)",
}}
>
<KBarSearch
style={searchStyles}
placeholder="Type a command or search…"
/>
<KBarResults
onRender={(action, handlers, state) => (
<Render action={action} handlers={handlers} state={state} />
)}
/>
</KBarContent>
<SearchDocsActions />
<KBarPortal>
<KBarPositioner>
<KBarAnimator style={animatorStyle}>
<KBarSearch
style={searchStyle}
placeholder="Type a command or search…"
/>
<KBarResults
style={resultsStyle}
onRender={(action, handlers, state) => (
<Render action={action} handlers={handlers} state={state} />
)}
/>
</KBarAnimator>
</KBarPositioner>
</KBarPortal>
<Layout>
<Switch>
<Route path="/docs">
<Route path="/docs" exact>
<Redirect to="/docs/overview" />
</Route>
<Route path="/docs/:slug">
<Docs />
</Route>
<Route path="/">
<Route path="*">
<Home />
</Route>
</Switch>
Expand All @@ -162,7 +152,7 @@ function Render({ action, handlers, state }) {

React.useEffect(() => {
if (active) {
// wait for the KBarContent to resize, _then_ scrollIntoView.
// wait for the KBarAnimator to resize, _then_ scrollIntoView.
// https://medium.com/@owencm/one-weird-trick-to-performant-touch-response-animations-with-react-9fe4a0838116
window.requestAnimationFrame(() =>
window.requestAnimationFrame(() => {
Expand Down Expand Up @@ -197,15 +187,20 @@ function Render({ action, handlers, state }) {
>
<span>{action.name}</span>
{action.shortcut?.length ? (
<kbd
style={{
padding: "4px 6px",
background: "rgba(0 0 0 / .1)",
borderRadius: "4px",
}}
>
{action.shortcut}
</kbd>
<div style={{ display: "grid", gridAutoFlow: "column", gap: "4px" }}>
{action.shortcut.map((sc) => (
<kbd
key={sc}
style={{
padding: "4px 6px",
background: "rgba(0 0 0 / .1)",
borderRadius: "4px",
}}
>
{sc}
</kbd>
))}
</div>
) : null}
</div>
);
Expand Down
5 changes: 0 additions & 5 deletions example/src/Docs.tsx

This file was deleted.

52 changes: 52 additions & 0 deletions example/src/Docs/Actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from "react";
import Code from "../Code";

export default function Actions() {
return (
<div>
<h1>Actions</h1>
<p>
When a user searches for something in kbar, the result is a list of
actions. These actions are represented by a simple object data
structure.
</p>
<Code
code={`interface Action {
id: string;
name: string;
shortcut: string[];
keywords: string;
perform?: () => void;
section?: string;
parent?: ActionId | null | undefined;
children?: ActionId[];
}`}
/>
<p>kbar manages an internal state of action objects.</p>
<p>
Actions can have nested actions, represented by <code>children</code>{" "}
above. With this, we can do things like building a folder-like
experience where toggling one action leads to displaying a "nested" list
of other actions.
</p>
<h3>Static, global actions</h3>
<p>
kbar takes an initial list of actions when instantiated. This initial
list is considered a static/global list of actions. These actions exist
on each page of your site.
</p>
<h3>Dynamic actions</h3>
<p>
While it is good to have a set of actions registered up front and
available globally, sometimes you will want to have actions available
only when on a specific page, or even when a specific component is
rendered.
</p>
<p>
Actions can be registered at runtime using the{" "}
<code>useRegisterActions</code> hook. This dynamically adds and removes
actions based on where the hook lives.
</p>
</div>
);
}
Loading

1 comment on commit c1de58a

@vercel
Copy link

@vercel vercel bot commented on c1de58a Sep 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

Please sign in to comment.