mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-22 14:25:18 +02:00
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:
@@ -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">
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user