Skip to content

Commit e0f1bfe

Browse files
committed
Add resizable option to side dialog.
1 parent 09ff774 commit e0f1bfe

File tree

5 files changed

+239
-22
lines changed

5 files changed

+239
-22
lines changed

Radzen.Blazor/DialogService.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -848,6 +848,7 @@ public int CloseTabIndex
848848
}
849849

850850
private RenderFragment<DialogService> titleContent;
851+
private bool resizable;
851852

852853
/// <summary>
853854
/// Gets or sets the title content.
@@ -865,6 +866,23 @@ public RenderFragment<DialogService> TitleContent
865866
}
866867
}
867868
}
869+
870+
/// <summary>
871+
/// Gets or sets a value indicating whether the dialog is resizable. Set to <c>false</c> by default.
872+
/// </summary>
873+
/// <value><c>true</c> if resizable; otherwise, <c>false</c>.</value>
874+
public bool Resizable
875+
{
876+
get => resizable;
877+
set
878+
{
879+
if (resizable != value)
880+
{
881+
resizable = value;
882+
OnPropertyChanged(nameof(Resizable));
883+
}
884+
}
885+
}
868886
}
869887

870888
/// <summary>
@@ -992,24 +1010,6 @@ public class DialogOptions : DialogOptionsBase
9921010
/// </summary>
9931011
public string IconStyle { get; set; } = "margin-right: 0.75rem";
9941012

995-
996-
private bool resizable;
997-
/// <summary>
998-
/// Gets or sets a value indicating whether the dialog is resizable. Set to <c>false</c> by default.
999-
/// </summary>
1000-
/// <value><c>true</c> if resizable; otherwise, <c>false</c>.</value>
1001-
public bool Resizable
1002-
{
1003-
get => resizable;
1004-
set
1005-
{
1006-
if (resizable != value)
1007-
{
1008-
resizable = value;
1009-
OnPropertyChanged(nameof(Resizable));
1010-
}
1011-
}
1012-
}
10131013

10141014
private Action<Size> resize;
10151015

Radzen.Blazor/RadzenDialog.razor

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,23 @@
1212
{
1313
<aside
1414
class="@GetSideDialogCssClass()"
15+
@ref="sideDialogHandle"
1516
tabindex="@(isSideDialogOpen ? "0" : "-1")"
1617
style="@GetSideDialogStyle()"
17-
aria-labelledby="rz-dialog-side-label"
18-
>
18+
aria-labelledby="rz-dialog-side-label">
19+
@if (sideDialogOptions.Resizable)
20+
{
21+
<div @ref="sideDialogResizeHandle"
22+
class="@GetSideDialogResizeHandleCssClass()"
23+
data-dir="@(sideDialogOptions.Position switch {
24+
DialogPosition.Left => "left",
25+
DialogPosition.Top => "top",
26+
DialogPosition.Bottom => "bottom",
27+
_ => "right"
28+
})"
29+
title="Drag to resize" aria-label="Resize side dialog" ></div>
30+
31+
}
1932
@if (sideDialogOptions.ShowTitle)
2033
{
2134
<div class="rz-dialog-side-titlebar">
@@ -83,6 +96,9 @@
8396
RenderFragment sideDialogContent;
8497
SideDialogOptions sideDialogOptions;
8598
Dialog sideDialog;
99+
ElementReference? sideDialogResizeHandle;
100+
ElementReference? sideDialogHandle;
101+
IJSObjectReference sideDialogResizeHandleJsModule;
86102

87103
public async Task Open(string title, Type type, Dictionary<string, object> parameters, DialogOptions options)
88104
{
@@ -219,6 +235,10 @@
219235
.Add("rz-close", sideDialogClosing)
220236
.ToString();
221237

238+
string GetSideDialogResizeHandleCssClass() => ClassList.Create("rz-dialog-side-resize-handle")
239+
.Add($"rz-dialog-side-resize-handle-{sideDialogOptions.Position.ToString().ToLower()}")
240+
.ToString();
241+
222242
string GetSideDialogStyle()
223243
{
224244
string widthStyle = string.IsNullOrEmpty(sideDialogOptions.Width) ? string.Empty : $"width: {sideDialogOptions.Width};";
@@ -233,7 +253,28 @@
233253

234254
if (isSideDialogOpen)
235255
{
236-
await JSRuntime.InvokeAsync<string>("Radzen.openSideDialog", sideDialogOptions);
256+
await JSRuntime.InvokeAsync<string>("Radzen.openSideDialog", sideDialogOptions);
257+
258+
if (sideDialogOptions.Resizable && sideDialogResizeHandle.HasValue)
259+
{
260+
sideDialogResizeHandleJsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("Radzen.initSideDialogResize", sideDialogResizeHandle, sideDialogHandle);
261+
}
262+
}
263+
}
264+
265+
public async ValueTask DisposeAsync()
266+
{
267+
try
268+
{
269+
if (sideDialogResizeHandleJsModule != null)
270+
{
271+
await sideDialogResizeHandleJsModule.InvokeVoidAsync("dispose");
272+
}
273+
}
274+
catch
275+
{
276+
/* Ignore */
237277
}
238278
}
279+
239280
}

Radzen.Blazor/themes/components/blazor/_dialog.scss

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,94 @@ $dialog-transition: .1s ease-in-out !default;
5858
overflow-y: auto;
5959
}
6060

61+
.rz-dialog-side-resize-handle-right,
62+
.rz-dialog-side-resize-handle-left,{
63+
position: absolute;
64+
width: 12px;
65+
height: 100%;
66+
top: 0;
67+
cursor: ew-resize;
68+
background: transparent;
69+
z-index: 1;
70+
}
71+
72+
.rz-dialog-side-resize-handle-right {
73+
left: 0;
74+
}
75+
76+
.rz-dialog-side-resize-handle-left {
77+
right: 0;
78+
}
79+
80+
.rz-dialog-side-resize-handle-top,
81+
.rz-dialog-side-resize-handle-bottom {
82+
position: absolute;
83+
width: 100%;
84+
height: 12px;
85+
left: 0;
86+
cursor: ns-resize;
87+
}
88+
89+
.rz-dialog-side-resize-handle-top {
90+
bottom: 0;
91+
}
92+
93+
.rz-dialog-side-resize-handle-bottom {
94+
top: 0;
95+
}
96+
97+
.rz-dialog-side-resize-handle-left::before,
98+
.rz-dialog-side-resize-handle-right::before {
99+
content: "";
100+
position: absolute;
101+
top: 40%;
102+
Height: 20%;
103+
width: 6px;
104+
background-image:
105+
radial-gradient(currentColor 1px, transparent 1px),
106+
radial-gradient(currentColor 1px, transparent 1px);
107+
background-size: 6px 6px, 6px 6px;
108+
background-position: 0 0, 3px 3px;
109+
color: color-mix(in oklab, var(--rz-secondary) 45%, var(--rz-text-color));
110+
opacity: 0.6;
111+
}
112+
113+
.rz-dialog-side-resize-handle-top::before,
114+
.rz-dialog-side-resize-handle-bottom::before {
115+
content: "";
116+
position: absolute;
117+
left: 40%;
118+
width: 20%;
119+
height: 6px;
120+
background-image:
121+
radial-gradient(currentColor 1px, transparent 1px),
122+
radial-gradient(currentColor 1px, transparent 1px);
123+
background-size: 6px 6px, 6px 6px;
124+
background-position: 0 0, 3px 3px;
125+
color: color-mix(in oklab, var(--rz-secondary) 45%, var(--rz-text-color));
126+
opacity: 0.6;
127+
}
128+
129+
.rz-dialog-side-resize-handle-top::before {
130+
bottom: 3px;
131+
}
132+
133+
.rz-dialog-side-resize-handle-bottom::before {
134+
top: 3px;
135+
}
136+
137+
.rz-dialog-side-resize-handle:hover::before {
138+
opacity: 1;
139+
}
140+
141+
.rz-dialog-side-resize-handle-left::before {
142+
right: 3px;
143+
}
144+
145+
.rz-dialog-side-resize-handle-right::before {
146+
left: 3px;
147+
}
148+
61149
.rz-dialog-titlebar,
62150
.rz-dialog-side-titlebar {
63151
display: flex;

Radzen.Blazor/wwwroot/Radzen.Blazor.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,89 @@ window.Radzen = {
14521452
}
14531453
}, 500);
14541454
},
1455+
initSideDialogResize: function(handle, sideDialog){
1456+
const dir = (handle.dataset.dir || 'right').toLowerCase();
1457+
const cs = window.getComputedStyle(sideDialog);
1458+
const parent = sideDialog.parentNode;
1459+
const parentIsFlex = parent && getComputedStyle(parent).display.includes('flex');
1460+
const toPixels = (v, axis) => {
1461+
if (!v || v === 'none') return NaN;
1462+
if (v.endsWith && v.endsWith('px')) return parseFloat(v);
1463+
if (v.endsWith && v.endsWith('%')) {
1464+
const base = axis === 'y' ? window.innerHeight : window.innerWidth;
1465+
const p = parseFloat(v);
1466+
return Number.isFinite(p) ? (base * p / 100) : NaN;
1467+
}
1468+
const n = parseFloat(v);
1469+
return Number.isFinite(n) ? n : NaN;
1470+
};
1471+
1472+
let MIN_W = toPixels(cs.minWidth, 'x') || 300;
1473+
let MAX_W = toPixels(cs.maxWidth, 'x') || Infinity;
1474+
let MIN_H = toPixels(cs.minHeight, 'y') || 200;
1475+
let MAX_H = toPixels(cs.maxHeight, 'y') || Infinity;
1476+
1477+
// Guard against invalid ranges caused by percentage max being smaller than min
1478+
if (Number.isFinite(MIN_W) && Number.isFinite(MAX_W) && MAX_W < MIN_W) MAX_W = Infinity;
1479+
if (Number.isFinite(MIN_H) && Number.isFinite(MAX_H) && MAX_H < MIN_H) MAX_H = Infinity;
1480+
1481+
let start = null;
1482+
1483+
const onDown = (e) => {
1484+
e.preventDefault();
1485+
handle.setPointerCapture?.(e.pointerId);
1486+
1487+
const rect = sideDialog.getBoundingClientRect();
1488+
start = { x: e.clientX, y: e.clientY, w: rect.width, h: rect.height };
1489+
1490+
document.addEventListener('pointermove', onMove);
1491+
document.addEventListener('pointerup', onUp, { once: true });
1492+
document.body.classList.add('dragging');
1493+
};
1494+
1495+
const clamp = (v, min, max) => Math.max(min, Math.min(max, v));
1496+
1497+
const applyWidth = (w) => {
1498+
if (parentIsFlex)
1499+
sideDialog.style.flexBasis = Math.round(w) + 'px';
1500+
else
1501+
sideDialog.style.width = Math.round(w) + 'px';
1502+
};
1503+
const applyHeight = (h) => {
1504+
sideDialog.style.height = `${Math.round(h)}px`;
1505+
};
1506+
1507+
const onMove = (e) => {
1508+
if (!start) return;
1509+
1510+
const dx = e.clientX - start.x;
1511+
const dy = e.clientY - start.y;
1512+
1513+
switch (dir) {
1514+
case 'right': applyWidth(clamp(start.w - dx, MIN_W, MAX_W)); break;
1515+
case 'left': applyWidth(clamp(start.w + dx, MIN_W, MAX_W)); break;
1516+
case 'bottom': applyHeight(clamp(start.h - dy, MIN_H, MAX_H)); break;
1517+
case 'top': applyHeight(clamp(start.h + dy, MIN_H, MAX_H)); break;
1518+
}
1519+
};
1520+
1521+
const onUp = (e) => {
1522+
handle.releasePointerCapture?.(e.pointerId);
1523+
start = null;
1524+
document.removeEventListener('pointermove', onMove);
1525+
document.body.classList.remove('dragging');
1526+
};
1527+
1528+
handle.addEventListener('pointerdown', onDown);
1529+
1530+
return {
1531+
dispose() {
1532+
handle.removeEventListener('pointerdown', onDown);
1533+
document.removeEventListener('pointermove', onMove);
1534+
document.body.classList.remove('dragging');
1535+
}
1536+
};
1537+
},
14551538
openDialog: function (options, dialogService, dialog) {
14561539
if (Radzen.closeAllPopups) {
14571540
Radzen.closeAllPopups();

RadzenBlazorDemos/Pages/DialogSide.razor

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
<RadzenLabel Text="Close on overlay click:" Component="Close" />
1616
<RadzenSwitch @bind-Value="@closeDialogOnOverlayClick" Disabled=@(!showMask) Name="Close" />
1717
</RadzenStack>
18+
<RadzenStack Orientation="Orientation.Horizontal" Gap="0.5rem" AlignItems="AlignItems.Center">
19+
<RadzenLabel Text="Resizable:" Component="Resizable" />
20+
<RadzenSwitch @bind-Value="@resizable" Name="Resizable" />
21+
</RadzenStack>
1822
</RadzenStack>
1923
<RadzenButton Text="Dialog on Side" ButtonStyle="ButtonStyle.Secondary" Click="@OpenSideDialog" />
2024
</div>
@@ -23,9 +27,10 @@
2327
DialogPosition position;
2428
bool closeDialogOnOverlayClick;
2529
bool showMask;
30+
bool resizable;
2631

2732
async Task OpenSideDialog()
2833
{
29-
await DialogService.OpenSideAsync<DialogSideContent>("Side Panel", options: new SideDialogOptions { CloseDialogOnOverlayClick = closeDialogOnOverlayClick, Position = position, ShowMask = showMask });
34+
await DialogService.OpenSideAsync<DialogSideContent>("Side Panel", options: new SideDialogOptions { CloseDialogOnOverlayClick = closeDialogOnOverlayClick, Resizable = resizable, Position = position, ShowMask = showMask });
3035
}
3136
}

0 commit comments

Comments
 (0)