From 23115753ea2d28648e7cc9a6f22a5cb9308e6d20 Mon Sep 17 00:00:00 2001 From: Jesus Balderrama Date: Tue, 24 Feb 2026 14:46:17 -0700 Subject: [PATCH] refactor: MailtoLink component migrated to TS --- ...ailtoLink.test.jsx => MailtoLink.test.tsx} | 8 +- src/MailtoLink/{index.jsx => index.tsx} | 103 ++++++++---------- src/index.ts | 4 +- 3 files changed, 49 insertions(+), 66 deletions(-) rename src/MailtoLink/{MailtoLink.test.jsx => MailtoLink.test.tsx} (88%) rename src/MailtoLink/{index.jsx => index.tsx} (58%) diff --git a/src/MailtoLink/MailtoLink.test.jsx b/src/MailtoLink/MailtoLink.test.tsx similarity index 88% rename from src/MailtoLink/MailtoLink.test.jsx rename to src/MailtoLink/MailtoLink.test.tsx index c2f525dc7f..59ee9f344d 100644 --- a/src/MailtoLink/MailtoLink.test.jsx +++ b/src/MailtoLink/MailtoLink.test.tsx @@ -8,11 +8,11 @@ const emailAddress = 'edx@example.com'; const emailAddresses = ['foo@example.com', 'bar@example.com', 'baz@example.com']; const subject = 'subject'; const body = 'body'; -const content = 'content'; +const children = 'content'; -const baseProps = { subject, body, content }; +const baseProps = { subject, body, children }; -function MailtoLinkWrapper(props) { +function MailtoLinkWrapper(props: React.ComponentProps) { return ( @@ -55,7 +55,7 @@ describe('correct rendering', () => { }); it('renders empty mailtoLink', () => { - const { getByText } = render(); + const { getByText } = render({children}); const linkElement = getByText('content'); expect(linkElement.getAttribute('href')).toEqual('mailto:'); }); diff --git a/src/MailtoLink/index.jsx b/src/MailtoLink/index.tsx similarity index 58% rename from src/MailtoLink/index.jsx rename to src/MailtoLink/index.tsx index 3dc143fdb9..20a2b423ea 100644 --- a/src/MailtoLink/index.jsx +++ b/src/MailtoLink/index.tsx @@ -1,7 +1,4 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import emailPropType from 'email-prop-type'; -import isRequiredIf from 'react-proptype-conditional-require'; import mailtoLink from 'mailto-link'; import classNames from 'classnames'; @@ -11,21 +8,50 @@ import withDeprecatedProps, { DeprTypes } from '../withDeprecatedProps'; export const MAIL_TO_LINK_EXTERNAL_LINK_ALTERNATIVE_TEXT = 'Dismiss'; export const MAIL_TO_LINK_EXTERNAL_LINK_TITLE = 'Opens in a new tab'; -const MailtoLink = React.forwardRef((props, ref) => { - const { - to, - cc, - bcc, - subject, - body, - children, - target, - onClick, - externalLink, - className, - ...attrs - } = props; +interface MailtoLinkProps { + /** Content of the MailtoLink */ + children: React.ReactNode; + /** Custom class names for the MailtoLink */ + className?: string; + /** Specifies the email's recipients */ + to?: string | string[]; + /** Specifies the email's carbon copy recipients */ + cc?: string | string[]; + /** Specifies the email's blind carbon copy recipients */ + bcc?: string | string[]; + /** Specifies the email's subject */ + subject?: string; + /** Specifies the email's body */ + body?: string; + /** Specifies where the link should open. The default behavior is `_self`, + * which means that the URL will be loaded into the same browsing context as the current one */ + target?: '_self' | '_blank'; + /** Specifies the callback function when the link is clicked */ + onClick?: (event: React.MouseEvent) => void; + /** The object that contains the `alternativeText` and `title` fields which specify + * the text and title for links with a `_blank` target (which loads the URL in a new browsing context). */ + externalLink?: { + alternativeText?: string; + title?: string; + }; +} +const MailtoLink = React.forwardRef(({ + children, + className, + to = [], + cc = [], + bcc = [], + subject = '', + body = '', + target = '_self', + onClick, + externalLink = { + alternativeText: MAIL_TO_LINK_EXTERNAL_LINK_ALTERNATIVE_TEXT, + title: MAIL_TO_LINK_EXTERNAL_LINK_TITLE, + }, + ...attrs +}, ref) => { const externalLinkAlternativeText = externalLink.alternativeText; const externalLinkTitle = externalLink.title; const destination = mailtoLink({ @@ -52,49 +78,6 @@ const MailtoLink = React.forwardRef((props, ref) => { ); }); -MailtoLink.defaultProps = { - to: [], - cc: [], - bcc: [], - subject: '', - body: '', - target: '_self', - onClick: null, - externalLink: { - alternativeText: MAIL_TO_LINK_EXTERNAL_LINK_ALTERNATIVE_TEXT, - title: MAIL_TO_LINK_EXTERNAL_LINK_TITLE, - }, - className: undefined, -}; - -MailtoLink.propTypes = { - /** Content of the ``MailToLink`` */ - children: PropTypes.node.isRequired, - /** Custom class names for the ``MailToLink`` */ - className: PropTypes.string, - /** Specifies the email's recipients */ - to: PropTypes.oneOfType([PropTypes.arrayOf(emailPropType), emailPropType]), - /** Specifies the email's carbon copy recipients */ - cc: PropTypes.oneOfType([PropTypes.arrayOf(emailPropType), emailPropType]), - /** Specifies the email's blind carbon copy recipients */ - bcc: PropTypes.oneOfType([PropTypes.arrayOf(emailPropType), emailPropType]), - /** Specifies the email's subject */ - subject: PropTypes.string, - /** Specifies the email's body */ - body: PropTypes.string, - /** Specifies where the link should open. The default behavior is `_self`, - * which means that the URL will be loaded into the same browsing context as the current one */ - target: PropTypes.string, - /** Specifies the callback function when the link is clicked */ - onClick: PropTypes.func, - /** The object that contains the `alternativeText` and `title` fields which specify - * the text and title for links with a `_blank` target (which loads the URL in a new browsing context). */ - externalLink: PropTypes.shape({ - alternativeText: isRequiredIf(PropTypes.string, props => props.target === '_blank'), - title: isRequiredIf(PropTypes.string, props => props.target === '_blank'), - }), -}; - export default withDeprecatedProps(MailtoLink, 'MailtoLink', { content: { deprType: DeprTypes.MOVED, diff --git a/src/index.ts b/src/index.ts index 7e11e26a8c..c44b21de2d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ export { default as Annotation } from './Annotation'; export { default as Avatar } from './Avatar'; export { default as AvatarButton } from './AvatarButton'; export { default as Badge } from './Badge'; +export { default as MailtoLink, MAIL_TO_LINK_EXTERNAL_LINK_ALTERNATIVE_TEXT, MAIL_TO_LINK_EXTERNAL_LINK_TITLE } from './MailtoLink'; export { default as Breadcrumb } from './Breadcrumb'; export { default as Bubble } from './Bubble'; export { default as Button, ButtonGroup, ButtonToolbar } from './Button'; @@ -98,8 +99,7 @@ export { default as Fade } from './Fade'; export { default as IconButtonToggle } from './IconButtonToggle'; // @ts-ignore: has yet to be converted to TypeScript export { default as Image, Figure } from './Image'; -// @ts-ignore: has yet to be converted to TypeScript -export { default as MailtoLink, MAIL_TO_LINK_EXTERNAL_LINK_ALTERNATIVE_TEXT, MAIL_TO_LINK_EXTERNAL_LINK_TITLE } from './MailtoLink'; + // @ts-ignore: has yet to be converted to TypeScript export { default as Media } from './Media'; // @ts-ignore: has yet to be converted to TypeScript