Skip to content

g1t/apps/web/app/components/ui/hint.tsx

79 lines3,071 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Merge branch 'worktree-agent-ae1299e92e4462012'1import { type ComponentProps, type MouseEvent, type PointerEvent, type ReactElement, type ReactNode, useRef, useState } from "react";
2
3import { Tooltip, TooltipContent, TooltipTrigger } from "./tooltip";
4
5/**
6 * A hover hint: the site's tooltip around exactly one element, in place of
7 * the browser's `title`, which g1t does not use (app/lib/no-native-title.test.ts
8 * keeps it out). It opens on hover and keyboard focus, and on a tap of
9 * something that does nothing else when tapped, since a touch screen has no
10 * hover. Without a `label` the child renders as it is.
11 *
12 * The hint is not the element's accessible name: an icon-only button keeps
13 * its `aria-label`, and text cut short keeps its whole text for a screen
14 * reader. Anything else passed (as from a parent's `asChild`) goes to the child.
15 */
16export function Hint({
17 label,
18 children,
19 side,
20 align,
21 disabled,
22 onClick,
23 onPointerDown,
24 ...props
25}: Omit<ComponentProps<typeof TooltipTrigger>, "children"> & {
26 label: ReactNode;
27 /** One element that takes a ref and passes its props on (asChild). */
28 children: ReactElement;
29 side?: ComponentProps<typeof TooltipContent>["side"];
30 align?: ComponentProps<typeof TooltipContent>["align"];
31 /**
32 * The child is disabled, so it gets no pointer events and no focus: the
33 * hint hangs on a focusable span around it instead, as when it says why.
34 */
35 disabled?: boolean;
36}) {
37 const [open, setOpen] = useState(false);
38 const touch = useRef(false);
39 if (label == null || label === false || label === "") return children;
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero40 // asChild merges className as a string, so a child whose className is a
41 // function (NavLink's) would lose its classes: it gets a plain box instead.
42 const functionClass = typeof (children.props as { className?: unknown }).className === "function";
Merge branch 'worktree-agent-ae1299e92e4462012'43 const trigger = disabled ? (
44 <span tabIndex={0} className="inline-flex rounded-md outline-none focus-visible:ring-2 focus-visible:ring-accent *:pointer-events-none">
45 {children}
46 </span>
Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero47 ) : functionClass ? (
48 <div>{children}</div>
Merge branch 'worktree-agent-ae1299e92e4462012'49 ) : (
50 children
51 );
52 return (
53 <Tooltip open={open} onOpenChange={setOpen}>
54 <TooltipTrigger
55 asChild
56 {...props}
57 onPointerDown={(event: PointerEvent<HTMLButtonElement>) => {
58 touch.current = event.pointerType === "touch";
59 onPointerDown?.(event);
60 }}
61 onClick={(event: MouseEvent<HTMLButtonElement>) => {
62 onClick?.(event);
63 // A tap on text or a badge shows the hint; a tap on a link or a
64 // button does what it does. preventDefault keeps the trigger's own
65 // click handler from closing the hint again.
66 if (touch.current && !event.defaultPrevented && !(event.target as Element).closest("a, button, input, select, textarea, label, summary")) {
67 event.preventDefault();
68 setOpen(true);
69 }
70 }}
71 >
72 {trigger}
73 </TooltipTrigger>
74 <TooltipContent side={side} align={align}>
75 {label}
76 </TooltipContent>
77 </Tooltip>
78 );
79}

This file's history is long; its oldest lines are credited to the oldest commit read.