Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat(web-react): Introduce ActionGroup component #DS-1621 #1843

Draft
wants to merge 2 commits into
base: feat/ds-1607-flex-extended
Choose a base branch
from
Draft
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
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
36 changes: 36 additions & 0 deletions packages/web-react/src/components/ActionGroup/ActionGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client';

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

const defaultProps: Partial<SpiritActionGroupProps> = {
alignmentX: AlignmentX.LEFT,
direction: {
mobile: DirectionExtended.VERTICAL,
tablet: DirectionExtended.HORIZONTAL_REVERSE,
},
};

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

return (
<Flex
{...otherProps}
UNSAFE_className={classNames(styleProps.className, classProps.root)}
UNSAFE_style={styleProps.style}
>
{children}
</Flex>
);
};

export default ActionGroup;
30 changes: 30 additions & 0 deletions packages/web-react/src/components/ActionGroup/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# ActionGroup

ActionGroup component is a container component that is used to position action components in a predefined layout.

- For desktop and tablet views, actions are positioned in a horizontal direction with reverse order.
- For mobile view, actions are positioned in a vertical layout and are stretched to the full width of the container.

```jsx
<ActionGroup>
<!-- Actions components goes here -->
</ActionGroup>
```

## Full Example

```jsx
<ActionGroup>
<ButtonLink href="#" color="primary">
Primary Action
</ButtonLink>
<ButtonLink href="#" color="secondary">
Secondary Action
</ButtonLink>
</ActionGroup>
```

This component uses the `Flex` component internally.
For more customization options, refer to the props available in the [Flex documentation][web-react-flex-documentation].

[web-react-flex-documentation]: https://github.com/lmc-eu/spirit-design-system/blob/main/packages/web-react/src/components/Flex/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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(
'Flex Flex--noWrap Flex--alignmentXLeft Flex--alignmentYStretch Flex--vertical Flex--tablet--horizontalReverse ActionGroup',
);
});

it('should transfer props to Flex', () => {
render(
<ActionGroup
data-testid={testId}
direction="vertical"
alignmentX="right"
alignmentY="center"
spacing="space-100"
isWrapping
>
{text}
</ActionGroup>,
);

const element = screen.getByTestId(testId);

expect(screen.getByText(text)).toBeInTheDocument();
expect(element).toHaveClass(
'Flex Flex--wrap Flex--alignmentXRight Flex--alignmentYCenter Flex--vertical ActionGroup',
);
expect(element).toHaveStyle({
'--flex-spacing-x': 'var(--spirit-space-100)',
'--flex-spacing-y': 'var(--spirit-space-100)',
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { renderHook } from '@testing-library/react';
import { useActionGroupStyleProps } from '../useActionGroupStyleProps';

describe('useActionGroupStyleProps', () => {
it('should return defaults', () => {
const props = {};
const { result } = renderHook(() => useActionGroupStyleProps(props));

expect(result.current.classProps.root).toBe('ActionGroup');
});
});
21 changes: 21 additions & 0 deletions packages/web-react/src/components/ActionGroup/demo/Basic.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { ButtonLink } from '../../ButtonLink';
import ActionGroup from '../ActionGroup';

const BasicLayout = () => {
return (
<ActionGroup>
<ButtonLink href="#" color="primary">
Primary Action
</ButtonLink>
<ButtonLink href="#" color="secondary">
Secondary Action
</ButtonLink>
<ButtonLink href="#" color="tertiary">
Tertiary Action
</ButtonLink>
</ActionGroup>
);
};

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

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

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

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

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

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

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

const HorizontalLayout = () => {
return (
<ActionGroup direction="horizontal">
<ButtonLink href="#" color="primary">
Primary Action
</ButtonLink>
<ButtonLink href="#" color="secondary">
Secondary Action
</ButtonLink>
<ButtonLink href="#" color="tertiary">
Tertiary Action
</ButtonLink>
</ActionGroup>
);
};

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

const HorizontalReverseLayout = () => {
return (
<ActionGroup direction="horizontal-reverse">
<ButtonLink href="#" color="primary">
Primary Action
</ButtonLink>
<ButtonLink href="#" color="secondary">
Secondary Action
</ButtonLink>
<ButtonLink href="#" color="tertiary">
Tertiary Action
</ButtonLink>
</ActionGroup>
);
};

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

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

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

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

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

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

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

const ResponsiveHorizontalReverseLayout = () => {
return (
<ActionGroup direction={{ mobile: 'horizontal-reverse', tablet: 'horizontal', desktop: 'horizontal' }}>
<ButtonLink href="#" color="primary">
Primary Action
</ButtonLink>
<ButtonLink href="#" color="secondary">
Secondary Action
</ButtonLink>
<ButtonLink href="#" color="tertiary">
Tertiary Action
</ButtonLink>
</ActionGroup>
);
};

export default ResponsiveHorizontalReverseLayout;
Loading
Loading