diff --git a/src/packages/configprovider/types.ts b/src/packages/configprovider/types.ts index 73bfd07a94..2a48c1a81d 100644 --- a/src/packages/configprovider/types.ts +++ b/src/packages/configprovider/types.ts @@ -441,6 +441,7 @@ export type NutCSSVariables = | 'nutuiRateFontSize' | 'nutuiRateFontSizeLarge' | 'nutuiRateFontSizeSmall' + | 'nutuiRateLabelColor' | 'nutuiTabbarHeight' | 'nutuiTabbarActiveColor' | 'nutuiTabbarInactiveColor' diff --git a/src/packages/rate/__test__/__snapshots__/rate.spec.tsx.snap b/src/packages/rate/__test__/__snapshots__/rate.spec.tsx.snap index ff28a24779..2c1762cc6b 100644 --- a/src/packages/rate/__test__/__snapshots__/rate.spec.tsx.snap +++ b/src/packages/rate/__test__/__snapshots__/rate.spec.tsx.snap @@ -3,24 +3,28 @@ exports[`size test 1`] = `
- - - + + + +
diff --git a/src/packages/rate/__test__/rate.spec.tsx b/src/packages/rate/__test__/rate.spec.tsx index 7fecf8af7c..fa0167f7ea 100644 --- a/src/packages/rate/__test__/rate.spec.tsx +++ b/src/packages/rate/__test__/rate.spec.tsx @@ -33,17 +33,17 @@ test('props test', () => { ) } - const { getByTestId, container } = render() + const { container } = render() const valueEl = container.querySelector('.score') const el = container.querySelectorAll('.nut-rate-item') if (el && valueEl) { - expect(el.length).toBe(state.count) // count - expect(el[2].childNodes[0]).toHaveClass('nut-rate-item-icon') // active + expect(el.length).toBe(state.count) + expect(el[2].childNodes[0]).toHaveClass('nut-rate-item-icon') expect(el[2].childNodes[0].childNodes[0]).toHaveClass('nut-icon-HeartFill') - expect(el[3].childNodes[0]).toHaveClass('nut-rate-item-icon-disabled') // disabled + expect(el[3].childNodes[0]).toHaveClass('nut-rate-item-icon-disabled') expect(el[3].childNodes[0].childNodes[0]).toHaveClass('nut-icon-Heart') fireEvent.click(el[1]) - expect(valueEl.innerHTML).toBe('2') // onChange + expect(valueEl.innerHTML).toBe('2') } }) @@ -74,6 +74,7 @@ test('allowHalf test', () => { ) } }) + test('size test', () => { const { container } = render() const el = container.querySelector('.nut-rate-item') @@ -91,6 +92,62 @@ test('showScore test', () => { } }) +test('direction horizontal test', () => { + const { container } = render() + const root = container.querySelector('.nut-rate') + expect(root).toHaveClass('nut-rate-horizontal') + expect(container.querySelector('.nut-rate-list')).toBeTruthy() +}) + +test('direction vertical test', () => { + const { container } = render() + const root = container.querySelector('.nut-rate') + expect(root).toHaveClass('nut-rate-vertical') +}) + +test('label test horizontal', () => { + const { container } = render( + + ) + const label = container.querySelector('.nut-rate-label') + expect(label).toBeTruthy() + expect(label?.innerHTML).toBe('评分') + const root = container.querySelector('.nut-rate') + const list = container.querySelector('.nut-rate-list') + if (root && label && list) { + const children = Array.from(root.children) + expect(children.indexOf(label)).toBeLessThan(children.indexOf(list)) + } +}) + +test('label test vertical', () => { + const { container } = render( + + ) + const label = container.querySelector('.nut-rate-label') + expect(label).toBeTruthy() + expect(label?.innerHTML).toBe('非常满意') + const root = container.querySelector('.nut-rate') + const list = container.querySelector('.nut-rate-list') + if (root && label && list) { + const children = Array.from(root.children) + expect(children.indexOf(label)).toBeGreaterThan(children.indexOf(list)) + } +}) + +test('array label test vertical', () => { + const labels = ['不满意', '', '', '', '非常满意'] + const { container } = render( + + ) + const labelsRow = container.querySelector('.nut-rate-labels') + expect(labelsRow).toBeTruthy() + const items = container.querySelectorAll('.nut-rate-labels-item') + expect(items.length).toBe(5) + expect(items[0].innerHTML).toBe('不满意') + expect(items[4].innerHTML).toBe('非常满意') +}) + test('touchable', () => { const state = { count: 5, diff --git a/src/packages/rate/demo.taro.tsx b/src/packages/rate/demo.taro.tsx index 0c73c8d9a9..2babef653a 100644 --- a/src/packages/rate/demo.taro.tsx +++ b/src/packages/rate/demo.taro.tsx @@ -16,6 +16,9 @@ import Demo9 from './demos/taro/demo9' import Demo10 from './demos/taro/demo10' import Demo11 from './demos/taro/demo11' import Demo12 from './demos/taro/demo12' +import Demo13 from './demos/taro/demo13' +import Demo14 from './demos/taro/demo14' +import Demo15 from './demos/taro/demo15' const RateDemo = () => { const [translated] = useTranslate({ @@ -32,6 +35,9 @@ const RateDemo = () => { event: '绑定事件', touchable: '滑动选择', touchend: '滑动事件', + verticalLayout: '上下布局', + withLabel: '带标签', + animation: '过渡动画', }, 'zh-TW': { basic: '基礎用法', @@ -46,6 +52,9 @@ const RateDemo = () => { event: '綁定事件', touchable: '滑動選擇', touchend: '滑動事件', + verticalLayout: '上下佈局', + withLabel: '帶標籤', + animation: '過渡動畫', }, 'en-US': { basic: 'Basic Usage', @@ -60,6 +69,9 @@ const RateDemo = () => { event: 'Event', touchable: 'Touch to Select', touchend: 'Touch Event', + verticalLayout: 'Vertical Layout', + withLabel: 'With Label', + animation: 'Transition Animation', }, }) const cellStyle: CSSProperties = { @@ -133,6 +145,21 @@ const RateDemo = () => { )} + + {translated.verticalLayout} + + + + + {translated.withLabel} + + + + + {translated.animation} + + + ) diff --git a/src/packages/rate/demo.tsx b/src/packages/rate/demo.tsx index 359adb703a..621dc1baa3 100644 --- a/src/packages/rate/demo.tsx +++ b/src/packages/rate/demo.tsx @@ -13,6 +13,9 @@ import Demo9 from './demos/h5/demo9' import Demo10 from './demos/h5/demo10' import Demo11 from './demos/h5/demo11' import Demo12 from './demos/h5/demo12' +import Demo13 from './demos/h5/demo13' +import Demo14 from './demos/h5/demo14' +import Demo15 from './demos/h5/demo15' const RateDemo = () => { const [translated] = useTranslate({ @@ -29,6 +32,9 @@ const RateDemo = () => { event: '绑定事件', touchable: '滑动选择', touchevent: '滑动事件', + verticalLayout: '上下布局', + withLabel: '带标签', + animation: '过渡动画', }, 'zh-TW': { basic: '基礎用法', @@ -43,6 +49,9 @@ const RateDemo = () => { event: '綁定事件', touchable: '滑動選擇', touchevent: '滑動事件', + verticalLayout: '上下佈局', + withLabel: '帶標籤', + animation: '過渡動畫', }, 'en-US': { basic: 'Basic Usage', @@ -57,6 +66,9 @@ const RateDemo = () => { event: 'Event', touchable: 'Touch to Select', touchevent: 'Touch Event', + verticalLayout: 'Vertical Layout', + withLabel: 'With Label', + animation: 'Transition Animation', }, }) const cellStyle: CSSProperties = { @@ -126,6 +138,21 @@ const RateDemo = () => { + +

{translated.verticalLayout}

+ + + + +

{translated.withLabel}

+ + + + +

{translated.animation}

+ + + ) diff --git a/src/packages/rate/demos/h5/demo1.tsx b/src/packages/rate/demos/h5/demo1.tsx index cdb18fbbbf..a297cab8f9 100644 --- a/src/packages/rate/demos/h5/demo1.tsx +++ b/src/packages/rate/demos/h5/demo1.tsx @@ -4,9 +4,9 @@ import { Rate, Space } from '@nutui/nutui-react' const Demo1 = () => { return ( - - + + ) } diff --git a/src/packages/rate/demos/h5/demo13.tsx b/src/packages/rate/demos/h5/demo13.tsx new file mode 100644 index 0000000000..ea0bbb1de2 --- /dev/null +++ b/src/packages/rate/demos/h5/demo13.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { Rate, Space } from '@nutui/nutui-react' + +const Demo13 = () => { + return ( + + + + + ) +} +export default Demo13 diff --git a/src/packages/rate/demos/h5/demo14.tsx b/src/packages/rate/demos/h5/demo14.tsx new file mode 100644 index 0000000000..3d60549184 --- /dev/null +++ b/src/packages/rate/demos/h5/demo14.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { Rate, Space } from '@nutui/nutui-react' + +const Demo14 = () => { + return ( + + + + + + + ) +} +export default Demo14 diff --git a/src/packages/rate/demos/h5/demo15.tsx b/src/packages/rate/demos/h5/demo15.tsx new file mode 100644 index 0000000000..1b14584c47 --- /dev/null +++ b/src/packages/rate/demos/h5/demo15.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { Rate, Space } from '@nutui/nutui-react' + +const Demo15 = () => { + return ( + + + + + ) +} +export default Demo15 diff --git a/src/packages/rate/demos/h5/demo9.tsx b/src/packages/rate/demos/h5/demo9.tsx index 3ba626cf83..439cfb0da1 100644 --- a/src/packages/rate/demos/h5/demo9.tsx +++ b/src/packages/rate/demos/h5/demo9.tsx @@ -1,7 +1,12 @@ import React from 'react' -import { Rate } from '@nutui/nutui-react' +import { Rate, Space } from '@nutui/nutui-react' const Demo9 = () => { - return + return ( + + + + + ) } export default Demo9 diff --git a/src/packages/rate/demos/taro/demo1.tsx b/src/packages/rate/demos/taro/demo1.tsx index 20f6942736..509f05488b 100644 --- a/src/packages/rate/demos/taro/demo1.tsx +++ b/src/packages/rate/demos/taro/demo1.tsx @@ -4,9 +4,9 @@ import { Rate, Space } from '@nutui/nutui-react-taro' const Demo1 = () => { return ( - - + + ) } diff --git a/src/packages/rate/demos/taro/demo13.tsx b/src/packages/rate/demos/taro/demo13.tsx new file mode 100644 index 0000000000..6c7c1323fe --- /dev/null +++ b/src/packages/rate/demos/taro/demo13.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { Rate, Space } from '@nutui/nutui-react-taro' + +const Demo13 = () => { + return ( + + + + + ) +} +export default Demo13 diff --git a/src/packages/rate/demos/taro/demo14.tsx b/src/packages/rate/demos/taro/demo14.tsx new file mode 100644 index 0000000000..4528fbf160 --- /dev/null +++ b/src/packages/rate/demos/taro/demo14.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { Rate, Space } from '@nutui/nutui-react-taro' + +const Demo14 = () => { + return ( + + + + + + + ) +} +export default Demo14 diff --git a/src/packages/rate/demos/taro/demo15.tsx b/src/packages/rate/demos/taro/demo15.tsx new file mode 100644 index 0000000000..a0b540db3d --- /dev/null +++ b/src/packages/rate/demos/taro/demo15.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { Rate, Space } from '@nutui/nutui-react-taro' + +const Demo15 = () => { + return ( + + + + + ) +} +export default Demo15 diff --git a/src/packages/rate/demos/taro/demo9.tsx b/src/packages/rate/demos/taro/demo9.tsx index a3ecdd1088..e037f0842d 100644 --- a/src/packages/rate/demos/taro/demo9.tsx +++ b/src/packages/rate/demos/taro/demo9.tsx @@ -1,7 +1,12 @@ import React from 'react' -import { Rate } from '@nutui/nutui-react-taro' +import { Rate, Space } from '@nutui/nutui-react-taro' const Demo9 = () => { - return + return ( + + + + + ) } export default Demo9 diff --git a/src/packages/rate/doc.en-US.md b/src/packages/rate/doc.en-US.md index 029e68a4fa..e2add16d5d 100644 --- a/src/packages/rate/doc.en-US.md +++ b/src/packages/rate/doc.en-US.md @@ -106,6 +106,30 @@ import { Rate } from '@nutui/nutui-react' ::: +### Vertical Layout + +:::demo + + + +::: + +### With Label + +:::demo + + + +::: + +### Transition Animation + +:::demo + + + +::: + ## Rate ### Props @@ -113,6 +137,8 @@ import { Rate } from '@nutui/nutui-react' | Property | Description | Type | Default | | --- | --- | --- | --- | | size | Score size | `large` \| `normal` \| `small` | `normal` | +| direction | Direction | `horizontal` \| `vertical` | `horizontal` | +| label | Label text | `ReactNode` | `-` | | showScore | Show Score | `boolean` | `false` | | defaultValue | Uncontrolled star value | `number` | `0` | | value | Controlled star value | `number` | `0` | @@ -123,7 +149,7 @@ import { Rate } from '@nutui/nutui-react' | allowHalf | Half star or not | `boolean` | `false` | | readOnly | Read only | `boolean` | `false` | | disabled | Disable or not | `boolean` | `false` | -| touchable | Enable touch to select | `boolean` | `false` | +| touchable | Enable touch to select | `boolean` | `false` | | onChange | Event triggered when the current score is modified | `(value: number) => void` | `-` | | onTouchEnd | Event triggered when touch end | `(event: TouchEvent, value: number) => void` | `-` | @@ -138,8 +164,9 @@ The component provides the following CSS variables, which can be used to customi | \--nutui-rate-item-margin | spacing | `4px` | | \--nutui-rate-icon-color | icon activation color | `$color-primary-icon` | | \--nutui-rate-icon-inactive-color | icon inactive color | `$color-primary-icon-disabled` | -| \--nutui-rate-icon-size | icon size | `12px` | +| \--nutui-rate-icon-size | icon size | `16px` | | \--nutui-rate-font-color | Rating font color | `$color-primary-icon` | | \--nutui-rate-font-size | Rating font size | `12px` | +| \--nutui-rate-label-color | Label font color | `$color-text-disabled` | diff --git a/src/packages/rate/doc.md b/src/packages/rate/doc.md index c058dbb949..af81919392 100644 --- a/src/packages/rate/doc.md +++ b/src/packages/rate/doc.md @@ -106,6 +106,30 @@ import { Rate } from '@nutui/nutui-react' ::: +### 上下布局 + +:::demo + + + +::: + +### 带标签 + +:::demo + + + +::: + +### 过渡动画 + +:::demo + + + +::: + ## Rate ### Props @@ -113,6 +137,8 @@ import { Rate } from '@nutui/nutui-react' | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | size | star 尺寸 | `large` \| `normal` \| `small` | `normal` | +| direction | 方向 | `horizontal` \| `vertical` | `horizontal` | +| label | 标签文字 | `ReactNode` | `-` | | showScore | 展示评分 | `boolean` | `false` | | defaultValue | 非受控的 star 默认值 | `number` | `0` | | value | 受控的 star 数值 | `number` | `0` | @@ -138,8 +164,9 @@ import { Rate } from '@nutui/nutui-react' | \--nutui-rate-item-margin | 间距 | `4px` | | \--nutui-rate-icon-color | icon 激活颜色 | `$color-primary-icon` | | \--nutui-rate-icon-inactive-color | icon 未激活颜色 | `$color-primary-icon-disabled` | -| \--nutui-rate-icon-size | icon 尺寸 | `12px` | +| \--nutui-rate-icon-size | icon 尺寸 | `16px` | | \--nutui-rate-font-color | 评分字体颜色 | `$color-primary-icon` | | \--nutui-rate-font-size | 评分字体大小 | `12px` | +| \--nutui-rate-label-color | 标签字体颜色 | `$color-text-disabled` | diff --git a/src/packages/rate/doc.taro.md b/src/packages/rate/doc.taro.md index 5693c71d87..f97caf52b6 100644 --- a/src/packages/rate/doc.taro.md +++ b/src/packages/rate/doc.taro.md @@ -106,6 +106,30 @@ import { Rate } from '@nutui/nutui-react-taro' ::: +### 上下布局 + +:::demo + + + +::: + +### 带标签 + +:::demo + + + +::: + +### 过渡动画 + +:::demo + + + +::: + ## Rate ### Props @@ -113,6 +137,8 @@ import { Rate } from '@nutui/nutui-react-taro' | 属性 | 说明 | 类型 | 默认值 | | --- | --- | --- | --- | | size | star 尺寸 | `large` \| `normal` \| `small` | `normal` | +| direction | 方向 | `horizontal` \| `vertical` | `horizontal` | +| label | 标签文字 | `ReactNode` | `-` | | showScore | 展示评分 | `boolean` | `false` | | defaultValue | 非受控的 star 默认值 | `number` | `0` | | value | 受控的 star 数值 | `number` | `0` | @@ -123,7 +149,7 @@ import { Rate } from '@nutui/nutui-react-taro' | allowHalf | 是否半星 | `boolean` | `false` | | readOnly | 是否只读 | `boolean` | `false` | | disabled | 是否禁用 | `boolean` | `false` | -| touchable | 是否允许滑动选择 | `boolean` | `false` | +| touchable | 是否允许滑动选择 | `boolean` | `false` | | onChange | 当前 star 数修改时触发 | `(value: number) => void` | `-` | | onTouchEnd | touch 滑动结束时触发 | `(event: TouchEvent, value: number) => void` | `-` | @@ -138,8 +164,9 @@ import { Rate } from '@nutui/nutui-react-taro' | \--nutui-rate-item-margin | 间距 | `4px` | | \--nutui-rate-icon-color | icon 激活颜色 | `$color-primary-icon` | | \--nutui-rate-icon-inactive-color | icon 未激活颜色 | `$color-primary-icon-disabled` | -| \--nutui-rate-icon-size | icon 尺寸 | `12px` | +| \--nutui-rate-icon-size | icon 尺寸 | `16px` | | \--nutui-rate-font-color | 评分字体颜色 | `$color-primary-icon` | | \--nutui-rate-font-size | 评分字体大小 | `12px` | +| \--nutui-rate-label-color | 标签字体颜色 | `$color-text-disabled` | diff --git a/src/packages/rate/doc.zh-TW.md b/src/packages/rate/doc.zh-TW.md index 67b7d6690f..332c5fe083 100644 --- a/src/packages/rate/doc.zh-TW.md +++ b/src/packages/rate/doc.zh-TW.md @@ -106,6 +106,30 @@ import { Rate } from '@nutui/nutui-react' ::: +### 上下佈局 + +:::demo + + + +::: + +### 帶標籤 + +:::demo + + + +::: + +### 過渡動畫 + +:::demo + + + +::: + ## Rate ### Props @@ -113,6 +137,8 @@ import { Rate } from '@nutui/nutui-react' | 屬性 | 說明 | 類型 | 默認值 | | --- | --- | --- | --- | | size | star 尺寸 | `large` \| `normal` \| `small` | `normal` | +| direction | 方向 | `horizontal` \| `vertical` | `horizontal` | +| label | 標籤文字 | `ReactNode` | `-` | | showScore | 展示評分 | `boolean` | `false` | | defaultValue | 非受控的 star 默認值 | `number` | `0` | | value | 受控的 star 數值 | `number` | `0` | @@ -123,7 +149,7 @@ import { Rate } from '@nutui/nutui-react' | allowHalf | 是否半星 | `boolean` | `false` | | readOnly | 是否只讀 | `boolean` | `false` | | disabled | 是否禁用 | `boolean` | `false` | -| touchable | 是否允許滑動選擇 | `boolean` | `false` | +| touchable | 是否允許滑動選擇 | `boolean` | `false` | | onChange | 當前 star 數修改時觸發 | `(value: number) => void` | `-` | | onTouchEnd | touch 滑動結束時觸發 | `(event: TouchEvent, value: number) => void` | `-` | @@ -138,8 +164,9 @@ import { Rate } from '@nutui/nutui-react' | \--nutui-rate-item-margin | 間距 | `4px` | | \--nutui-rate-icon-color | icon 激活顏色 | `$color-primary-icon` | | \--nutui-rate-icon-inactive-color | icon 未激活顏色 | `$color-primary-icon-disabled` | -| \--nutui-rate-icon-size | icon 尺寸 | `12px` | +| \--nutui-rate-icon-size | icon 尺寸 | `16px` | | \--nutui-rate-font-color | 評分字體顏色 | `$color-primary-icon` | | \--nutui-rate-font-size | 評分字體大小 | `12px` | +| \--nutui-rate-label-color | 標籤字體顏色 | `$color-text-disabled` | diff --git a/src/packages/rate/index.taro.ts b/src/packages/rate/index.taro.ts index add93669a6..b1ca1e41dc 100644 --- a/src/packages/rate/index.taro.ts +++ b/src/packages/rate/index.taro.ts @@ -1,4 +1,8 @@ import { Rate } from './rate.taro' -export type { RateSize, TaroRateProps as RateProps } from '@/types' +export type { + RateSize, + RateDirection, + TaroRateProps as RateProps, +} from '@/types' export default Rate diff --git a/src/packages/rate/index.ts b/src/packages/rate/index.ts index bd6cbd9157..346b2ad5e5 100644 --- a/src/packages/rate/index.ts +++ b/src/packages/rate/index.ts @@ -1,4 +1,8 @@ import { Rate } from './rate' -export type { RateSize, WebRateProps as RateProps } from '@/types' +export type { + RateSize, + RateDirection, + WebRateProps as RateProps, +} from '@/types' export default Rate diff --git a/src/packages/rate/rate.scss b/src/packages/rate/rate.scss index 319f5d60da..03b8ab234b 100644 --- a/src/packages/rate/rate.scss +++ b/src/packages/rate/rate.scss @@ -14,6 +14,37 @@ } } + &-horizontal { + flex-direction: row; + align-items: center; + } + + &-vertical { + flex-direction: column; + align-items: center; + } + + &-list { + display: flex; + align-items: center; + } + + &-label { + color: $rate-label-color; + white-space: nowrap; + } + + &-labels { + display: flex; + width: 100%; + + &-item { + flex: 1; + text-align: center; + color: $rate-label-color; + } + } + &-item { display: flex; align-items: center; @@ -33,38 +64,6 @@ } } - &-normal { - margin-left: $rate-item-margin; - .nut-icon { - height: $rate-icon-size; - width: $rate-icon-size; - } - } - - &-large { - margin-left: calc($rate-item-margin * 2); - .nut-icon { - height: calc($rate-icon-size + 8px); - width: calc($rate-icon-size + 8px); - } - } - - &-small { - margin-left: calc($rate-item-margin / 2); - .nut-icon { - height: calc($rate-icon-size - 4px); - width: calc($rate-icon-size - 4px); - } - } - - &-normal, - &-large, - &-small { - &:first-child { - margin-left: 0; - } - } - &-icon { display: flex; align-items: center; @@ -93,6 +92,10 @@ left: auto; transform: none; } + + &-animate { + animation: nut-rate-bounce 0.3s ease-in-out; + } } } @@ -107,85 +110,250 @@ color: $rate-font-color; font-family: 'JDZH-Regular'; - &-normal { - padding-left: $rate-item-margin; - font-size: $rate-font-size; + &-disabled { + color: $rate-icon-inactive-color; + } + } + + // horizontal layout + &-horizontal { + .nut-rate-item { + margin-left: $rate-item-margin; + &:first-child { + margin-left: 0; + } + } + + // 12DP - star gap 2px + .nut-rate-item-small { + margin-left: scale-px(2px); + &:first-child { + margin-left: 0; + } + .nut-icon { + height: scale-font-px(12px); + width: scale-font-px(12px); + } + } + + .nut-rate-item-normal { + .nut-icon { + height: scale-font-px(16px); + width: scale-font-px(16px); + } + } + + .nut-rate-item-large { + .nut-icon { + height: scale-font-px(20px); + width: scale-font-px(20px); + } + } + + .nut-rate-label-small { + font-size: $font-size-s; + margin-right: scale-px(4px); + } + + .nut-rate-label-normal { + font-size: $font-size-s; + margin-right: scale-px(12px); + } + + .nut-rate-label-large { + font-size: $font-size-base; + margin-right: scale-px(12px); + } + + .nut-rate-score-small { + padding-left: scale-px(4px); + font-size: $font-size-s; line-height: $line-height-s; } - &-large { - font-size: $rate-font-size-large; - line-height: $line-height-xl; - padding-left: calc($rate-item-margin * 2); + .nut-rate-score-normal { + padding-left: scale-px(12px); + font-size: $font-size-s; + line-height: $line-height-s; } - &-small { - font-size: $rate-font-size-small; - line-height: $line-height-xxs; - padding-left: calc($rate-item-margin / 2); + .nut-rate-score-large { + padding-left: scale-px(12px); + font-size: $font-size-base; + line-height: $line-height-base; } + } - &-disabled { - color: $rate-icon-inactive-color; + // vertical layout + &-vertical { + .nut-rate-list { + justify-content: center; + width: 100%; + } + + .nut-rate-item { + margin-left: $rate-item-margin; + &:first-child { + margin-left: 0; + } + } + + .nut-rate-item-small, + .nut-rate-item-normal { + .nut-icon { + height: scale-font-px(20px); + width: scale-font-px(20px); + } + } + + .nut-rate-item-large { + flex: 1; + justify-content: center; + + .nut-icon { + height: scale-font-px(28px); + width: scale-font-px(28px); + } + } + + .nut-rate-label-small, + .nut-rate-label-normal { + font-size: $font-size-xs; + margin-top: scale-px(6px); + } + + .nut-rate-label-large { + font-size: $font-size-s; + margin-top: scale-px(8px); + } + + .nut-rate-labels { + width: 100%; + } + + .nut-rate-labels-small, + .nut-rate-labels-normal { + margin-top: scale-px(6px); + + .nut-rate-labels-item { + font-size: $font-size-xs; + } + } + + .nut-rate-labels-large { + margin-top: scale-px(8px); + + .nut-rate-labels-item { + font-size: $font-size-s; + } + } + + .nut-rate-score-small, + .nut-rate-score-normal { + font-size: $font-size-xs; + line-height: $line-height-xs; + margin-top: scale-px(6px); + } + + .nut-rate-score-large { + font-size: $font-size-s; + line-height: $line-height-s; + margin-top: scale-px(8px); } } } +@keyframes nut-rate-bounce { + 0% { + transform: scale(1); + } + 50% { + transform: scale(1.25); + } + 100% { + transform: scale(1); + } +} + [dir='rtl'] .nut-rate, .nut-rtl .nut-rate { - &-item { - margin-left: 0; - &:first-child { + &-horizontal { + .nut-rate-item { + margin-left: 0; + margin-right: $rate-item-margin; + + &:first-child { + margin-right: 0; + } + } + + .nut-rate-item-small { + margin-left: 0; + margin-right: scale-px(2px); + + &:first-child { + margin-right: 0; + } + } + + .nut-rate-label { margin-right: 0; } - &-normal { - margin-right: $rate-item-margin; + .nut-rate-label-small { + margin-left: scale-px(4px); } - &-large { - margin-right: calc($rate-item-margin * 2); + .nut-rate-label-normal { + margin-left: scale-px(12px); } - &-small { - margin-right: calc($rate-item-margin - 2px); + .nut-rate-label-large { + margin-left: scale-px(12px); } - &:last-child { - margin-left: 0; + .nut-rate-score { + padding-left: 0; } - &-half { - left: auto; - right: 0; + .nut-rate-score-small { + padding-right: scale-px(4px); } - &-icon { - &.nut-rate-item-icon.nut-rate-item-icon-half { - left: auto; - right: 0; - } + .nut-rate-score-normal { + padding-right: scale-px(12px); + } - &.nut-rate-item-icon::before { - left: auto; - right: auto; - } + .nut-rate-score-large { + padding-right: scale-px(12px); } } - &-score { - padding-left: 0; + &-vertical { + .nut-rate-item { + margin-left: 0; + margin-right: $rate-item-margin; - &-large { - padding-right: calc($rate-item-margin * 2); + &:first-child { + margin-right: 0; + } } + } - &-normal { - padding-right: $rate-item-margin; + &-item-half { + left: auto; + right: 0; + } + + &-item-icon { + &.nut-rate-item-icon.nut-rate-item-icon-half { + left: auto; + right: 0; } - &-small { - padding-right: calc($rate-item-margin - 2px); + &.nut-rate-item-icon::before { + left: auto; + right: auto; } } } diff --git a/src/packages/rate/rate.taro.tsx b/src/packages/rate/rate.taro.tsx index 000c68f296..e4dc1d5b4d 100644 --- a/src/packages/rate/rate.taro.tsx +++ b/src/packages/rate/rate.taro.tsx @@ -1,6 +1,7 @@ import React, { FunctionComponent, ReactElement, + ReactNode, useCallback, useEffect, useLayoutEffect, @@ -19,6 +20,7 @@ import { TaroRateProps } from '@/types' const defaultProps = { ...ComponentDefaults, size: 'normal', + direction: 'horizontal', showScore: false, count: 5, min: 0, @@ -34,6 +36,8 @@ export const Rate: FunctionComponent> = (props) => { className, style, size, + direction, + label, showScore, count, value, @@ -54,6 +58,16 @@ export const Rate: FunctionComponent> = (props) => { const classPrefix = 'nut-rate' + const shouldAnimate = direction === 'vertical' || size === 'large' + const [animatingIndex, setAnimatingIndex] = useState(-1) + + useEffect(() => { + if (animatingIndex >= 0) { + const timer = setTimeout(() => setAnimatingIndex(-1), 300) + return () => clearTimeout(timer) + } + }, [animatingIndex]) + const [countArray, setCountArray] = useState([1, 2, 3, 4, 5]) const [refs, setRefs] = useRefs() @@ -105,6 +119,9 @@ export const Rate: FunctionComponent> = (props) => { value = index } value = Math.max(value, min) + if (shouldAnimate && value > score) { + setAnimatingIndex(index - 1) + } setScore(value) } @@ -115,6 +132,9 @@ export const Rate: FunctionComponent> = (props) => { event.preventDefault() event.stopPropagation() const value = Math.max(min, n - 0.5) + if (shouldAnimate && value > score) { + setAnimatingIndex(n - 1) + } setScore(value) } @@ -187,10 +207,62 @@ export const Rate: FunctionComponent> = (props) => { } } + const isArrayLabel = Array.isArray(label) + + const renderSingleLabel = () => { + if (label == null || isArrayLabel) return null + return ( + + {label} + + ) + } + + const renderArrayLabels = () => { + if (!isArrayLabel) return null + return ( + + {(label as ReactNode[]).map((item, index) => ( + + {item} + + ))} + + ) + } + + const renderScore = () => { + if (!showScore) return null + return ( + + {score.toFixed(1)} + + ) + } + return ( > = (props) => { onTouchEnd={handleTouchEnd} onTouchCancel={handleTouchEnd} > - {countArray.map((n, index) => { - return ( - onClick(event, n)} - > + {direction === 'horizontal' && renderSingleLabel()} + + {countArray.map((n, index) => { + return ( score, - })} + className={`${classPrefix}-item ${classPrefix}-item-${size}`} + key={n} + ref={setRefs(index)} + onClick={(event) => onClick(event, n)} > - {renderIcon(n)} - - {allowHalf && score > n - 1 && ( onHalfClick(event, n)} + className={classNames(`${classPrefix}-item-icon`, { + [`${classPrefix}-item-icon-disabled`]: disabled || n > score, + [`${classPrefix}-item-icon-animate`]: + animatingIndex === index, + })} > {renderIcon(n)} - )} - - ) - })} - {showScore ? ( - - {score.toFixed(1)} - - ) : null} + {allowHalf && score > n - 1 && ( + onHalfClick(event, n)} + > + {renderIcon(n)} + + )} + + ) + })} + + {direction === 'horizontal' && renderScore()} + {direction === 'vertical' && ( + <> + {renderScore()} + {isArrayLabel ? renderArrayLabels() : renderSingleLabel()} + + )} ) } diff --git a/src/packages/rate/rate.tsx b/src/packages/rate/rate.tsx index 3b503cfee2..54e99e384d 100644 --- a/src/packages/rate/rate.tsx +++ b/src/packages/rate/rate.tsx @@ -1,6 +1,7 @@ import React, { FunctionComponent, ReactElement, + ReactNode, useEffect, useRef, useState, @@ -16,6 +17,7 @@ import { WebRateProps } from '@/types' const defaultProps = { ...ComponentDefaults, size: 'normal', + direction: 'horizontal', showScore: false, count: 5, min: 0, @@ -31,6 +33,8 @@ export const Rate: FunctionComponent> = (props) => { className, style, size, + direction, + label, showScore, count, value, @@ -51,6 +55,16 @@ export const Rate: FunctionComponent> = (props) => { const classPrefix = 'nut-rate' + const shouldAnimate = direction === 'vertical' || size === 'large' + const [animatingIndex, setAnimatingIndex] = useState(-1) + + useEffect(() => { + if (animatingIndex >= 0) { + const timer = setTimeout(() => setAnimatingIndex(-1), 300) + return () => clearTimeout(timer) + } + }, [animatingIndex]) + const [countArray, setCountArray] = useState([1, 2, 3, 4, 5]) const [refs, setRefs] = useRefs() @@ -99,6 +113,9 @@ export const Rate: FunctionComponent> = (props) => { value = index } value = Math.max(value, min) + if (shouldAnimate && value > score) { + setAnimatingIndex(index - 1) + } setScore(value) } @@ -106,6 +123,9 @@ export const Rate: FunctionComponent> = (props) => { event.preventDefault() event.stopPropagation() const value = Math.max(min, n - 0.5) + if (shouldAnimate && value > score) { + setAnimatingIndex(n - 1) + } setScore(value) } @@ -193,10 +213,62 @@ export const Rate: FunctionComponent> = (props) => { } }, []) + const isArrayLabel = Array.isArray(label) + + const renderSingleLabel = () => { + if (label == null || isArrayLabel) return null + return ( + + {label} + + ) + } + + const renderArrayLabels = () => { + if (!isArrayLabel) return null + return ( +
+ {(label as ReactNode[]).map((item, index) => ( + + {item} + + ))} +
+ ) + } + + const renderScore = () => { + if (!showScore) return null + return ( + + {score.toFixed(1)} + + ) + } + return (
> = (props) => { ref={rateRef} style={style} > - {countArray.map((n, index) => { - return ( -
onClick(event, n)} - > + {direction === 'horizontal' && renderSingleLabel()} +
+ {countArray.map((n, index) => { + return (
score, - })} + className={`${classPrefix}-item ${classPrefix}-item-${size}`} + key={n} + ref={setRefs(index)} + onClick={(event) => onClick(event, n)} > - {renderIcon(n)} -
- {allowHalf && score > n - 1 && (
onHalfClick(event, n)} + className={classNames(`${classPrefix}-item-icon`, { + [`${classPrefix}-item-icon-disabled`]: disabled || n > score, + [`${classPrefix}-item-icon-animate`]: + animatingIndex === index, + })} > {renderIcon(n)}
- )} -
- ) - })} - {showScore ? ( - - {score.toFixed(1)} - - ) : null} + {allowHalf && score > n - 1 && ( +
onHalfClick(event, n)} + > + {renderIcon(n)} +
+ )} +
+ ) + })} +
+ {direction === 'horizontal' && renderScore()} + {direction === 'vertical' && ( + <> + {renderScore()} + {isArrayLabel ? renderArrayLabels() : renderSingleLabel()} + + )} ) } diff --git a/src/styles/variables-daojia.scss b/src/styles/variables-daojia.scss index 982b6bada4..3603c7d984 100644 --- a/src/styles/variables-daojia.scss +++ b/src/styles/variables-daojia.scss @@ -798,6 +798,7 @@ $rate-icon-inactive-color: var(--nutui-rate-icon-inactive-color, $rate-icon-size: var(--nutui-rate-icon-size, 12px) !default; $rate-font-color: var(--nutui-rate-font-color, $color-primary-icon) !default; $rate-font-size: var(--nutui-rate-font-size, 12px) !default; +$rate-label-color: var(--nutui-rate-label-color, $color-text-disabled) !default; // tabbar(✅) $tabbar-height: var(--nutui-tabbar-height, 46px) !default; diff --git a/src/styles/variables-jmapp.scss b/src/styles/variables-jmapp.scss index 699c9d6666..9f50ac70a0 100644 --- a/src/styles/variables-jmapp.scss +++ b/src/styles/variables-jmapp.scss @@ -1346,6 +1346,7 @@ $rate-font-color: var(--nutui-rate-font-color, $color-primary-icon) !default; $rate-font-size: var(--nutui-rate-font-size, 12px) !default; $rate-font-size-large: var(--nutui-rate-font-size-large, calc(#{$rate-font-size} + 6px)) !default; $rate-font-size-small: var(--nutui-rate-font-size-small, calc(#{$rate-font-size} - 2px)) !default; +$rate-label-color: var(--nutui-rate-label-color, $color-text-disabled) !default; // tabbar(✅) $tabbar-height: var(--nutui-tabbar-height, 50px) !default; diff --git a/src/styles/variables-jrkf.scss b/src/styles/variables-jrkf.scss index a39df2f708..088fb4ef61 100644 --- a/src/styles/variables-jrkf.scss +++ b/src/styles/variables-jrkf.scss @@ -1445,6 +1445,7 @@ $rate-font-color: var(--nutui-rate-font-color, $color-primary-icon) !default; $rate-font-size: var(--nutui-rate-font-size, 12px) !default; $rate-font-size-large: var(--nutui-rate-font-size-large, calc(#{$rate-font-size} + 6px)) !default; $rate-font-size-small: var(--nutui-rate-font-size-small, calc(#{$rate-font-size} - 2px)) !default; +$rate-label-color: var(--nutui-rate-label-color, $color-text-disabled) !default; $rate-item-margin: var(--nutui-rate-item-margin, 14px) !default; $rate-icon-color: var(--nutui-rate-icon-color, $color-primary) !default; $rate-icon-inactive-color: var( diff --git a/src/styles/variables.scss b/src/styles/variables.scss index fecc48c7b4..1ad90e86f2 100644 --- a/src/styles/variables.scss +++ b/src/styles/variables.scss @@ -1261,11 +1261,12 @@ $rate-icon-inactive-color: var( --nutui-rate-icon-inactive-color, $color-primary-icon-disabled ) !default; -$rate-icon-size: var(--nutui-rate-icon-size, scale-font-px(12px)) !default; +$rate-icon-size: var(--nutui-rate-icon-size, scale-font-px(16px)) !default; $rate-font-color: var(--nutui-rate-font-color, $color-primary-icon) !default; $rate-font-size: var(--nutui-rate-font-size, $font-size-s) !default; -$rate-font-size-large: var(--nutui-rate-font-size-large, calc($rate-font-size + scale-font-px(6px))) !default; -$rate-font-size-small: var(--nutui-rate-font-size-small, calc($rate-font-size - scale-font-px(2px))) !default; +$rate-font-size-large: var(--nutui-rate-font-size-large, $font-size-base) !default; +$rate-font-size-small: var(--nutui-rate-font-size-small, $font-size-s) !default; +$rate-label-color: var(--nutui-rate-label-color, $color-text-disabled) !default; // tabbar(✅) $tabbar-height: var(--nutui-tabbar-height, scale-px(46px)) !default; diff --git a/src/types/spec/rate/base.ts b/src/types/spec/rate/base.ts index 6a67aca553..de90280ef1 100644 --- a/src/types/spec/rate/base.ts +++ b/src/types/spec/rate/base.ts @@ -3,9 +3,12 @@ import { BasicComponent } from '@/utils/typings' import { UISize } from '../../base/atoms' export type RateSize = Extract +export type RateDirection = 'horizontal' | 'vertical' export interface BaseRate extends BasicComponent { size: RateSize + direction: RateDirection + label: ReactNode | ReactNode[] showScore: boolean count: number value: number