handzon-core 0.10.0 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "handzon-core",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Core framework for Handzon — layouts, components, content + AI libs, and server runtime (handlers, DB, auth, migration runner) consumed by Handzon scaffolds.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,44 +1,72 @@
1
1
  ---
2
2
  /**
3
- * Site footer. A single quiet line at the bottom of every page,
4
- * crediting the framework + linking to the Handzon repo. Sized to
5
- * match the rest of the brutalist surface treatment.
3
+ * Site footer. A single quiet line at the bottom of every page, sized
4
+ * to match the rest of the brutalist surface treatment.
5
+ *
6
+ * Two modes:
7
+ * - Default (no `siteUrl`): one centered "Built with Handzon" credit,
8
+ * preserving the framework's original footer for unbranded scaffolds.
9
+ * - Branded (`siteUrl` set): the site owner's credit leads on the left
10
+ * (linked to `siteUrl`), and "Built with Handzon" moves to a quieter
11
+ * secondary link on the right.
6
12
  */
7
13
  interface Props {
8
- /** Public URL to the framework repo. Override to point elsewhere. */
14
+ /** Primary credit label the site owner. */
15
+ siteName?: string;
16
+ /** Primary credit link. When set, the footer leads with this credit
17
+ * and demotes the Handzon link to a secondary side link. */
18
+ siteUrl?: string;
19
+ /** Handzon project/package link (the secondary "Built with" credit). */
9
20
  repoUrl?: string;
10
21
  /** Year displayed in the credit line; defaults to the current year. */
11
22
  year?: number;
12
23
  }
13
24
 
14
25
  const {
26
+ siteName = "Handzon",
27
+ siteUrl,
15
28
  repoUrl = "https://github.com/R4ph-t/handzon",
16
29
  year = new Date().getFullYear(),
17
30
  } = Astro.props;
18
31
  ---
19
32
  <footer class="hz-footer">
20
- <div class="hz-footer-inner">
21
- <span class="hz-footer-credit">
22
- © {year} · Built with
23
- <a class="hz-footer-link" href={repoUrl} target="_blank" rel="noopener">
24
- Handzon
25
- <svg
26
- viewBox="0 0 24 24"
27
- width="11"
28
- height="11"
29
- fill="none"
30
- stroke="currentColor"
31
- stroke-width="2"
32
- stroke-linecap="round"
33
- stroke-linejoin="round"
34
- aria-hidden="true"
35
- >
36
- <path d="M7 17 17 7" />
37
- <path d="M7 7h10v10" />
38
- </svg>
39
- </a>
40
- </span>
41
- </div>
33
+ {siteUrl ? (
34
+ <div class="hz-footer-inner hz-footer-split">
35
+ <span class="hz-footer-credit">
36
+ © {year}
37
+ <a class="hz-footer-link" href={siteUrl} target="_blank" rel="noopener">
38
+ {siteName}
39
+ <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
40
+ <path d="M7 17 17 7" />
41
+ <path d="M7 7h10v10" />
42
+ </svg>
43
+ </a>
44
+ </span>
45
+ <span class="hz-footer-built">
46
+ Built with
47
+ <a class="hz-footer-link" href={repoUrl} target="_blank" rel="noopener">
48
+ Handzon
49
+ <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
50
+ <path d="M7 17 17 7" />
51
+ <path d="M7 7h10v10" />
52
+ </svg>
53
+ </a>
54
+ </span>
55
+ </div>
56
+ ) : (
57
+ <div class="hz-footer-inner">
58
+ <span class="hz-footer-credit">
59
+ © {year} · Built with
60
+ <a class="hz-footer-link" href={repoUrl} target="_blank" rel="noopener">
61
+ Handzon
62
+ <svg viewBox="0 0 24 24" width="11" height="11" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
63
+ <path d="M7 17 17 7" />
64
+ <path d="M7 7h10v10" />
65
+ </svg>
66
+ </a>
67
+ </span>
68
+ </div>
69
+ )}
42
70
  </footer>
43
71
 
44
72
  <style is:global>
@@ -64,11 +92,24 @@ const {
64
92
  font-family: var(--font-mono);
65
93
  font-size: 0.75em;
66
94
  }
95
+ /* Branded mode: site credit left, "Built with Handzon" right. Falls
96
+ * back to a stacked, centered layout on narrow viewports. */
97
+ .hz-footer-split {
98
+ justify-content: space-between;
99
+ gap: 0.75rem 1.5rem;
100
+ flex-wrap: wrap;
101
+ }
67
102
  .hz-footer-credit {
68
103
  display: inline-flex;
69
104
  align-items: center;
70
105
  gap: 0.45rem;
71
106
  }
107
+ .hz-footer-built {
108
+ display: inline-flex;
109
+ align-items: center;
110
+ gap: 0.4rem;
111
+ opacity: 0.85;
112
+ }
72
113
  .hz-footer-link {
73
114
  display: inline-flex;
74
115
  align-items: center;
@@ -34,6 +34,10 @@ interface Props {
34
34
  showFooter?: boolean;
35
35
  /** Footer link URL; defaults to the Handzon repo. */
36
36
  repoUrl?: string;
37
+ /** Primary footer credit link (the site owner, e.g. https://render.com).
38
+ * When set, the footer leads with the site credit and demotes the
39
+ * Handzon link to a secondary side link. */
40
+ siteUrl?: string;
37
41
  /**
38
42
  * Width of the page's content column. Drives the navbar + footer
39
43
  * alignment via the --hz-page-max-width CSS custom property so the
@@ -63,6 +67,7 @@ const {
63
67
  faviconUrl = "/favicon.svg",
64
68
  showFooter = true,
65
69
  repoUrl,
70
+ siteUrl,
66
71
  // Default to a full-width navbar + footer with a small consistent
67
72
  // inset, matching the tutorial step page. Pages with a centred
68
73
  // column (home grid, tutorial landing) keep their own content
@@ -113,7 +118,13 @@ const desc = description ?? tagline;
113
118
  <div class="hz-page">
114
119
  <slot />
115
120
  </div>
116
- {showFooter && <Footer repoUrl={repoUrl} />}
121
+ {/* Footer is fully overridable: pass `slot="footer"` from any page
122
+ wrapper to replace it entirely. With no override, the default
123
+ Footer renders, configurable via siteName/siteUrl/repoUrl. */}
124
+ {/* Footer content is configurable via siteName/siteUrl/repoUrl. For
125
+ a fully custom footer, a scaffold can pass showFooter={false} and
126
+ render its own markup in the page body. */}
127
+ {showFooter && <Footer siteName={siteName} siteUrl={siteUrl} repoUrl={repoUrl} />}
117
128
  <script>
118
129
  // Render any <pre class="mermaid"> blocks emitted by rehype-mermaid.
119
130
  if (document.querySelector("pre.mermaid")) {
@@ -18,6 +18,9 @@ interface Props {
18
18
  logoHeight?: number;
19
19
  faviconUrl?: string;
20
20
  repoUrl?: string;
21
+ siteUrl?: string;
22
+ /** Set false to drop the built-in footer and supply your own. */
23
+ showFooter?: boolean;
21
24
  }
22
25
 
23
26
  const {
@@ -33,6 +36,8 @@ const {
33
36
  logoHeight,
34
37
  faviconUrl,
35
38
  repoUrl,
39
+ siteUrl,
40
+ showFooter = true,
36
41
  } = Astro.props;
37
42
 
38
43
  const stepSlugs = steps.map((s) => parseStepId(s.id).stepSlug);
@@ -47,6 +52,8 @@ const stepSlugs = steps.map((s) => parseStepId(s.id).stepSlug);
47
52
  logoHeight={logoHeight}
48
53
  faviconUrl={faviconUrl}
49
54
  repoUrl={repoUrl}
55
+ siteUrl={siteUrl}
56
+ showFooter={showFooter}
50
57
  >
51
58
  <slot name="head" slot="head" />
52
59
  <div class="layout">
@@ -16,6 +16,9 @@ interface Props {
16
16
  logoHeight?: number;
17
17
  faviconUrl?: string;
18
18
  repoUrl?: string;
19
+ siteUrl?: string;
20
+ /** Set false to drop the built-in footer and supply your own. */
21
+ showFooter?: boolean;
19
22
  showResumeRail?: boolean;
20
23
  emptyStateCommand?: string;
21
24
  /** Tutorials per page on the grid. */
@@ -31,6 +34,8 @@ const {
31
34
  logoHeight,
32
35
  faviconUrl,
33
36
  repoUrl,
37
+ siteUrl,
38
+ showFooter = true,
34
39
  showResumeRail = true,
35
40
  emptyStateCommand = "pnpm handzon:new",
36
41
  pageSize = 9,
@@ -64,6 +69,8 @@ for (const t of tutorials) {
64
69
  logoHeight={logoHeight}
65
70
  faviconUrl={faviconUrl}
66
71
  repoUrl={repoUrl}
72
+ siteUrl={siteUrl}
73
+ showFooter={showFooter}
67
74
  nav="userMenu"
68
75
  >
69
76
  <slot name="head" slot="head" />
@@ -13,6 +13,9 @@ interface Props {
13
13
  logoHeight?: number;
14
14
  faviconUrl?: string;
15
15
  repoUrl?: string;
16
+ siteUrl?: string;
17
+ /** Set false to drop the built-in footer and supply your own. */
18
+ showFooter?: boolean;
16
19
  }
17
20
 
18
21
  const {
@@ -24,6 +27,8 @@ const {
24
27
  logoHeight,
25
28
  faviconUrl,
26
29
  repoUrl,
30
+ siteUrl,
31
+ showFooter = true,
27
32
  } = Astro.props;
28
33
  const steps = await getStepsForTutorial(tutorial.id);
29
34
  const duration = tutorial.data.estimatedDuration ?? sumDurations(steps) ?? "";
@@ -39,6 +44,8 @@ const firstStepSlug = steps[0] ? parseStepId(steps[0].id).stepSlug : null;
39
44
  logoHeight={logoHeight}
40
45
  faviconUrl={faviconUrl}
41
46
  repoUrl={repoUrl}
47
+ siteUrl={siteUrl}
48
+ showFooter={showFooter}
42
49
  >
43
50
  <slot name="head" slot="head" />
44
51
  <div class="landing">
@@ -24,6 +24,9 @@ interface Props {
24
24
  logoHeight?: number;
25
25
  faviconUrl?: string;
26
26
  repoUrl?: string;
27
+ siteUrl?: string;
28
+ /** Set false to drop the built-in footer and supply your own. */
29
+ showFooter?: boolean;
27
30
  }
28
31
 
29
32
  const {
@@ -38,6 +41,8 @@ const {
38
41
  logoHeight,
39
42
  faviconUrl,
40
43
  repoUrl,
44
+ siteUrl,
45
+ showFooter = true,
41
46
  } = Astro.props;
42
47
  const { stepSlug } = parseStepId(currentStep.id);
43
48
  const { Content } = await render(currentStep);
@@ -68,6 +73,8 @@ const initialContext = buildContext({
68
73
  logoHeight={logoHeight}
69
74
  faviconUrl={faviconUrl}
70
75
  repoUrl={repoUrl}
76
+ siteUrl={siteUrl}
77
+ showFooter={showFooter}
71
78
  >
72
79
  <slot name="head" slot="head" />
73
80
  <Content components={components} />