-
Notifications
You must be signed in to change notification settings - Fork 4
ECHOES-1408 Allow icon-less accordion child items in SidebarNavigation #715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
david-cho-lerat-sonarsource
wants to merge
5
commits into
main
Choose a base branch
from
david/ECHOES-1408-optional-icon-for-navigation-accordion-items
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
784d7cf
ECHOES-1408 Add accordion child sidebar navigation item
david-cho-lerat-sonarsource a1bfe53
ECHOES-1408 Honor aria labels in sidebar accordion items
david-cho-lerat-sonarsource d03db63
ECHOES-1408 Update sidebar navigation stories for accordion child rows
david-cho-lerat-sonarsource cc554d3
ECHOES-1408 Add tests for accordion child sidebar items
david-cho-lerat-sonarsource d45a6c0
ECHOES-1408 Re-export sidebar navigation child item types
david-cho-lerat-sonarsource File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
src/components/layout/sidebar-navigation/SidebarNavigationAccordionChildItem.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| /* | ||
| * Echoes React | ||
| * Copyright (C) 2023-2025 SonarSource Sàrl | ||
| * mailto:info AT sonarsource DOT com | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 3 of the License, or (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public License | ||
| * along with this program; if not, write to the Free Software Foundation, | ||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| */ | ||
|
|
||
| import { SidebarNavigationBaseItem } from './SidebarNavigationBaseItem'; | ||
|
david-cho-lerat-sonarsource marked this conversation as resolved.
|
||
|
|
||
| import { | ||
| SidebarNavigationIconComponent, | ||
| SidebarNavigationItemBaseProps, | ||
| } from './SidebarNavigationTypes'; | ||
|
|
||
| export interface SidebarNavigationAccordionChildItemProps extends SidebarNavigationItemBaseProps { | ||
| /** | ||
| * The icon component to display at the start of the SidebarNavigationAccordionChildItem. | ||
| * Must be an Echoes Icon component. Omit it for icon-less accordion child rows. | ||
| */ | ||
| Icon?: SidebarNavigationIconComponent; | ||
| } | ||
|
|
||
| export function SidebarNavigationAccordionChildItem( | ||
| props: Readonly<SidebarNavigationAccordionChildItemProps>, | ||
| ) { | ||
| return <SidebarNavigationBaseItem {...props} />; | ||
| } | ||
|
david-cho-lerat-sonarsource marked this conversation as resolved.
|
||
|
|
||
| SidebarNavigationAccordionChildItem.displayName = 'SidebarNavigationAccordionChildItem'; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,40 +19,53 @@ | |
| */ | ||
|
|
||
| import styled from '@emotion/styled'; | ||
| import { | ||
| forwardRef, | ||
| ForwardRefExoticComponent, | ||
| ReactNode, | ||
| useCallback, | ||
| useEffect, | ||
| useId, | ||
| useRef, | ||
| useState, | ||
| } from 'react'; | ||
|
|
||
| import { ReactNode, Ref, useCallback, useEffect, useId, useRef, useState } from 'react'; | ||
|
|
||
|
Comment on lines
+22
to
+24
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some weird spaces got added between the imports, we might be missing some formatting rules there |
||
| import { TextNode } from '~types/utils'; | ||
| import { cssVar } from '~utils/design-tokens'; | ||
| import { IconChevronDown, IconChevronRight, IconFilledProps } from '../../icons'; | ||
| import { IconChevronDown, IconChevronRight } from '../../icons'; | ||
| import { Tooltip } from '../../tooltip'; | ||
|
|
||
| import { | ||
| sidebarNavigationBaseItemStyles, | ||
| sidebarNavigationItemIconStyles, | ||
| SidebarNavigationItemLabel, | ||
| } from './SidebarNavigationItemStyles'; | ||
|
|
||
| import { SidebarNavigationIconComponent } from './SidebarNavigationTypes'; | ||
| import { TOOLTIP_DELAY_IN_MS } from './utils'; | ||
|
|
||
| export interface SidebarNavigationAccordionItemProps { | ||
| /** | ||
| * ARIA label for the SidebarNavigationAccordionItem button. | ||
| */ | ||
| ariaLabel?: string; | ||
| /** | ||
| * List of SidebarNavigationItem that are displayed when the accordion is expanded. | ||
| * Should ideally be maximum 5 items. | ||
| * List of navigation child items displayed when the accordion is expanded. | ||
| * Prefer `SidebarNavigation.AccordionItem.Item` and keep the list to five items or fewer. | ||
| */ | ||
| children: ReactNode; | ||
| /** | ||
| * Optional CSS class name applied to the accordion button element. | ||
| */ | ||
| className?: string; | ||
| /** | ||
| * Whether to disable the tooltip on the accordion item or not. | ||
| * By default the tooltip is enabled, it should only be disabled if you don't expect the content to be ellipsed. | ||
| * @defaultValue false | ||
| */ | ||
| disableTooltip?: boolean; | ||
| /** | ||
| * The icon component to display at the start of the SidebarNavigationAccordionItem. | ||
| * Must be an Echoes Icon component. | ||
| */ | ||
| Icon: SidebarNavigationIconComponent; | ||
| /** | ||
| * Whether the accordion is open by default. | ||
| * @defaultValue false | ||
| */ | ||
| isDefaultOpen?: boolean; | ||
| /** | ||
| * The label for the SidebarNavigationAccordionItem. | ||
| */ | ||
|
|
@@ -66,37 +79,34 @@ export interface SidebarNavigationAccordionItemProps { | |
| */ | ||
| onOpen?: VoidFunction; | ||
| /** | ||
| * Whether the accordion is open by default. Defaults to false. | ||
| * React ref forwarded to the root button element. | ||
| */ | ||
| isDefaultOpen?: boolean; | ||
| ref?: Ref<HTMLButtonElement>; | ||
| /** | ||
|
david-cho-lerat-sonarsource marked this conversation as resolved.
|
||
| * When true, scrolls the last child item into view when the accordion opens. | ||
| * Useful when the accordion is near the bottom of a scrollable container. | ||
| * @defaultValue false | ||
| */ | ||
| scrollLastChildIntoViewOnOpen?: boolean; | ||
| /** | ||
| * Optional content to display on the right, before the chevron. Typically badges, item count and similar metadata. | ||
| */ | ||
| suffix?: ReactNode; | ||
| /** | ||
| * The icon component to display at the start of the SidebarNavigationAccordionItem. | ||
| * Must be an Echoes Icon component. | ||
| */ | ||
| Icon: ForwardRefExoticComponent<IconFilledProps & React.RefAttributes<HTMLSpanElement>>; | ||
| } | ||
|
|
||
| export const SidebarNavigationAccordionItem = forwardRef< | ||
| HTMLButtonElement, | ||
| SidebarNavigationAccordionItemProps | ||
| >((props, ref) => { | ||
| export function SidebarNavigationAccordionItem( | ||
| props: Readonly<SidebarNavigationAccordionItemProps>, | ||
| ) { | ||
| const { | ||
| ariaLabel, | ||
| children, | ||
| isDefaultOpen = false, | ||
| disableTooltip = false, | ||
| Icon, | ||
| isDefaultOpen = false, | ||
| label, | ||
| onClose, | ||
| onOpen, | ||
| ref, | ||
| scrollLastChildIntoViewOnOpen, | ||
| suffix, | ||
| ...htmlProps | ||
|
|
@@ -139,9 +149,11 @@ export const SidebarNavigationAccordionItem = forwardRef< | |
| {...htmlProps} | ||
| aria-controls={accordionPanelId} | ||
| aria-expanded={open} | ||
| aria-label={ariaLabel} | ||
| id={accordionId} | ||
| onClick={handleClick} | ||
| ref={ref}> | ||
| ref={ref} | ||
| type="button"> | ||
| <Icon css={sidebarNavigationItemIconStyles} isFilled={false} /> | ||
|
|
||
| <SidebarNavigationItemLabel>{label}</SidebarNavigationItemLabel> | ||
|
|
@@ -165,7 +177,7 @@ export const SidebarNavigationAccordionItem = forwardRef< | |
| </AccordionItemPanel> | ||
| </AccordionWrapper> | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| SidebarNavigationAccordionItem.displayName = 'SidebarNavigationAccordionItem'; | ||
|
|
||
|
|
||
148 changes: 148 additions & 0 deletions
148
src/components/layout/sidebar-navigation/SidebarNavigationBaseItem.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,148 @@ | ||
| /* | ||
| * Echoes React | ||
| * Copyright (C) 2023-2025 SonarSource Sàrl | ||
| * mailto:info AT sonarsource DOT com | ||
| * | ||
| * This program is free software; you can redistribute it and/or | ||
| * modify it under the terms of the GNU Lesser General Public | ||
| * License as published by the Free Software Foundation; either | ||
| * version 3 of the License, or (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| * Lesser General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU Lesser General Public License | ||
| * along with this program; if not, write to the Free Software Foundation, | ||
| * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
| */ | ||
|
|
||
| import { css } from '@emotion/react'; | ||
| import styled from '@emotion/styled'; | ||
|
|
||
| import { MouseEvent, useCallback } from 'react'; | ||
|
david-cho-lerat-sonarsource marked this conversation as resolved.
|
||
|
|
||
| import { NavLinkBase } from '~common/components/NavLinkBase'; | ||
| import { isDefined } from '~common/helpers/types'; | ||
| import { cssVar } from '~utils/design-tokens'; | ||
| import { Tooltip } from '../../tooltip'; | ||
|
|
||
| import { | ||
| sidebarNavigationBaseItemStyles, | ||
| sidebarNavigationItemIconStyles, | ||
| SidebarNavigationItemLabel, | ||
| UnstyledListItem, | ||
| } from './SidebarNavigationItemStyles'; | ||
|
|
||
| import { | ||
| SidebarNavigationIconComponent, | ||
| SidebarNavigationItemBaseProps, | ||
| } from './SidebarNavigationTypes'; | ||
|
|
||
| import { TOOLTIP_DELAY_IN_MS } from './utils'; | ||
|
|
||
| interface SidebarNavigationBaseItemInternalProps extends SidebarNavigationItemBaseProps { | ||
| Icon?: SidebarNavigationIconComponent; | ||
| } | ||
|
|
||
| export function SidebarNavigationBaseItem(props: Readonly<SidebarNavigationBaseItemInternalProps>) { | ||
| const { | ||
| ariaLabel, | ||
| children, | ||
| disableIconWhenSidebarOpen = false, | ||
| disableTooltip = false, | ||
| Icon, | ||
| onClick, | ||
| ref, | ||
| suffix, | ||
| ...htmlProps | ||
| } = props; | ||
|
david-cho-lerat-sonarsource marked this conversation as resolved.
|
||
|
|
||
| const handleClick = useCallback( | ||
| (event: MouseEvent<HTMLAnchorElement>) => { | ||
| event.currentTarget.blur(); | ||
| onClick?.(event); | ||
| }, | ||
| [onClick], | ||
| ); | ||
|
|
||
| return ( | ||
| <UnstyledListItem> | ||
| <Tooltip | ||
| content={disableTooltip ? undefined : children} | ||
| delayDuration={TOOLTIP_DELAY_IN_MS} | ||
| side="right"> | ||
| <SidebarNavigationBaseItemLink | ||
| {...htmlProps} | ||
| aria-label={ariaLabel} | ||
| onClick={handleClick} | ||
| ref={ref}> | ||
|
david-cho-lerat-sonarsource marked this conversation as resolved.
|
||
| {isDefined(Icon) && ( | ||
| <Icon | ||
| css={[ | ||
| sidebarNavigationBaseItemIconStyles, | ||
| disableIconWhenSidebarOpen | ||
| ? sidebarNavigationBaseItemHideWhenSidebarOpenStyles | ||
| : undefined, | ||
| ]} | ||
| isFilled={false} | ||
| /> | ||
| )} | ||
|
|
||
| <SidebarNavigationItemLabel>{children}</SidebarNavigationItemLabel> | ||
|
|
||
| {suffix} | ||
| </SidebarNavigationBaseItemLink> | ||
| </Tooltip> | ||
| </UnstyledListItem> | ||
| ); | ||
| } | ||
|
|
||
| SidebarNavigationBaseItem.displayName = 'SidebarNavigationBaseItem'; | ||
|
|
||
| const SidebarNavigationBaseItemLink = styled(NavLinkBase)` | ||
| ${sidebarNavigationBaseItemStyles} | ||
|
|
||
| // When the item is inside an accordion, the display and visibility values change based on the | ||
| // accordion state. Outside of accordions, they fall back to flex and visible. | ||
| display: var(--sidebar-navigation-accordion-children-display, flex); | ||
| visibility: var(--sidebar-navigation-accordion-children-visibility, visible); | ||
|
|
||
| // The accordion provides an outline in closed sidebar mode for the active child item. Keeping it | ||
| // outside the active selector avoids overriding the focus outline from the base item styles. | ||
| outline: var(--sidebar-navigation-accordion-children-outline); | ||
|
|
||
| &:active, | ||
| &.active { | ||
| // Active items stay visible even when the parent accordion is closed. | ||
| display: flex; | ||
| visibility: visible; | ||
|
|
||
| background-color: ${cssVar('sidebar-navigation-item-colors-background-active')}; | ||
| color: ${cssVar('color-text-accent')}; | ||
| font: ${cssVar('typography-text-default-semi-bold')}; | ||
|
|
||
| &:hover { | ||
| background-color: ${cssVar('sidebar-navigation-item-colors-background-hover')}; | ||
| } | ||
| } | ||
| `; | ||
|
|
||
| SidebarNavigationBaseItemLink.displayName = 'SidebarNavigationBaseItemLink'; | ||
|
|
||
| const sidebarNavigationBaseItemIconStyles = css` | ||
| ${sidebarNavigationItemIconStyles} | ||
|
|
||
| ${SidebarNavigationBaseItemLink}.active > &, | ||
| ${SidebarNavigationBaseItemLink}:active > & { | ||
| color: ${cssVar('color-icon-accent')}; | ||
| } | ||
| `; | ||
|
|
||
| const sidebarNavigationBaseItemHideWhenSidebarOpenStyles = css` | ||
| [data-sidebar-docked='true'] &, | ||
| [data-sidebar-docked='false'] nav:is(:hover, :focus-within) & { | ||
| display: none; | ||
| } | ||
| `; | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand correctly now we have a new AccordionItem specifc item that we didn't have before and we should use as the content of the AccordionItem right?
And the only difference between the AccordionChildItem and the SidebarNavigationItem we where using before is the optional Icon for AccordionChildItem? Or are there other differences?
It seems like a lot of changes for making a prop optional. Couldn't we just make it optional on
SidebarNavigationItemand that's it?