Semantic search UI tweaks (#13591)

* Semantic search UI tweaks

* clean up
This commit is contained in:
Josh Hawkins 2024-09-06 14:26:32 -05:00 committed by GitHub
parent 17fa830851
commit d6b16a7747
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 60 additions and 34 deletions

View File

@ -12,6 +12,7 @@ import { isMobile } from "react-device-detect";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { DateRangePicker } from "../ui/calendar-range"; import { DateRangePicker } from "../ui/calendar-range";
import { DateRange } from "react-day-picker"; import { DateRange } from "react-day-picker";
import { useState } from "react";
type CalendarFilterButtonProps = { type CalendarFilterButtonProps = {
reviewSummary?: ReviewSummary; reviewSummary?: ReviewSummary;
@ -91,6 +92,8 @@ export function CalendarRangeFilterButton({
defaultText, defaultText,
updateSelectedRange, updateSelectedRange,
}: CalendarRangeFilterButtonProps) { }: CalendarRangeFilterButtonProps) {
const [open, setOpen] = useState(false);
const selectedDate = useFormattedRange( const selectedDate = useFormattedRange(
range?.from == undefined ? 0 : range.from.getTime() / 1000 + 1, range?.from == undefined ? 0 : range.from.getTime() / 1000 + 1,
range?.to == undefined ? 0 : range.to.getTime() / 1000 - 1, range?.to == undefined ? 0 : range.to.getTime() / 1000 - 1,
@ -119,24 +122,18 @@ export function CalendarRangeFilterButton({
initialDateFrom={range?.from} initialDateFrom={range?.from}
initialDateTo={range?.to} initialDateTo={range?.to}
showCompare={false} showCompare={false}
onUpdate={(range) => updateSelectedRange(range.range)} onUpdate={(range) => {
updateSelectedRange(range.range);
setOpen(false);
}}
onReset={() => updateSelectedRange(undefined)}
/> />
<DropdownMenuSeparator />
<div className="flex items-center justify-center p-2">
<Button
onClick={() => {
updateSelectedRange(undefined);
}}
>
Reset
</Button>
</div>
</> </>
); );
if (isMobile) { if (isMobile) {
return ( return (
<Drawer> <Drawer open={open} onOpenChange={setOpen}>
<DrawerTrigger asChild>{trigger}</DrawerTrigger> <DrawerTrigger asChild>{trigger}</DrawerTrigger>
<DrawerContent>{content}</DrawerContent> <DrawerContent>{content}</DrawerContent>
</Drawer> </Drawer>
@ -144,9 +141,9 @@ export function CalendarRangeFilterButton({
} }
return ( return (
<Popover> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>{trigger}</PopoverTrigger> <PopoverTrigger asChild>{trigger}</PopoverTrigger>
<PopoverContent className="w-[840px]">{content}</PopoverContent> <PopoverContent className="w-auto">{content}</PopoverContent>
</Popover> </Popover>
); );
} }

View File

@ -1,7 +1,4 @@
/* eslint-disable max-lines */ import { useState, useEffect, useRef } from "react";
"use client";
import { type FC, useState, useEffect, useRef } from "react";
import { Button } from "./button"; import { Button } from "./button";
import { Calendar } from "./calendar"; import { Calendar } from "./calendar";
import { Label } from "./label"; import { Label } from "./label";
@ -19,6 +16,7 @@ import { LuCheck } from "react-icons/lu";
export interface DateRangePickerProps { export interface DateRangePickerProps {
/** Click handler for applying the updates from DateRangePicker. */ /** Click handler for applying the updates from DateRangePicker. */
onUpdate?: (values: { range: DateRange; rangeCompare?: DateRange }) => void; onUpdate?: (values: { range: DateRange; rangeCompare?: DateRange }) => void;
onReset?: () => void;
/** Initial value for start date */ /** Initial value for start date */
initialDateFrom?: Date | string; initialDateFrom?: Date | string;
/** Initial value for end date */ /** Initial value for end date */
@ -73,14 +71,15 @@ const PRESETS: Preset[] = [
]; ];
/** The DateRangePicker component allows a user to select a range of dates */ /** The DateRangePicker component allows a user to select a range of dates */
export const DateRangePicker: FC<DateRangePickerProps> = ({ export function DateRangePicker({
initialDateFrom = new Date(new Date().setHours(0, 0, 0, 0)), initialDateFrom = new Date(new Date().setHours(0, 0, 0, 0)),
initialDateTo, initialDateTo,
initialCompareFrom, initialCompareFrom,
initialCompareTo, initialCompareTo,
onUpdate, onUpdate,
onReset,
showCompare = true, showCompare = true,
}): JSX.Element => { }: DateRangePickerProps) {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [range, setRange] = useState<DateRange>({ const [range, setRange] = useState<DateRange>({
@ -283,7 +282,7 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
isSelected: boolean; isSelected: boolean;
}): JSX.Element => ( }): JSX.Element => (
<Button <Button
className={cn(isSelected && "pointer-events-none text-white")} className={cn(isSelected && "pointer-events-none text-primary")}
variant="ghost" variant="ghost"
onClick={() => { onClick={() => {
setPreset(preset); setPreset(preset);
@ -415,17 +414,19 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
</div> </div>
)} )}
</div> </div>
<div className="flex justify-end gap-2 py-2 pr-4"> <div className="flex justify-center gap-2 py-2 pr-4">
<Button <Button
onClick={() => { onClick={() => {
setIsOpen(false); setIsOpen(false);
resetValues(); resetValues();
onReset?.();
}} }}
variant="ghost" variant="ghost"
> >
Cancel Reset
</Button> </Button>
<Button <Button
variant="select"
onClick={() => { onClick={() => {
setIsOpen(false); setIsOpen(false);
if ( if (
@ -436,9 +437,9 @@ export const DateRangePicker: FC<DateRangePickerProps> = ({
} }
}} }}
> >
Update Apply
</Button> </Button>
</div> </div>
</div> </div>
); );
}; }

View File

@ -8,7 +8,13 @@ import { cn } from "@/lib/utils";
import { Preview } from "@/types/preview"; import { Preview } from "@/types/preview";
import { SearchFilter, SearchResult } from "@/types/search"; import { SearchFilter, SearchResult } from "@/types/search";
import { useCallback, useState } from "react"; import { useCallback, useState } from "react";
import { LuSearchCheck, LuSearchX } from "react-icons/lu"; import {
LuExternalLink,
LuSearchCheck,
LuSearchX,
LuXCircle,
} from "react-icons/lu";
import { Link } from "react-router-dom";
type SearchViewProps = { type SearchViewProps = {
search: string; search: string;
@ -63,12 +69,20 @@ export default function SearchView({
/> />
<div className="relative mb-2 flex h-11 items-center justify-between pl-2 pr-2 md:pl-3"> <div className="relative mb-2 flex h-11 items-center justify-between pl-2 pr-2 md:pl-3">
<Input <div className="relative w-full md:w-1/3">
className={"text-md mr-2 w-full bg-muted md:mr-0 md:w-1/3"} <Input
placeholder="Search for a specific detection..." className="text-md w-full bg-muted pr-10"
value={search} placeholder="Search for a specific detected object..."
onChange={(e) => setSearch(e.target.value)} value={search}
/> onChange={(e) => setSearch(e.target.value)}
/>
{search && (
<LuXCircle
className="absolute right-2 top-1/2 h-5 w-5 -translate-y-1/2 cursor-pointer text-muted-foreground hover:text-primary"
onClick={() => setSearch("")}
/>
)}
</div>
<SearchFilterGroup <SearchFilterGroup
filter={searchFilter} filter={searchFilter}
@ -80,14 +94,28 @@ export default function SearchView({
{searchTerm.length == 0 && ( {searchTerm.length == 0 && (
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center"> <div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
<LuSearchCheck className="size-16" /> <LuSearchCheck className="size-16" />
Search For Detections Search
<div className="mt-2 max-w-64 text-sm text-secondary-foreground">
Frigate can find detected objects in your review items.
</div>
<div className="mt-2 flex items-center text-center text-sm text-primary">
<Link
to="https://docs.frigate.video/configuration/semantic_search"
target="_blank"
rel="noopener noreferrer"
className="inline"
>
Read the Documentation{" "}
<LuExternalLink className="ml-2 inline-flex size-3" />
</Link>
</div>
</div> </div>
)} )}
{searchTerm.length > 0 && searchResults?.length == 0 && ( {searchTerm.length > 0 && searchResults?.length == 0 && (
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center"> <div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 flex-col items-center justify-center text-center">
<LuSearchX className="size-16" /> <LuSearchX className="size-16" />
No Detections Found No Detected Objects Found
</div> </div>
)} )}