Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/add-scroll-number-enhance-badge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiny-design/react": minor
---

Add ScrollNumber component with animated digit transitions and shortest-path scrolling; integrate into Badge for smooth count animations
2 changes: 2 additions & 0 deletions apps/docs/src/routers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ const c = {
popConfirm: ll(() => import('../../../packages/react/src/pop-confirm/index.md'), () => import('../../../packages/react/src/pop-confirm/index.zh_CN.md')),
result: ll(() => import('../../../packages/react/src/result/index.md'), () => import('../../../packages/react/src/result/index.zh_CN.md')),
scrollIndicator: ll(() => import('../../../packages/react/src/scroll-indicator/index.md'), () => import('../../../packages/react/src/scroll-indicator/index.zh_CN.md')),
scrollNumber: ll(() => import('../../../packages/react/src/scroll-number/index.md'), () => import('../../../packages/react/src/scroll-number/index.zh_CN.md')),
skeleton: ll(() => import('../../../packages/react/src/skeleton/index.md'), () => import('../../../packages/react/src/skeleton/index.zh_CN.md')),
strengthIndicator: ll(() => import('../../../packages/react/src/strength-indicator/index.md'), () => import('../../../packages/react/src/strength-indicator/index.zh_CN.md')),
backTop: ll(() => import('../../../packages/react/src/back-top/index.md'), () => import('../../../packages/react/src/back-top/index.zh_CN.md')),
Expand Down Expand Up @@ -214,6 +215,7 @@ export const getComponentMenu = (s: SiteLocale): RouterItem[] => {
{ title: 'Marquee', route: 'marquee', component: pick(c.marquee, z) },
{ title: 'Popover', route: 'popover', component: pick(c.popover, z) },
{ title: 'Progress', route: 'progress', component: pick(c.progress, z) },
{ title: 'ScrollNumber', route: 'scroll-number', component: pick(c.scrollNumber, z) },
{ title: 'Statistic', route: 'statistic', component: pick(c.statistic, z) },
{ title: 'Table', route: 'table', component: pick(c.table, z) },
{ title: 'Tag', route: 'tag', component: pick(c.tag, z) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,100 @@ exports[`<Badge /> should match the snapshot 1`] = `
class="ty-badge__count"
style="background-color: rgb(242, 69, 61);"
>
5
<div
class="ty-scroll-number ty-badge__scroll-number"
>
<div
class="ty-scroll-number__content"
>
<span
aria-hidden="true"
class="ty-scroll-number__value"
>
<span
class="ty-scroll-number__digit"
style="height: 0px; line-height: 0px;"
>
<span
class="ty-scroll-number__digit-column"
style="transform: translateY(0px);"
>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
0
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
1
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
2
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
3
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
4
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
5
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
6
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
7
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
8
</span>
<span
class="ty-scroll-number__digit-cell"
style="height: 0px; line-height: 0px;"
>
9
</span>
</span>
</span>
</span>
<span
aria-hidden="true"
class="ty-scroll-number__measure"
>
0
</span>
</div>
<span
class="ty-scroll-number__sr-only"
>
5
</span>
</div>
</sup>
</span>
</DocumentFragment>
Expand Down
25 changes: 18 additions & 7 deletions packages/react/src/badge/__tests__/badge.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ describe('<Badge />', () => {
expect(container.firstChild).toHaveClass('ty-badge');
});

it('should render count', () => {
const { getByText } = render(<Badge count={5}><div>content</div></Badge>);
expect(getByText('5')).toBeInTheDocument();
it('should render count with ScrollNumber', () => {
const { container } = render(<Badge count={5}><div>content</div></Badge>);
expect(container.querySelector('.ty-badge__count')).toBeTruthy();
expect(container.querySelector('.ty-badge__scroll-number')).toBeTruthy();
});

it('should render max+ when count exceeds max', () => {
const { getByText } = render(<Badge count={100} max={99}><div>content</div></Badge>);
expect(getByText('99+')).toBeInTheDocument();
const { container } = render(<Badge count={100} max={99}><div>content</div></Badge>);
const suffix = container.querySelector('.ty-scroll-number__suffix');
expect(suffix).toBeTruthy();
expect(suffix!.textContent).toBe('+');
});

it('should render as dot', () => {
Expand All @@ -34,7 +37,15 @@ describe('<Badge />', () => {
});

it('should show zero when showZero is true', () => {
const { getByText } = render(<Badge count={0} showZero><div>content</div></Badge>);
expect(getByText('0')).toBeInTheDocument();
const { container } = render(<Badge count={0} showZero><div>content</div></Badge>);
expect(container.querySelector('.ty-badge__count')).toBeTruthy();
expect(container.querySelector('.ty-badge__scroll-number')).toBeTruthy();
});

it('should render string count without ScrollNumber', () => {
const { container, getByText } = render(<Badge count="new"><div>content</div></Badge>);
expect(container.querySelector('.ty-badge__count')).toBeTruthy();
expect(container.querySelector('.ty-badge__scroll-number')).toBeFalsy();
expect(getByText('new')).toBeInTheDocument();
});
});
21 changes: 19 additions & 2 deletions packages/react/src/badge/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import classNames from 'classnames';
import warning from '../_utils/warning';
import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
import ScrollNumber from '../scroll-number';
import { BadgeProps } from './types';

const Badge = React.memo(React.forwardRef<HTMLSpanElement, BadgeProps>((props, ref) => {
Expand All @@ -28,16 +29,32 @@ const Badge = React.memo(React.forwardRef<HTMLSpanElement, BadgeProps>((props, r
warning(!dot && processing, 'only dot badge has the processing effect');

const renderCount = () => {
if (typeof count === 'number' || typeof count === 'string') {
if (typeof count === 'number') {
if (count === 0 && !showZero) {
return null;
}
const displayValue = count > max ? max : count;
const overflowSuffix = count > max ? '+' : undefined;
return (
<sup
title={title}
className={`${prefixCls}__count`}
style={{ backgroundColor: color, ...badgeStyle }}>
{typeof count === 'number' && count > max ? `${max}+` : count}
<ScrollNumber
value={displayValue}
suffix={overflowSuffix}
groupSeparator=""
className={`${prefixCls}__scroll-number`}
/>
</sup>
);
} else if (typeof count === 'string') {
return (
<sup
title={title}
className={`${prefixCls}__count`}
style={{ backgroundColor: color, ...badgeStyle }}>
{count}
</sup>
);
} else {
Expand Down
43 changes: 43 additions & 0 deletions packages/react/src/badge/demo/Dynamic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useState } from 'react';
import { Badge, Button, Switch, Flex } from '@tiny-design/react';

export default function DynamicDemo() {
const [count, setCount] = useState(5);
const [dot, setDot] = useState(true);

const spanStyle = {
width: '42px',
height: '42px',
borderRadius: '4px',
background: '#eee',
display: 'inline-block',
};

return (
<div>
<Flex gap="lg" align="center">
<Badge count={count}>
<span style={spanStyle} />
</Badge>
<Button.Group>
<Button
onClick={() => setCount((c) => Math.max(0, c - 1))}
style={{ display: 'inline-flex', alignItems: 'center' }}>
-
</Button>
<Button
onClick={() => setCount((c) => c + 1)}
style={{ display: 'inline-flex', alignItems: 'center' }}>
+
</Button>
</Button.Group>
</Flex>
<Flex gap="lg" align="center" style={{ marginTop: 16 }}>
<Badge dot={dot}>
<span style={spanStyle} />
</Badge>
<Switch checked={dot} onChange={setDot} />
</Flex>
</div>
);
}
11 changes: 11 additions & 0 deletions packages/react/src/badge/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import OverflowDemo from './demo/Overflow';
import OverflowSource from './demo/Overflow.tsx?raw';
import StandaloneDemo from './demo/Standalone';
import StandaloneSource from './demo/Standalone.tsx?raw';
import DynamicDemo from './demo/Dynamic';
import DynamicSource from './demo/Dynamic.tsx?raw';

# Badge

Expand Down Expand Up @@ -74,6 +76,15 @@ Set `color` to display the dot badge with different colors. `processing` can sho
> Only the dot badge has the `processing` effect.

<DemoBlock component={CustomDemo} source={CustomSource} />

</Demo>
<Demo>

### Dynamic

Increase or decrease the count with buttons, or toggle the dot with a switch.

<DemoBlock component={DynamicDemo} source={DynamicSource} />

</Demo>
</Column>
Expand Down
11 changes: 11 additions & 0 deletions packages/react/src/badge/index.zh_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import OverflowDemo from './demo/Overflow';
import OverflowSource from './demo/Overflow.tsx?raw';
import StandaloneDemo from './demo/Standalone';
import StandaloneSource from './demo/Standalone.tsx?raw';
import DynamicDemo from './demo/Dynamic';
import DynamicSource from './demo/Dynamic.tsx?raw';

# Badge

Expand Down Expand Up @@ -74,6 +76,15 @@ import { Badge } from 'tiny-design';
> 只有小圆点徽标才有 `processing` 效果。

<DemoBlock component={CustomDemo} source={CustomSource} />

</Demo>
<Demo>

### 动态变化

通过按钮增减数字,或通过开关切换小圆点。

<DemoBlock component={DynamicDemo} source={DynamicSource} />

</Demo>
</Column>
Expand Down
34 changes: 31 additions & 3 deletions packages/react/src/badge/style/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
line-height: 1;

&__count {
@include badge-base();
@include badge-base;

min-width: $badge-size;
min-height: $badge-size;
line-height: $badge-size;
Expand All @@ -29,13 +30,14 @@
}

&__dot {
@include badge-base();
@include badge-base;

width: $badge-dot-size;
height: $badge-dot-size;
line-height: $badge-dot-size;

&_wave {
&:after {
&::after {
content: '';
position: absolute;
top: 0;
Expand All @@ -49,6 +51,32 @@
}
}

&__scroll-number {
display: inline-flex;

.#{$prefix}-scroll-number__content {
font-size: inherit;
font-weight: inherit;
color: inherit;
font-family: inherit;
}

.#{$prefix}-scroll-number__measure {
font-size: inherit;
font-weight: inherit;
}

.#{$prefix}-scroll-number__suffix {
margin-left: 0;
font-size: inherit;
}

.#{$prefix}-scroll-number__sr-only,
.#{$prefix}-scroll-number__title {
display: none;
}
}

&_no-wrap {
.#{$prefix}-badge__count,
.#{$prefix}-badge__dot {
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export { default as Radio } from './radio';
export { default as Rate } from './rate';
export { default as Result } from './result';
export { default as ScrollIndicator } from './scroll-indicator';
export { default as ScrollNumber } from './scroll-number';
export { default as Segmented } from './segmented';
export { default as Select } from './select';
export { default as Skeleton } from './skeleton';
Expand Down
Loading
Loading