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

[FIX] 대댓글 수정, 삭제 기능 개선 + 동아리 캐러셀 반응형 추가 #85

Merged
merged 7 commits into from
Mar 3, 2024
23 changes: 13 additions & 10 deletions src/components/Atoms/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ButtonUnstyled, buttonUnstyledClasses, ButtonUnstyledProps } from '@mui/base';
import { CircularProgress, styled } from '@mui/material';

const CustomButtonRoot = styled('button')`
Expand All @@ -8,7 +7,7 @@ const CustomButtonRoot = styled('button')`
background-color: #312ed7;
font-size: 18px;
line-height: 21px;
color: ${(props: Props) => props.color || 'white'};
color: ${(props: ButtonProps) => props.color || 'white'};
transition: all 150ms ease;
cursor: pointer;
border: none;
Expand All @@ -17,34 +16,38 @@ const CustomButtonRoot = styled('button')`
opacity: 0.9;
}

&.${buttonUnstyledClasses.active} {
&:active {
opacity: 0.8;
}

&.${buttonUnstyledClasses.focusVisible} {
&:focus-visible {
box-shadow:
0 4px 20px 0 rgba(61, 71, 82, 0.1),
0 0 0 5px rgba(0, 127, 255, 0.5);
outline: none;
}

&.${buttonUnstyledClasses.disabled} {
&:disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: #dadada;
}
`;

interface Props extends ButtonUnstyledProps {
interface ButtonProps {
children?: React.ReactNode;
className?: string;
$loading?: boolean;
disabled?: boolean;
onClick?: (e: React.MouseEvent<HTMLElement>) => void;
color?: string;
type?: 'button' | 'submit' | 'reset' | undefined;
}
export const Button: React.FC<Props> = ({ children, ...props }) => (
<ButtonUnstyled component={CustomButtonRoot} {...props}>

export const Button: React.FC<ButtonProps> = ({ children, ...props }) => (
<CustomButtonRoot {...props}>
{props.$loading ? <CircularProgress size="1.2rem" color="inherit" /> : children}
</ButtonUnstyled>
</CustomButtonRoot>
);

export const NavButton = styled(Button)`
Expand All @@ -70,7 +73,7 @@ const ClearButtonNative = styled('button')`
}
`;

export const ClearButton: React.FC<Props> = ({ children, ...props }) => (
export const ClearButton: React.FC<ButtonProps> = ({ children, ...props }) => (
<ClearButtonNative {...props}>
{props.disabled ? <CircularProgress size="1.2rem" color="inherit" /> : children}
</ClearButtonNative>
Expand Down
12 changes: 1 addition & 11 deletions src/pages/auth/signIn/SignInPage.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
import PermIdentityOutlinedIcon from '@mui/icons-material/PermIdentityOutlined';
import { Checkbox } from '@mui/material';
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { useHistory, useLocation } from 'react-router-dom';

import { PasswordInput } from './components';
import { PageUiStoreImpl } from './SignInPageUiStore';
import {
CheckboxLabel,
Form,
Input,
Link,
LoginButton,
LogoImage,
PageWrapper,
SubLink,
} from './styled';
import { Form, Input, Link, LoginButton, LogoImage, PageWrapper, SubLink } from './styled';

import { PageStoreHOC } from '@/components';
import { PAGE_URL } from '@/configs/path';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,15 @@ export const ReplyCommentContainer: React.FC<{ model: Model.Comment }> = observe
commentInput.target?.id === model.id ? commentInput.state : InputState.WRITE,
).get();

const handeLongPress = useCallback(model => () => open(model), [open]);
const bind = useLongPress(handeLongPress(model), {
const handleLongPress = useCallback(model => () => open(model), [open]);
const bind = useLongPress(handleLongPress(model), {
cancelOnMovement: true,
captureEvent: true,
onFinish: ev => ev?.preventDefault(),
});

return (
<Li ref={ref} {...bind}>
<Li ref={ref} {...bind()}>
<CommentCardView state={state} model={model} />
</Li>
);
Expand Down
20 changes: 18 additions & 2 deletions src/pages/circle/home/CircleHomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';

import { PageUiStoreImpl } from './CircleHomePageUiStore';
import * as Circle from './components';
Expand All @@ -8,13 +8,29 @@ import { H2 } from './styled';
import { BodyScreen, GNB, Header, PageBody, PageStoreHOC } from '@/components';
import { usePageUiStore } from '@/hooks';

const WEB_WIDTH_CONDITION = 550;
const RESIZE_DELAY = 300;
let timer: string | number | NodeJS.Timeout | undefined;

const CircleHomePage: React.FC = observer(() => {
const { fetch, circles, joinedCircles } = usePageUiStore<PageUiStore.CircleHome>();
const [screenWidth, setScreenWidth] = useState(window.innerWidth);

useEffect(() => {
fetch();
}, [fetch]);

useEffect(() => {
const handleWindowResize = () => {
clearTimeout(timer);
timer = setTimeout(function () {
setScreenWidth(window.innerWidth);
}, RESIZE_DELAY);
};
window.addEventListener('resize', handleWindowResize);
return () => window.removeEventListener('resize', handleWindowResize);
});

return (
<>
<Header title="학부 동아리" />
Expand All @@ -25,7 +41,7 @@ const CircleHomePage: React.FC = observer(() => {
<Circle.ListFrame
items={circles}
emptyText={'아직 등록된 동아리가 없어요!'}
ListComponent={Circle.Slider}
ListComponent={screenWidth > WEB_WIDTH_CONDITION ? Circle.WebSlider : Circle.Slider}
/>
<H2>내 동아리</H2>
<Circle.ListFrame
Expand Down
136 changes: 136 additions & 0 deletions src/pages/circle/home/components/WebSlider/CircleWebSlideCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import { memo, useState } from 'react';
import { generatePath, useHistory } from 'react-router';

import { Article } from '@/assets/icons';
import { ClearButton } from '@/components';
import { PAGE_URL } from '@/configs/path';

export const CircleWebSlideCard: React.FC<{ model: Model.Circle }> = memo(
({ model: { id: circleId, mainImage, name, newLineDescription } }) => {
const { push } = useHistory();
const [isFlipped, setFlip] = useState(false);
const handleClick = () => {
if (isFlipped) setFlip(c => !c);
else push(generatePath(PAGE_URL.CircleJoin, { circleId }));
};
const handleFlip = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setFlip(c => !c);
};

return (
<Card onClick={handleClick}>
<Inner isFlipped={isFlipped}>
<Body>
<Cover mainImage={mainImage} />
<Content>
<ContentName>{name}</ContentName>
<p dangerouslySetInnerHTML={{ __html: newLineDescription }} />
</Content>
</Body>
<Footer>
<Name className="text-ellipsis-line">{name}</Name>
<ClearButton onClick={handleFlip}>
<Icon active={isFlipped} />
</ClearButton>
</Footer>
</Inner>
</Card>
);
},
);

const Card = styled.article`
box-sizing: border-box;
width: 90%;
min-width: 360px;
min-height: 500px;
background: #fff;
border: 1px solid #dadada;
box-shadow: 1px 2px 5px rgb(0 0 0 / 15%);
border-radius: 5px;
overflow: hidden;
text-align: left;
`;

const Body = styled.div`
position: absolute;
width: 100%;
height: calc(100% - 40px);
transition: transform 0.65s;
transform-style: preserve-3d;

> div {
position: absolute;
backface-visibility: hidden;
}
`;

const Cover = styled.div<{ mainImage: string | null }>`
top: 6px;
left: 6px;
width: calc(100% - 12px);
height: calc(100% - 6px);
border-radius: 5px;

${({ mainImage }) =>
mainImage
? css`
background: center / contain no-repeat url(${mainImage});
`
: css`
background: center / contain no-repeat url('/images/empty.png');
background-size: 65%;
`}
background-color: #efefef;
`;

const Name = styled.h3`
margin: 0 35px 0 13px;
line-height: 36px;
font-size: 12px;
font-weight: bold;
backface-visibility: hidden;
`;

const Inner = styled.div<{ isFlipped: boolean }>`
position: relative;
width: 100%;
padding-bottom: 140%; // 5:7 비율

${Body}, ${Name} {
transform: ${({ isFlipped }) => (isFlipped ? 'rotateY(180deg)' : 'rotateY(0deg)')};
}
`;

const Content = styled.div`
padding: 13px 9px;
width: 100%;
height: 100%;
background-color: #f8f8f8;
border-radius: 5px;
transform: rotateY(180deg);
overflow: hidden;
`;

const ContentName = styled.div`
margin-bottom: 9px;
line-height: 18px;
font-size: 15px;
font-weight: bold;
`;

const Footer = styled.div`
position: absolute;
bottom: 0;
width: 100%;
height: 40px;
`;

const Icon = styled(Article)`
position: absolute;
top: 9px;
right: 5px;
`;
23 changes: 23 additions & 0 deletions src/pages/circle/home/components/WebSlider/CircleWebSlider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styled from '@emotion/styled';
import { observer } from 'mobx-react-lite';

import { CircleWebSlideCard } from './CircleWebSlideCard';
import { ListComponent } from '../CircleListFrame';

export const CircleWebSlider: ListComponent = observer(({ items }) => {
return (
<WebScrollWrapper>
{items.map(item => (
<CircleWebSlideCard key={item.id} model={item} />
))}
</WebScrollWrapper>
);
});

const WebScrollWrapper = styled.div`
display: flex;
align-items: center;
gap: 20px;
overflow-x: auto;
padding: 10px 0px;
`;
1 change: 1 addition & 0 deletions src/pages/circle/home/components/WebSlider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { CircleWebSlider as WebSlider } from './CircleWebSlider';
1 change: 1 addition & 0 deletions src/pages/circle/home/components/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './List';
export * from './Slider';
export * from './WebSlider';

export { CircleListFrame as ListFrame } from './CircleListFrame';