feat(07-02): add tooltipText to registry fields + render ⓘ in FieldRenderer and PasswordField

- Populate tooltipText for azureblob.sas_url, azureblob.key, and onedrive.token in registry
- Add tooltipText prop and showTooltip state to PasswordField with toggle button + panel
- Add showTooltip state to FieldRenderer; pass tooltipText to PasswordField for password branch
- Add ⓘ button + panel for text/select branches in FieldRenderer
- Use sr-only span for accessible name to avoid getByLabelText collision
This commit is contained in:
2026-03-31 09:55:09 +02:00
parent 057ba80b68
commit 9aae9e33bd
3 changed files with 72 additions and 12 deletions
+46 -8
View File
@@ -1,3 +1,4 @@
import { useState } from 'react';
import type { UseFormRegister, FieldError } from 'react-hook-form';
import type { FieldDef } from '../../schemas/registry';
import { PasswordField } from './PasswordField';
@@ -9,6 +10,8 @@ interface FieldRendererProps {
}
export function FieldRenderer({ field, register, error }: FieldRendererProps) {
const [showTooltip, setShowTooltip] = useState(false);
// provider field: single-option select — hide from UI, auto-register with default value
if (field.key === 'provider' && field.options?.length === 1) {
return (
@@ -29,6 +32,7 @@ export function FieldRenderer({ field, register, error }: FieldRendererProps) {
registration={register(field.key)}
placeholder={field.placeholder}
helpText={field.helpText}
tooltipText={field.tooltipText}
/>
);
}
@@ -36,10 +40,27 @@ export function FieldRenderer({ field, register, error }: FieldRendererProps) {
if (field.inputType === 'select' && field.options) {
return (
<div className="flex flex-col gap-1">
<label htmlFor={field.key} className="text-sm font-medium text-gray-700">
{field.label}
{field.required && <span className="ml-1 text-red-500">*</span>}
</label>
<div className="flex items-center gap-1">
<label htmlFor={field.key} className="text-sm font-medium text-gray-700">
{field.label}
{field.required && <span className="ml-1 text-red-500">*</span>}
</label>
{field.tooltipText && (
<button
type="button"
onClick={() => setShowTooltip(v => !v)}
aria-label={`More info about ${field.label}`}
className="text-blue-500 hover:text-blue-700 text-xs leading-none"
>
</button>
)}
</div>
{field.tooltipText && showTooltip && (
<p className="text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1.5 mt-1">
{field.tooltipText}
</p>
)}
<select
id={field.key}
className={[
@@ -61,10 +82,27 @@ export function FieldRenderer({ field, register, error }: FieldRendererProps) {
// text (default)
return (
<div className="flex flex-col gap-1">
<label htmlFor={field.key} className="text-sm font-medium text-gray-700">
{field.label}
{field.required && <span className="ml-1 text-red-500">*</span>}
</label>
<div className="flex items-center gap-1">
<label htmlFor={field.key} className="text-sm font-medium text-gray-700">
{field.label}
{field.required && <span className="ml-1 text-red-500">*</span>}
</label>
{field.tooltipText && (
<button
type="button"
onClick={() => setShowTooltip(v => !v)}
className="text-blue-500 hover:text-blue-700 text-xs leading-none"
>
<span className="sr-only">More info about {field.label}</span>
<span aria-hidden="true"></span>
</button>
)}
</div>
{field.tooltipText && showTooltip && (
<p className="text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1.5 mt-1">
{field.tooltipText}
</p>
)}
<input
id={field.key}
type="text"
+23 -4
View File
@@ -8,15 +8,34 @@ interface PasswordFieldProps {
registration: UseFormRegisterReturn;
placeholder?: string;
helpText?: string;
tooltipText?: string;
}
export function PasswordField({ id, label, error, registration, placeholder, helpText }: PasswordFieldProps) {
export function PasswordField({ id, label, error, registration, placeholder, helpText, tooltipText }: PasswordFieldProps) {
const [show, setShow] = useState(false);
const [showTooltip, setShowTooltip] = useState(false);
return (
<div className="flex flex-col gap-1">
<label htmlFor={id} className="text-sm font-medium text-gray-700">
{label}
</label>
<div className="flex items-center gap-1">
<label htmlFor={id} className="text-sm font-medium text-gray-700">
{label}
</label>
{tooltipText && (
<button
type="button"
onClick={() => setShowTooltip(v => !v)}
className="text-blue-500 hover:text-blue-700 text-xs leading-none"
>
<span className="sr-only">More info about {label}</span>
<span aria-hidden="true"></span>
</button>
)}
</div>
{tooltipText && showTooltip && (
<p className="text-xs text-blue-700 bg-blue-50 border border-blue-200 rounded px-2 py-1.5 mt-1">
{tooltipText}
</p>
)}
<div className="relative">
<input
id={id}