Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tabs): support RTL mode #6313

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ exports[`CascaderView basic usage 1`] = `
>
<div
class="adm-tabs adm-cascader-view-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down Expand Up @@ -271,6 +272,7 @@ exports[`CascaderView basic usage 2`] = `
>
<div
class="adm-tabs adm-cascader-view-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down Expand Up @@ -669,6 +671,7 @@ exports[`CascaderView basic usage 3`] = `
>
<div
class="adm-tabs adm-cascader-view-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down Expand Up @@ -1105,6 +1108,7 @@ exports[`CascaderView controlled mode 1`] = `
>
<div
class="adm-tabs adm-cascader-view-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down Expand Up @@ -1541,6 +1545,7 @@ exports[`CascaderView same value in options 1`] = `
>
<div
class="adm-tabs adm-cascader-view-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down Expand Up @@ -1765,6 +1770,7 @@ exports[`CascaderView same value in options 2`] = `
>
<div
class="adm-tabs adm-cascader-view-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down Expand Up @@ -2103,6 +2109,7 @@ exports[`CascaderView same value in options 3`] = `
>
<div
class="adm-tabs adm-cascader-view-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down
69 changes: 69 additions & 0 deletions src/components/tabs/demos/demo5.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from 'react'
import { Tabs, TabsProps, Radio, Space, Divider, Switch } from 'antd-mobile'
import { DemoBlock } from 'demos'

const modes = ['auto', 'full', 'fixed'] as const

export default () => {
const [rtl, setRtl] = React.useState(true)
const [activeLineMode, setActiveLineMode] =
React.useState<TabsProps['activeLineMode']>('auto')

return (
<DemoBlock title='RTL' padding='0'>
<Switch
checkedText={'RTL'}
uncheckedText={'LTR'}
checked={rtl}
onChange={v => {
setRtl(v)
}}
/>
<Divider />
activeLineMode:
<Radio.Group
value={activeLineMode}
onChange={v => {
setActiveLineMode(v as TabsProps['activeLineMode'])
}}
>
<Space>
{modes.map(mode => (
<Radio value={mode} key={mode}>
{mode}
</Radio>
))}
</Space>
</Radio.Group>
<Divider />
<Tabs
defaultActiveKey='1'
style={{ marginTop: 12 }}
direction={rtl ? 'rtl' : 'ltr'}
activeLineMode={activeLineMode}
>
<Tabs.Tab title='Espresso' key='1'>
1
</Tabs.Tab>
<Tabs.Tab title='Coffee Latte' key='2'>
2
</Tabs.Tab>
<Tabs.Tab title='Cappuccino' key='3'>
3
</Tabs.Tab>
<Tabs.Tab title='Americano' key='4'>
4
</Tabs.Tab>
<Tabs.Tab title='Flat White' key='5'>
5
</Tabs.Tab>
<Tabs.Tab title='Caramel Macchiato' key='6'>
6
</Tabs.Tab>
<Tabs.Tab title='Cafe Mocha' key='7'>
7
</Tabs.Tab>
</Tabs>
</DemoBlock>
)
}
3 changes: 3 additions & 0 deletions src/components/tabs/index.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ The current content needs to be divided into groups of the same hierarchical str

<code src="./demos/demo4.tsx"></code>

<code src="./demos/demo5.tsx"></code>

## Tabs

### Props
Expand All @@ -27,6 +29,7 @@ The current content needs to be divided into groups of the same hierarchical str
| defaultActiveKey | The initialized `key` of the selected panel, if the `activeKey` is not set | `string \| null` | the `key` of the 1st pannel |
| onChange | Callback when switching panel | `(key: string) => void` | - |
| stretch | Whether stretch the tab header | `boolean` | `true` |
| direction | Document layout direction | `'ltr' \| 'rtl'` | `'ltr'` |

### CSS Variables

Expand Down
3 changes: 3 additions & 0 deletions src/components/tabs/index.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

<code src="./demos/demo4.tsx"></code>

<code src="./demos/demo5.tsx"></code>

## Tabs

### 属性
Expand All @@ -27,6 +29,7 @@
| defaultActiveKey | 初始化选中面板的 `key`,如果没有设置 `activeKey` | `string \| null` | 第一个面板的 `key` |
| onChange | 切换面板的回调 | `(key: string) => void` | - |
| stretch | 选项卡头部是否拉伸 | `boolean` | `true` |
| direction | 文档排版方向 | `'ltr' \| 'rtl'` | `'ltr'` |

### CSS 变量

Expand Down
65 changes: 59 additions & 6 deletions src/components/tabs/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
stretch?: boolean
onChange?: (key: string) => void
children?: React.ReactNode
direction?: 'ltr' | 'rtl'
} & NativeProps<
| '--fixed-active-line-width'
| '--active-line-height'
Expand All @@ -52,6 +53,7 @@
const defaultProps = {
activeLineMode: 'auto',
stretch: true,
direction: 'ltr',
}

export const Tabs: FC<TabsProps> = p => {
Expand All @@ -63,6 +65,8 @@

const panes: ReactElement<TabProps>[] = []

const isRTL = props.direction === 'rtl'

traverseReactNode(props.children, (child, index) => {
if (!isValidElement<TabProps>(child)) return

Expand Down Expand Up @@ -150,6 +154,19 @@
} else {
x = activeTabLeft + (activeTabWidth - activeLineWidth) / 2
}

if (isRTL) {
/**
* In RTL mode, x equals the container width minus the x-coordinate of the current tab minus the width of the current tab.
* https://github.com/Fog3211/reproduce-codesandbox/blob/f0a3396a114cc00e88a51a67d3be60a746519b30/assets/images/antd_mobile_tabs_rtl_x.jpg?raw=true
*/
const w =
props.activeLineMode === 'auto' || props.activeLineMode === 'full'
? width
: activeLineWidth
x = -(containerWidth - x - w)

Check warning on line 167 in src/components/tabs/tabs.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/tabs/tabs.tsx#L165-L167

Added lines #L165 - L167 were not covered by tests
}

api.start({
x,
width,
Expand All @@ -159,12 +176,27 @@
const maxScrollDistance = containerScrollWidth - containerWidth
if (maxScrollDistance <= 0) return

const nextScrollLeft = bound(
let nextScrollLeft = bound(

Check warning on line 179 in src/components/tabs/tabs.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/tabs/tabs.tsx#L179

Added line #L179 was not covered by tests
activeTabLeft - (containerWidth - activeTabWidth) / 2,
0,
containerScrollWidth - containerWidth
maxScrollDistance
)

if (isRTL) {
/**
* 位移距离等于:activeTab的中心坐标距离容器中心坐标的距离,然后RTL取负数
* containerWidth / 2 - (activeTabLeft + (activeTabWidth - activeLineWidth) / 2) - activeLineWidth / 2,
*/
nextScrollLeft = -bound(

Check warning on line 190 in src/components/tabs/tabs.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/tabs/tabs.tsx#L190

Added line #L190 was not covered by tests
containerWidth / 2 -
activeTabLeft +
activeTabWidth / 2 -
activeLineWidth,
0,
maxScrollDistance
)
}

scrollApi.start({
scrollLeft: nextScrollLeft,
from: { scrollLeft: containerScrollLeft },
Expand Down Expand Up @@ -202,9 +234,25 @@
if (!container) return

const scrollLeft = container.scrollLeft
const showLeftMask = scrollLeft > 0
const showRightMask =
scrollLeft + container.offsetWidth < container.scrollWidth

let showLeftMask = false
let showRightMask = false

if (isRTL) {
/**
* RTL模式下,只要滑动过,scrollLeft就再也回不到0(chrome是0.5)
* 所以要加round才能终止触发条件
* round(443.5) + 375 < 819
*/
showLeftMask =

Check warning on line 247 in src/components/tabs/tabs.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/tabs/tabs.tsx#L247

Added line #L247 was not covered by tests
Math.round(-scrollLeft) + container.offsetWidth <
container.scrollWidth
showRightMask = scrollLeft < 0

Check warning on line 250 in src/components/tabs/tabs.tsx

View check run for this annotation

Codecov / codecov/patch

src/components/tabs/tabs.tsx#L250

Added line #L250 was not covered by tests
} else {
showLeftMask = scrollLeft > 0
showRightMask =
scrollLeft + container.offsetWidth < container.scrollWidth
}

maskApi.start({
leftMaskOpacity: showLeftMask ? 1 : 0,
Expand All @@ -225,7 +273,12 @@

return withNativeProps(
props,
<div className={classPrefix}>
<div
className={classPrefix}
style={{
direction: props.direction,
}}
>
<div className={`${classPrefix}-header`}>
<animated.div
className={classNames(
Expand Down
2 changes: 2 additions & 0 deletions src/components/tabs/tests/__snapshots__/tabs.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ exports[`Tabs \`activeLineMode\` prop 1`] = `
<div>
<div
class="adm-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down Expand Up @@ -73,6 +74,7 @@ exports[`Tabs \`activeLineMode\` prop 2`] = `
<div>
<div
class="adm-tabs"
style="direction: ltr;"
>
<div
class="adm-tabs-header"
Expand Down
Loading