mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-14 23:25:25 +03:00
use command component
This commit is contained in:
parent
e106e44a9c
commit
e40a26e15c
379
web/package-lock.json
generated
379
web/package-lock.json
generated
@ -34,6 +34,7 @@
|
||||
"axios": "^1.7.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-react": "^8.2.0",
|
||||
@ -3722,6 +3723,384 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz",
|
||||
"integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "1.0.5",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/primitive": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
|
||||
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
|
||||
"integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-context": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
|
||||
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-dialog": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz",
|
||||
"integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.5",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.4",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-portal": "1.0.4",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz",
|
||||
"integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1",
|
||||
"@radix-ui/react-use-escape-keydown": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz",
|
||||
"integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz",
|
||||
"integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-id": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz",
|
||||
"integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-portal": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz",
|
||||
"integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-primitive": "1.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
|
||||
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
|
||||
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-slot": "1.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
|
||||
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-compose-refs": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
|
||||
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz",
|
||||
"integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
|
||||
"integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
|
||||
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cmdk/node_modules/react-remove-scroll": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz",
|
||||
"integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-remove-scroll-bar": "^2.3.3",
|
||||
"react-style-singleton": "^2.2.1",
|
||||
"tslib": "^2.1.0",
|
||||
"use-callback-ref": "^1.3.0",
|
||||
"use-sidecar": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
"axios": "^1.7.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"date-fns": "^3.6.0",
|
||||
"embla-carousel-react": "^8.2.0",
|
||||
|
||||
@ -1,10 +1,16 @@
|
||||
import { useState, useRef, useEffect, useMemo, useCallback } from "react";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { LuX, LuFilter, LuImage } from "react-icons/lu";
|
||||
import { FilterType, SearchFilter, SearchSource } from "@/types/search";
|
||||
import { DropdownMenuSeparator } from "../ui/dropdown-menu";
|
||||
import useSuggestions from "@/hooks/use-suggestions";
|
||||
import {
|
||||
Command,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
} from "@/components/ui/command";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const convertMMDDYYToTimestamp = (dateString: string): number => {
|
||||
const match = dateString.match(/^(\d{2})(\d{2})(\d{2})$/);
|
||||
@ -33,16 +39,13 @@ export default function InputWithTags({
|
||||
allSuggestions,
|
||||
}: InputWithTagsProps) {
|
||||
const [inputValue, setInputValue] = useState(search || "");
|
||||
const [showSuggestions, setShowSuggestions] = useState(false);
|
||||
const [showFilters, setShowFilters] = useState(false);
|
||||
const [currentFilterType, setCurrentFilterType] = useState<FilterType | null>(
|
||||
null,
|
||||
);
|
||||
const [inputFocused, setInputFocused] = useState(false);
|
||||
const [isSimilaritySearch, setIsSimilaritySearch] = useState(false);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const suggestionRef = useRef<HTMLDivElement>(null);
|
||||
const filterRef = useRef<HTMLDivElement>(null);
|
||||
const commandRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// TODO: search history from browser storage
|
||||
|
||||
@ -53,12 +56,11 @@ export default function InputWithTags({
|
||||
|
||||
// suggestions
|
||||
|
||||
const {
|
||||
suggestions,
|
||||
selectedSuggestionIndex,
|
||||
setSelectedSuggestionIndex,
|
||||
updateSuggestions,
|
||||
} = useSuggestions(filters, allSuggestions, searchHistory);
|
||||
const { suggestions, updateSuggestions } = useSuggestions(
|
||||
filters,
|
||||
allSuggestions,
|
||||
searchHistory,
|
||||
);
|
||||
|
||||
const resetSuggestions = useCallback(
|
||||
(value: string) => {
|
||||
@ -68,6 +70,21 @@ export default function InputWithTags({
|
||||
[updateSuggestions],
|
||||
);
|
||||
|
||||
const filterSuggestions = useCallback(
|
||||
(current_suggestions: string[]) => {
|
||||
if (!inputValue || currentFilterType) return suggestions;
|
||||
const words = inputValue.split(/[\s,]+/);
|
||||
const lastNonEmptyWordIndex = words
|
||||
.map((word) => word.trim())
|
||||
.lastIndexOf(words.filter((word) => word.trim() !== "").pop() || "");
|
||||
const currentWord = words[lastNonEmptyWordIndex];
|
||||
return current_suggestions.filter((suggestion) =>
|
||||
suggestion.toLowerCase().includes(currentWord.toLowerCase()),
|
||||
);
|
||||
},
|
||||
[inputValue, suggestions, currentFilterType],
|
||||
);
|
||||
|
||||
const removeFilter = useCallback(
|
||||
(filterType: FilterType, filterValue: string | number) => {
|
||||
const newFilters = { ...filters };
|
||||
@ -137,7 +154,6 @@ export default function InputWithTags({
|
||||
setFilters(newFilters);
|
||||
setInputValue((prev) => prev.replace(`${type}:${value}`, "").trim());
|
||||
setCurrentFilterType(null);
|
||||
setShowSuggestions(false);
|
||||
}
|
||||
},
|
||||
[filters, setFilters, allSuggestions],
|
||||
@ -172,8 +188,11 @@ export default function InputWithTags({
|
||||
);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value;
|
||||
(value: string) => {
|
||||
if (value === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue(value);
|
||||
|
||||
const words = value.split(/[\s,]+/);
|
||||
@ -219,55 +238,32 @@ export default function InputWithTags({
|
||||
} else {
|
||||
resetSuggestions(value);
|
||||
}
|
||||
|
||||
// Reset suggestion state
|
||||
setSelectedSuggestionIndex(-1);
|
||||
setShowSuggestions(true);
|
||||
},
|
||||
[
|
||||
updateSuggestions,
|
||||
resetSuggestions,
|
||||
allSuggestions,
|
||||
handleFilterCreation,
|
||||
setSelectedSuggestionIndex,
|
||||
],
|
||||
[updateSuggestions, resetSuggestions, allSuggestions, handleFilterCreation],
|
||||
);
|
||||
|
||||
const handleInputFocus = useCallback(() => {
|
||||
setShowSuggestions(true);
|
||||
setShowFilters(true);
|
||||
updateSuggestions(inputValue, currentFilterType);
|
||||
setSelectedSuggestionIndex(-1);
|
||||
}, [
|
||||
inputValue,
|
||||
currentFilterType,
|
||||
updateSuggestions,
|
||||
setSelectedSuggestionIndex,
|
||||
]);
|
||||
setInputFocused(true);
|
||||
}, []);
|
||||
|
||||
const handleClearInput = useCallback(() => {
|
||||
setInputValue("");
|
||||
setInputFocused(false);
|
||||
// setInputValue("");
|
||||
resetSuggestions("");
|
||||
setSearch("");
|
||||
inputRef?.current?.blur();
|
||||
setFilters({});
|
||||
setCurrentFilterType(null);
|
||||
updateSuggestions("", null);
|
||||
setShowFilters(false);
|
||||
setShowSuggestions(false);
|
||||
setIsSimilaritySearch(false);
|
||||
setSearch("");
|
||||
}, [setFilters, updateSuggestions, setSearch]);
|
||||
}, [setFilters, resetSuggestions, setSearch]);
|
||||
|
||||
const handleInputBlur = useCallback(() => {
|
||||
setTimeout(() => {
|
||||
if (!containerRef.current?.contains(document.activeElement)) {
|
||||
setShowSuggestions(false);
|
||||
setShowFilters(false);
|
||||
setSelectedSuggestionIndex(-1);
|
||||
}
|
||||
}, 0);
|
||||
}, [setSelectedSuggestionIndex]);
|
||||
|
||||
const toggleFilters = useCallback(() => {
|
||||
setShowFilters((prev) => !prev);
|
||||
const handleInputBlur = useCallback((e: React.FocusEvent) => {
|
||||
if (
|
||||
commandRef.current &&
|
||||
!commandRef.current.contains(e.relatedTarget as Node)
|
||||
) {
|
||||
setInputFocused(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleSuggestionClick = useCallback(
|
||||
@ -292,72 +288,36 @@ export default function InputWithTags({
|
||||
[createFilter, currentFilterType, allSuggestions],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
const handleSearch = useCallback(
|
||||
(value: string) => {
|
||||
setSearch(value);
|
||||
setInputFocused(false);
|
||||
inputRef?.current?.blur();
|
||||
},
|
||||
[setSearch],
|
||||
);
|
||||
|
||||
const handleInputKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "ArrowDown") {
|
||||
if (
|
||||
e.key === "Enter" &&
|
||||
inputValue.trim() !== "" &&
|
||||
filterSuggestions(suggestions).length == 0
|
||||
) {
|
||||
e.preventDefault();
|
||||
setSelectedSuggestionIndex((prev) => (prev + 1) % suggestions.length);
|
||||
} else if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
setSelectedSuggestionIndex(
|
||||
(prev) => (prev - 1 + suggestions.length) % suggestions.length,
|
||||
);
|
||||
} else if (e.key === "Enter" && selectedSuggestionIndex !== -1) {
|
||||
e.preventDefault();
|
||||
handleSuggestionClick(suggestions[selectedSuggestionIndex]);
|
||||
} else if (e.key === "Enter" && currentFilterType) {
|
||||
e.preventDefault();
|
||||
const currentWord = inputValue.split(/[\s,]+/).pop() || "";
|
||||
handleFilterCreation(currentFilterType, currentWord);
|
||||
} else if (e.key === "Enter" && !currentFilterType) {
|
||||
e.preventDefault();
|
||||
setSearch(inputValue);
|
||||
inputRef.current?.blur();
|
||||
handleInputBlur();
|
||||
|
||||
handleSearch(inputValue);
|
||||
}
|
||||
},
|
||||
[
|
||||
suggestions,
|
||||
selectedSuggestionIndex,
|
||||
handleSuggestionClick,
|
||||
currentFilterType,
|
||||
inputValue,
|
||||
handleFilterCreation,
|
||||
setSelectedSuggestionIndex,
|
||||
setSearch,
|
||||
handleInputBlur,
|
||||
],
|
||||
[inputValue, handleSearch, filterSuggestions, suggestions],
|
||||
);
|
||||
|
||||
// effects
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (
|
||||
suggestionRef.current &&
|
||||
!suggestionRef.current.contains(event.target as Node) &&
|
||||
containerRef.current &&
|
||||
!containerRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setShowSuggestions(false);
|
||||
setShowFilters(false);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener("mousedown", handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateSuggestions(inputValue, currentFilterType);
|
||||
}, [currentFilterType, inputValue, updateSuggestions]);
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(search || "");
|
||||
}, [search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (search?.startsWith("similarity:")) {
|
||||
setIsSimilaritySearch(true);
|
||||
@ -369,32 +329,26 @@ export default function InputWithTags({
|
||||
}, [search]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<div className="relative my-2">
|
||||
<Input
|
||||
<Command shouldFilter={false} ref={commandRef} className="rounded-md">
|
||||
<div className="relative">
|
||||
<CommandInput
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
onValueChange={handleInputChange}
|
||||
onFocus={handleInputFocus}
|
||||
onBlur={handleInputBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="pr-20"
|
||||
onKeyDown={handleInputKeyDown}
|
||||
className="text-md h-10 pr-20"
|
||||
placeholder="Search..."
|
||||
aria-label="Search input"
|
||||
aria-autocomplete="list"
|
||||
aria-controls="suggestions-list"
|
||||
aria-expanded={showSuggestions}
|
||||
/>
|
||||
<div className="absolute right-2 top-1/2 -translate-y-1/2 transform space-x-1">
|
||||
{(Object.keys(filters).length > 0 || isSimilaritySearch) && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={toggleFilters}
|
||||
aria-label="Toggle filters"
|
||||
className={
|
||||
Object.keys(filters).length > 0
|
||||
Object.keys(filters).length > 0 || isSimilaritySearch
|
||||
? "text-selected"
|
||||
: "text-secondary-foreground"
|
||||
}
|
||||
@ -406,7 +360,7 @@ export default function InputWithTags({
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
{(inputValue || Object.keys(filters).length > 0) && (
|
||||
{(search || Object.keys(filters).length > 0) && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
@ -418,130 +372,101 @@ export default function InputWithTags({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{((showFilters &&
|
||||
(Object.keys(filters).length > 0 || isSimilaritySearch)) ||
|
||||
showSuggestions) && (
|
||||
<div className="scrollbar-container absolute left-0 top-12 z-[100] max-h-[200px] w-full overflow-y-auto rounded-md border border-input bg-background p-2 text-primary shadow-md">
|
||||
{showFilters &&
|
||||
(Object.keys(filters).length > 0 || isSimilaritySearch) && (
|
||||
<div ref={filterRef} className="my-2 flex flex-wrap gap-2">
|
||||
{isSimilaritySearch && (
|
||||
<span className="inline-flex items-center whitespace-nowrap rounded-full bg-blue-100 px-2 py-0.5 text-sm text-blue-800">
|
||||
Similarity Search
|
||||
<button
|
||||
onClick={handleClearInput}
|
||||
className="ml-1 focus:outline-none"
|
||||
aria-label="Clear similarity search"
|
||||
>
|
||||
<LuX className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
{Object.entries(filters).map(([filterType, filterValues]) =>
|
||||
Array.isArray(filterValues) ? (
|
||||
filterValues.map((value, index) => (
|
||||
<span
|
||||
key={`${filterType}-${index}`}
|
||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm text-green-800"
|
||||
>
|
||||
{filterType}:{value}
|
||||
<button
|
||||
onClick={() =>
|
||||
removeFilter(filterType as FilterType, value)
|
||||
}
|
||||
className="ml-1 focus:outline-none"
|
||||
aria-label={`Remove ${filterType}:${value} filter`}
|
||||
>
|
||||
<LuX className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
|
||||
<CommandList
|
||||
className={cn(
|
||||
"scrollbar-container border-t",
|
||||
inputFocused ? "visible" : "hidden",
|
||||
)}
|
||||
>
|
||||
{(Object.keys(filters).length > 0 || isSimilaritySearch) && (
|
||||
<CommandGroup heading="Active Filters">
|
||||
<div className="my-2 flex flex-wrap gap-2">
|
||||
{isSimilaritySearch && (
|
||||
<span className="inline-flex items-center whitespace-nowrap rounded-full bg-blue-100 px-2 py-0.5 text-sm text-blue-800">
|
||||
Similarity Search
|
||||
<button
|
||||
onClick={handleClearInput}
|
||||
className="ml-1 focus:outline-none"
|
||||
aria-label="Clear similarity search"
|
||||
>
|
||||
<LuX className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
{Object.entries(filters).map(([filterType, filterValues]) =>
|
||||
Array.isArray(filterValues) ? (
|
||||
filterValues.map((value, index) => (
|
||||
<span
|
||||
key={filterType}
|
||||
key={`${filterType}-${index}`}
|
||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm text-green-800"
|
||||
>
|
||||
{filterType}:
|
||||
{filterType === "before" || filterType === "after"
|
||||
? new Date(filterValues as number).toLocaleDateString()
|
||||
: filterValues}
|
||||
{filterType}:{value}
|
||||
<button
|
||||
onClick={() =>
|
||||
removeFilter(
|
||||
filterType as FilterType,
|
||||
filterValues as string | number,
|
||||
)
|
||||
removeFilter(filterType as FilterType, value)
|
||||
}
|
||||
className="ml-1 focus:outline-none"
|
||||
aria-label={`Remove ${filterType}:${filterValues} filter`}
|
||||
aria-label={`Remove ${filterType}:${value} filter`}
|
||||
>
|
||||
<LuX className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{showSuggestions && (
|
||||
<div
|
||||
ref={suggestionRef}
|
||||
className="mt-1"
|
||||
role="listbox"
|
||||
id="suggestions-list"
|
||||
>
|
||||
{!currentFilterType && searchHistory.length > 0 && (
|
||||
<>
|
||||
<h3 className="px-2 py-1 text-xs font-semibold text-secondary-foreground">
|
||||
Previous Searches
|
||||
</h3>
|
||||
{searchHistory.map((suggestion, index) => (
|
||||
<button
|
||||
key={index}
|
||||
className={`w-full rounded px-2 py-1 text-left text-sm text-primary ${
|
||||
index === selectedSuggestionIndex
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "hover:bg-accent hover:text-accent-foreground"
|
||||
}`}
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
onMouseEnter={() => setSelectedSuggestionIndex(index)}
|
||||
role="option"
|
||||
aria-selected={index === selectedSuggestionIndex}
|
||||
>
|
||||
{suggestion}
|
||||
</button>
|
||||
))}
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
<h3 className="px-2 py-1 text-xs font-semibold text-secondary-foreground">
|
||||
{currentFilterType ? "Filter Values" : "Filters"}
|
||||
</h3>
|
||||
{suggestions
|
||||
.filter((item) => !searchHistory.includes(item))
|
||||
.map((suggestion, index) => (
|
||||
<button
|
||||
key={index + searchHistory.length}
|
||||
className={`w-full rounded px-2 py-1 text-left text-sm text-primary ${
|
||||
index + searchHistory.length === selectedSuggestionIndex
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "hover:bg-accent hover:text-accent-foreground"
|
||||
}`}
|
||||
onClick={() => handleSuggestionClick(suggestion)}
|
||||
onMouseEnter={() =>
|
||||
setSelectedSuggestionIndex(index + searchHistory.length)
|
||||
}
|
||||
role="option"
|
||||
aria-selected={
|
||||
index + searchHistory.length === selectedSuggestionIndex
|
||||
}
|
||||
))
|
||||
) : (
|
||||
<span
|
||||
key={filterType}
|
||||
className="inline-flex items-center whitespace-nowrap rounded-full bg-green-100 px-2 py-0.5 text-sm text-green-800"
|
||||
>
|
||||
{suggestion}
|
||||
</button>
|
||||
))}
|
||||
{filterType}:
|
||||
{filterType === "before" || filterType === "after"
|
||||
? new Date(filterValues as number).toLocaleDateString()
|
||||
: filterValues}
|
||||
<button
|
||||
onClick={() =>
|
||||
removeFilter(
|
||||
filterType as FilterType,
|
||||
filterValues as string | number,
|
||||
)
|
||||
}
|
||||
className="ml-1 focus:outline-none"
|
||||
aria-label={`Remove ${filterType}:${filterValues} filter`}
|
||||
>
|
||||
<LuX className="h-3 w-3" />
|
||||
</button>
|
||||
</span>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CommandGroup>
|
||||
)}
|
||||
{!currentFilterType && !inputValue && searchHistory.length > 0 && (
|
||||
<CommandGroup heading="Previous Searches">
|
||||
{searchHistory.map((suggestion, index) => (
|
||||
<CommandItem
|
||||
key={index}
|
||||
className="cursor-pointer"
|
||||
onSelect={() => handleSuggestionClick(suggestion)}
|
||||
>
|
||||
{suggestion}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
)}
|
||||
<CommandGroup heading={currentFilterType ? "Filter Values" : "Filters"}>
|
||||
{filterSuggestions(suggestions)
|
||||
.filter((item) => !searchHistory.includes(item))
|
||||
.map((suggestion, index) => (
|
||||
<CommandItem
|
||||
key={index + searchHistory.length}
|
||||
className="cursor-pointer"
|
||||
onSelect={() => handleSuggestionClick(suggestion)}
|
||||
>
|
||||
{suggestion}
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
);
|
||||
}
|
||||
|
||||
153
web/src/components/ui/command.tsx
Normal file
153
web/src/components/ui/command.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import * as React from "react";
|
||||
import { type DialogProps } from "@radix-ui/react-dialog";
|
||||
import { Command as CommandPrimitive } from "cmdk";
|
||||
import { Search } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog";
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Command.displayName = CommandPrimitive.displayName;
|
||||
|
||||
interface CommandDialogProps extends DialogProps {}
|
||||
|
||||
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||
return (
|
||||
<Dialog {...props}>
|
||||
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
||||
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||
{children}
|
||||
</Command>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const CommandInput = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="flex items-center px-3" cmdk-input-wrapper="">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<CommandPrimitive.Input
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
|
||||
CommandInput.displayName = CommandPrimitive.Input.displayName;
|
||||
|
||||
const CommandList = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.List
|
||||
ref={ref}
|
||||
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandList.displayName = CommandPrimitive.List.displayName;
|
||||
|
||||
const CommandEmpty = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||
>((props, ref) => (
|
||||
<CommandPrimitive.Empty
|
||||
ref={ref}
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
|
||||
|
||||
const CommandGroup = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Group
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandGroup.displayName = CommandPrimitive.Group.displayName;
|
||||
|
||||
const CommandSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
|
||||
|
||||
const CommandItem = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
CommandItem.displayName = CommandPrimitive.Item.displayName;
|
||||
|
||||
const CommandShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
CommandShortcut.displayName = "CommandShortcut";
|
||||
|
||||
export {
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
};
|
||||
@ -15,7 +15,6 @@ export default function useSuggestions(
|
||||
searchHistory: string[],
|
||||
) {
|
||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
|
||||
|
||||
const updateSuggestions = useCallback(
|
||||
(value: string, currentFilterType: FilterType | null) => {
|
||||
@ -59,8 +58,6 @@ export default function useSuggestions(
|
||||
|
||||
return {
|
||||
suggestions,
|
||||
selectedSuggestionIndex,
|
||||
setSelectedSuggestionIndex,
|
||||
updateSuggestions,
|
||||
};
|
||||
}
|
||||
|
||||
@ -260,7 +260,7 @@ export default function SearchView({
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col items-start space-y-2 pl-2 pr-2 md:mb-2 md:pl-3 lg:h-10 lg:flex-row lg:items-center lg:space-y-0",
|
||||
"flex flex-col items-start space-y-2 pl-2 pr-2 md:mb-2 md:pl-3 lg:relative lg:h-10 lg:flex-row lg:items-center lg:space-y-0",
|
||||
config?.semantic_search?.enabled
|
||||
? "justify-between"
|
||||
: "justify-center",
|
||||
@ -270,8 +270,8 @@ export default function SearchView({
|
||||
{config?.semantic_search?.enabled && (
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full",
|
||||
hasExistingSearch ? "lg:mr-3 lg:w-1/3" : "lg:ml-[25%] lg:w-1/2",
|
||||
"z-[41] w-full lg:absolute lg:top-0 lg:w-1/3",
|
||||
// hasExistingSearch ? "lg:mr-3 lg:w-1/3" : "lg:ml-[25%] lg:w-1/2",
|
||||
)}
|
||||
>
|
||||
<InputWithTags
|
||||
|
||||
Loading…
Reference in New Issue
Block a user