Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(web-react): Introduce variants to NavigationAction #DS-1672 #1898

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import classNames from 'classnames';
import React, { ElementType, forwardRef } from 'react';
import { useStyleProps } from '../../hooks';
import { PolymorphicRef, SpiritNavigationActionProps } from '../../types';
import { NavigationActionVariants } from './constants';
import { useNavigationActionProps } from './useNavigationActionProps';
import { useNavigationStyleProps } from './useNavigationStyleProps';

const defaultProps: Partial<SpiritNavigationActionProps> = {
elementType: 'a',
variant: NavigationActionVariants.BOX,
};

/* We need an exception for components exported with forwardRef */
Expand Down
6 changes: 6 additions & 0 deletions packages/web-react/src/components/Navigation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,15 @@ and [escape hatches][readme-escape-hatches].

The `NavigationAction` is component that is styled to be used as a navigation action.

It has to be either `box` or `pill` variant. Default variant is `box`.

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

<NavigationAction href="#">Link</NavigationAction>;
<NavigationAction href="#" variant="pill">
Link
</NavigationAction>;
```

It can obtain `isSelected` or `isDisabled` states by adding the respective props.
Expand Down Expand Up @@ -110,6 +115,7 @@ inherit the height of the `Header`.
| `isSelected` | `boolean` | `false` | ✕ | Whether the action is selected |
| `ref` | `ForwardedRef<HTMLAnchorElement>` | — | ✕ | Anchor element reference |
| `target` | `string` | `null` | ✕ | Link target |
| `variant` | `box` \| `pill` | `box` | ✕ | Variant of the NavigationAction |

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]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ describe('NavigationAction', () => {
it('should have default classname', () => {
render(<NavigationAction href="/">Content</NavigationAction>);

expect(screen.getByRole('link')).toHaveClass('NavigationAction');
const navigationAction = screen.getByRole('link');

expect(navigationAction).toHaveClass('NavigationAction');
expect(navigationAction).toHaveClass('NavigationAction--box');
});

it('should have selected classname', () => {
Expand Down Expand Up @@ -44,4 +47,17 @@ describe('NavigationAction', () => {

expect(screen.getByText('Content')).toBeInTheDocument();
});

it('should have correct className for pill variant', () => {
render(
<NavigationAction href="/" variant="pill">
Content
</NavigationAction>,
);

const navigationAction = screen.getByRole('link');

expect(navigationAction).toHaveClass('NavigationAction');
expect(navigationAction).toHaveClass('NavigationAction--pill');
});
});
Original file line number Diff line number Diff line change
@@ -1,30 +1,57 @@
import { renderHook } from '@testing-library/react';
import { Direction } from '../../../constants';
import { SpiritNavigationActionProps } from '../../../types';
import { NavigationActionVariants } from '../constants';
import { useNavigationStyleProps } from '../useNavigationStyleProps';

const navigationActionVariantDataProvider = [
{
variant: NavigationActionVariants.BOX,
className: 'NavigationAction--box',
description: 'box variant',
},
{
variant: NavigationActionVariants.PILL,
className: 'NavigationAction--pill',
description: 'pill variant',
},
];

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

expect(result.current.classProps.root).toBe('Navigation Navigation--horizontal');
expect(result.current.classProps.action).toBe('NavigationAction');
expect(result.current.classProps.action).toBe('NavigationAction NavigationAction--box');
expect(result.current.classProps.item).toBe('NavigationItem NavigationItem--alignmentYCenter');
});

it('should return disabled class', () => {
const props: SpiritNavigationActionProps = { isDisabled: true };
const { result } = renderHook(() => useNavigationStyleProps(props));
it.each(navigationActionVariantDataProvider)(
`should return disabled class for $description`,
({ variant, className }) => {
const props: SpiritNavigationActionProps = { isDisabled: true, variant };
const { result } = renderHook(() => useNavigationStyleProps(props));

expect(result.current.classProps.action).toBe('NavigationAction NavigationAction--disabled');
});
expect(result.current.classProps.action).toBe(`NavigationAction ${className} NavigationAction--disabled`);
},
);

it.each(navigationActionVariantDataProvider)(
'should return selected class for $description',
({ variant, className }) => {
const props = { isSelected: true, variant };
const { result } = renderHook(() => useNavigationStyleProps(props));

expect(result.current.classProps.action).toBe(`NavigationAction ${className} NavigationAction--selected`);
},
);

it('should return selected class', () => {
const props = { isSelected: true };
it('should return pill variant default class', () => {
const props = { variant: NavigationActionVariants.PILL };
const { result } = renderHook(() => useNavigationStyleProps(props));

expect(result.current.classProps.action).toBe('NavigationAction NavigationAction--selected');
expect(result.current.classProps.action).toBe('NavigationAction NavigationAction--pill');
});

it('should return alignment class', () => {
Expand All @@ -34,7 +61,7 @@ describe('useNavigationStyleProps', () => {
expect(result.current.classProps.item).toBe('NavigationItem NavigationItem--alignmentYStretch');
});

it('should return if navigation is vertical', () => {
it('should return correct class if navigation is vertical', () => {
const props = { direction: Direction.VERTICAL };
const { result } = renderHook(() => useNavigationStyleProps(props));

Expand Down
4 changes: 4 additions & 0 deletions packages/web-react/src/components/Navigation/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const NavigationActionVariants = {
BOX: 'box',
PILL: 'pill',
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Navigation from '../Navigation';
import NavigationAction from '../NavigationAction';
import NavigationItem from '../NavigationItem';

const NavigationHorizontalWithAction = () => {
const NavigationHorizontalWithBoxAction = () => {
return (
<Navigation aria-label="Main Navigation">
<NavigationItem>
Expand All @@ -22,4 +22,4 @@ const NavigationHorizontalWithAction = () => {
</Navigation>
);
};
export default NavigationHorizontalWithAction;
export default NavigationHorizontalWithBoxAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import Navigation from '../Navigation';
import NavigationAction from '../NavigationAction';
import NavigationItem from '../NavigationItem';

const NavigationHorizontalWithPillAction = () => {
return (
<Navigation aria-label="Main Navigation">
<NavigationItem>
<NavigationAction href="/" variant="pill">
Link
</NavigationAction>
</NavigationItem>
<NavigationItem>
<NavigationAction href="/" variant="pill" aria-current="page" isSelected>
Selected
</NavigationAction>
</NavigationItem>
<NavigationItem>
<NavigationAction href="/" variant="pill" isDisabled>
Disabled
</NavigationAction>
</NavigationItem>
</Navigation>
);
};
export default NavigationHorizontalWithPillAction;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Navigation from '../Navigation';
import NavigationAction from '../NavigationAction';
import NavigationItem from '../NavigationItem';

const NavigationVerticalWithAction = () => {
const NavigationVerticalWithBoxAction = () => {
return (
<Navigation aria-label="Main Navigation" direction="vertical">
<NavigationItem>
Expand All @@ -22,4 +22,4 @@ const NavigationVerticalWithAction = () => {
</Navigation>
);
};
export default NavigationVerticalWithAction;
export default NavigationVerticalWithBoxAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import Navigation from '../Navigation';
import NavigationAction from '../NavigationAction';
import NavigationItem from '../NavigationItem';

const NavigationVerticalWithPillAction = () => {
return (
<Navigation aria-label="Main Navigation" direction="vertical">
<NavigationItem>
<NavigationAction href="/" variant="pill">
Link
</NavigationAction>
</NavigationItem>
<NavigationItem>
<NavigationAction href="/" variant="pill" aria-current="page" isSelected>
Selected
</NavigationAction>
</NavigationItem>
<NavigationItem>
<NavigationAction href="/" variant="pill" isDisabled>
Disabled
</NavigationAction>
</NavigationItem>
</Navigation>
);
};
export default NavigationVerticalWithPillAction;
24 changes: 16 additions & 8 deletions packages/web-react/src/components/Navigation/demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import ReactDOM from 'react-dom/client';
import DocsSection from '../../../../docs/DocsSections';
import { IconsProvider } from '../../../context';
import NavigationHorizontal from './NavigationHorizontal';
import NavigationHorizontalWithAction from './NavigationHorizontalWithAction';
import NavigationHorizontalWithBoxAction from './NavigationHorizontalWithBoxAction';
import NavigationHorizontalWithButtons from './NavigationHorizontalWithButtons';
import NavigationHorizontalWithDropdown from './NavigationHorizontalWithDropdown';
import NavigationHorizontalWithPillAction from './NavigationHorizontalWithPillAction';
import NavigationVertical from './NavigationVertical';
import NavigationVerticalWithAction from './NavigationVerticalWithAction';
import NavigationVerticalWithBoxAction from './NavigationVerticalWithBoxAction';
import NavigationVerticalWithButtons from './NavigationVerticalWithButtons';
import NavigationVerticalWithCollapse from './NavigationVerticalWithCollapse';
import NavigationVerticalWithPillAction from './NavigationVerticalWithPillAction';

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
Expand All @@ -25,24 +27,30 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<DocsSection title="Vertical Navigation" stackAlignment="stretch">
<NavigationVertical />
</DocsSection>
<DocsSection title="Horizontal Navigation with NavigationAction" stackAlignment="stretch">
<NavigationHorizontalWithAction />
<DocsSection title="Horizontal Navigation with Box NavigationAction" stackAlignment="stretch">
<NavigationHorizontalWithBoxAction />
</DocsSection>
<DocsSection title="Vertical Navigation with NavigationAction" stackAlignment="stretch">
<NavigationVerticalWithAction />
<DocsSection title="Vertical Navigation with Box NavigationAction" stackAlignment="stretch">
<NavigationVerticalWithBoxAction />
</DocsSection>
<DocsSection title="Horizontal Navigation with Buttons" stackAlignment="stretch">
<NavigationHorizontalWithButtons />
</DocsSection>
<DocsSection title="Vertical Navigation with Buttons" stackAlignment="stretch">
<NavigationVerticalWithButtons />
</DocsSection>
<DocsSection title="Nested Horizontal Navigation with Dropdown" stackAlignment="stretch">
<DocsSection title="Nested Horizontal Box Navigation with Dropdown" stackAlignment="stretch">
<NavigationHorizontalWithDropdown />
</DocsSection>
<DocsSection title="Nested Vertical Navigation with Collapse" stackAlignment="stretch">
<DocsSection title="Nested Vertical Box Navigation with Collapse" stackAlignment="stretch">
<NavigationVerticalWithCollapse />
</DocsSection>
<DocsSection title="Horizontal Navigation with Pill NavigationAction" stackAlignment="stretch">
<NavigationHorizontalWithPillAction />
</DocsSection>
<DocsSection title="Vertical Navigation with Pill NavigationAction" stackAlignment="stretch">
<NavigationVerticalWithPillAction />
</DocsSection>
</IconsProvider>
</React.StrictMode>,
);
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Markdown } from '@storybook/blocks';
import type { Meta, StoryObj } from '@storybook/react';
import React from 'react';
import { NavigationActionVariants } from '../constants';
import NavigationAction from '../NavigationAction';
import ReadMe from '../README.md';

Expand All @@ -25,11 +26,19 @@ const meta: Meta<typeof NavigationAction> = {
defaultValue: { summary: 'false' },
},
},
variant: {
control: 'select',
options: [...Object.values(NavigationActionVariants)],
table: {
defaultValue: { summary: NavigationActionVariants.BOX },
},
},
},
args: {
children: 'Link',
isDisabled: false,
isSelected: false,
variant: NavigationActionVariants.BOX,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ import { AlignmentYExtended, Direction } from '../../constants';
import { AlignmentPropertyType, useAlignmentClass, useClassNamePrefix } from '../../hooks';
import {
DirectionDictionaryType,
NavigationActionVariantsType,
SpiritNavigationActionProps,
SpiritNavigationItemAlignmentYType,
SpiritNavigationItemProps,
} from '../../types';
import { NavigationActionVariants } from './constants';

export interface UseNavigationStyleProps {
alignmentY?: SpiritNavigationItemAlignmentYType;
direction?: DirectionDictionaryType;
isDisabled?: boolean;
isSelected?: boolean;
variant?: NavigationActionVariantsType;
}

export interface UseNavigationStyleReturn {
Expand All @@ -29,6 +32,7 @@ export const useNavigationStyleProps = ({
isSelected = false,
alignmentY = AlignmentYExtended.CENTER,
direction = Direction.HORIZONTAL,
variant = NavigationActionVariants.BOX,
...restProps
}: UseNavigationStyleProps): UseNavigationStyleReturn => {
const navigationClass = useClassNamePrefix('Navigation');
Expand All @@ -38,14 +42,15 @@ export const useNavigationStyleProps = ({
const navigationDirectionClass = `${navigationClass}--${direction}`;
const navigationActionDisabledClass = `${navigationActionClass}--disabled`;
const navigationActionSelectedClass = `${navigationActionClass}--selected`;
const navigationActionVariantClass = `${navigationActionClass}--${variant}`;

const navigationItemClasses = classNames(navigationItemClass, {
[useAlignmentClass(navigationItemClass, alignmentY as AlignmentPropertyType, 'alignmentY')]: alignmentY,
});

const classProps = {
root: classNames(navigationClass, navigationDirectionClass),
action: classNames(navigationActionClass, {
action: classNames(navigationActionClass, navigationActionVariantClass, {
[navigationActionDisabledClass]: isDisabled,
[navigationActionSelectedClass]: isSelected,
}),
Expand Down
Loading
Loading