Skip to content

Commit

Permalink
[Admin][UI] Add error indicators for form tabs and accordions (#16263)
Browse files Browse the repository at this point in the history
| Q               | A
|-----------------|-----
| Branch?         | bootstrap-admin-panel
| Bug fix?        | kinda
| New feature?    | no
| BC breaks?      | no
| Deprecations?   | no
| Related tickets | -
| License         | MIT


https://github.com/Sylius/Sylius/assets/9448101/a94e450e-6530-49e7-b594-d67ff828dd51
  • Loading branch information
Wojdylak committed May 17, 2024
2 parents e6bd06a + 865a481 commit cb9ac59
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 2 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@babel/register": "^7.0.0",
"@hotwired/stimulus": "^3.0.0",
"@semantic-ui-react/css-patch": "^1.1.2",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/stimulus-bridge": "^3.2.2",
"@symfony/webpack-encore": "^3.1.0",
"eslint": "^8.23.0",
"eslint-config-airbnb-base": "^15.0.0",
Expand Down
2 changes: 2 additions & 0 deletions src/Sylius/Bundle/AdminBundle/Resources/assets/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import SlugController from "./controllers/SlugController";
import TaxonSlugController from "./controllers/TaxonSlugController";
import ProductAttributeAutocomplete from "./controllers/ProductAttributeAutocomplete";
import SavePositionsController from "./controllers/SavePositionsController";
import CompoundFormErrorsController from "./controllers/CompoundFormErrorsController";

// Registers Stimulus controllers from controllers.json and in the controllers/ directory
export const app = startStimulusApp(require.context(
Expand All @@ -27,3 +28,4 @@ app.register('slug', SlugController);
app.register('taxon-slug', TaxonSlugController);
app.register('product-attribute-autocomplete', ProductAttributeAutocomplete);
app.register('save-positions', SavePositionsController);
app.register('compound-form-errors', CompoundFormErrorsController);
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* This file is part of the Sylius package.
*
* (c) Sylius Sp. z o.o.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

import { Controller } from '@hotwired/stimulus';

/* stimulusFetch: 'lazy' */
export default class extends Controller {
tabErrorBadgeClass = 'tab-error';
accordionErrorBadgeClass = 'accordion-error';
observer;

initialize() {
super.initialize();

this.observer = new MutationObserver(() => {
this.reloadBadges();
});
}

connect() {
super.connect();

this.updateFormErrors();
this.observe();
}

disconnect() {
super.disconnect();

this.observer.disconnect();
}

observe() {
this.observer.observe(this.element, { attributes: false, childList: true, subtree: true });
}

reloadBadges() {
this.observer.disconnect();
this.clearBadges();
this.updateFormErrors();
this.observe();
}

clearBadges() {
this.element.querySelectorAll(".tab-error").forEach(el => el.remove());
this.element.querySelectorAll(".accordion-error").forEach(el => el.remove());
}

updateFormErrors() {
this.updateErrorBadges(this.element.querySelector('[role="tablist"]'), this.tabErrorBadgeClass);
this.updateErrorBadges(this.element.querySelector('div.accordion'), this.accordionErrorBadgeClass);
}

updateErrorBadges(controlElementsContainer, badgeClass) {
if (null === controlElementsContainer) {
return;
}

const document = controlElementsContainer.ownerDocument;
controlElementsContainer.querySelectorAll('button[type="button"][data-bs-toggle]').forEach((controlElement) => {
const errorsCount = this.countErrors(controlElement);
if (errorsCount > 0) {
const errorElement = document.createElement('div');
errorElement.classList.add(badgeClass);
errorElement.innerText = errorsCount.toString();
controlElement.appendChild(errorElement);
}
});
}

countErrors(element) {
const elementTarget = this.element.querySelector(element.getAttribute('data-bs-target'));

return elementTarget.querySelectorAll('.is-invalid').length +
elementTarget.querySelectorAll('.alert-danger').length;
}
}
27 changes: 27 additions & 0 deletions src/Sylius/Bundle/AdminBundle/Resources/assets/styles/_form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,30 @@ textarea.form-control {
min-height: 8rem;
height: 12rem;
}

.tab-error {
@extend .float-end;
@extend .badge;
@extend .bg-danger;
@extend .rounded-pill;
@extend .text-white;
}

.accordion-error {
@extend .position-absolute;
@extend .top-50;
@extend .start-0;
@extend .translate-middle;
@extend .badge;
@extend .rounded-pill;
@extend .bg-danger;
@extend .text-white;
}

.accordion-item:has(.accordion-error),
.list-group-item:has(.tab-error),
.list-group-item.active:has(.tab-error) {
border-left-style: solid;
border-left-width: 2px;
border-left-color: #ff0017;
}
2 changes: 1 addition & 1 deletion src/Sylius/Bundle/AdminBundle/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dependencies": {
"@hotwired/stimulus": "^3.0.0",
"@popperjs/core": "^2.11.8",
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/stimulus-bridge": "^3.2.2",
"@symfony/webpack-encore": "^3.1.0",
"@tabler/core": "tabler/tabler#dev",
"apexcharts": "^3.41.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
{% extends 'bootstrap_5_layout.html.twig' %}

{%- block form_start -%}
{%- do form.setMethodRendered() -%}
{% set method = method|upper %}
{%- if method in ["GET", "POST"] -%}
{% set form_method = method %}
{%- else -%}
{% set form_method = "POST" %}
{%- endif -%}
<form{% if name != '' %} name="{{ name }}"{% endif %} method="{{ form_method|lower }}"{% if action != '' %} action="{{ action }}"{% endif %}{{ block('attributes') }}{% if multipart %} enctype="multipart/form-data"{% endif %} {{ stimulus_controller('compound-form-errors') }}>
{%- if form_method != method -%}
<input type="hidden" name="_method" value="{{ method }}" />
{%- endif -%}
{%- endblock form_start -%}

{%- block form_row -%}
{% set row_attr = row_attr|default({})|merge({ class: (row_attr.class|default('mb-3') ~ ' field')|trim }) %}
{{ parent() }}
Expand Down

0 comments on commit cb9ac59

Please sign in to comment.