From eb499bd63da132df6ff00076938f7641cdbe32be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=8B=E1=85=B5=E1=84=8C=E1=85=A2=E1=84=92=E1=85=A1?= Date: Mon, 28 Aug 2023 11:17:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/button/Button.tsx | 51 ++++++++ .../jurumarble/src/components/button/Chip.tsx | 8 ++ .../src/components/divide/DivideLine.tsx | 15 +++ apps/jurumarble/src/components/index.ts | 8 ++ .../jurumarble/src/components/input/Input.tsx | 29 +++++ .../modal/FloatComponentTemplate.tsx | 62 ++++++++++ .../src/components/modal/ModalTemplate.tsx | 64 ++++++++++ .../src/components/modal/Portal.tsx | 23 ++++ .../src/components/selectBox/Option.tsx | 23 ++++ .../src/components/selectBox/OptionList.tsx | 46 ++++++++ .../src/components/selectBox/Select.tsx | 45 ++++++++ .../src/components/selectBox/SelectButton.tsx | 36 ++++++ .../src/components/selectBox/index.ts | 4 + .../jurumarble/src/lib/styles/globalStyles.ts | 4 +- apps/jurumarble/src/lib/styles/index.ts | 6 + apps/jurumarble/src/lib/styles/media.ts | 21 ++++ apps/jurumarble/src/lib/styles/transitions.ts | 109 ++++++++++++++++++ 17 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 apps/jurumarble/src/components/button/Button.tsx create mode 100644 apps/jurumarble/src/components/button/Chip.tsx create mode 100644 apps/jurumarble/src/components/divide/DivideLine.tsx create mode 100644 apps/jurumarble/src/components/index.ts create mode 100644 apps/jurumarble/src/components/input/Input.tsx create mode 100644 apps/jurumarble/src/components/modal/FloatComponentTemplate.tsx create mode 100644 apps/jurumarble/src/components/modal/ModalTemplate.tsx create mode 100644 apps/jurumarble/src/components/modal/Portal.tsx create mode 100644 apps/jurumarble/src/components/selectBox/Option.tsx create mode 100644 apps/jurumarble/src/components/selectBox/OptionList.tsx create mode 100644 apps/jurumarble/src/components/selectBox/Select.tsx create mode 100644 apps/jurumarble/src/components/selectBox/SelectButton.tsx create mode 100644 apps/jurumarble/src/components/selectBox/index.ts create mode 100644 apps/jurumarble/src/lib/styles/index.ts create mode 100644 apps/jurumarble/src/lib/styles/media.ts create mode 100644 apps/jurumarble/src/lib/styles/transitions.ts diff --git a/apps/jurumarble/src/components/button/Button.tsx b/apps/jurumarble/src/components/button/Button.tsx new file mode 100644 index 00000000..d048fd36 --- /dev/null +++ b/apps/jurumarble/src/components/button/Button.tsx @@ -0,0 +1,51 @@ +import React from "react"; +import styled, { css } from "styled-components"; + +interface ButtonStyledProps { + /** + * 버튼 타입 + */ + variant?: "primary" | "inactive" | "outline" | "warning"; + /** + * 버튼 가로 길이 + */ + width?: `${number}px` | `${number}%`; + /** + * 버튼 세로 길이 + */ + height?: `${number}px` | `${number}%`; + /** + * 버튼 둥글기 + */ + borderRadius?: string; +} + +interface ButtonProps extends React.ButtonHTMLAttributes, ButtonStyledProps { + children: React.ReactNode; +} + +function Button({ width, height, variant, borderRadius = "4px", children, ...rest }: ButtonProps) { + return ( + + {children} + + ); +} + +const ButtonStyled = styled.button` + display: inline-flex; + justify-content: center; + align-items: center; + width: ${({ width }) => width}; + height: ${({ height }) => height}; + border-radius: ${({ borderRadius }) => borderRadius}; + font-weight: 700; +`; + +export default Button; diff --git a/apps/jurumarble/src/components/button/Chip.tsx b/apps/jurumarble/src/components/button/Chip.tsx new file mode 100644 index 00000000..cf6b729f --- /dev/null +++ b/apps/jurumarble/src/components/button/Chip.tsx @@ -0,0 +1,8 @@ +import React from "react"; + +// TODO: Chip 컴포넌트 구현 +function Chip() { + return
Chip
; +} + +export default Chip; diff --git a/apps/jurumarble/src/components/divide/DivideLine.tsx b/apps/jurumarble/src/components/divide/DivideLine.tsx new file mode 100644 index 00000000..46e338b1 --- /dev/null +++ b/apps/jurumarble/src/components/divide/DivideLine.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import styled from "styled-components"; + +function DivideLine() { + return ; +} + +const Line = styled.div` + width: 100%; + height: 1px; + background-color: ${({ theme }) => theme.colors.line_01}; + margin: 24px 0; +`; + +export default DivideLine; diff --git a/apps/jurumarble/src/components/index.ts b/apps/jurumarble/src/components/index.ts new file mode 100644 index 00000000..de3d00f9 --- /dev/null +++ b/apps/jurumarble/src/components/index.ts @@ -0,0 +1,8 @@ +export { default as Button } from "./button/Button"; +export { default as Portal } from "./modal/Portal"; +export { default as ModalTemplate } from "./modal/ModalTemplate"; +export { default as Input } from "./input/Input"; +export { default as FloatComponentTemplate } from "./modal/FloatComponentTemplate"; +export { default as DivideLine } from "./divide/DivideLine"; +export { default as ImageUploadButton } from "./ImageUploadButton"; +export * from "./selectBox"; diff --git a/apps/jurumarble/src/components/input/Input.tsx b/apps/jurumarble/src/components/input/Input.tsx new file mode 100644 index 00000000..50aabd4b --- /dev/null +++ b/apps/jurumarble/src/components/input/Input.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import styled, { css } from "styled-components"; + +interface InputProps extends React.InputHTMLAttributes { + /** + * Input 형태 + */ + variant?: "standard" | "outlined"; + /** + * Input 가로 길이 + */ + width: `${number}px` | `${number}%` | "auto"; + /** + * 포커스 여뿌 + * 기본 값 : false + */ + autoFocus?: boolean; +} + +function Input({ width, height, variant, ...rest }: InputProps) { + return ; +} + +const InputStyled = styled.input` + color: ${({ theme }) => theme.colors.black_01}; + width: ${({ width }) => width}; +`; + +export default Input; diff --git a/apps/jurumarble/src/components/modal/FloatComponentTemplate.tsx b/apps/jurumarble/src/components/modal/FloatComponentTemplate.tsx new file mode 100644 index 00000000..ac9a8d9b --- /dev/null +++ b/apps/jurumarble/src/components/modal/FloatComponentTemplate.tsx @@ -0,0 +1,62 @@ +import { Portal, transitions } from "@monorepo/ui"; +import React from "react"; +import styled from "styled-components"; + +/** + * @description + * 검은 배경에 특정 컴포넌트만 띄워주는 모달 + * + */ + +interface Props { + onToggleModal: () => void; + children: React.ReactNode; +} + +function FloatComponentTemplate({ onToggleModal, children }: Props) { + return ( + + + {children} + + + + ); +} + +const Inner = styled.div` + position: absolute; + background-color: unset; + z-index: 2000; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 24px; +`; + +const ModalTemplateBlock = styled.div` + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 1000000; +`; + +const ModalBackground = styled.div` + display: block; + width: 100%; + height: 100%; + background-color: black; + position: absolute; + left: 0; + top: 0; + opacity: 0.4; + z-index: 1100; +`; + +export default FloatComponentTemplate; diff --git a/apps/jurumarble/src/components/modal/ModalTemplate.tsx b/apps/jurumarble/src/components/modal/ModalTemplate.tsx new file mode 100644 index 00000000..132ca502 --- /dev/null +++ b/apps/jurumarble/src/components/modal/ModalTemplate.tsx @@ -0,0 +1,64 @@ +import { transitions } from "lib/styles"; +import React from "react"; +import styled from "styled-components"; +import Portal from "./Portal"; + +interface ModalInnerStyled { + width: `${number}px` | `${number}%`; + height: `${number}px` | `${number}%`; +} + +interface ModalTemplateProps extends ModalInnerStyled { + children: React.ReactNode; + onToggleModal: () => void; +} + +function ModalTemplate({ width, height, children, onToggleModal, ...rest }: ModalTemplateProps) { + return ( + + + e.stopPropagation()}> + {children} + + + + + ); +} + +const ModalTemplateBlock = styled.div` + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 9999; +`; + +const ModalInner = styled.div` + position: absolute; + z-index: 9999; + background-color: white; + top: 0; + bottom: 0; + right: 0; + left: 0; + margin: auto; + width: ${({ width }) => width}; + height: ${({ height }) => height}; + border-radius: 12px; + animation: ${transitions.fadeIn} 0.4s ease-in-out; +`; + +const ModalBackground = styled.div` + display: block; + width: 100%; + height: 100%; + background-color: black; + position: absolute; + left: 0; + top: 0; + opacity: 0.4; +`; + +export default ModalTemplate; diff --git a/apps/jurumarble/src/components/modal/Portal.tsx b/apps/jurumarble/src/components/modal/Portal.tsx new file mode 100644 index 00000000..e205c356 --- /dev/null +++ b/apps/jurumarble/src/components/modal/Portal.tsx @@ -0,0 +1,23 @@ +import { ReactElement, ReactNode, ReactPortal, useEffect, useState } from "react"; +import ReactDOM from "react-dom"; + +interface PortalProps { + children: ReactNode; + selector: string; +} + +function Portal({ children, selector }: PortalProps): ReactPortal | null { + const [element, setElement] = useState(null); + + useEffect(() => { + setElement(document.querySelector(selector)); + }, []); + + if (!element) { + return null; + } + + return ReactDOM.createPortal(children, element); +} + +export default Portal; diff --git a/apps/jurumarble/src/components/selectBox/Option.tsx b/apps/jurumarble/src/components/selectBox/Option.tsx new file mode 100644 index 00000000..e34789f8 --- /dev/null +++ b/apps/jurumarble/src/components/selectBox/Option.tsx @@ -0,0 +1,23 @@ +import styled from "styled-components"; + +interface Props { + label: string; + onChangeSelectedOption: () => void; +} + +function Option({ label, onChangeSelectedOption }: Props) { + return ( +
  • + {label} +
  • + ); +} + +const Li = styled.li` + padding: 14px 34px; + :hover { + background-color: ${({ theme }) => theme.palette.background.selectedSoft}; + } +`; + +export default Option; diff --git a/apps/jurumarble/src/components/selectBox/OptionList.tsx b/apps/jurumarble/src/components/selectBox/OptionList.tsx new file mode 100644 index 00000000..1331c0ec --- /dev/null +++ b/apps/jurumarble/src/components/selectBox/OptionList.tsx @@ -0,0 +1,46 @@ +import styled, { css } from "styled-components"; +import Option from "./Option"; + +interface Option { + value: string; + label: string; +} +interface Props { + options: Option[]; + // @todo 수정하기 + onChangeSelectedOption: (value: any) => void; + onToggleOpen: () => void; +} + +function OptionList({ options, onChangeSelectedOption, onToggleOpen }: Props) { + return ( + // @todo 고유한 id를 만들어야함 +
      + {options.map(({ value, label }) => ( +
    + ); +} + +const Ul = styled.ul` + position: absolute; + margin-top: 8px; + border-radius: 8px; + box-shadow: 0 0 40px 0 rgba(0, 0, 0, 0.1); + z-index: 99; + ${({ theme }) => + css` + border: solid 1px ${theme.palette.border.base}; + background-color: ${theme.palette.background.white}; + `} +`; + +export default OptionList; diff --git a/apps/jurumarble/src/components/selectBox/Select.tsx b/apps/jurumarble/src/components/selectBox/Select.tsx new file mode 100644 index 00000000..01c006be --- /dev/null +++ b/apps/jurumarble/src/components/selectBox/Select.tsx @@ -0,0 +1,45 @@ +import { useOutsideClick } from "@monorepo/hooks"; +import { Option, OptionList, SelectButton } from "@monorepo/ui"; + +interface Option { + value: string; + label: string; +} + +interface SelectProps extends React.HTMLAttributes { + defaultValue: string; + onChangeSelectedOption: (value: string) => void; + options: Option[]; + isOpen: boolean; + onToggleOpen: () => void; + children?: React.ReactNode; +} + +function Select({ + defaultValue, + onChangeSelectedOption, + options, + isOpen, + onToggleOpen, + children, +}: SelectProps) { + const { targetEl } = useOutsideClick(isOpen, onToggleOpen); + + return ( +
    + + {options.find(({ value }) => value === defaultValue)?.label} + {children} + + {isOpen ? ( + + ) : null} +
    + ); +} + +export default Select; diff --git a/apps/jurumarble/src/components/selectBox/SelectButton.tsx b/apps/jurumarble/src/components/selectBox/SelectButton.tsx new file mode 100644 index 00000000..7600791e --- /dev/null +++ b/apps/jurumarble/src/components/selectBox/SelectButton.tsx @@ -0,0 +1,36 @@ +import styled from "styled-components"; + +interface SelectProps { + children: React.ReactNode; + onChangeOpen: () => void; +} + +function SelectButton({ children, onChangeOpen }: SelectProps) { + return ( + <> + + {children} + + + ); +} + +const StyledButton = styled.button` + display: contents; +`; + +const SelectedText = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +export default SelectButton; diff --git a/apps/jurumarble/src/components/selectBox/index.ts b/apps/jurumarble/src/components/selectBox/index.ts new file mode 100644 index 00000000..66b3dd86 --- /dev/null +++ b/apps/jurumarble/src/components/selectBox/index.ts @@ -0,0 +1,4 @@ +export { default as Option } from "./Option"; +export { default as OptionList } from "./OptionList"; +export { default as SelectButton } from "./SelectButton"; +export { default as Select } from "./Select"; diff --git a/apps/jurumarble/src/lib/styles/globalStyles.ts b/apps/jurumarble/src/lib/styles/globalStyles.ts index a37a006b..fdc9d420 100644 --- a/apps/jurumarble/src/lib/styles/globalStyles.ts +++ b/apps/jurumarble/src/lib/styles/globalStyles.ts @@ -1,6 +1,8 @@ import { createGlobalStyle } from "styled-components"; import reset from "styled-reset"; -export const GlobalStyles = createGlobalStyle` +const GlobalStyles = createGlobalStyle` ${reset} `; + +export default GlobalStyles; diff --git a/apps/jurumarble/src/lib/styles/index.ts b/apps/jurumarble/src/lib/styles/index.ts new file mode 100644 index 00000000..86aea7bd --- /dev/null +++ b/apps/jurumarble/src/lib/styles/index.ts @@ -0,0 +1,6 @@ +export * from "./theme"; +export { default as transitions } from "./transitions"; +export { default as GlobalStyles } from "./globalStyles"; +export { default as StyledComponents } from "./StyledComponents"; +export * from "./media"; +export * from "./depths"; diff --git a/apps/jurumarble/src/lib/styles/media.ts b/apps/jurumarble/src/lib/styles/media.ts new file mode 100644 index 00000000..aa42938a --- /dev/null +++ b/apps/jurumarble/src/lib/styles/media.ts @@ -0,0 +1,21 @@ +export const mediaQuery = (minWidth: number) => `@media (min-width: ${minWidth}px)`; + +export const mediaSize = { + xxlarge: 1920, + xlarge: 1366, + large: 1024, + medium: 768, + small: 414, + xsmall: 320, +} as const; + +const { xxlarge, xlarge, large, medium, small, xsmall } = mediaSize; + +export const media = { + xxlarge: mediaQuery(xxlarge), + xlarge: mediaQuery(xlarge), + large: mediaQuery(large), + medium: mediaQuery(medium), + small: mediaQuery(small), + xsmall: mediaQuery(xsmall), +}; diff --git a/apps/jurumarble/src/lib/styles/transitions.ts b/apps/jurumarble/src/lib/styles/transitions.ts new file mode 100644 index 00000000..3d64bf6d --- /dev/null +++ b/apps/jurumarble/src/lib/styles/transitions.ts @@ -0,0 +1,109 @@ +import { keyframes } from "styled-components"; + +const fadeIn = keyframes` + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +`; + +const fadeOut = keyframes` + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +`; + +const popInFromBottom = keyframes` + 0% { + opacity: 0; + transform: translateY(60px); + } + 100% { + opacity: 1; + transform: translateY(0px); + }`; + +const delaypopInFromBottom = keyframes` +0% { + opacity: 0; + transform: translateY(60px); +} +50% { + opacity: 0; + transform: translateY(60px); +} +100% { + opacity: 1; + transform: translateY(0px); +}`; + +const popOutToBottom = keyframes` + 0% { + opacity: 1; + transform: translateY(0px) scale(1.0); + + } + 100% { + opacity: 0; + transform: translateY(400px) scale(0.75); + }`; + +const popIn = keyframes` + 0% { + opacity: 0.7; + transform: scale3d(0.8, 0.8, 1); + } + 100% { + opacity: 1; + transform: scale3d(1, 1, 1); + } +`; + +const slideUp = keyframes` + 0% { + transform: translateY(40%); + } + 100% { + transform: translateY(0%); + }; +`; + +const slideDown = keyframes` + 0% { + transform: translateY(0%); + } + 100% { + transform: translateY(40%); + }; +`; + +const blink = keyframes` + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +`; + +const transitions = { + fadeIn, + fadeOut, + popInFromBottom, + delaypopInFromBottom, + popOutToBottom, + popIn, + slideUp, + slideDown, + blink, +}; + +export default transitions;