Make annotations visible on hover, fix accessibility and outro animations (#3822)

This commit is contained in:
Zeno Kapitein
2025-11-28 17:15:29 +01:00
committed by GitHub
parent 65ae941687
commit f372eec393
5 changed files with 37 additions and 81 deletions
@@ -27,7 +27,7 @@ export function Annotation(props: InlineProps<DocumentInlineAnnotation>) {
ancestorBlocks={[]}
context={context}
nodes={fragment.nodes}
style={['space-y-4']}
style={['contents']}
/>
}
>
@@ -1,84 +1,30 @@
'use client';
import * as Popover from '@radix-ui/react-popover';
import type React from 'react';
import { tString, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind';
import { Tooltip } from '@/components/primitives';
export function AnnotationPopover(props: { children: React.ReactNode; body: React.ReactNode }) {
const { children, body } = props;
const language = useLanguage();
return (
<Popover.Root>
<Popover.Trigger asChild>
<button
data-testid="annotation-button"
aria-label={tString(language, 'annotation_button_label')}
className={tcls(
'decoration-dotted',
'decoration-1',
'underline',
'underline-offset-2'
)}
>
{children}
</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className={tcls(
'text-sm',
'max-w-[280px]',
'bg-tint',
'ring-1',
'ring-tint',
'rounded-sm',
'shadow-1xs',
'shadow-tint-12/1',
'dark:shadow-tint-1/2',
'p-3',
'[&_p]:leading-snug',
'-outline-offset-2',
'outline-2',
'outline-primary/8',
'z-20'
)}
sideOffset={4}
>
{body}
<Popover.Arrow asChild>
<svg
viewBox="0 0 8 5"
className={tcls(
'relative',
'z-2',
'fill-tint-3', // Same as bg-tint
'stroke-tint-7', // Same as ring-tint
'[paint-order:stroke_fill]'
)}
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clipAnnotation)">
<path
d="M0 0L4 4L8 0"
strokeWidth="2"
strokeLinecap="round"
stroke="inherit"
fill="inherit"
/>
</g>
<defs>
<clipPath id="clipAnnotation">
<rect width="8" height="5" fill="white" />
</clipPath>
</defs>
</svg>
</Popover.Arrow>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
<Tooltip
label={body}
contentProps={{
role: 'definition',
}}
className="bg-tint-base px-4 py-3 text-sm text-tint-strong shadow-lg shadow-tint-12/4 ring-1 ring-tint-subtle dark:shadow-tint-1"
arrow={true}
arrowProps={{ className: 'fill-tint-1' }}
>
<dfn
data-testid="annotation-button"
className="cursor-help underline decoration-1 decoration-dotted underline-offset-2"
// biome-ignore lint/a11y/noNoninteractiveTabindex: we want to be able to focus the definition to open the tooltip
tabIndex={0}
>
{children}
</dfn>
</Tooltip>
);
}
@@ -28,7 +28,7 @@ export function HoverCard(
<RadixHoverCard.Portal>
<RadixHoverCard.Content
side={props.side ?? 'top'}
className="z-40 w-screen max-w-md animate-present px-4 sm:w-auto"
className="z-40 w-screen max-w-md animate-scale-in px-4 data-[state='closed']:animate-scale-out sm:w-auto"
>
<div
className={tcls(
@@ -20,7 +20,7 @@ export function Popover(props: {
collisionPadding={contentProps?.collisionPadding ?? 16}
sideOffset={contentProps?.sideOffset ?? 4}
className={tcls(
'z-50 max-h-(--radix-popover-content-available-height) max-w-xs animate-scale-in overflow-y-auto overflow-x-hidden circular-corners:rounded-3xl rounded-corners:rounded-md bg-tint px-4 py-3 text-sm text-tint depth-subtle:shadow-xl shadow-tint-12/4 outline-hidden ring-1 ring-tint transition-all empty:hidden dark:shadow-tint-1/6',
'z-50 max-h-(--radix-popover-content-available-height) max-w-xs animate-scale-in overflow-y-auto overflow-x-hidden circular-corners:rounded-3xl rounded-corners:rounded-md bg-tint px-4 py-3 text-sm text-tint depth-subtle:shadow-xl shadow-tint-12/4 outline-hidden ring-1 ring-tint transition-all empty:hidden data-[state="closed"]:animate-scale-out dark:shadow-tint-1/6',
contentProps?.className
)}
style={{
@@ -2,13 +2,16 @@
import { tcls } from '@/lib/tailwind';
import * as RadixTooltip from '@radix-ui/react-tooltip';
import { useState } from 'react';
export function Tooltip(props: {
children: React.ReactNode;
label?: string | React.ReactNode;
triggerProps?: RadixTooltip.TooltipTriggerProps;
contentProps?: RadixTooltip.TooltipContentProps;
portalProps?: RadixTooltip.TooltipPortalProps;
rootProps?: RadixTooltip.TooltipProps;
arrowProps?: RadixTooltip.TooltipArrowProps;
arrow?: boolean;
className?: string;
}) {
@@ -17,27 +20,34 @@ export function Tooltip(props: {
label,
triggerProps,
contentProps,
portalProps,
rootProps,
arrowProps,
arrow = false,
className,
} = props;
const [open, setOpen] = useState(false);
const [clicked, setClicked] = useState(false);
return (
<RadixTooltip.Root delayDuration={300} {...rootProps}>
<RadixTooltip.Trigger asChild {...triggerProps}>
<RadixTooltip.Root open={open || clicked} onOpenChange={setOpen} {...rootProps}>
<RadixTooltip.Trigger asChild onClick={() => setClicked(true)} {...triggerProps}>
{children}
</RadixTooltip.Trigger>
<RadixTooltip.Portal>
<RadixTooltip.Portal {...portalProps}>
<RadixTooltip.Content
sideOffset={4}
collisionPadding={8}
className={tcls(
'z-50 max-w-xs animate-scale-in circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-12 px-2 py-1 text-contrast-tint-12 text-sm',
'z-50 max-w-xs circular-corners:rounded-2xl rounded-corners:rounded-md bg-tint-12 px-2 py-1 text-contrast-tint-12 text-sm data-[state$="closed"]:animate-scale-out data-[state$="open"]:animate-scale-in',
className
)}
onPointerDownOutside={() => setClicked(false)}
{...contentProps}
>
{label}
{arrow && <RadixTooltip.Arrow />}
{arrow && <RadixTooltip.Arrow {...arrowProps} />}
</RadixTooltip.Content>
</RadixTooltip.Portal>
</RadixTooltip.Root>