From 26c25dae2b40494761fe06dff9e788658b7cc4ee Mon Sep 17 00:00:00 2001 From: Sven van de Scheur Date: Mon, 15 Jan 2024 16:38:47 +0100 Subject: [PATCH] :sparkles: #4 - feat: add navbar component --- src/components/index.ts | 1 + src/components/navbar/index.ts | 1 + src/components/navbar/navbar.scss | 4 ++ src/components/navbar/navbar.stories.tsx | 74 ++++++++++++++++++++++++ src/components/navbar/navbar.tsx | 60 +++++++++++++++++++ src/components/page/page.scss | 8 ++- src/components/page/page.stories.tsx | 17 ++++-- 7 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 src/components/navbar/index.ts create mode 100644 src/components/navbar/navbar.scss create mode 100644 src/components/navbar/navbar.stories.tsx create mode 100644 src/components/navbar/navbar.tsx diff --git a/src/components/index.ts b/src/components/index.ts index 13f719c5..97edb121 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -4,6 +4,7 @@ export * from "./dropdown"; export * from "./icon"; export * from "./layout"; export * from "./logo"; +export * from "./navbar"; export * from "./page"; export * from "./toolbar"; export * from "./typography"; diff --git a/src/components/navbar/index.ts b/src/components/navbar/index.ts new file mode 100644 index 00000000..e4e192d5 --- /dev/null +++ b/src/components/navbar/index.ts @@ -0,0 +1 @@ +export * from "./navbar"; diff --git a/src/components/navbar/navbar.scss b/src/components/navbar/navbar.scss new file mode 100644 index 00000000..709e2b05 --- /dev/null +++ b/src/components/navbar/navbar.scss @@ -0,0 +1,4 @@ +.mykn-navbar { + float: right; // Fallback. + float: inline-end +} diff --git a/src/components/navbar/navbar.stories.tsx b/src/components/navbar/navbar.stories.tsx new file mode 100644 index 00000000..b616c1af --- /dev/null +++ b/src/components/navbar/navbar.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { expect, userEvent, within } from "@storybook/test"; +import React from "react"; + +import { Button, ButtonLink } from "../button"; +import { Outline } from "../icon"; +import { Navbar } from "./navbar"; + +const meta = { + title: "Controls/Navbar", + component: Navbar, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const NavbarComponent: Story = { + args: { + children: ( + <> + + + + + + + Admin + + + + + ), + }, +}; + +export const NavbarOnMobile: Story = { + ...NavbarComponent, + parameters: { + viewport: { defaultViewport: "mobile1" }, + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const button = canvas.getByRole("button"); + + // Click opens, escape closes. + await userEvent.click(button, { delay: 10 }); + expect(await canvas.findByRole("dialog")).toBeVisible(); + userEvent.keyboard("{Escape}"); + expect(await canvas.findByRole("dialog")).not.toBeVisible(); + await userEvent.click(button, { delay: 10 }); + expect(await canvas.findByRole("dialog")).toBeVisible(); + + // Tab focuses items. + await userEvent.tab({ delay: 10 }); + await userEvent.tab({ delay: 10 }); + await userEvent.tab({ delay: 10 }); + + const buttons = await canvas.findAllByRole("button"); + expect(buttons[buttons.length - 1]).toBe(document.activeElement); + }, +}; diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx new file mode 100644 index 00000000..db92e079 --- /dev/null +++ b/src/components/navbar/navbar.tsx @@ -0,0 +1,60 @@ +import React, { useEffect, useState } from "react"; + +import { Dropdown } from "../dropdown"; +import { Outline } from "../icon"; +import { Toolbar, ToolbarProps } from "../toolbar"; +import "./navbar.scss"; + +export type NavbarProps = ToolbarProps; + +/** + * Shows a toolbar or dropdown based on mobile/desktop. + * @param children + * @param props + * @constructor + */ +export const Navbar: React.FC = ({ children, ...props }) => { + const [isMobile, setIsMobile] = useState( + window?.matchMedia("(max-width: 767px)").matches, + ); + + /** + * Updates `isMobile` on resize. + */ + useEffect(() => { + const onResize = () => + setIsMobile(window?.matchMedia("(max-width: 767px)").matches); + + window.addEventListener("resize", onResize); + return () => window.removeEventListener("resize", onResize); + }); + + /** + * Renders the toolbar. + */ + const renderToolbar = () => ( + + {children} + + ); + + return ( +
+ {isMobile ? ( + } + variant="transparent" + aria-label="Menu openen/sluiten" + > + {renderToolbar()} + + ) : ( + renderToolbar() + )} +
+ ); +}; diff --git a/src/components/page/page.scss b/src/components/page/page.scss index a01246a0..c6aad0f8 100644 --- a/src/components/page/page.scss +++ b/src/components/page/page.scss @@ -1,9 +1,15 @@ +@use '../../settings/style'; + .mykn-page { background-color: var(--theme-color-primary-200); container-name: page; container-type: size; box-sizing: border-box; + padding: var(--spacing-h-xl); width: 100%; height: 100%; - padding: var(--spacing-v-xl) var(--spacing-h-xl); + + @media screen and (min-width: style.$breakpoint-desktop) { + padding: var(--spacing-v-xl) var(--spacing-h-xl); + } } diff --git a/src/components/page/page.stories.tsx b/src/components/page/page.stories.tsx index c23de5b1..e4d915a6 100644 --- a/src/components/page/page.stories.tsx +++ b/src/components/page/page.stories.tsx @@ -6,7 +6,7 @@ import { Outline } from "../icon"; import { Container, Grid } from "../layout"; import { Column } from "../layout/column"; import { Logo } from "../logo"; -import { Toolbar } from "../toolbar"; +import { Navbar } from "../navbar"; import { Page } from "./page"; type PagePropsAndCustomArgs = React.ComponentProps & { @@ -38,16 +38,14 @@ export const SamplePage: Story = { - + - - - + - + ), }; + +export const SamplePageOnMobile: Story = { + ...SamplePage, + parameters: { + viewport: { defaultViewport: "mobile1" }, + }, +};