-
Notifications
You must be signed in to change notification settings - Fork 535
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 InlineAutocomplete
component
#2157
Add InlineAutocomplete
component
#2157
Conversation
…e-autocomplete-component
🦋 Changeset detectedLatest commit: 0d4c896 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work. I'll keep an eye on this PR and give it a closer look when it's ready for review.
src/InlineAutocomplete/types.ts
Outdated
query: string | ||
} | ||
|
||
export type Suggestion = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're moving towards composable component APIs. ADR here - https://github.com/primer/react/blob/main/contributor-docs/adrs/adr-004-children-as-api.md
Could we pass suggestions as a component instead? Something like this...
<InlineAutocomplete>
<InlineAutocomplete.Overlay loading={true|false}>
<InlineAutocomplete.Item value="one" key={0}>One</InlineAutocomplete.Item>
<InlineAutocomplete.Item value="two" key={1}>Two</InlineAutocomplete.Item>
<InlineAutocomplete.Item value="three" key={2}>Three</InlineAutocomplete.Item>
</InlineAutocomplete.Overlay>
</InlineAutocomplete>
@siddharthkp - please correct me if I'm misinterpreting the ADR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that a lot. I am not a fan at all of the render
property.
My only hesitation is that it can be a lot harder to read when suggestions are dynamic (which they always are):
<InlineAutocomplete.Suggestions>
{suggestions.map(s => <InlineAutocomplete.Suggestion value={s} key={s} />)}
</InlineAutocomplete.Suggestions>
But I think the reduced readability probably is probably outweighed by the flexibility in the compositional API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may push this API change off to the later work if that's OK? I think we are willing to accept unstable APIs in experimental/draft components and I'd like to get this PR merged before too long.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, we're ok with unstable APIs in experimental/draft components. I think your current API is fine for now, but not something we'd have in an Alpha component.
src/InlineAutocomplete/types.ts
Outdated
*/ | ||
key?: string | ||
/** This must return an `ActionList.Item` instance. */ | ||
render: (props: ActionListItemProps) => React.ReactElement |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we went with this approach instead of using children, how would we restrict users from returning something that isn't an ActionList.item
instance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah at this point we don't - we let them use anything, which isn't ideal
Could we move this work to |
This comment was marked as duplicate.
This comment was marked as duplicate.
@iansan5653 I would love to do some work on the screen reader support on this component. Currently if I activate the menu, because the screen reader is not aware that the menu appears, it's not announced by the screen reader and if the user presses down, because the menu is not associated with the input, the screenreader hijacks the down arrow and items cannot be selected. Give me some time to discuss this in the Accessibility team but we might be able to resolve this by using aria-activedesendant I'll have to double check if it's compatible with this use-case. Update after speaking to a team-mate: We would be interested to see if you can reuse the autocomplete element for the autocomplete portion of this work. If there are features you require to enable this, please let us know and we'll see what we can do. Also, if there's some reason we can't use the I would thoroughly recommend familiarising yourself with the aria authoring practices guidance for combobox as this details a lot of the functionality we're trying to create here and how to make it accessible. |
@owenniblock thanks for the accessibility notes! I am 100% on board with fixing the accessibility issues if we can figure out how ❤️ At the moment, I'm using the If I understand correctly from reading the docs, it seems that I think/hope the solution to this problem lies in improving that Just for reference, expand below to see the (simplified) HTML when the menu is open vs closed. If we can figure out exactly what needs to change, we can then easily figure out whether the fix needs to be done inside View HTMLWhen the list is not visible: <label for="react-aria3864888683-1">Inline Autocomplete Demo</label>
<textarea
aria-required="false"
aria-invalid="false"
id="react-aria3864888683-1"
aria-describedby="react-aria3864888683-1-caption"
></textarea>
<span id="react-aria3864888683-1-caption">
Try typing '@' to show user suggestions.
</span>
<div id="portal-root"></div> When the list is visible (ie, after the user types the trigger character): <label for="react-aria3864888683-1">Inline Autocomplete Demo</label>
<textarea
aria-required="false"
aria-invalid="false"
id="react-aria3864888683-1"
aria-describedby="react-aria3864888683-1-caption"
<!-- new attributes: -->
role="combobox"
aria-controls="combobox-7750"
aria-expanded="true"
aria-autocomplete="list"
aria-haspopup="listbox"
></textarea>
<span id="react-aria3864888683-1-caption">
Try typing '@' to show user suggestions.
</span>
<div id="portal-root">
<ul role="listbox" id="combobox-7750">
<li
tabindex="0"
aria-labelledby="monalisa monalisa--inline-description"
role="option"
id="combobox-react-aria3864888683-2__option-0"
aria-selected="true"
>
<span id="monalisa">Monalisa Octocat</span>
<div id="monalisa--inline-description" title="monalisa">monalisa</div>
</li>
<li
tabindex="0"
aria-labelledby="primer primer--inline-description"
role="option"
id="combobox-react-aria3864888683-2__option-1"
aria-selected="false"
>
<span id="primer">Primer</span>
<div id="primer--inline-description" title="primer">primer</div>
</li>
<li
tabindex="0"
aria-labelledby="github github--inline-description"
role="option"
id="combobox-react-aria3864888683-2__option-2"
aria-selected="false"
>
<span id="github">GitHub</span>
<div id="github--inline-description" title="github">github</div>
</li>
</ul>
</div> |
Weird, I see that the combobox-nav example is actually setting the |
Oh, it's my bad! It is actually setting it, but I had blurred the input (after disabling hiding the menu) to copy the HTML and that must have cleared it. When I focus the input and press down the activedescendant is set. Hmm, I wonder then if the issue is just that I am constructing and destroying the class when the menu visibility changes, rather than starting and stopping it? I'm not totally sure if that's what's happening and/or if that was intentional - I'll have to dig back into the code. |
Fixes not having an `aria-activedescendant` initially defined
I think we should be good to go with this now! |
…m/iansan5653/react into add-inline-autocomplete-component
…e-autocomplete-component
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to go for drafts 👍
Is this empty file intentional? https://github.com/primer/react/pull/2157/files#diff-e5c61c2ea86f2684b47c752adc87f089e3c2c06efb57b23deea6158fea599114 |
Yes, it doesn't compile without it. I guess every folder in that directory needs an |
Writeup adapted from github/memex#105801:
Component Description
A new
InlineAutocomplete
component that provides support for inline completion suggestions (for example, emojis) intextarea
andinput
elements.Screen recording of the component being used to mention a suggested user in a multiline text box:
InlineAutocomplete.Demo.mov
The component is similar in some ways to the existing
Autocomplete
component, but this is a somewhat different pattern in that, when a suggestion is accepted, the value is inserted into the text (hence 'inline') rather than replacing the content. This pattern is closer to an IDE-style autocomplete, where the existing Primer component uses more of a 'search suggestions' pattern.Supported Features
input
ortextarea
element as well as PRCInput
andTextArea
componentsAdditional Work
While adding this component, I also added some new hooks to abstract out some of the underlying logic:
useCombobox
hookThe
useCombobox
hook provides a React-compatible wrapper around the@github/combobox-nav
class. This provides keyboard control and accessible roles for comboboxes (lists of items that can be selected to apply to an input). The hook provides support for comboboxes that can be opened and closed like this one, and calls an easy to use event callback when an option is selected.useSyntheticChange
hookWhen a suggestion is applied, we need to emit a change event from the underlying input. It should look to the API consumer as though the input was typed in - they shouldn't have to add any additional event handlers to react to suggestion application. In addition, we want to preserve the undo stack and caret position - if we have a separate event handler and just update the input's value, the caret position will be reset.
So this hook provides an easy way to simulate a change that is as close as possible to the real thing. The change event preserves the undo stack and caret position and calls the
onChange
handler that is already bound to the underlying input.Background / Context
In projects (beta), we are using this component to provide emoji suggestions across the entire application and also to provide support for multiple types of inline suggestions in the Markdown editor.
This component is a dependency of the
MarkdownEditor
1 (AKACommentBox
1) component as it provides the inline suggestions for#
references,@
mentions, and emojis (:
). Rather than open one giant PR, I wanted to split this work out into a separate task.🚧 IMO, this component should be added as experimental status, just like the Markdown editor itself.
Closes github/memex#105801. There is not (at this time) a Primer tracking issue for this component.
Risks
New Dependencies
Two new external dependencies are introduced:
@github/combobox-nav
: See theuseCombobox
hook note above.navigator
global, it's incompatible with SSR so it doesn't work with the docs. I have an open PR to fix this.@koddsson/textarea-caret
: Detects the position of a character index in a textarea or input element. This is used to position the suggestions list and is already used in GitHub for the existing Markdown editor.Accessibility
This component dynamically adds and removes a combobox as the user is typing. A combobox that is always visible is accessible, but one that is added and removed is not - the suggestions will likely never be read by a screen reader. This issue is already present in GitHub with the existing Markdown editor. When we have the resources to solve this, we should do so by fixing the
@github/combobox-nav
library so the fix will cascade down to the existing Markdown editor in addition to this component.Storybook error
The story works in Storybook, but there is a console error being thrown that I have not been able to fix. I'm not familiar with Slots and could use some help here if anyone has any thoughts. I can't figure out where
data-source
might be getting applied. Is this a Storybook-specific issue?Merge checklist
MarkdownEditor
in projects beta; I need to extract those tests and make them specific to this component)Need to merge and update combobox-nav library to fix docs issue:
ctrlBindings
check intokeyboardBindings
function body to avoid globalnavigator
reference github/combobox-nav#53Footnotes
Link only accessible to GitHub employees ↩ ↩2 ↩3 ↩4