fix: UI/UX fixes on Global Actions (CMD / CTRL + K) (#9739)
* feat: command K palette , removed kbar * chore: updated cmdk for login checks and icons * feat: updated icons and test cases * adding more llm monitoring sources to onbaording(frontend) (#9623) * feat: code update, PR comment fix, package.json update * feat: code update, removed expand icon, moved keyboard func * feat: css variable update * feat: removed kbar from applayout * feat: updated cursor bot comments * feat: updated cursor bot and test case file * feat: scss formatted * feat: deleted unwanted merge change --------- Co-authored-by: gkarthi-signoz <goutham@signoz.io> Co-authored-by: Aditya Singh <adityasinghssj1@gmail.com>
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
"@signozhq/calendar": "0.0.0",
|
||||
"@signozhq/callout": "0.0.2",
|
||||
"@signozhq/checkbox": "0.0.2",
|
||||
"@signozhq/command": "0.0.0",
|
||||
"@signozhq/design-tokens": "1.1.4",
|
||||
"@signozhq/input": "0.0.2",
|
||||
"@signozhq/popover": "0.0.0",
|
||||
@@ -104,7 +105,6 @@
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
"jest": "^27.5.1",
|
||||
"js-base64": "^3.7.2",
|
||||
"kbar": "0.1.0-beta.48",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
||||
@@ -4,7 +4,7 @@ import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import AppLoading from 'components/AppLoading/AppLoading';
|
||||
import KBarCommandPalette from 'components/KBarCommandPalette/KBarCommandPalette';
|
||||
import { CmdKPalette } from 'components/cmdKPalette/cmdKPalette';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
@@ -24,9 +24,9 @@ import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFall
|
||||
import posthog from 'posthog-js';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { IUser } from 'providers/App/types';
|
||||
import { CmdKProvider } from 'providers/cmdKProvider';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { ErrorModalProvider } from 'providers/ErrorModalProvider';
|
||||
import { KBarCommandPaletteProvider } from 'providers/KBarCommandPaletteProvider';
|
||||
import { PreferenceContextProvider } from 'providers/preferences/context/PreferenceContextProvider';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense, useCallback, useEffect, useState } from 'react';
|
||||
@@ -364,10 +364,10 @@ function App(): JSX.Element {
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
<Router history={history}>
|
||||
<CompatRouter>
|
||||
<KBarCommandPaletteProvider>
|
||||
<KBarCommandPalette />
|
||||
<CmdKProvider>
|
||||
<NotificationProvider>
|
||||
<ErrorModalProvider>
|
||||
{isLoggedInState && <CmdKPalette userRole={user.role} />}
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
@@ -398,7 +398,7 @@ function App(): JSX.Element {
|
||||
</PrivateRoute>
|
||||
</ErrorModalProvider>
|
||||
</NotificationProvider>
|
||||
</KBarCommandPaletteProvider>
|
||||
</CmdKProvider>
|
||||
</CompatRouter>
|
||||
</Router>
|
||||
</ConfigProvider>
|
||||
|
||||
1
frontend/src/auto-import-registry.d.ts
vendored
1
frontend/src/auto-import-registry.d.ts
vendored
@@ -14,6 +14,7 @@ import '@signozhq/badge';
|
||||
import '@signozhq/button';
|
||||
import '@signozhq/calendar';
|
||||
import '@signozhq/callout';
|
||||
import '@signozhq/command';
|
||||
import '@signozhq/design-tokens';
|
||||
import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
.kbar-command-palette__positioner {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 1rem;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(6px);
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.kbar-command-palette__animator {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.kbar-command-palette__card {
|
||||
background: var(--bg-ink-500);
|
||||
color: var(--text-vanilla-100);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.kbar-command-palette__search {
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--border-ink-200);
|
||||
color: var(--text-vanilla-100);
|
||||
outline: none;
|
||||
background-color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.kbar-command-palette__section {
|
||||
padding: 8px 16px 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: var(--text-robin-500);
|
||||
font-family: 'Inter', sans-serif;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.kbar-command-palette__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 16px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.kbar-command-palette__item:hover,
|
||||
.kbar-command-palette__item--active {
|
||||
background: var(--bg-ink-400);
|
||||
}
|
||||
|
||||
.kbar-command-palette__icon {
|
||||
flex-shrink: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.kbar-command-palette__shortcut {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.kbar-command-palette__key {
|
||||
padding: 2px 6px;
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
background: var(--bg-ink-300);
|
||||
color: var(--text-vanilla-300);
|
||||
text-transform: uppercase;
|
||||
font-family: 'Space Mono', monospace;
|
||||
}
|
||||
|
||||
.kbar-command-palette__results-container {
|
||||
div {
|
||||
&::-webkit-scrollbar {
|
||||
width: 0.3rem;
|
||||
height: 0.3rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-slate-300);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-slate-200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.kbar-command-palette__positioner {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.kbar-command-palette__card {
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.kbar-command-palette__search {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
color: var(--text-ink-500);
|
||||
background-color: var(--bg-vanilla-100);
|
||||
}
|
||||
|
||||
.kbar-command-palette__item {
|
||||
color: var(--text-ink-500);
|
||||
}
|
||||
|
||||
.kbar-command-palette__item:hover,
|
||||
.kbar-command-palette__item--active {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.kbar-command-palette__icon {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.kbar-command-palette__key {
|
||||
background: #eee;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.kbar-command-palette__results-container {
|
||||
div {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--bg-vanilla-300);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import './KBarCommandPalette.scss';
|
||||
|
||||
import {
|
||||
KBarAnimator,
|
||||
KBarPortal,
|
||||
KBarPositioner,
|
||||
KBarResults,
|
||||
KBarSearch,
|
||||
useMatches,
|
||||
} from 'kbar';
|
||||
|
||||
function Results(): JSX.Element {
|
||||
const { results } = useMatches();
|
||||
|
||||
const renderResults = ({
|
||||
item,
|
||||
active,
|
||||
}: {
|
||||
item: any;
|
||||
active: boolean;
|
||||
}): JSX.Element =>
|
||||
typeof item === 'string' ? (
|
||||
<div className="kbar-command-palette__section">{item}</div>
|
||||
) : (
|
||||
<div
|
||||
className={`kbar-command-palette__item ${
|
||||
active ? 'kbar-command-palette__item--active' : ''
|
||||
}`}
|
||||
>
|
||||
{item.icon}
|
||||
<span>{item.name}</span>
|
||||
{item.shortcut?.length ? (
|
||||
<span className="kbar-command-palette__shortcut">
|
||||
{item.shortcut.map((sc: string) => (
|
||||
<kbd key={sc} className="kbar-command-palette__key">
|
||||
{sc}
|
||||
</kbd>
|
||||
))}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="kbar-command-palette__results-container">
|
||||
<KBarResults items={results} onRender={renderResults} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function KBarCommandPalette(): JSX.Element {
|
||||
return (
|
||||
<KBarPortal>
|
||||
<KBarPositioner className="kbar-command-palette__positioner">
|
||||
<KBarAnimator className="kbar-command-palette__animator">
|
||||
<div className="kbar-command-palette__card">
|
||||
<KBarSearch
|
||||
className="kbar-command-palette__search"
|
||||
placeholder="Search or type a command..."
|
||||
/>
|
||||
<Results />
|
||||
</div>
|
||||
</KBarAnimator>
|
||||
</KBarPositioner>
|
||||
</KBarPortal>
|
||||
);
|
||||
}
|
||||
|
||||
export default KBarCommandPalette;
|
||||
@@ -0,0 +1,208 @@
|
||||
/**
|
||||
* src/components/cmdKPalette/__test__/cmdkPalette.test.tsx
|
||||
*/
|
||||
import '@testing-library/jest-dom/extend-expect';
|
||||
|
||||
// ---- Mocks (must run BEFORE importing the component) ----
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { render, screen, userEvent } from 'tests/test-utils';
|
||||
|
||||
import { CmdKPalette } from '../cmdKPalette';
|
||||
|
||||
const HOME_LABEL = 'Go to Home';
|
||||
|
||||
beforeAll(() => {
|
||||
Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
|
||||
configurable: true,
|
||||
value: jest.fn(),
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// restore
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
delete (HTMLElement.prototype as any).scrollIntoView;
|
||||
});
|
||||
|
||||
// mock history.push / replace / go / location
|
||||
jest.mock('lib/history', () => {
|
||||
const location = { pathname: '/', search: '', hash: '' };
|
||||
|
||||
const stack: { pathname: string; search: string }[] = [
|
||||
{ pathname: '/', search: '' },
|
||||
];
|
||||
|
||||
const push = jest.fn((path: string) => {
|
||||
const [rawPath, rawQuery] = path.split('?');
|
||||
const pathname = rawPath || '/';
|
||||
const search = path.includes('?') ? `?${rawQuery || ''}` : '';
|
||||
|
||||
location.pathname = pathname;
|
||||
location.search = search;
|
||||
|
||||
stack.push({ pathname, search });
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const replace = jest.fn((path: string) => {
|
||||
const [rawPath, rawQuery] = path.split('?');
|
||||
const pathname = rawPath || '/';
|
||||
const search = path.includes('?') ? `?${rawQuery || ''}` : '';
|
||||
|
||||
location.pathname = pathname;
|
||||
location.search = search;
|
||||
|
||||
if (stack.length > 0) {
|
||||
stack[stack.length - 1] = { pathname, search };
|
||||
} else {
|
||||
stack.push({ pathname, search });
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const listen = jest.fn();
|
||||
const go = jest.fn((n: number) => {
|
||||
if (n < 0 && stack.length > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
const top = stack[stack.length - 1] || { pathname: '/', search: '' };
|
||||
location.pathname = top.pathname;
|
||||
location.search = top.search;
|
||||
});
|
||||
|
||||
return {
|
||||
push,
|
||||
replace,
|
||||
listen,
|
||||
go,
|
||||
location,
|
||||
__stack: stack,
|
||||
};
|
||||
});
|
||||
|
||||
// Mock ResizeObserver for Jest/jsdom
|
||||
class ResizeObserver {
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, class-methods-use-this
|
||||
observe() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, class-methods-use-this
|
||||
unobserve() {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, class-methods-use-this
|
||||
disconnect() {}
|
||||
}
|
||||
|
||||
(global as any).ResizeObserver = ResizeObserver;
|
||||
|
||||
// mock cmdK provider hook (open state + setter)
|
||||
const mockSetOpen = jest.fn();
|
||||
jest.mock('providers/cmdKProvider', (): unknown => ({
|
||||
useCmdK: (): {
|
||||
open: boolean;
|
||||
setOpen: jest.Mock;
|
||||
openCmdK: jest.Mock;
|
||||
closeCmdK: jest.Mock;
|
||||
} => ({
|
||||
open: true,
|
||||
setOpen: mockSetOpen,
|
||||
openCmdK: jest.fn(),
|
||||
closeCmdK: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
// mock notifications hook
|
||||
jest.mock('hooks/useNotifications', (): unknown => ({
|
||||
useNotifications: (): { notifications: [] } => ({ notifications: [] }),
|
||||
}));
|
||||
|
||||
// mock theme hook
|
||||
jest.mock('hooks/useDarkMode', (): unknown => ({
|
||||
useThemeMode: (): {
|
||||
setAutoSwitch: jest.Mock;
|
||||
setTheme: jest.Mock;
|
||||
theme: string;
|
||||
} => ({
|
||||
setAutoSwitch: jest.fn(),
|
||||
setTheme: jest.fn(),
|
||||
theme: 'dark',
|
||||
}),
|
||||
}));
|
||||
|
||||
// mock updateUserPreference API and react-query mutation
|
||||
jest.mock('api/v1/user/preferences/name/update', (): jest.Mock => jest.fn());
|
||||
jest.mock('react-query', (): unknown => {
|
||||
const actual = jest.requireActual('react-query');
|
||||
return {
|
||||
...actual,
|
||||
useMutation: (): { mutate: jest.Mock } => ({ mutate: jest.fn() }),
|
||||
};
|
||||
});
|
||||
|
||||
// mock other side-effecty modules
|
||||
jest.mock('api/common/logEvent', () => jest.fn());
|
||||
jest.mock('api/browser/localstorage/set', () => jest.fn());
|
||||
jest.mock('utils/error', () => ({ showErrorNotification: jest.fn() }));
|
||||
|
||||
// ---- Tests ----
|
||||
describe('CmdKPalette', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
test('renders navigation and settings groups and items', () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
expect(screen.getByText('Navigation')).toBeInTheDocument();
|
||||
expect(screen.getByText('Settings')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(HOME_LABEL)).toBeInTheDocument();
|
||||
expect(screen.getByText('Go to Dashboards')).toBeInTheDocument();
|
||||
expect(screen.getByText('Open Sidebar')).toBeInTheDocument();
|
||||
expect(screen.getByText('Switch to Dark Mode')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('clicking a navigation item calls history.push with correct route', async () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const homeItem = screen.getByText(HOME_LABEL);
|
||||
await userEvent.click(homeItem);
|
||||
|
||||
expect(history.push).toHaveBeenCalledWith(ROUTES.HOME);
|
||||
});
|
||||
|
||||
test('role-based filtering (basic smoke)', () => {
|
||||
render(<CmdKPalette userRole="VIEWER" />);
|
||||
|
||||
// VIEWER still sees basic navigation items
|
||||
expect(screen.getByText(HOME_LABEL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('keyboard shortcut opens palette via setOpen', () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const event = new KeyboardEvent('keydown', { key: 'k', ctrlKey: true });
|
||||
window.dispatchEvent(event);
|
||||
|
||||
expect(mockSetOpen).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('items render with icons when provided', () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const iconHolders = document.querySelectorAll('.cmd-item-icon');
|
||||
expect(iconHolders.length).toBeGreaterThan(0);
|
||||
expect(screen.getByText(HOME_LABEL)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('closing the palette via handleInvoke sets open to false', async () => {
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const dashItem = screen.getByText('Go to Dashboards');
|
||||
await userEvent.click(dashItem);
|
||||
|
||||
// last call from handleInvoke should set open to false
|
||||
expect(mockSetOpen).toHaveBeenCalledWith(false);
|
||||
});
|
||||
});
|
||||
55
frontend/src/components/cmdKPalette/cmdKPalette.scss
Normal file
55
frontend/src/components/cmdKPalette/cmdKPalette.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
/* Overlay stays below content */
|
||||
[data-slot='dialog-overlay'] {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* Dialog content always above overlay */
|
||||
[data-slot='dialog-content'] {
|
||||
position: fixed;
|
||||
z-index: 60;
|
||||
}
|
||||
|
||||
.cmdk-section-heading [cmdk-group-heading] {
|
||||
text-transform: uppercase;
|
||||
color: var(--bg-slate-100);
|
||||
}
|
||||
|
||||
/* Hide scrollbar but keep scroll */
|
||||
.cmdk-list-scroll {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.cmdk-list-scroll::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Edge */
|
||||
}
|
||||
|
||||
.cmdk-list-scroll {
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.cmdk-input-wrapper {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.cmdk-item-light:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--bg-vanilla-200) !important;
|
||||
}
|
||||
|
||||
.cmdk-item-light[data-selected='true'] {
|
||||
background-color: var(--bg-vanilla-300) !important;
|
||||
color: var(--bg-ink-500);
|
||||
}
|
||||
|
||||
.cmdk-item {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
[cmdk-item] svg {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.cmd-item-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
336
frontend/src/components/cmdKPalette/cmdKPalette.tsx
Normal file
336
frontend/src/components/cmdKPalette/cmdKPalette.tsx
Normal file
@@ -0,0 +1,336 @@
|
||||
import './cmdKPalette.scss';
|
||||
|
||||
import {
|
||||
CommandDialog,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandShortcut,
|
||||
} from '@signozhq/command';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import updateUserPreference from 'api/v1/user/preferences/name/update';
|
||||
import { AxiosError } from 'axios';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { USER_PREFERENCES } from 'constants/userPreferences';
|
||||
import { useThemeMode } from 'hooks/useDarkMode';
|
||||
import { THEME_MODE } from 'hooks/useDarkMode/constant';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import history from 'lib/history';
|
||||
import {
|
||||
BellDot,
|
||||
BugIcon,
|
||||
DraftingCompass,
|
||||
Expand,
|
||||
HardDrive,
|
||||
Home,
|
||||
LayoutGrid,
|
||||
ListMinus,
|
||||
ScrollText,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
import { showErrorNotification } from 'utils/error';
|
||||
|
||||
import { useAppContext } from '../../providers/App/App';
|
||||
import { useCmdK } from '../../providers/cmdKProvider';
|
||||
|
||||
type CmdAction = {
|
||||
id: string;
|
||||
name: string;
|
||||
shortcut?: string[];
|
||||
keywords?: string;
|
||||
section?: string;
|
||||
icon?: React.ReactNode;
|
||||
roles?: UserRole[];
|
||||
perform: () => void;
|
||||
};
|
||||
|
||||
type UserRole = 'ADMIN' | 'EDITOR' | 'AUTHOR' | 'VIEWER';
|
||||
export function CmdKPalette({
|
||||
userRole,
|
||||
}: {
|
||||
userRole: UserRole;
|
||||
}): JSX.Element | null {
|
||||
const { open, setOpen } = useCmdK();
|
||||
|
||||
const { updateUserPreferenceInContext } = useAppContext();
|
||||
const { notifications } = useNotifications();
|
||||
const { setAutoSwitch, setTheme, theme } = useThemeMode();
|
||||
|
||||
const { mutate: updateUserPreferenceMutation } = useMutation(
|
||||
updateUserPreference,
|
||||
{
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
// toggle palette with ⌘/Ctrl+K
|
||||
function handleGlobalCmdK(
|
||||
e: KeyboardEvent,
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
): void {
|
||||
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
|
||||
e.preventDefault();
|
||||
setOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
const cmdKEffect = (): void | (() => void) => {
|
||||
const listener = (e: KeyboardEvent): void => {
|
||||
handleGlobalCmdK(e, setOpen);
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', listener);
|
||||
|
||||
return (): void => {
|
||||
window.removeEventListener('keydown', listener);
|
||||
setOpen(false);
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(cmdKEffect, [setOpen]);
|
||||
|
||||
function handleThemeChange(value: string): void {
|
||||
logEvent('Account Settings: Theme Changed', { theme: value });
|
||||
if (value === 'auto') {
|
||||
setAutoSwitch(true);
|
||||
} else {
|
||||
setAutoSwitch(false);
|
||||
setTheme(value);
|
||||
}
|
||||
}
|
||||
|
||||
function onClickHandler(key: string): void {
|
||||
history.push(key);
|
||||
}
|
||||
|
||||
function handleOpenSidebar(): void {
|
||||
setLocalStorageApi(USER_PREFERENCES.SIDENAV_PINNED, 'true');
|
||||
const save = { name: USER_PREFERENCES.SIDENAV_PINNED, value: true };
|
||||
updateUserPreferenceInContext(save as UserPreference);
|
||||
updateUserPreferenceMutation({
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: true,
|
||||
});
|
||||
}
|
||||
|
||||
function handleCloseSidebar(): void {
|
||||
setLocalStorageApi(USER_PREFERENCES.SIDENAV_PINNED, 'false');
|
||||
const save = { name: USER_PREFERENCES.SIDENAV_PINNED, value: false };
|
||||
updateUserPreferenceInContext(save as UserPreference);
|
||||
updateUserPreferenceMutation({
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: false,
|
||||
});
|
||||
}
|
||||
|
||||
const actions: CmdAction[] = [
|
||||
{
|
||||
id: 'home',
|
||||
name: 'Go to Home',
|
||||
shortcut: ['shift + h'],
|
||||
keywords: 'home',
|
||||
section: 'Navigation',
|
||||
icon: <Home size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.HOME),
|
||||
},
|
||||
{
|
||||
id: 'dashboards',
|
||||
name: 'Go to Dashboards',
|
||||
shortcut: ['shift + d'],
|
||||
keywords: 'dashboards',
|
||||
section: 'Navigation',
|
||||
icon: <LayoutGrid size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.ALL_DASHBOARD),
|
||||
},
|
||||
{
|
||||
id: 'services',
|
||||
name: 'Go to Services',
|
||||
shortcut: ['shift + s'],
|
||||
keywords: 'services monitoring',
|
||||
section: 'Navigation',
|
||||
icon: <HardDrive size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.APPLICATION),
|
||||
},
|
||||
{
|
||||
id: 'traces',
|
||||
name: 'Go to Traces',
|
||||
shortcut: ['shift + t'],
|
||||
keywords: 'traces',
|
||||
section: 'Navigation',
|
||||
icon: <DraftingCompass size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.TRACES_EXPLORER),
|
||||
},
|
||||
{
|
||||
id: 'logs',
|
||||
name: 'Go to Logs',
|
||||
shortcut: ['shift + l'],
|
||||
keywords: 'logs',
|
||||
section: 'Navigation',
|
||||
icon: <ScrollText size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.LOGS),
|
||||
},
|
||||
{
|
||||
id: 'alerts',
|
||||
name: 'Go to Alerts',
|
||||
shortcut: ['shift + a'],
|
||||
keywords: 'alerts',
|
||||
section: 'Navigation',
|
||||
icon: <BellDot size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.LIST_ALL_ALERT),
|
||||
},
|
||||
{
|
||||
id: 'exceptions',
|
||||
name: 'Go to Exceptions',
|
||||
shortcut: ['shift + e'],
|
||||
keywords: 'exceptions errors',
|
||||
section: 'Navigation',
|
||||
icon: <BugIcon size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.ALL_ERROR),
|
||||
},
|
||||
{
|
||||
id: 'messaging-queues',
|
||||
name: 'Go to Messaging Queues',
|
||||
shortcut: ['shift + m'],
|
||||
keywords: 'messaging queues mq',
|
||||
section: 'Navigation',
|
||||
icon: <ListMinus size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.MESSAGING_QUEUES_OVERVIEW),
|
||||
},
|
||||
{
|
||||
id: 'my-settings',
|
||||
name: 'Go to Account Settings',
|
||||
keywords: 'account settings',
|
||||
section: 'Navigation',
|
||||
icon: <Settings size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => onClickHandler(ROUTES.MY_SETTINGS),
|
||||
},
|
||||
|
||||
// Settings
|
||||
{
|
||||
id: 'open-sidebar',
|
||||
name: 'Open Sidebar',
|
||||
keywords: 'sidebar navigation menu expand',
|
||||
section: 'Settings',
|
||||
icon: <Expand size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => handleOpenSidebar(),
|
||||
},
|
||||
{
|
||||
id: 'collapse-sidebar',
|
||||
name: 'Collapse Sidebar',
|
||||
keywords: 'sidebar navigation menu collapse',
|
||||
section: 'Settings',
|
||||
icon: <Expand size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => handleCloseSidebar(),
|
||||
},
|
||||
{
|
||||
id: 'dark-mode',
|
||||
name: 'Switch to Dark Mode',
|
||||
keywords: 'theme dark mode appearance',
|
||||
section: 'Settings',
|
||||
icon: <Expand size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => handleThemeChange(THEME_MODE.DARK),
|
||||
},
|
||||
{
|
||||
id: 'light-mode',
|
||||
name: 'Switch to Light Mode [Beta]',
|
||||
keywords: 'theme light mode appearance',
|
||||
section: 'Settings',
|
||||
icon: <Expand size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => handleThemeChange(THEME_MODE.LIGHT),
|
||||
},
|
||||
{
|
||||
id: 'system-theme',
|
||||
name: 'Switch to System Theme',
|
||||
keywords: 'system theme appearance',
|
||||
section: 'Settings',
|
||||
icon: <Expand size={14} />,
|
||||
roles: ['ADMIN', 'EDITOR', 'AUTHOR', 'VIEWER'],
|
||||
perform: (): void => handleThemeChange(THEME_MODE.SYSTEM),
|
||||
},
|
||||
];
|
||||
|
||||
// RBAC filter: show action if no roles set OR current user role is included
|
||||
const permitted = actions.filter(
|
||||
(a) => !a.roles || a.roles.includes(userRole),
|
||||
);
|
||||
|
||||
// group permitted actions by section
|
||||
const grouped: [string, CmdAction[]][] = ((): [string, CmdAction[]][] => {
|
||||
const map = new Map<string, CmdAction[]>();
|
||||
|
||||
permitted.forEach((a) => {
|
||||
const section = a.section ?? 'Other';
|
||||
const existing = map.get(section);
|
||||
|
||||
if (existing) {
|
||||
existing.push(a);
|
||||
} else {
|
||||
map.set(section, [a]);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(map.entries());
|
||||
})();
|
||||
|
||||
const handleInvoke = (action: CmdAction): void => {
|
||||
try {
|
||||
action.perform();
|
||||
} catch (e) {
|
||||
console.error('Error invoking action', e);
|
||||
} finally {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<CommandDialog open={open} onOpenChange={setOpen} position="top" offset={110}>
|
||||
<CommandInput placeholder="Search…" className="cmdk-input-wrapper" />
|
||||
<CommandList className="cmdk-list-scroll">
|
||||
<CommandEmpty>No results</CommandEmpty>
|
||||
{grouped.map(([section, items]) => (
|
||||
<CommandGroup
|
||||
key={section}
|
||||
heading={section}
|
||||
className="cmdk-section-heading"
|
||||
>
|
||||
{items.map((it) => (
|
||||
<CommandItem
|
||||
key={it.id}
|
||||
onSelect={(): void => handleInvoke(it)}
|
||||
value={it.name}
|
||||
className={theme === 'light' ? 'cmdk-item-light' : 'cmdk-item'}
|
||||
>
|
||||
<span className="cmd-item-icon">{it.icon}</span>
|
||||
{it.name}
|
||||
{it.shortcut && it.shortcut.length > 0 && (
|
||||
<CommandShortcut>{it.shortcut.join(' • ')}</CommandShortcut>
|
||||
)}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</CommandList>
|
||||
</CommandDialog>
|
||||
);
|
||||
}
|
||||
@@ -35,7 +35,6 @@ import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useTabVisibility from 'hooks/useTabFocus';
|
||||
import { useKBar } from 'kbar';
|
||||
import history from 'lib/history';
|
||||
import { isNull } from 'lodash-es';
|
||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||
@@ -186,19 +185,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
const { query, disabled } = useKBar((state) => ({
|
||||
disabled: state.disabled,
|
||||
}));
|
||||
|
||||
// disable the kbar command palette when not logged in
|
||||
useEffect(() => {
|
||||
if (isLoggedIn) {
|
||||
query.disable(false);
|
||||
} else {
|
||||
query.disable(true);
|
||||
}
|
||||
}, [isLoggedIn, query, disabled]);
|
||||
|
||||
const changelogForTenant = isCloudUserVal
|
||||
? DeploymentType.CLOUD_ONLY
|
||||
: DeploymentType.OSS_ONLY;
|
||||
|
||||
@@ -5541,4 +5541,4 @@
|
||||
],
|
||||
"link": "https://signoz.io/docs/userguide/envoy-metrics/"
|
||||
}
|
||||
]
|
||||
]
|
||||
@@ -68,6 +68,7 @@ import { USER_ROLES } from 'types/roles';
|
||||
import { checkVersionState } from 'utils/app';
|
||||
import { showErrorNotification } from 'utils/error';
|
||||
|
||||
import { useCmdK } from '../../providers/cmdKProvider';
|
||||
import { routeConfig } from './config';
|
||||
import { getQueryString } from './helper';
|
||||
import {
|
||||
@@ -120,6 +121,7 @@ function SortableFilter({ item }: { item: SidebarItem }): JSX.Element {
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
const { openCmdK } = useCmdK();
|
||||
const { pathname, search } = useLocation();
|
||||
const { currentVersion, latestVersion, isCurrentVersionError } = useSelector<
|
||||
AppState,
|
||||
@@ -637,6 +639,8 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
} else {
|
||||
history.push(settingsRoute);
|
||||
}
|
||||
} else if (item.key === 'quick-search') {
|
||||
openCmdK();
|
||||
} else if (item) {
|
||||
onClickHandler(item?.key as string, event);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
Receipt,
|
||||
Route,
|
||||
ScrollText,
|
||||
Search,
|
||||
Settings,
|
||||
Slack,
|
||||
Unplug,
|
||||
@@ -188,6 +189,12 @@ export const primaryMenuItems: SidebarItem[] = [
|
||||
icon: <Home size={16} />,
|
||||
itemKey: 'home',
|
||||
},
|
||||
{
|
||||
key: 'quick-search',
|
||||
label: 'Search',
|
||||
icon: <Search size={16} />,
|
||||
itemKey: 'quick-search',
|
||||
},
|
||||
{
|
||||
key: ROUTES.LIST_ALL_ALERT,
|
||||
label: 'Alerts',
|
||||
|
||||
@@ -1,229 +0,0 @@
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import updateUserPreference from 'api/v1/user/preferences/name/update';
|
||||
import { AxiosError } from 'axios';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { USER_PREFERENCES } from 'constants/userPreferences';
|
||||
import { useThemeMode } from 'hooks/useDarkMode';
|
||||
import { THEME_MODE } from 'hooks/useDarkMode/constant';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { KBarProvider } from 'kbar';
|
||||
import history from 'lib/history';
|
||||
import { useCallback } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
import { showErrorNotification } from 'utils/error';
|
||||
|
||||
import { useAppContext } from './App/App';
|
||||
|
||||
export function KBarCommandPaletteProvider({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { setAutoSwitch, setTheme } = useThemeMode();
|
||||
|
||||
const handleThemeChange = (value: string): void => {
|
||||
logEvent('Account Settings: Theme Changed', {
|
||||
theme: value,
|
||||
});
|
||||
|
||||
if (value === 'auto') {
|
||||
setAutoSwitch(true);
|
||||
} else {
|
||||
setAutoSwitch(false);
|
||||
setTheme(value);
|
||||
}
|
||||
};
|
||||
|
||||
const onClickHandler = useCallback((key: string): void => {
|
||||
history.push(key);
|
||||
}, []);
|
||||
|
||||
const { updateUserPreferenceInContext } = useAppContext();
|
||||
|
||||
const { mutate: updateUserPreferenceMutation } = useMutation(
|
||||
updateUserPreference,
|
||||
{
|
||||
onError: (error) => {
|
||||
showErrorNotification(notifications, error as AxiosError);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const handleOpenSidebar = useCallback((): void => {
|
||||
setLocalStorageApi(USER_PREFERENCES.SIDENAV_PINNED, 'true');
|
||||
|
||||
// Update the context immediately
|
||||
const save = {
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: true,
|
||||
};
|
||||
|
||||
updateUserPreferenceInContext(save as UserPreference);
|
||||
|
||||
// Make the API call in the background
|
||||
updateUserPreferenceMutation({
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: true,
|
||||
});
|
||||
}, [updateUserPreferenceInContext, updateUserPreferenceMutation]);
|
||||
|
||||
const handleCloseSidebar = useCallback((): void => {
|
||||
setLocalStorageApi(USER_PREFERENCES.SIDENAV_PINNED, 'false');
|
||||
|
||||
// Update the context immediately
|
||||
const save = {
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: false,
|
||||
};
|
||||
|
||||
updateUserPreferenceInContext(save as UserPreference);
|
||||
|
||||
// Make the API call in the background
|
||||
updateUserPreferenceMutation({
|
||||
name: USER_PREFERENCES.SIDENAV_PINNED,
|
||||
value: false,
|
||||
});
|
||||
}, [updateUserPreferenceInContext, updateUserPreferenceMutation]);
|
||||
const kbarActions = [
|
||||
{
|
||||
id: 'home',
|
||||
name: 'Go to Home',
|
||||
shortcut: ['shift + h'],
|
||||
keywords: 'home',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.HOME);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'dashboards',
|
||||
name: 'Go to Dashboards',
|
||||
shortcut: ['shift + d'],
|
||||
keywords: 'dashboards',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.ALL_DASHBOARD);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'services',
|
||||
name: 'Go to Services',
|
||||
shortcut: ['shift + s'],
|
||||
keywords: 'services monitoring',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.APPLICATION);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'traces',
|
||||
name: 'Go to Traces',
|
||||
shortcut: ['shift + t'],
|
||||
keywords: 'traces',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.TRACES_EXPLORER);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'logs',
|
||||
name: 'Go to Logs',
|
||||
shortcut: ['shift + l'],
|
||||
keywords: 'logs',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.LOGS);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'alerts',
|
||||
name: 'Go to Alerts',
|
||||
shortcut: ['shift + a'],
|
||||
keywords: 'alerts',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.LIST_ALL_ALERT);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'exceptions',
|
||||
name: 'Go to Exceptions',
|
||||
shortcut: ['shift + e'],
|
||||
keywords: 'exceptions errors',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.ALL_ERROR);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'messaging-queues',
|
||||
name: 'Go to Messaging Queues',
|
||||
shortcut: ['shift + m'],
|
||||
keywords: 'messaging queues mq',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.MESSAGING_QUEUES_OVERVIEW);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'my-settings',
|
||||
name: 'Go to Account Settings',
|
||||
keywords: 'account settings',
|
||||
section: 'Navigation',
|
||||
perform: (): void => {
|
||||
onClickHandler(ROUTES.MY_SETTINGS);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'open-sidebar',
|
||||
name: 'Open Sidebar',
|
||||
keywords: 'sidebar navigation menu expand',
|
||||
section: 'Settings',
|
||||
perform: (): void => {
|
||||
handleOpenSidebar();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'collapse-sidebar',
|
||||
name: 'Collapse Sidebar',
|
||||
keywords: 'sidebar navigation menu collapse',
|
||||
section: 'Settings',
|
||||
perform: (): void => {
|
||||
handleCloseSidebar();
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'dark-mode',
|
||||
name: 'Switch to Dark Mode',
|
||||
keywords: 'theme dark mode appearance',
|
||||
section: 'Settings',
|
||||
perform: (): void => {
|
||||
handleThemeChange(THEME_MODE.DARK);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'light-mode',
|
||||
name: 'Switch to Light Mode [Beta]',
|
||||
keywords: 'theme light mode appearance',
|
||||
section: 'Settings',
|
||||
perform: (): void => {
|
||||
handleThemeChange(THEME_MODE.LIGHT);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'system-theme',
|
||||
name: 'Switch to System Theme',
|
||||
keywords: 'system theme appearance',
|
||||
section: 'Settings',
|
||||
perform: (): void => {
|
||||
handleThemeChange(THEME_MODE.SYSTEM);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return <KBarProvider actions={kbarActions}>{children}</KBarProvider>;
|
||||
}
|
||||
50
frontend/src/providers/cmdKProvider.tsx
Normal file
50
frontend/src/providers/cmdKProvider.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import React, {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
type CmdKContextType = {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
openCmdK: () => void;
|
||||
closeCmdK: () => void;
|
||||
};
|
||||
|
||||
const CmdKContext = createContext<CmdKContextType | null>(null);
|
||||
|
||||
export function CmdKProvider({
|
||||
children,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
}): JSX.Element {
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
|
||||
function openCmdK(): void {
|
||||
setOpen(true);
|
||||
}
|
||||
|
||||
function closeCmdK(): void {
|
||||
setOpen(false);
|
||||
}
|
||||
|
||||
const value = useMemo<CmdKContextType>(
|
||||
() => ({
|
||||
open,
|
||||
setOpen,
|
||||
openCmdK,
|
||||
closeCmdK,
|
||||
}),
|
||||
[open],
|
||||
);
|
||||
|
||||
return <CmdKContext.Provider value={value}>{children}</CmdKContext.Provider>;
|
||||
}
|
||||
|
||||
export function useCmdK(): CmdKContextType {
|
||||
const ctx = useContext(CmdKContext);
|
||||
if (!ctx) throw new Error('useCmdK must be used inside CmdKProvider');
|
||||
return ctx;
|
||||
}
|
||||
@@ -3583,7 +3583,7 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-compose-refs@1.1.2":
|
||||
"@radix-ui/react-compose-refs@1.1.2", "@radix-ui/react-compose-refs@^1.1.1":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
|
||||
integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
|
||||
@@ -3600,6 +3600,26 @@
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
|
||||
integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
|
||||
|
||||
"@radix-ui/react-dialog@^1.1.11", "@radix-ui/react-dialog@^1.1.6":
|
||||
version "1.1.15"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz#1de3d7a7e9a17a9874d29c07f5940a18a119b632"
|
||||
integrity sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==
|
||||
dependencies:
|
||||
"@radix-ui/primitive" "1.1.3"
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
"@radix-ui/react-context" "1.1.2"
|
||||
"@radix-ui/react-dismissable-layer" "1.1.11"
|
||||
"@radix-ui/react-focus-guards" "1.1.3"
|
||||
"@radix-ui/react-focus-scope" "1.1.7"
|
||||
"@radix-ui/react-id" "1.1.1"
|
||||
"@radix-ui/react-portal" "1.1.9"
|
||||
"@radix-ui/react-presence" "1.1.5"
|
||||
"@radix-ui/react-primitive" "2.1.3"
|
||||
"@radix-ui/react-slot" "1.2.3"
|
||||
"@radix-ui/react-use-controllable-state" "1.2.2"
|
||||
aria-hidden "^1.2.4"
|
||||
react-remove-scroll "^2.6.3"
|
||||
|
||||
"@radix-ui/react-direction@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b"
|
||||
@@ -3657,7 +3677,7 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-id@1.1.1":
|
||||
"@radix-ui/react-id@1.1.1", "@radix-ui/react-id@^1.1.0":
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.1.tgz#1404002e79a03fe062b7e3864aa01e24bd1471f7"
|
||||
integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==
|
||||
@@ -3726,7 +3746,7 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
|
||||
"@radix-ui/react-portal@1.1.9", "@radix-ui/react-portal@^1.0.1":
|
||||
"@radix-ui/react-portal@1.1.9":
|
||||
version "1.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz#14c3649fe48ec474ac51ed9f2b9f5da4d91c4472"
|
||||
integrity sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==
|
||||
@@ -3766,6 +3786,13 @@
|
||||
dependencies:
|
||||
"@radix-ui/react-slot" "1.2.3"
|
||||
|
||||
"@radix-ui/react-primitive@^2.0.2":
|
||||
version "2.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz#2626ea309ebd63bf5767d3e7fc4081f81b993df0"
|
||||
integrity sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==
|
||||
dependencies:
|
||||
"@radix-ui/react-slot" "1.2.4"
|
||||
|
||||
"@radix-ui/react-roving-focus@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974"
|
||||
@@ -3797,6 +3824,13 @@
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
|
||||
"@radix-ui/react-slot@1.2.4":
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.4.tgz#63c0ba05fdf90cc49076b94029c852d7bac1fb83"
|
||||
integrity sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "1.1.2"
|
||||
|
||||
"@radix-ui/react-tabs@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.0.4.tgz#993608eec55a5d1deddd446fa9978d2bc1053da2"
|
||||
@@ -4048,11 +4082,6 @@
|
||||
rc-resize-observer "^1.3.1"
|
||||
rc-util "^5.38.0"
|
||||
|
||||
"@reach/observe-rect@^1.1.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.2.0.tgz#d7a6013b8aafcc64c778a0ccb83355a11204d3b2"
|
||||
integrity sha512-Ba7HmkFgfQxZqqaeIWWkNK0rEhpxVQHIoVyW1YDSkGsGIXzcaW4deC8B0pZrNSSyLTdIk7y+5olKt5+g0GmFIQ==
|
||||
|
||||
"@react-dnd/asap@^5.0.1":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-5.0.2.tgz#1f81f124c1cd6f39511c11a881cfb0f715343488"
|
||||
@@ -4306,6 +4335,21 @@
|
||||
tailwind-merge "^2.5.2"
|
||||
tailwindcss-animate "^1.0.7"
|
||||
|
||||
"@signozhq/command@0.0.0":
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@signozhq/command/-/command-0.0.0.tgz#bd1e1cac7346e862dd61df64b756302e89e1a322"
|
||||
integrity sha512-AwRYxZTi4o8SBOL4hmgcgbhCKXl2Qb/TUSLbSYEMFdiQSl5VYA8XZJv5fSYVMJkAIlOaHzFzR04XNEU7lZcBpw==
|
||||
dependencies:
|
||||
"@radix-ui/react-dialog" "^1.1.11"
|
||||
"@radix-ui/react-icons" "^1.3.0"
|
||||
"@radix-ui/react-slot" "^1.1.0"
|
||||
class-variance-authority "^0.7.0"
|
||||
clsx "^2.1.1"
|
||||
cmdk "^1.1.1"
|
||||
lucide-react "^0.445.0"
|
||||
tailwind-merge "^2.5.2"
|
||||
tailwindcss-animate "^1.0.7"
|
||||
|
||||
"@signozhq/design-tokens@1.1.4":
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/@signozhq/design-tokens/-/design-tokens-1.1.4.tgz#5d5de5bd9d19b6a3631383db015cc4b70c3f7661"
|
||||
@@ -7397,6 +7441,16 @@ clsx@^2.1.1:
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999"
|
||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||
|
||||
cmdk@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/cmdk/-/cmdk-1.1.1.tgz#b8524272699ccaa37aaf07f36850b376bf3d58e5"
|
||||
integrity sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==
|
||||
dependencies:
|
||||
"@radix-ui/react-compose-refs" "^1.1.1"
|
||||
"@radix-ui/react-dialog" "^1.1.6"
|
||||
"@radix-ui/react-id" "^1.1.0"
|
||||
"@radix-ui/react-primitive" "^2.0.2"
|
||||
|
||||
co@^4.6.0:
|
||||
version "4.6.0"
|
||||
resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz"
|
||||
@@ -9439,11 +9493,6 @@ fast-diff@^1.1.2:
|
||||
resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz"
|
||||
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
|
||||
|
||||
fast-equals@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/fast-equals/-/fast-equals-2.0.4.tgz#3add9410585e2d7364c2deeb6a707beadb24b927"
|
||||
integrity sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w==
|
||||
|
||||
fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.3.0:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818"
|
||||
@@ -9797,11 +9846,6 @@ functions-have-names@^1.2.2, functions-have-names@^1.2.3:
|
||||
resolved "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz"
|
||||
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
|
||||
|
||||
fuse.js@^6.6.2:
|
||||
version "6.6.2"
|
||||
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111"
|
||||
integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==
|
||||
|
||||
gensync@^1.0.0-beta.2:
|
||||
version "1.0.0-beta.2"
|
||||
resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz"
|
||||
@@ -12018,17 +12062,6 @@ kapsule@1, kapsule@^1.14:
|
||||
dependencies:
|
||||
lodash-es "4"
|
||||
|
||||
kbar@0.1.0-beta.48:
|
||||
version "0.1.0-beta.48"
|
||||
resolved "https://registry.yarnpkg.com/kbar/-/kbar-0.1.0-beta.48.tgz#2db254cb2943f14200c5a5f47064135737527983"
|
||||
integrity sha512-HD5A1dqfK6XGeoH4fRWTmRt4y76sDbtGxY4Dh2xNa5MYtvtKsqfz+nRZ0tKgcrjjGYN4rf5TLXMJuiE7Pb8rXg==
|
||||
dependencies:
|
||||
"@radix-ui/react-portal" "^1.0.1"
|
||||
fast-equals "^2.0.3"
|
||||
fuse.js "^6.6.2"
|
||||
react-virtual "^2.8.2"
|
||||
tiny-invariant "^1.2.0"
|
||||
|
||||
keyv@^4.0.0:
|
||||
version "4.5.4"
|
||||
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
|
||||
@@ -15596,13 +15629,6 @@ react-use@^17.3.2:
|
||||
ts-easing "^0.2.0"
|
||||
tslib "^2.1.0"
|
||||
|
||||
react-virtual@^2.8.2:
|
||||
version "2.10.4"
|
||||
resolved "https://registry.yarnpkg.com/react-virtual/-/react-virtual-2.10.4.tgz#08712f0acd79d7d6f7c4726f05651a13b24d8704"
|
||||
integrity sha512-Ir6+oPQZTVHfa6+JL9M7cvMILstFZH/H3jqeYeKI4MSUX+rIruVwFC6nGVXw9wqAw8L0Kg2KvfXxI85OvYQdpQ==
|
||||
dependencies:
|
||||
"@reach/observe-rect" "^1.1.0"
|
||||
|
||||
react-virtuoso@4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-virtuoso/-/react-virtuoso-4.0.3.tgz#0dc8b10978095852d985b064157639b9fb9d9b1e"
|
||||
@@ -17317,11 +17343,6 @@ tiny-invariant@^1.0.2, tiny-invariant@^1.0.6:
|
||||
resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.1.tgz"
|
||||
integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==
|
||||
|
||||
tiny-invariant@^1.2.0:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127"
|
||||
integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==
|
||||
|
||||
tiny-warning@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz"
|
||||
|
||||
Reference in New Issue
Block a user