From 0cfafc21d35f9092c85c07cd8c0b16f9c76e452a Mon Sep 17 00:00:00 2001 From: Shelvin Date: Tue, 31 Oct 2023 10:56:35 -0700 Subject: [PATCH] `SearchNav` feat: add new CTA and text (#742) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 📝 Changes - adds `PrimaryCTAItem` - original `CTAItem` renamed to `SecondaryCTAItem` - adds `EmphasizedText` to handle rendering of text in `LogoGroup` ## ✅ Checklist - [x] Visuals are complete and match Figma - [x] Code is complete and in accordance with our style guide - [x] Design and theme tokens are audited for any relevant changes - [x] Unit tests are written and passing - [x] TSDoc is written or updated for any component API surface area - [x] Stories in Storybook accompany any relevant component changes - [x] Ensure no accessibility violations are reported in Storybook - [x] Specs and documentation are up-to-date - [x] Cross-browser check is performed (Chrome, Safari, Firefox) - [x] Changeset is added --- .changeset/nasty-cups-trade.md | 5 + documentation/specs/SearchNav.md | 31 ++-- easy-ui-react/src/Menu/MenuSection.tsx | 2 +- easy-ui-react/src/SearchNav/CTAGroup.tsx | 44 +++-- .../src/SearchNav/CondensedSearchNav.tsx | 47 ++--- easy-ui-react/src/SearchNav/LogoGroup.tsx | 35 ++-- .../src/SearchNav/PrimaryCTAItem.tsx | 24 +++ easy-ui-react/src/SearchNav/SearchNav.mdx | 39 +++-- .../src/SearchNav/SearchNav.stories.tsx | 97 +++++++++-- .../src/SearchNav/SearchNav.test.tsx | 80 ++++++++- easy-ui-react/src/SearchNav/SearchNav.tsx | 163 +++++++++--------- ...dule.scss => SecondaryCTAItem.module.scss} | 0 .../{CTAItem.tsx => SecondaryCTAItem.tsx} | 14 +- easy-ui-react/src/SearchNav/Title.tsx | 21 +++ easy-ui-react/src/SearchNav/context.ts | 13 +- easy-ui-react/src/SearchNav/utilities.ts | 100 +++++++++++ easy-ui-react/src/Text/Text.module.scss | 16 ++ easy-ui-react/src/Text/Text.test.tsx | 8 + easy-ui-react/src/Text/Text.tsx | 11 +- 19 files changed, 569 insertions(+), 181 deletions(-) create mode 100644 .changeset/nasty-cups-trade.md create mode 100644 easy-ui-react/src/SearchNav/PrimaryCTAItem.tsx rename easy-ui-react/src/SearchNav/{CTAItem.module.scss => SecondaryCTAItem.module.scss} (100%) rename easy-ui-react/src/SearchNav/{CTAItem.tsx => SecondaryCTAItem.tsx} (72%) create mode 100644 easy-ui-react/src/SearchNav/Title.tsx create mode 100644 easy-ui-react/src/SearchNav/utilities.ts diff --git a/.changeset/nasty-cups-trade.md b/.changeset/nasty-cups-trade.md new file mode 100644 index 000000000..d11dcb067 --- /dev/null +++ b/.changeset/nasty-cups-trade.md @@ -0,0 +1,5 @@ +--- +"@easypost/easy-ui": minor +--- + +feat(SearchNav): support PrimaryCTAItem and Title components diff --git a/documentation/specs/SearchNav.md b/documentation/specs/SearchNav.md index 3c9f16a31..86e25926e 100644 --- a/documentation/specs/SearchNav.md +++ b/documentation/specs/SearchNav.md @@ -19,9 +19,9 @@ A `SearchNav` is a navigation bar focused on handling dense information interact `SearchNav` will be made up of sub-component containers. At the top level, the `SearchNav` serves as the container for the logo, dropdown, search input, and CTAs. The logo and dropdown will be grouped into a `SearchNav.LogoGroup` container. The search input will be wrapped by a `SearchNav.Search` container. The CTAs will be wrapped by a `SearchNav.CTAGroup` container. -`SearchNav.LogoGroup` will be comprised of `SearchNav.Logo`, a minimal wrapper for the consumer provided logo, and `SearchNav.Selector`. `SearchNav.Selector` will be built using React Aria's `useSelect`, `useListBox`, `usePopover`, `useOption` and `HiddenSelect`. To help manage state, it will also rely on React Stately's `useSelectState`. +`SearchNav.LogoGroup` will be comprised of `SearchNav.Logo`, a minimal wrapper for the consumer provided logo, `SearchNav.Title`, and `SearchNav.Selector`. `SearchNav.Selector` will be built using React Aria's `useSelect`, `useListBox`, `usePopover`, `useOption` and `HiddenSelect`. To help manage state, it will also rely on React Stately's `useSelectState`. -`SearchNav.CTAGroup` will render individual CTAs via `SearchNav.CTAItem`, which will make use of Easy UI's `UnstyledButton` component. +`SearchNav.CTAGroup` will render a primary CTA, `SearchNav.PrimaryCTAItem`, and a secondary CTA, `SearchNav.SecondaryCTAItem`; both will make use of Easy UI's `UnstyledButton` component. `SearchNav` will also need to handle a unique configuration for smaller devices. Although it won't be exposed to consumers directly,this will be accomplished via a `SearchNavMobile` component, which will be responsible for rendering a clickable hamburger and search icon. The hamburger icon will effectively be a trigger to render a menu comprised of `SearchNav.Selector` and the CTAs in `SearchNav.CTAGroup`. The clickable search icon will render the contents of `SearchNav.Search` and a right aligned close button. @@ -78,22 +78,29 @@ export type SelectorProps = AriaSelectProps & export type CTAGroupProps = { /** - * The children of the element. Should include elements. + * The children of the element. Should include + * elements and */ children: ReactNode; }; export type CTAItemProps = AriaButtonProps<"button"> & { - /** - * Icon symbol SVG source from @easypost/easy-ui-icons. - */ - symbol?: IconSymbol; /** * Text content to display. */ label: string; +}; + +export type PrimaryCTAItemProps = CTAItemProps; + +export type SecondaryCTAItemProps = CTAItemProps & { + /** + * Icon symbol SVG source from @easypost/easy-ui-icons. + */ + symbol?: IconSymbol; /** * Hides label on desktop. + * @default false */ hideLabelOnDesktop?: boolean; /** @@ -145,9 +152,13 @@ function App() { - - - + + ({ return ( <> {section.key !== state.collection.getFirstKey() && - section.props.children && ( + section.nextKey !== state.collection.getLastKey() && (
  • )}
  • diff --git a/easy-ui-react/src/SearchNav/CTAGroup.tsx b/easy-ui-react/src/SearchNav/CTAGroup.tsx index 575691e0c..44d940730 100644 --- a/easy-ui-react/src/SearchNav/CTAGroup.tsx +++ b/easy-ui-react/src/SearchNav/CTAGroup.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useMemo, Fragment, ReactElement } from "react"; +import React, { ReactNode, Fragment, ReactElement } from "react"; import Help from "@easypost/easy-ui-icons/Help"; import { Separator } from "./Separator"; import { Menu } from "../Menu"; @@ -7,31 +7,37 @@ import { Icon } from "../Icon"; import { UnstyledButton } from "../UnstyledButton"; import { useInternalSearchNavContext } from "./context"; import { classNames } from "../utilities/css"; -import { flattenChildren, getFlattenedKey } from "../utilities/react"; +import { getFlattenedKey } from "../utilities/react"; import styles from "./CTAGroup.module.scss"; export type CTAGroupProps = { /** - * The children of the element. Should include elements. + * The children of the element. Should include + * elements and */ children: ReactNode; }; -export function CTAGroup(props: CTAGroupProps) { - const { children } = props; - const { menuOverlayProps, ctaMenuSymbol } = useInternalSearchNavContext(); +/** + * + * @privateRemarks + * This component doesn't directly use children and instead + * reads the nodes it renders from context. This is so we can + * efficiently share the same data across various configurations. + * + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function CTAGroup(_props: CTAGroupProps) { + const { menuOverlayProps, ctaMenuSymbol, primaryCTAItem, secondaryCTAItems } = + useInternalSearchNavContext(); - const items = useMemo(() => { - return flattenChildren(children); - }, [children]); - - const totalItems = items.length; + const totalItems = secondaryCTAItems?.length || 0; return ( <>
    - {items.map((item, index) => { + {secondaryCTAItems?.map((item, index) => { const isLastChild = index === totalItems - 1; const itemEle = item as ReactElement; return ( @@ -41,6 +47,12 @@ export function CTAGroup(props: CTAGroupProps) { ); })} + {primaryCTAItem && ( + <> + + {primaryCTAItem} + + )}
    @@ -52,7 +64,7 @@ export function CTAGroup(props: CTAGroupProps) { - {items.map((item) => { + {secondaryCTAItems?.map((item) => { const itemEle = item as ReactElement; return ( + {primaryCTAItem && ( + <> + + {primaryCTAItem} + + )}
    ); diff --git a/easy-ui-react/src/SearchNav/CondensedSearchNav.tsx b/easy-ui-react/src/SearchNav/CondensedSearchNav.tsx index 01a1cdf53..0d537ed5d 100644 --- a/easy-ui-react/src/SearchNav/CondensedSearchNav.tsx +++ b/easy-ui-react/src/SearchNav/CondensedSearchNav.tsx @@ -3,6 +3,7 @@ import Close from "@easypost/easy-ui-icons/Close"; import MenuSymbol from "@easypost/easy-ui-icons/Menu"; import Search from "@easypost/easy-ui-icons/Search"; import { Menu } from "../Menu"; +import { HorizontalStack } from "../HorizontalStack"; import { Text } from "../Text"; import { UnstyledButton } from "../UnstyledButton"; import { Icon } from "../Icon"; @@ -15,23 +16,24 @@ import { getFlattenedKey } from "../utilities/react"; /** * @privateRemarks * Renders a left aligned menu button and right aligned search button. - * The menu options come from `SearchNav.Selector` and `SearchNav.CTAGroup`. - * On small screens, this effectively replaces `SearchNav`. + * The menu options come from `` and ``. + * On small screens, this effectively replaces ``. */ export function CondensedSearchNav() { const [isMenuOpen, setIsMenuOpen] = useState(false); const [isSearchOpen, setIsSearchOpen] = useState(false); const { - searchNode, - selectChildren, - ctaGroupChildren, - selectLabel, + search, + selectorChildren, + secondaryCTAItems, + primaryCTAItem, + selectorLabel, menuOverlayProps, } = useInternalSearchNavContext(); - const hasMenuToShow = !!selectChildren || !!ctaGroupChildren; - const hasSearchToShow = searchNode !== null; + const hasMenuToShow = !!selectorChildren || !!secondaryCTAItems; + const hasSearchToShow = !!search; return (
    @@ -51,8 +53,8 @@ export function CondensedSearchNav() { - - {selectChildren?.map((item) => { + + {selectorChildren?.map((item) => { const itemEle = item as ReactElement; return ( @@ -62,7 +64,7 @@ export function CondensedSearchNav() { })} - {ctaGroupChildren?.map((item) => { + {secondaryCTAItems?.map((item) => { const itemEle = item as ReactElement; return ( - {searchNode && ( - setIsSearchOpen((prev) => !prev)} - > - - search - + {(search || primaryCTAItem) && ( + + {search && ( + setIsSearchOpen((prev) => !prev)} + > + + search + + )} + {primaryCTAItem} + )} ) : ( hasSearchToShow && (
    - {searchNode} + {search} setIsSearchOpen((prev) => !prev)} diff --git a/easy-ui-react/src/SearchNav/LogoGroup.tsx b/easy-ui-react/src/SearchNav/LogoGroup.tsx index c5b863ac3..7466772bf 100644 --- a/easy-ui-react/src/SearchNav/LogoGroup.tsx +++ b/easy-ui-react/src/SearchNav/LogoGroup.tsx @@ -1,9 +1,8 @@ -import React, { ReactNode, useMemo } from "react"; +import React, { ReactNode } from "react"; import { Separator } from "./Separator"; -import { flattenChildren } from "../utilities/react"; import { classNames } from "../utilities/css"; - import styles from "./LogoGroup.module.scss"; +import { useInternalSearchNavContext } from "./context"; export type LogoGroupProps = { /** @@ -13,23 +12,31 @@ export type LogoGroupProps = { children: ReactNode; }; -export function LogoGroup(props: LogoGroupProps) { - const { children } = props; - - const items = useMemo(() => { - return flattenChildren(children); - }, [children]); - - const logo = items[0]; - const select = items.length === 2 ? items[1] : null; +/** + * + * @privateRemarks + * This component doesn't directly use children and instead + * reads the nodes it renders from context. This is so we can + * efficiently share the same data across various configurations. + * + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function LogoGroup(_props: LogoGroupProps) { + const { logo, title, selector } = useInternalSearchNavContext(); return (
    {logo} - {select && ( + {title && ( + <> + + {title} + + )} + {selector && ( <> - {select} + {selector} )}
    diff --git a/easy-ui-react/src/SearchNav/PrimaryCTAItem.tsx b/easy-ui-react/src/SearchNav/PrimaryCTAItem.tsx new file mode 100644 index 000000000..c780a84be --- /dev/null +++ b/easy-ui-react/src/SearchNav/PrimaryCTAItem.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { AriaButtonProps } from "react-aria"; +import { Button } from "../Button"; + +export type CTAItemProps = AriaButtonProps<"button"> & { + /** + * Text content to display. + */ + label: string; +}; + +export type PrimaryCTAItemProps = CTAItemProps; + +export function PrimaryCTAItem(props: PrimaryCTAItemProps) { + const { label, ...restProps } = props; + + return ( + + ); +} + +PrimaryCTAItem.displayName = "SearchNav.PrimaryCTAItem"; diff --git a/easy-ui-react/src/SearchNav/SearchNav.mdx b/easy-ui-react/src/SearchNav/SearchNav.mdx index f695efcec..983e1b99d 100644 --- a/easy-ui-react/src/SearchNav/SearchNav.mdx +++ b/easy-ui-react/src/SearchNav/SearchNav.mdx @@ -12,10 +12,10 @@ A `SearchNav` is a navigation bar focused on handling dense information interact The `SearchNav.LogoGroup` and `SearchNav.Logo` components are required. On screen sizes between `640px` and `1024px`, `SearchNav.CTAGroup`, if applicable, collapses into a menu -where each `SearchNav.CTAItem` is transposed onto `Menu.Item`. The `key` provided is directly forwarded. +where each `SearchNav.SecondaryCTAItem` is transposed onto `Menu.Item`. The `key` provided is directly forwarded. On screen sizes smaller than `640px`, `SearchNav.CTAGroup` and `SearchNav.Select`, if applicable, collapse into -a single menu where `SearchNav.CTAItem` and `SearchNav.Option` are respectively transposed onto `Menu.Item`. +a single menu where `SearchNav.SecondaryCTAItem` and `SearchNav.Option` are respectively transposed onto `Menu.Item`. The `menuOverlayProps` prop can be used to control menu interactions. Note, on screen sizes greater than `640px`, `menuOverlayProps` has no bearing on `SearchNav.Select` since the data representation only collapses at screen sizes @@ -25,17 +25,27 @@ less than `640px`. +## Title + + + ## Selector `SearchNav.Selector` can be controlled with `onSelectionChange`. -## CTA +## Secondary CTAs + +`SearchNav.CTAGroup` can render multiple `SearchNav.SecondaryCTAItem` components + + + +## Primary CTA -Each `CTA.Item` is implemented as a button and hence, can accept `onPress` and `href`. +`SearchNav.CTAGroup` can render at **most** one `SearchNav.PrimaryCTAItem` component - + ## Search @@ -87,6 +97,10 @@ export type SearchNavOverlayMenuProps = { +### SearchNav.Title + + + ### SearchNav.Search @@ -95,20 +109,23 @@ export type SearchNavOverlayMenuProps = { -### SearchNav.CTAItem +### SearchNav.PrimaryCTAItem + + + +### SearchNav.SecondaryCTAItem - + ### SearchNav.Selector ```ts export type SelectorProps = { /** - * Hidden label that applies to expanded and will - * become aria-label to apply to when - * collapses. + * aria-label that applies to expanded and + * to when collapses. */ - label: string; + "aria-label": string; /** Method that is called when the open state of the select field changes. */ onOpenChange?: (isOpen: boolean) => void; /** Sets the open state of the select field. */ diff --git a/easy-ui-react/src/SearchNav/SearchNav.stories.tsx b/easy-ui-react/src/SearchNav/SearchNav.stories.tsx index eb0688eef..3f5dfd9c4 100644 --- a/easy-ui-react/src/SearchNav/SearchNav.stories.tsx +++ b/easy-ui-react/src/SearchNav/SearchNav.stories.tsx @@ -26,6 +26,11 @@ const Template = (args: SearchNavProps) => { const meta: Meta = { title: "Components/SearchNav", component: SearchNav, + parameters: { + controls: { + include: [], + }, + }, }; export default meta; @@ -45,6 +50,22 @@ export const Simple: Story = { }, }; +export const Title: Story = { + render: Template.bind({}), + args: { + children: ( + <> + + + + + Docs + + + ), + }, +}; + export const Selector: Story = { render: Template.bind({}), args: { @@ -54,6 +75,7 @@ export const Selector: Story = { + Docs + Docs - - - + Docs V99.99 + + + + + + + + ), + }, +}; + +export const Search: Story = { + render: Template.bind({}), + args: { + children: ( + <> + + + + + Docs + - Search Bar + Search + + + + ), }, @@ -158,6 +230,7 @@ export const FullBar: Story = { + Docs - Search Bar + Search - - - + ), diff --git a/easy-ui-react/src/SearchNav/SearchNav.test.tsx b/easy-ui-react/src/SearchNav/SearchNav.test.tsx index e965cea2d..aff090014 100644 --- a/easy-ui-react/src/SearchNav/SearchNav.test.tsx +++ b/easy-ui-react/src/SearchNav/SearchNav.test.tsx @@ -26,11 +26,33 @@ describe("", () => { expect(screen.getByRole("navigation")).toBeInTheDocument(); }); - it("should support rendering Search.Logo", () => { + it("should throw an error when SearchNav.LogoGroup is missing", () => { + expect(() => render(getSearchNavWithoutLogoGroup())).toThrow( + "SearchNav must contain SearchNav.LogoGroup.", + ); + }); + + it("should support rendering SearchNav.Logo", () => { render(getSearchNav({})); expect(screen.getByAltText("some logo")).toBeInTheDocument(); }); + it("should throw an error when SearchNav.Logo is missing", () => { + expect(() => render(getSearchNavWithoutLogo())).toThrow( + "SearchNav.LogoGroup must contain SearchNav.Logo.", + ); + }); + + it("should support rendering Search.Title with appropriate styles", () => { + render(getSearchNav({})); + const emphasizedText = screen.getByText("Docs"); + expect(emphasizedText).toBeInTheDocument(); + expect(emphasizedText).toHaveAttribute( + "class", + expect.stringContaining("subtitle1"), + ); + }); + it("should support SearchNav.Selector being controlled", async () => { const handleSelectionChange = vi.fn(); const { user } = render( @@ -64,8 +86,52 @@ describe("", () => { expect(menuBtn).toHaveAttribute("aria-expanded", "true"); expect(screen.getByRole("menu")).toBeInTheDocument(); }); + + it("should support rendering SearchNav.PrimaryCTAItem", () => { + render(getSearchNav({})); + expect(screen.getByRole("button", { name: "Sign up" })).toBeInTheDocument(); + }); + + it("should throw an error when more than one SearchNav.PrimaryCTAItem is provided", () => { + expect(() => render(getSearchNavWithMultiplePrimaryCTAItems())).toThrow( + "SearchNav.CTAGroup can support at most one SearchNav.PrimaryCTAItem.", + ); + }); }); +function getSearchNavWithMultiplePrimaryCTAItems() { + return ( + + + + some logo + + + + + + + + + ); +} + +function getSearchNavWithoutLogoGroup() { + return example; +} + +function getSearchNavWithoutLogo() { + return ( + + example + + ); +} + function getSearchNav({ selectorProps = {} }) { return ( @@ -73,6 +139,7 @@ function getSearchNav({ selectorProps = {} }) { some logo + Docs - - - + + + ); diff --git a/easy-ui-react/src/SearchNav/SearchNav.tsx b/easy-ui-react/src/SearchNav/SearchNav.tsx index 2200fd82d..65343c5aa 100644 --- a/easy-ui-react/src/SearchNav/SearchNav.tsx +++ b/easy-ui-react/src/SearchNav/SearchNav.tsx @@ -1,21 +1,26 @@ -import React, { ReactElement, ReactNode, useMemo } from "react"; +import React, { ReactNode, useMemo } from "react"; import { classNames } from "../utilities/css"; -import { - flattenChildren, - getDisplayNameFromReactNode, -} from "../utilities/react"; +import { flattenChildren } from "../utilities/react"; import styles from "./SearchNav.module.scss"; import { LogoGroup } from "./LogoGroup"; import { Search } from "./Search"; import { CTAGroup } from "./CTAGroup"; import { Logo } from "./Logo"; -import { CTAItem } from "./CTAItem"; +import { SecondaryCTAItem } from "./SecondaryCTAItem"; import { Selector } from "./Selector"; import { MenuOverlayProps } from "../Menu/MenuOverlay"; import { CondensedSearchNav } from "./CondensedSearchNav"; import { SelectOption } from "../Select/SelectOption"; import { InternalSearchNavContext } from "./context"; import { IconSymbol } from "../types"; +import { PrimaryCTAItem } from "./PrimaryCTAItem"; +import { Title } from "./Title"; +import { + getSearchChildren, + getCTAGroupChildren, + getLogoGroupChildren, + getSelectorLabel, +} from "./utilities"; export type SearchNavOverlayMenuProps = Omit< MenuOverlayProps, @@ -70,6 +75,7 @@ export type SearchNavProps = { * * some logo * +* Docs * = { * * * -* -* -* +* +* +* * * * ); @@ -103,76 +112,48 @@ export type SearchNavProps = { export function SearchNav(props: SearchNavProps) { const { menuOverlayProps, ctaMenuSymbol, children } = props; - const context = useMemo(() => { + const { onlyLogoGroup, context } = useMemo(() => { + // To support the various configurations on smaller screens, + // we extract data and nodes from the components provided + // by consumers and use context to share them efficiently. const topLevelChildren = flattenChildren(children); - const logoGroupDisplayName = getDisplayNameFromReactNode( + const { logo, title, selector, selectorChildren } = getLogoGroupChildren( topLevelChildren[0], ); + const selectorLabel = getSelectorLabel(selector); + + const search = + topLevelChildren.length > 1 + ? getSearchChildren(topLevelChildren[1]) + : undefined; + + const { secondaryCTAItems, primaryCTAItem } = getCTAGroupChildren( + topLevelChildren[topLevelChildren.length - 1], + ); - if (logoGroupDisplayName !== "SearchNav.LogoGroup") { - throw new Error("SearchNav must contain SearchNav.LogoGroup."); - } - const logoGroupElement = topLevelChildren[0] as ReactElement; - const logoGroupChildren = flattenChildren(logoGroupElement.props.children); - const logoDisplayName = getDisplayNameFromReactNode(logoGroupChildren[0]); - if (logoDisplayName !== "SearchNav.Logo") { - throw new Error("SearchNav.LogoGroup must contain SearchNav.Logo."); - } - - let selectChildren; - let selectLabel = ""; - if ( - logoGroupChildren.length === 2 && - getDisplayNameFromReactNode(logoGroupChildren[1]) === "SearchNav.Selector" - ) { - const selectElement = logoGroupChildren[1] as ReactElement; - const { "aria-label": label } = selectElement.props; - selectLabel = label; - selectChildren = flattenChildren(selectElement.props.children); - } - - let searchNode; - if ( - topLevelChildren.length > 1 && - getDisplayNameFromReactNode(topLevelChildren[1]) === "SearchNav.Search" - ) { - const searchChildren = flattenChildren(topLevelChildren[1]); - if (searchChildren.length === 1) { - searchNode = searchChildren[0]; - } - } - - let ctaGroupChildren; - let hasCtaGroup = false; - if ( - topLevelChildren.length > 1 && - getDisplayNameFromReactNode( - topLevelChildren[topLevelChildren.length - 1], - ) === "SearchNav.CTAGroup" - ) { - const ctaGroupElement = topLevelChildren[ - topLevelChildren.length - 1 - ] as ReactElement; - hasCtaGroup = true; - ctaGroupChildren = flattenChildren(ctaGroupElement.props.children); - } - - const onlyLogoGroup = !hasCtaGroup && searchNode === undefined; + const onlyLogoGroup = + secondaryCTAItems === undefined && + primaryCTAItem === undefined && + search === undefined; return { - selectChildren, - ctaGroupChildren, - searchNode, onlyLogoGroup, - menuOverlayProps, - selectLabel, - ctaMenuSymbol, + context: { + logo, + title, + selector, + selectorChildren, + secondaryCTAItems, + primaryCTAItem, + search, + menuOverlayProps, + selectorLabel, + ctaMenuSymbol, + }, }; }, [children, menuOverlayProps, ctaMenuSymbol]); - const { onlyLogoGroup } = context; - return (