Skip to content

Add dialog component#76

Merged
Aunshon merged 20 commits intomainfrom
add-dialog-component
Apr 3, 2026
Merged

Add dialog component#76
Aunshon merged 20 commits intomainfrom
add-dialog-component

Conversation

@Aunshon
Copy link
Copy Markdown
Collaborator

@Aunshon Aunshon commented Apr 2, 2026

Summary by CodeRabbit

  • New Features

    • Added Command, SmartSelect, and SmartMultiSelect controls with searchable, grouped, async, and create-on-the-fly flows; new combobox/select variants, chips multi-select, and many UI stories.
    • Added many new theme options and corresponding toolbar selections.
  • Bug Fixes

    • Improved invalid/disabled visuals and trigger behavior for more consistent interaction and accessibility.
  • Documentation

    • Large Storybook expansion: many interactive demos, dialog/RTL/shortcut variants, and enhanced story source previews.

Aunshon added 14 commits April 2, 2026 20:54
- Added new stories for Select component including WithGroups, WithLabel, SmallSize, DisabledItems, Controlled, Disabled, and Scrollable.
- Updated Select component to support grouped items and labels.
- Introduced AsyncCombobox component for dynamic searching with simulated API calls.
- Created Command component with various subcomponents for command palette functionality.
- Added Command stories demonstrating usage of Command, CommandDialog, and CommandGroups.
- Improved Popover styling in PopoverContent for better visual consistency.
…ive stories

- Introduced SmartMultiSelect component for multi-selection with async capabilities and custom rendering options.
- Added SmartSelect component for single selection with search functionality and grouping options.
- Created detailed Storybook stories for both components showcasing various use cases including async search, custom labels, and error handling.
- Updated index.ts to export new components and their types.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 2, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 452e2e20-b443-4260-8f46-9710ebe3366f

📥 Commits

Reviewing files that changed from the base of the PR and between 6399e0a and 7ca9cf5.

📒 Files selected for processing (2)
  • .storybook/preview.js
  • src/themes/index.ts

📝 Walkthrough

Walkthrough

Adds cmdk dependency; introduces Command wrappers and new SmartSelect/SmartMultiSelect components with async search, create flows, grouping, keyboard behavior; expands Storybook stories and tooling (plugin + preview brands + themes); tweaks Combobox/Popover styling and re-exports new UI primitives. (33 words)

Changes

Cohort / File(s) Summary
Dependency
package.json
Added dependency: cmdk ^1.1.1.
Command primitives & wrappers
src/components/ui/command.tsx, src/components/ui/Command.stories.tsx
New command.tsx wraps cmdk primitives into styled components (Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandSeparator, CommandItem, CommandShortcut). Added stories covering dialog, shortcuts, groups, and scrolling.
Smart select components
src/components/ui/smart-select.tsx, src/components/ui/smart-multi-select.tsx, src/components/ui/SmartSelect.stories.tsx, src/components/ui/SmartMultiSelect.stories.tsx
Added SmartSelect (single-select, async debounce, grouping, create flows, custom render) and SmartMultiSelect (multi-select, badges, select-all, async caching, create flows, keyboard handling). Storybook modules include many sync/async/create/visual variants.
Combobox & Popover adjustments
src/components/ui/combobox.tsx, src/components/ui/popover.tsx
Combobox: ComboboxChips converted to forwardRef, aria-invalid styling overrides added, ComboboxTrigger now receives disabled when shown. Popover: PopoverContent ring class changed from ring-foreground/10 to ring-border.
UI index exports
src/components/ui/index.ts
Re-exported new components and types: SmartSelect, SmartMultiSelect, and Command wrappers/types.
Combobox/Select story refactors & additions
src/components/ui/Combobox.stories.tsx, src/components/ui/Select.stories.tsx
Reworked Combobox stories into demo components and added many variants (popovers, menus, RTL, auto-highlight, chips, etc.). Select stories extended with groups, sizes, disabled/controlled/scrollable demos.
New Story modules
src/components/ui/Command.stories.tsx, src/components/ui/Smart*.stories.tsx
Added full Storybook coverage files for Command, SmartSelect, and SmartMultiSelect with numerous interactive examples.
Storybook tooling & preview
.storybook/babel-plugin-story-source.js, .storybook/main.ts, .storybook/preview.js
Added a custom Babel plugin to inject wrapper source into story parameters and registered it in Storybook loader; expanded preview toolbar brand items and mapped many new brand themes to theme tokens.
Themes
src/themes/index.ts
Added multiple new light/dark theme token exports (e.g., claudeTheme/claudeDarkTheme, claymorphismTheme/claymorphismDarkTheme, cleanSlateTheme/... etc.).
Misc (small tweaks)
src/components/ui/...
Minor styling/implementation adjustments across UI primitives (class tweaks, forwardRef usage, small logic changes).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Trigger as Trigger/Button
    participant UI as SmartSelect/SmartMultiSelect
    participant Cmd as Command (cmdk)
    participant Async as Async Search

    User->>Trigger: Click / type
    Trigger->>UI: open + forward input
    UI->>Cmd: render Command input/list
    alt async mode
        UI->>Async: debounce -> onSearch(query)
        Async-->>UI: loading -> results / error
    else sync mode
        UI-->>UI: filter local options
    end
    UI->>Cmd: populate items (groups, create, empty)
    Cmd-->>User: show list, accept selection/create
    User->>Cmd: select item or submit create
    Cmd->>UI: selection/create event
    UI->>UI: update value(s), optionally select-on-create
    UI-->>Trigger: update display, close popover
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped in with cmdk this morn,
New selects and badges gladly born,
Async queries hum and play,
Create flows blossom on display,
A tiny rabbit cheers — UI bright as dawn!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'Add dialog component' is misleading and does not accurately reflect the primary changes in the changeset. Revise the title to accurately describe the main contribution, such as 'Add Command and SmartSelect/SmartMultiSelect components with Storybook documentation and theme support' to better reflect the scope of changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 56.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-dialog-component

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
Copy Markdown

@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

🧹 Nitpick comments (2)
src/components/ui/smart-select.tsx (1)

340-342: Avoid unsafe type casting for event handlers.

The cast as unknown as React.MouseEvent is a type safety workaround. Consider using a shared handler that works with both event types, or separate handlers.

💡 Cleaner approach
+          const handleClearInteraction = (e: React.SyntheticEvent) => {
+            e.stopPropagation()
+            if ('preventDefault' in e) e.preventDefault()
+            onValueChange?.("")
+          }
+
           <span
             role="button"
             tabIndex={-1}
             className="rounded-sm opacity-50 hover:opacity-100 cursor-pointer"
-            onClick={handleClear}
+            onClick={handleClearInteraction}
             onKeyDown={(e) => {
-              if (e.key === "Enter" || e.key === " ") handleClear(e as unknown as React.MouseEvent)
+              if (e.key === "Enter" || e.key === " ") handleClearInteraction(e)
             }}
           >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/smart-select.tsx` around lines 340 - 342, The onKeyDown
handler is using an unsafe cast (as unknown as React.MouseEvent) when calling
handleClear; instead update the handlers so types align: either overload or
change handleClear's signature to accept both React.MouseEvent and
React.KeyboardEvent (e.g., React.SyntheticEvent) or add a dedicated keyboard
handler (e.g., handleClearKey) that calls the existing handleClear without
casting; update the onKeyDown prop to call the new keyboard-specific handler and
keep the click handler using handleClear to preserve correct event types and
type safety around the handleClear symbol used in this component.
src/components/ui/combobox.tsx (1)

240-257: Simplify the type definition for ComboboxChips.

The type intersection React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> & ComboboxPrimitive.Chips.Props is likely redundant. ComponentPropsWithRef already extracts props from the component type, so the additional & ComboboxPrimitive.Chips.Props may be unnecessary.

💡 Suggested simplification
 const ComboboxChips = React.forwardRef<
   HTMLDivElement,
-  React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
-    ComboboxPrimitive.Chips.Props
+  ComboboxPrimitive.Chips.Props
 >(({ className, ...props }, ref) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/combobox.tsx` around lines 240 - 257, The prop type for
ComboboxChips is overly verbose: remove the redundant intersection and use only
React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> as the forwardRef
generic and parameter type (replace the current
"React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
ComboboxPrimitive.Chips.Props" with React.ComponentPropsWithRef<typeof
ComboboxPrimitive.Chips>). Update the ComboboxChips forwardRef declaration and
its props destructuring accordingly while keeping the component implementation
and ComboboxChips.displayName unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ui/smart-multi-select.tsx`:
- Around line 298-304: The onKeyDown handler attached to the SmartMultiSelect
currently always calls e.stopPropagation(), which blocks Tab navigation; update
the handler (the inline onKeyDown and its use of handleDefaultCreate) to only
call e.stopPropagation() for keys that should be suppressed (e.g., when e.key
=== "Enter" or other non-Tab keys) and let Tab (and any navigation keys)
propagate normally; ensure Enter still calls e.preventDefault() and
handleDefaultCreate() while only stopping propagation for that case so keyboard
navigation is preserved.

In `@src/components/ui/smart-select.tsx`:
- Around line 286-292: The onKeyDown handler currently calls e.stopPropagation()
for all keys which breaks keyboard navigation; update the handler attached to
the SmartSelect component so it only calls e.preventDefault() and
e.stopPropagation() for the specific keys that require interception (e.g.,
"Enter" and optionally "Escape") and leaves other keys (Tab, Arrow keys, etc.)
to propagate normally; locate the onKeyDown handler and adjust the logic around
handleDefaultCreate() to conditionally stop propagation only when e.key ===
"Enter" (and add similar branches for "Escape" if needed).

In `@src/components/ui/SmartMultiSelect.stories.tsx`:
- Around line 491-532: The MultiComplexCreateForm collects a description but
doesn't include it when creating the new option; update the onClick handler that
calls onCreated inside MultiComplexCreateForm to pass the description (e.g.,
include a description property alongside value and label, using
description.trim() or the description state) so the created option matches
ComplexCreateForm's shape; locate the onClick that calls onCreated({ value:
name.toLowerCase(), label: name }) and extend the object to include description.

---

Nitpick comments:
In `@src/components/ui/combobox.tsx`:
- Around line 240-257: The prop type for ComboboxChips is overly verbose: remove
the redundant intersection and use only React.ComponentPropsWithRef<typeof
ComboboxPrimitive.Chips> as the forwardRef generic and parameter type (replace
the current "React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
ComboboxPrimitive.Chips.Props" with React.ComponentPropsWithRef<typeof
ComboboxPrimitive.Chips>). Update the ComboboxChips forwardRef declaration and
its props destructuring accordingly while keeping the component implementation
and ComboboxChips.displayName unchanged.

In `@src/components/ui/smart-select.tsx`:
- Around line 340-342: The onKeyDown handler is using an unsafe cast (as unknown
as React.MouseEvent) when calling handleClear; instead update the handlers so
types align: either overload or change handleClear's signature to accept both
React.MouseEvent and React.KeyboardEvent (e.g., React.SyntheticEvent) or add a
dedicated keyboard handler (e.g., handleClearKey) that calls the existing
handleClear without casting; update the onKeyDown prop to call the new
keyboard-specific handler and keep the click handler using handleClear to
preserve correct event types and type safety around the handleClear symbol used
in this component.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 49f363bd-a9c0-49e0-96c9-fd9f583c754b

📥 Commits

Reviewing files that changed from the base of the PR and between ff436c0 and 2a83360.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (12)
  • package.json
  • src/components/ui/Combobox.stories.tsx
  • src/components/ui/Command.stories.tsx
  • src/components/ui/Select.stories.tsx
  • src/components/ui/SmartMultiSelect.stories.tsx
  • src/components/ui/SmartSelect.stories.tsx
  • src/components/ui/combobox.tsx
  • src/components/ui/command.tsx
  • src/components/ui/index.ts
  • src/components/ui/popover.tsx
  • src/components/ui/smart-multi-select.tsx
  • src/components/ui/smart-select.tsx

Comment on lines +298 to +304
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
handleDefaultCreate()
}
e.stopPropagation()
}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same stopPropagation accessibility concern as SmartSelect.

Blanket e.stopPropagation() prevents Tab navigation. Apply the same fix as suggested for SmartSelect.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/smart-multi-select.tsx` around lines 298 - 304, The
onKeyDown handler attached to the SmartMultiSelect currently always calls
e.stopPropagation(), which blocks Tab navigation; update the handler (the inline
onKeyDown and its use of handleDefaultCreate) to only call e.stopPropagation()
for keys that should be suppressed (e.g., when e.key === "Enter" or other
non-Tab keys) and let Tab (and any navigation keys) propagate normally; ensure
Enter still calls e.preventDefault() and handleDefaultCreate() while only
stopping propagation for that case so keyboard navigation is preserved.

Comment on lines +286 to +292
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
handleDefaultCreate()
}
e.stopPropagation()
}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reconsider blanket stopPropagation() on keyDown.

Calling e.stopPropagation() for all key events prevents keyboard navigation (like Tab) from working properly, which impacts accessibility. Consider only stopping propagation for specific keys that need it (e.g., Enter, Escape).

🛠️ Suggested fix
           onKeyDown={(e) => {
             if (e.key === "Enter") {
               e.preventDefault()
               handleDefaultCreate()
+              e.stopPropagation()
             }
-            e.stopPropagation()
           }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
handleDefaultCreate()
}
e.stopPropagation()
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
handleDefaultCreate()
e.stopPropagation()
}
}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/smart-select.tsx` around lines 286 - 292, The onKeyDown
handler currently calls e.stopPropagation() for all keys which breaks keyboard
navigation; update the handler attached to the SmartSelect component so it only
calls e.preventDefault() and e.stopPropagation() for the specific keys that
require interception (e.g., "Enter" and optionally "Escape") and leaves other
keys (Tab, Arrow keys, etc.) to propagate normally; locate the onKeyDown handler
and adjust the logic around handleDefaultCreate() to conditionally stop
propagation only when e.key === "Enter" (and add similar branches for "Escape"
if needed).

Comment on lines +491 to +532
}) {
const [name, setName] = useState(ctx.searchValue);
const [description, setDescription] = useState("");
return (
<div className="p-3 border-t border-border space-y-3">
<p className="text-sm font-medium">Create new framework</p>
<div className="space-y-1.5">
<Label className="text-xs">Name</Label>
<Input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Framework name..."
autoFocus
onKeyDown={(e) => e.stopPropagation()}
/>
</div>
<div className="space-y-1.5">
<Label className="text-xs">Description</Label>
<Input
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="Short description..."
onKeyDown={(e) => e.stopPropagation()}
/>
</div>
<div className="flex gap-2 justify-end pt-1">
<Button variant="outline" size="sm" onClick={() => ctx.clearSearch()}>
Cancel
</Button>
<Button
size="sm"
disabled={!name.trim()}
onClick={() => {
onCreated({ value: name.toLowerCase(), label: name });
ctx.clearSearch();
}}
>
Create
</Button>
</div>
</div>
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Description field value is collected but not used in created option.

The MultiComplexCreateForm captures a description state (line 493) and renders an input for it (lines 508-514), but the created option at lines 523-524 doesn't include the description:

onCreated({ value: name.toLowerCase(), label: name });

This is inconsistent with ComplexCreateForm in SmartSelect.stories.tsx which correctly includes description in the created option.

🔧 Proposed fix
           onClick={() => {
-            onCreated({ value: name.toLowerCase(), label: name });
+            onCreated({
+              value: name.toLowerCase(),
+              label: name,
+              description: description || undefined,
+            });
             ctx.clearSearch();
           }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/SmartMultiSelect.stories.tsx` around lines 491 - 532, The
MultiComplexCreateForm collects a description but doesn't include it when
creating the new option; update the onClick handler that calls onCreated inside
MultiComplexCreateForm to pass the description (e.g., include a description
property alongside value and label, using description.trim() or the description
state) so the created option matches ComplexCreateForm's shape; locate the
onClick that calls onCreated({ value: name.toLowerCase(), label: name }) and
extend the object to include description.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
src/components/ui/SmartMultiSelect.stories.tsx (1)

434-435: Same stopPropagation() accessibility issue in story demo.

The onKeyDown={(e) => e.stopPropagation()} on line 434 unconditionally blocks all key events including Tab. For consistency with the fix needed in the component, only stop propagation for keys that need it (e.g., Enter, Escape).

🔧 Suggested fix
-        onKeyDown={(e) => e.stopPropagation()}
+        onKeyDown={(e) => {
+          if (e.key === "Escape") e.stopPropagation();
+        }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/SmartMultiSelect.stories.tsx` around lines 434 - 435, The
story demo currently uses onKeyDown={(e) => e.stopPropagation()} which blocks
all keyboard events (including Tab); update the onKeyDown handler in the
SmartMultiSelect story so it only calls e.stopPropagation() for specific keys
(e.g., when e.key is "Enter" or "Escape") and otherwise lets events propagate
(so Tab and other navigation keys still work); locate the onKeyDown prop in
SmartMultiSelect.stories.tsx and replace the unconditional stopPropagation with
a conditional check for e.key values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/ui/smart-select.tsx`:
- Around line 289-293: The input for the create form (the JSX <input> that uses
value={search} and onChange={(e) => setSearch(e.target.value)}) is missing an
accessible name; add an aria-label attribute to that input (e.g.,
aria-label="Search or create name" or similar descriptive text) so screen
readers can convey the field's purpose while keeping the existing value/search
and setSearch handlers intact.
- Around line 379-391: The create-toggle Button rendered when
hasCreateCapability is missing an accessible label; update the Button in the
SmartSelect component (the element using hasCreateCapability, setShowCreate,
showCreate and toggling between XIcon and PlusIcon) to include an appropriate
aria-label (e.g. "Close create form" when showCreate is true and "Open create
form" when false) so screen readers can convey the button's purpose; ensure the
aria-label updates based on showCreate to reflect the current action.
- Around line 347-358: The clear control is not keyboard focusable because it
uses span with tabIndex={-1} and an unsafe cast when calling handleClear;
replace the span with a semantic <button> (or set tabIndex={0}) so it is
reachable and operable via keyboard, remove role="button" and the onKeyDown type
coercion, and wire onClick to handleClear directly; also update handleClear's
signature if needed to accept the correct event type (e.g., React.MouseEvent |
React.KeyboardEvent) so no casts are required — reference the clear element
using the existing handleClear and XIcon identifiers and the surrounding
className attributes.

---

Nitpick comments:
In `@src/components/ui/SmartMultiSelect.stories.tsx`:
- Around line 434-435: The story demo currently uses onKeyDown={(e) =>
e.stopPropagation()} which blocks all keyboard events (including Tab); update
the onKeyDown handler in the SmartMultiSelect story so it only calls
e.stopPropagation() for specific keys (e.g., when e.key is "Enter" or "Escape")
and otherwise lets events propagate (so Tab and other navigation keys still
work); locate the onKeyDown prop in SmartMultiSelect.stories.tsx and replace the
unconditional stopPropagation with a conditional check for e.key values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 97c133ed-0301-48e2-84de-e98b8fa099f0

📥 Commits

Reviewing files that changed from the base of the PR and between 2a83360 and a76b96f.

📒 Files selected for processing (4)
  • src/components/ui/SmartMultiSelect.stories.tsx
  • src/components/ui/SmartSelect.stories.tsx
  • src/components/ui/smart-multi-select.tsx
  • src/components/ui/smart-select.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ui/SmartSelect.stories.tsx

Comment on lines +289 to +293
<input
className="flex h-8 w-full rounded-md border border-input bg-transparent px-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Enter name..."
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add accessible label to the create form input.

The input lacks an accessible name for screen readers. Add an aria-label to convey its purpose.

🛠️ Suggested fix
         <input
           className="flex h-8 w-full rounded-md border border-input bg-transparent px-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
           value={search}
           onChange={(e) => setSearch(e.target.value)}
           placeholder="Enter name..."
+          aria-label="New item name"
           autoFocus
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<input
className="flex h-8 w-full rounded-md border border-input bg-transparent px-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Enter name..."
<input
className="flex h-8 w-full rounded-md border border-input bg-transparent px-2 text-sm outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="Enter name..."
aria-label="New item name"
autoFocus
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/smart-select.tsx` around lines 289 - 293, The input for the
create form (the JSX <input> that uses value={search} and onChange={(e) =>
setSearch(e.target.value)}) is missing an accessible name; add an aria-label
attribute to that input (e.g., aria-label="Search or create name" or similar
descriptive text) so screen readers can convey the field's purpose while keeping
the existing value/search and setSearch handlers intact.

Comment on lines +347 to +358
<span
role="button"
tabIndex={-1}
className="rounded-sm opacity-50 hover:opacity-100 cursor-pointer"
onClick={handleClear}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") handleClear(e as unknown as React.MouseEvent)
}}
>
<XIcon className="size-3.5" />
<span className="sr-only">Clear</span>
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clear button is not keyboard accessible.

Using tabIndex={-1} with role="button" makes the clear button unreachable via keyboard navigation while still presenting it as an interactive element to assistive technologies. Additionally, the type coercion on line 353 is unsafe.

Consider using a proper <button> element or setting tabIndex={0}.

🛠️ Suggested fix
           {showClear && value && (
-            <span
-              role="button"
-              tabIndex={-1}
+            <button
+              type="button"
+              tabIndex={0}
               className="rounded-sm opacity-50 hover:opacity-100 cursor-pointer"
               onClick={handleClear}
-              onKeyDown={(e) => {
-                if (e.key === "Enter" || e.key === " ") handleClear(e as unknown as React.MouseEvent)
-              }}
             >
               <XIcon className="size-3.5" />
               <span className="sr-only">Clear</span>
-            </span>
+            </button>
           )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/smart-select.tsx` around lines 347 - 358, The clear control
is not keyboard focusable because it uses span with tabIndex={-1} and an unsafe
cast when calling handleClear; replace the span with a semantic <button> (or set
tabIndex={0}) so it is reachable and operable via keyboard, remove role="button"
and the onKeyDown type coercion, and wire onClick to handleClear directly; also
update handleClear's signature if needed to accept the correct event type (e.g.,
React.MouseEvent | React.KeyboardEvent) so no casts are required — reference the
clear element using the existing handleClear and XIcon identifiers and the
surrounding className attributes.

Comment on lines +379 to +391
{hasCreateCapability && (
<Button
variant="ghost"
size="icon-sm"
className="me-2 shrink-0"
onClick={() => setShowCreate((prev) => !prev)}
>
{showCreate ? (
<XIcon className="size-4" />
) : (
<PlusIcon className="size-4" />
)}
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add aria-label to the create toggle button.

The button toggles the create form but has no accessible label. Screen reader users won't understand its purpose.

🛠️ Suggested fix
                 <Button
                   variant="ghost"
                   size="icon-sm"
                   className="me-2 shrink-0"
                   onClick={() => setShowCreate((prev) => !prev)}
+                  aria-label={showCreate ? "Cancel create" : "Create new item"}
+                  aria-expanded={showCreate}
                 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{hasCreateCapability && (
<Button
variant="ghost"
size="icon-sm"
className="me-2 shrink-0"
onClick={() => setShowCreate((prev) => !prev)}
>
{showCreate ? (
<XIcon className="size-4" />
) : (
<PlusIcon className="size-4" />
)}
</Button>
{hasCreateCapability && (
<Button
variant="ghost"
size="icon-sm"
className="me-2 shrink-0"
onClick={() => setShowCreate((prev) => !prev)}
aria-label={showCreate ? "Cancel create" : "Create new item"}
aria-expanded={showCreate}
>
{showCreate ? (
<XIcon className="size-4" />
) : (
<PlusIcon className="size-4" />
)}
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/smart-select.tsx` around lines 379 - 391, The create-toggle
Button rendered when hasCreateCapability is missing an accessible label; update
the Button in the SmartSelect component (the element using hasCreateCapability,
setShowCreate, showCreate and toggling between XIcon and PlusIcon) to include an
appropriate aria-label (e.g. "Close create form" when showCreate is true and
"Open create form" when false) so screen readers can convey the button's
purpose; ensure the aria-label updates based on showCreate to reflect the
current action.

Copy link
Copy Markdown

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

🧹 Nitpick comments (2)
.storybook/babel-plugin-story-source.js (1)

36-44: Support wrapper components declared as const arrow/function expressions.

Line 36 only captures function DemoWrapper() {}. Wrappers like const DemoWrapper = () => (...) are ignored, so no source gets injected for those stories.

Suggested diff
             if (stmt.isFunctionDeclaration()) {
               const name = stmt.node.id?.name;
               if (!name) continue;

               const { start, end } = stmt.node;
               if (start == null || end == null) continue;

               wrapperFunctions.set(name, fileSource.slice(start, end));
             }
+
+            if (stmt.isVariableDeclaration()) {
+              for (const d of stmt.get('declarations')) {
+                if (!d.isVariableDeclarator()) continue;
+                const id = d.get('id');
+                const init = d.get('init');
+                if (!id.isIdentifier()) continue;
+                if (!init.isArrowFunctionExpression() && !init.isFunctionExpression()) continue;
+                const { start, end } = d.node;
+                if (start == null || end == null) continue;
+                wrapperFunctions.set(id.node.name, fileSource.slice(start, end));
+              }
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.storybook/babel-plugin-story-source.js around lines 36 - 44, The current
logic only handles function declarations via stmt.isFunctionDeclaration() and
misses wrappers declared as const/let/var with arrow or function expressions;
update the block that builds wrapperFunctions to also handle
stmt.isVariableDeclaration(): iterate stmt.node.declarations, for each
VariableDeclarator check declarator.id?.name and that declarator.init is an
ArrowFunctionExpression or FunctionExpression, then compute appropriate
start/end (use declarator.init.start/end if available, falling back to
declarator.start/end) and call wrapperFunctions.set(name,
fileSource.slice(start, end)); keep the existing function-declaration branch
(stmt.isFunctionDeclaration()) intact and add this variable-declaration handling
alongside it.
.storybook/main.ts (1)

91-105: Broaden the story pre-loader match to align with discovered story extensions.

At Line 14, Storybook includes .stories.js and .stories.ts, but Line 104 only pre-processes .stories.jsx|.stories.tsx. That leaves part of your stories without source injection/transforms.

Suggested diff
-    config.module?.rules?.unshift({
-      test: /\.stories\.(jsx|tsx)$/,
+    config.module?.rules?.unshift({
+      test: /\.stories\.(js|jsx|ts|tsx)$/,
       use: [babelLoaderForStories],
       enforce: "pre" as const,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.storybook/main.ts around lines 91 - 105, The test for the Storybook
pre-loader only matches .stories.jsx and .stories.tsx, so update the rule added
via config.module?.rules?.unshift that uses babelLoaderForStories to match the
full set of discovered story extensions (at least .stories.js, .stories.ts,
.stories.jsx, .stories.tsx); modify the test regex in that rule (the value used
in the config.module?.rules?.unshift call) to include js and ts as well so all
story files receive the babel-plugin-story-source pre-processing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.storybook/babel-plugin-story-source.js:
- Around line 93-125: The code currently bails out when a story already has a
parameters property (hasParams) — instead, locate the existing parameters object
in init.node.properties (the objectProperty with key 'parameters'), and
merge/augment it: ensure it has a docs objectProperty and within that a source
objectProperty, then set/replace the source's code and language using the source
string from wrapperFunctions.get(wrapperName). Build the sourceObj (code +
language) and docsObj as needed but when parameters exists, update its
properties rather than returning early so you only add/overwrite
parameters.docs.source while preserving any other existing parameter keys.

---

Nitpick comments:
In @.storybook/babel-plugin-story-source.js:
- Around line 36-44: The current logic only handles function declarations via
stmt.isFunctionDeclaration() and misses wrappers declared as const/let/var with
arrow or function expressions; update the block that builds wrapperFunctions to
also handle stmt.isVariableDeclaration(): iterate stmt.node.declarations, for
each VariableDeclarator check declarator.id?.name and that declarator.init is an
ArrowFunctionExpression or FunctionExpression, then compute appropriate
start/end (use declarator.init.start/end if available, falling back to
declarator.start/end) and call wrapperFunctions.set(name,
fileSource.slice(start, end)); keep the existing function-declaration branch
(stmt.isFunctionDeclaration()) intact and add this variable-declaration handling
alongside it.

In @.storybook/main.ts:
- Around line 91-105: The test for the Storybook pre-loader only matches
.stories.jsx and .stories.tsx, so update the rule added via
config.module?.rules?.unshift that uses babelLoaderForStories to match the full
set of discovered story extensions (at least .stories.js, .stories.ts,
.stories.jsx, .stories.tsx); modify the test regex in that rule (the value used
in the config.module?.rules?.unshift call) to include js and ts as well so all
story files receive the babel-plugin-story-source pre-processing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 55987055-8fe7-45d1-86d2-065b593f97c3

📥 Commits

Reviewing files that changed from the base of the PR and between a76b96f and 6399e0a.

📒 Files selected for processing (2)
  • .storybook/babel-plugin-story-source.js
  • .storybook/main.ts

Comment on lines +93 to +125
// Skip stories that already define their own `parameters`
const hasParams = properties.some(
(p) =>
p.isObjectProperty() &&
p.get('key').isIdentifier({ name: 'parameters' })
);
if (hasParams) continue;

// Build: parameters: { docs: { source: { code, language } } }
const sourceCode = wrapperFunctions.get(wrapperName);

const sourceObj = t.objectExpression([
t.objectProperty(
t.identifier('code'),
t.stringLiteral(sourceCode)
),
t.objectProperty(
t.identifier('language'),
t.stringLiteral('tsx')
),
]);

const docsObj = t.objectExpression([
t.objectProperty(t.identifier('source'), sourceObj),
]);

const paramsObj = t.objectExpression([
t.objectProperty(t.identifier('docs'), docsObj),
]);

init.node.properties.push(
t.objectProperty(t.identifier('parameters'), paramsObj)
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t bail out when parameters already exists; merge docs.source instead.

At Line 99, the plugin exits if parameters exists, so many valid stories won’t get injected source at all. This undermines the plugin’s core behavior.

Suggested diff
-              const hasParams = properties.some(
-                (p) =>
-                  p.isObjectProperty() &&
-                  p.get('key').isIdentifier({ name: 'parameters' })
-              );
-              if (hasParams) continue;
+              const paramsProp = properties.find(
+                (p) =>
+                  p.isObjectProperty() &&
+                  p.get('key').isIdentifier({ name: 'parameters' })
+              );
...
-              const paramsObj = t.objectExpression([
-                t.objectProperty(t.identifier('docs'), docsObj),
-              ]);
-
-              init.node.properties.push(
-                t.objectProperty(t.identifier('parameters'), paramsObj)
-              );
+              if (!paramsProp) {
+                const paramsObj = t.objectExpression([
+                  t.objectProperty(t.identifier('docs'), docsObj),
+                ]);
+                init.node.properties.push(
+                  t.objectProperty(t.identifier('parameters'), paramsObj)
+                );
+                continue;
+              }
+
+              const paramsVal = paramsProp.get('value');
+              if (!paramsVal.isObjectExpression()) continue;
+
+              const hasDocs = paramsVal.get('properties').some(
+                (p) =>
+                  p.isObjectProperty() &&
+                  p.get('key').isIdentifier({ name: 'docs' })
+              );
+              if (!hasDocs) {
+                paramsVal.node.properties.push(
+                  t.objectProperty(t.identifier('docs'), docsObj)
+                );
+              }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.storybook/babel-plugin-story-source.js around lines 93 - 125, The code
currently bails out when a story already has a parameters property (hasParams) —
instead, locate the existing parameters object in init.node.properties (the
objectProperty with key 'parameters'), and merge/augment it: ensure it has a
docs objectProperty and within that a source objectProperty, then set/replace
the source's code and language using the source string from
wrapperFunctions.get(wrapperName). Build the sourceObj (code + language) and
docsObj as needed but when parameters exists, update its properties rather than
returning early so you only add/overwrite parameters.docs.source while
preserving any other existing parameter keys.

@Aunshon Aunshon merged commit 0f08bb0 into main Apr 3, 2026
1 check passed
@Aunshon Aunshon deleted the add-dialog-component branch April 3, 2026 04:37
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