Skip to content

Commit

Permalink
Fix(web-react): Tablink and HeaderLink now accept a NextLink #DS-1018
Browse files Browse the repository at this point in the history
- TabLink and HeaderLink now accept a NextLink and have forwardRef
support
  • Loading branch information
pavelklibani committed Nov 29, 2023
1 parent c0ec4aa commit bc3efbf
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 40 deletions.
25 changes: 18 additions & 7 deletions packages/web-react/src/components/Header/HeaderLink.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
import React from 'react';
import React, { ElementType, forwardRef } from 'react';
import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { HeaderLinkProps } from '../../types';
import { PolymorphicRef, SpiritHeaderLinkProps } from '../../types';
import { useHeaderStyleProps } from './useHeaderStyleProps';

const HeaderLink = (props: HeaderLinkProps) => {
const { children, isCurrent, ...restProps } = props;

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

return (
<a {...otherProps} className={classNames(classProps.headerLink, styleProps.className)} style={styleProps.style}>
<ElementTag
{...otherProps}
className={classNames(classProps.headerLink, styleProps.className)}
style={styleProps.style}
ref={ref}
>
{children}
</a>
</ElementTag>
);
};

export const HeaderLink = forwardRef<HTMLAnchorElement, SpiritHeaderLinkProps<ElementType>>(_HeaderLink);

export default HeaderLink;
46 changes: 33 additions & 13 deletions packages/web-react/src/components/Header/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,38 @@ your specific design goals.

The Header is a composition of several subcomponents:

- [Header](#minimal-header)
- [HeaderMobileActions](#mobile-only-actions)
- [HeaderDesktopActions](#desktop-only-actions)
- [HeaderNav](#navigation)
- [HeaderNavItem](#navigation)
- [HeaderLink](#navigation)
- [HeaderDialog](#header-dialog)
- [HeaderDialogCloseButton](#close-button)
- [HeaderDialogActions](#primary-and-secondary-actions)
- [HeaderDialogNav](#navigation-1)
- [HeaderDialogNavItem](#navigation-1)
- [HeaderDialogLink](#navigation-1)
- [HeaderDialogText](#navigation-1)
- [Header and HeaderDialog](#header-and-headerdialog)
- [Accessibility Guidelines](#accessibility-guidelines)
- [Minimal Header](#minimal-header)
- [Color Variants](#color-variants)
- [Simple Header](#simple-header)
- [API](#api)
- [Supported Content](#supported-content)
- [Header](#header)
- [Mobile-Only Actions](#mobile-only-actions)
- [Custom Mobile Actions](#custom-mobile-actions)
- [API](#api-1)
- [Desktop-Only Actions](#desktop-only-actions)
- [API](#api-2)
- [Navigation](#navigation)
- [Other Content](#other-content)
- [HeaderNav API](#headernav-api)
- [HeaderNavItem API](#headernavitem-api)
- [HeaderLink API](#headerlink-api)
- [HeaderButton API](#headerbutton-api)
- [Header Dialog](#header-dialog)
- [API](#api-3)
- [Close Button](#close-button)
- [API](#api-4)
- [Primary and Secondary Actions](#primary-and-secondary-actions)
- [API](#api-5)
- [Navigation](#navigation-1)
- [HeaderDialogNav API](#headerdialognav-api)
- [HeaderDialogNavItem API](#headerdialognavitem-api)
- [HeaderDialogLink API](#headerdialoglink-api)
- [HeaderDialogButton API](#headerdialogbutton-api)
- [HeaderDialogText API](#headerdialogtext-api)
- [Composition](#composition)

## Accessibility Guidelines

Expand Down Expand Up @@ -243,6 +262,7 @@ The component further inherits properties from the [`<li>`][mdn-li-element] elem
| Name | Type | Default | Required | Description |
| ------------------ | --------------- | ------- | -------- | -------------------- |
| `children` | `ReactNode` ||| Children node |
| `elementType` | `ElementType` | `a` || Type of element |
| `isCurrent` | `bool` | `false` || Mark link as current |
| `UNSAFE_className` | `string` ||| Custom class name |
| `UNSAFE_style` | `CSSProperties` ||| Custom style |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,15 @@ describe('HeaderLink', () => {
const element = dom.container.querySelector('a') as HTMLElement;
expect(element.textContent).toBe('Hello World');
});

it('should render button element', () => {
const dom = render(
<HeaderLink id="test" elementType="button">
Hello World
</HeaderLink>,
);

const element = dom.container.querySelector('button') as HTMLElement;
expect(element.textContent).toBe('Hello World');
});
});
10 changes: 5 additions & 5 deletions packages/web-react/src/components/Tabs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,11 @@ Tab list link

#### API

| Name | Type | Default | Required | Description |
| ----------- | ---------------------------- | ------- | -------- | ----------------------------- |
| `children` | `any` ||| Child component |
| `href` | `string` | | | External link |
| `itemProps` | `StyleProps & HTMLLIElement` ||| Props for parent list element |
| Name | Type | Default | Required | Description |
| ------------- | ---------------------------- | ------- | -------- | ----------------------------- |
| `children` | `any` ||| Child component |
| `elementType` | `ElementType` | `a` | | Type of element |
| `itemProps` | `StyleProps & HTMLLIElement` ||| Props for parent list element |

### TabContent

Expand Down
22 changes: 10 additions & 12 deletions packages/web-react/src/components/Tabs/TabLink.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
import React from 'react';
import React, { ElementType, forwardRef } from 'react';
import classNames from 'classnames';
import { useStyleProps } from '../../hooks';
import { ChildrenProps, StyleProps } from '../../types';
import { PolymorphicRef, SpiritTabLinkProps } from '../../types';
import { useTabsStyleProps } from './useTabsStyleProps';

export type TabLinkItemProps = StyleProps & React.HTMLProps<HTMLLIElement>;

export interface TabLinkProps extends ChildrenProps {
href: string;
itemProps?: TabLinkItemProps;
}

const TabLink = ({ children, href, itemProps = {}, ...restProps }: TabLinkProps): JSX.Element => {
/* We need an exception for components exported with forwardRef */
/* eslint no-underscore-dangle: ['error', { allow: ['_TabLink'] }] */
const _TabLink = <E extends ElementType = 'a'>(props: SpiritTabLinkProps<E>, ref: PolymorphicRef<E>): JSX.Element => {
const { elementType: ElementTag = 'a', children, itemProps = {}, ...restProps } = props;
const { classProps } = useTabsStyleProps();
const { styleProps: itemStyleProps, props: itemTransferProps } = useStyleProps(itemProps);

return (
<li {...itemStyleProps} {...itemTransferProps} className={classNames(classProps.item, itemStyleProps.className)}>
<a {...restProps} href={href} className={classProps.link} role="tab">
<ElementTag {...restProps} className={classProps.link} role="tab" ref={ref}>
{children}
</a>
</ElementTag>
</li>
);
};

export const TabLink = forwardRef<HTMLAnchorElement, SpiritTabLinkProps<ElementType>>(_TabLink);

TabLink.defaultProps = {
itemProps: {},
};
Expand Down
11 changes: 11 additions & 0 deletions packages/web-react/src/components/Tabs/__tests__/TabLink.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,15 @@ describe('TabLink', () => {
const element = dom.container.querySelector('a') as HTMLElement;
expect(element).toHaveClass('Tabs__link');
});

it('should render button element', () => {
const dom = render(
<TabLink href="https://www.example.com" elementType="button">
Hello World
</TabLink>,
);

const element = dom.container.querySelector('button') as HTMLElement;
expect(element.textContent).toBe('Hello World');
});
});
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React, { useCallback, useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react';

import { TabId } from '../../../types';
import { TabContent, TabItem, TabLink, TabLinkProps, TabList, TabPane, Tabs } from '..';
import { TabId, TabLinkProps } from '../../../types';
import { TabContent, TabItem, TabLink, TabList, TabPane, Tabs } from '..';

const meta: Meta<typeof TabLink> = {
title: 'Components/Tabs',
Expand Down
17 changes: 16 additions & 1 deletion packages/web-react/src/types/header.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ElementType } from 'react';
import {
ChildrenProps,
ClickEvent,
Expand All @@ -6,8 +7,11 @@ import {
SpiritDialogElementProps,
SpiritElementProps,
SpiritLItemElementProps,
SpiritPolymorphicElementPropsWithRef,
SpiritSpanElementProps,
SpiritUListElementProps,
StyleProps,
TransferProps,
} from './shared';

export type HeaderActionsColorType = 'primary' | 'secondary';
Expand Down Expand Up @@ -58,10 +62,21 @@ export interface HeaderDialogNavItemProps extends SpiritLItemElementProps, Child

export interface HeaderDialogTextProps extends SpiritSpanElementProps, ChildrenProps {}

export interface HeaderLinkProps extends SpiritAnchorElementProps, ChildrenProps {
export interface HeaderLinkBaseProps extends ChildrenProps, StyleProps, TransferProps {
isCurrent?: boolean;
}

export type HeaderLinkProps<E extends ElementType = 'a'> = {
/**
* The HTML element or React element used to render the Link, e.g. 'a'.
* @default 'a'
*/
elementType?: E;
} & HeaderLinkBaseProps;

export type SpiritHeaderLinkProps<E extends ElementType = 'a'> = HeaderLinkProps<E> &
SpiritPolymorphicElementPropsWithRef<E, HeaderLinkProps<E>>;

export interface HeaderMobileActionsProps extends SpiritElementProps, HeaderMobileActionsHandlingProps, ChildrenProps {
dialogId: string;
menuToggleLabel?: string;
Expand Down
20 changes: 20 additions & 0 deletions packages/web-react/src/types/tabs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { ElementType } from 'react';
import { ChildrenProps, SpiritPolymorphicElementPropsWithRef, StyleProps, TransferProps } from './shared';

export type TabId = string | number;

export interface SpiritTabsProps {
Expand All @@ -8,3 +11,20 @@ export interface SpiritTabsProps {
/** Identification of affected pane */
forTab?: TabId;
}

export type TabLinkItemProps = StyleProps & React.HTMLProps<HTMLLIElement>;

export interface TabLinkBaseProps extends ChildrenProps, StyleProps, TransferProps {
itemProps?: TabLinkItemProps;
}

export type TabLinkProps<E extends ElementType = 'a'> = {
/**
* The HTML element or React element used to render the Link, e.g. 'a'.
* @default 'a'
*/
elementType?: E;
} & TabLinkBaseProps;

export type SpiritTabLinkProps<E extends ElementType = 'a'> = TabLinkProps<E> &
SpiritPolymorphicElementPropsWithRef<E, TabLinkProps<E>>;

0 comments on commit bc3efbf

Please sign in to comment.