mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-10 15:35:35 +00:00
feat(wip): add responsive
This commit is contained in:
22
src/components/ButtonLink.tsx
Normal file
22
src/components/ButtonLink.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import Link from "next/link";
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function ButtonLink({
|
||||
href = "#",
|
||||
onClick,
|
||||
children,
|
||||
}: {
|
||||
href?: string;
|
||||
onClick?: (...args: any) => any;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const handleClick = (event) => {
|
||||
event.preventDefault();
|
||||
onClick && onClick();
|
||||
};
|
||||
return (
|
||||
<Link href={href} onClick={handleClick}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
38
src/components/SideMenu/NavigationLinks.tsx
Normal file
38
src/components/SideMenu/NavigationLinks.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import PATHS from "constants/paths";
|
||||
import { SideMenuProps } from "./SideMenu";
|
||||
|
||||
import ButtonLink from "components/ButtonLink";
|
||||
import styles from "./sidemenu.module.scss";
|
||||
|
||||
export default function NavigationLinks({
|
||||
categoryActive,
|
||||
openSearchModal,
|
||||
}: {
|
||||
categoryActive: SideMenuProps["categoryActive"];
|
||||
openSearchModal: SideMenuProps["openSearchModal"];
|
||||
}) {
|
||||
const handleOpenSearchModal = (event) => {
|
||||
event.preventDefault();
|
||||
openSearchModal();
|
||||
};
|
||||
return (
|
||||
<div className={styles["menu-controls"]}>
|
||||
<div className={styles["action"]}>
|
||||
<ButtonLink onClick={openSearchModal}>Rechercher</ButtonLink>
|
||||
<kbd>S</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<ButtonLink href={PATHS.CATEGORY.CREATE}>Créer categorie</ButtonLink>
|
||||
<kbd>C</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<ButtonLink
|
||||
href={`${PATHS.LINK.CREATE}?categoryId=${categoryActive.id}`}
|
||||
>
|
||||
Créer lien
|
||||
</ButtonLink>
|
||||
<kbd>L</kbd>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,18 +1,17 @@
|
||||
import LinkTag from "next/link";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
import BlockWrapper from "components/BlockWrapper/BlockWrapper";
|
||||
import Categories from "./Categories/Categories";
|
||||
import NavigationLinks from "./NavigationLinks";
|
||||
import Favorites from "./Favorites/Favorites";
|
||||
import UserCard from "./UserCard/UserCard";
|
||||
|
||||
import * as Keys from "constants/keys";
|
||||
import PATHS from "constants/paths";
|
||||
import { Category, Link } from "types";
|
||||
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import styles from "./sidemenu.module.scss";
|
||||
|
||||
interface SideMenuProps {
|
||||
export interface SideMenuProps {
|
||||
categories: Category[];
|
||||
favorites: Link[];
|
||||
handleSelectCategory: (category: Category) => void;
|
||||
@@ -20,6 +19,7 @@ interface SideMenuProps {
|
||||
openSearchModal: () => void;
|
||||
isModalShowing: boolean;
|
||||
}
|
||||
|
||||
export default function SideMenu({
|
||||
categories,
|
||||
favorites,
|
||||
@@ -71,7 +71,7 @@ export default function SideMenu({
|
||||
/>
|
||||
</BlockWrapper>
|
||||
<BlockWrapper>
|
||||
<MenuControls
|
||||
<NavigationLinks
|
||||
categoryActive={categoryActive}
|
||||
openSearchModal={openSearchModal}
|
||||
/>
|
||||
@@ -82,36 +82,3 @@ export default function SideMenu({
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MenuControls({
|
||||
categoryActive,
|
||||
openSearchModal,
|
||||
}: {
|
||||
categoryActive: SideMenuProps["categoryActive"];
|
||||
openSearchModal: SideMenuProps["openSearchModal"];
|
||||
}) {
|
||||
const handleOpenSearchModal = (event) => {
|
||||
event.preventDefault();
|
||||
openSearchModal();
|
||||
};
|
||||
return (
|
||||
<div className={styles["menu-controls"]}>
|
||||
<div className={styles["action"]}>
|
||||
<LinkTag href={"/#"} onClick={handleOpenSearchModal}>
|
||||
Rechercher
|
||||
</LinkTag>
|
||||
<kbd>S</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<LinkTag href={PATHS.CATEGORY.CREATE}>Créer categorie</LinkTag>
|
||||
<kbd>C</kbd>
|
||||
</div>
|
||||
<div className={styles["action"]}>
|
||||
<LinkTag href={`${PATHS.LINK.CREATE}?categoryId=${categoryActive.id}`}>
|
||||
Créer lien
|
||||
</LinkTag>
|
||||
<kbd>L</kbd>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
25
src/hooks/useMediaQuery.tsx
Normal file
25
src/hooks/useMediaQuery.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
const [matches, setMatches] = useState<boolean>(getMediaMatches(query));
|
||||
|
||||
const handleMediaChange = () => setMatches(getMediaMatches(query));
|
||||
|
||||
useEffect(() => {
|
||||
const matchMedia = window.matchMedia(query);
|
||||
handleMediaChange();
|
||||
|
||||
matchMedia.addEventListener("change", handleMediaChange);
|
||||
return () => matchMedia.removeEventListener("change", handleMediaChange);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [query]);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
function getMediaMatches(query: string): boolean {
|
||||
if (typeof window !== "undefined") {
|
||||
return window.matchMedia(query).matches;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { useCallback, useMemo, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
|
||||
import Links from "components/Links/Links";
|
||||
import Modal from "components/Modal/Modal";
|
||||
import PageTransition from "components/PageTransition";
|
||||
import SearchModal from "components/SearchModal/SearchModal";
|
||||
import SideMenu from "components/SideMenu/SideMenu";
|
||||
@@ -13,6 +14,7 @@ import PATHS from "constants/paths";
|
||||
import useModal from "hooks/useModal";
|
||||
import { Category, Link, SearchItem } from "types";
|
||||
|
||||
import { useMediaQuery } from "hooks/useMediaQuery";
|
||||
import getUserCategories from "lib/category/getUserCategories";
|
||||
import getUser from "lib/user/getUser";
|
||||
import { pushStateVanilla } from "utils/link";
|
||||
@@ -25,7 +27,10 @@ interface HomePageProps {
|
||||
|
||||
function Home(props: HomePageProps) {
|
||||
const router = useRouter();
|
||||
const modal = useModal();
|
||||
const searchModal = useModal();
|
||||
|
||||
const isMobile = useMediaQuery("(max-width: 768px)");
|
||||
const mobileModal = useModal();
|
||||
|
||||
const [categories, setCategories] = useState<Category[]>(props.categories);
|
||||
const [categoryActive, setCategoryActive] = useState<Category | null>(
|
||||
@@ -105,12 +110,12 @@ function Home(props: HomePageProps) {
|
||||
Keys.OPEN_SEARCH_KEY,
|
||||
(event) => {
|
||||
event.preventDefault();
|
||||
modal.open();
|
||||
searchModal.open();
|
||||
},
|
||||
{ enabled: !modal.isShowing }
|
||||
{ enabled: !searchModal.isShowing }
|
||||
);
|
||||
useHotkeys(Keys.CLOSE_SEARCH_KEY, modal.close, {
|
||||
enabled: modal.isShowing,
|
||||
useHotkeys(Keys.CLOSE_SEARCH_KEY, searchModal.close, {
|
||||
enabled: searchModal.isShowing,
|
||||
enableOnFormTags: ["INPUT"],
|
||||
});
|
||||
|
||||
@@ -120,7 +125,7 @@ function Home(props: HomePageProps) {
|
||||
router.push(`${PATHS.LINK.CREATE}?categoryId=${categoryActive.id}`);
|
||||
},
|
||||
{
|
||||
enabled: !modal.isShowing,
|
||||
enabled: !searchModal.isShowing,
|
||||
}
|
||||
);
|
||||
useHotkeys(
|
||||
@@ -129,25 +134,45 @@ function Home(props: HomePageProps) {
|
||||
router.push("/category/create");
|
||||
},
|
||||
{
|
||||
enabled: !modal.isShowing,
|
||||
enabled: !searchModal.isShowing,
|
||||
}
|
||||
);
|
||||
|
||||
return (
|
||||
<PageTransition className="App">
|
||||
<SideMenu
|
||||
categories={categories}
|
||||
favorites={favorites}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
categoryActive={categoryActive}
|
||||
openSearchModal={modal.open}
|
||||
isModalShowing={modal.isShowing}
|
||||
/>
|
||||
{isMobile ? (
|
||||
<>
|
||||
<button onClick={mobileModal.open}>open mobile modal</button>
|
||||
<AnimatePresence>
|
||||
{mobileModal.isShowing && (
|
||||
<Modal close={mobileModal.close}>
|
||||
<SideMenu
|
||||
categories={categories}
|
||||
favorites={favorites}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
categoryActive={categoryActive}
|
||||
openSearchModal={searchModal.open}
|
||||
isModalShowing={searchModal.isShowing}
|
||||
/>
|
||||
</Modal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
) : (
|
||||
<SideMenu
|
||||
categories={categories}
|
||||
favorites={favorites}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
categoryActive={categoryActive}
|
||||
openSearchModal={searchModal.open}
|
||||
isModalShowing={searchModal.isShowing}
|
||||
/>
|
||||
)}
|
||||
<Links category={categoryActive} toggleFavorite={toggleFavorite} />
|
||||
<AnimatePresence>
|
||||
{modal.isShowing && (
|
||||
{searchModal.isShowing && (
|
||||
<SearchModal
|
||||
close={modal.close}
|
||||
close={searchModal.close}
|
||||
categories={categories}
|
||||
items={itemsSearch}
|
||||
handleSelectCategory={handleSelectCategory}
|
||||
|
||||
@@ -198,3 +198,9 @@ kbd {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.App {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user