diff --git a/.changeset/wise-months-taste.md b/.changeset/wise-months-taste.md
new file mode 100644
index 00000000000..d60d758db09
--- /dev/null
+++ b/.changeset/wise-months-taste.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": patch
+---
+
+Add support for custom emojis and a declarative "loading" state in `MarkdownEditor` suggestions
diff --git a/src/drafts/MarkdownEditor/MarkdownEditor.stories.tsx b/src/drafts/MarkdownEditor/MarkdownEditor.stories.tsx
index f140894d2c2..de951a2eac9 100644
--- a/src/drafts/MarkdownEditor/MarkdownEditor.stories.tsx
+++ b/src/drafts/MarkdownEditor/MarkdownEditor.stories.tsx
@@ -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[] = [
diff --git a/src/drafts/MarkdownEditor/MarkdownEditor.test.tsx b/src/drafts/MarkdownEditor/MarkdownEditor.test.tsx
index b946c373b6c..ce0b57c93c4 100644
--- a/src/drafts/MarkdownEditor/MarkdownEditor.test.tsx
+++ b/src/drafts/MarkdownEditor/MarkdownEditor.test.tsx
@@ -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
@@ -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[] = [
@@ -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()
+
+ 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', () => {
diff --git a/src/drafts/MarkdownEditor/suggestions/_useEmojiSuggestions.tsx b/src/drafts/MarkdownEditor/suggestions/_useEmojiSuggestions.tsx
index c9e78942680..ec3108f1387 100644
--- a/src/drafts/MarkdownEditor/suggestions/_useEmojiSuggestions.tsx
+++ b/src/drafts/MarkdownEditor/suggestions/_useEmojiSuggestions.tsx
@@ -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 => (
- {emoji.character}
+
+ {'character' in emoji ? (
+ emoji.character
+ ) : (
+
+ )}
+
{emoji.name}
),
diff --git a/src/drafts/MarkdownEditor/suggestions/index.ts b/src/drafts/MarkdownEditor/suggestions/index.ts
index 664180f3438..a00939c8ffd 100644
--- a/src/drafts/MarkdownEditor/suggestions/index.ts
+++ b/src/drafts/MarkdownEditor/suggestions/index.ts
@@ -2,11 +2,11 @@ import {Suggestion, Trigger} from '../../InlineAutocomplete'
const MAX_SUGGESTIONS = 5
-export type SuggestionOptions = T[] | (() => Promise)
+export type SuggestionOptions = T[] | (() => Promise) | 'loading'
export type UseSuggestionsHook = (options: SuggestionOptions) => {
trigger: Trigger
- calculateSuggestions: (query: string) => Promise
+ calculateSuggestions: (query: string) => Promise
}
export const suggestionsCalculator =
@@ -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