diff --git a/src/entities/projects/ui/projects-card/ProjectCard.tsx b/src/entities/projects/ui/projects-card/ProjectCard.tsx index ab3f95c..a628abd 100644 --- a/src/entities/projects/ui/projects-card/ProjectCard.tsx +++ b/src/entities/projects/ui/projects-card/ProjectCard.tsx @@ -226,6 +226,7 @@ const ContentSection = styled(Box)(({ theme }) => ({ display: "flex", flexDirection: "column", gap: theme.spacing(0.8), + flex: 1, })); const ProjectTitle = styled(Typography)(({ theme }) => ({ @@ -251,6 +252,7 @@ const SimpleInfo = styled(Typography)(() => ({ WebkitBoxOrient: "vertical", wordBreak: "break-word", overflowWrap: "break-word", + minHeight: "3em", })); const UserProfileContainer = styled(Stack)(({ theme }) => ({ diff --git a/src/entities/projects/ui/projects-detail/ProjectDescription.tsx b/src/entities/projects/ui/projects-detail/ProjectDescription.tsx index 98647e8..56158e5 100644 --- a/src/entities/projects/ui/projects-detail/ProjectDescription.tsx +++ b/src/entities/projects/ui/projects-detail/ProjectDescription.tsx @@ -1,7 +1,8 @@ import AdjustIcon from "@mui/icons-material/Adjust"; -import { Box } from "@mui/material"; +import { Box, Button, styled } from "@mui/material"; import type { JSX } from "react"; +import { useExpandableText } from "@shared/hooks/useExpandableText"; import type { ProjectListRes } from "@shared/types/project"; import TitleWithIcon from "@shared/ui/project-detail/TitleWithIcon"; @@ -10,14 +11,62 @@ type ProjectDescriptionType = Pick; const ProjectDescription = ({ description, }: ProjectDescriptionType): JSX.Element => { + const { isExpanded, shouldShowButton, textRef, handleToggle } = + useExpandableText({ + text: description, + maxLines: 10, + }); + return ( <> - - {description} + + + {description} + + {shouldShowButton && ( + + {isExpanded ? "접기" : "더 보기"} + + )} ); }; export default ProjectDescription; + +const DescriptionText = styled(Box)<{ isExpanded: boolean }>( + ({ isExpanded }) => ({ + lineHeight: 1.6, + wordBreak: "break-word", + overflowWrap: "break-word", + ...(!isExpanded && { + overflow: "hidden", + display: "-webkit-box", + WebkitLineClamp: 10, + WebkitBoxOrient: "vertical", + }), + }) +); + +const ToggleButton = styled(Button)(({ theme }) => ({ + marginTop: theme.spacing(1), + padding: theme.spacing(0.5, 1), + fontWeight: 600, + fontSize: "0.875rem", + textTransform: "none", + + "&:hover": { + backgroundColor: theme.palette.primary.main + "10", + }, +})); diff --git a/src/shared/hooks/useExpandableText.ts b/src/shared/hooks/useExpandableText.ts new file mode 100644 index 0000000..cb6837e --- /dev/null +++ b/src/shared/hooks/useExpandableText.ts @@ -0,0 +1,43 @@ +import { useState, useRef, useEffect } from "react"; + +interface UseExpandableTextProps { + text: string; + maxLines?: number; +} + +interface UseExpandableTextReturn { + isExpanded: boolean; + shouldShowButton: boolean; + textRef: React.RefObject; + handleToggle: () => void; +} + +export const useExpandableText = ({ + text, + maxLines = 10, +}: UseExpandableTextProps): UseExpandableTextReturn => { + const [isExpanded, setIsExpanded] = useState(false); + const [shouldShowButton, setShouldShowButton] = useState(false); + const textRef = useRef(null); + + useEffect(() => { + if (textRef.current) { + const lineHeight = parseInt( + window.getComputedStyle(textRef.current).lineHeight + ); + const maxHeight = lineHeight * maxLines; + setShouldShowButton(textRef.current.scrollHeight > maxHeight); + } + }, [text, maxLines]); + + const handleToggle = (): void => { + setIsExpanded(!isExpanded); + }; + + return { + isExpanded, + shouldShowButton, + textRef, + handleToggle, + }; +};