Skip to content

Added auto-hide timeout for Tooltip component after 1s on touch based devices - #243

Merged
Ryan-Millard merged 18 commits into
Ryan-Millard:mainfrom
codevory:main
Jan 28, 2026
Merged

Added auto-hide timeout for Tooltip component after 1s on touch based devices#243
Ryan-Millard merged 18 commits into
Ryan-Millard:mainfrom
codevory:main

Conversation

@codevory

@codevory codevory commented Jan 24, 2026

Copy link
Copy Markdown
Contributor

What was changed & why

Addressing the issue #168 I have added auto hide timeout of 1 sec. to Tooltip componet for touch devices like Mobile/Tablet etc.
Fixed someother bugs like The onClick setup in the cloneElement call replaces any existing click handlers on the tooltip's children mentioned in PR #173 , The existing progress made in PR #173 made it easy for me to solve it quickly Thanks to guy who had solved it almost & to maintainer @Ryan-Millard as well .

Fixes: #168

Changes

✨ What's Added
Touch Device Support for Tooltips:

Touchdevice detection using multiple methods ([navigator.maxTouchPoints] , media queries, ontouchstart)
Click-to-reveal - Tapping elements shows tooltips on touch devices
Focus-to-reveal - Keyboard navigation (Tab) triggers tooltips on touch devices
Auto-hide after 1 second - Tooltips automatically dismiss since hover-out isn't available
Dynamic detection - Listens for input changes (e.g., mouse plugged into tablet)
Event handler preservation - Existing [onClick] and [onFocus] handlers are merged and called before tooltip logic


Documentation

Updated component documentation with Touch Device Support section
Added Implementation notes explaining the new behavior
Comprehensive test documentation with 15 test case explanations
Testing recommendations for timer mocking and navigator property mocking


Testing & Verification

5 comprehensive tests covering

  • Basic tooltip behavior (5 tests)
  • Touch device interactions (5 tests)
  • Event handler preservation (5 tests)
  • All tests passing with proper timeout handling for auto-hide behavior.

Additional Resources

Technical Implementation
Uses React state ( isOpen, isTouchDevice ) for controlled tooltip visibility.
useEffect hook for client-side device detection with cleanup.
Event handler merging pattern preserves existing functionality
isOpen prop on ReactTooltip enables manual control for touch devices

Img2Num.Image-to-Color-by-Number.Templates.-.Google.Chrome.2026-01-24.21-05-01.mp4

Summary by CodeRabbit

  • New Features

    • Added touch device support to Tooltip component with auto-hide behavior and tap-to-reveal functionality.
    • Introduced Pagination component for improved navigation through large content lists.
  • Documentation

    • Enhanced Tooltip documentation with comprehensive touch device behavior guidelines and testing recommendations.
    • Added Pagination component documentation with usage examples and test specifications.
  • Style

    • Standardized codebase quote conventions and formatting consistency across all files.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 24, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR implements auto-hide timeout for the Tooltip component on touch devices with 1-second auto-hide behavior, adds client-side pagination to the credits page, and standardizes code formatting from single to double quotes across the entire codebase.

Changes

Cohort / File(s) Summary
Tooltip Touch Device Support
src/components/Tooltip.jsx, src/components/Tooltip.test.jsx, docs/docs/reference/react/components/Tooltip/index.md, docs/docs/reference/react/components/Tooltip/tests.md
Implements touch device detection using navigator.maxTouchPoints, media query any-pointer: coarse, and ontouchstart fallback. Adds click-to-reveal and focus-to-reveal behavior with 1-second auto-hide timeout on touch devices. Preserves existing onClick/onFocus handlers when cloning children or wrapping non-element children. Introduces state management (isOpen, isTouchDevice, hideTimeoutRef) and PropTypes for runtime validation. Extensive test coverage for touch device behavior, event handler preservation, and auto-hide timing.
Pagination Component Feature
src/components/Pagination.jsx, src/components/Pagination.test.jsx, docs/docs/reference/react/components/Pagination/index.md, docs/docs/reference/react/components/Pagination/tests.md
Minor formatting changes to existing Pagination component. New pagination integration in contributors credits page (src/pages/Credits/ContributorsCreditsCard.jsx) with client-side page state and dynamic rendering of contributor groups.
Code Style & Configuration
.editorconfig-checker.json, .prettierrc, .prettierignore, .github/ISSUE_TEMPLATE/bug_report.yml, .github/ISSUE_TEMPLATE/feature_request.yml, .github/dependabot.yml, .github/workflows/*.yml, docker-compose.yml, eslint.config.js, index.html
Standardized all string literals from single quotes to double quotes across configuration files. Simplified .prettierrc to use defaults and consolidated .prettierignore to only two patterns. Added boolean configuration fields to .editorconfig-checker.json Disable block. Minor YAML formatting consistency updates.
Documentation & Script Formatting
docs/docs/reference/react/components/**/*.md, docs/docs/reference/react/hooks/**/*.md, docs/docs/reference/react/pages/**/*.md, docs/docs/reference/**/*.md, docs/src/components/**/*.jsx, docs/src/css/**, docs/docusaurus.config.js, docs/sidebars.js, docs/plugins/webpack-alias/index.js, docs/scripts/*.js, docs/changelogSidebarGenerator.js
Comprehensive quote standardization from single to double quotes in all documentation code examples, import statements, and metadata. Updated JSX examples in docs to use consistent double-quoted imports and string literals. Minor code reformatting in Docusaurus configuration and build scripts. Updated changelog handler with improved release block parsing and directory management.
Source Code Formatting
src/**/*.jsx, src/**/*.js, src/**/*.css, scripts/build-wasm.js, scripts/format-wasm.js, scripts/generate-contributor-credits-json.js, scripts/handle-changelog.js, scripts/help.js, scripts/lib/*.js, scripts/validate-scripts.js, vite.config.js, vitest.config.js
Universal quote standardization from single to double quotes across all source files, test files, CSS files, and build scripts. Functional behavior preserved; only string literal syntax updated for consistency. Added conditional logic to CLI fuzzy search (scripts/lib/cli-fuzzy.js) for initialSearch parameter. Added configureServer hook to WASM build plugin in vite.config.js.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Browser
    participant TooltipComponent as Tooltip<br/>Component
    participant Timer as Timer<br/>(hideTimeoutRef)
    participant DOM as DOM/UI

    rect rgba(100, 200, 100, 0.5)
    Note over Browser: Touch Device Detection (On Mount)
    Browser->>TooltipComponent: useEffect detects navigator.maxTouchPoints
    TooltipComponent->>TooltipComponent: Set isTouchDevice state
    end

    rect rgba(100, 150, 200, 0.5)
    Note over User,DOM: Touch Device - Click/Focus Interaction
    User->>DOM: Click or Focus on child element
    DOM->>TooltipComponent: onClick/onFocus handler triggered
    TooltipComponent->>TooltipComponent: Call showTooltip()
    TooltipComponent->>TooltipComponent: Set isOpen = true
    TooltipComponent->>DOM: Render tooltip (visible)
    TooltipComponent->>Timer: Set hideTimeout (1000ms)
    end

    rect rgba(200, 100, 100, 0.5)
    Note over Timer,DOM: Auto-Hide After 1 Second
    Timer->>Timer: 1000ms elapsed
    TooltipComponent->>TooltipComponent: Set isOpen = false
    TooltipComponent->>DOM: Hide tooltip
    Timer->>TooltipComponent: Clear hideTimeoutRef
    end

    rect rgba(150, 150, 100, 0.5)
    Note over User,DOM: Desktop Device - Hover/Focus (Unchanged)
    User->>DOM: Hover or Focus on element
    DOM->>TooltipComponent: Standard ReactTooltip behavior
    TooltipComponent->>DOM: Show/hide via hover/focus state
    Note over TooltipComponent: isTouchDevice = false<br/>Standard behavior preserved
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #217: Introduces client-side pagination in Credits page (ContributorsCreditsCard.jsx) alongside new Pagination component usage, sharing common pagination logic and UI patterns.
  • PR #122: Extends existing Tooltip implementation with touch-device detection and auto-hide behavior, directly modifying the same src/components/Tooltip.jsx component.
  • PR #234: Modifies src/utils/image-utils.js for image-to-SVG conversion utility changes, potentially used in image processing workflows.

Suggested labels

enhancement, ux, mobile, accessibility

Suggested reviewers

  • Ryan-Millard

Poem

🐰 Whiskers twitch with joy divine,
Touch device detection—now mobile shines!
Auto-hide at one second's pace,
Quotes now double throughout this space!
Pagination flows, formatting gleams,
A hoppy hop toward better UX dreams! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning While the PR includes extensive quote normalization (single to double quotes) across 80+ files, these changes are not directly related to the stated objective of adding auto-hide functionality for touch devices. Remove or defer the quote normalization changes to a separate formatting PR; keep only the Tooltip-specific implementation and documentation changes for touch device support.
Docstring Coverage ⚠️ Warning Docstring coverage is 62.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added auto-hide timeout for Tooltip component after 1s on touch based devices' directly and clearly summarizes the main change in the pull request.
Linked Issues check ✅ Passed The PR implements all coding requirements from issue #168: touch device detection via maxTouchPoints/media queries/ontouchstart, 1-second auto-hide for touch devices, preservation of existing desktop behavior, and existing event handler preservation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@docs/docs/reference/react/components/Tooltip/index.md`:
- Around line 101-103: The implementation notes in the Tooltip docs are
concatenated into run-on lines; split each bullet into its own line/bullet so
each item is separate and readable. Specifically, ensure the lines referencing
`children` wrapper, `useEffect` touch detection, `onClick` handler preservation
(cloning child elements), `isOpen` controlled state and `ReactTooltip`, and the
`appendTo={document.body}` with `positionStrategy="fixed"` are each on their own
bullet/line in the Markdown so they render as separate list items.

In `@docs/docs/reference/react/components/Tooltip/tests.md`:
- Around line 29-31: The test description "auto-hides tooltip after 1 second on
touch devices" is inconsistent with the recommendations: update the docs and/or
the test so they match—either change the description to state it uses fake
timers and show use of vi.useFakeTimers() (and advanceTimersByTime instead of
waiting 1100ms) referencing the test behavior that currently uses
setTimeout/wait 1100ms, or change the recommendations to permit real timers and
explain the 1100ms real-wait behavior (and justify the 7000ms test timeout);
ensure references to vi.useFakeTimers(), advanceTimersByTime, and the existing
setTimeout/wait usage in the test are corrected so documentation and
implementation align.

In `@src/components/Tooltip.jsx`:
- Around line 55-63: The showTooltip function schedules a 1s setTimeout without
storing or clearing its id, causing potential state updates after unmount and
multiple queued timeouts on rapid interactions; fix by storing the timeout id in
a ref (e.g., tooltipTimeoutRef) inside the component, clear any existing timeout
before setting a new one in showTooltip, and also clear the timeout in a
useEffect cleanup (or on component unmount) to prevent calling setIsOpen on an
unmounted component; keep references to isTouchDevice and setIsOpen unchanged
while ensuring tooltipTimeoutRef is used to manage and clear the timer.

In `@src/components/Tooltip.test.jsx`:
- Around line 84-108: The tests in the "Touch device behavior" describe block
mutate navigator.maxTouchPoints via Object.defineProperty without restoring it,
causing cross-test pollution; add a beforeEach that saves the original
navigator.maxTouchPoints (e.g., let originalMaxTouchPoints) and an afterEach
that restores it with Object.defineProperty (configurable/writable true and
value originalMaxTouchPoints) so each test resets navigator; apply the same
beforeEach/afterEach pattern to the "Preserving existing onClick handlers"
describe block as well to ensure isolation.
🧹 Nitpick comments (7)
src/components/Tooltip.jsx (4)

3-3: Formatting inconsistency.

The import statement has inconsistent spacing. The pipeline is also flagging Prettier issues.

🔧 Suggested fix
-import {useState,useEffect, useId, cloneElement, isValidElement } from 'react';
+import { useState, useEffect, useId, cloneElement, isValidElement } from 'react';

8-9: Formatting: add semicolons and consistent spacing.

State declarations are missing semicolons and have inconsistent indentation compared to the rest of the file.

🔧 Suggested fix
-const [isOpen,setIsOpen] = useState(false)
-const [isTouchDevice,setIsTouchDevice] = useState(false)
+  const [isOpen, setIsOpen] = useState(false);
+  const [isTouchDevice, setIsTouchDevice] = useState(false);

65-69: handleFocus is redundant.

handleFocus just delegates to showTooltip, which already checks isTouchDevice. The extra wrapper and duplicate check are unnecessary.

♻️ Simplify by using `showTooltip` directly
-const handleFocus = () =>{
-  if(isTouchDevice){
-    showTooltip();
-  }
-}

Then update references:

       onFocus:(e) => { 
         children.props.onFocus?.(e)
-        handleFocus(e)
+        showTooltip()
       }
-    onClick = {showTooltip} onFocus={handleFocus} 
+    onClick={showTooltip} onFocus={showTooltip}

85-87: Formatting: spacing around = in JSX attributes.

🔧 Suggested fix
-    <span data-tooltip-id={tooltipId} data-tooltip-content={content} tabIndex={0}
-    onClick = {showTooltip} onFocus={handleFocus} 
-    >
+    <span
+      data-tooltip-id={tooltipId}
+      data-tooltip-content={content}
+      tabIndex={0}
+      onClick={showTooltip}
+      onFocus={showTooltip}
+    >
src/components/Tooltip.test.jsx (3)

110-141: Consider using fake timers instead of real timers for deterministic tests.

Using await new Promise(resolve => setTimeout(resolve, 1100)) with a 7000ms test timeout makes tests slow and potentially flaky. The documentation recommends using vi.useFakeTimers() for these scenarios.

♻️ Refactor to use fake timers
test('auto-hides tooltip after 1 second on touch devices', async () => {
  vi.useFakeTimers();
  
  Object.defineProperty(navigator, 'maxTouchPoints', {
    writable: true,
    configurable: true,
    value: 1,
  });

  const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });

  render(
    <Tooltip content="Touch tooltip">
      <button>Click me</button>
    </Tooltip>
  );

  const button = screen.getByText('Click me');
  await user.click(button);

  // Tooltip should be visible initially
  expect(screen.getByText('Touch tooltip')).toBeInTheDocument();

  // Advance timers by 1 second
  await act(() => {
    vi.advanceTimersByTime(1000);
  });

  // Tooltip should be hidden
  await waitFor(() => {
    expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
  });

  vi.useRealTimers();
});

138-140: Inconsistent assertion: use not.toBeInTheDocument() for consistency.

Other tests use not.toBeInTheDocument() for absence checks, but this uses toBeNull(). While functionally similar for queryBy*, consistency improves readability.

🔧 Suggested fix
       await waitFor(() => {
-        expect(screen.queryByRole('tooltip')).toBeNull();
+        expect(screen.queryByRole('tooltip')).not.toBeInTheDocument();
       });

143-169: Test assertion is weak; consider verifying tooltip does not appear.

The test comment acknowledges that "The tooltip might appear from hover side effect of click" but only asserts that the button exists. This doesn't meaningfully verify the intended behavior (click doesn't trigger tooltip on non-touch devices).

♻️ Suggested improvement
       await user.click(button);
       
-      // The onClick runs but doesn't show tooltip (showTooltip returns early for non-touch)
-      // Note: The tooltip might appear from hover side effect of click
-      // What we're really testing is that the onClick handler doesn't crash
-      expect(button).toBeInTheDocument();
+      // On non-touch devices, click alone shouldn't trigger the controlled tooltip state
+      // The tooltip may still appear via hover/focus from react-tooltip's native behavior,
+      // but isOpen state should remain false (not programmatically opened)
+      // Verify no crash and button is still functional
+      expect(button).toBeInTheDocument();
+      
+      // Optionally: verify that after unhover, tooltip hides normally
+      await user.unhover(button);
+      await waitFor(() => {
+        expect(screen.queryByRole('tooltip')).not.toBeVisible();
+      });

Comment thread docs/docs/reference/react/components/Tooltip/index.md
Comment thread docs/docs/reference/react/components/Tooltip/tests.md Outdated
Comment thread src/components/Tooltip.jsx Outdated
Comment thread src/components/Tooltip.test.jsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
scripts/handle-changelog.js (1)

62-73: Remove .md extension from the Docusaurus ID.

Line 66 sets id: ${fileName} where fileName includes the .md extension. All other Docasaurus documents in the project use IDs without extensions (e.g., id: contributing, id: coding-style). The ID should be just the slug without the extension:

id: ${fileName.replace(/\.md$/, "")}

The date format is safe—standard-version produces ISO format dates (YYYY-MM-DD) when generating CHANGELOG.md, which are valid for filenames and match what the changelogSidebarGenerator expects.

src/pages/Editor/index.jsx (1)

205-213: Guard onPointerUp to skip click/color logic on pointercancel.

Per the W3C Pointer Events specification, pointercancel signals that the interaction was suppressed/aborted by the UA (due to gestures, OS interruption, palm rejection, etc.). It should only trigger cleanup operations, not the same actions as pointerup. Currently, onPointerCancel={onPointerUp} runs the color logic even when the interaction is canceled, which violates pointer event semantics.

Guard in onPointerUp
   const onPointerUp = (e) => {
     const moved = pointerState.current.moved;
@@
     viewportRef.current?.classList.remove(styles.grabbing);
 
+    if (e.type === "pointercancel") {
+      return;
+    }
+
     // Only treat as click if user didn't drag
     if (!moved && isColorMode) {
       // Find nearest shape inside SVG
       const svgRoot = innerRef.current?.querySelector("svg");
♻️ Duplicate comments (4)
src/components/Tooltip.jsx (1)

52-61: Memory leak: setTimeout not cleaned up on unmount or rapid interactions.

This issue was previously flagged. The setTimeout reference is not stored, so it cannot be cleared if the component unmounts before the timeout fires, or if the user rapidly taps causing multiple queued timeouts and flickering behavior.

src/components/Tooltip.test.jsx (1)

84-227: Restore navigator.maxTouchPoints between tests.
These blocks override a global without cleanup, causing cross-test pollution. Add beforeEach/afterEach to save/restore the original value.

Suggested cleanup pattern
+  let originalMaxTouchPoints;
+
+  beforeEach(() => {
+    originalMaxTouchPoints = navigator.maxTouchPoints;
+  });
+
+  afterEach(() => {
+    Object.defineProperty(navigator, "maxTouchPoints", {
+      writable: true,
+      configurable: true,
+      value: originalMaxTouchPoints,
+    });
+  });

Also applies to: 229-355

docs/docs/reference/react/components/Tooltip/tests.md (1)

29-31: Inconsistency between “real timers” description and fake-timer recommendations.

Also applies to: 76-79, 91-95

docs/docs/reference/react/components/Tooltip/index.md (1)

101-103: Implementation notes bullets are still run-on; needs line breaks.

🧹 Nitpick comments (7)
docs/docs/writing-documentation/extras/translate-your-site.md (1)

16-17: Unrelated formatting changes should be in a separate PR.

This file contains only cosmetic quote-style changes (single → double quotes) in i18n configuration examples, which are unrelated to the PR's stated objective of adding touch-device support to the Tooltip component. Mixing unrelated changes makes the PR harder to review, understand, and potentially revert if needed.

Consider moving these formatting changes to a dedicated style-consistency PR.

Also applies to: 63-63

docs/docs/reference/react/components/GlassSwitch/index.md (2)

76-76: Consider multi-line formatting for documentation readability.

This single-line JSX is ~200+ characters, which may be difficult to read in documentation and cause horizontal scrolling. Multi-line formatting would improve clarity for readers.

📝 Suggested formatting
-  return <GlassSwitch isOn={notificationsOn} onChange={() => setNotificationsOn(!notificationsOn)} ariaLabel="Toggle notifications" thumbContent={notificationsOn ? <Bell size={16} /> : <BellOff size={16} />} />;
+  return (
+    <GlassSwitch
+      isOn={notificationsOn}
+      onChange={() => setNotificationsOn(!notificationsOn)}
+      ariaLabel="Toggle notifications"
+      thumbContent={notificationsOn ? <Bell size={16} /> : <BellOff size={16} />}
+    />
+  );

220-222: Same readability suggestion for the Dark Mode Toggle example.

The single-line JSX at line 222 has similar readability concerns as the previous example.

📝 Suggested formatting
-  return <GlassSwitch isOn={isDark} onChange={toggleTheme} ariaLabel={`Switch to ${isDark ? "light" : "dark"} mode`} thumbContent={isDark ? <Moon size={18} /> : <Sun size={18} />} />;
+  return (
+    <GlassSwitch
+      isOn={isDark}
+      onChange={toggleTheme}
+      ariaLabel={`Switch to ${isDark ? "light" : "dark"} mode`}
+      thumbContent={isDark ? <Moon size={18} /> : <Sun size={18} />}
+    />
+  );
docs/docs/reference/react/components/ThemeSwitch/index.md (1)

142-142: Consider multi-line formatting for the implementation example.

Similar to the GlassSwitch documentation, this long single-line JSX could benefit from multi-line formatting for better readability in the documentation context.

📝 Suggested formatting
-  return <GlassSwitch isOn={isDark} onChange={toggleTheme} thumbContent={isDark ? <Sun /> : <Moon />} ariaLabel={`switch to ${isDark ? "light" : "dark"} mode`} />;
+  return (
+    <GlassSwitch
+      isOn={isDark}
+      onChange={toggleTheme}
+      thumbContent={isDark ? <Sun /> : <Moon />}
+      ariaLabel={`switch to ${isDark ? "light" : "dark"} mode`}
+    />
+  );
package.json (1)

194-194: Consider aligning prettier's versioning strategy with other devDependencies or document the rationale for exact pinning.

The prettier version is pinned exactly to 3.8.1 (latest stable), while most other devDependencies use caret ranges (e.g., ^9.39.2 for eslint). If exact pinning is intentional for build reproducibility, document this decision; otherwise, consider aligning to ^3.8.1 for consistency.

src/pages/Credits/DependencyCreditsCard.jsx (1)

12-21: Consider adding error handling for the fetch call.

If the fetch fails (network error, 404, etc.), the component will silently fail and leave deps and devDeps as empty arrays. While this is acceptable fallback behavior for a credits page, you might want to log errors or show a user-friendly message.

♻️ Optional: Add basic error handling
   useEffect(() => {
     const url = "https://raw.githubusercontent.com/Ryan-Millard/Img2Num/main/package.json";

     fetch(url)
       .then((res) => res.json())
       .then((data) => {
         setDeps(Object.entries(data.dependencies || {}));
         setDevDeps(Object.entries(data.devDependencies || {}));
-      });
+      })
+      .catch((err) => console.error("Failed to fetch package.json:", err));
   }, []);
scripts/handle-changelog.js (1)

78-86: Git commit may fail if changelog already exists.

If running this script twice for the same release, the git commit on line 81 will fail because there are no new changes to commit. Consider using --allow-empty or checking if there are staged changes first.

♻️ Proposed fix to handle already-committed scenarios
 try {
   // Stage the changelog folder
   execSync(`git add ${outPath} ${completeChangelogPath}`, { stdio: "inherit" });
-  execSync(`git commit -m "chore(changelog): add ${version} release notes"`, { stdio: "inherit" });
+  // Only commit if there are staged changes
+  try {
+    execSync("git diff --cached --quiet");
+    console.log("[git] No changes to commit.");
+  } catch {
+    execSync(`git commit -m "chore(changelog): add ${version} release notes"`, { stdio: "inherit" });
+    console.log("[git] docs/changelog added and committed successfully.");
+  }
-
-  console.log("[git] docs/changelog added and commit amended successfully.");
 } catch (err) {
   console.error("[git] Error:", err.message);
   process.exit(1);
 }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@docs/docs/reference/react/components/Tooltip/tests.md`:
- Around line 79-82: In the docs snippet that explains the auto-hide wait,
hyphenate the compound adjective by changing the parenthetical "(1 second
timeout + 100ms buffer)" to "(1-second timeout + 100ms buffer)"; update the text
around the `await new Promise((resolve) => setTimeout(resolve, 1100))` example
so it uses "1-second" as the modifier.

In `@src/components/Tooltip.jsx`:
- Around line 61-71: The showTooltip handler currently calls
e?.stopPropagation?.(), which prevents parent handlers from receiving touch
events; unless tooltip behavior explicitly requires blocking bubbling, remove
that call from showTooltip (or conditionally call it only when a new prop like
blockTouchPropagation is true) so touch clicks bubble normally; update
references in the showTooltip function and ensure hideTimeoutRef and
isTouchDevice logic remain unchanged.

Comment thread docs/docs/reference/react/components/Tooltip/tests.md
Comment thread src/components/Tooltip.jsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/components/Tooltip.jsx`:
- Line 106: Run Prettier on src/components/Tooltip.jsx to fix the CI style
warning: format the JSX line containing the span element (the element using
data-tooltip-id={tooltipId}, data-tooltip-content={content}, tabIndex={0},
onClick={showTooltip}, onFocus={handleFocus}) so it matches the project's
Prettier rules (or run your repo's formatting script/pre-commit hook) and commit
the updated file.
- Around line 119-121: Remove the unsupported openOnFocus prop from the Tooltip
component: in the JSX block where fallbackPlacements and isOpen are set (near
the existing fallbackPlacements={dynamicPositioning ? ["bottom", "top", "left"]
: []} and isOpen={isTouchDevice ? isOpen : undefined}), delete the openOnFocus
attribute so focus handling falls back to the library's default openEvents; keep
the isOpen conditional for touch devices unchanged so non-touch devices remain
uncontrolled.
♻️ Duplicate comments (1)
src/components/Tooltip.jsx (1)

63-67: Avoid stopping propagation on touch unless explicitly required.
This still blocks parent click handlers on touch devices.

Comment thread src/components/Tooltip.jsx
Comment thread src/components/Tooltip.jsx
Ryan-Millard
Ryan-Millard previously approved these changes Jan 26, 2026
@vercel

vercel Bot commented Jan 27, 2026

Copy link
Copy Markdown

@codevory is attempting to deploy a commit to the Ryan Millard's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@package.json`:
- Around line 194-195: Remove the duplicate "prettier" key in devDependencies:
keep only one entry for "prettier" (either the pinned "3.8.1" or the semver
"^3.8.1") and delete the other so the JSON has a single unique key; update
package.json's devDependencies to contain a single "prettier" entry and ensure
the change is intentional for this PR (revert if unrelated to Tooltip
touch-device support).

Comment thread package.json Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/components/Tooltip.jsx`:
- Line 1: This file fails CI due to formatting; run Prettier on the Tooltip
component (e.g., run `prettier --write src/components/Tooltip.jsx`) or apply the
project's Prettier rules to the Tooltip component (Tooltip.jsx) so imports, JSX
and PropTypes usage are formatted correctly; after formatting, re-run lint/CI to
confirm the formatting errors are resolved.
- Line 120: The Tooltip component currently uses a direct reference
appendTo={document.body} which will throw during SSR; update Tooltip.jsx to
avoid accessing document at render by conditionally supplying appendTo (e.g.,
check typeof document !== "undefined" before using document.body) or set append
target after mount (useEffect to set a local state like appendTarget and pass
that to the Tooltip). Ensure the change targets the appendTo prop usage in the
Tooltip component so server renders do not access document.
🧹 Nitpick comments (3)
src/components/Tooltip.jsx (3)

66-70: Unnecessary defensive check — useRef always returns a stable ref object.

The check for hideTimeoutRef validity is defensive code that will never trigger in normal React usage. useRef guarantees a stable, mutable ref object across renders. This adds dead code that obscures the actual logic.

♻️ Suggested simplification
   const showTooltip = () => {
     if (!isTouchDevice) return;
 
-
-    // Bail if ref is not a valid ref object (defensive in hot-reload scenarios)
-    if (!hideTimeoutRef || typeof hideTimeoutRef !== "object") {
-      return;
-    }
-
     // Clear any existing timeout
     if (hideTimeoutRef.current) {
       clearTimeout(hideTimeoutRef.current);

77-81: Functional update does not prevent re-renders as intended.

React's state bailout optimization only works with direct value comparison, not functional updates. Calling setIsOpen((prev) => prev) still schedules a render. To prevent unnecessary work, check isOpen before calling the setter.

♻️ Suggested fix
-    // If already open, don't re-open (prevents unnecessary re-render)
-    setIsOpen((prev) => {
-      if (prev) return prev; // Already open, don't update
-      return true;
-    });
+    setIsOpen(true);

Since isOpen is only used for touch devices and showTooltip already bails early if !isTouchDevice, the simple setIsOpen(true) is sufficient. React will batch and dedupe if already true.


109-111: Add role="button" for accessibility on the interactive span.

The fallback <span> has onClick and tabIndex={0} making it interactive, but lacks a role attribute. Screen readers won't announce it as an actionable element without an explicit role.

♿ Suggested fix
-    <span data-tooltip-id={tooltipId} data-tooltip-content={content} tabIndex={0} onClick={showTooltip} onFocus={handleFocus}>
+    <span data-tooltip-id={tooltipId} data-tooltip-content={content} tabIndex={0} role="button" onClick={showTooltip} onFocus={handleFocus}>

Comment thread src/components/Tooltip.jsx
Comment thread src/components/Tooltip.jsx Outdated
@codevory
codevory requested a review from Ryan-Millard January 27, 2026 04:32

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/pages/Credits/DependencyCreditsCard.jsx (1)

39-40: Replace deprecated cacheTime with gcTime for React Query v5 compatibility.

In TanStack Query v5, cacheTime was renamed to gcTime (garbage collection time). This option controls how long an inactive query remains cached before being garbage-collected.

Suggested fix
       staleTime: ONE_WEEK,
-      cacheTime: ONE_WEEK,
+      gcTime: ONE_WEEK,
🧹 Nitpick comments (3)
src/pages/Credits/DependencyCreditsCard.jsx (1)

22-27: Consider adding error handling for the fetch call.

The fetch call lacks error handling. If the GitHub raw URL is unreachable or returns an error, the component will silently fail without user feedback.

Suggested improvement
     fetch(url)
       .then((res) => res.json())
       .then((data) => {
         setDeps(Object.entries(data.dependencies || {}));
         setDevDeps(Object.entries(data.devDependencies || {}));
-      });
+      })
+      .catch((err) => {
+        console.error("Failed to fetch package.json:", err);
+      });
src/pages/Credits/ContributorsCreditsCard.jsx (1)

23-52: Consider rendering only the active page directly instead of mapping all tables.

The current approach maps over all table chunks but conditionally renders only when i === page. This creates unnecessary iterations. You can render tables[page] directly for cleaner and slightly more efficient code.

Suggested simplification
       <div className={styles.contributorsGrid}>
-        {tables.map(
-          (group, i) =>
-            i === page && (
-              <table key={i}>
-                <tbody>
-                  {group.map((c) => (
-                    <tr key={c.id}>
+        {tables[page] && (
+          <table>
+            <tbody>
+              {tables[page].map((c) => (
+                <tr key={c.id}>
                       <td>
                         <Tooltip content={`Open ${c.login}'s GitHub profile`}>
                           <a href={c.html_url} target="_blank" rel="noopener noreferrer">
                             <FallbackImage src={c.avatar_url} fallback={<User color="var(--color-text-light)" />} alt={c.login} width="28" height="28" className={styles.avatar} />
                           </a>
                         </Tooltip>
                       </td>
                       <td>
                         <Tooltip content={`Visit ${c.login}'s GitHub profile`}>
                           <a href={c.html_url} target="_blank" rel="noopener noreferrer">
                             {c.login}
                           </a>
                         </Tooltip>
                       </td>
                       <td>
                         {c.contributions} {c.contributions === 1 ? "commit" : "commits"}
                       </td>
                     </tr>
                   ))}
                 </tbody>
               </table>
-            ),
-        )}
+        )}
       </div>
src/components/Tooltip.jsx (1)

21-60: Add legacy MediaQueryList listener fallback for older browsers.
Lines 50–57 only attach addEventListener/removeEventListener. Older Safari (pre-14) lacks addEventListener support on MediaQueryList and requires addListener/removeListener instead.

♻️ Suggested fallback
-    if (mediaQuery.addEventListener) {
-      mediaQuery.addEventListener("change", handleChange);
-    }
+    if (mediaQuery.addEventListener) {
+      mediaQuery.addEventListener("change", handleChange);
+    } else if (mediaQuery.addListener) {
+      mediaQuery.addListener(handleChange);
+    }
 ...
-      if (mediaQuery?.removeEventListener) {
-        mediaQuery.removeEventListener("change", handleChange);
-      }
+      if (mediaQuery?.removeEventListener) {
+        mediaQuery.removeEventListener("change", handleChange);
+      } else if (mediaQuery?.removeListener) {
+        mediaQuery.removeListener(handleChange);
+      }

@Ryan-Millard

Copy link
Copy Markdown
Owner

@codevory, don't worry about SSR suggestions from @CodeRabbit - they're a waste of time because we don't use SSR and don't plan on using it at all.

@coderabbitai

coderabbitai Bot commented Jan 28, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@Ryan-Millard, thank you for the clarification! I've noted that this project doesn't use Server-Side Rendering and has no plans to implement it. I'll avoid making SSR-related suggestions in future reviews for this repository.


✏️ Learnings added
Learnt from: Ryan-Millard
URL: https://github.com/Ryan-Millard/Img2Num/pull/243

Timestamp: 2026-01-28T21:50:29.550Z
Learning: This project does not use Server-Side Rendering (SSR) and has no plans to implement it. Do not suggest SSR-related changes or checks in code reviews.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@Ryan-Millard Ryan-Millard left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @codevory!

@Ryan-Millard
Ryan-Millard merged commit 3bade2a into Ryan-Millard:main Jan 28, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Add auto-hide timeout for Tooltip on mobile devices

2 participants