-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #677 from Orfium/feat/NDS-536_link_component_imple…
…mentation_v5 [NDS-536] feat: Link Component
- Loading branch information
Showing
10 changed files
with
1,430 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import { Meta, Preview, Props, Story } from '@storybook/addon-docs'; | ||
import { boolean, select, withKnobs } from '@storybook/addon-knobs'; | ||
import Link from './Link'; | ||
import Stack from '../storyUtils/Stack'; | ||
import { BASE_SHADE } from '../../theme/palette'; | ||
import { FIGMA_URL } from '../../utils/common'; | ||
import { getIconSelectorKnob } from '../../utils/stories'; | ||
import SectionHeader from '../../storybook/SectionHeader'; | ||
|
||
<Meta | ||
title="Design System/Link" | ||
component={Link} | ||
parameters={{ | ||
design: [ | ||
{ | ||
type: 'figma', | ||
name: 'Avatar', | ||
url: `${FIGMA_URL}?node-id=3325%3A58246`, | ||
}, | ||
], | ||
}} | ||
/> | ||
|
||
<SectionHeader title={'Link'} /> | ||
|
||
- [Overview](#overview) | ||
- [Props](#props) | ||
- [Usage](#usage) | ||
- [Variants](#variants) | ||
|
||
## Overview | ||
|
||
A link is a navigational component that takes a user to another URL, an element within the page, or a file. | ||
|
||
## Props | ||
|
||
<Props of={Link} /> | ||
|
||
## Usage | ||
|
||
<UsageGuidelines | ||
guidelines={[ | ||
'Use as navigation to a new URL', | ||
'Use to navigate to an element within a page (anchor link)', | ||
'Use to download a file', | ||
'Use to show a secondary action (e.g. "Cancel")', | ||
]} | ||
/> | ||
|
||
<Tip>To show a primary action (like “Save”), use a Button instead. </Tip> | ||
|
||
## Props | ||
|
||
<Props of={Link} /> | ||
|
||
## Variants | ||
|
||
### Style | ||
|
||
<Preview> | ||
<Story name="Style"> | ||
<Stack isVertical> | ||
<Link href="#">Primary</Link> | ||
</Stack> | ||
<Stack isInverted> | ||
<Link href="#" type="inverted"> | ||
Inverted | ||
</Link> | ||
</Stack> | ||
</Story> | ||
</Preview> | ||
|
||
### Placement | ||
|
||
Inline: Used within a body of copy; always with underline text decoration for better discoverability <br/> | ||
Block: Used without a body of copy; without underline text decoration | ||
|
||
<Preview> | ||
<Story name="Placement"> | ||
<Stack isVertical> | ||
<div> | ||
This is an{' '} | ||
<Link href="#" placement="inline" size={1}> | ||
inline Link | ||
</Link> | ||
. | ||
</div> | ||
<div> | ||
This is a | ||
<Link href="#" size={1}> | ||
block Link | ||
</Link> | ||
. | ||
</div> | ||
</Stack> | ||
</Story> | ||
</Preview> | ||
|
||
### Sizes | ||
|
||
<Preview> | ||
<Story name="Sizes"> | ||
<Stack isVertical> | ||
<Link href="#" size={1}> | ||
Size 1 | ||
</Link> | ||
<Link href="#" size={2}> | ||
Size 2 | ||
</Link> | ||
<Link href="#" size={3}> | ||
Size 3 | ||
</Link> | ||
</Stack> | ||
</Story> | ||
</Preview> | ||
|
||
### Link with Icon | ||
|
||
<Preview> | ||
<Story name="Link with Icon" parameters={{ decorators: [withKnobs] }}> | ||
<Stack isVertical> | ||
<Link href="#" size={1} iconName={getIconSelectorKnob('iconName', 'externalLink')}> | ||
Link | ||
</Link> | ||
<Link href="#" size={2} iconName={getIconSelectorKnob('iconName', 'externalLink')}> | ||
Link | ||
</Link> | ||
<Link href="#" size={3} iconName={getIconSelectorKnob('iconName', 'externalLink')}> | ||
Link | ||
</Link> | ||
</Stack> | ||
</Story> | ||
</Preview> | ||
|
||
### Playground | ||
|
||
<Preview> | ||
<Story name="Playground" parameters={{ decorators: [withKnobs] }}> | ||
<Stack isVertical> | ||
<Link | ||
href="#" | ||
size={select('size', [1, 2, 3], 1)} | ||
type={select('type', ['primary', 'inverted'], 'primary')} | ||
placement={select('placement', ['block', 'inline'], 'block')} | ||
isDisabled={boolean('isDisabled', false)} | ||
> | ||
Link | ||
</Link> | ||
</Stack> | ||
</Story> | ||
</Preview> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import { css, SerializedStyles } from '@emotion/react'; | ||
import { Theme } from 'theme'; | ||
|
||
import { getLinkTokens, LinkTokens } from './Link.tokens'; | ||
import { LinkProps } from './Link.types'; | ||
import { generateStylesFromTokens } from 'components/Typography/utils'; | ||
|
||
export const linkContainer = | ||
({ | ||
placement, | ||
type, | ||
size, | ||
isDisabled, | ||
}: Pick<LinkProps, 'placement' | 'type' | 'size' | 'isDisabled'>) => | ||
(theme: Theme): SerializedStyles => { | ||
const tokens = getLinkTokens(theme); | ||
|
||
return css` | ||
display: ${placement === 'inline' ? 'inline-flex' : 'flex'}; | ||
gap: ${tokens('padding')}; | ||
color: ${tokens(`textColor.${type}.default` as LinkTokens)}; | ||
text-decoration: none; | ||
border: ${tokens('borderWidth.1')} solid ${tokens('borderColor.default')}; | ||
&:hover { | ||
color: ${tokens(`textColor.${type}.hover` as LinkTokens)}; | ||
path { | ||
fill: ${tokens(`textColor.${type}.hover` as LinkTokens)}; | ||
} | ||
} | ||
&:visited { | ||
color: ${tokens(`textColor.${type}.visited` as LinkTokens)}; | ||
path { | ||
fill: ${tokens(`textColor.${type}.visited` as LinkTokens)}; | ||
} | ||
} | ||
&:focus-visible { | ||
border: ${tokens('borderWidth.2')} solid ${tokens('borderColor.focused')}; | ||
} | ||
opacity: ${isDisabled ? theme.tokens.disabledState.get('default') : 1}; | ||
width: fit-content; | ||
align-items: center; | ||
cursor: ${isDisabled ? 'default' : 'pointer'}; | ||
pointer-events: ${isDisabled ? 'none' : 'default'}; | ||
${generateStylesFromTokens(tokens(`${placement}.${size}` as LinkTokens))} | ||
`; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import link from 'theme/tokens/components/variables/link'; | ||
import { getComponentTokens, DotKeys } from 'theme/tokens/utils'; | ||
|
||
import { Theme } from '../../theme'; | ||
|
||
export type LinkTokens = DotKeys<typeof link>; | ||
|
||
export const getLinkTokens = ( | ||
theme: Theme | ||
): ((path: LinkTokens, fn?: (val: string) => any) => any) => { | ||
return getComponentTokens(link, theme); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import useTheme from 'hooks/useTheme'; | ||
import React, { AnchorHTMLAttributes } from 'react'; | ||
|
||
import { linkContainer } from './Link.style'; | ||
import { LinkTokens, getLinkTokens } from './Link.tokens'; | ||
import { LinkProps } from './Link.types'; | ||
import Icon from 'components/Icon'; | ||
|
||
const Link = React.forwardRef< | ||
HTMLAnchorElement, | ||
AnchorHTMLAttributes<HTMLAnchorElement> & LinkProps | ||
>((props, ref) => { | ||
const { | ||
type = 'primary', | ||
placement = 'block', | ||
size = 1, | ||
iconName, | ||
isDisabled, | ||
dataTestPrefixId = '', | ||
children, | ||
...rest | ||
} = props; | ||
|
||
const theme = useTheme(); | ||
|
||
const tokens = getLinkTokens(theme); | ||
|
||
return ( | ||
<a | ||
css={linkContainer({ placement, type, size, isDisabled })} | ||
ref={ref} | ||
data-testid={`${dataTestPrefixId}_link`} | ||
{...rest} | ||
> | ||
<span>{children}</span> | ||
{/** @TODO Set the right size for the icon based on tokens once Icon component is refactored */} | ||
{iconName && ( | ||
<Icon | ||
name={iconName} | ||
color={tokens(`textColor.${type}.default` as LinkTokens)} | ||
size={12} | ||
dataTestId={`${dataTestPrefixId}_link_icon`} | ||
/> | ||
)} | ||
</a> | ||
); | ||
}); | ||
|
||
Link.displayName = 'Link'; | ||
|
||
export default Link; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { AcceptedIconNames } from 'components/Icon'; | ||
|
||
type LinkSizes = 1 | 2 | 3; | ||
|
||
export type LinkProps = { | ||
/** The type of the Link in terms of style */ | ||
type?: 'primary' | 'inverted'; | ||
/** The placement of the link */ | ||
placement?: 'block' | 'inline'; | ||
/** The size of the Link */ | ||
size?: LinkSizes; | ||
/** Optional icon to add at the right of the Link */ | ||
iconName?: AcceptedIconNames; | ||
/** Whether the link is disabled*/ | ||
isDisabled?: boolean; | ||
/** Data Test Id prefix **/ | ||
dataTestPrefixId?: string; | ||
}; |
Oops, something went wrong.