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 @@ + + + PF-HorizontalLogo-Color + + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + + PF-HorizontalLogo-Reverse + + + + + + + + + + + + + + + + + + + + + + + + + \ 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 ( + + + + +