Skip to content
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

[Feat/FE] 운동 목록 페이지의 API를 연결하고 리팩토링한다. #35

Merged
merged 13 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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