Skip to content

docs(Dropdown): add accessibility testing stories#3351

Draft
rivka-ungar wants to merge 2 commits into
masterfrom
docs/dropdown-accessibility-testing-stories
Draft

docs(Dropdown): add accessibility testing stories#3351
rivka-ungar wants to merge 2 commits into
masterfrom
docs/dropdown-accessibility-testing-stories

Conversation

@rivka-ungar

@rivka-ungar rivka-ungar commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

User description

https://monday.monday.com/boards/3532714909/views/113184182/pulses/11731479098

User description

Summary

  • Add 4 new Storybook pages under a Dropdown Testing section, one per dropdown type: Single Select, Single Searchable, Multi Select, Multi Searchable
  • Each page includes comprehensive stories covering all prop variations (states, sizes, labels, ARIA, options, groups, direction, etc.) for accessibility testing
  • Add corresponding MDX files so each type renders as a single scrollable docs page

Test plan

  • Run Storybook (yarn storybook) and verify the "Dropdown Testing" section appears in the sidebar
  • Open each of the 4 pages and confirm all stories render correctly on a single docs page
  • Verify no regressions to existing Dropdown stories under Components

🤖 Generated with Claude Code


PR Type

Documentation


Description

  • Add "Dropdown Testing" section to Storybook sidebar navigation

  • Create 4 comprehensive testing pages for all dropdown variants

    • Single Select (non-searchable, single choice)
    • Single Searchable (searchable, single choice)
    • Multi Select (non-searchable, multiple choices)
    • Multi Searchable (searchable, multiple choices)
  • Each page includes 40+ stories covering states, sizes, ARIA labels, options, groups, and behaviors

  • Provide MDX documentation pages rendering all stories in single scrollable view


Diagram Walkthrough

flowchart LR
  A["Storybook Preview Config"] -->|Add section| B["Dropdown Testing"]
  B --> C["Single Select Stories"]
  B --> D["Single Searchable Stories"]
  B --> E["Multi Select Stories"]
  B --> F["Multi Searchable Stories"]
  C --> G["MDX Documentation Page"]
  D --> H["MDX Documentation Page"]
  E --> I["MDX Documentation Page"]
  F --> J["MDX Documentation Page"]
Loading

File Walkthrough

Relevant files
Configuration changes
1 files
preview.tsx
Add Dropdown Testing to sidebar navigation                             
+1/-0     
Tests
4 files
DropdownSingleSelect.stories.tsx
Create single select dropdown testing stories                       
+690/-0 
DropdownSingleSearchable.stories.tsx
Create single searchable dropdown testing stories               
+787/-0 
DropdownMultiSelect.stories.tsx
Create multi select dropdown testing stories                         
+901/-0 
DropdownMultiSearchable.stories.tsx
Create multi searchable dropdown testing stories                 
+980/-0 
Documentation
4 files
DropdownSingleSelect.mdx
Create single select documentation page                                   
+186/-0 
DropdownSingleSearchable.mdx
Create single searchable documentation page                           
+190/-0 
DropdownMultiSelect.mdx
Create multi select documentation page                                     
+212/-0 
DropdownMultiSearchable.mdx
Create multi searchable documentation page                             
+216/-0 


PR Type

Documentation, Tests, Enhancement


Description

  • Add accessibility enhancements to Dropdown component with screen reader support

    • Implement visually hidden CSS utility class for accessible text announcements
    • Add getSelectedValueText() helper to generate accessible descriptions for selected values
    • Enhance dropdown input with aria-describedby for screen reader announcements
    • Add aria-selected attribute to dropdown options for proper state communication
  • Create comprehensive Dropdown Testing section in Storybook with 4 dedicated pages

    • Single Select: 40+ stories covering states, sizes, ARIA labels, options, groups, and behaviors
    • Single Searchable: 35+ stories with search functionality, filtering, and custom renderers
    • Multi Select: 40+ stories covering multi-selection layouts, chip colors, and min visible count
    • Multi Searchable: 40+ stories combining search and multi-selection features
  • Provide MDX documentation pages rendering all stories in single scrollable view

    • Organize stories into logical sections for easy navigation and testing
    • Include all prop variations and edge cases for accessibility testing
  • Update Storybook sidebar navigation to include new "Dropdown Testing" section


Diagram Walkthrough

flowchart LR
  A["Dropdown Component"] -->|Add accessibility| B["Screen Reader Support"]
  B --> C["Visually Hidden CSS"]
  B --> D["aria-describedby Announcements"]
  B --> E["aria-selected Attributes"]
  A -->|Create testing stories| F["Dropdown Testing Section"]
  F --> G["Single Select Stories"]
  F --> H["Single Searchable Stories"]
  F --> I["Multi Select Stories"]
  F --> J["Multi Searchable Stories"]
  G --> K["MDX Documentation"]
  H --> L["MDX Documentation"]
  I --> M["MDX Documentation"]
  J --> N["MDX Documentation"]
Loading

File Walkthrough

Relevant files
Accessibility
3 files
Trigger.module.scss
Add visually hidden CSS utility class                                       

packages/core/src/components/Dropdown/components/Trigger/Trigger.module.scss

  • Add .visuallyHidden CSS class for screen reader-only text
  • Implements standard visually hidden pattern with absolute positioning
    and clip properties
  • Enables accessible announcements without visual display
+12/-0   
DropdownInput.tsx
Enhance dropdown input with screen reader announcements   

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx

  • Add getSelectedValueText() helper function to generate accessible text
    for selected values
  • Implement visually hidden span with aria-describedby to announce
    selections to screen readers
  • Add selectedValueId ref and selectedValueText memoization for
    performance
  • Enhance searchable dropdown input with accessibility announcements
+49/-23 
DropdownBaseList.tsx
Add aria-selected attribute to dropdown options                   

packages/core/src/components/Dropdown/components/DropdownBaseList/DropdownBaseList.tsx

  • Add aria-selected attribute to BaseItem component
  • Properly communicate selected state to assistive technologies
  • Improves accessibility for dropdown options
+1/-1     
Tests
4 files
DropdownSingleSelect.stories.tsx
Add single select dropdown testing stories                             

packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.stories.tsx

  • Create comprehensive testing stories for single select dropdown
    variant
  • Include 40+ stories covering states, sizes, ARIA labels, options,
    groups, and behaviors
  • Cover disabled, error, read-only, loading states with various
    configurations
  • Include icon options, avatar options, grouped options, and RTL/LTR
    directions
+717/-0 
DropdownSingleSearchable.stories.tsx
Add single searchable dropdown testing stories                     

packages/docs/src/pages/components/Dropdown/DropdownSingleSearchable.stories.tsx

  • Create comprehensive testing stories for single searchable dropdown
    variant
  • Include 35+ stories with search functionality, filtering, and custom
    renderers
  • Cover all states, sizes, ARIA labels, and option variations
  • Include custom filter options and value renderer examples
+820/-0 
DropdownMultiSelect.stories.tsx
Add multi select dropdown testing stories                               

packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.stories.tsx

  • Create comprehensive testing stories for multi select dropdown variant
  • Include 40+ stories covering multi-selection layouts, chip colors, and
    min visible count
  • Cover all states, sizes, ARIA labels, and option variations
  • Include multiline vs single-line display comparisons
+942/-0 
DropdownMultiSearchable.stories.tsx
Add multi searchable dropdown testing stories                       

packages/docs/src/pages/components/Dropdown/DropdownMultiSearchable.stories.tsx

  • Create comprehensive testing stories for multi searchable dropdown
    variant
  • Include 40+ stories combining search and multi-selection features
  • Cover all states, sizes, ARIA labels, chip colors, and layout options
  • Include custom filtering and grouped options with various
    configurations
+1022/-0
Documentation
4 files
DropdownSingleSelect.mdx
Add single select dropdown MDX documentation page               

packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.mdx

  • Create MDX documentation page for single select dropdown testing
  • Organize stories into logical sections: labels, states, sizes, ARIA
    labels, variants, options, groups, and behavior
  • Render all single select stories in a single scrollable documentation
    page
+182/-0 
DropdownSingleSearchable.mdx
Add single searchable dropdown MDX documentation page       

packages/docs/src/pages/components/Dropdown/DropdownSingleSearchable.mdx

  • Create MDX documentation page for single searchable dropdown testing
  • Organize stories into sections: labels, states, sizes, ARIA labels,
    variants, options, groups, and behavior
  • Include custom filter and value renderer examples in documentation
+186/-0 
DropdownMultiSelect.mdx
Add multi select dropdown MDX documentation page                 

packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.mdx

  • Create MDX documentation page for multi select dropdown testing
  • Organize stories into sections: labels, states, multi-select layouts,
    sizes, ARIA labels, variants, options, and groups
  • Include chip colors and min visible count variations
+208/-0 
DropdownMultiSearchable.mdx
Add multi searchable dropdown MDX documentation page         

packages/docs/src/pages/components/Dropdown/DropdownMultiSearchable.mdx

  • Create MDX documentation page for multi searchable dropdown testing
  • Organize stories into comprehensive sections covering all prop
    variations
  • Include custom filtering, chip colors, and multi-select layout options
+212/-0 
Configuration changes
1 files
preview.tsx
Add Dropdown Testing section to Storybook sidebar               

packages/docs/.storybook/preview.tsx

  • Added "Dropdown Testing" to the Storybook sidebar navigation
    configuration
  • Positioned the new section between "Accessibility" and "Hooks" in the
    navigation hierarchy
  • Enables organization of dropdown-related testing stories under a
    dedicated section
+1/-0     

Add dedicated Storybook pages under "Dropdown Testing" section with
comprehensive prop coverage for Single Select, Single Searchable,
Multi Select, and Multi Searchable dropdown variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rivka-ungar rivka-ungar requested a review from a team as a code owner April 23, 2026 11:49
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Unused Text import 🐞 Bug ⚙ Maintainability
Description
DropdownMultiSelect.stories.tsx and DropdownMultiSearchable.stories.tsx import Text from
@vibe/core but never reference it, leaving dead code that can trip unused-import linting and adds
noise to the story files.
Code

packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.stories.tsx[8]

+import { Dropdown, Flex, Text } from "@vibe/core";
Evidence
Both newly added story files import Text even though their story renders only use Dropdown and
Flex (no Text component usage).

packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.stories.tsx[1-10]
packages/docs/src/pages/components/Dropdown/DropdownMultiSearchable.stories.tsx[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Two new story files import `Text` from `@vibe/core` but never use it.

### Issue Context
This is dead code and can cause unused-import lint failures if/when linting is enabled for docs stories.

### Fix Focus Areas
- packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.stories.tsx[1-10]
- packages/docs/src/pages/components/Dropdown/DropdownMultiSearchable.stories.tsx[1-10]

### Suggested change
Update the imports to remove `Text`:
- `import { Dropdown, Flex, Text } from "@vibe/core";`
+ `import { Dropdown, Flex } from "@vibe/core";`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unused useMemo import 🐞 Bug ⚙ Maintainability
Description
DropdownSingleSelect.stories.tsx imports useMemo from React but never uses it, leaving dead code
that can trip unused-import linting and reduces file clarity.
Code

packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.stories.tsx[1]

+import React, { useMemo } from "react";
Evidence
The file’s React import includes useMemo even though the story implementations do not reference
it.

packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.stories.tsx[1-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`DropdownSingleSelect.stories.tsx` imports `useMemo` but does not use it.

### Issue Context
This is dead code and can cause unused-import lint failures if/when linting is enabled for docs stories.

### Fix Focus Areas
- packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.stories.tsx[1-5]

### Suggested change
Update the import:
- `import React, { useMemo } from "react";`
+ `import React from "react";`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@rivka-ungar rivka-ungar marked this pull request as draft April 23, 2026 11:50
… value

- Add aria-describedby on combobox input pointing to a visually-hidden
  span containing the selected value text, so screen readers announce
  the current selection when the input receives focus
- Fix aria-selected on dropdown options to reflect actual selection
  state instead of Downshift's highlighted index
- Add inputAriaLabel and label to all dropdown accessibility test stories
- Remove redundant WithLabel stories from all 4 dropdown test pages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@rivka-ungar rivka-ungar marked this pull request as ready for review April 26, 2026 07:53
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. New ARIA attributes untested 📘 Rule violation ☼ Reliability ⭐ New
Description
This PR adds aria-selected on dropdown options and aria-describedby on the input, but the
Dropdown unit tests do not assert these accessibility attributes. Without coverage, future changes
can silently regress accessibility behavior.
Code

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[R47-82]

+  const selectedValueId = useRef(`dropdown-selected-${Math.random().toString(36).slice(2, 9)}`).current;
+  const selectedValueText = useMemo(
+    () => getSelectedValueText(multi, selectedItem, selectedItems),
+    [multi, selectedItem, selectedItems]
+  );
+
  return (
    <>
      {searchable ? (
-        <BaseInput
-          {...getInputProps({
-            "aria-labelledby": label ? getLabelProps().id : undefined,
-            "aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
-            placeholder: hasSelection ? "" : placeholder,
-            ref: inputRef,
-            ...multipleSelectionDropdownProps
-          })}
-          inputRole="combobox"
-          value={inputValue || ""}
-          autoFocus={autoFocus}
-          size={inputSize || size}
-          className={cx(styles.inputWrapper, {
-            [styles.hasSelected]: !multi && selectedItem && !inputValue,
-            [styles.small]: inputSize === "small",
-            [styles.multi]: multi && hasSelection,
-            [styles.multiSelected]: multi && hasSelection && inputSize === "small",
-            [styles.fullWidth]: fullWidth
-          })}
-          disabled={disabled}
-          readOnly={readOnly}
-        />
+        <>
+          <BaseInput
+            {...getInputProps({
+              "aria-labelledby": label ? getLabelProps().id : undefined,
+              "aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
+              "aria-describedby": selectedValueText ? selectedValueId : undefined,
+              placeholder: hasSelection ? "" : placeholder,
+              ref: inputRef,
+              ...multipleSelectionDropdownProps
+            })}
+            inputRole="combobox"
+            value={inputValue || ""}
+            autoFocus={autoFocus}
+            size={inputSize || size}
+            className={cx(styles.inputWrapper, {
+              [styles.hasSelected]: !multi && selectedItem && !inputValue,
+              [styles.small]: inputSize === "small",
+              [styles.multi]: multi && hasSelection,
+              [styles.multiSelected]: multi && hasSelection && inputSize === "small",
+              [styles.fullWidth]: fullWidth
+            })}
+            disabled={disabled}
+            readOnly={readOnly}
+          />
+          <span id={selectedValueId} className={styles.visuallyHidden}>
+            {selectedValueText}
+          </span>
Evidence
PR Compliance ID 7 requires tests to verify real behavior and applicable accessibility attributes.
The changed component code introduces new ARIA attributes (aria-describedby and aria-selected),
while the existing Dropdown test suite does not include assertions for these attributes.

CLAUDE.md
packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[47-82]
packages/core/src/components/Dropdown/components/DropdownBaseList/DropdownBaseList.tsx[63-66]
packages/core/src/components/Dropdown/tests/Dropdown.test.tsx[36-112]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The Dropdown component now sets `aria-describedby` on the input (pointing to a visually-hidden element containing the selected value(s)) and sets `aria-selected` on each rendered option, but there are no unit tests asserting these accessibility attributes.

## Issue Context
This PR specifically introduces/changes ARIA attributes for accessibility behavior; these should be covered by tests to prevent regressions.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[47-82]
- packages/core/src/components/Dropdown/components/DropdownBaseList/DropdownBaseList.tsx[63-66]
- packages/core/src/components/Dropdown/__tests__/Dropdown.test.tsx[36-112]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Random describedby id 🐞 Bug ☼ Reliability ⭐ New
Description
DropdownInput generates selectedValueId using Math.random(), so the rendered
id/aria-describedby values are non-deterministic across renders. This can break SSR hydration
(server/client markup mismatch) and makes DOM output unstable for tests and automation relying on
consistent ids.
Code

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[R47-82]

+  const selectedValueId = useRef(`dropdown-selected-${Math.random().toString(36).slice(2, 9)}`).current;
+  const selectedValueText = useMemo(
+    () => getSelectedValueText(multi, selectedItem, selectedItems),
+    [multi, selectedItem, selectedItems]
+  );
+
  return (
    <>
      {searchable ? (
-        <BaseInput
-          {...getInputProps({
-            "aria-labelledby": label ? getLabelProps().id : undefined,
-            "aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
-            placeholder: hasSelection ? "" : placeholder,
-            ref: inputRef,
-            ...multipleSelectionDropdownProps
-          })}
-          inputRole="combobox"
-          value={inputValue || ""}
-          autoFocus={autoFocus}
-          size={inputSize || size}
-          className={cx(styles.inputWrapper, {
-            [styles.hasSelected]: !multi && selectedItem && !inputValue,
-            [styles.small]: inputSize === "small",
-            [styles.multi]: multi && hasSelection,
-            [styles.multiSelected]: multi && hasSelection && inputSize === "small",
-            [styles.fullWidth]: fullWidth
-          })}
-          disabled={disabled}
-          readOnly={readOnly}
-        />
+        <>
+          <BaseInput
+            {...getInputProps({
+              "aria-labelledby": label ? getLabelProps().id : undefined,
+              "aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
+              "aria-describedby": selectedValueText ? selectedValueId : undefined,
+              placeholder: hasSelection ? "" : placeholder,
+              ref: inputRef,
+              ...multipleSelectionDropdownProps
+            })}
+            inputRole="combobox"
+            value={inputValue || ""}
+            autoFocus={autoFocus}
+            size={inputSize || size}
+            className={cx(styles.inputWrapper, {
+              [styles.hasSelected]: !multi && selectedItem && !inputValue,
+              [styles.small]: inputSize === "small",
+              [styles.multi]: multi && hasSelection,
+              [styles.multiSelected]: multi && hasSelection && inputSize === "small",
+              [styles.fullWidth]: fullWidth
+            })}
+            disabled={disabled}
+            readOnly={readOnly}
+          />
+          <span id={selectedValueId} className={styles.visuallyHidden}>
+            {selectedValueText}
+          </span>
Evidence
The component creates an id using Math.random() and uses it both as the aria-describedby target
and as the id of the hidden element; this means the markup varies across renders.

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[47-82]
packages/base/src/BaseInput/BaseInput.tsx[36-46]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`DropdownInput` uses `Math.random()` to generate `selectedValueId`, which makes the DOM output non-deterministic. This can cause SSR hydration mismatch warnings/errors and creates unstable ids.

### Issue Context
The id is used by both `aria-describedby` on the `<input>` and the hidden `<span>` that provides the described-by text.

### Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[1-83]

### Suggested change
- Replace the `useRef(Math.random()...)` id generation with `useId()` (React 18).
- If you want a more readable id, prefix it: `const selectedValueId = `dropdown-selected-${useId()}`;`.
- Update the React import accordingly (add `useId`, remove `useRef` usage for the id if no longer needed).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Falsy selection text lost 🐞 Bug ≡ Correctness ⭐ New
Description
getSelectedValueText uses || and .filter(Boolean), which drops legitimate falsy values (e.g.,
numeric value: 0) from the generated selection text. This can cause the aria-describedby
selection announcement to be empty even when a value is selected.
Code

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[R9-21]

+function getSelectedValueText(
+  multi: boolean | undefined,
+  selectedItem: BaseItemData | null | undefined,
+  selectedItems: BaseItemData[]
+): string {
+  if (multi) {
+    if (selectedItems.length === 0) return "";
+    const labels = selectedItems.map(item => item.label || item.value || "").filter(Boolean);
+    return `Selected: ${labels.join(", ")}`;
+  }
+  if (!selectedItem) return "";
+  return `Selected: ${selectedItem.label || selectedItem.value || ""}`;
+}
Evidence
BaseItemData.value can be a number, so 0 is a valid value; using || treats 0 as missing and
.filter(Boolean) will remove it as well, resulting in missing announcement text.

packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-21]
packages/core/src/components/BaseItem/BaseItem.types.ts[60-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`getSelectedValueText` uses `||` (and in multi-select also `.filter(Boolean)`), which drops valid falsy values like `0`. This can produce an empty `aria-describedby` text even though a selection exists.

### Issue Context
`BaseItemData.value` is `string | number`, so numeric `0` is valid input.

### Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[9-21]
- packages/core/src/components/BaseItem/BaseItem.types.ts[60-69]

### Suggested change
- Prefer nullish coalescing for fallbacks:
 - single: `selectedItem.label ?? selectedItem.value ?? ""`
 - multi: map to a string via `String(item.label ?? item.value ?? "")`, then filter only empty strings (not falsy in general), e.g. `filter(text => text !== "")`.
- Avoid `.filter(Boolean)` when the array can contain valid falsy values.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Unused Text import 🐞 Bug ⚙ Maintainability
Description
DropdownMultiSelect.stories.tsx and DropdownMultiSearchable.stories.tsx import Text from
@vibe/core but never reference it, leaving dead code that can trip unused-import linting and adds
noise to the story files.
Code

packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.stories.tsx[8]

+import { Dropdown, Flex, Text } from "@vibe/core";
Evidence
Both newly added story files import Text even though their story renders only use Dropdown and
Flex (no Text component usage).

packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.stories.tsx[1-10]
packages/docs/src/pages/components/Dropdown/DropdownMultiSearchable.stories.tsx[1-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Two new story files import `Text` from `@vibe/core` but never use it.
### Issue Context
This is dead code and can cause unused-import lint failures if/when linting is enabled for docs stories.
### Fix Focus Areas
- packages/docs/src/pages/components/Dropdown/DropdownMultiSelect.stories.tsx[1-10]
- packages/docs/src/pages/components/Dropdown/DropdownMultiSearchable.stories.tsx[1-10]
### Suggested change
Update the imports to remove `Text`:
- `import { Dropdown, Flex, Text } from "@vibe/core";`
+ `import { Dropdown, Flex } from "@vibe/core";`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Unused useMemo import 🐞 Bug ⚙ Maintainability
Description
DropdownSingleSelect.stories.tsx imports useMemo from React but never uses it, leaving dead code
that can trip unused-import linting and reduces file clarity.
Code

packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.stories.tsx[1]

+import React, { useMemo } from "react";
Evidence
The file’s React import includes useMemo even though the story implementations do not reference
it.

packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.stories.tsx[1-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DropdownSingleSelect.stories.tsx` imports `useMemo` but does not use it.
### Issue Context
This is dead code and can cause unused-import lint failures if/when linting is enabled for docs stories.
### Fix Focus Areas
- packages/docs/src/pages/components/Dropdown/DropdownSingleSelect.stories.tsx[1-5]
### Suggested change
Update the import:
- `import React, { useMemo } from "react";`
+ `import React from "react";`

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +47 to +82
const selectedValueId = useRef(`dropdown-selected-${Math.random().toString(36).slice(2, 9)}`).current;
const selectedValueText = useMemo(
() => getSelectedValueText(multi, selectedItem, selectedItems),
[multi, selectedItem, selectedItems]
);

return (
<>
{searchable ? (
<BaseInput
{...getInputProps({
"aria-labelledby": label ? getLabelProps().id : undefined,
"aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
placeholder: hasSelection ? "" : placeholder,
ref: inputRef,
...multipleSelectionDropdownProps
})}
inputRole="combobox"
value={inputValue || ""}
autoFocus={autoFocus}
size={inputSize || size}
className={cx(styles.inputWrapper, {
[styles.hasSelected]: !multi && selectedItem && !inputValue,
[styles.small]: inputSize === "small",
[styles.multi]: multi && hasSelection,
[styles.multiSelected]: multi && hasSelection && inputSize === "small",
[styles.fullWidth]: fullWidth
})}
disabled={disabled}
readOnly={readOnly}
/>
<>
<BaseInput
{...getInputProps({
"aria-labelledby": label ? getLabelProps().id : undefined,
"aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
"aria-describedby": selectedValueText ? selectedValueId : undefined,
placeholder: hasSelection ? "" : placeholder,
ref: inputRef,
...multipleSelectionDropdownProps
})}
inputRole="combobox"
value={inputValue || ""}
autoFocus={autoFocus}
size={inputSize || size}
className={cx(styles.inputWrapper, {
[styles.hasSelected]: !multi && selectedItem && !inputValue,
[styles.small]: inputSize === "small",
[styles.multi]: multi && hasSelection,
[styles.multiSelected]: multi && hasSelection && inputSize === "small",
[styles.fullWidth]: fullWidth
})}
disabled={disabled}
readOnly={readOnly}
/>
<span id={selectedValueId} className={styles.visuallyHidden}>
{selectedValueText}
</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

1. New aria attributes untested 📘 Rule violation ☼ Reliability

This PR adds aria-selected on dropdown options and aria-describedby on the input, but the
Dropdown unit tests do not assert these accessibility attributes. Without coverage, future changes
can silently regress accessibility behavior.
Agent Prompt
## Issue description
The Dropdown component now sets `aria-describedby` on the input (pointing to a visually-hidden element containing the selected value(s)) and sets `aria-selected` on each rendered option, but there are no unit tests asserting these accessibility attributes.

## Issue Context
This PR specifically introduces/changes ARIA attributes for accessibility behavior; these should be covered by tests to prevent regressions.

## Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[47-82]
- packages/core/src/components/Dropdown/components/DropdownBaseList/DropdownBaseList.tsx[63-66]
- packages/core/src/components/Dropdown/__tests__/Dropdown.test.tsx[36-112]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +47 to +82
const selectedValueId = useRef(`dropdown-selected-${Math.random().toString(36).slice(2, 9)}`).current;
const selectedValueText = useMemo(
() => getSelectedValueText(multi, selectedItem, selectedItems),
[multi, selectedItem, selectedItems]
);

return (
<>
{searchable ? (
<BaseInput
{...getInputProps({
"aria-labelledby": label ? getLabelProps().id : undefined,
"aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
placeholder: hasSelection ? "" : placeholder,
ref: inputRef,
...multipleSelectionDropdownProps
})}
inputRole="combobox"
value={inputValue || ""}
autoFocus={autoFocus}
size={inputSize || size}
className={cx(styles.inputWrapper, {
[styles.hasSelected]: !multi && selectedItem && !inputValue,
[styles.small]: inputSize === "small",
[styles.multi]: multi && hasSelection,
[styles.multiSelected]: multi && hasSelection && inputSize === "small",
[styles.fullWidth]: fullWidth
})}
disabled={disabled}
readOnly={readOnly}
/>
<>
<BaseInput
{...getInputProps({
"aria-labelledby": label ? getLabelProps().id : undefined,
"aria-label": inputAriaLabel || (label ? undefined : getLabelProps()?.id),
"aria-describedby": selectedValueText ? selectedValueId : undefined,
placeholder: hasSelection ? "" : placeholder,
ref: inputRef,
...multipleSelectionDropdownProps
})}
inputRole="combobox"
value={inputValue || ""}
autoFocus={autoFocus}
size={inputSize || size}
className={cx(styles.inputWrapper, {
[styles.hasSelected]: !multi && selectedItem && !inputValue,
[styles.small]: inputSize === "small",
[styles.multi]: multi && hasSelection,
[styles.multiSelected]: multi && hasSelection && inputSize === "small",
[styles.fullWidth]: fullWidth
})}
disabled={disabled}
readOnly={readOnly}
/>
<span id={selectedValueId} className={styles.visuallyHidden}>
{selectedValueText}
</span>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Action required

2. Random describedby id 🐞 Bug ☼ Reliability

DropdownInput generates selectedValueId using Math.random(), so the rendered
id/aria-describedby values are non-deterministic across renders. This can break SSR hydration
(server/client markup mismatch) and makes DOM output unstable for tests and automation relying on
consistent ids.
Agent Prompt
### Issue description
`DropdownInput` uses `Math.random()` to generate `selectedValueId`, which makes the DOM output non-deterministic. This can cause SSR hydration mismatch warnings/errors and creates unstable ids.

### Issue Context
The id is used by both `aria-describedby` on the `<input>` and the hidden `<span>` that provides the described-by text.

### Fix Focus Areas
- packages/core/src/components/Dropdown/components/Trigger/DropdownInput.tsx[1-83]

### Suggested change
- Replace the `useRef(Math.random()...)` id generation with `useId()` (React 18).
- If you want a more readable id, prefix it: `const selectedValueId = `dropdown-selected-${useId()}`;`.
- Update the React import accordingly (add `useId`, remove `useRef` usage for the id if no longer needed).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@github-actions

Copy link
Copy Markdown
Contributor

📦 Bundle Size Analysis

✅ No bundle size changes detected.

Unchanged Components
Component Base PR Diff
@vibe/button 17.3KB 17.29KB -9B 🟢
@vibe/clickable 5.95KB 5.96KB +3B 🔺
@vibe/dialog 52.14KB 52.16KB +14B 🔺
@vibe/icon-button 66.09KB 66.1KB +13B 🔺
@vibe/icon 12.92KB 12.89KB -32B 🟢
@vibe/layer 2.96KB 2.96KB 0B ➖
@vibe/layout 9.82KB 9.83KB +11B 🔺
@vibe/loader 5.64KB 5.65KB +10B 🔺
@vibe/tooltip 61.33KB 61.32KB -7B 🟢
@vibe/typography 63.47KB 63.43KB -47B 🟢
Accordion 6.31KB 6.29KB -22B 🟢
AccordionItem 66.43KB 66.34KB -93B 🟢
AlertBanner 70.78KB 70.76KB -22B 🟢
AlertBannerButton 18.79KB 18.75KB -42B 🟢
AlertBannerLink 15.26KB 15.23KB -30B 🟢
AlertBannerText 63.95KB 63.94KB -12B 🟢
AttentionBox 74.21KB 74.18KB -31B 🟢
Avatar 66.7KB 66.75KB +55B 🔺
AvatarGroup 93.2KB 93.21KB +8B 🔺
Badge 43.18KB 43.19KB +10B 🔺
BreadcrumbItem 64.69KB 64.64KB -43B 🟢
BreadcrumbMenu 68.58KB 68.59KB +15B 🔺
BreadcrumbMenuItem 77.16KB 77.01KB -150B 🟢
BreadcrumbsBar 5.7KB 5.68KB -13B 🟢
ButtonGroup 68.34KB 68.27KB -66B 🟢
Checkbox 66.9KB 66.83KB -73B 🟢
Chips 74.97KB 75.03KB +67B 🔺
ColorPicker 74.41KB 74.47KB +70B 🔺
ColorPickerContent 73.76KB 73.79KB +27B 🔺
Combobox 83.94KB 83.99KB +49B 🔺
Counter 42.24KB 42.28KB +38B 🔺
DatePicker 112.38KB 112.58KB +202B 🔺
Divider 5.42KB 5.43KB +13B 🔺
Dropdown 95.26KB 95.33KB +71B 🔺
EditableHeading 66.5KB 66.52KB +12B 🔺
EditableText 66.36KB 66.3KB -60B 🟢
EmptyState 70.37KB 70.39KB +23B 🔺
ExpandCollapse 66.26KB 66.19KB -73B 🟢
FormattedNumber 5.81KB 5.83KB +15B 🔺
GridKeyboardNavigationContext 4.65KB 4.65KB -4B 🟢
HiddenText 5.4KB 5.39KB -14B 🟢
Info 72.02KB 71.97KB -47B 🟢
Label 68.7KB 68.68KB -24B 🟢
Link 14.89KB 14.93KB +39B 🔺
List 72.83KB 72.89KB +63B 🔺
ListItem 65.46KB 65.58KB +120B 🔺
ListItemAvatar 66.92KB 66.86KB -63B 🟢
ListItemIcon 14.01KB 14.01KB 0B ➖
ListTitle 65.04KB 64.97KB -68B 🟢
Menu 8.65KB 8.63KB -21B 🟢
MenuDivider 5.57KB 5.56KB -9B 🟢
MenuGridItem 7.16KB 7.19KB +36B 🔺
MenuItem 76.93KB 76.87KB -65B 🟢
MenuItemButton 70.01KB 70.05KB +40B 🔺
MenuTitle 65.33KB 65.26KB -68B 🟢
MenuButton 66.04KB 66.02KB -21B 🟢
Modal 79.13KB 79.04KB -96B 🟢
ModalContent 4.72KB 4.71KB -1B 🟢
ModalHeader 65.78KB 65.77KB -14B 🟢
ModalMedia 7.51KB 7.52KB +12B 🔺
ModalFooter 67.65KB 67.7KB +44B 🔺
ModalFooterWizard 68.54KB 68.58KB +39B 🔺
ModalBasicLayout 8.91KB 8.92KB +9B 🔺
ModalMediaLayout 8.11KB 8.06KB -50B 🟢
ModalSideBySideLayout 6.29KB 6.3KB +5B 🔺
MultiStepIndicator 52.9KB 52.9KB -1B 🟢
NumberField 72.89KB 72.8KB -91B 🟢
ProgressBar 7.36KB 7.32KB -46B 🟢
RadioButton 65.89KB 65.88KB -15B 🟢
Search 70.6KB 70.64KB +36B 🔺
Skeleton 6KB 6.02KB +19B 🔺
Slider 73.95KB 73.89KB -61B 🟢
SplitButton 66.52KB 66.49KB -29B 🟢
SplitButtonMenu 8.8KB 8.77KB -27B 🟢
Steps 71.28KB 71.3KB +21B 🔺
Table 7.26KB 7.29KB +24B 🔺
TableBody 66.75KB 66.72KB -40B 🟢
TableCell 65.21KB 65.12KB -96B 🟢
TableContainer 5.31KB 5.32KB +3B 🔺
TableHeader 5.67KB 5.65KB -18B 🟢
TableHeaderCell 72.15KB 72.14KB -6B 🟢
TableRow 5.55KB 5.55KB +1B 🔺
TableRowMenu 68.87KB 68.79KB -84B 🟢
TableVirtualizedBody 71.48KB 71.39KB -96B 🟢
Tab 63.97KB 63.98KB +13B 🔺
TabList 8.87KB 8.86KB -11B 🟢
TabPanel 5.31KB 5.3KB -4B 🟢
TabPanels 5.85KB 5.85KB +2B 🔺
TabsContext 5.48KB 5.51KB +29B 🔺
TextArea 66.35KB 66.28KB -79B 🟢
TextField 69.51KB 69.38KB -136B 🟢
TextWithHighlight 64.25KB 64.26KB +9B 🔺
ThemeProvider 4.36KB 4.36KB -1B 🟢
Tipseen 71.03KB 71.16KB +132B 🔺
TipseenContent 71.58KB 71.57KB -2B 🟢
TipseenMedia 71.35KB 71.29KB -70B 🟢
TipseenWizard 73.79KB 73.78KB -11B 🟢
Toast 74.03KB 73.94KB -94B 🟢
ToastButton 18.59KB 18.62KB +33B 🔺
ToastLink 15.09KB 15.07KB -25B 🟢
Toggle 66.59KB 66.57KB -20B 🟢
TransitionView 5.45KB 5.44KB -10B 🟢
VirtualizedGrid 12.57KB 12.53KB -40B 🟢
VirtualizedList 12.28KB 12.28KB +2B 🔺
List (Next) 8.17KB 8.18KB +8B 🔺
ListItem (Next) 69.85KB 69.83KB -24B 🟢
ListTitle (Next) 65.31KB 65.29KB -17B 🟢

📊 Summary:

  • Total Base Size: 4.75MB
  • Total PR Size: 4.75MB
  • Total Difference: 1.05KB

@rivka-ungar rivka-ungar marked this pull request as draft April 26, 2026 08:16
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.

1 participant