diff --git a/src/assets/icons/upper-right-arrow.svg b/src/assets/icons/upper-right-arrow.svg deleted file mode 100644 index 3c9ddba8a..000000000 --- a/src/assets/icons/upper-right-arrow.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/src/components/icon/iconList.ts b/src/components/icon/iconList.ts index 2a5eb04b0..0e45a8e2c 100644 --- a/src/components/icon/iconList.ts +++ b/src/components/icon/iconList.ts @@ -51,7 +51,6 @@ import TxFailure from '../../assets/icons/tx-failure.svg'; import TxSmartContract from '../../assets/icons/tx-smart-contract.svg'; import TxWithdraw from '../../assets/icons/tx-withdraw.svg'; import Update from '../../assets/icons/update.svg'; -import UpperRightArrow from '../../assets/icons/upper-right-arrow.svg'; import Warning from '../../assets/icons/warning.svg'; import WysiwygBold from '../../assets/icons/wysiwyg-bold.svg'; import WysiwygItalic from '../../assets/icons/wysiwyg-italic.svg'; @@ -61,7 +60,7 @@ import WysiwygListOrdered from '../../assets/icons/wysiwyg-list-ordered.svg'; import WysiwygListUnordered from '../../assets/icons/wysiwyg-list-unordered.svg'; import { IconType } from './iconType'; -export type IconComponent = React.FC>; +type IconComponent = React.FC>; export const iconList: Record = { [IconType.ADD]: Add, @@ -115,7 +114,6 @@ export const iconList: Record = { [IconType.TX_SMART_CONTRACT]: TxSmartContract, [IconType.TX_WITHDRAW]: TxWithdraw, [IconType.UPDATE]: Update, - [IconType.UPPER_RIGHT_ARROW]: UpperRightArrow, [IconType.WARNING]: Warning, [IconType.REMOVE]: Remove, [IconType.WYSIWYG_BOLD]: WysiwygBold, diff --git a/src/components/icon/iconType.ts b/src/components/icon/iconType.ts index 995c10c10..aaa9636be 100644 --- a/src/components/icon/iconType.ts +++ b/src/components/icon/iconType.ts @@ -51,7 +51,6 @@ export enum IconType { TX_SMART_CONTRACT = 'TX_SMART_CONTRACT', TX_WITHDRAW = 'TX_WITHDRAW', UPDATE = 'UPDATE', - UPPER_RIGHT_ARROW = 'UPPER_RIGHT_ARROW', WARNING = 'WARNING', WYSIWYG_BOLD = 'WYSIWYG_BOLD', WYSIWYG_ITALIC = 'WYSIWYG_ITALIC', diff --git a/src/components/link/link.api.ts b/src/components/link/link.api.ts index c63ecfbba..ccdd68dae 100644 --- a/src/components/link/link.api.ts +++ b/src/components/link/link.api.ts @@ -18,16 +18,15 @@ export interface ILinkProps extends AnchorHTMLAttributes { /** * Whether the link is disabled. */ - disabled?: boolean; /** * Whether the link is external (opens in a new tab). */ external?: boolean; /** - * The label or text content of the link. The only required prop + * This effectivly label or text content of the link. The only required prop and must be a string. */ - label: string; + children: string; /** * Optional description text. */ diff --git a/src/components/link/link.stories.tsx b/src/components/link/link.stories.tsx index edf6043a0..15abcccc4 100644 --- a/src/components/link/link.stories.tsx +++ b/src/components/link/link.stories.tsx @@ -25,12 +25,13 @@ const meta: Meta = { }, }, args: { - label: 'Label', + children: 'Label', variant: 'primary', description: 'Description', disabled: false, external: true, href: '#', + iconRight: IconType.LINK_EXTERNAL, /** only used for testing the icon right prop */ showIconRight: true, }, @@ -56,11 +57,12 @@ type Story = StoryObj; // can swap out the IconType for any icon in the iconList export const Default: Story = { args: { - variant: 'primary', - iconRight: IconType.UPPER_RIGHT_ARROW, + children: 'Label', }, - render: ({ showIconRight, iconRight, ...props }) => ( - + render: ({ showIconRight, iconRight, children, ...props }) => ( + + {children} + ), }; diff --git a/src/components/link/link.test.tsx b/src/components/link/link.test.tsx index 12ee2bdf4..60add59e3 100644 --- a/src/components/link/link.test.tsx +++ b/src/components/link/link.test.tsx @@ -1,80 +1,51 @@ -import '@testing-library/jest-dom'; import { fireEvent, render, screen } from '@testing-library/react'; -import { IconType } from '../icon'; -import { Link } from './link'; +import { Link, type ILinkProps } from '.'; + +describe(' component', () => { + const createTestComponent = (props?: Partial) => { + const completeProps: ILinkProps = { children: 'Default children', href: 'http://default.com', ...props }; + + return ; + }; -describe('Link', () => { it('renders correctly with minimum props', () => { - render(); - const linkElement = screen.getByTestId('link'); + render(createTestComponent({ children: 'Example', href: 'http://example.com' })); + const linkElement = screen.getByRole('link', { name: 'Example' }); expect(linkElement).toBeInTheDocument(); expect(linkElement).toHaveAttribute('href', 'http://example.com'); - expect(screen.getByText('Example')).toBeInTheDocument(); }); it('applies correct classes based on disabled prop', () => { - const { rerender } = render(); - let linkElement = screen.getByTestId('link'); - - rerender(); - linkElement = screen.getByTestId('link'); + render(createTestComponent({ children: 'Disabled Link', disabled: true })); + const linkElement = screen.getByLabelText('Disabled Link'); expect(linkElement).toHaveClass('truncate text-neutral-300 cursor-not-allowed'); }); it('prevents default behavior when clicked and disabled', () => { const handleClick = jest.fn(); - render(); - const linkElement = screen.getByTestId('link'); + render(createTestComponent({ children: 'Disabled Link', disabled: true, onClick: handleClick })); + const linkElement = screen.getByLabelText('Disabled Link'); fireEvent.click(linkElement); expect(handleClick).not.toHaveBeenCalled(); }); it('does not have href attribute when disabled', () => { - render(); - const linkElement = screen.getByTestId('link'); + render(createTestComponent({ children: 'Disabled Link', disabled: true, href: 'http://example.com' })); + const linkElement = screen.getByLabelText('Disabled Link'); expect(linkElement).not.toHaveAttribute('href', 'http://example.com'); }); - it('has correct attributes when link is external', () => { - render(); - const linkElement = screen.getByTestId('link'); - expect(linkElement).toHaveAttribute('target', '_blank'); - expect(linkElement).toHaveAttribute('rel', 'noopener noreferrer'); - }); - it('has correct aria attributes when disabled', () => { - render(); - const linkElement = screen.getByTestId('link'); + render(createTestComponent({ children: 'Disabled Link', disabled: true })); + const linkElement = screen.getByLabelText('Disabled Link'); expect(linkElement).toHaveAttribute('aria-disabled', 'true'); expect(linkElement).toHaveAttribute('tabIndex', '-1'); }); - it('fires click event when not disabled', () => { - const handleClick = jest.fn(); - render(); - const linkElement = screen.getByTestId('link'); - fireEvent.click(linkElement); - expect(handleClick).toHaveBeenCalled(); - }); - - it.each(Object.values(IconType))('renders correctly with %s icon', async (icon) => { - render(); - const iconElement = await screen.findByTestId(icon); - expect(iconElement).toBeInTheDocument(); - expect(screen.getByText(`Link with ${icon} icon`)).toBeInTheDocument(); - }); - it('renders correctly with no icon', () => { - const label = 'Link without icon'; - render(); - const linkElement = screen.getByTestId('link'); - expect(linkElement).toHaveTextContent(label); - }); - - it('receives focus when tabbed to', () => { - render(); - const linkElement = screen.getByTestId('link'); - fireEvent.keyDown(linkElement, { key: 'Tab', code: 'Tab' }); - expect(linkElement).toHaveClass('test-focus'); + const children = 'Link without icon'; + render(createTestComponent({ children, href: 'http://example.com' })); + const linkElement = screen.getByRole('link', { name: 'Link without icon' }); + expect(linkElement).toHaveTextContent(children); }); }); diff --git a/src/components/link/link.tsx b/src/components/link/link.tsx index 18efa5939..ae4efbc9c 100644 --- a/src/components/link/link.tsx +++ b/src/components/link/link.tsx @@ -21,28 +21,26 @@ const disabledStyle = 'truncate text-neutral-300 cursor-not-allowed'; export const Link = React.forwardRef( ( { + children, disabled = false, - external = true, variant = 'primary', description, - label, href, iconRight, onClick, className, + target, ...props }, ref, ) => { - // disabling eslint rule to throw in custom 'test-focs' class for focus ring test on tab selection - // eslint-disable-next-line tailwindcss/no-custom-classname const linkClassName = classNames( - 'gap-y-1/2 inline-flex max-w-fit flex-col truncate rounded text-sm leading-tight focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-offset md:gap-y-1 md:text-base', + 'inline-flex max-w-fit flex-col gap-y-0.5 truncate rounded text-sm leading-tight focus:outline-none focus-visible:ring focus-visible:ring-primary focus-visible:ring-offset md:gap-y-1 md:text-base', className, disabled ? disabledStyle : variantToLabelClassNames[variant], - 'test-focus', ); const descriptionClassName = classNames('truncate', disabled ? disabledStyle : 'text-neutral-500'); + const linkRel = target === '_blank' ? 'noopener noreferrer' : ''; return ( ( href={disabled ? undefined : href} className={linkClassName} {...(disabled && { tabIndex: -1, 'aria-disabled': 'true' })} - {...(external && !disabled && { target: '_blank', rel: 'noopener noreferrer' })} + target={target} + rel={linkRel} + aria-label={children} {...props} - data-testid="link" >
- {label} + {children} {iconRight && }
{description &&

{description}

}