Skip to content

Commit

Permalink
Feat(web-react): Introduce ActionGroup component #DS-1607
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelklibani committed Jan 6, 2025
1 parent 06895ab commit c01dc94
Show file tree
Hide file tree
Showing 25 changed files with 633 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/web-react/scripts/entryPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const entryPoints = [
{ dirs: ['utils'] },
{ dirs: ['components'] },
{ dirs: ['components', 'Accordion'] },
{ dirs: ['components', 'ActionGroup'] },
{ dirs: ['components', 'Alert'] },
{ dirs: ['components', 'Breadcrumbs'] },
{ dirs: ['components', 'Box'] },
Expand Down
37 changes: 37 additions & 0 deletions packages/web-react/src/components/ActionGroup/ActionGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import classNames from 'classnames';
import React, { ReactElement } from 'react';
import { useStyleProps } from '../../hooks';
import { SpiritActionGroupProps } from '../../types';
import { useActionGroupStyleProps } from './useActionGroupStyleProps';

const defaultProps: Partial<SpiritActionGroupProps> = {
direction: 'row',
};

const ActionGroup = (props: SpiritActionGroupProps): ReactElement => {
const { children, ...restProps } = props;
const propsWithDefaults = { ...defaultProps, ...restProps };
const {
classProps,
props: modifiedProps,
styleProps: actionGroupStyle,
} = useActionGroupStyleProps(propsWithDefaults);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);

const actionGroupStyleProps = {
style: {
...styleProps.style,
...actionGroupStyle,
},
};

return (
<div {...otherProps} {...actionGroupStyleProps} className={classNames(classProps, styleProps.className)}>
{children}
</div>
);
};

export default ActionGroup;
1 change: 1 addition & 0 deletions packages/web-react/src/components/ActionGroup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Action Group
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { classNamePrefixProviderTest, restPropsTest, stylePropsTest } from '@local/tests';
import ActionGroup from '../ActionGroup';

describe('ActionGroup', () => {
const text = 'Hello world';
const testId = 'action-group-test-id';

classNamePrefixProviderTest(ActionGroup, 'ActionGroup');

stylePropsTest(ActionGroup);

restPropsTest(ActionGroup, 'div');

it('should render text children', () => {
render(<ActionGroup data-testid={testId}>{text}</ActionGroup>);

expect(screen.getByText(text)).toBeInTheDocument();
expect(screen.getByTestId(testId)).toHaveClass('ActionGroup ActionGroup--row');
});

it('should have direction class name', () => {
render(<ActionGroup direction="column" data-testid={testId} />);

expect(screen.getByTestId(testId)).toHaveClass('ActionGroup--column');
});

it('should have responsive direction class name', () => {
render(<ActionGroup direction={{ mobile: 'row', tablet: 'column', desktop: 'column' }} data-testid={testId} />);

expect(screen.getByTestId(testId)).toHaveClass(
'ActionGroup--row ActionGroup--tablet--column ActionGroup--desktop--column',
);
});

it('should have alignmentX class name', () => {
render(<ActionGroup alignmentX="left" data-testid={testId} />);

expect(screen.getByTestId(testId)).toHaveClass('ActionGroup--alignmentXLeft');
});

it('should have responsive alignmentX class name', () => {
render(<ActionGroup alignmentX={{ mobile: 'left', tablet: 'center', desktop: 'right' }} data-testid={testId} />);

expect(screen.getByTestId(testId)).toHaveClass(
'ActionGroup--alignmentXLeft ActionGroup--tablet--alignmentXCenter ActionGroup--desktop--alignmentXRight',
);
});

it('should render with custom spacing', () => {
render(<ActionGroup spacing="space-600" data-testid={testId} />);

expect(screen.getByTestId(testId)).toHaveStyle({ '--flex-spacing-x': 'var(--spirit-space-600)' });
expect(screen.getByTestId(testId)).toHaveStyle({ '--flex-spacing-y': 'var(--spirit-space-600)' });
});

it('should render with custom spacingX and spacingY', () => {
render(<ActionGroup spacingX="space-500" spacingY="space-1600" data-testid={testId} />);

expect(screen.getByTestId(testId)).toHaveStyle({ '--flex-spacing-x': 'var(--spirit-space-500)' });
expect(screen.getByTestId(testId)).toHaveStyle({ '--flex-spacing-y': 'var(--spirit-space-1600)' });
});

it('should render with custom spacing for each breakpoint', () => {
render(
<ActionGroup
spacing={{ mobile: 'space-100', tablet: 'space-1000', desktop: 'space-1200' }}
data-testid={testId}
/>,
);

const element = screen.getByTestId(testId) as HTMLElement;

expect(element).toHaveStyle({ '--flex-spacing-x': 'var(--spirit-space-100)' });
expect(element).toHaveStyle({ '--flex-spacing-y': 'var(--spirit-space-100)' });
expect(element).toHaveStyle({ '--flex-spacing-x-tablet': 'var(--spirit-space-1000)' });
expect(element).toHaveStyle({ '--flex-spacing-y-tablet': 'var(--spirit-space-1000)' });
expect(element).toHaveStyle({ '--flex-spacing-x-desktop': 'var(--spirit-space-1200)' });
expect(element).toHaveStyle({ '--flex-spacing-y-desktop': 'var(--spirit-space-1200)' });
});

it('should render with custom spacingX and spacingY for each breakpoint', () => {
render(
<ActionGroup
spacingX={{ mobile: 'space-100', tablet: 'space-1000', desktop: 'space-1200' }}
spacingY={{ mobile: 'space-200', tablet: 'space-1100', desktop: 'space-1600' }}
data-testid={testId}
/>,
);

const element = screen.getByTestId(testId) as HTMLElement;

expect(element).toHaveStyle({ '--flex-spacing-x': 'var(--spirit-space-100)' });
expect(element).toHaveStyle({ '--flex-spacing-y': 'var(--spirit-space-200)' });
expect(element).toHaveStyle({ '--flex-spacing-x-tablet': 'var(--spirit-space-1000)' });
expect(element).toHaveStyle({ '--flex-spacing-y-tablet': 'var(--spirit-space-1100)' });
expect(element).toHaveStyle({ '--flex-spacing-x-desktop': 'var(--spirit-space-1200)' });
expect(element).toHaveStyle({ '--flex-spacing-y-desktop': 'var(--spirit-space-1600)' });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { renderHook } from '@testing-library/react';
import { SpiritFlexProps } from '../../../types';
import { useActionGroupStyleProps } from '../useActionGroupStyleProps';

describe('useActionGroupStyleProps', () => {
it.each([
// props, expectedClasses
[{}, 'ActionGroup'],
[{ direction: 'row' }, 'ActionGroup ActionGroup--row'],
[{ direction: 'column' }, 'ActionGroup ActionGroup--column'],
[
{
direction: { mobile: 'row', tablet: 'column', desktop: 'row' },
},
'ActionGroup ActionGroup--row ActionGroup--tablet--column ActionGroup--desktop--row',
],
[{ alignmentX: 'left' }, 'ActionGroup ActionGroup--alignmentXLeft'],
[
{
alignmentX: { mobile: 'left', tablet: 'center', desktop: 'right' },
},
'ActionGroup ActionGroup--alignmentXLeft ActionGroup--tablet--alignmentXCenter ActionGroup--desktop--alignmentXRight',
],
])('should return the correct classes for props %o', (props, expectedClasses) => {
const { result } = renderHook(() => useActionGroupStyleProps(props as SpiritFlexProps));

expect(result.current.classProps).toBe(expectedClasses);
});

it.each([
// props, expectedStyleProps
[{}, {}],
[
{ spacing: 'space-100' },
{ '--flex-spacing-x': 'var(--spirit-space-100)', '--flex-spacing-y': 'var(--spirit-space-100)' },
],
[
{ spacingX: 'space-100', spacingY: 'space-200' },
{ '--flex-spacing-x': 'var(--spirit-space-100)', '--flex-spacing-y': 'var(--spirit-space-200)' },
],
[
{
spacing: { mobile: 'space-100', tablet: 'space-200' },
},
{
'--flex-spacing-x': 'var(--spirit-space-100)',
'--flex-spacing-x-tablet': 'var(--spirit-space-200)',
'--flex-spacing-y': 'var(--spirit-space-100)',
'--flex-spacing-y-tablet': 'var(--spirit-space-200)',
},
],
[
{
spacingX: { mobile: 'space-100', tablet: 'space-200' },
spacingY: { mobile: 'space-300', tablet: 'space-400' },
},
{
'--flex-spacing-x': 'var(--spirit-space-100)',
'--flex-spacing-x-tablet': 'var(--spirit-space-200)',
'--flex-spacing-y': 'var(--spirit-space-300)',
'--flex-spacing-y-tablet': 'var(--spirit-space-400)',
},
],
[
{
spacing: { mobile: 'space-100', tablet: 'space-200', desktop: 'space-400' },
},
{
'--flex-spacing-x': 'var(--spirit-space-100)',
'--flex-spacing-x-tablet': 'var(--spirit-space-200)',
'--flex-spacing-x-desktop': 'var(--spirit-space-400)',
'--flex-spacing-y': 'var(--spirit-space-100)',
'--flex-spacing-y-tablet': 'var(--spirit-space-200)',
'--flex-spacing-y-desktop': 'var(--spirit-space-400)',
},
],
[
{
spacingX: { mobile: 'space-100', tablet: 'space-200', desktop: 'space-400' },
},
{
'--flex-spacing-x': 'var(--spirit-space-100)',
'--flex-spacing-x-tablet': 'var(--spirit-space-200)',
'--flex-spacing-x-desktop': 'var(--spirit-space-400)',
},
],
])('should return the correct style properties for props %o', (props, expectedStyleProps) => {
const { result } = renderHook(() => useActionGroupStyleProps(props as SpiritFlexProps));

expect(result.current.styleProps).toEqual(expectedStyleProps);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const ColumnLayout = () => {
return (
<ActionGroup direction="column">
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
);
};

export default ColumnLayout;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const ColumnReverseLayout = () => {
return (
<ActionGroup direction="column-reverse">
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
);
};

export default ColumnReverseLayout;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const CustomHorizontalSpacing = () => {
return (
<ActionGroup spacingX="space-1200">
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
);
};

export default CustomHorizontalSpacing;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const CustomVerticalSpacing = () => {
return (
<ActionGroup direction="column" spacingY="space-1200">
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
);
};

export default CustomVerticalSpacing;
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const HorizontalAlignment = () => {
return (
<>
<ActionGroup alignmentX="left">
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
<ActionGroup alignmentX="center">
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
<ActionGroup alignmentX="right">
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
</>
);
};

export default HorizontalAlignment;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const CustomHorizontalSpacing = () => {
return (
<ActionGroup spacingX={{ mobile: 'space-700', tablet: 'space-1200' }}>
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
);
};

export default CustomHorizontalSpacing;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const ResponsiveCustomVerticalSpacing = () => {
return (
<ActionGroup direction="column" spacingY={{ mobile: 'space-700', tablet: 'space-1200' }}>
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
);
};

export default ResponsiveCustomVerticalSpacing;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { Button } from '../../Button';
import ActionGroup from '../ActionGroup';

const ResponsiveHorizontalAlignment = () => {
return (
<ActionGroup alignmentX={{ mobile: 'right', tablet: 'center', desktop: 'left' }}>
<Button color="primary">Primary Action</Button>
<Button color="secondary">Secondary Action</Button>
<Button color="tertiary">Tertiary Action</Button>
</ActionGroup>
);
};

export default ResponsiveHorizontalAlignment;
Loading

0 comments on commit c01dc94

Please sign in to comment.