diff --git a/src/components/layout/sidebar-navigation/SidebarNavigation.tsx b/src/components/layout/sidebar-navigation/SidebarNavigation.tsx index 3da4c224..1c54a571 100644 --- a/src/components/layout/sidebar-navigation/SidebarNavigation.tsx +++ b/src/components/layout/sidebar-navigation/SidebarNavigation.tsx @@ -89,7 +89,6 @@ const SidebarNavigationWrapper = styled.nav` box-sizing: content-box; overflow: hidden; - padding-top: ${cssVar('dimension-space-100')}; border-right: ${cssVar('border-width-default')} solid ${cssVar('color-border-weak')}; background-color: ${cssVar('color-surface-default')}; diff --git a/src/components/layout/sidebar-navigation/SidebarNavigationAccordionItem.tsx b/src/components/layout/sidebar-navigation/SidebarNavigationAccordionItem.tsx index d754f7c4..8290497f 100644 --- a/src/components/layout/sidebar-navigation/SidebarNavigationAccordionItem.tsx +++ b/src/components/layout/sidebar-navigation/SidebarNavigationAccordionItem.tsx @@ -65,6 +65,10 @@ export interface SidebarNavigationAccordionItemProps { * The onOpen callback is called when the accordion is opened. */ onOpen?: VoidFunction; + /** + * Whether the accordion is open by default. Defaults to false. + */ + isDefaultOpen?: boolean; /** * 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. @@ -87,6 +91,7 @@ export const SidebarNavigationAccordionItem = forwardRef< >((props, ref) => { const { children, + isDefaultOpen = false, disableTooltip = false, Icon, label, @@ -97,7 +102,7 @@ export const SidebarNavigationAccordionItem = forwardRef< ...htmlProps } = props; - const [open, setOpen] = useState(false); + const [open, setOpen] = useState(isDefaultOpen); const panelRef = useRef(null); useEffect(() => { diff --git a/src/components/layout/sidebar-navigation/SidebarNavigationHeader.tsx b/src/components/layout/sidebar-navigation/SidebarNavigationHeader.tsx index 3f714db0..58c20a0a 100644 --- a/src/components/layout/sidebar-navigation/SidebarNavigationHeader.tsx +++ b/src/components/layout/sidebar-navigation/SidebarNavigationHeader.tsx @@ -56,47 +56,56 @@ export const SidebarNavigationHeader = forwardRef - - - {avatar && {avatar}} - - - {name} - - {qualifier && ( - - {qualifier} + + + + + {avatar && {avatar}} + + + {name} - )} - - - {isInteractive && } - - + {qualifier && ( + + {qualifier} + + )} + + + {isInteractive && } + + + ); }, ); SidebarNavigationHeader.displayName = 'SidebarNavigationHeader'; +const HeaderWrapper = styled.div` + padding: ${cssVar('dimension-space-100')}; + display: flex; + border-bottom: ${cssVar('border-width-default')} solid ${cssVar('color-border-weak')}; + + [data-sidebar-docked='false'] nav:not(:hover, :focus-within) & { + padding-left: ${cssVar('dimension-space-50')}; + padding-right: ${cssVar('dimension-space-50')}; + } +`; +HeaderWrapper.displayName = 'HeaderWrapper'; + const HeaderContainer = styled.button` all: unset; display: flex; align-items: center; justify-content: space-between; - flex-shrink: 0; + flex: 1 0 0; gap: ${cssVar('dimension-space-100')}; padding: ${cssVar('dimension-space-100')}; - margin: ${cssVar('dimension-space-100')}; overflow: hidden; - [data-sidebar-docked='false'] nav:not(:hover, :focus-within) & { - margin: ${cssVar('dimension-space-100')} ${cssVar('dimension-space-50')}; - } - /* If it is interactive, we want mouse interactivity */ :is(button) { border-radius: ${cssVar('border-radius-400')}; @@ -129,7 +138,7 @@ const MainContent = styled.div` display: flex; align-items: center; gap: ${cssVar('dimension-space-100')}; - min-width: ${cssVar('dimension-width-300')}; + min-width: ${cssVar('dimension-width-400')}; `; MainContent.displayName = 'MainContent'; diff --git a/src/components/layout/sidebar-navigation/__tests__/SidebarNavigationAccordionItem-test.tsx b/src/components/layout/sidebar-navigation/__tests__/SidebarNavigationAccordionItem-test.tsx index c495ac04..b4065c7c 100644 --- a/src/components/layout/sidebar-navigation/__tests__/SidebarNavigationAccordionItem-test.tsx +++ b/src/components/layout/sidebar-navigation/__tests__/SidebarNavigationAccordionItem-test.tsx @@ -53,6 +53,13 @@ it('should expand hidden elements when clicked', async () => { expect(onClose).toHaveBeenCalled(); }); +it('should render the accordion open when defaultOpen is true', () => { + setupSidebarNavigationAccordionItem({ isDefaultOpen: true }); + + checkAccordionPanelVisibility(true); + expect(screen.getAllByRole('link')).toHaveLength(2); +}); + it("shouldn't have any a11y violation", async () => { const { container, user } = setupSidebarNavigationAccordionItem({ Icon: IconBranch }); await expect(container).toHaveNoA11yViolations(); diff --git a/stories/layout/sidebar-navigation/SidebarNavigationAccordionItem-stories.tsx b/stories/layout/sidebar-navigation/SidebarNavigationAccordionItem-stories.tsx index 10f556b9..fe064e9b 100644 --- a/stories/layout/sidebar-navigation/SidebarNavigationAccordionItem-stories.tsx +++ b/stories/layout/sidebar-navigation/SidebarNavigationAccordionItem-stories.tsx @@ -20,12 +20,41 @@ /* eslint-disable no-console */ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Badge, IconBranch, Layout } from '../../../src'; +import { Badge, cssVar, IconBranch, Layout } from '../../../src'; import { basicWrapperDecorator } from '../../helpers/BasicWrapper'; -const meta: Meta = { +const baseAccordionChildren = ( + <> + + Item 1 + + + + Item 2 + + +); + +const dockedSidebarAccordionChildren = ( + <> + + Icon hidden while sidebar is open + + + + Icon stays visible while sidebar is open + + +); + +const meta: Meta = { title: 'Echoes/Layout/SidebarNavigation/AccordionItem', component: Layout.SidebarNavigation.AccordionItem, + argTypes: { + isDefaultOpen: { + control: { type: 'boolean' }, + }, + }, decorators: [ (Story) => ( @@ -34,6 +63,13 @@ const meta: Meta = { ), basicWrapperDecorator, ], + render: ({ isDefaultOpen = false, ...args }) => ( + + ), }; export default meta; @@ -43,16 +79,8 @@ type Story = StoryObj; export const base: Story = { args: { Icon: IconBranch, - children: ( - <> - - Item 1 - - - Item 2 - - - ), + children: baseAccordionChildren, + isDefaultOpen: false, label: 'Accordion', }, }; @@ -60,16 +88,8 @@ export const base: Story = { export const suffixed: Story = { args: { Icon: IconBranch, - children: ( - <> - - Item 1 - - - Item 2 - - - ), + children: baseAccordionChildren, + isDefaultOpen: false, label: 'Accordion', suffix: ( @@ -79,6 +99,35 @@ export const suffixed: Story = { }, }; +export const withDefaultOpen: Story = { + args: { + Icon: IconBranch, + children: baseAccordionChildren, + isDefaultOpen: true, + label: 'Accordion', + }, +}; + +export const withDisableIconWhenSidebarOpen: Story = { + args: { + Icon: IconBranch, + isDefaultOpen: true, + label: 'Accordion', + }, + render: ({ isDefaultOpen = true, ...args }) => ( +
+ + {dockedSidebarAccordionChildren} + +
+ ), +}; + const fourNavItems = Array.from({ length: 4 }, (_, i) => ( Item {`${i + 1}`}