-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 채팅 줄바꿈 및 전체보기 기능 구현 #257
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
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| 'use client'; | ||
|
|
||
| import { Icon } from '@/components/icon'; | ||
| import { useModal } from '@/components/ui'; | ||
| import { useLongText } from '@/hooks/use-chat/use-chat-longText'; | ||
|
|
||
| import { LongTextModal } from '../chat-modal'; | ||
|
|
||
| interface Props { | ||
| text: string; | ||
| className?: string; | ||
| } | ||
|
|
||
| export const ExpandableText = ({ text, className }: Props) => { | ||
| const { open } = useModal(); | ||
| const { textRef, isLongText } = useLongText(text); | ||
|
|
||
| return ( | ||
| <> | ||
| <span | ||
| ref={textRef} | ||
| className={`line-clamp-20 block whitespace-pre-line text-gray-800 ${className}`} | ||
| > | ||
| {text} | ||
| </span> | ||
|
|
||
| {isLongText && ( | ||
| <button | ||
| className='text-text-xs-medium mt-2 flex items-center' | ||
| onClick={() => open(<LongTextModal text={text} />)} | ||
| > | ||
| <span className='text-gray-500'>전체보기 </span> | ||
| <Icon id='chevron-right-1' className='w-4 text-gray-600' /> | ||
| </button> | ||
| )} | ||
| </> | ||
| ); | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { ModalContent, ModalTitle } from '@/components/ui'; | ||
|
|
||
| export const LongTextModal = ({ text }: { text: string }) => ( | ||
| <ModalContent className='mx-4 w-full max-w-[311px]'> | ||
| <div className='mb-4'> | ||
| <ModalTitle>메세지 전체보기</ModalTitle> | ||
| </div> | ||
|
|
||
| <div className='scrollbar-thin max-h-[70vh] overflow-y-auto px-1'> | ||
| <div className='whitespace-pre-line text-gray-800'>{text}</div> | ||
| </div> | ||
| </ModalContent> | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,7 @@ | ||
| export { Chat } from './chat'; | ||
| export { ChatHeader } from './chat-header'; | ||
| export { ChatInput } from './chat-input'; | ||
| export { ChatList } from './chat-list'; | ||
| export { ExpandableText } from './chat-long-text'; | ||
| export { LongTextModal } from './chat-modal'; | ||
| export { MyChat } from './chat-my-chat'; | ||
| export { OtherChat } from './chat-other-chat'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import { useLayoutEffect, useRef, useState } from 'react'; | ||
|
|
||
| export const useLongText = (text: string, maxLines = 20) => { | ||
| const textRef = useRef<HTMLSpanElement>(null); | ||
| const [isLongText, setIsLongText] = useState(false); | ||
|
|
||
| // 20줄이 넘어가면 longText로 판단. | ||
| useLayoutEffect(() => { | ||
| if (!textRef.current) return; | ||
|
|
||
| const el = textRef.current; | ||
|
|
||
| const originalDisplay = el.style.display; | ||
| const originalClamp = el.style.webkitLineClamp; | ||
| const originalOverflow = el.style.overflow; | ||
|
|
||
| el.style.display = 'block'; | ||
| el.style.webkitLineClamp = 'unset'; | ||
| el.style.overflow = 'visible'; | ||
|
|
||
| const lineHeight = parseFloat(getComputedStyle(el).lineHeight); | ||
| const fullHeight = el.scrollHeight; | ||
|
|
||
| setIsLongText(fullHeight > lineHeight * maxLines); | ||
|
|
||
| el.style.display = originalDisplay; | ||
| el.style.webkitLineClamp = originalClamp; | ||
| el.style.overflow = originalOverflow; | ||
| }, [text, maxLines]); | ||
|
|
||
| return { textRef, isLongText }; | ||
| }; | ||
|
Comment on lines
8
to
40
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 측정을 위해 DOM style을 직접 건드는 방식은 깜빡임과 같은 형상이 있진 않나요? const el = textRef.current;
if (!el) return;
...
const style = getComputedStyle(el);
...
const clone = el.cloneNode(true) as HTMLSpanElement;
clone.style.position = 'absolute';
clone.style.visibility = 'hidden';
clone.style.pointerEvents = 'none';
clone.style.height = 'auto';
...
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 지적 감사합니다! |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이거 top이랑 z-index 지정 안해줘도 잘 고정되나요??
저는 그 두 개 무조건 해야 하는 줄 알았어요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헛 제가 이것저것 테스트해보다가 못지웠네요.. 수정하겠습니다!🤓