From 046ed3edef609afe3eed1550168ba24d427935ec Mon Sep 17 00:00:00 2001 From: MyPrototypeWhat Date: Mon, 15 Sep 2025 15:23:03 +0800 Subject: [PATCH] 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. --- packages/ui/MIGRATION_STATUS.md | 7 +- packages/ui/MIGRATION_STATUS_EN.md | 7 +- .../src/components/display/ListItem/index.tsx | 92 ++++++++++++++ packages/ui/src/components/index.ts | 3 + .../interactive/EditableNumber/index.tsx | 116 ++++++++++++++++++ 5 files changed, 219 insertions(+), 6 deletions(-) create mode 100644 packages/ui/src/components/display/ListItem/index.tsx create mode 100644 packages/ui/src/components/interactive/EditableNumber/index.tsx diff --git a/packages/ui/MIGRATION_STATUS.md b/packages/ui/MIGRATION_STATUS.md index 57eb5b5329..cf17765677 100644 --- a/packages/ui/MIGRATION_STATUS.md +++ b/packages/ui/MIGRATION_STATUS.md @@ -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 依赖) | diff --git a/packages/ui/MIGRATION_STATUS_EN.md b/packages/ui/MIGRATION_STATUS_EN.md index 60c041204c..ad07d752a8 100644 --- a/packages/ui/MIGRATION_STATUS_EN.md +++ b/packages/ui/MIGRATION_STATUS_EN.md @@ -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) | diff --git a/packages/ui/src/components/display/ListItem/index.tsx b/packages/ui/src/components/display/ListItem/index.tsx new file mode 100644 index 0000000000..344dfbadf0 --- /dev/null +++ b/packages/ui/src/components/display/ListItem/index.tsx @@ -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 ( + + + {icon && {icon}} + + + {title} + + {subtitle && {subtitle}} + + {rightContent && {rightContent}} + + + ) +} + +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 diff --git a/packages/ui/src/components/index.ts b/packages/ui/src/components/index.ts index 3e9133eea1..75b1b63587 100644 --- a/packages/ui/src/components/index.ts +++ b/packages/ui/src/components/index.ts @@ -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' diff --git a/packages/ui/src/components/interactive/EditableNumber/index.tsx b/packages/ui/src/components/interactive/EditableNumber/index.tsx new file mode 100644 index 0000000000..e335df8160 --- /dev/null +++ b/packages/ui/src/components/interactive/EditableNumber/index.tsx @@ -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 = ({ + 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(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 ( + + + + {value ?? placeholder} + + + ) +} + +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