Skip to content

Conversation

@tanujbhaud
Copy link
Contributor

@tanujbhaud tanujbhaud commented Nov 27, 2025

What

Partially fixes #12641

Why

When TypeScript properties are optional (prop?: Type), TypeScript adds | undefined to the union type. The enum detection logic in the TypeScript converter was failing because undefined is not a literal type, causing the union to not be recognized as an enum.

This resulted in controls displaying as "object" type instead of "select" dropdown for optional properties with union types, regardless of the number of items in the union.

How

The fix filters out undefined elements before checking if all remaining elements are literals. This allows optional unions to be correctly recognized as enums and display as select controls.

Changes

  • Modified code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
  • Filter undefined from union elements when checking for enum type
  • Use only non-undefined elements for enum value array

Example

Before this fix:

interface Props {
  myProperty?: 'k1' | 'k2' | ... | 'k29';  // Shows as "object" control
}

After this fix:

interface Props {
  myProperty?: 'k1' | 'k2' | ... | 'k29';  // Shows as "select" control
}

How to test manually

  1. Create a sandbox: yarn task --task dev --template react-vite/default-ts
  2. Create a component with optional union type props:
    interface Props {
    variant?: 'primary' | 'secondary' | 'tertiary';
    size: 'small' | 'medium' | 'large';
    manyOptions?: 'k1' | 'k2' | 'k3' | 'k4' | 'k5';
    }
  3. Create a story for the component and open it in Storybook
  4. Verify the Controls panel shows:
    - variant (optional) as radio buttons — not "object"
    - size (required) as radio buttons
    - manyOptions (optional) as a select dropdown — not "object"
  5. Verify you can select options and the component updates
  6. Verify the reset button (↺) clears optional props back to undefined

Checklist

  • Fix tested locally
  • Minimal, focused change
  • Backward compatible with required unions
  • Type errors resolved
  • Unit tests pass

Summary by CodeRabbit

  • Bug Fixes

    • Better detection and generation of enums from TypeScript unions that mix literals and undefined.
  • Changes

    • Union processing now ignores undefined values when deriving enums and treats literal members distinctly.
    • Parser expects union elements to be explicit (stricter union shape) and recognizes literal values separately for more accurate output.

✏️ Tip: You can customize this high-level summary in your review settings.


IssueHunt Summary

Referenced issues

This pull request has been submitted to:


…n types

Fixes storybookjs#12641

When TypeScript properties are optional (prop?: Type), TypeScript adds
| undefined to the union type. The enum detection logic was failing
because undefined is not a literal type, causing the union to not be
recognized as an enum.

This resulted in controls displaying as "object" type instead of "select"
dropdown for optional properties with union types, regardless of the
number of items in the union.

The fix filters out undefined elements before checking if all remaining
elements are literals, allowing optional unions to be correctly recognized
as enums and display as select controls.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 1, 2025

📝 Walkthrough

Walkthrough

Added literal/undefined type guards and refined union handling in the TypeScript argTypes converter to exclude undefined when detecting literal-only unions; also tightened type declarations by requiring elements on combination types and adding a separate TSLiteralType while including 'undefined' in scalar names.

Changes

Cohort / File(s) Summary
Union literal handling & helpers
code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
Added isLiteral and isUndefined type guards; updated union handling to filter out undefined elements before checking for all-literal unions, construct enums from non-undefined literal members, and fall back to previous mapping for other unions. Switched to block scoping and explicit casts when extracting literal values.
Type declarations update
code/core/src/docs-tools/argTypes/convert/typescript/types.ts
Made elements required on TSCombinationType (elements?: TSType[]elements: TSType[]); introduced TSLiteralType (name: 'literal', value: string); replaced 'literal' with 'undefined' in the TSScalarType name union; included TSLiteralType in the exported TSType union.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20–30 minutes

  • Review convert.ts union branch for correct handling of combinations like (literal | undefined), mixed literal+non-literal unions, and preservation of prior fallback behavior.
  • Verify isLiteral / isUndefined guards are exhaustive for all TSType variants used in conversion.
  • Check call sites and consumers for TSCombinationType to ensure they handle elements now being required.
  • Confirm other code paths that relied on 'literal' in TSScalarType names are updated to expect TSLiteralType or 'undefined' where applicable.
✨ Finishing touches
  • 📝 Generate docstrings

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.

Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (1)

44-58: Wrap the case block in braces to scope the variable declaration.

The static analysis tool correctly identifies that nonUndefinedElements should be block-scoped within the union case. While the current code works due to the return statements, it's a best practice to wrap case blocks containing variable declarations in braces.

Apply this diff to fix the scoping issue:

-    case 'union':
+    case 'union': {
       let result;
       // Filter out undefined from optional props when checking for enum
       const nonUndefinedElements = type.elements?.filter((element) => element.name !== 'undefined');
       if (nonUndefinedElements?.length > 0 && nonUndefinedElements.every((element) => element.name === 'literal')) {
         result = {
           ...base,
           name: 'enum',
           // @ts-expect-error fix types
           value: nonUndefinedElements.map((v) => parseLiteral(v.value)),
         };
       } else {
         result = { ...base, name, value: type.elements?.map(convert) };
       }
       return result;
+    }
🧹 Nitpick comments (1)
code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (1)

52-53: Consider fixing the TypeScript error instead of suppressing it.

The @ts-expect-error comment suggests that parseLiteral might expect a different type than what v.value provides. Consider adding proper type assertions or fixing the type definitions rather than suppressing the error.

If the type is truly incorrect, you could add a type assertion:

           // @ts-expect-error fix types
-          value: nonUndefinedElements.map((v) => parseLiteral(v.value)),
+          value: nonUndefinedElements.map((v) => parseLiteral(v.value as string)),

Or investigate whether the type definitions in types.ts or parseLiteral need to be updated.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b90e26e and 456b6cf.

📒 Files selected for processing (1)
  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESLint and Prettier configurations that are enforced in the codebase

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
code/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions that need to be tested from their modules

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
code/**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
🧠 Learnings (1)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
🪛 Biome (2.1.2)
code/core/src/docs-tools/argTypes/convert/typescript/convert.ts

[error] 47-47: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (1)
code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (1)

46-53: Add regression tests for TypeScript union type handling in optional properties.

This fix for optional properties with union types (#12641) should include unit tests to prevent regression. Test coverage should include:

  • Optional properties with literal unions (e.g., prop?: 'a' | 'b' | 'c')
  • Mixed unions with undefined (e.g., 'a' | 'b' | undefined)
  • Edge case: union containing only undefined

Check existing test coverage in code/core/src/docs-tools/argTypes/convert/typescript/ for these scenarios and add tests if absent.

Copy link
Member

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution @tanujbhaud!

Did you test whether it's possible to clear the select once an option has been selected?

We have another Select implementation in storybook/internal/components which has a reset option. @ghengeveld I'm curious to see what you think. Should we switch Form.Select to the newer Select with a reset option?

@mrginglymus
Copy link
Contributor

Thanks for the contribution @tanujbhaud!

Did you test whether it's possible to clear the select once an option has been selected?

We have another Select implementation in storybook/internal/components which has a reset option. @ghengeveld I'm curious to see what you think. Should we switch Form.Select to the newer Select with a reset option?

Oh my goodness please. This is a significant missing feature from controls, currently requireing awkward mappings to allow for either genuinely optional properties, or just the ability to test that defaults are corretly applied.

(Same would be nice for tri-state bools and radios, but I guess we can achieve that with a resettable select)

@Sidnioulz
Copy link
Member

Thanks for the contribution @tanujbhaud!
Did you test whether it's possible to clear the select once an option has been selected?
We have another Select implementation in storybook/internal/components which has a reset option. @ghengeveld I'm curious to see what you think. Should we switch Form.Select to the newer Select with a reset option?

Oh my goodness please. This is a significant missing feature from controls, currently requireing awkward mappings to allow for either genuinely optional properties, or just the ability to test that defaults are corretly applied.

(Same would be nice for tri-state bools and radios, but I guess we can achieve that with a resettable select)

Given the size of our user base, someone will unavoidably report that it's now impossible to clear that control without resetting all controls :) The requirement to allow clearing was already highlighted by the author of the bug report.

@nx-cloud
Copy link

nx-cloud bot commented Dec 5, 2025

View your CI Pipeline Execution ↗ for commit d837343


☁️ Nx Cloud last updated this comment at 2025-12-10 12:36:17 UTC

Copy link
Member

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

CI reports several type errors, and you stripped the part of the PR template requesting a manual testing procedure.

Please provide both automatic tests and a manual testing procedure we can replicate to validate the PR.

Thanks,

let result;
if (type.elements?.every((element) => element.name === 'literal')) {
// Filter out undefined from optional props when checking for enum
const nonUndefinedElements = type.elements?.filter((element) => element.name !== 'undefined');
Copy link
Member

Choose a reason for hiding this comment

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

The fact that this throws TS errors either means that TSType is wrong, or that the check isn't done correctly, or both.

My preference would go towards fixing TSType to actually match all the inputs that can be received at runtime (so we can discover other type bugs elsewhere).

Can you also please check if typeof element.raw is consistently undefined when element.name is 'undefined'? If so, then inspecting the type of raw (like done on line 29) could be safer.

if (type.elements?.every((element) => element.name === 'literal')) {
// Filter out undefined from optional props when checking for enum
const nonUndefinedElements = type.elements?.filter((element) => element.name !== 'undefined');
if (nonUndefinedElements?.length > 0 && nonUndefinedElements.every((element) => element.name === 'literal')) {
Copy link
Member

Choose a reason for hiding this comment

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

Type errors here if nonUndefinedElements is undefined.

Comment on lines 52 to 53
// @ts-expect-error fix types
value: type.elements?.map((v) => parseLiteral(v.value)),
value: nonUndefinedElements.map((v) => parseLiteral(v.value)),
Copy link
Member

Choose a reason for hiding this comment

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

This might be fixable if the code above used type guard functions. The fact that you found a type bug in a code path with @ts-expect-error hints that we really want to remove @ts-expect-error and actually get types right this time (without any casting, just with type guards).

- Add type guard functions (isLiteral, isUndefined) for better type narrowing
- Add 'undefined' to TSScalarType union in types.ts
- Make elements non-optional in TSCombinationType (unions always have elements)
- Wrap case block in braces to fix Biome linting error
- Use proper type assertion instead of @ts-expect-error
@tanujbhaud tanujbhaud force-pushed the fix/controls-optional-union-enum branch from 0e34418 to d3b4545 Compare December 8, 2025 20:23
@tanujbhaud
Copy link
Contributor Author

tanujbhaud commented Dec 8, 2025

Hi @Sidnioulz, thanks for the review!

I've addressed the feedback:

  1. Clear/reset functionality - Tested and confirmed working. The reset button (↺) in the toolbar clears
    optional props back to undefined.

  2. Type errors - Fixed by:

    • Adding 'undefined' to TSScalarType in types.ts so it matches runtime inputs
    • Making elements non-optional in TSCombinationType (unions/intersections always have elements), which
      fixes the "nonUndefinedElements is undefined" issue
  3. Type guards instead of @ts-expect-error - Added isLiteral() and isUndefined() type guard functions
    to properly narrow types without casting or suppressing errors

  4. Manual testing procedure - Added to the PR description

Let me know if anything else is needed!

Copy link
Contributor

@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: 0

🧹 Nitpick comments (2)
code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (2)

7-9: Consider using proper TypeScript type guard syntax for automatic narrowing.

The comment says "Type guards for narrowing" but these are boolean predicates, not TypeScript type guards. Using the is keyword would enable automatic type narrowing and eliminate the need for the explicit cast on line 58.

-// Type guards for narrowing TSType discriminant unions
-const isLiteral = (type: TSType): boolean => type.name === 'literal';
-const isUndefined = (type: TSType): boolean => type.name === 'undefined';
+// Type guards for narrowing TSType discriminant unions
+const isLiteral = (type: TSType): type is Extract<TSType, { name: 'literal' }> =>
+  type.name === 'literal';
+const isUndefined = (type: TSType): type is Extract<TSType, { name: 'undefined' }> =>
+  type.name === 'undefined';

With proper type guards, the cast on line 58 could be removed since TypeScript would infer the narrowed type after the every(isLiteral) check when iterating with map.


56-60: The type assertion is safe but could be eliminated with proper type guards.

The cast to Extract<typeof element, { name: 'literal' }> is logically safe because allLiterals guarantees all nonUndefinedElements have name === 'literal'. However, TypeScript can't infer this without proper type guard syntax.

If you adopt the type guard refactor suggested above, this block simplifies to:

-          value: nonUndefinedElements.map((element) => {
-            // We know element is a literal type because of the allLiterals check
-            const literalElement = element as Extract<typeof element, { name: 'literal' }>;
-            return parseLiteral(literalElement.value);
-          }),
+          value: nonUndefinedElements.map((element) => parseLiteral(element.raw!)),

Note: This assumes raw contains the literal value. If value is a separate property on literal types not currently in the type definition, you'd need to update TSScalarType to include it when name === 'literal'.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 456b6cf and d3b4545.

📒 Files selected for processing (2)
  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (2 hunks)
  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESLint and Prettier configurations that are enforced in the codebase

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
code/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions that need to be tested from their modules

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
code/**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Select/Select.tsx:200-204
Timestamp: 2025-11-05T09:38:47.712Z
Learning: Repo: storybookjs/storybook — Guidance: Until Storybook 11 is released, do not suggest using React.useId anywhere (e.g., in code/core/src/components/components/Select/Select.tsx) to maintain compatibility with React 17 runtimes. Prefer advising: accept a caller-provided props.id and, if needed, generate a client-only fallback id to minimize SSR hydration issues — but avoid useId. Resume prompting for useId after Storybook 11.
📚 Learning: 2025-11-28T14:50:24.889Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T14:50:24.889Z
Learning: Applies to **/*.{ts,tsx} : Enable TypeScript strict mode

Applied to files:

  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
🧬 Code graph analysis (1)
code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (1)
code/core/src/docs-tools/argTypes/convert/typescript/types.ts (1)
  • TSType (46-46)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (4)
code/core/src/docs-tools/argTypes/convert/typescript/types.ts (2)

12-12: LGTM! Making elements required is the right fix.

This addresses the past review feedback about type errors when nonUndefinedElements could be undefined. Now type.elements is guaranteed to exist, enabling safe filtering and mapping in convert.ts.


36-36: LGTM! Adding 'undefined' to scalar names enables proper type narrowing.

This allows the isUndefined type guard to correctly identify undefined elements in unions, addressing the past feedback about fixing TSType to match runtime inputs rather than using @ts-expect-error.

code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (2)

48-64: Core logic correctly handles optional union types.

The approach is sound:

  1. Filter out undefined elements first
  2. Check if all remaining elements are literals
  3. Build enum from non-undefined elements only, or fall back to original behavior

This correctly fixes the issue where optional props (prop?: 'a' | 'b') were displayed as "object" controls instead of "select".


58-59: No action needed. The value property is properly defined on Storybook's literal type definitions (as part of SBEnumType and SBUnionType in the type system). The type cast correctly narrows the type and accesses a property that exists in the type definitions. The type definitions are complete and not incomplete as originally suggested.

@tanujbhaud tanujbhaud requested a review from Sidnioulz December 8, 2025 20:34
- Separate TSLiteralType from TSScalarType to include 'value' property
- Use proper TypeScript type guard syntax (type is X) for narrowing
- Filter with type guard to get properly typed literal elements
- Removes need for explicit type casting
Copy link
Contributor

@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: 0

🧹 Nitpick comments (1)
code/core/src/docs-tools/argTypes/convert/typescript/types.ts (1)

35-42: Consider broadening TSLiteralType.value for non‑string literal support

If these types ever need to represent numeric/boolean/null literals (not just string keys), value: string loses some information and allows mismatches. You might future‑proof this as string | number | boolean | null (or unknown) and narrow where needed; if the intent is to normalize everything to strings for controls, a brief comment here stating that invariant would help future readers.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3b4545 and d837343.

📒 Files selected for processing (2)
  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts (2 hunks)
  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/docs-tools/argTypes/convert/typescript/convert.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESLint and Prettier configurations that are enforced in the codebase

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
code/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions that need to be tested from their modules

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
code/**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing

Files:

  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
📚 Learning: 2025-11-28T14:50:24.889Z
Learnt from: CR
Repo: storybookjs/storybook PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-28T14:50:24.889Z
Learning: Applies to **/*.{ts,tsx} : Enable TypeScript strict mode

Applied to files:

  • code/core/src/docs-tools/argTypes/convert/typescript/types.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: normal
🔇 Additional comments (2)
code/core/src/docs-tools/argTypes/convert/typescript/types.ts (2)

10-13: Required elements on TSCombinationType aligns with usage; just confirm no callers omit it

Making elements mandatory better reflects how unions/intersections are actually used and removes the need for defensive checks under strict mode. Please double‑check any code constructing TSCombinationType manually (tests, fixtures, converters) to ensure they always populate elements so the declared type stays truthful.


49-51: Updated TSType union – ensure all discriminated usages now handle TSLiteralType explicitly

Adding TSLiteralType to TSType (and removing 'literal' from TSScalarType) is a clean separation, but anywhere that switches on type.name or assumes 'literal' is a scalar should be reviewed to handle the new literal branch via TSLiteralType. In particular, verify any exhaustive switch/if‑else chains over TSType still compile exhaustively and don’t fall through to never/default cases unexpectedly.

@Sidnioulz
Copy link
Member

Hi @Sidnioulz, thanks for the review!

I've addressed the feedback:

  1. Clear/reset functionality - Tested and confirmed working. The reset button (↺) in the toolbar clears
    optional props back to undefined.

  2. Type errors - Fixed by:

    • Adding 'undefined' to TSScalarType in types.ts so it matches runtime inputs
    • Making elements non-optional in TSCombinationType (unions/intersections always have elements), which
      fixes the "nonUndefinedElements is undefined" issue
  3. Type guards instead of @ts-expect-error - Added isLiteral() and isUndefined() type guard functions
    to properly narrow types without casting or suppressing errors

  4. Manual testing procedure - Added to the PR description

Let me know if anything else is needed!

Thanks @tanujbhaud! Looks good to me, let's now see how CI behaves. Some tests are known to be flaky so you might see us update your branch and re-run CI a couple of times.

@Sidnioulz Sidnioulz changed the title Fix controls displaying as object instead of select for optional union types Bug: Fix controls displaying as object instead of select for optional union types Dec 10, 2025
@Sidnioulz
Copy link
Member

@tanujbhaud by the way, this issue was funded on IssueHunt, so feel free to register your PR on the IssueHunt issue. I've no idea how this stuff actually works but let's try and ensure that reward goes to you if possible :)

@tanujbhaud
Copy link
Contributor Author

@Sidnioulz Thanks for the heads up! I'll check out IssueHunt

@valentinpalkovic valentinpalkovic changed the title Bug: Fix controls displaying as object instead of select for optional union types Controls: Fix displaying as object instead of select for optional union types Dec 10, 2025
@valentinpalkovic valentinpalkovic merged commit 8b278b0 into storybookjs:next Dec 10, 2025
54 checks passed
@tanujbhaud
Copy link
Contributor Author

@valentinpalkovic @Sidnioulz Hey, just wanted to follow up, I submitted the PR on IssueHunt. Does it need an approval on your end there?

@Sidnioulz
Copy link
Member

@shilman seems you are the project owner on IssueHunt. Are you able to act on the issue there?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Problem with storybook-controls of type "select"

5 participants