From 1dec1b9b4a73790161e60673bdf4a2c043bb8d56 Mon Sep 17 00:00:00 2001 From: Sauli Anto Date: Mon, 25 Mar 2024 09:22:04 +0200 Subject: [PATCH] OPHJOD-276: Add navigation bar --- lib/components/Header/Header.stories.ts | 22 --- lib/components/Header/Header.tsx | 25 --- .../NavigationBar/NavigationBar.stories.tsx | 168 ++++++++++++++++++ .../NavigationBar/NavigationBar.test.tsx | 120 +++++++++++++ .../NavigationBar/NavigationBar.tsx | 58 ++++++ .../__snapshots__/NavigationBar.test.tsx.snap | 161 +++++++++++++++++ lib/main.ts | 1 + 7 files changed, 508 insertions(+), 47 deletions(-) delete mode 100644 lib/components/Header/Header.stories.ts delete mode 100644 lib/components/Header/Header.tsx create mode 100644 lib/components/NavigationBar/NavigationBar.stories.tsx create mode 100644 lib/components/NavigationBar/NavigationBar.test.tsx create mode 100644 lib/components/NavigationBar/NavigationBar.tsx create mode 100644 lib/components/NavigationBar/__snapshots__/NavigationBar.test.tsx.snap diff --git a/lib/components/Header/Header.stories.ts b/lib/components/Header/Header.stories.ts deleted file mode 100644 index e88202d..0000000 --- a/lib/components/Header/Header.stories.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; - -import { Header } from './Header'; - -const meta = { - title: 'Primitives/Header', - component: Header, - tags: ['autodocs'], -} satisfies Meta; - -export default meta; - -type Story = StoryObj; - -export const Default: Story = { - parameters: { - design: { - type: 'figma', - url: 'https://www.figma.com/file/TpFgprt8pjcFcrHlMuL8Ry/cx_jod_ui?node-id=42%3A3498', - }, - }, -}; diff --git a/lib/components/Header/Header.tsx b/lib/components/Header/Header.tsx deleted file mode 100644 index a435c14..0000000 --- a/lib/components/Header/Header.tsx +++ /dev/null @@ -1,25 +0,0 @@ -// Autogenerated with Figma to Code - Tailwind CSS (JSX) -// Just for testing purposes -export const Header = () => ( -
-
-
-
-
- JOD -
-
- -
-
-
Yksilöille
-
Ohjaajille
-
Kouluttajille
-
Viranomaisille
-
-
-); diff --git a/lib/components/NavigationBar/NavigationBar.stories.tsx b/lib/components/NavigationBar/NavigationBar.stories.tsx new file mode 100644 index 0000000..a69c98d --- /dev/null +++ b/lib/components/NavigationBar/NavigationBar.stories.tsx @@ -0,0 +1,168 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { NavigationBar, type NavigationBarProps } from './NavigationBar'; + +const meta = { + component: NavigationBar, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; + +type Story = StoryObj; + +const parameters = { + design: { + type: 'figma', + url: 'https://www.figma.com/file/TpFgprt8pjcFcrHlMuL8Ry/cx_jod_ui?node-id=42%3A3498', + }, + layout: 'fullscreen', + viewport: { + defaultViewport: 'desktop', + }, +}; + +const items: NavigationBarProps['items'] = [ + { + text: 'Yksilöille', + active: true, + component: ({ children, ...rootProps }) => ( + + {children} + + ), + }, + { + text: 'Ohjaajille', + active: false, + component: ({ children, ...rootProps }) => ( + + {children} + + ), + }, + { + text: 'Kouluttajille', + active: false, + component: ({ children, ...rootProps }) => ( + + {children} + + ), + }, + { + text: 'Viranomaisille', + active: false, + component: ({ children, ...rootProps }) => ( + + {children} + + ), + }, +]; + +const user: NavigationBarProps['user'] = { + name: 'Jane Doe', + src: 'https://i.pravatar.cc/200?img=24', + component: ({ children, ...rootProps }) => ( + + {children} + + ), +}; + +const args: NavigationBarProps = { + items, + user, +}; + +export const Default: Story = { + parameters: { + ...parameters, + docs: { + description: { + story: 'This story demonstrates how the navigation bar looks with navigation items and an avatar.', + }, + }, + }, + args, +}; + +export const Plain: Story = { + parameters: { + ...parameters, + docs: { + description: { + story: 'This story demonstrates how the navigation bar looks without any items or an avatar.', + }, + }, + }, + args: {}, +}; + +export const Items: Story = { + parameters: { + ...parameters, + docs: { + description: { + story: 'This story demonstrates how the navigation bar looks with navigation items.', + }, + }, + }, + args: { + items, + }, +}; + +export const Avatar: Story = { + parameters: { + ...parameters, + docs: { + description: { + story: 'This story demonstrates how the navigation bar looks with an avatar.', + }, + }, + }, + args: { + user, + }, +}; + +export const Sticky: Story = { + decorators: [ + (Story) => ( +
+
+ +
+
+ {[...Array(20)].map((_p, index) => ( +

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et + dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex + ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu + fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt + mollit anim id est laborum. +

+ ))} +
+
+ ), + ], + parameters: { + ...parameters, + docs: { + description: { + story: 'This story demonstrates how the navigation bar behaves when it is sticky.', + }, + story: { + inline: false, + iframeHeight: 400, + }, + source: { + code: '
\n \n
', + }, + }, + }, + args, +}; diff --git a/lib/components/NavigationBar/NavigationBar.test.tsx b/lib/components/NavigationBar/NavigationBar.test.tsx new file mode 100644 index 0000000..137b998 --- /dev/null +++ b/lib/components/NavigationBar/NavigationBar.test.tsx @@ -0,0 +1,120 @@ +import { afterEach, describe, it, expect } from 'vitest'; +import { render, screen, cleanup } from '@testing-library/react'; +import '@testing-library/jest-dom/vitest'; + +import { NavigationBar, NavigationBarLinkProps } from './NavigationBar'; + +afterEach(() => { + cleanup(); +}); + +describe('NavigationBar', () => { + const items = [ + { + text: 'Home', + active: true, + href: '/home', + }, + { + text: 'About', + active: false, + href: '/about', + }, + { + text: 'Contact', + active: false, + href: '/contact', + }, + ].map(({ text, active, href }) => ({ + text, + active, + href, + component: ({ children, ...rootProps }: NavigationBarLinkProps) => ( + + {children} + + ), + })); + + const user = { + name: 'Jane Doe', + src: 'user.jpg', + component: ({ children, ...rootProps }: NavigationBarLinkProps) => ( + + {children} + + ), + }; + + it('renders navigation items and user', () => { + const { container } = render(); + + // Assert snapshot + expect(container.firstChild).toMatchSnapshot(); + + // Assert navigation items + items.forEach((item) => { + const linkElement = screen.getByText(item.text); + expect(linkElement).toBeInTheDocument(); + expect(linkElement).toHaveAttribute('href', item.href); + }); + + // Assert user + const userImage = screen.getByAltText(user.name); + expect(userImage).toBeInTheDocument(); + expect(userImage).toHaveAttribute('src', user.src); + }); + + it('renders only navigation items', () => { + const { container } = render(); + + // Assert snapshot + expect(container.firstChild).toMatchSnapshot(); + + // Assert navigation items + items.forEach((item) => { + const linkElement = screen.getByText(item.text); + expect(linkElement).toBeInTheDocument(); + expect(linkElement).toHaveAttribute('href', item.href); + }); + + // Assert user is not rendered + const userImage = screen.queryByAltText(user.name); + expect(userImage).toBeNull(); + }); + + it('renders only user', () => { + const { container } = render(); + + // Assert snapshot + expect(container.firstChild).toMatchSnapshot(); + + // Assert user + const userImage = screen.getByAltText(user.name); + expect(userImage).toBeInTheDocument(); + expect(userImage).toHaveAttribute('src', user.src); + + // Assert navigation items are not rendered + items.forEach((item) => { + const linkElement = screen.queryByText(item.text); + expect(linkElement).toBeNull(); + }); + }); + + it('renders no navigation items and no user', () => { + const { container } = render(); + + // Assert snapshot + expect(container.firstChild).toMatchSnapshot(); + + // Assert navigation items are not rendered + items.forEach((item) => { + const linkElement = screen.queryByText(item.text); + expect(linkElement).toBeNull(); + }); + + // Assert user is not rendered + const userImage = screen.queryByAltText(user.name); + expect(userImage).toBeNull(); + }); +}); diff --git a/lib/components/NavigationBar/NavigationBar.tsx b/lib/components/NavigationBar/NavigationBar.tsx new file mode 100644 index 0000000..fc9efc6 --- /dev/null +++ b/lib/components/NavigationBar/NavigationBar.tsx @@ -0,0 +1,58 @@ +export interface NavigationBarLinkProps { + className?: string; + children: React.ReactNode; +} + +export type NavigationBarLink = React.ComponentType; + +export interface NavigationBarProps { + /** Navigation items */ + items?: { + text: string; + active: boolean; + component: NavigationBarLink; + }[]; + /** Navigation avatar */ + user?: { + name: string; + src: string; + component?: NavigationBarLink; + }; +} + +/** + * This component is a navigation bar that displays a logo, navigation items, and an avatar. + */ +export const NavigationBar = ({ items, user }: NavigationBarProps) => ( +
+ +
+); diff --git a/lib/components/NavigationBar/__snapshots__/NavigationBar.test.tsx.snap b/lib/components/NavigationBar/__snapshots__/NavigationBar.test.tsx.snap new file mode 100644 index 0000000..e298811 --- /dev/null +++ b/lib/components/NavigationBar/__snapshots__/NavigationBar.test.tsx.snap @@ -0,0 +1,161 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`NavigationBar > renders navigation items and user 1`] = ` +
+ +
+`; + +exports[`NavigationBar > renders no navigation items and no user 1`] = ` +
+ +
+`; + +exports[`NavigationBar > renders only navigation items 1`] = ` +
+ +
+`; + +exports[`NavigationBar > renders only user 1`] = ` +
+ +
+`; diff --git a/lib/main.ts b/lib/main.ts index c6cfa06..3355513 100644 --- a/lib/main.ts +++ b/lib/main.ts @@ -2,3 +2,4 @@ import './index.css'; export { Button } from './components/Button/Button'; export { DropdownMenu } from './components/DropdownMenu/DropdownMenu'; +export { NavigationBar } from './components/NavigationBar/NavigationBar';