diff --git a/apps/website/pages/_app.tsx b/apps/website/pages/_app.tsx index 2603ad349..ed382f285 100644 --- a/apps/website/pages/_app.tsx +++ b/apps/website/pages/_app.tsx @@ -2,7 +2,7 @@ import { ReactElement, ReactNode, useMemo, useState } from "react"; import type { NextPage } from "next"; import type { AppProps } from "next/app"; import Head from "next/head"; -import { DxcApplicationLayout, DxcTextInput, DxcToastsQueue } from "@dxc-technology/halstack-react"; +import { DxcApplicationLayout, DxcToastsQueue } from "@dxc-technology/halstack-react"; import MainContent from "@/common/MainContent"; import { useRouter } from "next/router"; import { LinkDetails, LinksSectionDetails, LinksSections } from "@/common/pagesList"; @@ -108,19 +108,7 @@ export default function App({ Component, pageProps, emotionCache = clientSideEmo } - topContent={ - isExpanded && ( - { - setFilter(value); - }} - size="fillParent" - clearable - /> - ) - } + searchBar={{ placeholder: "Search docs", onChange: (value) => setFilter(value) }} expanded={isExpanded} onExpandedChange={() => { setIsExpanded((currentlyExpanded) => !currentlyExpanded); diff --git a/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx b/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx index d0bd0bc47..b21fb5400 100644 --- a/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx +++ b/apps/website/screens/components/sidenav/code/SidenavCodePage.tsx @@ -24,6 +24,15 @@ const sectionTypeString = `{ title?: string }; }`; +const searchBarTypeString = `{ + autoFocus?: boolean; + disabled?: boolean; + onBlur: (value: string) => void; + onChange: (value: string) => void; + onEnter: (value: string) => void; + placeholder?: string; +}`; + const sections = [ { title: "Props", @@ -147,6 +156,19 @@ const sections = [ Function called when the expansion state of the sidenav changes. - + + + + + searchBar + + + + {searchBarTypeString} + + When provided, a search bar will be rendered at the top of the sidenav. + - + 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..7481e199e 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,16 +69,26 @@ 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 shouldFocusSearchBar = useRef(false); const handleToggle = () => { const nextState = !isExpanded; if (!isControlled) setInternalExpanded(nextState); onExpandedChange?.(nextState); + if (searchBar && nextState === false) { + shouldFocusSearchBar.current = false; + } + }; + + const handleExpandSearch = () => { + shouldFocusSearchBar.current = true; + handleToggle(); }; return ( @@ -114,8 +126,14 @@ const DxcSidenav = ({ {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. */