Add base command palette component
This commit is contained in:
parent
547fe7be38
commit
822060b82c
4 changed files with 263 additions and 0 deletions
99
src/renderer/features/search/components/command-palette.tsx
Normal file
99
src/renderer/features/search/components/command-palette.tsx
Normal file
|
@ -0,0 +1,99 @@
|
|||
/* eslint-disable react/no-unknown-property */
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import styled from 'styled-components';
|
||||
import { GoToCommands } from './go-to-commands';
|
||||
import { Command, CommandPalettePages } from '/@/renderer/features/search/components/command';
|
||||
import { Modal } from '/@/renderer/components';
|
||||
import { HomeCommands } from './home-commands';
|
||||
|
||||
interface CommandPaletteProps {
|
||||
modalProps: typeof useDisclosure['arguments'];
|
||||
}
|
||||
|
||||
const CustomModal = styled(Modal)`
|
||||
& .mantine-Modal-header {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
|
||||
const [value, setValue] = useState('');
|
||||
const [query, setQuery] = useState('');
|
||||
const [pages, setPages] = useState<CommandPalettePages[]>([CommandPalettePages.HOME]);
|
||||
const activePage = pages[pages.length - 1];
|
||||
const isHome = activePage === CommandPalettePages.HOME;
|
||||
|
||||
const popPage = useCallback(() => {
|
||||
setPages((pages) => {
|
||||
const x = [...pages];
|
||||
x.splice(-1, 1);
|
||||
return x;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CustomModal
|
||||
{...modalProps}
|
||||
centered
|
||||
handlers={{
|
||||
...modalProps.handlers,
|
||||
close: () => {
|
||||
if (isHome) {
|
||||
modalProps.handlers.close();
|
||||
setQuery('');
|
||||
} else {
|
||||
popPage();
|
||||
}
|
||||
},
|
||||
toggle: () => {
|
||||
console.log('toggle');
|
||||
if (isHome) {
|
||||
modalProps.handlers.toggle();
|
||||
setQuery('');
|
||||
} else {
|
||||
popPage();
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Command
|
||||
filter={(value, search) => {
|
||||
if (value.includes(search)) return 1;
|
||||
if (value === 'search') return 1;
|
||||
return 0;
|
||||
}}
|
||||
label="Global Command Menu"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
>
|
||||
<Command.Input
|
||||
autoFocus
|
||||
placeholder="Enter your search..."
|
||||
value={query}
|
||||
onValueChange={setQuery}
|
||||
/>
|
||||
<Command.Separator />
|
||||
<Command.List>
|
||||
<Command.Empty>No results found.</Command.Empty>
|
||||
|
||||
{activePage === CommandPalettePages.HOME && (
|
||||
<HomeCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
pages={pages}
|
||||
query={query}
|
||||
setPages={setPages}
|
||||
setQuery={setQuery}
|
||||
/>
|
||||
)}
|
||||
{activePage === CommandPalettePages.GO_TO && (
|
||||
<GoToCommands
|
||||
handleClose={modalProps.handlers.close}
|
||||
setPages={setPages}
|
||||
/>
|
||||
)}
|
||||
</Command.List>
|
||||
</Command>
|
||||
</CustomModal>
|
||||
);
|
||||
};
|
61
src/renderer/features/search/components/command.tsx
Normal file
61
src/renderer/features/search/components/command.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import { Command as Cmdk } from 'cmdk';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export enum CommandPalettePages {
|
||||
GO_TO = 'go to',
|
||||
HOME = 'home',
|
||||
}
|
||||
|
||||
export const Command = styled(Cmdk)`
|
||||
[cmdk-root] {
|
||||
font-family: var(--content-font-family);
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
input[cmdk-input] {
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0 0.5rem;
|
||||
color: var(--input-fg);
|
||||
font-size: 1.1rem;
|
||||
background: transparent;
|
||||
border: none;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--input-placeholder-fg);
|
||||
}
|
||||
}
|
||||
|
||||
div[cmdk-group-heading] {
|
||||
margin: 1rem 0;
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
div[cmdk-item] {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
padding: 1rem 0.5rem;
|
||||
color: var(--btn-subtle-fg);
|
||||
background: var(--btn-subtle-bg);
|
||||
border-radius: 5px;
|
||||
|
||||
svg {
|
||||
width: 1.2rem;
|
||||
height: 1.2rem;
|
||||
}
|
||||
|
||||
&[data-selected] {
|
||||
color: var(--btn-subtle-fg-hover);
|
||||
background: rgba(255, 255, 255, 10%);
|
||||
}
|
||||
}
|
||||
|
||||
div[cmdk-separator] {
|
||||
height: 1px;
|
||||
margin: 0 0 0.5rem;
|
||||
background: var(--generic-border-color);
|
||||
}
|
||||
`;
|
41
src/renderer/features/search/components/go-to-commands.tsx
Normal file
41
src/renderer/features/search/components/go-to-commands.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Command, CommandPalettePages } from '/@/renderer/features/search/components/command';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
|
||||
interface GoToCommandsProps {
|
||||
handleClose: () => void;
|
||||
setPages: (pages: CommandPalettePages[]) => void;
|
||||
}
|
||||
|
||||
export const GoToCommands = ({ setPages, handleClose }: GoToCommandsProps) => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const goTo = useCallback(
|
||||
(route: string) => {
|
||||
navigate(route);
|
||||
setPages([CommandPalettePages.HOME]);
|
||||
handleClose();
|
||||
},
|
||||
[handleClose, navigate, setPages],
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.HOME)}>Home</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.SEARCH)}>Search</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.SETTINGS)}>Settings</Command.Item>
|
||||
<Command.Group heading="Library">
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_ALBUMS)}>Albums</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_SONGS)}>Tracks</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_ALBUM_ARTISTS)}>
|
||||
Album artists
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_GENRES)}>Genres</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.LIBRARY_FOLDERS)}>Folders</Command.Item>
|
||||
<Command.Item onSelect={() => goTo(AppRoute.PLAYLISTS)}>Playlists</Command.Item>
|
||||
</Command.Group>
|
||||
<Command.Separator />
|
||||
</>
|
||||
);
|
||||
};
|
62
src/renderer/features/search/components/home-commands.tsx
Normal file
62
src/renderer/features/search/components/home-commands.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { openModal, closeAllModals } from '@mantine/modals';
|
||||
import { Dispatch, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { CreatePlaylistForm } from '/@/renderer/features/playlists';
|
||||
import { Command, CommandPalettePages } from '/@/renderer/features/search/components/command';
|
||||
import { AppRoute } from '/@/renderer/router/routes';
|
||||
import { useCurrentServer } from '/@/renderer/store';
|
||||
import { ServerType } from '/@/renderer/types';
|
||||
|
||||
interface HomeCommandsProps {
|
||||
handleClose: () => void;
|
||||
pages: CommandPalettePages[];
|
||||
query: string;
|
||||
setPages: Dispatch<CommandPalettePages[]>;
|
||||
setQuery: Dispatch<string>;
|
||||
}
|
||||
|
||||
export const HomeCommands = ({
|
||||
query,
|
||||
setQuery,
|
||||
pages,
|
||||
setPages,
|
||||
handleClose,
|
||||
}: HomeCommandsProps) => {
|
||||
const navigate = useNavigate();
|
||||
const server = useCurrentServer();
|
||||
|
||||
const handleCreatePlaylistModal = useCallback(() => {
|
||||
handleClose();
|
||||
|
||||
openModal({
|
||||
children: <CreatePlaylistForm onCancel={() => closeAllModals()} />,
|
||||
size: server?.type === ServerType?.NAVIDROME ? 'lg' : 'sm',
|
||||
title: 'Create Playlist',
|
||||
});
|
||||
}, [handleClose, server?.type]);
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
navigate(AppRoute.SEARCH);
|
||||
setQuery('');
|
||||
handleClose();
|
||||
}, [handleClose, navigate, setQuery]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Group heading="Commands">
|
||||
<Command.Item onSelect={handleCreatePlaylistModal}>Create playlist...</Command.Item>
|
||||
<Command.Item onSelect={() => setPages([...pages, CommandPalettePages.GO_TO])}>
|
||||
Go to page...
|
||||
</Command.Item>
|
||||
{query !== '' && (
|
||||
<Command.Item
|
||||
value="Search"
|
||||
onSelect={handleSearch}
|
||||
>
|
||||
{query ? `Search for "${query}"...` : 'Search...'}
|
||||
</Command.Item>
|
||||
)}
|
||||
</Command.Group>
|
||||
</>
|
||||
);
|
||||
};
|
Reference in a new issue