Skip to content

Instantly share code, notes, and snippets.

@JakeDawkins
Last active September 27, 2023 05:33
Show Gist options
  • Save JakeDawkins/1a1716da579b626cd7258e6bf6bb1743 to your computer and use it in GitHub Desktop.
Save JakeDawkins/1a1716da579b626cd7258e6bf6bb1743 to your computer and use it in GitHub Desktop.
Input with Floating Label
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>
);
});
.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