true-perfect-code
Version: 1.1.69

P11FloatingPanel Component

The 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.
Note: This component offers drag-and-drop functionality, dynamic Z-index management to bring it to the front on click, and requires a unique <code>Id</code> for proper JavaScript interop. It uses its own CSS classes and does not rely on Bootstrap JavaScript.


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.
An unhandled error has occurred. Reload 🗙