Merge branch 'develop' into fix/article-interaction-buttons
This commit is contained in:
commit
b5443aa033
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"serv": {
|
"serv": {
|
||||||
"goHome": "Home",
|
"goHome": "Home page",
|
||||||
"noSuchPath": "We don't have this page"
|
"noSuchPath": "We don't have such a page"
|
||||||
},
|
},
|
||||||
"sidemenu": {
|
"sidemenu": {
|
||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
@ -19,45 +19,110 @@
|
|||||||
"hellousr": "Hello, {{username}}",
|
"hellousr": "Hello, {{username}}",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"language": "Language",
|
"language": "Language",
|
||||||
"selectLanguage": "Select language",
|
"selectLanguage": "Select a language",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"account": {
|
"account": {
|
||||||
"info": "Personal information",
|
"info": "Personal Information",
|
||||||
"mail": "Mail",
|
"mail": "Mail",
|
||||||
"connect": "Add account",
|
"connect": "Add Account",
|
||||||
"connectedAccounts_one": "Connected account",
|
"connectedAccounts_one": "Linked Account",
|
||||||
"connectedAccounts_other": "Connected accounts",
|
"connectedAccounts_other": "Linked Accounts",
|
||||||
"settings": "Account settings"
|
"settings": "Account Settings"
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
"password": {
|
"password": {
|
||||||
"caption": "Password",
|
"caption": "Password",
|
||||||
"twoFactor": "Two factor authentication (2FA)",
|
"twoFactor": "Two-factor authentication (2FA)",
|
||||||
"description": "Keep your account secure by enabling 2FA via SMS or using a temporary one-time passcode (TOTP) from an authenticator app."
|
"description": "Protect your account by enabling 2FA via SMS or using a temporary one-time password (OTP) from the authentication app."
|
||||||
},
|
},
|
||||||
"activity": {
|
"activity": {
|
||||||
"caption": "Device activity"
|
"caption": "Device activity"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"search": {
|
"search": {
|
||||||
"label": "Search for something.."
|
"label": "We will find something.."
|
||||||
},
|
},
|
||||||
"subscriptions": {
|
"subscriptions": {
|
||||||
"subscribed": "Service have been connected"
|
"subscribed": "The service is attached to the account"
|
||||||
},
|
},
|
||||||
"viewHistory": "View history",
|
"viewHistory": "View history",
|
||||||
"logOutEverywhere": "log out from all devices",
|
"logOutEverywhere": "Log out from all devices",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"logOut": "Log out",
|
"logOut": "Exit",
|
||||||
"failures": {
|
"failures": {
|
||||||
"subscriptions": {
|
"subscriptions": {
|
||||||
"failure": "Failed to connect service",
|
"failure": "Failed to attach the service to your account",
|
||||||
"exists": "Service have already been connected",
|
"exists": "The service was already attached to your account earlier",
|
||||||
"confirmation": "Invalid confirmation information provided"
|
"confirmation": "Invalid password"
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"fork": "Failed to authenticate in service"
|
"fork": "Failed to perform authorization in the service"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"articlePage": {
|
||||||
|
"abstract": "Abstract",
|
||||||
|
"keywords": "Keywords"
|
||||||
|
},
|
||||||
|
"navbar": {
|
||||||
|
"createNew": "Create an article",
|
||||||
|
"about": {
|
||||||
|
"navTitle": "About the project",
|
||||||
|
"aboutProject": "About Scipaper",
|
||||||
|
"contacts": "Contacts",
|
||||||
|
"help": "Help"
|
||||||
|
},
|
||||||
|
"library": {
|
||||||
|
"navTitle": "My library",
|
||||||
|
"publications": "Publications",
|
||||||
|
"favorites": "Favorites",
|
||||||
|
"collections": "Collections",
|
||||||
|
"recentViewed": "History"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"signIn": "Sign In",
|
||||||
|
"signUp": "Sign Up"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"accountSettings": "Account Settings",
|
||||||
|
"about": "About Scipaper",
|
||||||
|
"help": "Help",
|
||||||
|
"contactUs": "Contacts",
|
||||||
|
"allRightsReserved": "All rights reserved",
|
||||||
|
"termsOfUse": "Terms of Use",
|
||||||
|
"privacyPolicy": "Privacy Policy",
|
||||||
|
"coockiesPolicy": "coockies Usage Policy",
|
||||||
|
"supportedBy": "Created"
|
||||||
|
},
|
||||||
|
"mainPage": {
|
||||||
|
"title": "Scientific Library with free access",
|
||||||
|
"search": "Search",
|
||||||
|
"article_one": "Articles",
|
||||||
|
"article_few": "Articles",
|
||||||
|
"article_many": "Articles",
|
||||||
|
"advancedSearch": "Advanced search",
|
||||||
|
"featuredArticles": {
|
||||||
|
"title": "Popular articles",
|
||||||
|
"descriptionPart1": "Choose the one you are interested in",
|
||||||
|
"descriptionPart2": "Scientific category",
|
||||||
|
"categories": {
|
||||||
|
"Medical": "Medical",
|
||||||
|
"TechnicsAndTechlonogies": "Technics and Technology",
|
||||||
|
"Fundamental": "Fundamental",
|
||||||
|
"Humanitarian": "Humanitarian",
|
||||||
|
"Agricultural": "Agricultural",
|
||||||
|
"Social": "Social"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"featuredAuthors": "Popular authors",
|
||||||
|
"more": "More",
|
||||||
|
"showAll": "Show all"
|
||||||
|
},
|
||||||
|
"searchResults": {
|
||||||
|
"title": "Search results",
|
||||||
|
"totalResults":"Total results",
|
||||||
|
"nothingFound": "Nothing found"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -5,6 +5,7 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { StyleType } from "core/_variants";
|
import { StyleType } from "core/_variants";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import {ReactComponent as DeleteIcon} from "./Filters/svg/chest.svg"
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
/* Component props */
|
/* Component props */
|
||||||
@ -15,7 +16,8 @@ type Props = {
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
iconed?: boolean;
|
iconed?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: (element: any) => void;
|
||||||
|
closeOption?: Boolean;
|
||||||
} & Omit<React.MouseEventHandler<HTMLSpanElement>, "">;
|
} & Omit<React.MouseEventHandler<HTMLSpanElement>, "">;
|
||||||
|
|
||||||
/* -------------------------------------------------------------------------- */
|
/* -------------------------------------------------------------------------- */
|
||||||
@ -27,17 +29,18 @@ function Badge({
|
|||||||
children,
|
children,
|
||||||
onClick,
|
onClick,
|
||||||
emphasis = "low",
|
emphasis = "low",
|
||||||
|
closeOption = false,
|
||||||
...props
|
...props
|
||||||
}: Props): JSX.Element {
|
}: Props): JSX.Element {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<span
|
<span
|
||||||
onClick={onClick}
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"border p-2 rounded-sm text-xs text-center",
|
"border-none p-2 rounded-md text-xs text-center",
|
||||||
{
|
{
|
||||||
"cursor-pointer": onClick,
|
"cursor-pointer": onClick,
|
||||||
"border-transparent": emphasis == "low",
|
"border-transparent": emphasis == "low",
|
||||||
"bg-blue-400 text-white": emphasis == "high",
|
"bg-blue-400 text-white rounded-xs": emphasis == "high",
|
||||||
"border-gray-400 background-gray-200": emphasis == "medium",
|
"border-gray-400 background-gray-200": emphasis == "medium",
|
||||||
},
|
},
|
||||||
className
|
className
|
||||||
@ -45,7 +48,16 @@ function Badge({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
{closeOption && (
|
||||||
|
<button
|
||||||
|
onClick={onClick}
|
||||||
|
className=" text-white pr-1 pl-3 py-1"
|
||||||
|
>
|
||||||
|
<div><DeleteIcon className="h-[9px] w-[9px]" /></div>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
export default Badge;
|
export default Badge;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { ReactComponent as Checkmark } from "assets/svg/check.svg";
|
import { ReactComponent as Checkmark } from "../assets/svg/arrow-down.svg";
|
||||||
|
|
||||||
export type Props = {
|
export type Props = {
|
||||||
/**
|
/**
|
||||||
* Control the state of checkbox
|
* Control the state of checkbox
|
||||||
*/
|
*/
|
||||||
isChecked: boolean;
|
isChecked?: boolean;
|
||||||
/**
|
/**
|
||||||
* An Element next to the checkbox
|
* An Element next to the checkbox
|
||||||
*/
|
*/
|
||||||
@ -33,12 +33,8 @@ const Checkbox = ({ children, className, isChecked, ...props }: Props) => {
|
|||||||
checked={isChecked}
|
checked={isChecked}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
<div //
|
<div className="h-2 w-2 absolute top-0 left-0">
|
||||||
className=" w-4 h-3 leading-[0] absolute top-1.5 left-1 opacity-0
|
<Checkmark className="h-6 w-6 fill-white hover:fill-white stroke-white" />
|
||||||
pointer-events-none focus:pointer-events-auto flex items-center justify-center
|
|
||||||
fill-white peer-disabled:fill-gray-500 "
|
|
||||||
>
|
|
||||||
<Checkmark className="fill-inherit" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{children}
|
{children}
|
||||||
|
55
src/components/Disclosure.tsx
Normal file
55
src/components/Disclosure.tsx
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Imports */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import React from "react";
|
||||||
|
import { Disclosure as Disclose } from "@headlessui/react";
|
||||||
|
import { ReactComponent as SelectIcon } from "../assets/svg/arrow-down.svg";
|
||||||
|
import classNames from "classnames";
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component props */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
type Props = {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
caption?: string;
|
||||||
|
};
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* styles */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
const ButtonStyle = `
|
||||||
|
flex w-full
|
||||||
|
justify-between
|
||||||
|
items-center
|
||||||
|
py-2 text-left
|
||||||
|
text-base
|
||||||
|
font-medium
|
||||||
|
text-black-400
|
||||||
|
`;
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component implementation */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
export default function Disclosure({ children, className, caption }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={classNames("top-16 ", className)}>
|
||||||
|
<div className="mx-auto w-full bg-white py-2">
|
||||||
|
<Disclose as="div">
|
||||||
|
{({ open }) => (
|
||||||
|
<>
|
||||||
|
<Disclose.Button className={`${ButtonStyle}`}>
|
||||||
|
<span>{caption}</span>
|
||||||
|
<SelectIcon
|
||||||
|
className={`${
|
||||||
|
open ? "rotate-180 transform" : ""
|
||||||
|
} h-4 w-5 fill-black hover:fill-black stroke-black`}
|
||||||
|
/>
|
||||||
|
</Disclose.Button>
|
||||||
|
<Disclose.Panel className="py-2 text-sm text-gray-500 ">
|
||||||
|
{children}
|
||||||
|
</Disclose.Panel>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Disclose>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
64
src/components/Filters/AppiledFilters.tsx
Normal file
64
src/components/Filters/AppiledFilters.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Imports */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
import React from "react";
|
||||||
|
import Badge from "components/Badge";
|
||||||
|
import { IProduct } from "./IProdutct";
|
||||||
|
import classNames from "classnames";
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component props */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
type Props = {
|
||||||
|
hints?: IProduct[];
|
||||||
|
delFilter: (element: IProduct) => void;
|
||||||
|
clearAll: () => void;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Component implementation */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
export default function AppiledFilters({
|
||||||
|
hints,
|
||||||
|
delFilter,
|
||||||
|
clearAll,
|
||||||
|
className,
|
||||||
|
}: Props) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"w-full py-3 h-36 max-h-36 overflow-hidden ",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row items-center justify-between space-x-3 ">
|
||||||
|
<div className="font-medium">Фильтры</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
onClick={clearAll}
|
||||||
|
className="font-normal text-sm text-gray-400"
|
||||||
|
>
|
||||||
|
Очистить всё
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="list-none mt-2 h-24 max-h-32 overflow-auto">
|
||||||
|
{hints &&
|
||||||
|
hints.length > 0 &&
|
||||||
|
hints.map((item: IProduct) => (
|
||||||
|
<li className="mt-1" key={item.id}>
|
||||||
|
<Badge
|
||||||
|
className="py-1 cursor-default"
|
||||||
|
emphasis="high"
|
||||||
|
closeOption={true}
|
||||||
|
onClick={() => delFilter(item)}
|
||||||
|
>
|
||||||
|
{item.brand}
|
||||||
|
</Badge>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
109
src/components/Filters/Filter.tsx
Normal file
109
src/components/Filters/Filter.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Disclosure from "components/Disclosure";
|
||||||
|
import AppiledFilters from "./AppiledFilters";
|
||||||
|
import SearchFilterBar from "./SearchFilterBar";
|
||||||
|
import axios from "axios";
|
||||||
|
import { useDebounce } from "./functions/debounce";
|
||||||
|
import { IProduct } from "./IProdutct";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Fiter({ className }: Props) {
|
||||||
|
const [checkedId, setCheckedId] = React.useState<Array<number>>([]);
|
||||||
|
const [activeFilter, setActiveFilter] = React.useState<Array<IProduct>>([]);
|
||||||
|
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
/* Test request to demonstrate the basic functionality of the filter */
|
||||||
|
/* -------------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
const [query, setQuery] = React.useState("");
|
||||||
|
const [hints, setHints] = React.useState<Array<IProduct>>([]);
|
||||||
|
|
||||||
|
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setQuery(e.target.value);
|
||||||
|
};
|
||||||
|
const debounced = useDebounce(query);
|
||||||
|
|
||||||
|
async function fetchProducts() {
|
||||||
|
const response = await axios.get(
|
||||||
|
`https://dummyjson.com/products/search?q=${debounced}`
|
||||||
|
);
|
||||||
|
setHints(response.data.products);
|
||||||
|
}
|
||||||
|
React.useEffect(() => {
|
||||||
|
fetchProducts();
|
||||||
|
}, [debounced, query]);
|
||||||
|
|
||||||
|
const isChecked = (item: number) => (checkedId.includes(item) ? true : false);
|
||||||
|
|
||||||
|
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const selectedId = parseInt(e.target.value);
|
||||||
|
|
||||||
|
/* ------------- Check if "checkedId" contains "selectedIds" ------------- */
|
||||||
|
/* -------------- If true, this checkbox is already checked -------------- */
|
||||||
|
|
||||||
|
if (checkedId.includes(selectedId)) {
|
||||||
|
setCheckedId(checkedId.filter((id) => id !== selectedId));
|
||||||
|
setActiveFilter(activeFilter.filter((item) => item.id !== selectedId));
|
||||||
|
} else {
|
||||||
|
const newId = [...checkedId];
|
||||||
|
newId.push(selectedId);
|
||||||
|
setCheckedId(newId);
|
||||||
|
|
||||||
|
/* --------------- Adding checked filters to AppliedFilters component -------------- */
|
||||||
|
|
||||||
|
hints.forEach((hint) => {
|
||||||
|
if (hint.id === selectedId)
|
||||||
|
activeFilter.push({ id: selectedId, brand: hint.brand });
|
||||||
|
setActiveFilter(activeFilter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* ------------- Delete filter from AppliedFilters component ------------- */
|
||||||
|
|
||||||
|
const DelFilter = (e: IProduct) => {
|
||||||
|
setActiveFilter(activeFilter.filter((item) => item.id !== e.id));
|
||||||
|
setCheckedId(checkedId.filter((id) => id !== e.id));
|
||||||
|
};
|
||||||
|
const clearAll = () => {
|
||||||
|
setActiveFilter([]);
|
||||||
|
setCheckedId([]);
|
||||||
|
setQuery("");
|
||||||
|
};
|
||||||
|
//console.log(activeFilter)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="pl-2">
|
||||||
|
<div className="max-w-[268px] mx-auto divide-y divide-solid">
|
||||||
|
<AppiledFilters
|
||||||
|
hints={activeFilter}
|
||||||
|
delFilter={DelFilter}
|
||||||
|
clearAll={clearAll}
|
||||||
|
></AppiledFilters>
|
||||||
|
<Disclosure caption="Авторы">
|
||||||
|
<SearchFilterBar
|
||||||
|
hints={hints}
|
||||||
|
isChecked={isChecked}
|
||||||
|
handleChange={handleChange}
|
||||||
|
onChange={onChange}
|
||||||
|
query={query}
|
||||||
|
placeHolder={"Введите имя автора"}
|
||||||
|
/>
|
||||||
|
</Disclosure>
|
||||||
|
<Disclosure caption="Публикации">
|
||||||
|
<p>контент...</p>
|
||||||
|
</Disclosure>
|
||||||
|
<Disclosure caption="Тип издания">
|
||||||
|
<p>контент...</p>
|
||||||
|
</Disclosure>
|
||||||
|
<Disclosure caption="Тематика публикаций">
|
||||||
|
<p>контент...</p>
|
||||||
|
</Disclosure>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
23
src/components/Filters/IProdutct.ts
Normal file
23
src/components/Filters/IProdutct.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
export interface IProduct {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
price?: number;
|
||||||
|
discountPercentage?: number;
|
||||||
|
rating?: number;
|
||||||
|
stock?: number;
|
||||||
|
brand?: string;
|
||||||
|
category?: string;
|
||||||
|
thumbnail?: string;
|
||||||
|
images?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface serverResponse<T> {
|
||||||
|
products: T[];
|
||||||
|
total: number;
|
||||||
|
skip: number;
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
117
src/components/Filters/SearchFilterBar.tsx
Normal file
117
src/components/Filters/SearchFilterBar.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { useState, useCallback, Fragment } from "react";
|
||||||
|
import { Combobox, Transition } from "@headlessui/react";
|
||||||
|
import { IProduct } from "./IProdutct";
|
||||||
|
import Checkbox from "components/Checkbox";
|
||||||
|
import { ReactComponent as SearchIcon } from "./svg/searchIcon.svg";
|
||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
hints: IProduct[];
|
||||||
|
handleChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
isChecked: (item: number) => boolean;
|
||||||
|
className?: string;
|
||||||
|
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
|
placeHolder: string;
|
||||||
|
query: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SearchFilterBar({
|
||||||
|
hints,
|
||||||
|
isChecked,
|
||||||
|
handleChange,
|
||||||
|
className,
|
||||||
|
onChange,
|
||||||
|
placeHolder,
|
||||||
|
query,
|
||||||
|
}: Props): JSX.Element {
|
||||||
|
const [checkList, setCheckList] = useState(hints);
|
||||||
|
const [showFilters, SetShowFilters] = useState(false);
|
||||||
|
|
||||||
|
const ShowAllFilters = useCallback(() => {
|
||||||
|
SetShowFilters((prev) => !prev);
|
||||||
|
}, [SetShowFilters]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Combobox value={checkList} onChange={setCheckList}>
|
||||||
|
{({ open }) => (
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"flex flex-row w-full items-center border border-solid rounded border-gray-200",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="basis-6 ">
|
||||||
|
<Combobox.Button>
|
||||||
|
<div className="pl-1 pt-1">
|
||||||
|
<SearchIcon />
|
||||||
|
</div>
|
||||||
|
</Combobox.Button>
|
||||||
|
</div>
|
||||||
|
<div className="grow">
|
||||||
|
<Combobox.Input
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={placeHolder}
|
||||||
|
className="w-full text-sm py-1 focus:outline-none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="mt-4">
|
||||||
|
<Transition
|
||||||
|
as={Fragment}
|
||||||
|
leave="transition ease-in duration-100"
|
||||||
|
leaveFrom="opacity-100"
|
||||||
|
leaveTo="opacity-0"
|
||||||
|
>
|
||||||
|
<Combobox.Options static={true}>
|
||||||
|
{open && (
|
||||||
|
<ul
|
||||||
|
className={classNames([
|
||||||
|
"w-[276px] relative h-40 top-0 right-2",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"max-h-60 h-60 overflow-auto": showFilters === true,
|
||||||
|
"max-h-40 h-40 overflow-hidden":
|
||||||
|
showFilters === false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
])}
|
||||||
|
>
|
||||||
|
<li className="">
|
||||||
|
{hints.length >= 5 && query !== "" && (
|
||||||
|
<button
|
||||||
|
onClick={ShowAllFilters}
|
||||||
|
className="text-right text-blue-500 pl-2 text-sm font-medium"
|
||||||
|
>
|
||||||
|
Показать всё({hints.length})
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
{hints.length > 0 ? (
|
||||||
|
hints.map((item) => (
|
||||||
|
<li key={item.id} className="mt-1 pl-2">
|
||||||
|
<Checkbox
|
||||||
|
className="z-10 mt-1 w-full max-h-56"
|
||||||
|
value={item.id}
|
||||||
|
isChecked={item.id ? isChecked(item.id) : false}
|
||||||
|
onChange={handleChange}
|
||||||
|
>
|
||||||
|
<p>{item.brand}</p>
|
||||||
|
</Checkbox>
|
||||||
|
</li>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-blue-300 pl-2">Nothing Found</p>
|
||||||
|
)}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</Combobox.Options>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
11
src/components/Filters/functions/debounce.ts
Normal file
11
src/components/Filters/functions/debounce.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function useDebounce(value: string, delay: number = 300) {
|
||||||
|
const [debounced, setDebouced] = React.useState(value);
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handler = setTimeout(() => setDebouced(value), delay);
|
||||||
|
return () => clearTimeout(handler);
|
||||||
|
}, [value, delay]);
|
||||||
|
|
||||||
|
return debounced;
|
||||||
|
}
|
4
src/components/Filters/svg/chest.svg
Normal file
4
src/components/Filters/svg/chest.svg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<svg width="12" height="13" viewBox="0 0 12 13" fill="white" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.9497 11.4498L1.05026 1.55027" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
<path d="M10.9497 1.55029L1.05025 11.4498" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 304 B |
3
src/components/Filters/svg/searchIcon.svg
Normal file
3
src/components/Filters/svg/searchIcon.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15.1003 14.1184L10.4628 9.48086C11.1824 8.55051 11.5717 7.41301 11.5717 6.21658C11.5717 4.78444 11.0128 3.44158 10.0021 2.42908C8.99135 1.41658 7.64492 0.859436 6.21456 0.859436C4.78421 0.859436 3.43778 1.41836 2.42706 2.42908C1.41456 3.43979 0.857422 4.78444 0.857422 6.21658C0.857422 7.64694 1.41635 8.99336 2.42706 10.0041C3.43778 11.0166 4.78242 11.5737 6.21456 11.5737C7.41099 11.5737 8.54671 11.1844 9.47707 10.4666L14.1146 15.1023C14.1282 15.1159 14.1443 15.1267 14.1621 15.1341C14.1799 15.1414 14.1989 15.1452 14.2181 15.1452C14.2374 15.1452 14.2564 15.1414 14.2742 15.1341C14.292 15.1267 14.3081 15.1159 14.3217 15.1023L15.1003 14.3255C15.1139 14.3119 15.1247 14.2958 15.132 14.278C15.1394 14.2602 15.1432 14.2412 15.1432 14.2219C15.1432 14.2027 15.1394 14.1837 15.132 14.1659C15.1247 14.1481 15.1139 14.132 15.1003 14.1184ZM9.04314 9.04515C8.28599 9.80051 7.28242 10.2166 6.21456 10.2166C5.14671 10.2166 4.14314 9.80051 3.38599 9.04515C2.63064 8.28801 2.21456 7.28444 2.21456 6.21658C2.21456 5.14872 2.63064 4.14336 3.38599 3.38801C4.14314 2.63265 5.14671 2.21658 6.21456 2.21658C7.28242 2.21658 8.28778 2.63086 9.04314 3.38801C9.79849 4.14515 10.2146 5.14872 10.2146 6.21658C10.2146 7.28444 9.79849 8.28979 9.04314 9.04515Z" fill="#8C8C8C"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -98,10 +98,10 @@ const FeaturedArticlesCards = () => {
|
|||||||
const navigationPrevRef = useRef(null);
|
const navigationPrevRef = useRef(null);
|
||||||
const navigationNextRef = useRef(null);
|
const navigationNextRef = useRef(null);
|
||||||
const paginationRef = useRef(null);
|
const paginationRef = useRef(null);
|
||||||
const [t, i18next] = useTranslation()
|
const [t, i18next] = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="slider-wrapper articles">
|
<div className="slider-wrapper articles px-8">
|
||||||
<div className="flex justify-end gap-2 my-2">
|
<div className="flex justify-end gap-2 my-2">
|
||||||
<div
|
<div
|
||||||
className="prev inline-flex justify-center items-center
|
className="prev inline-flex justify-center items-center
|
||||||
@ -169,7 +169,10 @@ const FeaturedArticlesCards = () => {
|
|||||||
|
|
||||||
<Card.CardAction href={Articale.Link}>
|
<Card.CardAction href={Articale.Link}>
|
||||||
<Link to="*">
|
<Link to="*">
|
||||||
<Typography className="text-blue-500 font-bold">
|
<Typography
|
||||||
|
className="text-blue-500 font-bold"
|
||||||
|
fontWeightVariant="bold"
|
||||||
|
>
|
||||||
{t("mainPage.more")}
|
{t("mainPage.more")}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -75,10 +75,12 @@ export default function FeaturedAuthorsCards(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* The Title of Featured Authors section */}
|
{/* The Title of Featured Authors section */}
|
||||||
<Heading className="text-center my-8 text-3xl font-semibold">{t("mainPage.featuredAuthors")}</Heading>
|
<Heading className="text-center my-8 text-3xl font-semibold">
|
||||||
|
{t("mainPage.featuredAuthors")}
|
||||||
|
</Heading>
|
||||||
|
|
||||||
{/* Featured Authors section */}
|
{/* Featured Authors section */}
|
||||||
<div className="slider-wrapper Authors">
|
<div className="slider-wrapper Authors px-8">
|
||||||
<Swiper
|
<Swiper
|
||||||
slidesPerView={1.25}
|
slidesPerView={1.25}
|
||||||
slidesPerGroup={1}
|
slidesPerGroup={1}
|
||||||
@ -133,7 +135,7 @@ export default function FeaturedAuthorsCards(): JSX.Element {
|
|||||||
|
|
||||||
<Card.CardAction href={card.Link}>
|
<Card.CardAction href={card.Link}>
|
||||||
<Link className="text-blue-500 font-bold" to="*">
|
<Link className="text-blue-500 font-bold" to="*">
|
||||||
{t('mainPage.more')}
|
{t("mainPage.more")}
|
||||||
</Link>
|
</Link>
|
||||||
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
||||||
</Card.CardAction>
|
</Card.CardAction>
|
||||||
|
@ -33,7 +33,7 @@ const AnArticle = () => {
|
|||||||
<AskeletonArticle />
|
<AskeletonArticle />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ArticlePart.Article.Breadcumbs>
|
<ArticlePart.Article.Breadcumbs className="py-4">
|
||||||
{article?.topic}
|
{article?.topic}
|
||||||
</ArticlePart.Article.Breadcumbs>
|
</ArticlePart.Article.Breadcumbs>
|
||||||
<div className="flex flex-col gap-4 pb-4">
|
<div className="flex flex-col gap-4 pb-4">
|
||||||
@ -52,25 +52,18 @@ const AnArticle = () => {
|
|||||||
/>
|
/>
|
||||||
{article?.tags && (
|
{article?.tags && (
|
||||||
<div className="keywords my-10 flex flex-col gap-2">
|
<div className="keywords my-10 flex flex-col gap-2">
|
||||||
<Typography
|
<Typography className="text-2xl" fontWeightVariant="semibold">
|
||||||
className="text-2xl"
|
{t("articlePage.keywords")}
|
||||||
fontWeightVariant="semibold"
|
|
||||||
>
|
|
||||||
{t('articlePage.keywords')}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
|
|
||||||
<ArticlePart.Article.Keywords className="transition ease-in-out delay-50">
|
<ArticlePart.Article.Keywords className="transition ease-in-out delay-50">
|
||||||
{article?.tags}
|
{article?.tags}
|
||||||
</ArticlePart.Article.Keywords>
|
</ArticlePart.Article.Keywords>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="abstract my-10 flex flex-col gap-2">
|
<div className="abstract my-10 flex flex-col gap-2">
|
||||||
<Typography
|
<Typography className="text-2xl" fontWeightVariant="semibold">
|
||||||
className="text-2xl"
|
{t("articlePage.abstract")}
|
||||||
fontWeightVariant="semibold"
|
|
||||||
>
|
|
||||||
{t('articlePage.abstract')}
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<ArticlePart.Article.Description>
|
<ArticlePart.Article.Description>
|
||||||
{article?.summary !== undefined ? (
|
{article?.summary !== undefined ? (
|
||||||
|
@ -27,7 +27,7 @@ const AnArticleBody = () => {
|
|||||||
<Container variant="straight">
|
<Container variant="straight">
|
||||||
{shouldShowLoading ? (
|
{shouldShowLoading ? (
|
||||||
<>
|
<>
|
||||||
<Skeleton count={1} />
|
<Skeleton count={1} className="my-4" />
|
||||||
<div className="gap-4 py-12 px-20">
|
<div className="gap-4 py-12 px-20">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
count={1}
|
count={1}
|
||||||
@ -39,7 +39,7 @@ const AnArticleBody = () => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<ArticlePart.Article.Breadcumbs>
|
<ArticlePart.Article.Breadcumbs className="py-4">
|
||||||
{article?.topic}
|
{article?.topic}
|
||||||
</ArticlePart.Article.Breadcumbs>
|
</ArticlePart.Article.Breadcumbs>
|
||||||
<div className="gap-4 py-12 px-20">
|
<div className="gap-4 py-12 px-20">
|
||||||
|
@ -3,7 +3,7 @@ import "react-loading-skeleton/dist/skeleton.css";
|
|||||||
const AskeletonArticle = () => {
|
const AskeletonArticle = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Skeleton count={1} />
|
<Skeleton count={1} className="my-4" />
|
||||||
<div className="flex flex-col gap-4 pb-4">
|
<div className="flex flex-col gap-4 pb-4">
|
||||||
<Skeleton count={1} containerClassName="title w-3/4 text-2xl" />
|
<Skeleton count={1} containerClassName="title w-3/4 text-2xl" />
|
||||||
<Skeleton count={1} containerClassName="authors w-1/4" />
|
<Skeleton count={1} containerClassName="authors w-1/4" />
|
||||||
|
@ -51,6 +51,7 @@ export default function Link({
|
|||||||
: style
|
: style
|
||||||
}
|
}
|
||||||
aria-disabled={disabled}
|
aria-disabled={disabled}
|
||||||
|
className="flex items-center"
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -42,7 +42,8 @@ root.render(
|
|||||||
<Route path="/account">
|
<Route path="/account">
|
||||||
<Route path="settings" element={<AccountSettings />} />
|
<Route path="settings" element={<AccountSettings />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/search-results" element={<SearchResultsPage />}></Route>
|
<Route path="/search-results" element={<SearchResultsPage />} />
|
||||||
|
<Route path="/*" element={<NotFound />}></Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
|
@ -6,6 +6,7 @@ import { SearchResultSection } from "components/SearchResultsSection";
|
|||||||
import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation";
|
import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation";
|
||||||
import { useSearchViewModel } from "searchResults/controller/searchResultsViewModel";
|
import { useSearchViewModel } from "searchResults/controller/searchResultsViewModel";
|
||||||
import { Loader } from "components/Loader/Loader";
|
import { Loader } from "components/Loader/Loader";
|
||||||
|
import Fiter from "components/Filters/Filter";
|
||||||
|
|
||||||
export const SearchResultsPage = () => {
|
export const SearchResultsPage = () => {
|
||||||
return (
|
return (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user