enhance(ui): cleanup navbar template and styling (#39554)

Clean up the navbar template and styling, and fix the navbar stopwatch,
which navigated to the issue instead of opening its popup since
https://github.com/go-gitea/gitea/pull/36965.

1. Add hover background to the create and user menus
2. Simplify navbar HTML and CSS and remove Fomantic styles
3. Render the notification and stopwatch icons once instead of separate
mobile and desktop copies
4. Make the stopwatch a keyboard accessible button whose popup updates
on push and closes when the stopwatch stops

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
silverwind
2026-10-03 18:35:54 +02:00
committed by GitHub
parent f65e01226a
commit 516a4883fa
9 changed files with 165 additions and 225 deletions
+13 -9
View File
@@ -1,5 +1,5 @@
import {test, expect} from '@playwright/test';
import {loginUser, baseUrl, apiUserHeaders, apiCreateUser, apiCreateRepo, apiCreateIssue, apiStartStopwatch, apiCancelStopwatch, apiCloseIssue, randomString} from './utils.ts';
import {loginUser, baseUrl, apiUserHeaders, apiCreateUser, apiCreateRepo, apiCreateIssue, apiStartStopwatch, apiCloseIssue, randomString} from './utils.ts';
// The /-/ws WebSocket pipeline is push-only: every event is fired by the server
// immediately on the DB write. These tests exercise that each event type
@@ -17,7 +17,7 @@ test.describe('events', () => {
loginUser(page, owner),
]);
await page.goto('/');
const badge = page.locator('a.not-mobile .notification_count');
const badge = page.locator('#navbar .notification_count');
await expect(badge).toBeHidden();
await expect(page.locator('html[data-user-events-connected]')).toBeAttached();
@@ -26,7 +26,7 @@ test.describe('events', () => {
await expect(badge).toBeVisible();
});
test('stopwatch appears and hides via real-time push', async ({page, request}) => {
test('stopwatch appears via real-time push and stops from its popup', async ({page, request}) => {
const name = `ev-sw-push-${randomString(8)}`;
const headers = apiUserHeaders(name);
@@ -40,23 +40,23 @@ test.describe('events', () => {
]);
// Page loads before the stopwatch starts — the icon is hidden in the rendered HTML
await page.goto('/');
const stopwatch = page.locator('.active-stopwatch.not-mobile');
const stopwatch = page.getByTitle('Active Time Tracker');
// Element must exist in the DOM (just hidden); otherwise the push has nothing to reveal.
await expect(stopwatch).toHaveCount(1);
await expect(stopwatch).toBeHidden();
await expect(page.locator('html[data-user-events-connected]')).toBeAttached();
// Drive both directions from outside this tab; each push must reach it
await apiStartStopwatch(request, name, name, 1, {headers});
await expect(stopwatch).toBeVisible();
await apiCancelStopwatch(request, name, name, 1, {headers});
await stopwatch.click();
await page.getByRole('button', {name: 'Stop Timer'}).click();
await expect(stopwatch).toBeHidden();
});
// Closing an issue stops the timer away from any stopwatch route handler.
test('stopwatch renders when already active and hides when the issue is closed', async ({page, request}) => {
test('stopwatch renders when already active and hides with its popup when the issue is closed', async ({page, request}) => {
const name = `ev-sw-close-${randomString(8)}`;
const headers = apiUserHeaders(name);
@@ -70,12 +70,16 @@ test.describe('events', () => {
})(),
]);
await page.goto('/');
const stopwatch = page.locator('.active-stopwatch.not-mobile');
const stopwatch = page.getByTitle('Active Time Tracker');
await expect(stopwatch).toBeVisible();
await expect(page.locator('html[data-user-events-connected]')).toBeAttached();
await stopwatch.click();
const stopButton = page.getByRole('button', {name: 'Stop Timer'});
await expect(stopButton).toBeVisible();
await apiCloseIssue(request, name, name, 1, {headers});
await expect(stopwatch).toBeHidden();
await expect(stopButton).toBeHidden();
});
// Repro for https://github.com/go-gitea/gitea/pull/36965#issuecomment-4321282667:
@@ -103,7 +107,7 @@ test.describe('events', () => {
// neither of these would be present.
await expect(page.getByRole('button', {name: 'Stop timer'})).toBeVisible();
await expect(page.getByRole('button', {name: 'Discard timer'})).toBeVisible();
await expect(page.locator('.active-stopwatch.not-mobile')).toBeVisible();
await expect(page.getByTitle('Active Time Tracker')).toBeVisible();
});
test('logout propagation', async ({browser, request}) => {
-6
View File
@@ -84,12 +84,6 @@ export async function apiCreateFiles(requestContext: APIRequestContext, owner: s
}), 'apiCreateFiles');
}
export async function apiCancelStopwatch(requestContext: APIRequestContext, owner: string, repo: string, issueIndex: number, {headers}: {headers?: Record<string, string>} = {}) {
await apiRetry(() => requestContext.delete(`${baseUrl()}/api/v1/repos/${owner}/${repo}/issues/${issueIndex}/stopwatch/delete`, {
headers: headers || apiHeaders(),
}), 'apiCancelStopwatch');
}
export async function apiCloseIssue(requestContext: APIRequestContext, owner: string, repo: string, issueIndex: number, {headers}: {headers?: Record<string, string>} = {}) {
await apiRetry(() => requestContext.patch(`${baseUrl()}/api/v1/repos/${owner}/${repo}/issues/${issueIndex}`, {
headers: headers || apiHeaders(),