From 79e42880687d30ef4fe1e9a652704ec63eb15ada Mon Sep 17 00:00:00 2001 From: xiyehutao <1254524557@qq.com> Date: Tue, 9 Jun 2026 11:40:19 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20indicator=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../indicator/__test__/indicator.spec.tsx | 77 +++++++++++-- src/packages/indicator/demo.taro.tsx | 6 + src/packages/indicator/demo.tsx | 7 ++ src/packages/indicator/demos/h5/demo7.tsx | 31 ++++++ src/packages/indicator/demos/taro/demo7.tsx | 32 ++++++ src/packages/indicator/doc.en-US.md | 34 ++++-- src/packages/indicator/doc.md | 28 ++++- src/packages/indicator/doc.taro.md | 28 ++++- src/packages/indicator/doc.zh-TW.md | 30 ++++- src/packages/indicator/indicator.scss | 105 ++++++++++++------ src/packages/indicator/indicator.taro.tsx | 6 +- src/packages/indicator/indicator.tsx | 6 +- src/styles/theme-dark.scss | 3 + src/styles/theme-default.scss | 3 + src/styles/variables.scss | 32 +++++- src/types/spec/indicator/base.ts | 6 + 16 files changed, 360 insertions(+), 74 deletions(-) create mode 100644 src/packages/indicator/demos/h5/demo7.tsx create mode 100644 src/packages/indicator/demos/taro/demo7.tsx diff --git a/src/packages/indicator/__test__/indicator.spec.tsx b/src/packages/indicator/__test__/indicator.spec.tsx index b0d76e67e6..638c7e5dd1 100644 --- a/src/packages/indicator/__test__/indicator.spec.tsx +++ b/src/packages/indicator/__test__/indicator.spec.tsx @@ -33,17 +33,6 @@ test('should be shown when custom node', () => { expect(container.querySelectorAll('.nut-indicator-dot')).toHaveLength(5) }) -test('should be shown when custom node', () => { - const { container } = render( - - -
{5}
-
-
- ) - expect(container.querySelectorAll('.nut-indicator-dot')).toHaveLength(5) -}) - test('should be shown when slide', () => { const { container } = render( @@ -54,3 +43,69 @@ test('should be shown when slide', () => { 1 ) }) + +test('should render dualScreen type with fixed-width', () => { + const { container } = render( + + ) + expect( + container.querySelector('.nut-indicator-fixed-width') + ).toBeInTheDocument() + expect(container.querySelectorAll('.nut-indicator-dot')).toHaveLength(2) +}) + +test('should render white color variant', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-indicator-white')).toBeInTheDocument() +}) + +test('should render vertical direction', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-indicator-vertical')).toBeInTheDocument() +}) + +test('should render placement outside', () => { + const { container } = render( + + ) + expect(container.querySelector('.nut-indicator-outside')).toBeInTheDocument() +}) + +test('should render placement inside-top-right', () => { + const { container } = render( + + ) + expect( + container.querySelector('.nut-indicator-inside-top-right') + ).toBeInTheDocument() +}) + +test('should render placement inside-bottom-center', () => { + const { container } = render( + + ) + expect( + container.querySelector('.nut-indicator-inside-bottom-center') + ).toBeInTheDocument() +}) + +test('should render placement inside-bottom-left', () => { + const { container } = render( + + ) + expect( + container.querySelector('.nut-indicator-inside-bottom-left') + ).toBeInTheDocument() +}) + +test('slide type should calculate correct transform', () => { + const { container } = render() + const line = container.querySelector('.nut-indicator-line-active') + expect(line).toBeInTheDocument() + // stride = (24 - 8) / (3 - 1) = 8, current=1 → translateX(8px) + expect(line).toHaveStyle({ transform: 'translateX(8px)' }) +}) diff --git a/src/packages/indicator/demo.taro.tsx b/src/packages/indicator/demo.taro.tsx index 3fa9e1d208..638db49b2a 100644 --- a/src/packages/indicator/demo.taro.tsx +++ b/src/packages/indicator/demo.taro.tsx @@ -9,6 +9,7 @@ import Demo3 from './demos/taro/demo3' import Demo4 from './demos/taro/demo4' import Demo5 from './demos/taro/demo5' import Demo6 from './demos/taro/demo6' +import Demo7 from './demos/taro/demo7' const IndicatorDemo = () => { const [translated] = useTranslate({ @@ -19,6 +20,7 @@ const IndicatorDemo = () => { customNode: '自定义节点', custom: '自定义', vertical: '竖向展示', + placement: '定位布局', }, 'zh-TW': { basic: '基礎用法', @@ -27,6 +29,7 @@ const IndicatorDemo = () => { customNode: '自定义节点', custom: '自定义', vertical: '豎向展示', + placement: '定位佈局', }, 'en-US': { basic: 'Basic usage', @@ -35,6 +38,7 @@ const IndicatorDemo = () => { customNode: 'Custom Node', custom: 'Custom', vertical: 'Vertical display', + placement: 'Placement', }, }) @@ -54,6 +58,8 @@ const IndicatorDemo = () => { {translated.vertical} + {translated.placement} + ) diff --git a/src/packages/indicator/demo.tsx b/src/packages/indicator/demo.tsx index 1f2695c0af..253dd00e0f 100644 --- a/src/packages/indicator/demo.tsx +++ b/src/packages/indicator/demo.tsx @@ -6,6 +6,7 @@ import Demo3 from './demos/h5/demo3' import Demo4 from './demos/h5/demo4' import Demo5 from './demos/h5/demo5' import Demo6 from './demos/h5/demo6' +import Demo7 from './demos/h5/demo7' const IndicatorDemo = () => { const [translated] = useTranslate({ @@ -16,6 +17,7 @@ const IndicatorDemo = () => { customNode: '自定义节点', custom: '自定义', vertical: '竖向展示', + placement: '定位布局', }, 'zh-TW': { basic: '基礎用法', @@ -24,6 +26,7 @@ const IndicatorDemo = () => { customNode: '自定义节点', custom: '自定义', vertical: '豎向展示', + placement: '定位佈局', }, 'en-US': { basic: 'Basic usage', @@ -32,6 +35,7 @@ const IndicatorDemo = () => { customNode: 'Custom Node', custom: 'Custom', vertical: 'Vertical display', + placement: 'Placement', }, }) @@ -55,6 +59,9 @@ const IndicatorDemo = () => {

{translated.vertical}

+ +

{translated.placement}

+ ) diff --git a/src/packages/indicator/demos/h5/demo7.tsx b/src/packages/indicator/demos/h5/demo7.tsx new file mode 100644 index 0000000000..f1705b02e9 --- /dev/null +++ b/src/packages/indicator/demos/h5/demo7.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { Indicator } from '@nutui/nutui-react' + +const Demo7 = () => { + const containerStyle: React.CSSProperties = { + position: 'relative', + width: '100%', + height: '120px', + background: 'var(--nutui-color-background-overlay, #f5f5f5)', + borderRadius: '8px', + marginBottom: '12px', + } + return ( + <> +
+
+ +
+
+ +
+
+ +
+
+ +
+ + ) +} +export default Demo7 diff --git a/src/packages/indicator/demos/taro/demo7.tsx b/src/packages/indicator/demos/taro/demo7.tsx new file mode 100644 index 0000000000..a8f2565a68 --- /dev/null +++ b/src/packages/indicator/demos/taro/demo7.tsx @@ -0,0 +1,32 @@ +import React from 'react' +import { View } from '@tarojs/components' +import { Indicator } from '@nutui/nutui-react-taro' + +const Demo7 = () => { + const containerStyle: React.CSSProperties = { + position: 'relative', + width: '100%', + height: '120px', + background: 'var(--nutui-color-background-overlay, #f5f5f5)', + borderRadius: '8px', + marginBottom: '12px', + } + return ( + <> + + + + + + + + + + + + + + + ) +} +export default Demo7 diff --git a/src/packages/indicator/doc.en-US.md b/src/packages/indicator/doc.en-US.md index c31be85650..eb57e4a087 100644 --- a/src/packages/indicator/doc.en-US.md +++ b/src/packages/indicator/doc.en-US.md @@ -28,6 +28,8 @@ import { Indicator } from '@nutui/nutui-react' ### Type +Supports three types: anchor indicator (anchor, 2-6 pages), slide indicator (slide, 2-11 pages), dual-screen indicator (dualScreen, fixed 2 pages). + :::demo @@ -58,6 +60,16 @@ import { Indicator } from '@nutui/nutui-react' ::: +### Placement + +Supports placing the indicator outside or inside the content module at different positions. For inside placement (inside-\*), the parent container must have `position: relative`. + +:::demo + + + +::: + ## Indicator ### Props @@ -65,10 +77,11 @@ import { Indicator } from '@nutui/nutui-react' | Property | Description | Type | Default | | --- | --- | --- | --- | | current | current step | `number` | `0` | -| total | step total size | `number` | `3` | -| direction | display directory,default is horizontal | `horizontal` \| `vertical` | `horizontal` | +| total | step total size | `number` | `2` | +| direction | display directory, default is horizontal | `horizontal` \| `vertical` | `horizontal` | | color | color | `primary` \| `default` | `primary` | -| type | interactivity Type | `anchor` \| `slide`\| `dualScreen` | `anchor` | +| type | interactivity type | `anchor` \| `slide`\| `dualScreen` | `anchor` | +| placement | placement layout | `outside` \| `inside-top-right` \| `inside-bottom-center` \| `inside-bottom-left` | `-` | ## Theming @@ -79,10 +92,17 @@ The component provides the following CSS variables, which can be used to customi | Name | Description | Default Value | | --- | --- | --- | | \--nutui-indicator-color | indicator active color | `$color-primary` | -| \--nutui-indicator-dot-color | indicator default color | `$color-text-disabled` | -| \--nutui-indicator-dot-size | indicator dot size | `3px` | -| \--nutui-indicator-dot-active-size | indicator dot active size | `6px` | +| \--nutui-indicator-dot-color | indicator default color | `$color-border` | +| \--nutui-indicator-dot-size | indicator dot size | `4px` | +| \--nutui-indicator-dot-active-size | indicator dot active size | `8px` | | \--nutui-indicator-border-radius | indicator active border size | `$radius-xxs` | -| \--nutui-indicator-dot-margin | when horizontal, indicator margin | `$spacing-xxxs` | +| \--nutui-indicator-dot-margin | when horizontal, indicator margin | `$spacing-xxs` | +| \--nutui-indicator-dot-border | indicator inner border | `0.33px solid $color-border` | +| \--nutui-indicator-dot-inactive-color | indicator inactive fill color | `var(--nutui-color-background-component, #f0f2f7)` | +| \--nutui-indicator-track-width | slide indicator track width | `24px` | +| \--nutui-indicator-slider-width | slide indicator slider width | `8px` | +| \--nutui-indicator-dual-screen-inactive-width | dual-screen indicator inactive width | `16px` | +| \--nutui-indicator-placement-gap | placement gap to content | `8px` | +| \--nutui-indicator-placement-safe-gap | outside placement safe gap below | `12px` | diff --git a/src/packages/indicator/doc.md b/src/packages/indicator/doc.md index 295660e849..9e78954f4b 100644 --- a/src/packages/indicator/doc.md +++ b/src/packages/indicator/doc.md @@ -28,6 +28,8 @@ import { Indicator } from '@nutui/nutui-react' ### 类型 +支持三种类型:锚点指示器(anchor,2-6页)、滑动指示器(slide,2-11页)、双屏指示器(dualScreen,固定2页)。 + :::demo @@ -58,6 +60,16 @@ import { Indicator } from '@nutui/nutui-react' ::: +### 定位布局 + +支持将指示器放置在内容模块外部或内部的不同位置。内部定位(inside-\*)时需要父容器设置 `position: relative`。 + +:::demo + + + +::: + ## Indicator ### Props @@ -69,6 +81,7 @@ import { Indicator } from '@nutui/nutui-react' | direction | 方向,默认为水平方向 | `horizontal` \| `vertical` | `horizontal` | | color | 颜色 | `primary` \| `default` | `primary` | | type | 交互类型 | `anchor` \| `slide`\| `dualScreen` | `anchor` | +| placement | 定位布局 | `outside` \| `inside-top-right` \| `inside-bottom-center` \| `inside-bottom-left` | `-` | ## 主题定制 @@ -79,10 +92,17 @@ import { Indicator } from '@nutui/nutui-react' | 名称 | 说明 | 默认值 | | --- | --- | --- | | \--nutui-indicator-color | 指示器焦点时色值 | `$color-primary` | -| \--nutui-indicator-dot-color | 指示器默认色值 | `$color-text-disabled` | -| \--nutui-indicator-dot-size | 指示器尺寸 | `3px` | -| \--nutui-indicator-dot-active-size | 指示器焦点时尺寸 | `6px` | +| \--nutui-indicator-dot-color | 指示器默认色值 | `$color-border` | +| \--nutui-indicator-dot-size | 指示器尺寸 | `4px` | +| \--nutui-indicator-dot-active-size | 指示器焦点时尺寸 | `8px` | | \--nutui-indicator-border-radius | 指示器焦点时的border值 | `$radius-xxs` | -| \--nutui-indicator-dot-margin | 指示器横向时的margin值 | `$spacing-xxxs` | +| \--nutui-indicator-dot-margin | 指示器横向时的margin值 | `$spacing-xxs` | +| \--nutui-indicator-dot-border | 指示器内描边 | `0.33px solid $color-border` | +| \--nutui-indicator-dot-inactive-color | 指示器非焦点时的填充色 | `var(--nutui-color-background-component, #f0f2f7)` | +| \--nutui-indicator-track-width | 滑动指示器轨道宽度 | `24px` | +| \--nutui-indicator-slider-width | 滑动指示器滑块宽度 | `8px` | +| \--nutui-indicator-dual-screen-inactive-width | 双屏指示器非焦点时宽度 | `16px` | +| \--nutui-indicator-placement-gap | 定位布局与内容的间距 | `8px` | +| \--nutui-indicator-placement-safe-gap | 外部定位时与下方模块的安全间距 | `12px` | diff --git a/src/packages/indicator/doc.taro.md b/src/packages/indicator/doc.taro.md index 6de815d619..bceb739833 100644 --- a/src/packages/indicator/doc.taro.md +++ b/src/packages/indicator/doc.taro.md @@ -28,6 +28,8 @@ import { Indicator } from '@nutui/nutui-react-taro' ### 类型 +支持三种类型:锚点指示器(anchor,2-6页)、滑动指示器(slide,2-11页)、双屏指示器(dualScreen,固定2页)。 + :::demo @@ -58,6 +60,16 @@ import { Indicator } from '@nutui/nutui-react-taro' ::: +### 定位布局 + +支持将指示器放置在内容模块外部或内部的不同位置。内部定位(inside-\*)时需要父容器设置 `position: relative`。 + +:::demo + + + +::: + ## Indicator ### Props @@ -69,6 +81,7 @@ import { Indicator } from '@nutui/nutui-react-taro' | direction | 方向,默认为水平方向 | `horizontal` \| `vertical` | `horizontal` | | color | 颜色 | `primary` \| `default` | `primary` | | type | 交互类型 | `anchor` \| `slide`\| `dualScreen` | `anchor` | +| placement | 定位布局 | `outside` \| `inside-top-right` \| `inside-bottom-center` \| `inside-bottom-left` | `-` | ## 主题定制 @@ -79,10 +92,17 @@ import { Indicator } from '@nutui/nutui-react-taro' | 名称 | 说明 | 默认值 | | --- | --- | --- | | \--nutui-indicator-color | 指示器焦点时色值 | `$color-primary` | -| \--nutui-indicator-dot-color | 指示器默认色值 | `$color-text-disabled` | -| \--nutui-indicator-dot-size | 指示器尺寸 | `3px` | -| \--nutui-indicator-dot-active-size | 指示器焦点时尺寸 | `6px` | +| \--nutui-indicator-dot-color | 指示器默认色值 | `$color-border` | +| \--nutui-indicator-dot-size | 指示器尺寸 | `4px` | +| \--nutui-indicator-dot-active-size | 指示器焦点时尺寸 | `8px` | | \--nutui-indicator-border-radius | 指示器焦点时的border值 | `$radius-xxs` | -| \--nutui-indicator-dot-margin | 指示器横向时的margin值 | `$spacing-xxxs` | +| \--nutui-indicator-dot-margin | 指示器横向时的margin值 | `$spacing-xxs` | +| \--nutui-indicator-dot-border | 指示器内描边 | `0.33px solid $color-border` | +| \--nutui-indicator-dot-inactive-color | 指示器非焦点时的填充色 | `var(--nutui-color-background-component, #f0f2f7)` | +| \--nutui-indicator-track-width | 滑动指示器轨道宽度 | `24px` | +| \--nutui-indicator-slider-width | 滑动指示器滑块宽度 | `8px` | +| \--nutui-indicator-dual-screen-inactive-width | 双屏指示器非焦点时宽度 | `16px` | +| \--nutui-indicator-placement-gap | 定位布局与内容的间距 | `8px` | +| \--nutui-indicator-placement-safe-gap | 外部定位时与下方模块的安全间距 | `12px` | diff --git a/src/packages/indicator/doc.zh-TW.md b/src/packages/indicator/doc.zh-TW.md index 5647afb61c..852b62ea88 100644 --- a/src/packages/indicator/doc.zh-TW.md +++ b/src/packages/indicator/doc.zh-TW.md @@ -26,7 +26,9 @@ import { Indicator } from '@nutui/nutui-react' ::: -### 类型 +### 類型 + +支持三種類型:錨點指示器(anchor,2-6頁)、滑動指示器(slide,2-11頁)、雙屏指示器(dualScreen,固定2頁)。 :::demo @@ -58,6 +60,16 @@ import { Indicator } from '@nutui/nutui-react' ::: +### 定位佈局 + +支持將指示器放置在內容模塊外部或內部的不同位置。內部定位(inside-\*)時需要父容器設置 `position: relative`。 + +:::demo + + + +::: + ## Indicator ### Props @@ -69,6 +81,7 @@ import { Indicator } from '@nutui/nutui-react' | direction | 方向,預設為水平方向 | `horizontal` \| `vertical` | `horizontal` | | color | 顏色 | `primary` \| `default` | `primary` | | type | 互動類型 | `anchor` \| `slide`\| `dualScreen` | `anchor` | +| placement | 定位佈局 | `outside` \| `inside-top-right` \| `inside-bottom-center` \| `inside-bottom-left` | `-` | ## 主題定製 @@ -79,10 +92,17 @@ import { Indicator } from '@nutui/nutui-react' | 名稱 | 說明 | 默認值 | | --- | --- | --- | | \--nutui-indicator-color | 指示器焦點時色值 | `$color-primary` | -| \--nutui-indicator-dot-color | 指示器默認色值 | `$color-text-disabled` | -| \--nutui-indicator-dot-size | 指示器尺寸 | `3px` | -| \--nutui-indicator-dot-active-size | 指示器焦點時尺寸 | `6px` | +| \--nutui-indicator-dot-color | 指示器默認色值 | `$color-border` | +| \--nutui-indicator-dot-size | 指示器尺寸 | `4px` | +| \--nutui-indicator-dot-active-size | 指示器焦點時尺寸 | `8px` | | \--nutui-indicator-border-radius | 指示器焦點時的border值 | `$radius-xxs` | -| \--nutui-indicator-dot-margin | 指示器橫向時的margin值 | `$spacing-xxxs` | +| \--nutui-indicator-dot-margin | 指示器橫向時的margin值 | `$spacing-xxs` | +| \--nutui-indicator-dot-border | 指示器內描邊 | `0.33px solid $color-border` | +| \--nutui-indicator-dot-inactive-color | 指示器非焦點時的填充色 | `var(--nutui-color-background-component, #f0f2f7)` | +| \--nutui-indicator-track-width | 滑動指示器軌道寬度 | `24px` | +| \--nutui-indicator-slider-width | 滑動指示器滑塊寬度 | `8px` | +| \--nutui-indicator-dual-screen-inactive-width | 雙屏指示器非焦點時寬度 | `16px` | +| \--nutui-indicator-placement-gap | 定位佈局與內容的間距 | `8px` | +| \--nutui-indicator-placement-safe-gap | 外部定位時與下方模塊的安全間距 | `12px` | diff --git a/src/packages/indicator/indicator.scss b/src/packages/indicator/indicator.scss index 6c442de3f5..70fad738a8 100644 --- a/src/packages/indicator/indicator.scss +++ b/src/packages/indicator/indicator.scss @@ -6,7 +6,7 @@ align-items: center; &-fixed-width { - width: 21px; + width: $indicator-track-width; } &-dot, @@ -16,9 +16,10 @@ width: $indicator-dot-size; height: $indicator-dot-size; border-radius: 50%; - background-color: $color-border-disabled; + background-color: $indicator-dot-inactive-color; + border: $indicator-dot-border; + box-sizing: border-box; margin-left: $indicator-dot-margin; - opacity: 0.4; &-0 { margin-left: 0px; @@ -26,21 +27,23 @@ &-active { width: $indicator-dot-active-size; + height: $indicator-dot-size; border-radius: $indicator-border-radius; background: $indicator-color; - opacity: 1; + border: $indicator-dot-border; } } - // 固定宽度 + // 双屏模式 &-fixed-width { - // 两个页码的时候 .nut-indicator-dot { - width: 12px; + width: $indicator-dual-screen-inactive-width; + height: $indicator-dot-size; border-radius: $indicator-border-radius; &-active { - width: 6px; + width: $indicator-dot-active-size; + height: $indicator-dot-size; } } } @@ -49,44 +52,64 @@ // 竖向固定高度 &.nut-indicator-fixed-width { justify-content: flex-start; - height: 21px; + height: $indicator-track-width; width: auto; - // 竖向两个页码 + // 竖向双屏 .nut-indicator-dot { - width: 3px; - height: 12px; + width: $indicator-dot-size; + height: $indicator-dual-screen-inactive-width; border-radius: $indicator-border-radius; &-active { - height: 6px; - } - } - - &.nut-indicator-fixed-width { - &.nut-indicator-white { - .nut-indicator-dot { - &-active { - height: 6px; - } - } + width: $indicator-dot-size; + height: $indicator-dot-active-size; } } } } &-line { - width: $indicator-dot-active-size; + width: $indicator-slider-width; margin: 0; - border-radius: $indicator-border-radius; background-color: transparent; + border: none; &-active { + position: relative; + z-index: 1; transition: transform 0.3s ease-in-out; background: $indicator-color; + border: $indicator-dot-border; } } + + // 定位布局 + &-outside { + margin-top: $indicator-placement-gap; + margin-bottom: $indicator-placement-safe-gap; + justify-content: center; + } + + &-inside-top-right { + position: absolute; + top: $indicator-placement-gap; + right: $indicator-placement-gap; + } + + &-inside-bottom-center { + position: absolute; + bottom: $indicator-placement-gap; + left: 50%; + transform: translateX(-50%); + } + + &-inside-bottom-left { + position: absolute; + bottom: $indicator-placement-gap; + left: $indicator-placement-gap; + } } .nut-indicator-track { @@ -100,8 +123,8 @@ height: 100%; box-sizing: border-box; border-radius: $indicator-border-radius; - background-color: $color-border-disabled; - opacity: 0.4; + background-color: $indicator-dot-inactive-color; + border: $indicator-dot-border; } } @@ -110,11 +133,8 @@ &-dot, &-line { position: relative; - /* #ifndef dynamic*/ - box-sizing: content-box; - /* #endif */ - background: rgba(255, 255, 255, 0.4); - opacity: 1; + background: $color-primary-text; + opacity: 0.4; } &-line { @@ -122,13 +142,14 @@ &-active { opacity: 1; - background: rgba(255, 255, 255, 1); + background: $color-primary-text; } } &-dot { &-active { - background: rgba(255, 255, 255, 1); + background: $color-primary-text; + opacity: 1; } } } @@ -136,8 +157,8 @@ .nut-indicator-track.nut-indicator-white { &:after { - border: 1px solid rgba(0, 0, 0, 0.06); - background: rgba(255, 255, 255, 0.4); + background: $color-primary-text; + opacity: 0.4; } } @@ -167,7 +188,7 @@ .nut-indicator { &-line { width: $indicator-dot-size; - height: $indicator-dot-active-size; + height: $indicator-slider-width; } } } @@ -181,4 +202,14 @@ margin-left: 0px; } } + + &-inside-top-right { + right: auto; + left: $indicator-placement-gap; + } + + &-inside-bottom-left { + left: auto; + right: $indicator-placement-gap; + } } diff --git a/src/packages/indicator/indicator.taro.tsx b/src/packages/indicator/indicator.taro.tsx index e32d72161d..b0e4e4c282 100644 --- a/src/packages/indicator/indicator.taro.tsx +++ b/src/packages/indicator/indicator.taro.tsx @@ -25,6 +25,7 @@ export const Indicator: FunctionComponent< children, className, direction, + placement, ...rest } = { ...defaultProps, @@ -34,6 +35,7 @@ export const Indicator: FunctionComponent< [`${classPrefix}-vertical`]: direction === 'vertical', [`${classPrefix}-white`]: color === 'default', [`${classPrefix}-track`]: type === 'slide', + [`${classPrefix}-${placement}`]: !!placement, }) const renderDotElement = () => { @@ -58,8 +60,8 @@ export const Indicator: FunctionComponent< return childs } const renderLineElement = () => { - const trackWidth: number = 21 - const sliderWidth: number = 6 + const trackWidth: number = 24 + const sliderWidth: number = 8 const stride = (trackWidth - sliderWidth) / (total - 1) return ( > = ( children, className, direction, + placement, ...rest } = { ...defaultProps, @@ -32,6 +33,7 @@ export const Indicator: FunctionComponent> = ( [`${classPrefix}-vertical`]: direction === 'vertical', [`${classPrefix}-white`]: color === 'default', [`${classPrefix}-track`]: type === 'slide', + [`${classPrefix}-${placement}`]: !!placement, }) const renderDotElement = () => { @@ -53,8 +55,8 @@ export const Indicator: FunctionComponent> = ( return childs } const renderLineElement = () => { - const trackWidth: number = 21 - const sliderWidth: number = 6 + const trackWidth: number = 24 + const sliderWidth: number = 8 const stride = (trackWidth - sliderWidth) / (total - 1) return (
+export type IndicatorPlacement = + | 'outside' + | 'inside-top-right' + | 'inside-bottom-center' + | 'inside-bottom-left' export interface BaseIndicator extends BaseProps { total: number @@ -10,4 +15,5 @@ export interface BaseIndicator extends BaseProps { direction: string color: IndicatorColor type: IndicatorType + placement: IndicatorPlacement } From ede7ddd421d8d8dacf4abb3d0db9db91d436ced2 Mon Sep 17 00:00:00 2001 From: xiyehutao <1254524557@qq.com> Date: Tue, 9 Jun 2026 14:13:35 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=A1=AC=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../indicator/__test__/indicator.spec.tsx | 10 ++++++---- src/packages/indicator/indicator.scss | 17 +++++++++++++++++ src/packages/indicator/indicator.taro.tsx | 7 ++----- src/packages/indicator/indicator.tsx | 12 ++++++------ 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/packages/indicator/__test__/indicator.spec.tsx b/src/packages/indicator/__test__/indicator.spec.tsx index 638c7e5dd1..12f422e297 100644 --- a/src/packages/indicator/__test__/indicator.spec.tsx +++ b/src/packages/indicator/__test__/indicator.spec.tsx @@ -102,10 +102,12 @@ test('should render placement inside-bottom-left', () => { ).toBeInTheDocument() }) -test('slide type should calculate correct transform', () => { +test('slide type should set CSS custom properties for transform', () => { const { container } = render() - const line = container.querySelector('.nut-indicator-line-active') + const line = container.querySelector( + '.nut-indicator-line-active' + ) as HTMLElement expect(line).toBeInTheDocument() - // stride = (24 - 8) / (3 - 1) = 8, current=1 → translateX(8px) - expect(line).toHaveStyle({ transform: 'translateX(8px)' }) + expect(line.style.getPropertyValue('--nutui-indicator-current')).toBe('1') + expect(line.style.getPropertyValue('--nutui-indicator-total')).toBe('3') }) diff --git a/src/packages/indicator/indicator.scss b/src/packages/indicator/indicator.scss index 70fad738a8..93a153abc3 100644 --- a/src/packages/indicator/indicator.scss +++ b/src/packages/indicator/indicator.scss @@ -82,6 +82,13 @@ transition: transform 0.3s ease-in-out; background: $indicator-color; border: $indicator-dot-border; + transform: translateX( + calc( + var(--nutui-indicator-current, 0) / + (var(--nutui-indicator-total, 2) - 1) * + (#{$indicator-track-width} - #{$indicator-slider-width}) + ) + ); } } @@ -189,6 +196,16 @@ &-line { width: $indicator-dot-size; height: $indicator-slider-width; + + &-active { + transform: translateY( + calc( + var(--nutui-indicator-current, 0) / + (var(--nutui-indicator-total, 2) - 1) * + (#{$indicator-track-width} - #{$indicator-slider-width}) + ) + ); + } } } } diff --git a/src/packages/indicator/indicator.taro.tsx b/src/packages/indicator/indicator.taro.tsx index b0e4e4c282..962adbd2b8 100644 --- a/src/packages/indicator/indicator.taro.tsx +++ b/src/packages/indicator/indicator.taro.tsx @@ -1,7 +1,6 @@ import React, { FunctionComponent, ReactNode } from 'react' import classNames from 'classnames' import { View, ViewProps } from '@tarojs/components' -import { pxTransform } from '@/utils/taro/px-transform' import { TaroIndicatorProps, IndicatorType } from '@/types' const defaultProps = { @@ -60,13 +59,11 @@ export const Indicator: FunctionComponent< return childs } const renderLineElement = () => { - const trackWidth: number = 24 - const sliderWidth: number = 8 - const stride = (trackWidth - sliderWidth) / (total - 1) return ( diff --git a/src/packages/indicator/indicator.tsx b/src/packages/indicator/indicator.tsx index 2e8fd39a35..45556b50d1 100644 --- a/src/packages/indicator/indicator.tsx +++ b/src/packages/indicator/indicator.tsx @@ -55,14 +55,14 @@ export const Indicator: FunctionComponent> = ( return childs } const renderLineElement = () => { - const trackWidth: number = 24 - const sliderWidth: number = 8 - const stride = (trackWidth - sliderWidth) / (total - 1) return (
)