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

fix(badge): 四端适配 #2620

Open
wants to merge 4 commits into
base: dev-harmony
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
1 change: 0 additions & 1 deletion scripts/rn/copy-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ const removeFile = async (url) => {
}

const modify = (fileUrl, importStatement) => {
if(!fse.ensureFileSync(fileUrl)) return
fse.readFile(fileUrl, 'utf8').then((content) => {
let modifiedContent = content
modifiedContent = [importStatement, modifiedContent.slice(0)].join('')
Expand Down
2 changes: 1 addition & 1 deletion src/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -716,7 +716,7 @@
"author": "dsj"
},
{
"version": "2.0.0",
"version": "3.0.0",
"name": "Badge",
"type": "component",
"cName": "徽标",
Expand Down
31 changes: 14 additions & 17 deletions src/packages/badge/badge.harmony.css
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
.nut-badge {
position: relative;
display: inline-block;
display: inline-flex;
width: auto;
/* #ifdef harmony */
min-width: 1px;
/* #endif */
}
.nut-badge-icon {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #FF0F23 0%, #FF0F23 100%);
top: -10%;
right: -15%;
background: #ff0f23;
padding: 3px;
text-align: center;
border: 0px solid #ffffff;
Expand All @@ -27,7 +29,7 @@
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #FF0F23 0%, #FF0F23 100%);
background: #ff0f23;
color: #fff;
padding: 0 4px;
font-size: 10px;
Expand All @@ -45,7 +47,9 @@
padding: 0;
}
.nut-badge-content {
transform: translateY(-50%) translateX(100%);
/* #ifndef rn */
transform: translateX(100%);
/* #endif */
}
.nut-badge-dot {
width: 7px;
Expand All @@ -54,15 +58,8 @@
border-radius: 7px;
padding: 0;
}

[dir=rtl] .nut-badge-icon,
.nut-rtl .nut-badge-icon {
right: auto;
left: -15%;
}
[dir=rtl] .nut-badge-content {
transform: translateY(-50%) translateX(-100%);
}
.nut-rtl .nut-badge-content {
transform: translateY(-50%) translateX(-100%);
.nut-badge-outline {
background: #ffffff;
border: 1px solid #ff0f23;
color: #ff0f23;
}
34 changes: 10 additions & 24 deletions src/packages/badge/badge.scss
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
.nut-badge {
position: relative;
display: inline-block;

display: inline-flex;
width: auto;
/* #ifdef harmony */
min-width: 1px;
/* #endif */
&-icon {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
background: $badge-background-color;
top: -10%;
right: -15%;
padding: $badge-icon-padding;
text-align: center;
border: $badge-border;
Expand Down Expand Up @@ -49,7 +50,9 @@
}

&-content {
/* #ifndef rn */
transform: $badge-content-transform;
/* #endif */
}

&-dot {
Expand All @@ -61,25 +64,8 @@
}

&-outline {
.nut-badge-content {
background: $color-primary-text;
border: 1px solid $color-primary;
color: $color-primary;
}
}
}

[dir='rtl'] .nut-badge,
.nut-rtl .nut-badge {
&-icon {
right: auto;
left: -15%;
}

&-content {
transform: var(
--nutui-badge-content-transform,
translateY(-50%) translateX(-100%)
);
background: $color-primary-text;
border: 1px solid $color-primary;
color: $color-primary;
}
}
86 changes: 64 additions & 22 deletions src/packages/badge/badge.taro.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
import React, { CSSProperties, FunctionComponent, ReactNode } from 'react'
import React, {
CSSProperties,
FunctionComponent,
ReactNode,
useEffect,
useRef,
useState,
} from 'react'
import classNames from 'classnames'
import { View } from '@tarojs/components'
import { BasicComponent, ComponentDefaults } from '@/utils/typings'
import { useRtl } from '@/packages/configprovider/index.taro'
import pxTransform from '@/utils/px-transform'
import { getRectByTaro } from '@/utils/get-rect-by-taro'
import { harmony, rn } from '@/utils/platform-taro'

export type BadgeFill = 'solid' | 'outline'
export interface BadgeProps extends BasicComponent {
Expand Down Expand Up @@ -42,9 +52,9 @@ export const Badge: FunctionComponent<Partial<BadgeProps>> = (props) => {
...props,
}
const classPrefix = 'nut-badge'
const classes = classNames(classPrefix, className, {
[`${classPrefix}-${fill}`]: fill === 'outline',
})
const classes = classNames(classPrefix, className)
const badgeRef = useRef(null)
const [contentStyle, setContentStyle] = useState({})

function content() {
if (dot || typeof value === 'object' || value === 0) return null
Expand All @@ -66,41 +76,73 @@ export const Badge: FunctionComponent<Partial<BadgeProps>> = (props) => {
if (typeof value === 'string' && value) return value
}

const contentClasses = classNames(
{ [`${classPrefix}-dot`]: dot },
`${classPrefix}-content`,
{ [`${classPrefix}-sup`]: isNumber() || isString() || dot },
{
[`${classPrefix}-one`]:
typeof content() === 'string' && `${content()}`?.length === 1,
const contentClasses = classNames(`${classPrefix}-content`, {
[`${classPrefix}-sup`]: isNumber() || isString() || dot,
[`${classPrefix}-one`]:
typeof content() === 'string' && `${content()}`?.length === 1,
[`${classPrefix}-dot`]: dot,
[`${classPrefix}-${fill}`]: fill === 'outline',
})

useEffect(() => {
if (badgeRef.current) {
getPositionStyle()
}
)
const getStyle = () => {
}, [badgeRef.current])
Comment on lines +87 to +91
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

[问题] 使用 'badgeRef.current' 作为 useEffect 的依赖可能无法如预期工作

在第 87-91 行,使用 badgeRef.current 作为 useEffect 的依赖项可能不会在 badgeRef.current 变化时触发更新。React 的 useRefcurrent 属性变化不会引发组件重新渲染,因此依赖于它的 useEffect 可能无法按预期执行。

建议修改为在组件挂载后执行一次,或者根据需要使用其他依赖项触发 getPositionStyle

可以将依赖数组改为空数组,确保在组件挂载后执行一次:

- }, [badgeRef.current])
+ }, [])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
if (badgeRef.current) {
getPositionStyle()
}
)
const getStyle = () => {
}, [badgeRef.current])
useEffect(() => {
if (badgeRef.current) {
getPositionStyle()
}
}, [])

const getPositionStyle = async () => {
const style: CSSProperties = {}
style.top = `${Number(top) || parseFloat(String(top)) || 0}px`
const dir = rtl ? 'left' : 'right'
style[dir] = `${Number(right) || parseFloat(String(right)) || 0}px`
style.top = pxTransform(Number(-top) || 0)
if (rn()) {
Comment on lines +94 to +95
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

[问题] 'style.top' 的计算方式可能不正确

在第 94 行,style.top = pxTransform(Number(-top) || 0) 中,Number(-top) 会将 top 转为数字后再取反。如果 top 是字符串类型,例如 '4',结果可能不符合预期。

建议先将 top 转为数字后再取负值,以确保计算正确。

修改为:

- style.top = pxTransform(Number(-top) || 0)
+ style.top = pxTransform(-Number(top) || 0)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
style.top = pxTransform(Number(-top) || 0)
if (rn()) {
style.top = pxTransform(-Number(top) || 0)
if (rn()) {

const reacts = await getRectByTaro(badgeRef.current)
style.left =
reacts?.width && reacts?.width > Number(right)
? pxTransform(reacts.width - Number(right))
: 0
} else {
const dir = rtl ? 'left' : 'right'
style[dir] = harmony()
? pxTransform(Number(right))
: `${Number(right) || parseFloat(String(right)) || 0}px`
}
setContentStyle(style)
}

const getStyle = () => {
const style: CSSProperties = {}
if (color) {
if (fill === 'outline') {
style.color = color
style.background = '#fff'
style.backgroundColor = '#fff'
if (!color?.includes('gradient')) {
style.border = `1px solid ${color}`
style.borderColor = color
Comment on lines +115 to +117
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

[建议] 避免在样式中硬编码颜色值,提升组件的可定制性

在第 115 行,style.backgroundColor = '#fff' 将背景颜色固定为白色,可能限制了组件的灵活性。如果用户希望自定义边框模式下的背景颜色,当前实现无法满足需求。

建议允许通过属性传入背景颜色,或者使用一个默认值,同时提供自定义的可能性:

- style.backgroundColor = '#fff'
+ style.backgroundColor = backgroundColor || '#fff'

并在 BadgeProps 中添加 backgroundColor 属性:

export interface BadgeProps extends BasicComponent {
  // 其他属性...
  backgroundColor?: string
}

}
} else {
style.color = '#fff'
style.background = color
style.backgroundColor = color
}
}
return style
}

return (
<View className={classes} style={style}>
{isIcon() && <View className={`${classPrefix}-icon`}>{value}</View>}
<View className={classes} style={style} ref={badgeRef}>
{isIcon() && (
<View
className={classNames(`${classPrefix}-content`, {
[`${classPrefix}-icon`]: true,
[`${classPrefix}-icon-rtl`]: rtl,
})}
style={contentStyle}
>
{value}
</View>
)}
{children}
{!isIcon() && (
<View className={contentClasses} style={getStyle()}>
<View
className={contentClasses}
style={{ ...contentStyle, ...getStyle() }}
>
{content()}
</View>
)}
Expand Down
29 changes: 18 additions & 11 deletions src/packages/badge/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,18 +66,16 @@ export const Badge: FunctionComponent<Partial<BadgeProps>> = (props) => {
if (typeof value === 'string' && value) return value
}

const contentClasses = classNames(
{ [`${classPrefix}-dot`]: dot },
`${classPrefix}-content`,
{ [`${classPrefix}-sup`]: isNumber() || isString() || dot },
{
[`${classPrefix}-one`]:
typeof content() === 'string' && `${content()}`?.length === 1,
}
)
const contentClasses = classNames(`${classPrefix}-content`, {
[`${classPrefix}-sup`]: isNumber() || isString() || dot,
[`${classPrefix}-one`]:
typeof content() === 'string' && `${content()}`?.length === 1,
[`${classPrefix}-dot`]: dot,
[`${classPrefix}-${fill}`]: fill === 'outline',
})
const getStyle = () => {
const style: CSSProperties = {}
style.top = `${Number(top) || parseFloat(String(top)) || 0}px`
style.top = `${Number(-top) || parseFloat(String(-top)) || 0}px`
const dir = rtl ? 'left' : 'right'
style[dir] = `${Number(right) || parseFloat(String(right)) || 0}px`

Expand All @@ -97,7 +95,16 @@ export const Badge: FunctionComponent<Partial<BadgeProps>> = (props) => {
}
return (
<div className={classes} style={style}>
{isIcon() && <div className={`${classPrefix}-icon`}>{value}</div>}
{isIcon() && (
<div
className={classNames(`${classPrefix}-content`, {
[`${classPrefix}-icon`]: true,
[`${classPrefix}-icon-rtl`]: rtl,
})}
>
{value}
</div>
)}
{children}
{!isIcon() && (
<div className={contentClasses} style={getStyle()}>
Expand Down
30 changes: 22 additions & 8 deletions src/packages/badge/demos/taro/demo1.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
import { User } from '@nutui/icons-react-taro'
import { Avatar, Badge, Cell } from '@nutui/nutui-react-taro'
import React from 'react'
import pxTransform from '@/utils/px-transform'
import { harmonyAndRn } from '@/utils/platform-taro'

const Demo1 = () => {
const isRnAndHarmony = harmonyAndRn()
const renderChildren = () => {
return (
<>
{isRnAndHarmony ? (
<Avatar shape="square">N</Avatar>
) : (
<Avatar icon={<User />} shape="square" />
)}
</>
)
}
return (
<Cell>
<Badge style={{ marginInlineEnd: '40px' }} value={8}>
<Avatar icon={<User />} shape="square" />
<Badge style={{ marginRight: pxTransform(40) }} value={8}>
{renderChildren()}
</Badge>
<Badge style={{ marginInlineEnd: '40px' }} value={76}>
<Avatar icon={<User />} shape="square" />
<Badge style={{ marginRight: pxTransform(40) }} value={76}>
{renderChildren()}
</Badge>
<Badge style={{ marginInlineEnd: '40px' }} value="NEW">
<Avatar icon={<User />} shape="square" />
<Badge style={{ marginRight: pxTransform(40) }} value="NEW">
{renderChildren()}
</Badge>
<Badge style={{ marginInlineEnd: '40px' }} dot top="2" right="4">
<Avatar icon={<User />} shape="square" />
<Badge style={{ marginRight: pxTransform(40) }} dot top="2" right="4">
{renderChildren()}
</Badge>
</Cell>
)
Expand Down
26 changes: 20 additions & 6 deletions src/packages/badge/demos/taro/demo2.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { User } from '@nutui/icons-react-taro'
import { Avatar, Badge, Cell } from '@nutui/nutui-react-taro'
import React from 'react'
import pxTransform from '@/utils/px-transform'
import { harmonyAndRn } from '@/utils/platform-taro'

const Demo2 = () => {
const isRnAndHarmony = harmonyAndRn()
const renderChildren = () => {
return (
<>
{isRnAndHarmony ? (
<Avatar shape="square">N</Avatar>
) : (
<Avatar icon={<User />} shape="square" />
)}
</>
)
}
return (
<Cell>
<Badge style={{ marginInlineEnd: '40px' }} value={200} max={9}>
<Avatar icon={<User />} shape="square" />
<Badge style={{ marginRight: pxTransform(40) }} value={200} max={9}>
{renderChildren()}
</Badge>
<Badge style={{ marginInlineEnd: '40px' }} value={200} max={20}>
<Avatar icon={<User />} shape="square" />
<Badge style={{ marginRight: pxTransform(40) }} value={200} max={20}>
{renderChildren()}
</Badge>
<Badge style={{ marginInlineEnd: '40px' }} value={200} max={99}>
<Avatar icon={<User />} shape="square" />
<Badge style={{ marginRight: pxTransform(40) }} value={200} max={99}>
{renderChildren()}
Comment on lines +22 to +29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

确认修改有效,但发现其他文件仍使用 marginInlineEnd

此次对 Badge 组件的修改提升了代码复用性,并确保了不同设备上的一致性。然而,经过检查,发现代码库中仍有多个文件使用 marginInlineEnd,这可能会影响 RTL(从右到左)布局的支持。建议:

  • 在整个代码库中统一处理边距,考虑使用动态样式以支持 RTL,例如利用 I18nManager.isRTL
  • 或者,逐步替换 marginInlineEnd 为适应 RTL 的替代方案。
🔗 Analysis chain

Badge 组件更新提高了代码复用性,但需注意 RTL 布局

Badge 组件的更新很好地利用了 renderChildren 函数,提高了代码的复用性和可维护性。使用 pxTransform 函数有助于确保在不同设备上的一致性。

然而,将 marginInlineEnd 改为 marginRight 可能会影响右到左(RTL)布局的支持。建议:

  1. 如果需要支持 RTL 布局,考虑使用 React Native 的 I18nManager.isRTL 来动态设置 margin:
import { I18nManager } from 'react-native';

// ...

<Badge
  style={{
    [I18nManager.isRTL ? 'marginLeft' : 'marginRight']: pxTransform(40),
  }}
  value={200}
  max={9}
>
  {renderChildren()}
</Badge>
  1. 如果不需要 RTL 支持,请在代码中添加注释说明这一决定,以便将来的开发者理解。

为了确保这个变更不会影响其他使用 marginInlineEnd 的地方,请运行以下脚本:


为了确保代码库中没有其他地方使用 marginInlineEnd,请运行以下脚本:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# 描述:检查代码库中是否还有其他地方使用了 marginInlineEnd
rg --type typescript --type javascript 'marginInlineEnd'

Length of output: 91


Script:

#!/bin/bash
# 描述:检查代码库中是否还有其他地方使用了 marginInlineEnd
rg 'marginInlineEnd' -g '*.ts' -g '*.tsx'

Length of output: 4985

</Badge>
</Cell>
)
Expand Down
Loading