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

@@ -7,15 +7,15 @@
</a>
<span class="ui small label">{{.NumReleases}}</span>
</div>
<div class="flex-relaxed-list">
<div>
<div class="flex-text-block">
<div>{{svg "octicon-tag"}}</div>
<a class="gt-ellipsis muted" href="{{.LatestRelease.Link}}" title="{{.LatestRelease.Title}}">{{.LatestRelease.Title}}</a>
<a class="gt-ellipsis muted" href="{{.LatestRelease.Link}}" title="{{.LatestRelease.Title}}"><strong>{{.LatestRelease.Title}}</strong></a>
<div class="tw-shrink-0">
{{template "repo/release/label" (dict "Release" .LatestRelease "IsLatest" true)}}
</div>
</div>
<div class="tw-ml-[24px]">{{DateUtils.TimeSince .LatestRelease.CreatedUnix}}</div>
<div class="tw-ml-[24px] tw-text-12">{{DateUtils.TimeSince .LatestRelease.CreatedUnix}}</div>
</div>
{{end}}
@@ -28,7 +28,7 @@
<div>
<div class="language-stats">
{{range .LanguageStats}}
<div class="bar" style="width: {{.Percentage}}%; background-color: {{.Color}}" data-tooltip-placement="top" data-tooltip-content="{{.Language}}" data-tooltip-follow-cursor="horizontal"></div>
<div class="bar" style="width: {{.Percentage}}%; background-color: {{.Color}}" data-tooltip-content="{{.Language}}" data-tooltip-follow-cursor="horizontal"></div>
{{end}}
</div>
<div class="language-stats-details">

View File

@@ -63,7 +63,7 @@
{{svg "octicon-cross-reference"}} {{ctx.Locale.Tr "repo.cite_this_repo"}}
</a>
{{end}}
<div class="flex-text-block muted" {{if not (eq .Repository.Size 0)}}data-tooltip-placement="top" data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>
<div class="flex-text-block muted" {{if not (eq .Repository.Size 0)}}data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>
{{$fileSizeFormatted := FileSize .Repository.Size}}{{/* the formatted string is always "{val} {unit}" */}}
{{$fileSizeFields := StringUtils.Split $fileSizeFormatted " "}}
{{svg "octicon-database"}} <b>{{ctx.Locale.PrettyNumber (index $fileSizeFields 0)}}</b> {{index $fileSizeFields 1}}

View File

@@ -52,7 +52,7 @@
}
.tippy-box[data-theme="tooltip"] .tippy-content {
padding: 0.5rem 1rem;
padding: 5px 10px;
}
.tippy-box[data-theme="tooltip"] .tippy-svg-arrow-inner,

View File

@@ -249,7 +249,7 @@ gitea-theme-meta-info {
--color-reaction-hover-bg: var(--color-primary-light-4);
--color-reaction-active-bg: var(--color-primary-light-5);
--color-tooltip-text: #fafafa;
--color-tooltip-bg: #0b0b0cf0;
--color-tooltip-bg: #2b2e33;
--color-nav-bg: #18191b;
--color-nav-hover-bg: var(--color-secondary-light-1);
--color-nav-text: var(--color-text);

View File

@@ -249,7 +249,7 @@ gitea-theme-meta-info {
--color-reaction-hover-bg: var(--color-primary-light-5);
--color-reaction-active-bg: var(--color-primary-light-6);
--color-tooltip-text: #fbfdff;
--color-tooltip-bg: #000017f0;
--color-tooltip-bg: #25292e;
--color-nav-bg: #f6f7fa;
--color-nav-hover-bg: var(--color-secondary-light-1);
--color-nav-text: var(--color-text);

View File

@@ -493,7 +493,7 @@ async function hashChangeListener() {
<SvgIcon
v-else
name="octicon-chevron-right"
class="tw-mr-2 step-summary-chevron"
class="step-summary-chevron"
:class="{'tw-invisible': !isExpandable(jobStep.status)}"
/>
<ActionStatusIcon :status="jobStep.status" icon-variant="circle-fill"/>

View File

@@ -132,7 +132,7 @@ onBeforeUnmount(() => {
<!-- make the view container full width to make users easier to read logs -->
<div class="ui fluid container">
<div class="action-view-header">
<a v-if="backLink" class="action-view-back silenced" :href="backLink.href">
<a v-if="backLink" class="action-view-back" :href="backLink.href">
<SvgIcon name="octicon-arrow-left" :size="14"/>
<span>{{ backLink.prefix }} <span class="action-view-back-name">{{ backLink.name }}</span></span>
</a>
@@ -188,7 +188,7 @@ onBeforeUnmount(() => {
</div>
<div class="flex-text-block tw-pl-[20px]">
<span class="flex-text-inline tw-flex-shrink-0">
<ActionStatusIcon :locale-status="locale.status[attempt.status]" :status="attempt.status" :size="14" class="flex-text-block" icon-variant="circle-fill"/>
<ActionStatusIcon :locale-status="locale.status[attempt.status]" :status="attempt.status" :size="14" icon-variant="circle-fill"/>
<span>{{ locale.status[attempt.status] }}</span>
</span>
<span></span>
@@ -364,14 +364,10 @@ onBeforeUnmount(() => {
gap: 4px;
font-size: 13px;
color: var(--color-text-light-1);
text-decoration: none;
}
.action-view-back:hover {
color: var(--color-primary);
}
.action-view-back-name {
font-weight: var(--font-weight-bold);
color: var(--color-text);
}

View File

@@ -434,6 +434,7 @@ function onNodeClick(job: GraphNode | ActionsJob, event: MouseEvent) {
background: var(--color-console-bg);
gap: var(--gap-block);
flex-wrap: wrap;
border-bottom: 1px solid var(--color-secondary);
}
.graph-workflow-info {

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.