-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
508 additions
and
47 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof NavigationBar>; | ||
|
||
export default meta; | ||
|
||
type Story = StoryObj<typeof meta>; | ||
|
||
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 }) => ( | ||
<a href="/yksiloille" {...rootProps}> | ||
{children} | ||
</a> | ||
), | ||
}, | ||
{ | ||
text: 'Ohjaajille', | ||
active: false, | ||
component: ({ children, ...rootProps }) => ( | ||
<a href="/ohjaajille" {...rootProps}> | ||
{children} | ||
</a> | ||
), | ||
}, | ||
{ | ||
text: 'Kouluttajille', | ||
active: false, | ||
component: ({ children, ...rootProps }) => ( | ||
<a href="/kouluttajille" {...rootProps}> | ||
{children} | ||
</a> | ||
), | ||
}, | ||
{ | ||
text: 'Viranomaisille', | ||
active: false, | ||
component: ({ children, ...rootProps }) => ( | ||
<a href="/viranomaisille" {...rootProps}> | ||
{children} | ||
</a> | ||
), | ||
}, | ||
]; | ||
|
||
const user: NavigationBarProps['user'] = { | ||
name: 'Jane Doe', | ||
src: 'https://i.pravatar.cc/200?img=24', | ||
component: ({ children, ...rootProps }) => ( | ||
<a href="/profiili" {...rootProps}> | ||
{children} | ||
</a> | ||
), | ||
}; | ||
|
||
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) => ( | ||
<div> | ||
<header className="sticky top-0"> | ||
<Story /> | ||
</header> | ||
<main> | ||
{[...Array<undefined>(20)].map((_p, index) => ( | ||
<p key={index} className="p-4"> | ||
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. | ||
</p> | ||
))} | ||
</main> | ||
</div> | ||
), | ||
], | ||
parameters: { | ||
...parameters, | ||
docs: { | ||
description: { | ||
story: 'This story demonstrates how the navigation bar behaves when it is sticky.', | ||
}, | ||
story: { | ||
inline: false, | ||
iframeHeight: 400, | ||
}, | ||
source: { | ||
code: '<header className="sticky top-0">\n <NavigationBar />\n</header>', | ||
}, | ||
}, | ||
}, | ||
args, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) => ( | ||
<a href={href} {...rootProps}> | ||
{children} | ||
</a> | ||
), | ||
})); | ||
|
||
const user = { | ||
name: 'Jane Doe', | ||
src: 'user.jpg', | ||
component: ({ children, ...rootProps }: NavigationBarLinkProps) => ( | ||
<a href="/profile" {...rootProps}> | ||
{children} | ||
</a> | ||
), | ||
}; | ||
|
||
it('renders navigation items and user', () => { | ||
const { container } = render(<NavigationBar items={items} user={user} />); | ||
|
||
// 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(<NavigationBar items={items} />); | ||
|
||
// 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(<NavigationBar user={user} />); | ||
|
||
// 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(<NavigationBar />); | ||
|
||
// 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(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
export interface NavigationBarLinkProps { | ||
className?: string; | ||
children: React.ReactNode; | ||
} | ||
|
||
export type NavigationBarLink = React.ComponentType<NavigationBarLinkProps>; | ||
|
||
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) => ( | ||
<div className="min-w-min border-b-2 border-[#808080] bg-[#FFFFFFE5]"> | ||
<nav className="mx-auto flex h-[72px] items-center justify-between gap-4 p-4 font-semibold lg:container"> | ||
<div className="inline-flex select-none items-center gap-2 text-[24px] leading-[140%] text-[#888]"> | ||
<div className="h-8 w-8 bg-[#808080]"></div>JOD | ||
</div> | ||
{(items ?? user) && ( | ||
<ul className="inline-flex items-center gap-4"> | ||
{items?.map((item, index) => ( | ||
<li key={index}> | ||
<item.component | ||
className={`block rounded-lg px-4 py-2 ${item.active ? 'bg-[#697077] text-white' : 'hover:bg-[#edeff0] focus:bg-[#edeff0]'}`} | ||
> | ||
{item.text} | ||
</item.component> | ||
</li> | ||
))} | ||
{user && ( | ||
<li> | ||
{user.component ? ( | ||
<user.component className="block h-[40px] w-[40px] rounded-full"> | ||
<img className="rounded-full" src={user.src} alt={user.name} /> | ||
</user.component> | ||
) : ( | ||
<img className="block h-[40px] w-[40px] rounded-full" src={user.src} alt={user.name} /> | ||
)} | ||
</li> | ||
)} | ||
</ul> | ||
)} | ||
</nav> | ||
</div> | ||
); |
Oops, something went wrong.