From c84e3a4dc744386ed8f43a27caaedbd32aef32e6 Mon Sep 17 00:00:00 2001 From: danieldeng2 <danieldeng223@gmail.com> Date: Wed, 19 Aug 2020 17:46:40 +0100 Subject: [PATCH] Add search dropdown prompt and refactor into new component --- src/components/molecules/SearchBox/index.tsx | 85 ++++++++++++++++ .../SearchBox}/style.module.scss | 27 +++-- .../pages/ModuleResources/index.tsx | 99 ++++++++----------- 3 files changed, 141 insertions(+), 70 deletions(-) create mode 100644 src/components/molecules/SearchBox/index.tsx rename src/components/{pages/ModuleResources => molecules/SearchBox}/style.module.scss (66%) diff --git a/src/components/molecules/SearchBox/index.tsx b/src/components/molecules/SearchBox/index.tsx new file mode 100644 index 000000000..760abec56 --- /dev/null +++ b/src/components/molecules/SearchBox/index.tsx @@ -0,0 +1,85 @@ +import React from "react"; +import styles from "./style.module.scss"; + +import InputGroup from "react-bootstrap/InputGroup"; +import FormControl from "react-bootstrap/FormControl"; +import Button from "react-bootstrap/Button"; +import Dropdown from "react-bootstrap/Dropdown"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; + +export interface SearchBoxProps { + searchText: string; + onSearchTextChange: (searchText: string) => void; +} + +const SearchBox: React.FC<SearchBoxProps> = ({ + searchText, + onSearchTextChange, +}: SearchBoxProps) => { + return ( + <Dropdown alignRight> + <InputGroup> + <FormControl + className={styles.searchBar} + aria-label="Search" + placeholder="Search..." + value={searchText} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => + onSearchTextChange(e.target.value) + } + /> + <InputGroup.Append> + <Dropdown.Toggle as={CustomToggle} id="dropdown-custom-components" /> + + <Dropdown.Menu alignRight> + <Dropdown.Header>Search types: </Dropdown.Header> + <Dropdown.Item + onClick={() => onSearchTextChange(`${searchText} type(pdf) `)} + > + type(pdf) + </Dropdown.Item> + <Dropdown.Item + onClick={() => onSearchTextChange(`${searchText} type(video) `)} + > + type(video) + </Dropdown.Item> + <Dropdown.Item + onClick={() => onSearchTextChange(`${searchText} type(file) `)} + > + type(file) + </Dropdown.Item> + <Dropdown.Header>Search tags: </Dropdown.Header> + <Dropdown.Item + onClick={() => onSearchTextChange(`${searchText} tag(new) `)} + > + tag(new) + </Dropdown.Item> + <Dropdown.Item + onClick={() => onSearchTextChange(`${searchText} tag(my tag) `)} + > + tag(my tag) + </Dropdown.Item> + </Dropdown.Menu> + </InputGroup.Append> + </InputGroup> + </Dropdown> + ); +}; + +const CustomToggle = React.forwardRef(({ onClick }: any, ref: any) => ( + <Button + variant="secondary" + className={styles.searchBarIcon} + ref={ref} + onClick={(e) => { + e.preventDefault(); + onClick(e); + }} + > + <FontAwesomeIcon size="1x" icon={faInfoCircle} /> + </Button> +)); + +export default SearchBox; diff --git a/src/components/pages/ModuleResources/style.module.scss b/src/components/molecules/SearchBox/style.module.scss similarity index 66% rename from src/components/pages/ModuleResources/style.module.scss rename to src/components/molecules/SearchBox/style.module.scss index 574a813d9..5d1a2c477 100644 --- a/src/components/pages/ModuleResources/style.module.scss +++ b/src/components/molecules/SearchBox/style.module.scss @@ -32,26 +32,25 @@ $button-border: transparentize($gray-300, 1); } .searchBarIcon { - border-radius: 0.5rem; + border-radius: 0.5rem; + border-top-right-radius: 0.5rem !important; + border-bottom-right-radius: 0.5rem !important; background-color: $gray-100; border-color: $gray-100; color: $gray-500; - transition: 0.2s background-color; - -webkit-transition: 0.2s background-color; - -moz-transition: 0.2s back-ground-color; + transition: 0.2s background-color !important; + -webkit-transition: 0.2s background-color !important; + -moz-transition: 0.2s back-ground-color !important; } -.searchBarIcon:hover { - background: $gray-200; - color: $gray-700 !important; - border-color: $gray-200; -} .searchBarIcon:global(.active), -.searchBarIcon:active { - background: $black !important; - font-weight: 500 !important; - color: $white !important; - border-color: $black; +.searchBarIcon:active, +.searchBarIcon:hover, +.searchBarIcon:focus{ + background: $gray-200 !important; + color: $gray-700 !important; + border-color: $gray-200 !important; + box-shadow: none !important; } diff --git a/src/components/pages/ModuleResources/index.tsx b/src/components/pages/ModuleResources/index.tsx index 0283b05eb..8a1a7512e 100644 --- a/src/components/pages/ModuleResources/index.tsx +++ b/src/components/pages/ModuleResources/index.tsx @@ -1,18 +1,13 @@ import React from "react"; -import styles from "./style.module.scss"; import { request } from "../../../utils/api"; import { api } from "../../../constants/routes"; import MyBreadcrumbs from "components/atoms/MyBreadcrumbs"; -import InputGroup from "react-bootstrap/InputGroup"; -import FormControl from "react-bootstrap/FormControl"; -import Button from "react-bootstrap/Button"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; import QuickAccessView from "components/molecules/QuickAccessView"; import ResourcesFolderView from "components/molecules/ResourcesFolderView"; import CurrentDirectoryView from "components/molecules/CurrentDirectoryView"; +import SearchBox from "components/molecules/SearchBox"; interface Resource { title: string; @@ -55,7 +50,6 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { let resourceArr: Resource[] = []; data.json().then((json) => { - console.log(`data: ${data}`); for (const key in json) { let resource = json[key]; resourceArr.push({ @@ -85,6 +79,40 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { this.setState({ searchText: searchText }); } + includeInSearchResult(item: Resource, searchText: string) { + let rx = /([a-z]+)\(([^)]+)\)/gi; + let match: RegExpExecArray | null; + + let title = item.title.toLowerCase(); + let tags = item.tags.map((tag) => tag.toLowerCase()); + let type = item.type.toLowerCase(); + + while ((match = rx.exec(searchText)) !== null) { + switch (match[1]) { + case "type": + if (type !== match[2]) { + return false; + } + break; + case "tag": + let matchSafe = match as RegExpExecArray; + if (!tags.some((tag) => tag === matchSafe[2])) { + return false; + } + break; + default: + break; + } + } + let rest = searchText.replace(rx, "").trim(); + for (let i in tags) { + if (tags[i].indexOf(rest) !== -1) { + return true; + } + } + return title.indexOf(rest) !== -1; + } + render() { let scope = this.props.scope || ""; let resources = this.state.resources; @@ -98,41 +126,10 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { if (scope !== "") { filesContent = filesContent.filter(({ folder }) => folder === scope); } - - let searchText = this.state.searchText.toLowerCase(); - if (searchText !== "") { - filesContent = filesContent.filter((item) => { - let rx = /([a-z]+)\(([^)]+)\)/gi; - let match: RegExpExecArray | null; - - let title = item.title.toLowerCase(); - let tags = item.tags.map((tag) => tag.toLowerCase()); - let folder = item.folder.toLowerCase(); - - while ((match = rx.exec(searchText)) !== null) { - switch (match[1]) { - case "folder": - if (folder !== match[2]) { - return false; - } - break; - case "tag": - if (!tags.some((tag) => match !== null && tag === match[2])) { - return false; - } - break; - default: - break; - } - } - let rest = searchText.replace(rx, "").trim(); - for (let i in tags) { - if (tags[i].indexOf(rest) !== -1) { - return true; - } - } - return title.indexOf(rest) !== -1; - }); + if (this.state.searchText !== "") { + filesContent = filesContent.filter((item) => + this.includeInSearchResult(item, this.state.searchText.toLowerCase()) + ); } let folders: { title: string; id: number }[] = Array.from( @@ -145,20 +142,10 @@ class ModuleResources extends React.Component<ResourcesProps, ResourceState> { return ( <> <MyBreadcrumbs /> - <InputGroup> - <FormControl - className={styles.searchBar} - aria-label="Search" - placeholder="Search..." - value={this.state.searchText} - onChange={(e) => this.handleSearchTextChange(e.target.value)} - /> - <InputGroup.Append> - <Button variant="secondary" className={styles.searchBarIcon}> - <FontAwesomeIcon size="1x" icon={faInfoCircle} /> - </Button> - </InputGroup.Append> - </InputGroup> + <SearchBox + searchText={this.state.searchText} + onSearchTextChange={(text) => this.handleSearchTextChange(text)} + /> {this.state.isLoaded ? ( this.state.error ? ( <> Error retrieving data: {this.state.error} </> -- GitLab