Skip to content

Commit

Permalink
feat: complete input component
Browse files Browse the repository at this point in the history
  • Loading branch information
taoyage committed Jul 18, 2022
1 parent 25d1ff4 commit 36b5c60
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 17 deletions.
108 changes: 91 additions & 17 deletions packages/input/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
import React from 'react';
import cx from 'classnames';

import { CloseCircleFill } from 'antd-mobile-icons';

import './styles/index.scss';

export interface InputProps {
value: string;
id?: string;
value?: string;
placeholder?: string;
/** 是否显示清除icon */
clearable?: boolean;

autoFocus?: boolean;
disabled?: boolean;
readOnly?: boolean;
maxLength?: number;
minLength?: number;
max?: number;
min?: number;
pattern?: string;
name?: string;
autoComplete?: 'on' | 'off';
autoCapitalize?: 'on' | 'off';
autoCorrect?: 'on' | 'off';
inputMode?: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
type?: React.HTMLInputTypeAttribute;
onKeyDown?: React.KeyboardEventHandler<HTMLInputElement>;
onKeyUp?: React.KeyboardEventHandler<HTMLInputElement>;
onCompositionStart?: React.CompositionEventHandler<HTMLInputElement>;
onCompositionEnd?: React.CompositionEventHandler<HTMLInputElement>;
onClick?: React.MouseEventHandler<HTMLInputElement>;

onEnterPress?: (e: React.KeyboardEvent<HTMLInputElement>) => void;
onChange?: (val: string) => void;
onClear?: () => void;
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
Expand All @@ -14,36 +42,82 @@ export interface InputProps {
const classPrefix = `ygm-input`;

const Input: React.FC<InputProps> = React.memo((props) => {
const [value, setValue] = React.useState<string>(props.value);
const [value, setValue] = React.useState<string>(props.value!);

const onChange = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
props.onChange?.(e.target.value);
}, []);

const onFocus = React.useCallback((e: React.FocusEvent<HTMLInputElement>) => {
props?.onFocus(e);
}, []);
const handleKeydown = React.useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (props.onEnterPress && e.code === 'Enter') {
props.onEnterPress(e);
}
props.onKeyDown?.(e);
},
[props]
);

const onBlur = React.useCallback((e: React.FocusEvent<HTMLInputElement>) => {
props?.onBlur(e);
}, []);
const showClearable = React.useMemo(() => {
if (!props.clearable || !value || props.readOnly) return false;
return true;
}, [props.clearable, props.readOnly, value]);

return (
<div className={classPrefix}>
<div className={cx(classPrefix, { [`${classPrefix}-disabled`]: props.disabled })}>
<input
id={props.id}
className={`${classPrefix}-element`}
placeholder={props.placeholder}
autoFocus={props.autoFocus}
value={value}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
disabled={props.disabled}
readOnly={props.readOnly}
maxLength={props.maxLength}
minLength={props.minLength}
autoComplete={props.autoComplete}
inputMode={props.inputMode}
type={props.type}
name={props.name}
autoCapitalize={props.autoCapitalize}
autoCorrect={props.autoCorrect}
onKeyDown={handleKeydown}
onKeyUp={props.onKeyUp}
onCompositionStart={props.onCompositionStart}
onCompositionEnd={props.onCompositionEnd}
onClick={props.onClick}
onChange={(e) => {
setValue(e.target.value);
props.onChange?.(e.target.value);
}}
onFocus={(e) => {
props?.onFocus?.(e);
}}
onBlur={(e) => {
props?.onBlur?.(e);
}}
/>

{showClearable && (
<div
className={`${classPrefix}-clear`}
onMouseDown={(e) => {
e.preventDefault();
}}
onClick={() => {
setValue('');
props.onClear?.();
}}
>
<CloseCircleFill />
</div>
)}
</div>
);
});

Input.defaultProps = {
value: '',
id: 'ygm-input',
type: 'text',
};

Input.displayName = 'Input';

export default Input;
99 changes: 99 additions & 0 deletions packages/input/styles/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
$class-prefix-input: 'ygm-input';

.#{$class-prefix-input} {
--ygm-color-light: #cccccc;
--ygm-color-weak: #999999;
--color: #333333;
--placeholder-color: #cccccc;
--text-align: left;
--background-color: transparent;
--font-size: 14px;

display: flex;
justify-content: flex-start;
align-items: center;

width: 100%;
min-height: 24px;
background-color: var(--background-color);

&-disabled {
opacity: 0.4;
cursor: not-allowed;
}

&-element {
flex: auto;
display: inline-block;
box-sizing: border-box;
width: 100%;
max-width: 100%;
max-height: 100%;
padding: 0;
margin: 0;
color: var(--color);
font-size: var(--font-size);
line-height: 1.5;
background: transparent;
border: 0;
outline: none;
appearance: none;
text-align: var(--text-align);

&:-webkit-autofill {
background-color: transparent;
}

&:read-only {
cursor: default;
}

&:invalid {
box-shadow: none;
}

&::-ms-clear {
display: none;
}
&::-webkit-search-cancel-button {
display: none;
}
&::-webkit-search-decoration {
display: none;
}
&:disabled {
opacity: 1;
}

// for ios
&[type='date'],
&[type='time'],
&[type='datetime-local'] {
min-height: 1.5em;
}

// for safari
&[type='search'] {
-webkit-appearance: none;
}

&[readonly] {
pointer-events: none;
}
}

&-clear {
flex: none;
margin-left: 8px;
color: var(--ygm-color-light);
&:active {
color: var(--ygm-color-weak);
}
padding: 4px;
cursor: pointer;
.antd-mobile-icon {
display: block;
font-size: 15px;
}
}
}
38 changes: 38 additions & 0 deletions stories/input/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { Meta } from '@storybook/react';

import Input from '@/input';

import DemoWrap from '../../demos/demo-wrap';
import DemoBlock from '../../demos/demo-block';

const InputStory: Meta = {
title: '信息录入/Input 输入框',
component: Input,
};

export const Basic = () => {
return (
<DemoWrap>
<DemoBlock title="基础用法" style={{ padding: 12 }}>
<Input placeholder="请输入" type="search" />
</DemoBlock>

<DemoBlock title="显示清除" style={{ padding: 12 }}>
<Input placeholder="请输入" type="search" clearable />
</DemoBlock>

<DemoBlock title="密码" style={{ padding: 12 }}>
<Input placeholder="请输入密码" type="password" />
</DemoBlock>

<DemoBlock title="禁用" style={{ padding: 12 }}>
<Input placeholder="禁用" disabled value="禁用" />
</DemoBlock>
</DemoWrap>
);
};

Basic.storyName = '基本用法';

export default InputStory;

0 comments on commit 36b5c60

Please sign in to comment.