enhance(ui): tweak tooltip style and misc fixes (#38524)

Tooltip changes:

- center tooltips over their target (default placement `top` instead of
`top-start`)
- fix the arrow rendering off-center on references narrower than 22px
like 16px icons, caused by tippy's hardcoded 3px arrow padding
- fix arrowed tooltips overlapping their reference by 1px because the
offset distance was derived from the raw arrow option
- tweak tooltip colors and reduce content padding
- fix a Firefox bug where lazily created tooltips would not show on the
first hover, because Firefox skips enter/leave event dispatch when no
such listener existed in the window at the time of the pointer crossing

Other UI tweaks:

- tweak repo sidebar release style
- enhance actions back link
- center step chevron
- add border on workflow graph

<img width="92" height="76" alt="Screenshot 2026-07-21 at 18 07 42"
src="https://github.com/user-attachments/assets/968a51de-e470-48c0-b592-4a207dfcbb63"
/>
<img width="94" height="76" alt="Screenshot 2026-07-21 at 18 07 53"
src="https://github.com/user-attachments/assets/5e8e0ce3-7536-4689-a6ae-9cfb6e867ddb"
/>


<img width="203" height="77" alt="Screenshot 2026-07-19 at 00 14 32"
src="https://github.com/user-attachments/assets/71c16a54-ad14-4636-8af4-e68b3599f1d0"
/>
<img width="309" height="290" alt="Screenshot 2026-07-18 at 23 37 21"
src="https://github.com/user-attachments/assets/29630504-d69a-4e23-978b-41ae6d0bff92"
/>
<img width="151" height="110" alt="Screenshot 2026-07-18 at 23 35 47"
src="https://github.com/user-attachments/assets/b8107602-12f1-4f9b-a3f6-b930af25c9e7"
/>

---------

Signed-off-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwind
2026-07-22 02:22:05 +02:00
committed by GitHub
parent 4af9156c36
commit 871c807b95
9 changed files with 37 additions and 24 deletions

View File

@@ -12,10 +12,21 @@ type TippyOpts = {
const visibleInstances = new Set<Instance>();
const arrowSvg = html`<svg width="16" height="7"><path d="m0 7 8-7 8 7Z" class="tippy-svg-arrow-outer"/><path d="m0 8 8-7 8 7Z" class="tippy-svg-arrow-inner"/></svg>`;
// shrink tippy's default 3px arrow padding so the arrow can point at the center of
// narrow references like 16px icons with "start"/"end" placements
function arrowPadding({placement, reference}: {placement: Placement, reference: {width: number, height: number}}): number {
const isVertical = placement.startsWith('left') || placement.startsWith('right');
const referenceLength = isVertical ? reference.height : reference.width;
return Math.max(0, Math.min(3, referenceLength / 2 - 8)); // 8 = half of arrow width
}
export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
// the callback functions should be destructured from opts,
// because we should use our own wrapper functions to handle them, do not let the user override them
const {onHide, onShow, onDestroy, role, theme, arrow, ...other} = opts;
// CSS theme, either "default", "tooltip", "menu", "box-with-header" or "bare"
const resolvedTheme = theme || role || 'default';
const resolvedArrow = arrow ?? (resolvedTheme === 'bare' ? false : arrowSvg);
const instance: Instance = tippy(target, {
appendTo: document.body,
@@ -44,12 +55,12 @@ export function createTippy(target: Element, opts: TippyOpts = {}): Instance {
target.setAttribute('aria-controls', instance.popper.id);
return onShow?.(instance);
},
arrow: arrow ?? (theme === 'bare' ? false : arrowSvg),
arrow: resolvedArrow,
popperOptions: {modifiers: [{name: 'arrow', options: {padding: arrowPadding}}]},
// HTML role attribute, ideally the default role would be "popover" but it does not exist
role: role || 'menu',
// CSS theme, either "default", "tooltip", "menu", "box-with-header" or "bare"
theme: theme || role || 'default',
offset: [0, arrow ? 10 : 6],
theme: resolvedTheme,
offset: [0, resolvedArrow ? 10 : 6],
plugins: [followCursor],
...other,
} satisfies Partial<Props>);
@@ -87,7 +98,7 @@ function attachTooltip(target: Element, content: Content | null = null): Instanc
theme: 'tooltip',
hideOnClick,
allowHTML: target.getAttribute('data-tooltip-render') === 'html',
placement: target.getAttribute('data-tooltip-placement') as Placement || 'top-start',
placement: target.getAttribute('data-tooltip-placement') as Placement || 'top',
followCursor: target.getAttribute('data-tooltip-follow-cursor') as Props['followCursor'] || false,
...((target.getAttribute('data-tooltip-interactive') === 'true') && {interactive: true, aria: {content: 'describedby', expanded: false}}),
};
@@ -115,9 +126,14 @@ function switchTitleToTooltip(target: Element): void {
* Some browsers like PaleMoon don't support "addEventListener('mouseenter', capture)"
* The tippy by default uses "mouseenter" event to show, so we use "mouseover" event to switch to tippy
*/
function lazyTooltipOnMouseHover(this: HTMLElement, e: Event): void {
(e.target as HTMLElement).removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
attachTooltip(this);
function lazyTooltipOnMouseHover(e: Event): void {
const el = e.currentTarget as HTMLElement;
el.removeEventListener('mouseover', lazyTooltipOnMouseHover, true);
// Firefox skips enter/leave dispatch when the window had no such listeners at the time of the
// pointer crossing, so the new tippy misses its first "mouseenter". Show via a synthetic event,
// carrying over the cursor position for "followCursor" tooltips.
const {clientX, clientY} = e as MouseEvent;
attachTooltip(el)?.reference.dispatchEvent(new MouseEvent('mouseenter', {clientX, clientY}));
}
// Activate the tooltip for current element.