Repository: freeCodeCamp/freeCodeCamp
File: client/src/components/helpers/button-link.tsx (TypeScript)

Code before:
import React, { useMemo } from 'react';
import { Link as GatsbyLink } from 'gatsby';
import { Button } from '@freecodecamp/ui';

export type ButtonSize = 'small' | 'medium' | 'large';

interface Props extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
  children: React.ReactNode;
  href: string;
  external?: boolean;
  className?: string;
  size?: ButtonSize;
  onClick?: React.MouseEventHandler<HTMLAnchorElement>;
  block?: boolean;
  download?: string;
  target?: React.HTMLAttributeAnchorTarget;
  disabled?: boolean;
}

/**
 * ButtonLink is a link that looks like a button.
 * The component renders:
 * - Internal link with Gatsby's Link component
 * - External link with freecodecamp/ui's Button component,
 *   which renders a plain `<a>` if it receives an `href`.
 * NOTE: We probably want to move this component to @freecodecamp/ui.
 */
export const ButtonLink = ({
  className,
  external,
  children,
  href,
  target,
  download,
  onClick,
  size = 'medium',
  block,
  disabled
}: Props) => {
  // We only need to compute the styles of `size` and `block` for Gatsby Link.
  // freecodecamp/ui's Button already has the logic implemented.
  const gatsbyLinkCls = useMemo(() => {
    const cls = ['btn'];

    if (block) {
      cls.push('btn-block');
    }

    // The 'btn' class contains the base button styles as well as the `medium` variant styles.
    // So we only need to handle `large` and `small`.

Review comment (refactor):
> NOTE: We probably want to move this component to @freecodecamp/ui.

I'm not sure where this component should live. Only /learn uses Gatsby so I think it makes sense to keep this component here. But if we want to change the button styles, we'd need to update the styles in multiple places, and the CSS in here isn't pleasant to work with 😢 

The `useStyles` hook I mentioned in the PR description could help solve this, but I'll need some time to think about the whole link situation a bit more as we also have https://github.com/freeCodeCamp/ui/issues/211.

Code after:
import React from 'react';
import { Link as GatsbyLink } from 'gatsby';
import { Button } from '@freecodecamp/ui';

export type ButtonSize = 'small' | 'medium' | 'large';

interface Props extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
  children: React.ReactNode;
  href: string;
  className?: string;
  size?: ButtonSize;
  onClick?: React.MouseEventHandler<HTMLAnchorElement>;
  block?: boolean;
  download?: string;
  target?: React.HTMLAttributeAnchorTarget;
  disabled?: boolean;
}

/**
 * ButtonLink is a link that looks like a button.
 * The component renders:
 * - Internal link with Gatsby's Link component
 * - External link with freecodecamp/ui's Button component,
 *   which renders a plain `<a>` if it receives an `href`.
 * NOTE: We probably want to move this component to @freecodecamp/ui.
 */
export const ButtonLink = ({
  className,
  children,
  href,
  target,
  download,
  onClick,
  size = 'medium',
  block,
  disabled
}: Props) => {
  // We only need to compute the styles of `size` and `block` for Gatsby Link.
  // freecodecamp/ui's Button already has the logic implemented.
  const cls = ['btn'];
  if (block) cls.push('btn-block');
  // The 'btn' class contains the base button styles as well as the `medium` variant styles.
  // So we only need to handle `large` and `small`.
  if (size === 'large') cls.push('btn-lg');
  if (size === 'small') cls.push('btn-sm');
  if (className) cls.push(className);
  const gatsbyLinkCls = cls.join(' ');

  // Links cannot be disabled. So if `disabled` is true,
  // we pass the prop to `Button` in order to render a `<button>` instead of an `<a>`.