Skip to content

Commit 40b589b

Browse files
JonasBaclaudecodex
authored
ref(cmdk) jsx powered cmdk (#112262)
Refactor CMDK to a composable JSX approach. Previously, our CMDK required knowing actions aot and use a hook registration process. We want to move to a fully JSX powered approach and eventually also make actions render to the DOM (this is currently not possible because virtualization is required and the listbox API requires us to specify the full list) This change brings us half way to being able to power the CMDK UI by converting CMDK registration to JSX. This enables some interesting patterns where the UI logic can be colocated with the UI as seen [here](0158ce5). The way that this works is through the following mechanisms: - Collection context responsible for storing nodes and edges (essentially a recursive Context) that we use to maintain a parent<->child mapping. - A store with a `tree(root | undefined)` method which will reconstruct the tree on-demand so that callers can have access to the most complete UI at all times The slots API mentioned above this are used in CMDK as contextual priority slots that will appear as the first descendants of the CMDK action (similar to how priority int value used to work before). CMDK now has two slots, the first one is the `global` slot where global actions should be registered, the second is the `page` where broader page specific actions can be registered and the 3rd is the `task` slot, a slot that is contextual to what the user is doing (e.g. if they are editing a dashboard, saving or starring it might be a task they are currently interested). --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: OpenAI Codex <noreply@openai.com> Co-authored-by: OpenAI Codex <codex@openai.com>
1 parent 24704ee commit 40b589b

23 files changed

+2211
-1369
lines changed

static/app/bootstrap/processInitQueue.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {createBrowserRouter, RouterProvider} from 'react-router-dom';
44
import throttle from 'lodash/throttle';
55

66
import {exportedGlobals} from 'sentry/bootstrap/exportGlobals';
7-
import {CommandPaletteProvider} from 'sentry/components/commandPalette/context';
7+
import {CommandPaletteProvider} from 'sentry/components/commandPalette/ui/cmdk';
88
import {DocumentTitleManager} from 'sentry/components/sentryDocumentTitle/documentTitleManager';
99
import {ThemeAndStyleProvider} from 'sentry/components/themeAndStyleProvider';
1010
import {ScrapsProviders} from 'sentry/scrapsProviders';

static/app/components/commandPalette/__stories__/components.tsx

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,51 @@
11
import {useCallback} from 'react';
22

33
import {addSuccessMessage} from 'sentry/actionCreators/indicator';
4-
import {CommandPaletteProvider} from 'sentry/components/commandPalette/context';
5-
import {useCommandPaletteActionsRegister} from 'sentry/components/commandPalette/context';
6-
import type {
7-
CommandPaletteAction,
8-
CommandPaletteActionWithKey,
9-
} from 'sentry/components/commandPalette/types';
4+
import {CommandPaletteProvider} from 'sentry/components/commandPalette/ui/cmdk';
5+
import {CMDKAction} from 'sentry/components/commandPalette/ui/cmdk';
6+
import type {CMDKActionData} from 'sentry/components/commandPalette/ui/cmdk';
7+
import type {CollectionTreeNode} from 'sentry/components/commandPalette/ui/collection';
108
import {CommandPalette} from 'sentry/components/commandPalette/ui/commandPalette';
119
import {normalizeUrl} from 'sentry/utils/url/normalizeUrl';
1210
import {useNavigate} from 'sentry/utils/useNavigate';
1311

14-
export function RegisterActions({actions}: {actions: CommandPaletteAction[]}) {
15-
useCommandPaletteActionsRegister(actions);
16-
return null;
17-
}
18-
1912
export function CommandPaletteDemo() {
2013
const navigate = useNavigate();
2114

2215
const handleAction = useCallback(
23-
(action: CommandPaletteActionWithKey) => {
16+
(action: CollectionTreeNode<CMDKActionData>) => {
2417
if ('to' in action) {
2518
navigate(normalizeUrl(action.to));
2619
} else if ('onAction' in action) {
2720
action.onAction();
28-
} else {
29-
// @TODO: implement async actions
3021
}
3122
},
3223
[navigate]
3324
);
3425

35-
const demoActions: CommandPaletteAction[] = [
36-
{
37-
display: {label: 'Go to Flex story'},
38-
to: '/stories/layout/flex/',
39-
},
40-
{
41-
display: {label: 'Execute an action'},
42-
onAction: () => {
43-
addSuccessMessage('Action executed');
44-
},
45-
},
46-
{
47-
display: {label: 'Parent action'},
48-
actions: [
49-
{
50-
display: {label: 'Child action'},
51-
onAction: () => {
52-
addSuccessMessage('Child action executed');
53-
},
54-
},
55-
],
56-
},
57-
];
58-
5926
return (
6027
<CommandPaletteProvider>
61-
<RegisterActions actions={demoActions} />
28+
<CMDKAction display={{label: 'Go to Flex story'}} to="/stories/layout/flex/" />
29+
<CMDKAction
30+
display={{label: 'Execute an action'}}
31+
onAction={() => addSuccessMessage('Action executed')}
32+
/>
33+
<CMDKAction display={{label: 'Parent action'}}>
34+
<CMDKAction
35+
display={{label: 'Child action'}}
36+
onAction={() => addSuccessMessage('Child action executed')}
37+
/>
38+
</CMDKAction>
39+
<CMDKAction display={{label: 'Issues List'}}>
40+
<CMDKAction
41+
display={{label: 'Select all'}}
42+
onAction={() => addSuccessMessage('Select all')}
43+
/>
44+
<CMDKAction
45+
display={{label: 'Deselect all'}}
46+
onAction={() => addSuccessMessage('Deselect all')}
47+
/>
48+
</CMDKAction>
6249
<CommandPalette onAction={handleAction} />
6350
</CommandPaletteProvider>
6451
);

static/app/components/commandPalette/context.tsx

Lines changed: 0 additions & 177 deletions
This file was deleted.

static/app/components/commandPalette/types.tsx

Lines changed: 2 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -16,97 +16,23 @@ interface Action {
1616
keywords?: string[];
1717
}
1818

19-
/**
20-
* Actions that can be returned from an async resource query.
21-
* Async results cannot themselves carry a `resource` — chained async lookups
22-
* are not supported. Use CommandPaletteAction for registering top-level actions.
23-
*/
24-
interface CommandPaletteAsyncResultGroup extends Action {
25-
actions: CommandPaletteAsyncResult[];
26-
}
27-
28-
export type CommandPaletteAsyncResult =
29-
| CommandPaletteActionLink
30-
| CommandPaletteActionCallback
31-
| CommandPaletteAsyncResultGroup;
32-
33-
export type CMDKQueryOptions = UseQueryOptions<
34-
any,
35-
Error,
36-
CommandPaletteAsyncResult[],
37-
any
38-
>;
19+
export type CMDKQueryOptions = UseQueryOptions<any, Error, CommandPaletteAction[], any>;
3920

4021
export interface CommandPaletteActionLink extends Action {
4122
/** Navigate to a route when selected */
4223
to: LocationDescriptor;
4324
}
4425

4526
interface CommandPaletteActionCallback extends Action {
46-
/**
47-
* Execute a callback when the action is selected.
48-
* Use the `to` prop if you want to navigate to a route.
49-
*/
5027
onAction: () => void;
5128
}
5229

53-
interface CommandPaletteAsyncAction extends Action {
54-
/**
55-
* Execute a callback when the action is selected.
56-
* Use the `to` prop if you want to navigate to a route.
57-
*/
58-
resource: (query: string) => CMDKQueryOptions;
59-
}
60-
61-
interface CommandPaletteAsyncActionGroup extends Action {
62-
actions: CommandPaletteAction[];
63-
resource: (query: string) => CMDKQueryOptions;
64-
}
65-
6630
export type CommandPaletteAction =
6731
| CommandPaletteActionLink
6832
| CommandPaletteActionCallback
69-
| CommandPaletteActionGroup
70-
| CommandPaletteAsyncAction
71-
| CommandPaletteAsyncActionGroup;
33+
| CommandPaletteActionGroup;
7234

7335
interface CommandPaletteActionGroup extends Action {
7436
/** Nested actions to show when this action is selected */
7537
actions: CommandPaletteAction[];
7638
}
77-
78-
// Internally, a key is added to the actions in order to track them for registration and selection.
79-
type CommandPaletteActionLinkWithKey = CommandPaletteActionLink & {key: string};
80-
type CommandPaletteActionCallbackWithKey = CommandPaletteActionCallback & {
81-
key: string;
82-
};
83-
type CommandPaletteAsyncActionWithKey = CommandPaletteAsyncAction & {
84-
key: string;
85-
};
86-
type CommandPaletteAsyncActionGroupWithKey = Omit<
87-
CommandPaletteAsyncActionGroup,
88-
'actions'
89-
> & {
90-
actions: CommandPaletteActionWithKey[];
91-
key: string;
92-
};
93-
94-
export type CommandPaletteActionWithKey =
95-
// Sync actions (to, callback, group)
96-
| CommandPaletteActionLinkWithKey
97-
| CommandPaletteActionCallbackWithKey
98-
| CommandPaletteActionGroupWithKey
99-
// Async actions
100-
| CommandPaletteAsyncActionWithKey
101-
| CommandPaletteAsyncActionGroupWithKey;
102-
103-
interface CommandPaletteActionGroupWithKey extends CommandPaletteActionGroup {
104-
actions: Array<
105-
| CommandPaletteActionLinkWithKey
106-
| CommandPaletteActionCallbackWithKey
107-
| CommandPaletteActionGroupWithKey
108-
| CommandPaletteAsyncActionWithKey
109-
| CommandPaletteAsyncActionGroupWithKey
110-
>;
111-
key: string;
112-
}

0 commit comments

Comments
 (0)