Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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')};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -87,6 +91,7 @@ export const SidebarNavigationAccordionItem = forwardRef<
>((props, ref) => {
const {
children,
isDefaultOpen = false,
disableTooltip = false,
Icon,
label,
Expand All @@ -97,7 +102,7 @@ export const SidebarNavigationAccordionItem = forwardRef<
...htmlProps
} = props;

const [open, setOpen] = useState(false);
const [open, setOpen] = useState(isDefaultOpen);
const panelRef = useRef<HTMLElement>(null);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,47 +56,56 @@ export const SidebarNavigationHeader = forwardRef<HTMLButtonElement, SidebarNavi
const [isOverflow] = useIsOverflow(labelRef, [name]);

return (
<Tooltip content={isOverflow && isInteractive ? name : undefined} side={TooltipSide.Right}>
<HeaderContainer as={isInteractive ? 'button' : 'div'} ref={ref} {...radixProps}>
<MainContent>
{avatar && <AvatarWrapper>{avatar}</AvatarWrapper>}
<TextContent>
<Text isHighlighted ref={labelRef}>
{name}
</Text>
{qualifier && (
<Text isSubtle size="small">
{qualifier}
<HeaderWrapper>
<Tooltip content={isOverflow && isInteractive ? name : undefined} side={TooltipSide.Right}>
<HeaderContainer as={isInteractive ? 'button' : 'div'} ref={ref} {...radixProps}>
<MainContent>
{avatar && <AvatarWrapper>{avatar}</AvatarWrapper>}
<TextContent>
<Text isHighlighted ref={labelRef}>
{name}
</Text>
)}
</TextContent>
</MainContent>
{isInteractive && <IconExpandAll />}
</HeaderContainer>
</Tooltip>
{qualifier && (
<Text isSubtle size="small">
{qualifier}
</Text>
)}
</TextContent>
</MainContent>
{isInteractive && <IconExpandAll />}
</HeaderContainer>
</Tooltip>
</HeaderWrapper>
);
},
);
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')};
Expand Down Expand Up @@ -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';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
<>
<Layout.SidebarNavigation.Item Icon={IconBranch} to="/1">
Item 1
</Layout.SidebarNavigation.Item>

<Layout.SidebarNavigation.Item Icon={IconBranch} to="/2">
Item 2
</Layout.SidebarNavigation.Item>
</>
);

const dockedSidebarAccordionChildren = (
<>
<Layout.SidebarNavigation.Item Icon={IconBranch} disableIconWhenSidebarOpen to="/1">
Icon hidden while sidebar is open
</Layout.SidebarNavigation.Item>

<Layout.SidebarNavigation.Item Icon={IconBranch} to="/2">
Icon stays visible while sidebar is open
</Layout.SidebarNavigation.Item>
</>
);

const meta: Meta<typeof Layout.SidebarNavigation.AccordionItem> = {
title: 'Echoes/Layout/SidebarNavigation/AccordionItem',
component: Layout.SidebarNavigation.AccordionItem,
argTypes: {
isDefaultOpen: {
control: { type: 'boolean' },
},
},
decorators: [
(Story) => (
<Layout.SidebarNavigation.Body>
Expand All @@ -34,6 +63,13 @@ const meta: Meta = {
),
basicWrapperDecorator,
],
render: ({ isDefaultOpen = false, ...args }) => (
<Layout.SidebarNavigation.AccordionItem
isDefaultOpen={isDefaultOpen}
key={`accordion-default-open-${isDefaultOpen.toString()}`}
{...args}
/>
),
};

export default meta;
Expand All @@ -43,33 +79,17 @@ type Story = StoryObj<typeof Layout.SidebarNavigation.AccordionItem>;
export const base: Story = {
args: {
Icon: IconBranch,
children: (
<>
<Layout.SidebarNavigation.Item Icon={IconBranch} disableIconWhenSidebarOpen to="/1">
Item 1
</Layout.SidebarNavigation.Item>
<Layout.SidebarNavigation.Item Icon={IconBranch} disableIconWhenSidebarOpen to="/2">
Item 2
</Layout.SidebarNavigation.Item>
</>
),
children: baseAccordionChildren,
isDefaultOpen: false,
label: 'Accordion',
},
};

export const suffixed: Story = {
args: {
Icon: IconBranch,
children: (
<>
<Layout.SidebarNavigation.Item Icon={IconBranch} disableIconWhenSidebarOpen to="/1">
Item 1
</Layout.SidebarNavigation.Item>
<Layout.SidebarNavigation.Item Icon={IconBranch} disableIconWhenSidebarOpen to="/2">
Item 2
</Layout.SidebarNavigation.Item>
</>
),
children: baseAccordionChildren,
isDefaultOpen: false,
label: 'Accordion',
suffix: (
<Badge isHighContrast variety="highlight">
Expand All @@ -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 }) => (
<div
data-sidebar-docked="true"
style={{ width: cssVar('layout-sidebar-navigation-sizes-width-open') }}>
<Layout.SidebarNavigation.AccordionItem
isDefaultOpen={isDefaultOpen}
key={`accordion-disable-icon-when-sidebar-open-${isDefaultOpen.toString()}`}
{...args}>
{dockedSidebarAccordionChildren}
</Layout.SidebarNavigation.AccordionItem>
</div>
),
};

const fourNavItems = Array.from({ length: 4 }, (_, i) => (
<Layout.SidebarNavigation.Item Icon={IconBranch} key={i} to={`/item-${i}`}>
Item {`${i + 1}`}
Expand Down
Loading