Skip to content

Commit

Permalink
Feat(web-react): Introduce UNSTABLE_Header #DS-1524
Browse files Browse the repository at this point in the history
  • Loading branch information
curdaj authored and crishpeen committed Dec 17, 2024
1 parent c63f342 commit ffa5374
Show file tree
Hide file tree
Showing 19 changed files with 573 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/web-react/scripts/entryPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const entryPoints = [
{ dirs: ['components', 'Item'] },
{ dirs: ['components', 'Link'] },
{ dirs: ['components', 'Modal'] },
{ dirs: ['components', 'Navigation'] },
{ dirs: ['components', 'NoSsr'] },
{ dirs: ['components', 'Pagination'] },
{ dirs: ['components', 'PartnerLogo'] },
Expand All @@ -54,6 +55,7 @@ const entryPoints = [
{ dirs: ['components', 'UNSTABLE_ActionLayout'] },
{ dirs: ['components', 'UNSTABLE_Avatar'] },
{ dirs: ['components', 'UNSTABLE_EmptyState'] },
{ dirs: ['components', 'UNSTABLE_Header'] },
{ dirs: ['components', 'UNSTABLE_Slider'] },
{ dirs: ['components', 'UNSTABLE_Toggle'] },
{ dirs: ['components', 'UNSTABLE_Truncate'] },
Expand Down
178 changes: 178 additions & 0 deletions packages/web-react/src/components/UNSTABLE_Header/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# UNSTABLE Header

> ⚠️ This component is UNSTABLE. It may significantly change at any point in the future.
> Please use it with caution.
The `UNSTABLE_Header` component is planned to replace the `Header` component in the future.

The `UNSTABLE_Header` is a composition of several subcomponents:

- [UNSTABLE_Header](#unstable-header)
- [UNSTABLE_HeaderLogo](#unstable-headerlogo)

## UNSTABLE Header

The `UNSTABLE_Header` component is a main wrapper which provides mainly the visual for the Header.

```jsx
import { UNSTABLE_Header } from '@lmc-eu/spirit-web-react';

<UNSTABLE_Header>{/* Content go here */}</UNSTABLE_Header>;
```

It also sets CSS variable for the Header height which can be used in other nested components.

### API

| Name | Type | Default | Required | Description |
| ---------- | ----------------------- | ------- | -------- | ------------------------------ |
| `children` | `string` \| `ReactNode` | `null` || Content of the UNSTABLE_Header |

The components accept [additional attributes][readme-additional-attributes].
If you need more control over the styling of a component, you can use [style props][readme-style-props]
and [escape hatches][readme-escape-hatches].

## UNSTABLE HeaderLogo

The `UNSTABLE_HeaderLogo` component is a container for the logo.

Without any modifier, Header is ready to contain necessary blocks in a classic
left-to-right layout (in LTR documents).

```jsx
import { UNSTABLE_HeaderLogo } from '@lmc-eu/spirit-web-react';

<UNSTABLE_HeaderLogo>{/* Content go here */}</UNSTABLE_HeaderLogo>;
```

It inherits the `UNSTABLE_Header` height and sets the logo wrapper height to the same value.

You can use the `ProductLogo` component inside the `UNSTABLE_HeaderLogo` component.

```jsx
<UNSTABLE_HeaderLogo href="#">
<ProductLogo>{/* Logo go here */}</ProductLogo>
</UNSTABLE_HeaderLogo>
```

### API

| Name | Type | Default | Required | Description |
| ------------- | ----------------------- | ------- | -------- | ---------------------------------- |
| `children` | `string` \| `ReactNode` | `null` || Content of the UNSTABLE_HeaderLogo |
| `elementType` | `ElementType` | `a` || Type of element used as |

## Component Composition

Use [`Container`][web-react-container] and [`Flex`][web-react-flex] components to create a layout for the Header content.

```jsx
<UNSTABLE_Header>
<Container>
<Flex alignmentX="left" alignmentY="center">
<UNSTABLE_HeaderLogo href="#">
<ProductLogo>{/* Logo go here */}</ProductLogo>
</UNSTABLE_HeaderLogo>
{/* Navigation go here */}
{/* Other Navigation go here */}
</Flex>
</Container>
</UNSTABLE_Header>
```

This way you can modify the layout of the Header content easily and modify it how you need.

For example you can make the content centered by setting the `Flex` alignment properties to center.

```jsx
<UNSTABLE_Header>
<Flex alignmentX="center" alignmentY="center">
<UNSTABLE_HeaderLogo href="#">
<ProductLogo>{/* Content go here */}</ProductLogo>
</UNSTABLE_HeaderLogo>
</Flex>
</UNSTABLE_Header>
```

Or you can make modify gaps between the content by setting the `Flex` spacing property.

```jsx
<UNSTABLE_Header>
<Container>
<Flex alignmentX="left" alignmentY="center" spacing="space-500">
<UNSTABLE_HeaderLogo href="#">
<ProductLogo>{/* Logo go here */}</ProductLogo>
</UNSTABLE_HeaderLogo>
{/* Navigation go here */}
{/* Other Navigation go here */}
</Flex>
</Container>
</UNSTABLE_Header>
```

If you need the whole Header fluid you can do it by adding the `isFluid` prop to the `Container`.

```jsx
<UNSTABLE_Header>
<Container isFluid>
<Flex alignmentX="left" alignmentY="center" spacing="space-500">
<UNSTABLE_HeaderLogo href="#">
<ProductLogo>{/* Content go here */}</ProductLogo>
</UNSTABLE_HeaderLogo>
</Flex>
</Container>
</UNSTABLE_Header>
```

## With Navigation

You can use the [`Navigation`][web-react-navigation] component inside the `UNSTABLE_Header` component.

The `NavigationLink` components will inherit the `UNSTABLE_Header` height and set the navigation
link height to the same value.

Use the composition mentioned above to create the layout you need.

```jsx
<UNSTABLE_Header>
<Container>
<Flex alignmentX="left" alignmentY="center" spacing="space-1000">
<UNSTABLE_HeaderLogo href="/">
<ProductLogo>{/* Logo go here */}</ProductLogo>
</UNSTABLE_HeaderLogo>
<Navigation>
<NavigationItem>
<NavigationLink href="#">Link</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink href="#" aria-current="page" isSelected>
Selected
</NavigationLink>
</NavigationItem>
<NavigationItem>
<NavigationLink href="#" isDisabled>
Disabled
</NavigationLink>
</NavigationItem>
</Navigation>
<Navigation marginLeft="auto">
<NavigationItem>
<ButtonLink href="#" color="secondary">
Sign up
</ButtonLink>
</NavigationItem>
<NavigationItem>
<ButtonLink href="#">Post a job</ButtonLink>
</NavigationItem>
</Navigation>
</Flex>
</Container>
</UNSTABLE_Header>
```

[readme-additional-attributes]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#additional-attributes
[readme-escape-hatches]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#escape-hatches
[readme-style-props]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/README.md#style-props
[web-react-container]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Container/README.md
[web-react-flex]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Flex/README.md
[web-react-navigation]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Navigation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client';

import classNames from 'classnames';
import React from 'react';
import { useStyleProps } from '../../hooks';
import { SpiritHeaderProps } from '../../types';
import { useUnstableHeaderStyleProps } from './useUnstableHeaderStyleProps';

const UNSTABLE_Header = (props: SpiritHeaderProps): JSX.Element => {
const { children, ...restProps } = props;

const { classProps, props: modifiedProps } = useUnstableHeaderStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

return (
<header {...otherProps} className={classNames(classProps.root, styleProps.className)} style={styleProps.style}>
{children}
</header>
);
};

export default UNSTABLE_Header;
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';

import classNames from 'classnames';
import React, { ElementType, forwardRef } from 'react';
import { useStyleProps } from '../../hooks';
import { PolymorphicRef, SpiritHeaderLogoProps } from '../../types';
import { useUnstableHeaderStyleProps } from './useUnstableHeaderStyleProps';

const defaultProps: Partial<SpiritHeaderLogoProps> = {
elementType: 'a',
};

/* We need an exception for components exported with forwardRef */
/* eslint no-underscore-dangle: ['error', { allow: ['_HeaderLogo'] }] */
const _HeaderLogo = <E extends ElementType = 'a'>(
props: SpiritHeaderLogoProps<E>,
ref: PolymorphicRef<E>,
): JSX.Element => {
const propsWithDefaults = { ...defaultProps, ...props };
const {
elementType: ElementTag = defaultProps.elementType as ElementType,
children,
...restProps
} = propsWithDefaults;
const { classProps, props: modifiedProps } = useUnstableHeaderStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

return (
<ElementTag
{...otherProps}
{...styleProps}
href={restProps.href}
className={classNames(classProps.logo, styleProps.className)}
ref={ref}
>
{children}
</ElementTag>
);
};

const UNSTABLE_HeaderLogo = forwardRef<HTMLAnchorElement, SpiritHeaderLogoProps<ElementType>>(_HeaderLogo);

export default UNSTABLE_HeaderLogo;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import UNSTABLE_Header from '../UNSTABLE_Header';

describe('UNSTABLE_Header', () => {
classNamePrefixProviderTest(UNSTABLE_Header, 'UNSTABLE_Header');

stylePropsTest(UNSTABLE_Header);

restPropsTest(UNSTABLE_Header, 'header');

it('should have default classname', () => {
render(<UNSTABLE_Header>Content</UNSTABLE_Header>);

const header = screen.getByRole('banner');

expect(header).toHaveClass('UNSTABLE_Header');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { classNamePrefixProviderTest } from '../../../../tests/providerTests/classNamePrefixProviderTest';
import { restPropsTest } from '../../../../tests/providerTests/restPropsTest';
import { stylePropsTest } from '../../../../tests/providerTests/stylePropsTest';
import UNSTABLE_HeaderLogo from '../UNSTABLE_HeaderLogo';

describe('UNSTABLE_HeaderLogo', () => {
classNamePrefixProviderTest(UNSTABLE_HeaderLogo, 'UNSTABLE_HeaderLogo');

stylePropsTest(UNSTABLE_HeaderLogo);

restPropsTest(UNSTABLE_HeaderLogo, 'a');

it('should have default classname', () => {
render(<UNSTABLE_HeaderLogo href="#">Content</UNSTABLE_HeaderLogo>);

expect(screen.getByRole('link')).toHaveClass('UNSTABLE_HeaderLogo');
});

it('should render children', () => {
render(<UNSTABLE_HeaderLogo href="#">Content</UNSTABLE_HeaderLogo>);

expect(screen.getByText('Content')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { renderHook } from '@testing-library/react';
import { useUnstableHeaderStyleProps } from '../useUnstableHeaderStyleProps';

describe('useUnstableHeaderStyleProps', () => {
it('should return defaults', () => {
const props = {};
const { result } = renderHook(() => useUnstableHeaderStyleProps(props));

expect(result.current.classProps.root).toBe('UNSTABLE_Header');
expect(result.current.classProps.logo).toBe('UNSTABLE_HeaderLogo');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import { Container } from '../../Container';
import { Flex } from '../../Flex';
import { ProductLogo } from '../../ProductLogo';
import { defaultSvgLogo } from '../../ProductLogo/demo/ProductLogoDefault';
import UNSTABLE_Header from '../UNSTABLE_Header';
import UNSTABLE_HeaderLogo from '../UNSTABLE_HeaderLogo';

const HeaderDefault = () => {
return (
<UNSTABLE_Header>
<Container>
<Flex alignmentX="left" alignmentY="center">
<UNSTABLE_HeaderLogo href="#" aria-label="JobBoard homepage">
<ProductLogo>{defaultSvgLogo}</ProductLogo>
</UNSTABLE_HeaderLogo>
</Flex>
</Container>
</UNSTABLE_Header>
);
};
export default HeaderDefault;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { Container } from '../../Container';
import { Flex } from '../../Flex';
import { ProductLogo } from '../../ProductLogo';
import { defaultSvgLogo } from '../../ProductLogo/demo/ProductLogoDefault';
import UNSTABLE_Header from '../UNSTABLE_Header';
import UNSTABLE_HeaderLogo from '../UNSTABLE_HeaderLogo';

const HeaderFluid = () => {
return (
<UNSTABLE_Header>
<Container isFluid>
<Flex alignmentX="left" alignmentY="center">
<UNSTABLE_HeaderLogo href="#" aria-label="JobBoard homepage">
<ProductLogo>{defaultSvgLogo}</ProductLogo>
</UNSTABLE_HeaderLogo>
</Flex>
</Container>
</UNSTABLE_Header>
);
};

export default HeaderFluid;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import { Flex } from '../../Flex';
import { ProductLogo } from '../../ProductLogo';
import { defaultSvgLogo } from '../../ProductLogo/demo/ProductLogoDefault';
import UNSTABLE_Header from '../UNSTABLE_Header';
import UNSTABLE_HeaderLogo from '../UNSTABLE_HeaderLogo';

const HeaderMinimal = () => {
return (
<UNSTABLE_Header>
<Flex alignmentX="center" alignmentY="center">
<UNSTABLE_HeaderLogo href="#" aria-label="JobBoard homepage">
<ProductLogo>{defaultSvgLogo}</ProductLogo>
</UNSTABLE_HeaderLogo>
</Flex>
</UNSTABLE_Header>
);
};

export default HeaderMinimal;
Loading

0 comments on commit ffa5374

Please sign in to comment.