Skip to content

Controls: Fix select controls for optional Flow unions and nested options#34895

Closed
Pulkit7070 wants to merge 1 commit into
storybookjs:nextfrom
Pulkit7070:fix/select-controls-optional-union-nested-options
Closed

Controls: Fix select controls for optional Flow unions and nested options#34895
Pulkit7070 wants to merge 1 commit into
storybookjs:nextfrom
Pulkit7070:fix/select-controls-optional-union-nested-options

Conversation

@Pulkit7070

@Pulkit7070 Pulkit7070 commented May 24, 2026

Copy link
Copy Markdown

What

Fixes two separate bugs that caused select controls to silently break (issue #12641):

Bug 1 — Flow optional union types fall back to object control

Root cause: Flow represents optional types (?'sm' | 'md' | 'lg') as a union containing void and null elements. The every(isLiteral) check failed on these non-literal elements, so the converter fell back to a generic union/object control instead of a select.

Fix: Filter out void and null elements before the isLiteral check in convert/flow/convert.ts, matching the approach already applied to the TypeScript converter.

File: code/core/src/docs-tools/argTypes/convert/flow/convert.ts

Bug 2 — Options nested inside control object are silently ignored

Root cause: Users commonly write:

argTypes: { size: { control: { type: 'select', options: ['sm', 'md', 'lg'] } } }

instead of the documented top-level form:

argTypes: { size: { control: { type: 'select' }, options: ['sm', 'md', 'lg'] } }

The nested options were passed through as part of the control object but never surfaced to the controls addon, resulting in an empty select dropdown.

Fix: In normalizeInputType, when control is an object with an options property and no top-level options is present, hoist options to the top level and strip it from the control object.

File: code/core/src/preview-api/modules/store/csf/normalizeInputTypes.ts

Tests

  • New test file convert/flow/convert.test.ts with 4 cases covering the void/null filtering
  • Two new cases in normalizeInputTypes.test.ts covering options hoisting and precedence

Manual testing

  1. Create a story with a Flow-typed optional union prop: ?('sm' | 'md' | 'lg')

    • Before fix: Controls panel shows an object input
    • After fix: Controls panel shows a select dropdown with sm/md/lg options
  2. Define argTypes with options nested inside control:

    argTypes: { size: { control: { type: 'select', options: ['sm', 'md', 'lg'] } } }
    • Before fix: select dropdown renders with no options
    • After fix: select dropdown renders with sm/md/lg options

Checklist

  • Unit tests added for both fixes
  • No breaking changes (options hoisting only applies when top-level options is absent)

Fixes #12641

…d options

Two bugs caused the select control to break:

1. Flow optional union types (?'sm' | 'md' | 'lg') include void and null
   elements. The isLiteral check failed causing fallback to object control.
   Fix: filter void/null before the every(isLiteral) check.

2. Users commonly write { control: { type: 'select', options: [...] } }
   instead of the documented top-level options pattern. The nested options
   were silently ignored. Fix: hoist options from inside control to the
   top level in normalizeInputType when not already present.

Fixes storybookjs#12641
@github-actions

github-actions Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor
Fails
🚫

PR is not labeled with one of: ["ci:normal","ci:merged","ci:daily","ci:docs"]

Generated by 🚫 dangerJS against 9c7c6b4

@coderabbitai

coderabbitai Bot commented May 24, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2e72da5d-fa0f-48af-be1e-96a2d64fde2f

📥 Commits

Reviewing files that changed from the base of the PR and between 1411339 and 9c7c6b4.

📒 Files selected for processing (4)
  • code/core/src/docs-tools/argTypes/convert/flow/convert.test.ts
  • code/core/src/docs-tools/argTypes/convert/flow/convert.ts
  • code/core/src/preview-api/modules/store/csf/normalizeInputTypes.test.ts
  • code/core/src/preview-api/modules/store/csf/normalizeInputTypes.ts

📝 Walkthrough

Walkthrough

This PR updates argTypes conversion and normalization in two complementary ways. Flow union conversion now filters nullable markers before determining enum eligibility, improving union-to-enum detection. Input type normalization hoists options from nested control objects to the top level, simplifying option specification and ensuring correct precedence when both sources exist.

Changes

ArgTypes conversion and normalization

Layer / File(s) Summary
Flow union-to-enum conversion with nullable filtering
code/core/src/docs-tools/argTypes/convert/flow/convert.ts, code/core/src/docs-tools/argTypes/convert/flow/convert.test.ts
Flow union conversion filters out void and null before checking if remaining elements are literals; if all remaining elements are literals, the union is converted to enum; otherwise falls back to union. Tests verify literal unions convert to enums, optional literal unions are filtered to remove nullable markers, and non-literal unions correctly fall back.
Input options hoisting and effective control normalization
code/core/src/preview-api/modules/store/csf/normalizeInputTypes.ts, code/core/src/preview-api/modules/store/csf/normalizeInputTypes.test.ts
Input type normalization now hoists options from a nested control object to the top level when no top-level options exists, using derived effectiveRest and effectiveControl values. Tests verify that nested control.options are hoisted to the top level with control.disable set to false, and that existing top-level options take precedence and are not overwritten.

Possibly related PRs

  • storybookjs/storybook#33729: Modifies the same normalizeInputTypes.ts module to adjust control normalization so disable: false is applied when a story-level control is specified without disable, directly related to control override behavior.
  • storybookjs/storybook#33200: Updates TypeScript union-to-enum logic to ignore undefined markers (parallel to this PR's Flow void/null filtering) before checking if remaining union members are literals.

🎯 2 (Simple) | ⏱️ ~12 minutes


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@Pulkit7070 Pulkit7070 changed the title fix(controls): fix select controls for optional Flow unions and nested options Controls: Fix select controls for optional Flow unions and nested options May 24, 2026
@valentinpalkovic valentinpalkovic moved this to Empathy Queue (prioritized) in Core Team Projects May 26, 2026
@Sidnioulz Sidnioulz self-assigned this May 28, 2026
@Sidnioulz

Copy link
Copy Markdown
Contributor

Thanks for your contribution.

The PR addresses a fixed bug, therefore it will be closed. I have verified that the bugfix works according to the bug report author's instructions, and it does. Please reopen an issue with a minimal reproduction example before opening a new PR for this bug.

@Sidnioulz Sidnioulz closed this Jun 1, 2026
@github-project-automation github-project-automation Bot moved this from Empathy Queue (prioritized) to Done in Core Team Projects Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Problem with storybook-controls of type "select"

3 participants