Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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 @@ -19,6 +19,64 @@ describe('packages/collection-toolbar', () => {
expect(utils.getCollectionToolbar()?.className).toContain('test-class');
});

describe('variant: default', () => {
test('renders actions before filters', () => {
render(
<CollectionToolbar variant={Variant.Default}>
<CollectionToolbar.Filters>
<CollectionToolbar.Filters.TextInput aria-label="Filter" />
</CollectionToolbar.Filters>
<CollectionToolbar.Actions>
<CollectionToolbar.Actions.Button>
Action
</CollectionToolbar.Actions.Button>
</CollectionToolbar.Actions>
</CollectionToolbar>,
);

const utils = getTestUtils();
const actions = utils.getActions();
const filters = utils.getFilters();

expect(actions).toBeInTheDocument();
expect(filters).toBeInTheDocument();

// Actions should come before filters in DOM order
// Using compareDocumentPosition: DOCUMENT_POSITION_FOLLOWING (4) means the node follows
expect(actions.compareDocumentPosition(filters)).toBe(
Node.DOCUMENT_POSITION_FOLLOWING,
);
});
});

describe('variant: compact', () => {
test('renders filters before actions', () => {
render(
<CollectionToolbar variant={Variant.Compact}>
<CollectionToolbar.Filters>
<CollectionToolbar.Filters.TextInput aria-label="Filter" />
</CollectionToolbar.Filters>
<CollectionToolbar.Actions>
<CollectionToolbar.Actions.Button>
Action
</CollectionToolbar.Actions.Button>
</CollectionToolbar.Actions>
</CollectionToolbar>,
);

const utils = getTestUtils();
const actions = utils.getActions();
const filters = utils.getFilters();

expect(actions).toBeInTheDocument();
expect(filters).toBeInTheDocument();

expect(filters.compareDocumentPosition(actions)).toBe(
Node.DOCUMENT_POSITION_FOLLOWING,
);
});
});

describe('variant: collapsible', () => {
test('renders title when variant is collapsible', () => {
render(
Expand All @@ -37,5 +95,33 @@ describe('packages/collection-toolbar', () => {
);
expect(screen.queryByText('Test Title')).not.toBeInTheDocument();
});

test('does not render filters in main row (only in collapsible content)', () => {
render(
<CollectionToolbar variant={Variant.Collapsible}>
<CollectionToolbar.Title>Test Title</CollectionToolbar.Title>
<CollectionToolbar.Filters>
<CollectionToolbar.Filters.TextInput aria-label="Filter" />
</CollectionToolbar.Filters>
<CollectionToolbar.Actions showToggleButton>
<CollectionToolbar.Actions.Button>
Action
</CollectionToolbar.Actions.Button>
</CollectionToolbar.Actions>
</CollectionToolbar>,
);

const utils = getTestUtils();
const actions = utils.getActions();
const filters = utils.getFilters();

// Both should be rendered
expect(actions).toBeInTheDocument();
expect(filters).toBeInTheDocument();

// Filters should be inside the collapsible content, not a sibling of actions
// Actions should NOT have filters as a direct following sibling
expect(actions?.nextElementSibling).not.toBe(filters);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { forwardRef } from 'react';
import React, { forwardRef, useMemo } from 'react';

import {
CompoundComponent,
findChild,
} from '@leafygreen-ui/compound-component';

import { Actions, Filters, SearchInput, Title } from '../components';
import { CollapsibleContent } from '../components/CollapsibleContent';
import { CollectionToolbarProvider } from '../Context/CollectionToolbarProvider';
import {
CollectionToolbarSubComponentProperty,
Expand All @@ -15,7 +16,6 @@ import {
import { getLgIds } from '../utils';

import { getCollectionToolbarStyles } from './CollectionToolbar.styles';
import { collapsibleContentStyles } from './CollectionToolbar.styles';
import { CollectionToolbarProps } from './CollectionToolbar.types';

export const CollectionToolbar = CompoundComponent(
Expand Down Expand Up @@ -55,6 +55,25 @@ export const CollectionToolbar = CompoundComponent(
);

const isCollapsible = variant === Variant.Collapsible;
const isCompact = variant === Variant.Compact;

const content = useMemo(() => {
if (isCompact) {
return (
<>
{filters}
{actions}
</>
);
}

return (
<>
{actions}
{!isCollapsible && filters}
</>
);
}, [isCompact, isCollapsible, filters, actions]);

return (
<CollectionToolbarProvider
Expand All @@ -65,19 +84,14 @@ export const CollectionToolbar = CompoundComponent(
>
<div
data-lgid={lgIds.root}
// TODO: Compact and Collapsible styles in LG-5845
className={getCollectionToolbarStyles({ className })}
ref={fwdRef}
{...rest}
>
{isCollapsible ? title : searchInput}
{actions}
{!isCollapsible && filters}
{content}
{isCollapsible && (
<div className={collapsibleContentStyles}>
{searchInput}
{filters}
</div>
<CollapsibleContent searchInput={searchInput} filters={filters} />
)}
</div>
</CollectionToolbarProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface CollectionToolbarContextProps extends DarkModeProps {
*
* @default `'default'`
*/
variant?: Variant;
variant: Variant;
/**
* LGIDs for CollectionToolbar components
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { css, cx } from '@leafygreen-ui/emotion';
import { spacing } from '@leafygreen-ui/tokens';
import { spacing, transitionDuration } from '@leafygreen-ui/tokens';

export const baseStyles = css`
display: flex;
Expand All @@ -9,5 +9,18 @@ export const baseStyles = css`
margin-left: ${spacing[1800]}px;
`;

const iconBaseStyles = css`
transition: transform ${transitionDuration.default}ms ease-in-out;
`;

const iconExpandedStyles = css`
transform: rotate(180deg);
`;

export const getIconStyles = ({ isExpanded }: { isExpanded: boolean }) =>
cx(iconBaseStyles, {
[iconExpandedStyles]: isExpanded,
});

export const getActionStyles = ({ className }: { className?: string }) =>
cx(baseStyles, className);
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,31 @@ describe('packages/collection-toolbar/components/Actions', () => {
expect(toggleButton).toHaveAttribute('aria-expanded', 'true');
});

test('icon has rotation transform when expanded (isCollapsed is false)', () => {
renderActions({
variant: Variant.Collapsible,
showToggleButton: true,
isCollapsed: false,
});
const toggleButton = screen.getByLabelText('Toggle collapse');
const icon = toggleButton.querySelector('svg');
expect(icon).toBeInTheDocument();
expect(icon).toHaveStyle({ transform: 'rotate(180deg)' });
});

test('icon does not have rotation transform when collapsed (isCollapsed is true)', () => {
renderActions({
variant: Variant.Collapsible,
showToggleButton: true,
isCollapsed: true,
});
const toggleButton = screen.getByLabelText('Toggle collapse');
const icon = toggleButton.querySelector('svg');
expect(icon).toBeInTheDocument();
// When collapsed, the icon should not have the rotation transform
expect(icon).not.toHaveStyle({ transform: 'rotate(180deg)' });
});

test('does not render toggle IconButton when variant is "compact"', () => {
renderActions({ variant: Variant.Compact });
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
Variant,
} from '../../shared.types';

import { getActionStyles } from './Action.styles';
import { getActionStyles, getIconStyles } from './Action.styles';
import { ActionsProps } from './Actions.types';
import { Button } from './Button';
import { Menu } from './Menu';
Expand Down Expand Up @@ -83,14 +83,17 @@ export const Actions = CompoundSubComponent(
<Tooltip
justify={Justify.Middle}
trigger={
// TODO: Add animations in LG-5845
<IconButton
onClick={onToggleCollapsed}
aria-label="Toggle collapse"
aria-expanded={!isCollapsed}
aria-controls={ariaControls}
>
<Icon glyph="ChevronDown" />
<Icon
glyph="ChevronDown"
role="presentation"
className={getIconStyles({ isExpanded: !isCollapsed })}
/>
</IconButton>
}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { css, cx } from '@leafygreen-ui/emotion';
import { spacing, transitionDuration } from '@leafygreen-ui/tokens';

export const collapsibleContentBaseStyles = css`
display: grid;
transition-property: grid-template-rows, margin-bottom, opacity;
transition-duration: ${transitionDuration.default}ms;
transition-timing-function: ease-in-out;
grid-template-rows: 1fr;
margin-bottom: 0;
opacity: 1;
gap: ${spacing[200]}px;
`;

const collapsibleContentCollapsedStyles = css`
grid-template-rows: 0fr;
opacity: 0;
`;

export const getCollapsibleContentStyles = ({
isCollapsed,
}: {
isCollapsed?: boolean;
}) =>
cx(collapsibleContentBaseStyles, {
[collapsibleContentCollapsedStyles]: isCollapsed,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';

import { useCollectionToolbarContext } from '../../Context/CollectionToolbarProvider';

import { getCollapsibleContentStyles } from './CollapsibleContent.styles';
import { CollapsibleContentProps } from './CollapsibleContent.types';

/**
* @internal
* Internal CollapsibleContent component
*
* @remarks
* This component is intended to be used within the context of the `CollectionToolbarProvider`
* created in `CollectionToolbar.tsx`. It relies on the CollectionToolbarContext to access
* the `isCollapsed` state for styling purposes.
*
* Do not use `CollapsibleContent` directly—always use the `CollectionToolbar` component,
* which ensures the correct context is provided.
*/
export const CollapsibleContent = ({
searchInput,
filters,
}: CollapsibleContentProps) => {
const { isCollapsed } = useCollectionToolbarContext();

return (
<div className={getCollapsibleContentStyles({ isCollapsed })}>
{searchInput}
{filters}
</div>
);
};

CollapsibleContent.displayName = 'CollapsibleContent';
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface CollapsibleContentProps {
searchInput: React.ReactNode;
filters: React.ReactNode;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { CollapsibleContent } from './CollapsibleContent';
export { type CollapsibleContentProps } from './CollapsibleContent.types';
Loading
Loading