Skip to content

Commit 05b9e8a

Browse files
authored
[docs]: update StackLayout & Layout docs, implement scroll support (#2600)
* [docs]: update StackLayout & Layout docs, implement scroll support * chore: small fix * chore: small fix 2 * chore: fix docs * [frontend]: fix ScrollArea breaking flex layout in StackLayoutWidget * chore: fix format
1 parent c4e9987 commit 05b9e8a

File tree

6 files changed

+220
-40
lines changed

6 files changed

+220
-40
lines changed

src/Ivy.Docs.Shared/Docs/01_Onboarding/02_Concepts/04_Layout.md

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ All layout methods return a LayoutView that can be further configured:
6262

6363
### Gap
6464

65-
Control spacing between elements:
65+
Control spacing between elements with `.Gap()`:
6666

6767
```csharp demo-tabs
6868
Layout.Vertical()
@@ -74,6 +74,26 @@ Layout.Vertical()
7474
| new Badge("A") | new Badge("B") | new Badge("C"))
7575
```
7676

77+
### Independent Row & Column Gap
78+
79+
Use `.Gap(rowGap, columnGap)` to control vertical and horizontal spacing independently:
80+
81+
```csharp demo-tabs
82+
Layout.Vertical().Gap(4)
83+
| Text.Label("RowGap=8, ColumnGap=2:")
84+
| (Layout.Wrap().Gap(8, 2).Width(Size.Units(60))
85+
| new Badge("A") | new Badge("B") | new Badge("C")
86+
| new Badge("D") | new Badge("E") | new Badge("F")
87+
| new Badge("G") | new Badge("H") | new Badge("I")
88+
| new Badge("J") | new Badge("K") | new Badge("L")
89+
| new Badge("M") | new Badge("N"))
90+
| Text.Label("RowGap=2, ColumnGap=8:")
91+
| (Layout.Wrap().Gap(2, 8).Width(Size.Units(60))
92+
| new Badge("A") | new Badge("B") | new Badge("C")
93+
| new Badge("D") | new Badge("E") | new Badge("F")
94+
| new Badge("G") | new Badge("H"))
95+
```
96+
7797
### Padding and Margin
7898

7999
Add internal and external spacing:
@@ -110,6 +130,66 @@ Layout.Vertical().Gap(4)
110130
| new Badge("Right aligned"))
111131
```
112132

133+
### Space Distribution
134+
135+
Distribute space between elements using `SpaceBetween`, `SpaceAround`, or `SpaceEvenly`:
136+
137+
```csharp demo-tabs
138+
Layout.Vertical().Gap(4)
139+
| Text.Label("SpaceBetween — items pushed to edges:")
140+
| (Layout.Horizontal().Align(Align.SpaceBetween).Width(Size.Full())
141+
| new Badge("A") | new Badge("B") | new Badge("C"))
142+
| Text.Label("SpaceAround — equal space around each item:")
143+
| (Layout.Horizontal().Align(Align.SpaceAround).Width(Size.Full())
144+
| new Badge("A") | new Badge("B") | new Badge("C"))
145+
| Text.Label("SpaceEvenly — equal space between all items:")
146+
| (Layout.Horizontal().Align(Align.SpaceEvenly).Width(Size.Full())
147+
| new Badge("A") | new Badge("B") | new Badge("C"))
148+
```
149+
150+
### Wrap
151+
152+
Use `Layout.Wrap()` to create a layout where items flow and wrap to the next line when they run out of space. Try resizing the window to see the wrapping behavior:
153+
154+
```csharp demo-tabs
155+
Layout.Wrap().Gap(2)
156+
| new Badge("Tag 1").Primary()
157+
| new Badge("Tag 2").Secondary()
158+
| new Badge("Tag 3")
159+
| new Badge("Tag 4").Primary()
160+
| new Badge("Tag 5").Secondary()
161+
| new Badge("Tag 6")
162+
| new Badge("Tag 7").Primary()
163+
| new Badge("Tag 8").Secondary()
164+
| new Badge("Tag 9")
165+
| new Badge("Tag 10").Primary()
166+
| new Badge("Tag 11").Secondary()
167+
```
168+
169+
### AlignSelf
170+
171+
Override alignment for individual children using `.AlignSelf()`. In a horizontal layout, this controls vertical positioning of each child independently:
172+
173+
```csharp demo-tabs
174+
Layout.Vertical().Gap(4)
175+
| new Badge("Top").Primary().AlignSelf(Align.TopLeft)
176+
| new Badge("Center").Primary().AlignSelf(Align.Center)
177+
| new Badge("Bottom").Primary().AlignSelf(Align.BottomRight)
178+
```
179+
180+
181+
### Scroll
182+
183+
Add scrollable behavior to layouts with constrained height using `.Scroll()`:
184+
185+
```csharp demo-tabs
186+
Layout.Vertical().Height(Size.Units(30)).Scroll(Scroll.Vertical).Gap(2)
187+
| new Badge("Item 1") | new Badge("Item 2") | new Badge("Item 3")
188+
| new Badge("Item 4") | new Badge("Item 5") | new Badge("Item 6")
189+
| new Badge("Item 7") | new Badge("Item 8") | new Badge("Item 9")
190+
| new Badge("Item 10") | new Badge("Item 11") | new Badge("Item 12")
191+
```
192+
113193
## Combining with Other Layouts
114194

115195
The Layout methods integrate seamlessly with specialized layout [widgets](../../02_Widgets/02_Layouts/_Index.md) and [Card](../../02_Widgets/03_Common/04_Card.md):

src/Ivy.Docs.Shared/Docs/02_Widgets/02_Layouts/01_StackLayout.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,91 @@ public class StackLayoutExample : ViewBase
5656
}
5757
```
5858

59+
## Space Distribution
60+
61+
Use `SpaceBetween`, `SpaceAround`, or `SpaceEvenly` to distribute space between elements:
62+
63+
```csharp demo-tabs
64+
new StackLayout([
65+
Text.Label("SpaceBetween:"),
66+
new StackLayout([new Badge("A"), new Badge("B"), new Badge("C")], Orientation.Horizontal, align: Align.SpaceBetween),
67+
Text.Label("SpaceAround:"),
68+
new StackLayout([new Badge("A"), new Badge("B"), new Badge("C")], Orientation.Horizontal, align: Align.SpaceAround),
69+
Text.Label("SpaceEvenly:"),
70+
new StackLayout([new Badge("A"), new Badge("B"), new Badge("C")], Orientation.Horizontal, align: Align.SpaceEvenly)
71+
], gap: 4).Width(Size.Full())
72+
```
73+
74+
## Row Gap & Column Gap
75+
76+
Control vertical and horizontal spacing independently using `RowGap` and `ColumnGap` properties:
77+
78+
```csharp demo-tabs
79+
new StackLayout([
80+
Text.Label("RowGap=8, ColumnGap=2:"),
81+
new StackLayout([
82+
new Badge("A"), new Badge("B"), new Badge("C"),
83+
new Badge("D"), new Badge("E"), new Badge("F"),
84+
new Badge("G"), new Badge("H"), new Badge("I"),
85+
new Badge("J"), new Badge("K"), new Badge("L"),
86+
new Badge("M"), new Badge("N")
87+
], Orientation.Horizontal, wrap: true) { RowGap = 8, ColumnGap = 2 }
88+
.Width(Size.Units(60)),
89+
Text.Label("RowGap=2, ColumnGap=8:"),
90+
new StackLayout([
91+
new Badge("A"), new Badge("B"), new Badge("C"),
92+
new Badge("D"), new Badge("E"), new Badge("F"),
93+
new Badge("G"), new Badge("H")
94+
], Orientation.Horizontal, wrap: true) { RowGap = 2, ColumnGap = 8 }
95+
.Width(Size.Units(60))
96+
], gap: 4)
97+
```
98+
99+
## Wrap
100+
101+
Use the `wrap` parameter to allow items to flow to the next line when they run out of space. Try resizing the window to see the wrapping behavior:
102+
103+
```csharp demo-tabs
104+
new StackLayout([
105+
new Badge("Tag 1").Primary(),
106+
new Badge("Tag 2").Secondary(),
107+
new Badge("Tag 3"),
108+
new Badge("Tag 4").Primary(),
109+
new Badge("Tag 5").Secondary(),
110+
new Badge("Tag 6"),
111+
new Badge("Tag 7").Primary(),
112+
new Badge("Tag 8").Secondary(),
113+
new Badge("Tag 9"),
114+
new Badge("Tag 10").Primary(),
115+
new Badge("Tag 11").Secondary()
116+
], Orientation.Horizontal, gap: 2, wrap: true)
117+
```
118+
119+
## AlignSelf
120+
121+
Override alignment for individual children using `.AlignSelf()`:
122+
123+
```csharp demo-tabs
124+
new StackLayout([
125+
new Badge("Top").Primary().AlignSelf(Align.TopLeft),
126+
new Badge("Center").Primary().AlignSelf(Align.Center),
127+
new Badge("Bottom").Primary().AlignSelf(Align.BottomRight)
128+
], gap: 4).Width(Size.Full())
129+
```
130+
131+
## Scroll
132+
133+
Add scrollable behavior using the `Scroll` property on a height-constrained layout:
134+
135+
```csharp demo-tabs
136+
new StackLayout([
137+
new Badge("Item 1"), new Badge("Item 2"), new Badge("Item 3"),
138+
new Badge("Item 4"), new Badge("Item 5"), new Badge("Item 6"),
139+
new Badge("Item 7"), new Badge("Item 8"), new Badge("Item 9"),
140+
new Badge("Item 10"), new Badge("Item 11"), new Badge("Item 12")
141+
], gap: 2) { Scroll = Scroll.Vertical }.Height(Size.Units(30)).Width(Size.Full())
142+
```
143+
59144
## Advanced Features
60145

61146
Complete example showing padding, margins, background colors, and parent padding control. Use [Thickness](../../04_ApiReference/Ivy/Thickness.md) for padding and margin, and [Colors](../../04_ApiReference/Ivy/Colors.md) for background. Alignment options are in [Align](../../04_ApiReference/Ivy/Align.md):

src/Ivy/Views/LayoutView.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ public LayoutView RemoveParentPadding()
415415
_alignment, _removeParentPadding, _wrap)
416416
{
417417
ColumnGap = _columnGap,
418+
Scroll = _scroll,
418419
BorderColor = _borderColor,
419420
BorderRadius = _borderRadius,
420421
BorderStyle = _borderStyle,

src/Ivy/Widgets/Layouts/StackLayout.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ internal StackLayout() { }
4646
[Prop] public BorderStyle BorderStyle { get; set; } = BorderStyle.None;
4747
[Prop] public Thickness BorderThickness { get; set; } = new(0);
4848

49+
[Prop] public Scroll Scroll { get; set; } = Scroll.None;
50+
4951
[Prop(attached: nameof(StackLayoutExtensions.AlignSelf))] public Align?[] ChildAlignSelf { get; set; } = null!;
5052
}
5153

src/frontend/src/components/ui/scroll-area.tsx

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,39 @@ import { cn } from '@/lib/utils';
55

66
const ScrollArea = React.forwardRef<
77
React.ComponentRef<typeof ScrollAreaPrimitive.Root>,
8-
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
9-
>(({ className, children, scrollHideDelay = 0, ...props }, ref) => (
10-
<ScrollAreaPrimitive.Root
11-
ref={ref}
12-
className={cn('relative overflow-hidden', className)}
13-
scrollHideDelay={scrollHideDelay}
14-
{...props}
15-
>
16-
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
17-
{children}
18-
</ScrollAreaPrimitive.Viewport>
19-
<ScrollBar />
20-
<ScrollAreaPrimitive.Corner />
21-
</ScrollAreaPrimitive.Root>
22-
));
8+
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> & {
9+
viewportClassName?: string;
10+
viewportStyle?: React.CSSProperties;
11+
}
12+
>(
13+
(
14+
{
15+
className,
16+
children,
17+
scrollHideDelay = 0,
18+
viewportClassName,
19+
viewportStyle,
20+
...props
21+
},
22+
ref
23+
) => (
24+
<ScrollAreaPrimitive.Root
25+
ref={ref}
26+
className={cn('relative overflow-hidden', className)}
27+
scrollHideDelay={scrollHideDelay}
28+
{...props}
29+
>
30+
<ScrollAreaPrimitive.Viewport
31+
className={cn('h-full w-full rounded-[inherit]', viewportClassName)}
32+
style={viewportStyle}
33+
>
34+
{children}
35+
</ScrollAreaPrimitive.Viewport>
36+
<ScrollBar />
37+
<ScrollAreaPrimitive.Corner />
38+
</ScrollAreaPrimitive.Root>
39+
)
40+
);
2341
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
2442

2543
const ScrollBar = React.forwardRef<

src/frontend/src/widgets/layouts/StackLayoutWidget.tsx

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,6 @@ export const StackLayoutWidget: React.FC<StackLayoutWidgetProps> = ({
9696
return null;
9797
}
9898

99-
// Handle scroll modes
100-
const getScrollStyles = (): React.CSSProperties => {
101-
switch (scroll) {
102-
case 'Auto':
103-
case 'Vertical':
104-
return { overflowY: 'auto', overflowX: 'hidden' };
105-
case 'Horizontal':
106-
return { overflowX: 'auto', overflowY: 'hidden' };
107-
case 'Both':
108-
return { overflow: 'auto' };
109-
default:
110-
return {};
111-
}
112-
};
113-
114-
const styles = { ...baseStyles, ...getScrollStyles() };
115-
11699
// Wrap children with alignSelf styles if needed
117100
const wrappedChildren = React.Children.map(children, (child, index) => {
118101
const alignSelf = childAlignSelf[index];
@@ -123,19 +106,30 @@ export const StackLayoutWidget: React.FC<StackLayoutWidgetProps> = ({
123106
return child;
124107
});
125108

126-
if (scroll === 'Auto' || scroll === 'Vertical') {
109+
const hasScroll = scroll && scroll !== 'None';
110+
111+
if (hasScroll) {
112+
const { width: _w, height: _h, ...flexStyles } = baseStyles;
113+
const outerStyles: React.CSSProperties = {
114+
...getWidth(width),
115+
...getHeight(height),
116+
};
117+
127118
return (
128-
<div style={styles}>
129-
<ScrollArea className="h-full w-full">
130-
<div className="p-4">{wrappedChildren}</div>
131-
</ScrollArea>
132-
</div>
119+
<ScrollArea
120+
className={removeParentPadding ? 'remove-parent-padding' : ''}
121+
style={outerStyles}
122+
type="scroll"
123+
scrollHideDelay={600}
124+
>
125+
<div style={flexStyles}>{wrappedChildren}</div>
126+
</ScrollArea>
133127
);
134128
}
135129

136130
return (
137131
<div
138-
style={styles}
132+
style={baseStyles}
139133
className={removeParentPadding ? 'remove-parent-padding' : ''}
140134
>
141135
{wrappedChildren}

0 commit comments

Comments
 (0)