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

[FE][Fix][Docs] #145 : 플로팅 버튼 구조 변경 및 스토리북 추가 #192

Merged
merged 3 commits into from
Nov 19, 2024
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
56 changes: 22 additions & 34 deletions frontend/src/component/common/FloatingButton/FloatingButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import classNames from 'classnames';
import React, { useState } from 'react';
import React from 'react';
import { ButtonState } from '../enums';
import { IconType, ToolCategory } from '../types';

interface IFloatingButtonProps {
/** 메뉴가 열려있는지 여부를 나타냅니다. */
isMenuOpen?: boolean;
/** 메뉴를 토글할 수 있는 함수입니다. */
toggleMenu?: () => void;
/** 선택된 도구의 버튼 상태를 의미합니다. */
toolType: ButtonState;
/** 도구를 선택할 수 있는 함수입니다. */
handleMenuClick?: (type: ButtonState) => void;
}

/**
* FloatingButton 컴포넌트는 도구 선택 및 메뉴 토글 기능을 제공하는 플로팅 버튼을 렌더링합니다.
* @remarks
Expand All @@ -12,40 +23,14 @@ import { IconType, ToolCategory } from '../types';
* <FloatingButton />
* )
*/
export const FloatingButton = () => {
const [toolType, setToolType] = useState<ButtonState>(ButtonState.CLOSE);
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);

/**
* @remarks
* - 메뉴를 토글하는 함수입니다.
*/
const toggleMenu = () => {
setIsMenuOpen(prev => !prev);
if (!isMenuOpen) {
setToolType(ButtonState.OPEN);
} else {
setToolType(ButtonState.CLOSE);
}
};

/**
* @remarks
* 툴을 선택하고 메뉴를 닫는 함수입니다.
* @param {ButtonState} type - 선택된 툴 타입
*/
const handleMenuClick = (type: ButtonState) => {
setToolType(type);
setIsMenuOpen(!isMenuOpen);
};

export const FloatingButton = (props: IFloatingButtonProps) => {
return (
<div
className={classNames('absolute', 'bottom-5', 'right-10', 'flex', 'flex-col', 'items-center')}
>
<button
type="button"
onClick={toggleMenu}
onClick={props.toggleMenu}
className={classNames(
'absolute',
'bottom-0',
Expand All @@ -61,13 +46,13 @@ export const FloatingButton = () => {
'z-10',
)}
>
{React.createElement(IconType[toolType], { className: 'w-6 h-6' })}
{React.createElement(IconType[props.toolType], { className: 'w-6 h-6' })}
</button>

{ToolCategory.map(({ type, description, icon }, index) => (
<button
type="button"
onClick={() => handleMenuClick(type)}
onClick={() => props.handleMenuClick?.(type)}
key={type}
className={classNames(
'w-10',
Expand All @@ -83,14 +68,17 @@ export const FloatingButton = () => {
'duration-300',
'shadow-floatButton',
{
'shadow-none': !isMenuOpen,
'shadow-none': !props.isMenuOpen,
},
)}
style={{ bottom: toolType === ButtonState.OPEN ? `${48 * index + 64}px` : '0px' }}
style={{
bottom: props.isMenuOpen ? `${48 * index + 64}px` : '0px',
transition: 'bottom 0.3s ease',
}}
>
<div className={classNames('flex', 'items-center')}>
{React.createElement(icon, { className: 'w-5 h-5' })}
{isMenuOpen && (
{props.isMenuOpen && (
<div
className={classNames(
'w-20',
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/hooks/useFloatingButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ButtonState } from '@/component/common/enums';
import { useState } from 'react';

export const useFloatingButton = () => {
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
const [toolType, setToolType] = useState<ButtonState>(ButtonState.CLOSE);

const toggleMenu = () => {
setIsMenuOpen(prev => !prev);
if (!isMenuOpen) {
setToolType(ButtonState.OPEN);
} else {
setToolType(ButtonState.CLOSE);
}
};

const handleMenuClick = (type: ButtonState) => {
setToolType(type);
setIsMenuOpen(!isMenuOpen);
};

return { isMenuOpen, toolType, toggleMenu, handleMenuClick };
};
2 changes: 1 addition & 1 deletion frontend/src/pages/Main.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { Fragment } from 'react';
import { getUserLocation } from '@/hooks/getUserLocation';
import { Map } from '@/component/maps/Map';
import { BottomSheet } from '@/component/BottomSheet/BottomSheet';
import { BottomSheet } from '@/component/bottomSheet/BottomSheet';
import { Content } from '@/component/content/Content';
import { MdFormatListBulleted } from 'react-icons/md';

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/stories/BottomSheet.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { Fragment } from 'react';
import { Meta, Story } from '@storybook/react';
import { BottomSheet } from '@/component/BottomSheet/BottomSheet';
import { BottomSheet } from '@/component/bottomSheet/BottomSheet';
import { Content } from '@/component/content/Content';

export default {
Expand Down
117 changes: 117 additions & 0 deletions frontend/src/stories/FloatingButton/Floatingbutton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { Meta, Story } from '@storybook/react';
import { FloatingButton } from '@/component/common/floatingButton/FloatingButton'; // 경로에 맞게 수정하세요.
import { ButtonState } from '@/component/common/enums';
import { useFloatingButton } from '@/hooks/useFloatingButton';

export default {
title: 'Components/FloatingButton',
component: FloatingButton,
tags: ['autodocs'],
parameters: {
docs: {
description: {
component:
'FloatingButton은 열기와 닫기 상태를 관리하는 버튼입니다. 상태에 따라 버튼의 텍스트가 변경됩니다.',
},
},
},
argTypes: {
isMenuOpen: {
control: 'boolean',
description: '메뉴가 열려 있는지 여부를 나타냅니다.',
table: {
type: { summary: 'boolean' },
defaultValue: { summary: 'false' },
},
},
toggleMenu: {
action: 'toggleMenu',
description: '메뉴를 토글하는 함수입니다.',
table: {
type: { summary: 'function' },
},
},
toolType: {
control: 'select',
options: Object.values(ButtonState),
description: '도구 버튼의 상태를 나타냅니다.',
table: {
type: { summary: 'ButtonState' },
defaultValue: { summary: 'CLOSE' },
},
},
handleMenuClick: {
action: 'handleMenuClick',
description: '도구를 선택할 수 있는 함수입니다.',
table: {
type: { summary: 'function' },
},
},
},
} as Meta<typeof FloatingButton>;

const Template2: Story<typeof FloatingButton> = args => <FloatingButton {...args} />;

const Template: Story = () => {
const { isMenuOpen, toolType, toggleMenu, handleMenuClick } = useFloatingButton();

return (
<div>
<FloatingButton
isMenuOpen={isMenuOpen}
toolType={toolType}
toggleMenu={toggleMenu}
handleMenuClick={handleMenuClick}
/>
</div>
);
};
export const CloseState = Template2.bind({});
CloseState.argTypes = {
isMenuOpen: { table: { disable: true } }, // isMenuOpen을 테이블에서 제외
toolType: { table: { disable: true } }, // toolType을 테이블에서 제외
};
CloseState.args = {
isMenuOpen: false, // 닫힘 상태
toolType: ButtonState.CLOSE, // 닫기 상태
};
CloseState.parameters = {
docs: {
description: {
story:
'이 버튼은 닫힌 상태를 나타냅니다. 메뉴는 열려 있지 않으며, 버튼은 "열기"로 변경됩니다.',
},
},
};

export const OpenState = Template2.bind({});
OpenState.argTypes = {
isMenuOpen: { table: { disable: true } }, // isMenuOpen을 테이블에서 제외
toolType: { table: { disable: true } }, // toolType을 테이블에서 제외
};
OpenState.args = {
isMenuOpen: true, // 열림 상태
toolType: ButtonState.OPEN, // 열기 상태
};
OpenState.parameters = {
docs: {
description: {
story: '이 버튼은 열린 상태를 나타냅니다. 메뉴는 열려 있으며, 버튼은 "닫기"로 변경됩니다.',
},
},
};

// ToggleState에서는 control을 사용 가능하게 설정
export const ToggleState = Template.bind({});
ToggleState.args = {
isMenuOpen: false, // 초기 상태는 닫힘
toolType: ButtonState.CLOSE, // 닫기 상태
};
ToggleState.parameters = {
docs: {
description: {
story:
'이 버튼은 상태를 토글할 수 있는 버튼입니다. 클릭하면 메뉴가 열리고 닫히며 버튼 상태도 변경됩니다.',
},
},
};
Loading