mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 11:20:36 +08:00
Use anchors on home view for better accesibility
Convert clickable elements for filtering repos to anchors so that they can be focused and activated with the keyboard.
This commit is contained in:
@@ -22,6 +22,27 @@ body {
|
||||
background-color: var(--color-background-default);
|
||||
}
|
||||
|
||||
a,
|
||||
a:link,
|
||||
a:visited,
|
||||
a:hover,
|
||||
a:active,
|
||||
a:focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline;
|
||||
font-weight: inherit;
|
||||
font-style: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--color-fill-yellow);
|
||||
color: var(--color-foreground-black);
|
||||
|
||||
@@ -27,7 +27,6 @@
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
background-color: var(--color-background-float);
|
||||
clip-path: var(--1px-corner-fill);
|
||||
display: flex;
|
||||
font-size: var(--font-size-small);
|
||||
justify-content: space-between;
|
||||
@@ -54,65 +53,58 @@
|
||||
styleFlexDirection="column"
|
||||
styleGap="2px"
|
||||
styleBackgroundColor="var(--color-background-float)">
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
<a
|
||||
tabindex="0"
|
||||
class="tab txt-small"
|
||||
class:active={activeTab === "all"}
|
||||
onclick={() => router.push({ resource: "home", activeTab: "all" })}>
|
||||
href={router.routeToPath({
|
||||
resource: "home",
|
||||
activeTab: "all",
|
||||
})}>
|
||||
<div class="global-flex"><Icon name="repo" />Repositories</div>
|
||||
<div class="global-counter">
|
||||
{repoCount.total}
|
||||
</div>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
</a>
|
||||
<a
|
||||
class="tab"
|
||||
class:active={activeTab === "delegate"}
|
||||
onclick={() =>
|
||||
router.push({
|
||||
resource: "home",
|
||||
activeTab: "delegate",
|
||||
})}>
|
||||
href={router.routeToPath({
|
||||
resource: "home",
|
||||
activeTab: "delegate",
|
||||
})}>
|
||||
<div class="global-flex">
|
||||
<Icon name="delegate" />
|
||||
<div>Delegate</div>
|
||||
</div>
|
||||
<div class="global-counter">{repoCount.delegate}</div>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
</a>
|
||||
<a
|
||||
class="tab"
|
||||
class:active={activeTab === "contributor"}
|
||||
onclick={() =>
|
||||
router.push({
|
||||
resource: "home",
|
||||
activeTab: "contributor",
|
||||
})}>
|
||||
href={router.routeToPath({
|
||||
resource: "home",
|
||||
activeTab: "contributor",
|
||||
})}>
|
||||
<div class="global-flex">
|
||||
<Icon name="user" />
|
||||
<div>Contributor</div>
|
||||
</div>
|
||||
<div class="global-counter">{repoCount.contributor}</div>
|
||||
</div>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
</a>
|
||||
<a
|
||||
class="tab"
|
||||
class:active={activeTab === "private"}
|
||||
onclick={() =>
|
||||
router.push({
|
||||
resource: "home",
|
||||
activeTab: "private",
|
||||
})}>
|
||||
href={router.routeToPath({
|
||||
resource: "home",
|
||||
activeTab: "private",
|
||||
})}>
|
||||
<div class="global-flex">
|
||||
<Icon name="lock" />
|
||||
<div>Private</div>
|
||||
</div>
|
||||
<div class="global-counter">{repoCount.private}</div>
|
||||
</div>
|
||||
</a>
|
||||
</Border>
|
||||
</div>
|
||||
|
||||
|
||||
+32
-8
@@ -1,4 +1,9 @@
|
||||
import {
|
||||
homeRouteToPath as homeRouteToPath,
|
||||
homeUrlToRoute,
|
||||
} from "@app/views/home/router";
|
||||
import { repoRouteToPath, repoUrlToRoute } from "@app/views/repo/router";
|
||||
import { on } from "svelte/events";
|
||||
import { get, writable } from "svelte/store";
|
||||
|
||||
import * as mutexExecutor from "@app/lib/mutexExecutor";
|
||||
@@ -50,7 +55,27 @@ async function navigateToUrl(
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("popstate", () => loadFromLocation());
|
||||
const offPopstate = on(window, "popstate", () => loadFromLocation());
|
||||
|
||||
const offNavigateAnchor = on(document, "click", e => {
|
||||
const [anchor] = e
|
||||
.composedPath()
|
||||
.flatMap(target => (target instanceof HTMLAnchorElement ? [target] : []));
|
||||
if (anchor && anchor.getAttribute("href")?.startsWith("/")) {
|
||||
e.preventDefault();
|
||||
void navigateToUrl(
|
||||
"push",
|
||||
new URL(anchor.getAttribute("href") ?? "", window.location.href),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.dispose(() => {
|
||||
offPopstate();
|
||||
offNavigateAnchor();
|
||||
});
|
||||
}
|
||||
|
||||
const loadExecutor = mutexExecutor.create();
|
||||
|
||||
@@ -100,13 +125,12 @@ function urlToRoute(url: URL): Route | null {
|
||||
const segments = url.pathname.substring(1).split("/");
|
||||
const resource = segments.shift();
|
||||
|
||||
const homeRoute = homeUrlToRoute(url);
|
||||
if (homeRoute) {
|
||||
return homeRoute;
|
||||
}
|
||||
|
||||
switch (resource) {
|
||||
case "": {
|
||||
return {
|
||||
resource: "home",
|
||||
activeTab: "all",
|
||||
};
|
||||
}
|
||||
case "repos": {
|
||||
return repoUrlToRoute(segments, url.searchParams);
|
||||
}
|
||||
@@ -118,7 +142,7 @@ function urlToRoute(url: URL): Route | null {
|
||||
|
||||
export function routeToPath(route: Route): string {
|
||||
if (route.resource === "home") {
|
||||
return "/";
|
||||
return homeRouteToPath(route);
|
||||
} else if (
|
||||
route.resource === "repo.home" ||
|
||||
route.resource === "repo.createIssue" ||
|
||||
|
||||
@@ -2,10 +2,19 @@ import type { Config } from "@bindings/config/Config";
|
||||
import type { RepoCount } from "@bindings/repo/RepoCount";
|
||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||
|
||||
import z from "zod";
|
||||
|
||||
import { invoke } from "@app/lib/invoke";
|
||||
|
||||
export type HomeReposTab = "all" | "delegate" | "private" | "contributor";
|
||||
|
||||
const homeReposTabSchema = z.union([
|
||||
z.literal("all"),
|
||||
z.literal("delegate"),
|
||||
z.literal("private"),
|
||||
z.literal("contributor"),
|
||||
]);
|
||||
|
||||
export interface HomeRoute {
|
||||
resource: "home";
|
||||
activeTab: HomeReposTab;
|
||||
@@ -56,3 +65,22 @@ export async function loadHome(route: HomeRoute): Promise<LoadedHomeRoute> {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function homeUrlToRoute(url: URL): HomeRoute | undefined {
|
||||
if (url.pathname === "/") {
|
||||
return {
|
||||
resource: "home",
|
||||
activeTab: homeReposTabSchema
|
||||
.catch(() => "all" as const)
|
||||
.parse(url.searchParams.get("tab")),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function homeRouteToPath(route: HomeRoute): string {
|
||||
const searchParams = new URLSearchParams();
|
||||
if (route.activeTab !== "all") {
|
||||
searchParams.append("tab", route.activeTab);
|
||||
}
|
||||
return `/?${searchParams.toString()}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user