useMentions

useMentions(options)는 Field.Textarea에 멘션과 슬래시 명령을 더합니다. @ 같은 트리거 문자를 입력하면 그 단어에 제안 목록이 열리고, 하나를 고르면 그 단어가 @Ada Lovelace 같은 토큰의 텍스트로 바뀝니다. 텍스트는 네이티브 textarea 안의 일반 텍스트로 남고, 훅은 토큰을 그 텍스트의 범위로 기억해 텍스트 뒤에 그립니다.

이 페이지의 내용
const mentions = useMentions({
  triggers: [
    { char: '@', options: people, label: 'People' },
    { char: '/', options: commands, label: 'Commands' },
  ],
  value: draft,
  onValueChange: setDraft,
});

<Composer.Root onSubmit={send}>
  <Field.Root aria-label="Message">
    <Field.Textarea {...mentions.getTextareaProps()} autoGrow maxRows={6} />
    <Field.Row>
      <Composer.Submit disabled={draft.text.trim() === ''} />
    </Field.Row>
  </Field.Root>
</Composer.Root>
<Popover.Root {...mentions.getPopoverProps()}>
  <OptionList.Root {...mentions.getListboxProps()}>
    {mentions.items.map((item) => (
      <OptionList.Option key={item.id} {...mentions.getOptionProps(item)} />
    ))}
  </OptionList.Root>
</Popover.Root>

트리거

트리거마다 문자 하나를 지정합니다. 기본값인 position: 'word'이면 텍스트의 처음이나 공백 뒤에서 제안 단어를 시작하므로, 이메일 주소에서는 목록이 열리지 않습니다. position: 'start'이면 슬래시 명령처럼 앞에 공백만 올 수 있습니다. 단어는 트리거부터 다음 공백까지이므로 @src/index처럼 멘션 안에 다른 트리거 문자가 들어갈 수 있고, 캐럿이 그 안에 있는 동안 목록이 열려 있습니다.

트리거는 단어에 대해 filter로 거르는 options나, 단어와 AbortSignal로 불리고 오래된 결과는 버려지는 loadOptions를 받습니다. text는 고른 항목이 넣을 텍스트를 정하며, 기본값은 트리거 문자와 옵션의 레이블입니다. label은 스크린 리더를 위한 목록의 이름이며, 기본값은 StringsProvider의 suggestions 문자열입니다.

고르기와 편집

목록이 열려 있는 동안 ArrowDown과 ArrowUp은 목록의 옵션 사이를 움직이고, Enter나 Tab은 활성 옵션을 고릅니다. 단어의 옵션을 아직 불러오는 중에 누른 Enter는 아무것도 하지 않으므로, Composer가 덜 입력한 멘션을 보내지 않습니다. 고를 수 있는 옵션이 없을 때 누른 Enter는 목록을 닫고 textarea에 맡깁니다. Escape나 textarea와 목록 바깥을 누르면 그 단어에 대해 목록이 닫히고, 다음 트리거에서 다시 열립니다.

고르기는 document.execCommand('insertText')로 단어를 바꾸므로 textarea 자체의 실행 취소 기록에 한 단계로 남고, 뒤에 공백이 없으면 공백을 하나 더합니다. 실행 취소와 다시 실행은 되돌린 텍스트가 마지막으로 가졌던 토큰을 되살립니다. 토큰의 글자 하나에 Backspace나 Delete를 누르는 것을 포함해 토큰의 텍스트를 편집하면 그 토큰은 일반 텍스트로 돌아갑니다.

value는 임시 저장본으로, text와 tokens를 담습니다. tokens는 토큰마다 start, end, char, value, text를 담습니다. onValueChange는 편집마다 둘을 함께 돌려주므로 둘은 함께 저장됩니다. 백엔드에도 함께 보내고, 임시 저장본을 불러오거나 비울 때는 둘 다 설정하세요. 범위가 더 이상 자기 텍스트를 담지 않는 토큰은 버려집니다.

토큰 그리기

getTextareaProps()는 Field.Textarea에 highlights를 넘깁니다. Field.Textarea는 각 범위를 텍스트 뒤의 레이어에 그리고, 입력 중인 단어에 팝오버가 놓일 앵커를 줍니다. 레이어는 textarea의 글꼴, 패딩, 스크롤을 따라 하므로 textarea는 자기 텍스트, 캐럿, 선택, 맞춤법 검사를 그대로 가집니다. 토큰은 field.token.bg와 field.token.radius를 읽고, 트리거 문자를 data-kind로 가지므로 멘션과 명령을 따로 스타일링할 수 있습니다.

접근성을 지키는 조합

Todae가 맡는 것: textarea는 textbox 역할을 유지하고, aria-autocomplete="list", aria-haspopup="listbox"를 가지며, 목록이 열려 있는 동안 목록과 활성 옵션을 가리키는 aria-controls와 aria-activedescendant를 가집니다. 목록의 개수는 목록이 열릴 때와 바뀔 때 suggestionCount 문자열로 useAnnouncer를 통해 알립니다. IME 조합 중에는 키를 건드리지 않습니다. 그려진 토큰은 textarea가 같은 텍스트를 가지므로 스크린 리더에서 숨겨집니다.

직접 제공할 것: 필드의 이름과 트리거마다 목록의 label. 옵션과, 토큰이 제품에서 뜻하는 것.

주의할 점: Enter나 화살표 키를 취소하는 직접 작성한 onKeyDown은 훅의 처리를 멈춥니다. 의사 요소에 설정한 글꼴처럼 textarea 자체에 있지 않은 textarea 스타일.

언제 쓰나요

쓸 때: 메시지나 댓글 상자에서 사람, 채널, 파일의 멘션과 슬래시 명령.

쓰지 않을 때: 필드에 값 하나 고르기 → Combobox. 칩으로 보이는 개별 값 → Field.ChipInput. 서식 있는 리치 텍스트 → 제공하지 않습니다.

Props

useMentions props
이름타입설명
triggers 필수readonly MentionTrigger[]

트리거 문자마다 하나, 예를 들어 { char: "@", options }나 { char: "/", position: "start", loadOptions }. 각각 options나 loadOptions, filter, 고른 항목이 넣을 텍스트인 text, 목록의 이름인 label을 받습니다.

value{ text: string; tokens: readonly MentionToken[] }

제어 임시 저장본: 텍스트와 그 토큰, 즉 텍스트의 { start, end, char, value, text } 범위. 자기 텍스트를 담지 않은 토큰은 빠집니다. 임시 저장본을 불러오거나 비울 때는 둘 다 설정하세요.

defaultValue{ text: string; tokens: readonly MentionToken[] }

비제어일 때의 시작 임시 저장본.

기본값: { text: "", tokens: [] }

onValueChange(value: { text: string; tokens: MentionToken[] }) => void

고르기를 포함해 편집할 때마다 다음 텍스트와 그 토큰을 함께 넘겨 불립니다. 토큰은 편집을 따라 옮겨지고, 고르기는 토큰을 하나 더합니다.

maxLengthnumber

textarea의 maxLength. 텍스트가 들어가지 않는 고르기는 비활성화됩니다.

disabledboolean

목록을 닫아 둡니다.

idstring

listbox id의 기반.