@pagesmith/docs 0.2.0 → 0.3.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.
@@ -5,13 +5,18 @@
5
5
  * left sidebar (navigation) | content | right TOC
6
6
  */
7
7
 
8
- import { h } from '@pagesmith/core/jsx-runtime'
8
+ import { Fragment, h } from '@pagesmith/core/jsx-runtime'
9
9
  import { DocFooter } from '../components/DocFooter'
10
10
  import { DocHeader } from '../components/DocHeader'
11
11
  import { DocSidebar } from '../components/DocSidebar'
12
12
  import { DocTOC } from '../components/DocTOC'
13
13
  import { Html } from '../components/Html'
14
14
 
15
+ type Breadcrumb = {
16
+ label: string
17
+ path: string
18
+ }
19
+
15
20
  type Props = {
16
21
  content: string
17
22
  frontmatter: Record<string, any>
@@ -22,19 +27,48 @@ type Props = {
22
27
  sidebarSections?: any[]
23
28
  prev?: { title: string; path: string }
24
29
  next?: { title: string; path: string }
30
+ breadcrumbs?: Breadcrumb[]
31
+ editUrl?: string
32
+ editLabel?: string
33
+ lastUpdated?: string
25
34
  [key: string]: any
26
35
  }
27
36
 
37
+ function formatDate(isoDate: string): string {
38
+ const date = new Date(isoDate)
39
+ return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })
40
+ }
41
+
28
42
  export default function DocPage(props: Props) {
29
- const { content, frontmatter, headings, slug, site, next, prev, sidebarSections } = props
43
+ const {
44
+ content,
45
+ frontmatter,
46
+ headings,
47
+ slug,
48
+ site,
49
+ next,
50
+ prev,
51
+ sidebarSections,
52
+ breadcrumbs,
53
+ editUrl,
54
+ editLabel,
55
+ lastUpdated,
56
+ } = props
30
57
 
31
58
  const pageTitle = frontmatter.title ? `${frontmatter.title} — ${site.title}` : site.title
59
+ const ogImage = frontmatter.socialImage
60
+ ? frontmatter.socialImage.startsWith('http')
61
+ ? frontmatter.socialImage
62
+ : `${site.basePath || ''}/${frontmatter.socialImage.replace(/^\//, '')}`
63
+ : undefined
64
+ const hasPageMeta = editUrl || lastUpdated
32
65
 
33
66
  return (
34
67
  <Html
35
68
  title={pageTitle}
36
69
  description={frontmatter.description || site.description}
37
70
  url={`${slug}/`}
71
+ socialImage={ogImage}
38
72
  site={site}
39
73
  >
40
74
  <DocHeader
@@ -52,6 +86,33 @@ export default function DocPage(props: Props) {
52
86
  collapsible={site.sidebar?.collapsible}
53
87
  />
54
88
  <main class="doc-main" data-pagefind-body="">
89
+ {/* Breadcrumbs */}
90
+ {breadcrumbs && breadcrumbs.length > 1 ? (
91
+ <nav class="doc-breadcrumbs" aria-label="Breadcrumbs">
92
+ {breadcrumbs.map((crumb, i) =>
93
+ crumb.path ? (
94
+ <Fragment>
95
+ {i > 0 ? (
96
+ <span class="doc-breadcrumb-sep" aria-hidden="true">
97
+ /
98
+ </span>
99
+ ) : null}
100
+ <a href={`${crumb.path}/`}>{crumb.label}</a>
101
+ </Fragment>
102
+ ) : (
103
+ <Fragment>
104
+ {i > 0 ? (
105
+ <span class="doc-breadcrumb-sep" aria-hidden="true">
106
+ /
107
+ </span>
108
+ ) : null}
109
+ <span aria-current="page">{crumb.label}</span>
110
+ </Fragment>
111
+ ),
112
+ )}
113
+ </nav>
114
+ ) : null}
115
+
55
116
  {/* Mobile TOC — always at top of content area */}
56
117
  {headings.length > 0 ? (
57
118
  <details class="doc-toc-mobile">
@@ -63,6 +124,23 @@ export default function DocPage(props: Props) {
63
124
  <article>
64
125
  <div class="prose" innerHTML={content} />
65
126
  </article>
127
+
128
+ {/* Page meta: edit link + last updated */}
129
+ {hasPageMeta ? (
130
+ <div class="doc-page-meta">
131
+ {editUrl ? (
132
+ <a href={editUrl} class="doc-edit-link" target="_blank" rel="noopener noreferrer">
133
+ {editLabel || 'Edit this page'}
134
+ </a>
135
+ ) : null}
136
+ {lastUpdated ? (
137
+ <span class="doc-last-updated">
138
+ Last updated: <time datetime={lastUpdated}>{formatDate(lastUpdated)}</time>
139
+ </span>
140
+ ) : null}
141
+ </div>
142
+ ) : null}
143
+
66
144
  <DocFooter prev={prev} next={next} links={site.footerLinks} copyright={site.copyright} />
67
145
  </main>
68
146
  <aside class="doc-aside">
@@ -0,0 +1,65 @@
1
+ /* ═══════════════════════════════════════════════════════════════
2
+ Page meta: breadcrumbs, edit link, last updated
3
+ ═══════════════════════════════════════════════════════════════ */
4
+
5
+ /* ─── Breadcrumbs ─── */
6
+ .doc-breadcrumbs {
7
+ display: flex;
8
+ align-items: center;
9
+ flex-wrap: wrap;
10
+ gap: 0.25rem;
11
+ font-size: var(--ps-font-size-sm, 0.875rem);
12
+ color: var(--ps-color-text-muted, var(--color-text-muted, #6b7280));
13
+ margin-bottom: 1rem;
14
+ }
15
+
16
+ .doc-breadcrumbs a {
17
+ color: inherit;
18
+ text-decoration: none;
19
+ }
20
+
21
+ .doc-breadcrumbs a:hover {
22
+ color: var(--ps-color-text, var(--color-text, #1f2937));
23
+ text-decoration: underline;
24
+ }
25
+
26
+ .doc-breadcrumb-sep {
27
+ color: var(--ps-color-border, var(--color-border, #d1d5db));
28
+ user-select: none;
29
+ }
30
+
31
+ .doc-breadcrumbs [aria-current="page"] {
32
+ color: var(--ps-color-text, var(--color-text, #1f2937));
33
+ font-weight: 500;
34
+ }
35
+
36
+ /* ─── Page meta footer (edit link + last updated) ─── */
37
+ .doc-page-meta {
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ flex-wrap: wrap;
42
+ gap: 0.75rem;
43
+ margin-top: 2.5rem;
44
+ padding-top: 1.25rem;
45
+ border-top: 1px solid var(--ps-color-border-subtle, var(--color-border-subtle, #e5e7eb));
46
+ font-size: var(--ps-font-size-sm, 0.875rem);
47
+ color: var(--ps-color-text-muted, var(--color-text-muted, #6b7280));
48
+ }
49
+
50
+ .doc-edit-link {
51
+ color: var(--ps-color-primary, var(--color-primary, #3b82f6));
52
+ text-decoration: none;
53
+ }
54
+
55
+ .doc-edit-link:hover {
56
+ text-decoration: underline;
57
+ }
58
+
59
+ .doc-last-updated {
60
+ color: var(--ps-color-text-muted, var(--color-text-muted, #6b7280));
61
+ }
62
+
63
+ .doc-last-updated time {
64
+ color: var(--ps-color-text, var(--color-text, #374151));
65
+ }
@@ -6,6 +6,7 @@
6
6
  @import "./content/prose.css";
7
7
  @import "./content/toc.css";
8
8
  @import "./content/alerts.css";
9
+ @import "./content/page-meta.css";
9
10
  /* Code (inline only — block styling handled by Expressive Code) */
10
11
  @import "./code/inline.css";
11
12
  /* Layout */