Theme kibatic#14
Conversation
Nouveau thème Theme::KIBATIC, parallèle à bootstrap5 : - SCSS organisé dans assets/styles/ (tokens en custom properties, base, composants), point d'entrée theme.scss. - Templates Twig theme/kibatic/ : datagrid, table (avec icônes de tri), filtres (panneau FILTRES inline), column types boolean/roles/datetime. - Traductions result_count et Filters (fr/en).
- Template::ENUM_BADGE : rend une valeur (enum) en badge coloré, variante via une closure de colonne (fn(value, item) => variante), pastille optionnelle. Fallback neutre dans le thème de base. - datagrid_call(callable, ...args) : invoque une closure passée en paramètre de colonne depuis Twig.
- Filtres avancés repliables : convention row_attr data-filter-advanced + contrôleur Stimulus filters-collapse + bouton « Plus/Moins de filtres ». - Barre de sélection batch : compteur, actions, tout désélectionner, lignes surlignées ; contrôleur Stimulus batch ; addBatchAction accepte variant/icon et un libellé TranslatableMessage. - Helpers de cellules (référence mono, contact 2 lignes, libellé fort) et numérique sans retour à la ligne.
- N'impose plus fill:none/stroke sur les SVG de boutons : cassait les Bootstrap Icons (à remplissage), rendues en contours baveux. - .dropdown-menu masqué par défaut, affiché via .show (menus kebab).
…illé - Le datagrid ne rend plus de toolbar de comptage : le compteur de résultats relève désormais de l'en-tête de page (composition applicative). - Section des filtres avancés sans bordure pointillée.
- Table enveloppée dans un conteneur à défilement horizontal (mobile/tablette). - Filtres en pleine largeur sous 640px, cibles tactiles agrandies (pointer:coarse). - Remplace :has() par la classe .checkbox-cell ; -webkit-appearance sur les cases à cocher ; fallbacks @supports pour color-mix() (focus, bordures).
- Pagination : réinitialise la liste (plus de puces). - Menu kebab : items pleine largeur, y compris le bouton de suppression (réinitialisation du style natif du <button> dans .dropdown-item). - En-tête de table et barre batch en position: sticky sous la topbar ; carte en overflow: clip pour préserver le sticky tout en arrondissant les coins ; défilement horizontal de la table restreint au mobile.
- Police : Inter (variable) auto-hébergée dans assets/dist/fonts/ (+ licence OFL), @font-face local, suppression de l'@import depuis le CDN rsms.me (plus de dépendance réseau / CSP / fuite d'IP au runtime). - Sticky découplé de l'app : token --datagrid-sticky-top (défaut 0, le bundle ne suppose plus de topbar). - Corrige le chevauchement en-tête/barre batch : le thead colle sous la barre (hauteur exposée par le contrôleur Stimulus), plus de superposition. - Docblock de sécurité sur datagrid_call ; note sur _bootstrap.scss.
# Conflicts: # assets/package.json # src/Grid/GridBuilder.php
- Groupes de filtres (filterLayout) rendus côte à côte (.datagrid-filter-group) - Filtrage temps réel via relatedTurboFrames (form-turbo-frame-updater), sans bouton submit quand actif - Classe kibatic-datagrid propagée au composant de filtres - Retire les constantes de thème BOOTSTRAP4 et BOOTSTRAP4_SONATA
…i du bundle - setTheme(string ...$themes) : chaîne de thèmes avec fallback. Le premier est le thème structurel ; les suivants comblent les column types manquants. Ex. setTheme(Theme::KIBATIC, Theme::BOOTSTRAP5). - Grid : getThemes() expose la chaîne, getTheme() renvoie le thème structurel. - _column_value.html.twig : résolution UX → thèmes (ordre) → base → brut. - Retire le point d'entrée global kibatic/theme.scss : la composition greenfield est désormais assemblée côté projet à partir des partials du bundle. - README : documente le mode greenfield assemblé côté projet.
| if (this.hasCountTarget) { | ||
| this.countTarget.textContent = selected | ||
| } | ||
| if (this.hasBarTarget) { | ||
| this.barTarget.hidden = selected === 0 | ||
| // Expose la hauteur de la barre pour décaler le sticky de l'en-tête | ||
| // (évite tout chevauchement). 0 quand la barre est masquée. | ||
| const barHeight = selected === 0 ? 0 : this.barTarget.offsetHeight | ||
| this.element.style.setProperty('--datagrid-batchbar-h', `${barHeight}px`) | ||
| } | ||
| if (this.hasToolbarTarget) { | ||
| this.toolbarTarget.hidden = selected > 0 | ||
| } | ||
| if (this.hasMasterTarget) { | ||
| this.masterTarget.checked = selected > 0 && selected === total | ||
| this.masterTarget.indeterminate = selected > 0 && selected < total | ||
| } | ||
|
|
||
| for (const checkbox of this.checkboxTargets) { | ||
| const row = checkbox.closest('tr') | ||
| if (row) { | ||
| row.classList.toggle('selected', checkbox.checked) | ||
| } | ||
| } |
There was a problem hiding this comment.
| if (this.hasCountTarget) { | |
| this.countTarget.textContent = selected | |
| } | |
| if (this.hasBarTarget) { | |
| this.barTarget.hidden = selected === 0 | |
| // Expose la hauteur de la barre pour décaler le sticky de l'en-tête | |
| // (évite tout chevauchement). 0 quand la barre est masquée. | |
| const barHeight = selected === 0 ? 0 : this.barTarget.offsetHeight | |
| this.element.style.setProperty('--datagrid-batchbar-h', `${barHeight}px`) | |
| } | |
| if (this.hasToolbarTarget) { | |
| this.toolbarTarget.hidden = selected > 0 | |
| } | |
| if (this.hasMasterTarget) { | |
| this.masterTarget.checked = selected > 0 && selected === total | |
| this.masterTarget.indeterminate = selected > 0 && selected < total | |
| } | |
| for (const checkbox of this.checkboxTargets) { | |
| const row = checkbox.closest('tr') | |
| if (row) { | |
| row.classList.toggle('selected', checkbox.checked) | |
| } | |
| } | |
| if (this.hasCountTarget) { | |
| this.countTarget.textContent = selected | |
| } | |
| if (this.hasBarTarget) { | |
| this.barTarget.hidden = selected === 0 | |
| // Expose la hauteur de la barre pour décaler le sticky de l'en-tête | |
| // (évite tout chevauchement). 0 quand la barre est masquée. | |
| const barHeight = selected === 0 ? 0 : this.barTarget.offsetHeight | |
| this.element.style.setProperty('--datagrid-batchbar-h', `${barHeight}px`) | |
| } | |
| if (this.hasToolbarTarget) { | |
| this.toolbarTarget.hidden = selected > 0 | |
| } | |
| if (this.hasMasterTarget) { | |
| this.masterTarget.checked = selected > 0 && selected === total | |
| this.masterTarget.indeterminate = selected > 0 && selected < total | |
| } | |
| for (const checkbox of this.checkboxTargets) { | |
| const row = checkbox.closest('tr') | |
| if (row) { | |
| row.classList.toggle('selected', checkbox.checked) | |
| } | |
| } |
| clear() { | ||
| for (const checkbox of this.checkboxTargets) { | ||
| checkbox.checked = false | ||
| } | ||
| if (this.hasMasterTarget) { | ||
| this.masterTarget.checked = false | ||
| this.masterTarget.indeterminate = false | ||
| } | ||
| this.refresh() | ||
| } |
There was a problem hiding this comment.
| clear() { | |
| for (const checkbox of this.checkboxTargets) { | |
| checkbox.checked = false | |
| } | |
| if (this.hasMasterTarget) { | |
| this.masterTarget.checked = false | |
| this.masterTarget.indeterminate = false | |
| } | |
| this.refresh() | |
| } | |
| clear() { | |
| for (const checkbox of this.checkboxTargets) { | |
| checkbox.checked = false | |
| } | |
| if (this.hasMasterTarget) { | |
| this.masterTarget.checked = false | |
| this.masterTarget.indeterminate = false | |
| } | |
| this.refresh() | |
| } |
| if (button.dataset.confirm === 'true') { | ||
| // Même confirmation SweetAlert que le reste de l'app (style par défaut). | ||
| Swal.fire({ | ||
| title: 'Êtes-vous sûr ?', |
| this.formTarget.submit() | ||
| } | ||
|
|
||
| if (button.dataset.confirm === 'true') { |
There was a problem hiding this comment.
ce serait plus logique de passer par une option de controller stimulus
| { | ||
| return [ | ||
| new TwigFunction('datagrid_reset_url', $this->resetUrl(...)), | ||
| new TwigFunction('datagrid_call', $this->call(...)), |
There was a problem hiding this comment.
je vois pas pourquoi on a besoin de ça pour gérer le variant via une lambda.
| {# Chaîne de résolution : surcharge UX projet → chaque thème de la grille | ||
| (dans l'ordre de priorité) → base du bundle → template brut. #} |
There was a problem hiding this comment.
ce commentaire est pas clair je trouve, faudrait le reformuler toi à la main et le passer sur une seule ligne
| Le libellé vient de value.label (enum avec méthode label()), sinon value.name, sinon la valeur. | ||
| #} | ||
| {% if value is not null %} | ||
| {% set variant = parameters.variant is defined ? datagrid_call(parameters.variant, value, item) : 'neutral' %} |
There was a problem hiding this comment.
idem que mon commentaire dans AppExtension, pour moi on doit pouvoir simplement récupérer le resultat d'une lambda du builder sans avoir à passer par une fonction custom. pourquoi t'as eu besoin de faire datagrid_call ?
| {% set label = value | ||
| ? (parameters.trueLabel ?? 'column.boolean.true'|trans({}, 'KibaticDatagridBundle')) | ||
| : (parameters.falseLabel ?? 'column.boolean.false'|trans({}, 'KibaticDatagridBundle')) %} |
There was a problem hiding this comment.
| {% set label = value | |
| ? (parameters.trueLabel ?? 'column.boolean.true'|trans({}, 'KibaticDatagridBundle')) | |
| : (parameters.falseLabel ?? 'column.boolean.false'|trans({}, 'KibaticDatagridBundle')) %} | |
| {% set label = value | |
| ? (parameters.trueLabel ?? 'column.boolean.true'|trans({}, 'KibaticDatagridBundle')) | |
| : (parameters.falseLabel ?? 'column.boolean.false'|trans({}, 'KibaticDatagridBundle')) | |
| %} |
No description provided.