Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Completed Functionality UI Improvement left #1

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 6 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -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/<any other> 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! 💻
7 changes: 6 additions & 1 deletion src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -22,6 +24,7 @@
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
Expand All @@ -35,8 +38,10 @@

.card {
padding: 2em;
display: flex;
justify-content: center;
}

.read-the-docs {
color: #888;
}
}
37 changes: 19 additions & 18 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<>
<div>
{showPinned && <PinnedMessage msg={pinnedMsg} />}
{/* <div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
</div> */}
<h1>@-mentions in X/Twitter</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
{/* <button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</button> */}
<CustomInputField
setShowPinned={setShowPinned}
setPinnedMsg={setPinnedMsg}
/>
<div>{/* <p>{search}</p> */}</div>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
);
}

export default App
export default App;
62 changes: 62 additions & 0 deletions src/components/CustomInputField/CustomInputField.css
Original file line number Diff line number Diff line change
@@ -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;
}
95 changes: 95 additions & 0 deletions src/components/CustomInputField/CustomInputField.tsx
Original file line number Diff line number Diff line change
@@ -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<Array<HTMLDivElement | null>>([]);

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<HTMLDivElement>) => {
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 (
<div
className="show-dropdown"
key={id}
ref={(e) => (ref.current[id] = e)}
onClick={() => handleClick(id)}
>
{`${first_name} ${last_name}`}
</div>
);
};

return (
<div className="input-element">
<div className="input-bar">
<input
ref={(inp) => {
inp?.focus();
}}
value={search}
onChange={(e) => {
setSearch(e.target.value);
}}
onKeyDown={(e) => handleKeyDown(e)}
></input>
</div>
<div className="filter-data-container">
{toggle &&
data
.filter((v) => {
return v.first_name.toLowerCase().startsWith(filteredSearch);
})
.map((value, idx) => {
return <FilteredSearch {...value} key={idx} />;
})}
</div>
</div>
);
};
export default CustomInputField;
9 changes: 9 additions & 0 deletions src/components/PinnedMessage/PinnedMessage.css
Original file line number Diff line number Diff line change
@@ -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;
}
7 changes: 7 additions & 0 deletions src/components/PinnedMessage/PinnedMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import "./PinnedMessage.css";

export default function PinnedMessage(props: {
msg: string | number | boolean | null | undefined;
}) {
return <div className="pinned-msg-div">{props.msg}</div>;
}