Skip to content

Commit

Permalink
feat: mobile nav bar
Browse files Browse the repository at this point in the history
  • Loading branch information
fran-ink committed Dec 5, 2024
1 parent 67e6010 commit 52cb12d
Show file tree
Hide file tree
Showing 11 changed files with 183 additions and 44 deletions.
2 changes: 1 addition & 1 deletion src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const Button: React.FC<ButtonProps> = ({
<Component
className={classNames(
"ink:rounded-full ink:font-default ink:transition-colors ink:hover:cursor-pointer ink:disabled:cursor-not-allowed ink:transition-default-animation ink:box-border",
"ink:flex ink:items-center ink:justify-center ink:gap-1 ink:select-none ink:no-underline",
"ink:flex ink:items-center ink:justify-center ink:gap-1 ink:shrink-0 ink:select-none ink:no-underline",
variantClassNames(size, {
sm: "ink:px-2 ink:py-1.5 ink:text-body-2-bold ink:h-5",
md: "ink:px-3 ink:py-2 ink:text-body-2-bold ink:h-6",
Expand Down
17 changes: 17 additions & 0 deletions src/layout/ForStories/ExampleLayoutLinks.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { InkIcon } from "../..";
import { InkLayoutLink } from "../InkLayout/InkNavLink";

export const EXAMPLE_LINKS: InkLayoutLink[] = [
{
children: "Home",
href: "#home",
icon: <InkIcon.Home />,
target: "_self",
},
{
children: "Settings",
href: "#settings",
icon: <InkIcon.Settings />,
target: "_self",
},
];
11 changes: 11 additions & 0 deletions src/layout/ForStories/ExampleMobileNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { EXAMPLE_LINKS } from "./ExampleLayoutLinks";
import {
InkLayoutMobileNav,
InkLayoutMobileNavProps,
} from "../InkLayout/MobileNav";

export const ExampleMobileNav = (
props: Omit<InkLayoutMobileNavProps, "links">
) => {
return <InkLayoutMobileNav links={EXAMPLE_LINKS} {...props} />;
};
21 changes: 2 additions & 19 deletions src/layout/ForStories/ExampleSideNav.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,6 @@
import { InkIcon } from "../..";
import { InkLayoutSideNav } from "../InkLayout";
import { EXAMPLE_LINKS } from "./ExampleLayoutLinks";

export const ExampleSideNav = () => {
return (
<InkLayoutSideNav
links={[
{
children: "Home",
href: "#home",
icon: <InkIcon.Home />,
target: "_self",
},
{
children: "Settings",
href: "#settings",
icon: <InkIcon.Settings />,
target: "_self",
},
]}
/>
);
return <InkLayoutSideNav links={EXAMPLE_LINKS} />;
};
4 changes: 4 additions & 0 deletions src/layout/InkLayout/InkLayout.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -29,6 +30,9 @@ const meta: Meta<InkLayoutProps> = {
headerContent: <div>Header content</div>,
topNavigation: <ExampleTopNav />,
sideNavigation: <ExampleSideNav />,
mobileNavigation: (props) => (
<ExampleMobileNav onLinkClick={props.closeMobileNavigation} />
),
},
};

Expand Down
109 changes: 87 additions & 22 deletions src/layout/InkLayout/InkLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,109 @@
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<InkLayoutProps> = ({
mainIcon = <DefaultAppIcon className="ink:size-6" />,
headerContent,
sideNavigation,
topNavigation,
mobileNavigation,
children,
}) => {
const [isMobileNavOpen, setIsMobileNavOpen] = useState(false);
const hasSomethingInHeader = !!headerContent || !!mobileNavigation;
return (
<div
className={classNames(
"ink:flex ink:flex-col ink:min-h-full ink:pb-5 ink:min-w-[320px] ink:font-default ink:text-text-default ink:gap-5 ink:box-border"
)}
>
<div className="ink:w-full ink:flex ink:justify-between ink:items-center ink:gap-3 ink:px-5 ink:pt-4 ink:box-border">
<div className="ink:flex ink:items-center ink:justify-start ink:size-6 ink:gap-2">
{mainIcon}
</div>
{topNavigation && <div>{topNavigation}</div>}
{headerContent ? (
<div className="ink:flex ink:items-center">{headerContent}</div>
) : null}
</div>
<div className="ink:flex ink:flex-1 ink:mr-5 ink:box-border">
{sideNavigation && (
<div className={classNames("ink:w-[260px] ink:px-4")}>
{sideNavigation}
</div>
<>
<div
className={classNames(
"ink:flex ink:flex-col ink:min-h-full ink:pb-5 ink:min-w-[320px] ink:font-default ink:text-text-default ink:gap-5 ink:box-border"
)}
{children}
>
<div className="ink:w-full ink:flex ink:justify-between ink:items-center ink:gap-3 ink:px-5 ink:pt-4 ink:box-border">
<div className="ink:flex ink:items-center ink:justify-start ink:size-6 ink:gap-2">
{mainIcon}
</div>
<div className="ink:flex ink:flex-1 ink:justify-center ink:items-center">
{isMobileNavOpen && (
<div className="ink:text-h3 ink:transition-default-animation ink:opacity-100 ink:starting:opacity-0">
Menu
</div>
)}
{topNavigation && (
<div className="ink:hidden ink:lg:block">{topNavigation}</div>
)}
</div>
{hasSomethingInHeader && (
<div className="ink:flex ink:gap-1 ink:justify-end ink:items-center">
{!isMobileNavOpen && headerContent && (
<div className="ink:flex ink:items-center">{headerContent}</div>
)}
{mobileNavigation && (
<Button
variant="wallet"
size="md"
rounded="full"
className="ink:lg:hidden"
onClick={() => setIsMobileNavOpen(!isMobileNavOpen)}
>
{isMobileNavOpen ? <InkIcon.Close /> : <InkIcon.Menu />}
</Button>
)}
</div>
)}
</div>
<div
className={classNames(
"ink:flex ink:flex-1 ink:mx-5 ink:box-border",
sideNavigation && "ink:lg:ml-0"
)}
>
{sideNavigation && (
<div
className={classNames(
"ink:w-[260px] ink:px-4 ink:hidden ink:lg:block"
)}
>
{sideNavigation}
</div>
)}
{children}
</div>
</div>
</div>
{isMobileNavOpen && (
<div
style={
{
/** 48px components height + 32px top spacing + 32px spacing between header and content */
"--ink-mobile-nav-height":
"calc(var(--ink-spacing-6) + var(--ink-spacing-4) + var(--ink-spacing-4))",
} as React.CSSProperties
}
className={classNames(
"ink:fixed ink:lg:hidden ink:lg:pointer-events-none ink:inset-0 ink:top-[var(--ink-mobile-nav-height)] ink:z-50",
"ink:bg-background-light/20 ink:backdrop-blur-xl",
"ink:transition-default-animation ink:opacity-100 ink:starting:opacity-0"
)}
>
{mobileNavigation &&
mobileNavigation({
closeMobileNavigation: () => setIsMobileNavOpen(false),
})}
</div>
)}
</>
);
};
2 changes: 1 addition & 1 deletion src/layout/InkLayout/InkLayoutSideNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const InkLayoutSideNav: React.FC<InkLayoutSideNavProps> = ({
}) => {
return (
<nav className="ink:min-h-screen">
<div className="ink:flex ink:flex-col ink:gap-1">
<div className="ink:flex ink:flex-col ink:gap-1 ink:font-default ink:text-text-default">
{links.map((link) => {
return <InkNavLink {...link} key={link.href} />;
})}
Expand Down
5 changes: 4 additions & 1 deletion src/layout/InkLayout/InkNavLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Slot, Slottable } from "../../components/Slot";

export interface InkLayoutLink extends React.ComponentPropsWithoutRef<"a"> {
children: React.ReactNode;
onClick?: React.MouseEventHandler<HTMLElement>;
href: string;
icon?: React.ReactNode;
target?: StringWithAutocomplete<"_blank" | "_self">;
Expand All @@ -18,6 +19,7 @@ export const InkNavLink: React.FC<InkNavLinkProps> = ({
children,
className = "",
asChild,
onClick,
...props
}) => {
const Component = asChild ? Slot : "a";
Expand All @@ -26,10 +28,11 @@ export const InkNavLink: React.FC<InkNavLinkProps> = ({
<Component
href={href}
className={classNames(
"ink:flex ink:items-center ink:gap-1.5 ink:px-1.5 ink:py-1.5 ink:text-inherit ink:no-underline ink:rounded-md ink:transition-colors ink:transition-default-animation ink:hover:bg-background-container",
"ink:flex ink:items-center ink:gap-1.5 ink:px-1.5 ink:py-1.5 ink:text-inherit ink:no-underline ink:rounded-md ink:transition-colors ink:transition-default-animation ink:hover:bg-background-container ink:box-border",
className
)}
draggable={false}
onClick={onClick}
{...props}
>
<Slottable child={children}>
Expand Down
33 changes: 33 additions & 0 deletions src/layout/InkLayout/MobileNav/InkLayoutMobileNav.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Meta, StoryObj } from "@storybook/react";
import {
InkLayoutMobileNav,
InkLayoutMobileNavProps,
} from "./InkLayoutMobileNav";
import { EXAMPLE_LINKS } from "../../ForStories/ExampleLayoutLinks";

const meta: Meta<InkLayoutMobileNavProps> = {
decorators: [
(Story) => (
<>
<div className="ink:w-full ink:h-full ink:box-border" />
<Story />
</>
),
],
title: "Layouts/InkLayoutMobileNav",
component: InkLayoutMobileNav,
parameters: {
layout: "fullscreen",
},
tags: ["autodocs"],
args: {
links: EXAMPLE_LINKS,
},
};

export default meta;
type Story = StoryObj<typeof meta>;

export const Simple: Story = {
args: {},
};
22 changes: 22 additions & 0 deletions src/layout/InkLayout/MobileNav/InkLayoutMobileNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from "react";
import { InkLayoutLink, InkNavLink } from "../InkNavLink";

export interface InkLayoutMobileNavProps {
links: InkLayoutLink[];
onLinkClick?: React.MouseEventHandler<HTMLElement>;
}

export const InkLayoutMobileNav: React.FC<InkLayoutMobileNavProps> = ({
links,
onLinkClick,
}) => {
return (
<nav className="ink:h-full ink:w-full ink:p-3 ink:box-border ink:font-default ink:text-text-default">
<div className="ink:flex ink:flex-col ink:gap-1">
{links.map((link) => {
return <InkNavLink {...link} key={link.href} onClick={onLinkClick} />;
})}
</div>
</nav>
);
};
1 change: 1 addition & 0 deletions src/layout/InkLayout/MobileNav/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./InkLayoutMobileNav";

0 comments on commit 52cb12d

Please sign in to comment.