Skip to content

Commit 3141961

Browse files
authored
feat: add ability to disable kbar (#324)
1 parent 776e8fc commit 3141961

File tree

6 files changed

+117
-61
lines changed

6 files changed

+117
-61
lines changed

example/src/App.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
createAction,
2121
useMatches,
2222
ActionImpl,
23+
useKBar,
2324
} from "../../src";
2425
import useThemeActions from "./hooks/useThemeActions";
2526

example/src/Docs/APIReference.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import * as React from "react";
22
import { useLocation } from "react-router-dom";
3+
import Code from "../Code";
4+
import { useKBar } from "../../../src/useKBar";
35

46
export default function APIReference() {
7+
const { disabled, query } = useKBar((state) => ({
8+
disabled: state.disabled,
9+
}));
10+
511
return (
612
<div>
713
<h1>API Reference</h1>
@@ -38,6 +44,32 @@ export default function APIReference() {
3844
Only re renders the component when return value deeply changes. All kbar
3945
components are built using this hook.
4046
</p>
47+
48+
<p>For instance, let's disable kbar at any given time.</p>
49+
<Code
50+
code={`
51+
import { useKbar } from "kbar";
52+
53+
function MyApp() {
54+
const { query, disabled } = useKbar(state => ({
55+
disabled: state.disabled
56+
}));
57+
58+
return <button onClick={() => query.disable(!disabled)}>{disabled ? "Disabled" : "Disable"}</button>
59+
}
60+
`}
61+
/>
62+
63+
<p>Try it!</p>
64+
65+
<button
66+
onClick={() => {
67+
query.disable(!disabled);
68+
}}
69+
>
70+
{disabled ? "kbar is disabled" : "kbar is enabled!"}
71+
</button>
72+
4173
<Heading name="HistoryImpl" />
4274
<p>
4375
An internal history implementation which maintains a simple in memory

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/InternalEvents.tsx

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,29 @@ export function InternalEvents() {
1919
* `useToggleHandler` handles the keyboard events for toggling kbar.
2020
*/
2121
function useToggleHandler() {
22-
const { query, options, visualState, showing } = useKBar((state) => ({
23-
visualState: state.visualState,
24-
showing: state.visualState !== VisualState.hidden,
25-
}));
22+
const { query, options, visualState, showing, disabled } = useKBar(
23+
(state) => ({
24+
visualState: state.visualState,
25+
showing: state.visualState !== VisualState.hidden,
26+
disabled: state.disabled,
27+
})
28+
);
2629

2730
React.useEffect(() => {
31+
const close = () => {
32+
query.setVisualState((vs) => {
33+
if (vs === VisualState.hidden || vs === VisualState.animatingOut) {
34+
return vs;
35+
}
36+
return VisualState.animatingOut;
37+
});
38+
};
39+
40+
if (disabled) {
41+
close();
42+
return;
43+
}
44+
2845
const shortcut = options.toggleShortcut || "$mod+k";
2946

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

49-
query.setVisualState((vs) => {
50-
if (vs === VisualState.hidden || vs === VisualState.animatingOut) {
51-
return vs;
52-
}
53-
return VisualState.animatingOut;
54-
});
66+
close();
5567
},
5668
});
5769
return () => {
5870
unsubscribe();
5971
};
60-
}, [options.callbacks, options.toggleShortcut, query, showing]);
72+
}, [options.callbacks, options.toggleShortcut, query, showing, disabled]);
6173

6274
const timeoutRef = React.useRef<Timeout>();
6375
const runAnimateTimer = React.useCallback(
@@ -170,13 +182,14 @@ function wrap(handler: (event: KeyboardEvent) => void) {
170182
* performs actions for patterns that match the user defined `shortcut`.
171183
*/
172184
function useShortcuts() {
173-
const { actions, query, open, options } = useKBar((state) => ({
185+
const { actions, query, open, options, disabled } = useKBar((state) => ({
174186
actions: state.actions,
175187
open: state.visualState === VisualState.showing,
188+
disabled: state.disabled,
176189
}));
177190

178191
React.useEffect(() => {
179-
if (open) return;
192+
if (open || disabled) return;
180193

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

@@ -218,7 +231,7 @@ function useShortcuts() {
218231
return () => {
219232
unsubscribe();
220233
};
221-
}, [actions, open, options.callbacks, query]);
234+
}, [actions, open, options.callbacks, query, disabled]);
222235
}
223236

224237
/**

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export interface KBarState {
8080
actions: ActionTree;
8181
currentRootActionId?: ActionId | null;
8282
activeIndex: number;
83+
disabled: boolean;
8384
}
8485

8586
export interface KBarQuery {
@@ -93,6 +94,7 @@ export interface KBarQuery {
9394
setActiveIndex: (cb: number | ((currIndex: number) => number)) => void;
9495
inputRefSetter: (el: HTMLInputElement) => void;
9596
getInput: () => HTMLInputElement;
97+
disable: (disable: boolean) => void;
9698
}
9799

98100
export interface IKBarContext {

src/useStore.tsx

Lines changed: 53 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type {
88
IKBarContext,
99
KBarOptions,
1010
KBarProviderProps,
11+
KBarQuery,
1112
KBarState,
1213
} from "./types";
1314
import { VisualState } from "./types";
@@ -39,6 +40,7 @@ export function useStore(props: useStoreProps) {
3940
visualState: VisualState.hidden,
4041
actions: { ...actionsInterface.actions },
4142
activeIndex: 0,
43+
disabled: false,
4244
});
4345

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

7880
return React.useMemo(() => {
81+
const query: KBarQuery = {
82+
setCurrentRootAction: (actionId) => {
83+
setState((state) => ({
84+
...state,
85+
currentRootActionId: actionId,
86+
}));
87+
},
88+
setVisualState: (cb) => {
89+
setState((state) => ({
90+
...state,
91+
visualState: typeof cb === "function" ? cb(state.visualState) : cb,
92+
}));
93+
},
94+
setSearch: (searchQuery) =>
95+
setState((state) => ({
96+
...state,
97+
searchQuery,
98+
})),
99+
registerActions,
100+
toggle: () =>
101+
setState((state) => ({
102+
...state,
103+
visualState: [VisualState.animatingOut, VisualState.hidden].includes(
104+
state.visualState
105+
)
106+
? VisualState.animatingIn
107+
: VisualState.animatingOut,
108+
})),
109+
setActiveIndex: (cb) =>
110+
setState((state) => ({
111+
...state,
112+
activeIndex: typeof cb === "number" ? cb : cb(state.activeIndex),
113+
})),
114+
inputRefSetter: (el: HTMLInputElement) => {
115+
inputRef.current = el;
116+
},
117+
getInput: () => {
118+
invariant(
119+
inputRef.current,
120+
"Input ref is undefined, make sure you attach `query.inputRefSetter` to your search input."
121+
);
122+
return inputRef.current;
123+
},
124+
disable: (disable: boolean) => {
125+
setState((state) => ({
126+
...state,
127+
disabled: disable,
128+
}));
129+
},
130+
};
79131
return {
80132
getState,
81-
query: {
82-
setCurrentRootAction: (actionId) => {
83-
setState((state) => ({
84-
...state,
85-
currentRootActionId: actionId,
86-
}));
87-
},
88-
setVisualState: (cb) => {
89-
setState((state) => ({
90-
...state,
91-
visualState: typeof cb === "function" ? cb(state.visualState) : cb,
92-
}));
93-
},
94-
setSearch: (searchQuery) =>
95-
setState((state) => ({
96-
...state,
97-
searchQuery,
98-
})),
99-
registerActions,
100-
toggle: () =>
101-
setState((state) => ({
102-
...state,
103-
visualState: [
104-
VisualState.animatingOut,
105-
VisualState.hidden,
106-
].includes(state.visualState)
107-
? VisualState.animatingIn
108-
: VisualState.animatingOut,
109-
})),
110-
setActiveIndex: (cb) =>
111-
setState((state) => ({
112-
...state,
113-
activeIndex: typeof cb === "number" ? cb : cb(state.activeIndex),
114-
})),
115-
inputRefSetter: (el: HTMLInputElement) => {
116-
inputRef.current = el;
117-
},
118-
getInput: () => {
119-
invariant(
120-
inputRef.current,
121-
"Input ref is undefined, make sure you attach `query.inputRefSetter` to your search input."
122-
);
123-
return inputRef.current;
124-
},
125-
},
133+
query,
126134
options: optionsRef.current,
127135
subscribe: (collector, cb) => publisher.subscribe(collector, cb),
128136
} as IKBarContext;

0 commit comments

Comments
 (0)