P11FloatingPanel Component
P11FloatingPanel is a highly customizable, native HTML/CSS-based modal window designed to float independently on the screen. Unlike traditional modals, it can remain open alongside other interactive elements or modals, making it ideal for contextual help, auxiliary tools, or persistent information displays.P11FloatingPanel Component Examples
These examples demonstrate various configurations and functionalities of the P11FloatingPanel component, showcasing its flexibility for different use cases.
Create Panel at Specific Coordinates
Enter desired Top and Left coordinates (in pixels) and click 'Create Panel' to display a panel at that exact position. By default, it will appear at (100, 100).
Implementation
<h4 class="mb-3">Create Panel at Specific Coordinates</h4>
<p>Enter desired Top and Left coordinates (in pixels) and click 'Create Panel' to display a panel at that exact position. By default, it will appear at (100, 100).</p>
<EditForm Model="@specificPanelInputModel" OnValidSubmit="@CreateSpecificPositionPanel">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row mb-3">
<div class="col-md-6">
<P11Input Label="Top Coordinate (px)"
@bind-Value="specificPanelInputModel.TopCoordinate"
InputType="InputType.Number"
ValidationFor="@(() => specificPanelInputModel.TopCoordinate)" />
</div>
<div class="col-md-6">
<P11Input Label="Left Coordinate (px)"
@bind-Value="specificPanelInputModel.LeftCoordinate"
InputType="InputType.Number"
ValidationFor="@(() => specificPanelInputModel.LeftCoordinate)" />
</div>
</div>
<button type="submit" class="btn btn-success me-2">
<i class="bi bi-geo-alt"></i> Create Panel at Coordinates
</button>
</EditForm>
<P11FloatingPanel Id="specific-position-panel"
Title="Custom Position Panel"
Top="@specificPanelState.Top"
Left="@specificPanelState.Left"
Width="400"
IsVisible="@specificPanelState.IsVisible"
EnableDragDrop="true"
OnClose="@(() => specificPanelState.IsVisible = false)"
OnPositionUpdate="HandlePositionUpdate" >
<ChildContent>
<p>This panel was created at the coordinates you specified.</p>
<p>Current Position: Top @specificPanelState.Top.ToString("F0"), Left @specificPanelState.Left.ToString("F0")</p>
<button class="btn btn-sm btn-outline-danger mt-2" @onclick="() => specificPanelState.IsVisible = false">Close Panel</button>
</ChildContent>
</P11FloatingPanel>@* You may need the using *@
@* @using System.ComponentModel.DataAnnotations *@
@code {
// Model for dynamically added panels
public class PanelState
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Title { get; set; } = string.Empty;
public double Top { get; set; }
public double Left { get; set; }
public int ZIndex { get; set; } = 1;
public bool IsVisible { get; set; } = true;
}
// Model for specific panel input
public class SpecificPanelInputModel
{
[Required(ErrorMessage = "Top coordinate is required.")]
[Range(0, 2000, ErrorMessage = "Top must be between 0 and 2000.")] // Example range
public double TopCoordinate { get; set; } = 100;
[Required(ErrorMessage = "Left coordinate is required.")]
[Range(0, 2000, ErrorMessage = "Left must be between 0 and 2000.")] // Example range
public double LeftCoordinate { get; set; } = 100;
}
private SpecificPanelInputModel specificPanelInputModel = new();
private PanelState specificPanelState = new() { Id = "specific-position-panel", Title = "Custom Position Panel", Top = 100, Left = 100, IsVisible = false };
/// <summary>
/// Creates a single floating panel at the coordinates specified in the input fields.
/// </summary>
private void CreateSpecificPositionPanel()
{
specificPanelState.Top = specificPanelInputModel.TopCoordinate;
specificPanelState.Left = specificPanelInputModel.LeftCoordinate;
specificPanelState.IsVisible = true;
}
/// <summary>
/// Show current panel position.
/// </summary>
private async Task HandlePositionUpdate(Tuple<double, double> newPosition)
{
await Task.Delay(10);
specificPanelState.Top = newPosition.Item1;
specificPanelInputModel.TopCoordinate = newPosition.Item1;
specificPanelState.Left = newPosition.Item2;
specificPanelInputModel.LeftCoordinate = newPosition.Item2;
StateHasChanged();
}
}Basic Draggable Panels
Click the button to add new draggable panels one by one. Drag them around and click on them to bring them to the front (dynamic Z-index).
Implementation
<h4 class="mb-3">Basic Draggable Panels</h4>
<p>Click the button to add new draggable panels one by one. Drag them around and click on them to bring them to the front (dynamic Z-index).</p>
<div class="test-controls mb-4">
<button class="btn btn-primary me-2" @onclick="AddNewPanel">
<i class="bi bi-plus-lg"></i> Add New Panel
</button>
<button class="btn btn-danger" @onclick="ClearAllPanels">
<i class="bi bi-trash"></i> Clear All Panels
</button>
</div>
<div class="panel-container" style="min-height: 400px; border: 1px dashed #ccc; position: relative;">
@foreach (var panel in panels)
{
<P11FloatingPanel Id="@panel.Id"
Title="@panel.Title"
Top="@panel.Top"
Left="@panel.Left"
ZIndex="@panel.ZIndex"
IsVisible="@panel.IsVisible"
OnPositionUpdate="@(async (pos) => await OnPositionUpdate(panel.Id, pos.Item1, pos.Item2))"
OnClose="@(() => OnClose(panel.Id))">
<ChildContent>
<h4>@panel.Title</h4>
<p>Position: Top @panel.Top.ToString("F0"), Left @panel.Left.ToString("F0")</p>
<p>Z-Index: @panel.ZIndex</p>
<p>Click on the header to drag, click anywhere on the panel to bring to front.</p>
</ChildContent>
</P11FloatingPanel>
}
</div>@* You may need the using *@
@* @using System.ComponentModel.DataAnnotations *@
@code {
// Model for dynamically added panels
public class PanelState
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Title { get; set; } = string.Empty;
public double Top { get; set; }
public double Left { get; set; }
public int ZIndex { get; set; } = 1;
public bool IsVisible { get; set; } = true;
}
private PanelState specificPanelState = new() { Id = "specific-position-panel", Title = "Custom Position Panel", Top = 100, Left = 100, IsVisible = false };
private List<PanelState> panels = new();
private int nextPanelNumber = 1;
// Ensure panels list is empty on component initialization
protected override void OnInitialized()
{
panels.Clear();
nextPanelNumber = 1;
// Ensure specific panel is hidden on init
specificPanelState.IsVisible = false;
}
/// <summary>
/// Adds a new floating panel to the list.
/// </summary>
private void AddNewPanel()
{
var newPanel = new PanelState
{
Id = $"p11-panel-{nextPanelNumber}", // Using p11-panel prefix for consistency
Title = $"Test Panel #{nextPanelNumber}",
Top = 100 + (nextPanelNumber * 20), // Offset new panels to prevent exact overlap
Left = 100 + (nextPanelNumber * 20), // Offset new panels
ZIndex = 1
};
panels.Add(newPanel);
nextPanelNumber++;
}
/// <summary>
/// Clears all currently displayed floating panels.
/// </summary>
private void ClearAllPanels()
{
panels.Clear();
nextPanelNumber = 1; // Reset counter for new panels
// Also hide the specific panel if it's visible
specificPanelState.IsVisible = false;
}
/// <summary>
/// Updates the position of a panel after it has been dragged.
/// </summary>
/// <param name="panelId">The ID of the panel to update.</param>
/// <param name="top">The new top coordinate.</param>
/// <param name="left">The new left coordinate.</param>
private async Task OnPositionUpdate(string panelId, double top, double left)
{
var panelToUpdate = panels.FirstOrDefault(p => p.Id == panelId);
if (panelToUpdate != null)
{
panelToUpdate.Top = top;
panelToUpdate.Left = left;
}
// If the specific panel is being dragged, update its state too
if (specificPanelState.Id == panelId)
{
specificPanelState.Top = top;
specificPanelState.Left = left;
}
}
/// <summary>
/// Handles the close event for a panel, removing it from the list.
/// </summary>
/// <param name="panelId">The ID of the panel to close.</param>
private void OnClose(string panelId)
{
var panelToRemove = panels.FirstOrDefault(p => p.Id == panelId);
if (panelToRemove != null)
{
panels.Remove(panelToRemove);
}
}
}Adding Multiple Predefined Panels
Click the button below to add three predefined panels to the page simultaneously. Each will have a unique position and title.
Implementation
<div class="bg-light p-4 rounded mb-4">
<h4 class="mb-3">Adding Multiple Predefined Panels</h4>
<p>Click the button below to add three predefined panels to the page simultaneously. Each will have a unique position and title.</p>
<div class="test-controls mb-4">
<button class="btn btn-info me-2" @onclick="AddThreePredefinedPanels">
<i class="bi bi-three-dots"></i> Add 3 Predefined Panels
</button>
<button class="btn btn-danger" @onclick="ClearAllPanels2">
<i class="bi bi-trash"></i> Clear All Panels
</button>
</div>
</div>
<div class="panel-container" style="min-height: 400px; border: 1px dashed #ccc; position: relative;">
@foreach (var panel in panels2)
{
<P11FloatingPanel Id="@panel.Id"
Title="@panel.Title"
Top="@panel.Top"
Left="@panel.Left"
ZIndex="@panel.ZIndex"
IsVisible="@panel.IsVisible"
OnPositionUpdate="@(async (pos) => await OnPositionUpdate(panel.Id, pos.Item1, pos.Item2))"
OnClose="@(() => OnClose2(panel.Id))">
<ChildContent>
<h4>@panel.Title</h4>
<p>Position: Top @panel.Top.ToString("F0"), Left @panel.Left.ToString("F0")</p>
<p>Z-Index: @panel.ZIndex</p>
<p>Click on the header to drag, click anywhere on the panel to bring to front.</p>
</ChildContent>
</P11FloatingPanel>
}
</div>@* You may need the using *@
@* @using System.ComponentModel.DataAnnotations *@
@code {
// Model for dynamically added panels
public class PanelState
{
public string Id { get; set; } = Guid.NewGuid().ToString();
public string Title { get; set; } = string.Empty;
public double Top { get; set; }
public double Left { get; set; }
public int ZIndex { get; set; } = 1;
public bool IsVisible { get; set; } = true;
}
// Model for specific panel input
public class SpecificPanelInputModel
{
[Required(ErrorMessage = "Top coordinate is required.")]
[Range(0, 2000, ErrorMessage = "Top must be between 0 and 2000.")] // Example range
public double TopCoordinate { get; set; } = 100;
[Required(ErrorMessage = "Left coordinate is required.")]
[Range(0, 2000, ErrorMessage = "Left must be between 0 and 2000.")] // Example range
public double LeftCoordinate { get; set; } = 100;
}
private List<PanelState> panels = new();
private List<PanelState> panels2 = new();
private int nextPanelNumber = 1;
private SpecificPanelInputModel specificPanelInputModel = new();
private PanelState specificPanelState = new() { Id = "specific-position-panel", Title = "Custom Position Panel", Top = 100, Left = 100, IsVisible = false };
// Ensure panels list is empty on component initialization
protected override void OnInitialized()
{
panels.Clear();
nextPanelNumber = 1;
// Ensure specific panel is hidden on init
specificPanelState.IsVisible = false;
}
/// <summary>
/// Adds three predefined floating panels to the list.
/// Clears existing panels first for a clean demo.
/// </summary>
private void AddThreePredefinedPanels()
{
ClearAllPanels2(); // Clear existing panels first for a clean demo
panels2.Add(new PanelState
{
Id = "p21-predefined-1",
Title = "Predefined Panel 1",
Top = 50,
Left = 50,
ZIndex = 1
});
panels2.Add(new PanelState
{
Id = "p21-predefined-2",
Title = "Predefined Panel 2",
Top = 100,
Left = 150,
ZIndex = 2
});
panels2.Add(new PanelState
{
Id = "p21-predefined-3",
Title = "Predefined Panel 3",
Top = 150,
Left = 250,
ZIndex = 3
});
// nextPanelNumber is not incremented here as these are "predefined"
}
/// <summary>
/// Handles the close event for a panel, removing it from the list.
/// </summary>
/// <param name="panelId">The ID of the panel to close.</param>
private void OnClose(string panelId)
{
var panelToRemove = panels.FirstOrDefault(p => p.Id == panelId);
if (panelToRemove != null)
{
panels.Remove(panelToRemove);
}
}
/// <summary>
/// Handles the close event for a panel, removing it from the list.
/// </summary>
/// <param name="panelId">The ID of the panel to close.</param>
private void OnClose2(string panelId)
{
// Korrektur: Wir suchen das Panel in der richtigen Liste: panels2
var panelToRemove = panels2.FirstOrDefault(p => p.Id == panelId);
if (panelToRemove != null)
{
panels2.Remove(panelToRemove);
}
}
/// <summary>
/// Clears all currently displayed floating panels.
/// </summary>
private void ClearAllPanels2()
{
panels2.Clear();
}
/// <summary>
/// Updates the position of a panel after it has been dragged.
/// </summary>
/// <param name="panelId">The ID of the panel to update.</param>
/// <param name="top">The new top coordinate.</param>
/// <param name="left">The new left coordinate.</param>
private async Task OnPositionUpdate(string panelId, double top, double left)
{
var panelToUpdate = panels.FirstOrDefault(p => p.Id == panelId);
if (panelToUpdate != null)
{
panelToUpdate.Top = top;
panelToUpdate.Left = left;
}
// If the specific panel is being dragged, update its state too
if (specificPanelState.Id == panelId)
{
specificPanelState.Top = top;
specificPanelState.Left = left;
}
}
}Panel with Initial Position and Custom Styling
This panel is initialized at the top-left corner (0,0) and has a custom CSS class applied for unique styling. Its initial position is set explicitly.
Implementation
<h4 class="mb-3">Panel with Initial Position and Custom Styling</h4>
<p>This panel is initialized at the top-left corner (0,0) and has a custom CSS class applied for unique styling. Its initial position is set explicitly.</p>
<P11FloatingPanel Id="styled-panel"
Title="Styled Panel"
Top="0"
Left="0"
CssClass="custom-panel-style"
OnClose="@(() => isStyledPanelVisible = false)"
IsVisible="@isStyledPanelVisible">
<ChildContent>
<p>This panel has custom styling applied via the <code>CssClass</code> parameter.</p>
<p>Its initial position is set explicitly.</p>
<button class="btn btn-sm btn-outline-secondary" @onclick="() => isStyledPanelVisible = false">Close Panel</button>
</ChildContent>
</P11FloatingPanel>
<button class="btn btn-secondary mt-3" @onclick="() => isStyledPanelVisible = true">Show Styled Panel</button>@* You may need the using *@
@* @using System.ComponentModel.DataAnnotations *@
@code {
// State for "Panel with Initial Position and Custom Styling" example
private bool isStyledPanelVisible = false; // Changed to false so it doesn't appear automatically
}Toggleable Panel Visibility
This panel's visibility is controlled by the IsVisible parameter. Click the button to toggle its display.
Implementation
<h4 class="mb-3">Toggleable Panel Visibility</h4>
<p>This panel's visibility is controlled by the <code>IsVisible</code> parameter. Click the button to toggle its display.</p>
<button class="btn btn-secondary" @onclick="ToggleVisibility">
Toggle Panel Visibility (@(isTogglePanelVisible ? "Currently Visible" : "Currently Hidden"))
</button>
<P11FloatingPanel Id="toggle-panel"
Title="Toggle Panel"
Top="0"
Left="0"
IsVisible="@isTogglePanelVisible"
OnClose="@(() => isTogglePanelVisible = false)">
<ChildContent>
<p>You can show and hide this panel using the button.</p>
<button class="btn btn-sm btn-outline-secondary" @onclick="() => isTogglePanelVisible = false">Hide Panel</button>
</ChildContent>
</P11FloatingPanel>@* You may need the using *@
@* @using System.ComponentModel.DataAnnotations *@
@code {
// State for "Toggleable Panel Visibility" example
private bool isTogglePanelVisible = false; // Changed to false so it doesn't appear automatically
/// <summary>
/// Toggles the visibility of the toggleable panel.
/// </summary>
private void ToggleVisibility()
{
isTogglePanelVisible = !isTogglePanelVisible;
}
}Panel with Interactive Content
This panel demonstrates how interactive elements can be placed inside a floating panel. The panel can still be dragged and closed.
Implementation
<h4 class="mb-3">Panel with Interactive Content</h4>
<p>This panel demonstrates how interactive elements can be placed inside a floating panel. The panel can still be dragged and closed.</p>
<P11FloatingPanel Id="interactive-panel"
Title="Interactive Panel"
Top="0"
Left="0"
OnClose="@(() => isInteractivePanelVisible = false)"
IsVisible="@isInteractivePanelVisible">
<ChildContent>
<p>Enter some text:</p>
<input type="text" class="form-control mb-2" @bind="interactivePanelText" placeholder="Type here..." />
<p>Current Text: @interactivePanelText</p>
<button class="btn btn-success" @onclick="@(() => interactivePanelText = "Hello World!")">Set Text</button>
<button class="btn btn-sm btn-outline-danger mt-2" @onclick="() => isInteractivePanelVisible = false">Close Panel</button>
</ChildContent>
</P11FloatingPanel>
<button class="btn btn-secondary mt-3" @onclick="() => isInteractivePanelVisible = true">Show Interactive Panel</button>@* You may need the using *@
@* @using System.ComponentModel.DataAnnotations *@
@code {
// State for "Panel with Interactive Content" example
private bool isInteractivePanelVisible = false; // Changed to false so it doesn't appear automatically
private string interactivePanelText = "Initial Text";
}Component API
| Parameter | Type | Default | Description |
|---|---|---|---|
Id Required |
string |
string.Empty |
Gets or sets the unique HTML 'id' attribute for the panel's root element. This is crucial for JavaScript interop to identify and manipulate the panel. |
Title |
string |
\"Floating Panel\" |
Gets or sets the title text displayed in the panel's header. |
Top |
double |
100 |
Gets or sets the initial vertical position (top coordinate in pixels) of the panel. This value is updated internally when the panel is dragged. |
Left |
double |
100 |
Gets or sets the initial horizontal position (left coordinate in pixels) of the panel. This value is updated internally when the panel is dragged. |
Width |
int |
100 |
Sets the initial width of the panel. |
ZIndex |
int |
1 |
Gets or sets the initial CSS z-index for the panel. This value is updated dynamically by JavaScript when the panel is clicked to bring it to the front. |
IsVisible |
bool |
true |
Gets or sets a value indicating whether the panel is currently visible. Setting this to <code>false</code> will hide the panel. |
CssClass |
string? |
null |
Gets or sets an optional CSS class string to be applied to the panel's root element. |
ChildContent |
RenderFragment? |
null |
Gets or sets the content to be rendered inside the panel's body. |
| Events | |||
OnPositionUpdate |
EventCallback<Tuple<double, double>> |
- | An EventCallback that is invoked when the panel's position changes due to dragging. The callback receives a Tuple<double, double> containing the new Top and Left coordinates. |
OnClose |
EventCallback |
- | An EventCallback that is invoked when the panel's close button is clicked. This can be used to set IsVisible to false or remove the panel from a list. |