Skip to content

Add FormTokenField docs#22

Open
flexseth wants to merge 4 commits intofeature/wp-form-controlsfrom
feature/form-token-field
Open

Add FormTokenField docs#22
flexseth wants to merge 4 commits intofeature/wp-form-controlsfrom
feature/form-token-field

Conversation

@flexseth
Copy link
Copy Markdown
Owner

@flexseth flexseth commented Feb 23, 2026

Summary

  • Adds FormTokenField component mirroring @wordpress/components FormTokenField
  • Tag/token input with autocomplete dropdown, keyboard navigation, and free-form value entry
  • maxLength prop caps tokens and hides the input at the limit
  • tokenizeOnSpace option; onMouseDown + preventDefault prevents blur-before-select
  • Three demos persisting state via useLocalStorage

Test plan

  • Navigate to /docs/form-token-field — page loads without errors
  • BasicDemo: type to filter suggestions, click or press Enter to add tokens
  • BasicDemo: Backspace on empty input removes last token
  • TagsDemo: pre-populated tags visible; input disappears at maxLength 8
  • MaxLengthDemo: input disappears after 3 countries selected
  • Reload page — BasicDemo, TagsDemo, MaxLengthDemo state persists
  • Sidebar nav shows "FormTokenField" in list
  • npm run build passes with no errors

🤖 Generated with Claude Code

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gutendocs Ready Ready Preview, Comment Feb 23, 2026 10:05pm

Copy link
Copy Markdown
Owner Author

@flexseth flexseth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI is broken, see notes.

suggestions={ TAG_SUGGESTIONS }
onChange={ setTags }
placeholder="Add a tag…"
maxLength={ 8 }
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 23, 2026

@flexseth I've opened a new pull request, #23, to work on those changes. Once the pull request is ready, I'll request review from you.

…h and close

Co-authored-by: flexseth <3792502+flexseth@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +159 to +166
aria-controls="ftf-suggestions"
/>
) }
</div>

{ isOpen && filtered.length > 0 && (
<ul
id="ftf-suggestions"
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot open a new pull request to apply changes based on this feedback

Comment thread src/components/FormTokenField.jsx Outdated
value={ inputValue }
onChange={ handleInputChange }
onFocus={ () => setIsOpen( true ) }
onBlur={ () => setTimeout( () => setIsOpen( false ), 150 ) }
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
onBlur={ () => setTimeout( () => setIsOpen( false ), 150 ) }
onBlur={ () => setIsOpen( false ) }

Copilot uses AI. Check for mistakes.
{ isOpen && filtered.length > 0 && (
<ul
id="ftf-suggestions"
ref={ listRef }
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
ref={ listRef }

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,299 @@
import { useLocalStorage } from '../hooks/useLocalStorage';
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
import { useLocalStorage } from '../hooks/useLocalStorage';
import { useLocalStorage } from '../hooks/useLocalStorage';
import FormTokenField from '../components/FormTokenField';

Copilot uses AI. Check for mistakes.
onChange={ setTags }
placeholder="Add a tag…"
maxLength={ 8 }
help="Maximum 8 tags. Press Enter or comma to add."
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
onClick={ () => ! disabled && inputRef.current?.focus() }
>
{ value.map( ( token, i ) => (
<span key={ token + i } className="form-token-field__token">
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
<span key={ token + i } className="form-token-field__token">
<span key={ `${ token }-${ i }` } className="form-token-field__token">

Copilot uses AI. Check for mistakes.
setIsOpen( true );
} else if ( e.key === 'ArrowUp' ) {
e.preventDefault();
setActiveIndex( ( i ) => Math.max( i - 1, 0 ) );
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
setActiveIndex( ( i ) => Math.max( i - 1, 0 ) );
setActiveIndex( ( i ) => Math.max( i - 1, 0 ) );
setIsOpen( true );

Copilot uses AI. Check for mistakes.
setIsOpen( true );
} else if ( e.key === 'ArrowUp' ) {
e.preventDefault();
setActiveIndex( ( i ) => Math.max( i - 1, 0 ) );
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Suggested change
setActiveIndex( ( i ) => Math.max( i - 1, 0 ) );
setActiveIndex( ( i ) => ( i === -1 ? -1 : Math.max( i - 1, 0 ) ) );

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 23, 2026

@flexseth I've opened a new pull request, #24, to work on those changes. Once the pull request is ready, I'll request review from you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants