Skip to content

Commit 73f17fd

Browse files
authored
Merge pull request #4 from teambit/header-component
feat: header component
2 parents 8967069 + 07b2aa2 commit 73f17fd

File tree

9 files changed

+493
-176
lines changed

9 files changed

+493
-176
lines changed

.bitmap

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,20 @@
1616
"mainFile": "index.ts",
1717
"rootDir": "design/patterns/form"
1818
},
19+
"patterns/header": {
20+
"name": "patterns/header",
21+
"scope": "",
22+
"version": "",
23+
"defaultScope": "automations.design",
24+
"mainFile": "index.ts",
25+
"rootDir": "design/patterns/header",
26+
"config": {
27+
"bitdev.react/react-env@0a734828d968c74e981ef0ca77acb414e90ea08d": {},
28+
"teambit.envs/envs": {
29+
"env": "bitdev.react/react-env"
30+
}
31+
}
32+
},
1933
"ui/button": {
2034
"name": "ui/button",
2135
"scope": "automations.design",
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { Header } from './header.js';
3+
4+
export const BasicHeader = () => {
5+
const navItems = [
6+
{ label: 'Home', href: '/' },
7+
{ label: 'About', href: '/about' },
8+
{ label: 'Services', href: '/services' },
9+
{ label: 'Contact', href: '/contact' },
10+
];
11+
12+
const userMenuItems = [
13+
{ label: 'Profile', onClick: () => console.log('Profile clicked') },
14+
{ label: 'Settings', onClick: () => console.log('Settings clicked') },
15+
{ label: 'Logout', onClick: () => console.log('Logout clicked') },
16+
];
17+
18+
return (
19+
<Header
20+
logo={<div style={{ fontSize: '1.5rem', fontWeight: 'bold' }}>Logo</div>}
21+
navItems={navItems}
22+
userMenuItems={userMenuItems}
23+
/>
24+
);
25+
};
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
description: A responsive header component with navigation and user menu
3+
labels: ['react', 'header', 'navigation', 'ui']
4+
---
5+
6+
import { Header } from './header.js';
7+
import { BasicHeader } from './header.composition.js';
8+
9+
# Header Component
10+
11+
A flexible and responsive header component that includes a logo area, navigation menu, and user menu.
12+
13+
## Usage
14+
15+
```jsx
16+
import { Header } from '@your-scope/design.patterns.header';
17+
18+
const navItems = [
19+
{ label: 'Home', href: '/' },
20+
{ label: 'About', href: '/about' },
21+
{ label: 'Services', href: '/services' },
22+
];
23+
24+
const userMenuItems = [
25+
{ label: 'Profile', onClick: () => console.log('Profile clicked') },
26+
{ label: 'Settings', onClick: () => console.log('Settings clicked') },
27+
];
28+
29+
<Header
30+
logo={<YourLogo />}
31+
navItems={navItems}
32+
userMenuItems={userMenuItems}
33+
fixed={true}
34+
/>
35+
```
36+
37+
## Example
38+
39+
<BasicHeader />
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
.header {
2+
width: 100%;
3+
background-color: #ffffff;
4+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
5+
padding: 1rem 0;
6+
}
7+
8+
.fixed {
9+
position: fixed;
10+
top: 0;
11+
left: 0;
12+
z-index: 1000;
13+
}
14+
15+
.container {
16+
max-width: 1200px;
17+
margin: 0 auto;
18+
padding: 0 1rem;
19+
display: flex;
20+
align-items: center;
21+
justify-content: space-between;
22+
}
23+
24+
.logo {
25+
display: flex;
26+
align-items: center;
27+
}
28+
29+
.nav {
30+
display: flex;
31+
gap: 2rem;
32+
margin: 0 2rem;
33+
}
34+
35+
.navItem {
36+
color: #333;
37+
text-decoration: none;
38+
font-weight: 500;
39+
transition: color 0.2s ease;
40+
}
41+
42+
.navItem:hover {
43+
color: #007bff;
44+
}
45+
46+
.userMenu {
47+
display: flex;
48+
gap: 1rem;
49+
}
50+
51+
.userMenuItem {
52+
background: none;
53+
border: none;
54+
padding: 0.5rem 1rem;
55+
cursor: pointer;
56+
color: #333;
57+
font-weight: 500;
58+
transition: color 0.2s ease;
59+
}
60+
61+
.userMenuItem:hover {
62+
color: #007bff;
63+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { render, screen } from '@testing-library/react';
3+
import { BasicHeader } from './header.composition.js';
4+
5+
describe('Header component', () => {
6+
it('should render navigation items correctly', () => {
7+
render(<BasicHeader />);
8+
9+
// Check if navigation items are rendered
10+
expect(screen.getByText('Home')).toBeInTheDocument();
11+
expect(screen.getByText('About')).toBeInTheDocument();
12+
expect(screen.getByText('Services')).toBeInTheDocument();
13+
expect(screen.getByText('Contact')).toBeInTheDocument();
14+
});
15+
16+
it('should render user menu items correctly', () => {
17+
render(<BasicHeader />);
18+
19+
// Check if user menu items are rendered
20+
expect(screen.getByText('Profile')).toBeInTheDocument();
21+
expect(screen.getByText('Settings')).toBeInTheDocument();
22+
expect(screen.getByText('Logout')).toBeInTheDocument();
23+
});
24+
25+
it('should render logo correctly', () => {
26+
render(<BasicHeader />);
27+
28+
// Check if logo is rendered
29+
expect(screen.getByText('Logo')).toBeInTheDocument();
30+
});
31+
});

design/patterns/header/header.tsx

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React from 'react';
2+
import styles from './header.module.css';
3+
4+
export type HeaderProps = {
5+
/**
6+
* The logo to display in the header
7+
*/
8+
logo?: React.ReactNode;
9+
/**
10+
* Navigation items to display in the header
11+
*/
12+
navItems?: Array<{
13+
label: string;
14+
href: string;
15+
}>;
16+
/**
17+
* User menu items to display in the header
18+
*/
19+
userMenuItems?: Array<{
20+
label: string;
21+
onClick: () => void;
22+
}>;
23+
/**
24+
* Whether the header is fixed at the top
25+
*/
26+
fixed?: boolean;
27+
/**
28+
* Optional data-testid for testing
29+
*/
30+
'data-testid'?: string;
31+
};
32+
33+
export function Header({
34+
logo,
35+
navItems = [],
36+
userMenuItems = [],
37+
fixed = false,
38+
'data-testid': testId,
39+
}: HeaderProps) {
40+
return (
41+
<header
42+
className={`${styles.header} ${fixed ? styles.fixed : ''}`}
43+
data-testid={testId}
44+
>
45+
<div className={styles.container}>
46+
{logo && (
47+
<div className={styles.logo}>
48+
{logo}
49+
</div>
50+
)}
51+
52+
{navItems.length > 0 && (
53+
<nav className={styles.nav}>
54+
{navItems.map((item, index) => (
55+
<a
56+
key={index}
57+
href={item.href}
58+
className={styles.navItem}
59+
>
60+
{item.label}
61+
</a>
62+
))}
63+
</nav>
64+
)}
65+
66+
{userMenuItems.length > 0 && (
67+
<div className={styles.userMenu}>
68+
{userMenuItems.map((item, index) => (
69+
<button
70+
key={index}
71+
onClick={item.onClick}
72+
className={styles.userMenuItem}
73+
>
74+
{item.label}
75+
</button>
76+
))}
77+
</div>
78+
)}
79+
</div>
80+
</header>
81+
);
82+
}

design/patterns/header/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { Header } from './header.js';
2+
export type { HeaderProps } from './header.js';

0 commit comments

Comments
 (0)