Files
react/components/textarea/textarea.tsx
witt 6da0509316 release 2.1.0 (#451)
* feat: optimize fonts rendering on windows (#385)

* feat(styles): set Inter to highest font

* docs(fonts): add guide for fonts rendering on windows

* test: udpate snapshots

* chore: release v2.1.0-canary.0

* feat(table): add update row action to Table (#378)

* feat: add update to Table's actions. add test and doc

fix(table): fix comments

* feat(table): improve type for table actions

chore: update docs

chore: remove unused types

chore(table): improve docs

Co-authored-by: William Castandet <williamcastandet@williams-air.home>
Co-authored-by: unix <unix.bio@gmail.com>

* refactor(use-theme): move use-theme to the top directory (#397)

* refactor(use-theme): move use-theme to the top directory

* chore(jest): ignore use-theme of forwarding

* chore: release v2.1.0-canary.1

* feat(select): add clearable option to select multiple with test and english doc (#396)

* docs: add clearable option to select multiple with test and english doc

* fix: fix types for onClear

* fix: fix import path for use-theme

add more test for coverage

* docs(select): add chinese document

Co-authored-by: unix <unix.bio@gmail.com>

* chore: release v2.1.0-canary.2

* fix(tabs): scrollable (#404)

docs(tabs): scroll behavior

* feat(textarea): resize prop (#416)

* feat: add resize prop to textarea

* docs: add resize prop for textarea

* docs(textarea): improve docs and attributes for cn

* test(textarea): update snapshots

Co-authored-by: unix <unix.bio@gmail.com>

* fix(types): replace path aliases in type files (#432)

* fix(types): replace path aliases in type files

* chore(lint): upgrade eslint and optimize code style

* chore: fix type error for context handler

* test: update snapshots

* fix: use ttsc to identify aliases in type paths

* feat(hooks): add a tool hooks for react context (#439)

* feat(hooks): add a tool hooks for react context

* chore: move use-context-state to internal tools

style: fix lint warning

* chore: simplify the structure of the catalog

* refactor(themes): refactor theme module to keep multiple themes (#440)

* refactor(themes): refactor theme module to keep multiple themes

* chore: migrate APIs to be compatible with new theme system

* test: update snapshots

* chore: migrate the path of the theme module

* feat(themes): append static methods of themes

* chore: hide custom theme when no custom content in the context

* chore: manually add flush to preload styles in html

* docs(themes): update to fit the new theme system

* chore: release v2.1.0-canary.3 (#450)

* docs: add link to GH discussions

* chore: upgrade deps

* chore: update code style for prettier

* chore: release v2.1.0-canary.3

* chore(deps): upgrade babel

* chore: replace enzyme adapter with community repo to fit react.17

* test: updatee snapshots for auto typesetting

* test(config): ignore unexported parts of the tools

Co-authored-by: William <wcastand@gmail.com>
Co-authored-by: William Castandet <williamcastandet@williams-air.home>
Co-authored-by: Vaibhav Acharya <vaibhavacharya111@gmail.com>
Co-authored-by: Paul van Dyk <39598117+PaulPCIO@users.noreply.github.com>
2021-02-14 15:58:52 +08:00

175 lines
4.8 KiB
TypeScript

import React, { useRef, useImperativeHandle, useEffect, useMemo, useState } from 'react'
import useTheme from '../use-theme'
import withDefaults from '../utils/with-defaults'
import { NormalTypes, tuple } from '../utils/prop-types'
import { getColors } from '../input/styles'
const resizeTypes = tuple('none', 'both', 'horizontal', 'vertical', 'initial', 'inherit')
type ResizeTypes = typeof resizeTypes[number]
interface Props {
value?: string
initialValue?: string
placeholder?: string
status?: NormalTypes
width?: string
minHeight?: string
disabled?: boolean
readOnly?: boolean
onChange?: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
onFocus?: (e: React.FocusEvent<HTMLTextAreaElement>) => void
onBlur?: (e: React.FocusEvent<HTMLTextAreaElement>) => void
className?: string
resize?: ResizeTypes
}
const defaultProps = {
initialValue: '',
status: 'default' as NormalTypes,
width: 'initial',
minHeight: '6.25rem',
disabled: false,
readOnly: false,
className: '',
resize: 'none' as ResizeTypes,
}
type NativeAttrs = Omit<React.TextareaHTMLAttributes<any>, keyof Props>
export type TextareaProps = Props & typeof defaultProps & NativeAttrs
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.PropsWithChildren<TextareaProps>
>(
(
{
width,
status,
minHeight,
disabled,
readOnly,
onFocus,
onBlur,
className,
initialValue,
onChange,
value,
placeholder,
resize,
...props
},
ref: React.Ref<HTMLTextAreaElement | null>,
) => {
const theme = useTheme()
const textareaRef = useRef<HTMLTextAreaElement>(null)
useImperativeHandle(ref, () => textareaRef.current)
const isControlledComponent = useMemo(() => value !== undefined, [value])
const [selfValue, setSelfValue] = useState<string>(initialValue)
const [hover, setHover] = useState<boolean>(false)
const { color, borderColor, hoverBorder } = useMemo(
() => getColors(theme.palette, status),
[theme.palette, status],
)
const changeHandler = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
if (disabled || readOnly) return
setSelfValue(event.target.value)
onChange && onChange(event)
}
const focusHandler = (e: React.FocusEvent<HTMLTextAreaElement>) => {
setHover(true)
onFocus && onFocus(e)
}
const blurHandler = (e: React.FocusEvent<HTMLTextAreaElement>) => {
setHover(false)
onBlur && onBlur(e)
}
useEffect(() => {
if (isControlledComponent) {
setSelfValue(value as string)
}
})
const controlledValue = isControlledComponent
? { value: selfValue }
: { defaultValue: initialValue }
const textareaProps = {
...props,
...controlledValue,
}
return (
<div
className={`wrapper ${hover ? 'hover' : ''} ${
disabled ? 'disabled' : ''
} ${className}`}>
<textarea
ref={textareaRef}
disabled={disabled}
placeholder={placeholder}
readOnly={readOnly}
onFocus={focusHandler}
onBlur={blurHandler}
onChange={changeHandler}
{...textareaProps}
/>
<style jsx>{`
.wrapper {
display: inline-flex;
box-sizing: border-box;
user-select: none;
width: ${width};
min-width: 12.5rem;
max-width: 95vw;
height: auto;
border-radius: ${theme.layout.radius};
border: 1px solid ${borderColor};
color: ${color};
transition: border 0.2s ease 0s, color 0.2s ease 0s;
}
.wrapper.hover {
border-color: ${hoverBorder};
}
.wrapper.disabled {
background-color: ${theme.palette.accents_1};
border-color: ${theme.palette.accents_2};
cursor: not-allowed;
}
textarea {
background-color: transparent;
box-shadow: none;
display: block;
font-family: ${theme.font.sans};
font-size: 0.875rem;
width: 100%;
height: 100%;
min-height: ${minHeight};
resize: none;
border: none;
outline: none;
padding: ${theme.layout.gapHalf};
resize: ${resize};
}
.disabled > textarea {
cursor: not-allowed;
}
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:active,
textarea:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0 30px ${theme.palette.background} inset !important;
}
`}</style>
</div>
)
},
)
export default withDefaults(Textarea, defaultProps)