@plexui/ui 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -763,7 +763,11 @@
763
763
  /* MDX sometimes wraps children in a <p> tag; we bypass it to keep flex layout */
764
764
  .ButtonInner> :global(p) {
765
765
  display: contents;
766
- }.ButtonLoader {
766
+ }/* `.Button > .ButtonLoader` (specificity 0,2,0) beats `.TransitionGroupChild`
767
+ * (0,1,0). TransitionGroup applies both classes to the same <span>, and
768
+ * TransitionGroup.module.css loads after Button.module.css — without the
769
+ * specificity bump, `display: block` wins and the spinner pins to top-left
770
+ * instead of centring. */.Button > .ButtonLoader {
767
771
  position: absolute;
768
772
  inset: 0;
769
773
  z-index: 3;
@@ -773,22 +777,22 @@
773
777
  pointer-events: none;
774
778
  }
775
779
 
776
- .ButtonLoader[data-entering] {
780
+ .Button > .ButtonLoader[data-entering] {
777
781
  opacity: 0;
778
782
  }
779
783
 
780
- .ButtonLoader[data-exiting] {
784
+ .Button > .ButtonLoader[data-exiting] {
781
785
  opacity: 1;
782
786
  }
783
787
 
784
- .ButtonLoader[data-entering-active],
785
- .ButtonLoader[data-entering][data-interrupted] {
788
+ .Button > .ButtonLoader[data-entering-active],
789
+ .Button > .ButtonLoader[data-entering][data-interrupted] {
786
790
  opacity: 1;
787
791
  transition: opacity 0.15s ease 0.1s;
788
792
  }
789
793
 
790
- .ButtonLoader[data-exiting-active],
791
- .ButtonLoader[data-exiting][data-interrupted] {
794
+ .Button > .ButtonLoader[data-exiting-active],
795
+ .Button > .ButtonLoader[data-exiting][data-interrupted] {
792
796
  opacity: 0;
793
797
  transition: opacity 0.15s ease;
794
798
  }/* .Kbd {
@@ -3,10 +3,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import clsx from "clsx";
4
4
  import { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
5
5
  import { mergeRefs } from "react-merge-refs";
6
- import { X } from "../Icon";
6
+ import { Eye, EyeOff, X } from "../Icon";
7
7
  import s from "./FloatingLabelInput.module.css";
8
8
  export const FloatingLabelInput = forwardRef(function FloatingLabelInput(props, ref) {
9
- const { label, description, errorMessage, "invalid": invalidProp, disabled = false, readOnly = false, onClear, onAutofill, allowAutofillExtensions = false, startAdornment, endAdornment, multiline = false, rows = 2, className, "id": idProp, name, type = "text", value, defaultValue, onChange, onFocus, onBlur, onAnimationStart, "aria-describedby": ariaDescribedByProp, ...restProps } = props;
9
+ const { label, description, errorMessage, "invalid": invalidProp, disabled = false, readOnly = false, onClear, onAutofill, allowAutofillExtensions = false, startAdornment, endAdornment, revealable: revealableProp, onRevealedChange, multiline = false, rows = 2, className, "id": idProp, name, type = "text", value, defaultValue, onChange, onFocus, onBlur, onAnimationStart, "aria-describedby": ariaDescribedByProp, ...restProps } = props;
10
10
  const inputRef = useRef(null);
11
11
  const generatedId = useId();
12
12
  const inputId = idProp || `floating-label-input-${generatedId}`;
@@ -16,6 +16,7 @@ export const FloatingLabelInput = forwardRef(function FloatingLabelInput(props,
16
16
  const [hasValue, setHasValue] = useState(() => {
17
17
  return !!(value !== undefined ? value : defaultValue);
18
18
  });
19
+ const [revealed, setRevealed] = useState(false);
19
20
  // Sync hasValue with controlled value prop
20
21
  useEffect(() => {
21
22
  if (value !== undefined) {
@@ -33,6 +34,11 @@ export const FloatingLabelInput = forwardRef(function FloatingLabelInput(props,
33
34
  const invalid = invalidProp ?? !!errorMessage;
34
35
  // Determine if clear button should be shown
35
36
  const showClearButton = !!onClear && hasValue && !disabled && !readOnly;
37
+ // Built-in password reveal toggle
38
+ const isPassword = type === "password";
39
+ const revealable = revealableProp ?? (isPassword && !endAdornment);
40
+ const showRevealButton = revealable && !disabled;
41
+ const effectiveType = isPassword && revealed ? "text" : type;
36
42
  // Merge aria-describedby with error id
37
43
  const ariaDescribedBy = [ariaDescribedByProp, errorId, descriptionId].filter(Boolean).join(" ") || undefined;
38
44
  // Handle clicks on the container to focus the input
@@ -75,9 +81,16 @@ export const FloatingLabelInput = forwardRef(function FloatingLabelInput(props,
75
81
  onAutofill?.();
76
82
  }
77
83
  }, [onAnimationStart, onAutofill]);
84
+ const handleRevealToggle = useCallback(() => {
85
+ setRevealed((prev) => {
86
+ const next = !prev;
87
+ onRevealedChange?.(next);
88
+ return next;
89
+ });
90
+ }, [onRevealedChange]);
78
91
  return (_jsxs("div", { className: clsx(s.Root, className), children: [_jsxs("div", { className: clsx(s.FieldFootprint, {
79
92
  [s.HasValue]: hasValue || !!startAdornment,
80
- }), "data-focused": focused ? "" : undefined, "data-has-value": hasValue ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-disabled": disabled ? "" : undefined, "data-readonly": readOnly ? "" : undefined, "data-multiline": multiline ? "" : undefined, onMouseDown: handleContainerMouseDown, children: [_jsx("label", { className: s.TypeableLabel, htmlFor: inputId, children: _jsx("div", { className: s.LabelPositioner, children: _jsx("div", { className: s.LabelText, children: label }) }) }), startAdornment && (_jsx("div", { className: s.StartAdornment, children: startAdornment })), multiline ? (_jsx("textarea", { ref: mergeRefs([ref, inputRef]), id: inputId, name: name, rows: rows, className: s.Textarea, value: value, defaultValue: defaultValue, disabled: disabled, readOnly: readOnly, "aria-invalid": invalid ? true : undefined, "aria-describedby": ariaDescribedBy, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur })) : (_jsx("input", { ...restProps, ref: mergeRefs([ref, inputRef]), id: inputId, name: name, type: type, className: s.Input, value: value, defaultValue: defaultValue, disabled: disabled, readOnly: readOnly, "aria-invalid": invalid ? true : undefined, "aria-describedby": ariaDescribedBy, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onAnimationStart: handleAnimationStart, "data-lpignore": allowAutofillExtensions ? undefined : true, "data-1p-ignore": allowAutofillExtensions ? undefined : true })), endAdornment && (_jsx("div", { className: s.EndAdornment, children: endAdornment })), showClearButton && (_jsx("button", { type: "button", "aria-label": "Clear input", className: s.ClearButton, onClick: onClear, children: _jsx(X, {}) }))] }), errorMessage && (_jsx("div", { id: errorId, className: s.ErrorMessage, role: "alert", children: errorMessage })), description && (_jsx("div", { id: descriptionId, className: s.Description, children: description }))] }));
93
+ }), "data-focused": focused ? "" : undefined, "data-has-value": hasValue ? "" : undefined, "data-invalid": invalid ? "" : undefined, "data-disabled": disabled ? "" : undefined, "data-readonly": readOnly ? "" : undefined, "data-multiline": multiline ? "" : undefined, onMouseDown: handleContainerMouseDown, children: [_jsx("label", { className: s.TypeableLabel, htmlFor: inputId, children: _jsx("div", { className: s.LabelPositioner, children: _jsx("div", { className: s.LabelText, children: label }) }) }), startAdornment && (_jsx("div", { className: s.StartAdornment, children: startAdornment })), multiline ? (_jsx("textarea", { ref: mergeRefs([ref, inputRef]), id: inputId, name: name, rows: rows, className: s.Textarea, value: value, defaultValue: defaultValue, disabled: disabled, readOnly: readOnly, "aria-invalid": invalid ? true : undefined, "aria-describedby": ariaDescribedBy, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur })) : (_jsx("input", { ...restProps, ref: mergeRefs([ref, inputRef]), id: inputId, name: name, type: effectiveType, className: s.Input, value: value, defaultValue: defaultValue, disabled: disabled, readOnly: readOnly, "aria-invalid": invalid ? true : undefined, "aria-describedby": ariaDescribedBy, onChange: handleChange, onFocus: handleFocus, onBlur: handleBlur, onAnimationStart: handleAnimationStart, "data-lpignore": allowAutofillExtensions ? undefined : true, "data-1p-ignore": allowAutofillExtensions ? undefined : true })), endAdornment && (_jsx("div", { className: s.EndAdornment, children: endAdornment })), showClearButton && (_jsx("button", { type: "button", "aria-label": "Clear input", className: s.ClearButton, onClick: onClear, children: _jsx(X, {}) })), showRevealButton && (_jsx("button", { type: "button", "aria-label": revealed ? "Hide" : "Show", className: s.RevealButton, onClick: handleRevealToggle, children: revealed ? _jsx(EyeOff, {}) : _jsx(Eye, {}) }))] }), errorMessage && (_jsx("div", { id: errorId, className: s.ErrorMessage, role: "alert", children: errorMessage })), description && (_jsx("div", { id: descriptionId, className: s.Description, children: description }))] }));
81
94
  });
82
95
  FloatingLabelInput.displayName = "FloatingLabelInput";
83
96
  //# sourceMappingURL=FloatingLabelInput.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"FloatingLabelInput.js","sourceRoot":"","sources":["../../../../src/components/FloatingLabelInput/FloatingLabelInput.tsx"],"names":[],"mappings":"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","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from \"react\"\nimport { mergeRefs } from \"react-merge-refs\"\n\nimport { X } from \"../Icon\"\n\nimport s from \"./FloatingLabelInput.module.css\"\n\nexport type FloatingLabelInputProps = {\n /**\n * Label text for the floating label\n */\n label: string\n /**\n * Error message to display below the input\n */\n errorMessage?: string\n /**\n * Mark the input as invalid\n * @default false (or true if errorMessage is provided)\n */\n invalid?: boolean\n /**\n * Disables the input visually and from interactions\n * @default false\n */\n disabled?: boolean\n /**\n * Makes the input read-only\n * @default false\n */\n readOnly?: boolean\n /**\n * Callback invoked when the clear button is clicked\n */\n onClear?: () => void\n /**\n * Callback invoked when the input is autofilled by the browser\n */\n onAutofill?: () => void\n /**\n * Allow autofill extensions to appear in the input\n * @default false\n */\n allowAutofillExtensions?: boolean\n /**\n * Helper/description text displayed below the input.\n * Shown alongside the error message when both are present.\n */\n description?: React.ReactNode\n /**\n * Content rendered after the input, before the clear button.\n * Useful for toggle buttons (e.g. password visibility), icons, or other interactive elements.\n */\n startAdornment?: React.ReactNode\n endAdornment?: React.ReactNode\n /**\n * Render as a multi-line textarea instead of a single-line input.\n * @default false\n */\n multiline?: boolean\n /**\n * Number of visible text lines when `multiline` is true.\n * @default 2\n */\n rows?: number\n} & React.InputHTMLAttributes<HTMLInputElement>\n\nexport const FloatingLabelInput = forwardRef<HTMLInputElement, FloatingLabelInputProps>(\n function FloatingLabelInput(props, ref) {\n const {\n label,\n description,\n errorMessage,\n \"invalid\": invalidProp,\n disabled = false,\n readOnly = false,\n onClear,\n onAutofill,\n allowAutofillExtensions = false,\n startAdornment,\n endAdornment,\n multiline = false,\n rows = 2,\n className,\n \"id\": idProp,\n name,\n type = \"text\",\n value,\n defaultValue,\n onChange,\n onFocus,\n onBlur,\n onAnimationStart,\n \"aria-describedby\": ariaDescribedByProp,\n ...restProps\n } = props\n\n const inputRef = useRef<HTMLInputElement | null>(null)\n const generatedId = useId()\n const inputId = idProp || `floating-label-input-${generatedId}`\n const descriptionId = description ? `${inputId}-description` : undefined\n const errorId = errorMessage ? `${inputId}-error` : undefined\n\n const [focused, setFocused] = useState(false)\n const [hasValue, setHasValue] = useState(() => {\n return !!(value !== undefined ? value : defaultValue)\n })\n\n // Sync hasValue with controlled value prop\n useEffect(() => {\n if (value !== undefined) {\n setHasValue(!!value)\n }\n }, [value])\n\n const autoResize = useCallback(() => {\n const el = inputRef.current\n if (!multiline || !el) return\n el.style.height = \"auto\"\n el.style.height = `${el.scrollHeight}px`\n }, [multiline])\n\n useLayoutEffect(autoResize, [autoResize])\n\n const invalid = invalidProp ?? !!errorMessage\n\n // Determine if clear button should be shown\n const showClearButton = !!onClear && hasValue && !disabled && !readOnly\n\n // Merge aria-describedby with error id\n const ariaDescribedBy =\n [ariaDescribedByProp, errorId, descriptionId].filter(Boolean).join(\" \") || undefined\n\n // Handle clicks on the container to focus the input\n const handleContainerMouseDown = useCallback((evt: React.MouseEvent<HTMLDivElement>) => {\n const input = inputRef.current\n if (!evt.target || !(evt.target instanceof Element) || !input) {\n return\n }\n if (input.contains(evt.target)) {\n return\n }\n if (evt.target.closest(\"button, [type='button'], [role='button']\")) {\n return\n }\n evt.preventDefault()\n if (document.activeElement !== input) {\n input.focus()\n }\n const length = input.value.length\n input.setSelectionRange(length, length)\n }, [])\n\n const handleFocus = useCallback(\n (evt: React.FocusEvent<HTMLInputElement>) => {\n setFocused(true)\n onFocus?.(evt)\n },\n [onFocus],\n )\n\n const handleBlur = useCallback(\n (evt: React.FocusEvent<HTMLInputElement>) => {\n setFocused(false)\n onBlur?.(evt)\n },\n [onBlur],\n )\n\n const handleChange = useCallback(\n (evt: React.ChangeEvent<HTMLInputElement>) => {\n setHasValue(!!evt.currentTarget.value)\n onChange?.(evt)\n autoResize()\n },\n [onChange, autoResize],\n )\n\n const handleAnimationStart = useCallback(\n (evt: React.AnimationEvent<HTMLInputElement>) => {\n onAnimationStart?.(evt)\n // Detect browser autofill\n if (evt.animationName === \"native-autofill-in\") {\n setHasValue(true)\n onAutofill?.()\n }\n },\n [onAnimationStart, onAutofill],\n )\n\n return (\n <div className={clsx(s.Root, className)}>\n <div\n className={clsx(s.FieldFootprint, {\n [s.HasValue]: hasValue || !!startAdornment,\n })}\n data-focused={focused ? \"\" : undefined}\n data-has-value={hasValue ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-multiline={multiline ? \"\" : undefined}\n onMouseDown={handleContainerMouseDown}\n >\n <label className={s.TypeableLabel} htmlFor={inputId}>\n <div className={s.LabelPositioner}>\n <div className={s.LabelText}>{label}</div>\n </div>\n </label>\n {startAdornment && (\n <div className={s.StartAdornment}>{startAdornment}</div>\n )}\n {multiline ? (\n <textarea\n ref={mergeRefs([ref, inputRef]) as React.Ref<HTMLTextAreaElement>}\n id={inputId}\n name={name}\n rows={rows}\n className={s.Textarea}\n value={value}\n defaultValue={defaultValue}\n disabled={disabled}\n readOnly={readOnly}\n aria-invalid={invalid ? true : undefined}\n aria-describedby={ariaDescribedBy}\n onChange={handleChange as unknown as React.ChangeEventHandler<HTMLTextAreaElement>}\n onFocus={handleFocus as unknown as React.FocusEventHandler<HTMLTextAreaElement>}\n onBlur={handleBlur as unknown as React.FocusEventHandler<HTMLTextAreaElement>}\n />\n ) : (\n <input\n {...restProps}\n ref={mergeRefs([ref, inputRef])}\n id={inputId}\n name={name}\n type={type}\n className={s.Input}\n value={value}\n defaultValue={defaultValue}\n disabled={disabled}\n readOnly={readOnly}\n aria-invalid={invalid ? true : undefined}\n aria-describedby={ariaDescribedBy}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onAnimationStart={handleAnimationStart}\n data-lpignore={allowAutofillExtensions ? undefined : true}\n data-1p-ignore={allowAutofillExtensions ? undefined : true}\n />\n )}\n {endAdornment && (\n <div className={s.EndAdornment}>\n {endAdornment}\n </div>\n )}\n {showClearButton && (\n <button\n type=\"button\"\n aria-label=\"Clear input\"\n className={s.ClearButton}\n onClick={onClear}\n >\n <X />\n </button>\n )}\n </div>\n {errorMessage && (\n <div id={errorId} className={s.ErrorMessage} role=\"alert\">\n {errorMessage}\n </div>\n )}\n {description && (\n <div id={descriptionId} className={s.Description}>\n {description}\n </div>\n )}\n </div>\n )\n },\n)\n\nFloatingLabelInput.displayName = \"FloatingLabelInput\"\n"]}
1
+ {"version":3,"file":"FloatingLabelInput.js","sourceRoot":"","sources":["../../../../src/components/FloatingLabelInput/FloatingLabelInput.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACpG,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAE5C,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,SAAS,CAAA;AAExC,OAAO,CAAC,MAAM,iCAAiC,CAAA;AA8E/C,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAC1C,SAAS,kBAAkB,CAAC,KAAK,EAAE,GAAG;IACpC,MAAM,EACJ,KAAK,EACL,WAAW,EACX,YAAY,EACZ,SAAS,EAAE,WAAW,EACtB,QAAQ,GAAG,KAAK,EAChB,QAAQ,GAAG,KAAK,EAChB,OAAO,EACP,UAAU,EACV,uBAAuB,GAAG,KAAK,EAC/B,cAAc,EACd,YAAY,EACZ,UAAU,EAAE,cAAc,EAC1B,gBAAgB,EAChB,SAAS,GAAG,KAAK,EACjB,IAAI,GAAG,CAAC,EACR,SAAS,EACT,IAAI,EAAE,MAAM,EACZ,IAAI,EACJ,IAAI,GAAG,MAAM,EACb,KAAK,EACL,YAAY,EACZ,QAAQ,EACR,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,kBAAkB,EAAE,mBAAmB,EACvC,GAAG,SAAS,EACb,GAAG,KAAK,CAAA;IAET,MAAM,QAAQ,GAAG,MAAM,CAA0B,IAAI,CAAC,CAAA;IACtD,MAAM,WAAW,GAAG,KAAK,EAAE,CAAA;IAC3B,MAAM,OAAO,GAAG,MAAM,IAAI,wBAAwB,WAAW,EAAE,CAAA;IAC/D,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,OAAO,cAAc,CAAC,CAAC,CAAC,SAAS,CAAA;IACxE,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,OAAO,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;IAE7D,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,GAAG,EAAE;QAC5C,OAAO,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,CAAA;IACvD,CAAC,CAAC,CAAA;IACF,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;IAE/C,2CAA2C;IAC3C,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;QACtB,CAAC;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAA;IAEX,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;QAClC,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAA;QAC3B,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE;YAAE,OAAM;QAC7B,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAA;QACxB,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,IAAI,CAAA;IAC1C,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAA;IAEf,eAAe,CAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAC,CAAA;IAEzC,MAAM,OAAO,GAAG,WAAW,IAAI,CAAC,CAAC,YAAY,CAAA;IAE7C,4CAA4C;IAC5C,MAAM,eAAe,GAAG,CAAC,CAAC,OAAO,IAAI,QAAQ,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAA;IAEvE,kCAAkC;IAClC,MAAM,UAAU,GAAG,IAAI,KAAK,UAAU,CAAA;IACtC,MAAM,UAAU,GAAG,cAAc,IAAI,CAAC,UAAU,IAAI,CAAC,YAAY,CAAC,CAAA;IAClE,MAAM,gBAAgB,GAAG,UAAU,IAAI,CAAC,QAAQ,CAAA;IAChD,MAAM,aAAa,GAAG,UAAU,IAAI,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;IAE5D,uCAAuC;IACvC,MAAM,eAAe,GACnB,CAAC,mBAAmB,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAA;IAEtF,oDAAoD;IACpD,MAAM,wBAAwB,GAAG,WAAW,CAAC,CAAC,GAAqC,EAAE,EAAE;QACrF,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAA;QAC9B,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,YAAY,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;YAC9D,OAAM;QACR,CAAC;QACD,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/B,OAAM;QACR,CAAC;QACD,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,0CAA0C,CAAC,EAAE,CAAC;YACnE,OAAM;QACR,CAAC;QACD,GAAG,CAAC,cAAc,EAAE,CAAA;QACpB,IAAI,QAAQ,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;YACrC,KAAK,CAAC,KAAK,EAAE,CAAA;QACf,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA;QACjC,KAAK,CAAC,iBAAiB,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,CAAC,EAAE,EAAE,CAAC,CAAA;IAEN,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,GAAuC,EAAE,EAAE;QAC1C,UAAU,CAAC,IAAI,CAAC,CAAA;QAChB,OAAO,EAAE,CAAC,GAAG,CAAC,CAAA;IAChB,CAAC,EACD,CAAC,OAAO,CAAC,CACV,CAAA;IAED,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,GAAuC,EAAE,EAAE;QAC1C,UAAU,CAAC,KAAK,CAAC,CAAA;QACjB,MAAM,EAAE,CAAC,GAAG,CAAC,CAAA;IACf,CAAC,EACD,CAAC,MAAM,CAAC,CACT,CAAA;IAED,MAAM,YAAY,GAAG,WAAW,CAC9B,CAAC,GAAwC,EAAE,EAAE;QAC3C,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;QACtC,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAA;QACf,UAAU,EAAE,CAAA;IACd,CAAC,EACD,CAAC,QAAQ,EAAE,UAAU,CAAC,CACvB,CAAA;IAED,MAAM,oBAAoB,GAAG,WAAW,CACtC,CAAC,GAA2C,EAAE,EAAE;QAC9C,gBAAgB,EAAE,CAAC,GAAG,CAAC,CAAA;QACvB,0BAA0B;QAC1B,IAAI,GAAG,CAAC,aAAa,KAAK,oBAAoB,EAAE,CAAC;YAC/C,WAAW,CAAC,IAAI,CAAC,CAAA;YACjB,UAAU,EAAE,EAAE,CAAA;QAChB,CAAC;IACH,CAAC,EACD,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAC/B,CAAA;IAED,MAAM,kBAAkB,GAAG,WAAW,CAAC,GAAG,EAAE;QAC1C,WAAW,CAAC,CAAC,IAAI,EAAE,EAAE;YACnB,MAAM,IAAI,GAAG,CAAC,IAAI,CAAA;YAClB,gBAAgB,EAAE,CAAC,IAAI,CAAC,CAAA;YACxB,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;IACJ,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC,CAAA;IAEtB,OAAO,CACL,eAAK,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,SAAS,CAAC,aACrC,eACE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,cAAc,EAAE;oBAChC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,QAAQ,IAAI,CAAC,CAAC,cAAc;iBAC3C,CAAC,kBACY,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,oBACtB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,kBAC3B,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,mBACvB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,mBACzB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,oBACxB,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,EAC1C,WAAW,EAAE,wBAAwB,aAErC,gBAAO,SAAS,EAAE,CAAC,CAAC,aAAa,EAAE,OAAO,EAAE,OAAO,YACjD,cAAK,SAAS,EAAE,CAAC,CAAC,eAAe,YAC/B,cAAK,SAAS,EAAE,CAAC,CAAC,SAAS,YAAG,KAAK,GAAO,GACtC,GACA,EACP,cAAc,IAAI,CACjB,cAAK,SAAS,EAAE,CAAC,CAAC,cAAc,YAAG,cAAc,GAAO,CACzD,EACA,SAAS,CAAC,CAAC,CAAC,CACX,mBACE,GAAG,EAAE,SAAS,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAmC,EACjE,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,CAAC,CAAC,QAAQ,EACrB,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,kBACJ,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,sBACtB,eAAe,EACjC,QAAQ,EAAE,YAAwE,EAClF,OAAO,EAAE,WAAsE,EAC/E,MAAM,EAAE,UAAqE,GAC7E,CACH,CAAC,CAAC,CAAC,CACF,mBACM,SAAS,EACb,GAAG,EAAE,SAAS,CAAC,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,EAC/B,EAAE,EAAE,OAAO,EACX,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,aAAa,EACnB,SAAS,EAAE,CAAC,CAAC,KAAK,EAClB,KAAK,EAAE,KAAK,EACZ,YAAY,EAAE,YAAY,EAC1B,QAAQ,EAAE,QAAQ,EAClB,QAAQ,EAAE,QAAQ,kBACJ,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,sBACtB,eAAe,EACjC,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,UAAU,EAClB,gBAAgB,EAAE,oBAAoB,mBACvB,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,oBACzC,uBAAuB,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAC1D,CACH,EACA,YAAY,IAAI,CACf,cAAK,SAAS,EAAE,CAAC,CAAC,YAAY,YAC3B,YAAY,GACT,CACP,EACA,eAAe,IAAI,CAClB,iBACE,IAAI,EAAC,QAAQ,gBACF,aAAa,EACxB,SAAS,EAAE,CAAC,CAAC,WAAW,EACxB,OAAO,EAAE,OAAO,YAEhB,KAAC,CAAC,KAAG,GACE,CACV,EACA,gBAAgB,IAAI,CACnB,iBACE,IAAI,EAAC,QAAQ,gBACD,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EACtC,SAAS,EAAE,CAAC,CAAC,YAAY,EACzB,OAAO,EAAE,kBAAkB,YAE1B,QAAQ,CAAC,CAAC,CAAC,KAAC,MAAM,KAAG,CAAC,CAAC,CAAC,KAAC,GAAG,KAAG,GACzB,CACV,IACG,EACL,YAAY,IAAI,CACf,cAAK,EAAE,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,YAAY,EAAE,IAAI,EAAC,OAAO,YACtD,YAAY,GACT,CACP,EACA,WAAW,IAAI,CACd,cAAK,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC,CAAC,WAAW,YAC7C,WAAW,GACR,CACP,IACG,CACP,CAAA;AACH,CAAC,CACF,CAAA;AAED,kBAAkB,CAAC,WAAW,GAAG,oBAAoB,CAAA","sourcesContent":["\"use client\"\n\nimport clsx from \"clsx\"\nimport { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from \"react\"\nimport { mergeRefs } from \"react-merge-refs\"\n\nimport { Eye, EyeOff, X } from \"../Icon\"\n\nimport s from \"./FloatingLabelInput.module.css\"\n\nexport type FloatingLabelInputProps = {\n /**\n * Label text for the floating label\n */\n label: string\n /**\n * Error message to display below the input\n */\n errorMessage?: string\n /**\n * Mark the input as invalid\n * @default false (or true if errorMessage is provided)\n */\n invalid?: boolean\n /**\n * Disables the input visually and from interactions\n * @default false\n */\n disabled?: boolean\n /**\n * Makes the input read-only\n * @default false\n */\n readOnly?: boolean\n /**\n * Callback invoked when the clear button is clicked\n */\n onClear?: () => void\n /**\n * Callback invoked when the input is autofilled by the browser\n */\n onAutofill?: () => void\n /**\n * Allow autofill extensions to appear in the input\n * @default false\n */\n allowAutofillExtensions?: boolean\n /**\n * Helper/description text displayed below the input.\n * Shown alongside the error message when both are present.\n */\n description?: React.ReactNode\n /**\n * Content rendered before the input.\n */\n startAdornment?: React.ReactNode\n /**\n * Content rendered after the input, before the clear button.\n * For password fields the reveal toggle is built in — see `revealable`.\n */\n endAdornment?: React.ReactNode\n /**\n * Render a built-in reveal/hide toggle (eye icon) inside the field.\n * For `type=\"password\"` it swaps the input between `password` and `text`.\n * Defaults to `true` for password inputs that don't supply their own\n * `endAdornment`. Pass it explicitly to add the toggle to other sensitive\n * fields (e.g. a masked SSN) and react to it via `onRevealedChange`.\n * @default true when type=\"password\" and no endAdornment is provided\n */\n revealable?: boolean\n /**\n * Called with the new revealed state each time the reveal toggle is clicked.\n */\n onRevealedChange?: (revealed: boolean) => void\n /**\n * Render as a multi-line textarea instead of a single-line input.\n * @default false\n */\n multiline?: boolean\n /**\n * Number of visible text lines when `multiline` is true.\n * @default 2\n */\n rows?: number\n} & React.InputHTMLAttributes<HTMLInputElement>\n\nexport const FloatingLabelInput = forwardRef<HTMLInputElement, FloatingLabelInputProps>(\n function FloatingLabelInput(props, ref) {\n const {\n label,\n description,\n errorMessage,\n \"invalid\": invalidProp,\n disabled = false,\n readOnly = false,\n onClear,\n onAutofill,\n allowAutofillExtensions = false,\n startAdornment,\n endAdornment,\n revealable: revealableProp,\n onRevealedChange,\n multiline = false,\n rows = 2,\n className,\n \"id\": idProp,\n name,\n type = \"text\",\n value,\n defaultValue,\n onChange,\n onFocus,\n onBlur,\n onAnimationStart,\n \"aria-describedby\": ariaDescribedByProp,\n ...restProps\n } = props\n\n const inputRef = useRef<HTMLInputElement | null>(null)\n const generatedId = useId()\n const inputId = idProp || `floating-label-input-${generatedId}`\n const descriptionId = description ? `${inputId}-description` : undefined\n const errorId = errorMessage ? `${inputId}-error` : undefined\n\n const [focused, setFocused] = useState(false)\n const [hasValue, setHasValue] = useState(() => {\n return !!(value !== undefined ? value : defaultValue)\n })\n const [revealed, setRevealed] = useState(false)\n\n // Sync hasValue with controlled value prop\n useEffect(() => {\n if (value !== undefined) {\n setHasValue(!!value)\n }\n }, [value])\n\n const autoResize = useCallback(() => {\n const el = inputRef.current\n if (!multiline || !el) return\n el.style.height = \"auto\"\n el.style.height = `${el.scrollHeight}px`\n }, [multiline])\n\n useLayoutEffect(autoResize, [autoResize])\n\n const invalid = invalidProp ?? !!errorMessage\n\n // Determine if clear button should be shown\n const showClearButton = !!onClear && hasValue && !disabled && !readOnly\n\n // Built-in password reveal toggle\n const isPassword = type === \"password\"\n const revealable = revealableProp ?? (isPassword && !endAdornment)\n const showRevealButton = revealable && !disabled\n const effectiveType = isPassword && revealed ? \"text\" : type\n\n // Merge aria-describedby with error id\n const ariaDescribedBy =\n [ariaDescribedByProp, errorId, descriptionId].filter(Boolean).join(\" \") || undefined\n\n // Handle clicks on the container to focus the input\n const handleContainerMouseDown = useCallback((evt: React.MouseEvent<HTMLDivElement>) => {\n const input = inputRef.current\n if (!evt.target || !(evt.target instanceof Element) || !input) {\n return\n }\n if (input.contains(evt.target)) {\n return\n }\n if (evt.target.closest(\"button, [type='button'], [role='button']\")) {\n return\n }\n evt.preventDefault()\n if (document.activeElement !== input) {\n input.focus()\n }\n const length = input.value.length\n input.setSelectionRange(length, length)\n }, [])\n\n const handleFocus = useCallback(\n (evt: React.FocusEvent<HTMLInputElement>) => {\n setFocused(true)\n onFocus?.(evt)\n },\n [onFocus],\n )\n\n const handleBlur = useCallback(\n (evt: React.FocusEvent<HTMLInputElement>) => {\n setFocused(false)\n onBlur?.(evt)\n },\n [onBlur],\n )\n\n const handleChange = useCallback(\n (evt: React.ChangeEvent<HTMLInputElement>) => {\n setHasValue(!!evt.currentTarget.value)\n onChange?.(evt)\n autoResize()\n },\n [onChange, autoResize],\n )\n\n const handleAnimationStart = useCallback(\n (evt: React.AnimationEvent<HTMLInputElement>) => {\n onAnimationStart?.(evt)\n // Detect browser autofill\n if (evt.animationName === \"native-autofill-in\") {\n setHasValue(true)\n onAutofill?.()\n }\n },\n [onAnimationStart, onAutofill],\n )\n\n const handleRevealToggle = useCallback(() => {\n setRevealed((prev) => {\n const next = !prev\n onRevealedChange?.(next)\n return next\n })\n }, [onRevealedChange])\n\n return (\n <div className={clsx(s.Root, className)}>\n <div\n className={clsx(s.FieldFootprint, {\n [s.HasValue]: hasValue || !!startAdornment,\n })}\n data-focused={focused ? \"\" : undefined}\n data-has-value={hasValue ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-multiline={multiline ? \"\" : undefined}\n onMouseDown={handleContainerMouseDown}\n >\n <label className={s.TypeableLabel} htmlFor={inputId}>\n <div className={s.LabelPositioner}>\n <div className={s.LabelText}>{label}</div>\n </div>\n </label>\n {startAdornment && (\n <div className={s.StartAdornment}>{startAdornment}</div>\n )}\n {multiline ? (\n <textarea\n ref={mergeRefs([ref, inputRef]) as React.Ref<HTMLTextAreaElement>}\n id={inputId}\n name={name}\n rows={rows}\n className={s.Textarea}\n value={value}\n defaultValue={defaultValue}\n disabled={disabled}\n readOnly={readOnly}\n aria-invalid={invalid ? true : undefined}\n aria-describedby={ariaDescribedBy}\n onChange={handleChange as unknown as React.ChangeEventHandler<HTMLTextAreaElement>}\n onFocus={handleFocus as unknown as React.FocusEventHandler<HTMLTextAreaElement>}\n onBlur={handleBlur as unknown as React.FocusEventHandler<HTMLTextAreaElement>}\n />\n ) : (\n <input\n {...restProps}\n ref={mergeRefs([ref, inputRef])}\n id={inputId}\n name={name}\n type={effectiveType}\n className={s.Input}\n value={value}\n defaultValue={defaultValue}\n disabled={disabled}\n readOnly={readOnly}\n aria-invalid={invalid ? true : undefined}\n aria-describedby={ariaDescribedBy}\n onChange={handleChange}\n onFocus={handleFocus}\n onBlur={handleBlur}\n onAnimationStart={handleAnimationStart}\n data-lpignore={allowAutofillExtensions ? undefined : true}\n data-1p-ignore={allowAutofillExtensions ? undefined : true}\n />\n )}\n {endAdornment && (\n <div className={s.EndAdornment}>\n {endAdornment}\n </div>\n )}\n {showClearButton && (\n <button\n type=\"button\"\n aria-label=\"Clear input\"\n className={s.ClearButton}\n onClick={onClear}\n >\n <X />\n </button>\n )}\n {showRevealButton && (\n <button\n type=\"button\"\n aria-label={revealed ? \"Hide\" : \"Show\"}\n className={s.RevealButton}\n onClick={handleRevealToggle}\n >\n {revealed ? <EyeOff /> : <Eye />}\n </button>\n )}\n </div>\n {errorMessage && (\n <div id={errorId} className={s.ErrorMessage} role=\"alert\">\n {errorMessage}\n </div>\n )}\n {description && (\n <div id={descriptionId} className={s.Description}>\n {description}\n </div>\n )}\n </div>\n )\n },\n)\n\nFloatingLabelInput.displayName = \"FloatingLabelInput\"\n"]}
@@ -125,6 +125,29 @@
125
125
  transition: color 150ms ease;
126
126
  }.ClearButton:hover {
127
127
  color: var(--color-text);
128
+ }/* Password reveal toggle — circular ghost button hugging the right edge */.RevealButton {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ flex-shrink: 0;
133
+ width: 32px;
134
+ height: 32px;
135
+ margin: 0 -10px 0 0;
136
+ padding: 0;
137
+ border: none;
138
+ border-radius: 50%;
139
+ background-color: transparent;
140
+ color: var(--color-text-tertiary);
141
+ font-size: 1rem;
142
+ cursor: pointer;
143
+ position: relative;
144
+ z-index: 1;
145
+ transition:
146
+ color 150ms ease,
147
+ background-color 150ms ease;
148
+ }.RevealButton:hover {
149
+ color: var(--color-text);
150
+ background-color: var(--color-background-secondary-ghost-hover);
128
151
  }/* End adornment slot */.EndAdornment {
129
152
  display: flex;
130
153
  align-items: center;
@@ -41,11 +41,27 @@ export type FloatingLabelInputProps = {
41
41
  */
42
42
  description?: React.ReactNode;
43
43
  /**
44
- * Content rendered after the input, before the clear button.
45
- * Useful for toggle buttons (e.g. password visibility), icons, or other interactive elements.
44
+ * Content rendered before the input.
46
45
  */
47
46
  startAdornment?: React.ReactNode;
47
+ /**
48
+ * Content rendered after the input, before the clear button.
49
+ * For password fields the reveal toggle is built in — see `revealable`.
50
+ */
48
51
  endAdornment?: React.ReactNode;
52
+ /**
53
+ * Render a built-in reveal/hide toggle (eye icon) inside the field.
54
+ * For `type="password"` it swaps the input between `password` and `text`.
55
+ * Defaults to `true` for password inputs that don't supply their own
56
+ * `endAdornment`. Pass it explicitly to add the toggle to other sensitive
57
+ * fields (e.g. a masked SSN) and react to it via `onRevealedChange`.
58
+ * @default true when type="password" and no endAdornment is provided
59
+ */
60
+ revealable?: boolean;
61
+ /**
62
+ * Called with the new revealed state each time the reveal toggle is clicked.
63
+ */
64
+ onRevealedChange?: (revealed: boolean) => void;
49
65
  /**
50
66
  * Render as a multi-line textarea instead of a single-line input.
51
67
  * @default false
@@ -100,11 +116,27 @@ export declare const FloatingLabelInput: import("react").ForwardRefExoticCompone
100
116
  */
101
117
  description?: React.ReactNode;
102
118
  /**
103
- * Content rendered after the input, before the clear button.
104
- * Useful for toggle buttons (e.g. password visibility), icons, or other interactive elements.
119
+ * Content rendered before the input.
105
120
  */
106
121
  startAdornment?: React.ReactNode;
122
+ /**
123
+ * Content rendered after the input, before the clear button.
124
+ * For password fields the reveal toggle is built in — see `revealable`.
125
+ */
107
126
  endAdornment?: React.ReactNode;
127
+ /**
128
+ * Render a built-in reveal/hide toggle (eye icon) inside the field.
129
+ * For `type="password"` it swaps the input between `password` and `text`.
130
+ * Defaults to `true` for password inputs that don't supply their own
131
+ * `endAdornment`. Pass it explicitly to add the toggle to other sensitive
132
+ * fields (e.g. a masked SSN) and react to it via `onRevealedChange`.
133
+ * @default true when type="password" and no endAdornment is provided
134
+ */
135
+ revealable?: boolean;
136
+ /**
137
+ * Called with the new revealed state each time the reveal toggle is clicked.
138
+ */
139
+ onRevealedChange?: (revealed: boolean) => void;
108
140
  /**
109
141
  * Render as a multi-line textarea instead of a single-line input.
110
142
  * @default false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plexui/ui",
3
- "version": "0.17.1",
3
+ "version": "0.18.1",
4
4
  "description": "Modern design system for building high-quality applications",
5
5
  "type": "module",
6
6
  "license": "MIT",