Files
leetcode/apps/docs/components/SourceSidebar.astro
T

91 lines
3.4 KiB
Plaintext

---
/**
* The `Sidebar` slot (also the mobile drawer's nav, which defaults to it): a
* Fumadocs-style content-source selector, then Blume's own nav tree scoped to
* the active source (`sources.ts`).
*
* The selector is a `<details>` with Blume's `data-blume-dropdown` light
* dismiss (outside press, Escape, focus leaving), like its own NavSelector.
*/
import Icon from "blume/components/Icon.astro";
import { withMountedBase } from "blume/components/islands/base-path.ts";
import NavTree from "blume/components/layout/NavTree.astro";
import type { UIStrings } from "blume/core/i18n-ui.ts";
import { isUnderPath } from "blume/core/navigation.ts";
import type { NavNode } from "blume/core/types.ts";
import { SOURCES } from "../sources.ts";
interface Props {
items: NavNode[];
currentRoute: string;
strings?: UIStrings["nav"];
ids?: Map<NavNode, string>;
fragmentBase?: string;
}
const { items, currentRoute, ...tree } = Astro.props;
const active =
SOURCES.filter((s) => isUnderPath(currentRoute, s.path)).sort(
(a, b) => b.path.length - a.path.length,
)[0] ?? SOURCES[0]!;
const claims = (node: NavNode, path: string) => {
const at = node.kind === "page" ? node.route : node.path;
return at !== undefined && isUnderPath(at, path);
};
// The root source keeps every node no other source claims; any other source
// shows its folder's children, as if that folder were the content root.
const scoped =
active.path === "/"
? items.filter(
(node) => !SOURCES.some((s) => s.path !== "/" && claims(node, s.path)),
)
: items.flatMap((node) =>
node.kind === "group" && node.path === active.path ? node.children : [],
);
---
<details class="group relative mb-3" data-blume-dropdown>
<summary
aria-label={`Content source: ${active.label}`}
class="flex w-full cursor-pointer list-none items-center gap-2.5 rounded-xl border border-border bg-muted/50 p-1.5 pe-2.5 text-foreground text-sm transition-colors hover:bg-muted group-open:bg-muted [&::-webkit-details-marker]:hidden"
>
<span class="flex size-7 shrink-0 items-center justify-center rounded-lg border border-border bg-background">
<Icon name={active.icon} size={15} />
</span>
<span class="min-w-0 flex-1 truncate font-medium">{active.label}</span>
<Icon class="shrink-0 text-muted-foreground" name="chevrons-up-down" size={14} />
</summary>
<div
class="absolute inset-x-0 z-50 mt-1.5 rounded-xl border border-border bg-background p-1 shadow-xl"
data-blume-dropdown-panel
>
{
SOURCES.map((source) => (
<a
aria-current={source === active ? "true" : undefined}
class="flex items-start gap-2.5 rounded-lg p-2 text-muted-foreground text-sm transition-colors hover:bg-muted hover:text-foreground aria-[current=true]:text-foreground"
href={withMountedBase(source.path)}
>
<Icon class="mt-0.5 shrink-0" name={source.icon} size={16} />
<span class="min-w-0 flex-1">
<span class="block font-medium">{source.label}</span>
<span class="block text-muted-foreground text-xs">{source.description}</span>
</span>
{source === active && <Icon class="mt-0.5 shrink-0" name="check" size={14} />}
</a>
))
}
</div>
</details>
<NavTree {...tree} currentRoute={currentRoute} items={scoped} />
<script>
import { installDropdownDismiss } from "blume/components/dropdown-dismiss.ts";
installDropdownDismiss();
</script>