diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/App.js" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/App.js"
new file mode 100644
index 0000000..5354a84
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/App.js"
@@ -0,0 +1,22 @@
+import SearchInput from "./components/SearchInput.js";
+import { fetchLanguages } from "./core/api.js";
+
+export default function App({ $target }) {
+ this.state = {
+ fetchedLanguages: [],
+ selectedLanguages: [],
+ };
+
+ this.setState = function (nextState) {
+ //
+ };
+
+ const searchInput = new SearchInput({
+ $target,
+ initialState: "",
+ onChange: async (keyword) => {
+ const data = await fetchLanguages(keyword);
+ console.log(data);
+ },
+ });
+}
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/components/SearchInput.js" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/components/SearchInput.js"
new file mode 100644
index 0000000..7579e34
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/components/SearchInput.js"
@@ -0,0 +1,15 @@
+export default function SearchInput({ $target, initialState }) {
+ this.$element = document.createElement("form");
+ this.$element.className = "SearchInput";
+ this.state = initialState;
+
+ $target.appendChild(this.$element);
+
+ this.render = () => {
+ this.$element.innerHTML = `
+
+ `;
+ };
+
+ this.render();
+}
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/core/api.js" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/core/api.js"
new file mode 100644
index 0000000..74e4615
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/core/api.js"
@@ -0,0 +1,15 @@
+export const API_END_POINT = "API END POINT";
+
+const request = async (url) => {
+ const res = await fetch(url);
+
+ if (res.ok) {
+ const json = await res.json();
+ return json;
+ }
+
+ throw new Error("요청에 실패함");
+};
+
+export const fetchLanguages = async (keyword) =>
+ request(`${API_END_POINT}/languages?keyword=${keyword}`);
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/index.html" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/index.html"
new file mode 100644
index 0000000..1f7680a
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/index.html"
@@ -0,0 +1,36 @@
+
+
+ 2022 FE 데브매칭
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/index.js" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/index.js"
new file mode 100644
index 0000000..c0ca41c
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/index.js"
@@ -0,0 +1,3 @@
+import App from "./App.js";
+
+new App({ $target: document.querySelector(".App") });
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/j.md" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/j.md"
deleted file mode 100644
index e69de29..0000000
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/main.js" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/main.js"
new file mode 100644
index 0000000..2ec78d4
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/main.js"
@@ -0,0 +1,127 @@
+// 버벅임 ... 서버 키고 실행해야 함
+// 할 때마다 실행? 닫기?
+// 방향키, 보너스 제외 1시간 반
+
+const $ = (selector) => document.querySelector(selector);
+
+const resultContainer = $(".Suggestion");
+const resultWrapper = resultContainer.querySelector("ul");
+const resultArr = [];
+
+let focusingIdx = 0;
+
+function renderKeyword() {
+ const renderingWrapper = $(".SelectedLanguage").querySelector("ul");
+
+ renderingWrapper.innerHTML = "";
+ resultArr.map((result) => {
+ const li = document.createElement("li");
+ li.innerText = result;
+ renderingWrapper.appendChild(li);
+ });
+}
+
+function addRenderingKeyword(result) {
+ window.alert(result);
+
+ if (resultArr.includes(result)) return;
+
+ if (resultArr.length >= 5) resultArr.shift();
+ resultArr.push(result);
+
+ renderKeyword();
+}
+
+function separateResultStr(result, keyword) {
+ const _result = (result + "").toLowerCase();
+ const _keyword = (keyword + "").toLowerCase();
+
+ const keywordLength = _keyword.length;
+ const matchingIdx = _result.indexOf(_keyword);
+
+ const str1 = _result.slice(0, matchingIdx);
+ const str2 = _result.slice(matchingIdx, matchingIdx + keywordLength);
+ const str3 = _result.slice(matchingIdx + keywordLength);
+
+ return `${str1}${str2}${str3}`;
+}
+
+function focusListWithArrowKeyNEnter(e) {
+ const maxLength = resultWrapper.childNodes.length;
+ const prevFocusingIdx = focusingIdx;
+
+ switch (e.key) {
+ case "Enter":
+ addRenderingKeyword(resultWrapper.childNodes[focusingIdx].innerText);
+ return;
+ case "ArrowUp":
+ focusingIdx = (focusingIdx - 1) % maxLength;
+ break;
+ case "ArrowDown":
+ focusingIdx = (focusingIdx + 1) % maxLength;
+ break;
+ default:
+ break;
+ }
+
+ resultWrapper.childNodes[prevFocusingIdx]?.classList.remove(
+ "Suggestion__item--selected"
+ );
+ resultWrapper.childNodes[focusingIdx]?.classList.add(
+ "Suggestion__item--selected"
+ );
+}
+
+async function handleInputKeyword(keyword, key) {
+ if (`${key}` === "ArrowUp" || `${key}` === "ArrowDown") return;
+
+ // 함수로 분리하면 Promise 반환?
+ const response = await fetch(
+ `https://wr4a6p937i.execute-api.ap-northeast-2.amazonaws.com/dev/languages?keyword=${keyword}`,
+ {
+ method: "GET",
+ }
+ );
+ const resultData = await response.json();
+
+ if (keyword === "" || resultData.length === 0) {
+ resultWrapper.innerHTML = "";
+ resultContainer.style.display = "none";
+ return;
+ } else {
+ resultContainer.style.display = "block";
+ }
+
+ resultWrapper.innerHTML = "";
+ resultData.map((result, idx) => {
+ const li = document.createElement("li");
+
+ if (idx === focusingIdx) {
+ li.setAttribute("class", "Suggestion__item--selected");
+ }
+
+ li.innerHTML = separateResultStr(result, keyword);
+ li.addEventListener("click", () => addRenderingKeyword(result));
+ resultWrapper.appendChild(li);
+ });
+}
+
+function init() {
+ const form = $("form.SearchInput");
+ form.addEventListener("submit", (e) => e.preventDefault());
+
+ resultContainer.style.display = "none";
+
+ const input = $(".SearchInput__input");
+ input.addEventListener("keyup", (e) =>
+ handleInputKeyword(e.target.value, e.key)
+ );
+ input.focus();
+
+ document.addEventListener("keyup", focusListWithArrowKeyNEnter);
+}
+
+init();
+
+// useEffect 없이 디바운스?
+// API 캐싱?
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/style.css" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/style.css"
new file mode 100644
index 0000000..4c3f740
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/style.css"
@@ -0,0 +1,96 @@
+* {
+ box-sizing: border-box;
+}
+
+ul {
+ padding: 0;
+ margin: 0;
+}
+
+li {
+ list-style: none;
+}
+
+html, body {
+ width: 100%;
+ height: 100%;
+ margin: 0;
+}
+
+.App {
+ display: flex;
+ flex-direction: column;
+ position: relative;
+ height: 100%;
+ justify-content: center;
+ align-items: center;
+}
+
+.SearchInput {
+ width: 300px;
+}
+
+.SearchInput__input {
+ width: 100%;
+ border: 1px solid black;
+ border-radius: 24px;
+ padding: 10px 24px;
+ font-size: 20px
+}
+
+.Suggestion {
+ position: absolute;
+ font-size: 20px;
+ width: 300px;
+ top: 50%;
+ z-index: 1;
+ padding: 10px;
+ border-radius: 24px;
+ border: 1px solid black;
+
+}
+
+.Suggestion ul {
+ width: 100%;
+}
+
+.Suggestion li {
+ padding: 10px;
+ cursor: pointer;
+ width: 100%;
+}
+
+.Suggestion li:hover {
+ background-color: #90CDF4;
+}
+
+.Suggestion__item--selected {
+ background-color: #BEE3F8;
+}
+
+.Suggestion__item--matched {
+ background-color: #9AE6B4;
+}
+
+.SelectedLanguage {
+ display: flex;
+ justify-content: center;
+ margin: 0 auto;
+ flex-wrap: nowrap;
+ width: 800px;
+ height: 37.5px;
+ margin-top: -100px;
+ margin-bottom: 10px;
+
+}
+
+.SelectedLanguage li {
+ display: inline-block;
+ padding: 8px;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ max-width: 100px;
+ overflow:hidden;
+ text-overflow:ellipsis;
+ white-space:nowrap;
+}
\ No newline at end of file
diff --git "a/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211.md" "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211.md"
new file mode 100644
index 0000000..e95b301
--- /dev/null
+++ "b/\352\265\254\355\230\204 \352\263\274\354\240\234 \355\222\200\354\235\264/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211/\354\243\274\355\225\250/\355\224\204\353\241\234\352\267\270\353\236\230\353\260\215 \354\226\270\354\226\264 \352\262\200\354\203\211.md"
@@ -0,0 +1,57 @@
+## 1. 초반에 엄청 버벅임 ... 동작법 익숙해지는 데에 시간을 많이 소요함
+
+---
+
+## 2. 할 때마다 실행 클릭하고, 하나씩 닫고 ... 결국 고치고 저장하고 새로고침 했는데 이거 맞나?
+
+---
+
+## 3. 차례대로 쭉 풀며 기본 구현사항들 풀어나감,
+
+괜히 제한시간이 있다고 생각하니까 리팩토링보다는 막 갈기게 됨
+
+여유 가지는 연습이 필요할 듯
+
+---
+
+## 4. 방향키로 리스트 탐색 + 추가 구현사항 제외하고 1시간 반 정도 소요함
+
+괜히 시간 많다고 느껴지니까 집중력 매우 떨어짐
+
+한 줄 쓰고 인스타 보고 반복,, 쩝 실전 때는 안 그러겠지?
+
+---
+
+## 5. LocalStorage 사용해서 새로고침 상태 유지는 귀찮아서 그냥 넘김, 무슨 심보인지 모르겠음
+
+---
+
+## 6. 결과적으로 API 캐싱과 디바운스 기능 빼고는 얼추 완성함
+
+근데 이거 막 갈겨서 코드리뷰 할 맛 안 나겠다 싶음 ... 빨리 친구들 코드 봐보고 싶음
+
+---
+
+## 7. 해설지도 대충 봐봤는데 API 캐싱 신기 .! 디바운스 기능은 그냥 라이브러리 쓰는 건가~.~
+
+아래는 해설지의 캐싱 기능
+
+```javascript
+const cache = {};
+
+const request = async (url) => {
+ if (cache[url]) {
+ return cache[url];
+ }
+
+ const res = await fetch(url);
+
+ if (res.ok) {
+ const json = await res.json();
+ cache[url] = json;
+ return json;
+ }
+
+ throw new Error("요청에 실패함");
+};
+```