Skip to content

Commit

Permalink
feat: add ability to disable kbar (#324)
Browse files Browse the repository at this point in the history
  • Loading branch information
timc1 authored Jul 17, 2023
1 parent 776e8fc commit 3141961
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 61 deletions.
1 change: 1 addition & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
createAction,
useMatches,
ActionImpl,
useKBar,
} from "../../src";
import useThemeActions from "./hooks/useThemeActions";

Expand Down
32 changes: 32 additions & 0 deletions example/src/Docs/APIReference.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import * as React from "react";
import { useLocation } from "react-router-dom";
import Code from "../Code";
import { useKBar } from "../../../src/useKBar";

export default function APIReference() {
const { disabled, query } = useKBar((state) => ({
disabled: state.disabled,
}));

return (
<div>
<h1>API Reference</h1>
Expand Down Expand Up @@ -38,6 +44,32 @@ export default function APIReference() {
Only re renders the component when return value deeply changes. All kbar
components are built using this hook.
</p>

<p>For instance, let's disable kbar at any given time.</p>
<Code
code={`
import { useKbar } from "kbar";
function MyApp() {
const { query, disabled } = useKbar(state => ({
disabled: state.disabled
}));
return <button onClick={() => query.disable(!disabled)}>{disabled ? "Disabled" : "Disable"}</button>
}
`}
/>

<p>Try it!</p>

<button
onClick={() => {
query.disable(!disabled);
}}
>
{disabled ? "kbar is disabled" : "kbar is enabled!"}
</button>

<Heading name="HistoryImpl" />
<p>
An internal history implementation which maintains a simple in memory
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 27 additions & 14 deletions src/InternalEvents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,29 @@ export function InternalEvents() {
* `useToggleHandler` handles the keyboard events for toggling kbar.
*/
function useToggleHandler() {
const { query, options, visualState, showing } = useKBar((state) => ({
visualState: state.visualState,
showing: state.visualState !== VisualState.hidden,
}));
const { query, options, visualState, showing, disabled } = useKBar(
(state) => ({
visualState: state.visualState,
showing: state.visualState !== VisualState.hidden,
disabled: state.disabled,
})
);

React.useEffect(() => {
const close = () => {
query.setVisualState((vs) => {
if (vs === VisualState.hidden || vs === VisualState.animatingOut) {
return vs;
}
return VisualState.animatingOut;
});
};

if (disabled) {
close();
return;
}

const shortcut = options.toggleShortcut || "$mod+k";

const unsubscribe = tinykeys(window, {
Expand All @@ -46,18 +63,13 @@ function useToggleHandler() {
options.callbacks?.onClose?.();
}

query.setVisualState((vs) => {
if (vs === VisualState.hidden || vs === VisualState.animatingOut) {
return vs;
}
return VisualState.animatingOut;
});
close();
},
});
return () => {
unsubscribe();
};
}, [options.callbacks, options.toggleShortcut, query, showing]);
}, [options.callbacks, options.toggleShortcut, query, showing, disabled]);

const timeoutRef = React.useRef<Timeout>();
const runAnimateTimer = React.useCallback(
Expand Down Expand Up @@ -170,13 +182,14 @@ function wrap(handler: (event: KeyboardEvent) => void) {
* performs actions for patterns that match the user defined `shortcut`.
*/
function useShortcuts() {
const { actions, query, open, options } = useKBar((state) => ({
const { actions, query, open, options, disabled } = useKBar((state) => ({
actions: state.actions,
open: state.visualState === VisualState.showing,
disabled: state.disabled,
}));

React.useEffect(() => {
if (open) return;
if (open || disabled) return;

const actionsList = Object.keys(actions).map((key) => actions[key]);

Expand Down Expand Up @@ -218,7 +231,7 @@ function useShortcuts() {
return () => {
unsubscribe();
};
}, [actions, open, options.callbacks, query]);
}, [actions, open, options.callbacks, query, disabled]);
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export interface KBarState {
actions: ActionTree;
currentRootActionId?: ActionId | null;
activeIndex: number;
disabled: boolean;
}

export interface KBarQuery {
Expand All @@ -93,6 +94,7 @@ export interface KBarQuery {
setActiveIndex: (cb: number | ((currIndex: number) => number)) => void;
inputRefSetter: (el: HTMLInputElement) => void;
getInput: () => HTMLInputElement;
disable: (disable: boolean) => void;
}

export interface IKBarContext {
Expand Down
98 changes: 53 additions & 45 deletions src/useStore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type {
IKBarContext,
KBarOptions,
KBarProviderProps,
KBarQuery,
KBarState,
} from "./types";
import { VisualState } from "./types";
Expand Down Expand Up @@ -39,6 +40,7 @@ export function useStore(props: useStoreProps) {
visualState: VisualState.hidden,
actions: { ...actionsInterface.actions },
activeIndex: 0,
disabled: false,
});

const currState = React.useRef(state);
Expand Down Expand Up @@ -76,53 +78,59 @@ export function useStore(props: useStoreProps) {
const inputRef = React.useRef<HTMLInputElement | null>(null);

return React.useMemo(() => {
const query: KBarQuery = {
setCurrentRootAction: (actionId) => {
setState((state) => ({
...state,
currentRootActionId: actionId,
}));
},
setVisualState: (cb) => {
setState((state) => ({
...state,
visualState: typeof cb === "function" ? cb(state.visualState) : cb,
}));
},
setSearch: (searchQuery) =>
setState((state) => ({
...state,
searchQuery,
})),
registerActions,
toggle: () =>
setState((state) => ({
...state,
visualState: [VisualState.animatingOut, VisualState.hidden].includes(
state.visualState
)
? VisualState.animatingIn
: VisualState.animatingOut,
})),
setActiveIndex: (cb) =>
setState((state) => ({
...state,
activeIndex: typeof cb === "number" ? cb : cb(state.activeIndex),
})),
inputRefSetter: (el: HTMLInputElement) => {
inputRef.current = el;
},
getInput: () => {
invariant(
inputRef.current,
"Input ref is undefined, make sure you attach `query.inputRefSetter` to your search input."
);
return inputRef.current;
},
disable: (disable: boolean) => {
setState((state) => ({
...state,
disabled: disable,
}));
},
};
return {
getState,
query: {
setCurrentRootAction: (actionId) => {
setState((state) => ({
...state,
currentRootActionId: actionId,
}));
},
setVisualState: (cb) => {
setState((state) => ({
...state,
visualState: typeof cb === "function" ? cb(state.visualState) : cb,
}));
},
setSearch: (searchQuery) =>
setState((state) => ({
...state,
searchQuery,
})),
registerActions,
toggle: () =>
setState((state) => ({
...state,
visualState: [
VisualState.animatingOut,
VisualState.hidden,
].includes(state.visualState)
? VisualState.animatingIn
: VisualState.animatingOut,
})),
setActiveIndex: (cb) =>
setState((state) => ({
...state,
activeIndex: typeof cb === "number" ? cb : cb(state.activeIndex),
})),
inputRefSetter: (el: HTMLInputElement) => {
inputRef.current = el;
},
getInput: () => {
invariant(
inputRef.current,
"Input ref is undefined, make sure you attach `query.inputRefSetter` to your search input."
);
return inputRef.current;
},
},
query,
options: optionsRef.current,
subscribe: (collector, cb) => publisher.subscribe(collector, cb),
} as IKBarContext;
Expand Down

1 comment on commit 3141961

@vercel
Copy link

@vercel vercel bot commented on 3141961 Jul 17, 2023

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:

kbar – ./

kbar.vercel.app
kbar-timc.vercel.app
kbar-git-main-timc.vercel.app

Please sign in to comment.