diff --git a/.vscode/launch.json b/.vscode/launch.json
index d642209..482f620 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -6,6 +6,28 @@
"name": "Development server",
"request": "launch",
"type": "node-terminal"
+ },
+ {
+ "type": "node",
+ "request": "launch",
+ "name": "Test Spec File",
+ "program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
+ "args": [
+ "--collectCoverage=false",
+ "--colors",
+ "--config",
+ "${workspaceRoot}/jest.config.ts",
+ "--runInBand"
+ ],
+ "internalConsoleOptions": "neverOpen",
+ "sourceMaps": true,
+ "skipFiles": [
+ "${workspaceRoot}/../../node_modules/**/*"
+ ],
+ "windows": {
+ "skipFiles": ["C:\\\\**\\\\node_modules\\\\**\\\\*"]
+ },
+ "stopOnEntry": true
}
]
}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 43cf5a7..23c8107 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -48,7 +48,7 @@ export default [
'@typescript-eslint/array-type': 'error',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/consistent-type-assertions': 'error',
- '@typescript-eslint/consistent-type-definitions': 'error',
+ '@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/explicit-member-accessibility': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/indent': 'off',
diff --git a/src/components/Breadcrumbs.astro b/src/components/Breadcrumbs.astro
deleted file mode 100644
index 6255e8a..0000000
--- a/src/components/Breadcrumbs.astro
+++ /dev/null
@@ -1,10 +0,0 @@
----
-import { Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'
----
-
-
- Section home
- Section title
- Section title
- Section landing
-
diff --git a/src/components/KebabDropdownItems.astro b/src/components/KebabDropdownItems.astro
deleted file mode 100644
index 3490302..0000000
--- a/src/components/KebabDropdownItems.astro
+++ /dev/null
@@ -1,14 +0,0 @@
----
-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
deleted file mode 100644
index 18fd359..0000000
--- a/src/components/KebabDropdownItems.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import React from 'react'
-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/Toolbar.astro b/src/components/Toolbar.astro
index 0a90b78..196b27f 100644
--- a/src/components/Toolbar.astro
+++ b/src/components/Toolbar.astro
@@ -2,4 +2,4 @@
import { Toolbar as ReactToolbar } from './Toolbar.tsx'
---
-
+
\ No newline at end of file
diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx
index df6da83..20de500 100644
--- a/src/components/Toolbar.tsx
+++ b/src/components/Toolbar.tsx
@@ -1,60 +1,18 @@
import * as React from 'react'
-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 { ToggleThemeSwitcher } from './toolbar/ToogleThemeSwitcher'
+import { SearchComponent } from './toolbar/SearchComponent'
+import { DocumentReleaseDropdown } from './toolbar/DocumentReleaseDropdown';
+import GithubIcon from '@patternfly/react-icons/dist/esm/icons/github-icon';
-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 (
+export const Toolbar: React.FunctionComponent = () => (
{
gap={{ default: 'gapNone', md: 'gapMd' }}
>
- }
- />
+
-
-
- }
- />
-
-
- }
- />
-
-
-
- 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
deleted file mode 100644
index e474214..0000000
--- a/src/components/UserDropdownItems.astro
+++ /dev/null
@@ -1,9 +0,0 @@
----
-import { DropdownItem } from '@patternfly/react-core'
----
-
-<>
- My profile
- User management
- Logout
->
diff --git a/src/components/UserDropdownItems.tsx b/src/components/UserDropdownItems.tsx
deleted file mode 100644
index 1c779a7..0000000
--- a/src/components/UserDropdownItems.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import React from 'react'
-import { DropdownItem } from '@patternfly/react-core'
-
-export const UserDropdownItems: React.FunctionComponent = () => (
- <>
- My profile
- User management
- Logout
- >
-)
diff --git a/src/components/toolbar/DocumentReleaseDropdown.tsx b/src/components/toolbar/DocumentReleaseDropdown.tsx
new file mode 100644
index 0000000..55eeaf7
--- /dev/null
+++ b/src/components/toolbar/DocumentReleaseDropdown.tsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import {
+ Dropdown,
+ DropdownList,
+ MenuToggle,
+ DropdownGroup,
+ DropdownItem,
+ Divider,
+} from '@patternfly/react-core'
+import { Release } from '../../types'
+import versions from '../../versions.json'
+
+export const DocumentReleaseDropdown: React.FunctionComponent = () => {
+ const latestRelease = versions.Releases.find(
+ (release) => release.latest,
+ ) as Release
+ const previousReleases = Object.values(versions.Releases).filter(
+ (release) => !release.hidden && !release.latest,
+ ) as Release []
+
+ const previousVersions = Object.values(versions.ArchivedReleases) as Release[];
+
+ const [isDropdownOpen, setDropdownOpen] = React.useState(false)
+
+ const getDropdownItem = (version: Release, isLatest = false) => (
+
+ {`Release ${version.name}`}
+
+ )
+ return (
+ setDropdownOpen(!isDropdownOpen)}
+ onOpenChange={(isOpen) => setDropdownOpen(isOpen)}
+ isOpen={isDropdownOpen}
+ toggle={(toggleRef) => (
+ setDropdownOpen(!isDropdownOpen)}
+ isExpanded={isDropdownOpen}
+ >
+ {`Release ${latestRelease.name}`}
+
+ )}
+ popperProps={{ position: 'right' }}
+ >
+
+ {getDropdownItem(latestRelease, true)}
+
+ {previousReleases.length > 0 && (
+
+
+ {previousReleases
+ .slice(0, 3)
+ .map((version) => getDropdownItem(version))}
+
+
+ )}
+ {previousVersions.length > 0 && (
+ <>
+
+ {previousVersions.map((version) => getDropdownItem(version))}
+
+ >)}
+
+ )
+}
diff --git a/src/components/toolbar/SearchComponent.tsx b/src/components/toolbar/SearchComponent.tsx
new file mode 100644
index 0000000..f61c3e8
--- /dev/null
+++ b/src/components/toolbar/SearchComponent.tsx
@@ -0,0 +1,38 @@
+import React from 'react';
+import { SearchInput } from '@patternfly/react-core';
+
+interface SearchComponentProps {
+ /* Indicates if search compoonent should be visible or not. */
+ searchEnabled?: boolean
+}
+
+export const SearchComponent : React.FunctionComponent = ({searchEnabled = true}) => {
+ const [searchValue, setSearchValue] = React.useState('');
+ const [isSearchExpanded, setIsSearchExpanded] = React.useState(false);
+
+ //TODO: Add search for algolia or alternative search to this component.
+
+ const onChange = (_evt: any, value: any) => {
+ setSearchValue(value);
+ };
+
+ const onToggleExpand = (_evt: any, isSearchExpanded: boolean) => {
+ setIsSearchExpanded(!isSearchExpanded);
+ };
+
+
+ return (
+ searchEnabled && onChange(_evt, '')}
+ expandableInput={{
+ isExpanded: isSearchExpanded,
+ onToggleExpand,
+ toggleAriaLabel: 'Expandable search input toggle'
+ }}
+ />
+ )
+}
\ No newline at end of file
diff --git a/src/components/toolbar/ToogleThemeSwitcher.tsx b/src/components/toolbar/ToogleThemeSwitcher.tsx
new file mode 100644
index 0000000..532a31e
--- /dev/null
+++ b/src/components/toolbar/ToogleThemeSwitcher.tsx
@@ -0,0 +1,51 @@
+import React from 'react'
+import { Icon, ToggleGroup, ToggleGroupItem } from '@patternfly/react-core'
+import MoonIcon from '@patternfly/react-icons/dist/esm/icons/moon-icon'
+import SunIcon from '@patternfly/react-icons/dist/esm/icons/sun-icon'
+import { getThemePreference, updateThemePreference } from '../../utils/theme'
+
+export const ToggleThemeSwitcher: React.FunctionComponent = () => {
+ const [isDarkTheme, setIsDarkTheme] = React.useState(false);
+
+ React.useEffect(() => {
+ // const darkTheme = window?.localStorage?.getItem('darkMode') === 'true' ? true : false;
+ // const html = document.querySelector('html') as HTMLHtmlElement
+ // html.classList.toggle('pf-v6-theme-dark', darkTheme)
+ const darkTheme = getThemePreference() === 'dark';
+ setIsDarkTheme(darkTheme);
+ }, []);
+
+ const toggleDarkTheme = (_evt: unknown, selected: boolean) => {
+ const darkThemeToggleClicked = !selected === isDarkTheme
+ updateThemePreference(darkThemeToggleClicked ? 'dark' : 'light');
+ // const html = document.querySelector('html') as HTMLHtmlElement
+ // html.classList.toggle('pf-v6-theme-dark', darkThemeToggleClicked)
+ setIsDarkTheme(darkThemeToggleClicked);
+ //localStorage.setItem('darkMode', JSON.stringify(darkThemeToggleClicked));
+ }
+
+ return (
+
+
+
+
+ }
+ isSelected={!isDarkTheme}
+ onChange={toggleDarkTheme}
+ />
+
+
+
+ }
+ isSelected={isDarkTheme}
+ onChange={toggleDarkTheme}
+ />
+
+ )
+}
diff --git a/src/components/toolbar/__tests__/DocumentReleaseDropdown.test.tsx b/src/components/toolbar/__tests__/DocumentReleaseDropdown.test.tsx
new file mode 100644
index 0000000..c9e163d
--- /dev/null
+++ b/src/components/toolbar/__tests__/DocumentReleaseDropdown.test.tsx
@@ -0,0 +1,81 @@
+// DocumentReleaseDropdown.test.tsx
+
+import React from 'react';
+import { render, screen, fireEvent } from '@testing-library/react';
+import { DocumentReleaseDropdown } from '../DocumentReleaseDropdown';
+
+jest.mock('../../../versions.json', () => ({
+ Releases: [
+ { name: '1.0.0', latest: true, hidden: false, href: '' },
+ { name: '1.1.0', latest: false, hidden: false, href: '/1.1.0' },
+ { name: '1.2.0', latest: false, hidden: false, href: '/1.2.0' },
+ { name: '1.2.0', latest: false, hidden: true, href: '/1.2.1' },
+ ],
+ ArchivedReleases: [
+ { name: '0.9.0', latest: false, hidden: false, href: '' },
+ { name: '0.8.0', latest: false, hidden: false, href: '' },
+ ]
+}));
+
+describe('DocumentReleaseDropdown', () => {
+
+ it('renders the dropdown with the latest release', () => {
+ render();
+
+ // Check if the latest release is rendered
+ expect(screen.getByText('Release 1.0.0')).toBeInTheDocument();
+ });
+
+ it('opens and closes the dropdown when clicked', () => {
+ render();
+
+ const toggleButton = screen.getByText('Release 1.0.0');
+
+ expect(screen.queryByText('Release 1.1.0')).not.toBeInTheDocument();
+
+ fireEvent.click(toggleButton);
+ expect(screen.getByText('Release 1.1.0')).toBeInTheDocument();
+
+ fireEvent.click(toggleButton);
+ expect(screen.queryByText('Release 1.1.0')).not.toBeInTheDocument();
+ });
+
+ it('displays the previous releases', () => {
+ render();
+
+ fireEvent.click(screen.getByText('Release 1.0.0'));
+
+ expect(screen.getByText('Release 1.1.0')).toBeInTheDocument();
+ expect(screen.getByText('Release 1.2.0')).toBeInTheDocument();
+ });
+
+ it('displays the archived versions', () => {
+ render();
+
+ fireEvent.click(screen.getByText('Release 1.0.0'));
+
+ expect(screen.getByText('Release 0.9.0')).toBeInTheDocument();
+ expect(screen.getByText('Release 0.8.0')).toBeInTheDocument();
+ });
+
+ it('has the correct links for each release', () => {
+ render();
+
+ fireEvent.click(screen.getByText('Release 1.0.0'));
+
+ const release2Link = screen.getByText('Release 1.1.0').closest('a');
+ expect(release2Link).toHaveAttribute('href', '/1.1.0');
+
+ const archivedLink = screen.getByText('Release 0.9.0').closest('a');
+ expect(archivedLink).toHaveAttribute('href', '/0.9.0');
+ });
+
+ it('does not display hidden releases', () => {
+
+ render();
+
+ fireEvent.click(screen.getByText('Release 1.0.0'));
+
+ expect(screen.queryByText('Release 1.2.1')).not.toBeInTheDocument();
+ });
+});
diff --git a/src/components/toolbar/__tests__/SearchComponent.test.tsx b/src/components/toolbar/__tests__/SearchComponent.test.tsx
new file mode 100644
index 0000000..c58eb5e
--- /dev/null
+++ b/src/components/toolbar/__tests__/SearchComponent.test.tsx
@@ -0,0 +1,64 @@
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
+import { SearchComponent } from '../SearchComponent'
+import '@testing-library/jest-dom'
+
+describe('SearchComponent', () => {
+ it('renders when searchEnabled is true', () => {
+ render()
+
+ waitFor(() => {
+ const searchInput = screen.getByLabelText('Search input')
+ expect(searchInput).toBeInTheDocument()
+ })
+ })
+
+ it('does not render when searchEnabled is false', () => {
+ render()
+
+ const searchInput = screen.queryByPlaceholderText('Search')
+ expect(searchInput).not.toBeInTheDocument()
+ })
+
+ it('updates the search value when user types', () => {
+ render()
+ waitFor(() => {
+ const searchInput = screen.getByPlaceholderText('Search')
+ fireEvent.change(searchInput, { target: { value: 'test' } })
+
+ expect(searchInput).toHaveValue('test')
+ })
+ })
+
+ it('clears the search value when clear button is clicked', () => {
+ render()
+ waitFor(() => {
+ const searchInput = screen.getByPlaceholderText('Search')
+ fireEvent.change(searchInput, { target: { value: 'test' } })
+ fireEvent.click(screen.getByRole('button', { name: /clear/i }))
+
+ expect(searchInput).toHaveValue('')
+ })
+ })
+
+ it('toggles search expansion', () => {
+ render()
+
+ waitFor(() => {
+ const searchInput = screen.getByPlaceholderText('Search')
+ const toggleButton = screen.getByLabelText(
+ 'Expandable search input toggle',
+ )
+
+ // Test initial state (collapsed)
+ expect(searchInput).toHaveClass('pf-m-closed')
+
+ // Simulate toggle to expand
+ fireEvent.click(toggleButton)
+ expect(searchInput).toHaveClass('pf-m-expanded')
+
+ // Simulate toggle to collapse
+ fireEvent.click(toggleButton)
+ expect(searchInput).toHaveClass('pf-m-closed')
+ })
+ })
+})
diff --git a/src/layouts/Main.astro b/src/layouts/Main.astro
index b94d2b4..e7ea14f 100644
--- a/src/layouts/Main.astro
+++ b/src/layouts/Main.astro
@@ -5,6 +5,7 @@ import { ClientRouter } from 'astro:transitions'
import Page from '../components/Page.astro'
import Masthead from '../components/Masthead.astro'
import Navigation from '../components/Navigation.astro'
+
---
@@ -24,3 +25,13 @@ import Navigation from '../components/Navigation.astro'