Skip to content
Closed
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
8 changes: 6 additions & 2 deletions packages/collection-toolbar/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

{
"name": "@leafygreen-ui/collection-toolbar",
"version": "0.0.1",
Expand Down Expand Up @@ -28,10 +27,15 @@
"access": "public"
},
"dependencies": {
"@leafygreen-ui/button": "workspace:^",
"@leafygreen-ui/compound-component": "workspace:^",
"@leafygreen-ui/emotion": "workspace:^",
"@leafygreen-ui/icon": "workspace:^",
"@leafygreen-ui/icon-button": "workspace:^",
"@leafygreen-ui/lib": "workspace:^",
"@leafygreen-ui/tokens": "workspace:^",
"@lg-tools/test-harnesses": "workspace:^"
"@lg-tools/test-harnesses": "workspace:^",
"@leafygreen-ui/hooks": "workspace:^"
},
"peerDependencies": {
"@leafygreen-ui/leafygreen-provider": "workspace:^5.0.0 || ^4.0.0 || ^3.2.0"
Expand Down
38 changes: 36 additions & 2 deletions packages/collection-toolbar/src/CollectionToolbar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import React from 'react';
import { storybookArgTypes, StoryMetaType } from '@lg-tools/storybook-utils';
import { StoryFn } from '@storybook/react';

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

const meta: StoryMetaType<typeof CollectionToolbar> = {
title: 'Components/CollectionToolbar',
Expand All @@ -28,6 +33,22 @@ const meta: StoryMetaType<typeof CollectionToolbar> = {
options: Object.values(Variant),
},
},
decorators: [
(Story: StoryFn) => {
return (
<div
style={{
background: 'lightblue',
width: '100vw',
height: '100%',
margin: '-100px',
}}
>
<Story />
</div>
);
},
],
args: {
children: 'Collection Toolbar',
},
Expand All @@ -36,5 +57,18 @@ const meta: StoryMetaType<typeof CollectionToolbar> = {
export default meta;

export const LiveExample: StoryFn<typeof CollectionToolbar> = props => (
<CollectionToolbar {...props} />
<CollectionToolbar {...props}>
<CollectionToolbar.Actions>
<CollectionToolbar.Actions.Button
variant={CollectionToolbarActionsButtonVariant.Default}
>
Default Button
</CollectionToolbar.Actions.Button>
<CollectionToolbar.Actions.Button
variant={CollectionToolbarActionsButtonVariant.Primary}
>
Primary Button
</CollectionToolbar.Actions.Button>
</CollectionToolbar.Actions>
</CollectionToolbar>
);
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { css, cx } from '@leafygreen-ui/emotion';
import { spacing } from '@leafygreen-ui/tokens';

import { Size, Variant } from './CollectionToolbar.types';

export const baseStyles = css``;
export const baseStyles = css`
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: ${spacing[200]}px;
align-items: center;
justify-content: flex-end;
`;

export const getCollectionToolbarStyles = ({
size,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,59 @@
import React from 'react';
import React, { useState } 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 { CollectionToolbarActions } from './CollectionToolbarActions';
import { CollectionToolbarContextProvider } from './CollectionToolbarContext';

export const CollectionToolbar = CompoundComponent(
({
size = Size.Default,
variant = Variant.Default,
className,
children,
}: CollectionToolbarProps) => {
const lgIds = getLgIds();
const [isCollapsed, setIsCollapsed] = useState(false);

const toggleCollapse = () => {
setIsCollapsed(curr => !curr);
};

export function CollectionToolbar({
size = Size.Default,
variant = Variant.Default,
className,
children,
}: CollectionToolbarProps) {
return (
<div className={getCollectionToolbarStyles({ size, variant, className })}>
{children}
</div>
);
}
const Actions = findChild(
children,
CollectionToolbarSubComponentProperty.Actions,
);

CollectionToolbar.displayName = 'CollectionToolbar';
return (
<CollectionToolbarContextProvider
variant={variant}
isCollapsed={isCollapsed}
onCollapse={toggleCollapse}
size={size}
>
<div
data-testid={lgIds.root}
className={getCollectionToolbarStyles({ size, variant, className })}
>
{Actions}
</div>
</CollectionToolbarContextProvider>
);
},
{
displayName: 'CollectionToolbar',
Actions: CollectionToolbarActions,
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,24 @@ export interface CollectionToolbarProps {
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',
ActionsButtons: 'isCollectionToolbarActionsButtons',
ActionsMenu: 'isCollectionToolbarActionsMenu',
ActionsMenuItems: 'isCollectionToolbarActionsMenuItems',
ActionsPaginationView: 'isCollectionToolbarActionsPaginationView',
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,11 @@
import React from 'react';
import { render, screen } from '@testing-library/react';

import { CollectionToolbarActions } from '.';

describe('packages/collection-toolbar-actions', () => {
test('renders correctly', () => {
render(<CollectionToolbarActions />);
expect(screen.getByText('Collection Toolbar Actions')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { css, cx } from '@leafygreen-ui/emotion';
import { spacing } from '@leafygreen-ui/tokens';

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

export const baseStyles = css`
display: flex;
flex-direction: row;
gap: ${spacing[200]}px;
align-items: center;
justify-content: flex-end;
`;

export const getCollectionToolbarActionsStyles = ({
className,
}: {
variant?: Variant;
className?: string;
}) => cx(baseStyles, className);
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';

import {
CompoundSubComponent,
findChildren,
} from '@leafygreen-ui/compound-component';
import { Icon } from '@leafygreen-ui/icon';
import { IconButton } from '@leafygreen-ui/icon-button';
import { consoleOnce } from '@leafygreen-ui/lib';

import { CollectionToolbarActionsSubComponentProperty } from '../../shared.types';
import { getLgIds } from '../../utils/getLgIds';
import {
CollectionToolbarSubComponentProperty,
Variant,
} from '../CollectionToolbar.types';
import { useCollectionToolbarContext } from '../CollectionToolbarContext';

import { getCollectionToolbarActionsStyles } from './CollectionToolbarActions.styles';
import { CollectionToolbarActionsProps } from './CollectionToolbarActions.types';
import { CollectionToolbarActionsButton } from './CollectionToolbarActionsButton';

export const CollectionToolbarActions = CompoundSubComponent(
({ children, className }: CollectionToolbarActionsProps) => {
const lgIds = getLgIds();
const { onCollapse, variant } = useCollectionToolbarContext();

const showToggleButton = variant === Variant.Collapsible;

const Buttons = findChildren(
children,
CollectionToolbarActionsSubComponentProperty.Button,
);

if (Buttons.length > 2) {
consoleOnce.error(
'CollectionToolbarActions can only have up to 2 buttons',
);
}

const PrimaryButtons = Buttons.slice(0, 2);

return (
<div
data-testid={lgIds.actions}
className={getCollectionToolbarActionsStyles({ className, variant })}
>
{PrimaryButtons}
{showToggleButton && (
<IconButton
data-testid={lgIds.toggleButton}
onClick={onCollapse}
aria-label="Toggle collapse"
>
<Icon glyph="ChevronDown" />
</IconButton>
)}
</div>
);
},
{
displayName: 'CollectionToolbarActions',
key: CollectionToolbarSubComponentProperty.Actions,
Button: CollectionToolbarActionsButton,
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { CollectionToolbarProps } from '../CollectionToolbar.types';

export interface CollectionToolbarActionsProps
extends Pick<CollectionToolbarProps, 'children' | 'variant' | 'className'> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';

import { Button } from '@leafygreen-ui/button';
import { CompoundSubComponent } from '@leafygreen-ui/compound-component';

import { useCollectionToolbarContext } from '../../CollectionToolbarContext';
import { CollectionToolbarActionsSubComponentProperty } from '../CollectionToolbarActions.types';

import { CollectionToolbarActionsButtonProps } from './CollectionToolbarActionsButton.types';

export const CollectionToolbarActionsButton = CompoundSubComponent(
({ ...props }: CollectionToolbarActionsButtonProps) => {
const { size } = useCollectionToolbarContext();
return <Button size={size} {...props} />;
},
{
displayName: 'CollectionToolbarActionsButton',
key: CollectionToolbarActionsSubComponentProperty.Button,
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ButtonProps, Variant as ButtonVariant } from '@leafygreen-ui/button';

export type CollectionToolbarActionsButtonProps = Omit<ButtonProps, 'size'>;
export const CollectionToolbarActionsButtonVariant = ButtonVariant;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { CollectionToolbarActionsButton } from './CollectionToolbarActionsButton';
export type { CollectionToolbarActionsButtonProps } from './CollectionToolbarActionsButton.types';
export { CollectionToolbarActionsButtonVariant } from './CollectionToolbarActionsButton.types';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { CollectionToolbarActions } from './CollectionToolbarActions';
export { type CollectionToolbarActionsProps } from './CollectionToolbarActions.types';
export {
CollectionToolbarActionsButton,
type CollectionToolbarActionsButtonProps,
CollectionToolbarActionsButtonVariant,
} from './CollectionToolbarActionsButton';
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, {
createContext,
PropsWithChildren,
useContext,
useMemo,
} from 'react';

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

export interface CollectionToolbarContextProps
extends Pick<CollectionToolbarProps, 'variant' | 'size'> {
isCollapsed: boolean;
onCollapse?: () => void;
}

const CollectionToolbarContext = createContext<CollectionToolbarContextProps>({
isCollapsed: false,
});

export const useCollectionToolbarContext = () => {
const context = useContext(CollectionToolbarContext);

if (!context) {
throw new Error(
'useCollectionToolbarContext must be used within a CollectionToolbarContextProvider',
);
}

return context;
};

export const CollectionToolbarContextProvider = ({
children,
variant,
size,
isCollapsed,
onCollapse,
}: PropsWithChildren<CollectionToolbarContextProps>) => {
const contextValue = useMemo(
() => ({ variant, size, isCollapsed, onCollapse }),
[variant, size, isCollapsed, onCollapse],
);

return (
<CollectionToolbarContext.Provider value={contextValue}>
{children}
</CollectionToolbarContext.Provider>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export {
CollectionToolbarContextProvider,
useCollectionToolbarContext,
} from './CollectionToolbarContext';
Loading
Loading