Skip to content

Commit f0c840d

Browse files
authored
Merge branch 'main' into feat/AR-53845-make-button-v3-responsive
2 parents 788e22e + 30c4bfd commit f0c840d

23 files changed

Lines changed: 617 additions & 100 deletions

.changeset/add-ds-stack.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@drivenets/design-system': minor
3+
---
4+
5+
Add `DsStack` layout component

.changeset/fiery-walls-taste.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@drivenets/eslint-plugin-design-system': patch
3+
---
4+
5+
Fix package name in installation instructions

.changeset/silent-seas-rhyme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@drivenets/design-system': patch
3+
---
4+
5+
Fix `DsTable` bulk action hides table data

.changeset/strong-toys-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@drivenets/eslint-plugin-design-system': patch
3+
---
4+
5+
Refine the JSX reporting range to reduce noise

packages/design-system/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,8 @@
7272
"@tanstack/react-virtual": "^3.13.23",
7373
"@zag-js/react": "^1.37.0",
7474
"@zag-js/steps": "^1.37.0",
75-
"classnames": "^2.5.1"
75+
"classnames": "^2.5.1",
76+
"csstype": "^3.2.3"
7677
},
7778
"peerDependencies": {
7879
"@internationalized/date": "^3.0.0",
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { page } from 'vitest/browser';
3+
4+
import { DsStack } from '../ds-stack';
5+
6+
describe('DsStack', () => {
7+
it('renders children in a flex column by default', async () => {
8+
await page.render(
9+
<DsStack>
10+
<button type="button">First</button>
11+
<button type="button">Second</button>
12+
</DsStack>,
13+
);
14+
15+
const first = page.getByRole('button', { name: 'First' });
16+
const second = page.getByRole('button', { name: 'Second' });
17+
await expect.element(first).toBeVisible();
18+
await expect.element(second).toBeVisible();
19+
20+
const firstRect = first.element().getBoundingClientRect();
21+
const secondRect = second.element().getBoundingClientRect();
22+
expect(secondRect.top).toBeGreaterThanOrEqual(firstRect.bottom);
23+
});
24+
25+
it('renders children in a row when direction is "row"', async () => {
26+
await page.render(
27+
<DsStack direction="row">
28+
<button type="button">Left</button>
29+
<button type="button">Right</button>
30+
</DsStack>,
31+
);
32+
33+
const left = page.getByRole('button', { name: 'Left' });
34+
const right = page.getByRole('button', { name: 'Right' });
35+
36+
const leftRect = left.element().getBoundingClientRect();
37+
const rightRect = right.element().getBoundingClientRect();
38+
expect(rightRect.left).toBeGreaterThanOrEqual(leftRect.right);
39+
});
40+
41+
it('applies gap between children', async () => {
42+
await page.render(
43+
<DsStack direction="row" gap={20}>
44+
<button type="button">A</button>
45+
<button type="button">B</button>
46+
</DsStack>,
47+
);
48+
49+
const a = page.getByRole('button', { name: 'A' });
50+
const b = page.getByRole('button', { name: 'B' });
51+
52+
const aRect = a.element().getBoundingClientRect();
53+
const bRect = b.element().getBoundingClientRect();
54+
const actualGap = bRect.left - aRect.right;
55+
expect(actualGap).toBeCloseTo(20, 0);
56+
});
57+
58+
it('applies alignItems and justifyContent', async () => {
59+
await page.render(
60+
<DsStack
61+
direction="row"
62+
justifyContent="center"
63+
alignItems="center"
64+
style={{ height: 200, width: 400 }}
65+
>
66+
<button type="button">Centered</button>
67+
</DsStack>,
68+
);
69+
70+
const container = page.getByRole('button', { name: 'Centered' }).element().parentElement;
71+
expect(container).toBeTruthy();
72+
const containerStyle = getComputedStyle(container as Element);
73+
expect(containerStyle.justifyContent).toBe('center');
74+
expect(containerStyle.alignItems).toBe('center');
75+
});
76+
77+
it('applies flex, flexWrap, and width', async () => {
78+
await page.render(
79+
<DsStack flex="1" flexWrap="wrap" width="500px">
80+
<button type="button">Child</button>
81+
</DsStack>,
82+
);
83+
84+
const container = page.getByRole('button', { name: 'Child' }).element().parentElement;
85+
expect(container).toBeTruthy();
86+
const containerStyle = getComputedStyle(container as Element);
87+
expect(containerStyle.flexWrap).toBe('wrap');
88+
expect(containerStyle.width).toBe('500px');
89+
});
90+
91+
it('merges custom className and style', async () => {
92+
await page.render(
93+
<DsStack className="custom-class" style={{ padding: 12 }}>
94+
<button type="button">Styled</button>
95+
</DsStack>,
96+
);
97+
98+
const container = page.getByRole('button', { name: 'Styled' }).element().parentElement as HTMLElement;
99+
expect(container).toBeTruthy();
100+
expect(container.classList.contains('custom-class')).toBe(true);
101+
expect(container.style.padding).toBe('12px');
102+
});
103+
104+
it('forwards ref to the root div', async () => {
105+
let refElement: HTMLDivElement | null = null;
106+
107+
await page.render(
108+
<DsStack
109+
ref={(el) => {
110+
refElement = el;
111+
}}
112+
>
113+
<button type="button">Ref test</button>
114+
</DsStack>,
115+
);
116+
117+
await expect.element(page.getByRole('button', { name: 'Ref test' })).toBeVisible();
118+
expect(refElement).toBeInstanceOf(HTMLDivElement);
119+
});
120+
121+
it('user style overrides layout props', async () => {
122+
await page.render(
123+
<DsStack gap={10} style={{ gap: 30 }}>
124+
<button type="button">X</button>
125+
<button type="button">Y</button>
126+
</DsStack>,
127+
);
128+
129+
const container = page.getByRole('button', { name: 'X' }).element().parentElement as HTMLElement;
130+
expect(container).toBeTruthy();
131+
expect(container.style.gap).toBe('30px');
132+
});
133+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.root {
2+
display: flex;
3+
flex-direction: column;
4+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
.box {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
min-width: 80px;
6+
padding: var(--xs) var(--sm);
7+
border: 1px solid var(--color-border-default);
8+
border-radius: 4px;
9+
background-color: var(--color-bg-surface-1);
10+
font-size: var(--font-size-body-md);
11+
}
12+
13+
.container {
14+
width: 600px;
15+
}
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite';
2+
import { DsStack } from './index';
3+
import styles from './ds-stack.stories.module.scss';
4+
5+
const Box = ({ children }: { children: React.ReactNode }) => <div className={styles.box}>{children}</div>;
6+
7+
const meta: Meta<typeof DsStack> = {
8+
title: 'Design System/Stack',
9+
component: DsStack,
10+
parameters: {
11+
layout: 'centered',
12+
},
13+
argTypes: {
14+
direction: { control: 'select', options: ['row', 'column', 'row-reverse', 'column-reverse'] },
15+
gap: { control: 'text' },
16+
alignItems: { control: 'select', options: ['flex-start', 'center', 'flex-end', 'stretch', 'baseline'] },
17+
justifyContent: {
18+
control: 'select',
19+
options: ['flex-start', 'center', 'flex-end', 'space-between', 'space-around', 'space-evenly'],
20+
},
21+
flexWrap: { control: 'select', options: ['nowrap', 'wrap', 'wrap-reverse'] },
22+
width: { control: 'text' },
23+
flex: { control: 'text' },
24+
className: { table: { disable: true } },
25+
style: { table: { disable: true } },
26+
ref: { table: { disable: true } },
27+
},
28+
};
29+
30+
export default meta;
31+
32+
type Story = StoryObj<typeof DsStack>;
33+
34+
export const Default: Story = {
35+
args: {
36+
direction: 'column',
37+
gap: 8,
38+
},
39+
render: (args) => (
40+
<DsStack {...args}>
41+
<Box>Item 1</Box>
42+
<Box>Item 2</Box>
43+
<Box>Item 3</Box>
44+
</DsStack>
45+
),
46+
};
47+
48+
export const Row: Story = {
49+
args: {
50+
direction: 'row',
51+
gap: 16,
52+
alignItems: 'center',
53+
},
54+
render: (args) => (
55+
<DsStack {...args}>
56+
<Box>Item 1</Box>
57+
<Box>Item 2</Box>
58+
<Box>Item 3</Box>
59+
</DsStack>
60+
),
61+
};
62+
63+
export const Responsive: Story = {
64+
args: {
65+
direction: { md: 'column', lg: 'row' },
66+
gap: { md: 8, lg: 24 },
67+
alignItems: 'center',
68+
},
69+
render: (args) => (
70+
<DsStack {...args} className={styles.container}>
71+
<Box>Item 1</Box>
72+
<Box>Item 2</Box>
73+
<Box>Item 3</Box>
74+
</DsStack>
75+
),
76+
};
77+
78+
export const SpaceBetween: Story = {
79+
args: {
80+
direction: 'row',
81+
justifyContent: 'space-between',
82+
alignItems: 'center',
83+
width: '100%',
84+
},
85+
render: (args) => (
86+
<DsStack {...args} className={styles.container}>
87+
<Box>Left</Box>
88+
<Box>Right</Box>
89+
</DsStack>
90+
),
91+
};
92+
93+
export const Wrapping: Story = {
94+
args: {
95+
direction: 'row',
96+
gap: 8,
97+
flexWrap: 'wrap',
98+
},
99+
render: (args) => (
100+
<DsStack {...args} className={styles.container}>
101+
{Array.from({ length: 10 }, (_, i) => (
102+
<Box key={i}>Item {i + 1}</Box>
103+
))}
104+
</DsStack>
105+
),
106+
};
107+
108+
export const Nested: Story = {
109+
render: () => (
110+
<DsStack gap={24}>
111+
<DsStack direction="row" gap={16} alignItems="center">
112+
<Box>Row 1 - A</Box>
113+
<Box>Row 1 - B</Box>
114+
<Box>Row 1 - C</Box>
115+
</DsStack>
116+
117+
<DsStack direction="row" gap={16} alignItems="center">
118+
<Box>Row 2 - A</Box>
119+
<Box>Row 2 - B</Box>
120+
</DsStack>
121+
</DsStack>
122+
),
123+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import classNames from 'classnames';
2+
3+
import styles from './ds-stack.module.scss';
4+
import type { DsStackProps } from './ds-stack.types';
5+
6+
export const DsStack = ({
7+
direction,
8+
gap,
9+
alignItems,
10+
justifyContent,
11+
flex,
12+
flexWrap,
13+
width,
14+
children,
15+
className,
16+
style,
17+
ref,
18+
}: DsStackProps) => {
19+
const layoutStyle: React.CSSProperties = {
20+
flexDirection: direction,
21+
gap,
22+
alignItems,
23+
justifyContent,
24+
flex,
25+
flexWrap,
26+
width,
27+
...style,
28+
};
29+
30+
return (
31+
<div ref={ref} className={classNames(styles.root, className)} style={layoutStyle}>
32+
{children}
33+
</div>
34+
);
35+
};

0 commit comments

Comments
 (0)