From 122f8e52c750cdb74d9b0d8eb1bb3ee76d36d33e Mon Sep 17 00:00:00 2001 From: siiddhantt Date: Wed, 13 Sep 2023 08:40:04 +0530 Subject: [PATCH] Initial commit --- package-lock.json | 92 ++++++++++++++++++++++++ package.json | 8 +++ public/index.html | 4 +- src/App.css | 127 +++++++++++++++++++++++++++------ src/App.js | 113 ++++++++++++++++++++++++----- src/api/TTS.js | 10 +++ src/api/config.js | 2 + src/api/index.js | 1 + src/atoms/button/Button.js | 51 +++++++++++++ src/atoms/button/index.tsx | 3 + src/atoms/checkbox/Checkbox.js | 68 ++++++++++++++++++ src/atoms/checkbox/index.tsx | 3 + src/atoms/dropdown/Dropdown.js | 104 +++++++++++++++++++++++++++ src/atoms/dropdown/index.tsx | 3 + src/components/Form.js | 123 +++++++++++++++++++++++++++++++ src/components/TTS.js | 38 ++++++++++ tailwind.config.js | 8 +++ 17 files changed, 719 insertions(+), 39 deletions(-) create mode 100644 src/api/TTS.js create mode 100644 src/api/config.js create mode 100644 src/api/index.js create mode 100644 src/atoms/button/Button.js create mode 100644 src/atoms/button/index.tsx create mode 100644 src/atoms/checkbox/Checkbox.js create mode 100644 src/atoms/checkbox/index.tsx create mode 100644 src/atoms/dropdown/Dropdown.js create mode 100644 src/atoms/dropdown/index.tsx create mode 100644 src/components/Form.js create mode 100644 src/components/TTS.js create mode 100644 tailwind.config.js diff --git a/package-lock.json b/package-lock.json index 5f9b316..16ec59e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,21 @@ "name": "tts-playground", "version": "0.1.0", "dependencies": { + "@headlessui/react": "^1.7.17", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "clsx": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.11.0", + "react-router-dom": "^6.15.0", "react-scripts": "5.0.1", + "react-type-animation": "^3.1.0", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "tailwindcss": "^3.3.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -2363,6 +2371,21 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@headlessui/react": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.17.tgz", + "integrity": "sha512-4am+tzvkqDSSgiwrsEpGWqgGo9dz8qU5M3znCkC4PgkpY4HcCZzEDEvozltGGGHIKl9jbXbZPSH5TWn4sWJdow==", + "dependencies": { + "client-only": "^0.0.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18", + "react-dom": "^16 || ^17 || ^18" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -3241,6 +3264,14 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.8.0.tgz", + "integrity": "sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -5973,6 +6004,11 @@ "node": ">=0.10.0" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" + }, "node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -5983,6 +6019,14 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -14658,6 +14702,14 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-icons": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", + "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -14671,6 +14723,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.15.0.tgz", + "integrity": "sha512-NIytlzvzLwJkCQj2HLefmeakxxWHWAP+02EGqWEZy+DgfHHKQMUoBBjUQLOtFInBMhWtb3hiUy6MfFgwLjXhqg==", + "dependencies": { + "@remix-run/router": "1.8.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.15.0.tgz", + "integrity": "sha512-aR42t0fs7brintwBGAv2+mGlCtgtFQeOzK0BM1/OiqEzRejOZtpMZepvgkscpMUnKb8YO84G7s3LsHnnDNonbQ==", + "dependencies": { + "@remix-run/router": "1.8.0", + "react-router": "6.15.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14743,6 +14825,16 @@ } } }, + "node_modules/react-type-animation": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-type-animation/-/react-type-animation-3.1.0.tgz", + "integrity": "sha512-Ju74SpUFpSINqlGU8UeFAF+AnAn0nZcc8MB0Ho6QvRkh8uDKkOzAiMD3l9xEmkbKXnSYsljfVgIDM/zwqEImpQ==", + "peerDependencies": { + "prop-types": "^15.5.4", + "react": ">= 15.0.0", + "react-dom": ">= 15.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", diff --git a/package.json b/package.json index 84bf3c3..2ca85de 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,17 @@ "version": "0.1.0", "private": true, "dependencies": { + "@headlessui/react": "^1.7.17", "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "clsx": "^2.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-icons": "^4.11.0", + "react-router-dom": "^6.15.0", "react-scripts": "5.0.1", + "react-type-animation": "^3.1.0", "web-vitals": "^2.1.4" }, "scripts": { @@ -34,5 +39,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "tailwindcss": "^3.3.3" } } diff --git a/public/index.html b/public/index.html index aa069f2..786c047 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,7 @@ - React App + Siddhant diff --git a/src/App.css b/src/App.css index 74b5e05..79b7195 100644 --- a/src/App.css +++ b/src/App.css @@ -1,38 +1,123 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; +@import url("https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@500&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500&family=Source+Code+Pro:wght@500&display=swap"); + .App { text-align: center; + font-family: "Source Code Pro", monospace; +} + +.head { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 20vh; + font-size: 3rem; + color: #dbd9d9; +} + +.middle { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; + margin-top: 5rem; + font-size: 2rem; + color: #dbd9d9; +} + +.body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-evenly; + margin-top: 3rem; + font-size: 2rem; + color: #dbd9d9; } -.App-logo { - height: 40vmin; - pointer-events: none; +.body-text { + width: 100%; + max-width: 35rem; + padding: 0.3rem; + background-color: #2d2d43; + border-radius: 1.2rem; } -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } +.name-field { + border: 0; + outline: 0; + color: #dbd9d9; + background: transparent; + border-bottom: 1px solid #dbd9d9; + width: 10rem; + text-align: center; + font-family: "Source Code Pro", monospace; + font-size: 2rem; } -.App-header { - background-color: #282c34; - min-height: 100vh; +.form { display: flex; flex-direction: column; align-items: center; justify-content: center; - font-size: calc(10px + 2vmin); - color: white; + font-family: "Plus Jakarta Sans", sans-serif; + height: 100vh; +} + +.form-body { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2rem; + padding: 5%; + border: 2px solid #dbd9d930; + border-radius: 1.2rem; +} + +.form-field { + border: 0; + outline: 0; + color: #dbd9d9; + background: transparent; + border-bottom: 1px solid #dbd9d953; + width: 22rem; + max-width: 50vw; + font-family: "Source Code Pro", monospace; + font-size: 1.2rem; +} + +.form-drop { + margin-top: 0.5rem; +} + +.form-check { + margin-top: 0.5rem; + width: 22rem; + max-width: 50vw; +} + +.form-buttons { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-evenly; + width: 22rem; } -.App-link { - color: #61dafb; +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + margin: 0; } -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } +body { + background: #222233; + height: 100vh; } diff --git a/src/App.js b/src/App.js index 3784575..10ba7d4 100644 --- a/src/App.js +++ b/src/App.js @@ -1,23 +1,104 @@ -import logo from './logo.svg'; -import './App.css'; +import { useState } from "react"; +import { TypeAnimation } from "react-type-animation"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import { TTS_API } from "./api"; + +import "./App.css"; +import Form from "./components/Form"; function App() { + const [flag, setFlag] = useState(false); + const [name, setName] = useState(""); + const [isSubmit, setIsSubmit] = useState(false); + const handleButtonClick = async () => { + try { + const response = await TTS_API.brianTTS( + `Hello, ${name}! Welcome to our website. We're glad to have you here.` + ); + if (response.status !== 200) { + const errorText = await response.text(); + alert(errorText); + return; + } + const mp3 = await response.blob(); + const blobUrl = URL.createObjectURL(mp3); + const audio = new Audio(blobUrl); + audio.pause(); + audio.load(); + audio.play(); + } catch (error) { + console.error("An error occurred:", error); + } + }; return (
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
+ + + } /> + +
+
+ {flag ? ( +
Hello World!👋
+ ) : ( + { + setFlag(true); + }, + ]} + speed={10} + /> + )} +
+
+
+
+ {flag ? ( + <> +
Hi there!👀 What's your name?
+ { + setName(e.target.value); + setIsSubmit(false); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + setIsSubmit(true); + handleButtonClick(); + } + }} + > + + ) : ( + <> + )} +
+
+
+ {isSubmit ? ( +
+ { + setFlag(true); + }, + ]} + speed={20} + > +
+ ) : ( + <> + )} +
+
); } diff --git a/src/api/TTS.js b/src/api/TTS.js new file mode 100644 index 0000000..f34c587 --- /dev/null +++ b/src/api/TTS.js @@ -0,0 +1,10 @@ +import { STREAM_ELEMENTS_URI } from "./config"; + +export const TTS_API = { + brianTTS: (text) => { + return fetch( + `${STREAM_ELEMENTS_URI}?voice=Brian&text=` + + encodeURIComponent(text.trim()) + ); + }, +}; diff --git a/src/api/config.js b/src/api/config.js new file mode 100644 index 0000000..6be4b04 --- /dev/null +++ b/src/api/config.js @@ -0,0 +1,2 @@ +export const STREAM_ELEMENTS_URI = + "https://api.streamelements.com/kappa/v2/speech"; diff --git a/src/api/index.js b/src/api/index.js new file mode 100644 index 0000000..435505d --- /dev/null +++ b/src/api/index.js @@ -0,0 +1 @@ +export { TTS_API } from "./TTS"; diff --git a/src/atoms/button/Button.js b/src/atoms/button/Button.js new file mode 100644 index 0000000..1b524c6 --- /dev/null +++ b/src/atoms/button/Button.js @@ -0,0 +1,51 @@ +import React, { memo } from "react"; + +const appearances = { + primary: + "flex flex-row active:scale-95 cursor-pointer transition ease-in-out duration-300 justify-center items-center rounded-[8px] not-italic font-bold text-white text-center relative", + white: + "flex flex-row active:scale-95 cursor-pointer transition ease-in-out duration-300 justify-center items-center border-solid border-2 border-[#EEEEEE] rounded-[8px] not-italic font-bold text-gray-900 text-center relative", + dark: "flex flex-row active:scale-95 cursor-pointer transition ease-in-out duration-300 justify-center items-center rounded-[8px] not-italic font-bold text-white text-center relative", + ghost: + "flex flex-row active:scale-95 cursor-pointer hover:bg-gray-50 transition ease-in-out duration-300 justify-center items-center border-solid border-[1px] border-[#dbd9d9] rounded-[8px] not-italic font-bold text-[#dbd9d9] hover:text-neutral-900 text-center relative", +}; + +const colors = { + primary: "#5359EA", + white: "#FFFFFF", + dark: "#111827", + ghost: "", +}; + +const sizes = { + sm: "gap-2 min-w-[79px] h-10 text-sm px-4 py-2.5", + md: "gap-2.5 min-w-[102px] h-12 text-base px-6 py-3", + lg: "gap-2.5 min-w-[118px] h-14 text-base px-8 py-4", +}; + +const ButtonComponent = ({ + appearance, + type, + size, + leftIcon, + rightIcon, + children, + onClick, +}) => { + return ( + + ); +}; + +export default memo(ButtonComponent); diff --git a/src/atoms/button/index.tsx b/src/atoms/button/index.tsx new file mode 100644 index 0000000..150e111 --- /dev/null +++ b/src/atoms/button/index.tsx @@ -0,0 +1,3 @@ +import Button from "./Button"; + +export default Button; diff --git a/src/atoms/checkbox/Checkbox.js b/src/atoms/checkbox/Checkbox.js new file mode 100644 index 0000000..9f31dd0 --- /dev/null +++ b/src/atoms/checkbox/Checkbox.js @@ -0,0 +1,68 @@ +import React from "react"; +import { HiCheck } from "react-icons/hi"; + +const Checkbox = ({ active, title, size = "base", showBg, onClick }) => { + const sizeArray = ["xs", "sm", "base", "lg", "xl"]; + + return ( +
+ {showBg && ( +
+
+ +
+ {title} +
+ )} + {!showBg && ( +
+
+ +
+ + {title} + +
+ )} +
+ ); +}; + +export default Checkbox; diff --git a/src/atoms/checkbox/index.tsx b/src/atoms/checkbox/index.tsx new file mode 100644 index 0000000..8d18d05 --- /dev/null +++ b/src/atoms/checkbox/index.tsx @@ -0,0 +1,3 @@ +import Checkbox from "./Checkbox"; + +export default Checkbox; diff --git a/src/atoms/dropdown/Dropdown.js b/src/atoms/dropdown/Dropdown.js new file mode 100644 index 0000000..3302371 --- /dev/null +++ b/src/atoms/dropdown/Dropdown.js @@ -0,0 +1,104 @@ +import React, { Fragment, useState } from "react"; +import { Menu, Transition } from "@headlessui/react"; + +function classNames(...classes) { + return classes.filter(Boolean).join(" "); +} + +const Dropdown = ({ + items, + placeholder, + overlay, + disabled, + selectedValue, + setSelected, + onClick, +}) => { + const [key, setKey] = useState(selectedValue); + return ( + +
+ + {key >= 0 && key < items.length ? ( +
+ {items[key].optionName} +
+ ) : ( + placeholder + )} +
+ + + +
+
+
+ + + +
+ {items.map((item, i) => { + return ( + + {({ active }) => ( +
{ + setSelected(i); + setKey(i); + onClick(e); + }} + > +
+ {item.optionName} +
+
+ )} +
+ ); + })} +
+
+
+
+ ); +}; + +export default Dropdown; diff --git a/src/atoms/dropdown/index.tsx b/src/atoms/dropdown/index.tsx new file mode 100644 index 0000000..1de75f0 --- /dev/null +++ b/src/atoms/dropdown/index.tsx @@ -0,0 +1,3 @@ +import Dropdown from "./Dropdown"; + +export default Dropdown; diff --git a/src/components/Form.js b/src/components/Form.js new file mode 100644 index 0000000..87d1700 --- /dev/null +++ b/src/components/Form.js @@ -0,0 +1,123 @@ +import React, { useState } from "react"; + +import "../App.css"; +import Button from "../atoms/button/Button"; +import Dropdown from "../atoms/dropdown/Dropdown"; +import Checkbox from "../atoms/checkbox/Checkbox"; + +function isValidEmail(email) { + return /\S+@\S+\.\S+/.test(email); +} + +function Form() { + const [key, setKey] = useState(-1); + const [name, setName] = useState(""); + const [age, setAge] = useState(0); + const [email, setEmail] = useState(""); + const [location, setLocation] = useState(""); + const [gender, setGender] = useState(""); + const [isChecked, setIsChecked] = useState(false); + const dropdownData1 = { + optionName: "Male", + optionIcon: <>, + }; + const dropdownData2 = { + optionName: "Female", + optionIcon: <>, + }; + const dropdownData3 = { + optionName: "Other", + optionIcon: <>, + }; + const handleDrop = (e) => { + setGender(e.target.outerText); + }; + const handleCheck = () => + isChecked ? setIsChecked(false) : setIsChecked(true); + const handleClear = () => window.location.reload(); + const handleSubmit = () => { + if (!name || !age || !email || !location || !gender) + alert("One or more fields are empty"); + else if (!isValidEmail(email)) alert("Invalid email"); + else if (!isChecked) alert("You need to agree to the T&C"); + else alert("Form successfully submitted"); + }; + return ( +
+
+ { + setName(e.target.value); + }} + > + { + setAge(e.target.value); + }} + > + { + setEmail(e.target.value); + }} + > + { + setLocation(e.target.value); + }} + > +
+ +
+
+ +
+
+ + +
+
+
+ ); +} + +export default Form; diff --git a/src/components/TTS.js b/src/components/TTS.js new file mode 100644 index 0000000..693336b --- /dev/null +++ b/src/components/TTS.js @@ -0,0 +1,38 @@ +import React, { useState } from "react"; +import { TTS_API } from "../api"; + +function TTS_Page() { + const [audioSrc, setAudioSrc] = useState(""); + const handleButtonClick = async () => { + try { + const response = await TTS_API.brianTTS(""); + + if (response.status !== 200) { + const errorText = await response.text(); + alert(errorText); + return; + } + + const mp3 = await response.blob(); + const blobUrl = URL.createObjectURL(mp3); + setAudioSrc(blobUrl); + + const audio = document.getElementById("audio"); + audio.pause(); + audio.load(); + audio.play(); + } catch (error) { + console.error("An error occurred:", error); + } + }; + return ( +
+ + +
+ ); +} + +export default TTS_Page; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..37cc651 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./src/**/*.{js,jsx,ts,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +};