true-perfect-code
Version: 1.1.69

P11Modal Component

The P11Modal component provides a standard, accessible modal dialog window. It is designed to overlay the application content, making everything inactive except for itself (and any P11FloatingPanel instances). Built on Bootstrap classes, it ensures responsiveness and accessibility out-of-the-box.
Note: Proper use of AriaLabel or AriaLabelledby is crucial for screen reader users. The RestoreFocusId parameter allows controlling where focus returns after the modal closes, enhancing user experience for keyboard navigation.


Modal Examples

This section demonstrates the usage of the <code>P11Modal</code> component with various configurations and highlights its key features.

Event Log

The following log tracks events from the modals, such as opening and closing.

No events logged yet.



Implementation Details

<div class="component-description mt-4">
    <h2 class="mb-3">@AppState!.T("Modal Examples")</h2>
    <p>
        @AppState!.T("This section demonstrates the usage of the <code>P11Modal</code> component with various configurations and highlights its key features.")
    </p>

    <div class="d-flex flex-wrap gap-3 mb-5">
        <P11Button Text="Open Basic Modal" OnClick="(() => OpenModal(1))" />

        <P11Button Text="Open Large Modal" OnClick="(() => OpenModal(2))" />

        <P11Button Text="Open Centered Modal" OnClick="(() => OpenModal(3))" />

        <P11Button Text="Open Scrollable Modal" OnClick="(() => OpenModal(4))" />

        <P11Button Id="restoreFocusButton" Text="Open Modal (Restore Focus)" OnClick="(() => OpenModal(5))" />

        <P11Button Text="Open AriaLabel Only Modal" OnClick="(() => OpenModal(6))" />

        <P11Button Text="Open Nested Modals" OnClick="(() => OpenModal(7))" />

        <P11Button Text="Open fullscreen Modal" OnClick="(() => OpenModal(10))" />

        <P11Button Text="Pass bootstrap content class" OnClick="(() => OpenModal(11))" />

        <P11Button Text="Pass bootstrap header, body, footer classes" OnClick="(() => OpenModal(12))" />
    </div>
</div>

@* ----- MODAL IMPLEMENTATIONS ----- *@

@* Example 1: Basic Modal *@
<P11Modal @bind-Visible="showModal1" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle1"
          ShowDevelopmentErrors="true">
    <HeaderContent>
        <h5 id="modalTitle1" class="modal-title">@AppState!.T("Basic Modal Title")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is a basic modal. You should not be able to interact with the background.")</p>
        <p>@AppState!.T("Try pressing ESC or clicking the backdrop to close it.")</p>
        <p>@AppState!.T("The focus should be trapped within this modal. Try tabbing around.")</p>
        <input type="text" class="form-control mb-2" placeholder="Input 1" />
        <input type="text" class="form-control" placeholder="Input 2" />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close" OnClick="(() => CloseModal(1))" />
        <P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(1); })" />
    </FooterContent>
</P11Modal>

@* Example 2: Large Modal *@
<P11Modal @bind-Visible="showModal2" OnClose="OnModalClosed"
          Size="Size.Large"
          AriaLabelledby="modalTitle2">
    <HeaderContent>
        <h5 id="modalTitle2" class="modal-title">@AppState!.T("Large Modal Title")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is a large modal. It takes up more screen space.")</p>
        <input type="email" class="form-control" placeholder="Email" />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close Large" OnClick="(() => CloseModal(2))" />
    </FooterContent>
</P11Modal>

@* Example 3: Centered Modal *@
<P11Modal @bind-Visible="showModal3" OnClose="OnModalClosed"
          Position="ModalPosition.Centered"
          AriaLabelledby="modalTitle3">
    <HeaderContent>
        <h5 id="modalTitle3" class="modal-title">@AppState!.T("Centered Modal Title")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This modal is centered vertically and horizontally in the viewport.")</p>
        <p>@AppState!.T("Ideal for alerts or short confirmations.")</p>
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close Centered" OnClick="(() => CloseModal(3))" />
    </FooterContent>
</P11Modal>

@* Example 4: Scrollable Modal *@
<P11Modal @bind-Visible="showModal4" OnClose="OnModalClosed"
          Position="ModalPosition.Scrollable"
          AriaLabelledby="modalTitle4">
    <HeaderContent>
        <h5 id="modalTitle4" class="modal-title">@AppState!.T("Scrollable Modal Title")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This modal has a scrollable body. Use the scrollbar on the modal, not the background.")</p>
        @for (int i = 0; i < 50; i++)
        {
            <p>@AppState!.T($"Line {i + 1} of very long content. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.")</p>
        }
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close Scrollable" OnClick="(() => CloseModal(4))" />
    </FooterContent>
</P11Modal>

@* Example 5: Focus Restoration *@
<P11Modal @bind-Visible="showModal5" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle5"
          RestoreFocusId="restoreFocusButton">
    <HeaderContent>
        <h5 id="modalTitle5" class="modal-title">@AppState!.T("Focus Restoration Modal")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("When this modal closes, the focus should return to the \"Open Modal (Restore Focus)\" button.")</p>
        <input type="text" class="form-control" placeholder="Type something..." />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close & Restore" OnClick="(() => CloseModal(5))" />
    </FooterContent>
</P11Modal>

@* Example 6: AriaLabel (without AriaLabelledby) *@
<P11Modal @bind-Visible="showModal6" OnClose="OnModalClosed"
          AriaLabel="Information Dialog"
          ShowDevelopmentErrors="true">
    <HeaderContent>
        <p>@AppState!.T("Informational Message")</p>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This modal uses <code>AriaLabel</code> because no <code>AriaLabelledby</code> was provided. Check console for warnings if ShowDevelopmentErrors is true.")</p>
        <p>@AppState!.T("This is useful for simple alerts without a formal heading.")</p>
    </BodyContent>
    <FooterContent>
        <P11Button Text="Got It!" OnClick="(() => CloseModal(6))" />
    </FooterContent>
</P11Modal>

@* Example 7, 8, 9: Nested Modals *@
<P11Modal @bind-Visible="showModal7" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle7">
    <HeaderContent>
        <h5 id="modalTitle7" class="modal-title" tabindex="-1">@AppState!.T("Parent Modal")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is the parent modal.")</p>
        <P11Button Text="Open Child Modal" OnClick="(() => OpenModal(8))" />
        <input type="text" class="form-control mt-2" placeholder="Parent input" />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close Parent" OnClick="(() => CloseModal(7))" />
    </FooterContent>
</P11Modal>

<P11Modal @bind-Visible="showModal8" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle8"
          RestoreFocusId="modalTitle7"
          PreventScroll="false">
    <HeaderContent>
        <h5 id="modalTitle8" class="modal-title" tabindex="-1">@AppState!.T("Child Modal")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is a child modal. It should be on top of the parent.")</p>
        <p>@AppState!.T("Focus should be trapped here.")</p>
        <input type="text" class="form-control" placeholder="Child input" />
        <P11Button Text="Open Grandchild Modal" OnClick="(() => OpenModal(9))" />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close Child" OnClick="(() => CloseModal(8))" />
    </FooterContent>
</P11Modal>

<P11Modal @bind-Visible="showModal9" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle9"
          RestoreFocusId="modalTitle8"
          PreventScroll="false">
    <HeaderContent>
        <h5 id="modalTitle9" class="modal-title" tabindex="-1">@AppState!.T("Grandchild Modal")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is the grandchild modal. Deep nesting!")</p>
        <input type="text" class="form-control" placeholder="Grandchild input" />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close Grandchild" OnClick="(() => CloseModal(9))" />
    </FooterContent>
</P11Modal>

@* Example 10: Fullscreen Modal *@
<P11Modal @bind-Visible="showModal10" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle1"
          IsFullScreen="true"
          ShowDevelopmentErrors="true">
    <HeaderContent>
        <h5 id="fullscreenModalTitle" class="modal-title">@AppState!.T("New User Profile")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is a fullscreen modal that simulates a multi-step form or a new page.")</p>
        <p>@AppState!.T("It covers the entire screen, giving the user a focused experience.")</p>
        <div class="row">
            <div class="col-md-6 mb-3">
                <label for="firstName" class="form-label">@AppState!.T("First Name")</label>
                <input type="text" class="form-control" id="firstName" placeholder="@AppState!.T("Enter first name")" />
            </div>
            <div class="col-md-6 mb-3">
                <label for="lastName" class="form-label">@AppState!.T("Last Name")</label>
                <input type="text" class="form-control" id="lastName" placeholder="@AppState!.T("Enter last name")" />
            </div>
        </div>
        <div class="mb-3">
            <label for="email" class="form-label">@AppState!.T("Email Address")</label>
            <input type="email" class="form-control" id="email" placeholder="@AppState!.T("name@example.com")" />
        </div>
        <div class="mb-3">
            <label for="address" class="form-label">@AppState!.T("Address")</label>
            <textarea class="form-control" id="address" rows="3" placeholder="@AppState!.T("Enter full address")"></textarea>
        </div>
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close" OnClick="(() => CloseModal(10))" />
        <P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(10); })" />
    </FooterContent>
</P11Modal>

@* Example 11: Content dark mode *@
<P11Modal @bind-Visible="showModal11" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle1"
          ShowDevelopmentErrors="true"
          CssClassContent="text-bg-dark">
    <HeaderContent>
        <h5 id="modalTitle1" class="modal-title">@AppState!.T("Basic Modal Title")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is a basic modal. You should not be able to interact with the background.")</p>
        <p>@AppState!.T("Try pressing ESC or clicking the backdrop to close it.")</p>
        <p>@AppState!.T("The focus should be trapped within this modal. Try tabbing around.")</p>
        <input type="text" class="form-control mb-2" placeholder="Input 1" />
        <input type="text" class="form-control" placeholder="Input 2" />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close" OnClick="(() => CloseModal(11))" />
        <P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(11); })" />
    </FooterContent>
</P11Modal>

@* Example 12: Header, Body, Footer *@
<P11Modal @bind-Visible="showModal12" OnClose="OnModalClosed"
          AriaLabelledby="modalTitle1"
          ShowDevelopmentErrors="true"
          CssClassHeader="text-bg-danger"
          CssClassBody="text-bg-success"
          CssClassFooter="text-bg-primary">
    <HeaderContent>
        <h5 id="modalTitle1" class="modal-title">@AppState!.T("Basic Modal Title")</h5>
    </HeaderContent>
    <BodyContent>
        <p>@AppState!.T("This is a basic modal. You should not be able to interact with the background.")</p>
        <p>@AppState!.T("Try pressing ESC or clicking the backdrop to close it.")</p>
        <p>@AppState!.T("The focus should be trapped within this modal. Try tabbing around.")</p>
        <input type="text" class="form-control mb-2" placeholder="Input 1" />
        <input type="text" class="form-control" placeholder="Input 2" />
    </BodyContent>
    <FooterContent>
        <P11Button Text="Close" OnClick="(() => CloseModal(12))" />
        <P11Button Text="Save Changes" OnClick="@(() => { Console.WriteLine("Save Changes clicked!"); CloseModal(12); })" />
    </FooterContent>
</P11Modal>

<div class="component-description mt-4">
    <h2 class="mb-3">@AppState!.T("Event Log")</h2>
    <p>@AppState!.T("The following log tracks events from the modals, such as opening and closing.")</p>
    <div class="card p-3">
        @if (eventLogModal.Any())
        {
            <ul class="list-unstyled mb-0">
                @foreach (var logEntry in eventLogModal)
                {
                    <li>@logEntry</li>
                }
            </ul>
        }
        else
        {
            <p class="text-muted">@AppState!.T("No events logged yet.")</p>
        }
    </div>
</div>
@code {
    private bool showModal1;
    private bool showModal2;
    private bool showModal3;
    private bool showModal4;
    private bool showModal5;
    private bool showModal6;
    private bool showModal7; // Parent modal for nesting
    private bool showModal8; // Child modal for nesting
    private bool showModal9; // Grandchild modal for nesting
    private bool showModal10; // Grandchild modal for fullscreen
    private bool showModal11; // CSS class example
    private bool showModal12; // CSS class example

    private List<string> eventLogModal = new();

    private void OpenModal(int modalNumber)
    {
        switch (modalNumber)
        {
            case 1: showModal1 = true; break;
            case 2: showModal2 = true; break;
            case 3: showModal3 = true; break;
            case 4: showModal4 = true; break;
            case 5: showModal5 = true; break;
            case 6: showModal6 = true; break;
            case 7: showModal7 = true; break;
            case 8: showModal8 = true; break;
            case 9: showModal9 = true; break;
            case 10: showModal10 = true; break;
            case 11: showModal11 = true; break;
            case 12: showModal12 = true; break;
        }
        LogEventModal($"Modal {modalNumber} opened.");
    }

    private void CloseModal(int modalNumber)
    {
        switch (modalNumber)
        {
            case 1: showModal1 = false; break;
            case 2: showModal2 = false; break;
            case 3: showModal3 = false; break;
            case 4: showModal4 = false; break;
            case 5: showModal5 = false; break;
            case 6: showModal6 = false; break;
            case 7: showModal7 = false; break;
            case 8: showModal8 = false; break;
            case 9: showModal9 = false; break;
            case 10: showModal10 = false; break;
            case 11: showModal11 = false; break;
            case 12: showModal12 = false; break;
        }
        LogEventModal($"Modal {modalNumber} closed by internal button.");
    }

    private void OnModalClosed()
    {
        LogEventModal("A modal was closed (via ESC, backdrop, or X button).");
    }

    private void LogEventModal(string message)
    {
        eventLogModal.Insert(0, $"{DateTime.Now:HH:mm:ss} - {message}");
        if (eventLogModal.Count > 10)
        {
            eventLogModal.RemoveAt(eventLogModal.Count - 1);
        }
        StateHasChanged();
    }
}      


Component API

Parameter Type Default Description
Visible bool false Controls the visibility of the modal. Bind with @bind-Visible.
HeaderContent RenderFragment - Content to be rendered in the modal's header.
BodyContent RenderFragment - Content to be rendered in the modal's body.
FooterContent RenderFragment - Content to be rendered in the modal's footer.
Size Size Size.Medium Specifies the size of the modal.
Position ModalPosition ModalPosition.Default Specifies the position of the modal (e.g., Centered, Scrollable).
IsFullScreen bool false Gets or sets a boolean indicating whether the modal should take up the full screen. If true, this overrides the default width/height set by the Position parameter.
ShowCloseButton bool true If true, a close button (X icon) is shown in the product header.
RestoreFocusId string null Specifies the ID of the element that should receive focus after the modal closes. This is crucial for accessibility, especially when using nested modals or opening from specific buttons.
PreventScroll bool true Prevents scrolling on the body when the modal is open. Set to false if you manage scroll prevention globally or want background scrolling.
AriaLabel string null Provides an accessible name for the modal dialog. Use when no visible title is available. Either AriaLabelledby or AriaLabel should be provided for accessibility.
AriaLabelledby string null Refers to the ID of a visible element (e.g., an h1) that provides an accessible name for the modal dialog. Either AriaLabelledby or AriaLabel should be provided for accessibility.
ShowDevelopmentErrors bool false If true, logs warnings to the console for common development issues (e.g., missing accessibility attributes).
CssClassContent string? null Gets or sets an optional CSS class string that will be applied to the modal content of the component.
CssClassHeader string? null Gets or sets an optional CSS class string that will be applied to the modal header of the component.
CssClassBody string? null Gets or sets an optional CSS class string that will be applied to the modal body of the component.
CssClassFooter string? null Gets or sets an optional CSS class string that will be applied to the modal footer of the component.
Events
VisibleChanged EventCallback<bool> - Event callback for when the visibility changes.
OnClose EventCallback - Event callback invoked when the modal is closed (via Escape key, backdrop click, or close button).
An unhandled error has occurred. Reload 🗙