sillsdev / sillsdev/TheCombine

[TreeView] Hover badge persists on touch-screen domain touch

Open
#4,061 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug component: TreeView lowpriority Needs UX Design
Dominant language
TypeScript
Stars
22
Forks
10
Avg merge
2d 2h
Merged PRs (30d)
9

Description

#4046 causes an odd behavior on touch screen. Since hover isn't available, you can get the word-count badge to appear by tapping or press-and-hold-ing the button. In either case, the hover persists after the touch until something else is touched. This is good for press-and-hold, but bad for tap. When the button is tapped, that click navigates in the tree, so the hover shouldn't persist, but it does.

Claude's solution (to keep good press-and-hold hover and not have any tap hover) introduces too high a maintenance cost for the benefit:

import { useState, useRef } from 'react';

const badgeClass = "DomainCountBadge";

/** Style to show the child with given className only on hover of the parent */
const hoverSx = (className: string, showBadge: boolean): SxProps => ({
  [`& .${className}`]: { 
    opacity: showBadge ? 1 : 0, 
    transition: "opacity .25s ease" 
  },
  // Only apply CSS hover on devices that support hover
  '@media (hover: hover)': {
    [`&:hover .${className}`]: { opacity: 1 },
  },
});

interface DomainTileButtonProps extends DomainTileProps {
  onClick: (domain: SemanticDomain) => void;
}

export default function DomainTileButton(
  props: DomainTileButtonProps
): ReactElement {
  const { onClick, ...domainTileProps } = props;
  const [showBadge, setShowBadge] = useState(false);
  const touchStartTime = useRef<number | null>(null);

  const handleTouchStart = () => {
    touchStartTime.current = Date.now();
  };

  const handleTouchEnd = () => {
    const touchDuration = touchStartTime.current 
      ? Date.now() - touchStartTime.current 
      : 0;
    
    // Show badge on long press (>500ms)
    if (touchDuration >= 500) {
      setShowBadge(true);
      // Hide after a delay
      setTimeout(() => setShowBadge(false), 1000);
    }
    
    touchStartTime.current = null;
  };

  return (
    <Button
      id={props.domain.id}
      fullWidth
      onClick={() => {
        onClick(props.domain);
        setShowBadge(false); // Clear badge on tap
      }}
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
      sx={{ height: "100%", ...hoverSx(badgeClass, showBadge) }}
      tabIndex={-1}
      variant="outlined"
    >
      <DomainTile {...domainTileProps} />

      <DomainCountBadge className={badgeClass} domainId={props.domain.id} />
    </Button>
  );
}

But we could use

  // Only apply CSS hover on devices that support hover
  '@media (hover: hover)': {
    [`&:hover .${className}`]: { opacity: 1 },
  },

to disable the hover entirely on touch screen.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the TreeView or DomainTileButton implementation and reproduce the badge behavior on a touch screen. Check the hover styling and tap handling described in the issue; done means a tap navigates without leaving the word-count badge visible, while the intended press-and-hold behavior remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.