|
diff --git a/packages/lib/src/search-bar/SearchBar.tsx b/packages/lib/src/search-bar/SearchBar.tsx
index fe3b8e058..113c18d1d 100644
--- a/packages/lib/src/search-bar/SearchBar.tsx
+++ b/packages/lib/src/search-bar/SearchBar.tsx
@@ -1,9 +1,9 @@
import styled from "@emotion/styled";
import DxcButton from "../button/Button";
import DxcFlex from "../flex/Flex";
-import { SearchBarProps } from "./types";
+import { RefType, SearchBarProps } from "./types";
import DxcActionIcon from "../action-icon/ActionIcon";
-import { KeyboardEvent, useContext, useRef, useState } from "react";
+import { forwardRef, KeyboardEvent, useContext, useRef, useState } from "react";
import { HalstackLanguageContext } from "../HalstackContext";
import { css } from "@emotion/react";
import DxcIcon from "../icon/Icon";
@@ -59,80 +59,74 @@ const SearchBarInput = styled.input<{ disabled: Required["disabl
cursor: ${({ disabled }) => (disabled ? "not-allowed" : "text")};
`;
-const DxcSearchBar = ({
- autoFocus,
- disabled = false,
- onBlur,
- onCancel,
- onChange,
- onEnter,
- placeholder,
-}: SearchBarProps) => {
- const translatedLabels = useContext(HalstackLanguageContext);
- const inputRef = useRef(null);
- const [innerValue, setInnerValue] = useState("");
+const DxcSearchBar = forwardRef(
+ ({ autoFocus, disabled = false, onBlur, onCancel, onChange, onEnter, placeholder }, ref) => {
+ const translatedLabels = useContext(HalstackLanguageContext);
+ const inputRef = useRef(null);
+ const [innerValue, setInnerValue] = useState("");
- const handleClearActionOnClick = () => {
- setInnerValue("");
- inputRef.current?.focus();
- };
+ const handleClearActionOnClick = () => {
+ setInnerValue("");
+ inputRef.current?.focus();
+ };
- const handleSearchChangeValue = (value: string) => {
- setInnerValue(value);
- if (typeof onChange === "function") {
- onChange(value);
- }
- };
+ const handleSearchChangeValue = (value: string) => {
+ setInnerValue(value);
+ if (typeof onChange === "function") {
+ onChange(value);
+ }
+ };
- const handleInputOnKeyDown = (e: KeyboardEvent) => {
- switch (e.key) {
- case "Esc":
- case "Escape":
- e.preventDefault();
- if (innerValue.length > 0) {
- handleClearActionOnClick();
- }
- break;
- case "Enter":
- if (typeof onEnter === "function") {
- onEnter(innerValue);
- }
- break;
- default:
- break;
- }
- };
+ const handleInputOnKeyDown = (e: KeyboardEvent) => {
+ switch (e.key) {
+ case "Esc":
+ case "Escape":
+ e.preventDefault();
+ if (innerValue.length > 0) {
+ handleClearActionOnClick();
+ }
+ break;
+ case "Enter":
+ if (typeof onEnter === "function") {
+ onEnter(innerValue);
+ }
+ break;
+ default:
+ break;
+ }
+ };
- return (
-
-
-
- typeof onBlur === "function" && onBlur(e.target.value)}
- onChange={(e) => handleSearchChangeValue(e.target.value)}
- onKeyDown={handleInputOnKeyDown}
- disabled={disabled}
- />
- {!disabled && innerValue.length > 0 && (
-
+
+
+ typeof onBlur === "function" && onBlur(e.target.value)}
+ onChange={(e) => handleSearchChangeValue(e.target.value)}
+ onKeyDown={handleInputOnKeyDown}
+ disabled={disabled}
/>
- )}
-
+ {!disabled && innerValue.length > 0 && (
+
+ )}
+
- {typeof onCancel === "function" && (
-
- )}
-
- );
-};
+ {typeof onCancel === "function" && (
+
+ )}
+
+ );
+ }
+);
export default DxcSearchBar;
diff --git a/packages/lib/src/search-bar/types.ts b/packages/lib/src/search-bar/types.ts
index 5d67a8f39..869f84be2 100644
--- a/packages/lib/src/search-bar/types.ts
+++ b/packages/lib/src/search-bar/types.ts
@@ -4,6 +4,7 @@ export type SearchBarTriggerProps = {
*/
onTriggerClick?: () => void;
};
+export type RefType = HTMLDivElement;
export type SearchBarProps = {
/**
* If true, the search bar input will be focused when rendered.
diff --git a/packages/lib/src/sidenav/Sidenav.accessibility.test.tsx b/packages/lib/src/sidenav/Sidenav.accessibility.test.tsx
index 157d20d52..7b2ac21e1 100644
--- a/packages/lib/src/sidenav/Sidenav.accessibility.test.tsx
+++ b/packages/lib/src/sidenav/Sidenav.accessibility.test.tsx
@@ -47,7 +47,9 @@ describe("Sidenav component accessibility tests", () => {
],
},
];
- const { container } = render();
+ const { container } = render(
+
+ );
const results = await axe(container);
expect(results.violations).toHaveLength(0);
});
diff --git a/packages/lib/src/sidenav/Sidenav.stories.tsx b/packages/lib/src/sidenav/Sidenav.stories.tsx
index 8c47a0845..e0a68423d 100644
--- a/packages/lib/src/sidenav/Sidenav.stories.tsx
+++ b/packages/lib/src/sidenav/Sidenav.stories.tsx
@@ -140,6 +140,7 @@ const Sidenav = () => (
@@ -169,6 +170,7 @@ const Sidenav = () => (
@@ -208,6 +210,7 @@ const Collapsed = () => {
@@ -261,6 +264,7 @@ const Collapsed = () => {
@@ -314,6 +318,7 @@ const Collapsed = () => {
@@ -373,6 +378,7 @@ const Hovered = () => (
@@ -405,6 +411,7 @@ const SelectedGroup = () => (
diff --git a/packages/lib/src/sidenav/Sidenav.test.tsx b/packages/lib/src/sidenav/Sidenav.test.tsx
index eb0edfc1c..cbbc8c717 100644
--- a/packages/lib/src/sidenav/Sidenav.test.tsx
+++ b/packages/lib/src/sidenav/Sidenav.test.tsx
@@ -1,5 +1,5 @@
import "@testing-library/jest-dom";
-import { render, fireEvent } from "@testing-library/react";
+import { render, fireEvent, waitFor } from "@testing-library/react";
import DxcSidenav from "./Sidenav";
import { ReactNode } from "react";
@@ -103,4 +103,21 @@ describe("DxcSidenav component", () => {
const collapseButton = getByRole("button", { name: "Collapse" });
expect(collapseButton).toBeTruthy();
});
+
+ test("Sidenav renders search bar when searchBar prop is provided", () => {
+ const { getByPlaceholderText } = render();
+ expect(getByPlaceholderText("Search...")).toBeTruthy();
+ });
+
+ test("Sidenav expands and focuses search input when handleExpandSearch is called", async () => {
+ const { getByRole, getByPlaceholderText } = render(
+
+ );
+ const expandButton = getByRole("button", { name: "Search" });
+ fireEvent.click(expandButton);
+ const searchInput = getByPlaceholderText("Search...") as HTMLInputElement;
+ await waitFor(() => {
+ expect(document.activeElement).toBe(searchInput);
+ });
+ });
});
diff --git a/packages/lib/src/sidenav/Sidenav.tsx b/packages/lib/src/sidenav/Sidenav.tsx
index 8d2292058..8f7c885c2 100644
--- a/packages/lib/src/sidenav/Sidenav.tsx
+++ b/packages/lib/src/sidenav/Sidenav.tsx
@@ -5,10 +5,12 @@ import SidenavPropsType from "./types";
import DxcDivider from "../divider/Divider";
import DxcButton from "../button/Button";
import DxcImage from "../image/Image";
-import { useContext, useState } from "react";
+import { useContext, useRef, useState } from "react";
import DxcNavigationTree from "../navigation-tree/NavigationTree";
import DxcInset from "../inset/Inset";
import ApplicationLayoutContext from "../layout/ApplicationLayoutContext";
+import DxcSearchBar from "../search-bar/SearchBar";
+import DxcSearchBarTrigger from "../search-bar/SearchBarTrigger";
const SidenavContainer = styled.div<{ expanded: boolean }>`
box-sizing: border-box;
@@ -67,11 +69,13 @@ const DxcSidenav = ({
expanded,
defaultExpanded = true,
onExpandedChange,
+ searchBar,
}: SidenavPropsType): JSX.Element => {
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
const { logo, headerExists } = useContext(ApplicationLayoutContext);
const isControlled = expanded !== undefined;
const isExpanded = isControlled ? !!expanded : internalExpanded;
+ const searchBarRef = useRef(null);
const handleToggle = () => {
const nextState = !isExpanded;
@@ -79,6 +83,13 @@ const DxcSidenav = ({
onExpandedChange?.(nextState);
};
+ const handleExpandSearch = () => {
+ handleToggle();
+ setTimeout(() => {
+ searchBarRef.current?.querySelector("input")?.focus();
+ }, 1);
+ };
+
return (
{appTitle}
- {topContent && (
+ {(topContent || searchBar) && (
+ {searchBar &&
+ (isExpanded ? (
+
+ ) : (
+
+ ))}
{topContent}
)}
diff --git a/packages/lib/src/sidenav/types.ts b/packages/lib/src/sidenav/types.ts
index 378fd196f..5a315b75a 100644
--- a/packages/lib/src/sidenav/types.ts
+++ b/packages/lib/src/sidenav/types.ts
@@ -1,5 +1,6 @@
import { ReactElement, ReactNode } from "react";
import { SVG } from "../common/utils";
+import { SearchBarProps } from "../search-bar/types";
type Section = { items: (Item | GroupItem)[]; title?: string };
@@ -34,6 +35,10 @@ type Props = {
* Function called when the expansion state of the sidenav changes.
*/
onExpandedChange?: (value: boolean) => void;
+ /**
+ * When provided, a search bar will be rendered at the top of the sidenav.
+ */
+ searchBar?: Omit;
/**
* The additional content rendered in the upper part of the sidenav, under the branding.
*/
|