diff --git a/components/board/BoardDetailArticle.module.scss b/components/board/BoardDetailArticle.module.scss new file mode 100644 index 00000000..81dafb4d --- /dev/null +++ b/components/board/BoardDetailArticle.module.scss @@ -0,0 +1,100 @@ +@mixin font-style($size, $weight, $lineHeight, $color) { + font-size: $size; + font-weight: $weight; + color: $color; + line-height: $lineHeight; +} + +.freeboard-detail-main { + width: 1200px; + margin: 0 auto; + padding: 32px 0; +} + +.board-detail-article-section { + display: flex; + flex-direction: column; + justify-content: center; + margin-bottom: 32px; +} + +.title-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 16px; + + h1 { + @include font-style(20px, 700, 32px, var(--gray800)); + } + + img { + width: 24px; + height: 24px; + } +} + +.content-wrapper { + display: flex; + align-items: center; +} + +.user-wrapper { + display: flex; + align-items: center; + + img { + width: 40px; + height: 40px; + margin-right: 16px; + } + + h3 { + @include font-style(14px, 500, 24px, var(--gray600)); + margin-right: 8px; + } + + span { + @include font-style(14px, 400, 24px, var(--gray400)); + } +} + +.favorite-wrapper { + display: flex; + align-items: center; + padding: 4px 12px; + border: 1px solid var(--gray200); + border-radius: 35px; + gap: 3px; + + img { + width: 32px; + height: 32px; + } + + span { + @include font-style(16px, 500, 26px, var(--gray500)); + } +} + +.vertical-divider { + border-left: 1px solid var(--gray200); + width: 0; + height: 40px; + margin: 0 32px; +} + +.horizontal-divider { + border-top: 1px solid var(--gray200); + width: 100%; + height: 0px; + margin: 16px 0 24px; +} + +.content { + @include font-style(18px, 400, 26px, var(--gray800)); +} + +.comment { + margin-top: 40px; +} diff --git a/components/board/BoardDetailArticle.tsx b/components/board/BoardDetailArticle.tsx new file mode 100644 index 00000000..e78319c4 --- /dev/null +++ b/components/board/BoardDetailArticle.tsx @@ -0,0 +1,41 @@ +import { Article } from "@/types/articleTypes"; +import styles from "./BoardDetailArticle.module.scss"; +import defalutProfleImg from "@/public/images/icons/ic_user.svg"; +import kebabImg from "@/public/images/icons/ic_kebab.svg"; +import favoriteImg from "@/public/images/icons/ic_heart.svg"; +import { getFormatTime } from "@/utils/Utils"; + +export default function BoardDetailArticle({ + article: { + title, + content, + writer: { nickname }, + likeCount, + createdAt, + }, +}: { + article: Article; +}) { + return ( +
+
+

{title}

+ +
+
+
+ +

{nickname}

+ {getFormatTime(createdAt, false)} +
+ +
+ + {likeCount} +
+
+ + {content} +
+ ); +} diff --git a/components/board/BoardDetailConfig.ts b/components/board/BoardDetailConfig.ts new file mode 100644 index 00000000..485d37b2 --- /dev/null +++ b/components/board/BoardDetailConfig.ts @@ -0,0 +1,25 @@ +import { CommentObject } from "@/types/articleTypes"; +import emptyImg from "@/public/images/icons/Img_reply_empty.svg"; +import { FieldInfo } from "@/types/registerTypes"; + +export const commentInfo: CommentObject = { + comments: [], + content: "아직 댓글이 없어요,\n지금 댓글을 달아보세요!", + imgUrl: { + src: emptyImg.src, + alt: "빈 코멘트", + }, +}; + +export enum FIELDTYPE { + TITLE = "댓글달기", +} + +export const fields: { [id: string]: FieldInfo } = { + [FIELDTYPE.TITLE]: { + id: FIELDTYPE.TITLE, + name: FIELDTYPE.TITLE, + type: "input", + placeholder: "댓글을 입력해주세요", + }, +}; diff --git a/components/boards/AllArticleItem.tsx b/components/boards/AllArticleItem.tsx index 523d27c9..fe1891f5 100644 --- a/components/boards/AllArticleItem.tsx +++ b/components/boards/AllArticleItem.tsx @@ -2,20 +2,21 @@ import { ArticleProp } from "@/types/articleTypes"; import styles from "./Freeboard.module.scss"; import favoriteImg from "@/public/images/icons/ic_heart.svg"; import { getFormatTime } from "@/utils/Utils"; +import noImg from "@/public/images/icons/no_img.svg"; export default function AllArticleItem({ article: { createdAt, image, likeCount, title, writer }, }: ArticleProp) { + const titleImage = image ? image : noImg.src; + return (

{title}

- {image && ( -
- 대표 이미지 -
- )} +
+ 대표 이미지 +
diff --git a/components/boards/AllArticleList.tsx b/components/boards/AllArticleList.tsx index aa60d49b..5553d7a8 100644 --- a/components/boards/AllArticleList.tsx +++ b/components/boards/AllArticleList.tsx @@ -4,10 +4,17 @@ import AllArticleItem from "./AllArticleItem"; import { Article, ArticleApiData } from "@/types/articleTypes"; import SearchInput from "@/components/layout/SearchInput"; import Button from "@/components/layout/Button"; -import Dropdown from "@/components/layout/Dropdown"; import Link from "next/link"; import { getArticle } from "@/lib/articleApi"; import { useRouter } from "next/router"; +import SortDropdown from "../layout/Dropdown/SortDropdown"; +import { + defaultOrderType, + ORDER_TYPE_ENUM, + orderTypeKeysKR, + orderTypeKR, + orderTypeUS, +} from "@/constants/orderConstants"; export default function AllArticleList({ initialArticles, @@ -15,13 +22,14 @@ export default function AllArticleList({ initialArticles: Article[]; }) { const [articles, setArticles] = useState(initialArticles); - const [orderBy, setOrderBy] = useState("recent"); + const [orderBy, setOrderBy] = useState(ORDER_TYPE_ENUM.RECENT); + const items = orderTypeKeysKR; const router = useRouter(); const keyword = router.query.q; const handleOrderChange = (option: string) => { - if (option === "recent" || option === "like") setOrderBy(option); + setOrderBy(orderTypeUS[option as keyof typeof orderTypeUS]); }; const fetchData = async ({ orderBy, pageSize, keyword }: ArticleApiData) => { @@ -58,11 +66,15 @@ export default function AllArticleList({

게시글

- +
- +
{articles.length ? articles.map((article) => ( diff --git a/components/boards/BestArticleItem.tsx b/components/boards/BestArticleItem.tsx index 0e3bea38..21dd3ced 100644 --- a/components/boards/BestArticleItem.tsx +++ b/components/boards/BestArticleItem.tsx @@ -3,21 +3,22 @@ import badgeImg from "@/public/images/icons/img_badge.svg"; import favoriteImg from "@/public/images/icons/ic_heart.svg"; import { ArticleProp } from "@/types/articleTypes"; import { getFormatTime } from "@/utils/Utils"; +import noImg from "@/public/images/icons/no_img.svg"; export default function BestArticleItem({ article: { createdAt, image, likeCount, title, writer }, }: ArticleProp) { + const titleImage = image ? image : noImg.src; + return (
Best 뱃지

{title}

- {image && ( -
- 대표 이미지 -
- )} +
+ 대표 이미지 +
diff --git a/components/boards/BestArticleList.tsx b/components/boards/BestArticleList.tsx index 0accd18e..186d8d51 100644 --- a/components/boards/BestArticleList.tsx +++ b/components/boards/BestArticleList.tsx @@ -5,15 +5,15 @@ import { getArticle } from "@/lib/articleApi"; import { Article, ArticleApiData } from "@/types/articleTypes"; import Link from "next/link"; import useDeviceType from "@/hooks/useDeviceType"; -import { DeviceTypePageSize } from "@/types/articleTypes"; +import { DeviceTypePageSize } from "@/constants/deviceSizesConstants"; +import { ORDER_TYPE_ENUM } from "@/constants/orderConstants"; -const ORDERBY = "like"; +const { MOBILE_PAGE_SIZE, TABLET_PAGE_SIZE, DESKTOP_PAGE_SIZE } = + DeviceTypePageSize; export default function BestArticleList() { const [articles, setArticles] = useState([]); const { isMobile, isTablet } = useDeviceType(); - const { MOBILE_PAGE_SIZE, TABLET_PAGE_SIZE, DESKTOP_PAGE_SIZE } = - DeviceTypePageSize; const pageSize = isTablet ? TABLET_PAGE_SIZE @@ -32,7 +32,7 @@ export default function BestArticleList() { }; useEffect(() => { - fetchData({ orderBy: ORDERBY, pageSize: pageSize }); + fetchData({ orderBy: ORDER_TYPE_ENUM.LIKE, pageSize: pageSize }); }, [pageSize]); return ( diff --git a/components/layout/Button.tsx b/components/layout/Button.tsx index 53375c97..d2a98aa7 100644 --- a/components/layout/Button.tsx +++ b/components/layout/Button.tsx @@ -1,11 +1,25 @@ -import { ReactNode } from "react"; import styles from "./Button.module.scss"; import Link from "next/link"; +import { ButtonProps } from "@/types/commonTypes"; + +export default function Button({ + href = "", + children, + disabled = false, +}: ButtonProps) { + if (!href) { + return ( + + ); + } -export default function Button({ children }: { children: ReactNode }) { return ( - - + + ); } diff --git a/components/layout/Comment.tsx/Comment.tsx b/components/layout/Comment.tsx/Comment.tsx new file mode 100644 index 00000000..19e5720a --- /dev/null +++ b/components/layout/Comment.tsx/Comment.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import kebabImg from "@/public/images/icons/ic_kebab.svg"; +import { getFormatTime, getElapsedTime } from "@/utils/Utils"; +import { CommentType } from "@/types/articleTypes"; +import styles from "./Commtent.module.scss"; +import defaultProfileImg from "@/public/images/icons/ic_user.svg"; + +export default function Comment({ + comment: { + content, + writer: { image, nickname }, + createdAt, + }, +}: { + comment: CommentType; +}) { + const elapsedTime = getElapsedTime(createdAt); + const formattedTime = getFormatTime(createdAt); + const profileImg = image ? image : defaultProfileImg.src; + + return ( +
+
+

{content}

+ 더보기 +
+
+ {`${nickname}의 +
+

{nickname}

+

+ {formattedTime} {`(${elapsedTime})`} +

+
+
+
+
+ ); +} diff --git a/components/layout/Comment.tsx/CommentsSection.tsx b/components/layout/Comment.tsx/CommentsSection.tsx new file mode 100644 index 00000000..eece4a9c --- /dev/null +++ b/components/layout/Comment.tsx/CommentsSection.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import Comment from "./Comment"; +import { CommentsSectionProp } from "@/types/articleTypes"; +import styles from "./Commtent.module.scss"; + +export default function CommentsSection({ + comments: { + comments, + imgUrl: { src, alt }, + content, + }, + className, +}: CommentsSectionProp) { + const isCommentEmpty = !comments.length; + + return ( +
+ {isCommentEmpty && ( +
+ {alt} +

{content}

+
+ )} + {comments.map((comment) => ( + + ))} +
+ ); +} diff --git a/components/layout/Comment.tsx/Commtent.module.scss b/components/layout/Comment.tsx/Commtent.module.scss new file mode 100644 index 00000000..e0343825 --- /dev/null +++ b/components/layout/Comment.tsx/Commtent.module.scss @@ -0,0 +1,114 @@ +@mixin font-style($size, $weight, $lineHeight, $color) { + font-size: $size; + font-weight: $weight; + color: $color; + line-height: $lineHeight; +} + +.kebab-image { + cursor: pointer; + width: 24px; + height: 24px; +} + +.go-back-button { + margin: 40px auto 40px; + background-color: var(--activate-button-blue); + padding: 12px 71px 12px 71px; + border-radius: 40px; + @include font-style(18px, 600, 24px, white); + font-family: inherit; + + display: flex; + align-items: center; + justify-content: center; + gap: 10px; + + img { + width: 24px; + height: 24px; + } +} + +.comments-section { + p { + @include font-style(16px, 400, 22.4px, var(--gray800)); + } + + img { + width: 40px; + height: 40px; + } + + .comment { + margin-bottom: 24px; + + .comment-content-Wrapper { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 24px; + + img { + width: 24px; + height: 24px; + } + } + .writer-wrapper { + display: flex; + align-items: center; + gap: 8px; + + img { + width: 40px; + height: 40px; + } + + .nickname-wrapper { + display: flex; + flex-direction: column; + justify-content: center; + gap: 4px; + + h3 { + @include font-style(14px, 400, 16.71px, var(--gray600)); + } + + h4 { + @include font-style(12px, 400, 14.32px, var(--gray400)); + } + } + } + } + + .empty-comment { + display: flex; + flex-direction: column; + align-items: center; + + img { + width: 200px; + height: 200px; + } + + h3 { + @include font-style(16px, 400, 24px, var(--gray400)); + text-align: center; + } + } + + .horizontal-divider { + border-top: 1px solid var(--gray200); + width: 100%; + height: 0px; + margin: 16px 0 24px; + } +} + +/* Tablet Styles */ +@media screen and (min-width: 768px) and (max-width: 1199px) { +} + +/* Mobile Styles */ +@media screen and (max-width: 767px) { +} diff --git a/components/layout/Comment.tsx/GoBackToListButton.tsx b/components/layout/Comment.tsx/GoBackToListButton.tsx new file mode 100644 index 00000000..1f576cf1 --- /dev/null +++ b/components/layout/Comment.tsx/GoBackToListButton.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import backImg from "@/public/images/icons/ic_back.svg"; +import Link from "next/link"; +import styles from "./Commtent.module.scss"; + +export default function GoBackToListButton({ href = "" }: { href?: string }) { + return ( + + + + ); +} diff --git a/components/layout/Dropdown.tsx b/components/layout/Dropdown.tsx deleted file mode 100644 index a8ce1cba..00000000 --- a/components/layout/Dropdown.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import arrowDownImg from "@/public/images/icons/ic_arrow_down.svg"; -import { MouseEvent, useState } from "react"; -import styles from "./Dropdown.module.scss"; -import smallDropdownImg from "@/public/images/icons/ic_sort.svg"; -import useDeviceType from "@/hooks/useDeviceType"; - -export default function Dropdown({ - onOrderChange, -}: { - onOrderChange: (newOption: string) => void; -}) { - const [seletedOption, setSeletedOption] = useState("최신순"); - const [isOpen, setIsOpen] = useState(false); - const { isMobile } = useDeviceType(); - - const handleToggleDropdown = () => { - setIsOpen(!isOpen); - }; - - const handleOptionClick = (event: MouseEvent) => { - const option = event.currentTarget.innerText; - setSeletedOption(option); - // 왜 textContent를 사용하면 계속 타입 오류가 발생할까 - // event.target.innerText 으로 할 때도 왜 타입 오류가 발생할까 - onOrderChange(option === "최신순" ? "recent" : "like"); - setIsOpen(false); - }; - - - - return ( -
- - {isOpen && ( -
-
- 최신순 -
- -
- 좋아요순 -
-
- )} -
- ); -} diff --git a/components/layout/Dropdown/Dropdown.module.scss b/components/layout/Dropdown/Dropdown.module.scss new file mode 100644 index 00000000..d748da2f --- /dev/null +++ b/components/layout/Dropdown/Dropdown.module.scss @@ -0,0 +1,35 @@ +.dropdown { + position: relative; + display: inline-block; +} + +.trigger { + font: inherit; + cursor: pointer; + outline: none; + box-shadow: none; + background-color: var(--white); + display: flex; + align-items: center; +} + +.menu { + position: absolute; + top: calc(100% + 10px); + right: 0; + background: var(--white); + z-index: 20; + cursor: pointer; +} + +.menu-item { + display: flex; + align-items: center; + justify-content: center; +} + +.divider { + margin: 0; + border-top: 1px solid var(--gray200); + width: 100%; +} diff --git a/components/layout/Dropdown/Dropdown.tsx b/components/layout/Dropdown/Dropdown.tsx new file mode 100644 index 00000000..fb8ce50f --- /dev/null +++ b/components/layout/Dropdown/Dropdown.tsx @@ -0,0 +1,82 @@ +import React, { ReactElement, useEffect, useRef, useState } from "react"; +import styles from "./Dropdown.module.scss"; +import useClickOutside from "@/hooks/useClickOutside"; +import { DropdownProps } from "@/types/uiTypes"; + +export default function Dropdown({ + trigger = <>, + items = [], + children = null, + onSelect = () => {}, + textDrop = true, + triggerClassName = "", + menuClassName = "", + itemClassName = "", + onToggle = () => {}, + ...rest +}: DropdownProps) { + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + const [selectedItem, setSelectedItem] = useState( + typeof trigger === "string" ? trigger : "" + ); + + const handleToggle = () => { + setIsOpen(!isOpen); + onToggle(!isOpen); + }; + + const handleItemClick = (item: string) => { + setSelectedItem(item); + setIsOpen(false); + onSelect(item); + onToggle(false); + }; + + useClickOutside(dropdownRef, () => { + setIsOpen(false); + onToggle(false); + }); + + const renderTrigger = () => { + if (typeof trigger === "string") { + return <>{selectedItem}; + } else if (React.isValidElement(trigger)) + return React.cloneElement(trigger); + }; + + useEffect(() => {}, [trigger]); + + return ( +
+
+ {renderTrigger()} +
+
    + {isOpen && ( + <> + {items + ? items.map((item, index) => ( + <> +
  • handleItemClick(item)} + > + {item} +
  • + {index < items.length - 1 && ( + + )} + + )) + : children} + + )} +
+
+ ); +} diff --git a/components/layout/Dropdown.module.scss b/components/layout/Dropdown/SortDropdown.module.scss similarity index 82% rename from components/layout/Dropdown.module.scss rename to components/layout/Dropdown/SortDropdown.module.scss index fc3ba94e..ad3f1eb9 100644 --- a/components/layout/Dropdown.module.scss +++ b/components/layout/Dropdown/SortDropdown.module.scss @@ -18,12 +18,10 @@ @include font-style(16px, 400, 24px, var(--gray800)); font-family: inherit; - display: flex; - align-items: center; justify-content: space-between; } -.menu-wrapper { +.menu { background-color: var(--white); position: absolute; transform: translateY(10px); @@ -34,19 +32,21 @@ border-radius: 12px; } -.menu-line { - border: 1px solid var(--gray200); -} - -.menu { +.menu-item { width: 132px; height: 42px; - display: flex; - align-items: center; - justify-content: center; + + &:first-child { + border-radius: 12px 12px 0 0; + } + + &:last-child { + border-bottom: none; + border-radius: 0 0 12px 12px; + } &:hover { - cursor: pointer; + background-color: #f1f1f1; } } diff --git a/components/layout/Dropdown/SortDropdown.tsx b/components/layout/Dropdown/SortDropdown.tsx new file mode 100644 index 00000000..1fcd3e1a --- /dev/null +++ b/components/layout/Dropdown/SortDropdown.tsx @@ -0,0 +1,61 @@ +import arrowDownImg from "@/public/images/icons/ic_arrow_down.svg"; +import { useEffect, useState } from "react"; +import styles from "./SortDropdown.module.scss"; +import smallDropdownImg from "@/public/images/icons/ic_sort.svg"; +import useDeviceType from "@/hooks/useDeviceType"; +import Dropdown from "./Dropdown"; +import { SortDropdownProps } from "@/types/uiTypes"; + +export default function SortDropdown({ + onOrderChange, + items = [], + defaultOrderType, +}: SortDropdownProps) { + const { isMobile } = useDeviceType(); + const [isMounted, setIsMounted] = useState(false); + const [selectedItem, setSelectedItem] = useState(defaultOrderType); + + useEffect(() => { + setIsMounted(true); + }, []); + + if (!isMounted) { + return null; + } + + const handleOptionClick = (option: string) => { + setSelectedItem(option); + onOrderChange(option); + }; + + const triggerContent = isMobile ? ( + 정렬 드롭다운 + ) : ( + <> + {selectedItem} + 정렬 드롭다운 + + ); + + return ( +
+ +
+ ); +} diff --git a/components/layout/FileInput/FileInput.module.scss b/components/layout/FileInput/FileInput.module.scss new file mode 100644 index 00000000..91ac37f0 --- /dev/null +++ b/components/layout/FileInput/FileInput.module.scss @@ -0,0 +1,138 @@ +@mixin font-style($size, $weight, $lineHeight, $color) { + font-size: $size; + font-weight: $weight; + color: $color; + line-height: $lineHeight; +} + +.file-input-section { + display: flex; + align-items: center; + gap: 24px; + margin: 0 0 var(--additem-gap-24); +} + +.file-input-wrapper { + width: 282px; + height: 282px; + border-radius: 12px; + background-color: #f3f4f6; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + gap: var(--additem-gap-init); + + position: relative; + + span { + @include font-style(16px, 400, 26px, var(--gray400)); + } +} + +.file-input-wrapper:hover { + background-color: #e5e6e8; +} + +.file-input { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 0; + margin: 0; + opacity: 0; + cursor: pointer; +} + +.add-image-wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 12px; +} + +.add-img { + width: 48px; + height: 48px; +} + +.preview-image-wrapper { + display: flex; + position: relative; +} + +.preview-image { + width: 282px; + height: 282px; + object-fit: contain; + border-radius: 12px; +} + +.cancel-button { + margin-left: 8px; + padding: 0; + width: 20px; + height: 20px; + background-color: var(--gray400); + border-radius: 9999px; + + position: absolute; + right: 14px; + top: 14px; + + display: flex; + justify-content: center; + align-items: center; + + &:hover { + background-color: var(--activate-button-blue); + } +} + +/* Tablet Styles */ +@media screen and (min-width: 768px) and (max-width: 1199px) { + .file-input-section { + width: 100%; + gap: 16px; + } + + .file-input-wrapper, + .preview-image-wrapper { + width: 162px; + height: 162px; + } + + .preview-image { + width: 100%; + height: 100%; + } +} + +/* Mobile Styles */ +@media screen and (max-width: 767px) { + .file-input-section { + width: 100%; + justify-content: space-between; + gap: 8px; + margin-bottom: 9px; + } + + .file-input-wrapper, + .preview-image-wrapper { + flex: 0 0 calc(50% - 8px); + width: auto; + } + + .preview-image { + width: 100%; + } + + .file-input { + width: 100%; + } +} diff --git a/components/layout/FileInput/FileInput.tsx b/components/layout/FileInput/FileInput.tsx new file mode 100644 index 00000000..f76ad508 --- /dev/null +++ b/components/layout/FileInput/FileInput.tsx @@ -0,0 +1,69 @@ +import React, { ChangeEvent } from "react"; +import { useEffect, useRef, useState } from "react"; +import logoImg from "@/public/images/icons/ic_plus.svg"; +import xIcon from "@/public/images/icons/ic_x.svg"; +import { FileInputType } from "../../../types/commonTypes"; +import styles from "./FileInput.module.scss"; + +export default function FileInput({ + value, + onChange, + className, +}: FileInputType) { + const [preview, setPreview] = useState(""); + const inputRef = useRef(null); + + const handleChange = (e: ChangeEvent) => { + const nextValue = e.target?.files?.[0]; + if (nextValue) { + onChange(nextValue); + } + }; + + const handleClearClick = () => { + const inputNode = inputRef.current; + if (!inputNode) return; + + inputNode.value = ""; + onChange(null); + }; + + useEffect(() => { + if (!value) return; + const nextPreview = URL.createObjectURL(value); + setPreview(nextPreview); + }, [value]); + + return ( +
+
+ +
+ 추가하기 + 이미지 등록 +
+
+ {value && ( +
+ 이미지 미리보기 + +
+ )} +
+ ); +} diff --git a/components/layout/Header/Header.tsx b/components/layout/Header/Header.tsx index 00a83ea1..35c4121e 100644 --- a/components/layout/Header/Header.tsx +++ b/components/layout/Header/Header.tsx @@ -39,7 +39,7 @@ const Header: React.FC = () => {
{!isLogin && ( - + )} {isLogin && 유저 로그인 프로필} diff --git a/components/layout/RegisterForm/InputField.module.scss b/components/layout/RegisterForm/InputField.module.scss new file mode 100644 index 00000000..12f43614 --- /dev/null +++ b/components/layout/RegisterForm/InputField.module.scss @@ -0,0 +1,24 @@ +@mixin font-style($size, $weight, $lineHeight, $color) { + font-size: $size; + font-weight: $weight; + color: $color; + line-height: $lineHeight; +} + +.input { + width: 100%; + font-family: inherit; + background-color: var(--gray100); + @include font-style(16px, 400, 26px, var(--gray800)); + padding: 16px 24px; + height: 56px; + border-radius: 12px; + + &::placeholder { + @include font-style(16px, 400, 26px, var(--gray400)); + } + + &.textarea { + height: 282px; + } +} diff --git a/components/layout/RegisterForm/InputField.tsx b/components/layout/RegisterForm/InputField.tsx new file mode 100644 index 00000000..ea3d15f1 --- /dev/null +++ b/components/layout/RegisterForm/InputField.tsx @@ -0,0 +1,47 @@ +import { InputFieldProps } from "@/types/registerTypes"; +import React from "react"; +import FileInput from "../FileInput/FileInput"; +import styles from "./InputField.module.scss"; + +export default function InputField({ + field, + value, + onChange, +}: InputFieldProps) { + switch (field.type) { + case "textarea": + return ( +