mirror of
https://github.com/zhigang1992/react.git
synced 2026-01-31 09:08:41 +08:00
fix(input): hide clear icon when value is undefined
This commit is contained in:
22
components/auto-complete/auto-complete-context.ts
Normal file
22
components/auto-complete/auto-complete-context.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import React, { MutableRefObject } from 'react'
|
||||
import { NormalSizes } from '../utils/prop-types'
|
||||
|
||||
export interface SelectConfig {
|
||||
value?: string
|
||||
updateValue?: Function
|
||||
visible?: boolean
|
||||
updateVisible?: Function
|
||||
size?: NormalSizes
|
||||
disableAll?: boolean
|
||||
ref?: MutableRefObject<HTMLElement | null>
|
||||
}
|
||||
|
||||
const defaultContext = {
|
||||
visible: false,
|
||||
size: 'medium' as NormalSizes,
|
||||
disableAll: false,
|
||||
}
|
||||
|
||||
export const SelectContext = React.createContext<SelectConfig>(defaultContext)
|
||||
|
||||
export const useSelectContext = (): SelectConfig => React.useContext<SelectConfig>(SelectContext)
|
||||
11
components/auto-complete/auto-complete-dropdown.tsx
Normal file
11
components/auto-complete/auto-complete-dropdown.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const AutoComplete = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AutoComplete
|
||||
45
components/auto-complete/auto-complete-empty.tsx
Normal file
45
components/auto-complete/auto-complete-empty.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import useTheme from '../styles/use-theme'
|
||||
|
||||
interface Props {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
className: '',
|
||||
}
|
||||
|
||||
export type AutoCompleteSearchProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const AutoCompleteSearch: React.FC<React.PropsWithChildren<AutoCompleteSearchProps>> = ({
|
||||
children, className,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
font-weight: normal;
|
||||
white-space: pre;
|
||||
font-size: .875rem;
|
||||
padding: ${theme.layout.gapHalf};
|
||||
line-height: 1;
|
||||
background-color: ${theme.palette.background};
|
||||
color: ${theme.palette.accents_5};
|
||||
user-select: none;
|
||||
border: 0;
|
||||
border-radius: ${theme.layout.radius};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(AutoCompleteSearch, defaultProps)
|
||||
11
components/auto-complete/auto-complete-item.tsx
Normal file
11
components/auto-complete/auto-complete-item.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const AutoComplete = () => {
|
||||
return (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AutoComplete
|
||||
72
components/auto-complete/auto-complete-searching.tsx
Normal file
72
components/auto-complete/auto-complete-searching.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import { useAutoCompleteContext } from './auto-complete-context'
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
}
|
||||
|
||||
export type AutoCompleteItemProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const AutoCompleteItem: React.FC<React.PropsWithChildren<AutoCompleteItemProps>> = ({
|
||||
value: identValue, children, disabled,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { value, updateValue } = useAutoCompleteContext()
|
||||
const selectHandler = () => {
|
||||
updateValue && updateValue(identValue)
|
||||
}
|
||||
|
||||
const isActive = useMemo(() => value === identValue, [identValue, value])
|
||||
|
||||
return (
|
||||
<div className={`item ${isActive ? 'active' : ''}`} onClick={selectHandler}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.item {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
font-weight: normal;
|
||||
white-space: pre;
|
||||
font-size: .875rem;
|
||||
padding: ${theme.layout.gapHalf};
|
||||
line-height: 1;
|
||||
background-color: ${theme.palette.background};
|
||||
color: ${theme.palette.foreground};
|
||||
user-select: none;
|
||||
border: 0;
|
||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
||||
transition: background 0.2s ease 0s, border-color 0.2s ease 0s;
|
||||
}
|
||||
|
||||
.item:first-of-type {
|
||||
border-top-left-radius: ${theme.layout.radius};
|
||||
border-top-right-radius: ${theme.layout.radius};
|
||||
}
|
||||
|
||||
.item:last-of-type {
|
||||
border-bottom-left-radius: ${theme.layout.radius};
|
||||
border-bottom-right-radius: ${theme.layout.radius};
|
||||
}
|
||||
|
||||
.item:hover {
|
||||
background-color: ${theme.palette.accents_1};
|
||||
}
|
||||
|
||||
.item.active {
|
||||
background-color: ${theme.palette.accents_1};
|
||||
color: ${theme.palette.success};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(AutoCompleteItem, defaultProps)
|
||||
0
components/auto-complete/auto-complete.tsx
Normal file
0
components/auto-complete/auto-complete.tsx
Normal file
0
components/auto-complete/index.ts
Normal file
0
components/auto-complete/index.ts
Normal file
@@ -2,13 +2,14 @@ import React, { useMemo } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
|
||||
interface Props {
|
||||
visibale: boolean
|
||||
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void
|
||||
heightRatio?: string | undefined
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const InputIconClear: React.FC<Props> = ({
|
||||
onClick, heightRatio, disabled,
|
||||
onClick, heightRatio, disabled, visibale,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const width = useMemo(() => {
|
||||
@@ -21,7 +22,7 @@ const InputIconClear: React.FC<Props> = ({
|
||||
onClick && onClick(event)
|
||||
}
|
||||
return (
|
||||
<div onClick={clickHandler}>
|
||||
<div onClick={clickHandler} className={`${visibale ? 'visibale' : ''}`}>
|
||||
<svg viewBox="0 0 24 24" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"
|
||||
strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision">
|
||||
<path d="M18 6L6 18" />
|
||||
@@ -39,6 +40,13 @@ const InputIconClear: React.FC<Props> = ({
|
||||
box-sizing: border-box;
|
||||
transition: color 150ms ease 0s;
|
||||
color: ${theme.palette.accents_3};
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.visibale {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
div:hover {
|
||||
|
||||
@@ -32,6 +32,7 @@ const InputIcon: React.FC<InputIconProps> = React.memo(({
|
||||
margin: 0;
|
||||
padding: 0 ${padding};
|
||||
line-height: 1;
|
||||
position: relative;
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
|
||||
@@ -24,6 +24,9 @@ interface Props {
|
||||
clearable?: boolean
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void
|
||||
onClearClick?: (e: React.MouseEvent<HTMLDivElement>) => void
|
||||
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void
|
||||
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void
|
||||
autoComplete: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
@@ -33,6 +36,7 @@ const defaultProps = {
|
||||
width: 'initial',
|
||||
size: 'medium',
|
||||
status: 'default',
|
||||
autoComplete: 'off',
|
||||
className: '',
|
||||
placeholder: '',
|
||||
initialValue: '',
|
||||
@@ -43,12 +47,14 @@ export type InputProps = Props & typeof defaultProps & React.InputHTMLAttributes
|
||||
const Input: React.FC<InputProps> = ({
|
||||
placeholder, label, labelRight, size, status, disabled,
|
||||
icon, iconRight, initialValue, onChange, readOnly, value,
|
||||
onClearClick, clearable, width, className, ...props
|
||||
onClearClick, clearable, width, className, onBlur, onFocus,
|
||||
autoComplete, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const [selfValue, setSelfValue] = useState<string>(initialValue)
|
||||
const [hover, setHover] = useState<boolean>(false)
|
||||
const { heightRatio, fontSize } = useMemo(() => getSizes(size),[size])
|
||||
const showClearIcon = useMemo(() => clearable && selfValue !== '', [selfValue, clearable])
|
||||
const labelClasses = useMemo(
|
||||
() => labelRight ? 'right-label' : (label ? 'left-label' : ''),
|
||||
[label, labelRight],
|
||||
@@ -63,15 +69,23 @@ const Input: React.FC<InputProps> = ({
|
||||
)
|
||||
const changeHandler = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (disabled || readOnly) return
|
||||
console.log(123, event.target.value)
|
||||
setSelfValue(event.target.value)
|
||||
onChange && onChange(event)
|
||||
}
|
||||
|
||||
|
||||
const clearHandler = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||
setSelfValue('')
|
||||
onClearClick && onClearClick(event)
|
||||
}
|
||||
|
||||
const focusHandler = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
setHover(true)
|
||||
onFocus && onFocus(e)
|
||||
}
|
||||
const blurHandler = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
setHover(false)
|
||||
onBlur && onBlur(e)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (value === undefined) return
|
||||
@@ -88,12 +102,15 @@ const Input: React.FC<InputProps> = ({
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
onFocus={() => setHover(true)}
|
||||
onBlur={() => setHover(false)}
|
||||
onFocus={focusHandler}
|
||||
onBlur={blurHandler}
|
||||
onChange={changeHandler}
|
||||
autoComplete={autoComplete}
|
||||
{...props}
|
||||
/>
|
||||
{clearable && <InputClearIcon heightRatio={heightRatio}
|
||||
{clearable && <InputClearIcon
|
||||
visibale={showClearIcon}
|
||||
heightRatio={heightRatio}
|
||||
disabled={disabled || readOnly}
|
||||
onClick={clearHandler} />}
|
||||
{iconRight && <InputIcon icon={iconRight} ratio={heightRatio} />}
|
||||
|
||||
100
pages/docs/components/auto-complete.mdx
Normal file
100
pages/docs/components/auto-complete.mdx
Normal file
@@ -0,0 +1,100 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Avatar, Spacer } from 'components'
|
||||
|
||||
export const meta = {
|
||||
title: 'avatar',
|
||||
description: 'avatar',
|
||||
}
|
||||
|
||||
## Avatar
|
||||
|
||||
Avatars represent a user or a team. Stacked avatars represent a group of people.
|
||||
|
||||
|
||||
<Playground
|
||||
desc="The `Avatar` contains circle and square."
|
||||
scope={{ Avatar, Spacer }}
|
||||
code={`
|
||||
() => {
|
||||
const url = 'https://zeit.co/api/www/avatar/?u=evilrabbit&s=160'
|
||||
return (
|
||||
<>
|
||||
<Avatar src={url} />
|
||||
<Avatar src={url} />
|
||||
<Avatar src={url} />
|
||||
<Avatar src={url} />
|
||||
<Spacer y={.5} />
|
||||
<Avatar src={url} isSquare />
|
||||
<Avatar src={url} isSquare />
|
||||
<Avatar src={url} isSquare />
|
||||
<Avatar src={url} isSquare />
|
||||
</>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
<Playground
|
||||
title="Size"
|
||||
desc="You can specify different sizes of `Avatar`."
|
||||
scope={{ Avatar }}
|
||||
code={`
|
||||
() => {
|
||||
const url = 'https://zeit.co/api/www/avatar/?u=evilrabbit&s=160'
|
||||
return (
|
||||
<>
|
||||
<Avatar src={url} size="mini" />
|
||||
<Avatar src={url} size="small" />
|
||||
<Avatar src={url} size="medium" />
|
||||
<Avatar src={url} size="large" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Text"
|
||||
scope={{ Avatar }}
|
||||
code={`
|
||||
<>
|
||||
<Avatar text="W" />
|
||||
<Avatar text="A" />
|
||||
<Avatar text="W" />
|
||||
<Avatar text="Joe" />
|
||||
</>
|
||||
`} />
|
||||
|
||||
|
||||
<Playground
|
||||
title="Stacked"
|
||||
desc="Multiple avatars can overlap and stack together."
|
||||
scope={{ Avatar }}
|
||||
code={`
|
||||
() => {
|
||||
const url = 'https://zeit.co/api/www/avatar/?u=evilrabbit&s=160'
|
||||
return (
|
||||
<>
|
||||
<Avatar src={url} stacked />
|
||||
<Avatar src={url} stacked />
|
||||
<Avatar src={url} stacked />
|
||||
<Avatar src={url} stacked />
|
||||
</>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
<Attributes edit="/pages/docs/components/avatar.mdx">
|
||||
<Attributes.Title>Avatar.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **src** | image src | `string` | - | - |
|
||||
| **stacked** | stacked display group | `boolean` | - | `false` |
|
||||
| **text** | display text when image is missing | `string` | - | - |
|
||||
| **size** | avatar size | `string` / `number` | `'mini', 'small', 'medium', 'large', number` | `medium` |
|
||||
| **isSquare** | avatar shape | `boolean` | - | `false` |
|
||||
| ... | native props | `ImgHTMLAttributes` | `'alt', 'crossOrigin', 'className', ...` | - |
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
Reference in New Issue
Block a user