diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx index 9a275c1..39a4fa9 100644 --- a/src/components/Button/Button.tsx +++ b/src/components/Button/Button.tsx @@ -42,7 +42,7 @@ export const Button: React.FC = ({ = DisplayOnProps & { @@ -34,6 +35,7 @@ export const SegmentedControl = ({ () => options.findIndex((opt) => opt.value === selectedOption), [options, selectedOption] ); + const windowWidth = useWindowSize(); const [isMounted, setIsMounted] = useState(false); useEffect(() => { @@ -53,13 +55,13 @@ export const SegmentedControl = ({ left: selectedElement?.offsetLeft || 0, width: selectedElement?.offsetWidth || 0, }; - }, [itemsRef, selectedIndex, isMounted]); + }, [itemsRef, selectedIndex, isMounted, windowWidth]); return (
{isMounted && selectedOption && (
{ + const { width } = useWindowSize(); + + return useMemo(() => { + return width < BREAKPOINTS[size]; + }, [width, size]); +}; diff --git a/src/hooks/useWindowSize.ts b/src/hooks/useWindowSize.ts new file mode 100644 index 0000000..3361087 --- /dev/null +++ b/src/hooks/useWindowSize.ts @@ -0,0 +1,30 @@ +import { useState, useEffect } from "react"; + +interface WindowSize { + width: number; + height: number; +} + +export const useWindowSize = (): WindowSize => { + const [windowSize, setWindowSize] = useState({ + width: window.innerWidth, + height: window.innerHeight, + }); + + useEffect(() => { + const handleResize = () => { + setWindowSize({ + width: window.innerWidth, + height: window.innerHeight, + }); + }; + + window.addEventListener("resize", handleResize); + + return () => { + window.removeEventListener("resize", handleResize); + }; + }, []); + + return windowSize; +}; diff --git a/src/layout/ForStories/ExampleLayoutLinks.tsx b/src/layout/ForStories/ExampleLayoutLinks.tsx new file mode 100644 index 0000000..246923a --- /dev/null +++ b/src/layout/ForStories/ExampleLayoutLinks.tsx @@ -0,0 +1,17 @@ +import { InkIcon } from "../.."; +import { InkLayoutLink } from "../InkLayout/InkNavLink"; + +export const EXAMPLE_LINKS: InkLayoutLink[] = [ + { + children: "Home", + href: "#home", + icon: , + target: "_self", + }, + { + children: "Settings", + href: "#settings", + icon: , + target: "_self", + }, +]; diff --git a/src/layout/ForStories/ExampleMobileNav.tsx b/src/layout/ForStories/ExampleMobileNav.tsx new file mode 100644 index 0000000..18d5a6a --- /dev/null +++ b/src/layout/ForStories/ExampleMobileNav.tsx @@ -0,0 +1,11 @@ +import { EXAMPLE_LINKS } from "./ExampleLayoutLinks"; +import { + InkLayoutMobileNav, + InkLayoutMobileNavProps, +} from "../InkLayout/MobileNav"; + +export const ExampleMobileNav = ( + props: Omit +) => { + return ; +}; diff --git a/src/layout/ForStories/ExampleSideNav.tsx b/src/layout/ForStories/ExampleSideNav.tsx index aba651b..9dc56f6 100644 --- a/src/layout/ForStories/ExampleSideNav.tsx +++ b/src/layout/ForStories/ExampleSideNav.tsx @@ -1,23 +1,6 @@ -import { InkIcon } from "../.."; import { InkLayoutSideNav } from "../InkLayout"; +import { EXAMPLE_LINKS } from "./ExampleLayoutLinks"; export const ExampleSideNav = () => { - return ( - , - target: "_self", - }, - { - children: "Settings", - href: "#settings", - icon: , - target: "_self", - }, - ]} - /> - ); + return ; }; diff --git a/src/layout/InkLayout/InkLayout.stories.tsx b/src/layout/InkLayout/InkLayout.stories.tsx index 349d97d..701ec51 100644 --- a/src/layout/InkLayout/InkLayout.stories.tsx +++ b/src/layout/InkLayout/InkLayout.stories.tsx @@ -5,6 +5,7 @@ import { InkPageLayout } from "../InkParts/InkPageLayout"; import { ExampleSideNav } from "../ForStories/ExampleSideNav"; import { ExampleTopNav } from "../ForStories/ExampleTopNav"; import { InkPanel } from "../InkParts/InkPanel"; +import { ExampleMobileNav } from "../ForStories/ExampleMobileNav"; /** * This layout component provides a unified layout that can be used for most pages. @@ -29,6 +30,9 @@ const meta: Meta = { headerContent:
Header content
, topNavigation: , sideNavigation: , + mobileNavigation: (props) => ( + + ), }, }; diff --git a/src/layout/InkLayout/InkLayout.tsx b/src/layout/InkLayout/InkLayout.tsx index da57f69..612f3b8 100644 --- a/src/layout/InkLayout/InkLayout.tsx +++ b/src/layout/InkLayout/InkLayout.tsx @@ -1,12 +1,18 @@ -import { PropsWithChildren } from "react"; +import { PropsWithChildren, useState } from "react"; import { DefaultAppIcon } from "../../icons"; import { classNames } from "../../util/classes"; +import { Button, InkIcon } from "../.."; export interface InkLayoutProps extends PropsWithChildren { mainIcon?: React.ReactNode; headerContent?: React.ReactNode; sideNavigation?: React.ReactNode; topNavigation?: React.ReactNode; + mobileNavigation?: ({ + closeMobileNavigation, + }: { + closeMobileNavigation: () => void; + }) => React.ReactNode; } export const InkLayout: React.FC = ({ @@ -14,31 +20,90 @@ export const InkLayout: React.FC = ({ headerContent, sideNavigation, topNavigation, + mobileNavigation, children, }) => { + const [isMobileNavOpen, setIsMobileNavOpen] = useState(false); + const hasSomethingInHeader = !!headerContent || !!mobileNavigation; return ( -
-
-
- {mainIcon} -
- {topNavigation &&
{topNavigation}
} - {headerContent ? ( -
{headerContent}
- ) : null} -
-
- {sideNavigation && ( -
- {sideNavigation} -
+ <> +
+
+
+ {mainIcon} +
+
+ {isMobileNavOpen && ( +
+ Menu +
+ )} + {topNavigation && ( +
{topNavigation}
+ )} +
+ {hasSomethingInHeader && ( +
+ {!isMobileNavOpen && headerContent && ( +
{headerContent}
+ )} + {mobileNavigation && ( + + )} +
+ )} +
+
+ {sideNavigation && ( +
+ {sideNavigation} +
+ )} + {children} +
-
+ {isMobileNavOpen && ( +
+ {mobileNavigation && + mobileNavigation({ + closeMobileNavigation: () => setIsMobileNavOpen(false), + })} +
+ )} + ); }; diff --git a/src/layout/InkLayout/InkLayoutSideNav.tsx b/src/layout/InkLayout/InkLayoutSideNav.tsx index c29e3f6..b8b8a8e 100644 --- a/src/layout/InkLayout/InkLayoutSideNav.tsx +++ b/src/layout/InkLayout/InkLayoutSideNav.tsx @@ -10,7 +10,7 @@ export const InkLayoutSideNav: React.FC = ({ }) => { return (