mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 21:22:43 +00:00
91 lines
3.4 KiB
Plaintext
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>
|