fix(input): hide clear icon when value is undefined

This commit is contained in:
unix
2020-03-28 07:01:38 +08:00
parent 643ec5e99f
commit 2930287d17
11 changed files with 295 additions and 8 deletions

View 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)

View File

@@ -0,0 +1,11 @@
import React from 'react'
const AutoComplete = () => {
return (
<div>
</div>
)
}
export default AutoComplete

View 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)

View File

@@ -0,0 +1,11 @@
import React from 'react'
const AutoComplete = () => {
return (
<div>
</div>
)
}
export default AutoComplete

View 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)

View File

View 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 {

View File

@@ -32,6 +32,7 @@ const InputIcon: React.FC<InputIconProps> = React.memo(({
margin: 0;
padding: 0 ${padding};
line-height: 1;
position: relative;
}
`}</style>
</span>

View File

@@ -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} />}

View 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>