Download frontend/src/auth/InputField.jsx from KSS-1227/TradeMind: direct link, hf CLI and curl.
- Browser
- Download file 4.4 kB
-
https://huggingface.co/spaces/KSS-1227/TradeMind/resolve/main/frontend/src/auth/InputField.jsx
- Command line
-
hf download hf://spaces/KSS-1227/TradeMind/frontend/src/auth/InputField.jsx
-
curl -L -o InputField.jsx https://huggingface.co/spaces/KSS-1227/TradeMind/resolve/main/frontend/src/auth/InputField.jsx
4.4 kB
| import { useState } from "react"; | |
| import { AiOutlineEye, AiOutlineEyeInvisible } from "react-icons/ai"; | |
| /** | |
| * InputField | |
| * | |
| * Floating-label behaviour (Stripe / Vercel / Linear pattern): | |
| * Empty + not focused β label sits centred, acts as placeholder | |
| * Focused OR has value β label floats to top-left (small, uppercase) | |
| * Focused β label is teal | |
| * Has value, not focused β label stays floated but turns muted grey | |
| * | |
| * Props: | |
| * icon β React node for the left icon | |
| * hint β helper text shown below the field | |
| * error β error string; replaces hint when present | |
| */ | |
| export default function InputField({ | |
| label, | |
| name, | |
| type = "text", | |
| value, | |
| onChange, | |
| placeholder, | |
| autoComplete, | |
| error, | |
| onKeyDown, | |
| hint, | |
| icon, | |
| }) { | |
| const [focused, setFocused] = useState(false); | |
| const [showPass, setShowPass] = useState(false); | |
| const [autofilled, setAutofilled] = useState(false); | |
| const isPassword = type === "password"; | |
| const inputType = isPassword ? (showPass ? "text" : "password") : type; | |
| const hasValue = Boolean(value && value.length > 0); | |
| // Float the label if: focused, has a React-controlled value, OR browser autofilled | |
| const isFloated = focused || hasValue || autofilled; | |
| return ( | |
| <div className={`field-group ${error ? "field-group--error" : ""}`}> | |
| <div | |
| className={[ | |
| "input-wrapper", | |
| focused ? "input-wrapper--focused" : "", | |
| error ? "input-wrapper--error" : "", | |
| ].filter(Boolean).join(" ")} | |
| > | |
| {/* ββ Left icon ββ */} | |
| {icon && ( | |
| <span | |
| className={`input-icon ${focused ? "input-icon--active" : ""}`} | |
| aria-hidden="true" | |
| > | |
| {icon} | |
| </span> | |
| )} | |
| {/* ββ Floating label ββ */} | |
| {label && ( | |
| <label | |
| htmlFor={name} | |
| className={[ | |
| "floating-label", | |
| icon ? "floating-label--with-icon" : "", | |
| isFloated ? "floating-label--active" : "", | |
| /* When floated but NOT focused, use the muted colour variant */ | |
| isFloated && !focused ? "floating-label--filled" : "", | |
| ].filter(Boolean).join(" ")} | |
| > | |
| {label} | |
| </label> | |
| )} | |
| {/* ββ Input ββ */} | |
| <input | |
| id={name} | |
| name={name} | |
| type={inputType} | |
| value={value} | |
| onChange={onChange} | |
| onKeyDown={onKeyDown} | |
| onFocus={() => setFocused(true)} | |
| onBlur={() => setFocused(false)} | |
| /* Detect browser autofill β Chrome fires a CSS animation on | |
| :-webkit-autofill which we can catch here to float the label | |
| before React's value state catches up. */ | |
| onAnimationStart={(e) => { | |
| if (e.animationName === "autofillStart") setAutofilled(true); | |
| if (e.animationName === "autofillEnd") setAutofilled(false); | |
| }} | |
| placeholder={focused ? placeholder : ""} | |
| autoComplete={autoComplete} | |
| className={[ | |
| "auth-input", | |
| icon ? "auth-input--with-icon" : "", | |
| isPassword ? "auth-input--with-toggle" : "", | |
| ].filter(Boolean).join(" ")} | |
| aria-invalid={!!error} | |
| aria-describedby={ | |
| error ? `${name}-error` : hint ? `${name}-hint` : undefined | |
| } | |
| /> | |
| {/* ββ Password show / hide toggle ββ */} | |
| {isPassword && ( | |
| <button | |
| type="button" | |
| className="password-toggle" | |
| onClick={() => setShowPass((v) => !v)} | |
| aria-label={showPass ? "Hide password" : "Show password"} | |
| aria-pressed={showPass} | |
| > | |
| {showPass | |
| ? <AiOutlineEyeInvisible size={20} /> | |
| : <AiOutlineEye size={20} />} | |
| </button> | |
| )} | |
| </div> | |
| {/* Hint β only shown when there's no error */} | |
| {hint && !error && ( | |
| <div id={`${name}-hint`} className="field-hint"> | |
| {hint} | |
| </div> | |
| )} | |
| {/* Error */} | |
| {error && ( | |
| <div id={`${name}-error`} className="field-error" role="alert"> | |
| {error} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |