search page and many component updates

This commit is contained in:
BFlat committed 2023-10-12 22:09:31 +07:00
1 parent ff6f7821a2
commit cf3f79dc5f
31 files changed
+569 -71

No files matched your search

@@ -3,13 +3,14 @@
.button {
border-radius: 0.5rem;
outline: none;
font-size: 15px;
font-weight: 500;
font-size: 15px !important;
font-weight: 500 !important;
padding: 10px 20px;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
color: $white !important;
}
.button-primary,
@@ -19,19 +20,19 @@
border: 2.5px solid;
border-color: $primary;
transition: 0.2s;
color: $white;
color: $white !important;
}
.button-primary:hover {
background-color: lighten($primary, 5%);
border-color: lighten($primary, 5%);
color: $white;
color: $white !important;
}
.button-secondary,
.button-secondary:focus,
.button-secondary:active {
background-color: $secondary;
color: $white;
color: $white !important;
border: 2.5px solid;
border-color: $secondary;
transition: 0.2s;
@@ -39,7 +40,7 @@
.button-secondary:hover {
background-color: lighten($secondary, 5%);
color: $white;
color: $white !important;
border: 2.5px solid;
border-color: lighten($secondary, 5%);
}
@@ -48,7 +49,7 @@
.button-destructive:focus,
.button-destructive:active {
background-color: $danger;
color: $white;
color: $white !important;
border: 2.5px solid;
border-color: $danger;
transition: 0.2s;
@@ -56,7 +57,7 @@
.button-destructive:hover {
background-color: lighten($danger, 5%);
color: $white;
color: $white !important;
border: 2.5px solid;
border-color: lighten($danger, 5%);
}
@@ -65,7 +66,7 @@
.button-small:focus,
.button-small:active {
font-weight: 500;
font-size: 14px;
font-size: 14px !important;
padding: 8.25px 16px;
transition: 0.2s;
border: none;
@@ -1,4 +1,5 @@
import React, { ButtonHTMLAttributes } from "react";
import { Link } from "react-router-dom";
import "./Button.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
@@ -7,29 +8,68 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
label?: string;
icon?: IconDefinition;
small?: boolean;
secondary?: boolean;
danger?: boolean;
variant?: "primary" | "secondary" | "danger";
to?: string; // for internal links
href?: string; // for external links
target?: "_blank" | "_self";
}
export default function Button({
label,
icon,
small,
secondary,
danger,
variant = "primary",
to,
href,
target = "_self",
...rest
}: ButtonProps) {
const buttonClasses = `button ${small ? "button-small" : ""}
${
secondary
? "button-secondary"
: danger
? "button-destructive"
: "button-primary"
} `;
let buttonClass = `button`;
if (small) {
buttonClass += ` button-small`;
}
switch (variant) {
case "primary":
buttonClass += " button-primary";
break;
case "secondary":
buttonClass += " button-secondary";
break;
case "danger":
buttonClass += " button-destructive";
break;
default:
buttonClass += " button-primary";
break;
}
if (to) {
return (
<Link to={to} className={buttonClass}>
{icon && <FontAwesomeIcon icon={icon} className="me-2" />}
{label}
</Link>
);
}
if (href) {
return (
<a
href={href}
target={target}
rel="noopener noreferrer"
className={buttonClass}
>
{icon && <FontAwesomeIcon icon={icon} className="me-2" />}
{label}
</a>
);
}
// If neither 'to' nor 'href' are provided (regular button)
return (
<button className={buttonClasses} type="button" {...rest}>
<button className={buttonClass} type="button" {...rest}>
{icon && <FontAwesomeIcon icon={icon} className="me-2" />}
{label}
</button>
@@ -0,0 +1 @@
export { default } from "./Button";
@@ -0,0 +1,39 @@
@import "scss/colours.scss";
.checkable-tag {
font-weight: 500;
border-radius: 0.5rem;
font-size: 13px;
padding: 9px 12px;
background-color: $secondary;
color: $white;
cursor: pointer;
transition: 0.2s;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
border: 2px solid $secondary;
&.checked {
background-color: lighten($secondary, 10%) !important;
color: $white;
border: 2px solid $primary !important;
}
.remove-icon {
align-items: center;
font-size: 14px;
transition: 0.2s;
}
&:hover {
background-color: lighten($secondary, 5%);
border: 2px solid lighten($secondary, 5%);
}
&:hover > .remove-icon {
color: $primary;
}
}
@@ -0,0 +1,26 @@
import React from "react";
import "./CheckableTag.scss";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faXmark } from "@fortawesome/pro-solid-svg-icons";
interface Props {
tag: string;
isSelected: boolean;
onToggle: (tag: string) => void;
}
export default function CheckableTag({ tag, isSelected, onToggle }: Props) {
return (
<div
className={`d-flex align-items-center checkable-tag ${
isSelected ? "checked" : ""
}`}
onClick={() => onToggle(tag)}
>
{tag}
{isSelected && (
<FontAwesomeIcon icon={faXmark} className="ms-2 remove-icon" />
)}
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./CheckableTag";
@@ -0,0 +1,24 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
icon?: IconDefinition;
label?: string;
textArea?: boolean;
}
export default function Input({ label, icon, textArea, ...rest }: InputProps) {
return (
<div>
{label && <label className="sub-title">{label}</label>}
<div className={`form-group ${icon ? "input-icon" : ""}`}>
{icon && (
<FontAwesomeIcon icon={icon} className="form-control-feedback" />
)}
<input className="form-control" {...rest} />
</div>
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./Input";
@@ -0,0 +1,20 @@
import React from "react";
import Input from "../Input";
import { faSearch } from "@fortawesome/pro-solid-svg-icons";
interface Props {
value: string;
onChange: (value: string) => void;
}
export default function ModelSearch({ value, onChange }: Props) {
return (
<Input
icon={faSearch}
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder="Search..."
/>
);
}
@@ -0,0 +1,4 @@
export const dummyData = [
{ id: 1, name: "Item 1", tags: ["tag1", "tag2"] },
{ id: 2, name: "Item 2", tags: ["tag2", "tag3"] },
];
@@ -0,0 +1 @@
export { default } from "./ModelSearch";
@@ -0,0 +1,29 @@
@import "scss/colours.scss";
.model-search-results {
background-color: lighten($panel, 6%);
border-radius: 0.5rem;
border: 1px solid lighten($panel, 12%);
.type-tag {
font-weight: 600;
border-radius: 0.4rem;
font-size: 13px;
padding: 4px 8px;
background-color: $secondary;
color: $white;
cursor: pointer;
transition: 0.2s;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
height: 100%;
&.tt2 {
color: #494ea9;
background-color: #c6c7e1;
}
}
}
@@ -0,0 +1,28 @@
import React from "react";
import "./ModelSearchResults.scss";
interface Props {
data: any[];
}
export default function ModelSearchResults({ data }: Props) {
return (
<div className="row g-3">
{data.map((item) => (
<div className="col-12 col-lg-6">
<div className="model-search-results p-3" key={item.id}>
<h5 className="fw-semibold mb-0">{item.name}</h5>
<p className="">by {item.creator}</p>
<span
className={`type-tag ${
item.type === "Tacotron2" ? "tt2" : "tt2"
}`}
>
{item.type}
</span>
</div>
</div>
))}
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./ModelSearchResults";
@@ -0,0 +1,23 @@
import React from "react";
import CheckableTag from "../CheckableTag";
interface Props {
tags: string[];
selectedTags: string[];
onSelectTag: (tag: string) => void;
}
export default function ModelTags({ tags, selectedTags, onSelectTag }: Props) {
return (
<div className="d-flex gap-2 flex-wrap">
{tags.map((tag) => (
<CheckableTag
key={tag}
tag={tag}
isSelected={selectedTags.includes(tag)}
onToggle={onSelectTag}
/>
))}
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./ModelTags";
@@ -0,0 +1 @@
export { default } from "./Panel";
@@ -0,0 +1,68 @@
@import "scss/colours.scss";
.select__control {
&.select {
height: 48px;
border-width: 2px;
padding-left: 0.15rem;
padding-right: 0.15rem;
background-color: $input-dark;
border-radius: 0.5rem;
border-color: $input-dark !important;
box-shadow: none;
cursor: pointer;
&.no-padding {
padding-left: 0.5rem;
}
&:hover {
background-color: lighten($input-dark, 5%);
border-color: lighten($input-dark, 5%) !important;
}
&.focused {
border-color: $primary !important;
}
&.with-icon {
padding-left: 2rem;
}
&.rounded-full {
border-radius: 50px;
}
}
.select-option {
background-color: $input-dark;
cursor: pointer;
padding: 0.5rem 1rem;
&:hover {
background-color: lighten($input-dark, 8%);
}
}
.select-input,
.select-value {
color: $white;
}
.select-container {
background: $input-dark;
margin-top: 6px;
padding: 0;
border-radius: 0.5rem;
z-index: 3;
}
.select-separator {
width: 1px;
opacity: 0.3;
}
.select-placeholder {
color: rgba($white, 0.5) !important;
}
}
@@ -0,0 +1,53 @@
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import "./Select.scss";
import {
default as ReactSelect,
Props as ReactSelectProps,
} from "react-select";
interface SelectProps extends ReactSelectProps {
icon?: IconDefinition;
label?: string;
rounded?: boolean;
}
export default function Select({ label, icon, rounded, ...rest }: SelectProps) {
const SelectFieldClass = {
control: (state: any) =>
state.isFocused
? `select focused ${icon && "with-icon"} ${rounded && "rounded-full"}`
: `select ${icon && "with-icon"} ${rounded && "rounded-full"}`,
option: (state: any) =>
state.isFocused ? "select-option" : "select-option",
input: (state: any) => (state.isFocused ? "select-input" : "select-input"),
placeholder: (state: any) =>
state.isFocused ? "select-placeholder" : "select-placeholder",
singleValue: (state: any) =>
state.isFocused ? "select-value" : "select-value",
menu: (state: any) =>
state.isFocused ? "select-container" : "select-container",
indicatorSeparator: (state: any) =>
state.isFocused ? "select-separator" : "select-separator",
};
return (
<div>
{label && <label className="sub-title">{label}</label>}
<div className={`form-group ${icon ? "input-icon" : ""}`}>
{icon && (
<FontAwesomeIcon icon={icon} className="form-control-feedback" />
)}
<div className="w-100">
<ReactSelect
classNamePrefix="select"
classNames={SelectFieldClass}
{...rest}
/>
</div>
</div>
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./Select";
@@ -35,8 +35,8 @@ export default function ShareButton(props: ShareButtonProps) {
<Button
label="Share Link"
icon={faLink}
secondary
small
variant="secondary"
small={true}
onClick={copyToClipboard}
/>
</div>
@@ -0,0 +1,19 @@
import React from "react";
interface TextAreaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
label?: string;
textArea?: boolean;
}
export default function TextArea({ label, textArea, ...rest }: TextAreaProps) {
return (
<div>
{label && <label className="sub-title">{label}</label>}
<div className="form-group">
<textarea className="form-control" {...rest} />
</div>
</div>
);
}
@@ -0,0 +1 @@
export { default } from "./TextArea";
@@ -43,7 +43,7 @@ export default function MobileMenu(props: MobileMenuProps) {
icon={faUser}
label="Profile"
small
secondary
variant="secondary"
onClick={() => history.push(url)}
/>
</>
@@ -58,7 +58,7 @@ export default function MobileMenu(props: MobileMenuProps) {
<Button
label="Menu"
icon={faBars}
secondary
variant="secondary"
small
onClick={handleMenuButtonClick}
/>
@@ -1,46 +1,59 @@
import React from "react";
import Panel from "../../common/Panel";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import Button from "components/common/Button";
interface Props {
childProps?: any;
titleIcon?: JSX.Element;
titleComponent?: React.ComponentType;
title?: string;
subText: string;
showButtons: boolean;
actionButtons?: JSX.Element;
interface PageHeaderProps {
titleIcon?: IconDefinition;
title: string;
subText: string | React.ReactNode;
full?: boolean;
showButton?: boolean;
extension?: React.ReactNode;
buttonLabel?: string;
buttonVariant?: "primary" | "secondary" | "danger";
buttonTo?: string;
}
export default function PageHeader({
actionButtons,
childProps,
showButtons,
subText,
titleComponent: Title,
title,
titleIcon,
}: Props) {
title,
subText,
full,
showButton,
extension,
buttonLabel,
buttonVariant = "primary",
buttonTo,
}: PageHeaderProps) {
const icon = (
<>{titleIcon && <FontAwesomeIcon icon={titleIcon} className="me-3" />}</>
);
return (
<div className="container-panel hero-section py-4">
<div className="panel">
<div className="p-3 py-4 p-md-4">
{Title ? (
<Title {...childProps} />
) : (
<h1 className="fw-bold text-center text-md-start">
{titleIcon}
{title}
</h1>
)}
<p className="text-center text-md-start pt-1">{subText}</p>
{showButtons && (
<div className="d-flex flex-column flex-md-row gap-3 justify-content-center justify-content-md-start mt-4">
{actionButtons}
<div className="pt-3 pb-4 pt-lg-4">
<Panel padding full={full}>
<div className="d-flex flex-column gap-4">
<div>
<div className="d-flex">
<h2 className="fw-bold flex-grow-1">
{icon}
{title}
</h2>
{showButton && (
<Button
variant={buttonVariant}
label={buttonLabel}
to={buttonTo}
/>
)}
</div>
)}
<p>{subText}</p>
</div>
{extension && <div>{extension}</div>}
</div>
</div>
</Panel>
</div>
);
}
// export default PageHeader;
@@ -156,7 +156,7 @@ export default function SideNav(props: SideNavProps) {
<Button
label={t("loginButton")}
small
secondary
variant="secondary"
onClick={() => {
history.push("/login");
handleNavLinkClick();
@@ -194,7 +194,7 @@ export default function SideNav(props: SideNavProps) {
icon={faUser}
label={t("profileButton")}
small
secondary
variant="secondary"
onClick={() => {
history.push(url);
handleNavLinkClick();
@@ -209,7 +209,7 @@ export default function SideNav(props: SideNavProps) {
icon={faSignOutAlt}
label={t("logOutButton")}
small
danger
variant="danger"
onClick={async () => {
await logoutHandler();
handleNavLinkClick();
@@ -1,5 +0,0 @@
import PageHeader from './PageHeader';
export {
PageHeader,
};
@@ -0,0 +1 @@
export { default } from "./useSearch";
@@ -0,0 +1,32 @@
import { useState, useMemo } from "react";
export default function useSearch(initialData: any[]) {
const [searchTerm, setSearchTerm] = useState<string>("");
const [selectedTags, setSelectedTags] = useState<string[]>([]);
const filteredData = useMemo(() => {
let tempData = [...initialData];
if (searchTerm) {
tempData = tempData.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}
if (selectedTags.length) {
tempData = tempData.filter((item) =>
selectedTags.every((tag) => item.tags.includes(tag))
);
}
return tempData;
}, [searchTerm, selectedTags, initialData]);
return {
searchTerm,
setSearchTerm,
selectedTags,
setSelectedTags,
filteredData,
};
}
@@ -782,7 +782,7 @@ styles with important tags:
}
.select-placeholder {
color: $white !important;
color: $white;
}
.select-container {
@@ -1,17 +1,91 @@
import React from "react";
import { SessionWrapper } from "@storyteller/components/src/session/SessionWrapper";
import Container from "components/common/Container";
import Panel from "components/common/Panel/Panel";
import PageHeader from "components/layout/PageHeader";
import { faMessageDots } from "@fortawesome/pro-solid-svg-icons";
import ModelSearch from "components/common/ModelSearch";
import Panel from "components/common/Panel";
import useSearch from "hooks/useSearch/useSearch";
import ModelSearchResults from "components/common/ModelSearchResults";
import ModelTags from "components/common/ModelTags";
interface TtsModelSearchPageProps {
sessionWrapper: SessionWrapper;
}
const dummyData = [
{
id: 1,
name: "Spongebob",
tags: ["English", "High-pitched", "Character"],
type: "Tacotron2",
},
{
id: 2,
name: "Mariano Closs",
tags: ["Spanish", "Low-pitched"],
type: "Tacotron2",
},
{
id: 3,
name: "Cristiano Ronaldo",
tags: ["Portuguese", "Low-pitched"],
type: "Tacotron2",
},
];
const allTags = [
"English",
"Spanish",
"Portuguese",
"High-pitched",
"Low-pitched",
"Character",
];
export default function TtsModelSearchPage(props: TtsModelSearchPageProps) {
const {
searchTerm,
setSearchTerm,
selectedTags,
setSelectedTags,
filteredData,
} = useSearch(dummyData);
const handleSelectTag = (tag: string) => {
setSelectedTags((prev) => {
if (prev.includes(tag)) {
return prev.filter((t) => t !== tag);
}
return [...prev, tag];
});
};
const searchTts = (
<div className="d-flex flex-column gap-3">
<ModelSearch value={searchTerm} onChange={setSearchTerm} />
<ModelTags
tags={allTags}
selectedTags={selectedTags}
onSelectTag={handleSelectTag}
/>
</div>
);
return (
<Container type="panel">
<PageHeader
title="Text to Speech"
titleIcon={faMessageDots}
subText="Turn text into your favorite character's speaking voice."
showButton={true}
buttonLabel="Create a voice"
buttonVariant="secondary"
buttonTo="/voice-designer"
extension={searchTts}
/>
<Panel padding={true}>
<div>test</div>
<ModelSearchResults data={filteredData} />
</Panel>
</Container>
);