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

Tabs交互体验优化 #72

Open
wants to merge 4 commits into
base: releases/250318
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ BUI是基于`React`技术栈开发的 _跨端_ 组件库,一套代码在web和
- 💪 完善的的说明文档和组件示例,便于开发者快速上手
- 🎯 支持按需引用
- 💡 单元测试覆盖率超过90%, 稳定性有保障
- ✨ 30+ 高质量组件, 移动端优先的的组件场景
- ✨ 40+ 高质量组件, 移动端优先的的组件场景
- 👋 组件封装更灵活,自由的搭配使用

## 快速上手
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/introduce.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ _Bifrost UI_, originally referred to the rainbow bridge in Norse mythology that
- 💪 Complete documentation and component examples for developers to quickly get started
- 🎯 Support on-demand referencing
- 💡 Unit testing coverage exceeds 90%, with guaranteed stability
- ✨ 30+high-quality components, mobile first component scenarios
- ✨ 40+high-quality components, mobile first component scenarios
- 👋 Component packaging is more flexible, allowing for free combination and use

## Get started quickly
Expand Down
2 changes: 1 addition & 1 deletion docs/guide/introduce.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ _Bifrost UI_,bifrost原指北欧神话中连接天宫和大地的彩虹桥,
- 💪 完善的的说明文档和组件示例,便于开发者快速上手
- 🎯 支持按需引用
- 💡 单元测试覆盖率超过90%, 稳定性有保障
- ✨ 30+ 高质量组件, 移动端优先的的组件场景
- ✨ 40+ 高质量组件, 移动端优先的的组件场景
- 👋 组件封装更灵活,自由的搭配使用

## 快速上手
Expand Down
12 changes: 8 additions & 4 deletions packages/bui-core/src/Tabs/Tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ import './Tab.less';
const prefixCls = 'bui-tab';

const Tab = React.forwardRef<HTMLDivElement, TabProps>((props, ref) => {
const { className, children, index, disabled, onClick, ...others } = props;
const {
className,
children,
index,
disabled = false,
onClick,
...others
} = props;
const tabsContext = React.useContext(TabsContext);
const { value, align, triggerChange } = tabsContext;

Expand Down Expand Up @@ -46,8 +53,5 @@ const Tab = React.forwardRef<HTMLDivElement, TabProps>((props, ref) => {
});

Tab.displayName = 'BuiTab';
Tab.defaultProps = {
disabled: false,
};

export default Tab;
48 changes: 33 additions & 15 deletions packages/bui-core/src/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ import React, {
import Tab from './Tab';
import { TabsProps } from './Tabs.types';
import { TabsContextProvider } from './TabsContext';
import bound from './utils/bound';
import scrollLeftTo from './utils/scroll';
import './Tabs.less';

const prefixCls = 'bui-tabs';
const duration = 300;

const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
const { children, className, value, tabs, align, onChange, ...others } =
Expand All @@ -35,16 +36,14 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
rightMaskOpacity: 0,
});

const animate = ({ transitionInUse }: { transitionInUse: boolean }) => {
const getActiveTabElement = () => {
const container = tabsRef.current;
if (!container) return;
if (!container) return undefined;

const activeIndex =
!!tabs.length && tabs.findIndex((item) => item.index === active);
const activeLine = activeLineRef.current;
if (!activeLine) return;

let activeTab;

if (tabs.length) {
activeTab =
activeIndex > -1
Expand All @@ -61,6 +60,31 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
}) as HTMLDivElement;
}

return activeTab;
};

const scrollIntoView = () => {
const tabsContainer = tabsRef.current;
const activeTab = getActiveTabElement();
if (!tabsContainer || !activeTab) {
return;
}

const to =
activeTab.offsetLeft -
(tabsContainer.offsetWidth - activeTab.offsetWidth) / 2;
scrollLeftTo(tabsContainer, to, duration);
};

const animate = ({ transitionInUse }: { transitionInUse: boolean }) => {
const container = tabsRef.current;
if (!container) return;

const activeLine = activeLineRef.current;
if (!activeLine) return;

const activeTab = getActiveTabElement();

let activeTabLeft = 0;
let activeTabWidth = 0;
let containerWidth = 0;
Expand All @@ -83,14 +107,8 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
const maxScrollDistance = containerScrollWidth - containerWidth;
if (maxScrollDistance <= 0 || !activeTab) return;

const nextScrollLeft = bound(
activeTabLeft - (containerWidth - activeTabWidth) / 2,
0,
containerScrollWidth - containerWidth,
);

if (tabsRef.current) {
tabsRef.current.scrollLeft = nextScrollLeft;
if (!isMini) {
scrollIntoView();
}
};

Expand Down Expand Up @@ -190,7 +208,7 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>((props, ref) => {
})}
style={{
transition: lineData.transitionInUse
? 'transform 0.25s ease-in-out'
? `transform ${duration / 1000}s ease`
: undefined,
transform: `translate3d(${lineData.x}px, 0px, 0px)`,
}}
Expand Down
5 changes: 4 additions & 1 deletion packages/bui-core/src/Tabs/__tests__/Tabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ describe('Tabs', () => {

beforeEach(() => {
document.body.innerHTML = '';
jest.mock('@bifrostui/utils', () => ({
isMini: false,
}));
jest.useFakeTimers();
});

Expand Down Expand Up @@ -74,7 +77,7 @@ describe('Tabs', () => {
const tabLine = container.querySelector(`.${rootClass.tabs}-tabline`);
expect(tabLine).toHaveStyle({
transform: 'translate3d(0px, 0px, 0px)',
transition: 'transform 0.25s ease-in-out',
transition: 'transform 0.3s ease',
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ exports[`Tabs test with TabPanel should be actived by \`value\` property 1`] = `
>
<div
class="bui-tabs-tabline bui-tabline-invisible"
style="transform: translate3d(0px, 0px, 0px); transition: transform 0.25s ease-in-out;"
style="transform: translate3d(0px, 0px, 0px); transition: transform 0.3s ease;"
/>
<div
class="bui-tab bui-tab-center"
Expand Down
31 changes: 0 additions & 31 deletions packages/bui-core/src/Tabs/__tests__/bound.test.ts

This file was deleted.

53 changes: 53 additions & 0 deletions packages/bui-core/src/Tabs/__tests__/scroll.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import scrollLeftTo from '../utils/scroll';

// 创建一个自定义的 HTML 元素用于测试
const createScroller = (scrollLeft = 0) => {
const scroller = document.createElement('div');
scroller.style.overflowX = 'scroll';
scroller.style.width = '100px';
scroller.style.height = '100px';

// 创建一个内嵌的宽度大于 scroller 的子元素
const content = document.createElement('div');
content.style.width = '200px';
content.style.height = '100%';

scroller.appendChild(content);
scroller.scrollLeft = scrollLeft;

return scroller;
};

describe('scrollLeftTo', () => {
let scroller: HTMLElement;

beforeEach(() => {
scroller = createScroller();
document.body.appendChild(scroller);
});

afterEach(() => {
document.body.innerHTML = '';
jest.clearAllTimers(); // 清除所有定时器
});

test('should scroll left to the correct position over the specified duration', () => {
scroller.scrollLeft = 0;
scrollLeftTo(scroller, 160, 160);

expect(scroller.scrollLeft).toBe(16);
});

test('should handle zero duration', () => {
scrollLeftTo(scroller, 100, 0);
expect(scroller.scrollLeft).toBe(100);
});

test('should work currrently when requestAnimationFrame is not available', () => {
window.requestAnimationFrame = undefined;
scroller.scrollLeft = 0;
scrollLeftTo(scroller, 160, 160);

expect(scroller.scrollLeft).toBe(16);
});
});
14 changes: 0 additions & 14 deletions packages/bui-core/src/Tabs/utils/bound.ts

This file was deleted.

42 changes: 42 additions & 0 deletions packages/bui-core/src/Tabs/utils/scroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const root = window || global;
let rafId: number;
let prev = Date.now();

const rafPolyfill = (fn: FrameRequestCallback) => {
const curr = Date.now();
const ms = Math.max(0, 16 - (curr - prev));
const id = setTimeout(fn, ms);
prev = curr + ms;
return id;
};

const cancelRaf = (id: number) => {
const cancelAnimationFrame =
root?.cancelAnimationFrame || root?.clearTimeout || clearTimeout;
cancelAnimationFrame.call(root, id);
};

const raf = (fn: FrameRequestCallback): number => {
const requestAnimationFrame = root?.requestAnimationFrame || rafPolyfill;
return requestAnimationFrame.call(root, fn);
};

const scrollLeftTo = (scroller: HTMLElement, to: number, duration: number) => {
cancelRaf(rafId);

let count = 0;
const from = scroller.scrollLeft;
const frames = duration === 0 ? 1 : Math.round(duration / 16);

function animate() {
scroller.scrollLeft += (to - from) / frames;
count += 1;
if (count < frames) {
rafId = raf(animate);
}
}

animate();
};

export default scrollLeftTo;
Loading