@silicajs/components 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/backlinks.d.ts +7 -6
- package/dist/backlinks.js +9 -18
- package/dist/backlinks.js.map +1 -1
- package/dist/breadcrumbs.d.ts +7 -4
- package/dist/breadcrumbs.js +13 -21
- package/dist/breadcrumbs.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/search.js +14 -2
- package/dist/search.js.map +1 -1
- package/dist/vault-tree.d.ts +4 -3
- package/dist/vault-tree.js +43 -4
- package/dist/vault-tree.js.map +1 -1
- package/package.json +2 -2
package/dist/backlinks.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { Graph, Manifest } from '@silicajs/core/runtime';
|
|
3
2
|
|
|
4
|
-
type
|
|
5
|
-
graph: Graph;
|
|
3
|
+
type BacklinkItem = {
|
|
6
4
|
slug: string;
|
|
7
|
-
|
|
5
|
+
title: string;
|
|
6
|
+
};
|
|
7
|
+
type BacklinksProps = {
|
|
8
|
+
backlinks: BacklinkItem[];
|
|
8
9
|
className?: string;
|
|
9
10
|
};
|
|
10
|
-
declare function Backlinks({
|
|
11
|
+
declare function Backlinks({ backlinks, className }: BacklinksProps): react_jsx_runtime.JSX.Element | null;
|
|
11
12
|
|
|
12
|
-
export { Backlinks, type BacklinksProps };
|
|
13
|
+
export { type BacklinkItem, Backlinks, type BacklinksProps };
|
package/dist/backlinks.js
CHANGED
|
@@ -7,27 +7,18 @@ import {
|
|
|
7
7
|
} from "@silicajs/ui/components/card";
|
|
8
8
|
import { SilicaLink } from "./routing.js";
|
|
9
9
|
import { slugToHref } from "./slug.js";
|
|
10
|
-
function Backlinks({
|
|
11
|
-
graph,
|
|
12
|
-
slug,
|
|
13
|
-
manifest,
|
|
14
|
-
className
|
|
15
|
-
}) {
|
|
16
|
-
const backlinks = graph.backlinks[slug] ?? [];
|
|
10
|
+
function Backlinks({ backlinks, className }) {
|
|
17
11
|
if (backlinks.length === 0) return null;
|
|
18
12
|
return /* @__PURE__ */ jsxs(Card, { size: "sm", className, "data-slot": "backlinks", children: [
|
|
19
13
|
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: "Backlinks" }) }),
|
|
20
|
-
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1.5 text-sm", children: backlinks.map((
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
) }, source);
|
|
30
|
-
}) }) })
|
|
14
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("ul", { className: "flex flex-col gap-1.5 text-sm", children: backlinks.map((backlink) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
|
|
15
|
+
SilicaLink,
|
|
16
|
+
{
|
|
17
|
+
href: slugToHref(backlink.slug),
|
|
18
|
+
className: "text-primary hover:underline",
|
|
19
|
+
children: backlink.title
|
|
20
|
+
}
|
|
21
|
+
) }, backlink.slug)) }) })
|
|
31
22
|
] });
|
|
32
23
|
}
|
|
33
24
|
export {
|
package/dist/backlinks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/backlinks.tsx"],"sourcesContent":["import
|
|
1
|
+
{"version":3,"sources":["../src/backlinks.tsx"],"sourcesContent":["import {\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n} from \"@silicajs/ui/components/card\";\n\nimport { SilicaLink } from \"./routing.js\";\nimport { slugToHref } from \"./slug.js\";\n\nexport type BacklinkItem = {\n slug: string;\n title: string;\n};\n\nexport type BacklinksProps = {\n backlinks: BacklinkItem[];\n className?: string;\n};\n\nexport function Backlinks({ backlinks, className }: BacklinksProps) {\n if (backlinks.length === 0) return null;\n return (\n <Card size=\"sm\" className={className} data-slot=\"backlinks\">\n <CardHeader>\n <CardTitle>Backlinks</CardTitle>\n </CardHeader>\n <CardContent>\n <ul className=\"flex flex-col gap-1.5 text-sm\">\n {backlinks.map((backlink) => (\n <li key={backlink.slug}>\n <SilicaLink\n href={slugToHref(backlink.slug)}\n className=\"text-primary hover:underline\"\n >\n {backlink.title}\n </SilicaLink>\n </li>\n ))}\n </ul>\n </CardContent>\n </Card>\n );\n}\n"],"mappings":"AAuBI,SAEI,KAFJ;AAvBJ;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,kBAAkB;AAC3B,SAAS,kBAAkB;AAYpB,SAAS,UAAU,EAAE,WAAW,UAAU,GAAmB;AAClE,MAAI,UAAU,WAAW,EAAG,QAAO;AACnC,SACE,qBAAC,QAAK,MAAK,MAAK,WAAsB,aAAU,aAC9C;AAAA,wBAAC,cACC,8BAAC,aAAU,uBAAS,GACtB;AAAA,IACA,oBAAC,eACC,8BAAC,QAAG,WAAU,iCACX,oBAAU,IAAI,CAAC,aACd,oBAAC,QACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,WAAW,SAAS,IAAI;AAAA,QAC9B,WAAU;AAAA,QAET,mBAAS;AAAA;AAAA,IACZ,KANO,SAAS,IAOlB,CACD,GACH,GACF;AAAA,KACF;AAEJ;","names":[]}
|
package/dist/breadcrumbs.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
|
+
type BreadcrumbLinkItem = {
|
|
4
|
+
label: string;
|
|
5
|
+
href?: string;
|
|
6
|
+
};
|
|
3
7
|
type BreadcrumbsProps = {
|
|
4
|
-
|
|
5
|
-
allSlugs: readonly string[];
|
|
8
|
+
items: BreadcrumbLinkItem[];
|
|
6
9
|
className?: string;
|
|
7
10
|
};
|
|
8
|
-
declare function Breadcrumbs({
|
|
11
|
+
declare function Breadcrumbs({ items, className }: BreadcrumbsProps): react_jsx_runtime.JSX.Element | null;
|
|
9
12
|
|
|
10
|
-
export { Breadcrumbs, type BreadcrumbsProps };
|
|
13
|
+
export { type BreadcrumbLinkItem, Breadcrumbs, type BreadcrumbsProps };
|
package/dist/breadcrumbs.js
CHANGED
|
@@ -9,27 +9,19 @@ import {
|
|
|
9
9
|
BreadcrumbSeparator
|
|
10
10
|
} from "@silicajs/ui/components/breadcrumb";
|
|
11
11
|
import { SilicaLink } from "./routing.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
BreadcrumbLink,
|
|
26
|
-
{
|
|
27
|
-
render: /* @__PURE__ */ jsx(SilicaLink, { href, children: label })
|
|
28
|
-
}
|
|
29
|
-
) : /* @__PURE__ */ jsx(BreadcrumbPage, { children: label }) })
|
|
30
|
-
] }, acc);
|
|
31
|
-
})
|
|
32
|
-
] }) });
|
|
12
|
+
function Breadcrumbs({ items, className }) {
|
|
13
|
+
if (items.length === 0) return null;
|
|
14
|
+
return /* @__PURE__ */ jsx(Breadcrumb, { className, children: /* @__PURE__ */ jsx(BreadcrumbList, { children: items.map((item, index) => {
|
|
15
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
16
|
+
index > 0 ? /* @__PURE__ */ jsx(BreadcrumbSeparator, {}) : null,
|
|
17
|
+
/* @__PURE__ */ jsx(BreadcrumbItem, { children: item.href ? /* @__PURE__ */ jsx(
|
|
18
|
+
BreadcrumbLink,
|
|
19
|
+
{
|
|
20
|
+
render: /* @__PURE__ */ jsx(SilicaLink, { href: item.href, children: item.label })
|
|
21
|
+
}
|
|
22
|
+
) : /* @__PURE__ */ jsx(BreadcrumbPage, { children: item.label }) })
|
|
23
|
+
] }, `${item.href ?? item.label}:${index}`);
|
|
24
|
+
}) }) });
|
|
33
25
|
}
|
|
34
26
|
export {
|
|
35
27
|
Breadcrumbs
|
package/dist/breadcrumbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/breadcrumbs.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@silicajs/ui/components/breadcrumb\";\n\nimport { SilicaLink } from \"./routing.js\";\
|
|
1
|
+
{"version":3,"sources":["../src/breadcrumbs.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbLink,\n BreadcrumbList,\n BreadcrumbPage,\n BreadcrumbSeparator,\n} from \"@silicajs/ui/components/breadcrumb\";\n\nimport { SilicaLink } from \"./routing.js\";\n\nexport type BreadcrumbLinkItem = {\n label: string;\n href?: string;\n};\n\nexport type BreadcrumbsProps = {\n items: BreadcrumbLinkItem[];\n className?: string;\n};\n\nexport function Breadcrumbs({ items, className }: BreadcrumbsProps) {\n if (items.length === 0) return null;\n\n return (\n <Breadcrumb className={className}>\n <BreadcrumbList>\n {items.map((item, index) => {\n return (\n <React.Fragment key={`${item.href ?? item.label}:${index}`}>\n {index > 0 ? <BreadcrumbSeparator /> : null}\n <BreadcrumbItem>\n {item.href ? (\n <BreadcrumbLink\n render={\n <SilicaLink href={item.href}>{item.label}</SilicaLink>\n }\n />\n ) : (\n <BreadcrumbPage>{item.label}</BreadcrumbPage>\n )}\n </BreadcrumbItem>\n </React.Fragment>\n );\n })}\n </BreadcrumbList>\n </Breadcrumb>\n );\n}\n"],"mappings":"AA+BY,SACe,KADf;AA/BZ,YAAY,WAAW;AAEvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,kBAAkB;AAYpB,SAAS,YAAY,EAAE,OAAO,UAAU,GAAqB;AAClE,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,SACE,oBAAC,cAAW,WACV,8BAAC,kBACE,gBAAM,IAAI,CAAC,MAAM,UAAU;AAC1B,WACE,qBAAC,MAAM,UAAN,EACE;AAAA,cAAQ,IAAI,oBAAC,uBAAoB,IAAK;AAAA,MACvC,oBAAC,kBACE,eAAK,OACJ;AAAA,QAAC;AAAA;AAAA,UACC,QACE,oBAAC,cAAW,MAAM,KAAK,MAAO,eAAK,OAAM;AAAA;AAAA,MAE7C,IAEA,oBAAC,kBAAgB,eAAK,OAAM,GAEhC;AAAA,SAZmB,GAAG,KAAK,QAAQ,KAAK,KAAK,IAAI,KAAK,EAaxD;AAAA,EAEJ,CAAC,GACH,GACF;AAEJ;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { Backlinks, BacklinksProps } from './backlinks.js';
|
|
2
|
-
export { Breadcrumbs, BreadcrumbsProps } from './breadcrumbs.js';
|
|
1
|
+
export { BacklinkItem, Backlinks, BacklinksProps } from './backlinks.js';
|
|
2
|
+
export { BreadcrumbLinkItem, Breadcrumbs, BreadcrumbsProps } from './breadcrumbs.js';
|
|
3
3
|
export { DarkModeToggle, DarkModeToggleProps } from './dark-mode-toggle.js';
|
|
4
4
|
export { GoogleIcon } from './google-icon.js';
|
|
5
5
|
export { NotAllowed, NotAllowedProps } from './not-allowed.js';
|
|
@@ -14,5 +14,5 @@ export { UserMenu, UserMenuProps } from './user-menu.js';
|
|
|
14
14
|
export { VaultTree, VaultTreeProps } from './vault-tree.js';
|
|
15
15
|
export { breadcrumbSegmentHref, prettySegment, slugToHref } from './slug.js';
|
|
16
16
|
import 'react/jsx-runtime';
|
|
17
|
-
import '@silicajs/core/runtime';
|
|
18
17
|
import 'react';
|
|
18
|
+
import '@silicajs/core/runtime';
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export {\n Backlinks,\n type BacklinkItem,\n type BacklinksProps,\n} from \"./backlinks.js\";\nexport {\n Breadcrumbs,\n type BreadcrumbLinkItem,\n type BreadcrumbsProps,\n} from \"./breadcrumbs.js\";\nexport {\n DarkModeToggle,\n type DarkModeToggleProps,\n} from \"./dark-mode-toggle.js\";\nexport { GoogleIcon } from \"./google-icon.js\";\nexport { NotAllowed, type NotAllowedProps } from \"./not-allowed.js\";\nexport { SignInShell, type SignInShellProps } from \"./sign-in-shell.js\";\nexport { NotFound, type NotFoundProps } from \"./not-found.js\";\nexport { PageProperties, type PagePropertiesProps } from \"./page-properties.js\";\nexport {\n SearchPalette,\n SearchTrigger,\n type SearchPaletteProps,\n type SearchTriggerProps,\n} from \"./search.js\";\nexport {\n SilicaLink,\n SilicaRoutingProvider,\n useSilicaRouting,\n type SilicaLinkComponent,\n type SilicaLinkProps,\n type SilicaRoutingContextValue,\n type SilicaRoutingProviderProps,\n} from \"./routing.js\";\nexport {\n TableOfContents,\n type TableOfContentsProps,\n} from \"./table-of-contents.js\";\nexport { TagsList, type TagsListProps } from \"./tags-list.js\";\nexport { UserMenu, type UserMenuProps } from \"./user-menu.js\";\nexport { VaultTree, type VaultTreeProps } from \"./vault-tree.js\";\nexport { breadcrumbSegmentHref, prettySegment, slugToHref } from \"./slug.js\";\n"],"mappings":"AAAA;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,kBAAkB;AAC3B,SAAS,kBAAwC;AACjD,SAAS,mBAA0C;AACnD,SAAS,gBAAoC;AAC7C,SAAS,sBAAgD;AACzD;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AACP;AAAA,EACE;AAAA,OAEK;AACP,SAAS,gBAAoC;AAC7C,SAAS,gBAAoC;AAC7C,SAAS,iBAAsC;AAC/C,SAAS,uBAAuB,eAAe,kBAAkB;","names":[]}
|
package/dist/search.js
CHANGED
|
@@ -139,8 +139,8 @@ function SearchPalette({ open, onOpenChange }) {
|
|
|
139
139
|
close();
|
|
140
140
|
},
|
|
141
141
|
children: /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
|
|
142
|
-
/* @__PURE__ */ jsx("span", { className: "truncate font-medium text-foreground", children: result.
|
|
143
|
-
result.
|
|
142
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-medium text-foreground", children: /* @__PURE__ */ jsx(HighlightedText, { parts: result.titleParts }) }),
|
|
143
|
+
result.excerptParts.length > 0 ? /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: /* @__PURE__ */ jsx(HighlightedText, { parts: result.excerptParts }) }) : null
|
|
144
144
|
] })
|
|
145
145
|
},
|
|
146
146
|
result.slug
|
|
@@ -151,6 +151,18 @@ function SearchPalette({ open, onOpenChange }) {
|
|
|
151
151
|
)
|
|
152
152
|
] });
|
|
153
153
|
}
|
|
154
|
+
function HighlightedText({ parts }) {
|
|
155
|
+
return /* @__PURE__ */ jsx(Fragment, { children: parts.map(
|
|
156
|
+
(part, index) => part.highlighted ? /* @__PURE__ */ jsx(
|
|
157
|
+
"mark",
|
|
158
|
+
{
|
|
159
|
+
className: "bg-transparent p-0 font-medium text-primary",
|
|
160
|
+
children: part.text
|
|
161
|
+
},
|
|
162
|
+
index
|
|
163
|
+
) : /* @__PURE__ */ jsx(React.Fragment, { children: part.text }, index)
|
|
164
|
+
) });
|
|
165
|
+
}
|
|
154
166
|
export {
|
|
155
167
|
SearchPalette,
|
|
156
168
|
SearchTrigger
|
package/dist/search.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/search.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@silicajs/ui/components/command\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from \"@silicajs/ui/components/dialog\";\nimport { Button } from \"@silicajs/ui/components/button\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { useSilicaRouting } from \"./routing.js\";\nimport { slugToHref } from \"./slug.js\";\n\ntype SearchResult = {\n slug: string;\n title: string;\n
|
|
1
|
+
{"version":3,"sources":["../src/search.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n Command,\n CommandEmpty,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@silicajs/ui/components/command\";\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n} from \"@silicajs/ui/components/dialog\";\nimport { Button } from \"@silicajs/ui/components/button\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { useSilicaRouting } from \"./routing.js\";\nimport { slugToHref } from \"./slug.js\";\n\ntype SearchHighlightPart = {\n text: string;\n highlighted: boolean;\n};\n\ntype SearchResult = {\n slug: string;\n title: string;\n titleParts: SearchHighlightPart[];\n excerptParts: SearchHighlightPart[];\n};\n\nexport type SearchTriggerProps = {\n placeholder?: string;\n className?: string;\n};\n\nexport function SearchTrigger({\n placeholder = \"Search…\",\n className,\n}: SearchTriggerProps) {\n const [open, setOpen] = React.useState(false);\n\n React.useEffect(() => {\n const onKeyDown = (event: KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === \"k\") {\n event.preventDefault();\n setOpen((value) => !value);\n }\n };\n window.addEventListener(\"keydown\", onKeyDown);\n return () => window.removeEventListener(\"keydown\", onKeyDown);\n }, []);\n\n return (\n <>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setOpen(true)}\n className={className}\n >\n <SearchIcon\n data-icon=\"inline-start\"\n className=\"text-muted-foreground\"\n />\n <span className=\"flex-1 text-left text-muted-foreground\">\n {placeholder}\n </span>\n <kbd\n data-icon=\"inline-end\"\n className=\"pointer-events-none ml-2 inline-flex h-5 select-none items-center gap-1 rounded border border-border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground\"\n >\n <span className=\"text-xs\">⌘</span>K\n </kbd>\n </Button>\n <SearchPalette open={open} onOpenChange={setOpen} />\n </>\n );\n}\n\nexport type SearchPaletteProps = {\n open: boolean;\n onOpenChange: (next: boolean) => void;\n};\n\nexport function SearchPalette({ open, onOpenChange }: SearchPaletteProps) {\n const { navigate } = useSilicaRouting();\n const [query, setQuery] = React.useState(\"\");\n const [results, setResults] = React.useState<SearchResult[]>([]);\n const [isLoading, setIsLoading] = React.useState(false);\n\n React.useEffect(() => {\n if (!open) {\n setQuery(\"\");\n setResults([]);\n setIsLoading(false);\n }\n }, [open]);\n\n React.useEffect(() => {\n const controller = new AbortController();\n const trimmed = query.trim();\n if (!trimmed) {\n setResults([]);\n setIsLoading(false);\n return () => controller.abort();\n }\n\n setIsLoading(true);\n const timeout = window.setTimeout(() => {\n fetch(`/api/search?q=${encodeURIComponent(trimmed)}`, {\n signal: controller.signal,\n })\n .then((response) => (response.ok ? response.json() : { results: [] }))\n .then((payload: { results?: SearchResult[] }) => {\n setResults(payload.results ?? []);\n })\n .catch((error: unknown) => {\n if (error instanceof DOMException && error.name === \"AbortError\")\n return;\n setResults([]);\n })\n .finally(() => setIsLoading(false));\n }, 120);\n\n return () => {\n window.clearTimeout(timeout);\n controller.abort();\n };\n }, [query]);\n\n const close = () => onOpenChange(false);\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogHeader className=\"sr-only\">\n <DialogTitle>Search</DialogTitle>\n <DialogDescription>Search your vault.</DialogDescription>\n </DialogHeader>\n <DialogContent\n className=\"top-1/3 translate-y-0 overflow-hidden rounded-xl! p-0\"\n showCloseButton={false}\n >\n <Command shouldFilter={false}>\n <CommandInput\n autoFocus\n placeholder=\"Search your vault…\"\n value={query}\n onValueChange={setQuery}\n />\n <CommandList>\n {isLoading ? (\n <div className=\"px-4 py-6 text-center text-sm text-muted-foreground\">\n Searching…\n </div>\n ) : null}\n {!isLoading && query.trim() && results.length === 0 ? (\n <CommandEmpty>No results</CommandEmpty>\n ) : null}\n {results.map((result) => (\n <CommandItem\n key={result.slug}\n value={`${result.title} ${result.slug}`}\n onSelect={() => {\n navigate(slugToHref(result.slug));\n close();\n }}\n >\n <div className=\"flex min-w-0 flex-col gap-0.5\">\n <span className=\"truncate font-medium text-foreground\">\n <HighlightedText parts={result.titleParts} />\n </span>\n {result.excerptParts.length > 0 ? (\n <span className=\"truncate text-xs text-muted-foreground\">\n <HighlightedText parts={result.excerptParts} />\n </span>\n ) : null}\n </div>\n </CommandItem>\n ))}\n </CommandList>\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction HighlightedText({ parts }: { parts: SearchHighlightPart[] }) {\n return (\n <>\n {parts.map((part, index) =>\n part.highlighted ? (\n <mark\n key={index}\n className=\"bg-transparent p-0 font-medium text-primary\"\n >\n {part.text}\n </mark>\n ) : (\n <React.Fragment key={index}>{part.text}</React.Fragment>\n ),\n )}\n </>\n );\n}\n"],"mappings":"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","names":[]}
|
package/dist/vault-tree.d.ts
CHANGED
|
@@ -6,10 +6,11 @@ type VaultTreeEntry = {
|
|
|
6
6
|
sortKey?: string;
|
|
7
7
|
};
|
|
8
8
|
type VaultTreeProps = {
|
|
9
|
-
|
|
9
|
+
navigationEndpoint: string;
|
|
10
10
|
currentSlug?: string;
|
|
11
11
|
showHomeLink?: boolean;
|
|
12
12
|
};
|
|
13
|
-
declare function VaultTree({
|
|
13
|
+
declare function VaultTree({ navigationEndpoint, currentSlug: currentSlugProp, showHomeLink, }: VaultTreeProps): react_jsx_runtime.JSX.Element;
|
|
14
|
+
declare function loadNavigationEntries(endpoint: string): Promise<VaultTreeEntry[]>;
|
|
14
15
|
|
|
15
|
-
export { VaultTree, type VaultTreeEntry, type VaultTreeProps };
|
|
16
|
+
export { VaultTree, type VaultTreeEntry, type VaultTreeProps, loadNavigationEntries };
|
package/dist/vault-tree.js
CHANGED
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
import { useSilicaRouting } from "./routing.js";
|
|
19
19
|
import { prettySegment, slugToHref } from "./slug.js";
|
|
20
20
|
const STORAGE_KEY = "silica-tree-expanded";
|
|
21
|
+
const SUBMENU_CLASS_NAME = "ml-2.5 mr-0 pl-2 pr-0";
|
|
22
|
+
const navigationCache = /* @__PURE__ */ new Map();
|
|
21
23
|
function emptyNode(name, fullPath, sortKey) {
|
|
22
24
|
return { key: fullPath || "/", name, fullPath, sortKey, children: [] };
|
|
23
25
|
}
|
|
@@ -99,11 +101,18 @@ function activeIdFromSlug(slug) {
|
|
|
99
101
|
return slug;
|
|
100
102
|
}
|
|
101
103
|
function VaultTree({
|
|
102
|
-
|
|
104
|
+
navigationEndpoint,
|
|
103
105
|
currentSlug: currentSlugProp,
|
|
104
106
|
showHomeLink = true
|
|
105
107
|
}) {
|
|
106
108
|
const { Link, currentSlug: routedCurrentSlug } = useSilicaRouting();
|
|
109
|
+
const [navigationState, setNavigationState] = React.useState(
|
|
110
|
+
{
|
|
111
|
+
status: "loading",
|
|
112
|
+
entries: []
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
const entries = navigationState.entries;
|
|
107
116
|
const currentSlug = currentSlugProp ?? routedCurrentSlug;
|
|
108
117
|
const { nodes, homeEntry } = React.useMemo(
|
|
109
118
|
() => buildTreeFromEntries(entries),
|
|
@@ -112,6 +121,20 @@ function VaultTree({
|
|
|
112
121
|
const activeId = activeIdFromSlug(currentSlug);
|
|
113
122
|
const homeIsActive = currentSlug === "index" || currentSlug === void 0;
|
|
114
123
|
const [expanded, setExpanded] = React.useState(/* @__PURE__ */ new Set());
|
|
124
|
+
React.useEffect(() => {
|
|
125
|
+
let active = true;
|
|
126
|
+
setNavigationState({ status: "loading", entries: [] });
|
|
127
|
+
loadNavigationEntries(navigationEndpoint).then((nextEntries) => {
|
|
128
|
+
if (active) {
|
|
129
|
+
setNavigationState({ status: "loaded", entries: nextEntries });
|
|
130
|
+
}
|
|
131
|
+
}).catch(() => {
|
|
132
|
+
if (active) setNavigationState({ status: "error", entries: [] });
|
|
133
|
+
});
|
|
134
|
+
return () => {
|
|
135
|
+
active = false;
|
|
136
|
+
};
|
|
137
|
+
}, [navigationEndpoint]);
|
|
115
138
|
React.useEffect(() => {
|
|
116
139
|
const next = /* @__PURE__ */ new Set();
|
|
117
140
|
try {
|
|
@@ -141,6 +164,8 @@ function VaultTree({
|
|
|
141
164
|
}, []);
|
|
142
165
|
return /* @__PURE__ */ jsxs(SidebarMenu, { children: [
|
|
143
166
|
showHomeLink ? /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, { isActive: homeIsActive, render: /* @__PURE__ */ jsx(Link, { href: "/" }), children: /* @__PURE__ */ jsx("span", { className: "truncate", children: homeEntry?.title ?? "Home" }) }) }) : null,
|
|
167
|
+
navigationState.status === "loading" ? /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, { disabled: true, children: /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: "Loading navigation..." }) }) }) : null,
|
|
168
|
+
navigationState.status === "error" ? /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsx(SidebarMenuButton, { disabled: true, children: /* @__PURE__ */ jsx("span", { className: "truncate text-muted-foreground", children: "Navigation unavailable" }) }) }) : null,
|
|
144
169
|
nodes.map((node) => /* @__PURE__ */ jsx(
|
|
145
170
|
VaultTreeNode,
|
|
146
171
|
{
|
|
@@ -154,6 +179,19 @@ function VaultTree({
|
|
|
154
179
|
))
|
|
155
180
|
] });
|
|
156
181
|
}
|
|
182
|
+
function loadNavigationEntries(endpoint) {
|
|
183
|
+
const cached = navigationCache.get(endpoint);
|
|
184
|
+
if (cached) return cached;
|
|
185
|
+
const promise = fetch(endpoint).then((response) => {
|
|
186
|
+
if (!response.ok) throw new Error("Failed to load navigation.");
|
|
187
|
+
return response.json();
|
|
188
|
+
}).then((navigation) => navigation.entries).catch((error) => {
|
|
189
|
+
navigationCache.delete(endpoint);
|
|
190
|
+
throw error;
|
|
191
|
+
});
|
|
192
|
+
navigationCache.set(endpoint, promise);
|
|
193
|
+
return promise;
|
|
194
|
+
}
|
|
157
195
|
function VaultTreeNode({
|
|
158
196
|
node,
|
|
159
197
|
activeId,
|
|
@@ -197,7 +235,7 @@ function VaultTreeNode({
|
|
|
197
235
|
)
|
|
198
236
|
}
|
|
199
237
|
),
|
|
200
|
-
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: node.children.map((child) => /* @__PURE__ */ jsx(
|
|
238
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { className: SUBMENU_CLASS_NAME, children: node.children.map((child) => /* @__PURE__ */ jsx(
|
|
201
239
|
VaultTreeNode,
|
|
202
240
|
{
|
|
203
241
|
node: child,
|
|
@@ -227,7 +265,7 @@ function VaultTreeNode({
|
|
|
227
265
|
),
|
|
228
266
|
/* @__PURE__ */ jsx("span", { className: "truncate", children: label })
|
|
229
267
|
] }),
|
|
230
|
-
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { children: node.children.map((child) => /* @__PURE__ */ jsx(
|
|
268
|
+
/* @__PURE__ */ jsx(CollapsibleContent, { children: /* @__PURE__ */ jsx(SidebarMenuSub, { className: SUBMENU_CLASS_NAME, children: node.children.map((child) => /* @__PURE__ */ jsx(
|
|
231
269
|
VaultTreeNode,
|
|
232
270
|
{
|
|
233
271
|
node: child,
|
|
@@ -243,6 +281,7 @@ function VaultTreeNode({
|
|
|
243
281
|
) });
|
|
244
282
|
}
|
|
245
283
|
export {
|
|
246
|
-
VaultTree
|
|
284
|
+
VaultTree,
|
|
285
|
+
loadNavigationEntries
|
|
247
286
|
};
|
|
248
287
|
//# sourceMappingURL=vault-tree.js.map
|
package/dist/vault-tree.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vault-tree.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronRight } from \"lucide-react\";\n\nimport { cn } from \"@silicajs/ui/lib/utils\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@silicajs/ui/components/collapsible\";\nimport {\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n} from \"@silicajs/ui/components/sidebar\";\n\nimport { useSilicaRouting, type SilicaLinkComponent } from \"./routing.js\";\nimport { prettySegment, slugToHref } from \"./slug.js\";\n\nconst STORAGE_KEY = \"silica-tree-expanded\";\n\nexport type VaultTreeEntry = {\n slug: string;\n title: string;\n sortKey?: string;\n};\n\ntype FolderNode = {\n key: string;\n name: string;\n fullPath: string;\n sortKey?: string;\n entry?: VaultTreeEntry;\n children: FolderNode[];\n};\n\nfunction emptyNode(\n name: string,\n fullPath: string,\n sortKey?: string,\n): FolderNode {\n return { key: fullPath || \"/\", name, fullPath, sortKey, children: [] };\n}\n\nfunction buildTreeFromEntries(entries: VaultTreeEntry[]): {\n nodes: FolderNode[];\n homeEntry?: VaultTreeEntry;\n} {\n const root = emptyNode(\"\", \"\");\n const byPath = new Map<string, FolderNode>([[\"\", root]]);\n let homeEntry: VaultTreeEntry | undefined;\n\n for (const entry of entries) {\n if (entry.slug === \"index\") {\n homeEntry = entry;\n continue;\n }\n const segments = entry.slug.split(\"/\");\n const sortSegments = entry.sortKey?.split(\"/\") ?? [];\n let parent = root;\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const isLast = i === segments.length - 1;\n const fullPath = segments.slice(0, i + 1).join(\"/\");\n const nodeSortKey = sortSegments.slice(0, i + 1).join(\"/\") || undefined;\n if (isLast && segment === \"index\") {\n parent.entry = entry;\n continue;\n }\n let node = byPath.get(fullPath);\n if (!node) {\n node = emptyNode(segment, fullPath, nodeSortKey);\n byPath.set(fullPath, node);\n parent.children.push(node);\n } else if (!node.sortKey && nodeSortKey) {\n node.sortKey = nodeSortKey;\n }\n if (isLast) node.entry = entry;\n parent = node;\n }\n }\n\n const sortRecursively = (node: FolderNode) => {\n if (node.children.some((child) => child.sortKey)) {\n node.children.sort(compareOrderedNodes);\n } else {\n const folders = node.children.filter((c) => c.children.length > 0);\n const leaves = node.children.filter((c) => c.children.length === 0);\n folders.sort((a, b) => a.name.localeCompare(b.name));\n leaves.sort((a, b) => {\n const ta = a.entry?.title ?? a.name;\n const tb = b.entry?.title ?? b.name;\n return ta.localeCompare(tb);\n });\n node.children = [...folders, ...leaves];\n }\n for (const child of node.children) sortRecursively(child);\n };\n sortRecursively(root);\n\n return { nodes: root.children, homeEntry };\n}\n\nfunction compareOrderedNodes(a: FolderNode, b: FolderNode): number {\n return (\n (a.sortKey ?? fallbackNodeSortKey(a)).localeCompare(\n b.sortKey ?? fallbackNodeSortKey(b),\n ) || fallbackNodeLabel(a).localeCompare(fallbackNodeLabel(b))\n );\n}\n\nfunction fallbackNodeSortKey(node: FolderNode): string {\n return `~~~~~~~~~~:${fallbackNodeLabel(node)}`;\n}\n\nfunction fallbackNodeLabel(node: FolderNode): string {\n return node.entry?.title ?? node.name;\n}\n\nfunction ancestorIdsOf(slug: string | undefined): string[] {\n if (!slug || slug === \"index\") return [];\n const segments = slug.split(\"/\");\n const out: string[] = [];\n for (let i = 1; i < segments.length; i++) {\n out.push(segments.slice(0, i).join(\"/\"));\n }\n return out;\n}\n\nfunction activeIdFromSlug(slug: string | undefined): string | undefined {\n if (!slug || slug === \"index\") return undefined;\n if (slug.endsWith(\"/index\")) return slug.slice(0, -\"/index\".length);\n return slug;\n}\n\nexport type VaultTreeProps = {\n entries: VaultTreeEntry[];\n currentSlug?: string;\n showHomeLink?: boolean;\n};\n\nexport function VaultTree({\n entries,\n currentSlug: currentSlugProp,\n showHomeLink = true,\n}: VaultTreeProps) {\n const { Link, currentSlug: routedCurrentSlug } = useSilicaRouting();\n const currentSlug = currentSlugProp ?? routedCurrentSlug;\n const { nodes, homeEntry } = React.useMemo(\n () => buildTreeFromEntries(entries),\n [entries],\n );\n\n const activeId = activeIdFromSlug(currentSlug);\n const homeIsActive = currentSlug === \"index\" || currentSlug === undefined;\n\n const [expanded, setExpanded] = React.useState<Set<string>>(new Set());\n\n // Hydrate expansion state from localStorage and re-sync ancestor expansion\n // whenever the active slug changes (e.g. on client-side navigation).\n React.useEffect(() => {\n const next = new Set<string>();\n try {\n const raw = window.localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const parsed = JSON.parse(raw) as unknown;\n if (Array.isArray(parsed)) {\n for (const x of parsed) if (typeof x === \"string\") next.add(x);\n }\n }\n } catch {\n // ignore\n }\n for (const id of ancestorIdsOf(currentSlug)) next.add(id);\n setExpanded(next);\n }, [currentSlug]);\n\n const handleToggle = React.useCallback((id: string, open: boolean) => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (open) next.add(id);\n else next.delete(id);\n try {\n window.localStorage.setItem(STORAGE_KEY, JSON.stringify([...next]));\n } catch {\n // ignore\n }\n return next;\n });\n }, []);\n\n return (\n <SidebarMenu>\n {showHomeLink ? (\n <SidebarMenuItem>\n <SidebarMenuButton isActive={homeIsActive} render={<Link href=\"/\" />}>\n <span className=\"truncate\">{homeEntry?.title ?? \"Home\"}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ) : null}\n {nodes.map((node) => (\n <VaultTreeNode\n key={node.key}\n node={node}\n activeId={activeId}\n expanded={expanded}\n onToggle={handleToggle}\n Link={Link}\n />\n ))}\n </SidebarMenu>\n );\n}\n\ntype VaultTreeNodeProps = {\n node: FolderNode;\n activeId: string | undefined;\n expanded: Set<string>;\n onToggle: (id: string, open: boolean) => void;\n Link: SilicaLinkComponent;\n};\n\nfunction VaultTreeNode({\n node,\n activeId,\n expanded,\n onToggle,\n Link,\n}: VaultTreeNodeProps) {\n const hasChildren = node.children.length > 0;\n const isActive = activeId === node.key;\n const isOpen = expanded.has(node.key);\n const label = node.entry?.title ?? prettySegment(node.name);\n const href = node.entry ? slugToHref(node.entry.slug) : undefined;\n\n if (!hasChildren) {\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n isActive={isActive}\n {...(href ? { render: <Link href={href} /> } : {})}\n >\n <span className=\"truncate\">{label}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n );\n }\n\n // Folder that is also a page: label navigates, separate chevron toggles.\n if (href) {\n return (\n <SidebarMenuItem>\n <Collapsible\n open={isOpen}\n onOpenChange={(open) => onToggle(node.key, open)}\n >\n <SidebarMenuButton isActive={isActive} render={<Link href={href} />}>\n <span className=\"truncate\">{label}</span>\n </SidebarMenuButton>\n <CollapsibleTrigger\n render={\n <SidebarMenuAction\n aria-label={isOpen ? \"Collapse\" : \"Expand\"}\n className={cn(\"transition-transform\", isOpen && \"rotate-90\")}\n >\n <ChevronRight />\n </SidebarMenuAction>\n }\n />\n <CollapsibleContent>\n <SidebarMenuSub>\n {node.children.map((child) => (\n <VaultTreeNode\n key={child.key}\n node={child}\n activeId={activeId}\n expanded={expanded}\n onToggle={onToggle}\n Link={Link}\n />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuItem>\n );\n }\n\n // Folder without its own page: whole row toggles.\n return (\n <SidebarMenuItem>\n <Collapsible\n open={isOpen}\n onOpenChange={(open) => onToggle(node.key, open)}\n >\n <SidebarMenuButton isActive={isActive} render={<CollapsibleTrigger />}>\n <ChevronRight\n className={cn(\"transition-transform\", isOpen && \"rotate-90\")}\n />\n <span className=\"truncate\">{label}</span>\n </SidebarMenuButton>\n <CollapsibleContent>\n <SidebarMenuSub>\n {node.children.map((child) => (\n <VaultTreeNode\n key={child.key}\n node={child}\n activeId={activeId}\n expanded={expanded}\n onToggle={onToggle}\n Link={Link}\n />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuItem>\n );\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/vault-tree.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { ChevronRight } from \"lucide-react\";\n\nimport { cn } from \"@silicajs/ui/lib/utils\";\nimport {\n Collapsible,\n CollapsibleContent,\n CollapsibleTrigger,\n} from \"@silicajs/ui/components/collapsible\";\nimport {\n SidebarMenu,\n SidebarMenuAction,\n SidebarMenuButton,\n SidebarMenuItem,\n SidebarMenuSub,\n} from \"@silicajs/ui/components/sidebar\";\n\nimport { useSilicaRouting, type SilicaLinkComponent } from \"./routing.js\";\nimport { prettySegment, slugToHref } from \"./slug.js\";\n\nconst STORAGE_KEY = \"silica-tree-expanded\";\nconst SUBMENU_CLASS_NAME = \"ml-2.5 mr-0 pl-2 pr-0\";\n\nexport type VaultTreeEntry = {\n slug: string;\n title: string;\n sortKey?: string;\n};\n\ntype NavigationResponse = {\n version: 1;\n entries: VaultTreeEntry[];\n};\n\nconst navigationCache = new Map<string, Promise<VaultTreeEntry[]>>();\n\ntype NavigationState =\n | { status: \"loading\"; entries: VaultTreeEntry[] }\n | { status: \"loaded\"; entries: VaultTreeEntry[] }\n | { status: \"error\"; entries: VaultTreeEntry[] };\n\ntype FolderNode = {\n key: string;\n name: string;\n fullPath: string;\n sortKey?: string;\n entry?: VaultTreeEntry;\n children: FolderNode[];\n};\n\nfunction emptyNode(\n name: string,\n fullPath: string,\n sortKey?: string,\n): FolderNode {\n return { key: fullPath || \"/\", name, fullPath, sortKey, children: [] };\n}\n\nfunction buildTreeFromEntries(entries: VaultTreeEntry[]): {\n nodes: FolderNode[];\n homeEntry?: VaultTreeEntry;\n} {\n const root = emptyNode(\"\", \"\");\n const byPath = new Map<string, FolderNode>([[\"\", root]]);\n let homeEntry: VaultTreeEntry | undefined;\n\n for (const entry of entries) {\n if (entry.slug === \"index\") {\n homeEntry = entry;\n continue;\n }\n const segments = entry.slug.split(\"/\");\n const sortSegments = entry.sortKey?.split(\"/\") ?? [];\n let parent = root;\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i]!;\n const isLast = i === segments.length - 1;\n const fullPath = segments.slice(0, i + 1).join(\"/\");\n const nodeSortKey = sortSegments.slice(0, i + 1).join(\"/\") || undefined;\n if (isLast && segment === \"index\") {\n parent.entry = entry;\n continue;\n }\n let node = byPath.get(fullPath);\n if (!node) {\n node = emptyNode(segment, fullPath, nodeSortKey);\n byPath.set(fullPath, node);\n parent.children.push(node);\n } else if (!node.sortKey && nodeSortKey) {\n node.sortKey = nodeSortKey;\n }\n if (isLast) node.entry = entry;\n parent = node;\n }\n }\n\n const sortRecursively = (node: FolderNode) => {\n if (node.children.some((child) => child.sortKey)) {\n node.children.sort(compareOrderedNodes);\n } else {\n const folders = node.children.filter((c) => c.children.length > 0);\n const leaves = node.children.filter((c) => c.children.length === 0);\n folders.sort((a, b) => a.name.localeCompare(b.name));\n leaves.sort((a, b) => {\n const ta = a.entry?.title ?? a.name;\n const tb = b.entry?.title ?? b.name;\n return ta.localeCompare(tb);\n });\n node.children = [...folders, ...leaves];\n }\n for (const child of node.children) sortRecursively(child);\n };\n sortRecursively(root);\n\n return { nodes: root.children, homeEntry };\n}\n\nfunction compareOrderedNodes(a: FolderNode, b: FolderNode): number {\n return (\n (a.sortKey ?? fallbackNodeSortKey(a)).localeCompare(\n b.sortKey ?? fallbackNodeSortKey(b),\n ) || fallbackNodeLabel(a).localeCompare(fallbackNodeLabel(b))\n );\n}\n\nfunction fallbackNodeSortKey(node: FolderNode): string {\n return `~~~~~~~~~~:${fallbackNodeLabel(node)}`;\n}\n\nfunction fallbackNodeLabel(node: FolderNode): string {\n return node.entry?.title ?? node.name;\n}\n\nfunction ancestorIdsOf(slug: string | undefined): string[] {\n if (!slug || slug === \"index\") return [];\n const segments = slug.split(\"/\");\n const out: string[] = [];\n for (let i = 1; i < segments.length; i++) {\n out.push(segments.slice(0, i).join(\"/\"));\n }\n return out;\n}\n\nfunction activeIdFromSlug(slug: string | undefined): string | undefined {\n if (!slug || slug === \"index\") return undefined;\n if (slug.endsWith(\"/index\")) return slug.slice(0, -\"/index\".length);\n return slug;\n}\n\nexport type VaultTreeProps = {\n navigationEndpoint: string;\n currentSlug?: string;\n showHomeLink?: boolean;\n};\n\nexport function VaultTree({\n navigationEndpoint,\n currentSlug: currentSlugProp,\n showHomeLink = true,\n}: VaultTreeProps) {\n const { Link, currentSlug: routedCurrentSlug } = useSilicaRouting();\n const [navigationState, setNavigationState] = React.useState<NavigationState>(\n {\n status: \"loading\",\n entries: [],\n },\n );\n const entries = navigationState.entries;\n const currentSlug = currentSlugProp ?? routedCurrentSlug;\n const { nodes, homeEntry } = React.useMemo(\n () => buildTreeFromEntries(entries),\n [entries],\n );\n\n const activeId = activeIdFromSlug(currentSlug);\n const homeIsActive = currentSlug === \"index\" || currentSlug === undefined;\n\n const [expanded, setExpanded] = React.useState<Set<string>>(new Set());\n\n React.useEffect(() => {\n let active = true;\n setNavigationState({ status: \"loading\", entries: [] });\n loadNavigationEntries(navigationEndpoint)\n .then((nextEntries) => {\n if (active) {\n setNavigationState({ status: \"loaded\", entries: nextEntries });\n }\n })\n .catch(() => {\n if (active) setNavigationState({ status: \"error\", entries: [] });\n });\n return () => {\n active = false;\n };\n }, [navigationEndpoint]);\n\n // Hydrate expansion state from localStorage and re-sync ancestor expansion\n // whenever the active slug changes (e.g. on client-side navigation).\n React.useEffect(() => {\n const next = new Set<string>();\n try {\n const raw = window.localStorage.getItem(STORAGE_KEY);\n if (raw) {\n const parsed = JSON.parse(raw) as unknown;\n if (Array.isArray(parsed)) {\n for (const x of parsed) if (typeof x === \"string\") next.add(x);\n }\n }\n } catch {\n // ignore\n }\n for (const id of ancestorIdsOf(currentSlug)) next.add(id);\n setExpanded(next);\n }, [currentSlug]);\n\n const handleToggle = React.useCallback((id: string, open: boolean) => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (open) next.add(id);\n else next.delete(id);\n try {\n window.localStorage.setItem(STORAGE_KEY, JSON.stringify([...next]));\n } catch {\n // ignore\n }\n return next;\n });\n }, []);\n\n return (\n <SidebarMenu>\n {showHomeLink ? (\n <SidebarMenuItem>\n <SidebarMenuButton isActive={homeIsActive} render={<Link href=\"/\" />}>\n <span className=\"truncate\">{homeEntry?.title ?? \"Home\"}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ) : null}\n {navigationState.status === \"loading\" ? (\n <SidebarMenuItem>\n <SidebarMenuButton disabled>\n <span className=\"truncate text-muted-foreground\">\n Loading navigation...\n </span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ) : null}\n {navigationState.status === \"error\" ? (\n <SidebarMenuItem>\n <SidebarMenuButton disabled>\n <span className=\"truncate text-muted-foreground\">\n Navigation unavailable\n </span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ) : null}\n {nodes.map((node) => (\n <VaultTreeNode\n key={node.key}\n node={node}\n activeId={activeId}\n expanded={expanded}\n onToggle={handleToggle}\n Link={Link}\n />\n ))}\n </SidebarMenu>\n );\n}\n\nexport function loadNavigationEntries(\n endpoint: string,\n): Promise<VaultTreeEntry[]> {\n const cached = navigationCache.get(endpoint);\n if (cached) return cached;\n\n const promise = fetch(endpoint)\n .then((response) => {\n if (!response.ok) throw new Error(\"Failed to load navigation.\");\n return response.json() as Promise<NavigationResponse>;\n })\n .then((navigation) => navigation.entries)\n .catch((error: unknown) => {\n navigationCache.delete(endpoint);\n throw error;\n });\n navigationCache.set(endpoint, promise);\n return promise;\n}\n\ntype VaultTreeNodeProps = {\n node: FolderNode;\n activeId: string | undefined;\n expanded: Set<string>;\n onToggle: (id: string, open: boolean) => void;\n Link: SilicaLinkComponent;\n};\n\nfunction VaultTreeNode({\n node,\n activeId,\n expanded,\n onToggle,\n Link,\n}: VaultTreeNodeProps) {\n const hasChildren = node.children.length > 0;\n const isActive = activeId === node.key;\n const isOpen = expanded.has(node.key);\n const label = node.entry?.title ?? prettySegment(node.name);\n const href = node.entry ? slugToHref(node.entry.slug) : undefined;\n\n if (!hasChildren) {\n return (\n <SidebarMenuItem>\n <SidebarMenuButton\n isActive={isActive}\n {...(href ? { render: <Link href={href} /> } : {})}\n >\n <span className=\"truncate\">{label}</span>\n </SidebarMenuButton>\n </SidebarMenuItem>\n );\n }\n\n // Folder that is also a page: label navigates, separate chevron toggles.\n if (href) {\n return (\n <SidebarMenuItem>\n <Collapsible\n open={isOpen}\n onOpenChange={(open) => onToggle(node.key, open)}\n >\n <SidebarMenuButton isActive={isActive} render={<Link href={href} />}>\n <span className=\"truncate\">{label}</span>\n </SidebarMenuButton>\n <CollapsibleTrigger\n render={\n <SidebarMenuAction\n aria-label={isOpen ? \"Collapse\" : \"Expand\"}\n className={cn(\"transition-transform\", isOpen && \"rotate-90\")}\n >\n <ChevronRight />\n </SidebarMenuAction>\n }\n />\n <CollapsibleContent>\n <SidebarMenuSub className={SUBMENU_CLASS_NAME}>\n {node.children.map((child) => (\n <VaultTreeNode\n key={child.key}\n node={child}\n activeId={activeId}\n expanded={expanded}\n onToggle={onToggle}\n Link={Link}\n />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuItem>\n );\n }\n\n // Folder without its own page: whole row toggles.\n return (\n <SidebarMenuItem>\n <Collapsible\n open={isOpen}\n onOpenChange={(open) => onToggle(node.key, open)}\n >\n <SidebarMenuButton isActive={isActive} render={<CollapsibleTrigger />}>\n <ChevronRight\n className={cn(\"transition-transform\", isOpen && \"rotate-90\")}\n />\n <span className=\"truncate\">{label}</span>\n </SidebarMenuButton>\n <CollapsibleContent>\n <SidebarMenuSub className={SUBMENU_CLASS_NAME}>\n {node.children.map((child) => (\n <VaultTreeNode\n key={child.key}\n node={child}\n activeId={activeId}\n expanded={expanded}\n onToggle={onToggle}\n Link={Link}\n />\n ))}\n </SidebarMenuSub>\n </CollapsibleContent>\n </Collapsible>\n </SidebarMenuItem>\n );\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silicajs/components",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Silica-aware, framework-agnostic React composables built on @silicajs/ui.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"test": "vitest run --passWithNoTests"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@silicajs/core": "^0.
|
|
31
|
+
"@silicajs/core": "^0.2.0",
|
|
32
32
|
"@silicajs/remark-obsidian": "^0.1.0",
|
|
33
33
|
"@silicajs/ui": "^0.1.2",
|
|
34
34
|
"lucide-react": "^1.17.0"
|