feat: add context specific search and actions (#1570)
This commit is contained in:
@@ -34,6 +34,10 @@ export const SpotlightGroupActions = <TOption extends Record<string, unknown>>({
|
||||
});
|
||||
|
||||
if (Array.isArray(options)) {
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filteredOptions = options
|
||||
.filter((option) => ("filter" in group ? group.filter(query, option) : false))
|
||||
.sort((optionA, optionB) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ActionIcon, Center, Group, Kbd } from "@mantine/core";
|
||||
import { Spotlight as MantineSpotlight } from "@mantine/spotlight";
|
||||
import { IconSearch, IconX } from "@tabler/icons-react";
|
||||
@@ -12,6 +12,7 @@ import { useI18n } from "@homarr/translation/client";
|
||||
import type { inferSearchInteractionOptions } from "../lib/interaction";
|
||||
import type { SearchMode } from "../lib/mode";
|
||||
import { searchModes } from "../modes";
|
||||
import { useSpotlightContextResults } from "../modes/home/context";
|
||||
import { selectAction, spotlightStore } from "../spotlight-store";
|
||||
import { SpotlightChildrenActions } from "./actions/children-actions";
|
||||
import { SpotlightActionGroups } from "./actions/groups/action-group";
|
||||
@@ -19,24 +20,45 @@ import { SpotlightActionGroups } from "./actions/groups/action-group";
|
||||
type SearchModeKey = keyof TranslationObject["search"]["mode"];
|
||||
|
||||
export const Spotlight = () => {
|
||||
const searchModeState = useState<SearchModeKey>("help");
|
||||
const items = useSpotlightContextResults();
|
||||
// We fallback to help if no context results are available
|
||||
const defaultMode = items.length >= 1 ? "home" : "help";
|
||||
const searchModeState = useState<SearchModeKey>(defaultMode);
|
||||
const mode = searchModeState[0];
|
||||
const activeMode = useMemo(() => searchModes.find((searchMode) => searchMode.modeKey === mode), [mode]);
|
||||
|
||||
/**
|
||||
* The below logic is used to switch to home page if any context results are registered
|
||||
* or to help page if context results are unregistered
|
||||
*/
|
||||
const previousLengthRef = useRef(items.length);
|
||||
useEffect(() => {
|
||||
if (items.length >= 1 && previousLengthRef.current === 0) {
|
||||
searchModeState[1]("home");
|
||||
} else if (items.length === 0 && previousLengthRef.current >= 1) {
|
||||
searchModeState[1]("help");
|
||||
}
|
||||
|
||||
previousLengthRef.current = items.length;
|
||||
}, [items.length, searchModeState]);
|
||||
|
||||
if (!activeMode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// We use the "key" below to prevent the 'Different amounts of hooks' error
|
||||
return <SpotlightWithActiveMode key={mode} modeState={searchModeState} activeMode={activeMode} />;
|
||||
return (
|
||||
<SpotlightWithActiveMode key={mode} modeState={searchModeState} activeMode={activeMode} defaultMode={defaultMode} />
|
||||
);
|
||||
};
|
||||
|
||||
interface SpotlightWithActiveModeProps {
|
||||
modeState: [SearchModeKey, Dispatch<SetStateAction<SearchModeKey>>];
|
||||
activeMode: SearchMode;
|
||||
defaultMode: SearchModeKey;
|
||||
}
|
||||
|
||||
const SpotlightWithActiveMode = ({ modeState, activeMode }: SpotlightWithActiveModeProps) => {
|
||||
const SpotlightWithActiveMode = ({ modeState, activeMode, defaultMode }: SpotlightWithActiveModeProps) => {
|
||||
const [query, setQuery] = useState("");
|
||||
const [mode, setMode] = modeState;
|
||||
const [childrenOptions, setChildrenOptions] = useState<inferSearchInteractionOptions<"children"> | null>(null);
|
||||
@@ -50,12 +72,12 @@ const SpotlightWithActiveMode = ({ modeState, activeMode }: SpotlightWithActiveM
|
||||
<MantineSpotlight.Root
|
||||
yOffset={8}
|
||||
onSpotlightClose={() => {
|
||||
setMode("help");
|
||||
setMode(defaultMode);
|
||||
setChildrenOptions(null);
|
||||
}}
|
||||
query={query}
|
||||
onQueryChange={(query) => {
|
||||
if (mode !== "help" || query.length !== 1) {
|
||||
if ((mode !== "help" && mode !== "home") || query.length !== 1) {
|
||||
setQuery(query);
|
||||
}
|
||||
|
||||
@@ -73,13 +95,13 @@ const SpotlightWithActiveMode = ({ modeState, activeMode }: SpotlightWithActiveM
|
||||
<MantineSpotlight.Search
|
||||
placeholder={`${t("search.placeholder")}...`}
|
||||
ref={inputRef}
|
||||
leftSectionWidth={activeMode.modeKey !== "help" ? 80 : 48}
|
||||
leftSectionWidth={activeMode.modeKey !== defaultMode ? 80 : 48}
|
||||
leftSection={
|
||||
<Group align="center" wrap="nowrap" gap="xs" w="100%" h="100%">
|
||||
<Center w={48} h="100%">
|
||||
<IconSearch stroke={1.5} />
|
||||
</Center>
|
||||
{activeMode.modeKey !== "help" ? <Kbd size="sm">{activeMode.character}</Kbd> : null}
|
||||
{activeMode.modeKey !== defaultMode ? <Kbd size="sm">{activeMode.character}</Kbd> : null}
|
||||
</Group>
|
||||
}
|
||||
styles={{
|
||||
@@ -88,10 +110,10 @@ const SpotlightWithActiveMode = ({ modeState, activeMode }: SpotlightWithActiveM
|
||||
},
|
||||
}}
|
||||
rightSection={
|
||||
mode === "help" ? undefined : (
|
||||
mode === defaultMode ? undefined : (
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
setMode("help");
|
||||
setMode(defaultMode);
|
||||
setChildrenOptions(null);
|
||||
inputRef.current?.focus();
|
||||
}}
|
||||
@@ -103,8 +125,8 @@ const SpotlightWithActiveMode = ({ modeState, activeMode }: SpotlightWithActiveM
|
||||
}
|
||||
value={query}
|
||||
onKeyDown={(event) => {
|
||||
if (query.length === 0 && mode !== "help" && event.key === "Backspace") {
|
||||
setMode("help");
|
||||
if (query.length === 0 && mode !== defaultMode && event.key === "Backspace") {
|
||||
setMode(defaultMode);
|
||||
setChildrenOptions(null);
|
||||
}
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user