From be6942924ad1e488a89540fa32c64318a793aed1 Mon Sep 17 00:00:00 2001 From: Daniel Andrews Date: Mon, 28 Oct 2024 17:53:33 +1100 Subject: [PATCH] added the menu bar component (#31) Co-authored-by: Leesa Ward Co-authored-by: ben-AI-cybersec <142491786+ben-AI-cybersec@users.noreply.github.com> --- src/components/MenuBar/MenuBar.stories.ts | 44 +++++++++++++++++++ src/components/MenuBar/MenuBar.style.ts | 52 +++++++++++++++++++++++ src/components/MenuBar/MenuBar.test.tsx | 28 ++++++++++++ src/components/MenuBar/MenuBar.tsx | 38 +++++++++++++++++ 4 files changed, 162 insertions(+) create mode 100644 src/components/MenuBar/MenuBar.stories.ts create mode 100644 src/components/MenuBar/MenuBar.style.ts create mode 100644 src/components/MenuBar/MenuBar.test.tsx create mode 100644 src/components/MenuBar/MenuBar.tsx diff --git a/src/components/MenuBar/MenuBar.stories.ts b/src/components/MenuBar/MenuBar.stories.ts new file mode 100644 index 0000000..13902a6 --- /dev/null +++ b/src/components/MenuBar/MenuBar.stories.ts @@ -0,0 +1,44 @@ +import { MenuBar } from './MenuBar'; +import { action } from '@storybook/addon-actions'; // Use 'action' instead of 'fn' +import type { Meta, StoryObj } from '@storybook/react'; +import { themeColorSubset } from '../../types'; + +const meta: Meta = { + title: 'Components/MenuBar', + component: MenuBar, + argTypes: { + color: { control: 'select', options: Object.keys(themeColorSubset) }, + }, + args: { + color: 'primary', + items: [ + { label: 'Home', link: '/', onClick: action('Home clicked') }, + { label: 'About', link: '/about', onClick: action('About clicked') }, + { label: 'Contact', link: '/contact', onClick: action('Contact clicked') }, + ], + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + color: 'primary', + }, +}; + +export const Secondary: Story = { + args: { + color: 'secondary', + }, +}; + +export const WithCustomItems: Story = { + args: { + items: [ + { label: 'Services', link: '/services', onClick: action('Services clicked') }, + { label: 'Blog', link: '/blog', onClick: action('Blog clicked') }, + ], + }, +}; diff --git a/src/components/MenuBar/MenuBar.style.ts b/src/components/MenuBar/MenuBar.style.ts new file mode 100644 index 0000000..9144238 --- /dev/null +++ b/src/components/MenuBar/MenuBar.style.ts @@ -0,0 +1,52 @@ +import styled from 'styled-components'; +import { ThemeColor } from '../../types'; + +type StyledMenuBarProps = { + $color: ThemeColor; +}; + +export const StyledMenuBar = styled.div` + display: flex; + align-items: center; + background-color: ${(props) => props.theme.colors[props.$color]}; + padding: ${(props) => props.theme.spacing.md}; + border-bottom: 1px solid ${(props) => props.theme.colors.subtle}; + justify-content: space-between; +`; + +export const MenuItem = styled.a` + color: ${(props) => props.theme.colors.dark}; + margin: 0 ${(props) => props.theme.spacing.sm}; + text-decoration: none; + font-size: ${(props) => props.theme.fontSizes.default}; + + &:hover { + text-decoration: underline; + } + + @media (max-width: 768px) { + font-size: ${(props) => props.theme.fontSizes.sm}; + } +`; + +export const HamburgerIcon = styled.div` + cursor: pointer; + font-size: 1.5rem; + display: none; + + @media (max-width: 768px) { + display: block; + } +`; + +export const IconImage = styled.img` + width: 40px; + height: 40px; + margin-right: ${(props) => props.theme.spacing.md}; + cursor: pointer; + + @media (max-width: 768px) { + width: 30px; + height: 30px; + } +`; diff --git a/src/components/MenuBar/MenuBar.test.tsx b/src/components/MenuBar/MenuBar.test.tsx new file mode 100644 index 0000000..3560d0b --- /dev/null +++ b/src/components/MenuBar/MenuBar.test.tsx @@ -0,0 +1,28 @@ +import { screen, fireEvent } from '@testing-library/react'; +import { renderWithDeps } from '../../../jest.utils'; +import { MenuBar } from './MenuBar'; + +const mockClick = jest.fn(); + +describe('', () => { + const menuItems = [ + { label: 'Home', link: '/', onClick: mockClick }, + { label: 'About', link: '/about', onClick: mockClick }, + { label: 'Contact', link: '/contact', onClick: mockClick }, + ]; + + it('renders the menu items', () => { + renderWithDeps(); + + expect(screen.getByText('Home')).toBeVisible(); + expect(screen.getByText('About')).toBeVisible(); + expect(screen.getByText('Contact')).toBeVisible(); + }); + + it('calls onClick when a menu item is clicked', () => { + renderWithDeps(); + + fireEvent.click(screen.getByText('Home')); + expect(mockClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/MenuBar/MenuBar.tsx b/src/components/MenuBar/MenuBar.tsx new file mode 100644 index 0000000..163c0a1 --- /dev/null +++ b/src/components/MenuBar/MenuBar.tsx @@ -0,0 +1,38 @@ +import { FC } from 'react'; +import { StyledMenuBar, MenuItem, HamburgerIcon, IconImage } from './MenuBar.style'; +import { ThemeColor } from '../../types'; + +type MenuBarItem = { + label: string; + link: string; + onClick?: () => void; +}; + +type MenuBarProps = { + items: MenuBarItem[]; + color?: ThemeColor; // Added color prop like in the Button component +}; + +export const MenuBar: FC = ({ items, color = 'primary' }) => { + return ( + + + + + + + + ); +}; + +export default MenuBar;