diff --git a/README.md b/README.md index 43b6cf1..6c2201a 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,13 @@ # Frontend Assignment πŸš€ -Project: Mentions component -Time provided: 2 hours -Tech stack: React + Typescript -Link to design: [Design](https://www.figma.com/file/EEmRktq44VPR3u8Lx7otOJ/Frontend-Assignment---Dropdown?type=design&t=YyUdu9qHBb3sS66T-6) +Twitter Mention functionality mock up -Description -This is similar to the @-mentions in X/Twitter. The user can type any text in the input element but when user types `@` then a select box should appear from which they can select an option and it should be displayed in input element. For example `Hi @Luke Skywalker may the force be with you.`. An `onChange` handler should be triggered with the input and the options selected, in a format of your choice. - -Instructions: +### Clone the repo using: "https://github.com/raiden8051/TwitterMentionAssignment.git" -- Implement as much as possible in the given time. -- Push your code to Github/Gitlab/ and send us the link. -- Please write us a note on what else you would do if you could spend more time. - -What we look for: +`npm i` -- To install required packeges -- Thinking: Are you able to think through the flow and edge cases? -- Tests: How well is the code tested through unit/integration tests? -- Documentation: How can anyone new run this app locally and contribute to it? -- Code organisation: How are you organizing your components? -- Component Reusability: Ensure that mention component that you are creating is re-usable. - - Ensure that consumer of mention component can pass `onChange` and `value` prop to make it controlled. +`npm run dev` -- To deploy on local server -What we don’t look for: +### The project will be running on the local server - - Custom select box: You need not implement a custom select box. You're welcome to use the native select box or your favorite library. - - Styling: It can look & feel very bare-bones, that's perfectly fine. - -From where you can get data for options while triggering mention actions - - Use data present in data.json as raw data for mention component. - -Happy coding! πŸ’» +Enjoy! πŸ’» diff --git a/src/App.css b/src/App.css index b9d355d..846bbe5 100644 --- a/src/App.css +++ b/src/App.css @@ -11,9 +11,11 @@ will-change: filter; transition: filter 300ms; } + .logo:hover { filter: drop-shadow(0 0 2em #646cffaa); } + .logo.react:hover { filter: drop-shadow(0 0 2em #61dafbaa); } @@ -22,6 +24,7 @@ from { transform: rotate(0deg); } + to { transform: rotate(360deg); } @@ -35,8 +38,10 @@ .card { padding: 2em; + display: flex; + justify-content: center; } .read-the-docs { color: #888; -} +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index afe48ac..4c2916a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,36 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import { useState } from "react"; +import CustomInputField from "./components/CustomInputField/CustomInputField"; +import "./App.css"; +import PinnedMessage from "./components/PinnedMessage/PinnedMessage"; function App() { - const [count, setCount] = useState(0) + const [showPinned, setShowPinned] = useState(false); + const [pinnedMsg, setPinnedMsg] = useState(""); return ( <> -
+ {showPinned && } + {/*
Vite logo React logo -
-

Vite + React

+
*/} +

@-mentions in X/Twitter

- -

- Edit src/App.tsx and save to test HMR -

+ */} + +
{/*

{search}

*/}
-

- Click on the Vite and React logos to learn more -

- ) + ); } -export default App +export default App; diff --git a/src/components/CustomInputField/CustomInputField.css b/src/components/CustomInputField/CustomInputField.css new file mode 100644 index 0000000..7efd0e4 --- /dev/null +++ b/src/components/CustomInputField/CustomInputField.css @@ -0,0 +1,62 @@ +.input-element{ + padding: 10px; + width: 500px; +} +.input-element input{ + height: 40px; + width: 92%; + border-radius: 10px; + border: none; + padding: 5px 20px; + box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(138, 137, 137, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;; +} + +.show-dropdown{ + background: #f1f1f1; + width: 177px; + margin: 0 auto; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border: 1px solid #3f3d3d; + cursor: pointer; + color: #3f3d3d; + padding: 5px; + width: 100%; +} +.filter-data-container{ + border-radius: 4px; + width: 100%; + margin-top:10px; + max-height: 300px; + overflow-y: auto; + overflow-x: hidden; +} +::-webkit-scrollbar { + width: 4px; +} + +::-webkit-scrollbar-track { + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3); +} + +::-webkit-scrollbar-thumb { + background-color: darkgrey; + border-radius: 4px; +} +.input-field-span{ + top: 50%; + right: 15px; + transform: translateY(-50%); + position: absolute; + z-index: 100; + padding: 4px 4px; + border-radius: 4px; + background: rgba(255, 255, 255, 1); + color: #262626; + font-size: 12px; +} +.input-bar{ + position: relative; +} \ No newline at end of file diff --git a/src/components/CustomInputField/CustomInputField.tsx b/src/components/CustomInputField/CustomInputField.tsx new file mode 100644 index 0000000..0dbb08c --- /dev/null +++ b/src/components/CustomInputField/CustomInputField.tsx @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import React, { useEffect, useRef, useState } from "react"; +import "./CustomInputField.css"; +import data from "../../../data.json"; + +const CustomInputField = (props: { + setPinnedMsg: (arg0: string) => void; + setShowPinned: (arg0: boolean) => void; +}) => { + const [search, setSearch] = useState(""); + const [toggle, setToggle] = useState(false); + const [filteredSearch, setFilteredSearch] = useState(""); + + const ref = useRef>([]); + + useEffect(() => { + // console.log(search); + if (toggle) { + const v = search.toLowerCase().split("@"); + setFilteredSearch(v[v.length - 1]); + } + }, [search, toggle]); + + interface dataProps { + id: number; + first_name: string; + last_name: string; + email: string; + gender: string; + } + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "@") { + setSearch(search + " "); + setToggle(true); + } else if (e.key === "Enter") { + props.setPinnedMsg(search); + props.setShowPinned(true); + setSearch(""); + } + }; + + const handleClick = (id: number) => { + if (ref.current[id] != undefined) { + setSearch( + search.substring(0, search.lastIndexOf("@") + 1) + + ref.current[id]?.textContent + + " " + ); + } + setToggle(false); + setFilteredSearch(""); + }; + + const FilteredSearch = ({ id, first_name, last_name }: dataProps) => { + return ( +
(ref.current[id] = e)} + onClick={() => handleClick(id)} + > + {`${first_name} ${last_name}`} +
+ ); + }; + + return ( +
+
+ { + inp?.focus(); + }} + value={search} + onChange={(e) => { + setSearch(e.target.value); + }} + onKeyDown={(e) => handleKeyDown(e)} + > +
+
+ {toggle && + data + .filter((v) => { + return v.first_name.toLowerCase().startsWith(filteredSearch); + }) + .map((value, idx) => { + return ; + })} +
+
+ ); +}; +export default CustomInputField; diff --git a/src/components/PinnedMessage/PinnedMessage.css b/src/components/PinnedMessage/PinnedMessage.css new file mode 100644 index 0000000..e2f1546 --- /dev/null +++ b/src/components/PinnedMessage/PinnedMessage.css @@ -0,0 +1,9 @@ +.pinned-msg-div{ + background-color: #2AAA8A; + width: 100%; + height: 40px; + border-radius: 6px; + display: flex; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/src/components/PinnedMessage/PinnedMessage.tsx b/src/components/PinnedMessage/PinnedMessage.tsx new file mode 100644 index 0000000..6a11bfd --- /dev/null +++ b/src/components/PinnedMessage/PinnedMessage.tsx @@ -0,0 +1,7 @@ +import "./PinnedMessage.css"; + +export default function PinnedMessage(props: { + msg: string | number | boolean | null | undefined; +}) { + return
{props.msg}
; +}