diff --git a/packages/web-react/src/components/Stack/README.md b/packages/web-react/src/components/Stack/README.md
index 0d5379de84..2485e8a321 100644
--- a/packages/web-react/src/components/Stack/README.md
+++ b/packages/web-react/src/components/Stack/README.md
@@ -28,17 +28,37 @@ Advanced example usage:
```
+## Custom Spacing
+
+You can use the `spacing` prop to apply custom spacing between items. The prop
+accepts either a spacing token (eg. `space-100`) or an object with breakpoint keys and spacing token values.
+
+```jsx
+
+ Block 1
+ Block 2
+ Block 3
+
+
+
+ Block 1
+ Block 2
+ Block 3
+
+```
+
## API
-| Name | Type | Default | Required | Description |
-| ------------------------- | --------------- | ------- | -------- | -------------------------------------- |
-| `elementType` | `string` | `div` | ✕ | Element type of the wrapper element |
-| `hasEndDivider` | `bool` | `false` | ✕ | Render a divider after the last item |
-| `hasIntermediateDividers` | `bool` | `false` | ✕ | Render dividers between items |
-| `hasSpacing` | `bool` | `false` | ✕ | Apply a spacing between items |
-| `hasStartDivider` | `bool` | `false` | ✕ | Render a divider before the first item |
-| `UNSAFE_className` | `string` | — | ✕ | Wrapper custom class name |
-| `UNSAFE_style` | `CSSProperties` | — | ✕ | Wrapper custom style |
+| Name | Type | Default | Required | Description |
+| ------------------------- | ---------------------------------------------------------------- | ------- | -------- | ------------------------------------------------------------------- |
+| `elementType` | `string` | `div` | ✕ | Element type of the wrapper element |
+| `hasEndDivider` | `bool` | `false` | ✕ | Render a divider after the last item |
+| `hasIntermediateDividers` | `bool` | `false` | ✕ | Render dividers between items |
+| `hasSpacing` | `bool` | `false` | ✕ | Apply a spacing between items |
+| `hasStartDivider` | `bool` | `false` | ✕ | Render a divider before the first item |
+| `spacing` | [`SpaceToken` \| `Partial>`] | — | ✕ | Custom spacing between items, see [Custom Spacing](#custom-spacing) |
+| `UNSAFE_className` | `string` | — | ✕ | Wrapper custom class name |
+| `UNSAFE_style` | `CSSProperties` | — | ✕ | Wrapper custom style |
You can add `id`, `data-*` or `aria-*` attributes to further extend component's
descriptiveness and accessibility.
diff --git a/packages/web-react/src/components/Stack/Stack.tsx b/packages/web-react/src/components/Stack/Stack.tsx
index 62a17ae708..e5fd5e917f 100644
--- a/packages/web-react/src/components/Stack/Stack.tsx
+++ b/packages/web-react/src/components/Stack/Stack.tsx
@@ -14,11 +14,18 @@ const defaultProps: SpiritStackProps = {
export const Stack = (props: SpiritStackProps): JSX.Element => {
const { elementType: ElementTag = 'div', children, ...restProps } = props;
- const { classProps, props: modifiedProps } = useStackStyleProps(restProps);
+ const { classProps, props: modifiedProps, styleProps: stackStyle } = useStackStyleProps(restProps);
const { styleProps, props: otherProps } = useStyleProps(modifiedProps);
+ const stackStyleProps = {
+ style: {
+ ...styleProps.style,
+ ...stackStyle,
+ },
+ };
+
return (
-
+
{children}
);
diff --git a/packages/web-react/src/components/Stack/__tests__/Stack.test.tsx b/packages/web-react/src/components/Stack/__tests__/Stack.test.tsx
index 95debab940..790b249064 100644
--- a/packages/web-react/src/components/Stack/__tests__/Stack.test.tsx
+++ b/packages/web-react/src/components/Stack/__tests__/Stack.test.tsx
@@ -19,4 +19,60 @@ describe('Stack', () => {
const element = dom.container.querySelector('div') as HTMLElement;
expect(element.textContent).toBe('Hello World');
});
+
+ it('should render element children', () => {
+ const dom = render(
+
+ Child 1
+ Child 2
+ ,
+ );
+
+ const element = dom.container.querySelector('div') as HTMLElement;
+ expect(element.children).toHaveLength(2);
+ expect(element.children[0].textContent).toBe('Child 1');
+ expect(element.children[1].textContent).toBe('Child 2');
+ });
+
+ it('should render a ul element', () => {
+ const dom = render();
+
+ const element = dom.container.querySelector('ul') as HTMLElement;
+ expect(element).toBeInTheDocument();
+ });
+
+ it('should render with spacing', () => {
+ const dom = render();
+
+ const element = dom.container.querySelector('div') as HTMLElement;
+ expect(element).toHaveClass('Stack--hasSpacing');
+ });
+
+ it('should render with custom spacing', () => {
+ const dom = render();
+
+ const element = dom.container.querySelector('div') as HTMLElement;
+ expect(element).toHaveClass('Stack--hasSpacing');
+ expect(element).toHaveStyle({ '--stack-spacing': 'var(--spirit-space-1000)' });
+ });
+
+ it('should render with custom spacing for each breakpoint', () => {
+ const dom = render(
+ ,
+ );
+
+ const element = dom.container.querySelector('div') as HTMLElement;
+ expect(element).toHaveClass('Stack--hasSpacing');
+ expect(element).toHaveStyle({ '--stack-spacing': 'var(--spirit-space-100)' });
+ expect(element).toHaveStyle({ '--stack-spacing-tablet': 'var(--spirit-space-1000)' });
+ expect(element).toHaveStyle({ '--stack-spacing-desktop': 'var(--spirit-space-1200)' });
+ });
+
+ it('should render with custom spacing for only one breakpoint', () => {
+ const dom = render();
+
+ const element = dom.container.querySelector('div') as HTMLElement;
+ expect(element).toHaveClass('Stack--hasSpacing');
+ expect(element).toHaveStyle({ '--stack-spacing-tablet': 'var(--spirit-space-1000)' });
+ });
});
diff --git a/packages/web-react/src/components/Stack/__tests__/useStackStyleProps.test.ts b/packages/web-react/src/components/Stack/__tests__/useStackStyleProps.test.ts
index db73bd42d2..2d944211c1 100644
--- a/packages/web-react/src/components/Stack/__tests__/useStackStyleProps.test.ts
+++ b/packages/web-react/src/components/Stack/__tests__/useStackStyleProps.test.ts
@@ -24,4 +24,24 @@ describe('useStackStyleProps', () => {
expect(result.current.classProps).toBe(expectedClasses);
},
);
+
+ it.each([
+ // spacing, expectedStyle
+ [undefined, {}],
+ ['space-100', { '--stack-spacing': 'var(--spirit-space-100)' }],
+ [{ tablet: 'space-100' }, { '--stack-spacing-tablet': 'var(--spirit-space-100)' }],
+ [
+ { mobile: 'space-100', tablet: 'space-200', desktop: 'space-300' },
+ {
+ '--stack-spacing': 'var(--spirit-space-100)',
+ '--stack-spacing-tablet': 'var(--spirit-space-200)',
+ '--stack-spacing-desktop': 'var(--spirit-space-300)',
+ },
+ ],
+ ])('should return style', (spacing, expectedStyle) => {
+ const props = { spacing } as SpiritStackProps;
+ const { result } = renderHook(() => useStackStyleProps(props));
+
+ expect(result.current.styleProps).toEqual(expectedStyle);
+ });
});
diff --git a/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacing.tsx b/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacing.tsx
new file mode 100644
index 0000000000..7dae8e03ee
--- /dev/null
+++ b/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacing.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import DocsBox from '../../../../docs/DocsBox';
+import Stack from '../Stack';
+
+const StackBlocksWithCustomSpacing = () => (
+
+ {[1, 2, 3].map((i) => (
+
+ Block {i}
+
+ ))}
+
+);
+
+export default StackBlocksWithCustomSpacing;
diff --git a/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacingForEachBreakpoint.tsx b/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacingForEachBreakpoint.tsx
new file mode 100644
index 0000000000..993ed8c4f5
--- /dev/null
+++ b/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacingForEachBreakpoint.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import DocsBox from '../../../../docs/DocsBox';
+import Stack from '../Stack';
+
+const StackBlocksWithCustomSpacingForEachBreakpoint = () => (
+
+ {[1, 2, 3].map((i) => (
+
+ Block {i}
+
+ ))}
+
+);
+
+export default StackBlocksWithCustomSpacingForEachBreakpoint;
diff --git a/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacingFromTabletBreakpoint.tsx b/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacingFromTabletBreakpoint.tsx
new file mode 100644
index 0000000000..a5646dcf06
--- /dev/null
+++ b/packages/web-react/src/components/Stack/demo/StackBlocksWithCustomSpacingFromTabletBreakpoint.tsx
@@ -0,0 +1,15 @@
+import React from 'react';
+import DocsBox from '../../../../docs/DocsBox';
+import Stack from '../Stack';
+
+const StackBlocksWithCustomSpacingFromTabletBreakpoint = () => (
+
+ {[1, 2, 3].map((i) => (
+
+ Block {i}
+
+ ))}
+
+);
+
+export default StackBlocksWithCustomSpacingFromTabletBreakpoint;
diff --git a/packages/web-react/src/components/Stack/demo/index.tsx b/packages/web-react/src/components/Stack/demo/index.tsx
index 2e8da8e9d6..899424cdc7 100644
--- a/packages/web-react/src/components/Stack/demo/index.tsx
+++ b/packages/web-react/src/components/Stack/demo/index.tsx
@@ -9,6 +9,9 @@ import StackBlocksWithVerticalSpacing from './StackBlocksWithVerticalSpacing';
import StackBlocksWithInnerDividersAndVerticalSpacing from './StackBlocksWithInnerDividersAndVerticalSpacing';
import StackBlocksWithInnerAndOuterDividersAndVerticalSpacing from './StackBlocksWithInnerAndOuterDividersAndVerticalSpacing';
import StackBlocksWithInnerDividersWithoutVerticalSpacing from './StackBlocksWithInnerDividersWithoutVerticalSpacing';
+import StackBlocksWithCustomSpacing from './StackBlocksWithCustomSpacing';
+import StackBlocksWithCustomSpacingFromTabletBreakpoint from './StackBlocksWithCustomSpacingFromTabletBreakpoint';
+import StackBlocksWithCustomSpacingForEachBreakpoint from './StackBlocksWithCustomSpacingForEachBreakpoint';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
@@ -30,5 +33,14 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
+
+
+
+
+
+
+
+
+
,
);
diff --git a/packages/web-react/src/components/Stack/stories/Stack.stories.tsx b/packages/web-react/src/components/Stack/stories/Stack.stories.tsx
index d7538906fa..4f659a498d 100644
--- a/packages/web-react/src/components/Stack/stories/Stack.stories.tsx
+++ b/packages/web-react/src/components/Stack/stories/Stack.stories.tsx
@@ -45,6 +45,9 @@ const meta: Meta = {
defaultValue: { summary: false },
},
},
+ spacing: {
+ control: 'object',
+ },
},
args: {
children: (
@@ -65,6 +68,10 @@ const meta: Meta = {
hasIntermediateDividers: false,
hasSpacing: false,
hasStartDivider: false,
+ spacing: {
+ mobile: 'space-500',
+ tablet: 'space-600',
+ },
},
};
diff --git a/packages/web-react/src/components/Stack/useStackStyleProps.ts b/packages/web-react/src/components/Stack/useStackStyleProps.ts
index 5cefd17b06..06d7c1617e 100644
--- a/packages/web-react/src/components/Stack/useStackStyleProps.ts
+++ b/packages/web-react/src/components/Stack/useStackStyleProps.ts
@@ -1,17 +1,23 @@
import classNames from 'classnames';
-import { ElementType } from 'react';
+import { CSSProperties, ElementType } from 'react';
import { useClassNamePrefix } from '../../hooks';
import { SpiritStackProps } from '../../types';
+interface StackCSSProperties extends CSSProperties {
+ [key: string]: string | undefined | number;
+}
+
export interface StackStyles {
/** className props */
classProps: string;
/** props to be passed to the element */
props: SpiritStackProps;
+ /** Style props for the element */
+ styleProps: StackCSSProperties;
}
export function useStackStyleProps(props: SpiritStackProps): StackStyles {
- const { hasEndDivider, hasIntermediateDividers, hasSpacing, hasStartDivider, ...restProps } = props;
+ const { hasEndDivider, hasIntermediateDividers, hasSpacing, hasStartDivider, spacing, ...restProps } = props;
const StackClass = useClassNamePrefix('Stack');
const StackBottomDividerClass = `${StackClass}--hasEndDivider`;
@@ -25,8 +31,22 @@ export function useStackStyleProps(props: SpiritS
[StackTopDividerClass]: hasStartDivider,
});
+ const stackStyle: StackCSSProperties = {};
+
+ if (typeof spacing === 'object' && spacing !== null) {
+ Object.keys(spacing).forEach((key) => {
+ const suffix = key === 'mobile' ? '' : `-${key}`;
+ (stackStyle as Record)[`--stack-spacing${suffix}`] = `var(--spirit-${spacing[
+ key as keyof typeof spacing
+ ]?.toString()})`;
+ });
+ } else if (spacing) {
+ (stackStyle as Record)['--stack-spacing'] = `var(--spirit-${spacing})`;
+ }
+
return {
classProps,
props: restProps,
+ styleProps: stackStyle,
};
}
diff --git a/packages/web-react/src/types/shared/index.ts b/packages/web-react/src/types/shared/index.ts
index 3ee36dedca..5b2edce57d 100644
--- a/packages/web-react/src/types/shared/index.ts
+++ b/packages/web-react/src/types/shared/index.ts
@@ -13,6 +13,7 @@ export * from './events';
export * from './inputs';
export * from './item';
export * from './style';
+export * from './tokens';
export * from './refs';
export * from './rest';
diff --git a/packages/web-react/src/types/shared/tokens.ts b/packages/web-react/src/types/shared/tokens.ts
new file mode 100644
index 0000000000..f06f696151
--- /dev/null
+++ b/packages/web-react/src/types/shared/tokens.ts
@@ -0,0 +1,4 @@
+import { breakpoints, space } from '@lmc-eu/spirit-design-tokens';
+
+export type BreakpointToken = keyof typeof breakpoints;
+export type SpaceToken = `${'space-'}${keyof typeof space}`;
diff --git a/packages/web-react/src/types/stack.ts b/packages/web-react/src/types/stack.ts
index b727c20d86..3d2eb048df 100644
--- a/packages/web-react/src/types/stack.ts
+++ b/packages/web-react/src/types/stack.ts
@@ -1,5 +1,11 @@
import { ElementType } from 'react';
-import { ChildrenProps, StyleProps, SpiritPolymorphicElementPropsWithoutRef } from './shared';
+import {
+ BreakpointToken,
+ ChildrenProps,
+ SpaceToken,
+ SpiritPolymorphicElementPropsWithoutRef,
+ StyleProps,
+} from './shared';
export interface StackBaseProps extends ChildrenProps, StyleProps {
/** Whether the Stack has divider on the end */
@@ -10,12 +16,13 @@ export interface StackBaseProps extends ChildrenProps, StyleProps {
hasSpacing?: boolean;
/** Whether the Stack has divider on the start */
hasStartDivider?: boolean;
+ /** Custom spacing between items */
+ spacing?: SpaceToken | Partial>;
}
export type StackProps = {
/**
* The HTML element or React element used to render the stack, e.g. 'div'.
- *
* @default 'div'
*/
elementType?: E;