Skip to content

Commit 9914932

Browse files
authored
Merge branch 'main' into fix-slots-infinite-render
2 parents fbee1fc + 47725a9 commit 9914932

File tree

67 files changed

+5492
-390
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+5492
-390
lines changed

.changeset/fluffy-cycles-shave.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@primer/react": patch
3+
---
4+
5+
- Add `MarkdownEditor` and `MarkdownViewer` draft components. The `MarkdownEditor` is also known as the `CommentBox` component
6+
- Add `useUnifiedFileSelect`, `useIgnoreKeyboardInputWhileComposing`, `useDynamicTextareaHeight`, and `useSafeAsyncCallback` draft hooks

.changeset/old-experts-applaud.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@primer/react': patch
3+
---
4+
5+
- Fixes `role` and keyboard behavior for SegmentedControl.
6+
- Fixes a bug where icon-only SegmentedControl buttons did not fill the parent width when the `fullWidth` prop was set
7+
- Fixes a bug where click handlers were not passed correctly when the responsive variant was set to `'hideLabels'`

.changeset/wicked-otters-warn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/react': patch
3+
---
4+
5+
Overlay documentation fixes

@types/fzy-js/index.d.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
declare module 'fzy.js' {
2+
// as defined by https://github.com/jhawthorn/fzy.js/blob/master/index.js#L189
3+
export const SCORE_MIN: typeof Infinity // for -Infinity
4+
export const SCORE_MAX: typeof Infinity
5+
6+
export const SCORE_GAP_LEADING: number
7+
export const SCORE_GAP_TRAILING: number
8+
export const SCORE_GAP_INNER: number
9+
export const SCORE_MATCH_CONSECUTIVE: number
10+
export const SCORE_MATCH_SLASH: number
11+
export const SCORE_MATCH_WORD: number
12+
export const SCORE_MATCH_CAPITAL: number
13+
export const SCORE_MATCH_DOT: number
14+
15+
/**
16+
* score
17+
* @param searchQuery - the user filter (the "needle")
18+
* @param text - full text of the item being matched (the "haystack")
19+
* @returns the score
20+
*/
21+
export function score(searchQuery: string, text: string): number
22+
/**
23+
* positions
24+
* @param searchQuery - the user filter (the "needle")
25+
* @param text - full text of the item being matched (the "haystack")
26+
* @returns the position for each character match in the sequence
27+
*/
28+
export function positions(searchQuery: string, text: string): Array<number>
29+
/**
30+
* hasMatch
31+
* @param searchQuery - the user filter (the "needle")
32+
* @param text - full text of the item being matched (the "haystack")
33+
* @returns whether or not there is a match in the sequence
34+
*/
35+
export function hasMatch(searchQuery: string, text: string): boolean
36+
}

docs/content/Overlay.mdx

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,23 +72,13 @@ See the W3C accessibility recommendations for modals [here](https://www.w3.org/T
7272
7373
## Positioning
7474
75-
`Overlay` renders its `children` within a div positioned absolutely within a portal within the default portal root. The overlay will not update its positioning if the portal root's positioning changes (e.g., if the portal root is statically positioned after some DOM element that dynamically resizes). You may consider [customizing the portal or specifying a different portal root](/Portal#customizing-the-portal-root) to achieve different positioning behavior.
75+
`Overlay` renders its `children` within a div positioned absolutely within a portal within the default portal root. The overlay will not update its positioning if the portal root's positioning changes (e.g., if the portal root is statically positioned after some DOM element that dynamically resizes). You may consider using the [AnchoredOverlay](/AnchoredOverlay) component or [customizing the portal root](/Portal#customizing-the-portal-root) to achieve different positioning behavior.
7676
7777
## Props
7878
7979
### Overlay
8080
8181
<PropsTable>
82-
<PropsTableRow
83-
required
84-
name="anchorRef"
85-
type="React.RefObject<HTMLElement>"
86-
description={
87-
<>
88-
Element the <InlineCode>Overlay</InlineCode> should be anchored to.
89-
</>
90-
}
91-
/>
9282
<PropsTableRow
9383
required
9484
name="returnFocusRef"
@@ -182,6 +172,23 @@ See the W3C accessibility recommendations for modals [here](https://www.w3.org/T
182172
</>
183173
}
184174
/>
175+
<PropsTableRow
176+
name="maxHeight"
177+
type={`| 'xsmall'
178+
| 'small'
179+
| 'medium'
180+
| 'large'
181+
| 'xlarge'`}
182+
description={
183+
<>
184+
Sets the maximum height of the <InlineCode>Overlay</InlineCode>, pick from our set list of heights.
185+
<InlineCode>xsmall</InlineCode> corresponds to <InlineCode>192px</InlineCode>, <InlineCode>small</InlineCode> corresponds
186+
to <InlineCode>256px</InlineCode>, <InlineCode>medium</InlineCode> corresponds to <InlineCode>320px</InlineCode>,{' '}
187+
<InlineCode>large</InlineCode> corresponds to <InlineCode>432px</InlineCode>, <InlineCode>xlarge</InlineCode> corresponds
188+
to <InlineCode>600px</InlineCode>.
189+
</>
190+
}
191+
/>
185192
<PropsTableRow
186193
name="visibility"
187194
type={`| 'visible'
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
---
2+
componentId: markdown_editor
3+
title: MarkdownEditor
4+
status: Draft
5+
description: Full-featured Markdown input.
6+
storybook: '/react/storybook?path=/story/forms-markdowneditor--default'
7+
---
8+
9+
```js
10+
import {MarkdownEditor} from '@primer/react/drafts'
11+
```
12+
13+
`MarkdownEditor` is a full-featured editor for GitHub Flavored Markdown, with support for:
14+
15+
- Formatting (keyboard shortcuts & toolbar buttons)
16+
- File uploads (drag & drop, paste, click to upload)
17+
- Inline suggestions (emojis, `@` mentions, and `#` references)
18+
- Saved replies
19+
- Markdown pasting (ie, paste URL onto selected text to create a link)
20+
- List editing (create a new list item on `Enter`)
21+
- Indenting selected text
22+
23+
## Examples
24+
25+
### Minimal Example
26+
27+
A `Label` is always required for accessibility:
28+
29+
```javascript live noinline drafts
30+
const renderMarkdown = async (markdown) => {
31+
// In production code, this would make a query to some external API endpoint to render
32+
return "Rendered Markdown."
33+
}
34+
35+
const MinimalExample = () => {
36+
const [value, setValue] = React.useState('')
37+
38+
return (
39+
<MarkdownEditor
40+
value={value}
41+
onChange={setValue}
42+
onRenderPreview={renderMarkdown}
43+
>
44+
<MarkdownEditor.Label>Minimal Example</MarkdownEditor.Label>
45+
</MarkdownEditor>
46+
)
47+
}
48+
49+
render(MinimalExample)
50+
```
51+
52+
### Suggestions, File Uploads, and Saved Replies
53+
54+
```javascript live noinline drafts
55+
const renderMarkdown = async (markdown) => "Rendered Markdown."
56+
57+
const uploadFile = async (file) => ({
58+
url: `https://example.com/${encodeURIComponent(file.name)}`,
59+
file
60+
})
61+
62+
const emojis = [
63+
{name: '+1', character: '👍'},
64+
{name: '-1', character: '👎'},
65+
{name: 'heart', character: '❤️'},
66+
{name: 'wave', character: '👋'},
67+
{name: 'raised_hands', character: '🙌'},
68+
{name: 'pray', character: '🙏'},
69+
{name: 'clap', character: '👏'},
70+
{name: 'ok_hand', character: '👌'},
71+
{name: 'point_up', character: '☝️'},
72+
{name: 'point_down', character: '👇'},
73+
{name: 'point_left', character: '👈'},
74+
{name: 'point_right', character: '👉'},
75+
{name: 'raised_hand', character: ''},
76+
{name: 'thumbsup', character: '👍'},
77+
{name: 'thumbsdown', character: '👎'}
78+
]
79+
80+
const references = [
81+
{id: '1', titleText: 'Add logging functionality', titleHtml: 'Add logging functionality'},
82+
{
83+
id: '2',
84+
titleText: 'Error: `Failed to install` when installing',
85+
titleHtml: 'Error: <code>Failed to install</code> when installing'
86+
},
87+
{id: '3', titleText: 'Add error-handling functionality', titleHtml: 'Add error-handling functionality'}
88+
]
89+
90+
const mentionables = [
91+
{identifier: 'monalisa', description: 'Monalisa Octocat'},
92+
{identifier: 'github', description: 'GitHub'},
93+
{identifier: 'primer', description: 'Primer'}
94+
]
95+
96+
const savedReplies = [
97+
{name: 'Duplicate', content: 'Duplicate of #'},
98+
{name: 'Welcome', content: 'Welcome to the project!\n\nPlease be sure to read the contributor guidelines.'},
99+
{name: 'Thanks', content: 'Thanks for your contribution!'}
100+
]
101+
102+
const MinimalExample = () => {
103+
const [value, setValue] = React.useState('')
104+
105+
return (
106+
<MarkdownEditor
107+
value={value}
108+
onChange={setValue}
109+
onRenderPreview={renderMarkdown}
110+
111+
onUploadFile={uploadFile}
112+
113+
emojiSuggestions={emojis}
114+
referenceSuggestions={references}
115+
mentionSuggestions={mentionables}
116+
117+
savedReplies={savedReplies}
118+
>
119+
<MarkdownEditor.Label>Suggestions, File Uploads, and Saved Replies Example</MarkdownEditor.Label>
120+
</MarkdownEditor>
121+
)
122+
}
123+
124+
render(MinimalExample)
125+
```
126+
127+
### Custom Buttons
128+
129+
```javascript live noinline drafts
130+
const renderMarkdown = async (markdown) => "Rendered Markdown."
131+
132+
const MinimalExample = () => {
133+
const [value, setValue] = React.useState('')
134+
135+
return (
136+
<MarkdownEditor
137+
value={value}
138+
onChange={setValue}
139+
onRenderPreview={renderMarkdown}
140+
>
141+
<MarkdownEditor.Label visuallyHidden>Custom Buttons</MarkdownEditor.Label>
142+
143+
<MarkdownEditor.Toolbar>
144+
<MarkdownEditor.ToolbarButton icon={SquirrelIcon} aria-label="Custom button 1" />
145+
<MarkdownEditor.DefaultToolbarButtons />
146+
<MarkdownEditor.ToolbarButton icon={BugIcon} aria-label="Custom button 2" />
147+
</MarkdownEditor.Toolbar>
148+
149+
<MarkdownEditor.Actions>
150+
<MarkdownEditor.ActionButton variant="danger">
151+
Cancel
152+
</MarkdownEditor.ActionButton>
153+
<MarkdownEditor.ActionButton variant="primary">
154+
Submit
155+
</MarkdownEditor.ActionButton>
156+
</MarkdownEditor.Actions>
157+
</MarkdownEditor>
158+
)
159+
}
160+
161+
render(MinimalExample)
162+
```
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
componentId: markdown_viewer
3+
title: MarkdownViewer
4+
status: Draft
5+
description: Displays rendered Markdown and facilitates interaction.
6+
---
7+
8+
```js
9+
import {MarkdownViewer} from '@primer/react/drafts'
10+
```
11+
12+
The `MarkdownViewer` displays rendered Markdown with appropriate styling and handles interaction (link clicking and checkbox checking/unchecking) with that content.
13+
14+
## Examples
15+
16+
### Simple Example
17+
18+
```javascript live noinline drafts
19+
const MarkdownViewerExample = () => {
20+
return (
21+
// eslint-disable-next-line github/unescaped-html-literal
22+
<MarkdownViewer dangerousRenderedHtml={{__html: '<strong>Lorem ipsum</strong> dolor sit amet.'}} />
23+
)
24+
}
25+
26+
render(MarkdownViewerExample)
27+
```
28+
29+
### Link-Handling Example
30+
31+
```javascript live noinline drafts
32+
const MarkdownViewerExample = () => {
33+
return (
34+
<MarkdownViewer
35+
// eslint-disable-next-line github/unescaped-html-literal
36+
dangerousRenderedHtml={{__html: "<a href='https://example.com'>Example link</a>"}}
37+
onLinkClick={ev => console.log(ev)}
38+
/>
39+
)
40+
}
41+
42+
render(MarkdownViewerExample)
43+
```
44+
45+
### Checkbox Interaction Example
46+
47+
```javascript live noinline drafts
48+
const markdownSource = `
49+
text before list
50+
51+
- [ ] item 1
52+
- [ ] item 2
53+
54+
text after list`
55+
56+
const renderedHtml = `
57+
<p>text before list</p>
58+
<ul class='contains-task-list'>
59+
<li class='task-list-item'><input type='checkbox' class='task-list-item-checkbox' disabled/> item 1</li>
60+
<li class='task-list-item'><input type='checkbox' class='task-list-item-checkbox' disabled/> item 2</li>
61+
</ul>
62+
<p>text after list</p>`
63+
64+
const MarkdownViewerExample = () => {
65+
return (
66+
<MarkdownViewer
67+
dangerousRenderedHtml={{__html: renderedHtml}}
68+
markdownValue={markdownSource}
69+
onChange={value => console.log(value) /* save the value to the server */}
70+
disabled={false}
71+
/>
72+
)
73+
}
74+
75+
render(MarkdownViewerExample)
76+
```
77+
78+
## Status
79+
80+
<ComponentChecklist
81+
items={{
82+
propsDocumented: false,
83+
noUnnecessaryDeps: true,
84+
adaptsToThemes: true,
85+
adaptsToScreenSizes: true,
86+
fullTestCoverage: true,
87+
usedInProduction: true,
88+
usageExamplesDocumented: false,
89+
hasStorybookStories: false,
90+
designReviewed: false,
91+
a11yReviewed: false,
92+
stableApi: false,
93+
addressedApiFeedback: false,
94+
hasDesignGuidelines: false,
95+
hasFigmaComponent: false
96+
}}
97+
/>

0 commit comments

Comments
 (0)