diff --git a/src/components/Input/Input/index.js b/src/components/Input/index.js
similarity index 100%
rename from src/components/Input/Input/index.js
rename to src/components/Input/index.js
diff --git a/src/components/Input/Input/style.js b/src/components/Input/style.js
similarity index 100%
rename from src/components/Input/Input/style.js
rename to src/components/Input/style.js
diff --git a/src/components/TopBar/components/ChangePasswordModalChildren.js b/src/components/TopBar/components/ChangePasswordModalChildren.js
new file mode 100644
index 0000000..4da9f24
--- /dev/null
+++ b/src/components/TopBar/components/ChangePasswordModalChildren.js
@@ -0,0 +1,29 @@
+import Input from '../../Input';
+import { VStack } from '../../../styles/Stack.styles';
+import { forwardRef } from 'react';
+
+const ChangePasswordModalChildren = forwardRef(
+ (
+ { nextPasswordRef, changePasswordFocus, checkedNextPasswordRef, checkedChangePasswordFocus },
+ ref
+ ) => {
+ return (
+
+
+
+
+ );
+ }
+);
+
+export default ChangePasswordModalChildren;
diff --git a/src/components/TopBar/components/TopBarLeft.js b/src/components/TopBar/components/TopBarLeft.js
new file mode 100644
index 0000000..a0f6568
--- /dev/null
+++ b/src/components/TopBar/components/TopBarLeft.js
@@ -0,0 +1,15 @@
+import { useNavigate } from 'react-router-dom';
+import logo from '../../../assets/logo.svg';
+import typo from '../../../assets/typo.svg';
+import { HStack } from '../../../styles/Stack.styles';
+
+const TopBarLeft = () => {
+ const navigate = useNavigate();
+ return (
+ navigate('/')}>
+
+
+
+ );
+};
+export default TopBarLeft;
diff --git a/src/components/TopBar/components/TopBarRight.js b/src/components/TopBar/components/TopBarRight.js
new file mode 100644
index 0000000..0ca5519
--- /dev/null
+++ b/src/components/TopBar/components/TopBarRight.js
@@ -0,0 +1,44 @@
+import S from '../style';
+import { HStack } from '../../../styles/Stack.styles';
+import { useState, useEffect } from 'react';
+import { useNavigate } from 'react-router-dom';
+import useLoginState from '../../../hooks/useLoginState';
+import UserMenuConainer from './UserMenuContainer';
+import { TopBarItems } from '../constants';
+import { goRoute } from '../utils';
+
+const TopBarRight = () => {
+ const [selectedItem, _] = useState(window.location.pathname);
+ const { isLoggedIn } = useLoginState();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ const shopChecked = localStorage.getItem('shopChecked');
+ if (shopChecked === null) {
+ // shopChecked 항목이 없으면 false로 초기화
+ localStorage.setItem('shopChecked', 'false');
+ }
+ }, []);
+
+ return (
+
+ {TopBarItems.map((item, index) => (
+ goRoute(item.route, selectedItem, navigate)}
+ >
+ {item.name}
+
+ ))}
+ {isLoggedIn ? (
+
+ ) : (
+ navigate('/login')}>
+ 로그인
+
+ )}
+
+ );
+};
+export default TopBarRight;
diff --git a/src/components/TopBar/components/UserMenuChildren.js b/src/components/TopBar/components/UserMenuChildren.js
new file mode 100644
index 0000000..951a226
--- /dev/null
+++ b/src/components/TopBar/components/UserMenuChildren.js
@@ -0,0 +1,36 @@
+import Button from '../../Buttons';
+const UserMenuChildren = ({
+ showChangePasswordModal,
+ showProfileChangeModal,
+ hideUserMenu,
+ showLogoutModal,
+}) => {
+ return (
+ <>
+
+
+
+ >
+ );
+};
+export default UserMenuChildren;
diff --git a/src/components/TopBar/components/UserMenuContainer.js b/src/components/TopBar/components/UserMenuContainer.js
new file mode 100644
index 0000000..720d75c
--- /dev/null
+++ b/src/components/TopBar/components/UserMenuContainer.js
@@ -0,0 +1,110 @@
+import { useState, useRef } from 'react';
+import useUserState from '../../../hooks/useUserState';
+import useLoginState from '../../../hooks/useLoginState';
+
+import { Message } from '../../Message';
+import useModal from '../../../hooks/useModal';
+import useContainer from '../../../hooks/useContainer';
+
+import { checkPasswordValidate, uploadImage } from '../utils';
+import { renderUserImage } from '../utils/renderUserImage';
+
+import ChangePasswordModalChildren from '../components/ChangePasswordModalChildren';
+import ProfileChangeModalChildren from '../components/ProfileChangeModalChildren';
+import UserMenuChildren from '../components/UserMenuChildren';
+
+import { HStack } from '../../../styles/Stack.styles';
+
+const UserMenuConainer = () => {
+ const [messageText, setMessageText] = useState('');
+ const [selectedFile, setselectedFile] = useState(null);
+ const [changePasswordFocus, setChangePasswordFocus] = useState(false);
+ const [checkedChangePasswordFocus, setCheckedChangePasswordFocus] = useState(false);
+
+ const nextPasswordRef = useRef();
+ const checkedNextPasswordRef = useRef();
+
+ const userMenu = useContainer();
+ const { user, setUserInfo } = useUserState();
+ const { initLoginStatus } = useLoginState();
+
+ const logoutModal = useModal({
+ description: '정말 로그아웃하시겠어요?',
+ cancelText: '취소',
+ okText: '확인',
+ closable: true,
+ onOk: () => {
+ initLoginStatus();
+ },
+ });
+
+ const profileChangeModal = useModal({
+ description: '변경할 이미지를 올려주세요',
+ cancelText: '취소',
+ okText: '확인',
+ closable: true,
+ onOk: () =>
+ uploadImage(selectedFile, setUserInfo, setselectedFile, setMessageText, toastMessage),
+ });
+
+ const changePasswordModal = useModal({
+ cancelText: '취소',
+ closable: true,
+ onOk: () =>
+ checkPasswordValidate(
+ nextPasswordRef,
+ checkedNextPasswordRef,
+ setChangePasswordFocus,
+ setCheckedChangePasswordFocus,
+ setMessageText,
+ changePasswordModal,
+ toastMessage
+ ),
+ });
+ const toastMessage = Message();
+ return (
+ <>
+ {toastMessage.render({
+ children: (
+
+ ✅
+ {messageText}
+
+ ),
+ })}
+ {profileChangeModal.render({
+ children: (
+
+ ),
+ })}
+ {changePasswordModal.render({
+ children: (
+
+ ),
+ })}
+ {logoutModal.render()}
+
+ {userMenu.render({
+ children: (
+
+ ),
+ })}
+
+ {renderUserImage(user, userMenu)}
+ >
+ );
+};
+export default UserMenuConainer;
diff --git a/src/components/TopBar/constants/index.js b/src/components/TopBar/constants/index.js
new file mode 100644
index 0000000..253ef22
--- /dev/null
+++ b/src/components/TopBar/constants/index.js
@@ -0,0 +1,17 @@
+import { PATH_NAME } from '../../../constants/index';
+
+export const MIN_PASSWORD_LENGTH = 4;
+export const TopBarItems = [
+ {
+ name: '문제 목록',
+ route: PATH_NAME.PROBLEM_LIST,
+ },
+ {
+ name: '구성원',
+ route: PATH_NAME.MEMBER,
+ },
+ {
+ name: '상점',
+ route: PATH_NAME.SHOP,
+ },
+];
diff --git a/src/components/TopBar/index.js b/src/components/TopBar/index.js
index 58975c5..3148814 100644
--- a/src/components/TopBar/index.js
+++ b/src/components/TopBar/index.js
@@ -1,306 +1,14 @@
-import styled from 'styled-components';
-import logo from '../../assets/logo.svg';
-import typo from '../../assets/typo.svg';
-import React, { useEffect, useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-import Input from '../Input/Input';
-import {
- TopBarContainer,
- TopBarLeft,
- TopBarRight,
- TopBarItem,
- TopBarButton,
- ImageWrapper,
- LogoImage,
- TypoImage,
- UserImage,
- UserImageWrapper,
-} from './style';
-import ProfileChangeModalChildren from './components/ProfileChangeModalChildren';
-import useLoginState from '../../hooks/useLoginState';
-import useUserState from '../../hooks/useUserState';
-import DefaultProfile from '../../assets/default-profile.svg';
-import useContainer from '../../hooks/useContainer';
-import Button from '../Buttons';
-import useModal from '../../hooks/useModal';
-import memberIcon from '../../assets/member-icon.svg';
-import { useRef } from 'react';
-import { serverAPI } from '../../api/axios';
-import { Message } from '../Message';
-
-const TopBarItems = [
- {
- name: '문제 목록',
- route: '/problem',
- },
- {
- name: '구성원',
- route: '/member',
- },
- {
- name: '상점',
- route: '/shop',
- },
-];
-
-const TopBar = ({ active }) => {
- const [selectedItem, setSelectedItem] = useState(window.location.pathname);
- const [isScroll, setIsScroll] = useState(false);
- const { isLoggedIn, initLoginStatus } = useLoginState();
- const [shopUpdated, setShopUpdated] = useState(true);
- const { user, setUserInfo } = useUserState();
- const navigate = useNavigate();
- const nextPasswordRef = useRef();
- const [selectedFile, setselectedFile] = useState(null);
- const checkedNextPasswordRef = useRef();
- const [changePasswordFocus, setChangePasswordFocus] = useState(false);
- const [checkedChangePasswordFocus, setCheckedChangePasswordFocus] = useState(false);
- const [messageText, setMessageText] = useState('');
-
- const userMenu = useContainer();
- const logoutModal = useModal({
- description: '정말 로그아웃하시겠어요?',
- cancelText: '취소',
- okText: '확인',
- closable: true,
- onOk: () => {
- initLoginStatus();
- },
- });
-
- const profileChangeModal = useModal({
- description: '변경할 이미지를 올려주세요',
- cancelText: '취소',
- okText: '확인',
- closable: true,
- onOk: async () => {
- const formData = new FormData();
- formData.append('file', selectedFile);
- try {
- await serverAPI.post('/images/upload/profile', formData, {
- headers: {
- 'Content-Type': 'multipart/form-data',
- },
- });
-
- const response = await serverAPI.get('/user');
- setUserInfo(response.data.result);
- setselectedFile(null);
- } catch (error) {
- console.log(error);
- if (error.code === 'ERR_NETWORK') {
- setMessageText('이미지 용량이 너무 커요!');
- passwordChangeMessage.toast();
- }
- }
- },
- });
- const changePasswordModal = useModal({
- cancelText: '취소',
- closable: true,
- onOk: () => {
- const nextPassword = nextPasswordRef.current.getValue();
- const checkedPassword = checkedNextPasswordRef.current.getValue();
- //비밀번호 검증
- if (nextPassword.length <= 3) {
- nextPasswordRef.current.focus();
- nextPasswordRef.current.setValue('');
- checkedNextPasswordRef.current.setValue('');
- nextPasswordRef.current.setPlaceholder('비밀번호는 4글자 이상이어야 합니다.');
- setChangePasswordFocus(true);
-
- return false;
- }
- if (nextPassword !== checkedPassword) {
- checkedNextPasswordRef.current.focus();
- checkedNextPasswordRef.current.setValue('');
- checkedNextPasswordRef.current.setPlaceholder('비밀번호가 일치하지 않습니다.');
- setCheckedChangePasswordFocus(true);
- return false;
- }
- serverAPI
- .patch('/user/reset-password', { password: nextPasswordRef.current.getValue() })
- .then(response => {
- setMessageText(response.data.result);
- setChangePasswordFocus(false);
- nextPasswordRef.current.setValue('');
- checkedNextPasswordRef.current.setValue('');
- nextPasswordRef.current.setPlaceholder('변경할 비밀번호');
- checkedNextPasswordRef.current.setPlaceholder('비밀번호 재입력');
- changePasswordModal.setIsPending(false);
- passwordChangeMessage.toast();
- })
- .catch(error => {
- if (error.data) {
- setMessageText(error.data.result);
- passwordChangeMessage.toast();
- } else {
- console.log(error);
- }
- });
- },
- });
- const passwordChangeMessage = Message();
-
- useEffect(() => {
- const shopChecked = localStorage.getItem('shopChecked');
- if (shopChecked === null) {
- // shopChecked 항목이 없으면 false로 초기화
- localStorage.setItem('shopChecked', 'false');
- setShopUpdated(true);
- }
- setShopUpdated(shopChecked === 'false');
- }, []);
-
- useEffect(() => {
- const path = window.location.pathname;
- if (path === '/') {
- setSelectedItem('/');
- } else if (path === '/problem/' || path === '/problem') {
- setSelectedItem('/problem');
- } else if (path === '/member/' || path === '/member') {
- setSelectedItem('/member');
- } else if (path === '/shop/' || path === '/shop') {
- setSelectedItem('/shop');
- }
- }, []);
-
- useEffect(() => {
- window.addEventListener('scroll', () => {
- if (window.scrollY > 10) {
- setIsScroll(true);
- } else {
- setIsScroll(false);
- }
- });
- }, []);
-
- function goRoute(route) {
- if (route === selectedItem) return;
- window.scrollTo(0, 0);
- if (route === '/shop') {
- localStorage.setItem('shopChecked', 'true');
- setShopUpdated(false);
- }
- navigate(route);
- }
-
- const renderUserImage = () => {
- return user ? (
-
- {user.profileImageFileName ? (
-
- ) : (
-
- )}
-
- ) : (
-
-
-
- );
- };
+import TopBarLeft from './components/TopBarLeft';
+import TopBarRight from './components/TopBarRight';
+import React from 'react';
+import S from './style';
+const TopBar = () => {
return (
-
- {passwordChangeMessage.render({
- children: (
- ✅ {messageText}
- ),
- })}
- {logoutModal.render()}
- {profileChangeModal.render({
- children: (
-
- ),
- })}
- {changePasswordModal.render({
- children: (
-
-
-
-
- ),
- })}
- navigate('/')}>
-
-
-
-
-
-
-
-
- {TopBarItems.map((item, index) => (
- goRoute(item.route)}
- >
- {item.name}
- {item.route === '/shop' && shopUpdated && (
- •
- )}
-
- ))}
- {isLoggedIn ? (
- <>
- {renderUserImage()}
-
- {userMenu.render({
- children: (
- <>
-
-
-
- >
- ),
- })}
-
- >
- ) : (
- navigate('/login')}>
- 로그인
-
- )}
-
-
+
+
+
+
);
};
diff --git a/src/components/TopBar/style.js b/src/components/TopBar/style.js
index 83558fc..4b29cc8 100644
--- a/src/components/TopBar/style.js
+++ b/src/components/TopBar/style.js
@@ -1,9 +1,9 @@
import styled, { css } from 'styled-components';
-const TopBarContainer = styled('div')`
+const TopBarContainer = styled.div`
z-index: 100;
position: fixed;
- top: 0px;
+ top: 0;
left: 0;
right: 0;
display: flex;
@@ -17,29 +17,17 @@ const TopBarContainer = styled('div')`
-webkit-backdrop-filter: blur(8px);
box-shadow: 0 4px 32px rgba(0, 0, 0, 0.05);
- ${props =>
- props.isScroll &&
- css`
- background-color: ${props => props.theme.foreground}18;
- `}
@media (max-width: 480px) {
top: 0;
margin: 0;
padding: 8px 16px;
- border-radius: 0px;
+ border-radius: 0;
}
`;
-
-const TopBarLeft = styled.div`
- display: flex;
- flex-direction: row;
- gap: 10px;
- cursor: pointer;
-`;
-const TopBarRight = styled.div`
+const ImageWrapper = styled.div`
display: flex;
- flex-direction: row;
- gap: 40px;
+ justify-content: center;
+ align-items: center;
`;
const TopBarItem = styled.div`
@@ -68,7 +56,7 @@ const TopBarButton = styled.div`
border-radius: 12px;
background-color: ${props => props.theme.primary};
- color: #ffffff;
+ color: ${props => props.theme.white};
font-size: 12px;
font-weight: 500;
cursor: pointer;
@@ -78,38 +66,9 @@ const TopBarButton = styled.div`
}
`;
-const ImageWrapper = styled.div`
- display: flex;
- justify-content: center;
- align-items: center;
-`;
-
-const LogoImage = styled.img`
- width: 32px;
- height: 32px;
-`;
-
-const TypoImage = styled.img`
- width: 55px;
- height: 32px;
-`;
-
-const UserImage = styled.img`
- width: 32px;
- height: 32px;
- border-radius: 32px;
- cursor: pointer;
-`;
-const UserImageWrapper = styled.div``;
-export {
+export default {
TopBarContainer,
- TopBarLeft,
- TopBarRight,
+ ImageWrapper,
TopBarItem,
TopBarButton,
- ImageWrapper,
- TypoImage,
- LogoImage,
- UserImage,
- UserImageWrapper,
};
diff --git a/src/components/TopBar/utils/checkPasswordValidate.js b/src/components/TopBar/utils/checkPasswordValidate.js
new file mode 100644
index 0000000..29b4cad
--- /dev/null
+++ b/src/components/TopBar/utils/checkPasswordValidate.js
@@ -0,0 +1,51 @@
+import { serverAPI } from '../../../api/axios';
+import MIN_PASSWORD_LENGTH from '../constants';
+
+const resetInput = (ref, placeholderText) => {
+ ref.current.setValue('');
+ ref.current.setPlaceholder('');
+ ref.current.setPlaceholder(placeholderText);
+};
+export const checkPasswordValidate = (
+ nextPasswordRef,
+ checkedNextPasswordRef,
+ setChangePasswordFocus,
+ setCheckedChangePasswordFocus,
+ setMessageText,
+ changePasswordModal,
+ passwordChangeMessage
+) => {
+ const nextPassword = nextPasswordRef.current.getValue();
+ const checkedPassword = checkedNextPasswordRef.current.getValue();
+ //비밀번호 검증
+ if (nextPassword.length < MIN_PASSWORD_LENGTH) {
+ resetInput(nextPasswordRef, '비밀번호는 4글자 이상이어야 합니다.');
+ checkedNextPasswordRef.current.setValue('');
+ setChangePasswordFocus(true);
+
+ return false;
+ }
+ if (nextPassword !== checkedPassword) {
+ resetInput(checkedNextPasswordRef, '비밀번호가 일치하지 않습니다.');
+ setCheckedChangePasswordFocus(true);
+ return false;
+ }
+ serverAPI
+ .patch('/user/reset-password', { password: nextPasswordRef.current.getValue() })
+ .then(response => {
+ setMessageText(response.data.result);
+ setChangePasswordFocus(false);
+ resetInput(nextPasswordRef, '비밀번호는 4글자 이상이어야 합니다.');
+ resetInput(checkedNextPasswordRef, '비밀번호가 일치하지 않습니다.');
+ changePasswordModal.setIsPending(false);
+ passwordChangeMessage.toast();
+ })
+ .catch(error => {
+ if (error.data) {
+ setMessageText(error.data.result);
+ passwordChangeMessage.toast();
+ } else {
+ console.log(error);
+ }
+ });
+};
diff --git a/src/components/TopBar/utils/index.js b/src/components/TopBar/utils/index.js
new file mode 100644
index 0000000..62b641d
--- /dev/null
+++ b/src/components/TopBar/utils/index.js
@@ -0,0 +1,88 @@
+import { serverAPI } from '../../../api/axios';
+import { MIN_PASSWORD_LENGTH } from '../constants';
+
+const resetInput = (ref, placeholderText) => {
+ ref.current.setValue('');
+ ref.current.setPlaceholder('');
+ ref.current.setPlaceholder(placeholderText);
+};
+export const checkPasswordValidate = (
+ nextPasswordRef,
+ checkedNextPasswordRef,
+ setChangePasswordFocus,
+ setCheckedChangePasswordFocus,
+ setMessageText,
+ changePasswordModal,
+ passwordChangeMessage
+) => {
+ const nextPassword = nextPasswordRef.current.getValue();
+ const checkedPassword = checkedNextPasswordRef.current.getValue();
+ //비밀번호 검증
+ if (nextPassword.length < MIN_PASSWORD_LENGTH) {
+ resetInput(nextPasswordRef, '비밀번호는 4글자 이상이어야 합니다.');
+ checkedNextPasswordRef.current.setValue('');
+ setChangePasswordFocus(true);
+
+ return false;
+ }
+ if (nextPassword !== checkedPassword) {
+ resetInput(checkedNextPasswordRef, '비밀번호가 일치하지 않습니다.');
+ setCheckedChangePasswordFocus(true);
+ return false;
+ }
+ serverAPI
+ .patch('/user/reset-password', { password: nextPasswordRef.current.getValue() })
+ .then(response => {
+ setMessageText(response.data.result);
+ setChangePasswordFocus(false);
+ resetInput(nextPasswordRef, '비밀번호는 4글자 이상이어야 합니다.');
+ resetInput(checkedNextPasswordRef, '비밀번호가 일치하지 않습니다.');
+ changePasswordModal.setIsPending(false);
+ passwordChangeMessage.toast();
+ })
+ .catch(error => {
+ if (error.data) {
+ setMessageText(error.data.result);
+ passwordChangeMessage.toast();
+ } else {
+ console.log(error);
+ }
+ });
+};
+
+export const uploadImage = async (
+ selectedFile,
+ setUserInfo,
+ setselectedFile,
+ setMessageText,
+ passwordChangeMessage
+) => {
+ const formData = new FormData();
+ formData.append('file', selectedFile);
+ try {
+ await serverAPI.post('/images/upload/profile', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+
+ const response = await serverAPI.get('/user');
+ setUserInfo(response.data.result);
+ setselectedFile(null);
+ } catch (error) {
+ console.log(error);
+ if (error.code === 'ERR_NETWORK') {
+ setMessageText('이미지 용량이 너무 커요!');
+ passwordChangeMessage.toast();
+ }
+ }
+};
+
+export function goRoute(route, selectedItem, navigate) {
+ if (route === selectedItem) return;
+ window.scrollTo(0, 0);
+ if (route === '/shop') {
+ localStorage.setItem('shopChecked', 'true');
+ }
+ navigate(route);
+}
diff --git a/src/components/TopBar/utils/renderUserImage.js b/src/components/TopBar/utils/renderUserImage.js
new file mode 100644
index 0000000..6e6344c
--- /dev/null
+++ b/src/components/TopBar/utils/renderUserImage.js
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+import memberIcon from '../../../assets/member-icon.svg';
+import DefaultProfile from '../../../assets/default-profile.svg';
+
+export const renderUserImage = (user, userMenu) => {
+ return user ? (
+ user.profileImageFileName ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ );
+};
+
+const UserImage = styled.img`
+ width: 32px;
+ height: 32px;
+ border-radius: 32px;
+ cursor: pointer;
+`;
diff --git a/src/components/TopBar/utils/uploadImage.js b/src/components/TopBar/utils/uploadImage.js
new file mode 100644
index 0000000..d0cc7de
--- /dev/null
+++ b/src/components/TopBar/utils/uploadImage.js
@@ -0,0 +1,28 @@
+import { serverAPI } from '../../../api/axios';
+export const uploadImage = async (
+ selectedFile,
+ setUserInfo,
+ setselectedFile,
+ setMessageText,
+ passwordChangeMessage
+) => {
+ const formData = new FormData();
+ formData.append('file', selectedFile);
+ try {
+ await serverAPI.post('/images/upload/profile', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+
+ const response = await serverAPI.get('/user');
+ setUserInfo(response.data.result);
+ setselectedFile(null);
+ } catch (error) {
+ console.log(error);
+ if (error.code === 'ERR_NETWORK') {
+ setMessageText('이미지 용량이 너무 커요!');
+ passwordChangeMessage.toast();
+ }
+ }
+};
diff --git a/src/constants/index.js b/src/constants/index.js
new file mode 100644
index 0000000..63387d2
--- /dev/null
+++ b/src/constants/index.js
@@ -0,0 +1,5 @@
+export const PATH_NAME = {
+ PROBLEM_LIST: '/problem',
+ MEMBER: '/member',
+ SHOP: '/shop',
+};
diff --git a/src/hooks/useContainer.js b/src/hooks/useContainer.js
index 4ed3d1f..9de04e5 100644
--- a/src/hooks/useContainer.js
+++ b/src/hooks/useContainer.js
@@ -5,9 +5,8 @@ const useContainer = () => {
const [isOpen, setIsOpen] = useState(false);
const show = () => setIsOpen(true);
const hide = () => setIsOpen(false);
- const toggle = () => {
- setIsOpen(prev => !prev);
- };
+ const toggle = () => setIsOpen(prev => !prev);
+
const render = ({ children = null } = {}) => {
return {children};
};