@nexpress/theme-default 0.1.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.
@@ -0,0 +1,120 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/components/mobile-nav.tsx
5
+ import { useEffect, useState } from "react";
6
+ import Link from "next/link";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ function MobileNav({ items, label = "Menu" }) {
9
+ const [open, setOpen] = useState(false);
10
+ useEffect(() => {
11
+ if (!open) return;
12
+ const onKey = (e) => {
13
+ if (e.key === "Escape") setOpen(false);
14
+ };
15
+ document.addEventListener("keydown", onKey);
16
+ const previousOverflow = document.body.style.overflow;
17
+ document.body.style.overflow = "hidden";
18
+ return () => {
19
+ document.removeEventListener("keydown", onKey);
20
+ document.body.style.overflow = previousOverflow;
21
+ };
22
+ }, [open]);
23
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
24
+ /* @__PURE__ */ jsx(
25
+ "button",
26
+ {
27
+ type: "button",
28
+ className: "np-mobile-nav-toggle",
29
+ "aria-label": open ? "Close menu" : "Open menu",
30
+ "aria-expanded": open,
31
+ "aria-controls": "np-mobile-nav-drawer",
32
+ onClick: () => setOpen((prev) => !prev),
33
+ children: open ? /* @__PURE__ */ jsx(CloseIcon, {}) : /* @__PURE__ */ jsx(MenuIcon, {})
34
+ }
35
+ ),
36
+ open ? /* @__PURE__ */ jsx(
37
+ "div",
38
+ {
39
+ className: "np-mobile-nav-overlay",
40
+ role: "presentation",
41
+ onClick: () => setOpen(false)
42
+ }
43
+ ) : null,
44
+ /* @__PURE__ */ jsxs(
45
+ "aside",
46
+ {
47
+ id: "np-mobile-nav-drawer",
48
+ className: "np-mobile-nav-drawer",
49
+ "data-open": open ? "true" : "false",
50
+ "aria-hidden": open ? "false" : "true",
51
+ children: [
52
+ /* @__PURE__ */ jsxs("header", { className: "np-mobile-nav-drawer-header", children: [
53
+ /* @__PURE__ */ jsx("span", { className: "np-mobile-nav-drawer-label", children: label }),
54
+ /* @__PURE__ */ jsx(
55
+ "button",
56
+ {
57
+ type: "button",
58
+ className: "np-mobile-nav-close",
59
+ onClick: () => setOpen(false),
60
+ "aria-label": "Close menu",
61
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
62
+ }
63
+ )
64
+ ] }),
65
+ /* @__PURE__ */ jsx("ul", { className: "np-mobile-nav-list", children: items.map((item, index) => /* @__PURE__ */ jsxs("li", { children: [
66
+ item.url ? /* @__PURE__ */ jsx(Link, { href: item.url, onClick: () => setOpen(false), children: item.label }) : /* @__PURE__ */ jsx("span", { children: item.label }),
67
+ item.children && item.children.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "np-mobile-subnav", children: item.children.map((child, childIndex) => /* @__PURE__ */ jsx("li", { children: child.url ? /* @__PURE__ */ jsx(Link, { href: child.url, onClick: () => setOpen(false), children: child.label }) : /* @__PURE__ */ jsx("span", { children: child.label }) }, `mobile-nav-${index.toString()}-${childIndex.toString()}`)) }) : null
68
+ ] }, `mobile-nav-${index.toString()}`)) })
69
+ ]
70
+ }
71
+ )
72
+ ] });
73
+ }
74
+ function MenuIcon() {
75
+ return /* @__PURE__ */ jsxs(
76
+ "svg",
77
+ {
78
+ xmlns: "http://www.w3.org/2000/svg",
79
+ width: "20",
80
+ height: "20",
81
+ viewBox: "0 0 24 24",
82
+ fill: "none",
83
+ stroke: "currentColor",
84
+ strokeWidth: "2",
85
+ strokeLinecap: "round",
86
+ strokeLinejoin: "round",
87
+ "aria-hidden": "true",
88
+ children: [
89
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "6", x2: "20", y2: "6" }),
90
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" }),
91
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "18", x2: "20", y2: "18" })
92
+ ]
93
+ }
94
+ );
95
+ }
96
+ function CloseIcon() {
97
+ return /* @__PURE__ */ jsxs(
98
+ "svg",
99
+ {
100
+ xmlns: "http://www.w3.org/2000/svg",
101
+ width: "20",
102
+ height: "20",
103
+ viewBox: "0 0 24 24",
104
+ fill: "none",
105
+ stroke: "currentColor",
106
+ strokeWidth: "2",
107
+ strokeLinecap: "round",
108
+ strokeLinejoin: "round",
109
+ "aria-hidden": "true",
110
+ children: [
111
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
112
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "18", x2: "18", y2: "6" })
113
+ ]
114
+ }
115
+ );
116
+ }
117
+ export {
118
+ MobileNav
119
+ };
120
+ //# sourceMappingURL=mobile-nav.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/mobile-nav.tsx"],"sourcesContent":["\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport Link from \"next/link\";\nimport type { NpNavItem } from \"@nexpress/core\";\n\n/**\n * Mobile-first nav drawer. The desktop header keeps its inline\n * link list visible above ~768px (CSS handles the hide/show);\n * below that breakpoint the inline nav is hidden by CSS and the\n * hamburger button + slide-in drawer take over.\n *\n * Why a client component: the drawer needs `useState` for\n * open/closed, focus-trap on Escape, and scroll-lock on the\n * body. The link list itself is passed in from the server-\n * rendered header so the markup stays SEO-visible even when\n * the drawer is closed.\n */\nexport interface MobileNavProps {\n items: NpNavItem[];\n /** Optional brand label for the drawer header. Defaults to \"Menu\". */\n label?: string;\n}\n\nexport function MobileNav({ items, label = \"Menu\" }: MobileNavProps) {\n const [open, setOpen] = useState(false);\n\n // Close on Escape and lock body scroll while open. Both effects\n // run only when the drawer is actually open so no listeners\n // hang around in the closed state.\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"keydown\", onKey);\n const previousOverflow = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n return () => {\n document.removeEventListener(\"keydown\", onKey);\n document.body.style.overflow = previousOverflow;\n };\n }, [open]);\n\n return (\n <>\n <button\n type=\"button\"\n className=\"np-mobile-nav-toggle\"\n aria-label={open ? \"Close menu\" : \"Open menu\"}\n aria-expanded={open}\n aria-controls=\"np-mobile-nav-drawer\"\n onClick={() => setOpen((prev) => !prev)}\n >\n {open ? <CloseIcon /> : <MenuIcon />}\n </button>\n {open ? (\n <div\n className=\"np-mobile-nav-overlay\"\n role=\"presentation\"\n onClick={() => setOpen(false)}\n />\n ) : null}\n <aside\n id=\"np-mobile-nav-drawer\"\n className=\"np-mobile-nav-drawer\"\n data-open={open ? \"true\" : \"false\"}\n aria-hidden={open ? \"false\" : \"true\"}\n >\n <header className=\"np-mobile-nav-drawer-header\">\n <span className=\"np-mobile-nav-drawer-label\">{label}</span>\n <button\n type=\"button\"\n className=\"np-mobile-nav-close\"\n onClick={() => setOpen(false)}\n aria-label=\"Close menu\"\n >\n <CloseIcon />\n </button>\n </header>\n <ul className=\"np-mobile-nav-list\">\n {items.map((item, index) => (\n <li key={`mobile-nav-${index.toString()}`}>\n {item.url ? (\n <Link href={item.url} onClick={() => setOpen(false)}>\n {item.label}\n </Link>\n ) : (\n <span>{item.label}</span>\n )}\n {item.children && item.children.length > 0 ? (\n <ul className=\"np-mobile-subnav\">\n {item.children.map((child, childIndex) => (\n <li key={`mobile-nav-${index.toString()}-${childIndex.toString()}`}>\n {child.url ? (\n <Link href={child.url} onClick={() => setOpen(false)}>\n {child.label}\n </Link>\n ) : (\n <span>{child.label}</span>\n )}\n </li>\n ))}\n </ul>\n ) : null}\n </li>\n ))}\n </ul>\n </aside>\n </>\n );\n}\n\nfunction MenuIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <line x1=\"4\" y1=\"6\" x2=\"20\" y2=\"6\" />\n <line x1=\"4\" y1=\"12\" x2=\"20\" y2=\"12\" />\n <line x1=\"4\" y1=\"18\" x2=\"20\" y2=\"18\" />\n </svg>\n );\n}\n\nfunction CloseIcon() {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n <line x1=\"6\" y1=\"18\" x2=\"18\" y2=\"6\" />\n </svg>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,17 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+
3
+ /**
4
+ * Lightweight newsletter form. Submits to `/api/newsletter`
5
+ * (operators wire this up in their app, or hand it off to a
6
+ * provider like ConvertKit / Buttondown via a small route
7
+ * handler). When the endpoint isn't present we degrade
8
+ * gracefully — the user sees an "endpoint not configured"
9
+ * notice rather than a stack trace.
10
+ *
11
+ * Optimistic UX: the input is replaced by a "thanks" message
12
+ * the moment the response is OK. Errors keep the input visible
13
+ * so the user can retry.
14
+ */
15
+ declare function NewsletterForm(): react_jsx_runtime.JSX.Element;
16
+
17
+ export { NewsletterForm };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/components/newsletter-form.tsx
5
+ import { useState } from "react";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function NewsletterForm() {
8
+ const [email, setEmail] = useState("");
9
+ const [state, setState] = useState({ kind: "idle" });
10
+ async function onSubmit(e) {
11
+ e.preventDefault();
12
+ if (state.kind === "submitting") return;
13
+ setState({ kind: "submitting" });
14
+ try {
15
+ const res = await fetch("/api/newsletter", {
16
+ method: "POST",
17
+ headers: { "content-type": "application/json" },
18
+ body: JSON.stringify({ email })
19
+ });
20
+ if (res.status === 404) {
21
+ setState({
22
+ kind: "error",
23
+ message: "Newsletter endpoint not configured."
24
+ });
25
+ return;
26
+ }
27
+ if (!res.ok) {
28
+ const body = await res.json().catch(() => null);
29
+ setState({
30
+ kind: "error",
31
+ message: body?.error?.message ?? "Subscription failed. Try again."
32
+ });
33
+ return;
34
+ }
35
+ setState({ kind: "ok" });
36
+ setEmail("");
37
+ } catch {
38
+ setState({ kind: "error", message: "Network error." });
39
+ }
40
+ }
41
+ if (state.kind === "ok") {
42
+ return /* @__PURE__ */ jsx("p", { className: "np-site-footer-subscribe-success", role: "status", children: "Thanks \u2014 you're subscribed." });
43
+ }
44
+ return /* @__PURE__ */ jsxs(
45
+ "form",
46
+ {
47
+ className: "np-site-footer-subscribe-form",
48
+ onSubmit: (e) => {
49
+ void onSubmit(e);
50
+ },
51
+ children: [
52
+ /* @__PURE__ */ jsx("label", { className: "sr-only", htmlFor: "np-newsletter-email", children: "Email address" }),
53
+ /* @__PURE__ */ jsx(
54
+ "input",
55
+ {
56
+ id: "np-newsletter-email",
57
+ type: "email",
58
+ required: true,
59
+ autoComplete: "email",
60
+ placeholder: "you@example.com",
61
+ value: email,
62
+ onChange: (e) => setEmail(e.target.value)
63
+ }
64
+ ),
65
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: state.kind === "submitting", children: state.kind === "submitting" ? "Subscribing\u2026" : "Subscribe" }),
66
+ state.kind === "error" ? /* @__PURE__ */ jsx("p", { className: "np-site-footer-subscribe-error", role: "alert", children: state.message }) : null
67
+ ]
68
+ }
69
+ );
70
+ }
71
+ export {
72
+ NewsletterForm
73
+ };
74
+ //# sourceMappingURL=newsletter-form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/newsletter-form.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\n\n/**\n * Lightweight newsletter form. Submits to `/api/newsletter`\n * (operators wire this up in their app, or hand it off to a\n * provider like ConvertKit / Buttondown via a small route\n * handler). When the endpoint isn't present we degrade\n * gracefully — the user sees an \"endpoint not configured\"\n * notice rather than a stack trace.\n *\n * Optimistic UX: the input is replaced by a \"thanks\" message\n * the moment the response is OK. Errors keep the input visible\n * so the user can retry.\n */\nexport function NewsletterForm() {\n const [email, setEmail] = useState(\"\");\n const [state, setState] = useState<\n | { kind: \"idle\" }\n | { kind: \"submitting\" }\n | { kind: \"ok\" }\n | { kind: \"error\"; message: string }\n >({ kind: \"idle\" });\n\n async function onSubmit(e: React.FormEvent<HTMLFormElement>) {\n e.preventDefault();\n if (state.kind === \"submitting\") return;\n setState({ kind: \"submitting\" });\n try {\n const res = await fetch(\"/api/newsletter\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ email }),\n });\n if (res.status === 404) {\n setState({\n kind: \"error\",\n message: \"Newsletter endpoint not configured.\",\n });\n return;\n }\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as\n | { error?: { message?: string } }\n | null;\n setState({\n kind: \"error\",\n message: body?.error?.message ?? \"Subscription failed. Try again.\",\n });\n return;\n }\n setState({ kind: \"ok\" });\n setEmail(\"\");\n } catch {\n setState({ kind: \"error\", message: \"Network error.\" });\n }\n }\n\n if (state.kind === \"ok\") {\n return (\n <p className=\"np-site-footer-subscribe-success\" role=\"status\">\n Thanks — you're subscribed.\n </p>\n );\n }\n\n return (\n <form\n className=\"np-site-footer-subscribe-form\"\n onSubmit={(e) => {\n void onSubmit(e);\n }}\n >\n <label className=\"sr-only\" htmlFor=\"np-newsletter-email\">\n Email address\n </label>\n <input\n id=\"np-newsletter-email\"\n type=\"email\"\n required\n autoComplete=\"email\"\n placeholder=\"you@example.com\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n />\n <button type=\"submit\" disabled={state.kind === \"submitting\"}>\n {state.kind === \"submitting\" ? \"Subscribing…\" : \"Subscribe\"}\n </button>\n {state.kind === \"error\" ? (\n <p className=\"np-site-footer-subscribe-error\" role=\"alert\">\n {state.message}\n </p>\n ) : null}\n </form>\n );\n}\n"],"mappings":";;;;AAEA,SAAS,gBAAgB;AA2DnB,cAOF,YAPE;AA7CC,SAAS,iBAAiB;AAC/B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAKxB,EAAE,MAAM,OAAO,CAAC;AAElB,iBAAe,SAAS,GAAqC;AAC3D,MAAE,eAAe;AACjB,QAAI,MAAM,SAAS,aAAc;AACjC,aAAS,EAAE,MAAM,aAAa,CAAC;AAC/B,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,mBAAmB;AAAA,QACzC,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,MAChC,CAAC;AACD,UAAI,IAAI,WAAW,KAAK;AACtB,iBAAS;AAAA,UACP,MAAM;AAAA,UACN,SAAS;AAAA,QACX,CAAC;AACD;AAAA,MACF;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,OAAQ,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,IAAI;AAG/C,iBAAS;AAAA,UACP,MAAM;AAAA,UACN,SAAS,MAAM,OAAO,WAAW;AAAA,QACnC,CAAC;AACD;AAAA,MACF;AACA,eAAS,EAAE,MAAM,KAAK,CAAC;AACvB,eAAS,EAAE;AAAA,IACb,QAAQ;AACN,eAAS,EAAE,MAAM,SAAS,SAAS,iBAAiB,CAAC;AAAA,IACvD;AAAA,EACF;AAEA,MAAI,MAAM,SAAS,MAAM;AACvB,WACE,oBAAC,OAAE,WAAU,oCAAmC,MAAK,UAAS,8CAE9D;AAAA,EAEJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,UAAU,CAAC,MAAM;AACf,aAAK,SAAS,CAAC;AAAA,MACjB;AAAA,MAEA;AAAA,4BAAC,WAAM,WAAU,WAAU,SAAQ,uBAAsB,2BAEzD;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,IAAG;AAAA,YACH,MAAK;AAAA,YACL,UAAQ;AAAA,YACR,cAAa;AAAA,YACb,aAAY;AAAA,YACZ,OAAO;AAAA,YACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA;AAAA,QAC1C;AAAA,QACA,oBAAC,YAAO,MAAK,UAAS,UAAU,MAAM,SAAS,cAC5C,gBAAM,SAAS,eAAe,sBAAiB,aAClD;AAAA,QACC,MAAM,SAAS,UACd,oBAAC,OAAE,WAAU,kCAAiC,MAAK,SAChD,gBAAM,SACT,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -0,0 +1,192 @@
1
+ import * as _nexpress_theme from '@nexpress/theme';
2
+ import { NpThemeShellProps, NpTemplateRenderProps } from '@nexpress/theme';
3
+ import { ReactNode } from 'react';
4
+ import * as react_jsx_runtime from 'react/jsx-runtime';
5
+ export { DefaultFooter } from './components/footer-columns.js';
6
+ export { MemberStatusWidget } from './components/member-status-widget.js';
7
+ export { MobileNav } from './components/mobile-nav.js';
8
+ export { NewsletterForm } from './components/newsletter-form.js';
9
+
10
+ /**
11
+ * Default theme shell — wraps every (site) route. The shell
12
+ * also owns this theme's color-mode policy: it mounts
13
+ * `<NpColorSchemeScript />` so the saved `np-color-scheme`
14
+ * cookie / `prefers-color-scheme` choice is applied to
15
+ * `<html data-theme="…">` before first paint, and the dark
16
+ * variants ride on the rules in `defaultThemeCss`.
17
+ *
18
+ * Dark mode is no longer auto-wired by the framework — every
19
+ * theme decides whether to ship light/dark switching, what
20
+ * tokens it flips, and how the toggle UX looks. Themes that
21
+ * want a different policy (no dark mode, time-of-day,
22
+ * seasonal palette, …) simply omit this script and the
23
+ * dark CSS overrides.
24
+ */
25
+ declare function DefaultShell({ children }: NpThemeShellProps): ReactNode;
26
+
27
+ /**
28
+ * Default theme header — server component. Reads the
29
+ * `header` navigation menu and renders the desktop / mobile
30
+ * surfaces in one go:
31
+ *
32
+ * - Desktop (≥768px): inline link list, search bar, lang
33
+ * picker, dark toggle, member widget.
34
+ * - Mobile (<768px): the inline list collapses (CSS-only) and
35
+ * a hamburger button opens a slide-in drawer (`<MobileNav />`,
36
+ * a small client component that owns its own open/closed
37
+ * state). The same nav items feed both surfaces — markup is
38
+ * server-rendered once and reused.
39
+ *
40
+ * The header is `position: sticky` (see styles.ts) so the search
41
+ * + member widget stay reachable as the page scrolls.
42
+ */
43
+ declare function DefaultHeader(): Promise<react_jsx_runtime.JSX.Element>;
44
+
45
+ /**
46
+ * Social-link strip read from `process.env.NP_SOCIAL_*` so
47
+ * sites can light up the footer icons without forking the theme.
48
+ * Empty when no env vars are set — the column collapses cleanly.
49
+ *
50
+ * Recognized env vars:
51
+ * NP_SOCIAL_GITHUB → https://github.com/<handle>
52
+ * NP_SOCIAL_TWITTER → https://twitter.com/<handle> or x.com URL
53
+ * NP_SOCIAL_LINKEDIN → company / personal URL
54
+ * NP_SOCIAL_MASTODON → https://mastodon.social/@<handle>
55
+ * NP_SOCIAL_RSS → defaults to /feed.xml; set explicit URL to override
56
+ * NP_SOCIAL_EMAIL → mailto: address
57
+ */
58
+ declare function SocialLinks(): react_jsx_runtime.JSX.Element | null;
59
+
60
+ /**
61
+ * Card representation of a post in a list / grid context. Used
62
+ * by the post-list template's grid + the related-posts strip on
63
+ * post detail.
64
+ *
65
+ * Renders a small card with title, optional cover image, excerpt,
66
+ * date, and reading time when those fields exist on the doc.
67
+ * Stays defensive on field shapes — sites can fork the posts
68
+ * collection schema, so we use type guards rather than a hard
69
+ * shape requirement.
70
+ */
71
+ interface PostCardDoc {
72
+ id?: string;
73
+ slug?: string;
74
+ title?: string;
75
+ excerpt?: string;
76
+ cover?: {
77
+ url?: string;
78
+ alt?: string;
79
+ } | string | null;
80
+ publishedAt?: string | Date;
81
+ readingTime?: number | string;
82
+ author?: {
83
+ name?: string;
84
+ } | string;
85
+ }
86
+ interface PostCardProps {
87
+ doc: PostCardDoc;
88
+ /** Visual variant. "grid" is the default; "feature" is bigger and lets the cover image stretch. */
89
+ variant?: "grid" | "feature";
90
+ }
91
+ declare function PostCard({ doc, variant }: PostCardProps): react_jsx_runtime.JSX.Element;
92
+
93
+ /**
94
+ * Server-rendered pagination strip. Templates pass in the current
95
+ * page + total count + URL builder; we render Prev / page numbers
96
+ * / Next via next/link so navigation stays SPA-style and a
97
+ * no-JS user still gets working anchors.
98
+ */
99
+ interface PaginationProps {
100
+ page: number;
101
+ totalPages: number;
102
+ /** Builds the href for a given page number. Letting the
103
+ * caller own this means /blog?page= and /search?q=foo&page=
104
+ * share the same component. */
105
+ hrefForPage: (page: number) => string;
106
+ /** How many neighbours to show on each side of the active
107
+ * page before collapsing to "…". Default 1, like GitHub. */
108
+ siblings?: number;
109
+ }
110
+ declare function Pagination({ page, totalPages, hrefForPage, siblings }: PaginationProps): react_jsx_runtime.JSX.Element | null;
111
+
112
+ /**
113
+ * Landing-page template — full-bleed hero from the doc's first
114
+ * block, then the rest of the blocks render edge-to-edge so each
115
+ * one (Hero / FeatureGrid / CTA / Pricing) can use the full
116
+ * viewport width. The page's `title` and `seoDescription` form a
117
+ * fallback hero when the doc has no blocks yet.
118
+ *
119
+ * For pages where the operator wants a single max-width column
120
+ * and a sticky table of contents, pick the "default" template
121
+ * instead.
122
+ */
123
+ declare function PageLandingTemplate({ doc, blockCtx }: NpTemplateRenderProps): react_jsx_runtime.JSX.Element;
124
+
125
+ /**
126
+ * Page template with a sticky sidebar on the right. Suited to
127
+ * documentation / knowledge-base pages — the main column carries
128
+ * the body, the aside carries supporting links / version pickers
129
+ * / contributor cards.
130
+ *
131
+ * The aside is sourced from `doc.sidebar` (free-form blocks) when
132
+ * present; sites that don't model a sidebar field on their pages
133
+ * collection just see the main column. We keep the aside slot
134
+ * always-rendered (with a fallback "On this page" placeholder) so
135
+ * the layout doesn't reflow when an editor toggles the field.
136
+ */
137
+ declare function PageSidebarTemplate({ doc, blockCtx }: NpTemplateRenderProps): react_jsx_runtime.JSX.Element;
138
+
139
+ declare function PostDefaultTemplate({ doc }: NpTemplateRenderProps): react_jsx_runtime.JSX.Element;
140
+
141
+ declare function PostListTemplate({ doc }: NpTemplateRenderProps): react_jsx_runtime.JSX.Element;
142
+
143
+ /**
144
+ * Layout-level CSS for `@nexpress/theme-default`. Production polish
145
+ * lives here: sticky header with mobile drawer, four-column footer
146
+ * with social + newsletter, post detail / list, page templates
147
+ * (default / wide / landing / sidebar), pagination, and a typography
148
+ * ramp that holds together across page widths.
149
+ *
150
+ * Design tokens come from `--np-color-*` / `--np-radius-*` /
151
+ * `--np-font-*` (set in apps/web/src/app/globals.css and switched
152
+ * by `[data-theme="dark"]` further down). Themes that want a
153
+ * different palette override the same custom properties — this
154
+ * file is structural.
155
+ *
156
+ * The framework injects this string as a `<style data-np-theme="default">`
157
+ * tag at SSR time so the rules race the document with no extra
158
+ * stylesheet round-trip.
159
+ */
160
+ declare const defaultThemeCss: string;
161
+
162
+ /**
163
+ * `@nexpress/theme-default` — v0.1-era baseline theme.
164
+ *
165
+ * **Status (v0.2):** kept for back-compat. The v0.2 reference
166
+ * themes are `theme-magazine` / `theme-docs` / `theme-portfolio`
167
+ * — they exercise the new contract surfaces (manifest.requires,
168
+ * settingsSchema, blocks, patterns, navLocations, archives,
169
+ * routes, seo). New sites should start from one of those.
170
+ *
171
+ * `theme-default` doesn't declare v0.2 surfaces: operators using
172
+ * it skip the no-code-customization workflow (no admin auto-
173
+ * form for theme settings, no `theme:install` data-shape
174
+ * checks, no theme-shipped blocks/patterns). It remains a valid
175
+ * `defineTheme` caller — production sites pinned to v0.1 keep
176
+ * working — but consider migrating to a v0.2 reference if you
177
+ * want operator-tunable settings.
178
+ *
179
+ * Production-grade defaults: sticky header with a mobile drawer,
180
+ * a four-column footer (brand / sitemap / resources / newsletter)
181
+ * with optional social icons, post list / detail templates that
182
+ * surface excerpt / cover / tags / reading time, and three page
183
+ * templates (default centered column, edge-to-edge wide, marketing
184
+ * landing, doc-style sidebar). All CSS is theme-owned so the
185
+ * framework drops it as a single `<style data-np-theme="default">`
186
+ * tag at SSR time — no extra round-trip.
187
+ *
188
+ * Sites brand by overriding the design tokens (`--np-color-*` etc).
189
+ */
190
+ declare const defaultTheme: _nexpress_theme.NpTheme;
191
+
192
+ export { DefaultHeader, DefaultShell, PageLandingTemplate, PageSidebarTemplate, Pagination, type PaginationProps, PostCard, type PostCardDoc, type PostCardProps, PostDefaultTemplate, PostListTemplate, SocialLinks, defaultTheme, defaultThemeCss };