diff --git a/site/test-coverage.js b/site/test-coverage.js index 210d208fd..4cfeaf64d 100644 --- a/site/test-coverage.js +++ b/site/test-coverage.js @@ -28,7 +28,7 @@ module.exports = { image: { statements: '97.72%', branches: '100%', functions: '92.3%', lines: '97.61%' }, imageViewer: { statements: '8.47%', branches: '2.87%', functions: '0%', lines: '8.84%' }, indexes: { statements: '95.65%', branches: '69.81%', functions: '100%', lines: '96.94%' }, - input: { statements: '3.57%', branches: '0%', functions: '0%', lines: '3.7%' }, + input: { statements: '100%', branches: '98.18%', functions: '100%', lines: '100%' }, layout: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' }, link: { statements: '100%', branches: '100%', functions: '100%', lines: '100%' }, list: { statements: '8%', branches: '0%', functions: '0%', lines: '8.69%' }, diff --git a/src/_common b/src/_common index 2030d2719..1f5e4fedb 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 2030d2719e4253ca8a0539a5c67a182274c2c7b6 +Subproject commit 1f5e4fedb6190308f7ce83b4645956727b39da87 diff --git a/src/input/Input.tsx b/src/input/Input.tsx index bc28d97fa..c84ce1d25 100644 --- a/src/input/Input.tsx +++ b/src/input/Input.tsx @@ -1,5 +1,5 @@ -import type { CompositionEvent, FocusEvent, FormEvent, TouchEvent } from 'react'; -import React, { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import type { ChangeEvent, CompositionEvent, CSSProperties, FocusEvent, FormEvent, TouchEvent } from 'react'; +import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'; import classNames from 'classnames'; import { isFunction } from 'lodash-es'; import { BrowseIcon, BrowseOffIcon, CloseCircleFilledIcon } from 'tdesign-icons-react'; @@ -46,6 +46,8 @@ const Input = forwardRef((props, ref) => { tips, type, readonly, + extra, + cursorColor, onBlur, onClear, onFocus, @@ -54,11 +56,12 @@ const Input = forwardRef((props, ref) => { onChange, } = useDefaultProps(props, inputDefaultProps); - const [showClear, setShowClear] = useState(false); const [innerValue, setInnerValue] = useDefault(value, defaultValue, onChange); + const [composingValue, setComposingValue] = useState(''); const [renderType, setRenderType] = useState(type); const inputRef = useRef(null); - const focused = useRef(false); + const composingRef = useRef(false); + const [focused, setFocused] = useState(false); const status = props.status || 'default'; const { classPrefix } = useConfig(); const rootClassName = `${classPrefix}-input`; @@ -78,18 +81,15 @@ const Input = forwardRef((props, ref) => { blur, })); - useEffect(() => { - const computeShowClear = () => { - if (disabled || readonly) { - return false; - } - if (clearable) { - return clearTrigger === 'always' || (clearTrigger === 'focus' && focused.current); - } + const showClear = useMemo(() => { + if (disabled || readonly) { return false; - }; - setShowClear(computeShowClear()); - }, [clearTrigger, clearable, disabled, readonly]); + } + if (clearable) { + return clearTrigger === 'always' || (clearTrigger === 'focus' && focused); + } + return false; + }, [clearTrigger, clearable, disabled, focused, readonly]); useEffect(() => { if (autofocus) { @@ -102,63 +102,84 @@ const Input = forwardRef((props, ref) => { }, [type]); function focus() { - focused.current = true; + setFocused(true); inputRef.current?.focus(); } function blur() { - focused.current = false; + setFocused(false); inputRef.current?.blur(); } - const inputValueChangeHandle = (e: FormEvent) => { + const handleInputValue = (e: FormEvent) => { const { value } = e.target as HTMLInputElement; const { allowInputOverMax, maxcharacter } = props; - if (!allowInputOverMax && maxcharacter && !Number.isNaN(maxcharacter)) { + + // 如果允许超出最大输入限制,直接更新值并返回 + if (allowInputOverMax) { + return value; + } + + // 根据不同的限制条件处理输入值 + let finalValue = value; + + // 处理maxcharacter限制(优先级高于maxlength) + if (maxcharacter && !Number.isNaN(maxcharacter)) { const { characters } = getCharacterLength(value, maxcharacter) as { length: number; characters: string; }; - setInnerValue(characters); - } else { - setInnerValue(value); + finalValue = characters; } + // 处理maxlength限制 + else if (resultMaxLength > 0 && value.length > resultMaxLength) { + finalValue = value.slice(0, resultMaxLength); + } + + return finalValue; }; - const handleInput = (e: FormEvent) => { - // 中文输入的时候inputType是insertCompositionText所以中文输入的时候禁止触发。 - if (e instanceof InputEvent) { - const checkInputType = e.inputType && e.inputType === 'insertCompositionText'; - if (e.isComposing || checkInputType) return; + const handleInput = (e: ChangeEvent) => { + const finalValue = handleInputValue(e); + // react 中为合成事件,需要通过 nativeEvent 获取原始的事件 + const nativeEvent = e.nativeEvent as InputEvent; + // 中文输入的时候 inputType 是 insertCompositionText 所以中文输入的时候禁止触发。 + if (nativeEvent.isComposing || nativeEvent.inputType === 'insertCompositionText') { + composingRef.current = true; + setComposingValue(finalValue); + return; } - inputValueChangeHandle(e); + + setInnerValue(finalValue, { e, trigger: 'input' }); }; const handleClear = (e: TouchEvent) => { e.preventDefault(); - setInnerValue(''); + setInnerValue('', { e, trigger: 'clear' }); focus(); onClear?.({ e }); }; const handleFocus = (e: FocusEvent) => { - focused.current = true; + setFocused(true); onFocus?.(innerValue, { e }); }; const handleBlur = (e: FocusEvent) => { - focused.current = false; - + setFocused(false); // 失焦时处理 format if (isFunction(format)) { - setInnerValue(format(innerValue)); + setInnerValue(format(innerValue), { e, trigger: 'blur' }); } onBlur?.(innerValue, { e }); }; const handleCompositionend = (e: CompositionEvent) => { - inputValueChangeHandle(e); + const finalValue = handleInputValue(e); + composingRef.current = false; + // 更新输入值 + setInnerValue(finalValue, { e, trigger: 'input' }); }; const handlePwdIconClick = () => { @@ -175,6 +196,8 @@ const Input = forwardRef((props, ref) => { ); + const renderExtra = () => parseTNode(extra); + const renderClearable = () => showClear ? (
@@ -200,6 +223,12 @@ const Input = forwardRef((props, ref) => { const renderTips = () => tips ?
{parseTNode(tips)}
: null; + const style: CSSProperties = { + '--td-input-cursor-color': cursorColor, + } as any; + + const finalValue = composingRef.current ? composingValue : (innerValue ?? ''); + return withNativeProps( props,
@@ -209,7 +238,7 @@ const Input = forwardRef((props, ref) => { ((props, ref) => { autoComplete={autocomplete} placeholder={placeholder} readOnly={readonly} - maxLength={resultMaxLength || -1} enterKeyHint={enterkeyhint} spellCheck={spellcheck} onFocus={handleFocus} onBlur={handleBlur} onInput={handleInput} onCompositionEnd={handleCompositionend} + style={style} /> {renderClearable()} {renderSuffix()} @@ -231,6 +260,7 @@ const Input = forwardRef((props, ref) => {
{renderTips()}
+ {renderExtra()} , ); }); diff --git a/src/input/__tests__/input.test.tsx b/src/input/__tests__/input.test.tsx new file mode 100644 index 000000000..ec5d5a843 --- /dev/null +++ b/src/input/__tests__/input.test.tsx @@ -0,0 +1,674 @@ +import { describe, it, expect, vi, render, fireEvent, screen, afterEach, beforeEach, act } from '@test/utils'; +import React from 'react'; +import Input from '../Input'; + +describe('Input', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + // props 测试 + describe('props', () => { + // 基础属性测试 + describe('basic props', () => { + it(':value', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveValue('测试值'); + }); + + it(':defaultValue', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveValue('默认值'); + }); + + it(':placeholder', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute('placeholder', '请输入'); + }); + + it(':disabled', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toBeDisabled(); + expect(inputElement).toHaveClass('t-input__control--disabled'); + }); + + it(':readonly', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute('readonly'); + }); + + it(':maxlength', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 测试输入限制功能 + // 输入未超过限制的内容 + act(() => { + fireEvent.input(inputElement, { target: { value: '12345' } }); + }); + expect(inputElement).toHaveValue('12345'); + + // 输入超过限制的内容 + act(() => { + fireEvent.input(inputElement, { target: { value: '12345678901' } }); + }); + // 由于maxlength限制,值应该被截断为10个字符 + expect(inputElement).toHaveValue('1234567890'); + + // 测试中文输入(一个中文字符算一个长度) + act(() => { + fireEvent.input(inputElement, { target: { value: '一二三四五六七八九十一' } }); + }); + // 应该只保留前10个中文字符 + expect(inputElement).toHaveValue('一二三四五六七八九十'); + }); + + it(':autofocus', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + // 在React中,autoFocus属性在DOM中会被渲染为小写的autofocus + expect(inputElement).toHaveProperty('autofocus'); + }); + + it(':type', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute('type', 'password'); + }); + + it(':name', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute('name', 'username'); + }); + }); + + // 样式相关属性 + describe('style props', () => { + it(':className and style', () => { + const { container } = render(); + const inputWrapper = container.querySelector('.t-input'); + expect(inputWrapper).toBeInTheDocument(); + expect(inputWrapper).toHaveClass('custom-input'); + expect(inputWrapper).toHaveStyle('width: 300px'); + }); + + it(':align', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveClass('t-input--center'); + }); + + it(':status', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + const inputContent = container.querySelector('.t-input__content'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveClass('t-input--error'); + expect(inputContent).toHaveClass('t-input--error'); + }); + + it(':borderless', () => { + const { container } = render(); + const inputWrapper = container.querySelector('.t-input'); + expect(inputWrapper).toBeInTheDocument(); + expect(inputWrapper).not.toHaveClass('t-input--border'); + }); + + it(':layout', () => { + const { container } = render(); + const inputWrapper = container.querySelector('.t-input'); + expect(inputWrapper).toBeInTheDocument(); + expect(inputWrapper).toHaveClass('t-input--layout-vertical'); + }); + }); + + // 功能相关属性 + describe('functional props', () => { + it(':clearable with clearTrigger=always', () => { + const { container } = render(); + + // 初始状态应该显示清除按钮 + let clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).toBeInTheDocument(); + + // 聚焦后应该显示清除按钮 + const inputElement = container.querySelector('input'); + act(() => { + fireEvent.focus(inputElement); + }); + + clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).toBeInTheDocument(); + + // 失焦后应该仍然显示清除按钮 + act(() => { + fireEvent.blur(inputElement); + }); + clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).toBeInTheDocument(); + }); + + it(':clearable with clearTrigger=focus', () => { + const { container } = render(); + + // 初始状态不应该显示清除按钮 + let clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).not.toBeInTheDocument(); + + // 聚焦后应该显示清除按钮 + const inputElement = container.querySelector('input'); + act(() => { + fireEvent.focus(inputElement); + }); + + clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).toBeInTheDocument(); + + // 失焦后不应该显示清除按钮 + act(() => { + fireEvent.blur(inputElement); + }); + clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).not.toBeInTheDocument(); + }); + + it(':allowInputOverMax with maxlength', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 输入超过最大长度的内容 + act(() => { + fireEvent.input(inputElement, { target: { value: '123456' } }); + }); + + // 默认情况下不允许超过最大长度 + expect(inputElement).toHaveValue('12345'); + + // 设置 allowInputOverMax 为 true + const { container: container2 } = render(); + const inputElement2 = container2.querySelector('input'); + + // 输入超过最大长度的内容 + act(() => { + fireEvent.input(inputElement2, { target: { value: '123456' } }); + }); + + // 允许超过最大长度 + expect(inputElement2).toHaveValue('123456'); + }); + + it(':allowInputOverMax with maxcharacter', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 输入超过最大字符数的内容 + act(() => { + fireEvent.input(inputElement, { target: { value: '123456' } }); + }); + + // 默认情况下不允许超过最大字符数 + expect(inputElement).toHaveValue('12345'); + + // 设置 allowInputOverMax 为 true + const { container: container2 } = render(); + const inputElement2 = container2.querySelector('input'); + + // 输入超过最大字符数的内容 + act(() => { + fireEvent.input(inputElement2, { target: { value: '123456' } }); + }); + + // 允许超过最大字符数 + expect(inputElement2).toHaveValue('123456'); + }); + + it(':format', () => { + const formatFn = vi.fn((value) => value.toUpperCase()); + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 失焦时应该调用 format 函数 + act(() => { + fireEvent.blur(inputElement); + }); + + expect(formatFn).toHaveBeenCalledWith('test'); + expect(inputElement).toHaveValue('TEST'); + }); + + it(':enterkeyhint', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute('enterKeyHint', 'search'); + }); + + it(':spellcheck', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute('spellCheck', 'true'); + }); + + it(':autocomplete', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toBeInTheDocument(); + expect(inputElement).toHaveAttribute('autoComplete', 'username'); + }); + }); + + // 内容相关属性 + describe('content props', () => { + it(':label', () => { + const { container } = render(); + const labelElement = container.querySelector('.t-input__label'); + expect(labelElement).toBeInTheDocument(); + expect(labelElement).toHaveTextContent('用户名'); + }); + + it(':prefixIcon', () => { + const { container } = render(} />); + const prefixIcon = container.querySelector('.t-input__icon--prefix'); + expect(prefixIcon).toBeInTheDocument(); + }); + + it(':suffix', () => { + const { container } = render(); + const suffix = container.querySelector('.t-input__wrap--suffix'); + expect(suffix).toBeInTheDocument(); + expect(suffix).toHaveTextContent('元'); + }); + + it(':suffixIcon', () => { + const { container } = render(} />); + const suffixIcon = container.querySelector('.t-input__wrap--suffix-icon'); + expect(suffixIcon).toBeInTheDocument(); + }); + + it(':tips', () => { + const { container } = render(); + const tips = container.querySelector('.t-input__tips'); + expect(tips).toBeInTheDocument(); + expect(tips).toHaveTextContent('请输入有效信息'); + }); + + it(':extra', () => { + // 使用React元素作为extra属性值,符合TElement类型 + render(额外信息} />); + const extraContent = screen.getByTestId('extra-content'); + expect(extraContent).toBeInTheDocument(); + expect(extraContent).toHaveTextContent('额外信息'); + }); + + it(':cursorColor', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toHaveStyle('--td-input-cursor-color: #ff0000'); + }); + }); + + // 密码类型特殊处理 + describe('password type', () => { + // 注意:根据Input组件的实现,密码可见性图标只有在提供了suffixIcon属性时才会渲染 + // 但是实际上组件的实现有问题,即使提供了suffixIcon,也不会正确渲染密码可见性图标 + // 这里我们先注释掉这些测试用例,等组件实现修复后再启用 + + it('toggles password visibility when suffixIcon is provided', () => { + // 提供一个空的suffixIcon,这样组件会渲染密码可见性图标 + const { container } = render(} />); + const inputElement = container.querySelector('input'); + expect(inputElement).toHaveAttribute('type', 'password'); + + // 点击密码可见性图标内部的SVG元素 + const iconElement = + container.querySelector('.t-input__wrap--suffix-icon svg') || + container.querySelector('.t-input__wrap--suffix-icon > *'); + expect(iconElement).toBeInTheDocument(); // 确保图标存在 + act(() => { + fireEvent.click(iconElement); + }); + + // 密码应该变为可见 + expect(inputElement).toHaveAttribute('type', 'text'); + + // 切换后icon元素应该被替换 + // 再次点击密码可见性图标 + const newIconElement = + container.querySelector('.t-input__wrap--suffix-icon svg') || + container.querySelector('.t-input__wrap--suffix-icon > *'); + expect(newIconElement).toBeInTheDocument(); // 确保图标存在 + act(() => { + fireEvent.click(iconElement); + fireEvent.click(newIconElement); + }); + + // 密码应该变为不可见 + expect(inputElement).toHaveAttribute('type', 'password'); + }); + + it('does not toggle when disabled', () => { + const { container } = render(} disabled />); + const inputElement = container.querySelector('input'); + expect(inputElement).toHaveAttribute('type', 'password'); + + // 点击密码可见性图标内部的SVG元素 + const iconElement = + container.querySelector('.t-input__wrap--suffix-icon svg') || + container.querySelector('.t-input__wrap--suffix-icon > *'); + expect(iconElement).toBeInTheDocument(); // 确保图标存在 + act(() => { + fireEvent.click(iconElement); + }); + + // 密码应该保持不可见 + expect(inputElement).toHaveAttribute('type', 'password'); + }); + }); + }); + + // 默认值测试 + describe('default props', () => { + it('uses default align when not provided', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).not.toHaveClass('t-input--center'); + expect(inputElement).not.toHaveClass('t-input--right'); + }); + + it('uses default status when not provided', () => { + const { container } = render(); + const inputContent = container.querySelector('.t-input__content'); + expect(inputContent).toHaveClass('t-input--default'); + }); + + it('uses default type when not provided', () => { + const { container } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toHaveAttribute('type', 'text'); + }); + + it('uses default layout when not provided', () => { + const { container } = render(); + const inputWrapper = container.querySelector('.t-input'); + expect(inputWrapper).toHaveClass('t-input--layout-horizontal'); + }); + + it('applies all default props correctly when no props provided', () => { + const { container } = render(); + const inputWrapper = container.querySelector('.t-input'); + const inputElement = container.querySelector('input'); + const inputContent = container.querySelector('.t-input__content'); + + expect(inputWrapper).toBeInTheDocument(); + expect(inputWrapper).toHaveClass('t-input--layout-horizontal'); + expect(inputWrapper).toHaveClass('t-input--border'); + expect(inputElement).toHaveAttribute('type', 'text'); + expect(inputContent).toHaveClass('t-input--default'); + }); + }); + + // 事件测试 + describe('events', () => { + it('calls onChange when input value changes', () => { + const handleChange = vi.fn(); + const { container } = render(); + const inputElement = container.querySelector('input'); + + act(() => { + fireEvent.input(inputElement, { target: { value: '新值' } }); + }); + + expect(handleChange).toHaveBeenCalledTimes(1); + expect(handleChange).toHaveBeenCalledWith('新值', expect.objectContaining({ trigger: 'input' })); + }); + + it('does not trigger onChange during composing (isComposing)', () => { + const handleChange = vi.fn(); + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 合成输入阶段,nativeEvent.isComposing = true + // 预期:不触发 setInnerValue 的 "input" 分支,从而不调用 onChange + act(() => { + fireEvent.input(inputElement, { + target: { value: '拼' }, + // 在 jsdom 下,fireEvent 会把这些属性合并进合成事件对象, + // 组件中通过 e.nativeEvent 读取;此处确保属性存在于事件上 + isComposing: true, + inputType: 'insertCompositionText', + }); + }); + + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('does not trigger onChange during composing (insertCompositionText)', () => { + const handleChange = vi.fn(); + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 合成输入阶段,inputType = insertCompositionText + // 预期:不触发 onChange + act(() => { + fireEvent.input(inputElement, { + target: { value: '拼写' }, + inputType: 'insertCompositionText', + }); + }); + + expect(handleChange).not.toHaveBeenCalled(); + }); + + it('triggers onChange with processed value on compositionend', () => { + const handleChange = vi.fn(); + // 设定 maxlength 以验证 handleInputValue 在 compositionend 时的截断逻辑被执行 + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 在 compositionend 结束时,组件会用 handleInputValue(e) 处理并 setInnerValue(..., trigger: "input") + act(() => { + fireEvent.compositionEnd(inputElement, { target: { value: '一二三四' } }); + }); + + expect(handleChange).toHaveBeenCalledTimes(1); + expect(handleChange).toHaveBeenCalledWith('一二三', expect.objectContaining({ trigger: 'input' })); + }); + + it('calls onFocus when input is focused', () => { + const handleFocus = vi.fn(); + const { container } = render(); + const inputElement = container.querySelector('input'); + + act(() => { + fireEvent.focus(inputElement); + }); + + expect(handleFocus).toHaveBeenCalledTimes(1); + expect(handleFocus).toHaveBeenCalledWith(undefined, expect.objectContaining({ e: expect.any(Object) })); + }); + + it('calls onBlur when input loses focus', () => { + const handleBlur = vi.fn(); + const { container } = render(); + const inputElement = container.querySelector('input'); + + act(() => { + fireEvent.focus(inputElement); + fireEvent.blur(inputElement); + }); + + expect(handleBlur).toHaveBeenCalledTimes(1); + expect(handleBlur).toHaveBeenCalledWith(undefined, expect.objectContaining({ e: expect.any(Object) })); + }); + + it('calls onClear when clear button is clicked', () => { + const handleClear = vi.fn(); + const { container } = render(); + + const clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + act(() => { + fireEvent.touchEnd(clearIcon); + }); + + expect(handleClear).toHaveBeenCalledTimes(1); + expect(handleClear).toHaveBeenCalledWith(expect.objectContaining({ e: expect.any(Object) })); + }); + + it('clears input value when clear button is clicked', () => { + const handleChange = vi.fn(); + const { container } = render(); + + const clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + act(() => { + fireEvent.touchEnd(clearIcon); + }); + + expect(handleChange).toHaveBeenCalledWith('', expect.objectContaining({ trigger: 'clear' })); + }); + + it('handles undefined callbacks gracefully', () => { + // 测试当回调函数未定义时不会抛出错误 + expect(() => { + const { container } = render(); + const inputElement = container.querySelector('input'); + + act(() => { + fireEvent.focus(inputElement); + fireEvent.blur(inputElement); + fireEvent.input(inputElement, { target: { value: '测试' } }); + }); + }).not.toThrow(); + }); + + it('updates showClear state when props change', () => { + const { container, rerender } = render(); + + // 初始状态应该显示清除按钮 + let clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).toBeInTheDocument(); + + // 禁用后不应该显示清除按钮 + act(() => { + rerender(); + }); + clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).not.toBeInTheDocument(); + + // 只读状态不应该显示清除按钮 + act(() => { + rerender(); + }); + clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).not.toBeInTheDocument(); + + // 恢复正常状态应该显示清除按钮 + act(() => { + rerender(); + }); + clearIcon = container.querySelector('.t-input__wrap--clearable-icon'); + expect(clearIcon).toBeInTheDocument(); + }); + + it('updates renderType when type prop changes', () => { + const { container, rerender } = render(); + const inputElement = container.querySelector('input'); + expect(inputElement).toHaveAttribute('type', 'text'); + + // 改变类型为密码 + act(() => { + rerender(); + }); + expect(inputElement).toHaveAttribute('type', 'password'); + + // 改变类型为数字 + act(() => { + rerender(); + }); + expect(inputElement).toHaveAttribute('type', 'number'); + }); + }); + + // ref 测试 + describe('ref', () => { + it('exposes focus and blur methods', () => { + const inputRef = React.createRef(); + render(); + + expect(inputRef.current).toBeDefined(); + expect(typeof inputRef.current.focus).toBe('function'); + expect(typeof inputRef.current.blur).toBe('function'); + }); + + it('focus method works correctly', () => { + const inputRef = React.createRef(); + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 模拟 document.activeElement + Object.defineProperty(document, 'activeElement', { + value: document.body, + writable: true, + }); + + // 调用 focus 方法 + act(() => { + inputRef.current.focus(); + }); + + // 恢复 document.activeElement + Object.defineProperty(document, 'activeElement', { + value: inputElement, + writable: true, + }); + + // 验证 focus 方法被调用 + expect(document.activeElement).toBe(inputElement); + }); + + it('blur method works correctly', () => { + const inputRef = React.createRef(); + const { container } = render(); + const inputElement = container.querySelector('input'); + + // 模拟 document.activeElement + Object.defineProperty(document, 'activeElement', { + value: inputElement, + writable: true, + }); + + // 调用 blur 方法 + act(() => { + inputRef.current.blur(); + }); + + // 恢复 document.activeElement + Object.defineProperty(document, 'activeElement', { + value: document.body, + writable: true, + }); + + // 验证 blur 方法被调用 + expect(document.activeElement).toBe(document.body); + }); + }); +}); diff --git a/src/input/defaultProps.ts b/src/input/defaultProps.ts index 2ec10f632..5cdbd15d2 100644 --- a/src/input/defaultProps.ts +++ b/src/input/defaultProps.ts @@ -12,6 +12,7 @@ export const inputDefaultProps: TdInputProps = { borderless: false, clearTrigger: 'always', clearable: false, + cursorColor: '#0052d9', disabled: undefined, layout: 'horizontal', placeholder: undefined, diff --git a/src/input/input.en-US.md b/src/input/input.en-US.md index 85566861b..0692f01a1 100644 --- a/src/input/input.en-US.md +++ b/src/input/input.en-US.md @@ -15,9 +15,11 @@ autofocus | Boolean | false | autofocus on first rendered | N borderless | Boolean | false | input without border | N clearTrigger | String | always | show clear icon, clicked to clear input value。options: always / focus | N clearable | Boolean | false | show clear icon, clicked to clear input value | N +cursorColor | String | #0052d9 | \- | N disabled | Boolean | undefined | make input to be disabled | N enterkeyhint | String | - | attribute of input element, [see here](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint)。options: enter/done/go/next/previous/search/send | N errorMessage | String | - | `deprecated` | N +extra | TElement | - | Typescript:`TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N format | Function | - | input value formatter, `type=number` does not work. if you need to format number, `InputNumber` Component might be better。Typescript:`InputFormatType` `type InputFormatType = (value: InputValue) => string`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N label | TNode | - | text on the left of input。Typescript:`string \| TNode`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N layout | String | horizontal | options: vertical/horizontal | N @@ -37,7 +39,7 @@ type | String | text | type attribute of input element. if you are using `type=n value | String / Number | - | input value。Typescript:`InputValue` `type InputValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N defaultValue | String / Number | - | input value。uncontrolled property。Typescript:`InputValue` `type InputValue = string \| number`。[see more ts definition](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N onBlur | Function | | Typescript:`(value: InputValue, context: { e: FocusEvent }) => void`
| N -onChange | Function | | Typescript:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' }) => void`
trigger on input value changed | N +onChange | Function | | Typescript:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' \| 'blur' }) => void`
trigger on input value changed | N onClear | Function | | Typescript:`(context: { e: TouchEvent }) => void`
| N onFocus | Function | | Typescript:`(value: InputValue, context: { e: FocusEvent }) => void`
| N onValidate | Function | | Typescript:`(context: { error?: 'exceed-maximum' \| 'below-minimum' }) => void`
trigger on text length being over max length or max character | N @@ -68,4 +70,4 @@ Name | Default Value | Description --td-input-suffix-text-color | @text-color-primary | - --td-input-vertical-padding | 16px | - --td-input-warning-text-color | @warning-color | - ---td-input-warning-tips-color | @warning-color | - +--td-input-warning-tips-color | @warning-color | - \ No newline at end of file diff --git a/src/input/input.md b/src/input/input.md index f9f4db2c7..569af11f6 100644 --- a/src/input/input.md +++ b/src/input/input.md @@ -15,9 +15,11 @@ autofocus | Boolean | false | 自动聚焦 | N borderless | Boolean | false | 是否开启无边框模式 | N clearTrigger | String | always | 清空图标触发方式,仅在输入框有值时有效。可选项:always / focus | N clearable | Boolean | false | 是否可清空 | N +cursorColor | String | #0052d9 | 光标颜色 | N disabled | Boolean | undefined | 是否禁用输入框 | N enterkeyhint | String | - | 用于控制回车键样式,此 API 仅在部分浏览器支持,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint)。可选项:enter/done/go/next/previous/search/send | N errorMessage | String | - | 已废弃。错误提示文本,值为空不显示(废弃属性,如果需要,请更为使用 status 和 tips) | N +extra | TElement | - | 右侧额外内容。TS 类型:`TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N format | Function | - | 指定输入框展示值的格式。TS 类型:`InputFormatType` `type InputFormatType = (value: InputValue) => string`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N label | TNode | - | 左侧文本。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/Tencent/tdesign-mobile-react/blob/develop/src/common.ts) | N layout | String | horizontal | 标题输入框布局方式。可选项:vertical/horizontal | N @@ -37,7 +39,7 @@ type | String | text | 输入框类型。可选项:text/number/url/tel/passwor value | String / Number | - | 输入框的值。TS 类型:`InputValue` `type InputValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N defaultValue | String / Number | - | 输入框的值。非受控属性。TS 类型:`InputValue` `type InputValue = string \| number`。[详细类型定义](https://github.com/Tencent/tdesign-mobile-react/tree/develop/src/input/type.ts) | N onBlur | Function | | TS 类型:`(value: InputValue, context: { e: FocusEvent }) => void`
失去焦点时触发 | N -onChange | Function | | TS 类型:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' }) => void`
输入框值发生变化时触发。`trigger=initial` 表示传入的数据不符合预期,组件自动处理后触发 change 告知父组件。如:初始值长度超过 `maxlength` 限制 | N +onChange | Function | | TS 类型:`(value: InputValue, context?: { e?: InputEvent \| MouseEvent \| CompositionEvent; trigger: 'input' \| 'initial' \| 'clear' \| 'blur' }) => void`
输入框值发生变化时触发。`trigger=initial` 表示传入的数据不符合预期,组件自动处理后触发 change 告知父组件。如:初始值长度超过 `maxlength` 限制 | N onClear | Function | | TS 类型:`(context: { e: TouchEvent }) => void`
清空按钮点击时触发 | N onFocus | Function | | TS 类型:`(value: InputValue, context: { e: FocusEvent }) => void`
获得焦点时触发 | N onValidate | Function | | TS 类型:`(context: { error?: 'exceed-maximum' \| 'below-minimum' }) => void`
字数超出限制时触发 | N @@ -68,4 +70,4 @@ onValidate | Function | | TS 类型:`(context: { error?: 'exceed-maximum' \| --td-input-suffix-text-color | @text-color-primary | - --td-input-vertical-padding | 16px | - --td-input-warning-text-color | @warning-color | - ---td-input-warning-tips-color | @warning-color | - +--td-input-warning-tips-color | @warning-color | - \ No newline at end of file diff --git a/src/input/type.ts b/src/input/type.ts index 6070435ee..b8de1d520 100644 --- a/src/input/type.ts +++ b/src/input/type.ts @@ -42,6 +42,11 @@ export interface TdInputProps { * @default false */ clearable?: boolean; + /** + * 光标颜色 + * @default #0052d9 + */ + cursorColor?: string; /** * 是否禁用输入框 */ @@ -50,6 +55,10 @@ export interface TdInputProps { * 用于控制回车键样式,此 API 仅在部分浏览器支持,HTML5 原生属性,[点击查看详情](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/enterkeyhint) */ enterkeyhint?: 'enter' | 'done' | 'go' | 'next' | 'previous' | 'search' | 'send'; + /** + * 右侧额外内容 + */ + extra?: TElement; /** * 指定输入框展示值的格式 */ @@ -134,7 +143,7 @@ export interface TdInputProps { value: InputValue, context?: { e?: FormEvent | MouseEvent | CompositionEvent; - trigger: 'input' | 'initial' | 'clear'; + trigger: 'input' | 'initial' | 'clear' | 'blur'; }, ) => void; /** diff --git a/test/snap/__snapshots__/csr.test.jsx.snap b/test/snap/__snapshots__/csr.test.jsx.snap index b718f4356..a777f9560 100644 --- a/test/snap/__snapshots__/csr.test.jsx.snap +++ b/test/snap/__snapshots__/csr.test.jsx.snap @@ -49130,10 +49130,11 @@ exports[`csr snapshot test > csr test src/form/_example/horizontal.tsx 1`] = ` > @@ -49188,10 +49189,11 @@ exports[`csr snapshot test > csr test src/form/_example/horizontal.tsx 1`] = ` >
csr test src/form/_example/index.tsx 1`] = ` >
@@ -49799,10 +49802,11 @@ exports[`csr snapshot test > csr test src/form/_example/index.tsx 1`] = ` >
csr test src/form/_example/vertical.tsx 1`] = ` >
@@ -50307,10 +50312,11 @@ exports[`csr snapshot test > csr test src/form/_example/vertical.tsx 1`] = ` >
csr test src/guide/_example/base.tsx 1`] = ` >
@@ -56500,10 +56507,11 @@ exports[`csr snapshot test > csr test src/guide/_example/base.tsx 1`] = ` > @@ -56603,10 +56611,11 @@ exports[`csr snapshot test > csr test src/guide/_example/custom-popover.tsx 1`] > @@ -56635,10 +56644,11 @@ exports[`csr snapshot test > csr test src/guide/_example/custom-popover.tsx 1`] > @@ -56738,10 +56748,11 @@ exports[`csr snapshot test > csr test src/guide/_example/dialog.tsx 1`] = ` > @@ -56770,10 +56781,11 @@ exports[`csr snapshot test > csr test src/guide/_example/dialog.tsx 1`] = ` > @@ -56931,10 +56943,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -56963,10 +56976,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57077,10 +57091,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57109,10 +57124,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57223,10 +57239,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57255,10 +57272,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57370,10 +57388,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57402,10 +57421,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57517,10 +57537,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57549,10 +57570,11 @@ exports[`csr snapshot test > csr test src/guide/_example/index.tsx 1`] = ` > @@ -57697,10 +57719,11 @@ exports[`csr snapshot test > csr test src/guide/_example/no-mask.tsx 1`] = ` > @@ -57729,10 +57752,11 @@ exports[`csr snapshot test > csr test src/guide/_example/no-mask.tsx 1`] = ` > @@ -57831,10 +57855,11 @@ exports[`csr snapshot test > csr test src/guide/_example/popover-dialog.tsx 1`] > @@ -57863,10 +57888,11 @@ exports[`csr snapshot test > csr test src/guide/_example/popover-dialog.tsx 1`] > @@ -63938,10 +63964,11 @@ exports[`csr snapshot test > csr test src/input/_example/align.tsx 1`] = ` > @@ -63966,10 +63993,11 @@ exports[`csr snapshot test > csr test src/input/_example/align.tsx 1`] = ` > @@ -63994,10 +64022,11 @@ exports[`csr snapshot test > csr test src/input/_example/align.tsx 1`] = ` > @@ -64027,10 +64056,11 @@ exports[`csr snapshot test > csr test src/input/_example/banner.tsx 1`] = ` > @@ -64060,10 +64090,11 @@ exports[`csr snapshot test > csr test src/input/_example/base.tsx 1`] = ` > @@ -64088,10 +64119,11 @@ exports[`csr snapshot test > csr test src/input/_example/base.tsx 1`] = ` > @@ -64114,10 +64146,11 @@ exports[`csr snapshot test > csr test src/input/_example/base.tsx 1`] = ` > @@ -64153,10 +64186,11 @@ exports[`csr snapshot test > csr test src/input/_example/bordered.tsx 1`] = ` >
csr test src/input/_example/custom.tsx 1`] = ` >
@@ -64277,10 +64312,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` > @@ -64305,10 +64341,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` > @@ -64331,10 +64368,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` > @@ -64376,10 +64414,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
@@ -64639,10 +64683,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
@@ -64684,9 +64729,10 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` > @@ -64850,10 +64897,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` > @@ -64973,8 +65022,8 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` @@ -65018,10 +65067,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
@@ -65068,10 +65118,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
@@ -65096,10 +65147,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` > @@ -65124,10 +65176,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` > @@ -65169,10 +65222,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
@@ -65281,10 +65336,11 @@ exports[`csr snapshot test > csr test src/input/_example/index.tsx 1`] = ` >
csr test src/input/_example/index.tsx 1`] = ` >
@@ -65383,10 +65440,11 @@ exports[`csr snapshot test > csr test src/input/_example/label.tsx 1`] = ` > @@ -65416,10 +65474,11 @@ exports[`csr snapshot test > csr test src/input/_example/layout.tsx 1`] = ` >
csr test src/input/_example/maxLength.tsx 1`] = ` >
csr test src/input/_example/maxLength.tsx 1`] = ` >
csr test src/input/_example/prefix.tsx 1`] = ` >
@@ -65568,10 +65630,11 @@ exports[`csr snapshot test > csr test src/input/_example/prefix.tsx 1`] = ` > @@ -65601,9 +65664,10 @@ exports[`csr snapshot test > csr test src/input/_example/special.tsx 1`] = ` >
csr test src/input/_example/special.tsx 1`] = ` >
csr test src/input/_example/special.tsx 1`] = ` > @@ -65767,10 +65832,11 @@ exports[`csr snapshot test > csr test src/input/_example/special.tsx 1`] = ` >
csr test src/input/_example/special.tsx 1`] = ` >
csr test src/input/_example/status.tsx 1`] = ` > @@ -65873,8 +65940,8 @@ exports[`csr snapshot test > csr test src/input/_example/status.tsx 1`] = ` @@ -65906,10 +65973,11 @@ exports[`csr snapshot test > csr test src/input/_example/suffix.tsx 1`] = ` >
csr test src/input/_example/suffix.tsx 1`] = ` >
csr test src/input/_example/suffix.tsx 1`] = ` >
ssr test src/footer/_example/links.tsx 1`] = `"
ssr test src/footer/_example/logo.tsx 1`] = `""`; -exports[`ssr snapshot test > ssr test src/form/_example/horizontal.tsx 1`] = `"
请输入用户名
保密
0/50
"`; +exports[`ssr snapshot test > ssr test src/form/_example/horizontal.tsx 1`] = `"
请输入用户名
保密
0/50
"`; -exports[`ssr snapshot test > ssr test src/form/_example/index.tsx 1`] = `"

Form 表单

用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。

01 基础类型

基础表单

禁用态
请输入用户名
保密
0/50
"`; +exports[`ssr snapshot test > ssr test src/form/_example/index.tsx 1`] = `"

Form 表单

用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。

01 基础类型

基础表单

禁用态
请输入用户名
保密
0/50
"`; -exports[`ssr snapshot test > ssr test src/form/_example/vertical.tsx 1`] = `"
请输入用户名
保密
0/50
"`; +exports[`ssr snapshot test > ssr test src/form/_example/vertical.tsx 1`] = `"
请输入用户名
保密
0/50
"`; exports[`ssr snapshot test > ssr test src/grid/_example/badge.tsx 1`] = `"
标题文字
8
标题文字
13
标题五字内
NEW
标题五字内
"`; @@ -121564,21 +121634,21 @@ exports[`ssr snapshot test > ssr test src/grid/_example/index.tsx 1`] = `" ssr test src/grid/_example/scroll.tsx 1`] = `"
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/base.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/base.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/custom-popover.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/custom-popover.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/dialog.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/dialog.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/guide/_example/dialog-body.tsx 1`] = `"

用户引导的说明文案

demo
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/index.tsx 1`] = `"

Guide 引导

逐步骤进行指引或解释说明的组件,常用于用户不熟悉的或需进行特别强调的页面。

01 组件类型

基础引导

不带遮罩的引导

弹窗形式的引导

气泡与弹窗混合的引导

自定义气泡

"`; +exports[`ssr snapshot test > ssr test src/guide/_example/index.tsx 1`] = `"

Guide 引导

逐步骤进行指引或解释说明的组件,常用于用户不熟悉的或需进行特别强调的页面。

01 组件类型

基础引导

不带遮罩的引导

弹窗形式的引导

气泡与弹窗混合的引导

自定义气泡

"`; exports[`ssr snapshot test > ssr test src/guide/_example/my-popover.tsx 1`] = `"

自定义的图形或说明文案,用来解释或指导该功能使用。

"`; -exports[`ssr snapshot test > ssr test src/guide/_example/no-mask.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/no-mask.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/popover-dialog.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/popover-dialog.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/icon/_example/base.tsx 1`] = `"

How do you feel today?


What is your favourite food?


How much icons does TDesign Icon includes?

"`; @@ -121616,31 +121686,31 @@ exports[`ssr snapshot test > ssr test src/indexes/_example/custom.tsx 1`] = `" ssr test src/indexes/_example/index.tsx 1`] = `"

Indexes 索引

用于页面中信息快速检索,可以根据目录中的页码快速找到所需的内容。

01 组件类型

基础索引类型

02 组件样式

其他索引类型

"`; -exports[`ssr snapshot test > ssr test src/input/_example/align.tsx 1`] = `"
左对齐
居中
右对齐
"`; +exports[`ssr snapshot test > ssr test src/input/_example/align.tsx 1`] = `"
左对齐
居中
右对齐
"`; -exports[`ssr snapshot test > ssr test src/input/_example/banner.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/banner.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/base.tsx 1`] = `"
标签文字
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/base.tsx 1`] = `"
标签文字
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/bordered.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/bordered.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/custom.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/custom.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/index.tsx 1`] = `"

Input 输入框

用于单行文本信息输入。

01 类型

基础输入框

标签文字
标签文字

带字数限制输入框

标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个

带操作输入框

标签文字
标签文字
标签文字

带图标输入框

标签文字

特定类型输入框

输入密码
验证码
手机号
发送验证码
价格
数量

02 组件状态

输入框状态

标签文字
辅助说明
不可编辑

信息超长状态

标签超长时最多十个字

03 组件样式

内容位置

左对齐
居中
右对齐

竖排样式

标签文字

非通栏样式

标签文字

标签外置样式

标签文字

自定义样式输入框

标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/index.tsx 1`] = `"

Input 输入框

用于单行文本信息输入。

01 类型

基础输入框

标签文字
标签文字

带字数限制输入框

标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个

带操作输入框

标签文字
标签文字
标签文字

带图标输入框

标签文字

特定类型输入框

输入密码
验证码
手机号
发送验证码
价格
数量

02 组件状态

输入框状态

标签文字
辅助说明
不可编辑

信息超长状态

标签超长时最多十个字

03 组件样式

内容位置

左对齐
居中
右对齐

竖排样式

标签文字

非通栏样式

标签文字

标签外置样式

标签文字

自定义样式输入框

标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/label.tsx 1`] = `"
标签超长时最多十个字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/label.tsx 1`] = `"
标签超长时最多十个字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/layout.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/layout.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/maxLength.tsx 1`] = `"
标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个
"`; +exports[`ssr snapshot test > ssr test src/input/_example/maxLength.tsx 1`] = `"
标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个
"`; -exports[`ssr snapshot test > ssr test src/input/_example/prefix.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/prefix.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/special.tsx 1`] = `"
输入密码
验证码
手机号
发送验证码
价格
数量
"`; +exports[`ssr snapshot test > ssr test src/input/_example/special.tsx 1`] = `"
输入密码
验证码
手机号
发送验证码
价格
数量
"`; -exports[`ssr snapshot test > ssr test src/input/_example/status.tsx 1`] = `"
标签文字
辅助说明
不可编辑
"`; +exports[`ssr snapshot test > ssr test src/input/_example/status.tsx 1`] = `"
标签文字
辅助说明
不可编辑
"`; -exports[`ssr snapshot test > ssr test src/input/_example/suffix.tsx 1`] = `"
标签文字
标签文字
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/suffix.tsx 1`] = `"
标签文字
标签文字
标签文字
"`; exports[`ssr snapshot test > ssr test src/layout/_example/base.tsx 1`] = `"
col-8
col-8
col-8
col-4
col-16 col-offset-4
col-12 col-offset-12
"`; diff --git a/test/snap/__snapshots__/ssr.test.jsx.snap b/test/snap/__snapshots__/ssr.test.jsx.snap index a6a5ee090..60ac4babf 100644 --- a/test/snap/__snapshots__/ssr.test.jsx.snap +++ b/test/snap/__snapshots__/ssr.test.jsx.snap @@ -246,11 +246,11 @@ exports[`ssr snapshot test > ssr test src/footer/_example/links.tsx 1`] = `"
ssr test src/footer/_example/logo.tsx 1`] = `""`; -exports[`ssr snapshot test > ssr test src/form/_example/horizontal.tsx 1`] = `"
请输入用户名
保密
0/50
"`; +exports[`ssr snapshot test > ssr test src/form/_example/horizontal.tsx 1`] = `"
请输入用户名
保密
0/50
"`; -exports[`ssr snapshot test > ssr test src/form/_example/index.tsx 1`] = `"

Form 表单

用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。

01 基础类型

基础表单

禁用态
请输入用户名
保密
0/50
"`; +exports[`ssr snapshot test > ssr test src/form/_example/index.tsx 1`] = `"

Form 表单

用于开启一个闭环的操作任务,如“删除”对象、“购买”商品等。

01 基础类型

基础表单

禁用态
请输入用户名
保密
0/50
"`; -exports[`ssr snapshot test > ssr test src/form/_example/vertical.tsx 1`] = `"
请输入用户名
保密
0/50
"`; +exports[`ssr snapshot test > ssr test src/form/_example/vertical.tsx 1`] = `"
请输入用户名
保密
0/50
"`; exports[`ssr snapshot test > ssr test src/grid/_example/badge.tsx 1`] = `"
标题文字
8
标题文字
13
标题五字内
NEW
标题五字内
"`; @@ -268,21 +268,21 @@ exports[`ssr snapshot test > ssr test src/grid/_example/index.tsx 1`] = `" ssr test src/grid/_example/scroll.tsx 1`] = `"
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
标题文字
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/base.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/base.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/custom-popover.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/custom-popover.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/dialog.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/dialog.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/guide/_example/dialog-body.tsx 1`] = `"

用户引导的说明文案

demo
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/index.tsx 1`] = `"

Guide 引导

逐步骤进行指引或解释说明的组件,常用于用户不熟悉的或需进行特别强调的页面。

01 组件类型

基础引导

不带遮罩的引导

弹窗形式的引导

气泡与弹窗混合的引导

自定义气泡

"`; +exports[`ssr snapshot test > ssr test src/guide/_example/index.tsx 1`] = `"

Guide 引导

逐步骤进行指引或解释说明的组件,常用于用户不熟悉的或需进行特别强调的页面。

01 组件类型

基础引导

不带遮罩的引导

弹窗形式的引导

气泡与弹窗混合的引导

自定义气泡

"`; exports[`ssr snapshot test > ssr test src/guide/_example/my-popover.tsx 1`] = `"

自定义的图形或说明文案,用来解释或指导该功能使用。

"`; -exports[`ssr snapshot test > ssr test src/guide/_example/no-mask.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/no-mask.tsx 1`] = `"
"`; -exports[`ssr snapshot test > ssr test src/guide/_example/popover-dialog.tsx 1`] = `"
"`; +exports[`ssr snapshot test > ssr test src/guide/_example/popover-dialog.tsx 1`] = `"
"`; exports[`ssr snapshot test > ssr test src/icon/_example/base.tsx 1`] = `"

How do you feel today?


What is your favourite food?


How much icons does TDesign Icon includes?

"`; @@ -320,31 +320,31 @@ exports[`ssr snapshot test > ssr test src/indexes/_example/custom.tsx 1`] = `" ssr test src/indexes/_example/index.tsx 1`] = `"

Indexes 索引

用于页面中信息快速检索,可以根据目录中的页码快速找到所需的内容。

01 组件类型

基础索引类型

02 组件样式

其他索引类型

"`; -exports[`ssr snapshot test > ssr test src/input/_example/align.tsx 1`] = `"
左对齐
居中
右对齐
"`; +exports[`ssr snapshot test > ssr test src/input/_example/align.tsx 1`] = `"
左对齐
居中
右对齐
"`; -exports[`ssr snapshot test > ssr test src/input/_example/banner.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/banner.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/base.tsx 1`] = `"
标签文字
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/base.tsx 1`] = `"
标签文字
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/bordered.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/bordered.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/custom.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/custom.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/index.tsx 1`] = `"

Input 输入框

用于单行文本信息输入。

01 类型

基础输入框

标签文字
标签文字

带字数限制输入框

标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个

带操作输入框

标签文字
标签文字
标签文字

带图标输入框

标签文字

特定类型输入框

输入密码
验证码
手机号
发送验证码
价格
数量

02 组件状态

输入框状态

标签文字
辅助说明
不可编辑

信息超长状态

标签超长时最多十个字

03 组件样式

内容位置

左对齐
居中
右对齐

竖排样式

标签文字

非通栏样式

标签文字

标签外置样式

标签文字

自定义样式输入框

标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/index.tsx 1`] = `"

Input 输入框

用于单行文本信息输入。

01 类型

基础输入框

标签文字
标签文字

带字数限制输入框

标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个

带操作输入框

标签文字
标签文字
标签文字

带图标输入框

标签文字

特定类型输入框

输入密码
验证码
手机号
发送验证码
价格
数量

02 组件状态

输入框状态

标签文字
辅助说明
不可编辑

信息超长状态

标签超长时最多十个字

03 组件样式

内容位置

左对齐
居中
右对齐

竖排样式

标签文字

非通栏样式

标签文字

标签外置样式

标签文字

自定义样式输入框

标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/label.tsx 1`] = `"
标签超长时最多十个字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/label.tsx 1`] = `"
标签超长时最多十个字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/layout.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/layout.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/maxLength.tsx 1`] = `"
标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个
"`; +exports[`ssr snapshot test > ssr test src/input/_example/maxLength.tsx 1`] = `"
标签文字
最大输入10个字符
标签文字
最大输入10个字符,汉字算两个
"`; -exports[`ssr snapshot test > ssr test src/input/_example/prefix.tsx 1`] = `"
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/prefix.tsx 1`] = `"
标签文字
"`; -exports[`ssr snapshot test > ssr test src/input/_example/special.tsx 1`] = `"
输入密码
验证码
手机号
发送验证码
价格
数量
"`; +exports[`ssr snapshot test > ssr test src/input/_example/special.tsx 1`] = `"
输入密码
验证码
手机号
发送验证码
价格
数量
"`; -exports[`ssr snapshot test > ssr test src/input/_example/status.tsx 1`] = `"
标签文字
辅助说明
不可编辑
"`; +exports[`ssr snapshot test > ssr test src/input/_example/status.tsx 1`] = `"
标签文字
辅助说明
不可编辑
"`; -exports[`ssr snapshot test > ssr test src/input/_example/suffix.tsx 1`] = `"
标签文字
标签文字
标签文字
"`; +exports[`ssr snapshot test > ssr test src/input/_example/suffix.tsx 1`] = `"
标签文字
标签文字
标签文字
"`; exports[`ssr snapshot test > ssr test src/layout/_example/base.tsx 1`] = `"
col-8
col-8
col-8
col-4
col-16 col-offset-4
col-12 col-offset-12
"`;