Skip to content

Commit

Permalink
Merge pull request #86 from HolodexNet/feature/search-ui-fix
Browse files Browse the repository at this point in the history
  • Loading branch information
uetchy committed Jul 26, 2022
2 parents 3406e91 + 2d8025c commit e0053cc
Show file tree
Hide file tree
Showing 10 changed files with 627 additions and 267 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"react-youtube": "^7.14.0",
"setimmediate": "^1.0.5",
"typescript": "4.6.3",
"use-debounce": "^8.0.2",
"workbox-background-sync": "^5.1.3",
"workbox-broadcast-update": "^5.1.3",
"workbox-cacheable-response": "^5.1.3",
Expand Down
5 changes: 3 additions & 2 deletions src/components/nav/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ import { useTranslation } from "react-i18next";
import { FiMenu, FiChevronDown } from "react-icons/fi";
import { Link } from "react-router-dom";
import { useClient } from "../../modules/client";
import { Searchbox } from "./Searchbox";
import { SearchBox } from "./SearchBox";
import { LogoWithText } from "./LogoWithText";

interface MobileProps extends FlexProps {
onOpen: () => void;
}

export function NavBar({ onOpen, ...rest }: MobileProps) {
const { t } = useTranslation();
const { isLoggedIn, logout, user } = useClient();
Expand Down Expand Up @@ -57,7 +58,7 @@ export function NavBar({ onOpen, ...rest }: MobileProps) {
/>
<LogoWithText display={{ base: "none", lg: "flex" }} />

<Searchbox w={{ base: "100%", lg: "40%" }} paddingX={4} />
<SearchBox w={{ base: "100%", lg: "40%" }} paddingX={4} />

<HStack spacing={{ base: "0", lg: "6" }}>
<Flex alignItems={"center"}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,24 @@ import { RiSearch2Line } from "react-icons/ri";
import { useNavigate } from "react-router";
import { useSearchParams } from "react-router-dom";

export function Searchbox(props: BoxProps) {
export function SearchBox(props: BoxProps) {
const { t } = useTranslation();
let [isFocused, setFocused] = useState(false);
let navigate = useNavigate();
let [searchParams] = useSearchParams();
let [currentValue, setValue] = useState("");
const input = useRef<any>();
const input = useRef<HTMLInputElement>(null);

useEffect(() => {
const q = searchParams.get("q");
const prettyValue = q ? JSON.parse(q) : "";
const prettyValue: string = q ? JSON.parse(q) : "";
setValue(prettyValue);
input.current.value = prettyValue;
input.current!.value = prettyValue;
}, [searchParams]);

useHotkeys("ctrl+l, cmd+l, cmd+alt+f", (e) => {
e.preventDefault();
input.current.focus();
input.current!.focus();
});

const submitHandler = (e: FormEvent) => {
Expand Down
116 changes: 116 additions & 0 deletions src/components/search/CheckboxSearchList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {
Checkbox,
CheckboxGroup,
IconButton,
Input,
InputGroup,
InputRightElement,
Tag,
VStack,
} from "@chakra-ui/react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { RiCloseFill } from "react-icons/ri";

interface CheckboxSearchListProps {
dataField: string;
placeholder?: string;
showSearch?: boolean;
aggregations: {
[k: string]: { buckets: Array<{ key: string; doc_count: number }> };
};
setQuery: (query: any) => void;
value: string[] | null;
tagLabel?: string;
}

export const CheckboxSearchList = ({
dataField,
placeholder,
showSearch = false,
aggregations,
setQuery,
value,
tagLabel,
}: CheckboxSearchListProps) => {
const { t } = useTranslation();
const [filterValue, setFilterValue] = useState("");
const [checkboxValues, setCheckboxValues] = useState<Array<string | number>>(
value!,
);

const getTermsQuery = useCallback(
(values: typeof checkboxValues) => {
if (!values?.length) return {};

return { query: { terms: { [dataField]: values } } };
},
[dataField],
);

useEffect(() => {
setQuery({
value: checkboxValues,
query: getTermsQuery(checkboxValues),
});
}, [checkboxValues, getTermsQuery, setQuery]);

// Support resetting from SelectedFilters
useEffect(() => {
if (value === null) setCheckboxValues([]);
}, [value]);

if (!aggregations?.[dataField]?.buckets?.length) {
return null;
}

return (
<>
{tagLabel && (
<Tag colorScheme="brand" size="md" alignSelf="start">
{tagLabel}
</Tag>
)}
{showSearch && (
<InputGroup>
<Input
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
placeholder={placeholder!}
/>
<InputRightElement>
{filterValue && (
<IconButton
color="red.400"
colorScheme="red"
size="sm"
variant="ghost"
aria-label={t("Clear")}
icon={<RiCloseFill />}
type="button"
title={t("Clear")}
onClick={() => setFilterValue("")}
></IconButton>
)}
</InputRightElement>
</InputGroup>
)}
<CheckboxGroup
value={checkboxValues}
onChange={(e) => setCheckboxValues(e)}
>
<VStack maxH="200px" alignItems="stretch" overflowY="scroll" p={2}>
{aggregations?.[dataField]?.buckets
?.filter(({ key }) =>
key.toLowerCase().includes(filterValue.toLowerCase()),
)
.map(({ key }) => (
<Checkbox key={key} value={key}>
{key}
</Checkbox>
))}
</VStack>
</CheckboxGroup>
</>
);
};
84 changes: 84 additions & 0 deletions src/components/search/GeneralSearchInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
IconButton,
Input,
InputGroup,
InputRightElement,
Tag,
} from "@chakra-ui/react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { RiSearch2Line } from "react-icons/ri";
import { useDebounce } from "use-debounce";

interface GeneralInputProps {
debounceValue?: number;
placeholder?: string;
getQuery: (q: string) => object;
setQuery: (query: { value?: string; query?: any; opts?: any }) => void;
value: string | null;
tagLabel?: string;
}

export const GeneralSearchInput = ({
debounceValue = 1000,
placeholder,
getQuery,
setQuery,
value,
tagLabel,
}: GeneralInputProps) => {
const { t } = useTranslation();
const [searchText, setSearchText] = useState<string>(value!);
const [debouncedSearchText, { flush }] = useDebounce(
searchText,
debounceValue,
);

useEffect(() => {
setQuery({
value: debouncedSearchText,
query: getQuery(debouncedSearchText),
});
}, [debouncedSearchText, getQuery, setQuery]);

// Support resetting from SelectedFilters
useEffect(() => {
if (value === null) setSearchText("");
}, [value]);

return (
<>
{tagLabel && (
<Tag colorScheme="brand" size="md" alignSelf="start">
{tagLabel}
</Tag>
)}
<form
onSubmit={(e) => {
e.preventDefault();
flush();
}}
>
<InputGroup>
<Input
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
placeholder={placeholder}
></Input>
<InputRightElement>
<IconButton
color="brand.400"
colorScheme="brand"
size="sm"
variant="ghost"
aria-label={t("Search")}
icon={<RiSearch2Line />}
type="submit"
title={t("Search")}
></IconButton>
</InputRightElement>
</InputGroup>
</form>
</>
);
};
111 changes: 111 additions & 0 deletions src/components/search/RadioButtonSearchList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import {
Radio,
RadioGroup,
Input,
VStack,
Tag,
InputRightElement,
InputGroup,
IconButton,
} from "@chakra-ui/react";
import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { RiCloseFill } from "react-icons/ri";

interface RadioButtonSearchListProps {
dataField: string;
placeholder?: string;
showSearch?: boolean;
aggregations: {
[k: string]: { buckets: Array<{ key: string; doc_count: number }> };
};
setQuery: (query: any) => void;
value: string | null;
tagLabel?: string;
}

export const RadioButtonSearchList = ({
dataField,
placeholder,
showSearch = false,
aggregations,
setQuery,
value,
tagLabel,
}: RadioButtonSearchListProps) => {
const { t } = useTranslation();
const [filterValue, setFilterValue] = useState("");
const [radioValue, setRadioValue] = useState<string>(value!);

const getQuery = useCallback(
(value: string) => {
if (!value) return {};

return { query: { term: { [dataField]: value } } };
},
[dataField],
);

useEffect(() => {
setQuery({
value: radioValue,
query: getQuery(radioValue),
});
}, [radioValue, getQuery, setQuery]);

// Support resetting from SelectedFilters
useEffect(() => {
if (value === null) setRadioValue("");
}, [value]);

if (!aggregations?.[dataField]?.buckets?.length) {
return null;
}

return (
<>
{tagLabel && (
<Tag colorScheme="brand" size="md" alignSelf="start">
{tagLabel}
</Tag>
)}
{showSearch && (
<InputGroup>
<Input
value={filterValue}
onChange={(e) => setFilterValue(e.target.value)}
placeholder={placeholder!}
/>
<InputRightElement>
{filterValue && (
<IconButton
color="red.400"
colorScheme="red"
size="sm"
variant="ghost"
aria-label={t("Clear")}
icon={<RiCloseFill />}
type="button"
title={t("Clear")}
onClick={() => setFilterValue("")}
></IconButton>
)}
</InputRightElement>
</InputGroup>
)}
<RadioGroup value={radioValue} onChange={(value) => setRadioValue(value)}>
<VStack maxH="200px" alignItems="stretch" overflowY="scroll" p={2}>
{aggregations?.[dataField]?.buckets
?.filter(({ key }) =>
key.toLowerCase().includes(filterValue.toLowerCase()),
)
.map(({ key }) => (
<Radio key={key} value={key}>
{key}
</Radio>
))}
</VStack>
</RadioGroup>
</>
);
};
Loading

0 comments on commit e0053cc

Please sign in to comment.