diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index f98e5fb73173f..42eebde26cbdf 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -24,6 +24,7 @@
- `FontSizePicker`: updated to satisfy `react/exhuastive-deps` eslint rule ([#41600](https://github.com/WordPress/gutenberg/pull/41600)).
- `Dropdown`: Make sure cleanup (closing the dropdown) only runs when the menu has actually been opened.
- Enhance the TypeScript migration guidelines ([#41669](https://github.com/WordPress/gutenberg/pull/41669)).
+- `ExternalLink`: Convert to TypeScript ([#41681](https://github.com/WordPress/gutenberg/pull/41681)).
## 19.12.0 (2022-06-01)
diff --git a/packages/components/src/external-link/README.md b/packages/components/src/external-link/README.md
index 73fe8b5c3fbdd..c0e5621154cc2 100644
--- a/packages/components/src/external-link/README.md
+++ b/packages/components/src/external-link/README.md
@@ -1,5 +1,7 @@
# ExternalLink
+Link to an external resource.
+
## Usage
```jsx
@@ -9,3 +11,19 @@ const MyExternalLink = () => (
WordPress.org
);
```
+
+## Props
+
+The component accepts the following props. Any other props will be passed through to the `a`.
+
+### `children`: `ReactNode`
+
+The content to be displayed within the link.
+
+- Required: Yes
+
+### `href`: `string`
+
+The URL of the external resource.
+
+- Required: Yes
diff --git a/packages/components/src/external-link/index.js b/packages/components/src/external-link/index.tsx
similarity index 57%
rename from packages/components/src/external-link/index.js
rename to packages/components/src/external-link/index.tsx
index 45c2e403f14c4..44d006a676e26 100644
--- a/packages/components/src/external-link/index.js
+++ b/packages/components/src/external-link/index.tsx
@@ -3,6 +3,7 @@
*/
import classnames from 'classnames';
import { compact, uniq } from 'lodash';
+import type { ForwardedRef } from 'react';
/**
* WordPress dependencies
@@ -16,12 +17,18 @@ import { external } from '@wordpress/icons';
*/
import { VisuallyHidden } from '../visually-hidden';
import { StyledIcon } from './styles/external-link-styles';
+import type { ExternalLinkProps } from './types';
+import type { WordPressComponentProps } from '../ui/context';
-export function ExternalLink(
- { href, children, className, rel = '', ...additionalProps },
- ref
+function UnforwardedExternalLink(
+ props: Omit<
+ WordPressComponentProps< ExternalLinkProps, 'a', false >,
+ 'target'
+ >,
+ ref: ForwardedRef< HTMLAnchorElement >
) {
- rel = uniq(
+ const { href, children, className, rel = '', ...additionalProps } = props;
+ const optimizedRel = uniq(
compact( [ ...rel.split( ' ' ), 'external', 'noreferrer', 'noopener' ] )
).join( ' ' );
const classes = classnames( 'components-external-link', className );
@@ -32,7 +39,7 @@ export function ExternalLink(
className={ classes }
href={ href }
target="_blank"
- rel={ rel }
+ rel={ optimizedRel }
ref={ ref }
>
{ children }
@@ -51,4 +58,17 @@ export function ExternalLink(
);
}
-export default forwardRef( ExternalLink );
+/**
+ * Link to an external resource.
+ *
+ * ```jsx
+ * import { ExternalLink } from '@wordpress/components';
+ *
+ * const MyExternalLink = () => (
+ * WordPress.org
+ * );
+ * ```
+ */
+export const ExternalLink = forwardRef( UnforwardedExternalLink );
+
+export default ExternalLink;
diff --git a/packages/components/src/external-link/stories/index.js b/packages/components/src/external-link/stories/index.js
deleted file mode 100644
index 5f69a3f1d9cc9..0000000000000
--- a/packages/components/src/external-link/stories/index.js
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * External dependencies
- */
-import { text } from '@storybook/addon-knobs';
-/**
- * Internal dependencies
- */
-import ExternalLink from '../';
-
-export default {
- title: 'Components/ExternalLink',
- component: ExternalLink,
- parameters: {
- knobs: { disable: false },
- },
-};
-
-export const _default = () => {
- const title = text( 'children', 'WordPress' );
- const href = text( 'href', 'https://wordpress.org' );
-
- return { title };
-};
diff --git a/packages/components/src/external-link/stories/index.tsx b/packages/components/src/external-link/stories/index.tsx
new file mode 100644
index 0000000000000..f782d962f3740
--- /dev/null
+++ b/packages/components/src/external-link/stories/index.tsx
@@ -0,0 +1,36 @@
+/**
+ * External dependencies
+ */
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+
+/**
+ * Internal dependencies
+ */
+import ExternalLink from '..';
+
+const meta: ComponentMeta< typeof ExternalLink > = {
+ component: ExternalLink,
+ title: 'Components/ExternalLink',
+ argTypes: {
+ children: { control: { type: 'text' } },
+ },
+ parameters: {
+ controls: {
+ expanded: true,
+ },
+ docs: { source: { state: 'open' } },
+ },
+};
+export default meta;
+
+const Template: ComponentStory< typeof ExternalLink > = ( { ...args } ) => {
+ return ;
+};
+
+export const Default: ComponentStory< typeof ExternalLink > = Template.bind(
+ {}
+);
+Default.args = {
+ children: 'WordPress',
+ href: 'https://wordpress.org',
+};
diff --git a/packages/components/src/external-link/styles/external-link-styles.js b/packages/components/src/external-link/styles/external-link-styles.ts
similarity index 100%
rename from packages/components/src/external-link/styles/external-link-styles.js
rename to packages/components/src/external-link/styles/external-link-styles.ts
diff --git a/packages/components/src/external-link/types.ts b/packages/components/src/external-link/types.ts
new file mode 100644
index 0000000000000..a2b5cfb8324ce
--- /dev/null
+++ b/packages/components/src/external-link/types.ts
@@ -0,0 +1,15 @@
+/**
+ * External dependencies
+ */
+import type { ReactNode } from 'react';
+
+export type ExternalLinkProps = {
+ /**
+ * The content to be displayed within the link.
+ */
+ children: ReactNode;
+ /**
+ * The URL of the external resource.
+ */
+ href: string;
+};
diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json
index 4ed0fba2b54c9..060575f52426d 100644
--- a/packages/components/tsconfig.json
+++ b/packages/components/tsconfig.json
@@ -54,6 +54,7 @@
"src/dropdown/**/*",
"src/dropdown-menu/**/*",
"src/elevation/**/*",
+ "src/external-link/**/*",
"src/flex/**/*",
"src/form-group/**/*",
"src/form-token-field/**/*",