@levino/shipyard-base 0.5.7 → 0.5.8
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.
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { Entry } from './types'
|
|
3
|
+
|
|
4
|
+
interface BreadcrumbItem {
|
|
5
|
+
label: string
|
|
6
|
+
href?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
navigation: Entry
|
|
11
|
+
currentPath?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { navigation, currentPath = Astro.url.pathname } = Astro.props
|
|
15
|
+
|
|
16
|
+
// Normalize path by removing trailing slash for comparison
|
|
17
|
+
const normalizePath = (path: string) => path.replace(/\/$/, '') || '/'
|
|
18
|
+
|
|
19
|
+
// Find the path to the current page in the navigation tree
|
|
20
|
+
const findBreadcrumbs = (
|
|
21
|
+
entry: Entry,
|
|
22
|
+
targetPath: string,
|
|
23
|
+
path: BreadcrumbItem[] = [],
|
|
24
|
+
): BreadcrumbItem[] | null => {
|
|
25
|
+
for (const [key, item] of Object.entries(entry)) {
|
|
26
|
+
const label = item.label ?? key
|
|
27
|
+
const currentItem: BreadcrumbItem = { label, href: item.href }
|
|
28
|
+
|
|
29
|
+
if (item.href && normalizePath(item.href) === normalizePath(targetPath)) {
|
|
30
|
+
return [...path, currentItem]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (item.subEntry) {
|
|
34
|
+
const result = findBreadcrumbs(item.subEntry, targetPath, [
|
|
35
|
+
...path,
|
|
36
|
+
currentItem,
|
|
37
|
+
])
|
|
38
|
+
if (result) return result
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const breadcrumbs = findBreadcrumbs(navigation, currentPath) ?? []
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
breadcrumbs.length > 0 && (
|
|
49
|
+
<div class="breadcrumbs text-sm">
|
|
50
|
+
<ul>
|
|
51
|
+
{breadcrumbs.map((item, index) => (
|
|
52
|
+
<li>
|
|
53
|
+
{item.href && index < breadcrumbs.length - 1 ? (
|
|
54
|
+
<a href={item.href}>{item.label}</a>
|
|
55
|
+
) : (
|
|
56
|
+
<span>{item.label}</span>
|
|
57
|
+
)}
|
|
58
|
+
</li>
|
|
59
|
+
))}
|
|
60
|
+
</ul>
|
|
61
|
+
</div>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { cn } from '../../src/tools/cn'
|
|
2
3
|
import type { Entry } from './types'
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
6
|
entry: Entry
|
|
7
|
+
currentPath?: string
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
const { entry } = Astro.props
|
|
10
|
+
const { entry, currentPath = Astro.url.pathname } = Astro.props
|
|
11
|
+
|
|
12
|
+
// Normalize path by removing trailing slash for comparison
|
|
13
|
+
const normalizePath = (path: string) => path.replace(/\/$/, '') || '/'
|
|
14
|
+
|
|
15
|
+
// Check if entry or any of its children are active
|
|
16
|
+
const isActiveOrHasActiveChild = (entryValue: Entry[string]): boolean => {
|
|
17
|
+
if (
|
|
18
|
+
entryValue.href &&
|
|
19
|
+
normalizePath(entryValue.href) === normalizePath(currentPath)
|
|
20
|
+
)
|
|
21
|
+
return true
|
|
22
|
+
if (entryValue.subEntry) {
|
|
23
|
+
return Object.values(entryValue.subEntry).some(isActiveOrHasActiveChild)
|
|
24
|
+
}
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
9
27
|
---
|
|
10
28
|
|
|
11
|
-
{Object.entries(entry).map(([key,
|
|
12
|
-
const label =
|
|
29
|
+
{Object.entries(entry).map(([key, entryValue]) => {
|
|
30
|
+
const label = entryValue.label ?? key;
|
|
31
|
+
const isActive = entryValue.href && normalizePath(entryValue.href) === normalizePath(currentPath);
|
|
32
|
+
const hasActiveChild = entryValue.subEntry && Object.values(entryValue.subEntry).some(isActiveOrHasActiveChild);
|
|
13
33
|
return (
|
|
14
|
-
<li>
|
|
15
|
-
{
|
|
16
|
-
? <a href={
|
|
34
|
+
<li class={entryValue.className}>
|
|
35
|
+
{entryValue.href
|
|
36
|
+
? <a href={entryValue.href} class={cn({ 'bg-base-200/50 font-medium text-primary': isActive })}>{label}</a>
|
|
17
37
|
: <span class='menu-title'>{label}</span>}
|
|
18
|
-
{
|
|
38
|
+
{entryValue.subEntry ? (
|
|
19
39
|
<ul>
|
|
20
|
-
<Astro.self entry={
|
|
40
|
+
<Astro.self entry={entryValue.subEntry} currentPath={currentPath} />
|
|
21
41
|
</ul>
|
|
22
42
|
) : null}
|
|
23
43
|
</li>
|
|
@@ -1,19 +1,84 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { cn } from '../../src/tools/cn'
|
|
3
|
+
|
|
2
4
|
interface Link {
|
|
3
5
|
depth: number
|
|
4
6
|
text: string
|
|
5
7
|
slug: string
|
|
6
8
|
}
|
|
7
9
|
|
|
8
|
-
interface
|
|
10
|
+
interface Props {
|
|
9
11
|
links: Link[]
|
|
12
|
+
label?: string
|
|
13
|
+
class?: string
|
|
14
|
+
desktopOnly?: boolean
|
|
10
15
|
}
|
|
11
16
|
|
|
12
|
-
const {
|
|
17
|
+
const {
|
|
18
|
+
links,
|
|
19
|
+
label = 'On this page',
|
|
20
|
+
class: className,
|
|
21
|
+
desktopOnly = false,
|
|
22
|
+
} = Astro.props
|
|
23
|
+
|
|
24
|
+
// Filter to only include h2 and h3 headings (depth 2 and 3)
|
|
25
|
+
const filteredLinks = links.filter((link) => link.depth >= 2 && link.depth <= 3)
|
|
13
26
|
---
|
|
14
27
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
{
|
|
29
|
+
filteredLinks.length > 0 && (
|
|
30
|
+
<div class={className}>
|
|
31
|
+
{/* Mobile: Collapsible dropdown */}
|
|
32
|
+
{!desktopOnly && (
|
|
33
|
+
<div class="xl:hidden mb-4">
|
|
34
|
+
<details class="collapse collapse-arrow bg-base-200">
|
|
35
|
+
<summary class="collapse-title min-h-0 py-2 font-medium">
|
|
36
|
+
{label}
|
|
37
|
+
</summary>
|
|
38
|
+
<div class="collapse-content">
|
|
39
|
+
<ul class="menu menu-sm">
|
|
40
|
+
{filteredLinks.map((link) => (
|
|
41
|
+
<li>
|
|
42
|
+
<a
|
|
43
|
+
href={`#${link.slug}`}
|
|
44
|
+
class={cn({
|
|
45
|
+
'pl-4': link.depth === 2,
|
|
46
|
+
'pl-8': link.depth === 3,
|
|
47
|
+
})}
|
|
48
|
+
>
|
|
49
|
+
{link.text}
|
|
50
|
+
</a>
|
|
51
|
+
</li>
|
|
52
|
+
))}
|
|
53
|
+
</ul>
|
|
54
|
+
</div>
|
|
55
|
+
</details>
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
|
|
59
|
+
{/* Desktop: Sidebar within the grid column */}
|
|
60
|
+
{desktopOnly && (
|
|
61
|
+
<div class="border-l border-base-300 pl-4">
|
|
62
|
+
<h4 class="font-medium text-sm mb-2 text-base-content/60">
|
|
63
|
+
{label}
|
|
64
|
+
</h4>
|
|
65
|
+
<ul class="menu menu-xs p-0">
|
|
66
|
+
{filteredLinks.map((link) => (
|
|
67
|
+
<li>
|
|
68
|
+
<a
|
|
69
|
+
href={`#${link.slug}`}
|
|
70
|
+
class={cn('text-base-content/70 hover:text-primary', {
|
|
71
|
+
'pl-0': link.depth === 2,
|
|
72
|
+
'pl-4': link.depth === 3,
|
|
73
|
+
})}
|
|
74
|
+
>
|
|
75
|
+
{link.text}
|
|
76
|
+
</a>
|
|
77
|
+
</li>
|
|
78
|
+
))}
|
|
79
|
+
</ul>
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
</div>
|
|
83
|
+
)
|
|
84
|
+
}
|