feat(components): 添加 Input 组件
- 实现了一个通用的 Input 组件,支持多种变体和自定义样式 - 组件属性包括 name、variant、left、right、value、defaultValue 等 - 支持 onInput 事件处理 - 优化了组件的默认值设置和响应式行为
This commit is contained in:
73
src/components/Input.tsx
Normal file
73
src/components/Input.tsx
Normal 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;
|
Reference in New Issue
Block a user