@use "../variables" as *;
@use "../mixins/border-radius" as *;
@use "../mixins/color-mode" as *;
+@use "../mixins/focus-ring" as *;
// scss-docs-start close-variables
$btn-close-size: 1.25rem !default;
-// $btn-close-width: 1em !default;
-// $btn-close-height: $btn-close-width !default;
-// $btn-close-padding-x: .25em !default;
-// $btn-close-padding-y: $btn-close-padding-x !default;
$btn-close-color: var(--#{$prefix}fg-body) !default;
-$btn-close-focus-shadow: $focus-ring-box-shadow !default;
$btn-close-opacity: .5 !default;
$btn-close-hover-opacity: .75 !default;
$btn-close-focus-opacity: .85 !default;
--#{$prefix}btn-close-color: #{$btn-close-color};
--#{$prefix}btn-close-opacity: #{$btn-close-opacity};
--#{$prefix}btn-close-hover-opacity: #{$btn-close-hover-opacity};
- --#{$prefix}btn-close-focus-shadow: #{$btn-close-focus-shadow};
--#{$prefix}btn-close-focus-opacity: #{$btn-close-focus-opacity};
--#{$prefix}btn-close-disabled-opacity: #{$btn-close-disabled-opacity};
// scss-docs-end close-css-vars
padding: 0;
color: var(--#{$prefix}btn-close-color);
background: transparent;
- // background: transparent var(--#{$prefix}btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements
- // filter: var(--#{$prefix}btn-close-filter);
border: 0; // for button elements
+ @include border-radius(var(--#{$prefix}border-radius-sm));
opacity: var(--#{$prefix}btn-close-opacity);
> svg {
- // width: 100%;
- // height: 100%
+ display: block;
+ width: 100%;
+ height: 100%;
fill: currentcolor;
}
}
&:focus {
- outline: 0;
- box-shadow: var(--#{$prefix}btn-close-focus-shadow);
+ @include focus-ring();
opacity: var(--#{$prefix}btn-close-focus-opacity);
}
opacity: var(--#{$prefix}btn-close-disabled-opacity);
}
}
-
- // @mixin btn-close-white() {
- // // --#{$prefix}btn-close-filter: #{$btn-close-filter-dark};
- // }
-
- // .btn-close-white {
- // @include btn-close-white();
- // }
-
- // :root,
- // [data-bs-theme="light"] {
- // // --#{$prefix}btn-close-filter: #{$btn-close-filter};
- // }
-
- // @if $enable-dark-mode {
- // @include color-mode(dark, true) {
- // @include btn-close-white();
- // }
- // }
}
--- /dev/null
+---
+interface Props {
+ /**
+ * Additional CSS classes to add to the button (e.g., "me-2 m-auto").
+ */
+ class?: string
+ /**
+ * The component to dismiss (e.g., "dialog", "alert", "offcanvas", "toast").
+ * Sets the `data-bs-dismiss` attribute.
+ */
+ dismiss?: string
+}
+
+const { class: className, dismiss } = Astro.props
+---
+
+<button type="button" class:list={["btn-close", className]} data-bs-dismiss={dismiss} aria-label="Close">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20" fill="none">
+ <path fill="currentcolor" d="M12 0a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm-.646 4.646a.5.5 0 0 0-.707 0L8 7.293 5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.647a.5.5 0 1 0 .708.707L8 8.707l2.647 2.646a.5.5 0 1 0 .707-.707L8.707 8l2.646-2.646a.5.5 0 0 0 0-.708z"/>
+ </svg>
+</button>
<Example code={`<div class="alert alert-warning alert-dismissible fade show" role="alert">
<strong>Holy guacamole!</strong> You should check in on some of those fields below.
- <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+ <CloseButton dismiss="alert" />
</div>`} />
<Callout type="warning">
<dialog class="dialog" id="exampleDialog">
<div class="dialog-header">
<h1 class="dialog-title">Dialog title</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This is a native dialog element. It uses the browser's built-in modal behavior for accessibility and focus management.</p>
<dialog class="dialog" id="exampleDialog">
<div class="dialog-header">
<h1 class="dialog-title">Dialog title</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20" fill="none">
+ <path fill="currentcolor" d="M12 0a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm-.646 4.646a.5.5 0 0 0-.707 0L8 7.293 5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.647a.5.5 0 1 0 .708.707L8 8.707l2.647 2.646a.5.5 0 1 0 .707-.707L8.707 8l2.646-2.646a.5.5 0 0 0 0-.708z"/>
+ </svg>
+ </button>
</div>
<div class="dialog-body">
<p>Dialog body content goes here.</p>
<dialog class="dialog" id="staticBackdropDialog">
<div class="dialog-header">
<h1 class="dialog-title">Static backdrop</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>I will not close if you click outside of me. Use the close button or press Escape.</p>
<dialog class="dialog" id="scrollingLongDialog">
<div class="dialog-header">
<h1 class="dialog-title">Scrolling dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This is some placeholder content to show the scrolling behavior for dialogs. When the content exceeds the viewport height, you can scroll the entire dialog within the window—the header, body, and footer all move together.</p>
<dialog class="dialog dialog-scrollable" id="scrollableBodyDialog">
<div class="dialog-header">
<h1 class="dialog-title">Scrollable body</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This is some placeholder content to show the scrolling behavior for dialogs. We use repeated line breaks to demonstrate how content can exceed the dialog's inner height, showing scrolling within the body while the header and footer remain fixed.</p>
<dialog class="dialog dialog-scrollable" id="scrollableBodyDialog">
<div class="dialog-header">
<h1 class="dialog-title">Scrollable body</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close">
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20" fill="none">
+ <path fill="currentcolor" d="M12 0a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm-.646 4.646a.5.5 0 0 0-.707 0L8 7.293 5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.647a.5.5 0 1 0 .708.707L8 8.707l2.647 2.646a.5.5 0 1 0 .707-.707L8.707 8l2.646-2.646a.5.5 0 0 0 0-.708z"/>
+ </svg>
+ </button>
</div>
<div class="dialog-body">
<!-- Long content here -->
<div class="dialog-box">
<div class="dialog-header">
<h1 class="dialog-title">Overflow dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This dialog extends beyond the viewport height. Scroll to see more content. Notice how the entire dialog—including header and footer—shifts up and down as you scroll.</p>
<dialog class="dialog" id="swapDialog1">
<div class="dialog-header">
<h1 class="dialog-title">First dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>Click below to swap to a second dialog. Notice the backdrop stays visible—no flash!</p>
<dialog class="dialog" id="swapDialog2">
<div class="dialog-header">
<h1 class="dialog-title">Second dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This is the second dialog. You can swap back to the first, or close this one entirely.</p>
<dialog class="dialog" id="nonModalDialog">
<div class="dialog-header">
<h1 class="dialog-title">Non-modal dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This dialog doesn't block the page. You can still interact with content behind it.</p>
<dialog class="dialog dialog-xl" id="exampleDialogXl">
<div class="dialog-header">
<h1 class="dialog-title">Extra large dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This is an extra large dialog using the <code>.dialog-xl</code> class.</p>
<dialog class="dialog dialog-lg" id="exampleDialogLg">
<div class="dialog-header">
<h1 class="dialog-title">Large dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This is a large dialog using the <code>.dialog-lg</code> class.</p>
<dialog class="dialog dialog-sm" id="exampleDialogSm">
<div class="dialog-header">
<h1 class="dialog-title">Small dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This is a small dialog using the <code>.dialog-sm</code> class.</p>
<dialog class="dialog dialog-fullscreen" id="exampleDialogFullscreen">
<div class="dialog-header">
<h1 class="dialog-title">Fullscreen dialog</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This dialog covers the entire viewport.</p>
<dialog class="dialog dialog-fullscreen-lg-down" id="exampleDialogFullscreenLg">
<div class="dialog-header">
<h1 class="dialog-title">Fullscreen below lg</h1>
- <button type="button" class="btn-close" data-bs-dismiss="dialog" aria-label="Close"></button>
+ <CloseButton dismiss="dialog" />
</div>
<div class="dialog-body">
<p>This dialog is fullscreen below the <code>lg</code> breakpoint.</p>
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasNavbar" aria-labelledby="offcanvasNavbarLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasNavbarLabel">Offcanvas</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
<div class="offcanvas offcanvas-end text-bg-dark" tabindex="-1" id="offcanvasDarkNavbar" aria-labelledby="offcanvasDarkNavbarLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasDarkNavbarLabel">Dark offcanvas</h5>
- <button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
<Example class="bd-example-offcanvas p-0 bg-body-tertiary overflow-hidden" code={`<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvas" aria-labelledby="offcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasLabel">Offcanvas</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
Content for the offcanvas goes here. You can place just about any Bootstrap component or custom elements here.
<div class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasExample" aria-labelledby="offcanvasExampleLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasExampleLabel">Offcanvas</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
<div>
<div class="offcanvas offcanvas-start" data-bs-scroll="true" data-bs-backdrop="false" tabindex="-1" id="offcanvasScrolling" aria-labelledby="offcanvasScrollingLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasScrollingLabel">Offcanvas with body scrolling</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
<p>Try scrolling the rest of the page to see this option in action.</p>
<div class="offcanvas offcanvas-start" data-bs-scroll="true" tabindex="-1" id="offcanvasWithBothOptions" aria-labelledby="offcanvasWithBothOptionsLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasWithBothOptionsLabel">Backdrop with scrolling</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
<p>Try scrolling the rest of the page to see this option in action.</p>
<div class="offcanvas offcanvas-start" data-bs-backdrop="static" tabindex="-1" id="staticBackdrop" aria-labelledby="staticBackdropLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="staticBackdropLabel">Offcanvas</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
<div>
<Example class="bd-example-offcanvas p-0 bg-body-secondary overflow-hidden" code={`<div class="offcanvas offcanvas-start show" tabindex="-1" id="offcanvasDark" aria-labelledby="offcanvasDarkLabel" data-bs-theme="dark">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasDarkLabel">Offcanvas</h5>
- <button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvasDark" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvasDark" />
</div>
<div class="offcanvas-body">
<p>Place offcanvas content here.</p>
<div class="offcanvas-lg offcanvas-end" tabindex="-1" id="offcanvasResponsive" aria-labelledby="offcanvasResponsiveLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasResponsiveLabel">Responsive offcanvas</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" data-bs-target="#offcanvasResponsive" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
<p class="mb-0">This is content within an <code>.offcanvas-lg</code>.</p>
<div class="offcanvas offcanvas-top" tabindex="-1" id="offcanvasTop" aria-labelledby="offcanvasTopLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasTopLabel">Offcanvas top</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
...
<div class="offcanvas offcanvas-end" tabindex="-1" id="offcanvasRight" aria-labelledby="offcanvasRightLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasRightLabel">Offcanvas right</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body">
...
<div class="offcanvas offcanvas-bottom" tabindex="-1" id="offcanvasBottom" aria-labelledby="offcanvasBottomLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasBottomLabel">Offcanvas bottom</h5>
- <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
+ <CloseButton dismiss="offcanvas" />
</div>
<div class="offcanvas-body small">
...
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small>11 mins ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Hello, world! This is a toast message.
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small>11 mins ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Hello, world! This is a toast message.
<img src="..." class="rounded me-2" alt="...">
<strong class="me-auto">Bootstrap</strong>
<small>11 mins ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Hello, world! This is a toast message.
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small class="text-body-secondary">11 mins ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Hello, world! This is a toast message.
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small class="text-body-secondary">just now</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
See? Just like this.
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small class="text-body-secondary">2 seconds ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Heads up, toasts will stack automatically
<div class="toast-body">
Hello, world! This is a toast message.
</div>
- <button type="button" class="btn-close me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" class="me-2 m-auto" />
</div>
</div>`} />
### Color schemes
-Building on the above example, you can create different toast color schemes with our [color]([[docsref:/utilities/colors]]) and [background]([[docsref:/utilities/background]]) utilities. Here we’ve added `.text-bg-primary` to the `.toast`, and then added `.btn-close-white` to our close button. For a crisp edge, we remove the default border with `.border-0`.
+Building on the above example, you can create different toast color schemes with our [color]([[docsref:/utilities/colors]]) and [background]([[docsref:/utilities/background]]) utilities. Here we’ve added `.text-bg-primary` to the `.toast`. For a crisp edge, we remove the default border with `.border-0`.
<Example code={`<div class="toast align-items-center text-bg-primary border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
Hello, world! This is a toast message.
</div>
- <button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" class="me-2 m-auto" />
</div>
</div>`} />
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small class="text-body-secondary">just now</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
See? Just like this.
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small class="text-body-secondary">2 seconds ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Heads up, toasts will stack automatically
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small>11 mins ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Hello, world! This is a toast message.
<Placeholder width="20" height="20" background="#007aff" class="rounded me-2" text={false} title={false} />
<strong class="me-auto">Bootstrap</strong>
<small>11 mins ago</small>
- <button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+ <CloseButton dismiss="toast" />
</div>
<div class="toast-body">
Hello, world! This is a toast message.
import { getData } from './data'
const placeholderRegex = /<Placeholder\s+([^>]+)\/>/g
+const closeButtonRegex = /<CloseButton\s*([^>]*?)\/>/g
+
+// Close button SVG icon
+const CLOSE_BUTTON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="20" height="20" fill="none">
+ <path fill="currentcolor" d="M12 0a4 4 0 0 1 4 4v8a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4zm-.646 4.646a.5.5 0 0 0-.707 0L8 7.293 5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.647a.5.5 0 1 0 .708.707L8 8.707l2.647 2.646a.5.5 0 1 0 .707-.707L8.707 8l2.646-2.646a.5.5 0 0 0 0-.708z"/>
+ </svg>`
/**
* Generates all the placeholder attributes and options required to render a placeholder.
}
}
+/**
+ * Renders a CloseButton component to its HTML string representation.
+ * Supports optional `dismiss` and `class` attributes.
+ */
+function renderCloseButtonToString(attributes: Record<string, string>): string {
+ const dismiss = attributes.dismiss
+ const extraClass = attributes.class
+ const dismissAttr = dismiss ? ` data-bs-dismiss="${dismiss}"` : ''
+ const classValue = extraClass ? `btn-close ${extraClass}` : 'btn-close'
+
+ return `<button type="button" class="${classValue}"${dismissAttr} aria-label="Close">
+ ${CLOSE_BUTTON_SVG}
+ </button>`
+}
+
/**
* Replaces placeholders described using the `<Placeholder />` component in HTML markup with the expected HTML content.
+ * Also replaces `<CloseButton />` components with the full close button HTML.
+ *
* This is useful to render examples that have a pretty large set of constraints:
*
* - The provided HTML code is not valid MDX (e.g. unclosed void elements like <img>) but can contain the
* If you are not sure if you need to use this function, you probably don't.
*/
export function replacePlaceholdersInHtml(html: string) {
+ // Replace CloseButton components
+ html = html.replace(closeButtonRegex, (match) => {
+ const document = htmlparser2.parseDocument(match, { xmlMode: true })
+ const closeButtonElement = document.firstChild
+
+ if (
+ document.children.length > 1 ||
+ !closeButtonElement ||
+ closeButtonElement.type !== htmlparser2.ElementType.Tag ||
+ closeButtonElement.name !== 'CloseButton'
+ ) {
+ throw new Error('Invalid CloseButton element.')
+ }
+
+ return renderCloseButtonToString(closeButtonElement.attribs as Record<string, string>)
+ })
+
+ // Replace Placeholder components
return html.replace(placeholderRegex, (match) => {
const document = htmlparser2.parseDocument(match, { xmlMode: true })
const placeholderElement = document.firstChild
export const CSSVariables: typeof import('@shortcodes/CSSVariables.astro').default
export const Callout: typeof import('@shortcodes/Callout.astro').default
export const CalloutDeprecatedDarkVariants: typeof import('@shortcodes/CalloutDeprecatedDarkVariants.astro').default
+ export const CloseButton: typeof import('@shortcodes/CloseButton.astro').default
export const Code: typeof import('@shortcodes/Code.astro').default
export const DeprecatedIn: typeof import('@shortcodes/DeprecatedIn.astro').default
export const Details: typeof import('@shortcodes/Details.astro').default