Skip to content

feat(Sidebar): add resizable functionality#6190

Open
mikenewbon wants to merge 6 commits intonuxt:v4from
mikenewbon:v4
Open

feat(Sidebar): add resizable functionality#6190
mikenewbon wants to merge 6 commits intonuxt:v4from
mikenewbon:v4

Conversation

@mikenewbon
Copy link
Contributor

@mikenewbon mikenewbon commented Mar 15, 2026

🔗 Linked issue

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality)
  • ✨ New feature (a non-breaking change that adds functionality)
  • 🧹 Chore (updates to the build process or auxiliary tools and libraries)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

📝 Checklist

  • I have linked an issue or discussion.
  • I have updated the documentation accordingly.

@mikenewbon mikenewbon marked this pull request as ready for review March 16, 2026 17:06
@mikenewbon mikenewbon mentioned this pull request Mar 16, 2026
8 tasks
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional drag-to-resize support to Sidebar by introducing a public resizable?: boolean prop and incorporating id, minSize, maxSize, defaultSize, and collapsedSize from the useResizable composable into SidebarProps. Implements draggable rail behavior (mouse/touch/double-click), dragging state, size clamping, CSS width variable updates, and synchronization between resize and open/collapse state across desktop and mobile. Also adds leading/trailing slots to ScrollArea, updates theme transition classes for dragging, updates docs and examples, and adds tests for the new ScrollArea slots.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive The PR description is a template with empty content fields and unchecked checkboxes, containing no actual description of the changes, though the title and raw_summary confirm this is about adding Sidebar resizable functionality. Provide a meaningful description explaining why the resizable functionality was added, what problem it solves, and any relevant implementation details or considerations.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat(Sidebar): add resizable functionality' directly and clearly describes the main change—adding resizable functionality to the Sidebar component, which aligns with the primary focus of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

🧹 Nitpick comments (2)
src/runtime/components/Sidebar.vue (2)

197-208: Falsy check on collapsedSize may override explicit zero values.

Line 204 uses props.collapsedSize || Math.max(...) which treats 0 as falsy. Since the default is collapsedSize: 0 (line 128), the fallback always applies. If a user explicitly sets collapsedSize: 0 intending a fully hidden collapsed state, the fallback would override it.

Consider using nullish coalescing if 0 should be a valid explicit value:

♻️ Proposed fix
-  collapsedSize: props.collapsedSize || Math.max(0, props.minSize - 8),
+  collapsedSize: props.collapsedSize ?? Math.max(0, props.minSize - 8),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Sidebar.vue` around lines 197 - 208, The current
useResizable call computes collapsedSize using the falsy check
props.collapsedSize || Math.max(0, props.minSize - 8), which treats 0 as falsy
and overrides an explicit zero; change this to use the nullish coalescing
operator so that props.collapsedSize is only replaced when it is null or
undefined (i.e., use props.collapsedSize ?? Math.max(0, props.minSize - 8)),
updating the collapsedSize expression inside the object passed to useResizable
(referencing props.collapsedSize and the collapsedSize field in that object).

242-248: Sync logic is correct but could benefit from clarification.

The condition isCollapsed.value === v works because modelOpen and isCollapsed are semantic inverses—when both have the same boolean value, they're out of sync. The implementation is correct and avoids infinite loops, but the logic is non-obvious.

📝 Suggested inline comment for clarity
 // Sync useResizable collapse ↔ open model
 watch(isCollapsed, (collapsed) => {
   if (!isMobile.value && canCollapse.value) modelOpen.value = !collapsed
 })
 watch(modelOpen, (v) => {
+  // modelOpen and isCollapsed are semantic inverses: open=true ↔ collapsed=false.
+  // If both have the same value, they're out of sync and need correction.
   if (!isMobile.value && canCollapse.value && isCollapsed.value === v) collapse(!v)
 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Sidebar.vue` around lines 242 - 248, Add a short
inline comment above the second watcher explaining that modelOpen and
isCollapsed are semantic inverses and that the check isCollapsed.value === v
detects an out-of-sync state (same boolean means inverted semantics), which
prevents loops by only calling collapse(!v) when actually needed; reference the
watch callbacks for isCollapsed and modelOpen and mention isMobile.value and
canCollapse.value guards so future readers understand why the equality check is
used instead of a straightforward inequality.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/runtime/components/Sidebar.vue`:
- Around line 197-208: The current useResizable call computes collapsedSize
using the falsy check props.collapsedSize || Math.max(0, props.minSize - 8),
which treats 0 as falsy and overrides an explicit zero; change this to use the
nullish coalescing operator so that props.collapsedSize is only replaced when it
is null or undefined (i.e., use props.collapsedSize ?? Math.max(0, props.minSize
- 8)), updating the collapsedSize expression inside the object passed to
useResizable (referencing props.collapsedSize and the collapsedSize field in
that object).
- Around line 242-248: Add a short inline comment above the second watcher
explaining that modelOpen and isCollapsed are semantic inverses and that the
check isCollapsed.value === v detects an out-of-sync state (same boolean means
inverted semantics), which prevents loops by only calling collapse(!v) when
actually needed; reference the watch callbacks for isCollapsed and modelOpen and
mention isMobile.value and canCollapse.value guards so future readers understand
why the equality check is used instead of a straightforward inequality.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e2be26c3-557b-4269-a3b9-ca5d999fd671

📥 Commits

Reviewing files that changed from the base of the PR and between 9544d85 and 83b85ff.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/Sidebar-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/Sidebar.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (5)
  • docs/app/components/content/examples/sidebar/SidebarExample.vue
  • docs/content/docs/2.components/dashboard-sidebar.md
  • docs/content/docs/2.components/sidebar.md
  • src/runtime/components/Sidebar.vue
  • src/theme/sidebar.ts

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: 3

🤖 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/runtime/components/Sidebar.vue`:
- Around line 124-128: The prop default for collapsedSize is currently set to 0
which conflates "omitted" and explicit zero; change the default in withDefaults
so collapsedSize is undefined (or remove the default) and introduce a single
computed/effective value (e.g., effectiveCollapsedSize) used everywhere
(template, the options passed to useResizable, and any resize logic) that
resolves to collapsedSize when explicitly provided, otherwise to the fallback
(minSize - 8 or whatever the intended fallback is); also replace any truthy-OR
patterns (e.g., `||`) that treated 0 as absent with nullish checks (`??`) or
explicit undefined checks in the useResizable options and the other occurrences
referenced (the blocks around the other mentioned ranges) so an explicit 0 width
is honored consistently.
- Around line 228-231: The click handler on the rail (onRailClick) must be made
aware of subsequent dblclicks so a dblclick reset doesn't get preempted by the
first click; change onRailClick to schedule its collapse/toggle action via a
short timeout (e.g. 200–250ms) and store the timer id, and in handleDoubleClick
clear that timer before running the reset logic so the single-click action is
suppressed when a dblclick follows; apply the same pattern to the other similar
handler referenced around the 397-398 area. Use the existing symbols
(onRailClick, handleDoubleClick, isResizable, didDrag, canCollapse, collapse,
isCollapsed, open) to locate and implement the timed/cancellable click behavior.
- Around line 247-253: The initial persisted collapsed state from useResizable
(isCollapsed) isn't applied on mount, so set the initial modelOpen to reflect
isCollapsed and ensure the collapse state is consistent: immediately after
useResizable (or in a mounted/initialization block) do something like if
(!isMobile.value && canCollapse.value) modelOpen.value = !isCollapsed.value; if
you detect isCollapsed.value === modelOpen.value then call
collapse(!modelOpen.value) (same check used in the modelOpen watcher) so the UI
and stored state are synchronized on first render; reference isCollapsed,
modelOpen, isMobile, canCollapse, and collapse when applying this fix.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 705cebf6-ac01-4208-9951-ef893c654934

📥 Commits

Reviewing files that changed from the base of the PR and between 83b85ff and f51eb2b.

📒 Files selected for processing (1)
  • src/runtime/components/Sidebar.vue

Comment on lines +228 to +231
function onRailClick() {
if (!isResizable.value) return (open.value = !open.value)
if (!didDrag && canCollapse.value) collapse(!isCollapsed.value)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Separate single-click collapse from double-click reset.

Browsers dispatch click before dblclick, so double-clicking the resizable rail currently triggers onRailClick first and only then runs handleDoubleClick. On a collapsible sidebar that means the first click can collapse/expand the panel before the reset gesture completes.

🛠️ One way to avoid the interaction conflict
+let railClickTimer: ReturnType<typeof setTimeout> | undefined
+
 function onRailClick() {
-  if (!isResizable.value) return (open.value = !open.value)
-  if (!didDrag && canCollapse.value) collapse(!isCollapsed.value)
+  if (!isResizable.value) {
+    open.value = !open.value
+    return
+  }
+
+  if (railClickTimer) clearTimeout(railClickTimer)
+  railClickTimer = window.setTimeout(() => {
+    if (!didDrag && canCollapse.value) collapse(!isCollapsed.value)
+  }, 200)
+}
+
+function onRailDoubleClick(e: MouseEvent) {
+  if (railClickTimer) clearTimeout(railClickTimer)
+  handleDoubleClick(e)
 }
-            `@dblclick`="isResizable ? handleDoubleClick($event) : undefined"
+            `@dblclick`="isResizable ? onRailDoubleClick($event) : undefined"

Also applies to: 397-398

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

In `@src/runtime/components/Sidebar.vue` around lines 228 - 231, The click handler
on the rail (onRailClick) must be made aware of subsequent dblclicks so a
dblclick reset doesn't get preempted by the first click; change onRailClick to
schedule its collapse/toggle action via a short timeout (e.g. 200–250ms) and
store the timer id, and in handleDoubleClick clear that timer before running the
reset logic so the single-click action is suppressed when a dblclick follows;
apply the same pattern to the other similar handler referenced around the
397-398 area. Use the existing symbols (onRailClick, handleDoubleClick,
isResizable, didDrag, canCollapse, collapse, isCollapsed, open) to locate and
implement the timed/cancellable click behavior.

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.

♻️ Duplicate comments (3)
src/runtime/components/Sidebar.vue (3)

248-250: ⚠️ Potential issue | 🟠 Major

Apply persisted collapsed state on initial render.

The watch(isCollapsed, ...) on Line [248] is not immediate, so stored collapsed state can be ignored until a later change.

🛠️ Suggested fix
 watch(isCollapsed, (collapsed) => {
   if (!isMobile.value && canCollapse.value) modelOpen.value = !collapsed
-})
+}, { immediate: true })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/runtime/components/Sidebar.vue` around lines 248 - 250, The watcher on
isCollapsed currently doesn't run on initial render so persisted collapsed state
may not be applied; modify the watcher on isCollapsed (the call to watch(...))
to run immediately (use the immediate option) so that modelOpen is set from
isCollapsed on mount (or alternatively set modelOpen from isCollapsed inside
mounted/setup once) — target the watch invocation that updates modelOpen.value
and ensure it uses { immediate: true } (or an initial assignment) so the stored
collapsed state is applied right away.

124-128: ⚠️ Potential issue | 🟠 Major

Use one effective collapsedSize value everywhere.

Line [128] sets collapsedSize to 0 by default, so Line [204]’s ?? fallback never applies when omitted. Also, Line [368] uses a truthy check, so explicit 0 is treated as absent in CSS output. This can desync resize behavior vs rendered icon width.

🛠️ Suggested fix
 const props = withDefaults(defineProps<SidebarProps<T>>(), {
   as: 'aside',
   variant: 'sidebar',
   collapsible: 'offcanvas',
   side: 'left',
   close: false,
   rail: false,
   resizable: false,
   minSize: 12,
   maxSize: 24,
   defaultSize: 16,
-  collapsedSize: 0,
   mode: 'slideover' as never
 })
+
+const effectiveCollapsedSize = computed(() =>
+  props.collapsedSize ?? Math.max(0, props.minSize - 8)
+)
 
 const { el: containerEl, size: sidebarSize, isDragging, isCollapsed, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleDoubleClick, collapse } = useResizable(sidebarId, computed(() => ({
   side: props.side,
   minSize: props.minSize,
   maxSize: props.maxSize,
   defaultSize: props.defaultSize,
   resizable: isResizable.value,
   collapsible: canCollapse.value,
-  collapsedSize: props.collapsedSize ?? Math.max(0, props.minSize - 8),
+  collapsedSize: effectiveCollapsedSize.value,
   unit: 'rem' as const,
   persistent: true,
   storage: 'cookie' as const
 })), { collapsed: desktopCollapsed })
       :style="isResizable ? {
         '--sidebar-width': `${expandedWidth}rem`,
-        ...(props.collapsedSize && props.collapsible === 'icon' ? { '--sidebar-width-icon': `${props.collapsedSize}rem` } : {})
+        ...(props.collapsible === 'icon' ? { '--sidebar-width-icon': `${effectiveCollapsedSize}rem` } : {})
       } : undefined"

Also applies to: 204-205, 366-369

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

In `@src/runtime/components/Sidebar.vue` around lines 124 - 128, The component
sets collapsedSize = 0 by default but other code paths treat 0 as "absent" (a
truthy check and a ?? fallback), causing inconsistent resize vs icon width;
update the implementation so one effective sentinel is used: either (A) make the
default collapsedSize be undefined/null and keep the existing ?? fallback and
truthy checks, or (B) keep collapsedSize = 0 but change every consumer to use
nullish checks (use collapsedSize ?? <fallback>) and explicit comparisons
(collapsedSize === 0) instead of truthy checks so 0 is treated as a valid size;
locate all references to collapsedSize (the default value definition and the
places mentioned that use ?? and truthy checks) and make them consistent.

228-231: ⚠️ Potential issue | 🟠 Major

Prevent single-click action from firing during double-click reset.

Line [398] click runs before Line [397] dblclick in browsers. A double-click on the rail can trigger collapse/toggle first, then reset, causing interaction conflicts.

🛠️ Suggested fix
+let railClickTimer: ReturnType<typeof setTimeout> | undefined
+
 function onRailClick() {
-  if (!isResizable.value) return (open.value = !open.value)
-  if (!didDrag && canCollapse.value) collapse(!isCollapsed.value)
+  if (!isResizable.value) {
+    open.value = !open.value
+    return
+  }
+  if (railClickTimer) clearTimeout(railClickTimer)
+  railClickTimer = window.setTimeout(() => {
+    if (!didDrag && canCollapse.value) collapse(!isCollapsed.value)
+  }, 220)
 }
+
+function onRailDoubleClick(e: MouseEvent) {
+  if (railClickTimer) clearTimeout(railClickTimer)
+  handleDoubleClick(e)
+}
-            `@dblclick`="isResizable ? handleDoubleClick($event) : undefined"
+            `@dblclick`="isResizable ? onRailDoubleClick($event) : undefined"

Also applies to: 397-398

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

In `@src/runtime/components/Sidebar.vue` around lines 228 - 231, The onRailClick
handler is firing before the dblclick handler so a double-click can erroneously
trigger collapse/toggle; modify the rail double-click flow to suppress the
subsequent single-click action: add a short-lived flag (e.g., ignoreNextClick)
that onRailDblClick sets (and clears after a small timeout or on next event loop
tick) and have onRailClick return early when that flag is true; update
references in the handlers (onRailClick, onRailDblClick) and keep existing
checks for didDrag, isResizable, canCollapse, open and collapse/isCollapsed
unchanged otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/runtime/components/Sidebar.vue`:
- Around line 248-250: The watcher on isCollapsed currently doesn't run on
initial render so persisted collapsed state may not be applied; modify the
watcher on isCollapsed (the call to watch(...)) to run immediately (use the
immediate option) so that modelOpen is set from isCollapsed on mount (or
alternatively set modelOpen from isCollapsed inside mounted/setup once) — target
the watch invocation that updates modelOpen.value and ensure it uses {
immediate: true } (or an initial assignment) so the stored collapsed state is
applied right away.
- Around line 124-128: The component sets collapsedSize = 0 by default but other
code paths treat 0 as "absent" (a truthy check and a ?? fallback), causing
inconsistent resize vs icon width; update the implementation so one effective
sentinel is used: either (A) make the default collapsedSize be undefined/null
and keep the existing ?? fallback and truthy checks, or (B) keep collapsedSize =
0 but change every consumer to use nullish checks (use collapsedSize ??
<fallback>) and explicit comparisons (collapsedSize === 0) instead of truthy
checks so 0 is treated as a valid size; locate all references to collapsedSize
(the default value definition and the places mentioned that use ?? and truthy
checks) and make them consistent.
- Around line 228-231: The onRailClick handler is firing before the dblclick
handler so a double-click can erroneously trigger collapse/toggle; modify the
rail double-click flow to suppress the subsequent single-click action: add a
short-lived flag (e.g., ignoreNextClick) that onRailDblClick sets (and clears
after a small timeout or on next event loop tick) and have onRailClick return
early when that flag is true; update references in the handlers (onRailClick,
onRailDblClick) and keep existing checks for didDrag, isResizable, canCollapse,
open and collapse/isCollapsed unchanged otherwise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4390214-2f5b-4058-b968-d02ecf8bd48f

📥 Commits

Reviewing files that changed from the base of the PR and between f51eb2b and 3428c79.

📒 Files selected for processing (1)
  • src/runtime/components/Sidebar.vue

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 16, 2026

npm i https://pkg.pr.new/@nuxt/ui@6190

commit: b9dcd92

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.

♻️ Duplicate comments (2)
src/runtime/components/Sidebar.vue (2)

197-205: ⚠️ Potential issue | 🟡 Minor

Use one effective collapsed width for both behavior and CSS output.

Line 373 still gates --sidebar-width-icon with props.collapsedSize truthiness, which can diverge from the value actually used by useResizable (Line 204), especially for explicit 0 or omitted values.

🛠️ Suggested alignment
+const effectiveCollapsedSize = computed(() =>
+  props.collapsedSize ?? Math.max(0, props.minSize - 8)
+)
+
 const { el: containerEl, size: sidebarSize, isDragging, isCollapsed, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleDoubleClick, collapse } = useResizable(sidebarId, computed(() => ({
   side: props.side,
   minSize: props.minSize,
   maxSize: props.maxSize,
   defaultSize: props.defaultSize,
   resizable: isResizable.value,
   collapsible: canCollapse.value,
-  collapsedSize: props.collapsedSize ?? Math.max(0, props.minSize - 8),
+  collapsedSize: effectiveCollapsedSize.value,
   unit: 'rem' as const,
   persistent: true,
   storage: 'cookie' as const
 })), { collapsed: desktopCollapsed })
       :style="isResizable ? {
         '--sidebar-width': `${expandedWidth}rem`,
-        ...(props.collapsedSize && props.collapsible === 'icon' ? { '--sidebar-width-icon': `${props.collapsedSize}rem` } : {})
+        ...(props.collapsible === 'icon' ? { '--sidebar-width-icon': `${effectiveCollapsedSize}rem` } : {})
       } : undefined"

Also applies to: 371-374

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

In `@src/runtime/components/Sidebar.vue` around lines 197 - 205, The component
uses different expressions for the collapsed width which can diverge; unify by
computing a single collapsedWidth value and use it everywhere: compute
collapsedWidth = props.collapsedSize ?? Math.max(0, props.minSize - 8) and pass
collapsedWidth into useResizable (collapsedSize) and into the CSS
output/--sidebar-width-icon (remove any separate truthiness gating on
props.collapsedSize); update any references that previously read
props.collapsedSize or duplicated Math.max(...) to use collapsedWidth instead
(symbols: Sidebar.vue, useResizable, props.collapsedSize, --sidebar-width-icon).

233-236: ⚠️ Potential issue | 🟠 Major

Prevent single-click toggle from firing before double-click reset.

A dblclick sequence still triggers click first, so the rail may collapse/expand before reset runs.

🛠️ Suggested conflict-free click handling
 let didDrag = false
+let railClickTimer: ReturnType<typeof setTimeout> | undefined

 function onRailClick() {
   if (!isResizable.value) return (open.value = !open.value)
-  if (!didDrag && canCollapse.value) collapse(!isCollapsed.value)
+  if (railClickTimer) clearTimeout(railClickTimer)
+  railClickTimer = window.setTimeout(() => {
+    if (!didDrag && canCollapse.value) collapse(!isCollapsed.value)
+  }, 220)
 }
+
+function onRailDoubleClick(e: MouseEvent) {
+  if (railClickTimer) clearTimeout(railClickTimer)
+  handleDoubleClick(e)
+}
-            `@dblclick`="isResizable ? handleDoubleClick($event) : undefined"
+            `@dblclick`="isResizable ? onRailDoubleClick($event) : undefined"

Also applies to: 402-403

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

In `@src/runtime/components/Sidebar.vue` around lines 233 - 236, onRailClick is
firing immediately and interferes with the dblclick reset; introduce a short
click-delay mechanism: add a component-scoped clickTimeout (or
isDoubleClickPending) and change onRailClick to start a timeout (e.g.,
200–300ms) that runs the existing logic (check isResizable, didDrag,
canCollapse, collapse/open) instead of executing immediately; ensure the
dblclick handler clears that timeout and runs the double-click reset so the
single-click action is canceled when a dblclick occurs. Apply the same
timeout/clear pattern to the other click handler referenced at lines 402-403
(i.e., where similar click-vs-dblclick behavior exists).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/runtime/components/Sidebar.vue`:
- Around line 197-205: The component uses different expressions for the
collapsed width which can diverge; unify by computing a single collapsedWidth
value and use it everywhere: compute collapsedWidth = props.collapsedSize ??
Math.max(0, props.minSize - 8) and pass collapsedWidth into useResizable
(collapsedSize) and into the CSS output/--sidebar-width-icon (remove any
separate truthiness gating on props.collapsedSize); update any references that
previously read props.collapsedSize or duplicated Math.max(...) to use
collapsedWidth instead (symbols: Sidebar.vue, useResizable, props.collapsedSize,
--sidebar-width-icon).
- Around line 233-236: onRailClick is firing immediately and interferes with the
dblclick reset; introduce a short click-delay mechanism: add a component-scoped
clickTimeout (or isDoubleClickPending) and change onRailClick to start a timeout
(e.g., 200–300ms) that runs the existing logic (check isResizable, didDrag,
canCollapse, collapse/open) instead of executing immediately; ensure the
dblclick handler clears that timeout and runs the double-click reset so the
single-click action is canceled when a dblclick occurs. Apply the same
timeout/clear pattern to the other click handler referenced at lines 402-403
(i.e., where similar click-vs-dblclick behavior exists).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3c581f7f-ce9a-4221-9659-e60dba23e1c8

📥 Commits

Reviewing files that changed from the base of the PR and between 3428c79 and b9dcd92.

📒 Files selected for processing (1)
  • src/runtime/components/Sidebar.vue

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.

🧹 Nitpick comments (1)
test/components/ScrollArea.spec.ts (1)

29-31: Consider adding explicit slot content assertions.

The snapshot tests verify HTML structure but don't explicitly assert that slot content ('Leading content', 'Trailing content') appears in the rendered output. If slot rendering breaks silently, snapshots might still pass if the overall structure is preserved.

For more robust coverage, consider adding a targeted test that explicitly checks for slot content:

it('renders leading and trailing slot content', async () => {
  const wrapper = await mountSuspended(ScrollArea, {
    props: { items },
    slots: {
      leading: () => 'Leading content',
      trailing: () => 'Trailing content'
    }
  })
  expect(wrapper.text()).toContain('Leading content')
  expect(wrapper.text()).toContain('Trailing content')
})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/components/ScrollArea.spec.ts` around lines 29 - 31, The tests for
ScrollArea currently rely on snapshots but don't explicitly assert that slot
content is rendered; add a focused unit test (e.g., it('renders leading and
trailing slot content')) that mounts ScrollArea using mountSuspended with props:
{ items } and slots: { leading: () => 'Leading content', trailing: () =>
'Trailing content' } and then assert wrapper.text() contains both 'Leading
content' and 'Trailing content' to ensure slot rendering is verified.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/components/ScrollArea.spec.ts`:
- Around line 29-31: The tests for ScrollArea currently rely on snapshots but
don't explicitly assert that slot content is rendered; add a focused unit test
(e.g., it('renders leading and trailing slot content')) that mounts ScrollArea
using mountSuspended with props: { items } and slots: { leading: () => 'Leading
content', trailing: () => 'Trailing content' } and then assert wrapper.text()
contains both 'Leading content' and 'Trailing content' to ensure slot rendering
is verified.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2eef131a-b5e2-47c0-a71e-f18d7e933613

📥 Commits

Reviewing files that changed from the base of the PR and between b9dcd92 and bd366bd.

⛔ Files ignored due to path filters (2)
  • test/components/__snapshots__/ScrollArea-vue.spec.ts.snap is excluded by !**/*.snap
  • test/components/__snapshots__/ScrollArea.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • docs/app/components/content/examples/scroll-area/ScrollAreaInfiniteScrollExample.vue
  • docs/app/components/content/examples/scroll-area/ScrollAreaLeadingTrailingExample.vue
  • docs/content/docs/2.components/scroll-area.md
  • playgrounds/nuxt/app/pages/components/scroll-area.vue
  • src/runtime/components/ScrollArea.vue
  • test/components/ScrollArea.spec.ts

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

Labels

v4 #4488

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant