Add FormTokenField docs#22
Conversation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
flexseth
left a comment
There was a problem hiding this comment.
The UI is broken, see notes.
| suggestions={ TAG_SUGGESTIONS } | ||
| onChange={ setTags } | ||
| placeholder="Add a tag…" | ||
| maxLength={ 8 } |
There was a problem hiding this comment.
When I have more tags than fit on a single line in this component box, and I click, it doesn't always load the ability to add new tags. The select list shows up then disappears immediately.
Can we fix this?
There was a problem hiding this comment.
@copilot - Appears to be a CSS related issue or something with event handling when trying to click to add new tags to the Post Tags with Max Length section.
…h and close Co-authored-by: flexseth <3792502+flexseth@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive documentation for the FormTokenField component, which is a tag/token input with autocomplete functionality that mirrors the @wordpress/components FormTokenField API. The component enables users to add and remove tokens (tags) with keyboard navigation, autocomplete suggestions, and support for limiting the number of tokens via maxLength.
Changes:
- Added FormTokenField component with full keyboard navigation support (arrow keys, Enter, Backspace, Escape)
- Created comprehensive documentation with three interactive demos using localStorage for state persistence
- Integrated component into the documentation site via routes, navigation, MDX provider, and CSS styling
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/components/FormTokenField.jsx | New component implementation with token management, keyboard navigation, and autocomplete dropdown |
| src/components/index.js | Barrel export for FormTokenField component |
| src/docs/form-token-field.mdx | Documentation page with three interactive demos, usage examples, props table, and WordPress integration guidance |
| src/styles/docs.css | BEM-style CSS for FormTokenField component with token styling, suggestions dropdown, and interaction states |
| src/providers/MDXComponents.jsx | Added FormTokenField to available MDX components |
| src/layouts/DocLayout.jsx | Added FormTokenField to sidebar navigation |
| src/App.jsx | Added route for /docs/form-token-field |
| CHANGELOG.md | Documented the new FormTokenField component and its features |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| aria-controls="ftf-suggestions" | ||
| /> | ||
| ) } | ||
| </div> | ||
|
|
||
| { isOpen && filtered.length > 0 && ( | ||
| <ul | ||
| id="ftf-suggestions" |
There was a problem hiding this comment.
Hardcoded ID "ftf-suggestions" will cause ID collisions when multiple FormTokenField instances exist on the same page, violating HTML uniqueness requirements and breaking accessibility. Following the established codebase pattern (seen in CheckboxControl, ComboboxControl, SelectControl, etc.), generate a unique ID from the label prop using the pattern formtoken-${label?.toLowerCase().replace(/\s+/g, '-')}. This ID should be used consistently for the suggestions list (line 166), the aria-controls attribute (line 159), and any other ID references.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
| value={ inputValue } | ||
| onChange={ handleInputChange } | ||
| onFocus={ () => setIsOpen( true ) } | ||
| onBlur={ () => setTimeout( () => setIsOpen( false ), 150 ) } |
There was a problem hiding this comment.
The setTimeout in onBlur doesn't clean up its timer if the component unmounts before the timeout executes, which can lead to React warnings about setState on unmounted components. Consider storing the timeout ID in a ref and clearing it in a useEffect cleanup function. Alternatively, adopt the outside-click pattern used in ComboboxControl (lines 54-63 in ComboboxControl.jsx) which is more robust and doesn't require setTimeout delays.
| onBlur={ () => setTimeout( () => setIsOpen( false ), 150 ) } | |
| onBlur={ () => setIsOpen( false ) } |
| { isOpen && filtered.length > 0 && ( | ||
| <ul | ||
| id="ftf-suggestions" | ||
| ref={ listRef } |
There was a problem hiding this comment.
The listRef on line 35 is declared and assigned to the suggestions ul element on line 167, but it's never actually used for any functionality. This is dead code. Consider removing it unless there's a planned future use case (e.g., for scrolling the active item into view). Other components like ComboboxControl don't use a list ref.
| ref={ listRef } |
| @@ -0,0 +1,299 @@ | |||
| import { useLocalStorage } from '../hooks/useLocalStorage'; | |||
There was a problem hiding this comment.
Missing import statement for FormTokenField component. All MDX documentation files that use components in their demos must explicitly import them. Add import FormTokenField from '../components/FormTokenField'; at the top of the file, similar to how other component documentation files import their components (e.g., combobox-control.mdx, dropdown.mdx, checkbox-control.mdx).
| import { useLocalStorage } from '../hooks/useLocalStorage'; | |
| import { useLocalStorage } from '../hooks/useLocalStorage'; | |
| import FormTokenField from '../components/FormTokenField'; |
| onChange={ setTags } | ||
| placeholder="Add a tag…" | ||
| maxLength={ 8 } | ||
| help="Maximum 8 tags. Press Enter or comma to add." |
There was a problem hiding this comment.
The help text mentions "Press Enter or comma to add" but the component does not support comma as a delimiter. The handleKeyDown function in FormTokenField.jsx only handles Enter key and optionally Space (when tokenizeOnSpace is true), but not comma. Either update the help text to remove the mention of comma, or add comma support to the component.
| onClick={ () => ! disabled && inputRef.current?.focus() } | ||
| > | ||
| { value.map( ( token, i ) => ( | ||
| <span key={ token + i } className="form-token-field__token"> |
There was a problem hiding this comment.
The key prop uses string concatenation token + i which can lead to key collisions. For example, if you have token "test1" at index 0, the key would be "test10", which is the same as token "test" at index 10. While the code prevents duplicate tokens (line 53), this is still a code smell. Consider using just the index as the key (since tokens are in a controlled order and duplicates are prevented), or use a combination that properly separates the values like {token}-{i} with a delimiter.
| <span key={ token + i } className="form-token-field__token"> | |
| <span key={ `${ token }-${ i }` } className="form-token-field__token"> |
| setIsOpen( true ); | ||
| } else if ( e.key === 'ArrowUp' ) { | ||
| e.preventDefault(); | ||
| setActiveIndex( ( i ) => Math.max( i - 1, 0 ) ); |
There was a problem hiding this comment.
Inconsistent behavior between ArrowDown and ArrowUp keyboard navigation. ArrowDown sets isOpen to true (line 84), but ArrowUp does not (line 87). This creates confusing UX where pressing ArrowUp when the suggestions are closed won't open them, while ArrowDown will. For consistency, ArrowUp should also set isOpen to true, or both should rely on the existing open state.
| setActiveIndex( ( i ) => Math.max( i - 1, 0 ) ); | |
| setActiveIndex( ( i ) => Math.max( i - 1, 0 ) ); | |
| setIsOpen( true ); |
| setIsOpen( true ); | ||
| } else if ( e.key === 'ArrowUp' ) { | ||
| e.preventDefault(); | ||
| setActiveIndex( ( i ) => Math.max( i - 1, 0 ) ); |
There was a problem hiding this comment.
ArrowUp key handling has unexpected behavior when no item is selected (activeIndex = -1). Pressing ArrowUp calculates Math.max(-1 - 1, 0) = 0, which selects the first suggestion. This is inconsistent with typical dropdown behavior where ArrowUp from no selection should either do nothing or select the last item. Consider checking if activeIndex is -1 and either preventing the action or wrapping to the last item (filtered.length - 1).
| setActiveIndex( ( i ) => Math.max( i - 1, 0 ) ); | |
| setActiveIndex( ( i ) => ( i === -1 ? -1 : Math.max( i - 1, 0 ) ) ); |
Summary
FormTokenFieldcomponent mirroring@wordpress/componentsFormTokenFieldmaxLengthprop caps tokens and hides the input at the limittokenizeOnSpaceoption;onMouseDown+preventDefaultprevents blur-before-selectuseLocalStorageTest plan
/docs/form-token-field— page loads without errorsnpm run buildpasses with no errors🤖 Generated with Claude Code