custom hook

This commit is contained in:
jerempy 2023-10-04 13:12:52 -04:00
parent b7d569de98
commit 33e5e74228
7 changed files with 102 additions and 67 deletions

View File

@ -2,24 +2,19 @@ import { Routes, Route } from 'react-router-dom';
import Navigation from './Navigation';
import Conversation from './conversation/Conversation';
import About from './About';
import { useState } from 'react';
import { ActiveState } from './models/misc';
import { inject } from '@vercel/analytics';
import { useMediaQuery } from './hooks';
inject();
export default function App() {
//TODO : below media query is disjoint from tailwind. Please wire it together.
const [navState, setNavState] = useState<ActiveState>(
window.matchMedia('(min-width: 768px)').matches ? 'ACTIVE' : 'INACTIVE',
);
const { isMobile } = useMediaQuery();
return (
<div className="min-h-full min-w-full">
<Navigation navState={navState} setNavState={setNavState} />
<Navigation />
<div
className={`transition-all duration-200 ${
navState === 'ACTIVE' ? 'ml-0 md:ml-72 lg:ml-60' : 'ml-0 md:ml-16'
!isMobile ? 'ml-0 md:ml-72 lg:ml-60' : 'ml-0 md:ml-16'
}`}
>
<Routes>

View File

@ -10,8 +10,8 @@ export default function Hero({ className = '' }: { className?: string }) {
</p>
<p className="mb-3 text-center leading-6 text-black-1000">
Enter a query related to the information in the documentation you
selected to receive and we will provide you with the most relevant
answers.
selected to receive
<br /> and we will provide you with the most relevant answers.
</p>
<p className="mb-3 text-center leading-6 text-black-1000">
Start by entering your query in the input field below and we will do the

View File

@ -26,23 +26,19 @@ import {
setConversation,
updateConversationId,
} from './conversation/conversationSlice';
import { useOutsideAlerter } from './hooks';
import { useMediaQuery, useOutsideAlerter } from './hooks';
import Upload from './upload/Upload';
import { Doc, getConversations } from './preferences/preferenceApi';
import SelectDocsModal from './preferences/SelectDocsModal';
export default function Navigation({
navState,
setNavState,
}: {
navState: ActiveState;
setNavState: React.Dispatch<React.SetStateAction<ActiveState>>;
}) {
export default function Navigation() {
const dispatch = useDispatch();
const docs = useSelector(selectSourceDocs);
const selectedDocs = useSelector(selectSelectedDocs);
const conversations = useSelector(selectConversations);
const conversationId = useSelector(selectConversationId);
const { isMobile } = useMediaQuery();
const [navOpen, setNavOpen] = useState(!isMobile);
const [isDocsListOpen, setIsDocsListOpen] = useState(false);
@ -59,7 +55,8 @@ export default function Navigation({
const navRef = useRef(null);
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const embeddingsName = import.meta.env.VITE_EMBEDDINGS_NAME || 'openai_text-embedding-ada-002';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME || 'openai_text-embedding-ada-002';
useEffect(() => {
if (!conversations) {
@ -123,51 +120,46 @@ export default function Navigation({
useOutsideAlerter(
navRef,
() => {
if (
window.matchMedia('(max-width: 768px)').matches &&
navState === 'ACTIVE' &&
apiKeyModalState === 'INACTIVE'
) {
setNavState('INACTIVE');
if (isMobile && navOpen && apiKeyModalState === 'INACTIVE') {
setNavOpen(false);
setIsDocsListOpen(false);
}
},
[navState, isDocsListOpen, apiKeyModalState],
[navOpen, isDocsListOpen, apiKeyModalState],
);
/*
Needed to fix bug where if mobile nav was closed and then window was resized to desktop, nav would still be closed but the button to open would be gone, as per #1 on issue #146
*/
useEffect(() => {
window.addEventListener('resize', () => {
if (window.matchMedia('(min-width: 768px)').matches) {
setNavState('ACTIVE');
} else {
setNavState('INACTIVE');
}
});
}, []);
if (isMobile) {
setNavOpen(false);
return;
}
setNavOpen(true);
}, [isMobile]);
return (
<>
<div
ref={navRef}
className={`${
navState === 'INACTIVE' && '-ml-96 md:-ml-[14rem]'
!navOpen && '-ml-96 md:-ml-[14rem]'
} duration-20 fixed z-20 flex h-full w-72 flex-col border-r-2 bg-gray-50 transition-all`}
>
<div className={'visible h-16 w-full border-b-2 md:hidden'}>
<button
className="float-right mr-5 mt-5 h-5 w-5"
onClick={() =>
setNavState(navState === 'ACTIVE' ? 'INACTIVE' : 'ACTIVE')
}
onClick={() => {
setNavOpen(!navOpen);
}}
>
<img
src={Arrow1}
alt="menu toggle"
className={`${
navState === 'INACTIVE' ? 'rotate-180' : 'rotate-0'
!navOpen ? 'rotate-180' : 'rotate-0'
} m-auto w-3 transition-all duration-200`}
/>
</button>
@ -176,7 +168,11 @@ export default function Navigation({
to={'/'}
onClick={() => {
dispatch(setConversation([]));
dispatch(updateConversationId({ query: { conversationId: null } }));
dispatch(
updateConversationId({
query: { conversationId: null },
}),
);
}}
className={({ isActive }) =>
`${
@ -327,7 +323,7 @@ export default function Navigation({
<img src={Link} alt="link" className="ml-2 w-5" />
<p className="my-auto text-eerie-black">Documentation</p>
</a>
<a
href="https://discord.gg/WHJdfbQDR4"
target="_blank"
@ -352,7 +348,7 @@ export default function Navigation({
<div className="fixed h-16 w-full border-b-2 bg-gray-50 md:hidden">
<button
className="mt-5 ml-6 h-6 w-6 md:hidden"
onClick={() => setNavState('ACTIVE')}
onClick={() => setNavOpen(true)}
>
<img src={Hamburger} alt="menu toggle" className="w-7" />
</button>

View File

@ -1,19 +0,0 @@
import { useEffect, RefObject } from 'react';
export function useOutsideAlerter<T extends HTMLElement>(
ref: RefObject<T>,
handler: () => void,
additionalDeps: unknown[],
) {
useEffect(() => {
function handleClickOutside(this: Document, event: MouseEvent) {
if (ref.current && !ref.current.contains(event.target as Node)) {
handler();
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref, ...additionalDeps]);
}

View File

@ -0,0 +1,20 @@
// When testing library added this is unit test for the useMediaQuery
// import { renderHook } from '@testing-library/react-hooks';
// import { useMediaQuery } from '.';
// describe('useMediaQuery', () => {
// it('should update isMobile and isDesktop when window is resized', () => {
// const { result } = renderHook(() => useMediaQuery());
// global.innerWidth = 800;
// global.dispatchEvent(new Event('resize'));
// expect(result.current.isMobile).toBe(true);
// expect(result.current.isDesktop).toBe(false);
// global.innerWidth = 1200;
// global.dispatchEvent(new Event('resize'));
// expect(result.current.isMobile).toBe(false);
// expect(result.current.isDesktop).toBe(true);
// });
// });

View File

@ -0,0 +1,45 @@
import { useEffect, RefObject, useState } from 'react';
export function useOutsideAlerter<T extends HTMLElement>(
ref: RefObject<T>,
handler: () => void,
additionalDeps: unknown[],
) {
useEffect(() => {
function handleClickOutside(this: Document, event: MouseEvent) {
if (ref.current && !ref.current.contains(event.target as Node)) {
handler();
}
}
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [ref, ...additionalDeps]);
}
// Use isMobile for checking if the width is in the expected mobile range (less than 768px)
// use IsDesktop for effects you explicitly only want when width is wider than 960px.
export function useMediaQuery() {
const mobileQuery = '(max-width: 768px)';
const desktopQuery = '(min-width: 960px)';
const [isMobile, setIsMobile] = useState(false);
const [isDesktop, setIsDesktop] = useState(false);
useEffect(() => {
const mobileMedia = window.matchMedia(mobileQuery);
const desktopMedia = window.matchMedia(desktopQuery);
const updateMediaQueries = () => {
setIsMobile(mobileMedia.matches);
setIsDesktop(desktopMedia.matches);
};
updateMediaQueries();
const listener = () => updateMediaQueries();
window.addEventListener('resize', listener);
return () => {
window.removeEventListener('resize', listener);
};
}, [mobileQuery, desktopQuery]);
return { isMobile, isDesktop };
}

View File

@ -2,7 +2,7 @@ import { useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { ActiveState } from '../models/misc';
import { selectApiKey, setApiKey } from './preferenceSlice';
import { useOutsideAlerter } from './../hooks';
import { useMediaQuery, useOutsideAlerter } from './../hooks';
import Modal from '../Modal';
export default function APIKeyModal({
@ -19,14 +19,12 @@ export default function APIKeyModal({
const [key, setKey] = useState(apiKey);
const [isError, setIsError] = useState(false);
const modalRef = useRef(null);
const { isMobile } = useMediaQuery();
useOutsideAlerter(
modalRef,
() => {
if (
window.matchMedia('(max-width: 768px)').matches &&
modalState === 'ACTIVE'
) {
if (isMobile && modalState === 'ACTIVE') {
setModalState('INACTIVE');
}
},