Skip to content

Conversation

@QuintonJason
Copy link
Contributor

@QuintonJason QuintonJason commented Jan 21, 2026

Description

Adds a new pds-multiselect component - a Select2-style pillbox multiselect with typeahead search, checkbox options, and async data support.

Features

  • Typeahead Search: Filter options as you type with debounced input
  • Checkbox Options: Clear selection state with checkboxes in the dropdown
  • Async Support: Fetch options from a URL endpoint or manage data externally via props/events
  • Infinite Scroll: Automatic pagination when scrolling to the bottom of the dropdown
  • Keyboard Navigation: Full keyboard support (Arrow keys, Enter, Space, Escape, Backspace, Tab)
  • Form Integration: Form-associated custom element that works with native forms and FormData
  • Clear All: Button to clear all selections at once
  • Max Selections: Optional limit on number of selections
  • Validation: Required field support with proper validity state

Type of change

  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

Fixes DSS-87

Usage

ERB (Static Options)

<pds-multiselect
  component-id="category-filter"
  label="Categories"
  placeholder="Select categories..."
  name="categories[]"
>
  <option value="1">Marketing</option>
  <option value="2">Sales</option>
  <option value="3">Support</option>
  <option value="4">Engineering</option>
</pds-multiselect>

ERB (Async URL)

<pds-multiselect
  component-id="product-selector"
  label="Select Products"
  placeholder="Search products..."
  async-url="<%= admin_site_products_path(@site, format: :json) %>"
  async-method="GET"
  debounce-ms="300"
>
</pds-multiselect>

React

import { PdsMultiselect } from '@pine-ds/react';

const [selected, setSelected] = useState<string[]>([]);

<PdsMultiselect
  componentId="offer-selector"
  label="Choose Offers"
  placeholder="Search offers..."
  options={offers.map(o => ({ id: String(o.id), text: o.title }))}
  value={selected}
  onPdsMultiselectChange={(e) => setSelected(e.detail.values)}
/>

How Has This Been Tested?

  • unit tests
  • tested manually

Comprehensive spec tests cover:

  • Rendering (label, placeholder, disabled, error/helper messages, hidden label)
  • Selection behavior (select, remove, clear all, max selections)
  • Keyboard navigation (Arrow keys, Enter, Space, Escape, Backspace, Tab)
  • Search filtering
  • Preselected values with async timing
  • Form validation (required state)
  • ARIA attributes and accessibility

Test Configuration:

  • Pine versions: 3.14.0
  • OS: macOS
  • Browsers: Chrome, Safari

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing tests pass locally with my changes
  • Design has QA'ed and approved this PR

@netlify
Copy link

netlify bot commented Jan 21, 2026

Deploy Preview for pine-design-system ready!

Name Link
🔨 Latest commit 23d282a
🔍 Latest deploy log https://app.netlify.com/projects/pine-design-system/deploys/6973f66c9a69c80008e51395
😎 Deploy Preview https://deploy-preview-638--pine-design-system.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions bot added package: core Changes have been made to the Core package package: react Changes have been made to the React package labels Jan 21, 2026
@QuintonJason QuintonJason changed the title Feat/add multiselect feat(pds-multiselect): add pds-multiselect component Jan 21, 2026
@QuintonJason QuintonJason marked this pull request as ready for review January 22, 2026 23:41
@QuintonJason QuintonJason self-assigned this Jan 22, 2026
@QuintonJason
Copy link
Contributor Author

@coderabbitai review this pr

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

@QuintonJason: I'll review the changes in this PR for you. Let me analyze the new pds-multiselect component implementation, including its features, test coverage, accessibility, and code quality.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Jan 22, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new pds-multiselect web component to the design system. The addition includes a complete Stencil-based component implementation with multi-select functionality, search capability, async data loading support, keyboard navigation, and form integration. Supporting artifacts include TypeScript interfaces, SCSS styling, comprehensive documentation, Storybook stories, and unit tests. ESLint configurations are updated to adjust rule severity and file patterns. Type declarations are extended to expose the component and its event interfaces. A React proxy is added to enable usage in React applications. Form documentation is updated to demonstrate the new component in context.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: introducing a new pds-multiselect component. It is concise, specific, and clearly conveys the primary purpose of the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description comprehensively covers all required template sections with detailed feature descriptions, type of change, usage examples, testing information, and completed checklist items.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@libs/core/.lintstagedrc.json`:
- Around line 5-6: You removed js/jsx from the lint-staged glob ("*.{ts,tsx}")
which means Storybook files like IconCopyComponent.jsx will no longer be linted;
either confirm this is intentional or restore linting by updating the
lint-staged config to include jsx (e.g. add js and jsx back to the glob) or add
an explicit pattern that covers story files (e.g. include
"*.stories.{js,jsx,ts,tsx}" or the specific story path), and update the
.lintstagedrc.json entry accordingly so IconCopyComponent.jsx is linted on
commit if desired.

In `@libs/core/src/components/pds-multiselect/pds-multiselect.tsx`:
- Around line 32-35: Search-triggered asyncUrl fetches must be debounced using
the component's debounceMs prop: create a debounced wrapper (e.g.,
debouncedFetchAsyncOptions) that delays calling the existing async fetch routine
and call that wrapper from the search input handler instead of invoking the
fetch directly; reuse and cancel the AbortController before each real fetch to
abort in-flight requests, ensure the debounced timer is cleared and the
controller is aborted in cleanupAutoUpdate/disconnectedCallback, and apply the
same debounced wrapper to the other fetch call sites referenced (around the
blocks at ~214-218 and ~470-482) so all asyncUrl requests honor debounceMs.
- Around line 824-867: The trigger button's ARIA attributes only consider
this.invalid, so when this.errorMessage is present but this.invalid is false the
UI looks invalid but aria-invalid and aria-describedby don't reference the
error; update the trigger's attributes to consider errorMessage as well: change
aria-invalid to use (this.invalid || !!this.errorMessage) and call
assignDescription with the same boolean (assignDescription(this.componentId,
this.invalid || !!this.errorMessage, this.errorMessage || this.helperMessage))
so the error element is always referenced and aria-invalid reflects the visible
error; update references in the render() for the trigger button where
aria-invalid and aria-describedby are set.
🧹 Nitpick comments (2)
libs/core/.eslintrc.json (1)

10-15: Consider keeping no-unused-vars as an error.

Downgrading from "error" to "warn" allows unused variables to accumulate in the codebase without blocking commits. If this change was made to accommodate specific cases in the new component, consider using ESLint disable comments for those specific lines instead of globally relaxing the rule.

libs/core/src/components/pds-multiselect/test/pds-multiselect.spec.tsx (1)

309-436: Consider adding Space key test.

The keyboard navigation tests comprehensively cover Escape, ArrowDown, ArrowUp, Enter, and Tab keys. However, the PR description mentions Space key support for selection. Consider adding a test case for Space key behavior to ensure complete coverage.

📝 Suggested test for Space key
it('selects highlighted option on Space key', async () => {
  const page = await newSpecPage({
    components: [PdsMultiselect],
    html: `<pds-multiselect component-id="test"></pds-multiselect>`,
  });

  const changeSpy = jest.fn();
  page.root.addEventListener('pdsMultiselectChange', changeSpy);

  page.rootInstance.internalOptions = [
    { id: '1', text: 'Option 1' },
    { id: '2', text: 'Option 2' },
  ];
  page.rootInstance.isOpen = true;
  page.rootInstance.highlightedIndex = 0;
  await page.waitForChanges();

  const event = new KeyboardEvent('keydown', { key: ' ' });
  Object.defineProperty(event, 'preventDefault', { value: jest.fn() });
  page.rootInstance.handleSearchInputKeyDown(event);
  await page.waitForChanges();

  expect(changeSpy).toHaveBeenCalled();
  expect(changeSpy.mock.calls[0][0].detail.values).toContain('1');
});

Copy link
Member

@pixelflips pixelflips left a comment

Choose a reason for hiding this comment

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

A few comments, mostly on unexpected file changes and small consistency fixes to prop descriptions.

This is great work!

Copy link
Member

@pixelflips pixelflips left a comment

Choose a reason for hiding this comment

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

Some small nitpicks around prop names and descriptions, and a couple of styling things. Functionally seems to be working great.

Copy link
Member

@pixelflips pixelflips left a comment

Choose a reason for hiding this comment

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

LGTM! Great work!

@QuintonJason QuintonJason merged commit 82663fe into main Jan 23, 2026
15 checks passed
@QuintonJason QuintonJason deleted the feat/add-multiselect branch January 23, 2026 22:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

package: core Changes have been made to the Core package package: react Changes have been made to the React package

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants