From eab2024c216ee184ff62a1a4d829b09c5b3c9826 Mon Sep 17 00:00:00 2001 From: aojunhao123 <1844749591@qq.com> Date: Sat, 11 Jul 2026 15:54:59 +0800 Subject: [PATCH] feat: support rtl direction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `direction?: 'ltr' | 'rtl'` prop threaded to both list backends: - VirtualList forwards `direction` to the underlying `@rc-component/virtual-list`, which sets `dir`, the `rc-listy-rtl` class, flips the scrollbar, and exposes `rtl` to sticky-header logic. - RawList sets `dir` and the `rc-listy-rtl` class on its root for parity. No CSS mirroring needed — the sticky/fixed group header already spans the full width (`left:0; right:0`), so it is direction-agnostic. Adds an RTL demo, README prop docs, and tests covering both backends. --- README.md | 1 + README.zh-CN.md | 1 + docs/demos/rtl.md | 8 ++++ docs/examples/rtl.tsx | 71 +++++++++++++++++++++++++++++++++++ src/List.tsx | 2 + src/RawList/index.tsx | 8 +++- src/VirtualList/index.tsx | 2 + tests/listy.behavior.test.tsx | 15 ++++++++ 8 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 docs/demos/rtl.md create mode 100644 docs/examples/rtl.tsx diff --git a/README.md b/README.md index 5196fc5..d6292f4 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ export default () => ( | `group` | Group configuration. | `{ key: (item: T) => K; title: (groupKey: K, items: T[]) => React.ReactNode }` | - | | `sticky` | Enable sticky group headers. | `boolean` | `false` | | `virtual` | Enable virtual scrolling. | `boolean` | `true` | +| `direction` | Layout direction; set `rtl` for right-to-left. | `'ltr' \| 'rtl'` | `'ltr'` | | `onScroll` | Triggered when the inner scroll container scrolls. | `React.UIEventHandler` | - | | `prefixCls` | Component class name prefix. | `string` | `rc-listy` | diff --git a/README.zh-CN.md b/README.zh-CN.md index 8e274b6..bd4ab4e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -60,6 +60,7 @@ export default () => ( | `group` | 分组配置。 | `{ key: (item: T) => K; title: (groupKey: K, items: T[]) => React.ReactNode }` | - | | `sticky` | 启用粘性组头。 | `boolean` | `false` | | `virtual` | 启用虚拟滚动。 | `boolean` | `true` | +| `direction` | 布局方向,设为 `rtl` 时启用从右到左布局。 | `'ltr' \| 'rtl'` | `'ltr'` | | `onScroll` | 内部滚动容器滚动时触发。 | `React.UIEventHandler` | - | | `prefixCls` | 组件样式前缀。 | `string` | `rc-listy` | diff --git a/docs/demos/rtl.md b/docs/demos/rtl.md new file mode 100644 index 0000000..4bb7df2 --- /dev/null +++ b/docs/demos/rtl.md @@ -0,0 +1,8 @@ +--- +title: RTL +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/examples/rtl.tsx b/docs/examples/rtl.tsx new file mode 100644 index 0000000..0cca943 --- /dev/null +++ b/docs/examples/rtl.tsx @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import Listy from '@rc-component/listy'; +import '../../assets/index.less'; + +export default () => { + const [direction, setDirection] = useState<'ltr' | 'rtl'>('rtl'); + const [virtual, setVirtual] = useState(true); + + const rtl = direction === 'rtl'; + const groupSize = 10; + const total = 120; + const items = Array.from({ length: total }, (_, index) => ({ + id: index + 1, + index, + groupIndex: Math.floor(index / groupSize), + })); + + const itemStyle: React.CSSProperties = { + padding: '0 12px', + height: 32, + lineHeight: '32px', + borderBottom: '1px solid #efefef', + background: '#fff', + }; + + return ( +
+
+ + +
+ item.groupIndex, + title: (groupKey) => ( +
+ {rtl ? `مجموعة ${groupKey}` : `Group ${groupKey}`} +
+ ), + }} + itemRender={(item) => ( +
+ {rtl ? `العنصر ${item.index}` : `Item ${item.index}`} +
+ )} + /> +
+ ); +}; diff --git a/src/List.tsx b/src/List.tsx index deb25d2..0728fd8 100644 --- a/src/List.tsx +++ b/src/List.tsx @@ -50,6 +50,7 @@ export interface ListyProps { height?: number; group?: Group; virtual?: boolean; + direction?: 'ltr' | 'rtl'; prefixCls?: string; rowKey: RowKey; classNames?: ListyClassNames; @@ -64,6 +65,7 @@ export interface ListComponentProps { itemHeight?: number; height?: number; group?: Group; + direction?: 'ltr' | 'rtl'; prefixCls: string; rowKey: RowKey; classNames?: ListyClassNames; diff --git a/src/RawList/index.tsx b/src/RawList/index.tsx index facb150..5437218 100644 --- a/src/RawList/index.tsx +++ b/src/RawList/index.tsx @@ -24,6 +24,7 @@ function RawList( prefixCls, rowKey, sticky, + direction, classNames, styles, } = props; @@ -118,7 +119,12 @@ function RawList( return (
( prefixCls, rowKey, sticky, + direction, classNames, styles, } = props; @@ -170,6 +171,7 @@ function VirtualList( { }), ).toBe(29); }); + + it('forwards direction to the virtual list', () => { + renderList({ direction: 'rtl' }); + + expect(MockedVirtualList.__getLastProps().direction).toBe('rtl'); + }); + + it('applies dir and rtl class on the raw list', () => { + const { container } = renderList({ virtual: false, direction: 'rtl' }); + + const holder = container.querySelector('.rc-listy') as HTMLDivElement; + + expect(holder).toHaveClass('rc-listy-rtl'); + expect(holder).toHaveAttribute('dir', 'rtl'); + }); });