diff --git a/package-lock.json b/package-lock.json index 5861699..543e1ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,8 @@ "lucide-react": "^0.378.0", "polished": "^4.3.1", "react-icons": "^5.2.1", + "react-router-dom": "^6.26.1", + "react-select": "^5.8.0", "styled-components": "^6.1.8" }, @@ -3603,6 +3605,13 @@ "node": ">= 8" } }, + "node_modules/@remix-run/router": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz", + "integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==", + "engines": { + "node": ">=14.0.0" + "node_modules/@octokit/auth-token": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", @@ -3786,6 +3795,7 @@ }, "engines": { "node": ">=12" + } }, "node_modules/@rollup/pluginutils": { @@ -18747,6 +18757,35 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz", + "integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==", + "dependencies": { + "@remix-run/router": "1.19.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz", + "integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==", + "dependencies": { + "@remix-run/router": "1.19.1", + "react-router": "6.26.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + "node_modules/react-select": { "version": "5.8.0", "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.8.0.tgz", diff --git a/package.json b/package.json index 42a7146..82d1729 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "lucide-react": "^0.378.0", "polished": "^4.3.1", "react-icons": "^5.2.1", + "react-router-dom": "^6.26.1", "react-select": "^5.8.0", "styled-components": "^6.1.8" }, diff --git a/src/components/PageHeader/PageHeader.stories.ts b/src/components/PageHeader/PageHeader.stories.ts new file mode 100644 index 0000000..2727e8d --- /dev/null +++ b/src/components/PageHeader/PageHeader.stories.ts @@ -0,0 +1,39 @@ +import { PageHeader } from './PageHeader'; +import type { Meta, StoryObj } from '@storybook/react'; + +const meta = { + title: 'Components/PageHeader', + component: PageHeader, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const defaultProps = { + +}; + +const disableControls = { + parameters: { + controls: { + disable: true + }, + actions: { + disable: true + }, + } +}; + +export const Demo: Story = { + args: { + ...defaultProps + }, + tags: ['excludeFromSidebar'] +}; + +export const Default: Story = { + args: { + ...defaultProps + }, + ...disableControls +}; diff --git a/src/components/PageHeader/PageHeader.style.ts b/src/components/PageHeader/PageHeader.style.ts new file mode 100644 index 0000000..84bbf4f --- /dev/null +++ b/src/components/PageHeader/PageHeader.style.ts @@ -0,0 +1,56 @@ +import styled from 'styled-components'; +import { ThemeElementSize, ThemeColor } from '../../types'; +import { readableColor, shade } from 'polished'; + +type StyledPageHeaderProps = { + $size: ThemeElementSize; + $color: ThemeColor; +}; + +export const StyledPageHeader = styled.header` + .header-container { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: ${props => (props.$size === 'sm' ? '600px' : '1200px')}; + height: ${props => props.$size === 'sm' ? props.theme.spacing.xl : props.theme.spacing.xxl}; + padding: ${props => props.$size === 'sm' ? props.theme.spacing.sm : props.theme.spacing.md}; + margin: 0 auto; + background-color: ${props => props.theme.colors[props.$color]}; + color: ${props => readableColor(props.theme.colors[props.$color])}; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + } + + .logo-container { + flex-shrink: 0; + } + + .logo { + height: 100%; + max-height: ${props => props.$size === 'sm' ? props.theme.spacing.sm : props.theme.spacing.lg}; + width: auto; + } + + .menu-container { + display: flex; + gap: ${props => props.$size === 'sm' ? props.theme.spacing.sm : props.theme.spacing.md}; + } + + .menu-item { + all: unset; + font-size: ${props => props.$size === 'sm' ? props.theme.fontSizes.sm : props.theme.fontSizes.md}; + padding: ${props => props.$size === 'sm' ? props.theme.spacing.xs : props.theme.spacing.sm}; + cursor: pointer; + transition: color 150ms ease-in-out, background-color 150ms ease-in-out; + text-decoration: none; + } + + .menu-item:hover { + background-color: ${props => shade(0.15, props.theme.colors.primary)}; + } + + .menu-item.active { + font-weight: ${props => props.theme.fontWeights.bold}; + } +`; diff --git a/src/components/PageHeader/PageHeader.test.tsx b/src/components/PageHeader/PageHeader.test.tsx new file mode 100644 index 0000000..b82f700 --- /dev/null +++ b/src/components/PageHeader/PageHeader.test.tsx @@ -0,0 +1,13 @@ +import { screen } from '@testing-library/react'; +import { renderWithDeps } from '../../../jest.utils'; +import { PageHeader } from './PageHeader'; + +describe('', () => { + it('renders', () => { + renderWithDeps(); + + const pageHeader = screen.getByTestId('PageHeader'); + + expect(pageHeader).toBeInTheDocument(); + }); +}); diff --git a/src/components/PageHeader/PageHeader.tsx b/src/components/PageHeader/PageHeader.tsx new file mode 100644 index 0000000..903b608 --- /dev/null +++ b/src/components/PageHeader/PageHeader.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react'; +import { NavLink } from 'react-router-dom'; +import { StyledPageHeader } from './PageHeader.style'; +import { ThemeElementSize, ThemeColor } from '../../types'; + +type MenuItem = { + label: string; + path: string; +}; + +type PageHeaderProps = { + logo: string; // URL or path to the logo image + menuItems: MenuItem[]; // Array of menu items + size?: ThemeElementSize; // Optional size prop, if needed + color?: ThemeColor; +}; + +export const PageHeader: FC = ({ + logo = 'Path/to/logo.png', + menuItems = [], + size = 'lg', + color = 'primary', +}: PageHeaderProps) => { + return ( + +
+
+ Logo +
+ +
+
+ ); +}; diff --git a/src/index.ts b/src/index.ts index 641e997..8219d0d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,4 +6,5 @@ export * from './components/Label/Label'; export * from './components/LinkButton/LinkButton'; export * from './components/Table/Table'; export * from './components/TruncatedText/TruncatedText'; -export * from './components/Modal/Modal'; +export * from './components/PageHeader/PageHeader'; +export * from './components/Modal/Modal'; \ No newline at end of file diff --git a/templates/TemplateName.test.tsx b/templates/TemplateName.test.tsx index ff6ee92..86d9672 100644 --- a/templates/TemplateName.test.tsx +++ b/templates/TemplateName.test.tsx @@ -1,4 +1,5 @@ import { screen } from '@testing-library/react'; + import { renderWithDeps } from '../../../jest.utils'; import TemplateName from './TemplateName'; import { axe } from 'jest-axe';