exercism/website

View on GitHub
app/javascript/components/common/MarkdownEditorForm.tsx

Summary

Maintainability
A
1 hr
Test Coverage
import React, { useCallback, useState, useEffect, useContext } from 'react'
import { MutationStatus } from '@tanstack/react-query'
import {
  default as MarkdownEditor,
  MarkdownEditorHandle,
} from './MarkdownEditor'
import { FormFooter } from './markdown-editor-form/FormFooter'
import { ErrorBoundary, useErrorHandler } from '../ErrorBoundary'
import { MobileIdleFormFooter } from './markdown-editor-form/MobileIdleFormFooter'
import { ScreenSizeContext } from '../mentoring/session/ScreenSizeContext'

const ErrorMessage = ({
  error,
  defaultError,
}: {
  error: unknown
  defaultError: Error
}) => {
  useErrorHandler(error, { defaultError: defaultError })

  return null
}

const ErrorFallback = ({ error }: { error: Error }) => {
  return <div className="editor-error">{error.message}</div>
}

export type MarkdownEditorFormAction = 'new' | 'edit'

export const MarkdownEditorForm = ({
  expanded,
  onSubmit,
  onClick = () => null,
  onCancel,
  onDelete,
  onChange = () => null,
  contextId,
  value,
  status,
  error,
  defaultError,
  action,
}: {
  expanded: boolean
  onSubmit: () => void
  onClick?: () => void
  onCancel?: () => void
  onDelete?: () => void
  onChange: (value: string) => void
  contextId?: string
  value: string
  status: MutationStatus
  error: unknown
  defaultError: Error
  action: MarkdownEditorFormAction
}): JSX.Element => {
  const [editor, setEditor] = useState<MarkdownEditorHandle | null>(null)
  const classNames = [
    'c-markdown-editor',
    expanded ? '--expanded' : '--compressed',
  ].filter((className) => className.length > 0)
  const { isBelowLgWidth = false } = useContext(ScreenSizeContext) || {}

  const handleSubmit = useCallback(
    (e) => {
      e.preventDefault()

      onSubmit()
    },
    [onSubmit]
  )

  const handleClick = useCallback(() => {
    onClick()
  }, [onClick])

  const handleEditorDidMount = useCallback((editor) => {
    setEditor(editor)
  }, [])

  const handleChange = useCallback(
    (value) => {
      onChange(value)
    },
    [onChange]
  )

  const handleCancel = useCallback(
    (e) => {
      e.stopPropagation()

      if (!onCancel) {
        return
      }

      onCancel()
    },
    [onCancel]
  )

  const handleDelete = useCallback(
    (e: React.FormEvent) => {
      if (!onDelete) {
        return
      }

      e.stopPropagation()

      confirm('Are you sure you want to delete this?') ? onDelete() : null
    },
    [onDelete]
  )

  useEffect(() => {
    if (!expanded) {
      return
    }

    editor?.focus()
  }, [editor, expanded])

  return (
    <>
      <ErrorBoundary FallbackComponent={ErrorFallback} resetKeys={[status]}>
        <ErrorMessage error={error} defaultError={defaultError} />
      </ErrorBoundary>
      <form
        data-turbo="false"
        onSubmit={handleSubmit}
        onClick={handleClick}
        className={classNames.join(' ')}
        data-testid="markdown-editor"
      >
        <MarkdownEditor
          contextId={contextId}
          value={value}
          onChange={handleChange}
          editorDidMount={handleEditorDidMount}
        />
        {expanded ? (
          <FormFooter
            onCancel={onCancel ? handleCancel : undefined}
            onDelete={onDelete ? handleDelete : undefined}
            value={value}
            status={status}
            action={action}
          />
        ) : isBelowLgWidth ? (
          <MobileIdleFormFooter />
        ) : null}
      </form>
    </>
  )
}