docs(09): create gap closure plan for tooltip hover and icon placement
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -66,12 +66,13 @@ Plans:
|
||||
3. Backend selection cards and output blocks display MD3 elevation with tonal surface tint and consistent shape tokens
|
||||
4. Step indicator shows numbered circles connected by lines, with checkmarks on completed steps, highlight on current, and muted future steps
|
||||
5. FieldRenderer produces identical `aria-label` attributes for both text-branch and select-branch inputs
|
||||
**Plans:** 4/4 plans complete
|
||||
**Plans:** 5 plans (4 complete + 1 gap closure)
|
||||
Plans:
|
||||
- [x] 09-01-PLAN.md — TextFieldMD3 component, MD3 button constants, DEBT-01 aria-label fix
|
||||
- [x] 09-02-PLAN.md — StepIndicator rebuild with MD3 circles and connectors
|
||||
- [x] 09-03-PLAN.md — Integration: wire TextFieldMD3 + button styles + elevation into all wizard steps
|
||||
- [ ] 09-04-PLAN.md — Gap closure: Replace BackendSelectionStep plain input with TextFieldMD3
|
||||
- [ ] 09-05-PLAN.md — Gap closure: Tooltip hover behavior + icon placement beside helpText
|
||||
|
||||
### Phase 10: Content & Clarity
|
||||
**Goal**: A first-time visitor understands what Ready2Blob does and what each wizard step expects without external documentation
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
---
|
||||
phase: 09-md3-components
|
||||
plan: 05
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: ["09-04"]
|
||||
files_modified:
|
||||
- src/components/ui/TextFieldMD3.tsx
|
||||
- src/components/ui/TextFieldMD3.test.tsx
|
||||
- src/components/ui/FieldRenderer.tsx
|
||||
- src/components/ui/FieldRenderer.test.tsx
|
||||
- src/components/ui/PasswordField.tsx
|
||||
autonomous: true
|
||||
gap_closure: true
|
||||
requirements: [COMP-04]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Hovering the tooltip info button reveals tooltip text without clicking"
|
||||
- "Clicking the tooltip info button pins it open; clicking again dismisses it"
|
||||
- "Tooltip icon appears beside the helpText below the field, not above the field"
|
||||
artifacts:
|
||||
- path: "src/components/ui/TextFieldMD3.tsx"
|
||||
provides: "helpTextPrefix prop for rendering inline content left of helpText"
|
||||
contains: "helpTextPrefix"
|
||||
- path: "src/components/ui/FieldRenderer.tsx"
|
||||
provides: "Tooltip icon passed as helpTextPrefix, hover+click behavior"
|
||||
contains: "onMouseEnter"
|
||||
- path: "src/components/ui/PasswordField.tsx"
|
||||
provides: "Same tooltip fix as FieldRenderer text-branch"
|
||||
contains: "onMouseEnter"
|
||||
key_links:
|
||||
- from: "src/components/ui/FieldRenderer.tsx"
|
||||
to: "src/components/ui/TextFieldMD3.tsx"
|
||||
via: "helpTextPrefix prop"
|
||||
pattern: "helpTextPrefix.*ⓘ"
|
||||
- from: "src/components/ui/PasswordField.tsx"
|
||||
to: "src/components/ui/TextFieldMD3.tsx"
|
||||
via: "helpTextPrefix prop"
|
||||
pattern: "helpTextPrefix.*ⓘ"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Fix two UAT-reported tooltip issues: (1) tooltip doesn't show on hover, only on click; (2) tooltip icon is positioned above the field instead of beside the helpText below it.
|
||||
|
||||
Purpose: Close the last remaining UAT gap in Phase 9 (test 7: Tooltip Info Buttons Accessibility).
|
||||
Output: Tooltip icon renders inline with helpText below the field, and tooltip text appears on hover (pinnable via click).
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/09-md3-components/09-03-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- TextFieldMD3 current props -->
|
||||
From src/components/ui/TextFieldMD3.tsx:
|
||||
```typescript
|
||||
interface TextFieldMD3Props {
|
||||
id: string;
|
||||
label: string;
|
||||
error?: FieldError;
|
||||
registration: UseFormRegisterReturn;
|
||||
type?: 'text' | 'password';
|
||||
helpText?: string;
|
||||
required?: boolean;
|
||||
suffix?: React.ReactNode;
|
||||
}
|
||||
```
|
||||
|
||||
<!-- FieldRenderer current tooltip pattern (lines 84-108) — tooltip button ABOVE TextFieldMD3 -->
|
||||
From src/components/ui/FieldRenderer.tsx:
|
||||
```typescript
|
||||
// text (default) — uses TextFieldMD3 for floating label
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
{field.tooltipText && (
|
||||
<button type="button" onClick={() => setShowTooltip(v => !v)}
|
||||
aria-label={`More info about ${field.label}`}
|
||||
className="text-primary hover:text-primary text-xs leading-none self-start">
|
||||
ⓘ
|
||||
</button>
|
||||
)}
|
||||
{field.tooltipText && showTooltip && (
|
||||
<p className="text-xs text-primary bg-primary/10 border border-primary/30 rounded px-2 py-1.5">
|
||||
{field.tooltipText}
|
||||
</p>
|
||||
)}
|
||||
<TextFieldMD3 id={field.key} label={field.label} error={error}
|
||||
registration={register(field.key)} helpText={field.helpText} required={field.required} />
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
<!-- PasswordField has identical tooltip pattern (lines 30-56) -->
|
||||
<!-- TextFieldMD3 helpText rendering (lines 65-67) -->
|
||||
```typescript
|
||||
{helpText && !error && (
|
||||
<p className="text-xs text-on-surface-container/70">{helpText}</p>
|
||||
)}
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add helpTextPrefix prop to TextFieldMD3 and fix tooltip placement + hover in FieldRenderer and PasswordField</name>
|
||||
<files>src/components/ui/TextFieldMD3.tsx, src/components/ui/FieldRenderer.tsx, src/components/ui/PasswordField.tsx</files>
|
||||
<action>
|
||||
**TextFieldMD3.tsx — Add helpTextPrefix prop:**
|
||||
|
||||
1. Add `helpTextPrefix?: React.ReactNode` to `TextFieldMD3Props` interface.
|
||||
2. Destructure `helpTextPrefix` in the component function.
|
||||
3. Change the helpText rendering block (lines 65-67) from:
|
||||
```tsx
|
||||
{helpText && !error && (
|
||||
<p className="text-xs text-on-surface-container/70">{helpText}</p>
|
||||
)}
|
||||
```
|
||||
To:
|
||||
```tsx
|
||||
{helpText && !error && (
|
||||
<div className="flex items-start gap-1">
|
||||
{helpTextPrefix}
|
||||
<p className="text-xs text-on-surface-container/70">{helpText}</p>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
This renders optional inline content (the tooltip icon) to the left of helpText. When `helpTextPrefix` is undefined/null, the flex container still renders correctly with just the `<p>`.
|
||||
|
||||
**FieldRenderer.tsx — Fix text-branch tooltip (lines 83-109):**
|
||||
|
||||
1. Remove the tooltip `<button>` from ABOVE `TextFieldMD3` (lines 85-93). Remove the tooltip content `<p>` from above as well (lines 95-98).
|
||||
2. Add `onMouseEnter` / `onMouseLeave` state: add `const [hoverTooltip, setHoverTooltip] = useState(false)` alongside existing `showTooltip` state.
|
||||
3. Create a tooltip icon element that will be passed as `helpTextPrefix`:
|
||||
```tsx
|
||||
const tooltipIcon = field.tooltipText ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowTooltip(v => !v)}
|
||||
onMouseEnter={() => setHoverTooltip(true)}
|
||||
onMouseLeave={() => setHoverTooltip(false)}
|
||||
aria-label={`More info about ${field.label}`}
|
||||
className="text-primary hover:text-primary/80 text-xs leading-none mt-px shrink-0"
|
||||
>
|
||||
ⓘ
|
||||
</button>
|
||||
) : undefined;
|
||||
```
|
||||
4. Compute tooltip visibility: `const tooltipVisible = showTooltip || hoverTooltip`.
|
||||
5. Render the tooltip content BELOW TextFieldMD3 (after the component, inside the flex-col wrapper), conditioned on `tooltipVisible`:
|
||||
```tsx
|
||||
{field.tooltipText && tooltipVisible && (
|
||||
<p className="text-xs text-primary bg-primary/10 border border-primary/30 rounded px-2 py-1.5">
|
||||
{field.tooltipText}
|
||||
</p>
|
||||
)}
|
||||
```
|
||||
6. Pass `helpTextPrefix={tooltipIcon}` to TextFieldMD3.
|
||||
7. The final text-branch return should look like:
|
||||
```tsx
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<TextFieldMD3
|
||||
id={field.key}
|
||||
label={field.label}
|
||||
error={error}
|
||||
registration={register(field.key)}
|
||||
helpText={field.helpText}
|
||||
required={field.required}
|
||||
helpTextPrefix={tooltipIcon}
|
||||
/>
|
||||
{field.tooltipText && tooltipVisible && (
|
||||
<p className="text-xs text-primary bg-primary/10 border border-primary/30 rounded px-2 py-1.5">
|
||||
{field.tooltipText}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
```
|
||||
|
||||
**FieldRenderer.tsx — Fix select-branch tooltip (lines 40-78):**
|
||||
|
||||
The select branch also has a tooltip button. Apply the same hover fix to the select branch:
|
||||
1. Add `onMouseEnter={() => setHoverTooltip(true)}` and `onMouseLeave={() => setHoverTooltip(false)}` to the existing select-branch tooltip button (line 51).
|
||||
2. Change the tooltip content visibility condition from `showTooltip` to `tooltipVisible` (line 59).
|
||||
3. The select branch does NOT use TextFieldMD3, so no `helpTextPrefix` needed there — keep the icon in its current position above the select (it already has a visible label, not a floating label).
|
||||
|
||||
**PasswordField.tsx — Same fix as FieldRenderer text-branch:**
|
||||
|
||||
1. Add `const [hoverTooltip, setHoverTooltip] = useState(false)` alongside existing `showTooltip`.
|
||||
2. Remove the tooltip button from above TextFieldMD3 (lines 31-40). Remove the tooltip content `<p>` from above (lines 41-44).
|
||||
3. Create the same `tooltipIcon` element with `onClick`, `onMouseEnter`, `onMouseLeave`, `aria-label={`More info about ${label}`}`.
|
||||
4. Compute `const tooltipVisible = showTooltip || hoverTooltip`.
|
||||
5. Pass `helpTextPrefix={tooltipIcon}` to TextFieldMD3.
|
||||
6. Render tooltip content after TextFieldMD3, conditioned on `tooltipVisible`.
|
||||
|
||||
IMPORTANT: Preserve existing `aria-label` format exactly as `More info about ${field.label}` (or `${label}` in PasswordField) — existing tests assert this.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run --reporter=verbose 2>&1 | tail -30</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- Tooltip icon renders inline to the left of helpText below the field (not above the field)
|
||||
- Hovering the tooltip icon shows tooltip text
|
||||
- Clicking the tooltip icon pins tooltip open; clicking again dismisses
|
||||
- Mouse-leaving the icon hides tooltip (unless pinned via click)
|
||||
- All existing tests pass including FieldRenderer aria-label tests
|
||||
- PasswordField has identical fix
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Add tests for tooltip hover behavior and helpTextPrefix rendering</name>
|
||||
<files>src/components/ui/FieldRenderer.test.tsx, src/components/ui/TextFieldMD3.test.tsx</files>
|
||||
<behavior>
|
||||
- Test: Hovering tooltip button shows tooltip text (fireEvent.mouseEnter on button, then expect tooltipText visible)
|
||||
- Test: Mouse-leaving tooltip button hides tooltip text (fireEvent.mouseLeave, then expect tooltipText not visible)
|
||||
- Test: Clicking tooltip button pins tooltip open, mouseLeave does NOT hide it
|
||||
- Test: TextFieldMD3 renders helpTextPrefix inline with helpText when prop provided
|
||||
- Test: TextFieldMD3 renders helpText normally when helpTextPrefix is not provided
|
||||
</behavior>
|
||||
<action>
|
||||
**FieldRenderer.test.tsx — Add hover behavior tests:**
|
||||
|
||||
Add a new describe block `'FieldRenderer - tooltip hover behavior'` with these tests:
|
||||
|
||||
1. `'hovering tooltip button shows tooltip text'`:
|
||||
- Render FieldRenderer with `textFieldWithTooltip` (already defined in test file)
|
||||
- Get tooltip button via `screen.getByRole('button', { name: /more info about bucket name/i })`
|
||||
- `fireEvent.mouseEnter(tooltipBtn)`
|
||||
- `expect(screen.getByText('The name of your storage bucket')).toBeDefined()`
|
||||
|
||||
2. `'mouse-leaving tooltip button hides tooltip text'`:
|
||||
- Render, mouseEnter, verify visible, then `fireEvent.mouseLeave(tooltipBtn)`
|
||||
- `expect(screen.queryByText('The name of your storage bucket')).toBeNull()`
|
||||
|
||||
3. `'clicking tooltip button pins it open through mouseLeave'`:
|
||||
- Render, `fireEvent.click(tooltipBtn)`, `fireEvent.mouseLeave(tooltipBtn)`
|
||||
- `expect(screen.getByText('The name of your storage bucket')).toBeDefined()` (still visible because pinned)
|
||||
|
||||
4. `'clicking pinned tooltip button again dismisses it'`:
|
||||
- Render, click (pin), click again (unpin)
|
||||
- `expect(screen.queryByText('The name of your storage bucket')).toBeNull()`
|
||||
|
||||
Import `fireEvent` from `@testing-library/react` (add to existing import).
|
||||
|
||||
**TextFieldMD3.test.tsx — Add helpTextPrefix tests:**
|
||||
|
||||
Add tests in existing file (or create new describe block):
|
||||
|
||||
1. `'renders helpTextPrefix inline with helpText'`:
|
||||
- Render TextFieldMD3 with `helpText="Some help"` and `helpTextPrefix={<span data-testid="prefix">icon</span>}`
|
||||
- `expect(screen.getByTestId('prefix')).toBeDefined()`
|
||||
- `expect(screen.getByText('Some help')).toBeDefined()`
|
||||
|
||||
2. `'renders helpText without wrapper issues when helpTextPrefix is undefined'`:
|
||||
- Render TextFieldMD3 with `helpText="Some help"` and no helpTextPrefix
|
||||
- `expect(screen.getByText('Some help')).toBeDefined()`
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/Ready2Blob && npx vitest run src/components/ui/FieldRenderer.test.tsx src/components/ui/TextFieldMD3.test.tsx --reporter=verbose 2>&1 | tail -40</automated>
|
||||
</verify>
|
||||
<done>
|
||||
- All new tooltip hover tests pass (mouseEnter shows, mouseLeave hides, click pins, click again unpins)
|
||||
- helpTextPrefix rendering test passes
|
||||
- All pre-existing FieldRenderer and TextFieldMD3 tests still pass
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `npx vitest run --reporter=verbose` — all tests pass (179+ existing + new tooltip tests)
|
||||
2. Manual spot-check: tooltip icon appears beside helpText below field, not above field
|
||||
3. Hover behavior: mouseEnter shows tooltip, mouseLeave hides it, click pins it
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- UAT test 7 passes: tooltip info buttons show tooltip text on hover AND icon is positioned beside helpText below the field
|
||||
- Zero test regressions
|
||||
- Both FieldRenderer (text-branch + select-branch) and PasswordField have hover support
|
||||
- TextFieldMD3 has helpTextPrefix prop for extensibility
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/09-md3-components/09-05-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user