Browse Source

ref(ui): Converted the Link component from to typescript (#17346)

Priscila Oliveira 5 years ago
parent
commit
97e33a441e

+ 0 - 12
docs-ui/components/textLink.stories.js

@@ -1,12 +0,0 @@
-import React from 'react';
-import {storiesOf} from '@storybook/react';
-import {withInfo} from '@storybook/addon-info';
-
-import TextLink from 'app/components/links/textLink';
-
-storiesOf('UI|Links/TextLink', module).add(
-  'default',
-  withInfo(
-    'A react-router <Link> but styled to be more like normal text (i.e. not blue)'
-  )(() => <TextLink to="https://www.sentry.io">Sentry</TextLink>)
-);

+ 25 - 18
src/sentry/static/sentry/app/components/alertLink.tsx

@@ -1,9 +1,9 @@
 import styled from '@emotion/styled';
 import React from 'react';
-import {LocationDescriptor} from 'history';
 import omit from 'lodash/omit';
 
 import Link from 'app/components/links/link';
+import ExternalLink from 'app/components/links/externalLink';
 import InlineSvg from 'app/components/inlineSvg';
 import {IconChevron} from 'app/icons';
 import space from 'app/styles/space';
@@ -11,30 +11,28 @@ import space from 'app/styles/space';
 type Size = 'small' | 'normal';
 type Priority = 'info' | 'warning' | 'success' | 'error' | 'muted';
 
-type PropsWithHref = {href: string};
-type PropsWithTo = {to: LocationDescriptor};
+type LinkProps = React.ComponentPropsWithoutRef<typeof Link>;
+
 type OtherProps = {
   icon?: string | React.ReactNode;
   onClick?: (e: React.MouseEvent) => void;
 };
+
 type DefaultProps = {
   size: Size;
   priority: Priority;
   withoutMarginBottom: boolean;
   openInNewTab: boolean;
+  href?: string;
 };
 
-type Props = (PropsWithHref | PropsWithTo) & OtherProps & DefaultProps;
+type Props = OtherProps & DefaultProps & Partial<Pick<LinkProps, 'to'>>;
 
-// TODO(Priscila): Improve it as soon as we merge this PR: https://github.com/getsentry/sentry/pull/17346
-type StyledLinkProps = PropsWithHref &
-  PropsWithTo &
-  Omit<DefaultProps, 'openInNewTab'> &
-  Pick<OtherProps, 'onClick'> & {
-    target: '_blank' | '_self';
-  };
+type StyledLinkProps = DefaultProps &
+  Partial<Pick<LinkProps, 'to'>> &
+  Omit<LinkProps, 'to' | 'size'>;
 
-export default class AlertLink extends React.Component<Props> {
+class AlertLink extends React.Component<Props> {
   static defaultProps: DefaultProps = {
     priority: 'warning',
     size: 'normal',
@@ -51,16 +49,18 @@ export default class AlertLink extends React.Component<Props> {
       onClick,
       withoutMarginBottom,
       openInNewTab,
+      to,
+      href,
     } = this.props;
     return (
       <StyledLink
-        to={(this.props as PropsWithTo).to}
-        href={(this.props as PropsWithHref).href}
+        to={to}
+        href={href}
         onClick={onClick}
         size={size}
         priority={priority}
         withoutMarginBottom={withoutMarginBottom}
-        target={openInNewTab ? '_blank' : '_self'}
+        openInNewTab={openInNewTab}
       >
         {icon && (
           <IconWrapper>
@@ -76,9 +76,16 @@ export default class AlertLink extends React.Component<Props> {
   }
 }
 
-const StyledLink = styled((props: StyledLinkProps) => (
-  <Link {...omit(props, ['withoutMarginBottom', 'priority', 'size'])} />
-))`
+export default AlertLink;
+
+const StyledLink = styled(({openInNewTab, to, href, ...props}: StyledLinkProps) => {
+  const linkProps = omit(props, ['withoutMarginBottom', 'priority', 'size']);
+  if (href) {
+    return <ExternalLink {...linkProps} href={href} openInNewTab={openInNewTab} />;
+  }
+
+  return <Link {...linkProps} to={to || ''} />;
+})`
   display: flex;
   background-color: ${p => p.theme.alert[p.priority].backgroundLight};
   color: ${p => p.theme.gray4};

+ 6 - 4
src/sentry/static/sentry/app/components/alertMessage.tsx

@@ -2,14 +2,14 @@ import React from 'react';
 import styled from '@emotion/styled';
 
 import space from 'app/styles/space';
-import Link from 'app/components/links/link';
+import ExternalLink from 'app/components/links/externalLink';
 import Alert from 'app/components/alert';
 import AlertActions from 'app/actions/alertActions';
 import Button from 'app/components/button';
 import {IconCheckmark, IconClose, IconWarning} from 'app/icons';
 import {t} from 'app/locale';
 
-type Alert = {
+type AlertType = {
   id: string;
   message: React.ReactNode;
   type: 'success' | 'error' | 'warning' | 'info';
@@ -17,7 +17,7 @@ type Alert = {
 };
 
 type Props = {
-  alert: Alert;
+  alert: AlertType;
   system: boolean;
 };
 
@@ -32,7 +32,9 @@ const AlertMessage = ({alert, system}: Props) => {
 
   return (
     <StyledAlert type={type} icon={icon} system={system}>
-      <StyledMessage>{url ? <Link href={url}>{message}</Link> : message}</StyledMessage>
+      <StyledMessage>
+        {url ? <ExternalLink href={url}>{message}</ExternalLink> : message}
+      </StyledMessage>
       <StyledCloseButton
         icon={<IconClose size="md" circle />}
         aria-label={t('Close')}

+ 4 - 2
src/sentry/static/sentry/app/components/assigneeSelector.tsx

@@ -279,6 +279,7 @@ const AssigneeSelectorComponent = createReactClass<Props, State>({
             }
             menuFooter={
               <InviteMemberLink
+                to=""
                 data-test-id="invite-member"
                 disabled={loading}
                 onClick={() => openInviteMembersModal({source: 'assignee_selector'})}
@@ -369,8 +370,9 @@ const IconContainer = styled('div')`
 
 const MenuItemWrapper = styled('div')<{
   py?: number;
+  disabled?: boolean;
 }>`
-  cursor: pointer;
+  cursor: ${p => (p.disabled ? 'not-allowed' : 'pointer')};
   display: flex;
   align-items: center;
   font-size: 13px;
@@ -383,7 +385,7 @@ const MenuItemWrapper = styled('div')<{
 `;
 
 const InviteMemberLink = styled(Link)`
-  color: ${p => p.theme.textColor};
+  color: ${p => (p.disabled ? p.theme.disabled : p.theme.textColor)};
 `;
 
 const Label = styled(TextOverflow)`

+ 1 - 0
src/sentry/static/sentry/app/components/commitRow.tsx

@@ -46,6 +46,7 @@ class CommitRow extends React.Component<Props> {
             accountSettings: <StyledLink to="/settings/account/emails/" />,
             inviteUser: (
               <StyledLink
+                to=""
                 onClick={() =>
                   openInviteMembersModal({
                     initialData: [

+ 3 - 1
src/sentry/static/sentry/app/components/contextPickerModal.tsx

@@ -246,7 +246,9 @@ class ContextPickerModal extends React.Component<Props> {
       return (
         <div>
           {tct('You have no projects. Click [link] to make one.', {
-            link: <Link href={`/organizations/${organization}/projects/new/`}>here</Link>,
+            link: (
+              <Link to={`/organizations/${organization}/projects/new/`}>{t('here')}</Link>
+            ),
           })}
         </div>
       );

+ 1 - 5
src/sentry/static/sentry/app/components/footer.tsx

@@ -18,11 +18,7 @@ const Footer = () => {
             {t('API')}
           </FooterLink>
           <FooterLink href="/docs/">{t('Docs')}</FooterLink>
-          <FooterLink
-            className="hidden-xs"
-            href="https://github.com/getsentry/sentry"
-            rel="noreferrer"
-          >
+          <FooterLink className="hidden-xs" href="https://github.com/getsentry/sentry">
             {t('Contribute')}
           </FooterLink>
           {config.isOnPremise && (

+ 4 - 4
src/sentry/static/sentry/app/components/idBadge/memberBadge.tsx

@@ -61,9 +61,9 @@ const MemberBadge = ({
       <StyledAvatar user={user} size={avatarSize} />
       <StyledNameAndEmail>
         <StyledName
-          useLink={useLink && orgId}
+          useLink={useLink && !!orgId}
           hideEmail={hideEmail}
-          to={member && orgId && `/settings/${orgId}/members/${member.id}/`}
+          to={(member && orgId && `/settings/${orgId}/members/${member.id}/`) || ''}
         >
           {title}
         </StyledName>
@@ -107,9 +107,9 @@ const StyledEmail = styled('div')`
 type NameProps = {
   useLink: boolean;
   hideEmail: boolean;
-} & Link['props'];
+} & Pick<Link['props'], 'to'>;
 
-const StyledName = styled<NameProps>(({useLink, to, ...props}) => {
+const StyledName = styled(({useLink, to, ...props}: NameProps) => {
   const forwardProps = omit(props, 'hideEmail');
   return useLink ? <Link to={to} {...forwardProps} /> : <span {...forwardProps} />;
 })`

+ 1 - 1
src/sentry/static/sentry/app/components/issues/compactIssue.jsx

@@ -86,7 +86,7 @@ class CompactIssueHeader extends React.Component {
         <IssueHeaderMetaWrapper>
           <StyledErrorLevel size="12px" level={data.level} title={data.level} />
           <h3 className="truncate">
-            <Link to={issueLink}>
+            <Link to={issueLink || ''}>
               <span className="icon icon-soundoff" />
               <span className="icon icon-star-solid" />
               {this.getTitle()}

+ 19 - 4
src/sentry/static/sentry/app/components/links/externalLink.tsx

@@ -1,8 +1,23 @@
 import React from 'react';
+import PropTypes from 'prop-types';
 
 type AnchorProps = React.HTMLProps<HTMLAnchorElement>;
-type Props = AnchorProps & Required<Pick<AnchorProps, 'href'>>;
 
-export default React.forwardRef<HTMLAnchorElement, Props>((props, ref) => (
-  <a ref={ref} target="_blank" rel="noreferrer noopener" {...props} />
-));
+type Props = {
+  className?: string;
+  openInNewTab?: boolean;
+} & Omit<AnchorProps, 'target'>;
+
+const ExternalLink = React.forwardRef<HTMLAnchorElement, Props>(function ExternalLink(
+  {openInNewTab = true, ...props},
+  ref
+) {
+  const anchorProps = openInNewTab ? {target: '_blank', rel: 'noreferrer noopener'} : {};
+  return <a ref={ref} {...anchorProps} {...props} />;
+});
+
+ExternalLink.propTypes = {
+  openInNewTab: PropTypes.bool,
+};
+
+export default ExternalLink;

Some files were not shown because too many files changed in this diff