feat: update migration status and add ListItem and EditableNumber components

- Updated migration status documentation to reflect the migration of 38 components, with 198 pending.
- Enhanced the component status table with new entries for ListItem and EditableNumber.
- Added ListItem and EditableNumber components with their respective implementations and styles.
- Updated index.ts to export the newly added components for improved accessibility.
This commit is contained in:
MyPrototypeWhat 2025-09-15 15:23:03 +08:00
parent 6eb9ab30b0
commit 046ed3edef
5 changed files with 219 additions and 6 deletions

View File

@ -49,9 +49,9 @@ function MyComponent() {
## 迁移概览
- **总组件数**: 236
- **已迁移**: 36
- **已迁移**: 38
- **已重构**: 0
- **待迁移**: 200
- **待迁移**: 198
## 组件状态表
@ -74,6 +74,7 @@ function MyComponent() {
| | ExpandableText | ✅ | ❌ | 可展开文本 |
| | ThinkingEffect | ✅ | ❌ | 思考效果动画 |
| | EmojiAvatar | ✅ | ❌ | 表情头像 |
| | ListItem | ✅ | ❌ | 列表项 |
| | CodeViewer | ❌ | ❌ | 代码查看器 (外部依赖) |
| | OGCard | ❌ | ❌ | OG 卡片 |
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown 渲染器 |
@ -105,8 +106,8 @@ function MyComponent() {
| | InfoTooltip | ✅ | ❌ | 信息提示 |
| | HelpTooltip | ✅ | ❌ | 帮助提示 |
| | WarnTooltip | ✅ | ❌ | 警告提示 |
| | EditableNumber | ✅ | ❌ | 可编辑数字 |
| | DraggableList | ❌ | ❌ | 可拖拽列表 |
| | EditableNumber | ❌ | ❌ | 可编辑数字 |
| | EmojiPicker | ❌ | ❌ | 表情选择器 |
| | Selector | ✅ | ❌ | 选择器 (i18n 依赖) |
| | ModelSelector | ❌ | ❌ | 模型选择器 (Redux 依赖) |

View File

@ -48,9 +48,9 @@ When submitting PRs, please place components in the correct directory based on t
## Migration Overview
- **Total Components**: 236
- **Migrated**: 36
- **Migrated**: 38
- **Refactored**: 0
- **Pending Migration**: 200
- **Pending Migration**: 198
## Component Status Table
@ -73,6 +73,7 @@ When submitting PRs, please place components in the correct directory based on t
| | ExpandableText | ✅ | ❌ | Expandable text |
| | ThinkingEffect | ✅ | ❌ | Thinking effect animation |
| | EmojiAvatar | ✅ | ❌ | Emoji avatar |
| | ListItem | ✅ | ❌ | List item |
| | CodeViewer | ❌ | ❌ | Code viewer (external deps) |
| | OGCard | ❌ | ❌ | OG card |
| | MarkdownShadowDOMRenderer | ❌ | ❌ | Markdown renderer |
@ -104,8 +105,8 @@ When submitting PRs, please place components in the correct directory based on t
| | InfoTooltip | ✅ | ❌ | Info tooltip |
| | HelpTooltip | ✅ | ❌ | Help tooltip |
| | WarnTooltip | ✅ | ❌ | Warning tooltip |
| | EditableNumber | ✅ | ❌ | Editable number |
| | DraggableList | ❌ | ❌ | Draggable list |
| | EditableNumber | ❌ | ❌ | Editable number |
| | EmojiPicker | ❌ | ❌ | Emoji picker |
| | Selector | ✅ | ❌ | Selector (i18n dependency) |
| | ModelSelector | ❌ | ❌ | Model selector (Redux dependency) |

View File

@ -0,0 +1,92 @@
// Original path: src/renderer/src/components/ListItem/index.tsx
import { Typography } from 'antd'
import { ReactNode } from 'react'
import styled from 'styled-components'
interface ListItemProps {
active?: boolean
icon?: ReactNode
title: ReactNode
subtitle?: string
titleStyle?: React.CSSProperties
onClick?: () => void
rightContent?: ReactNode
style?: React.CSSProperties
}
const ListItem = ({ active, icon, title, subtitle, titleStyle, onClick, rightContent, style }: ListItemProps) => {
return (
<ListItemContainer className={active ? 'active' : ''} onClick={onClick} style={style}>
<ListItemContent>
{icon && <IconWrapper>{icon}</IconWrapper>}
<TextContainer>
<Typography.Text style={titleStyle} ellipsis={{ expanded: false, tooltip: title }}>
{title}
</Typography.Text>
{subtitle && <SubtitleText>{subtitle}</SubtitleText>}
</TextContainer>
{rightContent && <RightContentWrapper>{rightContent}</RightContentWrapper>}
</ListItemContent>
</ListItemContainer>
)
}
const ListItemContainer = styled.div`
padding: 7px 12px;
border-radius: var(--list-item-border-radius);
font-size: 13px;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
cursor: pointer;
border: 1px solid transparent;
&:hover {
background-color: var(--color-background-soft);
}
&.active {
background-color: var(--color-background-soft);
border: 1px solid var(--color-border-soft);
}
`
const ListItemContent = styled.div`
display: flex;
align-items: center;
gap: 2px;
overflow: hidden;
font-size: 13px;
`
const IconWrapper = styled.span`
display: flex;
align-items: center;
justify-content: center;
margin-right: 8px;
`
const TextContainer = styled.div`
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
`
const SubtitleText = styled.div`
font-size: 10px;
color: var(--color-text-soft);
margin-top: 2px;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
color: var(--color-text-3);
`
const RightContentWrapper = styled.div`
margin-left: auto;
`
export default ListItem

View File

@ -15,6 +15,7 @@ export { WarnTag } from './base/WarnTag'
export { default as Ellipsis } from './display/Ellipsis'
export { default as EmojiAvatar } from './display/EmojiAvatar'
export { default as ExpandableText } from './display/ExpandableText'
export { default as ListItem } from './display/ListItem'
export { default as ThinkingEffect } from './display/ThinkingEffect'
// Layout Components
@ -39,6 +40,8 @@ export { default as WebSearchIcon } from './icons/WebSearchIcon'
export { default as WrapIcon } from './icons/WrapIcon'
// Interactive Components
export type { EditableNumberProps } from './interactive/EditableNumber'
export { default as EditableNumber } from './interactive/EditableNumber'
export { default as HelpTooltip } from './interactive/HelpTooltip'
export { default as InfoTooltip } from './interactive/InfoTooltip'
export { default as Selector } from './interactive/Selector'

View File

@ -0,0 +1,116 @@
// Original path: src/renderer/src/components/EditableNumber/index.tsx
import { InputNumber } from 'antd'
import { FC, useEffect, useRef, useState } from 'react'
import styled from 'styled-components'
export interface EditableNumberProps {
value?: number | null
min?: number
max?: number
step?: number
precision?: number
placeholder?: string
disabled?: boolean
changeOnBlur?: boolean
onChange?: (value: number | null) => void
onBlur?: () => void
style?: React.CSSProperties
className?: string
size?: 'small' | 'middle' | 'large'
suffix?: string
prefix?: string
align?: 'start' | 'center' | 'end'
}
const EditableNumber: FC<EditableNumberProps> = ({
value,
min,
max,
step = 0.01,
precision,
placeholder,
disabled = false,
onChange,
onBlur,
changeOnBlur = false,
style,
className,
size = 'middle',
align = 'end'
}) => {
const [isEditing, setIsEditing] = useState(false)
const [inputValue, setInputValue] = useState(value)
const inputRef = useRef<HTMLInputElement>(null)
useEffect(() => {
setInputValue(value)
}, [value])
const handleFocus = () => {
if (disabled) return
setIsEditing(true)
}
const handleInputChange = (newValue: number | null) => {
onChange?.(newValue ?? null)
}
const handleBlur = () => {
setIsEditing(false)
onBlur?.()
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleBlur()
} else if (e.key === 'Escape') {
e.stopPropagation()
setInputValue(value)
setIsEditing(false)
}
}
return (
<Container>
<InputNumber
style={{ ...style, opacity: isEditing ? 1 : 0 }}
ref={inputRef}
value={inputValue}
min={min}
max={max}
step={step}
precision={precision}
size={size}
onChange={handleInputChange}
onBlur={handleBlur}
onFocus={handleFocus}
onKeyDown={handleKeyDown}
className={className}
controls={isEditing}
changeOnBlur={changeOnBlur}
/>
<DisplayText style={style} className={className} $align={align} $isEditing={isEditing}>
{value ?? placeholder}
</DisplayText>
</Container>
)
}
const Container = styled.div`
display: inline-block;
position: relative;
`
const DisplayText = styled.div<{
$align: 'start' | 'center' | 'end'
$isEditing: boolean
}>`
position: absolute;
inset: 0;
display: ${({ $isEditing }) => ($isEditing ? 'none' : 'flex')};
align-items: center;
justify-content: ${({ $align }) => $align};
pointer-events: none;
`
export default EditableNumber