Commit fad9f84
[Toolkit][Bootstrap] Add the Bootstrap kit
[Toolkit][Bootstrap] Harden Offcanvas a11y wiring and Toast attributes
Offcanvas now mirrors Modal's provide/inject pattern with Offcanvas:Header,
Offcanvas:Title and Offcanvas:Body sub-components. The title id is auto-wired
to the root aria-labelledby, so a custom header can no longer leave a dangling
reference (previously consumers had to repeat "<id>-label" by hand).
Toast defaults `delay` to null and emits data-bs-delay only when it is set,
dropping the hard-coded 5000 magic number, and clamps `role`/`live` to their
allowed values like the other enum props in the kit.
Adjust code previews height
Migrate documentation & examples to the new format
diff --git a/src/Toolkit/CHANGELOG.md b/src/Toolkit/CHANGELOG.md
index c7b1423329a..29eb1aae2ea 100644
--- a/src/Toolkit/CHANGELOG.md
+++ b/src/Toolkit/CHANGELOG.md
@@ -2,11 +2,12 @@
## 3.4.0
-- Add new linter checker `ClassMergeSpacingChecker` that checks for invalid `'<literal>' ~ attributes.render(...)` pattern
+- [Bootstrap] Add the Bootstrap kit
- [Common] Add the `common` kit, with design-system agnostic `logout-link` and `post-link` recipes
+- Add new linter checker `ClassMergeSpacingChecker` that checks for invalid `'<literal>' ~ attributes.render(...)` pattern
- Add per-recipe `README.md` documentation
-- Add `RecipeDocRenderer` that renders recipes docs as HTML or Markdown, to ease wiring official kits into ux.symfony.com and, in the future, previewing community kits
- Add optional `color` and `icon` fields to the kit manifest
+- Add `RecipeDocRenderer` that renders recipes docs as HTML or Markdown, to ease wiring official kits into ux.symfony.com and, in the future, previewing community kits
## 3.3.0
diff --git a/src/Toolkit/kits/bootstrap/INSTALL.md b/src/Toolkit/kits/bootstrap/INSTALL.md
new file mode 100644
index 00000000000..049d781d3cc
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/INSTALL.md
@@ -0,0 +1,32 @@
+# Getting started
+
+This kit provides ready-to-use Twig components based on [Bootstrap 5.3](https://getbootstrap.com/docs/5.3/).
+
+## Requirements
+
+Bootstrap's CSS is required by all components. Bootstrap's JavaScript is also required by interactive components and examples.
+
+## Installation
+
+Install Bootstrap with AssetMapper:
+
+```bash
+php bin/console importmap:require bootstrap bootstrap/dist/css/bootstrap.min.css
+```
+
+Then import Bootstrap from `assets/app.js`:
+
+```js
+import 'bootstrap/dist/css/bootstrap.min.css';
+import 'bootstrap';
+```
+
+With npm, pnpm, or Yarn, install Bootstrap instead with your package manager:
+
+```bash
+npm install bootstrap@^5.3.0
+```
+
+The imports in `assets/app.js` remain the same.
+
+And that's it! You can now use the Bootstrap Twig components in your templates.
diff --git a/src/Toolkit/kits/bootstrap/accordion/README.md b/src/Toolkit/kits/bootstrap/accordion/README.md
new file mode 100644
index 00000000000..091b51a9b06
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/README.md
@@ -0,0 +1,102 @@
+# Accordion
+
+Build vertically collapsing sections powered by Bootstrap's Collapse plugin.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="faq-accordion">
+ <twig:Accordion:Item id="faq-first" label="What is Symfony UX?" expanded>
+ <strong>Symfony UX connects Symfony with modern frontend tools.</strong>
+ It provides JavaScript packages, Twig components, and integrations designed to work naturally with Symfony applications.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="faq-second" label="Can I customize these components?">
+ Yes. Pass HTML attributes and Bootstrap utility classes directly to the Twig components, or customize Bootstrap through Sass and CSS variables.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="faq-third" label="Does the accordion support multiple open items?">
+ Set <code>alwaysOpen</code> on the root accordion to omit Bootstrap's parent constraint.
+ </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Accordion id="account-accordion">
+ <twig:Accordion:Item id="account-profile" label="Profile" expanded>
+ Update your personal details and contact information.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="account-security" label="Security">
+ Review your password and two-factor authentication settings.
+ </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## Accessibility
+
+Choose a `headingTag` that fits the surrounding document hierarchy. Each heading contains a button with `aria-expanded` and `aria-controls`, and each panel references its heading with `aria-labelledby`.
+
+Bootstrap's transition respects `prefers-reduced-motion`. Keep labels concise and do not hide essential information exclusively inside collapsed sections.
+
+## Examples
+
+Render a conventional accordion where opening one item closes the currently open item.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="accordion-example">
+ <twig:Accordion:Item id="accordion-item-one" label="Accordion Item #1" expanded>
+ <strong>This is the first item's accordion body.</strong>
+ It is shown by default until the Collapse plugin updates the appropriate classes and ARIA state.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="accordion-item-two" label="Accordion Item #2">
+ <strong>This is the second item's accordion body.</strong>
+ It is hidden by default and can contain nearly any HTML content.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="accordion-item-three" label="Accordion Item #3">
+ <strong>This is the third item's accordion body.</strong>
+ Opening it closes the currently open item.
+ </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+### Flush
+
+Remove outer borders and rounded corners for an edge-to-edge accordion.
+
+```twig {"preview":true,"height":"360px"}
+<twig:Accordion id="accordion-flush-example" flush>
+ <twig:Accordion:Item id="flush-item-one" label="Accordion Item #1">
+ Placeholder content for this edge-to-edge accordion item.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="flush-item-two" label="Accordion Item #2">
+ The flush style removes some borders and rounded corners.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="flush-item-three" label="Accordion Item #3">
+ Items retain the same Collapse behavior and accessibility attributes.
+ </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+### Always open
+
+Allow several items to remain expanded by omitting Bootstrap's parent constraint.
+
+```twig {"preview":true,"height":"430px"}
+<twig:Accordion id="accordion-always-open" alwaysOpen>
+ <twig:Accordion:Item id="always-open-one" label="Accordion Item #1" expanded>
+ This item starts open and remains open when another item is expanded.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="always-open-two" label="Accordion Item #2">
+ Multiple items can stay open because no <code>data-bs-parent</code> constraint is rendered.
+ </twig:Accordion:Item>
+ <twig:Accordion:Item id="always-open-three" label="Accordion Item #3">
+ Each button still controls its own panel and synchronizes <code>aria-expanded</code>.
+ </twig:Accordion:Item>
+</twig:Accordion>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/accordion/manifest.json b/src/Toolkit/kits/bootstrap/accordion/manifest.json
new file mode 100644
index 00000000000..0c313137099
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/manifest.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "../../../schema-kit-recipe-v1.json",
+ "type": "component",
+ "name": "Accordion",
+ "description": "Build vertically collapsing sections powered by Bootstrap's Collapse plugin.",
+ "version-added": "3.4",
+ "copy-files": {
+ "templates/": "templates/"
+ },
+ "dependencies": {
+ "composer": ["symfony/ux-twig-component:^3.1", "twig/extra-bundle", "twig/html-extra:^3.12.0"]
+ }
+}
diff --git a/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig
new file mode 100644
index 00000000000..0959ef8e4fe
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion.html.twig
@@ -0,0 +1,16 @@
+{# @prop id string The unique identifier used to coordinate the accordion items. #}
+{# @prop flush boolean Whether to remove outer borders and rounded corners. #}
+{# @prop alwaysOpen boolean Whether multiple accordion items may remain open. #}
+{# @block content The accordion items, typically `Accordion:Item` components. #}
+{%- props id, flush = false, alwaysOpen = false -%}
+{%- do provide('accordion.id', id) -%}
+{%- do provide('accordion.alwaysOpen', alwaysOpen) -%}
+<div {{ attributes.defaults({
+ id: id,
+ class: html_classes({
+ accordion: true,
+ 'accordion-flush': flush,
+ }),
+}) }}>
+ {%- block content %}{% endblock -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig
new file mode 100644
index 00000000000..9ddada634c7
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/accordion/templates/components/Accordion/Item.html.twig
@@ -0,0 +1,43 @@
+{# @prop id string The unique identifier used for the heading and collapsible panel. #}
+{# @prop label string The text displayed in the accordion button. #}
+{# @prop expanded boolean Whether the accordion item is initially expanded. #}
+{# @prop headingTag 'h1'|'h2'|'h3'|'h4'|'h5'|'h6' The heading element to render. #}
+{# @block label The accordion button label. #}
+{# @block content The content displayed inside the collapsible panel. #}
+{%- props id, label = '', expanded = false, headingTag = 'h2' -%}
+{%- set _accordion_id = inject('accordion.id', null) -%}
+{%- set _accordion_always_open = inject('accordion.alwaysOpen', false) -%}
+{%- set heading_id = id ~ '-heading' -%}
+{%- set collapse_id = id ~ '-collapse' -%}
+{%- set final_heading_tag = headingTag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] ? headingTag : 'h2' -%}
+<div {{ attributes.defaults({class: 'accordion-item'}) }}>
+ <{{ final_heading_tag }} class="accordion-header" id="{{ heading_id }}">
+ <button
+ class="{{ html_classes({
+ 'accordion-button': true,
+ collapsed: not expanded,
+ }) }}"
+ type="button"
+ data-bs-toggle="collapse"
+ data-bs-target="#{{ collapse_id }}"
+ aria-expanded="{{ expanded ? 'true' : 'false' }}"
+ aria-controls="{{ collapse_id }}"
+ >
+ {%- block label %}{{ label }}{% endblock -%}
+ </button>
+ </{{ final_heading_tag }}>
+ <div
+ id="{{ collapse_id }}"
+ class="{{ html_classes({
+ 'accordion-collapse': true,
+ collapse: true,
+ show: expanded,
+ }) }}"
+ aria-labelledby="{{ heading_id }}"
+ {% if not _accordion_always_open and _accordion_id is not null %}data-bs-parent="#{{ _accordion_id }}"{% endif %}
+ >
+ <div class="accordion-body">
+ {%- block content %}{% endblock -%}
+ </div>
+ </div>
+</div>
diff --git a/src/Toolkit/kits/bootstrap/alert/README.md b/src/Toolkit/kits/bootstrap/alert/README.md
new file mode 100644
index 00000000000..de97d720d76
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/README.md
@@ -0,0 +1,131 @@
+# Alert
+
+Provides contextual feedback messages for typical user actions.
+
+```twig {"preview":true,"height":"210px"}
+<twig:Alert color="success" heading="Well done!" dismissible>
+ Your changes were saved successfully. <a href="#" class="alert-link">Review them now</a>.
+</twig:Alert>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Alert>A simple primary alert - check it out!</twig:Alert>
+```
+
+## Accessibility
+
+Do not rely on color alone to communicate the alert's meaning. Include clear visible text or additional context for assistive technologies.
+
+Dismissal removes the alert from the document. When appropriate, listen for Bootstrap's `closed.bs.alert` event and move focus to a logical destination.
+
+## Examples
+
+### Contextual variants
+
+Use one of Bootstrap's eight contextual colors to match the message's purpose.
+
+```twig {"preview":true,"height":"620px"}
+<div class="d-flex flex-column gap-2">
+ <twig:Alert class="mb-0">A simple primary alert - check it out!</twig:Alert>
+ <twig:Alert color="secondary" class="mb-0">A simple secondary alert - check it out!</twig:Alert>
+ <twig:Alert color="success" class="mb-0">A simple success alert - check it out!</twig:Alert>
+ <twig:Alert color="danger" class="mb-0">A simple danger alert - check it out!</twig:Alert>
+ <twig:Alert color="warning" class="mb-0">A simple warning alert - check it out!</twig:Alert>
+ <twig:Alert color="info" class="mb-0">A simple info alert - check it out!</twig:Alert>
+ <twig:Alert color="light" class="mb-0">A simple light alert - check it out!</twig:Alert>
+ <twig:Alert color="dark" class="mb-0">A simple dark alert - check it out!</twig:Alert>
+</div>
+```
+
+### Link color
+
+Use `alert-link` for links that inherit a suitable color from their alert variant.
+
+```twig {"preview":true,"height":"620px"}
+<div class="d-flex flex-column gap-2">
+ <twig:Alert class="mb-0">A simple primary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+ <twig:Alert color="secondary" class="mb-0">A simple secondary alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+ <twig:Alert color="success" class="mb-0">A simple success alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+ <twig:Alert color="danger" class="mb-0">A simple danger alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+ <twig:Alert color="warning" class="mb-0">A simple warning alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+ <twig:Alert color="info" class="mb-0">A simple info alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+ <twig:Alert color="light" class="mb-0">A simple light alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+ <twig:Alert color="dark" class="mb-0">A simple dark alert with <a href="#" class="alert-link">an example link</a>. Give it a click if you like.</twig:Alert>
+</div>
+```
+
+### Additional content
+
+Alerts can contain headings, paragraphs, dividers, and other structured content.
+
+```twig {"preview":true,"height":"300px"}
+<twig:Alert color="success" heading="Well done!">
+ <p>Aww yeah, you successfully read this important alert message. This example text is long enough to show how spacing works with additional content.</p>
+ <hr>
+ <p class="mb-0">Whenever you need to, use margin utilities to keep things nice and tidy.</p>
+</twig:Alert>
+```
+
+### Icons
+
+Combine alerts with flex utilities and accessible inline icons.
+
+```twig {"preview":true,"height":"420px"}
+<div class="d-flex flex-column gap-2">
+ <twig:Alert class="d-flex align-items-center mb-0">
+ <svg xmlns="http://www.w3.org/2000/svg" class="bi flex-shrink-0 me-2" width="16" height="16" viewBox="0 0 16 16" role="img" aria-label="Warning:">
+ <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
+ </svg>
+ <div>An example alert with an icon</div>
+ </twig:Alert>
+
+ <svg xmlns="http://www.w3.org/2000/svg" class="d-none">
+ <symbol id="check-circle-fill" viewBox="0 0 16 16">
+ <path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z" />
+ </symbol>
+ <symbol id="info-fill" viewBox="0 0 16 16">
+ <path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm.93-9.412-1 4.705c-.07.34.029.533.304.533.194 0 .487-.07.686-.246l-.088.416c-.287.346-.92.598-1.465.598-.703 0-1.002-.422-.808-1.319l.738-3.468c.064-.293.006-.399-.287-.47l-.451-.081.082-.381 2.29-.287zM8 5.5a1 1 0 1 1 0-2 1 1 0 0 1 0 2z" />
+ </symbol>
+ <symbol id="exclamation-triangle-fill" viewBox="0 0 16 16">
+ <path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z" />
+ </symbol>
+ </svg>
+
+ <twig:Alert class="d-flex align-items-center mb-0">
+ <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Info:"><use href="#info-fill" /></svg>
+ <div>An example alert with an icon</div>
+ </twig:Alert>
+ <twig:Alert color="success" class="d-flex align-items-center mb-0">
+ <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Success:"><use href="#check-circle-fill" /></svg>
+ <div>An example success alert with an icon</div>
+ </twig:Alert>
+ <twig:Alert color="warning" class="d-flex align-items-center mb-0">
+ <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Warning:"><use href="#exclamation-triangle-fill" /></svg>
+ <div>An example warning alert with an icon</div>
+ </twig:Alert>
+ <twig:Alert color="danger" class="d-flex align-items-center mb-0">
+ <svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:"><use href="#exclamation-triangle-fill" /></svg>
+ <div>An example danger alert with an icon</div>
+ </twig:Alert>
+</div>
+```
+
+### Dismissing
+
+Enable Bootstrap's alert plugin with a close button, dismissal classes, and transition classes.
+
+```twig {"preview":true,"height":"180px"}
+<twig:Alert color="warning" dismissible>
+ <strong>Holy guacamole!</strong> You should check in on some of those fields below.
+</twig:Alert>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/alert/manifest.json b/src/Toolkit/kits/bootstrap/alert/manifest.json
new file mode 100644
index 00000000000..439a7ea7b77
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/manifest.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "../../../schema-kit-recipe-v1.json",
+ "type": "component",
+ "name": "Alert",
+ "description": "Provides contextual feedback messages for typical user actions.",
+ "version-added": "3.4",
+ "copy-files": {
+ "templates/": "templates/"
+ },
+ "dependencies": {
+ "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+ }
+}
diff --git a/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig b/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig
new file mode 100644
index 00000000000..deaecf3b74a
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/alert/templates/components/Alert.html.twig
@@ -0,0 +1,39 @@
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The Bootstrap contextual color. #}
+{# @prop dismissible boolean Whether to render a dismiss button and dismissal transition classes. #}
+{# @prop heading string|null The optional alert heading. #}
+{# @prop content string The fallback message when no content block is provided. #}
+{# @block heading The alert heading, rendered before the main content. #}
+{# @block content The alert message and optional additional content. #}
+{# @block close The dismiss control rendered when the alert is dismissible. #}
+{%- props
+ color = 'primary',
+ dismissible = false,
+ heading = null,
+ content = ''
+-%}
+
+{%- set classes = html_classes({
+ alert: true,
+ ('alert-' ~ color): true,
+ 'alert-dismissible': dismissible,
+ fade: dismissible,
+ show: dismissible,
+}) -%}
+
+<div {{ attributes.defaults({class: classes, role: 'alert'}) }}>
+ {%- block heading -%}
+ {%- if heading is not null and heading is not empty -%}
+ <h4 class="alert-heading">{{ heading }}</h4>
+ {%- endif -%}
+ {%- endblock -%}
+
+ {%- block content -%}
+ {{- content -}}
+ {%- endblock -%}
+
+ {%- if dismissible -%}
+ {%- block close -%}
+ <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
+ {%- endblock -%}
+ {%- endif -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/badge/README.md b/src/Toolkit/kits/bootstrap/badge/README.md
new file mode 100644
index 00000000000..0e98fe71e50
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/README.md
@@ -0,0 +1,121 @@
+# Badge
+
+A small count or label used to highlight status, counts, or short contextual information.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex flex-wrap align-items-center gap-2">
+ <twig:Badge color="primary">New</twig:Badge>
+ <twig:Badge color="success">Published</twig:Badge>
+ <twig:Badge color="warning">Pending</twig:Badge>
+ <twig:Badge color="danger" pill>99+</twig:Badge>
+ <button type="button" class="btn btn-primary position-relative ms-2">
+ Inbox
+ <twig:Badge color="danger" pill class="position-absolute top-0 start-100 translate-middle">
+ 12
+ <span class="visually-hidden">unread messages</span>
+ </twig:Badge>
+ </button>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Badge>New</twig:Badge>
+```
+
+## Accessibility
+
+Do not rely on badge color alone to communicate meaning. Include meaningful visible text or additional context with Bootstrap's `visually-hidden` utility.
+
+When a badge contains a count, make sure its relationship to the surrounding heading, button, or link is clear to assistive technologies.
+
+## Examples
+
+### Headings
+
+Badges inherit their size from the immediate parent, so they scale naturally inside headings.
+
+```twig {"preview":true,"height":"350px"}
+<h1>Example heading <twig:Badge color="secondary">New</twig:Badge></h1>
+<h2>Example heading <twig:Badge color="secondary">New</twig:Badge></h2>
+<h3>Example heading <twig:Badge color="secondary">New</twig:Badge></h3>
+<h4>Example heading <twig:Badge color="secondary">New</twig:Badge></h4>
+<h5>Example heading <twig:Badge color="secondary">New</twig:Badge></h5>
+<h6>Example heading <twig:Badge color="secondary">New</twig:Badge></h6>
+```
+
+### Buttons
+
+Place a badge inside a button to display a related count.
+
+```twig {"preview":true,"height":"140px"}
+<button type="button" class="btn btn-primary">
+ Notifications <twig:Badge color="secondary">4</twig:Badge>
+</button>
+```
+
+### Positioned
+
+Combine badges with Bootstrap's position utilities to create counters and status indicators.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex align-items-center gap-5">
+ <button type="button" class="btn btn-primary position-relative">
+ Inbox
+ <twig:Badge color="danger" pill class="position-absolute top-0 start-100 translate-middle">
+ 99+
+ <span class="visually-hidden">unread messages</span>
+ </twig:Badge>
+ </button>
+
+ <button type="button" class="btn btn-primary position-relative">
+ Profile
+ <span class="position-absolute top-0 start-100 translate-middle p-2 bg-danger border border-light rounded-circle">
+ <span class="visually-hidden">New alerts</span>
+ </span>
+ </button>
+</div>
+```
+
+### Background colors
+
+Use contextual background colors while keeping the badge's meaning clear from its text.
+
+```twig {"preview":true,"height":"120px"}
+<div class="d-flex flex-wrap gap-2">
+ <twig:Badge color="primary">Primary</twig:Badge>
+ <twig:Badge color="secondary">Secondary</twig:Badge>
+ <twig:Badge color="success">Success</twig:Badge>
+ <twig:Badge color="danger">Danger</twig:Badge>
+ <twig:Badge color="warning">Warning</twig:Badge>
+ <twig:Badge color="info">Info</twig:Badge>
+ <twig:Badge color="light">Light</twig:Badge>
+ <twig:Badge color="dark">Dark</twig:Badge>
+</div>
+```
+
+### Pill badges
+
+Use the pill style for a more rounded badge with a larger border radius.
+
+```twig {"preview":true,"height":"120px"}
+<div class="d-flex flex-wrap gap-2">
+ <twig:Badge color="primary" pill>Primary</twig:Badge>
+ <twig:Badge color="secondary" pill>Secondary</twig:Badge>
+ <twig:Badge color="success" pill>Success</twig:Badge>
+ <twig:Badge color="danger" pill>Danger</twig:Badge>
+ <twig:Badge color="warning" pill>Warning</twig:Badge>
+ <twig:Badge color="info" pill>Info</twig:Badge>
+ <twig:Badge color="light" pill>Light</twig:Badge>
+ <twig:Badge color="dark" pill>Dark</twig:Badge>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/badge/manifest.json b/src/Toolkit/kits/bootstrap/badge/manifest.json
new file mode 100644
index 00000000000..5de93966a98
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/manifest.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "../../../schema-kit-recipe-v1.json",
+ "type": "component",
+ "name": "Badge",
+ "description": "A small count or label used to highlight status, counts, or short contextual information.",
+ "version-added": "3.4",
+ "copy-files": {
+ "templates/": "templates/"
+ },
+ "dependencies": {
+ "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+ }
+}
diff --git a/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig b/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig
new file mode 100644
index 00000000000..feed2b1e604
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/badge/templates/components/Badge.html.twig
@@ -0,0 +1,21 @@
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The Bootstrap contextual color. #}
+{# @prop pill boolean Whether to use Bootstrap's rounded pill style. #}
+{# @prop label string The fallback badge text when no content block is provided. #}
+{# @block content The badge text and optional inline content. #}
+{%- props
+ color = 'primary',
+ pill = false,
+ label = ''
+-%}
+
+{%- set classes = html_classes({
+ badge: true,
+ ('text-bg-' ~ color): true,
+ 'rounded-pill': pill,
+}) -%}
+
+<span {{ attributes.defaults({class: classes}) }}>
+ {%- block content -%}
+ {{- label -}}
+ {%- endblock -%}
+</span>
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/README.md b/src/Toolkit/kits/bootstrap/breadcrumb/README.md
new file mode 100644
index 00000000000..32f9ae2ab22
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/README.md
@@ -0,0 +1,78 @@
+# Breadcrumb
+
+Indicates the current page's location within a navigational hierarchy.
+
+```twig {"preview":true,"height":"150px"}
+<twig:Breadcrumb>
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
+ <li class="breadcrumb-item"><a href="#">Library</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Data</li>
+</twig:Breadcrumb>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Breadcrumb>
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Library</li>
+</twig:Breadcrumb>
+```
+
+## Accessibility
+
+Give the navigation a meaningful accessible name. Apply `aria-current="page"` to the final breadcrumb item so assistive technologies can identify the current page.
+
+## Examples
+
+Build breadcrumb trails with linked items followed by an active item for the current page.
+
+```twig {"preview":true,"height":"240px"}
+<div class="d-flex flex-column gap-2">
+ <twig:Breadcrumb>
+ <li class="breadcrumb-item active" aria-current="page">Home</li>
+ </twig:Breadcrumb>
+
+ <twig:Breadcrumb>
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Library</li>
+ </twig:Breadcrumb>
+
+ <twig:Breadcrumb>
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
+ <li class="breadcrumb-item"><a href="#">Library</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Data</li>
+ </twig:Breadcrumb>
+</div>
+```
+
+### Dividers
+
+Customize the divider with text, an escaped SVG data URL, or an empty CSS custom property.
+
+```twig {"preview":true,"height":"240px"}
+<div class="d-flex flex-column gap-2">
+ <twig:Breadcrumb divider=">">
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Library</li>
+ </twig:Breadcrumb>
+
+ <twig:Breadcrumb style="--bs-breadcrumb-divider: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath d='M2.5 0L1 1.5 3.5 4 1 6.5 2.5 8l4-4-4-4z' fill='%236c757d'/%3E%3C/svg%3E");">
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Library</li>
+ </twig:Breadcrumb>
+
+ <twig:Breadcrumb style="--bs-breadcrumb-divider: ''">
+ <li class="breadcrumb-item"><a href="#">Home</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Library</li>
+ </twig:Breadcrumb>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json b/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json
new file mode 100644
index 00000000000..403cad75101
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/manifest.json
@@ -0,0 +1,10 @@
+{
+ "$schema": "../../../schema-kit-recipe-v1.json",
+ "type": "component",
+ "name": "Breadcrumb",
+ "description": "Indicates the current page's location within a navigational hierarchy.",
+ "version-added": "3.4",
+ "copy-files": {
+ "templates/": "templates/"
+ }
+}
diff --git a/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig b/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig
new file mode 100644
index 00000000000..f612e2ae9fd
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/breadcrumb/templates/components/Breadcrumb.html.twig
@@ -0,0 +1,21 @@
+{# @prop divider string|null The custom text used as the divider between breadcrumb items. #}
+{# @prop label string|null The accessible name for the breadcrumb navigation. #}
+{# @block content The ordered list of breadcrumb items. #}
+{%- props
+ divider = null,
+ label = 'breadcrumb'
+-%}
+
+{%- set default_attrs = {} -%}
+{%- if label is not null -%}
+ {%- set default_attrs = default_attrs|merge({'aria-label': label}) -%}
+{%- endif -%}
+{%- if divider is not null -%}
+ {%- set default_attrs = default_attrs|merge({style: "--bs-breadcrumb-divider: '" ~ divider ~ "';"}) -%}
+{%- endif -%}
+
+<nav {{ attributes.defaults(default_attrs) }}>
+ <ol class="breadcrumb">
+ {%- block content -%}{%- endblock -%}
+ </ol>
+</nav>
diff --git a/src/Toolkit/kits/bootstrap/button-group/README.md b/src/Toolkit/kits/bootstrap/button-group/README.md
new file mode 100644
index 00000000000..a171ddf64bc
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/README.md
@@ -0,0 +1,267 @@
+# Button Group
+
+Groups a series of buttons on a single line or in a vertical column.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+ <twig:ButtonGroup label="Primary actions">
+ <twig:Button>Left</twig:Button>
+ <twig:Button>Middle</twig:Button>
+ <twig:Button>Right</twig:Button>
+ </twig:ButtonGroup>
+
+ <twig:ButtonGroup label="Mixed actions">
+ <twig:Button color="danger">Delete</twig:Button>
+ <twig:Button color="warning">Archive</twig:Button>
+ <twig:Button color="success">Publish</twig:Button>
+ </twig:ButtonGroup>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:ButtonGroup label="Basic example">
+ <twig:Button>Left</twig:Button>
+ <twig:Button>Middle</twig:Button>
+ <twig:Button>Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+## Accessibility
+
+Button groups need `role="group"` and an explicit accessible name. The `label` prop supplies `aria-label`; set it to `null` only when providing an equivalent `aria-labelledby` attribute.
+
+Use `role="toolbar"` and a meaningful label on toolbars that combine multiple button groups.
+
+## Examples
+
+### Basic example
+
+Group buttons or links together as one related set of controls.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+ <twig:ButtonGroup label="Basic example">
+ <twig:Button>Left</twig:Button>
+ <twig:Button>Middle</twig:Button>
+ <twig:Button>Right</twig:Button>
+ </twig:ButtonGroup>
+
+ <twig:ButtonGroup label="Page navigation">
+ <twig:Button tag="a" href="#" class="active" aria-current="page">Active link</twig:Button>
+ <twig:Button tag="a" href="#">Link</twig:Button>
+ <twig:Button tag="a" href="#">Link</twig:Button>
+ </twig:ButtonGroup>
+</div>
+```
+
+### Mixed styles
+
+Mix contextual button styles within one group.
+
+```twig {"preview":true,"height":"140px"}
+<twig:ButtonGroup label="Basic mixed styles example">
+ <twig:Button color="danger">Left</twig:Button>
+ <twig:Button color="warning">Middle</twig:Button>
+ <twig:Button color="success">Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+### Outlined styles
+
+Use outline buttons for a group with less visual weight.
+
+```twig {"preview":true,"height":"140px"}
+<twig:ButtonGroup label="Basic outlined example">
+ <twig:Button outline>Left</twig:Button>
+ <twig:Button outline>Middle</twig:Button>
+ <twig:Button outline>Right</twig:Button>
+</twig:ButtonGroup>
+```
+
+### Checkbox and radio button groups
+
+Combine Bootstrap toggle inputs and labels into seamless checkbox or radio groups.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-column align-items-start gap-3">
+ <twig:ButtonGroup label="Basic checkbox toggle button group">
+ <input type="checkbox" class="btn-check" id="btncheck1" autocomplete="off">
+ <label class="btn btn-outline-primary" for="btncheck1">Checkbox 1</label>
+ <input type="checkbox" class="btn-check" id="btncheck2" autocomplete="off">
+ <label class="btn btn-outline-primary" for="btncheck2">Checkbox 2</label>
+ <input type="checkbox" class="btn-check" id="btncheck3" autocomplete="off">
+ <label class="btn btn-outline-primary" for="btncheck3">Checkbox 3</label>
+ </twig:ButtonGroup>
+
+ <twig:ButtonGroup label="Basic radio toggle button group">
+ <input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" checked>
+ <label class="btn btn-outline-primary" for="btnradio1">Radio 1</label>
+ <input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off">
+ <label class="btn btn-outline-primary" for="btnradio2">Radio 2</label>
+ <input type="radio" class="btn-check" name="btnradio" id="btnradio3" autocomplete="off">
+ <label class="btn btn-outline-primary" for="btnradio3">Radio 3</label>
+ </twig:ButtonGroup>
+</div>
+```
+
+### Button toolbar
+
+Combine several button groups, and optionally input groups, inside a labeled toolbar.
+
+```twig {"preview":true,"height":"250px"}
+<div class="d-flex flex-column gap-3">
+ <div class="btn-toolbar" role="toolbar" aria-label="Toolbar with button groups">
+ <twig:ButtonGroup label="First group" class="me-2">
+ <twig:Button>1</twig:Button>
+ <twig:Button>2</twig:Button>
+ <twig:Button>3</twig:Button>
+ <twig:Button>4</twig:Button>
+ </twig:ButtonGroup>
+ <twig:ButtonGroup label="Second group" class="me-2">
+ <twig:Button color="secondary">5</twig:Button>
+ <twig:Button color="secondary">6</twig:Button>
+ <twig:Button color="secondary">7</twig:Button>
+ </twig:ButtonGroup>
+ <twig:ButtonGroup label="Third group">
+ <twig:Button color="info">8</twig:Button>
+ </twig:ButtonGroup>
+ </div>
+
+ <div class="btn-toolbar" role="toolbar" aria-label="Toolbar with input group">
+ <twig:ButtonGroup label="First group" class="me-2">
+ <twig:Button color="secondary" outline>1</twig:Button>
+ <twig:Button color="secondary" outline>2</twig:Button>
+ <twig:Button color="secondary" outline>3</twig:Button>
+ <twig:Button color="secondary" outline>4</twig:Button>
+ </twig:ButtonGroup>
+ <div class="input-group">
+ <div class="input-group-text" id="btnGroupAddon">@</div>
+ <input type="text" class="form-control" placeholder="Input group example" aria-label="Input group example" aria-describedby="btnGroupAddon">
+ </div>
+ </div>
+
+ <div class="btn-toolbar justify-content-between" role="toolbar" aria-label="Toolbar with justified input group">
+ <twig:ButtonGroup label="First group">
+ <twig:Button color="secondary" outline>1</twig:Button>
+ <twig:Button color="secondary" outline>2</twig:Button>
+ <twig:Button color="secondary" outline>3</twig:Button>
+ <twig:Button color="secondary" outline>4</twig:Button>
+ </twig:ButtonGroup>
+ <div class="input-group">
+ <div class="input-group-text" id="btnGroupAddon2">@</div>
+ <input type="text" class="form-control" placeholder="Input group example" aria-label="Input group example" aria-describedby="btnGroupAddon2">
+ </div>
+ </div>
+</div>
+```
+
+### Sizing
+
+Apply a size to the group instead of repeating it on every button.
+
+```twig {"preview":true,"height":"230px"}
+<div class="d-flex flex-column align-items-start gap-2">
+ <twig:ButtonGroup size="lg" label="Large button group">
+ <twig:Button outline>Left</twig:Button>
+ <twig:Button outline>Middle</twig:Button>
+ <twig:Button outline>Right</twig:Button>
+ </twig:ButtonGroup>
+
+ <twig:ButtonGroup label="Default button group">
+ <twig:Button outline>Left</twig:Button>
+ <twig:Button outline>Middle</twig:Button>
+ <twig:Button outline>Right</twig:Button>
+ </twig:ButtonGroup>
+
+ <twig:ButtonGroup size="sm" label="Small button group">
+ <twig:Button outline>Left</twig:Button>
+ <twig:Button outline>Middle</twig:Button>
+ <twig:Button outline>Right</twig:Button>
+ </twig:ButtonGroup>
+</div>
+```
+
+### Nesting
+
+Nest a button group to place a dropdown alongside other buttons.
+
+```twig {"preview":true,"height":"260px"}
+<twig:ButtonGroup label="Button group with nested dropdown">
+ <twig:Button>1</twig:Button>
+ <twig:Button>2</twig:Button>
+ <twig:ButtonGroup label="Dropdown menu">
+ <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ </ul>
+ </twig:ButtonGroup>
+</twig:ButtonGroup>
+```
+
+### Vertical variation
+
+Stack buttons, dropdowns, or radio controls in a vertical group.
+
+```twig {"preview":true,"height":"320px"}
+<div class="d-flex flex-wrap align-items-start gap-4">
+ <twig:ButtonGroup vertical label="Vertical button group">
+ <twig:Button>Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ </twig:ButtonGroup>
+
+ <twig:ButtonGroup vertical label="Vertical button group with dropdowns">
+ <twig:ButtonGroup label="Dropdown menu">
+ <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ </ul>
+ </twig:ButtonGroup>
+ <twig:Button>Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ <twig:ButtonGroup label="Dropstart menu" class="dropstart">
+ <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ </ul>
+ </twig:ButtonGroup>
+ <twig:ButtonGroup label="Dropend menu" class="dropend">
+ <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ </ul>
+ </twig:ButtonGroup>
+ <twig:ButtonGroup label="Dropup menu" class="dropup">
+ <twig:Button class="dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</twig:Button>
+ <ul class="dropdown-menu">
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ <li><a class="dropdown-item" href="#">Dropdown link</a></li>
+ </ul>
+ </twig:ButtonGroup>
+ </twig:ButtonGroup>
+
+ <twig:ButtonGroup vertical label="Vertical radio toggle button group">
+ <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio1" autocomplete="off" checked>
+ <label class="btn btn-outline-danger" for="vbtn-radio1">Radio 1</label>
+ <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio2" autocomplete="off">
+ <label class="btn btn-outline-danger" for="vbtn-radio2">Radio 2</label>
+ <input type="radio" class="btn-check" name="vbtn-radio" id="vbtn-radio3" autocomplete="off">
+ <label class="btn btn-outline-danger" for="vbtn-radio3">Radio 3</label>
+ </twig:ButtonGroup>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/button-group/manifest.json b/src/Toolkit/kits/bootstrap/button-group/manifest.json
new file mode 100644
index 00000000000..1bf2a3a363b
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/manifest.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "../../../schema-kit-recipe-v1.json",
+ "type": "component",
+ "name": "Button Group",
+ "description": "Groups a series of buttons on a single line or in a vertical column.",
+ "version-added": "3.4",
+ "copy-files": {
+ "templates/": "templates/"
+ },
+ "dependencies": {
+ "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+ }
+}
diff --git a/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig b/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig
new file mode 100644
index 00000000000..3bae952f3a3
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button-group/templates/components/ButtonGroup.html.twig
@@ -0,0 +1,22 @@
+{# @prop vertical boolean Whether to stack the buttons vertically. #}
+{# @prop size 'sm'|'lg'|null The Bootstrap size applied to every button in the group. #}
+{# @prop label string|null The accessible name for the group. #}
+{# @block content The buttons, links, or form controls contained in the group. #}
+{%- props
+ vertical = false,
+ size = null,
+ label = 'Button group'
+-%}
+
+{%- set classes = html_classes({
+ (vertical ? 'btn-group-vertical' : 'btn-group'): true,
+ ('btn-group-' ~ size): size in ['lg', 'sm'],
+}) -%}
+{%- set default_attrs = {class: classes, role: 'group'} -%}
+{%- if label not in [null, ''] -%}
+ {%- set default_attrs = default_attrs|merge({'aria-label': label}) -%}
+{%- endif -%}
+
+<div {{ attributes.defaults(default_attrs) }}>
+ {%- block content -%}{%- endblock -%}
+</div>
diff --git a/src/Toolkit/kits/bootstrap/button/README.md b/src/Toolkit/kits/bootstrap/button/README.md
new file mode 100644
index 00000000000..f2c5d177f7d
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/README.md
@@ -0,0 +1,197 @@
+# Button
+
+Use Bootstrap button styles for actions in forms, dialogs, navigation, and more.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+ <twig:Button>Primary</twig:Button>
+ <twig:Button color="secondary">Secondary</twig:Button>
+ <twig:Button color="success">Success</twig:Button>
+ <twig:Button color="danger">Danger</twig:Button>
+ <twig:Button color="warning">Warning</twig:Button>
+ <twig:Button color="info">Info</twig:Button>
+ <twig:Button color="light">Light</twig:Button>
+ <twig:Button color="dark">Dark</twig:Button>
+ <twig:Button color="link">Link</twig:Button>
+</div>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Button>Save changes</twig:Button>
+```
+
+## Accessibility
+
+Do not rely on button color alone to communicate meaning. Use explicit labels or additional visually hidden text when needed.
+
+Disabled links omit their `href`, expose `aria-disabled="true"`, and are removed from keyboard navigation.
+
+## Examples
+
+### Base class
+
+Use the base Bootstrap button class without a contextual color when defining a custom style.
+
+```twig {"preview":true,"height":"140px"}
+<twig:Button :color="null">Base class</twig:Button>
+```
+
+### Variants
+
+Use contextual colors to communicate the purpose of an action.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+ <twig:Button>Primary</twig:Button>
+ <twig:Button color="secondary">Secondary</twig:Button>
+ <twig:Button color="success">Success</twig:Button>
+ <twig:Button color="danger">Danger</twig:Button>
+ <twig:Button color="warning">Warning</twig:Button>
+ <twig:Button color="info">Info</twig:Button>
+ <twig:Button color="light">Light</twig:Button>
+ <twig:Button color="dark">Dark</twig:Button>
+ <twig:Button color="link">Link</twig:Button>
+</div>
+```
+
+### Disable text wrapping
+
+Add Bootstrap's `text-nowrap` utility when a button label must remain on one line.
+
+```twig {"preview":true,"height":"140px"}
+<twig:Button class="text-nowrap">This button label does not wrap</twig:Button>
+```
+
+### Button tags
+
+Render the component as a button, link, or input depending on the semantic element required.
+
+```twig {"preview":true,"height":"140px"}
+<div class="d-flex flex-wrap gap-2">
+ <twig:Button tag="a" href="#">Link</twig:Button>
+ <twig:Button type="submit">Button</twig:Button>
+ <twig:Button tag="input" value="Input" />
+ <twig:Button tag="input" type="submit" value="Submit" />
+ <twig:Button tag="input" type="reset" value="Reset" />
+</div>
+```
+
+### Outline buttons
+
+Use outline styles for actions that need less visual weight.
+
+```twig {"preview":true,"height":"190px"}
+<div class="d-flex flex-wrap gap-2">
+ <twig:Button outline>Primary</twig:Button>
+ <twig:Button color="secondary" outline>Secondary</twig:Button>
+ <twig:Button color="success" outline>Success</twig:Button>
+ <twig:Button color="danger" outline>Danger</twig:Button>
+ <twig:Button color="warning" outline>Warning</twig:Button>
+ <twig:Button color="info" outline>Info</twig:Button>
+ <twig:Button color="light" outline>Light</twig:Button>
+ <twig:Button color="dark" outline>Dark</twig:Button>
+</div>
+```
+
+### Sizes
+
+Use Bootstrap's large and small sizes, or customize the component with Bootstrap CSS variables.
+
+```twig {"preview":true,"height":"250px"}
+<div class="d-flex flex-column align-items-start gap-3">
+ <div class="d-flex flex-wrap gap-2">
+ <twig:Button size="lg">Large button</twig:Button>
+ <twig:Button color="secondary" size="lg">Large button</twig:Button>
+ </div>
+ <div class="d-flex flex-wrap gap-2">
+ <twig:Button size="sm">Small button</twig:Button>
+ <twig:Button color="secondary" size="sm">Small button</twig:Button>
+ </div>
+ <twig:Button style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;">
+ Custom button
+ </twig:Button>
+</div>
+```
+
+### Disabled state
+
+Disable buttons and links while preserving the appropriate HTML and accessibility semantics.
+
+```twig {"preview":true,"height":"200px"}
+<div class="d-flex flex-column align-items-start gap-3">
+ <div class="d-flex flex-wrap gap-2">
+ <twig:Button disabled>Primary button</twig:Button>
+ <twig:Button color="secondary" disabled>Button</twig:Button>
+ <twig:Button outline disabled>Primary button</twig:Button>
+ <twig:Button color="secondary" outline disabled>Button</twig:Button>
+ </div>
+ <div class="d-flex flex-wrap gap-2">
+ <twig:Button tag="a" disabled>Primary link</twig:Button>
+ <twig:Button tag="a" color="secondary" disabled>Link</twig:Button>
+ </div>
+</div>
+```
+
+### Block buttons
+
+Combine the component with Bootstrap layout utilities to create responsive full-width buttons.
+
+```twig {"preview":true,"height":"470px"}
+<div class="d-flex flex-column gap-4">
+ <div class="d-grid gap-2">
+ <twig:Button>Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ </div>
+ <div class="d-grid gap-2 d-md-block">
+ <twig:Button>Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ </div>
+ <div class="d-grid gap-2 col-6 mx-auto">
+ <twig:Button>Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ </div>
+ <div class="d-grid gap-2 d-md-flex justify-content-md-end">
+ <twig:Button class="me-md-2">Button</twig:Button>
+ <twig:Button>Button</twig:Button>
+ </div>
+</div>
+```
+
+### Toggle states
+
+Use Bootstrap's button plugin for controls that toggle between pressed and unpressed states.
+
+```twig {"preview":true,"height":"300px"}
+<div class="d-flex flex-column align-items-start gap-3">
+ <div class="d-inline-flex flex-wrap gap-1">
+ <twig:Button :color="null" data-bs-toggle="button">Toggle button</twig:Button>
+ <twig:Button :color="null" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle button</twig:Button>
+ <twig:Button :color="null" disabled data-bs-toggle="button">Disabled toggle button</twig:Button>
+ </div>
+ <div class="d-inline-flex flex-wrap gap-1">
+ <twig:Button data-bs-toggle="button">Toggle button</twig:Button>
+ <twig:Button class="active" data-bs-toggle="button" aria-pressed="true">Active toggle button</twig:Button>
+ <twig:Button disabled data-bs-toggle="button">Disabled toggle button</twig:Button>
+ </div>
+ <div class="d-inline-flex flex-wrap gap-1">
+ <twig:Button tag="a" :color="null" href="#" data-bs-toggle="button">Toggle link</twig:Button>
+ <twig:Button tag="a" :color="null" href="#" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle link</twig:Button>
+ <twig:Button tag="a" :color="null" disabled data-bs-toggle="button">Disabled toggle link</twig:Button>
+ </div>
+ <div class="d-inline-flex flex-wrap gap-1">
+ <twig:Button tag="a" href="#" data-bs-toggle="button">Toggle link</twig:Button>
+ <twig:Button tag="a" href="#" class="active" data-bs-toggle="button" aria-pressed="true">Active toggle link</twig:Button>
+ <twig:Button tag="a" disabled data-bs-toggle="button">Disabled toggle link</twig:Button>
+ </div>
+</div>
+```
+
+## API Reference
+
+::: api-reference
diff --git a/src/Toolkit/kits/bootstrap/button/manifest.json b/src/Toolkit/kits/bootstrap/button/manifest.json
new file mode 100644
index 00000000000..582b11f728e
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/manifest.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "../../../schema-kit-recipe-v1.json",
+ "type": "component",
+ "name": "Button",
+ "description": "Use Bootstrap button styles for actions in forms, dialogs, navigation, and more.",
+ "version-added": "3.4",
+ "copy-files": {
+ "templates/": "templates/"
+ },
+ "dependencies": {
+ "composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
+ }
+}
diff --git a/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig b/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig
new file mode 100644
index 00000000000..dfbdedd725b
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/button/templates/components/Button.html.twig
@@ -0,0 +1,64 @@
+{# @prop tag 'button'|'a'|'input' The HTML element to render. #}
+{# @prop type 'button'|'submit'|'reset'|null The type used for button and input elements. #}
+{# @prop href string|null The destination used for anchor elements. #}
+{# @prop color 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark'|'link'|null The Bootstrap contextual color. #}
+{# @prop outline boolean Whether to use an outline style. #}
+{# @prop size 'sm'|'lg'|null The Bootstrap button size. #}
+{# @prop disabled boolean Whether the control is disabled. #}
+{# @prop value string|null The value rendered by input elements. #}
+{# @prop content string The fallback label when no content block is provided. #}
+{# @block content The button label and optional inline content. #}
+{%- props
+ tag = 'button',
+ type = null,
+ href = null,
+ color = 'primary',
+ outline = false,
+ size = null,
+ disabled = false,
+ value = null,
+ content = ''
+-%}
+
+{%- set final_tag = tag|lower in ['a', 'input'] ? tag|lower : 'button' -%}
+{%- set final_type = type in ['button', 'submit', 'reset'] ? type : 'button' -%}
+{%- set valid_colors = ['primary', 'secondary', 'success', 'danger', 'warning', 'info', 'light', 'dark', 'link'] -%}
+{%- set classes = html_classes({
+ btn: true,
+ ('btn-' ~ (outline and color != 'link' ? 'outline-' : '') ~ color): color in valid_colors,
+ ('btn-' ~ size): size in ['lg', 'sm'],
+ disabled: disabled and final_tag == 'a',
+}) -%}
+{%- set default_attrs = {class: classes} -%}
+
+{%- if final_tag == 'a' -%}
+ {%- set default_attrs = default_attrs|merge({role: 'button'}) -%}
+ {%- if disabled -%}
+ {%- set default_attrs = default_attrs|merge({'aria-disabled': 'true', tabindex: '-1'}) -%}
+ {%- else -%}
+ {%- set default_attrs = default_attrs|merge({href: href not in [null, ''] ? href : '#'}) -%}
+ {%- endif -%}
+{%- elseif final_tag == 'input' -%}
+ {%- set default_attrs = default_attrs|merge({
+ type: final_type,
+ value: value ?? content,
+ }) -%}
+ {%- if disabled -%}
+ {%- set default_attrs = default_attrs|merge({disabled: true}) -%}
+ {%- endif -%}
+{%- else -%}
+ {%- set default_attrs = default_attrs|merge({type: final_type}) -%}
+ {%- if disabled -%}
+ {%- set default_attrs = default_attrs|merge({disabled: true}) -%}
+ {%- endif -%}
+{%- endif -%}
+
+{%- if final_tag == 'input' -%}
+ <input {{ attributes.defaults(default_attrs) }}>
+{%- else -%}
+ <{{ final_tag }} {{ attributes.defaults(default_attrs) }}>
+ {%- block content -%}
+ {{- content -}}
+ {%- endblock -%}
+ </{{ final_tag }}>
+{%- endif -%}
diff --git a/src/Toolkit/kits/bootstrap/card/README.md b/src/Toolkit/kits/bootstrap/card/README.md
new file mode 100644
index 00000000000..fbde5eab9b6
--- /dev/null
+++ b/src/Toolkit/kits/bootstrap/card/README.md
@@ -0,0 +1,357 @@
+# Card
+
+Build flexible content containers with optional headers, footers, images, and contextual styles.
+
+```twig {"preview":true,"height":"480px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<twig:Card style="width: 22rem;">
+ <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+ <twig:Card:Body>
+ <twig:Card:Title>Explore the outdoors</twig:Card:Title>
+ <twig:Card:Subtitle class="mb-2 text-body-secondary">Weekend inspiration</twig:Card:Subtitle>
+ <twig:Card:Text>Discover a new trail and make time for a change of scenery.</twig:Card:Text>
+ <a href="#" class="btn btn-primary">Find a trail</a>
+ <twig:Card:Link href="#">View guide</twig:Card:Link>
+ </twig:Card:Body>
+ <twig:Card:Footer class="text-body-secondary">Updated today</twig:Card:Footer>
+</twig:Card>
+```
+
+## Installation
+
+::: installation
+
+## Usage
+
+```twig
+<twig:Card style="max-width: 24rem;">
+ <twig:Card:Header>Featured</twig:Card:Header>
+ <twig:Card:Body>
+ <twig:Card:Title>Card title</twig:Card:Title>
+ <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
+ </twig:Card:Body>
+ <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
+</twig:Card>
+```
+
+## Accessibility
+
+Choose a semantic heading level for each card title that fits the surrounding page hierarchy.
+
+Do not rely on background, text, or border color alone to communicate meaning. Keep sufficient contrast and provide an equivalent text cue when color carries information.
+
+## Examples
+
+Combine an image, title, supporting text, and action in a fixed-width card.
+
+```twig {"preview":true,"height":"400px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<twig:Card style="width: 18rem;">
+ <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+ <twig:Card:Body>
+ <twig:Card:Title>Card title</twig:Card:Title>
+ <twig:Card:Text>Some quick example text to build on the card title and make up the bulk of the card's content.</twig:Card:Text>
+ <a href="#" class="btn btn-primary">Go somewhere</a>
+ </twig:Card:Body>
+</twig:Card>
+```
+
+### Content types
+
+Mix card bodies, titles, subtitles, text, links, images, and list groups.
+
+```twig {"preview":true,"height":"470px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=720&q=80' %}
+
+<div class="d-flex flex-wrap gap-3">
+ <twig:Card style="width: 18rem;">
+ <twig:Card:Body>
+ <twig:Card:Title>Card title</twig:Card:Title>
+ <twig:Card:Subtitle class="mb-2 text-body-secondary">Card subtitle</twig:Card:Subtitle>
+ <twig:Card:Text>Some quick example text to build on the card title.</twig:Card:Text>
+ <twig:Card:Link href="#">Card link</twig:Card:Link>
+ <twig:Card:Link href="#">Another link</twig:Card:Link>
+ </twig:Card:Body>
+ </twig:Card>
+
+ <twig:Card style="width: 18rem;">
+ <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+ <twig:Card:Body>
+ <twig:Card:Text>Text, images, list groups, and links can be freely combined.</twig:Card:Text>
+ </twig:Card:Body>
+ <ul class="list-group list-group-flush">
+ <li class="list-group-item">An item</li>
+ <li class="list-group-item">A second item</li>
+ <li class="list-group-item">A third item</li>
+ </ul>
+ <twig:Card:Body>
+ <twig:Card:Link href="#">Card link</twig:Card:Link>
+ </twig:Card:Body>
+ </twig:Card>
+</div>
+```
+
+### Header and footer
+
+Add optional headers and footers, including semantic heading and quote content.
+
+```twig {"preview":true,"height":"420px"}
+<div class="d-grid gap-3">
+ <twig:Card>
+ <twig:Card:Header>Featured</twig:Card:Header>
+ <twig:Card:Body>
+ <twig:Card:Title>Special title treatment</twig:Card:Title>
+ <twig:Card:Text>With supporting text below as a natural lead-in to additional content.</twig:Card:Text>
+ <a href="#" class="btn btn-primary">Go somewhere</a>
+ </twig:Card:Body>
+ </twig:Card>
+
+ <twig:Card class="text-center">
+ <twig:Card:Header tag="h5">Featured</twig:Card:Header>
+ <twig:Card:Body>
+ <figure class="mb-0">
+ <blockquote class="blockquote"><p>A well-known quote, contained in a blockquote element.</p></blockquote>
+ <figcaption class="blockquote-footer mb-0">Someone famous in <cite title="Source Title">Source Title</cite></figcaption>
+ </figure>
+ </twig:Card:Body>
+ <twig:Card:Footer class="text-body-secondary">2 days ago</twig:Card:Footer>
+ </twig:Card>
+</div>
+```
+
+### Sizing
+
+Size cards with Bootstrap's grid, width utilities, or custom CSS.
+
+```twig {"preview":true,"height":"190px"}
+<div class="row">
+ <div class="col-sm-6 mb-3 mb-sm-0">
+ <twig:Card>
+ <twig:Card:Body>
+ <twig:Card:Title>Grid-sized card</twig:Card:Title>
+ <twig:Card:Text>The surrounding Bootstrap grid controls this card's width.</twig:Card:Text>
+ </twig:Card:Body>
+ </twig:Card>
+ </div>
+ <div class="col-sm-6">
+ <twig:Card class="w-75">
+ <twig:Card:Body>
+ <twig:Card:Title>Utility-sized card</twig:Card:Title>
+ <twig:Card:Text>Width utilities or custom CSS can size a card directly.</twig:Card:Text>
+ </twig:Card:Body>
+ </twig:Card>
+ </div>
+</div>
+```
+
+### Text alignment
+
+Apply Bootstrap text alignment utilities to a card or one of its sections.
+
+```twig {"preview":true,"height":"440px"}
+<div class="d-flex flex-wrap gap-3">
+ {% for alignment, label in {'': 'Start', 'text-center': 'Center', 'text-end': 'End'} %}
+ <twig:Card class="{{ alignment }}" style="width: 18rem;">
+ <twig:Card:Body>
+ <twig:Card:Title>{{ label }} aligned</twig:Card:Title>
+ <twig:Card:Text>Use Bootstrap text utilities on the whole card or an individual section.</twig:Card:Text>
+ <a href="#" class="btn btn-primary">Go somewhere</a>
+ </twig:Card:Body>
+ </twig:Card>
+ {% endfor %}
+</div>
+```
+
+### Navigation
+
+Place tabs or pills in the card header.
+
+```twig {"preview":true,"height":"350px"}
+<div class="d-grid gap-3">
+ {% for style in ['tabs', 'pills'] %}
+ <twig:Card class="text-center">
+ <twig:Card:Header>
+ <ul class="nav nav-{{ style }} card-header-{{ style }}">
+ <li class="nav-item"><a class="nav-link active" aria-current="page" href="#">Active</a></li>
+ <li class="nav-item"><a class="nav-link" href="#">Link</a></li>
+ <li class="nav-item"><a class="nav-link disabled" aria-disabled="true">Disabled</a></li>
+ </ul>
+ </twig:Card:Header>
+ <twig:Card:Body>
+ <twig:Card:Title>Special title treatment</twig:Card:Title>
+ <twig:Card:Text>Card headers can contain Bootstrap tab or pill navigation.</twig:Card:Text>
+ </twig:Card:Body>
+ </twig:Card>
+ {% endfor %}
+</div>
+```
+
+### Image caps
+
+Position an image at the top or bottom edge of a card.
+
+```twig {"preview":true,"height":"350px"}
+{% set image_url = 'https://images.unsplash.com/photo-1535189043414-47a3c49a0bed?auto=format&fit=crop&w=960&q=80' %}
+
+<div class="row row-cols-1 row-cols-sm-2 g-3">
+ <div class="col">
+ <twig:Card class="h-100">
+ <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" />
+ <twig:Card:Body>
+ <twig:Card:Title>Top image cap</twig:Card:Title>
+ <twig:Card:Text>Images can sit at either end of a card.</twig:Card:Text>
+ </twig:Card:Body>
+ </twig:Card>
+ </div>
+ <div class="col">
+ <twig:Card class="h-100">
+ <twig:Card:Body>
+ <twig:Card:Title>Bottom image cap</twig:Card:Title>
+ <twig:Card:Text>The bottom position matches the lower card corners.</twig:Card:Text>
+ </twig:Card:Body>
+ <twig:Card:Image src="{{ image_url }}" alt="A quiet mountain landscape" position="bottom" />
+ </twig:Card>
+ </div>
+</div>
+```
+
+### Image overlays
+
+Use an image as the card background and place concise, high-contrast content over it.
+
+```twig {"preview":true,"height":"410px"}
+{% set image_url = 'https://images.unsplash.com/p…1 parent 4af61f6 commit fad9f84
333 files changed
Lines changed: 15332 additions & 0 deletions
File tree
- src/Toolkit
- kits/bootstrap
- accordion
- templates/components
- Accordion
- alert
- templates/components
- badge
- templates/components
- breadcrumb
- templates/components
- button-group
- templates/components
- button
- templates/components
- card
- templates/components
- Card
- carousel
- templates/components
- Carousel
- close-button
- templates/components
- collapse
- templates/components
- dropdown
- templates/components
- Dropdown
- figure
- templates/components
- list-group
- templates/components
- ListGroup
- modal
- templates/components
- Modal
- offcanvas
- templates/components
- Offcanvas
- pagination
- templates/components
- Pagination
- placeholder
- templates/components
- Placeholder
- popover
- templates/components
- progress
- templates/components
- scrollspy
- templates/components
- spinner
- templates/components
- toast
- templates/components
- Toast
- tooltip
- templates/components
- tests
- Command
- Functional/__snapshots__
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
Lines changed: 16 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
Lines changed: 43 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
0 commit comments