UNPKG

@primer/react

Version:

An implementation of GitHub's Primer Design System using React

141 lines (136 loc) 5.46 kB
import { subscribe } from '@github/paste-markdown'; import React, { forwardRef, useState, useMemo, useRef, useEffect } from 'react'; import { useDynamicTextareaHeight } from '../hooks/useDynamicTextareaHeight.js'; import InlineAutocomplete from '../InlineAutocomplete/InlineAutocomplete.js'; import Textarea from '../../Textarea/Textarea.js'; import { useEmojiSuggestions } from './suggestions/_useEmojiSuggestions.js'; import { useMentionSuggestions } from './suggestions/_useMentionSuggestions.js'; import { useReferenceSuggestions } from './suggestions/_useReferenceSuggestions.js'; import '@primer/behaviors/utils'; import '@primer/behaviors'; import { useRefObjectAsForwardedRef } from '../../hooks/useRefObjectAsForwardedRef.js'; function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } const emptyArray = []; // constant reference to avoid re-running effects const MarkdownInput = /*#__PURE__*/forwardRef(({ value, onChange, disabled, placeholder, id, maxLength, onKeyDown, fullHeight, isDraggedOver, emojiSuggestions, mentionSuggestions, referenceSuggestions, minHeightLines, maxHeightLines, visible, monospace, pasteUrlsAsPlainText, ...props }, forwardedRef) => { const [suggestions, setSuggestions] = useState(null); const [event, setEvent] = useState(null); const { trigger: emojiTrigger, calculateSuggestions: calculateEmojiSuggestions } = useEmojiSuggestions(emojiSuggestions !== null && emojiSuggestions !== void 0 ? emojiSuggestions : emptyArray); const { trigger: mentionsTrigger, calculateSuggestions: calculateMentionSuggestions } = useMentionSuggestions(mentionSuggestions !== null && mentionSuggestions !== void 0 ? mentionSuggestions : emptyArray); const { trigger: referencesTrigger, calculateSuggestions: calculateReferenceSuggestions } = useReferenceSuggestions(referenceSuggestions !== null && referenceSuggestions !== void 0 ? referenceSuggestions : emptyArray); const triggers = useMemo(() => [mentionsTrigger, referencesTrigger, emojiTrigger], [mentionsTrigger, referencesTrigger, emojiTrigger]); const lastEventRef = useRef(null); const onHideSuggestions = () => { setEvent(null); setSuggestions(null); // the effect would do this anyway, but this allows React to batch the update }; // running the calculation in an effect (rather than in the onShowSuggestions handler) allows us // to automatically recalculate if the suggestions change while the menu is open useEffect(() => { if (!event) { setSuggestions(null); return; } (async function () { lastEventRef.current = event; setSuggestions('loading'); if (event.trigger.triggerChar === emojiTrigger.triggerChar) { setSuggestions(await calculateEmojiSuggestions(event.query)); } else if (event.trigger.triggerChar === mentionsTrigger.triggerChar) { setSuggestions(await calculateMentionSuggestions(event.query)); } else if (event.trigger.triggerChar === referencesTrigger.triggerChar) { setSuggestions(await calculateReferenceSuggestions(event.query)); } })(); }, [event, calculateEmojiSuggestions, calculateMentionSuggestions, calculateReferenceSuggestions, // The triggers never actually change because they are statically defined emojiTrigger, mentionsTrigger, referencesTrigger]); const ref = useRef(null); useRefObjectAsForwardedRef(forwardedRef, ref); useEffect(() => { const subscription = ref.current && subscribe(ref.current, { defaultPlainTextPaste: { urlLinks: pasteUrlsAsPlainText } }); return subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe; }, [pasteUrlsAsPlainText]); const heightStyles = useDynamicTextareaHeight({ // if fullHeight is enabled, there is no need to compute a dynamic height (for perfs reasons) disabled: fullHeight, maxHeightLines, minHeightLines, elementRef: ref, value }); return /*#__PURE__*/React.createElement(InlineAutocomplete, { triggers: triggers, suggestions: suggestions, onShowSuggestions: setEvent, onHideSuggestions: onHideSuggestions, sx: { flex: 'auto' }, tabInsertsSuggestions: true }, /*#__PURE__*/React.createElement(Textarea, _extends({ id: id, ref: ref, placeholder: placeholder, maxLength: maxLength, value: value, onKeyDown: onKeyDown, disabled: disabled, "aria-label": "Markdown value", onChange: onChange, sx: { borderStyle: 'none', boxShadow: 'none', height: fullHeight ? '100%' : undefined, outline: theme => { return isDraggedOver ? `dashed 2px ${theme.colors.border.muted}` : undefined; }, outlineOffset: '-4px', display: visible ? undefined : 'none', '&: focus-within': { boxShadow: 'none', outline: 'none' }, '& textarea': { lineHeight: 'var(--text-body-lineHeight-medium, 1.4285)', resize: fullHeight ? 'none' : 'vertical', p: 3, fontFamily: monospace ? 'mono' : 'normal', ...heightStyles } } }, props))); }); MarkdownInput.displayName = 'MarkdownInput'; export { MarkdownInput };