diff --git a/src/renderer/features/search/components/collapsible-command-group.module.css b/src/renderer/features/search/components/collapsible-command-group.module.css
new file mode 100644
index 000000000..73e4122f4
--- /dev/null
+++ b/src/renderer/features/search/components/collapsible-command-group.module.css
@@ -0,0 +1,42 @@
+.root {
+ display: flex;
+ flex-direction: column;
+ gap: var(--theme-spacing-xs);
+
+ &:not(:last-child) {
+ margin-bottom: var(--theme-spacing-xs);
+ }
+}
+
+
+.heading {
+ display: flex;
+ gap: var(--theme-spacing-xs);
+ align-items: center;
+ font-size: var(--theme-font-size-sm);
+ cursor: pointer;
+ user-select: none;
+ opacity: 0.8;
+}
+
+.heading:hover {
+ opacity: 1;
+}
+
+.heading:focus-visible {
+ outline: 2px solid var(--theme-colors-primary);
+ outline-offset: 2px;
+}
+
+.chevron {
+ flex-shrink: 0;
+ width: 1rem;
+ height: 1rem;
+ opacity: 0.9;
+}
+
+.items {
+ display: flex;
+ flex-direction: column;
+ gap: 0;
+}
diff --git a/src/renderer/features/search/components/collapsible-command-group.tsx b/src/renderer/features/search/components/collapsible-command-group.tsx
new file mode 100644
index 000000000..e74bb4a99
--- /dev/null
+++ b/src/renderer/features/search/components/collapsible-command-group.tsx
@@ -0,0 +1,52 @@
+import { ReactNode, useCallback, useState } from 'react';
+
+import styles from './collapsible-command-group.module.css';
+
+import { Icon } from '/@/shared/components/icon/icon';
+import { Paper } from '/@/shared/components/paper/paper';
+
+interface CollapsibleCommandGroupProps {
+ children: ReactNode;
+ defaultExpanded?: boolean;
+ heading: string;
+}
+
+export function CollapsibleCommandGroup({
+ children,
+ defaultExpanded = true,
+ heading,
+}: CollapsibleCommandGroupProps) {
+ const [expanded, setExpanded] = useState(defaultExpanded);
+
+ const toggle = useCallback(() => {
+ setExpanded((prev) => !prev);
+ }, []);
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ toggle();
+ }
+ },
+ [toggle],
+ );
+
+ return (
+
+
+
+
+ {heading}
+
+
+ {expanded &&
{children}
}
+
+ );
+}
diff --git a/src/renderer/features/search/components/command-palette.tsx b/src/renderer/features/search/components/command-palette.tsx
index f7784e67e..a2faaf77d 100644
--- a/src/renderer/features/search/components/command-palette.tsx
+++ b/src/renderer/features/search/components/command-palette.tsx
@@ -1,9 +1,10 @@
import { useQuery } from '@tanstack/react-query';
-import { Fragment, useCallback, useRef, useState } from 'react';
+import { useCallback, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { generatePath, useNavigate } from 'react-router';
import { searchQueries } from '/@/renderer/features/search/api/search-api';
+import { CollapsibleCommandGroup } from '/@/renderer/features/search/components/collapsible-command-group';
import { Command, CommandPalettePages } from '/@/renderer/features/search/components/command';
import { CommandItemSelectable } from '/@/renderer/features/search/components/command-item-selectable';
import { GoToCommands } from '/@/renderer/features/search/components/go-to-commands';
@@ -13,8 +14,9 @@ import { ServerCommands } from '/@/renderer/features/search/components/server-co
import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
-import { Box } from '/@/shared/components/box/box';
+import { Breadcrumb } from '/@/shared/components/breadcrumb/breadcrumb';
import { Button } from '/@/shared/components/button/button';
+import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
import { Kbd } from '/@/shared/components/kbd/kbd';
@@ -94,19 +96,10 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
}}
size="lg"
styles={{
+ body: { padding: '0' },
header: { display: 'none' },
}}
>
-
- {pages.map((page, index) => (
-
- {index > 0 && ' > '}
-
-
- ))}
-
{
if (value.includes(search)) return 1;
@@ -130,26 +123,32 @@ export const CommandPalette = ({ modalProps }: CommandPaletteProps) => {
onChange={(e) => setQuery(e.currentTarget.value)}
ref={searchInputRef}
rightSection={
- query && (
- {
- setQuery('');
- searchInputRef.current?.focus();
- }}
- variant="transparent"
- >
-
-
+ isLoading ? (
+
+ ) : (
+ query && (
+ {
+ setQuery('');
+ searchInputRef.current?.focus();
+ }}
+ variant="transparent"
+ >
+
+
+ )
)
}
size="sm"
value={query}
/>
-
+
- No results found.
+
+ {t('common.noResultsFromQuery', { postProcess: 'sentenceCase' })}
+
{showAlbumGroup && (
-
+
{data?.albums?.map((album) => (
{
)}
))}
-
+
)}
{showArtistGroup && (
-
+
{data?.albumArtists.map((artist) => (
{
)}
))}
-
+
)}
{showTrackGroup && (
-
+
{data?.songs.map((song) => (
{
)}
))}
-
+
)}
{activePage === CommandPalettePages.HOME && (
{
)}
-
-
-
- {isHome && isLoading && query !== '' && }
-
-
- ESC
- ↑
- ↓
- ⏎
-
+
+
+ }>
+ {pages.map((page, index) => (
+
+ ))}
+
+
+
+ ESC
+ ↑
+ ↓
+ ⏎
-
+
);
};
diff --git a/src/renderer/features/search/components/command.css b/src/renderer/features/search/components/command.css
index 8a0cd1620..204257a6c 100644
--- a/src/renderer/features/search/components/command.css
+++ b/src/renderer/features/search/components/command.css
@@ -14,7 +14,7 @@ input[cmdk-input] {
[cmdk-group-items] {
display: flex;
flex-direction: column;
- gap: var(--theme-spacing-xs);
+ gap: 0;
}
[cmdk-item] {
diff --git a/src/renderer/features/search/components/library-command-item.tsx b/src/renderer/features/search/components/library-command-item.tsx
index ac1cf84b1..a02dcddd6 100644
--- a/src/renderer/features/search/components/library-command-item.tsx
+++ b/src/renderer/features/search/components/library-command-item.tsx
@@ -113,7 +113,7 @@ export const LibraryCommandItem = ({
{title}
-
+
{subtitle}