@automattic/vip-design-system 2.4.3 → 2.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,6 +52,9 @@ var Button = exports.Button = /*#__PURE__*/(0, _react.forwardRef)(function (_ref
52
52
  cursor: 'not-allowed',
53
53
  pointerEvents: 'none'
54
54
  },
55
+ '&:hover, &:focus': {
56
+ textDecoration: 'none'
57
+ },
55
58
  flexGrow: Boolean(grow) === true ? '1' : undefined,
56
59
  width: Boolean(full) === true ? '100%' : undefined
57
60
  }, sx)
@@ -0,0 +1,11 @@
1
+ /// <reference types="react" />
2
+ type Props = {
3
+ children?: React.ReactNode;
4
+ screenReaderText?: string | number;
5
+ href: string;
6
+ showExternalIcon?: boolean;
7
+ defaultScreenReaderText?: boolean;
8
+ newTab?: boolean;
9
+ };
10
+ export declare const LinkExternal: React.FC<Props>;
11
+ export default LinkExternal;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports["default"] = exports.LinkExternal = void 0;
5
+ var _i18nCalypso = require("i18n-calypso");
6
+ var _Link = require("../Link");
7
+ var _ScreenReaderText = _interopRequireDefault(require("../ScreenReaderText"));
8
+ var _jsxRuntime = require("theme-ui/jsx-runtime");
9
+ var _excluded = ["children", "screenReaderText", "href", "showExternalIcon", "newTab"];
10
+ /**
11
+ * External dependencies
12
+ */
13
+ /**
14
+ * Internal dependencies
15
+ */
16
+ // Screen reader announcements
17
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
18
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
19
+ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
20
+ var DEFAULT_EXTERNAL_LINK_TEXT = (0, _i18nCalypso.translate)(', external link'); // reads as: link, <link text>, external link
21
+ var NEW_TAB_TEXT = (0, _i18nCalypso.translate)(', opens in a new tab'); // reads as: link, <link text>, external link, opens in a new tab
22
+
23
+ var LinkExternal = exports.LinkExternal = function LinkExternal(_ref) {
24
+ var _ref$children = _ref.children,
25
+ children = _ref$children === void 0 ? null : _ref$children,
26
+ _ref$screenReaderText = _ref.screenReaderText,
27
+ screenReaderText = _ref$screenReaderText === void 0 ? '' : _ref$screenReaderText,
28
+ href = _ref.href,
29
+ _ref$showExternalIcon = _ref.showExternalIcon,
30
+ showExternalIcon = _ref$showExternalIcon === void 0 ? true : _ref$showExternalIcon,
31
+ _ref$newTab = _ref.newTab,
32
+ newTab = _ref$newTab === void 0 ? false : _ref$newTab,
33
+ rest = _objectWithoutPropertiesLoose(_ref, _excluded);
34
+ return (0, _jsxRuntime.jsxs)(_Link.Link, _extends({
35
+ as: "a",
36
+ target: newTab ? '_blank' : '_self',
37
+ rel: newTab ? 'noopener noreferrer' : '',
38
+ href: href
39
+ }, rest, {
40
+ children: [children, (0, _jsxRuntime.jsxs)(_ScreenReaderText["default"], {
41
+ children: [screenReaderText, DEFAULT_EXTERNAL_LINK_TEXT, newTab ? NEW_TAB_TEXT : '']
42
+ }), showExternalIcon && (0, _jsxRuntime.jsx)("span", {
43
+ "aria-hidden": "true",
44
+ children: "\xA0\u2197"
45
+ })]
46
+ }));
47
+ };
48
+ var _default = exports["default"] = LinkExternal;
@@ -0,0 +1,17 @@
1
+ /// <reference types="react" />
2
+ import LinkExternal from './LinkExternal';
3
+ import type { StoryObj } from '@storybook/react';
4
+ declare const _default: {
5
+ title: string;
6
+ component: import("react").FC<{
7
+ children?: import("react").ReactNode;
8
+ screenReaderText?: string | number | undefined;
9
+ href: string;
10
+ showExternalIcon?: boolean | undefined;
11
+ defaultScreenReaderText?: boolean | undefined;
12
+ newTab?: boolean | undefined;
13
+ }>;
14
+ };
15
+ export default _default;
16
+ type Story = StoryObj<typeof LinkExternal>;
17
+ export declare const Default: Story;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+
3
+ exports.__esModule = true;
4
+ exports["default"] = exports.Default = void 0;
5
+ var _LinkExternal = _interopRequireDefault(require("./LinkExternal"));
6
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
7
+ var _default = exports["default"] = {
8
+ title: 'Navigation/LinkExternal',
9
+ component: _LinkExternal["default"]
10
+ };
11
+ var Default = exports.Default = {
12
+ args: {
13
+ children: 'View on GitHub',
14
+ href: 'https://github.com/Automattic/vip-design-system'
15
+ }
16
+ };
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom';
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ require("@testing-library/jest-dom");
4
+ var _react = require("@testing-library/react");
5
+ var _LinkExternal = _interopRequireDefault(require("./LinkExternal"));
6
+ var _jsxRuntime = require("theme-ui/jsx-runtime");
7
+ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
8
+ function _extends() { _extends = Object.assign ? Object.assign.bind() : function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } /**
9
+ * Internal dependencies
10
+ */
11
+ var props = {
12
+ children: 'View on Github',
13
+ href: 'https://github.com/Automattic/vip-design-system'
14
+ };
15
+ describe('<LinkExternal />', function () {
16
+ it('should render correctly', function () {
17
+ (0, _react.render)((0, _jsxRuntime.jsx)(_LinkExternal["default"], _extends({}, props, {
18
+ children: "View on Github"
19
+ })));
20
+ var link = _react.screen.getByRole('link');
21
+ expect(link).toHaveTextContent(/view on github/i);
22
+ expect(link).toHaveTextContent(/external link/i);
23
+ expect(link).toHaveAttribute('target', '_self');
24
+ expect(link).toHaveAttribute('rel', '');
25
+ });
26
+ it('should open in new tab when newTab is true', function () {
27
+ (0, _react.render)((0, _jsxRuntime.jsx)(_LinkExternal["default"], _extends({}, props, {
28
+ newTab: true
29
+ })));
30
+ var link = _react.screen.getByRole('link');
31
+ expect(link).toHaveTextContent(/opens in a new tab/i);
32
+ expect(link).toHaveAttribute('target', '_blank');
33
+ expect(link).toHaveAttribute('rel', 'noopener noreferrer');
34
+ });
35
+ it('should contain additional screenreader text when added', function () {
36
+ (0, _react.render)((0, _jsxRuntime.jsx)(_LinkExternal["default"], _extends({}, props, {
37
+ screenReaderText: "VIP Design System"
38
+ })));
39
+ expect(_react.screen.getByRole('link', {
40
+ name: /vip design system/i
41
+ })).toBeInTheDocument();
42
+ });
43
+ it('should hide icon when showExternalIcon is false', function () {
44
+ (0, _react.render)((0, _jsxRuntime.jsx)(_LinkExternal["default"], _extends({}, props, {
45
+ showExternalIcon: false
46
+ })));
47
+ expect(_react.screen.queryByText('↗')).not.toBeInTheDocument();
48
+ });
49
+ });
@@ -98,6 +98,7 @@ var MobileMenuTrigger = exports.MobileMenuTrigger = function MobileMenuTrigger(_
98
98
  sx: {
99
99
  display: display,
100
100
  alignItems: 'center',
101
+ flexShrink: 0,
101
102
  color: variant === 'inverse' ? 'button.primary.label.default' : 'button.tertiary.label.default',
102
103
  width: 38,
103
104
  height: 38,
@@ -25,7 +25,7 @@ var Logo = exports.Logo = function Logo(_ref) {
25
25
  sx: {
26
26
  width: 54,
27
27
  color: 'toolbar.brand',
28
- mr: 5,
28
+ flexShrink: 0,
29
29
  display: 'inline-block'
30
30
  },
31
31
  href: href,
@@ -38,6 +38,7 @@ import { TableRow } from './Table';
38
38
  import { TableCell } from './Table';
39
39
  import { Tooltip } from './Tooltip';
40
40
  import { Link } from './Link';
41
+ import LinkExternal from './LinkExternal/LinkExternal';
41
42
  import { Radio } from './Form';
42
43
  import { RadioBoxGroup } from './Form';
43
44
  import { Textarea } from './Form';
@@ -56,4 +57,4 @@ import { Validation } from './Form';
56
57
  import { Wizard } from './Wizard';
57
58
  import { WizardStep } from './Wizard';
58
59
  import theme from './theme';
59
- export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, Grid, Flex, Notice, OptionRow, Heading, Input, Label, Spinner, Table, TableRow, TableCell, Tooltip, Link, Radio, RadioBoxGroup, Textarea, Progress, Text, Tabs, Nav, NavItem, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Validation, Wizard, WizardStep, theme };
60
+ export { Accordion, Avatar, Badge, Box, Breadcrumbs, Button, ButtonSubmit, ButtonVariant, Card, Checkbox, Code, Dialog, NewDialog, Form, Drawer, Dropdown, DialogButton, DialogMenu, DialogMenuItem, DialogDivider, DialogContent, DialogTrigger, ConfirmationDialog, MobileMenu, MobileMenuTrigger, MobileMenuWrapper, NewConfirmationDialog, Grid, Flex, Notice, OptionRow, Heading, Input, Label, Spinner, Table, TableRow, TableCell, Tooltip, Link, LinkExternal, Radio, RadioBoxGroup, Textarea, Progress, Text, Tabs, Nav, NavItem, TabsTrigger, TabsContent, TabsList, Toggle, ToggleRow, Toolbar, Validation, Wizard, WizardStep, theme };
@@ -36,6 +36,7 @@ import {
36
36
  import { Grid } from './Grid';
37
37
  import { Heading } from './Heading';
38
38
  import { Link } from './Link';
39
+ import LinkExternal from './LinkExternal/LinkExternal';
39
40
  import { MobileMenuWrapper, MobileMenuTrigger, MobileMenu } from './MobileMenu/MobileMenu';
40
41
  import { Nav } from './Nav/Nav';
41
42
  import { NavItem } from './Nav/NavItem';
@@ -95,6 +96,7 @@ export {
95
96
  TableCell,
96
97
  Tooltip,
97
98
  Link,
99
+ LinkExternal,
98
100
  Radio,
99
101
  RadioBoxGroup,
100
102
  Textarea,
@@ -4,7 +4,10 @@ exports.__esModule = true;
4
4
  exports.generateBreakpoints = void 0;
5
5
  var generateBreakpoints = exports.generateBreakpoints = function generateBreakpoints(breakpoints) {
6
6
  var values = Object.values(breakpoints);
7
- return values.map(function (bp) {
7
+ return values.map(function (bp, index) {
8
+ if (index === 0) {
9
+ return "0px";
10
+ }
8
11
  return bp + "px";
9
12
  });
10
13
  };
@@ -72,9 +72,10 @@ declare namespace _default {
72
72
  fontWeight: string;
73
73
  boxShadow: string;
74
74
  borderRadius: number;
75
- '&:hover, &:focus': {
75
+ '&:hover': {
76
76
  backgroundColor: string;
77
77
  color: string;
78
+ textDecoration: string;
78
79
  };
79
80
  verticalAlign: string;
80
81
  alignItems: string;
@@ -86,16 +87,13 @@ declare namespace _default {
86
87
  fill: string;
87
88
  };
88
89
  };
89
- '&:hover': {
90
- textDecoration: string;
91
- };
92
90
  };
93
91
  export { primary_1 as primary };
94
92
  let secondary_1: {
95
93
  variant: string;
96
94
  color: string;
97
95
  bg: string;
98
- '&:hover, &:focus': {
96
+ '&:hover': {
99
97
  backgroundColor: string;
100
98
  color: string;
101
99
  };
@@ -107,7 +105,7 @@ declare namespace _default {
107
105
  bg: string;
108
106
  border: string;
109
107
  borderColor: string;
110
- '&:hover, &:focus': {
108
+ '&:hover': {
111
109
  backgroundColor: string;
112
110
  color: string;
113
111
  border: string;
@@ -120,7 +118,7 @@ declare namespace _default {
120
118
  bg: string;
121
119
  border: string;
122
120
  borderColor: string;
123
- '&:hover, &:focus': {
121
+ '&:hover': {
124
122
  backgroundColor: string;
125
123
  color: string;
126
124
  border: string;
@@ -133,7 +131,7 @@ declare namespace _default {
133
131
  bg: string;
134
132
  border: string;
135
133
  borderColor: string;
136
- '&:hover, &:focus': {
134
+ '&:hover': {
137
135
  backgroundColor: string;
138
136
  color: string;
139
137
  border: string;
@@ -146,7 +144,7 @@ declare namespace _default {
146
144
  bg: string;
147
145
  border: string;
148
146
  borderColor: string;
149
- '&:hover, &:focus': {
147
+ '&:hover': {
150
148
  backgroundColor: string;
151
149
  color: string;
152
150
  border: string;
@@ -174,7 +172,7 @@ declare namespace _default {
174
172
  variant: string;
175
173
  color: string;
176
174
  padding: number;
177
- '&:hover, &:focus': {
175
+ '&:hover': {
178
176
  backgroundColor: string;
179
177
  };
180
178
  };
@@ -187,17 +185,14 @@ declare namespace _default {
187
185
  };
188
186
  '&:hover': {
189
187
  color: string;
188
+ textDecorationLine: string;
189
+ textDecorationThickness: string;
190
190
  };
191
191
  '&:active': {
192
192
  color: string;
193
193
  };
194
194
  textDecorationThickness: string;
195
195
  textUnderlineOffset: string;
196
- '&:hover, &:focus': {
197
- color: string;
198
- textDecorationLine: string;
199
- textDecorationThickness: string;
200
- };
201
196
  };
202
197
  'button-primary': {
203
198
  variant: string;
@@ -313,37 +308,32 @@ declare namespace _default {
313
308
  };
314
309
  };
315
310
  export namespace styles {
316
- namespace root {
317
- export let fontFamily: string;
318
- export let lineHeight: string;
319
- export let fontWeight: string;
320
- let color_1: string;
321
- export { color_1 as color };
322
- let backgroundColor_5: string;
323
- export { backgroundColor_5 as backgroundColor };
324
- export let fontSmoothing: string;
325
- export let a: {
311
+ let root: {
312
+ fontFamily: string;
313
+ lineHeight: string;
314
+ fontWeight: string;
315
+ color: string;
316
+ backgroundColor: string;
317
+ '-webkit-font-smoothing': string;
318
+ '-moz-osx-font-smoothing': string;
319
+ a: {
326
320
  '&:hover': {
327
321
  textDecorationLine: string;
328
322
  textDecorationThickness: string;
329
323
  textUnderlineOffset: string;
330
324
  };
331
325
  };
332
- export namespace svg {
333
- export let fill: string;
334
- let display_1: string;
335
- export { display_1 as display };
336
- }
337
- export namespace pre {
338
- let fontFamily_1: string;
339
- export { fontFamily_1 as fontFamily };
340
- }
341
- export namespace p_2 {
342
- let color_2: string;
343
- export { color_2 as color };
344
- }
345
- export { p_2 as p };
346
- }
326
+ svg: {
327
+ fill: string;
328
+ display: string;
329
+ };
330
+ pre: {
331
+ fontFamily: string;
332
+ };
333
+ p: {
334
+ color: string;
335
+ };
336
+ };
347
337
  }
348
338
  }
349
339
  export default _default;
@@ -283,9 +283,10 @@ export default {
283
283
  fontWeight: 'medium',
284
284
  boxShadow: 'none',
285
285
  borderRadius: 1,
286
- '&:hover, &:focus': {
286
+ '&:hover': {
287
287
  backgroundColor: 'button.primary.background.hover',
288
288
  color: 'button.primary.label.hover',
289
+ textDecoration: 'none',
289
290
  },
290
291
  verticalAlign: 'middle',
291
292
  alignItems: 'center',
@@ -297,9 +298,6 @@ export default {
297
298
  fill: 'inherit',
298
299
  },
299
300
  },
300
- '&:hover': {
301
- textDecoration: 'none',
302
- },
303
301
  },
304
302
 
305
303
  secondary: {
@@ -307,7 +305,7 @@ export default {
307
305
  color: 'button.secondary.label.default',
308
306
  bg: 'button.secondary.background.default',
309
307
 
310
- '&:hover, &:focus': {
308
+ '&:hover': {
311
309
  backgroundColor: 'button.secondary.background.hover',
312
310
  color: 'button.secondary.label.hover',
313
311
  },
@@ -320,7 +318,7 @@ export default {
320
318
  border: '1px solid',
321
319
  borderColor: 'button.tertiary.border.default',
322
320
 
323
- '&:hover, &:focus': {
321
+ '&:hover': {
324
322
  backgroundColor: 'button.tertiary.background.hover',
325
323
  color: 'button.tertiary.label.hover',
326
324
  border: '1px solid',
@@ -335,7 +333,7 @@ export default {
335
333
  border: '1px solid',
336
334
  borderColor: 'transparent',
337
335
 
338
- '&:hover, &:focus': {
336
+ '&:hover': {
339
337
  backgroundColor: 'button.display.background.hover',
340
338
  color: 'button.display.label.hover',
341
339
  border: '1px solid',
@@ -350,7 +348,7 @@ export default {
350
348
  border: '1px solid',
351
349
  borderColor: 'transparent',
352
350
 
353
- '&:hover, &:focus': {
351
+ '&:hover': {
354
352
  backgroundColor: 'button.ghost.background.hover',
355
353
  color: 'button.ghost.label.hover',
356
354
  border: '1px solid',
@@ -365,7 +363,7 @@ export default {
365
363
  border: '1px solid',
366
364
  borderColor: 'transparent',
367
365
 
368
- '&:hover, &:focus': {
366
+ '&:hover': {
369
367
  backgroundColor: 'button.danger.primary.background.hover',
370
368
  color: 'button.danger.primary.label.hover',
371
369
  border: '1px solid',
@@ -396,7 +394,7 @@ export default {
396
394
  color: 'text',
397
395
  padding: 1,
398
396
 
399
- '&:hover, &:focus': {
397
+ '&:hover': {
400
398
  backgroundColor: 'borders.2',
401
399
  },
402
400
  },
@@ -409,7 +407,9 @@ export default {
409
407
  color: 'links.visited',
410
408
  },
411
409
  '&:hover': {
412
- color: 'hover',
410
+ color: 'links.hover',
411
+ textDecorationLine: 'underline',
412
+ textDecorationThickness: '0.125rem',
413
413
  },
414
414
  '&:active': {
415
415
  color: 'links.active',
@@ -417,12 +417,6 @@ export default {
417
417
 
418
418
  textDecorationThickness: '0.125rem',
419
419
  textUnderlineOffset: '0.250rem',
420
-
421
- '&:hover, &:focus': {
422
- color: 'links.hover',
423
- textDecorationLine: 'underline',
424
- textDecorationThickness: '0.125rem',
425
- },
426
420
  },
427
421
  'button-primary': {
428
422
  variant: 'buttons.primary',
@@ -532,7 +526,8 @@ export default {
532
526
  fontWeight: 'body',
533
527
  color: 'text',
534
528
  backgroundColor: 'backgrounds.primary',
535
- fontSmoothing: 'antialiased',
529
+ '-webkit-font-smoothing': 'antialiased',
530
+ '-moz-osx-font-smoothing': 'grayscale',
536
531
  a: {
537
532
  '&:hover': {
538
533
  textDecorationLine: 'underline',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/vip-design-system",
3
- "version": "2.4.3",
3
+ "version": "2.4.5",
4
4
  "main": "build/system/index.js",
5
5
  "scripts": {
6
6
  "build-storybook": "storybook build",
@@ -43,6 +43,7 @@
43
43
  "accessible-autocomplete": "^2.0.4",
44
44
  "classnames": "^2.3.1",
45
45
  "framer-motion": "^3.9.1",
46
+ "i18n-calypso": "^7.0.0",
46
47
  "react-icons": "^4.7.0"
47
48
  },
48
49
  "peerDependencies": {
@@ -54,6 +54,9 @@ const Button = forwardRef< HTMLButtonElement, ButtonProps >(
54
54
  cursor: 'not-allowed',
55
55
  pointerEvents: 'none',
56
56
  },
57
+ '&:hover, &:focus': {
58
+ textDecoration: 'none',
59
+ },
57
60
  flexGrow: Boolean( grow ) === true ? '1' : undefined,
58
61
  width: Boolean( full ) === true ? '100%' : undefined,
59
62
  ...sx,
@@ -0,0 +1,17 @@
1
+ import LinkExternal from './LinkExternal';
2
+
3
+ import type { StoryObj } from '@storybook/react';
4
+
5
+ export default {
6
+ title: 'Navigation/LinkExternal',
7
+ component: LinkExternal,
8
+ };
9
+
10
+ type Story = StoryObj< typeof LinkExternal >;
11
+
12
+ export const Default: Story = {
13
+ args: {
14
+ children: 'View on GitHub',
15
+ href: 'https://github.com/Automattic/vip-design-system',
16
+ },
17
+ };
@@ -0,0 +1,47 @@
1
+ import '@testing-library/jest-dom';
2
+ import { render, screen } from '@testing-library/react';
3
+
4
+ /**
5
+ * Internal dependencies
6
+ */
7
+ import LinkExternal from './LinkExternal';
8
+
9
+ const props = {
10
+ children: 'View on Github',
11
+ href: 'https://github.com/Automattic/vip-design-system',
12
+ };
13
+
14
+ describe( '<LinkExternal />', () => {
15
+ it( 'should render correctly', () => {
16
+ render( <LinkExternal { ...props }>View on Github</LinkExternal> );
17
+
18
+ const link = screen.getByRole( 'link' );
19
+
20
+ expect( link ).toHaveTextContent( /view on github/i );
21
+ expect( link ).toHaveTextContent( /external link/i );
22
+ expect( link ).toHaveAttribute( 'target', '_self' );
23
+ expect( link ).toHaveAttribute( 'rel', '' );
24
+ } );
25
+
26
+ it( 'should open in new tab when newTab is true', () => {
27
+ render( <LinkExternal { ...props } newTab /> );
28
+
29
+ const link = screen.getByRole( 'link' );
30
+
31
+ expect( link ).toHaveTextContent( /opens in a new tab/i );
32
+ expect( link ).toHaveAttribute( 'target', '_blank' );
33
+ expect( link ).toHaveAttribute( 'rel', 'noopener noreferrer' );
34
+ } );
35
+
36
+ it( 'should contain additional screenreader text when added', () => {
37
+ render( <LinkExternal { ...props } screenReaderText="VIP Design System" /> );
38
+
39
+ expect( screen.getByRole( 'link', { name: /vip design system/i } ) ).toBeInTheDocument();
40
+ } );
41
+
42
+ it( 'should hide icon when showExternalIcon is false', () => {
43
+ render( <LinkExternal { ...props } showExternalIcon={ false } /> );
44
+
45
+ expect( screen.queryByText( '↗' ) ).not.toBeInTheDocument();
46
+ } );
47
+ } );
@@ -0,0 +1,50 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { translate } from 'i18n-calypso';
5
+
6
+ /**
7
+ * Internal dependencies
8
+ */
9
+ import { Link } from '../Link';
10
+ import ScreenReaderText from '../ScreenReaderText';
11
+
12
+ // Screen reader announcements
13
+ const DEFAULT_EXTERNAL_LINK_TEXT = translate( ', external link' ); // reads as: link, <link text>, external link
14
+ const NEW_TAB_TEXT = translate( ', opens in a new tab' ); // reads as: link, <link text>, external link, opens in a new tab
15
+
16
+ type Props = {
17
+ children?: React.ReactNode;
18
+ screenReaderText?: string | number;
19
+ href: string;
20
+ showExternalIcon?: boolean;
21
+ defaultScreenReaderText?: boolean;
22
+ newTab?: boolean;
23
+ };
24
+
25
+ export const LinkExternal: React.FC< Props > = ( {
26
+ children = null,
27
+ screenReaderText = '',
28
+ href,
29
+ showExternalIcon = true,
30
+ newTab = false,
31
+ ...rest
32
+ } ) => (
33
+ <Link
34
+ as="a"
35
+ target={ newTab ? '_blank' : '_self' }
36
+ rel={ newTab ? 'noopener noreferrer' : '' }
37
+ href={ href }
38
+ { ...rest }
39
+ >
40
+ { children }
41
+ <ScreenReaderText>
42
+ { screenReaderText }
43
+ { DEFAULT_EXTERNAL_LINK_TEXT }
44
+ { newTab ? NEW_TAB_TEXT : '' }
45
+ </ScreenReaderText>
46
+ { showExternalIcon && <span aria-hidden="true">&nbsp;↗</span> }
47
+ </Link>
48
+ );
49
+
50
+ export default LinkExternal;
@@ -108,6 +108,7 @@ export const MobileMenuTrigger = ( {
108
108
  sx={ {
109
109
  display,
110
110
  alignItems: 'center',
111
+ flexShrink: 0,
111
112
  color:
112
113
  variant === 'inverse' ? 'button.primary.label.default' : 'button.tertiary.label.default',
113
114
  width: 38,
@@ -23,7 +23,7 @@ export const Logo = ( { className, as = 'a', href }: LogoProps ) => (
23
23
  sx={ {
24
24
  width: 54,
25
25
  color: 'toolbar.brand',
26
- mr: 5,
26
+ flexShrink: 0,
27
27
  display: 'inline-block',
28
28
  } }
29
29
  href={ href }
@@ -36,6 +36,7 @@ import {
36
36
  import { Grid } from './Grid';
37
37
  import { Heading } from './Heading';
38
38
  import { Link } from './Link';
39
+ import LinkExternal from './LinkExternal/LinkExternal';
39
40
  import { MobileMenuWrapper, MobileMenuTrigger, MobileMenu } from './MobileMenu/MobileMenu';
40
41
  import { Nav } from './Nav/Nav';
41
42
  import { NavItem } from './Nav/NavItem';
@@ -95,6 +96,7 @@ export {
95
96
  TableCell,
96
97
  Tooltip,
97
98
  Link,
99
+ LinkExternal,
98
100
  Radio,
99
101
  RadioBoxGroup,
100
102
  Textarea,
@@ -5,5 +5,11 @@ type Breakpoints = {
5
5
  export const generateBreakpoints = ( breakpoints: Breakpoints ) => {
6
6
  const values = Object.values( breakpoints );
7
7
 
8
- return values.map( bp => `${ bp }px` );
8
+ return values.map( ( bp, index ) => {
9
+ if ( index === 0 ) {
10
+ return `0px`;
11
+ }
12
+
13
+ return `${ bp }px`;
14
+ } );
9
15
  };
@@ -283,9 +283,10 @@ export default {
283
283
  fontWeight: 'medium',
284
284
  boxShadow: 'none',
285
285
  borderRadius: 1,
286
- '&:hover, &:focus': {
286
+ '&:hover': {
287
287
  backgroundColor: 'button.primary.background.hover',
288
288
  color: 'button.primary.label.hover',
289
+ textDecoration: 'none',
289
290
  },
290
291
  verticalAlign: 'middle',
291
292
  alignItems: 'center',
@@ -297,9 +298,6 @@ export default {
297
298
  fill: 'inherit',
298
299
  },
299
300
  },
300
- '&:hover': {
301
- textDecoration: 'none',
302
- },
303
301
  },
304
302
 
305
303
  secondary: {
@@ -307,7 +305,7 @@ export default {
307
305
  color: 'button.secondary.label.default',
308
306
  bg: 'button.secondary.background.default',
309
307
 
310
- '&:hover, &:focus': {
308
+ '&:hover': {
311
309
  backgroundColor: 'button.secondary.background.hover',
312
310
  color: 'button.secondary.label.hover',
313
311
  },
@@ -320,7 +318,7 @@ export default {
320
318
  border: '1px solid',
321
319
  borderColor: 'button.tertiary.border.default',
322
320
 
323
- '&:hover, &:focus': {
321
+ '&:hover': {
324
322
  backgroundColor: 'button.tertiary.background.hover',
325
323
  color: 'button.tertiary.label.hover',
326
324
  border: '1px solid',
@@ -335,7 +333,7 @@ export default {
335
333
  border: '1px solid',
336
334
  borderColor: 'transparent',
337
335
 
338
- '&:hover, &:focus': {
336
+ '&:hover': {
339
337
  backgroundColor: 'button.display.background.hover',
340
338
  color: 'button.display.label.hover',
341
339
  border: '1px solid',
@@ -350,7 +348,7 @@ export default {
350
348
  border: '1px solid',
351
349
  borderColor: 'transparent',
352
350
 
353
- '&:hover, &:focus': {
351
+ '&:hover': {
354
352
  backgroundColor: 'button.ghost.background.hover',
355
353
  color: 'button.ghost.label.hover',
356
354
  border: '1px solid',
@@ -365,7 +363,7 @@ export default {
365
363
  border: '1px solid',
366
364
  borderColor: 'transparent',
367
365
 
368
- '&:hover, &:focus': {
366
+ '&:hover': {
369
367
  backgroundColor: 'button.danger.primary.background.hover',
370
368
  color: 'button.danger.primary.label.hover',
371
369
  border: '1px solid',
@@ -396,7 +394,7 @@ export default {
396
394
  color: 'text',
397
395
  padding: 1,
398
396
 
399
- '&:hover, &:focus': {
397
+ '&:hover': {
400
398
  backgroundColor: 'borders.2',
401
399
  },
402
400
  },
@@ -409,7 +407,9 @@ export default {
409
407
  color: 'links.visited',
410
408
  },
411
409
  '&:hover': {
412
- color: 'hover',
410
+ color: 'links.hover',
411
+ textDecorationLine: 'underline',
412
+ textDecorationThickness: '0.125rem',
413
413
  },
414
414
  '&:active': {
415
415
  color: 'links.active',
@@ -417,12 +417,6 @@ export default {
417
417
 
418
418
  textDecorationThickness: '0.125rem',
419
419
  textUnderlineOffset: '0.250rem',
420
-
421
- '&:hover, &:focus': {
422
- color: 'links.hover',
423
- textDecorationLine: 'underline',
424
- textDecorationThickness: '0.125rem',
425
- },
426
420
  },
427
421
  'button-primary': {
428
422
  variant: 'buttons.primary',
@@ -532,7 +526,8 @@ export default {
532
526
  fontWeight: 'body',
533
527
  color: 'text',
534
528
  backgroundColor: 'backgrounds.primary',
535
- fontSmoothing: 'antialiased',
529
+ '-webkit-font-smoothing': 'antialiased',
530
+ '-moz-osx-font-smoothing': 'grayscale',
536
531
  a: {
537
532
  '&:hover': {
538
533
  textDecorationLine: 'underline',