Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MarkdownEditor and MarkdownViewer components #2182

Merged
merged 139 commits into from
Aug 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
ca40174
Add `useCombobox` hook, extending `@github/combobox-nav`
iansan5653 Jun 14, 2022
9ef18e3
Add `useSyntheticChange` hook
iansan5653 Jun 14, 2022
d14418e
Add `InlineAutocomplete` component
iansan5653 Jun 14, 2022
34ab6f3
Refactor and improve comments
iansan5653 Jun 16, 2022
f63e606
Remove extra type
iansan5653 Jun 16, 2022
2ef8d23
Add story and make it work with `FormControl`
iansan5653 Jun 16, 2022
02d8d1a
Add to main exports
iansan5653 Jun 16, 2022
ed4b12e
Add MDX file
iansan5653 Jun 16, 2022
c9b8ead
Merge branch 'main' of https://github.com/primer/react into add-inlin…
iansan5653 Jun 29, 2022
b110c98
Remove unecessary ID on textarea in story
iansan5653 Jun 29, 2022
c029876
Remove version-lock from new dependencies
iansan5653 Jun 29, 2022
d8dad97
Make type of render function more specific
iansan5653 Jun 29, 2022
375d6c9
Add unit tests
iansan5653 Jun 30, 2022
c53bf3d
Simplify `useCombobox` and use `navigate` to focus first item
iansan5653 Jul 1, 2022
7b55370
Fix tests by wrapping `userEvent.type` in `act`
iansan5653 Jul 12, 2022
1b39b54
Fix preventing blur when tabbing from loading state
iansan5653 Jul 12, 2022
0a30f05
Delete unused imports
iansan5653 Jul 12, 2022
9a97bdf
Change interfaces out for object types
iansan5653 Jul 12, 2022
af4ea26
Add accessible live status message to describe suggestions
iansan5653 Jul 12, 2022
431f1eb
Dynamically assign the combobox role to avoid treating the textarea a…
iansan5653 Jul 12, 2022
20b619e
Shorten & revise status message
iansan5653 Jul 15, 2022
010674c
Merge branch 'main' of https://github.com/primer/react into add-inlin…
iansan5653 Jul 19, 2022
f91691f
Add `MarkdownViewer` component
iansan5653 Jul 19, 2022
b7ba947
Move to drafts
iansan5653 Jul 19, 2022
6a00205
Merge branch 'add-inline-autocomplete-component' into add-markdown-ed…
iansan5653 Jul 19, 2022
39158ff
Add markdown viewer export to drafts
iansan5653 Jul 19, 2022
b3f0808
Move docs to drafts
iansan5653 Jul 19, 2022
f9d915b
Merge branch 'add-inline-autocomplete-component' into add-markdown-ed…
iansan5653 Jul 19, 2022
de624b4
Fix import in docs
iansan5653 Jul 19, 2022
b555fc7
Add markdown viewer doc page
iansan5653 Jul 19, 2022
ae4e5e5
Add `useUnifiedFileSelect` hook
iansan5653 Jul 19, 2022
97a21e9
Add `useIgnoreKeyboardActionsWhileComposing` hook
iansan5653 Jul 19, 2022
d1d2529
Improve the `useCombinedRefs` hook
iansan5653 Jul 19, 2022
b3adbeb
Make file types optional in `useUnifiedFileSelect`
iansan5653 Jul 19, 2022
9a26e90
Export `SyntheticChangeEmitter`
iansan5653 Jul 19, 2022
5a11ba2
Move character coordinates calculator to utils (from `InlineAutocompl…
iansan5653 Jul 20, 2022
4404a00
Add `useDynamicTextareaHeight` hook
iansan5653 Jul 20, 2022
0932dcc
Update `resizeObserver` to support other elements
iansan5653 Jul 20, 2022
45e0237
Add `MarkdownEditor` component
iansan5653 Jul 20, 2022
d0686a2
Fix dynamic height calculation when no line-height is set
iansan5653 Jul 20, 2022
3648ed4
Add a story for `MarkdownEditor`
iansan5653 Jul 20, 2022
bffe348
Add `MarkdownEditor/index.ts` file
iansan5653 Jul 20, 2022
f72e149
Move markdown builders into utils file
iansan5653 Jul 20, 2022
96a4f2b
Add inline suggestions to story
iansan5653 Jul 20, 2022
f0ccf34
Update combobox-nav dependency
iansan5653 Jul 20, 2022
ebd1c6f
Add option to control whether `Tab` key inserts suggestions
iansan5653 Jul 20, 2022
90bcff5
Style the defaulted-to first option differently from the selected option
iansan5653 Jul 20, 2022
729ba94
Improve labelling
iansan5653 Jul 21, 2022
230db8d
Change 'entity' for 'mentionable' naming
iansan5653 Jul 21, 2022
c2478de
Accept `ReactNode` for `label`
iansan5653 Jul 21, 2022
3b6d522
Fade out the hint link when disabled
iansan5653 Jul 21, 2022
25c1919
Improve story
iansan5653 Jul 21, 2022
d3119bb
Fix lint issues in MarkdownViewer tests
iansan5653 Jul 21, 2022
1af8adc
Allow custom toolbar using declarative API
iansan5653 Jul 21, 2022
59ed67c
Fix infinite rendering bug
iansan5653 Jul 22, 2022
02427db
Assign displayNames to public components
iansan5653 Jul 22, 2022
cc45aba
Replace `actionButtons` prop with a slots-based API
iansan5653 Jul 22, 2022
68b523d
Rename describedBy to aria-describedby
iansan5653 Jul 22, 2022
a049972
Move label to slots-based API
iansan5653 Jul 22, 2022
cc61dad
Add display name for label
iansan5653 Jul 22, 2022
54bde5c
Refactor and optimize
iansan5653 Jul 22, 2022
658fd5c
Add documentation for subcomponents
iansan5653 Jul 22, 2022
2303cc0
Make file upload support optional
iansan5653 Jul 22, 2022
cdea599
Add to drafts exports
iansan5653 Jul 22, 2022
a71080c
Update src/MarkdownEditor/index.ts
iansan5653 Jul 22, 2022
20aa279
Merge branch 'add-markdown-editor-component' of https://github.com/pr…
iansan5653 Jul 22, 2022
82ba405
Update combobox-nav dependency
iansan5653 Jul 22, 2022
0e71c7a
Fix isMacOS calls breaking tests
iansan5653 Jul 25, 2022
90893db
Fix toolbar button aria-labels
iansan5653 Jul 26, 2022
62a5e33
Add another story
iansan5653 Jul 26, 2022
1243523
Fix fallback toolbar
iansan5653 Jul 26, 2022
e71df1b
Add initial batch of tests
iansan5653 Jul 26, 2022
5c26f0b
Upgrade `userEvent` to v14
iansan5653 Jul 26, 2022
af3d0bb
Fix Autocomplete tests
iansan5653 Jul 26, 2022
4498c9f
Merge branch 'update-userevent' into add-markdown-editor-component
iansan5653 Jul 26, 2022
bc5b1b1
Update userEvent and fix remaining tests
iansan5653 Jul 26, 2022
a62212f
Add indenting tests
iansan5653 Jul 26, 2022
91f1bb8
Add file upload tests
iansan5653 Jul 26, 2022
50d243e
Add useSafeAsyncCallback hook
iansan5653 Jul 27, 2022
c04ecf7
Improve the `useCombinedRefs` hook
iansan5653 Jul 19, 2022
9db19a4
Remove unused import
iansan5653 Jul 27, 2022
f91f682
Add `useCombinedRefs` to hooks index
iansan5653 Jul 27, 2022
543b1d3
Change createSlots to use layout effects instead of regular effects
iansan5653 Jul 27, 2022
4256bcf
Merge branch 'fix-use-combined-refs' into add-markdown-editor-component
iansan5653 Jul 27, 2022
39af449
Fix tests and lint errors
iansan5653 Jul 27, 2022
d070bc8
Add demo and test for file failing to upload
iansan5653 Jul 27, 2022
d846fe3
Add tests for previewing and fix bug with controlled view mode
iansan5653 Jul 27, 2022
22dd509
Make `InputLabel` work as `legend` and have correct stricter props
iansan5653 Jul 27, 2022
73a605f
Remove forwarded refs from `MarkdownEditor.Label`
iansan5653 Jul 27, 2022
1aef690
Add tests for basic props and config
iansan5653 Jul 27, 2022
2ee63de
Add accessible labelling tests & fix tests around refs and disabling
iansan5653 Jul 27, 2022
0732db3
Add tests and a story for suggestions
iansan5653 Jul 27, 2022
8e04828
Merge branch 'main' of https://github.com/primer/react into add-markd…
iansan5653 Jul 27, 2022
a137fd3
Add support for saved replies :tada:
iansan5653 Jul 28, 2022
e7c6a16
Bake suggestions filtering into the component using fuzzy matching
iansan5653 Jul 28, 2022
3373f33
Merge branch 'main' of https://github.com/primer/react into add-inlin…
iansan5653 Jul 28, 2022
0ee19db
Update and fix unit tests
iansan5653 Jul 28, 2022
af9bcc6
Remove unused import (fix lint error)
iansan5653 Jul 29, 2022
3bb3b42
Add `MarkdownEditor` docs
iansan5653 Jul 29, 2022
4d1d810
Merge branch 'main' into add-markdown-editor-component
iansan5653 Jul 29, 2022
d37426d
docs: add drafts metastring
siddharthkp Aug 1, 2022
b6d2e28
Remove `selectionVariant` from suggestions list
iansan5653 Aug 1, 2022
841b41c
Merge branch 'add-inline-autocomplete-component' of https://github.co…
iansan5653 Aug 1, 2022
ac68a54
Merge branch 'main' of https://github.com/primer/react into add-inlin…
iansan5653 Aug 1, 2022
17b3747
Add `install:docs` script
iansan5653 Aug 1, 2022
1b7cb9c
Add more examples to docs
iansan5653 Aug 1, 2022
07af717
Add more stories
iansan5653 Aug 1, 2022
0a608ab
Fix _another_ bug with the caret-coordinates utility and single-line …
iansan5653 Aug 1, 2022
f48c8e5
Move component & hooks to drafts folder
iansan5653 Aug 1, 2022
dcfbffc
Move stories & tests into drafts
iansan5653 Aug 1, 2022
ed30cc6
Remove non-null assertions in tests
iansan5653 Aug 1, 2022
468b5a9
Move `textarea-caret` type declaration to `@types`
iansan5653 Aug 1, 2022
592b179
Add props table
iansan5653 Aug 1, 2022
69f3203
Fix TS issue
iansan5653 Aug 1, 2022
1456caf
Create cuddly-bags-sort.md
iansan5653 Aug 2, 2022
bf2e8d4
Merge branch 'add-inline-autocomplete-component' of https://github.co…
iansan5653 Aug 2, 2022
659f16d
Update imports
iansan5653 Aug 2, 2022
461c45c
Move changes into `drafts` directory
iansan5653 Aug 2, 2022
1c1a6b6
Format
iansan5653 Aug 2, 2022
5a75824
Fix lint errors
iansan5653 Aug 2, 2022
a59ef7a
Merge branch 'main' of https://github.com/primer/react into add-markd…
iansan5653 Aug 2, 2022
6eaa130
Merge branch 'main' of https://github.com/primer/react into add-markd…
iansan5653 Aug 2, 2022
e350270
Update useListInteraction to use a tracking ref
iansan5653 Aug 2, 2022
989fac4
Replace`useCombinedRefs` in `MarkdownInput`
iansan5653 Aug 3, 2022
3d9af5d
Improve `useSafeAsyncCallback`
iansan5653 Aug 3, 2022
033ada5
Add `MarkdownViewer` stories
iansan5653 Aug 3, 2022
c167ff0
Fix documentation
iansan5653 Aug 3, 2022
e03e2d6
Add changeset
iansan5653 Aug 3, 2022
d27be6a
Merge branch 'main' into add-markdown-editor-component
iansan5653 Aug 3, 2022
5098829
Fix `useIgnoreKeyboardActionsWhileComposing` tests
iansan5653 Aug 3, 2022
2d67b4c
Merge branch 'add-markdown-editor-component' of https://github.com/pr…
iansan5653 Aug 3, 2022
82a853d
Fix markdown-toolbar-element initialization
iansan5653 Aug 4, 2022
969b981
Fix remaining test cases
iansan5653 Aug 4, 2022
3bb8f9d
Merge branch 'main' of https://github.com/primer/react into add-markd…
iansan5653 Aug 4, 2022
9216121
Remove console.error
iansan5653 Aug 4, 2022
7af9d09
Merge branch 'main' of https://github.com/primer/react into add-markd…
iansan5653 Aug 5, 2022
09c3c75
Update changeset
iansan5653 Aug 5, 2022
4555257
Move character coordinates utils to drafts
iansan5653 Aug 10, 2022
10a96fe
Merge branch 'main' of https://github.com/primer/react into add-markd…
iansan5653 Aug 10, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/fluffy-cycles-shave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@primer/react": patch
---

- Add `MarkdownEditor` and `MarkdownViewer` draft components. The `MarkdownEditor` is also known as the `CommentBox` component
- Add `useUnifiedFileSelect`, `useIgnoreKeyboardInputWhileComposing`, `useDynamicTextareaHeight`, and `useSafeAsyncCallback` draft hooks
36 changes: 36 additions & 0 deletions @types/fzy-js/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
declare module 'fzy.js' {
// as defined by https://github.com/jhawthorn/fzy.js/blob/master/index.js#L189
export const SCORE_MIN: typeof Infinity // for -Infinity
export const SCORE_MAX: typeof Infinity

export const SCORE_GAP_LEADING: number
export const SCORE_GAP_TRAILING: number
export const SCORE_GAP_INNER: number
export const SCORE_MATCH_CONSECUTIVE: number
export const SCORE_MATCH_SLASH: number
export const SCORE_MATCH_WORD: number
export const SCORE_MATCH_CAPITAL: number
export const SCORE_MATCH_DOT: number

/**
* score
* @param searchQuery - the user filter (the "needle")
* @param text - full text of the item being matched (the "haystack")
* @returns the score
*/
export function score(searchQuery: string, text: string): number
/**
* positions
* @param searchQuery - the user filter (the "needle")
* @param text - full text of the item being matched (the "haystack")
* @returns the position for each character match in the sequence
*/
export function positions(searchQuery: string, text: string): Array<number>
/**
* hasMatch
* @param searchQuery - the user filter (the "needle")
* @param text - full text of the item being matched (the "haystack")
* @returns whether or not there is a match in the sequence
*/
export function hasMatch(searchQuery: string, text: string): boolean
}
162 changes: 162 additions & 0 deletions docs/content/drafts/MarkdownEditor.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
componentId: markdown_editor
title: MarkdownEditor
status: Draft
description: Full-featured Markdown input.
storybook: '/react/storybook?path=/story/forms-markdowneditor--default'
---

```js
import {MarkdownEditor} from '@primer/react/drafts'
```

`MarkdownEditor` is a full-featured editor for GitHub Flavored Markdown, with support for:

- Formatting (keyboard shortcuts & toolbar buttons)
- File uploads (drag & drop, paste, click to upload)
- Inline suggestions (emojis, `@` mentions, and `#` references)
- Saved replies
- Markdown pasting (ie, paste URL onto selected text to create a link)
- List editing (create a new list item on `Enter`)
- Indenting selected text

## Examples

### Minimal Example

A `Label` is always required for accessibility:

```javascript live noinline drafts
const renderMarkdown = async (markdown) => {
// In production code, this would make a query to some external API endpoint to render
return "Rendered Markdown."
}

const MinimalExample = () => {
const [value, setValue] = React.useState('')

return (
<MarkdownEditor
value={value}
onChange={setValue}
onRenderPreview={renderMarkdown}
>
<MarkdownEditor.Label>Minimal Example</MarkdownEditor.Label>
</MarkdownEditor>
)
}

render(MinimalExample)
```

### Suggestions, File Uploads, and Saved Replies

```javascript live noinline drafts
const renderMarkdown = async (markdown) => "Rendered Markdown."

const uploadFile = async (file) => ({
url: `https://example.com/${encodeURIComponent(file.name)}`,
file
})

const emojis = [
{name: '+1', character: '👍'},
{name: '-1', character: '👎'},
{name: 'heart', character: '❤️'},
{name: 'wave', character: '👋'},
{name: 'raised_hands', character: '🙌'},
{name: 'pray', character: '🙏'},
{name: 'clap', character: '👏'},
{name: 'ok_hand', character: '👌'},
{name: 'point_up', character: '☝️'},
{name: 'point_down', character: '👇'},
{name: 'point_left', character: '👈'},
{name: 'point_right', character: '👉'},
{name: 'raised_hand', character: '✋'},
{name: 'thumbsup', character: '👍'},
{name: 'thumbsdown', character: '👎'}
]

const references = [
{id: '1', titleText: 'Add logging functionality', titleHtml: 'Add logging functionality'},
{
id: '2',
titleText: 'Error: `Failed to install` when installing',
titleHtml: 'Error: <code>Failed to install</code> when installing'
},
{id: '3', titleText: 'Add error-handling functionality', titleHtml: 'Add error-handling functionality'}
]

const mentionables = [
{identifier: 'monalisa', description: 'Monalisa Octocat'},
{identifier: 'github', description: 'GitHub'},
{identifier: 'primer', description: 'Primer'}
]

const savedReplies = [
{name: 'Duplicate', content: 'Duplicate of #'},
{name: 'Welcome', content: 'Welcome to the project!\n\nPlease be sure to read the contributor guidelines.'},
{name: 'Thanks', content: 'Thanks for your contribution!'}
]

const MinimalExample = () => {
const [value, setValue] = React.useState('')

return (
<MarkdownEditor
value={value}
onChange={setValue}
onRenderPreview={renderMarkdown}

onUploadFile={uploadFile}

emojiSuggestions={emojis}
referenceSuggestions={references}
mentionSuggestions={mentionables}

savedReplies={savedReplies}
>
<MarkdownEditor.Label>Suggestions, File Uploads, and Saved Replies Example</MarkdownEditor.Label>
</MarkdownEditor>
)
}

render(MinimalExample)
```

### Custom Buttons

```javascript live noinline drafts
const renderMarkdown = async (markdown) => "Rendered Markdown."

const MinimalExample = () => {
const [value, setValue] = React.useState('')

return (
<MarkdownEditor
value={value}
onChange={setValue}
onRenderPreview={renderMarkdown}
>
<MarkdownEditor.Label visuallyHidden>Custom Buttons</MarkdownEditor.Label>

<MarkdownEditor.Toolbar>
<MarkdownEditor.ToolbarButton icon={SquirrelIcon} aria-label="Custom button 1" />
<MarkdownEditor.DefaultToolbarButtons />
<MarkdownEditor.ToolbarButton icon={BugIcon} aria-label="Custom button 2" />
</MarkdownEditor.Toolbar>

<MarkdownEditor.Actions>
<MarkdownEditor.ActionButton variant="danger">
Cancel
</MarkdownEditor.ActionButton>
<MarkdownEditor.ActionButton variant="primary">
Submit
</MarkdownEditor.ActionButton>
</MarkdownEditor.Actions>
</MarkdownEditor>
)
}

render(MinimalExample)
```
97 changes: 97 additions & 0 deletions docs/content/drafts/MarkdownViewer.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
---
componentId: markdown_viewer
title: MarkdownViewer
status: Draft
description: Displays rendered Markdown and facilitates interaction.
---

```js
import {MarkdownViewer} from '@primer/react/drafts'
```

The `MarkdownViewer` displays rendered Markdown with appropriate styling and handles interaction (link clicking and checkbox checking/unchecking) with that content.

## Examples

### Simple Example

```javascript live noinline drafts
const MarkdownViewerExample = () => {
return (
// eslint-disable-next-line github/unescaped-html-literal
<MarkdownViewer dangerousRenderedHtml={{__html: '<strong>Lorem ipsum</strong> dolor sit amet.'}} />
)
}

render(MarkdownViewerExample)
```

### Link-Handling Example

```javascript live noinline drafts
const MarkdownViewerExample = () => {
return (
<MarkdownViewer
// eslint-disable-next-line github/unescaped-html-literal
dangerousRenderedHtml={{__html: "<a href='https://example.com'>Example link</a>"}}
onLinkClick={ev => console.log(ev)}
/>
)
}

render(MarkdownViewerExample)
```

### Checkbox Interaction Example

```javascript live noinline drafts
const markdownSource = `
text before list

- [ ] item 1
- [ ] item 2

text after list`

const renderedHtml = `
<p>text before list</p>
<ul class='contains-task-list'>
<li class='task-list-item'><input type='checkbox' class='task-list-item-checkbox' disabled/> item 1</li>
<li class='task-list-item'><input type='checkbox' class='task-list-item-checkbox' disabled/> item 2</li>
</ul>
<p>text after list</p>`

const MarkdownViewerExample = () => {
return (
<MarkdownViewer
dangerousRenderedHtml={{__html: renderedHtml}}
markdownValue={markdownSource}
onChange={value => console.log(value) /* save the value to the server */}
disabled={false}
/>
)
}

render(MarkdownViewerExample)
```

## Status

<ComponentChecklist
items={{
propsDocumented: false,
noUnnecessaryDeps: true,
adaptsToThemes: true,
adaptsToScreenSizes: true,
fullTestCoverage: true,
usedInProduction: true,
usageExamplesDocumented: false,
hasStorybookStories: false,
designReviewed: false,
a11yReviewed: false,
stableApi: false,
addressedApiFeedback: false,
hasDesignGuidelines: false,
hasFigmaComponent: false
}}
/>
4 changes: 4 additions & 0 deletions docs/src/@primer/gatsby-theme-doctocat/nav.yml
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@
url: /drafts/Dialog
- title: InlineAutocomplete
url: /drafts/InlineAutocomplete
- title: MarkdownEditor
url: /drafts/MarkdownEditor
- title: MarkdownViewer
url: /drafts/MarkdownViewer
- title: Deprecated
children:
- title: ActionList (legacy)
Expand Down
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ module.exports = {
'<rootDir>/src/utils/test-helpers.tsx'
],
testMatch: ['<rootDir>/(src|codemods)/**/*.test.[jt]s?(x)', '!**/*.types.test.[jt]s?(x)'],
transformIgnorePatterns: ['node_modules/(?!@github/combobox-nav|@koddsson/textarea-caret)']
transformIgnorePatterns: [
'node_modules/(?!@github/combobox-nav|@koddsson/textarea-caret|@github/markdown-toolbar-element)'
]
}
33 changes: 33 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading