diff --git a/apps/website/screens/components/header/code/HeaderCodePage.tsx b/apps/website/screens/components/header/code/HeaderCodePage.tsx
index 6f45766ae..054d0a8c0 100644
--- a/apps/website/screens/components/header/code/HeaderCodePage.tsx
+++ b/apps/website/screens/components/header/code/HeaderCodePage.tsx
@@ -21,6 +21,16 @@ const groupItemTypeString = `{
items: (Item)[];
}`;
+const searchBarTypeString = `{
+ autoFocus?: boolean;
+ disabled?: boolean;
+ onBlur: (value: string) => void;
+ onCancel: () => void;
+ onChange: (value: string) => void;
+ onEnter: (value: string) => void;
+ placeholder?: string;
+}`;
+
const sections = [
{
title: "Props",
@@ -91,6 +101,26 @@ const sections = [
- |
+
+ |
+
+
+ searchBar
+
+ |
+
+ {searchBarTypeString}
+ |
+
+ When provided, a search bar trigger is shown at the start of the side content. Activating the trigger
+ expands the search bar, enabling search interactions.
+
+ In responsive mode, the search bar is displayed directly (without a trigger), and the{" "}
+ onCancel callback is not called.
+
+ |
+ - |
+
|
@@ -105,6 +135,7 @@ const sections = [
Content to be displayed on the right side of the header. It can be a ReactNode or a function that receives
a boolean indicating if the header is in responsive mode and returns a ReactNode.
|
+ - |
diff --git a/packages/lib/src/header/Header.accessibility.test.tsx b/packages/lib/src/header/Header.accessibility.test.tsx
index e337488f2..e4ccced8b 100644
--- a/packages/lib/src/header/Header.accessibility.test.tsx
+++ b/packages/lib/src/header/Header.accessibility.test.tsx
@@ -59,6 +59,7 @@ describe("Header component accessibility tests", () => {
{}} />
}
diff --git a/packages/lib/src/header/Header.stories.tsx b/packages/lib/src/header/Header.stories.tsx
index 35d43c5a4..4eeed6ddb 100644
--- a/packages/lib/src/header/Header.stories.tsx
+++ b/packages/lib/src/header/Header.stories.tsx
@@ -114,6 +114,8 @@ const Header = () => (
Side Content} />
Side Content} />
+
+ Side Content} searchBar={{ placeholder: "Search..." }} />
{longSideContent}} />
@@ -131,6 +133,7 @@ const HeaderInLayout = () => (
isResponsive ? (
<>
diff --git a/packages/lib/src/header/Header.test.tsx b/packages/lib/src/header/Header.test.tsx
index a1546a6d4..0b4367cbf 100644
--- a/packages/lib/src/header/Header.test.tsx
+++ b/packages/lib/src/header/Header.test.tsx
@@ -59,4 +59,40 @@ describe("Header component tests", () => {
expect(screen.queryByText("Frontend")).not.toBeInTheDocument();
expect(screen.queryByText("Backend")).not.toBeInTheDocument();
});
+
+ test("search bar appears and functions correctly", () => {
+ const onEnterMock = jest.fn();
+ const onCancelMock = jest.fn();
+
+ render();
+
+ const searchIcon = screen.getByRole("button", { name: /search/i });
+ fireEvent.click(searchIcon);
+ const searchInput = screen.getByPlaceholderText("Search...");
+ expect(searchInput).toBeInTheDocument();
+ fireEvent.change(searchInput, { target: { value: "test query" } });
+ fireEvent.keyDown(searchInput, { key: "Enter", code: "Enter" });
+ expect(onEnterMock).toHaveBeenCalledWith("test query");
+ const cancelButton = screen.getByRole("button", { name: /cancel/i });
+ fireEvent.click(cancelButton);
+ expect(onCancelMock).toHaveBeenCalled();
+ expect(searchInput).not.toBeInTheDocument();
+ });
+
+ test("search bar appears correctly in responsive mode", () => {
+ mockMatchMedia.mockImplementation(() => ({
+ matches: true,
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ }));
+
+ render();
+
+ const menuButton = screen.getByRole("button", { name: /toggle menu/i });
+ fireEvent.click(menuButton);
+ const searchInput = screen.getByPlaceholderText("Search...");
+ expect(searchInput).toBeInTheDocument();
+ const cancelButton = screen.queryByRole("button", { name: /cancel/i });
+ expect(cancelButton).not.toBeInTheDocument();
+ });
});
diff --git a/packages/lib/src/header/Header.tsx b/packages/lib/src/header/Header.tsx
index d175649c1..7fb78d348 100644
--- a/packages/lib/src/header/Header.tsx
+++ b/packages/lib/src/header/Header.tsx
@@ -12,6 +12,8 @@ import { responsiveSizes } from "../common/variables";
import DxcButton from "../button/Button";
import scrollbarStyles from "../styles/scroll";
import ApplicationLayoutContext from "../layout/ApplicationLayoutContext";
+import DxcSearchBarTrigger from "../search-bar/SearchBarTrigger";
+import DxcSearchBar from "../search-bar/SearchBar";
const MAX_MAIN_NAV_SIZE = "60%";
const LEVEL_LIMIT = 1;
@@ -132,9 +134,16 @@ const sanitizeNavItems = (navItems: HeaderProps["navItems"], level?: number): (G
return sanitizedItems;
};
-const DxcHeader = ({ appTitle, navItems, sideContent, responsiveBottomContent }: HeaderProps): JSX.Element => {
+const DxcHeader = ({
+ appTitle,
+ navItems,
+ responsiveBottomContent,
+ searchBar,
+ sideContent,
+}: HeaderProps): JSX.Element => {
const [isResponsive, setIsResponsive] = useState(false);
const [isMenuVisible, setIsMenuVisible] = useState(false);
+ const [showSearch, setShowSearch] = useState(false);
const logo = useContext(ApplicationLayoutContext).logo || undefined;
useEffect(() => {
@@ -157,58 +166,86 @@ const DxcHeader = ({ appTitle, navItems, sideContent, responsiveBottomContent }:
};
const sanitizedNavItems = useMemo(() => (navItems ? sanitizeNavItems(navItems) : []), [navItems]);
+ const handleCancelSearch = () => {
+ if (typeof searchBar?.onCancel === "function") {
+ searchBar.onCancel();
+ }
+ setShowSearch(false);
+ };
+
return (
0
- ? [`auto`, `minmax(auto, ${MAX_MAIN_NAV_SIZE})`, `auto`]
- : ["auto", "auto"]
+ showSearch && !isResponsive
+ ? ["auto"]
+ : !isResponsive && sanitizedNavItems && sanitizedNavItems.length > 0
+ ? [`auto`, `minmax(auto, ${MAX_MAIN_NAV_SIZE})`, `auto`]
+ : ["auto", "auto"]
}
templateRows={["var(--height-xxxl)"]}
gap="var(--spacing-gap-ml)"
placeItems="center"
>
-
- {logo && (
-
- {typeof logo.src === "string" ? (
-
- ) : (
- logo.src
- )}
-
- )}
- {appTitle && !isResponsive && (
- <>
- {logo && }
-
- >
- )}
-
- {!isResponsive && sanitizedNavItems && sanitizedNavItems.length > 0 && (
+ {(!showSearch || isResponsive) && (
+
+ {logo && (
+
+ {typeof logo.src === "string" ? (
+
+ ) : (
+ logo.src
+ )}
+
+ )}
+ {appTitle && !isResponsive && (
+ <>
+ {logo && }
+
+ >
+ )}
+
+ )}
+
+ {!isResponsive && ((sanitizedNavItems && sanitizedNavItems.length > 0) || (!!searchBar && showSearch)) && (
-
+ {!!searchBar && showSearch ? (
+
+ ) : (
+
+ )}
)}
- {(sideContent || isResponsive) && (
+
+ {(!showSearch || isResponsive) && (sideContent || isResponsive || !!searchBar) && (
+ {!!searchBar && !isResponsive && (
+ setShowSearch(!showSearch)} />
+ )}
{typeof sideContent === "function" ? sideContent(isResponsive) : sideContent}
- {isResponsive && ((navItems && navItems.length) || responsiveBottomContent) && (
+ {isResponsive && ((navItems && navItems.length) || responsiveBottomContent || !!searchBar) && (
)}
@@ -219,6 +256,16 @@ const DxcHeader = ({ appTitle, navItems, sideContent, responsiveBottomContent }:
{appTitle && }
+ {!!searchBar && (
+
+ )}
ReactNode);
};