diff --git a/.changeset/fifty-seas-relax.md b/.changeset/fifty-seas-relax.md new file mode 100644 index 00000000000..56e1ccce064 --- /dev/null +++ b/.changeset/fifty-seas-relax.md @@ -0,0 +1,7 @@ +--- +'@primer/react': patch +--- + +Adds the defaultOpen prop to NavList.Item + + diff --git a/src/NavList/NavList.docs.json b/src/NavList/NavList.docs.json index 9d76c1e656f..9d356697217 100644 --- a/src/NavList/NavList.docs.json +++ b/src/NavList/NavList.docs.json @@ -47,6 +47,11 @@ "defaultValue": "false", "description": "Set `aria-current` to `\"page\"` to indicate that the item represents the current page. Set `aria-current` to `\"location\"` to indicate that the item represents the current location on a page. For more information about `aria-current`, see [MDN](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-current)." }, + { + "name": "defaultOpen", + "type": "boolean", + "description": "The open state of the item when it is initially rendered if the item has a SubNav." + }, { "name": "ref", "type": "React.RefObject" diff --git a/src/NavList/NavList.stories.tsx b/src/NavList/NavList.stories.tsx index 5baf3de9793..2ce3f77d3ea 100644 --- a/src/NavList/NavList.stories.tsx +++ b/src/NavList/NavList.stories.tsx @@ -51,7 +51,12 @@ export const WithNestedSubItems: Story = () => ( - Item 1 + + Item 1 + + Sub item 1 + + Item 2{/* NOTE: Don't nest SubNavs. For testing purposes only */} diff --git a/src/NavList/NavList.tsx b/src/NavList/NavList.tsx index 591b75a08f7..0a7687bb204 100644 --- a/src/NavList/NavList.tsx +++ b/src/NavList/NavList.tsx @@ -48,12 +48,13 @@ Root.displayName = 'NavList' export type NavListItemProps = { children: React.ReactNode + defaultOpen?: boolean href?: string 'aria-current'?: 'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false' | boolean } & SxProp const Item = React.forwardRef( - ({'aria-current': ariaCurrent, children, sx: sxProp = defaultSxProp, ...props}, ref) => { + ({'aria-current': ariaCurrent, children, defaultOpen, sx: sxProp = defaultSxProp, ...props}, ref) => { const {depth} = React.useContext(SubNavContext) // Get SubNav from children @@ -64,10 +65,14 @@ const Item = React.forwardRef( isValidElement(child) ? child.type !== SubNav : true, ) + if (!isValidElement(subNav) && defaultOpen) + // eslint-disable-next-line no-console + console.error('NavList.Item must have a NavList.SubNav to use defaultOpen.') + // Render ItemWithSubNav if SubNav is present if (subNav && isValidElement(subNav)) { return ( - + {childrenWithoutSubNav} ) @@ -96,6 +101,7 @@ type ItemWithSubNavProps = { children: React.ReactNode subNav: React.ReactNode depth: number + defaultOpen?: boolean } & SxProp const ItemWithSubNavContext = React.createContext<{buttonId: string; subNavId: string; isOpen: boolean}>({ @@ -106,10 +112,10 @@ const ItemWithSubNavContext = React.createContext<{buttonId: string; subNavId: s // TODO: ref prop // TODO: Animate open/close transition -function ItemWithSubNav({children, subNav, depth, sx: sxProp = defaultSxProp}: ItemWithSubNavProps) { +function ItemWithSubNav({children, subNav, depth, defaultOpen, sx: sxProp = defaultSxProp}: ItemWithSubNavProps) { const buttonId = useId() const subNavId = useId() - const [isOpen, setIsOpen] = React.useState(false) + const [isOpen, setIsOpen] = React.useState((defaultOpen || null) ?? false) const subNavRef = React.useRef(null) const [containsCurrentItem, setContainsCurrentItem] = React.useState(false) @@ -124,7 +130,7 @@ function ItemWithSubNav({children, subNav, depth, sx: sxProp = defaultSxProp}: I setIsOpen(true) } } - }, [subNav]) + }, [subNav, buttonId]) return (