feat(components): 添加 Input 组件

- 实现了一个通用的 Input 组件,支持多种变体和自定义样式
- 组件属性包括 name、variant、left、right、value、defaultValue 等
- 支持 onInput 事件处理
- 优化了组件的默认值设置和响应式行为
This commit is contained in:
Vixalie
2025-08-22 10:11:51 +08:00
parent 75291cda8c
commit 4b394f8143

73
src/components/Input.tsx Normal file
View File

@@ -0,0 +1,73 @@
import cx from 'clsx';
import { isNotNil } from 'es-toolkit';
import { Component, createEffect, createSignal, JSX, mergeProps, onMount, Show } from 'solid-js';
interface InputProps extends JSX.HTMLAttributes<HTMLInputElement> {
name?: string;
variant?: 'normal' | 'underlined' | 'immersive';
left?: JSX.Element;
right?: JSX.Element;
value?: string;
defaultValue?: string;
placeholder?: string;
disabled?: boolean;
onInput?: (value: string | null) => void;
wrapperClass?: JSX.HTMLAttributes<HTMLDivElement>['class'];
inputClass?: JSX.HTMLAttributes<HTMLInputElement>['class'];
}
const Input: Component<InputProps> = (props) => {
const mProps = mergeProps<InputProps[]>(
{
variant: 'normal',
disabled: false,
placeholder: '',
},
props,
);
const [internalValue, setInternalValue] = createSignal<string>('');
onMount(() => {
if (isNotNil(mProps.defaultValue)) {
setInternalValue(mProps.defaultValue);
}
});
createEffect(() => {
if (isNotNil(mProps.value) && mProps.value !== internalValue()) {
setInternalValue(mProps.value);
}
});
const handleInput: JSX.EventHandler<HTMLInputElement, InputEvent> = (evt) => {
const value = evt.currentTarget.value;
setInternalValue(value);
mProps.onInput?.(value);
};
return (
<div
aria-disabled={mProps.disabled}
class={cx(
'flex flex-row items-center gap-2 min-h-[1em] px-3 py-1.5',
mProps.variant === 'normal' &&
'rounded-sm bg-neutral text-on-neutral aria-disabled:cursor-not-allowed aria-disabled:bg-neutral-disabled aria-disabled:text-on-neutral-disabled disabled:bg-neutral-disabled disabled:text-on-neutral-disabled',
mProps.variant === 'underlined' &&
'border-b border-solid pb-[calc(var(--spacing)*1.5-1px)] border-on-surface text-on-surface aria-disabled:border-neutral-disabled aria-disabled:text-neutral-disabled',
mProps.variant === 'immersive' && 'text-on-surface aria-disabled:text-neutral-disabled',
mProps.wrapperClass,
)}>
<Show when={isNotNil(mProps.left)}>{mProps.left}</Show>
<input
name={mProps.name}
type={mProps.type}
placeholder={mProps.placeholder}
disabled={mProps.disabled}
value={internalValue()}
class={cx('min-h-[1em] grow focus:outline-none placeholder:italic', mProps.inputClass)}
onInput={handleInput}
/>
<Show when={isNotNil(mProps.right)}>{mProps.right}</Show>
</div>
);
};
export default Input;