<commit_before>import React, { useCallback, useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import getValidProps from '@helpscout/react-utils/dist/getValidProps'
import MessageCardButton from './MessageCard.Button'
import { classNames } from '../../utilities/classNames'
import { noop } from '../../utilities/other'
import { MessageCardUI, MessageCardWrapperUI } from './MessageCard.css'
import { MessageCardTitle } from './components/MessageCard.Title'
import { MessageCardSubtitle } from './components/MessageCard.Subtitle'
import { MessageCardImage } from './components/MessageCard.Image'
import { MessageCardAction } from './components/MessageCard.Action'
import { MessageCardBody } from './components/MessageCard.Body'
import { MessageCardContent } from './components/MessageCard.Content'

export const MessageCard = React.memo(
  React.forwardRef(
    (
      {
        onShow,
        in: inProp,
        image,
        action,
        animationDuration,
        animationEasing,
        animationSequence,
        children,
        title,
        subtitle,
        onBodyClick,
        body,
        withAnimation,
        className,
        align,
        isMobile,
        isWithBoxShadow,
        ...rest
      },
      ref
    ) => {
      const [visible, setVisible] = useState(false)
      const isShown = useRef(false)

      const hasImage = useCallback(() => image && image.url, [image])

      useEffect(() => {
        if (inProp && !hasImage() && !isShown.current) {
          makeMessageVisible()
        }
      }, [inProp, hasImage])

      useEffect(() => {
        if (visible && !isShown.current) {
          onShow()
          isShown.current = true
        }
      }, [visible, onShow])

      const makeMessageVisible = () => {
        setTimeout(() => {
          setVisible(true)
        }, 0)
      }

      const getClassName = () => {
        return classNames(
          MessageCard.className,
          align && `is-align-${align}`,
          className,
          isMobile && 'is-mobile',
          isWithBoxShadow && `is-with-box-shadow`
        )
      }

      return inProp ? (
        <MessageCardWrapperUI
          className="c-MessageCardWrapper"
          visible={visible}
          withAnimation={withAnimation}
        >
          <MessageCardUI
            {...getValidProps(rest)}
            className={getClassName()}
            ref={ref}
          >
            <MessageCardTitle title={title} />
            <MessageCardSubtitle subtitle={subtitle} />
            <MessageCardContent
              withMargin={!!(title || subtitle)}
              render={!!(body || image || children)}
            >
              <MessageCardBody body={body} onClick={onBodyClick} />
              <MessageCardImage image={image} onLoad={makeMessageVisible} />
              {children}
            </MessageCardContent>
            <MessageCardAction action={action} />
          </MessageCardUI>
        </MessageCardWrapperUI>
      ) : null
    }
  )
)

MessageCard.defaultProps = {
  align: 'right',
  animationSequence: '',
  'data-cy': 'MessageCard',
  innerRef: noop,
  in: true,
  isMobile: false,
  isWithBoxShadow: true,
  onBodyClick: noop,
  onShow: noop,
  withAnimation: false,
}

MessageCard.propTypes = {
  /** Render-prop that is placed in the CTA section of the Message. */
  action: PropTypes.func,
  /** Apply styles for left or right-aligned Message. */
  align: PropTypes.oneOf(['left', 'right']),
  /** The duration (in `ms`) for the animation sequence. */
  animationDuration: PropTypes.number,
  /** Determines the CSS easing transition function. */
  animationEasing: PropTypes.string,
  /** Names of animation styles to apply. */
  animationSequence: PropTypes.string,
  /** Main text of the Message. */
  body: PropTypes.string,
  /** The className of the component. */
  className: PropTypes.string,
  ref: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
  ]),
  /** Programatically triggering the animation. */
  in: PropTypes.bool,
  /** Adds mobile styles */
  isMobile: PropTypes.bool,
  /** Adds a box shadow. */
  isWithBoxShadow: PropTypes.bool,
  /** Callback invoked when the body of the Message is clicked. */
  onBodyClick: PropTypes.func,
  /** Subtitle of the Message. */
  subtitle: PropTypes.string,
  /** Title of the Message. */
  title: PropTypes.string,
  /** Definition of the Message image */
  image: PropTypes.shape({
    url: PropTypes.string.isRequired,
    width: PropTypes.string,
    height: PropTypes.string,
    altText: PropTypes.string,
  }),
  /** Data attr for Cypress tests. */
  'data-cy': PropTypes.string,
  /** Callback invoked when the MessageCard is show to the user. */
  onShow: PropTypes.func,
  /** Enable animations when showing the Message. */
  withAnimation: PropTypes.bool,
}
<commit_msg>Fixed prop types on image width and height to avoid console error (#931)<commit_after>import React, { useCallback, useEffect, useRef, useState } from 'react'
import PropTypes from 'prop-types'
import getValidProps from '@helpscout/react-utils/dist/getValidProps'
import MessageCardButton from './MessageCard.Button'
import { classNames } from '../../utilities/classNames'
import { noop } from '../../utilities/other'
import { MessageCardUI, MessageCardWrapperUI } from './MessageCard.css'
import { MessageCardTitle } from './components/MessageCard.Title'
import { MessageCardSubtitle } from './components/MessageCard.Subtitle'
import { MessageCardImage } from './components/MessageCard.Image'
import { MessageCardAction } from './components/MessageCard.Action'
import { MessageCardBody } from './components/MessageCard.Body'
import { MessageCardContent } from './components/MessageCard.Content'

export const MessageCard = React.memo(
  React.forwardRef(
    (
      {
        onShow,
        in: inProp,
        image,
        action,
        animationDuration,
        animationEasing,
        animationSequence,
        children,
        title,
        subtitle,
        onBodyClick,
        body,
        withAnimation,
        className,
        align,
        isMobile,
        isWithBoxShadow,
        ...rest
      },
      ref
    ) => {
      const [visible, setVisible] = useState(false)
      const isShown = useRef(false)

      const hasImage = useCallback(() => image && image.url, [image])

      useEffect(() => {
        if (inProp && !hasImage() && !isShown.current) {
          makeMessageVisible()
        }
      }, [inProp, hasImage])

      useEffect(() => {
        if (visible && !isShown.current) {
          onShow()
          isShown.current = true
        }
      }, [visible, onShow])

      const makeMessageVisible = () => {
        setTimeout(() => {
          setVisible(true)
        }, 0)
      }

      const getClassName = () => {
        return classNames(
          MessageCard.className,
          align && `is-align-${align}`,
          className,
          isMobile && 'is-mobile',
          isWithBoxShadow && `is-with-box-shadow`
        )
      }

      return inProp ? (
        <MessageCardWrapperUI
          className="c-MessageCardWrapper"
          visible={visible}
          withAnimation={withAnimation}
        >
          <MessageCardUI
            {...getValidProps(rest)}
            className={getClassName()}
            ref={ref}
          >
            <MessageCardTitle title={title} />
            <MessageCardSubtitle subtitle={subtitle} />
            <MessageCardContent
              withMargin={!!(title || subtitle)}
              render={!!(body || image || children)}
            >
              <MessageCardBody body={body} onClick={onBodyClick} />
              <MessageCardImage image={image} onLoad={makeMessageVisible} />
              {children}
            </MessageCardContent>
            <MessageCardAction action={action} />
          </MessageCardUI>
        </MessageCardWrapperUI>
      ) : null
    }
  )
)

MessageCard.defaultProps = {
  align: 'right',
  animationSequence: '',
  'data-cy': 'MessageCard',
  innerRef: noop,
  in: true,
  isMobile: false,
  isWithBoxShadow: true,
  onBodyClick: noop,
  onShow: noop,
  withAnimation: false,
}

MessageCard.propTypes = {
  /** Render-prop that is placed in the CTA section of the Message. */
  action: PropTypes.func,
  /** Apply styles for left or right-aligned Message. */
  align: PropTypes.oneOf(['left', 'right']),
  /** The duration (in `ms`) for the animation sequence. */
  animationDuration: PropTypes.number,
  /** Determines the CSS easing transition function. */
  animationEasing: PropTypes.string,
  /** Names of animation styles to apply. */
  animationSequence: PropTypes.string,
  /** Main text of the Message. */
  body: PropTypes.string,
  /** The className of the component. */
  className: PropTypes.string,
  ref: PropTypes.oneOfType([
    PropTypes.func,
    PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
  ]),
  /** Programatically triggering the animation. */
  in: PropTypes.bool,
  /** Adds mobile styles */
  isMobile: PropTypes.bool,
  /** Adds a box shadow. */
  isWithBoxShadow: PropTypes.bool,
  /** Callback invoked when the body of the Message is clicked. */
  onBodyClick: PropTypes.func,
  /** Subtitle of the Message. */
  subtitle: PropTypes.string,
  /** Title of the Message. */
  title: PropTypes.string,
  /** Definition of the Message image */
  image: PropTypes.shape({
    url: PropTypes.string.isRequired,
    width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    altText: PropTypes.string,
  }),
  /** Data attr for Cypress tests. */
  'data-cy': PropTypes.string,
  /** Callback invoked when the MessageCard is show to the user. */
  onShow: PropTypes.func,
  /** Enable animations when showing the Message. */
  withAnimation: PropTypes.bool,
}

{'fraction_non_alphanumeric': 0.12476084986406202, 'fraction_numerical': 0.0005034739703957305, 'mean_word_length': 2.481247809323519, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7546948', 'n_tokens_mistral': 2726, 'n_tokens_neox': 2774, 'n_words': 684}