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
34 changes: 34 additions & 0 deletions PR_DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# PR Title

`[LG-5844] feat(collection-toolbar): add CollectionToolbarTitle compound component`

---

# PR Description

## ✍️ Proposed changes

This PR introduces the `CollectionToolbarTitle` compound component for the `CollectionToolbar` package. The title component:

- Uses the compound component pattern (`CompoundComponent` and `CompoundSubComponent` utilities) to integrate seamlessly with the parent `CollectionToolbar`
- Renders an `H3` typography element for the title content
- Only displays when the `CollectionToolbar` variant is set to `Collapsible`
- Exports `Size` and `Variant` enums for better component API ergonomics

Additionally, this PR improves the Storybook configuration with proper `StoryMetaType`, argument types, and a `LiveExample` story that demonstrates the title component usage.

🎟️ _Jira ticket:_ [LG-5844](https://jira.mongodb.org/browse/LG-5844)

## ✅ Checklist

- [ ] I have added stories/tests that prove my fix is effective or that my feature works
- [ ] I have added necessary documentation (if appropriate)
- [ ] I have run `pnpm changeset` and documented my changes

## 🧪 How to test changes

1. Run `pnpm storybook` and navigate to **Components / CollectionToolbar**
2. View the **LiveExample** story to see the `CollectionToolbar.Title` in action
3. Toggle the `variant` control to `Collapsible` - the title should appear
4. Toggle the `variant` control to `Default` - the title should be hidden
5. Verify that the title renders correctly with different `size` and `darkMode` combinations
2 changes: 2 additions & 0 deletions packages/collection-toolbar/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
"@leafygreen-ui/emotion": "workspace:^",
"@leafygreen-ui/lib": "workspace:^",
"@leafygreen-ui/tokens": "workspace:^",
"@leafygreen-ui/compound-component": "workspace:^",
"@leafygreen-ui/typography": "workspace:^",
"@lg-tools/test-harnesses": "workspace:^"
},
"peerDependencies": {
Expand Down
15 changes: 14 additions & 1 deletion packages/collection-toolbar/src/CollectionToolbar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ const meta: StoryMetaType<typeof CollectionToolbar> = {
variant: Object.values(Variant),
},
},
docs: {
description: {
component:
'CollectionToolbar is a component that displays a toolbar for a collection.',
},
},
},
argTypes: {
darkMode: storybookArgTypes.darkMode,
Expand All @@ -30,11 +36,18 @@ const meta: StoryMetaType<typeof CollectionToolbar> = {
},
args: {
children: 'Collection Toolbar',
size: {
control: 'select',
options: Object.values(Size),
},
darkMode: storybookArgTypes.darkMode,
},
};

export default meta;

export const LiveExample: StoryFn<typeof CollectionToolbar> = props => (
<CollectionToolbar {...props} />
<CollectionToolbar {...props}>
<CollectionToolbar.Title>Collection Title</CollectionToolbar.Title>
</CollectionToolbar>
);
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
import React from 'react';
import { render, screen } from '@testing-library/react';

import { CollectionToolbar } from '.';
import { CollectionToolbar, Variant } from '.';

describe('packages/collection-toolbar', () => {
test('renders correctly', () => {
render(<CollectionToolbar>Collection Toolbar</CollectionToolbar>);
expect(screen.getByText('Collection Toolbar')).toBeInTheDocument();
render(<CollectionToolbar />);
expect(screen.getByTestId('lg-collection_toolbar')).toBeInTheDocument();
});

test('applies className to the root element', () => {
render(<CollectionToolbar className="test-class" />);
expect(screen.getByTestId('lg-collection_toolbar')).toHaveClass(
'test-class',
);
});

describe('variant: collapsible', () => {
test('renders title when variant is collapsible', () => {
render(
<CollectionToolbar variant={Variant.Collapsible}>
<CollectionToolbar.Title>Test Title</CollectionToolbar.Title>
</CollectionToolbar>,
);
expect(screen.getByText('Test Title')).toBeInTheDocument();
});

test('does not render title when variant is not collapsible', () => {
render(
<CollectionToolbar variant={Variant.Default}>
<CollectionToolbar.Title>Test Title</CollectionToolbar.Title>
</CollectionToolbar>,
);
expect(screen.queryByText('Test Title')).not.toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import { Size, Variant } from './CollectionToolbar.types';
export const baseStyles = css``;

export const getCollectionToolbarStyles = ({
size,
variant,
className,
}: {
size?: Size;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
import React from 'react';

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

import { getLgIds } from '../utils/getLgIds';

import { getCollectionToolbarStyles } from './CollectionToolbar.styles';
import {
CollectionToolbarProps,
CollectionToolbarSubComponentProperty,
Size,
Variant,
} from './CollectionToolbar.types';
import { CollectionToolbarTitle } from './CollectionToolbarTitle';

export const CollectionToolbar = CompoundComponent(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to include a forwardRef for this component?

Copy link
Collaborator

@stephl3 stephl3 Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is wrapped in forwardRef now, but we're still missing a ref being passed to the root <div>

({
size = Size.Default,
variant = Variant.Default,
className,
children,
}: CollectionToolbarProps) => {
const lgIds = getLgIds();
const title = findChild(
children,
CollectionToolbarSubComponentProperty.Title,
);

const showTitle = title && variant === Variant.Collapsible;

export function CollectionToolbar({
size = Size.Default,
variant = Variant.Default,
className,
children,
}: CollectionToolbarProps) {
return (
<div className={getCollectionToolbarStyles({ size, variant, className })}>
{children}
</div>
);
}
return (
<div
data-testid={lgIds.root}
className={getCollectionToolbarStyles({ size, variant, className })}
>
{showTitle && title}
CollectionToolbar
</div>
);
},
{
displayName: 'CollectionToolbar',
Title: CollectionToolbarTitle,
},
);

CollectionToolbar.displayName = 'CollectionToolbar';
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';

import { DarkModeProps } from '@leafygreen-ui/lib';
import { Size as ImportedSize } from '@leafygreen-ui/tokens';

export const Variant = {
Expand All @@ -15,9 +16,26 @@ export const Size = {
} as const;
export type Size = (typeof Size)[keyof typeof Size];

export interface CollectionToolbarProps {
export interface CollectionToolbarProps extends DarkModeProps {
children?: React.ReactNode;
size?: typeof ImportedSize.Default | typeof ImportedSize.Small;
variant?: Variant;
className?: string;
children?: React.ReactNode;
}

/**
* Static property names used to identify CollectionToolbar compound components.
* These are implementation details for the compound component pattern and should not be exported.
*/
export const CollectionToolbarSubComponentProperty = {
Title: 'isCollectionToolbarTitle',
SearchInput: 'isCollectionToolbarSearchInput',
Actions: 'isCollectionToolbarActions',
Filters: 'isCollectionToolbarFilters',
} as const;

/**
* Type representing the possible static property names for CollectionToolbar sub components.
*/
export type CollectionToolbarSubComponentProperty =
(typeof CollectionToolbarSubComponentProperty)[keyof typeof CollectionToolbarSubComponentProperty];
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { render, screen } from '@testing-library/react';

import { CollectionToolbarSubComponentProperty } from '../CollectionToolbar.types';

import CollectionToolbarTitle from './CollectionToolbarTitle';

describe('packages/collection-toolbar/CollectionToolbarTitle', () => {
test('renders children correctly', () => {
render(<CollectionToolbarTitle>Test Title</CollectionToolbarTitle>);
expect(screen.getByText('Test Title')).toBeInTheDocument();
});

test('renders as an h3 element', () => {
render(<CollectionToolbarTitle>Test Title</CollectionToolbarTitle>);
expect(screen.getByRole('heading', { level: 3 })).toBeInTheDocument();
});

test('applies className to the rendered element', () => {
render(
<CollectionToolbarTitle className="custom-class">
Test Title
</CollectionToolbarTitle>,
);
expect(screen.getByText('Test Title')).toHaveClass('custom-class');
});

test('passes additional props to the rendered element', () => {
render(
<CollectionToolbarTitle data-testid="custom-test-id">
Test Title
</CollectionToolbarTitle>,
);
expect(screen.getByTestId('custom-test-id')).toBeInTheDocument();
});

test('has the correct displayName', () => {
expect(CollectionToolbarTitle.displayName).toBe('CollectionToolbarTitle');
});

test('has the correct static property for compound component identification', () => {
expect(
CollectionToolbarTitle[CollectionToolbarSubComponentProperty.Title],
).toBe(true);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';

import { CompoundSubComponent } from '@leafygreen-ui/compound-component';
import { H3 } from '@leafygreen-ui/typography';

import { CollectionToolbarSubComponentProperty } from '../CollectionToolbar.types';

import { CollectionToolbarTitleProps } from './CollectionToolbarTitle.types';

/**
* CollectionToolbarTitle is a compound component that renders a title for a collection toolbar.
* It will only render if the CollectionToolbar variant is set to Collapsible.
*/
const CollectionToolbarTitle = CompoundSubComponent(
({ className, children, ...rest }: CollectionToolbarTitleProps) => (
<H3 className={className} {...rest}>
{children}
</H3>
),
{
displayName: 'CollectionToolbarTitle',
key: CollectionToolbarSubComponentProperty.Title,
},
);

CollectionToolbarTitle.displayName = 'CollectionToolbarTitle';

export default CollectionToolbarTitle;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ComponentPropsWithRef } from 'react';

import { DarkModeProps } from '@leafygreen-ui/lib';

export interface CollectionToolbarTitleProps
extends ComponentPropsWithRef<'h3'>,
DarkModeProps {
children?: React.ReactNode;
className?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as CollectionToolbarTitle } from './CollectionToolbarTitle';
export { type CollectionToolbarTitleProps } from './CollectionToolbarTitle.types';
4 changes: 4 additions & 0 deletions packages/collection-toolbar/src/CollectionToolbar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ export {
Size,
Variant,
} from './CollectionToolbar.types';
export {
CollectionToolbarTitle,
type CollectionToolbarTitleProps,
} from './CollectionToolbarTitle';
1 change: 1 addition & 0 deletions packages/collection-toolbar/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export {
CollectionToolbar,
type CollectionToolbarProps,
type CollectionToolbarTitleProps,
Size,
Variant,
} from './CollectionToolbar';
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import React from 'react';
import { render } from '@testing-library/react';

import { CollectionToolbar } from '.';

describe('packages/collection-toolbar/getTestUtils', () => {
test('condition', () => {});
});
6 changes: 6 additions & 0 deletions packages/collection-toolbar/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
{
"path": "../tokens"
},
{
"path": "../compound-component"
},
{
"path": "../typography"
},
{
"path": "../../tools/test-harnesses"
}
Expand Down
Loading
Loading