2023-05-11 11:33:01 +00:00
|
|
|
import { clsx } from 'clsx'
|
2023-09-16 06:38:56 +00:00
|
|
|
import { createEffect, createSignal, Show } from 'solid-js'
|
2023-05-11 11:33:01 +00:00
|
|
|
|
2023-11-18 14:10:02 +00:00
|
|
|
import { ShowOnlyOnClient } from '../ShowOnlyOnClient'
|
|
|
|
|
2023-11-14 15:10:00 +00:00
|
|
|
import styles from './GrowingTextarea.module.scss'
|
|
|
|
|
2023-05-11 11:33:01 +00:00
|
|
|
type Props = {
|
|
|
|
class?: string
|
|
|
|
placeholder: string
|
|
|
|
initialValue?: string
|
|
|
|
value: (string) => void
|
2023-05-12 17:50:28 +00:00
|
|
|
maxLength?: number
|
2023-07-14 13:06:21 +00:00
|
|
|
allowEnterKey: boolean
|
2023-08-12 07:48:43 +00:00
|
|
|
variant?: 'bordered'
|
2023-08-16 13:11:58 +00:00
|
|
|
fieldName?: string
|
2023-08-17 04:31:03 +00:00
|
|
|
textAreaRef?: (el: HTMLTextAreaElement) => void
|
2023-05-11 11:33:01 +00:00
|
|
|
}
|
|
|
|
|
2023-11-18 14:10:02 +00:00
|
|
|
const GrowingTextarea = (props: Props) => {
|
2023-09-16 11:39:41 +00:00
|
|
|
const [value, setValue] = createSignal<string>('')
|
2023-05-12 17:50:28 +00:00
|
|
|
const [isFocused, setIsFocused] = createSignal(false)
|
2023-09-09 12:04:46 +00:00
|
|
|
|
|
|
|
createEffect(() => {
|
|
|
|
if (props.maxLength && props.initialValue?.length > props.maxLength) {
|
2023-09-21 05:46:32 +00:00
|
|
|
setValue(props.initialValue?.slice(0, props.maxLength))
|
2023-09-09 12:04:46 +00:00
|
|
|
} else {
|
|
|
|
setValue(props.initialValue ?? '')
|
|
|
|
}
|
|
|
|
})
|
2023-05-11 11:33:01 +00:00
|
|
|
const handleChangeValue = (event) => {
|
|
|
|
setValue(event.target.value)
|
|
|
|
}
|
|
|
|
|
|
|
|
const handleKeyDown = async (event) => {
|
|
|
|
if (event.key === 'Enter' && event.shiftKey) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (event.key === 'Enter' && !event.shiftKey && value()?.trim().length > 0) {
|
|
|
|
event.preventDefault()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2023-11-18 14:10:02 +00:00
|
|
|
<ShowOnlyOnClient>
|
|
|
|
<div
|
|
|
|
class={clsx(styles.GrowingTextarea, {
|
|
|
|
[styles.bordered]: props.variant === 'bordered',
|
|
|
|
[styles.hasFieldName]: props.fieldName && value().length > 0,
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
<Show when={props.fieldName && value().length > 0}>
|
|
|
|
<div class={styles.fieldName}>{props.fieldName}</div>
|
|
|
|
</Show>
|
|
|
|
<div class={clsx(styles.growWrap, props.class)} data-replicated-value={value()}>
|
|
|
|
<textarea
|
|
|
|
ref={props.textAreaRef}
|
|
|
|
rows={1}
|
|
|
|
maxlength={props.maxLength}
|
|
|
|
autocomplete="off"
|
|
|
|
class={clsx(styles.textInput, props.class)}
|
|
|
|
value={
|
|
|
|
props.initialValue && props.maxLength
|
|
|
|
? props.initialValue?.slice(0, props.maxLength)
|
|
|
|
: props.initialValue
|
|
|
|
}
|
|
|
|
onKeyDown={props.allowEnterKey ? handleKeyDown : null}
|
|
|
|
onInput={(event) => handleChangeValue(event)}
|
|
|
|
onChange={(event) => props.value(event.target.value)}
|
|
|
|
placeholder={props.placeholder}
|
|
|
|
onFocus={() => setIsFocused(true)}
|
|
|
|
onBlur={() => setIsFocused(false)}
|
|
|
|
/>
|
2023-09-15 09:30:50 +00:00
|
|
|
</div>
|
2023-11-18 14:10:02 +00:00
|
|
|
<Show when={(props.maxLength && value() && isFocused()) || props.variant === 'bordered'}>
|
|
|
|
<div
|
|
|
|
class={clsx(styles.maxLength, {
|
|
|
|
[styles.visible]: isFocused(),
|
|
|
|
[styles.limited]: value().length === props.maxLength,
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
<Show when={props.variant === 'bordered'} fallback={`${value().length} / ${props.maxLength}`}>
|
|
|
|
{`${props.maxLength - value().length}`}
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
</Show>
|
|
|
|
</div>
|
|
|
|
</ShowOnlyOnClient>
|
2023-05-11 11:33:01 +00:00
|
|
|
)
|
|
|
|
}
|
2023-11-18 14:10:02 +00:00
|
|
|
|
|
|
|
export default GrowingTextarea // for async load
|