soames-astro-theme 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.
- package/LICENSE +29 -0
- package/README.md +67 -0
- package/package.json +68 -0
- package/src/components/Bio.tsx +36 -0
- package/src/components/BlogSidebar.tsx +48 -0
- package/src/components/DocsSidebar.tsx +45 -0
- package/src/components/Footer.tsx +59 -0
- package/src/components/FooterMenu.tsx +36 -0
- package/src/components/Header.tsx +57 -0
- package/src/components/HeaderMenu.tsx +77 -0
- package/src/components/HeroHeader.tsx +66 -0
- package/src/components/Logo.tsx +37 -0
- package/src/components/shortcodes/RemoveContentAreaPadding.tsx +13 -0
- package/src/components/shortcodes/Shortcodes.tsx +319 -0
- package/src/components/shortcodes/SoamesFeature.tsx +54 -0
- package/src/components/shortcodes/SoamesGalleryMenu.tsx +90 -0
- package/src/components/shortcodes/SoamesIconList.tsx +90 -0
- package/src/components/shortcodes/SoamesSoundCloud.tsx +71 -0
- package/src/components/shortcodes/SoamesTextBlock.tsx +27 -0
- package/src/components/shortcodes/SoamesTextList.tsx +27 -0
- package/src/components/shortcodes/SoamesTitle.tsx +24 -0
- package/src/components/shortcodes/SoamesTitleBar.tsx +22 -0
- package/src/components/shortcodes/SoamesTitleBarLg.tsx +56 -0
- package/src/components/shortcodes/SoamesVideo.tsx +35 -0
- package/src/components/shortcodes/getAttributes.ts +19 -0
- package/src/components/shortcodes/getContent.ts +4 -0
- package/src/integration.ts +147 -0
- package/src/layouts/Base.astro +93 -0
- package/src/lib/wp.ts +351 -0
- package/src/routes/[...uri].astro +41 -0
- package/src/routes/blog/[...page].astro +88 -0
- package/src/routes/blog/post/[...slug].astro +79 -0
- package/src/routes/docs/[...slug].astro +58 -0
- package/src/routes/docs/index.astro +66 -0
- package/src/routes/index.astro +39 -0
- package/src/scripts/soames-nav.js +72 -0
- package/src/styles/site-overrides.css +4 -0
- package/src/styles/soames/base.css +593 -0
- package/src/styles/soames/components.css +1556 -0
- package/src/styles/soames/layout.css +209 -0
- package/src/styles/soames/overrides.css +2138 -0
- package/src/styles/soames/typography.css +23 -0
- package/src/styles/theme.css +8 -0
- package/src/styles/vendor/normalize.css +343 -0
- package/src/styles/vendor/wordpress-blocks.css +3451 -0
- package/src/theme-shadow.ts +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025, Orbi Software
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://soames.app">
|
|
3
|
+
<img alt="Soames" src="https://raw.githubusercontent.com/orbivision/soames-astro-theme/main/assets/soames-mark.svg" width="60" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
<h1 align="center">
|
|
7
|
+
Soames Astro Theme
|
|
8
|
+
</h1>
|
|
9
|
+
|
|
10
|
+
[](https://www.npmjs.com/package/soames-astro-theme)
|
|
11
|
+
[](./LICENSE)
|
|
12
|
+
|
|
13
|
+
Shared **Astro** theme for the Soames ecosystem — WordPress as a headless CMS,
|
|
14
|
+
static output, React islands. Successor to `soames-gatsby-theme` (see ORBI-23/24/25).
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
npm install soames-astro-theme
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
```js
|
|
25
|
+
// astro.config.mjs
|
|
26
|
+
import { defineConfig } from 'astro/config';
|
|
27
|
+
import soamesTheme from 'soames-astro-theme';
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
output: 'static',
|
|
31
|
+
integrations: [
|
|
32
|
+
soamesTheme({ wordpressUrl: process.env.WORDPRESS_GRAPHQL_URL }),
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The integration registers `@astrojs/react`, sources content from WordPress at
|
|
38
|
+
build time, injects the page routes (front page, WP pages, blog archive,
|
|
39
|
+
blog posts), authorizes WP image domains, and installs the **theme-shadow**
|
|
40
|
+
resolver.
|
|
41
|
+
|
|
42
|
+
## Component overrides (the shadowing successor)
|
|
43
|
+
|
|
44
|
+
Astro has no built-in component shadowing. This theme provides it via a Vite
|
|
45
|
+
resolver: theme files are imported as `@theme/<path>`, and a site overrides any
|
|
46
|
+
of them by creating a file at the matching path under `src/overrides/`.
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/overrides/components/Footer.tsx → replaces the theme's components/Footer.tsx
|
|
50
|
+
src/overrides/layouts/Base.astro → replaces the theme's layouts/Base.astro
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Whole-file replacement, resolved at build time, zero changes at the import site —
|
|
54
|
+
the direct successor to Gatsby component shadowing.
|
|
55
|
+
|
|
56
|
+
## WordPress sourcing notes
|
|
57
|
+
|
|
58
|
+
- Sources via WPGraphQL (`fetch()`), with Site Assets from the Soames plugin REST
|
|
59
|
+
endpoint (`/wp-json/soames/v1/settings`).
|
|
60
|
+
- **Wordfence gotcha:** some WP installs' WAF 403-blocks GraphQL whose first
|
|
61
|
+
selection-set field is `pages`/`posts`. `lib/wp.ts` aliases the root field
|
|
62
|
+
(e.g. `wpPages: pages`) to pass; the durable fix is a Wordfence allowlist rule.
|
|
63
|
+
|
|
64
|
+
## Status
|
|
65
|
+
|
|
66
|
+
Published to npm (ORBI-25). Under active migration — routing/URL parity, menu
|
|
67
|
+
interactivity, and remaining shortcodes are being completed phase by phase.
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "soames-astro-theme",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Shared Astro theme for the Soames ecosystem (WordPress headless + React islands). Successor to soames-gatsby-theme.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"astro",
|
|
8
|
+
"astro-integration",
|
|
9
|
+
"astro-theme",
|
|
10
|
+
"wordpress",
|
|
11
|
+
"headless-cms",
|
|
12
|
+
"typescript",
|
|
13
|
+
"react",
|
|
14
|
+
"blog",
|
|
15
|
+
"personal-site"
|
|
16
|
+
],
|
|
17
|
+
"author": "Orbi Software",
|
|
18
|
+
"license": "BSD-3-Clause",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/orbivision/soames-astro-theme"
|
|
22
|
+
},
|
|
23
|
+
"bugs": {
|
|
24
|
+
"url": "https://github.com/orbivision/soames-astro-theme/issues"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/orbivision/soames-astro-theme#readme",
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.20.8",
|
|
29
|
+
"npm": ">=9.0.0"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./src/integration.ts",
|
|
36
|
+
"./integration": "./src/integration.ts",
|
|
37
|
+
"./theme-shadow": "./src/theme-shadow.ts",
|
|
38
|
+
"./lib/wp": "./src/lib/wp.ts",
|
|
39
|
+
"./routes/*": "./src/routes/*",
|
|
40
|
+
"./components/*": "./src/components/*",
|
|
41
|
+
"./layouts/*": "./src/layouts/*",
|
|
42
|
+
"./styles/*": "./src/styles/*",
|
|
43
|
+
"./package.json": "./package.json"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"src",
|
|
47
|
+
"README.md",
|
|
48
|
+
"LICENSE"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"check": "astro check"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"astro": "^5.0.0",
|
|
55
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
56
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@astrojs/react": "^4.2.0",
|
|
60
|
+
"bootstrap": "^5.3.8",
|
|
61
|
+
"html-react-parser": "^5.2.3"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/react": "^18.3.12",
|
|
65
|
+
"@types/react-dom": "^18.3.1",
|
|
66
|
+
"astro": "^5.7.0"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme Bio.tsx. Change for Astro: author comes in as a
|
|
2
|
+
// prop (from the post's author node) instead of useStaticQuery(wpUser) — the
|
|
3
|
+
// standalone users query is auth/WAF-gated. Uses firstName, falling back to name.
|
|
4
|
+
import React from "react";
|
|
5
|
+
import type { WpAuthor } from "../lib/wp";
|
|
6
|
+
|
|
7
|
+
interface BioProps {
|
|
8
|
+
author: WpAuthor | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const Bio: React.FC<BioProps> = ({ author }) => {
|
|
12
|
+
if (!author) return null;
|
|
13
|
+
const displayName = author.firstName || author.name;
|
|
14
|
+
const twitterHandle = author.name;
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div className="bio">
|
|
18
|
+
{author.avatarUrl && (
|
|
19
|
+
<img alt={displayName ?? "Author"} className="bio-avatar" src={author.avatarUrl} />
|
|
20
|
+
)}
|
|
21
|
+
{displayName && (
|
|
22
|
+
<p>
|
|
23
|
+
Written by <strong>{displayName}</strong>
|
|
24
|
+
{author.description ? ` ${author.description}` : ""}{" "}
|
|
25
|
+
{twitterHandle && (
|
|
26
|
+
<a href={`https://twitter.com/${twitterHandle}`}>
|
|
27
|
+
You should follow them on Twitter
|
|
28
|
+
</a>
|
|
29
|
+
)}
|
|
30
|
+
</p>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default Bio;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme BlogSidebar.tsx. Changes for Astro: posts come in
|
|
2
|
+
// as a prop (no useStaticQuery); gatsby <Link> → <a>; links use the Astro post path
|
|
3
|
+
// (/blog/post/<slug>/). Markup/classes UNCHANGED.
|
|
4
|
+
import React from "react";
|
|
5
|
+
import parse from "html-react-parser";
|
|
6
|
+
import type { WpPost } from "../lib/wp";
|
|
7
|
+
|
|
8
|
+
interface BlogSidebarProps {
|
|
9
|
+
posts: WpPost[];
|
|
10
|
+
currentId: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const BlogSidebar: React.FC<BlogSidebarProps> = ({ posts, currentId }) => {
|
|
14
|
+
return (
|
|
15
|
+
<section className="soames-blog-roll pt-3">
|
|
16
|
+
<div className="container">
|
|
17
|
+
{posts.map((post) =>
|
|
18
|
+
post.databaseId !== currentId ? (
|
|
19
|
+
<div key={post.databaseId} className="media-container-row">
|
|
20
|
+
<div className="card p-3 col-12">
|
|
21
|
+
<div className="card-wrapper">
|
|
22
|
+
<div className="card-box">
|
|
23
|
+
<h4 className="card-title mbr-fonts-style display-5">
|
|
24
|
+
{parse(post.title)}
|
|
25
|
+
</h4>
|
|
26
|
+
<h4 className="mbr-fonts-style display-7">{post.date}</h4>
|
|
27
|
+
{parse(post.excerpt)}
|
|
28
|
+
</div>
|
|
29
|
+
<div className="mbr-section-btn text-center">
|
|
30
|
+
<a
|
|
31
|
+
href={`/blog/post/${post.slug}/`}
|
|
32
|
+
itemProp="url"
|
|
33
|
+
className="btn btn-primary display-4"
|
|
34
|
+
>
|
|
35
|
+
<span itemProp="headline">Read More</span>
|
|
36
|
+
</a>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
) : null
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</section>
|
|
45
|
+
);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default BlogSidebar;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Hierarchical documentation nav for the docs template — the docs equivalent of
|
|
2
|
+
// BlogSidebar's "Recent Posts". Renders the weDocs doc tree (built by
|
|
3
|
+
// buildDocTree) as a nested menu, highlighting the current doc. Static (no
|
|
4
|
+
// hydration needed); links use each doc's WP `uri` (e.g. /docs/getting-started/).
|
|
5
|
+
import React from "react";
|
|
6
|
+
import parse from "html-react-parser";
|
|
7
|
+
import type { DocTreeNode } from "../lib/wp";
|
|
8
|
+
|
|
9
|
+
interface DocsSidebarProps {
|
|
10
|
+
tree: DocTreeNode[];
|
|
11
|
+
currentId: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DocList: React.FC<{ nodes: DocTreeNode[]; currentId: number; top?: boolean }> = ({
|
|
15
|
+
nodes,
|
|
16
|
+
currentId,
|
|
17
|
+
top = false,
|
|
18
|
+
}) => (
|
|
19
|
+
<ul className={top ? "soames-docs-menu" : "soames-docs-menu soames-docs-submenu"}>
|
|
20
|
+
{nodes.map((node) => {
|
|
21
|
+
const active = node.databaseId === currentId;
|
|
22
|
+
return (
|
|
23
|
+
<li
|
|
24
|
+
key={node.databaseId}
|
|
25
|
+
className={`soames-docs-menu-item${active ? " is-active" : ""}`}
|
|
26
|
+
>
|
|
27
|
+
<a href={node.uri} aria-current={active ? "page" : undefined}>
|
|
28
|
+
{parse(node.title)}
|
|
29
|
+
</a>
|
|
30
|
+
{node.children.length > 0 && (
|
|
31
|
+
<DocList nodes={node.children} currentId={currentId} />
|
|
32
|
+
)}
|
|
33
|
+
</li>
|
|
34
|
+
);
|
|
35
|
+
})}
|
|
36
|
+
</ul>
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const DocsSidebar: React.FC<DocsSidebarProps> = ({ tree, currentId }) => (
|
|
40
|
+
<nav className="soames-docs-nav" aria-label="Documentation">
|
|
41
|
+
<DocList nodes={tree} currentId={currentId} top />
|
|
42
|
+
</nav>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export default DocsSidebar;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme/src/components/Footer.tsx (post-ORBI-22).
|
|
2
|
+
// Changes for Astro: FooterMenu's useStaticQuery → `menuItems` prop (fetched in the
|
|
3
|
+
// layout). Copyright logic (companyName || title) UNCHANGED from ORBI-22.
|
|
4
|
+
import React from "react";
|
|
5
|
+
import FooterMenu from "./FooterMenu";
|
|
6
|
+
import type { MenuItem } from "../lib/wp";
|
|
7
|
+
|
|
8
|
+
interface FooterProps {
|
|
9
|
+
title: string;
|
|
10
|
+
contactBlurb?: string | null;
|
|
11
|
+
companyName?: string | null;
|
|
12
|
+
menuItems?: MenuItem[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Footer: React.FC<FooterProps> = ({
|
|
16
|
+
title,
|
|
17
|
+
contactBlurb = null,
|
|
18
|
+
companyName = null,
|
|
19
|
+
menuItems = [],
|
|
20
|
+
}) => {
|
|
21
|
+
return (
|
|
22
|
+
<section className="soames-footer mt-5">
|
|
23
|
+
<div className="container">
|
|
24
|
+
<div className="media-container-row content text-white">
|
|
25
|
+
<div className="col-12 col-md-2">
|
|
26
|
+
<div className="media-wrap">{` `}</div>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="col-12 col-md-4 mbr-fonts-style display-7">
|
|
29
|
+
<FooterMenu items={menuItems} />
|
|
30
|
+
</div>
|
|
31
|
+
<div className="col-12 col-md-4 mbr-fonts-style display-7">
|
|
32
|
+
{contactBlurb && (
|
|
33
|
+
<>
|
|
34
|
+
<h5 className="pb-3">Contact</h5>
|
|
35
|
+
<div
|
|
36
|
+
className="soames-text pr-3"
|
|
37
|
+
dangerouslySetInnerHTML={{ __html: contactBlurb }}
|
|
38
|
+
/>
|
|
39
|
+
</>
|
|
40
|
+
)}
|
|
41
|
+
<p className="soames-text pt-3" data-testid="footer-copyright">
|
|
42
|
+
© {new Date().getFullYear()} {companyName || title}
|
|
43
|
+
<br />
|
|
44
|
+
Built with{" "}
|
|
45
|
+
<a href="https://www.soames.app" target="_blank" rel="noreferrer">Soames</a>,{" "}
|
|
46
|
+
<a href="https://astro.build" target="_blank" rel="noreferrer">Astro</a>, and{" "}
|
|
47
|
+
<a href="https://wordpress.org/" target="_blank" rel="noreferrer">WordPress</a>
|
|
48
|
+
</p>
|
|
49
|
+
</div>
|
|
50
|
+
<div className="col-12 col-md-2 mbr-fonts-style display-7">
|
|
51
|
+
<h5 className="pb-3">{` `}</h5>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</section>
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export default Footer;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme FooterMenu.tsx. Changes: useStaticQuery → `items`
|
|
2
|
+
// prop (getMenuByLocation("FOOTER")); gatsby <Link> → <a>. Markup UNCHANGED.
|
|
3
|
+
import React from "react";
|
|
4
|
+
import type { MenuItem } from "../lib/wp";
|
|
5
|
+
|
|
6
|
+
interface FooterMenuProps {
|
|
7
|
+
items: MenuItem[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const FooterMenu: React.FC<FooterMenuProps> = ({ items }) => {
|
|
11
|
+
const topLevel = items.filter((item) => item.parentDatabaseId === 0);
|
|
12
|
+
if (topLevel.length === 0) return null;
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<div className="soames-footer-content">
|
|
16
|
+
<h5 className="pb-3">Links</h5>
|
|
17
|
+
<ul>
|
|
18
|
+
{topLevel.map((item) =>
|
|
19
|
+
item.uri.includes("http") ? (
|
|
20
|
+
<li className="soames-footer-list-item" key={item.id}>
|
|
21
|
+
<a href={item.uri} target="_blank" rel="noreferrer">
|
|
22
|
+
{item.label}
|
|
23
|
+
</a>
|
|
24
|
+
</li>
|
|
25
|
+
) : (
|
|
26
|
+
<li className="soames-footer-list-item" key={item.id}>
|
|
27
|
+
<a href={item.uri}>{item.label}</a>
|
|
28
|
+
</li>
|
|
29
|
+
)
|
|
30
|
+
)}
|
|
31
|
+
</ul>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export default FooterMenu;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme Header.tsx. Logo/menu data now flow as props
|
|
2
|
+
// (no useStaticQuery). Markup UNCHANGED.
|
|
3
|
+
import React from "react";
|
|
4
|
+
import HeaderMenu from "./HeaderMenu";
|
|
5
|
+
import Logo from "./Logo";
|
|
6
|
+
import type { MenuItem } from "../lib/wp";
|
|
7
|
+
|
|
8
|
+
interface HeaderProps {
|
|
9
|
+
title: string;
|
|
10
|
+
logoUrl?: string | null;
|
|
11
|
+
logoAlt?: string | null;
|
|
12
|
+
companyName?: string | null;
|
|
13
|
+
showCompanyName?: boolean;
|
|
14
|
+
menuItems: MenuItem[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const Header: React.FC<HeaderProps> = ({
|
|
18
|
+
title,
|
|
19
|
+
logoUrl = null,
|
|
20
|
+
logoAlt = null,
|
|
21
|
+
companyName = null,
|
|
22
|
+
showCompanyName = true,
|
|
23
|
+
menuItems,
|
|
24
|
+
}) => {
|
|
25
|
+
return (
|
|
26
|
+
<section className="menu soames-menu">
|
|
27
|
+
<nav className="navbar navbar-expand beta-menu navbar-dropdown align-items-center navbar-fixed-top navbar-toggleable-sm">
|
|
28
|
+
<button
|
|
29
|
+
className="navbar-toggler navbar-toggler-right"
|
|
30
|
+
type="button"
|
|
31
|
+
data-bs-toggle="collapse"
|
|
32
|
+
data-bs-target="#navbarSupportedContent"
|
|
33
|
+
aria-controls="navbarSupportedContent"
|
|
34
|
+
aria-expanded="false"
|
|
35
|
+
aria-label="Toggle navigation"
|
|
36
|
+
>
|
|
37
|
+
<div className="hamburger">
|
|
38
|
+
<span></span>
|
|
39
|
+
<span></span>
|
|
40
|
+
<span></span>
|
|
41
|
+
<span></span>
|
|
42
|
+
</div>
|
|
43
|
+
</button>
|
|
44
|
+
<Logo
|
|
45
|
+
title={title}
|
|
46
|
+
logoUrl={logoUrl}
|
|
47
|
+
logoAlt={logoAlt}
|
|
48
|
+
companyName={companyName}
|
|
49
|
+
showCompanyName={showCompanyName}
|
|
50
|
+
/>
|
|
51
|
+
<HeaderMenu items={menuItems} />
|
|
52
|
+
</nav>
|
|
53
|
+
</section>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export default Header;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme HeaderMenu.tsx. Changes: useStaticQuery → `items`
|
|
2
|
+
// prop (fetched in the layout via getMenuByLocation("HEADER")); gatsby <Link> → <a>.
|
|
3
|
+
// Markup/classes UNCHANGED — Bootstrap dropdown behavior is wired by global JS
|
|
4
|
+
// (Phase 4), not React, so this renders as static HTML.
|
|
5
|
+
import React from "react";
|
|
6
|
+
import type { MenuItem } from "../lib/wp";
|
|
7
|
+
|
|
8
|
+
interface HeaderMenuProps {
|
|
9
|
+
items: MenuItem[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const HeaderMenu: React.FC<HeaderMenuProps> = ({ items }) => {
|
|
13
|
+
return (
|
|
14
|
+
<div className="collapse navbar-collapse" id="navbarSupportedContent">
|
|
15
|
+
<ul className="navbar-nav nav-dropdown nav-right" data-app-modern-menu="true">
|
|
16
|
+
{items.map((item) =>
|
|
17
|
+
item.path !== "/home/" && item.parentDatabaseId === 0 ? (
|
|
18
|
+
item.childItems.length === 0 ? (
|
|
19
|
+
<li key={item.id} className="nav-item">
|
|
20
|
+
{item.uri.includes("http") ? (
|
|
21
|
+
<a
|
|
22
|
+
className="nav-link link text-white display-4"
|
|
23
|
+
href={item.uri}
|
|
24
|
+
target="_blank"
|
|
25
|
+
rel="noreferrer"
|
|
26
|
+
>
|
|
27
|
+
{item.label}
|
|
28
|
+
</a>
|
|
29
|
+
) : (
|
|
30
|
+
<a href={item.uri} className="nav-link link text-white display-4">
|
|
31
|
+
{item.label}
|
|
32
|
+
</a>
|
|
33
|
+
)}
|
|
34
|
+
</li>
|
|
35
|
+
) : (
|
|
36
|
+
<li key={item.id} className="nav-item dropdown">
|
|
37
|
+
<a
|
|
38
|
+
className="nav-link link text-white dropdown-toggle display-4"
|
|
39
|
+
href={item.uri}
|
|
40
|
+
data-toggle="dropdown-submenu"
|
|
41
|
+
aria-expanded="false"
|
|
42
|
+
>
|
|
43
|
+
{item.label}
|
|
44
|
+
</a>
|
|
45
|
+
<div className="dropdown-menu">
|
|
46
|
+
<ul className="navbar-nav nav-dropdown nav-right">
|
|
47
|
+
{item.childItems.map((childItem) => (
|
|
48
|
+
<li key={childItem.id}>
|
|
49
|
+
{childItem.uri.includes("http") ? (
|
|
50
|
+
<a
|
|
51
|
+
className="text-white dropdown-item display-4"
|
|
52
|
+
target="_blank"
|
|
53
|
+
rel="noreferrer"
|
|
54
|
+
href={childItem.uri}
|
|
55
|
+
>
|
|
56
|
+
{childItem.label}
|
|
57
|
+
<br />
|
|
58
|
+
</a>
|
|
59
|
+
) : (
|
|
60
|
+
<a href={childItem.uri} className="text-white dropdown-item display-4">
|
|
61
|
+
{childItem.label}
|
|
62
|
+
</a>
|
|
63
|
+
)}
|
|
64
|
+
</li>
|
|
65
|
+
))}
|
|
66
|
+
</ul>
|
|
67
|
+
</div>
|
|
68
|
+
</li>
|
|
69
|
+
)
|
|
70
|
+
) : null
|
|
71
|
+
)}
|
|
72
|
+
</ul>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default HeaderMenu;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme HeroHeader.tsx. Change for Astro: title/subhead
|
|
2
|
+
// come in as HTML strings and are run through html-react-parser here (the Gatsby
|
|
3
|
+
// page template parsed them before passing). Parallax/overlay markup UNCHANGED.
|
|
4
|
+
import React from "react";
|
|
5
|
+
import parse from "html-react-parser";
|
|
6
|
+
|
|
7
|
+
export interface HeroHeaderProps {
|
|
8
|
+
title: string;
|
|
9
|
+
subhead?: string | null;
|
|
10
|
+
backgroundImage?: string | null;
|
|
11
|
+
overlayOpacity?: number | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const HeroHeader: React.FC<HeroHeaderProps> = ({
|
|
15
|
+
title,
|
|
16
|
+
subhead,
|
|
17
|
+
backgroundImage,
|
|
18
|
+
overlayOpacity,
|
|
19
|
+
}) => {
|
|
20
|
+
const bg = backgroundImage || "https://picsum.photos/1080/720";
|
|
21
|
+
const opacity = overlayOpacity ?? 0.6;
|
|
22
|
+
|
|
23
|
+
const css = `
|
|
24
|
+
.soames-background-lg::after {
|
|
25
|
+
background: url(${bg});
|
|
26
|
+
background-position: 50% 50%;
|
|
27
|
+
background-size: cover;
|
|
28
|
+
background-repeat: no-repeat;
|
|
29
|
+
position: fixed;
|
|
30
|
+
top: 0px;
|
|
31
|
+
left: 0px;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
pointer-events: none;
|
|
34
|
+
margin-top: -180px;
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<>
|
|
40
|
+
<style>{css}</style>
|
|
41
|
+
<section
|
|
42
|
+
className="soames-header-lg soames-parallax soames-background-lg"
|
|
43
|
+
id="header1"
|
|
44
|
+
>
|
|
45
|
+
<div
|
|
46
|
+
className="soames-overlay"
|
|
47
|
+
style={{ opacity, backgroundColor: "rgb(46, 46, 46)" }}
|
|
48
|
+
/>
|
|
49
|
+
<div className="container">
|
|
50
|
+
<div className="row justify-content-md-center">
|
|
51
|
+
<div className="soames-hero-header soames-white col-md-10">
|
|
52
|
+
<h1 className="soames-section-title align-center soames-bold mbr-fonts-style display-1">
|
|
53
|
+
{parse(title || "")}
|
|
54
|
+
</h1>
|
|
55
|
+
<div className="soames-section-subtitle align-center soames-light soames-white mbr-fonts-style display-5">
|
|
56
|
+
{subhead ? parse(subhead) : ""}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</section>
|
|
62
|
+
</>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export default HeroHeader;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// PORTED from soames-gatsby-theme Logo.tsx. Change: useStaticQuery(soamesSettings)
|
|
2
|
+
// → props supplied by the layout at build time (Astro has no useStaticQuery).
|
|
3
|
+
import React from "react";
|
|
4
|
+
|
|
5
|
+
interface LogoProps {
|
|
6
|
+
title: string;
|
|
7
|
+
logoUrl?: string | null;
|
|
8
|
+
logoAlt?: string | null;
|
|
9
|
+
companyName?: string | null;
|
|
10
|
+
showCompanyName?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Logo: React.FC<LogoProps> = ({
|
|
14
|
+
title,
|
|
15
|
+
logoUrl = null,
|
|
16
|
+
logoAlt = null,
|
|
17
|
+
companyName = null,
|
|
18
|
+
showCompanyName = true,
|
|
19
|
+
}) => {
|
|
20
|
+
const displayName = companyName || title;
|
|
21
|
+
const alt = logoAlt || title;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<div className="menu-logo">
|
|
25
|
+
<div className="navbar-brand">
|
|
26
|
+
<span className="navbar-caption-wrap">
|
|
27
|
+
<a className="navbar-caption text-white display-5" href="/">
|
|
28
|
+
{logoUrl && <img width="108" alt={alt} src={logoUrl} />}
|
|
29
|
+
{showCompanyName && <> {displayName}</>}
|
|
30
|
+
</a>
|
|
31
|
+
</span>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default Logo;
|