Skip to content

Commit

Permalink
Merge pull request #35 from PortalCube/dev
Browse files Browse the repository at this point in the history
[Feat/FE] 운동 목록 페이지의 API를 연결하고 리팩토링한다.
  • Loading branch information
PortalCube authored Oct 31, 2023
2 parents 238e8f8 + de05e26 commit 60b09eb
Show file tree
Hide file tree
Showing 22 changed files with 10,483 additions and 354 deletions.
100 changes: 100 additions & 0 deletions src/components/Common/Modal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import styled from "styled-components";
import PropTypes from "prop-types";

import { hide, selectVisible } from "../../redux/modalSlice.js";
import { useDispatch, useSelector } from "react-redux";
import { useCallback, useEffect, useRef, useState } from "react";
import classNames from "classnames";

const Background = styled.div`
width: 100%;
height: 100%;
left: 0;
top: 0;
z-index: 99;
position: fixed;
background-color: rgba(0, 0, 0, 0.25);
overflow-y: auto;
display: flex;
justify-content: center;
align-items: flex-start;
transition: opacity 0.25s;
&.locked {
visibility: hidden;
}
&.hidden {
opacity: 0;
}
`;

const Content = styled.div`
width: 600px;
min-height: 240px;
margin: 90px;
padding: 16px 50px;
border-radius: 10px;
background-color: #ffffff;
box-shadow: 0px 4px 24px rgba(0, 0, 0, 0.2);
overflow: hidden;
z-index: 999;
`;

const Modal = ({ id, className, style, children, onToggle }) => {
const dispatch = useDispatch();
const isVisible = useSelector(selectVisible(id));
const [interactable, setInteractable] = useState(false);
const ref = useRef(null);

const backgroundClass = {
hidden: !isVisible,
locked: !(isVisible || interactable),
};

const onClick = useCallback(
(e) => {
if (ref.current && !ref.current.contains(e.target)) {
dispatch(hide(id));
}
},
[ref, dispatch, id],
);

useEffect(() => {
document.addEventListener("mousedown", onClick, true);
return () => {
document.removeEventListener("mousedown", onClick, true);
};
}, [onClick]);

useEffect(() => {
onToggle(isVisible);
}, [isVisible]);

return (
<Background
className={classNames(backgroundClass)}
onTransitionEnd={() => setInteractable(isVisible)}
>
<Content ref={ref} style={style} className={className}>
{children}
</Content>
</Background>
);
};

Modal.propTypes = {
id: PropTypes.string,
className: PropTypes.string,
style: PropTypes.object,
children: PropTypes.node,
onToggle: PropTypes.func,
};

Modal.defaultProps = {
id: "modal",
onToggle: () => {},
};

export default Modal;
52 changes: 52 additions & 0 deletions src/components/Common/ModalTitleText.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import styled from "styled-components";
import PropTypes from "prop-types";
import classNames from "classnames";

import { MdOutlineClose } from "react-icons/md";
import { useDispatch } from "react-redux";
import { hide } from "../../redux/modalSlice.js";

const Container = styled.div`
width: 100%;
height: 70px;
border-bottom: 1px solid #d9d9d9;
display: flex;
align-items: center;
justify-content: space-between;
`;

const Text = styled.h1`
vertical-align: center;
color: black;
font-size: 32px;
font-weight: 800;
`;

const Icon = styled(MdOutlineClose)`
width: 36px;
height: 36px;
color: #cccccc;
cursor: pointer;
`;

const ModalTitleText = ({ id, text }) => {
const dispatch = useDispatch();
return (
<Container>
<Text>{text}</Text>
<Icon onClick={() => dispatch(hide(id))} />
</Container>
);
};

ModalTitleText.propTypes = {
id: PropTypes.string,
text: PropTypes.string,
};

ModalTitleText.defaultProps = {
text: "",
};

export default ModalTitleText;
116 changes: 66 additions & 50 deletions src/components/Dropdown/DropdownFilter.jsx
Original file line number Diff line number Diff line change
@@ -1,99 +1,115 @@
import { useState } from 'react';
import styled from 'styled-components';
import { useState } from "react";
import styled from "styled-components";
import PropTypes from "prop-types";
import dropdownicon from "../../assets/icons/dropdownicon.png";

import { MdOutlineKeyboardArrowDown } from "react-icons/md";

const Container = styled.div`
position: relative;
`;

const Label = styled.div`
font-family: 'Spoqa Han Sans Neo', 'sans-serif';
font-weight: 500;
font-size: 16px;
margin-bottom: 3.75px;
margin-top:-10px;
margin-top: -10px;
padding-top: 7.5px;
`;

const DropdownContainer = styled.div`
width: 210px;
height: 40px;
background-color: #FFFFFF;
border: 0.75px solid #BBBBBB;
position: relative;
padding: 0 8px;
border: 0.75px solid #bbbbbb;
display: flex;
align-items: center;
cursor: pointer;
margin-bottom: 7.5px;
border-radius: 7.5px;
box-shadow: 0px 1.5px 3px rgba(0, 0, 0, 0.1);
box-shadow: 0px 1.5px 3px rgba(0, 0, 0, 0.1);
`;

const DropdownText = styled.input`
width: calc(100% - 45px);
height: 100%;
const DropdownText = styled.p`
border: none;
font-family: 'Spoqa Han Sans Neo', 'sans-serif';
font-weight: 500;
font-size: 16px;
padding-left: 7.5px;
&:focus {
outline: none;
}
font-size: 14px;
flex-grow: 1;
`;

const DropdownIcon = styled.img`
position: absolute;
right: 7.5px;
top: 50%;
transform: translateY(-50%);
const DropdownIcon = styled(MdOutlineKeyboardArrowDown)`
width: 36px;
height: 36px;
`;

const DropdownList = styled.div`
width: 100%;
max-height: ${props => (props.open ? '112.5px' : '0')};
width: 210px;
max-height: ${(props) => (props.open ? "120px" : "0px")};
margin-top: 8px;
overflow-y: auto;
position: absolute;
top: 100%;
left: 0;
background-color: #FFFFFF;
background-color: #ffffff;
border: 0.75px solid #ccc;
border-radius: 8px;
z-index: 1;
opacity: ${(props) => (props.open ? "1" : "0")};
transition: all 0.25s;
`;

const DropdownItem = styled.div`
padding: 7.5px;
transition: background-color 0.2s;
cursor: pointer;
&:hover {
background-color: #dfdfdf;
}
`;

function DropdownFilter({ label, items }) {
function DropdownFilter({ label, items, defaultText, onSelect }) {
const [isOpen, setIsOpen] = useState(false);
const [text, setText] = useState('정렬 선택'); // 초기값을 '정렬 선택'으로 설정
const [value, setValue] = useState(null);

const toggleDropdown = () => {
setIsOpen(!isOpen);
};

const handleItemClick = (item) => {
setText(item);
setIsOpen(false);
setValue(item);
setIsOpen(false);
onSelect(item);
};

return (
<div>
<Label>{label}</Label>
<Container>
{label ? <Label>{label}</Label> : null}
<DropdownContainer onClick={toggleDropdown}>
<DropdownText
value={text}
onChange={e => setText(e.target.value)}
style={{fontSize: '14px', color: '#000000'}}
/>
<DropdownIcon src={dropdownicon} alt="Dropdown Icon" />
{isOpen && (
<DropdownList open={isOpen}>
{items.map((item, index) => (
<DropdownItem key={index} onClick={() => handleItemClick(item)}>
{item}
</DropdownItem>
))}
</DropdownList>
)}
<DropdownText>{value?.value || defaultText}</DropdownText>
<DropdownIcon />
</DropdownContainer>
</div>
<DropdownList open={isOpen}>
<DropdownItem onClick={() => handleItemClick(null)}>
{defaultText}
</DropdownItem>
{items.map((item) => (
<DropdownItem key={item.key} onClick={() => handleItemClick(item)}>
{item.value}
</DropdownItem>
))}
</DropdownList>
</Container>
);
}

DropdownFilter.propTypes = {
label: PropTypes.string,
items: PropTypes.arrayOf(PropTypes.object),
defaultText: PropTypes.string,
onSelect: PropTypes.func,
};

DropdownFilter.defaultProps = {
defaultText: "선택하기...",
};

export default DropdownFilter;
7 changes: 6 additions & 1 deletion src/components/Input/InputTextContainer.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const Label = styled.p`
margin-bottom: 6px;
`;

function InputTextContainer({ label, value, onInput, ...props }) {
function InputTextContainer({ label, required, value, onInput, ...props }) {
return (
<InputContainer {...props}>
<Label>{label}</Label>
Expand All @@ -28,8 +28,13 @@ function InputTextContainer({ label, value, onInput, ...props }) {

InputTextContainer.propTypes = {
label: PropTypes.string,
required: PropTypes.bool,
value: PropTypes.string,
onInput: PropTypes.func,
};

InputTextContainer.defaultProps = {
required: false,
};

export default InputTextContainer;
Loading

0 comments on commit 60b09eb

Please sign in to comment.