Skip to content

Commit

Permalink
Support custom emojis & declarative loading state in `MarkdownEdito…
Browse files Browse the repository at this point in the history
…r` suggestions (#3004)

* Add support for custom emoji suggestions

* Allow declaratively setting suggestions as "loading"

* Create .changeset/wise-months-taste.md

* Add test for custom emoji

* Remove `satisfies` (seems to be unsupported by Jest's transpiler)
  • Loading branch information
iansan5653 authored Mar 9, 2023
1 parent 54923b7 commit 45107bc
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-months-taste.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

Add support for custom emojis and a declarative "loading" state in `MarkdownEditor` suggestions
1 change: 1 addition & 0 deletions src/drafts/MarkdownEditor/MarkdownEditor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ const emojis: Emoji[] = [
{name: 'raised_hand', character: '✋'},
{name: 'thumbsup', character: '👍'},
{name: 'thumbsdown', character: '👎'},
{name: 'octocat', url: 'https://github.githubassets.com/images/icons/emoji/octocat.png'},
]

const references: Reference[] = [
Expand Down
16 changes: 14 additions & 2 deletions src/drafts/MarkdownEditor/MarkdownEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'
import {UserEvent} from '@testing-library/user-event/dist/types/setup/setup'
import React, {forwardRef, useRef, useState} from 'react'
import {act} from 'react-dom/test-utils'
import MarkdownEditor, {Emoji, MarkdownEditorHandle, MarkdownEditorProps, Mentionable, Reference, SavedReply} from '.'
import MarkdownEditor, {MarkdownEditorHandle, MarkdownEditorProps, Mentionable, Reference, SavedReply} from '.'
import ThemeProvider from '../../ThemeProvider'

declare const REACT_VERSION_LATEST: boolean
Expand Down Expand Up @@ -866,12 +866,13 @@ describe('MarkdownEditor', () => {
})

describe('suggestions', () => {
const emojis: Emoji[] = [
const emojis = [
{name: '+1', character: '👍'},
{name: '-1', character: '👎'},
{name: 'heart', character: '❤️'},
{name: 'wave', character: '👋'},
{name: 'raised_hands', character: '🙌'},
{name: 'octocat', url: 'https://github.githubassets.com/images/icons/emoji/octocat.png'},
]

const mentionables: Mentionable[] = [
Expand Down Expand Up @@ -1076,6 +1077,17 @@ describe('MarkdownEditor', () => {
expect(getAllSuggestions()[0]).toHaveTextContent('+1')
expect(getAllSuggestions()[1]).toHaveTextContent('-1')
})

it('inserts shortcode for custom emojis', async () => {
const {queryForSuggestionsList, getAllSuggestions, getInput, user} = await render(<EditorWithSuggestions />)

const input = getInput()
await user.type(input, `Mona Lisa :octo`)
await user.click(getAllSuggestions()[0])

expect(input.value).toBe(`Mona Lisa :octocat: `)
expect(queryForSuggestionsList()).not.toBeInTheDocument()
})
})

describe('saved replies', () => {
Expand Down
24 changes: 20 additions & 4 deletions src/drafts/MarkdownEditor/suggestions/_useEmojiSuggestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,40 @@ import {suggestionsCalculator, UseSuggestionsHook} from '.'
import {ActionList} from '../../../ActionList'
import {Suggestion, Trigger} from '../../InlineAutocomplete'

export type Emoji = {
type BaseEmoji = {
/** Name (shortcode) of the emoji. Do not include the wrapping `:` symbols. */
name: string
}

type UnicodeEmoji = BaseEmoji & {
/** Unicode representation of the emoji. */
character: string
}

type CustomEmoji = BaseEmoji & {
/** URL to an image of the emoji. */
url: string
}

export type Emoji = UnicodeEmoji | CustomEmoji

const trigger: Trigger = {
triggerChar: ':',
keepTriggerCharOnCommit: false,
}

const emojiToSugggestion = (emoji: Emoji): Suggestion => ({
value: emoji.character,
key: emoji.name, // emoji characters may not be unique - ie haircut and haircut_man both have the same emoji codepoint. But names are guarunteed to be unique.
value: 'character' in emoji ? emoji.character : `:${emoji.name}:`,
key: emoji.name, // emoji characters may not be unique - ie haircut and haircut_man both have the same emoji codepoint. But names are guaranteed to be unique.
render: props => (
<ActionList.Item {...props}>
<ActionList.LeadingVisual>{emoji.character}</ActionList.LeadingVisual>
<ActionList.LeadingVisual>
{'character' in emoji ? (
emoji.character
) : (
<img src={emoji.url} alt={`${emoji.name} emoji`} height="16" width="16" />
)}
</ActionList.LeadingVisual>
{emoji.name}
</ActionList.Item>
),
Expand Down
6 changes: 4 additions & 2 deletions src/drafts/MarkdownEditor/suggestions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import {Suggestion, Trigger} from '../../InlineAutocomplete'

const MAX_SUGGESTIONS = 5

export type SuggestionOptions<T> = T[] | (() => Promise<T[]>)
export type SuggestionOptions<T> = T[] | (() => Promise<T[]>) | 'loading'

export type UseSuggestionsHook<T> = (options: SuggestionOptions<T>) => {
trigger: Trigger
calculateSuggestions: (query: string) => Promise<Suggestion[]>
calculateSuggestions: (query: string) => Promise<Suggestion[] | 'loading'>
}

export const suggestionsCalculator =
Expand All @@ -16,6 +16,8 @@ export const suggestionsCalculator =
toSuggestion: (option: T) => Suggestion,
) =>
async (query: string) => {
if (options === 'loading') return 'loading'

const optionsArray = Array.isArray(options) ? options : await options()

// If the query is empty, scores will be -INFINITY
Expand Down

0 comments on commit 45107bc

Please sign in to comment.