Skip to content

Commit fad9f84

Browse files
smnandreKocal
authored andcommitted
[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(&quot;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&quot;);"> + <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

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/Toolkit/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## 3.4.0
44

5+
- [Bootstrap] Add the Bootstrap kit
56
- [Common] Add the `common` kit, with design-system agnostic `logout-link` and `post-link` recipes
67
- Add new linter checker `ClassMergeSpacingChecker` that checks for invalid `'<literal>' ~ attributes.render(...)` pattern
78
- Add per-recipe `README.md` documentation
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Getting started
2+
3+
This kit provides ready-to-use Twig components based on [Bootstrap 5.3](https://getbootstrap.com/docs/5.3/).
4+
5+
## Requirements
6+
7+
Bootstrap's CSS is required by all components. Bootstrap's JavaScript is also required by interactive components and examples.
8+
9+
## Installation
10+
11+
Install Bootstrap with AssetMapper:
12+
13+
```bash
14+
php bin/console importmap:require bootstrap bootstrap/dist/css/bootstrap.min.css
15+
```
16+
17+
Then import Bootstrap from `assets/app.js`:
18+
19+
```js
20+
import 'bootstrap/dist/css/bootstrap.min.css';
21+
import 'bootstrap';
22+
```
23+
24+
With npm, pnpm, or Yarn, install Bootstrap instead with your package manager:
25+
26+
```bash
27+
npm install bootstrap@^5.3.0
28+
```
29+
30+
The imports in `assets/app.js` remain the same.
31+
32+
And that's it! You can now use the Bootstrap Twig components in your templates.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Accordion
2+
3+
Build vertically collapsing sections powered by Bootstrap's Collapse plugin.
4+
5+
```twig {"preview":true,"height":"360px"}
6+
<twig:Accordion id="faq-accordion">
7+
<twig:Accordion:Item id="faq-first" label="What is Symfony UX?" expanded>
8+
<strong>Symfony UX connects Symfony with modern frontend tools.</strong>
9+
It provides JavaScript packages, Twig components, and integrations designed to work naturally with Symfony applications.
10+
</twig:Accordion:Item>
11+
<twig:Accordion:Item id="faq-second" label="Can I customize these components?">
12+
Yes. Pass HTML attributes and Bootstrap utility classes directly to the Twig components, or customize Bootstrap through Sass and CSS variables.
13+
</twig:Accordion:Item>
14+
<twig:Accordion:Item id="faq-third" label="Does the accordion support multiple open items?">
15+
Set <code>alwaysOpen</code> on the root accordion to omit Bootstrap's parent constraint.
16+
</twig:Accordion:Item>
17+
</twig:Accordion>
18+
```
19+
20+
## Installation
21+
22+
::: installation
23+
24+
## Usage
25+
26+
```twig
27+
<twig:Accordion id="account-accordion">
28+
<twig:Accordion:Item id="account-profile" label="Profile" expanded>
29+
Update your personal details and contact information.
30+
</twig:Accordion:Item>
31+
<twig:Accordion:Item id="account-security" label="Security">
32+
Review your password and two-factor authentication settings.
33+
</twig:Accordion:Item>
34+
</twig:Accordion>
35+
```
36+
37+
## Accessibility
38+
39+
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`.
40+
41+
Bootstrap's transition respects `prefers-reduced-motion`. Keep labels concise and do not hide essential information exclusively inside collapsed sections.
42+
43+
## Examples
44+
45+
Render a conventional accordion where opening one item closes the currently open item.
46+
47+
```twig {"preview":true,"height":"360px"}
48+
<twig:Accordion id="accordion-example">
49+
<twig:Accordion:Item id="accordion-item-one" label="Accordion Item #1" expanded>
50+
<strong>This is the first item's accordion body.</strong>
51+
It is shown by default until the Collapse plugin updates the appropriate classes and ARIA state.
52+
</twig:Accordion:Item>
53+
<twig:Accordion:Item id="accordion-item-two" label="Accordion Item #2">
54+
<strong>This is the second item's accordion body.</strong>
55+
It is hidden by default and can contain nearly any HTML content.
56+
</twig:Accordion:Item>
57+
<twig:Accordion:Item id="accordion-item-three" label="Accordion Item #3">
58+
<strong>This is the third item's accordion body.</strong>
59+
Opening it closes the currently open item.
60+
</twig:Accordion:Item>
61+
</twig:Accordion>
62+
```
63+
64+
### Flush
65+
66+
Remove outer borders and rounded corners for an edge-to-edge accordion.
67+
68+
```twig {"preview":true,"height":"360px"}
69+
<twig:Accordion id="accordion-flush-example" flush>
70+
<twig:Accordion:Item id="flush-item-one" label="Accordion Item #1">
71+
Placeholder content for this edge-to-edge accordion item.
72+
</twig:Accordion:Item>
73+
<twig:Accordion:Item id="flush-item-two" label="Accordion Item #2">
74+
The flush style removes some borders and rounded corners.
75+
</twig:Accordion:Item>
76+
<twig:Accordion:Item id="flush-item-three" label="Accordion Item #3">
77+
Items retain the same Collapse behavior and accessibility attributes.
78+
</twig:Accordion:Item>
79+
</twig:Accordion>
80+
```
81+
82+
### Always open
83+
84+
Allow several items to remain expanded by omitting Bootstrap's parent constraint.
85+
86+
```twig {"preview":true,"height":"430px"}
87+
<twig:Accordion id="accordion-always-open" alwaysOpen>
88+
<twig:Accordion:Item id="always-open-one" label="Accordion Item #1" expanded>
89+
This item starts open and remains open when another item is expanded.
90+
</twig:Accordion:Item>
91+
<twig:Accordion:Item id="always-open-two" label="Accordion Item #2">
92+
Multiple items can stay open because no <code>data-bs-parent</code> constraint is rendered.
93+
</twig:Accordion:Item>
94+
<twig:Accordion:Item id="always-open-three" label="Accordion Item #3">
95+
Each button still controls its own panel and synchronizes <code>aria-expanded</code>.
96+
</twig:Accordion:Item>
97+
</twig:Accordion>
98+
```
99+
100+
## API Reference
101+
102+
::: api-reference
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "../../../schema-kit-recipe-v1.json",
3+
"type": "component",
4+
"name": "Accordion",
5+
"description": "Build vertically collapsing sections powered by Bootstrap's Collapse plugin.",
6+
"version-added": "3.4",
7+
"copy-files": {
8+
"templates/": "templates/"
9+
},
10+
"dependencies": {
11+
"composer": ["symfony/ux-twig-component:^3.1", "twig/extra-bundle", "twig/html-extra:^3.12.0"]
12+
}
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{# @prop id string The unique identifier used to coordinate the accordion items. #}
2+
{# @prop flush boolean Whether to remove outer borders and rounded corners. #}
3+
{# @prop alwaysOpen boolean Whether multiple accordion items may remain open. #}
4+
{# @block content The accordion items, typically `Accordion:Item` components. #}
5+
{%- props id, flush = false, alwaysOpen = false -%}
6+
{%- do provide('accordion.id', id) -%}
7+
{%- do provide('accordion.alwaysOpen', alwaysOpen) -%}
8+
<div {{ attributes.defaults({
9+
id: id,
10+
class: html_classes({
11+
accordion: true,
12+
'accordion-flush': flush,
13+
}),
14+
}) }}>
15+
{%- block content %}{% endblock -%}
16+
</div>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{# @prop id string The unique identifier used for the heading and collapsible panel. #}
2+
{# @prop label string The text displayed in the accordion button. #}
3+
{# @prop expanded boolean Whether the accordion item is initially expanded. #}
4+
{# @prop headingTag 'h1'|'h2'|'h3'|'h4'|'h5'|'h6' The heading element to render. #}
5+
{# @block label The accordion button label. #}
6+
{# @block content The content displayed inside the collapsible panel. #}
7+
{%- props id, label = '', expanded = false, headingTag = 'h2' -%}
8+
{%- set _accordion_id = inject('accordion.id', null) -%}
9+
{%- set _accordion_always_open = inject('accordion.alwaysOpen', false) -%}
10+
{%- set heading_id = id ~ '-heading' -%}
11+
{%- set collapse_id = id ~ '-collapse' -%}
12+
{%- set final_heading_tag = headingTag in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] ? headingTag : 'h2' -%}
13+
<div {{ attributes.defaults({class: 'accordion-item'}) }}>
14+
<{{ final_heading_tag }} class="accordion-header" id="{{ heading_id }}">
15+
<button
16+
class="{{ html_classes({
17+
'accordion-button': true,
18+
collapsed: not expanded,
19+
}) }}"
20+
type="button"
21+
data-bs-toggle="collapse"
22+
data-bs-target="#{{ collapse_id }}"
23+
aria-expanded="{{ expanded ? 'true' : 'false' }}"
24+
aria-controls="{{ collapse_id }}"
25+
>
26+
{%- block label %}{{ label }}{% endblock -%}
27+
</button>
28+
</{{ final_heading_tag }}>
29+
<div
30+
id="{{ collapse_id }}"
31+
class="{{ html_classes({
32+
'accordion-collapse': true,
33+
collapse: true,
34+
show: expanded,
35+
}) }}"
36+
aria-labelledby="{{ heading_id }}"
37+
{% if not _accordion_always_open and _accordion_id is not null %}data-bs-parent="#{{ _accordion_id }}"{% endif %}
38+
>
39+
<div class="accordion-body">
40+
{%- block content %}{% endblock -%}
41+
</div>
42+
</div>
43+
</div>
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# Alert
2+
3+
Provides contextual feedback messages for typical user actions.
4+
5+
```twig {"preview":true,"height":"210px"}
6+
<twig:Alert color="success" heading="Well done!" dismissible>
7+
Your changes were saved successfully. <a href="#" class="alert-link">Review them now</a>.
8+
</twig:Alert>
9+
```
10+
11+
## Installation
12+
13+
::: installation
14+
15+
## Usage
16+
17+
```twig
18+
<twig:Alert>A simple primary alert - check it out!</twig:Alert>
19+
```
20+
21+
## Accessibility
22+
23+
Do not rely on color alone to communicate the alert's meaning. Include clear visible text or additional context for assistive technologies.
24+
25+
Dismissal removes the alert from the document. When appropriate, listen for Bootstrap's `closed.bs.alert` event and move focus to a logical destination.
26+
27+
## Examples
28+
29+
### Contextual variants
30+
31+
Use one of Bootstrap's eight contextual colors to match the message's purpose.
32+
33+
```twig {"preview":true,"height":"620px"}
34+
<div class="d-flex flex-column gap-2">
35+
<twig:Alert class="mb-0">A simple primary alert - check it out!</twig:Alert>
36+
<twig:Alert color="secondary" class="mb-0">A simple secondary alert - check it out!</twig:Alert>
37+
<twig:Alert color="success" class="mb-0">A simple success alert - check it out!</twig:Alert>
38+
<twig:Alert color="danger" class="mb-0">A simple danger alert - check it out!</twig:Alert>
39+
<twig:Alert color="warning" class="mb-0">A simple warning alert - check it out!</twig:Alert>
40+
<twig:Alert color="info" class="mb-0">A simple info alert - check it out!</twig:Alert>
41+
<twig:Alert color="light" class="mb-0">A simple light alert - check it out!</twig:Alert>
42+
<twig:Alert color="dark" class="mb-0">A simple dark alert - check it out!</twig:Alert>
43+
</div>
44+
```
45+
46+
### Link color
47+
48+
Use `alert-link` for links that inherit a suitable color from their alert variant.
49+
50+
```twig {"preview":true,"height":"620px"}
51+
<div class="d-flex flex-column gap-2">
52+
<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>
53+
<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>
54+
<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>
55+
<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>
56+
<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>
57+
<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>
58+
<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>
59+
<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>
60+
</div>
61+
```
62+
63+
### Additional content
64+
65+
Alerts can contain headings, paragraphs, dividers, and other structured content.
66+
67+
```twig {"preview":true,"height":"300px"}
68+
<twig:Alert color="success" heading="Well done!">
69+
<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>
70+
<hr>
71+
<p class="mb-0">Whenever you need to, use margin utilities to keep things nice and tidy.</p>
72+
</twig:Alert>
73+
```
74+
75+
### Icons
76+
77+
Combine alerts with flex utilities and accessible inline icons.
78+
79+
```twig {"preview":true,"height":"420px"}
80+
<div class="d-flex flex-column gap-2">
81+
<twig:Alert class="d-flex align-items-center mb-0">
82+
<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:">
83+
<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" />
84+
</svg>
85+
<div>An example alert with an icon</div>
86+
</twig:Alert>
87+
88+
<svg xmlns="http://www.w3.org/2000/svg" class="d-none">
89+
<symbol id="check-circle-fill" viewBox="0 0 16 16">
90+
<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" />
91+
</symbol>
92+
<symbol id="info-fill" viewBox="0 0 16 16">
93+
<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" />
94+
</symbol>
95+
<symbol id="exclamation-triangle-fill" viewBox="0 0 16 16">
96+
<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" />
97+
</symbol>
98+
</svg>
99+
100+
<twig:Alert class="d-flex align-items-center mb-0">
101+
<svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Info:"><use href="#info-fill" /></svg>
102+
<div>An example alert with an icon</div>
103+
</twig:Alert>
104+
<twig:Alert color="success" class="d-flex align-items-center mb-0">
105+
<svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Success:"><use href="#check-circle-fill" /></svg>
106+
<div>An example success alert with an icon</div>
107+
</twig:Alert>
108+
<twig:Alert color="warning" class="d-flex align-items-center mb-0">
109+
<svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Warning:"><use href="#exclamation-triangle-fill" /></svg>
110+
<div>An example warning alert with an icon</div>
111+
</twig:Alert>
112+
<twig:Alert color="danger" class="d-flex align-items-center mb-0">
113+
<svg class="bi flex-shrink-0 me-2" width="16" height="16" role="img" aria-label="Danger:"><use href="#exclamation-triangle-fill" /></svg>
114+
<div>An example danger alert with an icon</div>
115+
</twig:Alert>
116+
</div>
117+
```
118+
119+
### Dismissing
120+
121+
Enable Bootstrap's alert plugin with a close button, dismissal classes, and transition classes.
122+
123+
```twig {"preview":true,"height":"180px"}
124+
<twig:Alert color="warning" dismissible>
125+
<strong>Holy guacamole!</strong> You should check in on some of those fields below.
126+
</twig:Alert>
127+
```
128+
129+
## API Reference
130+
131+
::: api-reference
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"$schema": "../../../schema-kit-recipe-v1.json",
3+
"type": "component",
4+
"name": "Alert",
5+
"description": "Provides contextual feedback messages for typical user actions.",
6+
"version-added": "3.4",
7+
"copy-files": {
8+
"templates/": "templates/"
9+
},
10+
"dependencies": {
11+
"composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"]
12+
}
13+
}

0 commit comments

Comments
 (0)