1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

link component (#17897)

Added externlalLink to ButtonLink

Co-authored-by: George Marshall <george.marshall@consensys.net>
This commit is contained in:
witmicko 2023-03-10 17:47:01 +00:00 committed by GitHub
parent 11c79dd629
commit fde18dec0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 254 additions and 41 deletions

View File

@ -46,6 +46,8 @@ ignores:
- '@storybook/builder-webpack5'
- '@storybook/manager-webpack5'
- 'storybook-dark-mode'
- '@whitespace/storybook-addon-html'
- 'react-syntax-highlighter'
- 'style-loader'
- 'css-loader'
- 'sass-loader'

View File

@ -23,6 +23,7 @@ module.exports = {
'@storybook/addon-knobs',
'./i18n-party-addon/register.js',
'storybook-dark-mode',
'@whitespace/storybook-addon-html'
],
staticDirs: ['../app', './images'],
// Uses babel.config.js settings and prevents "Missing class properties transform" error

View File

@ -4302,18 +4302,13 @@
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true,
"react-markdown>remark-parse>mdast-util-from-markdown>micromark": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": true,
"react-markdown>vfile>unist-util-stringify-position": true
"react-markdown>vfile>unist-util-stringify-position": true,
"react-syntax-highlighter>refractor>parse-entities": true
}
},
"react-markdown>remark-parse>mdast-util-from-markdown>micromark": {
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": true
}
},
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": {
"globals": {
"document.createElement": true
"react-syntax-highlighter>refractor>parse-entities": true
}
},
"react-markdown>remark-rehype": {
@ -4532,6 +4527,11 @@
"react": true
}
},
"react-syntax-highlighter>refractor>parse-entities": {
"globals": {
"document.createElement": true
}
},
"react-tippy": {
"globals": {
"Element": true,

View File

@ -4302,18 +4302,13 @@
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>mdast-util-to-string": true,
"react-markdown>remark-parse>mdast-util-from-markdown>micromark": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": true,
"react-markdown>vfile>unist-util-stringify-position": true
"react-markdown>vfile>unist-util-stringify-position": true,
"react-syntax-highlighter>refractor>parse-entities": true
}
},
"react-markdown>remark-parse>mdast-util-from-markdown>micromark": {
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": true
}
},
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": {
"globals": {
"document.createElement": true
"react-syntax-highlighter>refractor>parse-entities": true
}
},
"react-markdown>remark-rehype": {
@ -4532,6 +4527,11 @@
"react": true
}
},
"react-syntax-highlighter>refractor>parse-entities": {
"globals": {
"document.createElement": true
}
},
"react-tippy": {
"globals": {
"Element": true,

View File

@ -6562,22 +6562,6 @@
"define": true
}
},
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": {
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>character-entities": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>character-entities-legacy": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>character-reference-invalid": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>is-alphanumerical": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>is-hexadecimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true
}
},
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>is-alphanumerical": {
"packages": {
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true
}
},
"react-markdown>unified": {
"packages": {
"jsdom>request>extend": true,
@ -6620,6 +6604,22 @@
"react-markdown>vfile>unist-util-stringify-position": true
}
},
"react-syntax-highlighter>refractor>parse-entities": {
"packages": {
"react-syntax-highlighter>refractor>parse-entities>character-entities": true,
"react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true,
"react-syntax-highlighter>refractor>parse-entities>character-reference-invalid": true,
"react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true,
"react-syntax-highlighter>refractor>parse-entities>is-decimal": true,
"react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true
}
},
"react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": {
"packages": {
"react-syntax-highlighter>refractor>parse-entities>is-decimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true
}
},
"readable-stream": {
"builtin": {
"events.EventEmitter": true,
@ -6964,11 +6964,11 @@
},
"stylelint>@stylelint/postcss-markdown>remark>remark-parse": {
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": true,
"react-syntax-highlighter>refractor>parse-entities": true,
"react-syntax-highlighter>refractor>parse-entities>is-decimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>collapse-white-space": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-alphabetical": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-word-character": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true,
@ -6995,9 +6995,9 @@
},
"stylelint>@stylelint/postcss-markdown>remark>remark-stringify": {
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities": true,
"react-syntax-highlighter>refractor>parse-entities": true,
"react-syntax-highlighter>refractor>parse-entities>is-decimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>ccount": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-whitespace-character": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>markdown-escapes": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>repeat-string": true,
@ -7023,10 +7023,10 @@
},
"stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities": {
"packages": {
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>character-entities-legacy": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>is-alphanumerical": true,
"react-markdown>remark-parse>mdast-util-from-markdown>parse-entities>is-hexadecimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-parse>is-decimal": true,
"react-syntax-highlighter>refractor>parse-entities>character-entities-legacy": true,
"react-syntax-highlighter>refractor>parse-entities>is-alphanumerical": true,
"react-syntax-highlighter>refractor>parse-entities>is-decimal": true,
"react-syntax-highlighter>refractor>parse-entities>is-hexadecimal": true,
"stylelint>@stylelint/postcss-markdown>remark>remark-stringify>stringify-entities>character-entities-html4": true
}
},

View File

@ -421,6 +421,7 @@
"@types/yargs": "^17.0.8",
"@typescript-eslint/eslint-plugin": "^5.30.7",
"@typescript-eslint/parser": "^5.30.7",
"@whitespace/storybook-addon-html": "^5.1.1",
"addons-linter": "^5.2.0",
"babelify": "^10.0.0",
"bify-module-groups": "^2.0.0",
@ -504,6 +505,7 @@
"pumpify": "^2.0.1",
"randomcolor": "^0.5.4",
"react-devtools": "^4.11.0",
"react-syntax-highlighter": "^15.5.0",
"read-installed": "^4.0.3",
"redux-mock-store": "^1.5.4",
"remote-redux-devtools": "^0.5.16",

View File

@ -98,6 +98,20 @@ import { ButtonBase } from '../../component-library';
<ButtonBase href="/metamask">Anchor Element</ButtonBase>;
```
### External link
When an `externalLink` prop is passed it will change the element to an anchor(`a`) tag and add the `target="_blank"` and `rel="noopener noreferrer"` attributes.
<Story id="components-componentlibrary-buttonbase--external-link" />
```jsx
import { ButtonBase } from '../../component-library';
<ButtonBase href="https://metamask.io" externalLink>
Anchor element with external link
</ButtonBase>;
```
### Disabled
Use the boolean `disabled` prop to disable button

View File

@ -1,5 +1,23 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ButtonBase should render anchor element correctly by href and externalLink, href target and rel exist 1`] = `
<div>
<a
class="box mm-button-base mm-button-base--size-md box--padding-right-4 box--padding-left-4 box--display-inline-flex box--flex-direction-row box--justify-content-center box--align-items-center box--color-text-default box--background-color-background-alternative box--rounded-pill"
data-testid="button-base"
href="https://www.test.com/"
rel="noopener noreferrer"
target="_blank"
>
<span
class="box mm-text mm-button-base__content mm-text--body-md mm-text--color-inherit box--gap-2 box--flex-direction-row box--justify-content-center box--align-items-center box--display-flex"
>
Button Base
</span>
</a>
</div>
`;
exports[`ButtonBase should render button element correctly and match snapshot 1`] = `
<div>
<button

View File

@ -24,6 +24,7 @@ export const ButtonBase = ({
children,
className,
href,
externalLink,
size = BUTTON_BASE_SIZES.MD,
startIconName,
startIconProps,
@ -36,6 +37,10 @@ export const ButtonBase = ({
...props
}) => {
const Tag = href ? 'a' : as;
if (Tag === 'a' && externalLink) {
props.target = '_blank';
props.rel = 'noopener noreferrer';
}
return (
<Box
as={Tag}
@ -121,6 +126,10 @@ ButtonBase.propTypes = {
* When an `href` prop is passed, ButtonBase will automatically change the root element to be an `a` (anchor) tag
*/
href: PropTypes.string,
/**
* Boolean indicating if the link targets external content, it will cause the link to open in a new tab
*/
externalLink: PropTypes.bool,
/**
* Add icon to start (left side) of button text passing icon name
* The name of the icon to display. Should be one of ICON_NAMES

View File

@ -149,6 +149,15 @@ Href.args = {
href: '/metamask',
};
export const ExternalLink = (args) => (
<ButtonBase {...args}>Anchor element with external link</ButtonBase>
);
ExternalLink.args = {
href: 'https://metamask.io',
externalLink: true,
};
export const Disabled = (args) => (
<ButtonBase {...args}>Disabled Button</ButtonBase>
);

View File

@ -27,7 +27,23 @@ describe('ButtonBase', () => {
it('should render anchor element correctly by href only being passed and href exists', () => {
const { getByTestId, container } = render(
<ButtonBase href="https://www.test.com/" data-testid="button-base">
<ButtonBase href="/metamask" data-testid="button-base">
Button Base
</ButtonBase>,
);
expect(getByTestId('button-base')).toHaveClass('mm-button-base');
expect(getByTestId('button-base')).toHaveAttribute('href', '/metamask');
const anchor = container.getElementsByTagName('a').length;
expect(anchor).toBe(1);
});
it('should render anchor element correctly by href and externalLink, href target and rel exist', () => {
const { getByTestId, container } = render(
<ButtonBase
href="https://www.test.com/"
externalLink
data-testid="button-base"
>
Button Base
</ButtonBase>,
);
@ -35,9 +51,24 @@ describe('ButtonBase', () => {
expect(getByTestId('button-base')).toHaveAttribute(
'href',
'https://www.test.com/',
'target',
'_blank',
'rel',
'noopener noreferrer',
);
expect(getByTestId('button-base')).toHaveAttribute(
'target',
'_blank',
'rel',
'noopener noreferrer',
);
expect(getByTestId('button-base')).toHaveAttribute(
'rel',
'noopener noreferrer',
);
const anchor = container.getElementsByTagName('a').length;
expect(anchor).toBe(1);
expect(container).toMatchSnapshot();
});
it('should render button as block', () => {

View File

@ -101,6 +101,24 @@ import { ButtonLink } from '../../component-library';
<ButtonLink href="/">Href example</ButtonLink>;
```
### External Link
When an `externalLink` prop is passed it adds the `target="_blank"` and `rel="noopener noreferrer"` attributes.
`rel="noreferrer noopener"` is used in links to prevent security vulnerabilities that can be exploited by malicious websites. It disables the window.opener property and prevents the new page from sending the referrer information, providing an additional layer of security.
<Canvas>
<Story id="components-componentlibrary-buttonlink--external-link" />
</Canvas>
```jsx
import { ButtonLink } from '../../component-library';
<ButtonLink href="https://metamask.io" externalLink>
Anchor element with external link
</ButtonLink>;
```
### Hit area
The default hit area for `ButtonLink` is the width of the text and height based on the `size` prop which is set to `Size.auto` by default. There may be times when you want to increase the hit area of the `ButtonLink`. To do this you can use the `Box` props `paddingLeft` and `paddingRight`. Or alternatively you can use the `block` prop which sets the width to 100%.

View File

@ -68,6 +68,9 @@ export default {
href: {
control: 'text',
},
externalLink: {
control: 'boolean',
},
startIconName: {
control: 'select',
options: Object.values(ICON_NAMES),
@ -189,6 +192,15 @@ Href.args = {
href: '/metamask',
};
export const ExternalLink = (args) => (
<ButtonLink {...args}>Anchor element with external link</ButtonLink>
);
ExternalLink.args = {
href: 'https://metamask.io/',
externalLink: true,
};
export const HitArea = (args) => (
<>
<Text marginBottom={4}>Default</Text>

View File

@ -8519,6 +8519,28 @@ __metadata:
languageName: node
linkType: hard
"@whitespace/storybook-addon-html@npm:^5.1.1":
version: 5.1.1
resolution: "@whitespace/storybook-addon-html@npm:5.1.1"
peerDependencies:
"@storybook/addons": ^6.5.8
"@storybook/api": ^6.5.8
"@storybook/components": ^6.5.8
"@storybook/core-events": ^6.5.8
"@storybook/theming": ^6.5.8
prettier: ^2.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
react-syntax-highlighter: ^15.0.0
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
checksum: 34bdd5e2ffeffeb7c6fb57e4c913dce4cbf87e118cfd6d26556fce0313e7233f1eaa17eb61da3eef3c8b8c345cf341f774a26e927c4d3a6aa791b8685ed66c92
languageName: node
linkType: hard
"@xstate/fsm@npm:^2.0.0":
version: 2.0.0
resolution: "@xstate/fsm@npm:2.0.0"
@ -16715,6 +16737,15 @@ __metadata:
languageName: node
linkType: hard
"fault@npm:^1.0.0":
version: 1.0.4
resolution: "fault@npm:1.0.4"
dependencies:
format: ^0.2.0
checksum: 5ac610d8b09424e0f2fa8cf913064372f2ee7140a203a79957f73ed557c0e79b1a3d096064d7f40bde8132a69204c1fe25ec23634c05c6da2da2039cff26c4e7
languageName: node
linkType: hard
"faye-websocket@npm:~0.10.0":
version: 0.10.0
resolution: "faye-websocket@npm:0.10.0"
@ -17328,6 +17359,13 @@ __metadata:
languageName: node
linkType: hard
"format@npm:^0.2.0":
version: 0.2.2
resolution: "format@npm:0.2.2"
checksum: 646a60e1336250d802509cf24fb801e43bd4a70a07510c816fa133aa42cdbc9c21e66e9cc0801bb183c5b031c9d68be62e7fbb6877756e52357850f92aa28799
languageName: node
linkType: hard
"formidable@npm:^1.2.0":
version: 1.2.1
resolution: "formidable@npm:1.2.1"
@ -18984,6 +19022,13 @@ __metadata:
languageName: node
linkType: hard
"highlight.js@npm:^10.4.1, highlight.js@npm:~10.7.0":
version: 10.7.3
resolution: "highlight.js@npm:10.7.3"
checksum: defeafcd546b535d710d8efb8e650af9e3b369ef53e28c3dc7893eacfe263200bba4c5fcf43524ae66d5c0c296b1af0870523ceae3e3104d24b7abf6374a4fea
languageName: node
linkType: hard
"history@npm:^4.9.0":
version: 4.10.1
resolution: "history@npm:4.10.1"
@ -23480,6 +23525,16 @@ __metadata:
languageName: node
linkType: hard
"lowlight@npm:^1.17.0":
version: 1.20.0
resolution: "lowlight@npm:1.20.0"
dependencies:
fault: ^1.0.0
highlight.js: ~10.7.0
checksum: 14a1815d6bae202ddee313fc60f06d46e5235c02fa483a77950b401d85b4c1e12290145ccd17a716b07f9328bd5864aa2d402b6a819ff3be7c833d9748ff8ba7
languageName: node
linkType: hard
"lru-cache@npm:^4.0.1":
version: 4.1.1
resolution: "lru-cache@npm:4.1.1"
@ -24233,6 +24288,7 @@ __metadata:
"@types/yargs": ^17.0.8
"@typescript-eslint/eslint-plugin": ^5.30.7
"@typescript-eslint/parser": ^5.30.7
"@whitespace/storybook-addon-html": ^5.1.1
"@zxing/browser": ^0.0.10
"@zxing/library": 0.8.0
addons-linter: ^5.2.0
@ -24383,6 +24439,7 @@ __metadata:
react-responsive-carousel: ^3.2.21
react-router-dom: ^5.1.2
react-simple-file-input: ^2.0.0
react-syntax-highlighter: ^15.5.0
react-tippy: ^1.2.2
react-toggle-button: ^2.2.0
react-transition-group: ^1.2.1
@ -27826,6 +27883,20 @@ __metadata:
languageName: node
linkType: hard
"prismjs@npm:^1.27.0":
version: 1.29.0
resolution: "prismjs@npm:1.29.0"
checksum: 007a8869d4456ff8049dc59404e32d5666a07d99c3b0e30a18bd3b7676dfa07d1daae9d0f407f20983865fd8da56de91d09cb08e6aa61f5bc420a27c0beeaf93
languageName: node
linkType: hard
"prismjs@npm:~1.27.0":
version: 1.27.0
resolution: "prismjs@npm:1.27.0"
checksum: 85c7f4a3e999073502cc9e1882af01e3709706369ec254b60bff1149eda701f40d02512acab956012dc7e61cfd61743a3a34c1bd0737e8dbacd79141e5698bbc
languageName: node
linkType: hard
"process-nextick-args@npm:^1.0.6, process-nextick-args@npm:~1.0.6":
version: 1.0.7
resolution: "process-nextick-args@npm:1.0.7"
@ -28817,6 +28888,21 @@ __metadata:
languageName: node
linkType: hard
"react-syntax-highlighter@npm:^15.5.0":
version: 15.5.0
resolution: "react-syntax-highlighter@npm:15.5.0"
dependencies:
"@babel/runtime": ^7.3.1
highlight.js: ^10.4.1
lowlight: ^1.17.0
prismjs: ^1.27.0
refractor: ^3.6.0
peerDependencies:
react: ">= 0.14.0"
checksum: c082b48f30f8ba8d0c55ed1d761910630860077c7ff5793c4c912adcb5760df06436ed0ad62be0de28113aac9ad2af55eccd995f8eee98df53382e4ced2072fb
languageName: node
linkType: hard
"react-tippy@npm:^1.2.2":
version: 1.2.2
resolution: "react-tippy@npm:1.2.2"
@ -29178,6 +29264,17 @@ __metadata:
languageName: node
linkType: hard
"refractor@npm:^3.6.0":
version: 3.6.0
resolution: "refractor@npm:3.6.0"
dependencies:
hastscript: ^6.0.0
parse-entities: ^2.0.0
prismjs: ~1.27.0
checksum: 39b01c4168c77c5c8486f9bf8907bbb05f257f15026057ba5728535815a2d90eed620468a4bfbb2b8ceefbb3ce3931a1be8b17152dbdbc8b0eef92450ff750a2
languageName: node
linkType: hard
"regenerate-unicode-properties@npm:^10.1.0":
version: 10.1.0
resolution: "regenerate-unicode-properties@npm:10.1.0"