fix: reveal SecretInput controls on hover

This commit is contained in:
Marco Beretta 2026-05-06 16:06:41 +02:00
parent d0fc0ed941
commit c5bb1446b4
No known key found for this signature in database
GPG key ID: D918033D8E74CC11
10 changed files with 27 additions and 3 deletions

View file

@ -97,6 +97,7 @@ export default function MCPConfigDialog({
autoComplete="new-password"
data-lpignore="true"
data-1p-ignore="true"
controlsOnHover
placeholder={localize('com_ui_mcp_enter_var', { 0: details.title })}
className="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white sm:text-sm"
/>

View file

@ -50,6 +50,7 @@ const InputWithLabel: FC<InputWithLabelProps> = forwardRef((props, ref) => {
autoComplete="new-password"
data-lpignore="true"
data-1p-ignore="true"
controlsOnHover
placeholder={`${localize('com_endpoint_config_value')} ${label}`}
className={cn('flex h-10 max-h-10 w-full resize-none px-3 py-2', inputClassName)}
/>

View file

@ -91,6 +91,7 @@ function AuthField({ name, config, hasValue, control, errors, autoFocus }: AuthF
autoComplete="new-password"
data-lpignore="true"
data-1p-ignore="true"
controlsOnHover
/* autoFocus is generally disabled due to the fact that it can disorient users,
* but in this case, the required field would logically be immediately navigated to anyways, and the component's
* functionality emulates that of a new modal opening, where users would expect focus to be shifted to the new content */

View file

@ -40,6 +40,7 @@ export const QRPhase: React.FC<QRPhaseProps> = ({ secret, otpauthUrl, onNext })
value={secret}
readOnly
showCopy
controlsOnHover
aria-label={localize('com_ui_secret_key')}
className="font-mono text-lg tracking-wider"
/>

View file

@ -109,6 +109,7 @@ function CreateKeyDialog({ onKeyCreated }: { onKeyCreated?: () => void }) {
value={newKey}
readOnly
showCopy
controlsOnHover
onCopy={handleCopy}
aria-label={localize('com_ui_your_api_key')}
className="font-mono text-sm"

View file

@ -54,6 +54,7 @@ function PluginAuthForm({ plugin, onSubmit, isEntityTool }: TPluginAuthFormProps
autoComplete="new-password"
data-lpignore="true"
data-1p-ignore="true"
controlsOnHover
id={authField}
aria-invalid={!!errors[authField]}
aria-describedby={`${authField}-error`}

View file

@ -83,6 +83,7 @@ export default function InputSection({
autoComplete="one-time-code"
data-lpignore="true"
data-1p-ignore="true"
controlsOnHover
className="mb-2"
{...register(name as keyof SearchApiKeyFormData)}
/>

View file

@ -193,6 +193,7 @@ const ApiKey = () => {
<SecretInput
placeholder="<HIDDEN>"
autoComplete="new-password"
controlsOnHover
className={inputClasses}
{...register('api_key', { required: type === AuthTypeEnum.ServiceHttp })}
/>
@ -311,6 +312,7 @@ const OAuth = () => {
<SecretInput
placeholder="<HIDDEN>"
autoComplete="new-password"
controlsOnHover
className={inputClasses}
{...register('oauth_client_id', { required: false })}
/>
@ -318,6 +320,7 @@ const OAuth = () => {
<SecretInput
placeholder="<HIDDEN>"
autoComplete="new-password"
controlsOnHover
className={inputClasses}
{...register('oauth_client_secret', { required: false })}
/>

View file

@ -105,7 +105,12 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
<Label htmlFor="api_key" className="text-sm font-medium">
{localize('com_ui_api_key')}
</Label>
<SecretInput id="api_key" placeholder="sk-..." {...register('auth.api_key')} />
<SecretInput
id="api_key"
placeholder="sk-..."
controlsOnHover
{...register('auth.api_key')}
/>
</div>
)}
@ -165,6 +170,7 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
autoComplete="new-password"
data-lpignore="true"
data-1p-ignore="true"
controlsOnHover
placeholder={isEditMode ? localize('com_ui_leave_blank_to_keep') : ''}
aria-invalid={errors.auth?.oauth_client_id ? 'true' : 'false'}
aria-describedby={
@ -190,6 +196,7 @@ export default function AuthSection({ isEditMode, serverName }: AuthSectionProps
<SecretInput
id="oauth_client_secret"
placeholder={isEditMode ? localize('com_ui_leave_blank_to_keep') : ''}
controlsOnHover
{...register('auth.oauth_client_secret')}
/>
</div>

View file

@ -16,6 +16,7 @@ export interface SecretInputProps
containerClassName?: string;
controlsClassName?: string;
buttonClassName?: string;
controlsOnHover?: boolean;
}
const SecretInput = React.forwardRef<HTMLInputElement, SecretInputProps>(
@ -29,6 +30,7 @@ const SecretInput = React.forwardRef<HTMLInputElement, SecretInputProps>(
containerClassName,
controlsClassName,
buttonClassName,
controlsOnHover = false,
onCopy,
copyFeedbackDuration = 2000,
disabled,
@ -68,7 +70,7 @@ const SecretInput = React.forwardRef<HTMLInputElement, SecretInputProps>(
}, [value, isCopied, disabled, onCopy, copyFeedbackDuration]);
return (
<div className={cn('relative flex items-center', containerClassName)}>
<div className={cn('group/secret-input relative flex items-center', containerClassName)}>
<input
id={id}
type={isVisible ? 'text' : 'password'}
@ -90,7 +92,12 @@ const SecretInput = React.forwardRef<HTMLInputElement, SecretInputProps>(
</label>
)}
<div
className={cn('absolute inset-y-0 right-2 flex items-center gap-0.5', controlsClassName)}
className={cn(
'absolute inset-y-0 right-2 flex items-center gap-0.5',
controlsOnHover &&
'pointer-events-none opacity-0 transition-opacity duration-150 group-focus-within/secret-input:pointer-events-auto group-focus-within/secret-input:opacity-100 group-hover/secret-input:pointer-events-auto group-hover/secret-input:opacity-100',
controlsClassName,
)}
>
{showCopy && (
<button