Skip to content
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.79.4",
"sass-embedded": "^1.79.4"
"sass-embedded": "^1.79.4",
"zustand": "^5.0.0"
},
"devDependencies": {
"@chromatic-com/storybook": "1.9.0",
Expand Down
69 changes: 63 additions & 6 deletions src/components/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import "./input.scss";
import { fetchData } from "../../utils/fetch-data";
import { debounce } from "../../utils/deboucne";
import Loader from "../Loader";
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
import { debounce } from '../../utils/deboucne';
import { fetchData } from '../../utils/fetch-data';
import Loader from '../Loader';
import './input.scss';

export interface InputProps {
/** Placeholder of the input */
Expand All @@ -12,10 +13,66 @@ export interface InputProps {

const Input = ({ placeholder, onSelectItem }: InputProps) => {
// DO NOT remove this log
console.log('input re-render')
console.log('input re-render');
const [searchTerm, setSearchTerm] = useState<string>('');
const [listResult, setListResult] = useState<string[]>([]);
const [isSearching, setIsSearch] = useState<boolean>(false);

const handleClick = useCallback((item: string) => {
onSelectItem(item)
},[onSelectItem])

function ListResult() {
return (
searchTerm === '' ? null : listResult.length > 0 ? <div className='list-result'>
{listResult.map((item, index) => <div className='list-result-item' onClick={() => handleClick(item)} key={index}>
{item}
</div> )}
</div>
: <div className='no-result'>No result</div> );
}

const getData = async (searchTerm: string) => {
if(searchTerm === '') return
try {
setIsSearch(true)
const res = await fetchData(searchTerm);
setListResult(res);
} catch(error) {console.log('error', error);
} finally {
setIsSearch(false)
}
}

const handleOnChange = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault();
setSearchTerm(e.target.value);
};

useEffect(() => {
let ignore = false;
if(!ignore) {
getData(searchTerm)
}
return () => {
ignore = true
};
}, [searchTerm])

// Your code start here
return <input></input>
return (
<div>
<input
className='input'
placeholder={placeholder}
onChange={debounce(
(e: ChangeEvent<HTMLInputElement>) => handleOnChange(e),
100
)}
></input>
{isSearching ? <Loader /> : <ListResult />}
</div>
);
// Your code end here
};

Expand Down
30 changes: 30 additions & 0 deletions src/components/Input/input.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,33 @@
html{
font-size: 16px;
}

.input {
border-radius: 4px;
line-height: 1.5em;
font-size: 16px;
padding: .5em 1em;
width: 100%;
}

.list-result {
border: solid 1px grey;
}

.list-result-item {
padding: 1em 2em;

&:hover {
background-color: #eee;
cursor: pointer;
}
}

.no-result {
border: solid 1px grey;
font-style: italic;
color: #333;
text-align: center;
padding: 1em 0;
}

69 changes: 69 additions & 0 deletions src/components/Todo/ResultItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { useEffect, useRef, useState } from "react";
import { newStateType, useDataStore } from "./store";
import { useShallow } from "zustand/shallow";

const ResultItem = (props: newStateType) => {
const { name, id, active } = props;
const editInputRef = useRef<any>(null);
const [editInputValue, setEditInputValue] = useState(name);
const [isEdit, setIsEdit] = useState(false);
const handleDoubleClick = () => {
setIsEdit(!isEdit);
};

const updateName = useDataStore(useShallow((state) => state.updateName));
const toggle = useDataStore(useShallow((state) => state.toggle)) ;
const remove = useDataStore(useShallow((state) => state.removeData)) ;

const editOnChange = (e: any) => {
setEditInputValue(e.target.value);
};

const editOnBlur = () => {
setIsEdit(false);
setEditInputValue(name);
};

useEffect(() => {
if(isEdit) {
editInputRef.current.focus()
}
},[isEdit])



return (
<div className="item" onDoubleClick={handleDoubleClick}>
{isEdit ? (
<input
value={editInputValue}
ref={editInputRef}
type="text"
onBlur={editOnBlur}
onChange={(e) => editOnChange(e)}
onKeyDown={(e: any) => {
if (e.key === "Enter") {
updateName({ id, active, name: editInputValue });
setIsEdit(false);
}
}}
className="edit-input"
/>
) : (
<>
<input
className="toggle"
type="checkbox"
data-testid="todo-item-toggle"
checked={!active}
onChange={() => toggle(props)}
/>
<label className={`${!active ? 'result-input--checked' : 'result-input'}`}>{name}</label>
<div className="close-cta" data-testid="todo-item-button" onClick={() => remove(id)}></div>
</>
)}
</div>
);
};

export default ResultItem;
146 changes: 146 additions & 0 deletions src/components/Todo/Todo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
.wrapper {
position: relative;
width: 550px;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}

.active-all-cta {
position: absolute;
left: 0;
top: 0;
&:before {
content: "❯";
display: inline-block;
font-size: 22px;
padding: 15px 22px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
}

.header {
color: #111;
font: 14px Helvetica Neue, Helvetica, Arial, sans-serif;
font-weight: normal;
line-height: normal;
background: rgba(0, 0, 0, 0.003);
border: none;
box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
height: 65px;
padding: 16px 16px 16px 60px;
box-sizing: border-box;
color: inherit;
font-family: inherit;
font-size: 24px;
font-weight: inherit;
line-height: 1.4em;
margin: 0;
width: 100%;
}

.body {
font-size: 24px;
position: relative;
}

.toggle {
-webkit-appearance: none;
appearance: none;
border: none;
bottom: 0;
height: 40px;
background: none;
margin: auto 0;
opacity: 0;
position: absolute;
text-align: center;
top: 0;
width: 40px;
}

.close-cta {
bottom: 0;
color: var(--set-close-cta--color, #949494);
display: var(--set-close-cta--display, none);
font-size: 30px;
height: 40px;
margin: auto 0;
position: absolute;
right: 10px;
top: 0;
transition: color 0.2s ease-out;
width: 40px;

&:after {
content: "×";
display: block;
height: 100%;
line-height: 1.1;
}
}

.item {
position: relative;
&:hover {
--set-close-cta--display: block;
--set-close-cta--color: #c18585;
}
}

.result-input,
.result-input--checked,
.edit-input {
background-position: 0;
background-repeat: no-repeat;
color: #484848;
display: block;
font-weight: 400;
line-height: 1.2;
padding: 15px 15px 15px 60px;
transition: color 0.4s;
font-size: 24px;
word-break: break-all;
width: 100%;
box-sizing: border-box;
border-bottom: 1px solid #ededed;
}

.edit-input {
border: solid 1px red;
}

.result-input {
background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E");
}

.result-input--checked {
text-decoration: line-through;
background-image: url("data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E");
}

.footer {
display: flex;
justify-content: space-between;
border: 2px solid #e6e6e6;
font-size: 15px;
padding: 10px 15px;
text-align: center;
&--center {
display: flex;
justify-content: space-between;
gap: 18px;
}

&--right {
&:hover {
text-decoration: underline;
}
}
}

.filter {
border: solid 1px transparent;
&-active {
border: solid 1px red;
}
}
Loading