Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Toolkit/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# CHANGELOG

## Unreleased

- [Bootstrap] Add Bootstrap kit with

## 3.0.0

- Minimum required Symfony version is now 7.4
Expand Down
34 changes: 34 additions & 0 deletions src/Toolkit/kits/bootstrap/INSTALL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Getting started

This kit provides ready-to-use and fully-customizable UI Twig components based on [Bootstrap](https://getbootstrap.com/) components's **design**.

Please note that not every Bootstrap component is available in this kit, but we are working on it!

## Requirements

This kit requires Bootstrap 5.3+ to work.

## Installation

1. Install Bootstrap, either with `importmap:require` for AssetMapper, or `npm` for Webpack Encore:

```
# With AssetMapper
php bin/console importmap:require bootstrap/dist/css/bootstrap.min.css bootstrap

# With npm
npm install bootstrap
```

2. Import Bootstrap CSS in your `assets/styles/app.css`:

```css
@import 'bootstrap/dist/css/bootstrap.min.css';
```

Or if using Webpack Encore, import it in your `assets/app.js`:

```js
import 'bootstrap/dist/css/bootstrap.min.css';
import 'bootstrap';
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Controller } from '@hotwired/stimulus';
import { Collapse } from 'bootstrap';

export default class extends Controller {
static targets = ['item', 'header', 'body'];

static values = {
alwaysOpen: { type: Boolean, default: false },
};

/** @type {Map<HTMLElement, Collapse>} */
_collapses = new Map();

connect() {
for (const body of this.bodyTargets) {
const collapse = new Collapse(body, {
toggle: false,
parent: !this.alwaysOpenValue ? this.element : undefined,
});
this._collapses.set(body, collapse);
}
}

disconnect() {
for (const collapse of this._collapses.values()) {
collapse.dispose();
}
this._collapses.clear();
}

toggle(event) {
const item = event.currentTarget.closest('[data-accordion-target="item"]');
if (!item) return;

const body = item.querySelector('[data-accordion-target="body"]');
if (!body) return;

const collapse = this._collapses.get(body);
if (!collapse) return;

const header = item.querySelector('[data-accordion-target="header"]');
const isOpen = body.classList.contains('show');

if (isOpen) {
collapse.hide();
if (header) header.setAttribute('aria-expanded', 'false');
if (header) header.classList.add('collapsed');
} else {
// If not alwaysOpen, close others first (Bootstrap handles this via parent option)
if (!this.alwaysOpenValue) {
for (const otherHeader of this.headerTargets) {
if (otherHeader !== header) {
otherHeader.setAttribute('aria-expanded', 'false');
otherHeader.classList.add('collapsed');
}
}
}
collapse.show();
if (header) header.setAttribute('aria-expanded', 'true');
if (header) header.classList.remove('collapsed');
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<twig:Accordion id="always-open-accordion" alwaysOpen>
<twig:Accordion:Item value="1" open>
<twig:Accordion:Header>Accordion Item #1</twig:Accordion:Header>
<twig:Accordion:Body>This item stays open when other items are opened.</twig:Accordion:Body>
</twig:Accordion:Item>
<twig:Accordion:Item value="2" open>
<twig:Accordion:Header>Accordion Item #2</twig:Accordion:Header>
<twig:Accordion:Body>This item also stays open independently.</twig:Accordion:Body>
</twig:Accordion:Item>
</twig:Accordion>
20 changes: 20 additions & 0 deletions src/Toolkit/kits/bootstrap/accordion/examples/Demo.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<twig:Accordion id="demo-accordion">
<twig:Accordion:Item value="1" open>
<twig:Accordion:Header>Accordion Item #1</twig:Accordion:Header>
<twig:Accordion:Body>
<strong>This is the first item's accordion body.</strong> It is shown by default.
</twig:Accordion:Body>
</twig:Accordion:Item>
<twig:Accordion:Item value="2">
<twig:Accordion:Header>Accordion Item #2</twig:Accordion:Header>
<twig:Accordion:Body>
<strong>This is the second item's accordion body.</strong> It is hidden by default.
</twig:Accordion:Body>
</twig:Accordion:Item>
<twig:Accordion:Item value="3">
<twig:Accordion:Header>Accordion Item #3</twig:Accordion:Header>
<twig:Accordion:Body>
<strong>This is the third item's accordion body.</strong> It is hidden by default.
</twig:Accordion:Body>
</twig:Accordion:Item>
</twig:Accordion>
10 changes: 10 additions & 0 deletions src/Toolkit/kits/bootstrap/accordion/examples/Flush.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<twig:Accordion id="flush-accordion" flush>
<twig:Accordion:Item value="1" open>
<twig:Accordion:Header>Accordion Item #1</twig:Accordion:Header>
<twig:Accordion:Body>Flush accordion content for item #1.</twig:Accordion:Body>
</twig:Accordion:Item>
<twig:Accordion:Item value="2">
<twig:Accordion:Header>Accordion Item #2</twig:Accordion:Header>
<twig:Accordion:Body>Flush accordion content for item #2.</twig:Accordion:Body>
</twig:Accordion:Item>
</twig:Accordion>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<twig:Accordion id="my-accordion">
<twig:Accordion:Item value="1" open>
<twig:Accordion:Header>Section title</twig:Accordion:Header>
<twig:Accordion:Body>Section content goes here.</twig:Accordion:Body>
</twig:Accordion:Item>
</twig:Accordion>
14 changes: 14 additions & 0 deletions src/Toolkit/kits/bootstrap/accordion/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "../../../schema-kit-recipe-v1.json",
"type": "component",
"name": "Accordion",
"description": "A vertically stacked set of interactive headings that each reveal a section of content.",
"copy-files": {
"assets/": "assets/",
"templates/": "templates/"
},
"dependencies": {
"composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"],
"importmap": ["bootstrap/dist/css/bootstrap.min.css", "bootstrap"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{# @prop id string Unique identifier for the Accordion #}
{# @prop flush bool Whether to remove the default background and borders. Defaults to `false` #}
{# @prop alwaysOpen bool Whether multiple items can stay open at once. Defaults to `false` #}
{# @block content The accordion items, typically multiple `Accordion:Item` components #}
{%- props id, flush = false, alwaysOpen = false -%}
{%- set _accordion_id = id -%}
{%- set _accordion_always_open = alwaysOpen -%}
{%- set style = html_cva(
base: 'accordion',
variants: {
flush: {
true: 'accordion-flush',
},
},
) -%}
<div
class="{{ style.apply({flush}, attributes.render('class')) }}"
{{ attributes.defaults({
id: _accordion_id,
'data-controller': 'accordion',
'data-accordion-always-open-value': _accordion_always_open ? 'true' : 'false',
}) }}
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{# @block content The collapsible content of the accordion item #}
{%- props -%}
<div
id="{{ _accordion_item_id }}-collapse"
class="accordion-collapse collapse{{ _accordion_item_is_open ? ' show' : '' }}"
data-accordion-target="body"
>
<div class="{{ ('accordion-body ' ~ attributes.render('class'))|trim }}" {{ attributes }}>
{%- block content %}{% endblock -%}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{# @block content The clickable header label that toggles the accordion item #}
{%- props -%}
{%- set style = html_cva(
base: 'accordion-button',
variants: {
collapsed: {
true: 'collapsed',
},
},
) -%}
<h2 class="accordion-header">
<button
class="{{ style.apply({collapsed: not _accordion_item_is_open}, attributes.render('class')) }}"
type="button"
data-action="click->accordion#toggle"
data-accordion-target="header"
aria-expanded="{{ _accordion_item_is_open ? 'true' : 'false' }}"
aria-controls="{{ _accordion_item_id }}-collapse"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</button>
</h2>
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{# @prop value string Unique value for this accordion item #}
{# @prop open bool Whether the item is open by default. Defaults to `false` #}
{# @block content The item content, typically an `Accordion:Header` and `Accordion:Body` #}
{%- props value, open = false -%}
{%- set _accordion_item_id = _accordion_id ~ '-' ~ value -%}
{%- set _accordion_item_is_open = open -%}
<div
class="{{ ('accordion-item ' ~ attributes.render('class'))|trim }}"
data-accordion-target="item"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</div>
6 changes: 6 additions & 0 deletions src/Toolkit/kits/bootstrap/alert/examples/Demo.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="d-flex flex-column gap-2">
<twig:Alert variant="primary">A simple primary alert.</twig:Alert>
<twig:Alert variant="success">A simple success alert.</twig:Alert>
<twig:Alert variant="danger">A simple danger alert.</twig:Alert>
<twig:Alert variant="warning" dismissible>A dismissible warning alert.</twig:Alert>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<twig:Alert variant="warning" dismissible>
<strong>Holy guacamole!</strong> You should check in on some of those fields below.
</twig:Alert>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<twig:Alert variant="success">Operation completed successfully!</twig:Alert>
13 changes: 13 additions & 0 deletions src/Toolkit/kits/bootstrap/alert/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../schema-kit-recipe-v1.json",
"type": "component",
"name": "Alert",
"description": "A notification component that displays important messages with contextual feedback.",
"copy-files": {
"templates/": "templates/"
},
"dependencies": {
"composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"],
"importmap": ["bootstrap/dist/css/bootstrap.min.css"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{# @prop variant 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The visual style variant. Defaults to `primary` #}
{# @prop dismissible bool Whether the alert can be dismissed. Defaults to `false` #}
{# @block content The alert content #}
{%- props variant = 'primary', dismissible = false -%}
{%- set style = html_cva(
base: 'alert',
variants: {
variant: {
primary: 'alert-primary',
secondary: 'alert-secondary',
success: 'alert-success',
danger: 'alert-danger',
warning: 'alert-warning',
info: 'alert-info',
light: 'alert-light',
dark: 'alert-dark',
},
dismissible: {
true: 'alert-dismissible fade show',
},
},
) -%}
<div
class="{{ style.apply({variant, dismissible}, attributes.render('class')) }}"
role="alert"
{{ attributes }}
>
{%- block content %}{% endblock -%}
{%- if dismissible %}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{%- endif -%}
</div>
6 changes: 6 additions & 0 deletions src/Toolkit/kits/bootstrap/badge/examples/Demo.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="d-flex flex-wrap align-items-center gap-2">
<twig:Badge>Primary</twig:Badge>
<twig:Badge variant="secondary">Secondary</twig:Badge>
<twig:Badge variant="success">Success</twig:Badge>
<twig:Badge variant="danger" pill>Pill Danger</twig:Badge>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<twig:Badge variant="success">New</twig:Badge>
13 changes: 13 additions & 0 deletions src/Toolkit/kits/bootstrap/badge/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../schema-kit-recipe-v1.json",
"type": "component",
"name": "Badge",
"description": "A small labeling component for counts, statuses, or categories.",
"copy-files": {
"templates/": "templates/"
},
"dependencies": {
"composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"],
"importmap": ["bootstrap/dist/css/bootstrap.min.css"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{# @prop variant 'primary'|'secondary'|'success'|'danger'|'warning'|'info'|'light'|'dark' The visual style variant. Defaults to `primary` #}
{# @prop pill bool Whether to use rounded-pill style. Defaults to `false` #}
{# @block content The badge content #}
{%- props variant = 'primary', pill = false -%}
{%- set style = html_cva(
base: 'badge',
variants: {
variant: {
primary: 'text-bg-primary',
secondary: 'text-bg-secondary',
success: 'text-bg-success',
danger: 'text-bg-danger',
warning: 'text-bg-warning',
info: 'text-bg-info',
light: 'text-bg-light',
dark: 'text-bg-dark',
},
pill: {
true: 'rounded-pill',
},
},
) -%}
<span
class="{{ style.apply({variant, pill}, attributes.render('class')) }}"
{{ attributes }}
>
{%- block content %}{% endblock -%}
</span>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<twig:Breadcrumb divider=">">
<twig:Breadcrumb:Item href="#">Home</twig:Breadcrumb:Item>
<twig:Breadcrumb:Item href="#">Components</twig:Breadcrumb:Item>
<twig:Breadcrumb:Item active>Breadcrumb</twig:Breadcrumb:Item>
</twig:Breadcrumb>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<twig:Breadcrumb>
<twig:Breadcrumb:Item href="#">Home</twig:Breadcrumb:Item>
<twig:Breadcrumb:Item href="#">Library</twig:Breadcrumb:Item>
<twig:Breadcrumb:Item active>Data</twig:Breadcrumb:Item>
</twig:Breadcrumb>
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<twig:Breadcrumb>
<twig:Breadcrumb:Item href="/">Home</twig:Breadcrumb:Item>
<twig:Breadcrumb:Item active>Current page</twig:Breadcrumb:Item>
</twig:Breadcrumb>
13 changes: 13 additions & 0 deletions src/Toolkit/kits/bootstrap/breadcrumb/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"$schema": "../../../schema-kit-recipe-v1.json",
"type": "component",
"name": "Breadcrumb",
"description": "A navigation aid that displays the current page's location within a hierarchy.",
"copy-files": {
"templates/": "templates/"
},
"dependencies": {
"composer": ["twig/extra-bundle", "twig/html-extra:^3.12.0"],
"importmap": ["bootstrap/dist/css/bootstrap.min.css"]
}
}
Loading
Loading