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

Code before:
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`.
    if (size === 'large') {
      cls.push('btn-lg');

Review comment (refactor):
We already have a `Link` component: https://github.com/freeCodeCamp/freeCodeCamp/blob/main/client/src/components/helpers/link.tsx, which also renders a Gatsby Link.

Previously we would need to pass `btn-*` classes to that `Link` in order to display button links. That works, but I think the props system works better because:
- Class names can easily be missed or mistyped
- Props help surface additional styling needs better (the props provided should be sufficient. If the consumer needs additional classes, we can consider if the custom styles should be integrated into the shared component)
- The props interface of `Button` and `ButtonLink` are almost identical, which makes it easier to migrate

Code after:
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>`.
  if (disabled || target === '_blank' || download) {
    return (