Skip to content
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
### Changed

- 채팅페이지 반응형 개선

## [0.2.3] - 2025-03-31

### Changed

- 채팅방 반응형 개선
- 로그아웃 시 홈페이지로 이동하도록 수결
- 채팅방 이미지 전송 시 이미지 사이즈 최적화

### Fixed

- 피드, 프로젝트 검색 결과 표시 안 되는 문제 해결
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"react": "^18.3.1",
"react-day-picker": "8.10.1",
"react-dom": "^18.3.1",
"react-error-boundary": "^5.0.0",
"react-github-calendar": "^4.5.3",
"react-helmet-async": "^2.0.5",
"react-intersection-observer": "^9.15.1",
Expand Down
1,162 changes: 625 additions & 537 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

49 changes: 7 additions & 42 deletions src/components/molecules/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,68 +1,33 @@
import { IDropdown } from '@/hooks/useDropdown';
import clsx from 'clsx';
import { HTMLAttributes } from 'react';
import FileUploadButton from '@/components/molecules/FileUploadButton';
import { FileUploadDropdownItem } from '@/components/organisms/chat/FileUploadDropdown';

interface DropdownProps<T extends IDropdown = IDropdown>
extends Pick<HTMLAttributes<HTMLDivElement>, 'style'> {
options?: T[];
focusedIndex?: number;
setFocusedIndex?: (value: number) => void;
onClickDropdownItem?: ({ id }: Pick<IDropdown, 'id'>) => void;
className?: string;
itemClassName?: string;
type: 'status' | 'file';
}

const Dropdown = ({
options,
focusedIndex,
setFocusedIndex,
onClickDropdownItem,
className,
itemClassName,
style,
type,
}: DropdownProps) => {
return (
<div
className={clsx(
className ??
'absolute bg-white w-[220px] h-30 top-20 rounded-[10px] border border-[#838383] text-[15px] overflow-hidden h-[138px] z-10'
)}
style={style}
>
<div className={className} style={style}>
<ul>
{options?.map((option, index) => (
{options?.map((option) => (
<li
key={option.id}
className={clsx(
itemClassName ??
`py-[6px] px-3 cursor-pointer ${focusedIndex === index ? 'bg-gray-200' : ''}`
)}
onMouseOver={() => setFocusedIndex && setFocusedIndex(index)}
onMouseLeave={() => setFocusedIndex && setFocusedIndex(-1)}
onClick={() =>
onClickDropdownItem && onClickDropdownItem({ id: option.id })
}
className={itemClassName}
onClick={() => {
if (onClickDropdownItem) onClickDropdownItem({ id: option.id });
}}
>
{type === 'status' && option.label}
{type === 'file' && (
<FileUploadButton
className='cursor-pointer text-white'
accept={(option as FileUploadDropdownItem).fileUploader.accept}
onChange={
(option as FileUploadDropdownItem).fileUploader
.handleFileChange
}
>
<label className='cursor-pointer w-full h-full flex justify-center items-center gap-3'>
<div>{(option as FileUploadDropdownItem).icon}</div>
<div>{option.label}</div>
</label>
</FileUploadButton>
)}
{option.children}
</li>
))}
</ul>
Expand Down
47 changes: 35 additions & 12 deletions src/components/molecules/FileUploadButton.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
import { ChangeEvent, cloneElement, ReactElement, useId } from 'react';
import { useFileContext } from '@/context/useFileContext';
import { optimizeImage } from '@/utils/optimizeImage';
import {
ButtonHTMLAttributes,
ChangeEvent,
InputHTMLAttributes,
ReactElement,
useId,
} from 'react';

interface Props {
onChange?: (e: ChangeEvent<HTMLInputElement>) => void;
accept?: string;
className?: string;
children: ReactElement<HTMLLabelElement>;
interface Props
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onChange'>,
Pick<InputHTMLAttributes<HTMLInputElement>, 'accept' | 'onChange'> {
children: ReactElement;
}

const FileUploadButton = ({ onChange, accept, children, className }: Props) => {
const FileUploadButton = ({ accept, children, className, onChange }: Props) => {
const inputId = useId();
const cloneChildren = cloneElement(children, { htmlFor: inputId });
const { setFile } = useFileContext();

const handleChange = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
if (file.type.match(/image\/*/)) {
const optimizedImage = await optimizeImage(file);
setFile(optimizedImage);
} else {
setFile(file);
}
}
if (onChange) onChange(e);
};

return (
<div className={className}>
<button className={className} type='button'>
<input
type='file'
className='hidden'
id={inputId}
accept={accept}
onChange={onChange}
onChange={handleChange}
/>
{cloneChildren}
</div>
<label htmlFor={inputId} className='cursor-pointer'>
{children}
</label>
</button>
);
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/molecules/TabButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const TabButton = ({
variants='outline'
className={cn(
isActive ? 'bg-white font-medium' : 'bg-none text-darkgray font-normal',
'!w-full'
'!w-full my-1'
)}
{...rest}
>
Expand Down
11 changes: 11 additions & 0 deletions src/components/molecules/ToggleButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { HTMLAttributes } from 'react';

interface SearchToggleProps
extends React.PropsWithChildren,
HTMLAttributes<HTMLButtonElement> {}

const ToggleButton = ({ children, ...props }: SearchToggleProps) => {
return <button {...props}>{children}</button>;
};

export default ToggleButton;
62 changes: 46 additions & 16 deletions src/components/molecules/chat/ChatHeaderInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
import Icon from '@/components/atoms/Icon';
import Title from '@/components/atoms/Title';
import ToggleButton from '@/components/molecules/ToggleButton';
import SearchMessage from '@/components/organisms/chat/SearchMessage';
import { useChannel } from '@/hooks/chat/useChannel';
import { useChatStore } from '@/store/chatStore';
import { Channel } from '@/types/channel.type';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useShallow } from 'zustand/shallow';

interface ChatHeaderInfoProps {
currentChannelId: Channel['channelId'];
}

const ChannelInfo = ({ channel }: { channel: Channel }) => {
return (
<div className='flex flex-col h-full justify-center w-full'>
<Title
size='sm'
fontWeight='bold'
lineClamp={1}
className='text-ellipsis w-[90%] md:text-[25px]'
>
{channel.title}
</Title>
<div className='text-caption1 text-[#838383]'>
{channel.users.length}명의 맴버가 있습니다.
</div>
</div>
);
};

const ChatHeaderInfo = ({ currentChannelId }: ChatHeaderInfoProps) => {
const { setState, channels } = useChatStore(
useShallow((state) => ({
Expand All @@ -18,6 +38,7 @@ const ChatHeaderInfo = ({ currentChannelId }: ChatHeaderInfoProps) => {
}))
);
const { channel } = useChannel(currentChannelId);
const [isSearchMode, setIsSearchMode] = useState(false);

useEffect(() => {
setState({
Expand All @@ -30,22 +51,31 @@ const ChatHeaderInfo = ({ currentChannelId }: ChatHeaderInfoProps) => {

return (
<div className='flex justify-between flex-1 h-[76px] items-center'>
<div className='flex flex-col h-full justify-center w-full'>
<Title
size='md'
fontWeight='bold'
lineClamp={1}
className='text-ellipsis w-[90%]'
>
{channel.title}
</Title>
<div className='text-caption1 text-[#838383]'>
{channel.users.length}명의 맴버가 있습니다.
{isSearchMode ? (
<div className='flex justify-between items-center w-full'>
<ToggleButton
onClick={() => setIsSearchMode(!isSearchMode)}
className='w-[30px] h-[30px] text-darkgray'
>
<Icon
type='plus'
color='black'
className='text-darkgray rotate-45'
/>
</ToggleButton>
<SearchMessage currentChannelId={currentChannelId} />
</div>
</div>
<div className='flex-shrink-0'>
<SearchMessage currentChannelId={currentChannelId} />
</div>
) : (
<div className='flex items-center gap-2 w-full'>
<ChannelInfo channel={channel} />
<ToggleButton
onClick={() => setIsSearchMode(!isSearchMode)}
className='w-[24px] h-[24px] text-[#CCCCCC]'
>
<Icon type='search' color='gray' />
</ToggleButton>
</div>
)}
</div>
);
};
Expand Down
Loading