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

Fix(web-react): TabLink, HeaderLink and HeaderDialogLink now accept a NextLink #DS-1018 #DS-1003 #1168

Merged
merged 2 commits into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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)
literat marked this conversation as resolved.
Show resolved Hide resolved

## 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
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>>;
Loading