diff --git a/package-lock.json b/package-lock.json
index d2000ea..19d18e7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,11 +10,13 @@
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/react": "^3.6.3",
+ "@nanostores/react": "^0.8.0",
"@patternfly/patternfly": "^6.0.0",
"@patternfly/react-core": "^6.0.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"astro": "^4.16.13",
+ "nanostores": "^0.11.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.81.0",
@@ -25,7 +27,6 @@
"@types/node": "^22.9.1",
"globals": "^15.12.0",
"jest": "^29.7.0",
- "jest-css-modules-transform": "^4.4.2",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript-eslint": "^8.15.0"
@@ -2369,6 +2370,25 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@nanostores/react": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@nanostores/react/-/react-0.8.0.tgz",
+ "integrity": "sha512-MhbVB7NQLboq/Z9fRTDen9zib/YCffe6mn+3Xg5MOYByMX5Xx98SOZjk/Nd3yvOd/g7GjlQqwXj0KF2lPb6CEQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "nanostores": "^0.9.0 || ^0.10.0 || ^0.11.0",
+ "react": ">=18.0.0"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -6729,34 +6749,6 @@
"node": ">=8"
}
},
- "node_modules/jest-css-modules-transform": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/jest-css-modules-transform/-/jest-css-modules-transform-4.4.2.tgz",
- "integrity": "sha512-qsUVOcY26chaFMJNMVrFYJBtYvPt1TImi9FWGaVycfsP6xnFW2HlnKRdZdKdg2LVVBv2Q9M4aLv7IbxPSXqPmg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "camelcase": "6.3.0",
- "postcss": "^7.0.30 || ^8.0.0",
- "postcss-nested": "^4.2.1 || ^5.0.0"
- },
- "engines": {
- "node": ">=10.0.0"
- }
- },
- "node_modules/jest-css-modules-transform/node_modules/camelcase": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
- "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/jest-diff": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
@@ -8781,6 +8773,21 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
+ "node_modules/nanostores": {
+ "version": "0.11.3",
+ "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-0.11.3.tgz",
+ "integrity": "sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -9180,40 +9187,6 @@
"node": "^10 || ^12 || >=14"
}
},
- "node_modules/postcss-nested": {
- "version": "5.0.6",
- "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-5.0.6.tgz",
- "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "postcss-selector-parser": "^6.0.6"
- },
- "engines": {
- "node": ">=12.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- "peerDependencies": {
- "postcss": "^8.2.14"
- }
- },
- "node_modules/postcss-selector-parser": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
- "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "cssesc": "^3.0.0",
- "util-deprecate": "^1.0.2"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/preferred-pm": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-4.0.0.tgz",
@@ -10688,13 +10661,6 @@
"punycode": "^2.1.0"
}
},
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/v8-compile-cache-lib": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
diff --git a/package.json b/package.json
index 92ab4eb..7b21159 100644
--- a/package.json
+++ b/package.json
@@ -15,11 +15,13 @@
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/react": "^3.6.3",
+ "@nanostores/react": "^0.8.0",
"@patternfly/patternfly": "^6.0.0",
"@patternfly/react-core": "^6.0.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"astro": "^4.16.13",
+ "nanostores": "^0.11.3",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"sass": "^1.81.0",
diff --git a/public/PF-HorizontalLogo-Color.svg b/public/PF-HorizontalLogo-Color.svg
new file mode 100644
index 0000000..7f22715
--- /dev/null
+++ b/public/PF-HorizontalLogo-Color.svg
@@ -0,0 +1,29 @@
+
+
\ No newline at end of file
diff --git a/public/PF-HorizontalLogo-Reverse.svg b/public/PF-HorizontalLogo-Reverse.svg
new file mode 100644
index 0000000..a47a3cb
--- /dev/null
+++ b/public/PF-HorizontalLogo-Reverse.svg
@@ -0,0 +1,28 @@
+
+
\ No newline at end of file
diff --git a/public/avatarImg.svg b/public/avatarImg.svg
new file mode 100644
index 0000000..73726f9
--- /dev/null
+++ b/public/avatarImg.svg
@@ -0,0 +1,18 @@
+
+
+
diff --git a/public/avatarImgDark.svg b/public/avatarImgDark.svg
new file mode 100644
index 0000000..d26cca8
--- /dev/null
+++ b/public/avatarImgDark.svg
@@ -0,0 +1,16 @@
+
+
+
diff --git a/src/components/Breadcrumbs.astro b/src/components/Breadcrumbs.astro
new file mode 100644
index 0000000..8072000
--- /dev/null
+++ b/src/components/Breadcrumbs.astro
@@ -0,0 +1,10 @@
+---
+import { Breadcrumb, BreadcrumbItem } from "@patternfly/react-core";
+---
+
+
+ Section home
+ Section title
+ Section title
+ Section landing
+
diff --git a/src/components/Foo.astro b/src/components/Foo.astro
deleted file mode 100644
index 608faac..0000000
--- a/src/components/Foo.astro
+++ /dev/null
@@ -1,5 +0,0 @@
----
-import { Button } from "@patternfly/react-core";
----
-
-
\ No newline at end of file
diff --git a/src/components/KebabDropdownItems.astro b/src/components/KebabDropdownItems.astro
new file mode 100644
index 0000000..e73dfaf
--- /dev/null
+++ b/src/components/KebabDropdownItems.astro
@@ -0,0 +1,14 @@
+---
+import { DropdownItem } from "@patternfly/react-core";
+import CogIcon from "@patternfly/react-icons/dist/esm/icons/cog-icon";
+import HelpIcon from "@patternfly/react-icons/dist/esm/icons/help-icon";
+---
+
+<>
+
+ Settings
+
+
+ Help
+
+>
diff --git a/src/components/KebabDropdownItems.tsx b/src/components/KebabDropdownItems.tsx
new file mode 100644
index 0000000..361b0a4
--- /dev/null
+++ b/src/components/KebabDropdownItems.tsx
@@ -0,0 +1,14 @@
+import { DropdownItem } from "@patternfly/react-core";
+import CogIcon from "@patternfly/react-icons/dist/esm/icons/cog-icon";
+import HelpIcon from "@patternfly/react-icons/dist/esm/icons/help-icon";
+
+export const KebabDropdownItems: React.FunctionComponent = () => (
+ <>
+
+ Settings
+
+
+ Help
+
+ >
+);
diff --git a/src/components/Masthead.astro b/src/components/Masthead.astro
new file mode 100644
index 0000000..011b5ee
--- /dev/null
+++ b/src/components/Masthead.astro
@@ -0,0 +1,30 @@
+---
+import {
+ Brand,
+ Masthead as PFMasthead,
+ MastheadMain,
+ MastheadToggle,
+ MastheadBrand,
+ MastheadLogo,
+ MastheadContent,
+} from "@patternfly/react-core";
+import pfLogo from "/PF-HorizontalLogo-Color.svg?url";
+import Toolbar from "./Toolbar.astro";
+import { PageToggle } from "./PageToggle";
+---
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/Navigation.astro b/src/components/Navigation.astro
new file mode 100644
index 0000000..6977a28
--- /dev/null
+++ b/src/components/Navigation.astro
@@ -0,0 +1,9 @@
+---
+import { getCollection } from "astro:content";
+
+import { Navigation as ReactNav } from "./Navigation.tsx";
+
+const navEntries = await getCollection("test");
+---
+
+
diff --git a/src/components/Navigation.tsx b/src/components/Navigation.tsx
new file mode 100644
index 0000000..ff81791
--- /dev/null
+++ b/src/components/Navigation.tsx
@@ -0,0 +1,66 @@
+import { useState, type ReactNode } from "react";
+import {
+ Nav,
+ NavList,
+ NavItem,
+ PageSidebar,
+ PageSidebarBody,
+} from "@patternfly/react-core";
+import { useStore } from "@nanostores/react";
+import { isNavOpen } from "../stores/navStore";
+
+interface NavOnSelectProps {
+ groupId: number | string;
+ itemId: number | string;
+ to: string;
+}
+
+interface NavEntry {
+ id: string;
+ slug: string;
+ data: {
+ title: string;
+ };
+ collection: string;
+}
+
+interface NavigationProps {
+ navEntries: NavEntry[];
+}
+
+export const Navigation: React.FunctionComponent = ({
+ navEntries,
+}: NavigationProps) => {
+ const [activeItem, setActiveItem] = useState("");
+
+ const onNavSelect = (
+ _event: React.FormEvent,
+ selectedItem: NavOnSelectProps
+ ) => {
+ typeof selectedItem.itemId === "string" &&
+ setActiveItem(selectedItem.itemId);
+ };
+
+ const $isNavOpen = useStore(isNavOpen);
+
+ const navItems = navEntries.map((entry) => (
+
+ {entry.data.title}
+
+ ));
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/src/components/Page.astro b/src/components/Page.astro
new file mode 100644
index 0000000..a27c1bd
--- /dev/null
+++ b/src/components/Page.astro
@@ -0,0 +1,19 @@
+---
+import styles from "@patternfly/react-styles/css/components/Page/page";
+import { Content, PageSection } from "@patternfly/react-core";
+---
+
+
diff --git a/src/components/PageToggle.tsx b/src/components/PageToggle.tsx
new file mode 100644
index 0000000..cbf73d8
--- /dev/null
+++ b/src/components/PageToggle.tsx
@@ -0,0 +1,48 @@
+import type React from "react";
+import { PageToggleButton } from "@patternfly/react-core";
+import BarsIcon from "@patternfly/react-icons/dist/esm/icons/bars-icon";
+import { useStore } from "@nanostores/react";
+import { isNavOpen } from "../stores/navStore";
+import { useEffect } from "react";
+
+export const PageToggle: React.FunctionComponent = () => {
+ const $isNavOpen = useStore(isNavOpen);
+
+ /** Applies sidebar styles to the island element astro creates as a wrapper for the sidebar.
+ * Without it the page content will not expand to fill the space left by the sidebar when it is collapsed.
+ */
+ function applySidebarStylesToIsland() {
+ const isClientSide = typeof window !== "undefined";
+ const sideBarIsland =
+ document.getElementById("page-sidebar")?.parentElement;
+
+ if (!isClientSide || !sideBarIsland) {
+ return;
+ }
+
+ if (!sideBarIsland.classList.contains("pf-v6-c-page__sidebar")) {
+ sideBarIsland.classList.add("pf-v6-c-page__sidebar", "pf-m-expanded");
+ } else {
+ sideBarIsland.classList.toggle("pf-m-expanded");
+ sideBarIsland.classList.toggle("pf-m-collapsed");
+ }
+ }
+
+ function onToggle() {
+ isNavOpen.set(!$isNavOpen);
+ }
+
+ useEffect(() => {
+ applySidebarStylesToIsland();
+ }, [$isNavOpen]);
+
+ return (
+
+
+
+ );
+};
diff --git a/src/components/Toolbar.astro b/src/components/Toolbar.astro
new file mode 100644
index 0000000..87da098
--- /dev/null
+++ b/src/components/Toolbar.astro
@@ -0,0 +1,5 @@
+---
+import { Toolbar as ReactToolbar } from "./Toolbar.tsx";
+---
+
+
diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx
new file mode 100644
index 0000000..9d53af1
--- /dev/null
+++ b/src/components/Toolbar.tsx
@@ -0,0 +1,178 @@
+import { useState } from "react";
+import {
+ Avatar,
+ Button,
+ ButtonVariant,
+ Divider,
+ Dropdown,
+ DropdownGroup,
+ DropdownList,
+ MenuToggle,
+ type MenuToggleElement,
+ Toolbar as PFToolbar,
+ ToolbarContent,
+ ToolbarGroup,
+ ToolbarItem,
+} from "@patternfly/react-core";
+import BellIcon from "@patternfly/react-icons/dist/esm/icons/bell-icon";
+import CogIcon from "@patternfly/react-icons/dist/esm/icons/cog-icon";
+import QuestionCircleIcon from "@patternfly/react-icons/dist/esm/icons/question-circle-icon";
+import EllipsisVIcon from "@patternfly/react-icons/dist/esm/icons/ellipsis-v-icon";
+
+import imgAvatar from "/avatarImg.svg?url";
+
+import { KebabDropdownItems } from "./KebabDropdownItems";
+import { UserDropdownItems } from "./UserDropdownItems";
+
+export const Toolbar: React.FunctionComponent = () => {
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
+ const [isKebabDropdownOpen, setIsKebabDropdownOpen] = useState(false);
+ const [isFullKebabDropdownOpen, setIsFullKebabDropdownOpen] = useState(false);
+
+ const onDropdownToggle = () => {
+ setIsDropdownOpen(!isDropdownOpen);
+ };
+
+ const onDropdownSelect = () => {
+ setIsDropdownOpen(false);
+ };
+
+ const onKebabDropdownToggle = () => {
+ setIsKebabDropdownOpen(!isKebabDropdownOpen);
+ };
+
+ const onKebabDropdownSelect = () => {
+ setIsKebabDropdownOpen(false);
+ };
+
+ const onFullKebabDropdownToggle = () => {
+ setIsFullKebabDropdownOpen(!isFullKebabDropdownOpen);
+ };
+
+ const onFullKebabDropdownSelect = () => {
+ setIsFullKebabDropdownOpen(false);
+ };
+
+ return (
+
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+ }
+ />
+
+
+
+ setIsKebabDropdownOpen(isOpen)}
+ popperProps={{ position: "right" }}
+ toggle={(toggleRef: React.Ref) => (
+
+
+
+ )}
+ >
+
+
+
+
+
+
+
+ setIsFullKebabDropdownOpen(isOpen)
+ }
+ popperProps={{ position: "right" }}
+ toggle={(toggleRef: React.Ref) => (
+
+
+
+ )}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setIsDropdownOpen(isOpen)}
+ popperProps={{ position: "right" }}
+ toggle={(toggleRef: React.Ref) => (
+
+ }
+ >
+ Ned Username
+
+ )}
+ >
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/UserDropdownItems.astro b/src/components/UserDropdownItems.astro
new file mode 100644
index 0000000..a60ff1d
--- /dev/null
+++ b/src/components/UserDropdownItems.astro
@@ -0,0 +1,9 @@
+---
+import { DropdownItem } from "@patternfly/react-core";
+---
+
+<>
+ My profile
+ User management
+ Logout
+>
diff --git a/src/components/UserDropdownItems.tsx b/src/components/UserDropdownItems.tsx
new file mode 100644
index 0000000..10c82a4
--- /dev/null
+++ b/src/components/UserDropdownItems.tsx
@@ -0,0 +1,9 @@
+import { DropdownItem } from "@patternfly/react-core";
+
+export const UserDropdownItems: React.FunctionComponent = () => (
+ <>
+ My profile
+ User management
+ Logout
+ >
+);
diff --git a/src/content/config.ts b/src/content/config.ts
new file mode 100644
index 0000000..31d2605
--- /dev/null
+++ b/src/content/config.ts
@@ -0,0 +1,10 @@
+import { defineCollection, z } from "astro:content";
+
+const testCollection = defineCollection({
+ type: "content",
+ schema: z.object({ title: z.string() }),
+});
+
+export const collections = {
+ test: testCollection,
+};
diff --git a/src/content/test/test-1.md b/src/content/test/test-1.md
new file mode 100644
index 0000000..935920d
--- /dev/null
+++ b/src/content/test/test-1.md
@@ -0,0 +1,18 @@
+---
+layout: '../../layouts/Main.astro'
+title: 'Test title 1'
+---
+
+# Title 1
+
+## Section
+
+### Subsection
+
+Text content
+
+## Another section
+
+### Another subsection
+
+More text content
\ No newline at end of file
diff --git a/src/content/test/test-2.md b/src/content/test/test-2.md
new file mode 100644
index 0000000..3dc94b5
--- /dev/null
+++ b/src/content/test/test-2.md
@@ -0,0 +1,18 @@
+---
+layout: '../../layouts/Main.astro'
+title: 'Test title 2'
+---
+
+# Title 2
+
+## Section
+
+### Subsection
+
+Text content
+
+## Another section
+
+### Another subsection
+
+More text content
\ No newline at end of file
diff --git a/src/layouts/Main.astro b/src/layouts/Main.astro
new file mode 100644
index 0000000..b97c1b6
--- /dev/null
+++ b/src/layouts/Main.astro
@@ -0,0 +1,13 @@
+---
+import '@patternfly/patternfly/patternfly.css';
+
+import Page from "../components/Page.astro";
+import Masthead from "../components/Masthead.astro";
+import Navigation from "../components/Navigation.astro";
+---
+
+
+
+
+
+
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 646fb35..3dd4372 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,6 +1,6 @@
---
import "../styles/global.scss"
-import Foo from '../components/Foo.astro'
+import MainLayout from "../layouts/Main.astro"
---
@@ -12,6 +12,8 @@ import Foo from '../components/Foo.astro'
Astro
-
+
+ Page content
+