Last active
September 27, 2023 05:33
-
-
Save JakeDawkins/1a1716da579b626cd7258e6bf6bb1743 to your computer and use it in GitHub Desktop.
Input with Floating Label
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { InputHTMLAttributes, useMemo, forwardRef } from 'react'; | |
import ReactInputMask from 'react-input-mask'; | |
import styles from './index.module.css'; | |
type FloatingLabelInputProps = InputHTMLAttributes<HTMLInputElement> & { | |
label: string; | |
mask?: string; | |
className?: string; | |
errorMessage?: string; | |
}; | |
// uses forwardRef to allow the parent component to access the input ref | |
// for libraries like react-hook-form to work | |
export default forwardRef(function FloatingLabelInput( | |
{ | |
label, | |
mask, | |
className = '', | |
errorMessage, | |
...inputProps | |
}: FloatingLabelInputProps, | |
ref, | |
) { | |
// if there is a passed in id, use that. Otherwise, generate one from the label | |
const id = | |
inputProps.id || | |
label.toLowerCase().replace(/ /g, '-') + | |
'-' + | |
Math.random().toString(16).slice(2); | |
const inputCompProps = useMemo( | |
() => ({ | |
...inputProps, | |
// we need to have a placeholder to make the label float, | |
// but we don't want to show it. | |
// TODO -- figure out a way to show a placeholder later | |
placeholder: ' ', | |
id: id, | |
className: | |
styles.input + | |
' bg-lightestGray ' + | |
(inputProps.disabled ? ' cursor-not-allowed' : '') + | |
(errorMessage ? ' border-2 border-darkerRed' : ''), | |
disabled: inputProps.disabled, | |
'aria-errormessage': errorMessage ? `${id}-error` : undefined, | |
'aria-invalid': !!errorMessage, | |
}), | |
[errorMessage, id, inputProps], | |
); | |
return ( | |
<div className={styles.container + ' ' + className}> | |
{mask ? ( | |
<ReactInputMask | |
{...inputCompProps} | |
mask={mask} | |
maskPlaceholder={null} | |
// @ts-ignore | |
ref={ref} | |
/> | |
) : ( | |
<input | |
{...inputCompProps} | |
// @ts-ignore | |
ref={ref} | |
/> | |
)} | |
<label | |
htmlFor={id} | |
className={ | |
styles.label + (errorMessage ? ' text-darkerRed' : ' text-darkGray') | |
} | |
> | |
{label} | |
</label> | |
{errorMessage ? ( | |
<p | |
className="ml-4 mt-1 text-darkerRed text-sm font-bold" | |
id={`${id}-error`} | |
> | |
{errorMessage} | |
</p> | |
) : null} | |
</div> | |
); | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.container { | |
position: relative; | |
display: flex; | |
flex-direction: column; | |
} | |
/* positioned in the "placeholder" state, in the middle of the input */ | |
.container .label { | |
position: absolute; | |
/* label can be clicked through */ | |
pointer-events: none; | |
transform: translate(0, 23px) scale(1); | |
transform-origin: top left; | |
transition: 150ms cubic-bezier(0, 0, 0.2, 1) 0ms; | |
font-size: 16px; | |
line-height: 1; | |
left: 16px; | |
} | |
.container .input { | |
display: flex; | |
padding: 24px 16px 8px; | |
height: 60px; | |
border-radius: 8px; | |
} | |
.input::placeholder { | |
color: transparent; | |
} | |
/* when the input is focused, do this to the label */ | |
.container:focus-within .label { | |
transform: translate(0, 12px) scale(0.8); | |
color: #5e71c4; | |
font-weight: bold; | |
} | |
/* when the input has a value (no placeholder), do this */ | |
.input:not(:placeholder-shown) + .label { | |
transform: translate(0, 12px) scale(0.8); | |
font-weight: bold; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment