-
Notifications
You must be signed in to change notification settings - Fork 26
[김참솔] Sprint9 #124
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
The head ref may contain hidden characters: "Next-\uAE40\uCC38\uC194-sprint9"
[김참솔] Sprint9 #124
Changes from all commits
1fec91f
c50f6f7
fba3252
5b8c8e4
fc5eb4e
33b547d
67eac7b
80f89ae
fba7503
ad556b0
96d583d
74ba397
3438aeb
e56342f
7198c53
55ca1a7
4e9e5cc
6416546
59e35c3
644b899
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| const BUTTON_TYPE = Object.freeze({ | ||
| add: "add", | ||
| edit: "edit", | ||
| delete: "delete", | ||
| }); | ||
|
|
||
| export { BUTTON_TYPE }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import { BUTTON_TYPE } from "./button-type"; | ||
| import styles from "./button.module.css"; | ||
|
|
||
| const className = { | ||
| [BUTTON_TYPE.add]: `${styles.button} ${styles.add}`, | ||
| [BUTTON_TYPE.edit]: `${styles.button} ${styles.edit}`, | ||
| [BUTTON_TYPE.delete]: `${styles.button} ${styles.delete}`, | ||
| }; | ||
|
|
||
| const leadingIcon = { | ||
| [BUTTON_TYPE.add]: "/icons/ic-plus.svg", | ||
| [BUTTON_TYPE.edit]: "/icons/ic-check.svg", | ||
| [BUTTON_TYPE.delete]: "/icons/ic-xmark-white.svg", | ||
| }; | ||
|
|
||
| function Button({ children, type = BUTTON_TYPE.add, ...props }) { | ||
| return ( | ||
| <button className={className[type]} {...props}> | ||
| <div className={styles.trailingIcon}> | ||
| <img src={leadingIcon[type]} alt={type} /> | ||
| </div> | ||
| {children} | ||
| </button> | ||
| ); | ||
| } | ||
|
Comment on lines
+4
to
+25
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 크으 공통 컴포넌트가 참 깔끔하네요 👍다만, |
||
|
|
||
| export default Button; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| .button { | ||
| color: var(--color-state-900); | ||
| background-color: var(--color-state-200); | ||
| padding: 18px 40px; | ||
| border: 2px solid var(--color-state-900); | ||
| border-radius: 24px; | ||
| box-shadow: 4px 4px 0px var(--color-state-900); | ||
| font-size: 16px; | ||
| font-weight: 700; | ||
| line-height: 100%; | ||
| cursor: pointer; | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 4px; | ||
| white-space: nowrap; | ||
| } | ||
| .button .trailingIcon { | ||
| width: 16px; | ||
| height: 16px; | ||
| } | ||
| .button .trailingIcon img { | ||
| width: 100%; | ||
| height: 100%; | ||
| } | ||
|
|
||
| .button.add:active { | ||
| color: white; | ||
| background-color: var(--color-violet-600); | ||
| } | ||
| .button.add:active img { | ||
| filter: invert(100%) sepia(14%) saturate(1801%) hue-rotate(190deg) | ||
| brightness(116%) contrast(101%); | ||
| } | ||
|
|
||
| .button.edit:active { | ||
| background-color: var(--color-lime-300); | ||
| } | ||
|
|
||
| .button.delete { | ||
| color: white; | ||
| background-color: var(--color-rose-500); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| function colorVariable(name, shade) { | ||
| return `var(--color-${name}-${Math.max(100, Math.min(900, shade))})`; | ||
| } | ||
|
Comment on lines
+1
to
+3
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 크으.. 여기서도 꼼꼼함이 돋보이는군요.
|
||
|
|
||
| const Colors = { | ||
| state: { | ||
| 100: colorVariable("state", 100), | ||
| 200: colorVariable("state", 200), | ||
| 300: colorVariable("state", 300), | ||
| 400: colorVariable("state", 400), | ||
| 500: colorVariable("state", 500), | ||
| 800: colorVariable("state", 800), | ||
| 900: colorVariable("state", 900), | ||
| }, | ||
| violet: { | ||
| 100: colorVariable("violet", 100), | ||
| 600: colorVariable("violet", 600), | ||
| }, | ||
| rose: { | ||
| 500: colorVariable("rose", 500), | ||
| }, | ||
| lime: { | ||
| 300: colorVariable("lime", 300), | ||
| }, | ||
| amber: { | ||
| 800: colorVariable("amber", 800), | ||
| }, | ||
| }; | ||
|
|
||
| export default Colors; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import styles from "./search-input.module.css"; | ||
|
|
||
| function SearchInput({ ...props }) { | ||
| return ( | ||
| <div className={styles.searchInput}> | ||
| <input type="text" {...props} /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default SearchInput; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| .searchInput { | ||
| width: 100%; | ||
| background-color: var(--color-state-100); | ||
| padding: 16px 24px; | ||
| border: 2px solid var(--color-state-900); | ||
| border-radius: 24px; | ||
| box-shadow: 4px 4px 0px var(--color-state-900); | ||
| } | ||
| .searchInput input { | ||
| background: none; | ||
| border: none; | ||
| outline: none; | ||
| width: 100%; | ||
| font-size: 16px; | ||
| font-weight: 400; | ||
| line-height: 100%; | ||
| color: var(--color-state-900); | ||
| } | ||
| .searchInput input::placeholder { | ||
| font-size: 16px; | ||
| font-weight: 400; | ||
| line-height: 100%; | ||
| color: var(--color-state-500); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import Link from "next/link"; | ||
| import styles from "./global-nav-bar.module.css"; | ||
|
|
||
| function GlobalNavBar() { | ||
| return ( | ||
| <nav className={styles.globalNavBar}> | ||
| <div className={styles.content}> | ||
| <Link href="/"> | ||
| <picture> | ||
| <source | ||
| srcSet="/images/logo-small.svg" | ||
| media="(max-width: 743px)" | ||
| /> | ||
| <img src="/images/logo-large.svg" alt="logo" /> | ||
| </picture> | ||
| </Link> | ||
| </div> | ||
| </nav> | ||
| ); | ||
| } | ||
|
|
||
| export default GlobalNavBar; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| .globalNavBar { | ||
| height: 60px; | ||
| background-color: white; | ||
| padding: 0 200px; | ||
| border-bottom: 1px solid var(--color-state-200); | ||
| } | ||
|
|
||
| .globalNavBar .content { | ||
| max-width: 1200px; | ||
| height: 100%; | ||
| margin: 0 auto; | ||
| display: flex; | ||
| align-items: center; | ||
| } | ||
|
|
||
| @media (max-width: 1199px) { | ||
| .globalNavBar { | ||
| padding: 0 24px; | ||
| } | ||
|
|
||
| .globalNavBar .content { | ||
| max-width: none; | ||
| margin: 0; | ||
| } | ||
| } | ||
|
|
||
| @media (max-width: 743px) { | ||
| .globalNavBar { | ||
| padding: 0 16px; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { useEffect, useState } from "react"; | ||
| import { createPortal } from "react-dom"; | ||
|
|
||
| function Portal({ children }) { | ||
| const [container, setContainer] = useState(null); | ||
|
|
||
| useEffect(() => { | ||
| // setContainer(document.getElementById("portal")); | ||
| setContainer(document.body); | ||
| }, []); | ||
|
|
||
| return container && createPortal(children, container); | ||
| } | ||
|
|
||
| export default Portal; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import styles from "./todo-label.module.css"; | ||
|
|
||
| function TodoLabel({ done }) { | ||
| let className = styles.todoLabel; | ||
| if (done) { | ||
| className += ` ${styles.done}`; | ||
| } | ||
|
|
||
| return <span className={className}>{done ? "DONE" : "TO DO"}</span>; | ||
| } | ||
|
Comment on lines
+3
to
+10
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (제안) 좀 더 확장성있게 만들어볼 수 있겠군요 !const colorMap = {
success: styles.success,
warning: styles.warning,
error: styles.error,
info: styles.info,
};
function Label({ status = "info", children }) {
const baseClass = styles.label;
const statusClass = colorMap[status] || "";
const className = `${baseClass} ${statusClass}`.trim();
return <span className={className}>{children}</span>;
}위 제안드린 코드는 디자인에 명시된 프로퍼티는 아니지만 핵심은 "컬러를 결정짓는 |
||
|
|
||
| export default TodoLabel; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| .todoLabel { | ||
| background-color: var(--color-lime-300); | ||
| color: var(--color-green-700); | ||
| font-family: "HsSantoki20"; | ||
| font-size: 18px; | ||
| font-weight: 400; | ||
| line-height: 100%; | ||
| padding: 6px 27px; | ||
| border-radius: 24px; | ||
| } | ||
|
|
||
| .todoLabel.done { | ||
| background-color: var(--color-green-700); | ||
| color: var(--color-amber-300); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import TodoLabel from "./todo-label"; | ||
| import styles from "./todo-list.module.css"; | ||
|
|
||
| function EmptyMessage({ children }) { | ||
| const chunks = children.split("\n"); | ||
| let messageChunks = []; | ||
| for (const chunk of chunks) { | ||
| messageChunks.push(<span key={chunk}>{chunk}</span>); | ||
| messageChunks.push(<br key={`${chunk}-br`} />); | ||
| } | ||
| messageChunks.pop(); | ||
| return <p>{messageChunks}</p>; | ||
| } | ||
|
|
||
| function TodoList({ done = false, children }) { | ||
| const emptyMessage = done | ||
| ? "아직 다 한 일이 없어요.\n해야 할 일을 체크해보세요!" | ||
| : "할 일이 없어요.\nTODO를 새롭게 추가해주세요!"; | ||
|
|
||
| return ( | ||
| <div className={styles.todoList}> | ||
| <TodoLabel done={done} /> | ||
| {children.length > 0 ? ( | ||
| <div className={styles.todoListContent}>{children}</div> | ||
| ) : ( | ||
| <div className={styles.todoListEmptyContainer}> | ||
| <img | ||
| src={`/images/${done ? "done" : "todo"}-list-empty.svg`} | ||
| alt="empty" | ||
| /> | ||
| <EmptyMessage>{emptyMessage}</EmptyMessage> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default TodoList; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| .todoList { | ||
| width: 100%; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: flex-start; | ||
| gap: 16px; | ||
| } | ||
|
|
||
| .todoListContent { | ||
| width: 100%; | ||
| display: flex; | ||
| flex-direction: column; | ||
| gap: 16px; | ||
| } | ||
|
|
||
| .todoListEmptyContainer { | ||
| width: 100%; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| } | ||
| .todoListEmptyContainer img { | ||
| aspect-ratio: 1 / 1; | ||
| } | ||
| .todoListEmptyContainer p { | ||
| margin: 0; | ||
| font-size: 16px; | ||
| font-weight: 700; | ||
| line-height: 100%; | ||
| color: var(--color-state-400); | ||
| text-align: center; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import styles from "./todo.module.css"; | ||
|
|
||
| function Todo({ children, checked = false, ...props }) { | ||
| let className = styles.todo; | ||
| if (checked) { | ||
| className += ` ${styles.checked}`; | ||
| } | ||
|
|
||
| const checkImage = checked | ||
| ? "/images/checkbox-checked.svg" | ||
| : "/images/checkbox.svg"; | ||
|
|
||
| return ( | ||
| <div className={className} {...props}> | ||
| <div className={styles.checkImage}> | ||
| <img src={checkImage} alt="check" /> | ||
| </div> | ||
| <p className={styles.title}>{children}</p> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export default Todo; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| .todo { | ||
| background-color: white; | ||
| border: 2px solid var(--color-state-900); | ||
| border-radius: 27px; | ||
| font-size: 16px; | ||
| font-weight: 400; | ||
| line-height: 100%; | ||
| color: var(--color-state-800); | ||
| display: flex; | ||
| align-items: center; | ||
| gap: 16px; | ||
| height: 50px; | ||
| padding: 9px 12px; | ||
| cursor: pointer; | ||
| } | ||
| .todo.checked { | ||
| background-color: var(--color-violet-100); | ||
| text-decoration: line-through; | ||
| } | ||
|
|
||
| .todo .checkImage { | ||
| width: 32px; | ||
| height: 32px; | ||
| } | ||
|
|
||
| .todo .checkImage img { | ||
| width: 100%; | ||
| height: 100%; | ||
| } | ||
|
|
||
| .todo .title { | ||
| flex-grow: 1; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| const { useState } = require("react"); | ||
|
|
||
| export function useAsyncCall() { | ||
| const [isLoading, setLoading] = useState(false); | ||
|
|
||
| const execute = async (callback) => { | ||
| setLoading(true); | ||
| try { | ||
| const result = await callback(); | ||
| return result; | ||
| } finally { | ||
| setLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| return [isLoading, execute]; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오우...
Object.freeze라니. ㄷㄷ 꼼꼼하시군요? 👍어라 근데 왜 타입스크립트는 안사용하셨나요? 🤔
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
강의가 JavaScript로 진행되어서 별 생각 없이 이어지는 미션도 JavaScript를 사용했어요 ㅎㅎ.. 미션 10은 TypeScript로 바꾼 다음 진행할 예정입니다!