Skip to content

feat: support raw list sticky sections#46

Merged
zombieJ merged 25 commits into
masterfrom
codex/raw-list-sticky-sections
May 25, 2026
Merged

feat: support raw list sticky sections#46
zombieJ merged 25 commits into
masterfrom
codex/raw-list-sticky-sections

Conversation

@zombieJ
Copy link
Copy Markdown
Member

@zombieJ zombieJ commented May 18, 2026

Summary

  • add a RawList path for virtual={false} that renders grouped content as section blocks
  • keep virtual rendering on @rc-component/virtual-list, with shared list props for both paths
  • support scrollTo by item key and group key across virtual and raw list modes
  • offset sticky group headers so top-aligned scroll targets remain visible

Checks

  • CI: bun run lint
  • CI: bunx tsc --noEmit
  • CI: bun run compile
  • CI: bun run test -- --coverage
  • Local: ut test -- --runInBand

Summary by CodeRabbit

发行说明

  • 新增功能

    • 增强了列表滚动定位 API,现支持坐标定位和对齐选项
    • GroupHeader 组件现支持 ref 引用
  • 改进

    • 优化了虚拟列表性能表现
    • 完善了组表头样式层级关系
  • 依赖更新

    • 更新虚拟列表依赖至新版本

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 18, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aca636c9-5a49-46f4-b463-bce71c639640

📥 Commits

Reviewing files that changed from the base of the PR and between cc4923b and f8a742a.

⛔ Files ignored due to path filters (1)
  • tests/__snapshots__/listy.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (13)
  • README.md
  • assets/index.less
  • docs/examples/basic.tsx
  • docs/examples/no-virtual.tsx
  • package.json
  • src/GroupHeader.tsx
  • src/List.tsx
  • src/RawList/index.tsx
  • src/RawList/useRawListScroll.ts
  • src/VirtualList/index.tsx
  • src/VirtualList/useStickyGroupHeader.tsx
  • tests/hooks.test.tsx
  • tests/listy.behavior.test.tsx

总体说明

本 PR 对 Listy 列表组件进行了深度架构重构,将原先在单个 List.tsx 中混合的渲染、分组、粘性头、虚拟化等逻辑拆解为独立的、各司其职的组件层:

  • List.tsx 演变为纯粹的路由层,根据 virtual 属性分发到 RawList 或 VirtualList
  • RawList 承载普通列表的完整逻辑,包括分组、粘性头(通过 ResizeObserver 测量)与滚动定位
  • VirtualList 在虚拟列表库之上构建分组和粘性头能力,并通过二分查找优化粘性头查询
  • GroupHeader 支持 ref 转发,使上层组件能获取组头元素引用
  • scrollTo API 从依赖外部虚拟列表库的类型改为本地定义,支持更明确的坐标和 key/groupKey 定位

改动概览

层级 文件(s) 变更内容
GroupHeader ref 转发与 API 更新
src/GroupHeader.tsx, README.md
通过 React.forwardRef 暴露 HTMLDivElement,同时更新 README 中 ListyRef.scrollTo 的参数类型为 number | null | {left?/top?} | {key/align/offset} | {groupKey/align/offset}
List 简化与滚动配置重构
src/List.tsx, package.json
List 变为薄包装层,新增本地 ListyScrollToConfig 类型,调整依赖版本 virtual-list ^1.1.0 → ^1.2.0
RawList 普通列表实现
src/RawList/index.tsx, src/RawList/useRawListScroll.ts
新增完整的非虚拟列表实现,支持分组、粘性组头(ResizeObserver 测量)与三种滚动模式(数值、key/groupKey 定位、坐标)
VirtualList 虚拟列表实现
src/VirtualList/index.tsx, src/VirtualList/useStickyGroupHeader.tsx
新增虚拟列表封装,集成分组与粘性头,通过二分查找优化表头索引查询,支持 offset 函数动态计算
样式与示例
assets/index.less, docs/examples/*
为粘性组头增加 z-index,为分组容器增加相对定位;示例演示 groupKey 和 key 两种滚动方式
测试扩展
tests/listy.behavior.test.tsx, tests/hooks.test.tsx
补充虚拟项包装验证、RawList 分组/不分组滚动、粘性组头高度应用、虚拟滚动 offset 动态计算等用例

序列图

sequenceDiagram
  participant App as 应用层
  participant Listy as Listy<br/>薄包装
  participant RawList as RawList<br/>普通列表
  participant VirtualList as VirtualList<br/>虚拟列表
  participant RcVirtualList as rc-virtual-list<br/>底层库

  App->>Listy: props(virtual=false)
  Listy->>RawList: 分发
  RawList->>App: 暴露 scrollTo

  App->>Listy: props(virtual=true)
  Listy->>VirtualList: 分发
  VirtualList->>RcVirtualList: 配置虚拟化
  VirtualList->>App: 暴露 scrollTo
Loading

审核要点

  1. 架构分层的清晰性:List 是否真正成为无状态的路由层,RawList/VirtualList 是否完全独立且可复用
  2. scrollTo 实现的一致性:RawList 与 VirtualList 的 scrollTo 签名和行为是否对齐
  3. 粘性组头的正确性:RawList 的 ResizeObserver + scrollMarginTop 与 VirtualList 的 useStickyGroupHeader 动态偏移计算是否准确
  4. 向下兼容性:新的 ListyScrollToConfig 与旧的 scrollTo 调用方式是否兼容
  5. 性能影响:二分查找替代线性查询是否带来可观的性能提升;虚拟化在大列表中的渲染效果

总览

本 PR 重构列表组件的内部架构,将单体 List.tsx 拆解为 RawList(普通列表)与 VirtualList(虚拟列表)两个独立实现,由 List 作为薄包装层根据 virtual 属性分发;同时为 GroupHeader 增加 ref 转发能力,并优化 useStickyGroupHeader 的性能,引入本地 scrollTo 配置类型。

改动概览

组件架构重构

层级 文件(s) 内容
GroupHeader ref 转发与 API 更新
src/GroupHeader.tsx, README.md
GroupHeader 通过 React.forwardRef 暴露根元素 ref;README 更新 ListyRef.scrollTo 参数类型,支持坐标、key/groupKey 定位
List 简化与类型重构
src/List.tsx, package.json
List 转为无状态路由层,新增本地 ListyScrollToConfig/ListComponentProps 类型,更新依赖版本
RawList 普通列表实现
src/RawList/index.tsx, src/RawList/useRawListScroll.ts
新增非虚拟列表实现,支持分组、粘性组头(ResizeObserver 测量)、三种滚动模式
VirtualList 虚拟列表实现
src/VirtualList/index.tsx, src/VirtualList/useStickyGroupHeader.tsx
新增虚拟列表封装,优化粘性头查询(二分查找),支持 offset 函数
样式、示例与测试
assets/index.less, docs/examples/*, tests/*
调整样式分层,演示新 API,补充行为断言

🎯 3 (Moderate) | ⏱️ ~25 分钟

可能相关的 PR

  • react-component/listy#45:提取 GroupHeader 组件的初期变更,与本 PR 对 GroupHeader ref 转发的重构形成演进关系
  • react-component/listy#17:定义了公开的 ListyRef.scrollTo 类型契约,本 PR 在其基础上重新实现了该 API 的本地类型与双路径实现
  • react-component/listy#44:对 src/List.tsx 中分组滚动配置的先期重构,本 PR 延续了 GroupScrollToConfig 与 scrollTo 联合类型的设计模式

🐰 一个兔子的感言

架构拆分,层次分明,
RawList 与 Virtual 齐奋进。
粘性之头,高度自测,
二分查找,滚动更疾。
🌟 妙哉,重构悠悠然!

✨ 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 codex/raw-list-sticky-sections

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

docs/examples/basic.tsx

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

docs/examples/no-virtual.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

src/GroupHeader.tsx

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 7 others

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 18, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (cc4923b) to head (f8a742a).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff             @@
##           master       #46      +/-   ##
===========================================
+ Coverage   98.94%   100.00%   +1.05%     
===========================================
  Files           5         8       +3     
  Lines          95       201     +106     
  Branches       23        55      +32     
===========================================
+ Hits           94       201     +107     
+ Misses          1         0       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a non-virtualized list implementation through the new RawList component, which is used when the virtual prop is set to false. It also optimizes the sticky header logic by replacing a linear search with a binary search for finding the active header index. Review feedback suggests preventing runtime warnings by ensuring refs are not attached to React.Fragment and improving performance by using the mapping function within Array.from when rendering grouped data.

Comment thread src/RawList.tsx Outdated
registerElement(key, rowIndex, element);
};

if (React.isValidElement(node)) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Attaching a ref to a React.Fragment will result in a runtime warning and the ref will not be attached, which would break the scrollTo functionality for that item. It is safer to check if the element is a Fragment before attempting to clone it with a ref.

Suggested change
if (React.isValidElement(node)) {
if (React.isValidElement(node) && node.type !== React.Fragment) {

Comment thread src/RawList.tsx Outdated
let rowIndex = 0;
const rawContent = group
? Array.from(groupData).map(([groupKey, groupItems]) => {
const headerRow = { type: 'header', groupKey } as Row<T, K>;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When converting a Map to an array for rendering, you can use the second argument of Array.from as a mapping function. This is slightly more efficient than calling .map() on the intermediate array created by Array.from(groupData).

    ? Array.from(groupData, ([groupKey, groupItems]) => {

Comment thread src/RawList.tsx Outdated
})}
</section>
);
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

This closing parenthesis needs to be adjusted if the Array.from mapping function optimization is applied.

      )

@zombieJ zombieJ force-pushed the codex/raw-list-sticky-sections branch from a49095e to c69d6cc Compare May 18, 2026 15:46
@zombieJ zombieJ changed the title [codex] add raw list sticky sections feat: support raw list sticky sections May 25, 2026
@zombieJ zombieJ marked this pull request as ready for review May 25, 2026 08:40
@zombieJ zombieJ merged commit bb0a4c9 into master May 25, 2026
8 of 9 checks passed
@zombieJ zombieJ deleted the codex/raw-list-sticky-sections branch May 25, 2026 08:40
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