예제 #1
0
    def full(title, datatable, primary_button, helper_text=None):
        header = [hg.H4(title)]
        if helper_text:
            header.append(
                hg.P(helper_text, _class="bx--data-table-header__description"))

        return hg.DIV(
            hg.DIV(*header, _class="bx--data-table-header"),
            hg.SECTION(
                hg.DIV(
                    hg.DIV(_class="bx--action-list"),
                    hg.DIV(
                        hg.P(
                            hg.SPAN(0, data_items_selected=True),
                            _(" items selected"),
                            _class="bx--batch-summary__para",
                        ),
                        _class="bx--batch-summary",
                    ),
                    _class="bx--batch-actions",
                    aria_label=_("Table Action Bar"),
                ),
                hg.DIV(
                    hg.DIV(Search(),
                           _class="bx--toolbar-search-container-expandable"),
                    primary_button,
                    _class="bx--toolbar-content",
                ),
                _class="bx--table-toolbar",
            ),
            datatable,
            _class="bx--data-table-container",
            data_table=True,
        )
예제 #2
0
    def __init__(
        self,
        title,
        subtitle,
        kind="info",
        lowcontrast=False,
        hideclosebutton=False,
        hidetimestamp=False,
        **attributes,
    ):
        """
        kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt"
        """
        assert (
            kind in KIND_ICON_MAPPING
        ), f"kind '{kind}' does not exists, must be one of {KIND_ICON_MAPPING.keys()}"
        self.hidetimestamp = hidetimestamp

        attributes["data-notification"] = True
        attributes["_class"] = (
            attributes.get("_class", "")
            + f" bx--toast-notification bx--toast-notification--{kind}"
        )
        if lowcontrast:
            attributes["_class"] += "  bx--toast-notification--low-contrast"
        attributes["role"] = "alert"

        timestampelem = (
            [
                htmlgenerator.P(
                    _("Time stamp "), _class="bx--toast-notification__caption"
                )
            ]
            if not hidetimestamp
            else []
        )
        children = [
            Icon(
                KIND_ICON_MAPPING[kind],
                size=20,
                _class="bx--toast-notification__icon",
            ),
            htmlgenerator.DIV(
                htmlgenerator.H3(title, _class="bx--toast-notification__title"),
                htmlgenerator.P(subtitle, _class="bx--toast-notification__subtitle"),
                *timestampelem,
                _class="bx--toast-notification__details",
            ),
        ]
        if not hideclosebutton:
            children.append(
                htmlgenerator.BUTTON(
                    Icon("close", size=20, _class="bx--toast-notification__close-icon"),
                    data_notification_btn=True,
                    _class="bx--toast-notification__close-button",
                    aria_label="close",
                )
            )
        super().__init__(*children, **attributes)
예제 #3
0
def error_layout(
    request,
    status_code: int,
    status_title: str,
    description: Union[str, hg.BaseElement],
    exception_detail: str = None,
):
    return hg.BaseElement(
        hg.H1(f"{status_code}: {status_title}", style="margin-bottom: 1rem;"),
        hg.P(
            description,
            style="margin-bottom: 1rem;",
        ),
        hg.If(
            bool(exception_detail),
            hg.BaseElement(
                hg.H4("Detail", style="margin-bottom: 1rem;"),
                hg.DIV(
                    exception_detail,
                    style=(
                        "border: 1px solid grey;"
                        "padding: 1rem;"
                        "font-family: monospace;"
                        "margin-bottom: 1rem;"
                    ),
                ),
            ),
        ),
        Button.from_link(
            Link(
                label=_("Back to homepage"),
                href=hg.F(lambda c: c["request"].META["SCRIPT_NAME"] or "/"),
            )
        ),
    )
예제 #4
0
def maintenance_search_reindex(request):
    class ReindexForm(forms.Form):
        confirmed = forms.BooleanField(
            widget=forms.HiddenInput,
            initial=True,
        )

    form: ReindexForm
    received_post = False
    logmsg: str = None

    if request.method == "POST":
        form = ReindexForm(request.POST)
        if form.is_valid() and "confirmed" in form.cleaned_data:
            received_post = True

            out = StringIO()
            management.call_command("rebuild_index",
                                    interactive=False,
                                    stdout=out)
            logmsg = out.getvalue().replace("\n", "<br>")

            # try adding some message here.
            messages.info(request, _("Rebuilt search index"))

    if not received_post:
        form = ReindexForm()

    reindex_btn = Form(
        form,
        FormField("confirmed"),
        Button(
            _("Rebuild"),
            type="submit",
            style="margin-bottom: 1rem;",
        ),
    )

    return hg.BaseElement(
        hg.P(
            _(("After certain kind of database updates the search index may become outdated. "
               "You can reindex the search index by clicking the button below. "
               "This should fix most problems releated to search fields.")),
            style="margin-bottom: 1rem;",
        ),
        reindex_btn,
        hg.If(
            logmsg,
            hg.BaseElement(
                hg.H6(_("Log from the server"),
                      style="margin-bottom: 0.75rem;"),
                hg.SAMP(hg.mark_safe(logmsg), style="font-family: monospace;"),
            ),
        ),
    )
예제 #5
0
    def __init__(self,
                 label,
                 status,
                 optional=False,
                 tooltip=None,
                 disabled=False,
                 **kwargs):
        assert (status in ProgressStep.STATUS
                ), f"{status} must be one of {ProgressStep.STATUS}"
        kwargs["class"] = (kwargs.get("class", "") +
                           f" bx--progress-step bx--progress-step--{status}")
        if disabled:
            kwargs["aria_disabled"] = "true"
            kwargs["class"] += "  bx--progress-step--disabled"
        elements = [
            Icon(ProgressStep.STATUS[status], size=16),
            hg.P(label, tabindex=0, _class="bx--progress-label"),
            hg.SPAN(_class="bx--progress-line"),
        ]

        if optional:
            elements.insert(
                2, hg.P(_("Optional"), _class="bx--progress-optional"))

        if tooltip is not None:
            tooltipid = hg.html_id(tooltip, "tooltip-label")
            elements[1]["aria-describedby"] = tooltipid
            elements.insert(
                2,
                hg.DIV(
                    hg.SPAN(_class="bx--tooltip__caret"),
                    hg.P(tooltip, _class="bx--tooltip__text"),
                    id=tooltipid,
                    role="tooltip",
                    data_floating_menu_direction="bottom",
                    _class="bx--tooltip",
                    data_avoid_focus_on_open=True,
                ),
            )

        super().__init__(*elements, **kwargs)
예제 #6
0
    def __init__(
        self,
        title,
        subtitle,
        action=None,
        kind="info",
        lowcontrast=False,
        hideclosebutton=False,
        **attributes,
    ):
        """
        action: typle with (action_name, javascript_onclick), e.g. ("Open Google", "windows.location='https://google.com'")
        kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt"
        """
        assert (
            kind in KIND_ICON_MAPPING
        ), f"kind '{kind}' does not exists, must be one of {KIND_ICON_MAPPING.keys()}"
        assert action is None or (
            len(action) == 2
        ), "action must be a tuple with: (action_name, javascript_onclick)"

        attributes["data-notification"] = True
        attributes["_class"] = (
            attributes.get("_class", "")
            + f" bx--inline-notification bx--inline-notification--{kind}"
        )
        if lowcontrast:
            attributes["_class"] += "  bx--inline-notification--low-contrast"
        attributes["role"] = "alert"

        children = [
            htmlgenerator.DIV(
                Icon(
                    KIND_ICON_MAPPING[kind],
                    size=20,
                    _class="bx--inline-notification__icon",
                ),
                htmlgenerator.DIV(
                    htmlgenerator.P(title, _class="bx--inline-notification__title"),
                    htmlgenerator.P(
                        subtitle, _class="bx--inline-notification__subtitle"
                    ),
                    _class="bx--inline-notification__text-wrapper",
                ),
                _class="bx--inline-notification__details",
            ),
        ]
        if action is not None:
            children.append(
                Button(
                    action[0],
                    onclick=action[1],
                    type="ghost",
                    small=True,
                    _class="bx--inline-notification__action-button",
                )
            )
        if not hideclosebutton:
            children.append(
                htmlgenerator.BUTTON(
                    Icon(
                        "close", size=20, _class="bx--inline-notification__close-icon"
                    ),
                    data_notification_btn=True,
                    _class="bx--inline-notification__close-button",
                    aria_label="close",
                )
            )
        super().__init__(*children, **attributes)
예제 #7
0
    def with_toolbar(
        self,
        title: Any,
        helper_text: Any = None,
        primary_button: Optional[Button] = None,
        bulkactions: Iterable[Link] = (),
        pagination_config: Optional[PaginationConfig] = None,
        checkbox_for_bulkaction_name: str = "_selected",
        search_urlparameter: Optional[str] = None,
        settingspanel: Any = None,
    ):
        """
        wrap this datatable with title and toolbar
        title: table title
        helper_text: sub title
        primary_button: bread.layout.button.Button instance
        bulkactions: List of bread.utils.links.Link instances. Will send a post or a get (depending
                     on its "method" attribute) to the target url the sent data will be a form with
                     the selected checkboxes as fields if the head-checkbox has been selected only
                     that field will be selected.
        """
        checkboxallid = f"datatable-check-{hg.html_id(self)}"
        header: List[hg.BaseElement] = [
            hg.H4(title, _class="bx--data-table-header__title")
        ]
        if helper_text is not None:
            header.append(
                hg.P(helper_text, _class="bx--data-table-header__description"))
        bulkactionlist = []
        for link in bulkactions:
            bulkactionlist.append(
                Button(
                    link.label,
                    icon=link.iconname,
                    onclick=hg.BaseElement(
                        "submitbulkaction(this.closest('[data-table]'), '",
                        link.href,
                        "', method='GET')",
                    ),
                ), )

        if bulkactions:
            self.head.insert(
                0,
                hg.TH(
                    hg.INPUT(
                        data_event="select-all",
                        id=checkboxallid,
                        _class="bx--checkbox",
                        type="checkbox",
                        name=checkbox_for_bulkaction_name,
                        value="all",
                    ),
                    hg.LABEL(
                        _for=checkboxallid,
                        _class="bx--checkbox-label",
                    ),
                    _class="bx--table-column-checkbox",
                ),
            )
            self.iterator[0].insert(
                0,
                hg.TD(
                    hg.INPUT(
                        data_event="select",
                        id=hg.BaseElement(
                            checkboxallid,
                            "-",
                            hg.C(self.iterator.loopvariable + "_index"),
                        ),
                        _class="bx--checkbox",
                        type="checkbox",
                        name=checkbox_for_bulkaction_name,
                        value=hg.If(
                            hg.F(lambda c: hasattr(
                                c[self.iterator.loopvariable], "pk")),
                            hg.C(f"{self.iterator.loopvariable}.pk"),
                            hg.C(f"{self.iterator.loopvariable}_index"),
                        ),
                    ),
                    hg.LABEL(
                        _for=hg.BaseElement(
                            checkboxallid,
                            "-",
                            hg.C(self.iterator.loopvariable + "_index"),
                        ),
                        _class="bx--checkbox-label",
                        aria_label="Label name",
                    ),
                    _class="bx--table-column-checkbox",
                ),
            )

        return hg.DIV(
            hg.DIV(*header, _class="bx--data-table-header"),
            hg.SECTION(
                hg.DIV(
                    hg.DIV(
                        *(bulkactionlist + [
                            Button(
                                _("Cancel"),
                                data_event="action-bar-cancel",
                                _class="bx--batch-summary__cancel",
                            )
                        ]),
                        _class="bx--action-list",
                    ),
                    hg.DIV(
                        hg.P(
                            hg.SPAN(0, data_items_selected=True),
                            _(" items selected"),
                            _class="bx--batch-summary__para",
                        ),
                        _class="bx--batch-summary",
                    ),
                    _class="bx--batch-actions",
                    aria_label=_("Table Action Bar"),
                ),
                hg.DIV(
                    searchbar(search_urlparameter)
                    if search_urlparameter else None,
                    Button(
                        icon="settings--adjust",
                        buttontype="ghost",
                        onclick="""
let settings = this.parentElement.parentElement.parentElement.querySelector('.settingscontainer');
settings.style.display = settings.style.display == 'block' ? 'none' : 'block';
event.stopPropagation()""",
                    ) if settingspanel else None,
                    primary_button or None,
                    _class="bx--toolbar-content",
                ),
                _class="bx--table-toolbar",
            ),
            hg.DIV(
                hg.DIV(
                    hg.DIV(
                        settingspanel,
                        _class="bx--tile raised",
                        style="margin: 0; padding: 0",
                    ),
                    _class="settingscontainer",
                    style=
                    "position: absolute; z-index: 999; right: 0; display: none",
                    onload=
                    "document.addEventListener('click', (e) => {this.style.display = 'none'})",
                ),
                style="position: relative",
                onclick="event.stopPropagation()",
            ),
            self,
            Pagination.from_config(pagination_config)
            if pagination_config else None,
            _class="bx--data-table-container",
            data_table=True,
        )
예제 #8
0
    def __init__(
        self,
        label=None,
        help_text=None,
        errors=None,
        inputelement_attrs=None,
        boundfield=None,
        **attributes,
    ):
        inputelement_attrs = inputelement_attrs or {}
        uploadbutton = hg.LABEL(
            hg.SPAN(_("Select file"), role="button"),
            tabindex=0,
            _class=hg.BaseElement(
                "bx--btn bx--btn--tertiary",
                hg.If(inputelement_attrs.get("disabled"),
                      " bx--btn--disabled"),
            ),
            data_file_drop_container=True,
            disabled=inputelement_attrs.get("disabled"),
            data_invalid=hg.If(getattr(errors, "condition", False), True),
            _for=inputelement_attrs.get("id"),
        )
        input = self.get_input_element(
            inputelement_attrs,
            errors,
            onload="""
that = this;
document.addEventListener('change', (e) => {
    that.parentElement.querySelector('[data-file-container]').innerHTML = '';
    var widget = new CarbonComponents.FileUploader(that.parentElement);
    widget._displayFilenames();
    widget.setState('edit');
});
""",
        )
        # we can only clear the field if it originates form a django field
        # otherwise it has no use
        clearbox = None
        if boundfield:
            checkbox_name = hg.F(
                lambda c: hg.resolve_lazy(boundfield, c).field.widget.
                clear_checkbox_name(hg.resolve_lazy(boundfield, c).html_name))
            checkbox_id = hg.F(
                lambda c: hg.resolve_lazy(boundfield, c).field.widget.
                clear_checkbox_id(hg.resolve_lazy(checkbox_name, c)))
            clearbox = hg.If(
                self.clearable,
                hg.INPUT(
                    type="checkbox",
                    name=checkbox_name,
                    id=checkbox_id,
                    style="display: none",
                ),
            )

        # clearbutton is always used, to allow clearing a just selected field in the browser
        clearbutton = hg.If(
            self.clearable,
            hg.SPAN(
                hg.BUTTON(
                    Icon("close", size=16),
                    _class="bx--file-close",
                    type="button",
                    aria_label="close",
                    onclick=hg.If(
                        clearbox,
                        hg.format("$('#{}').checked = 'checked';",
                                  checkbox_id),
                    ),
                ),
                data_for=inputelement_attrs.get("id"),
                _class="bx--file__state-container",
            ),
        )

        super().__init__(
            label,
            hg.DIV(
                uploadbutton,
                input,
                clearbox,
                hg.DIV(
                    hg.If(
                        inputelement_attrs.get("value"),
                        hg.SPAN(
                            hg.P(
                                hg.If(
                                    hg.F(lambda c: hasattr(
                                        hg.resolve_lazy(inputelement_attrs, c).
                                        get("value").file,
                                        "name",
                                    )),
                                    hg.A(
                                        hg.F(lambda c: os.path.basename(
                                            hg.resolve_lazy(
                                                inputelement_attrs, c).get(
                                                    "value").name)),
                                        href=hg.F(lambda c: settings.MEDIA_URL
                                                  + hg.resolve_lazy(
                                                      inputelement_attrs, c
                                                  ).get("value").name),
                                    ),
                                    hg.F(lambda c: os.path.basename(
                                        hg.resolve_lazy(inputelement_attrs, c).
                                        get("value").name)),
                                ),
                                _class="bx--file-filename",
                            ),
                            clearbutton,
                            _class="bx--file__selected-file",
                        ),
                    ),
                    data_file_container=True,
                    _class="bx--file-container",
                ),
                help_text,
                errors,
                _class="bx--file",
                data_file=True,
            ),
            **attributes,
        )
예제 #9
0
    def __init__(
        self,
        label: Any,
        body: Any,
        heading: Optional[Any] = None,
        link: Optional[Link] = None,
        button: Optional[Button] = None,
        icon: Union[Icon, str] = "information",
        menudirection: str = "bottom",
        **attributes,
    ):
        """
        Parameters
        ----------
        label : Any
            Label for a tooltip
        body : Any
            The content inside a tooltip
        heading : Any, optional
            A heading for a tooltip.
        link : Link, optional
            Bread's Link NamedTuple in case you want to bring users to a specific webpage.
        button : Button, optional
            Insert the Bread's Button onto the tooltip.
        icon : Icon, str, optional
            Specify an icon for the tooltip
            The default value is "information".
        menudirection : str
            Where should the tooltip appear besides the tooltip icon.
            It can be either top, left, right, or bottom.
            The default value is "bottom".
        """
        base_class = "bx--tooltip"

        footer_elements: typing.List[hg.BaseElement] = []
        if link:
            footer_elements.append(hg.A(link.label, href=link.href, _class="bx--link"))
        if button:
            footer_elements.append(button)

        base_id = hg.html_id(self, base_class + "-id")
        label_id = f"{base_id}-label"

        trigger_attributes = {
            "_class": base_class + "__trigger",
            "aria_controls": base_id,
            "aria_expanded": "false",
            "aria_haspopup": "true",
            "aria_labelledby": label_id,
            "data_tooltip_target": "#" + base_id,
            "data_tooltip_trigger": True,
        }

        tooltip_attributes = {
            "_class": base_class,
            "aria_hidden": "true",
            "data_floating_menu_direction": menudirection,
            "id": base_id,
        }

        tooltip_content_attributes = {
            "_class": base_class + "__content",
            "aria_describedby": base_id + "-body",
            "aria_labelledby": label_id,
            "role": "dialog",
            "tabindex": "-1",
        }

        tooltip_attributes = hg.merge_html_attrs(tooltip_attributes, attributes)
        icon = _get_icon(icon)

        super().__init__(
            # tooltip label
            hg.DIV(
                label,
                # cannot use bread's Button class because
                # only the class bx--tooltip__trigger can be used
                hg.DIV(
                    icon,
                    **trigger_attributes,
                ),
                id=label_id,
                _class=base_class + "__label",
            ),
            # the real tooltip goes here
            hg.DIV(
                hg.SPAN(_class=base_class + "__caret"),
                hg.DIV(
                    hg.If(
                        bool(heading),
                        hg.H4(
                            heading,
                            id=base_id + "-heading",
                            _class=base_class + "__heading",
                        ),
                    ),
                    hg.P(body, id=base_id + "-body"),
                    hg.If(
                        len(footer_elements) > 0,
                        hg.DIV(
                            *footer_elements,
                            _class=base_class + "__footer",
                        ),
                    ),
                    **tooltip_content_attributes,
                ),
                hg.SPAN(tabindex="0"),
                **tooltip_attributes,
            ),
        )
예제 #10
0
    def __init__(
        self,
        message,
        details,
        kind="info",
        lowcontrast=False,
        hideclosebutton=False,
        hidetimestamp=False,
        autoremove=4.0,
        **attributes,
    ):
        """
        kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt"
        autoremove: remove notification after ``autoremove`` seconds
        """
        self.hidetimestamp = hidetimestamp

        attributes["data-notification"] = True
        attributes["_class"] = hg.BaseElement(
            attributes.get("_class", ""),
            " bx--toast-notification bx--toast-notification--",
            kind,
            hg.If(lowcontrast, " bx--toast-notification--low-contrast"),
        )
        attributes["role"] = "alert"

        attributes["style"] = hg.BaseElement(
            attributes.get("style", ""),
            ";opacity: 0; animation: ",
            hg.F(lambda c: autoremove * (c["message_index"] + 1)),
            "s ease-in-out notification",
        )
        attributes["onload"] = hg.BaseElement(
            attributes.get("onload", ""),
            ";setTimeout(() => this.style.display = 'None', ",
            hg.F(lambda c: (autoremove * 1000 * (c["message_index"] + 1))),
            ")",
        )

        timestampelem = ([
            hg.P(
                _("Time stamp"), " ", _class="bx--toast-notification__caption")
        ] if not hidetimestamp else [])
        children = [
            Icon(
                hg.F(lambda c: KIND_ICON_MAPPING[hg.resolve_lazy(kind, c)]),
                size=20,
                _class="bx--toast-notification__icon",
            ),
            hg.DIV(
                hg.DIV(message, _class="bx--toast-notification__title"),
                hg.DIV(details, _class="bx--toast-notification__subtitle"),
                *timestampelem,
                _class="bx--toast-notification__details",
            ),
        ]
        children.append(
            hg.If(
                hideclosebutton,
                None,
                hg.BUTTON(
                    Icon("close",
                         size=20,
                         _class="bx--toast-notification__close-icon"),
                    data_notification_btn=True,
                    _class="bx--toast-notification__close-button",
                    aria_label="close",
                ),
            ))
        super().__init__(*children, **attributes)
예제 #11
0
    def __init__(
        self,
        message,
        details,
        action=None,
        kind="info",
        lowcontrast=False,
        hideclosebutton=False,
        **attributes,
    ):
        """
        action: typle with (action_name, javascript_onclick), e.g. ("Open Google", "windows.location='https://google.com'")
        kind: can be one of "error" "info", "info-square", "success", "warning", "warning-alt"
        """
        if action is not None and (len(action) != 2):
            raise ValueError(
                "action must be a tuple with: (action_name, javascript_onclick)"
            )

        attributes["data-notification"] = True
        attributes["_class"] = hg.BaseElement(
            attributes.get("_class", ""),
            " bx--inline-notification bx--inline-notification--",
            kind,
            hg.If(lowcontrast, " bx--inline-notification--low-contrast"),
        )
        attributes["role"] = "alert"

        children = [
            hg.DIV(
                Icon(
                    hg.F(
                        lambda c: KIND_ICON_MAPPING[hg.resolve_lazy(kind, c)]),
                    size=20,
                    _class="bx--inline-notification__icon",
                ),
                hg.DIV(
                    hg.P(message, _class="bx--inline-notification__title"),
                    hg.P(details, _class="bx--inline-notification__subtitle"),
                    _class="bx--inline-notification__text-wrapper",
                ),
                _class="bx--inline-notification__details",
            ),
        ]
        if action is not None:
            children.append(
                Button(
                    action[0],
                    onclick=action[1],
                    buttontype="ghost",
                    small=True,
                    _class="bx--inline-notification__action-button",
                ))
        children.append(
            hg.If(
                hideclosebutton,
                None,
                hg.BUTTON(
                    Icon("close",
                         size=20,
                         _class="bx--inline-notification__close-icon"),
                    data_notification_btn=True,
                    _class="bx--inline-notification__close-button",
                    aria_label="close",
                ),
            ))
        super().__init__(*children, **attributes)
예제 #12
0
    def __init__(
            self,
            heading,
            *content,
            label="",
            size="sm",
            buttons=(),
            id=None,
            with_form=False,
            **attributes,
    ):
        """
        heading: Modal title
        content: content elements of the modal, can be empty
        label: small informative label above heading
        size: one of ["xs", "sm", "md", "lg"]
        buttons: buttons displayed on bottom of modal, last button has default focus
                 the attribute "data_modal_close" can be set on an button in order to make it a cancel button

        In order to open the modal just pass self.openerattributes as kwargs to another html element, e.g. a button

            modal = modal.Modal("My Modal", "Hello world")
            button.Button("Model", **modal.openerattributes)

        """
        if size not in Modal.SIZES:
            raise ValueError(
                f"argument 'size' has value {size} but needs to be one of {Modal.SIZES}"
            )
        if buttons and "data_modal_primary_focus" not in buttons[-1].attributes:
            buttons[-1].attributes["data_modal_primary_focus"] = True
        attributes["_class"] = attributes.get("_class", "") + " bx--modal"
        self.id = hg.html_id(self, prefix="modal-") if id is None else id
        self.openerattributes = {
            "data_modal_target": hg.format("#{}", self.id)
        }
        self.contentcontainer = hg.DIV(
            *content,
            _class="bx--modal-content" +
            (" bx--modal-content--with-form" if with_form else ""),
            tabindex=0,
        )
        super().__init__(
            hg.DIV(
                hg.DIV(
                    hg.P(
                        label,
                        _class="bx--modal-header__label",
                    ),
                    hg.P(
                        heading,
                        _class="bx--modal-header__heading",
                    ),
                    hg.BUTTON(
                        Icon(
                            "close",
                            size=16,
                            _class="bx--modal-close__icon",
                            aria_hidden="true",
                        ),
                        _class="bx--modal-close",
                        type="button",
                        data_modal_close=True,
                    ),
                    _class="bx--modal-header",
                ),
                self.contentcontainer,
                hg.DIV(_class="bx--modal-content--overflow-indicator"),
                hg.DIV(
                    *buttons,
                    _class="bx--modal-footer",
                ) if buttons else "",
                _class=f"bx--modal-container  bx--modal-container--{size}",
            ),
            data_modal=True,
            id=self.id,
            role="dialog",
            aria_modal="true",
            tabindex="-1",
            **attributes,
        )