Skip to content

Commit

Permalink
feat: 공통 컴포넌트 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
Leejha committed Aug 28, 2023
1 parent d6fc84f commit eb499bd
Show file tree
Hide file tree
Showing 17 changed files with 553 additions and 1 deletion.
51 changes: 51 additions & 0 deletions apps/jurumarble/src/components/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLButtonElement>, ButtonStyledProps {
children: React.ReactNode;
}

function Button({ width, height, variant, borderRadius = "4px", children, ...rest }: ButtonProps) {
return (
<ButtonStyled
variant={variant}
width={width}
height={height}
borderRadius={borderRadius}
{...rest}
>
{children}
</ButtonStyled>
);
}

const ButtonStyled = styled.button<ButtonStyledProps>`
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;
8 changes: 8 additions & 0 deletions apps/jurumarble/src/components/button/Chip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react";

// TODO: Chip 컴포넌트 구현
function Chip() {
return <div>Chip</div>;
}

export default Chip;
15 changes: 15 additions & 0 deletions apps/jurumarble/src/components/divide/DivideLine.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react";
import styled from "styled-components";

function DivideLine() {
return <Line />;
}

const Line = styled.div`
width: 100%;
height: 1px;
background-color: ${({ theme }) => theme.colors.line_01};
margin: 24px 0;
`;

export default DivideLine;
8 changes: 8 additions & 0 deletions apps/jurumarble/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -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";
29 changes: 29 additions & 0 deletions apps/jurumarble/src/components/input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from "react";
import styled, { css } from "styled-components";

interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
/**
* Input 형태
*/
variant?: "standard" | "outlined";
/**
* Input 가로 길이
*/
width: `${number}px` | `${number}%` | "auto";
/**
* 포커스 여뿌
* 기본 값 : false
*/
autoFocus?: boolean;
}

function Input({ width, height, variant, ...rest }: InputProps) {
return <InputStyled type="text" width={width} height={height} variant={variant} {...rest} />;
}

const InputStyled = styled.input<InputProps>`
color: ${({ theme }) => theme.colors.black_01};
width: ${({ width }) => width};
`;

export default Input;
62 changes: 62 additions & 0 deletions apps/jurumarble/src/components/modal/FloatComponentTemplate.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Portal selector="#portal">
<ModalTemplateBlock onMouseDown={onToggleModal}>
<Inner>{children}</Inner>
<ModalBackground />
</ModalTemplateBlock>
</Portal>
);
}

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;
64 changes: 64 additions & 0 deletions apps/jurumarble/src/components/modal/ModalTemplate.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<Portal selector="#portal">
<ModalTemplateBlock onMouseDown={onToggleModal} {...rest}>
<ModalInner width={width} height={height} onMouseDown={(e) => e.stopPropagation()}>
{children}
</ModalInner>
<ModalBackground />
</ModalTemplateBlock>
</Portal>
);
}

const ModalTemplateBlock = styled.div`
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 9999;
`;

const ModalInner = styled.div<ModalInnerStyled>`
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;
23 changes: 23 additions & 0 deletions apps/jurumarble/src/components/modal/Portal.tsx
Original file line number Diff line number Diff line change
@@ -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<Element | null>(null);

useEffect(() => {
setElement(document.querySelector(selector));
}, []);

if (!element) {
return null;
}

return ReactDOM.createPortal(children, element);
}

export default Portal;
23 changes: 23 additions & 0 deletions apps/jurumarble/src/components/selectBox/Option.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from "styled-components";

interface Props {
label: string;
onChangeSelectedOption: () => void;
}

function Option({ label, onChangeSelectedOption }: Props) {
return (
<Li role="option" onClick={onChangeSelectedOption}>
{label}
</Li>
);
}

const Li = styled.li`
padding: 14px 34px;
:hover {
background-color: ${({ theme }) => theme.palette.background.selectedSoft};
}
`;

export default Option;
46 changes: 46 additions & 0 deletions apps/jurumarble/src/components/selectBox/OptionList.tsx
Original file line number Diff line number Diff line change
@@ -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를 만들어야함
<Ul id="select-list" aria-labelledby="select-box-1" role="listbox">
{options.map(({ value, label }) => (
<Option
key={`select_${value}`}
label={label}
onChangeSelectedOption={() => {
onChangeSelectedOption(value);
onToggleOpen();
}}
/>
))}
</Ul>
);
}

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;
45 changes: 45 additions & 0 deletions apps/jurumarble/src/components/selectBox/Select.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLDivElement> {
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<HTMLDivElement>(isOpen, onToggleOpen);

return (
<div ref={targetEl}>
<SelectButton onChangeOpen={onToggleOpen}>
{options.find(({ value }) => value === defaultValue)?.label}
<span id="indicator">{children}</span>
</SelectButton>
{isOpen ? (
<OptionList
options={options}
onChangeSelectedOption={onChangeSelectedOption}
onToggleOpen={onToggleOpen}
></OptionList>
) : null}
</div>
);
}

export default Select;
Loading

0 comments on commit eb499bd

Please sign in to comment.