commit b871dc707efa9313ae4e7b18e614eccb05ee02df
parent 720926afc98f5ab2db7dd29caf060f446309a35e
Author: Yongbin Kim <iam@yongbin.kim>
Date: Wed, 25 Jan 2023 16:26:18 +0900
feat(components): Field 컴포넌트에 textarea 지원 추가
Signed-off-by: Yongbin Kim <iam@yongbin.kim>
Diffstat:
4 files changed, 54 insertions(+), 16 deletions(-)
diff --git a/components/form/Field.module.css b/components/form/Field.module.css
@@ -1 +1 @@
-.field{display:flex;position:relative;border:1px solid rgba(0,0,0,0);margin-bottom:1rem;border-radius:4px;background-color:inherit;box-shadow:0 0 0 1px rgba(0,0,0,0);transition:all 200ms;color:#191c1b;border-color:#6f7975}.field.is-disabled{color:rgba(25,28,27,.5);border-color:rgba(111,121,117,.5)}.field.is-not-readonly.has-no-color:focus-within{border-color:#006b5a;box-shadow:0 0 0 1px #006b5a}.field.is-primary{color:#006b5a;border-color:#006b5a}.field.is-primary:focus-within{box-shadow:0 0 0 1px #006b5a}.field.is-secondary{color:#7b4998;border-color:#7b4998}.field.is-secondary:focus-within{box-shadow:0 0 0 1px #7b4998}.field.is-tertiary{color:#426278;border-color:#426278}.field.is-tertiary:focus-within{box-shadow:0 0 0 1px #426278}.field.is-error{color:red;border-color:red}.field.is-error:focus-within{box-shadow:0 0 0 1px red}@media(prefers-color-scheme: dark){.field{color:#e1e3e0;border-color:#89938f}.field.is-disabled{color:rgba(225,227,224,.5);border-color:rgba(137,147,143,.5)}.field.is-not-readonly.has-no-color:focus-within{border-color:#2cdebf;box-shadow:0 0 0 1px #2cdebf}.field.is-primary{color:#2cdebf;border-color:#2cdebf}.field.is-primary:focus-within{box-shadow:0 0 0 1px #2cdebf}.field.is-secondary{color:#e6b4ff;border-color:#e6b4ff}.field.is-secondary:focus-within{box-shadow:0 0 0 1px #e6b4ff}.field.is-tertiary{color:#aacbe4;border-color:#aacbe4}.field.is-tertiary:focus-within{box-shadow:0 0 0 1px #aacbe4}.field.is-error{color:#ffb4ab;border-color:#ffb4ab}.field.is-error:focus-within{box-shadow:0 0 0 1px #ffb4ab}}.input{width:100%;height:3.5rem;border:0;padding:0 1rem;color:inherit;background:rgba(0,0,0,0);outline:none}.field.has-icon-left .input{padding-left:3.25rem}.field.has-icon-right .input{padding-right:3.25rem}.label-background{position:absolute;top:-3px;left:.6875rem;height:4px;padding:0 .3125rem;line-height:0;color:rgba(0,0,0,0);background-color:inherit;transform:translateX(-10%) scaleX(0);transition:200ms transform ease-in-out}.field.is-not-readonly:focus-within .label-background,.field.has-content .label-background{transform:translateX(-10%) scaleX(0.8)}.label{position:absolute;top:50%;left:1rem;pointer-events:none;transform-origin:left top;transform:translateY(-50%);transition:all .2s ease-in-out;z-index:1;color:#3f4946}.field.has-icon-left .label{left:.75rem}.field.is-not-readonly:focus-within .label,.field.has-content .label{top:0;transform:scale(0.8) translateY(-50%)}.field.is-not-readonly.has-no-color:focus-within .label{color:#006b5a}.field.is-disabled .label{color:rgba(63,73,70,.5)}.is-primary .label{color:#006b5a}.is-secondary .label{color:#7b4998}.is-tertiary .label{color:#426278}.is-error .label{color:red}@media(prefers-color-scheme: dark){.label{color:#bfc9c4}.field.is-not-readonly.has-no-color:focus-within .label{color:#2cdebf}.field.is-disabled .label{color:rgba(191,201,196,.5)}.is-primary .label{color:#2cdebf}.is-secondary .label{color:#e6b4ff}.is-tertiary .label{color:#aacbe4}.is-error .label{color:#ffb4ab}}.message{line-height:1.25rem;font-size:.875rem;letter-spacing:.0178571429rem;font-weight:400;margin-top:-0.75rem;margin-bottom:1rem;color:#3f4946}.is-primary .message{color:#006b5a}.is-secondary .message{color:#7b4998}.is-tertiary .message{color:#426278}.is-error .message{color:red}@media(prefers-color-scheme: dark){.message{color:#bfc9c4}.is-primary .message{color:#2cdebf}.is-secondary .message{color:#e6b4ff}.is-tertiary .message{color:#aacbe4}.is-error .message{color:#ffb4ab}}/*# sourceMappingURL=Field.module.css.map */
+.field{display:flex;position:relative;border:1px solid rgba(0,0,0,0);margin-bottom:1rem;border-radius:4px;background-color:inherit;box-shadow:0 0 0 1px rgba(0,0,0,0);transition:all 200ms;color:#191c1b;border-color:#6f7975}.field.is-disabled{color:rgba(25,28,27,.5);border-color:rgba(111,121,117,.5)}.field.is-not-readonly.has-no-color:focus-within{border-color:#006b5a;box-shadow:0 0 0 1px #006b5a}.field.is-primary{color:#006b5a;border-color:#006b5a}.field.is-primary:focus-within{box-shadow:0 0 0 1px #006b5a}.field.is-secondary{color:#7b4998;border-color:#7b4998}.field.is-secondary:focus-within{box-shadow:0 0 0 1px #7b4998}.field.is-tertiary{color:#426278;border-color:#426278}.field.is-tertiary:focus-within{box-shadow:0 0 0 1px #426278}.field.is-error{color:red;border-color:red}.field.is-error:focus-within{box-shadow:0 0 0 1px red}@media(prefers-color-scheme: dark){.field{color:#e1e3e0;border-color:#89938f}.field.is-disabled{color:rgba(225,227,224,.5);border-color:rgba(137,147,143,.5)}.field.is-not-readonly.has-no-color:focus-within{border-color:#2cdebf;box-shadow:0 0 0 1px #2cdebf}.field.is-primary{color:#2cdebf;border-color:#2cdebf}.field.is-primary:focus-within{box-shadow:0 0 0 1px #2cdebf}.field.is-secondary{color:#e6b4ff;border-color:#e6b4ff}.field.is-secondary:focus-within{box-shadow:0 0 0 1px #e6b4ff}.field.is-tertiary{color:#aacbe4;border-color:#aacbe4}.field.is-tertiary:focus-within{box-shadow:0 0 0 1px #aacbe4}.field.is-error{color:#ffb4ab;border-color:#ffb4ab}.field.is-error:focus-within{box-shadow:0 0 0 1px #ffb4ab}}.input,.textarea{width:100%;height:3.5rem;border:0;padding:0 1rem;color:inherit;background:rgba(0,0,0,0);outline:none}.field.has-icon-left .input,.field.has-icon-left .textarea{padding-left:3.25rem}.field.has-icon-right .input,.field.has-icon-right .textarea{padding-right:3.25rem}.textarea{resize:none;line-height:1.5rem;padding:1rem 1rem;overflow-y:hidden}.label-background{position:absolute;top:-3px;left:.6875rem;height:4px;padding:0 .3125rem;line-height:0;color:rgba(0,0,0,0);background-color:inherit;transform:translateX(-10%) scaleX(0);transition:200ms transform ease-in-out}.field.is-not-readonly:focus-within .label-background,.field.has-content .label-background{transform:translateX(-10%) scaleX(0.8)}.label{position:absolute;top:50%;left:1rem;pointer-events:none;transform-origin:left top;transform:translateY(-50%);transition:all .2s ease-in-out;z-index:1;color:#3f4946}.field.has-icon-left .label{left:.75rem}.field.is-not-readonly:focus-within .label,.field.has-content .label{top:0;transform:scale(0.8) translateY(-50%)}.field.is-not-readonly.has-no-color:focus-within .label{color:#006b5a}.field.is-disabled .label{color:rgba(63,73,70,.5)}.is-primary .label{color:#006b5a}.is-secondary .label{color:#7b4998}.is-tertiary .label{color:#426278}.is-error .label{color:red}@media(prefers-color-scheme: dark){.label{color:#bfc9c4}.field.is-not-readonly.has-no-color:focus-within .label{color:#2cdebf}.field.is-disabled .label{color:rgba(191,201,196,.5)}.is-primary .label{color:#2cdebf}.is-secondary .label{color:#e6b4ff}.is-tertiary .label{color:#aacbe4}.is-error .label{color:#ffb4ab}}.message{line-height:1.25rem;font-size:.875rem;letter-spacing:.0178571429rem;font-weight:400;margin-top:-0.75rem;margin-bottom:1rem;color:#3f4946}.is-primary .message{color:#006b5a}.is-secondary .message{color:#7b4998}.is-tertiary .message{color:#426278}.is-error .message{color:red}@media(prefers-color-scheme: dark){.message{color:#bfc9c4}.is-primary .message{color:#2cdebf}.is-secondary .message{color:#e6b4ff}.is-tertiary .message{color:#aacbe4}.is-error .message{color:#ffb4ab}}/*# sourceMappingURL=Field.module.css.map */
diff --git a/components/form/Field.module.css.map b/components/form/Field.module.css.map
@@ -1 +1 @@
-{"version":3,"sourceRoot":"","sources":["Field.module.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss"],"names":[],"mappings":"AAWA,OACE,aACA,kBACA,+BACA,mBACA,kBACA,yBACA,mCACA,qBAGE,cACA,qBAEA,mBACE,wBACA,kCAGF,iDACE,qBACA,6BAIA,kBACE,MC2GM,QD1GN,aC0GM,QDxGN,+BACE,6BALJ,oBACE,MC2GM,QD1GN,aC0GM,QDxGN,iCACE,6BALJ,mBACE,MC2GM,QD1GN,aC0GM,QDxGN,gCACE,6BALJ,gBACE,MC2GM,ID1GN,aC0GM,IDxGN,6BACE,yBCuFR,mCDrHF,OAWI,cACA,qBAEA,mBACE,2BACA,kCAGF,iDACE,qBACA,6BAIA,kBACE,MC2GM,QD1GN,aC0GM,QDxGN,+BACE,6BALJ,oBACE,MC2GM,QD1GN,aC0GM,QDxGN,iCACE,6BALJ,mBACE,MC2GM,QD1GN,aC0GM,QDxGN,gCACE,6BALJ,gBACE,MC2GM,QD1GN,aC0GM,QDxGN,6BACE,8BAOV,OACE,WACA,OA9CO,OA+CP,SACA,eACA,cACA,yBACA,aAIA,4BACE,aAHwB,QAM1B,6BACE,cAPwB,QAW5B,kBAGE,kBACA,SACA,cACA,WACA,mBACA,cACA,oBACA,yBACA,qCACA,uCAEA,2FAEE,uCAIJ,OACE,kBACA,QACA,KApFqB,KAqFrB,oBACA,0BACA,2BACA,+BACA,UAaE,cAXF,4BACE,KA3FgB,OA8FlB,qEAEE,MACA,sCAMA,wDACE,cAGF,0BACE,wBAIA,mBACE,MCuBM,QDxBR,qBACE,MCuBM,QDxBR,oBACE,MCuBM,QDxBR,iBACE,MCuBM,IAhBZ,mCDxCF,OAqBI,cAEA,wDACE,cAGF,0BACE,2BAIA,mBACE,MCuBM,QDxBR,qBACE,MCuBM,QDxBR,oBACE,MCuBM,QDxBR,iBACE,MCuBM,SDjBd,SETI,oBACA,kBACA,8BACA,gBFQF,oBACA,mBAGE,cAGE,qBACE,MCOM,QDRR,uBACE,MCOM,QDRR,sBACE,MCOM,QDRR,mBACE,MCOM,IAhBZ,mCDDF,SAMI,cAGE,qBACE,MCOM,QDRR,uBACE,MCOM,QDRR,sBACE,MCOM,QDRR,mBACE,MCOM","file":"Field.module.css"}
-\ No newline at end of file
+{"version":3,"sourceRoot":"","sources":["Field.module.scss","../../styles/core/_colors.scss","../../styles/core/_typography.scss"],"names":[],"mappings":"AAYA,OACE,aACA,kBACA,+BACA,mBACA,kBACA,yBACA,mCACA,qBAGE,cACA,qBAEA,mBACE,wBACA,kCAGF,iDACE,qBACA,6BAIA,kBACE,MC0GM,QDzGN,aCyGM,QDvGN,+BACE,6BALJ,oBACE,MC0GM,QDzGN,aCyGM,QDvGN,iCACE,6BALJ,mBACE,MC0GM,QDzGN,aCyGM,QDvGN,gCACE,6BALJ,gBACE,MC0GM,IDzGN,aCyGM,IDvGN,6BACE,yBCsFR,mCDpHF,OAWI,cACA,qBAEA,mBACE,2BACA,kCAGF,iDACE,qBACA,6BAIA,kBACE,MC0GM,QDzGN,aCyGM,QDvGN,+BACE,6BALJ,oBACE,MC0GM,QDzGN,aCyGM,QDvGN,iCACE,6BALJ,mBACE,MC0GM,QDzGN,aCyGM,QDvGN,gCACE,6BALJ,gBACE,MC0GM,QDzGN,aCyGM,QDvGN,6BACE,8BAOV,iBAEE,WACA,OAhDO,OAiDP,SACA,eACA,cACA,yBACA,aAIA,2DACE,aAHwB,QAM1B,6DACE,cAPwB,QAW5B,UACE,YACA,YA9DqB,OA+DrB,kBACA,kBAGF,kBAGE,kBACA,SACA,cACA,WACA,mBACA,cACA,oBACA,yBACA,qCACA,uCAEA,2FAEE,uCAIJ,OACE,kBACA,QACA,KA7FqB,KA8FrB,oBACA,0BACA,2BACA,+BACA,UAaE,cAXF,4BACE,KApGgB,OAuGlB,qEAEE,MACA,sCAMA,wDACE,cAGF,0BACE,wBAIA,mBACE,MCcM,QDfR,qBACE,MCcM,QDfR,oBACE,MCcM,QDfR,iBACE,MCcM,IAhBZ,mCD/BF,OAqBI,cAEA,wDACE,cAGF,0BACE,2BAIA,mBACE,MCcM,QDfR,qBACE,MCcM,QDfR,oBACE,MCcM,QDfR,iBACE,MCcM,SDRd,SElBI,oBACA,kBACA,8BACA,gBFiBF,oBACA,mBAGE,cAGE,qBACE,MCFM,QDCR,uBACE,MCFM,QDCR,sBACE,MCFM,QDCR,mBACE,MCFM,IAhBZ,mCDQF,SAMI,cAGE,qBACE,MCFM,QDCR,uBACE,MCFM,QDCR,sBACE,MCFM,QDCR,mBACE,MCFM","file":"Field.module.css"}
+\ No newline at end of file
diff --git a/components/form/Field.module.scss b/components/form/Field.module.scss
@@ -8,6 +8,7 @@ $field-gap: 1rem;
$padding-without-icon: 1rem;
$padding-with-icon: 0.75rem;
$label-focused-scale: 0.8;
+$textarea-line-height: 1.5rem;
.field {
display: flex;
@@ -46,7 +47,8 @@ $label-focused-scale: 0.8;
}
}
-.input {
+.input,
+.textarea {
width: 100%;
height: $height;
border: 0;
@@ -66,6 +68,13 @@ $label-focused-scale: 0.8;
}
}
+.textarea {
+ resize: none;
+ line-height: $textarea-line-height;
+ padding: math.div(($height - $textarea-line-height), 2) $padding-without-icon;
+ overflow-y: hidden;
+}
+
.label-background {
$label-background-padding: math.div(0.25rem, $label-focused-scale);
diff --git a/components/form/Field.tsx b/components/form/Field.tsx
@@ -1,5 +1,5 @@
import classNames from '@/lib/classnames'
-import { FormEvent, HTMLAttributes, useMemo } from 'react'
+import { createRef, FormEvent, HTMLAttributes, useEffect, useMemo } from 'react'
import styles from './Field.module.css'
export interface FieldProps extends HTMLAttributes<HTMLLabelElement> {
@@ -9,6 +9,7 @@ export interface FieldProps extends HTMLAttributes<HTMLLabelElement> {
readOnly?: boolean
type: 'date' | 'datetime-local' | 'email' | 'month' | 'number'
| 'password' | 'search' | 'tel' | 'time' | 'url' | 'week' | 'text'
+ | 'textarea'
value?: string
onValueChange?: (value: string) => void
@@ -26,22 +27,39 @@ export default function Field (props: FieldProps) {
placeholder,
readOnly,
type,
- value: initialValue,
+ value,
onValueChange,
...restProps
} = props
- const handleInput = (e: FormEvent<HTMLInputElement>) => {
+ const handleInput = (e: FormEvent<HTMLInputElement | HTMLTextAreaElement>) => {
onValueChange?.(e.currentTarget.value)
}
+ const textareaRef = createRef<HTMLTextAreaElement>()
+
+ useEffect(() => {
+ if (type !== 'textarea') {
+ return
+ }
+
+ const textarea = textareaRef.current
+ if (textarea === null) {
+ return
+ }
+
+ textarea.style.height = '0px'
+ const scrollHeight = textarea.scrollHeight
+ textarea.style.height = `${scrollHeight}px`
+ }, [type, value])
+
return (
<>
<label
{...classNames(
className,
styles['field'],
- (props.value?.length ?? 0) > 0 ? styles['has-content'] : null,
+ (value?.length ?? 0) > 0 ? styles['has-content'] : null,
color == null ? styles[`has-no-color`] : undefined,
color != null ? styles[`is-${color}`] : undefined,
readOnly !== true ? styles['is-not-readonly'] : null,
@@ -49,14 +67,25 @@ export default function Field (props: FieldProps) {
)}
{...restProps}
>
- <input
- className={styles['input']}
- type={type}
- disabled={disabled}
- readOnly={readOnly}
- value={props.value}
- onInput={handleInput}
- />
+ {type === 'textarea' ? (
+ <textarea
+ className={styles['textarea']}
+ disabled={disabled}
+ readOnly={readOnly}
+ value={value}
+ onInput={handleInput}
+ ref={textareaRef}
+ />
+ ) : (
+ <input
+ className={styles['input']}
+ type={type}
+ disabled={disabled}
+ readOnly={readOnly}
+ value={value}
+ onInput={handleInput}
+ />
+ )}
<span className={styles['label']}>{placeholder}</span>
<div className={styles['label-background']}>{placeholder}</div>
</label>