Skip to content

feat(layout): add layout component#364

Open
SonyLeo wants to merge 6 commits into
opentiny:developfrom
SonyLeo:feat/layout-code
Open

feat(layout): add layout component#364
SonyLeo wants to merge 6 commits into
opentiny:developfrom
SonyLeo:feat/layout-code

Conversation

@SonyLeo

@SonyLeo SonyLeo commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

背景

文档在线预览 🔗

新增 Layout 布局组件,统一承载页面骨架、可收起侧栏、浮层工作区和主区代理滚动条这几类布局能力。

这个组件的目标不是做一个简单的页面容器,而是提供一套可组合、可控的布局基础能力,覆盖以下几类常见场景:

  • 标准页面布局:header / main / footer / left aside / right aside
  • 带侧栏的工作台布局:支持 dock / drawer
  • 可收起、可调宽侧栏:支持 rail、hidden、slide / overlay
  • 浮层布局:支持拖拽、缩放、受控 / 非受控状态
  • 主区代理滚动条:支持滚动宿主与可视滚动条分离

组件能力

1. 标准布局骨架

Layout 提供以下基础插槽:

  • left-aside
  • header
  • main
  • footer
  • right-aside

内部使用 grid 组织整体结构。普通模式下参与正常文档流;浮层模式下通过 Teleport 挂载到 body,并支持拖拽与缩放。

2. 双侧栏模型

左右侧栏都支持统一配置:

  • mode
  • open / defaultOpen
  • expandedWidth / defaultExpandedWidth
  • minExpandedWidth / maxExpandedWidth
  • collapsedWidth
  • collapseEffect
  • resizable

支持两种展示模式:

  • dock:占据布局空间
  • drawer:覆盖在主区之上

支持两种收起表现:

  • overlay:内容区保持原位
  • slide:内容区跟随侧栏宽度变化

collapsedWidth > 0 时,侧栏关闭后进入 rail 状态;否则进入完全隐藏状态。

3. 受控 / 非受控状态

Layout 的侧栏与浮层状态都同时支持:

  • 受控模式:由外部传入当前值并回写
  • 非受控模式:由组件内部初始化并维护后续状态

侧栏:

  • open / defaultOpen
  • expandedWidth / defaultExpandedWidth

浮层:

  • floatingState / defaultFloatingState

当前实现中,状态收口在 useControllableStatedefault* 只用于非受控初始化,受控判定统一基于显式 prop 是否为 undefined

设计说明

一、状态源集中在 root state

useLayoutRootState 负责管理布局的原始状态与受控/非受控同步,包括:

  • 左右侧栏的 open / width / mode / rail / hidden / canResize
  • 浮层状态的初始化、提交与对外回写

这一层只负责状态解析和状态提交,不负责模板结构。

二、结构编排回收到 Layout.vue

这一版没有继续把渲染层和 drawer 行为拆成额外 composable,而是把结构编排保留在 Layout.vue

  • drawer 互斥打开
  • backdrop 显示与关闭
  • resize 过程态桥接
  • layout class / style 派生
  • 插槽是否渲染判断

原因是这些规则只服务于 Layout 自身,保留在根组件里更直接,也更符合当前场景。

三、浮层交互拆成“结构组件 + 子交互组件”

浮层相关能力拆为:

  • LayoutSurface:负责浮层容器、定位、状态同步与交互编排
  • FloatingDragBar:负责拖拽入口
  • FloatingResizeTriggers:负责缩放入口

其中 drag / resize 在输入层面已经解耦,LayoutSurface 只负责统一编排当前交互状态、提交位置尺寸变化,并向外发出浮层事件。

四、侧栏 resize 采用事件上抛

侧栏宽度拖拽链路为:

  • AsideResizeTrigger 负责指针交互与宽度计算
  • AsideContent 负责向父层透传事件
  • Layout 负责承接宽度变化并更新 panel 状态
  • Layout 再向外发出公共 resize 事件

父层只维护一份 isAsideResizing 过程态,用于禁用过渡和切换交互样式,避免父子之间重复维护 resizing 状态。

五、Layout.AsideToggle 通过内部 context 共享最小状态

Layout.AsideToggle 通过 provide/inject 获取内部上下文,但上下文只暴露最小必要能力:

  • isOpen
  • toggle

它不直接暴露完整 panel 状态,也不允许插槽内容拿到一整套可写控制器。这样可以保持数据流单向:

  • 状态从 Layout 向下传递
  • 交互通过事件或最小 action 向上收口

六、双层结构是有意设计

Layout 采用:

  • 根层 tr-layout
  • 内容层 tr-layout__body

根层负责:

  • 浮层定位
  • 外层 outline / shadow
  • drag bar
  • floating resize triggers

内容层负责:

  • grid 布局
  • 背景
  • overflow hidden
  • drawer/backdrop 的裁切层

这样可以同时满足“浮层外沿交互层需要 overflow: visible”和“内容区需要统一裁切”这两类需求。

实现细节

1. 侧栏背景变量语义化

侧栏背景使用显式变量:

  • --tr-layout-left-aside-bg
  • --tr-layout-right-aside-bg

header / main / footer 的背景变量保持一致的命名粒度。

2. drawer 宽度支持显式变量覆盖

drawer 宽度优先由 --tr-layout-drawer-width 控制;未设置时回退到侧栏展开宽度。

3. floating resize handle 收敛为 7 个方向

当前浮层缩放保留 7 个 handle:

  • s
  • e
  • w
  • ne
  • nw
  • se
  • sw

顶部中间 n handle 被移除,避免与顶部拖拽入口冲突。

4. 代理滚动条采用“滚动宿主外置”模型

Layout.ProxyScrollbar 不直接决定主区谁来滚动,而是通过 scrollTarget 接收真实滚动宿主:

  • ProxyScrollbar 负责滚动条显示、拖拽和同步
  • 使用方负责声明真实滚动元素的尺寸、overflow 和是否隐藏原生滚动条

这种方式比组件内部隐式接管滚动宿主样式更稳,也更符合显式契约。

对外 API

Props

Layout

  • mode
  • leftAside
  • rightAside
  • floatingState
  • defaultFloatingState
  • floatingOptions

Layout.ProxyScrollbar

  • scrollTarget

Layout.AsideToggle

  • side

Events

侧栏事件:

  • aside-open-change
  • left-aside-open-change
  • right-aside-open-change
  • aside-resize-start
  • aside-resize
  • aside-resize-end
  • left-aside-resize-start
  • left-aside-resize
  • left-aside-resize-end
  • right-aside-resize-start
  • right-aside-resize
  • right-aside-resize-end

浮层事件:

  • update:floatingState
  • floating-drag-start
  • floating-drag
  • floating-drag-end
  • floating-resize-start
  • floating-resize
  • floating-resize-end

Slots

Layout

  • left-aside
  • header
  • main
  • footer
  • right-aside

Layout.AsideToggle

  • default

其中:

  • left-aside / right-aside 作为纯内容插槽使用
  • 开关状态通过外部受控 props/events 或 Layout.AsideToggle 的默认插槽 { isOpen } 获取

组件结构

核心文件如下:

  • Layout.vue:布局根组件,负责结构编排、drawer 行为和事件桥接
  • LayoutAsideToggle.vue:侧栏开关组件
  • LayoutProxyScrollbar.vue:主区代理滚动条组件
  • LayoutSurface.vue:浮层外壳与浮层交互编排
  • AsideContent.vue:侧栏结构包装
  • AsideResizeTrigger.vue:侧栏拖宽触发器
  • FloatingDragBar.vue:浮层拖拽入口
  • FloatingResizeTriggers.vue:浮层缩放入口
  • useLayoutRootState.ts:原始状态与受控/非受控同步
  • useLayoutContext.ts:provide/inject 上下文
  • usePointerDragSession.ts:指针拖拽会话抽象

验证

  • pnpm.cmd -F @opentiny/tiny-robot build

Summary by CodeRabbit

New Features

  • Layout component with configurable left/right side panels, plus header/main/footer slots
  • Support for docked/drawer/rail side panel modes, including collapse/hidden behaviors
  • New controls: Aside toggle button and proxy scrollbar for scrollable targets
  • Floating mode with drag repositioning and resize handles
  • Interactive resizing for side panels (start/progress/end lifecycle) with backdrop + Escape-to-close behavior

Styling

  • Added centralized layout theme variables (Less) for consistent sizing and resize visuals

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@SonyLeo, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 58 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8ae1fa45-3c8b-4e1f-a0ba-6c1b9054faba

📥 Commits

Reviewing files that changed from the base of the PR and between bb231bf and 46e968d.

📒 Files selected for processing (1)
  • packages/components/src/layout/Layout.vue

Walkthrough

Introduces a complete Layout Vue component system including left/right aside panels (dock/drawer/rail modes), a floating detachable mode with drag and resize, a proxy scrollbar component, an aside toggle component, and full Vue plugin registration. New shared utilities, composables, geometry algorithms, CSS tokens, and type contracts support the implementation.

Changes

Layout Component System

Layer / File(s) Summary
Shared useControllableState and public/internal type contracts
packages/components/src/shared/composables/useControllableState.ts, packages/components/src/shared/composables/index.ts, packages/components/src/layout/index.type.ts, packages/components/src/layout/internal.type.ts
Adds useControllableState for controlled/uncontrolled state patterns. Defines all public-facing layout types (side, aside mode, floating state/options/handles, props, emits, slot shapes) and all internal runtime types (LayoutPanel, LayoutFloatingContext, LayoutContext, LayoutState).
Layout utility functions
packages/components/src/layout/utils/number.ts, packages/components/src/layout/utils/domInteraction.ts, packages/components/src/layout/utils/cssLength.ts, packages/components/src/layout/utils/slots.ts, packages/components/src/layout/utils/asidePresets.ts, packages/components/src/layout/utils/asideEventEmitters.ts, packages/components/src/layout/utils/layoutElements.ts
Adds clamp, body interaction lock/restore, CSS length parsing (toPx/resolveCssLengthToPx), VNode slot renderability detection, default aside sizing presets, typed emit helpers routing side-specific aside open/resize events, and DOM element selector/type-guard helpers.
Floating geometry and resize utilities
packages/components/src/layout/utils/surfaceGeometry.ts, packages/components/src/layout/utils/surfaceResize.ts
Implements viewport-bound resolution, placement-to-position calculations, floating rect clamping (free and handle-aware), snapshot creation, committed-state conversion, and equality comparison; adds resolveFloatingResizeRect with directional N/S/E/W adjustment helpers.
usePointerDragSession composable
packages/components/src/layout/composables/usePointerDragSession.ts
Reusable composable managing one active pointer-drag session: guards primary/left-button events, dispatches pointermove to the matching pointer ID, stops on pointerup/pointercancel, and auto-cleans on Vue scope disposal.
Root state factory and layout context DI
packages/components/src/layout/composables/useLayoutContext.ts, packages/components/src/layout/composables/useLayoutRootState.ts
useLayoutContext provides/injects LayoutContext via Vue DI with a missing-provider guard. createLayoutState builds left/right aside panel contexts (computing dock/drawer/rail/hidden flags and clamped widths via useControllableState) and a floating context (controllable floating state, merged resolved floating config, deduplicated initialize/commit actions).
AsideContent and AsideResizeTrigger components
packages/components/src/layout/components/AsideContent.vue, packages/components/src/layout/components/AsideResizeTrigger.vue
AsideResizeTrigger renders a pointer-driven resize handle: resolves layout element bounds, computes min/max width including CSS main min width, queues rAF width updates during drag, and emits aside resize lifecycle events with column-resize cursor lock. AsideContent accepts explicit layout state booleans, computes asideClass, conditionally renders AsideResizeTrigger, and applies scoped Less for dock/drawer/rail/hidden transforms.
FloatingDragBar, FloatingResizeTriggers, and LayoutSurface
packages/components/src/layout/components/FloatingDragBar.vue, packages/components/src/layout/components/FloatingResizeTriggers.vue, packages/components/src/layout/components/LayoutSurface.vue
FloatingDragBar wraps VueUse useDraggable with canDrag gating and emits drag lifecycle events. FloatingResizeTriggers renders per-handle triggers, runs pointer-drag resize sessions using geometry helpers, locks body interaction, and emits resize lifecycle events. LayoutSurface prevents concurrent drag+resize via activeFloatingInteraction, teleports to body when floating, wires drag/resize lifecycles to floating-state-change emissions, and syncs floating rect via a mode/viewport watcher.
LayoutProxyScrollbar component
packages/components/src/layout/LayoutProxyScrollbar.vue
Resolves scrollTarget to an HTMLElement, tracks scroll metrics via rAF-coalesced sync (scroll/wheel/resize/mutation observers), computes thumb geometry with minimum-size clamping, implements pointer-drag thumb dragging with body interaction lock, manages data-tr-layout-scroll-target attribute lifecycle, and renders the conditionally visible thumb.
Root Layout SFC and LayoutAsideToggle
packages/components/src/layout/Layout.vue, packages/components/src/layout/LayoutAsideToggle.vue
Layout.vue assembles the full component: creates layout state, coordinates drawer sibling conflicts, provides LayoutContext, emits aside resize events, computes CSS variable style maps and layoutClass flags, registers Escape-key drawer close, and renders the grid body with conditional AsideContent, header/main/footer slots, backdrop, and LayoutSurface with full floating event wiring. LayoutAsideToggle reads the selected panel's isOpen from context and renders a toggle button with fallback text.
Plugin wiring, design tokens, and component bundle registration
packages/components/src/layout/index.ts, packages/components/src/index.ts, packages/components/src/styles/components/layout.less, packages/components/src/styles/components/index.css
layout/index.ts assigns TrLayout* names, attaches install functions, and builds the compound Layout export with .ProxyScrollbar and .AsideToggle. The bundle index adds import, type re-export, array registrations, and named/alias exports. layout.less defines the .tr-layout-vars() Less mixin emitting CSS custom properties for backgrounds, floating geometry, resize indicators, and scrollbar sizing; index.css imports it.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Layout_vue as Layout.vue
  participant LayoutSurface
  participant FloatingDragBar
  participant FloatingResizeTriggers
  participant AsideResizeTrigger

  rect rgba(100, 149, 237, 0.5)
    note over User,AsideResizeTrigger: Aside resize flow
    User->>AsideResizeTrigger: pointerdown
    AsideResizeTrigger->>AsideResizeTrigger: lockBodyInteraction + capture pointer
    AsideResizeTrigger->>Layout_vue: aside-resize-start(detail)
    AsideResizeTrigger->>Layout_vue: width-change(value) via rAF
    AsideResizeTrigger->>Layout_vue: aside-resize-end(detail)
  end

  rect rgba(144, 238, 144, 0.5)
    note over User,FloatingDragBar: Floating drag flow
    User->>FloatingDragBar: pointerdown
    FloatingDragBar->>LayoutSurface: drag-start(rect)
    FloatingDragBar->>LayoutSurface: drag(rect)
    FloatingDragBar->>LayoutSurface: drag-end(rect)
    LayoutSurface->>Layout_vue: floating-drag-start/drag/drag-end + floating-state-change
  end

  rect rgba(255, 165, 0, 0.5)
    note over User,FloatingResizeTriggers: Floating resize flow
    User->>FloatingResizeTriggers: pointerdown (handle)
    FloatingResizeTriggers->>FloatingResizeTriggers: resolveFloatingResizeRect + clampFloatingRectByHandle
    FloatingResizeTriggers->>LayoutSurface: resize-start/resize/resize-end
    LayoutSurface->>Layout_vue: floating-resize-start/resize/resize-end + floating-state-change
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 Hoppity-hop through panels left and right,
The sidebar glides with dock and drawer delight.
Floating windows drift and resize with grace,
A proxy scrollbar tracks each scrolling pace.
CSS variables bloom like clover in spring—
What a layout system the rabbits did bring! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(layout): add layout component' accurately and concisely describes the primary change—introducing a new comprehensive Layout component to the library.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
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 unit tests (beta)
  • Create PR with unit tests

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.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

✅ Preview build completed successfully!

Click the image above to preview.
Preview will be automatically removed when this PR is closed.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

@SonyLeo SonyLeo changed the title feat(layout-comp): introduce layout components with aside and floating states feat(layout): add layout component Jun 12, 2026
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/index.type.ts
Comment thread packages/components/src/layout/Layout.vue Outdated
Comment thread packages/components/src/layout/composables/createLayoutContext.ts Outdated
Comment thread packages/components/src/layout/LayoutMain.vue Outdated
Comment thread packages/components/src/layout/index.type.ts Outdated
@SonyLeo SonyLeo marked this pull request as ready for review June 16, 2026 07:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/components/src/layout/index.type.ts`:
- Around line 74-82: The LayoutFloatingStateControlProps union type at line
74-82 has both branches requiring a property with type never, making the type
uninhabitable. Remove the never-typed properties from each branch instead: the
first branch should only require floatingState with no defaultFloatingState
property, and the second branch should only have an optional
defaultFloatingState property with no floatingState property. This creates a
proper exclusive union where one branch accepts floatingState and the other
accepts defaultFloatingState.

In `@packages/components/src/layout/LayoutAsideToggle.vue`:
- Around line 37-40: The toggle button in LayoutAsideToggle.vue lacks
accessibility attributes needed for screen readers and users relying on
assistive technology. Add an aria-label attribute to the button element with
class "tr-layout-aside-toggle" to provide an accessible name (especially
important since the default slot may contain only an icon), and add an
aria-pressed or aria-expanded attribute bound to the panel's state to convey
whether the aside panel is currently open or closed. These attributes should be
bound dynamically to reflect the current toggle state.

In `@packages/components/src/layout/utils/surfaceGeometry.ts`:
- Around line 282-288: The `resolveDefaultFloatingRect` function accepts a
`bounds` parameter but ignores it when computing constraints. Currently,
`resolveFloatingConstraints(config)` derives constraints from default viewport
bounds instead of the passed `bounds`, causing the clamped width and height
values to potentially exceed the custom bounds. Modify the function to pass the
`bounds` parameter to `resolveFloatingConstraints` (or update how constraints
are calculated) so that the width and height clamping respects the actual bounds
provided to the function rather than always defaulting to viewport bounds.

In `@packages/components/src/shared/composables/useControllableState.ts`:
- Around line 17-19: The issue is in the useControllableState composable where
internalState is only initialized once with options.defaultValue and never
updated while the component is in controlled mode. When isControlled transitions
from true to false, the resolvedState computed property falls back to the stale
internalState instead of the latest controlled value. Add a watcher that
monitors options.value and synchronizes internalState whenever isControlled is
true, ensuring that when the component transitions to uncontrolled mode,
internalState preserves the last controlled value instead of reverting to the
initial default value.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9b176ce3-faf4-4f31-98b8-33f6a4c0c3cc

📥 Commits

Reviewing files that changed from the base of the PR and between 045d26b and 6170efc.

📒 Files selected for processing (31)
  • packages/components/src/index.ts
  • packages/components/src/layout/Layout.vue
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/LayoutProxyScrollbar.vue
  • packages/components/src/layout/components/AsideContent.vue
  • packages/components/src/layout/components/AsideResizeTrigger.vue
  • packages/components/src/layout/components/FloatingResizeTrigger.vue
  • packages/components/src/layout/composables/useLayoutAsideResize.ts
  • packages/components/src/layout/composables/useLayoutContext.ts
  • packages/components/src/layout/composables/useLayoutDrawerActions.ts
  • packages/components/src/layout/composables/useLayoutFloating.ts
  • packages/components/src/layout/composables/useLayoutFloatingDrag.ts
  • packages/components/src/layout/composables/useLayoutFloatingResize.ts
  • packages/components/src/layout/composables/useLayoutProxyScrollbar.ts
  • packages/components/src/layout/composables/useLayoutRenderState.ts
  • packages/components/src/layout/composables/useLayoutRootState.ts
  • packages/components/src/layout/index.ts
  • packages/components/src/layout/index.type.ts
  • packages/components/src/layout/internal.type.ts
  • packages/components/src/layout/utils/asideDefaults.ts
  • packages/components/src/layout/utils/cssLength.ts
  • packages/components/src/layout/utils/domInteraction.ts
  • packages/components/src/layout/utils/emitAsideEvents.ts
  • packages/components/src/layout/utils/math.ts
  • packages/components/src/layout/utils/slots.ts
  • packages/components/src/layout/utils/surfaceGeometry.ts
  • packages/components/src/layout/utils/surfaceResize.ts
  • packages/components/src/shared/composables/index.ts
  • packages/components/src/shared/composables/useControllableState.ts
  • packages/components/src/styles/components/index.css
  • packages/components/src/styles/components/layout.less

Comment thread packages/components/src/layout/index.type.ts
Comment thread packages/components/src/layout/LayoutAsideToggle.vue Outdated
Comment thread packages/components/src/layout/utils/surfaceGeometry.ts
Comment thread packages/components/src/shared/composables/useControllableState.ts
Comment thread packages/components/src/layout/index.type.ts
export type LayoutFloatingResizeHandle = 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'

export interface LayoutAsideOpenEventDetail {
placement: LayoutPlacement

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

placement 很容易和 floating 的 placement 混淆,建议改成 side,专门表示 left-aside 和 right-aside 的位置,其他相关的地方也需要更改

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

好的,已经按照要求优化

expandedWidth: number
collapsedWidth: number | undefined
resizable: boolean
isRail: boolean

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

为什么需要 isRailisHiddencanResize 这些变量。
isRail 我理解是要判断窄边框,完全可以 open + collapsedWidth 替代。因为就算你用了 isRail,你还是可能需要 collapsedWidth 来写布局,所以 isRail 是多余的,而且理解难度也很大,第一眼看不出来这个变量表达什么意思。isHidden 也是同理。
canResizeresizable 有什么区别?看不出来

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

已经直接把 left-aside / right-aside 的 slotProps 移除了。

因为这组 LayoutAsideSlotProps 按照实际使用场景梳理下来算是冗余设计:

open、expandedWidth、collapsedWidth、resizable、mode 这类信息,外层本来就可以通过传入的 state 和 option 得到,没必要再从插槽重复暴露
isRail、isHidden、canResize 这类值更偏内部派生状态,也不适合暴露给内容插槽
移除后使用方式不受影响:

侧栏状态继续由外层通过 leftAside / rightAside + events 管理
插槽内部如果只需要局部开关,用 Layout.AsideToggle 即可

isResizing: isAsideResizing,
})

const { isFloating, showDragBar, floatingClass, floatingStyle, dragBarClass, resizeHandles } = useLayoutFloating({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

你这里把是否支持悬浮、drag的逻辑、resize的逻辑全部混合到一起了。直接下放到组件更好。组件只需要管理自己的交互逻辑

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

后续没有把 drag / resize 逻辑直接放回 Layout.vue,按照下述设计进行分层

Layout.vue 负责整体布局编排和状态组织,比如 aside / drawer / floating 状态管理,以及对外事件转发。

LayoutSurface 负责 floating surface 这一层能力,包括 Teleport、浮层定位、drag、resize 和对应的 class/style。

具体交互细节由 FloatingDragBarFloatingResizeTriggers 负责,分别处理浮层拖动、浮层缩放。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
packages/components/src/layout/composables/useLayoutRootState.ts (1)

113-121: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate resolved floating state by mode to avoid stale state after mode switches.

At Line 114, resolvedFloatingState is not mode-guarded. When mode changes from floating to normal, the last floating state can remain cached and keep resolvedFloating non-empty.

Suggested fix
-  const resolvedFloatingState = computed(() => floatingState.resolvedState.value)
+  const resolvedFloatingState = computed(() =>
+    props.mode === 'floating' ? floatingState.resolvedState.value : undefined,
+  )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/components/src/layout/composables/useLayoutRootState.ts` around
lines 113 - 121, The resolvedFloatingState is not mode-guarded in the
resolvedFloating computed property, causing cached floating state to persist
when the mode switches from floating to normal. Gate the nextFloatingState
assignment by checking the mode first, similar to how nextFloatingOptions is
already guarded - only retrieve floatingState.resolvedState.value when
props.mode === 'floating', otherwise set nextFloatingState to undefined to
prevent stale state from remaining after a mode switch.
packages/components/src/layout/utils/cssLength.ts (1)

6-21: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Accept zero-valued CSS lengths with units.

At Line 6 and Line 19, values like 0rem / 0% now miss the zero fast-path and incorrectly resolve to fallback instead of 0.

Suggested fix
-const ZERO_LENGTH_RE = /^0(?:\.0+)?$/i
+const ZERO_LENGTH_RE = /^0(?:\.0+)?(?:[a-z%]+)?$/i
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/components/src/layout/utils/cssLength.ts` around lines 6 - 21, The
ZERO_LENGTH_RE regex pattern at the top of the resolveCssLengthToPx function
currently only matches plain zero values like "0" or "0.0" but does not match
zero-valued CSS lengths with units such as "0rem", "0%", or "0px". When these
unit-based values are tested against the regex in the function, the test fails
and the function falls through to other logic instead of correctly returning 0.
Update the ZERO_LENGTH_RE regex pattern to also match optional CSS units (such
as rem, px, %, em, vh, vw, etc.) that may appear after the zero value.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/components/src/layout/Layout.vue`:
- Around line 110-117: The computed properties leftDockWidth and rightDockWidth
are calculated unconditionally regardless of whether their corresponding aside
slots actually exist. This causes incorrect resize bounds to be applied even
when the slot is not rendered. Gate leftDockWidth to return a value from
getDockedAsideWidth(drawer.left) only when hasLeftAside is true, and similarly
gate rightDockWidth to return a value from getDockedAsideWidth(drawer.right)
only when hasRightAside is true. Otherwise, both should return 0 or a default
value. Apply this same fix to the other locations mentioned (lines 192-193 and
225-226).

---

Outside diff comments:
In `@packages/components/src/layout/composables/useLayoutRootState.ts`:
- Around line 113-121: The resolvedFloatingState is not mode-guarded in the
resolvedFloating computed property, causing cached floating state to persist
when the mode switches from floating to normal. Gate the nextFloatingState
assignment by checking the mode first, similar to how nextFloatingOptions is
already guarded - only retrieve floatingState.resolvedState.value when
props.mode === 'floating', otherwise set nextFloatingState to undefined to
prevent stale state from remaining after a mode switch.

In `@packages/components/src/layout/utils/cssLength.ts`:
- Around line 6-21: The ZERO_LENGTH_RE regex pattern at the top of the
resolveCssLengthToPx function currently only matches plain zero values like "0"
or "0.0" but does not match zero-valued CSS lengths with units such as "0rem",
"0%", or "0px". When these unit-based values are tested against the regex in the
function, the test fails and the function falls through to other logic instead
of correctly returning 0. Update the ZERO_LENGTH_RE regex pattern to also match
optional CSS units (such as rem, px, %, em, vh, vw, etc.) that may appear after
the zero value.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6a8038d9-3cb2-4ff5-ad57-2c28c7897bc1

📥 Commits

Reviewing files that changed from the base of the PR and between 69d167a and bb231bf.

📒 Files selected for processing (20)
  • packages/components/src/layout/Layout.vue
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/LayoutProxyScrollbar.vue
  • packages/components/src/layout/components/AsideContent.vue
  • packages/components/src/layout/components/AsideResizeTrigger.vue
  • packages/components/src/layout/components/FloatingDragBar.vue
  • packages/components/src/layout/components/FloatingResizeTriggers.vue
  • packages/components/src/layout/components/LayoutSurface.vue
  • packages/components/src/layout/composables/useLayoutContext.ts
  • packages/components/src/layout/composables/useLayoutRootState.ts
  • packages/components/src/layout/composables/usePointerDragSession.ts
  • packages/components/src/layout/index.type.ts
  • packages/components/src/layout/internal.type.ts
  • packages/components/src/layout/utils/asideEventEmitters.ts
  • packages/components/src/layout/utils/asidePresets.ts
  • packages/components/src/layout/utils/cssLength.ts
  • packages/components/src/layout/utils/layoutElements.ts
  • packages/components/src/layout/utils/number.ts
  • packages/components/src/layout/utils/slots.ts
  • packages/components/src/layout/utils/surfaceGeometry.ts
💤 Files with no reviewable changes (1)
  • packages/components/src/layout/utils/number.ts
✅ Files skipped from review due to trivial changes (2)
  • packages/components/src/layout/utils/asidePresets.ts
  • packages/components/src/layout/utils/layoutElements.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/components/src/layout/LayoutAsideToggle.vue
  • packages/components/src/layout/utils/surfaceGeometry.ts

Comment thread packages/components/src/layout/Layout.vue Outdated
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.

2 participants