Search UI tweaks (#13965)

* Prevent keyboard shortcuts from running when input is focused

* fix reset button and update time pickers when using input

* simplify css

* consistent button order and spacing
This commit is contained in:
Josh Hawkins 2024-09-25 13:45:42 -05:00 committed by GitHub
parent fef30bc671
commit 32c7669b28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 56 additions and 43 deletions

View File

@ -430,6 +430,13 @@ function TimeRangeFilterButton({
const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour); const formattedSelectedAfter = useFormattedHour(config, selectedAfterHour);
const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour); const formattedSelectedBefore = useFormattedHour(config, selectedBeforeHour);
useEffect(() => {
setSelectedAfterHour(afterHour);
setSelectedBeforeHour(beforeHour);
// only refresh when state changes
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [timeRange]);
const trigger = ( const trigger = (
<Button <Button
size="sm" size="sm"
@ -447,18 +454,8 @@ function TimeRangeFilterButton({
</Button> </Button>
); );
const content = ( const content = (
<div <div className="scrollbar-container h-auto max-h-[80dvh] overflow-y-auto overflow-x-hidden">
className={cn( <div className="my-5 flex flex-row items-center justify-center gap-2">
"scrollbar-container flex h-auto max-h-[80dvh] flex-col overflow-y-auto overflow-x-hidden",
isDesktop ? "w-64" : "w-full gap-2 pt-2",
)}
>
<div
className={cn(
"mt-3 flex w-full items-center rounded-lg text-secondary-foreground",
isDesktop ? "mx-6 gap-2 px-2" : "justify-center gap-2",
)}
>
<Popover <Popover
open={startOpen} open={startOpen}
onOpenChange={(open) => { onOpenChange={(open) => {
@ -480,7 +477,7 @@ function TimeRangeFilterButton({
{formattedSelectedAfter} {formattedSelectedAfter}
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="flex flex-col items-center"> <PopoverContent className="flex flex-row items-center justify-center">
<input <input
className="text-md mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]" className="text-md mx-4 w-full border border-input bg-background p-1 text-secondary-foreground hover:bg-accent hover:text-accent-foreground dark:[color-scheme:dark]"
id="startTime" id="startTime"
@ -534,8 +531,8 @@ function TimeRangeFilterButton({
/> />
</PopoverContent> </PopoverContent>
</Popover> </Popover>
<DropdownMenuSeparator />
</div> </div>
<DropdownMenuSeparator />
<div className="flex items-center justify-evenly p-2"> <div className="flex items-center justify-evenly p-2">
<Button <Button
variant="select" variant="select"
@ -558,6 +555,7 @@ function TimeRangeFilterButton({
onClick={() => { onClick={() => {
setSelectedAfterHour(DEFAULT_TIME_RANGE_AFTER); setSelectedAfterHour(DEFAULT_TIME_RANGE_AFTER);
setSelectedBeforeHour(DEFAULT_TIME_RANGE_BEFORE); setSelectedBeforeHour(DEFAULT_TIME_RANGE_BEFORE);
updateTimeRange(undefined);
}} }}
> >
Reset Reset

View File

@ -1,4 +1,4 @@
import { useState, useRef, useEffect, useCallback } from "react"; import React, { useState, useRef, useEffect, useCallback } from "react";
import { import {
LuX, LuX,
LuFilter, LuFilter,
@ -45,6 +45,8 @@ import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig"; import { FrigateConfig } from "@/types/frigateConfig";
type InputWithTagsProps = { type InputWithTagsProps = {
inputFocused: boolean;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
filters: SearchFilter; filters: SearchFilter;
setFilters: (filter: SearchFilter) => void; setFilters: (filter: SearchFilter) => void;
search: string; search: string;
@ -55,6 +57,8 @@ type InputWithTagsProps = {
}; };
export default function InputWithTags({ export default function InputWithTags({
inputFocused,
setInputFocused,
filters, filters,
setFilters, setFilters,
search, search,
@ -69,7 +73,6 @@ export default function InputWithTags({
const [currentFilterType, setCurrentFilterType] = useState<FilterType | null>( const [currentFilterType, setCurrentFilterType] = useState<FilterType | null>(
null, null,
); );
const [inputFocused, setInputFocused] = useState(false);
const [isSimilaritySearch, setIsSimilaritySearch] = useState(false); const [isSimilaritySearch, setIsSimilaritySearch] = useState(false);
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const commandRef = useRef<HTMLDivElement>(null); const commandRef = useRef<HTMLDivElement>(null);
@ -381,7 +384,7 @@ export default function InputWithTags({
const handleInputFocus = useCallback(() => { const handleInputFocus = useCallback(() => {
setInputFocused(true); setInputFocused(true);
}, []); }, [setInputFocused]);
const handleClearInput = useCallback(() => { const handleClearInput = useCallback(() => {
setInputFocused(false); setInputFocused(false);
@ -392,16 +395,19 @@ export default function InputWithTags({
setFilters({}); setFilters({});
setCurrentFilterType(null); setCurrentFilterType(null);
setIsSimilaritySearch(false); setIsSimilaritySearch(false);
}, [setFilters, resetSuggestions, setSearch]); }, [setFilters, resetSuggestions, setSearch, setInputFocused]);
const handleInputBlur = useCallback((e: React.FocusEvent) => { const handleInputBlur = useCallback(
(e: React.FocusEvent) => {
if ( if (
commandRef.current && commandRef.current &&
!commandRef.current.contains(e.relatedTarget as Node) !commandRef.current.contains(e.relatedTarget as Node)
) { ) {
setInputFocused(false); setInputFocused(false);
} }
}, []); },
[setInputFocused],
);
const handleSuggestionClick = useCallback( const handleSuggestionClick = useCallback(
(suggestion: string) => { (suggestion: string) => {
@ -449,7 +455,7 @@ export default function InputWithTags({
setInputFocused(false); setInputFocused(false);
inputRef?.current?.blur(); inputRef?.current?.blur();
}, },
[setSearch], [setSearch, setInputFocused],
); );
const handleInputKeyDown = useCallback( const handleInputKeyDown = useCallback(

View File

@ -414,17 +414,7 @@ export function DateRangePicker({
</div> </div>
)} )}
</div> </div>
<div className="flex justify-center gap-2 py-2 pr-4"> <div className="mx-auto flex w-64 items-center justify-evenly gap-2 py-2">
<Button
onClick={() => {
setIsOpen(false);
resetValues();
onReset?.();
}}
variant="ghost"
>
Reset
</Button>
<Button <Button
variant="select" variant="select"
onClick={() => { onClick={() => {
@ -439,6 +429,16 @@ export function DateRangePicker({
> >
Apply Apply
</Button> </Button>
<Button
onClick={() => {
setIsOpen(false);
resetValues();
onReset?.();
}}
variant="ghost"
>
Reset
</Button>
</div> </div>
</div> </div>
); );

View File

@ -10,6 +10,7 @@ export type KeyModifiers = {
export default function useKeyboardListener( export default function useKeyboardListener(
keys: string[], keys: string[],
listener: (key: string | null, modifiers: KeyModifiers) => void, listener: (key: string | null, modifiers: KeyModifiers) => void,
preventDefault: boolean = true,
) { ) {
const keyDownListener = useCallback( const keyDownListener = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {
@ -25,13 +26,13 @@ export default function useKeyboardListener(
}; };
if (keys.includes(e.key)) { if (keys.includes(e.key)) {
e.preventDefault(); if (preventDefault) e.preventDefault();
listener(e.key, modifiers); listener(e.key, modifiers);
} else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") { } else if (e.key === "Shift" || e.key === "Control" || e.key === "Meta") {
listener(null, modifiers); listener(null, modifiers);
} }
}, },
[keys, listener], [keys, listener, preventDefault],
); );
const keyUpListener = useCallback( const keyUpListener = useCallback(

View File

@ -209,9 +209,11 @@ export default function SearchView({
// keyboard listener // keyboard listener
const [inputFocused, setInputFocused] = useState(false);
const onKeyboardShortcut = useCallback( const onKeyboardShortcut = useCallback(
(key: string | null, modifiers: KeyModifiers) => { (key: string | null, modifiers: KeyModifiers) => {
if (!modifiers.down || !uniqueResults) { if (!modifiers.down || !uniqueResults || inputFocused) {
return; return;
} }
@ -236,10 +238,14 @@ export default function SearchView({
break; break;
} }
}, },
[uniqueResults], [uniqueResults, inputFocused],
); );
useKeyboardListener(["ArrowLeft", "ArrowRight"], onKeyboardShortcut); useKeyboardListener(
["ArrowLeft", "ArrowRight"],
onKeyboardShortcut,
!inputFocused,
);
// scroll into view // scroll into view
@ -310,6 +316,8 @@ export default function SearchView({
{config?.semantic_search?.enabled && ( {config?.semantic_search?.enabled && (
<div className={cn("z-[41] w-full lg:absolute lg:top-0 lg:w-1/3")}> <div className={cn("z-[41] w-full lg:absolute lg:top-0 lg:w-1/3")}>
<InputWithTags <InputWithTags
inputFocused={inputFocused}
setInputFocused={setInputFocused}
filters={searchFilter ?? {}} filters={searchFilter ?? {}}
setFilters={setSearchFilter} setFilters={setSearchFilter}
search={search} search={search}