Onglets
Le composant tabs permet une navigation par onglet
Classes du composant
Modificateurs de tailles
.tabs--sm: Small.tabs--md: Medium.tabs--lg: Large.tabs--xl: Extra-large
Modificateurs d'apparence
.tabs--box: Permet d'intégrer visuellement un composant tabs dans un élément.box(les onglets se retrouvent centrés).
Ajouter l'attribut data-selected="true" permet de sélectionner un onglet à l'affichage initial.
Size
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
Empty
Empty tabs
Pre-selected tabs
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="kuik" uri="kuik" %>
<kuik:heading size="lg" skin="a" title="Size" level="3"/>
<div class="g-col-12">
<kuik:tabs size="sm" skin="pills">
<kuik:tabs-item id="pills-a" title="Lorem Ipsum">
<p>Lorem ipsum dolor sit amet, <a href="">consectetur</a> adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</kuik:tabs-item>
<kuik:tabs-item id="pills-b" title="Sed ut perspiciatis">
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</kuik:tabs-item>
<kuik:tabs-item id="pills-c" title="At vero eos et accusamus">
<p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.</p>
</kuik:tabs-item>
</kuik:tabs>
</div>
<div class="g-col-12">
<kuik:tabs size="xl" skin="pills">
<kuik:tabs-item id="pills-e" title="e" icon="icon://ui/paperclip">content e</kuik:tabs-item>
<kuik:tabs-item id="pills-f" title="f">content f</kuik:tabs-item>
<kuik:tabs-item id="pills-g" title="g" icon="icon://ui/at-sign">content g</kuik:tabs-item>
</kuik:tabs>
</div>
<div class="g-col-12">
<kuik:tabs size="md" skin="pills">
<kuik:tabs-item id="pills-h" title="Onglet numéro un avec un long titre">content h</kuik:tabs-item>
<kuik:tabs-item id="pills-i" title="Onglet numéro deux avec un long titre">content i</kuik:tabs-item>
<kuik:tabs-item id="pills-j" title="Onglet numéro trois avec un long titre">content j</kuik:tabs-item>
<kuik:tabs-addon>
<div class="form-field form-field--md form-field--expanded">
<div class="form-field__control">
<label for="myTabs3_recherche">
<span class="form-field__icon"><kuik:icon source="icon://ui/search" size="md" title="Recherche"/></span>
</label>
<input id="myTabs3_recherche" class="form-field__input" type="search" placeholder="Rechercher…" />
</div>
</div>
</kuik:tabs-addon>
</kuik:tabs>
</div>
<div class="g-col-12">
<kuik:tabs size="md" skin="pills">
<kuik:tabs-item id="pills-k" title="Onglet numéro un avec un long titre">content h</kuik:tabs-item>
<kuik:tabs-item id="pills-l" title="Onglet numéro deux avec un long titre">content i</kuik:tabs-item>
<kuik:tabs-item id="pills-m" title="Onglet numéro trois avec un long titre">
</kuik:tabs-item>
<kuik:tabs-addon>
<div class="form-field form-field--md form-field--expanded">
<div class="form-field__control">
<label for="myTabs4_recherche">
<span class="form-field__icon"><kuik:icon source="icon://ui/search" size="md" title="Recherche"/></span>
</label>
<input id="myTabs4_recherche" class="form-field__input" type="search" placeholder="Rechercher…" />
</div>
</div>
</kuik:tabs-addon>
</kuik:tabs>
</div>
<div class="g-col-12">
<kuik:tabs size="md" moreitems="Autres catégories" skin="pills">
<kuik:tabs-item id="pills-n" title="Onglet numéro un avec un long titre">Onglet numéro un avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-o" title="Onglet numéro deux avec un long titre">Onglet numéro deux avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-p" title="Onglet numéro trois avec un long titre">Onglet numéro trois avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-q" title="Onglet numéro quatre avec un long titre">Onglet numéro quatre avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-r" title="Onglet numéro cinq avec un long titre">Onglet numéro cinq avec un long titre</kuik:tabs-item>
<kuik:tabs-addon>
<div class="form-field form-field--md form-field--expanded">
<div class="form-field__control">
<label for="myTabs5_recherche">
<span class="form-field__icon"><kuik:icon source="icon://ui/search" size="md" title="Recherche"/></span>
</label>
<input id="myTabs5_recherche" class="form-field__input" type="search" placeholder="Rechercher…" />
</div>
</div>
</kuik:tabs-addon>
</kuik:tabs>
</div>
<div class="g-col-12">
<kuik:tabs size="md" moreitems="Autres catégories" skin="pills">
<kuik:tabs-item id="pills-s" title="Onglet numéro un avec un long titre">Onglet numéro un avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-t" title="Onglet numéro deux avec un long titre">Onglet numéro deux avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-u" title="Onglet numéro trois avec un long titre">Onglet numéro trois avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-v" title="Onglet numéro quatre avec un long titre">Onglet numéro quatre avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="pills-w" title="Onglet numéro cinq avec un long titre">Onglet numéro cinq avec un long titre</kuik:tabs-item>
</kuik:tabs>
</div>
<kuik:heading size="lg" skin="a" title="Empty" level="3"/>
<div class="g-col-12">
<kuik:tabs size="sm" id="myTab5" skin="pills"></kuik:tabs>
</div>
<kuik:heading size="lg" skin="a" title="Empty tabs" level="3"/>
<div class="g-col-12">
<kuik:tabs size="sm" id="myTab6" skin="pills">
<kuik:tabs-item id="pills-x" title="Vide pas affiché"></kuik:tabs-item>
<kuik:tabs-item id="pills-y" title="Vide affiché quand même" force="true"></kuik:tabs-item>
</kuik:tabs>
</div>
<kuik:heading size="lg" skin="a" title="Pre-selected tabs" level="3"/>
<kuik:tabs size="md" moreitems="Autres catégories" skin="pills">
<kuik:tabs-item id="pills-aa" title="Onglet 1">Onglet 1</kuik:tabs-item>
<kuik:tabs-item id="pills-ab" title="Onglet 2">Onglet 2</kuik:tabs-item>
<kuik:tabs-item id="pills-ac" title="Onglet 3" data-selected="true">Onglet 3 affiché par défaut</kuik:tabs-item>
<kuik:tabs-item id="pills-ad" title="Onglet 4" >Onglet 4</kuik:tabs-item>
<kuik:tabs-item id="pills-ae" title="Onglet 5">Onglet 5</kuik:tabs-item>
</kuik:tabs>
Size
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
Empty
Empty tabs
Pre-selected tabs
<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" trimDirectiveWhitespaces="true" %>
<%@ taglib prefix="kuik" uri="kuik" %>
<kuik:heading size="lg" skin="a" title="Size" level="3"/>
<kuik:tabs size="sm">
<kuik:tabs-item id="a" title="Lorem Ipsum">
<p>Lorem ipsum dolor sit amet, <a href="">consectetur</a> adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</kuik:tabs-item>
<kuik:tabs-item id="b" title="Sed ut perspiciatis">
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</kuik:tabs-item>
<kuik:tabs-item id="c" title="At vero eos et accusamus">
<p>At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.</p>
</kuik:tabs-item>
</kuik:tabs>
<kuik:tabs size="xl">
<kuik:tabs-item id="e" title="e" icon="icon://ui/paperclip">content e</kuik:tabs-item>
<kuik:tabs-item id="f" title="f">content f</kuik:tabs-item>
<kuik:tabs-item id="g" title="g" icon="icon://ui/at-sign">content g</kuik:tabs-item>
</kuik:tabs>
<kuik:tabs size="md">
<kuik:tabs-item id="h" title="Onglet numéro un avec un long titre">content h</kuik:tabs-item>
<kuik:tabs-item id="i" title="Onglet numéro deux avec un long titre">content i</kuik:tabs-item>
<kuik:tabs-item id="j" title="Onglet numéro trois avec un long titre">content j</kuik:tabs-item>
<kuik:tabs-addon>
<div class="form-field form-field--md form-field--expanded">
<div class="form-field__control">
<label for="myTabs3_recherche">
<span class="form-field__icon"><kuik:icon source="icon://ui/search" size="md" title="Recherche"/></span>
</label>
<input id="myTabs3_recherche" class="form-field__input" type="search" placeholder="Rechercher…" />
</div>
</div>
</kuik:tabs-addon>
</kuik:tabs>
<kuik:tabs size="md">
<kuik:tabs-item id="k" title="Onglet numéro un avec un long titre">content h</kuik:tabs-item>
<kuik:tabs-item id="l" title="Onglet numéro deux avec un long titre">content i</kuik:tabs-item>
<kuik:tabs-item id="m" title="Onglet numéro trois avec un long titre">
</kuik:tabs-item>
<kuik:tabs-addon>
<div class="form-field form-field--md form-field--expanded">
<div class="form-field__control">
<label for="myTabs4_recherche">
<span class="form-field__icon"><kuik:icon source="icon://ui/search" size="md" title="Recherche"/></span>
</label>
<input id="myTabs4_recherche" class="form-field__input" type="search" placeholder="Rechercher…" />
</div>
</div>
</kuik:tabs-addon>
</kuik:tabs>
<kuik:tabs size="md" moreitems="Autres catégories">
<kuik:tabs-item id="n" title="Onglet numéro un avec un long titre">Onglet numéro un avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="o" title="Onglet numéro deux avec un long titre">Onglet numéro deux avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="p" title="Onglet numéro trois avec un long titre">Onglet numéro trois avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="q" title="Onglet numéro quatre avec un long titre">Onglet numéro quatre avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="r" title="Onglet numéro cinq avec un long titre">Onglet numéro cinq avec un long titre</kuik:tabs-item>
<kuik:tabs-addon>
<div class="form-field form-field--md form-field--expanded">
<div class="form-field__control">
<label for="myTabs5_recherche">
<span class="form-field__icon"><kuik:icon source="icon://ui/search" size="md" title="Recherche"/></span>
</label>
<input id="myTabs5_recherche" class="form-field__input" type="search" placeholder="Rechercher…" />
</div>
</div>
</kuik:tabs-addon>
</kuik:tabs>
<kuik:tabs size="md" moreitems="Autres catégories">
<kuik:tabs-item id="s" title="Onglet numéro un avec un long titre">Onglet numéro un avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="t" title="Onglet numéro deux avec un long titre">Onglet numéro deux avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="u" title="Onglet numéro trois avec un long titre">Onglet numéro trois avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="v" title="Onglet numéro quatre avec un long titre">Onglet numéro quatre avec un long titre</kuik:tabs-item>
<kuik:tabs-item id="w" title="Onglet numéro cinq avec un long titre">Onglet numéro cinq avec un long titre</kuik:tabs-item>
</kuik:tabs>
<kuik:heading size="lg" skin="a" title="Empty" level="3"/>
<kuik:tabs size="sm" id="myTab5" />
<kuik:heading size="lg" skin="a" title="Empty tabs" level="3"/>
<kuik:tabs size="sm" id="myTab6">
<kuik:tabs-item id="x" title="Vide pas affiché" />
<kuik:tabs-item id="y" title="Vide affiché quand même" force="true" />
</kuik:tabs>
<kuik:heading size="lg" skin="a" title="Pre-selected tabs" level="3"/>
<kuik:tabs size="md" moreitems="Autres catégories">
<kuik:tabs-item id="aa" title="Onglet 1">Onglet 1</kuik:tabs-item>
<kuik:tabs-item id="ab" title="Onglet 2">Onglet 2</kuik:tabs-item>
<kuik:tabs-item id="ac" title="Onglet 3" data-selected="true">Onglet 3 affiché par défaut</kuik:tabs-item>
<kuik:tabs-item id="ad" title="Onglet 4">Onglet 4</kuik:tabs-item>
<kuik:tabs-item id="ae" title="Onglet 5">Onglet 5</kuik:tabs-item>
</kuik:tabs>
| Name | Type | Required | Description |
|---|---|---|---|
| size | java.lang.String | false | Taille (défaut : sm) |
| moreitems | java.lang.String | false | Titre du groupe si la limite s'applique |
| skin | java.lang.String | false | Apparence du composant (tabs ou pills) Défaut : tabs |
$prefix-list: 'tabs-list';
$prefix-header: 'tabs-header';
$prefix-button: 'tabs-button';
.tabs__header {
--wrap: wrap;
align-items: stretch;
display: flex;
flex-wrap: var(--wrap);
max-width: 100%;
}
.tabs__header--addon {
margin-inline-start: auto;
@media (max-width: $screen-sm) {
flex-basis: 100%;
}
}
.tabs__limit-popover:popover-open menu {
align-items: start;
display: flex;
flex-direction: column;
list-style: none;
}
.tabs__list {
@include custom-space($prefix-list, true);
align-items: stretch;
display: flex;
flex-wrap: var(--wrap);
list-style: none;
margin: var(--space-0);
}
.tabs__button,
.tabs__button.button--tertiary.button--accent { // pour le dropdown
@include custom-typo($prefix-button, true);
@include custom-color($prefix-button, true);
@include custom-space($prefix-button, true);
@include custom-border($prefix-button, true);
align-items: center;
cursor: pointer;
display: inline-flex;
height: 100%;
justify-content: center;
white-space: nowrap;
.icon {
@include custom-icon;
}
svg {
flex-shrink: 0;
}
}
.tabs__button--current,
.tabs__button[aria-selected='true'] {
@include custom-color($prefix-button);
.icon {
@include custom-icon-color;
}
}
.tabs__panel {
padding-block: var(--space-4);
}
.tabs--box {
.tabs__header {
border-bottom: var(--line-2xs) solid var(--neutral-30);
justify-content: center;
max-width: none;
padding-block-start: var(--space-4);
}
.tabs__item {
border-bottom: none;
}
.tabs__panel:has(> .box) {
padding-block: var(--space-0);
}
}
.tabs--skin-tabs {
.tabs__header {
@include custom-color-fill($prefix-header, true);
@include custom-color-stroke($prefix-header, true);
@include custom-border($prefix-header, true);
}
.button[aria-selected='true'] {
--button-content: var(--color1-10);
--button-fill: var(--color1-70);
--icon-foreground: var(--color1-10);
}
}
.tabs--skin-tabs, .tabs--skin-pills {
&.tabs--sm,
&.tabs--md,
&.tabs--lg,
&.tabs--xl {
.tabs__button,
.tabs__button.button--tertiary.button--accent {
@include custom-space($prefix-button);
@include custom-border($prefix-button);
.icon {
@include custom-icon-size;
}
}
}
.button {
--button-stroke: transparent;
}
.tabs__button:hover {
@include custom-color($prefix-button);
text-decoration: none;
.icon {
@include custom-icon-color;
}
}
.tabs__button:focus {
@include custom-color($prefix-button);
.icon {
@include custom-icon-color;
}
}
}
/**
* Affiche un élement.
* @param {HTMLElement} element - Element à afficher
* @return {undefined}
*/
export function show(element) {
element?.classList.remove('hidden');
}
/**
* Cache un élement.
* @param {HTMLElement} element - Element à cacher
* @return {undefined}
*/
export function hide(element) {
element?.classList.add('hidden');
}
/**
* Sélectionne tab parmi les tabs.
* @param {HTMLElement} tab le tab à sélectionner
* @return {undefined}
*/
export function select(tab) {
if (!tab) {
return;
}
tab.closest('.js-tabs__list').querySelectorAll('.js-tabs__button').forEach((currentTab) => {
currentTab.setAttribute('aria-selected', 'false');
currentTab.setAttribute('tabindex', '-1');
hide(getPanel(currentTab));
});
tab.setAttribute('aria-selected', 'true');
tab.removeAttribute('tabindex');
const panel = getPanel(tab);
show(panel);
tab.dispatchEvent(new CustomEvent('tab:change'));
}
/**
* Récupère le panel associé à un tab.
* @param {HTMLElement} tab le tab
* @return {HTMLElement} le panel associé au tab
*/
function getPanel(tab) {
return document.getElementById(tab.getAttribute('aria-controls'));
}
import {show, hide, select} from './tabs-core.js'
import {throttle} from './tabs-utils.js';
/**
* Préparation de la limite des onglets basée sur la largeur du header.
* @param {HTMLElement} tabs les onglets
*/
export function initEventsDropdown(tabs) {
const dropdown = getDropdown(tabs);
if (!dropdown) {
return;
}
dropdown.addEventListener('keyup', handleKeyupDropdownButton(dropdown));
dropdown.addEventListener('keydown', noGlobalScroll, {capture: true});
dropdown.popoverTargetElement.addEventListener('keyup', handleKeyupDropdown(dropdown));
dropdown.popoverTargetElement.addEventListener('click', handleClickDropdown(dropdown));
dropdown.popoverTargetElement.addEventListener('keydown', noGlobalScroll, {capture: true});
}
/**
* Préparation de la limite des onglets basée sur la largeur du header.
* @param {HTMLElement} tabs les onglets
*/
export function prepareLimit(tabs) {
const tabsHeader = tabs.querySelector(':scope > .js-tabs__header');
const tabsList = tabsHeader.querySelector(':scope > .js-tabs__list');
const dropdown = tabsList.querySelector(`[data-dropdown="${tabsList.dataset.scope}"]`);
const dropdownMenu = document.createElement('ul');
dropdownMenu.classList.add('u-list-naked');
dropdown.popoverTargetElement.appendChild(dropdownMenu);
createResizeObserver(tabsHeader, tabsList, dropdown, dropdownMenu);
createMutationObserver(tabsHeader, tabsList, dropdown, dropdownMenu);
}
/**
* Observe les modifications de taille de la barre d'onglets
* @param {HTMLElement} tabsHeader conteneur sur lequel la largeur est testée
* @param {HTMLElement} tabsList liste des onglets
* @param {HTMLButtonElement} dropdown bouton dropdown
* @param {HTMLElement} popoverTarget cible de rangement des onglets
*/
function createResizeObserver(tabsHeader, tabsList, dropdown, popoverTarget) {
const resizeObserver = new ResizeObserver(() => {
throttledAdapt(tabsHeader, tabsList, dropdown, popoverTarget);
});
resizeObserver.observe(tabsHeader);
adapt(tabsHeader, tabsList, dropdown, popoverTarget);
}
/**
* Observe les modifications de taille de la barre d'onglets
* @param {HTMLElement} tabsHeader conteneur sur lequel la largeur est testée
* @param {HTMLElement} tabsList liste des onglets
* @param {HTMLButtonElement} dropdown bouton dropdown
* @param {HTMLElement} popoverTarget cible de rangement des onglets
*/
function createMutationObserver(tabsHeader, tabsList, dropdown, popoverTarget) {
const mutationObserver = new MutationObserver(() => {
updateDropdown(tabsHeader, dropdown, popoverTarget);
});
mutationObserver.observe(popoverTarget, {subtree: true, attributes: true, attributeFilter: ['aria-selected']});
}
/**
* Décide du titre à afficher pour le bouton « plus d'onglets»
* @param {HTMLElement} tabsHeader conteneur sur lequel la largeur est testée
* @param {HTMLButtonElement} dropdown bouton dropdown
* @param {HTMLElement} popoverTarget cible de rangement des onglets
*/
function updateDropdown(tabsHeader, dropdown, popoverTarget) {
const selectedEntry = popoverTarget.querySelector(':scope [aria-selected="true"]');
const dropdownTitleElement = dropdown.querySelector(':scope .u-popover__title');
if (selectedEntry) {
if (!dropdownTitleElement.dataset.oldTitle) {
dropdownTitleElement.dataset.oldTitle = dropdownTitleElement.innerHTML;
}
dropdownTitleElement.innerHTML = selectedEntry.querySelector('.js-tabs__label').innerHTML;
dropdown.setAttribute('aria-selected', 'true');
dropdown.setAttribute('tabindex', '0');
} else {
dropdown.setAttribute('aria-selected', 'false');
dropdown.setAttribute('tabindex', '-1');
}
if (!selectedEntry && dropdownTitleElement.dataset.oldTitle) {
dropdownTitleElement.innerHTML = dropdownTitleElement.dataset.oldTitle;
}
}
/**
* Adaptation des onglets si trop large
* @param {HTMLElement} tabsHeader conteneur sur lequel la largeur est testée
* @param {HTMLElement} tabsList liste des onglets
* @param {HTMLButtonElement} dropdown bouton dropdown
* @param {HTMLElement} popoverTarget cible de rangement des onglets
*/
function adapt(tabsHeader, tabsList, dropdown, popoverTarget) {
updateDropdown(tabsHeader, dropdown, popoverTarget);
popAllTabs(tabsList, popoverTarget);
show(dropdown);
while (isOverflown(tabsList, tabsHeader) && tabsList.querySelectorAll(':scope > .js-tabs__item').length > 0) {
const toMove = Array.from(tabsList.querySelectorAll(':scope > .js-tabs__item')).at(-1);
toMove.removeAttribute('tabindex');
popoverTarget.prepend(toMove);
}
if (popoverTarget.querySelectorAll(':scope > .js-tabs__item').length === 0) {
hide(dropdown);
}
tabsList.append(dropdown.parentElement);
updateDropdown(tabsHeader, dropdown, popoverTarget);
}
/**
* Version limitée de `adapt`.
* @type {Function}
*/
const throttledAdapt = throttle(adapt, 50);
/**
* Sort tous les boutons du popover s'arrête si l'un d'entre eux est sélectionné.
* @param {HTMLElement} tabsList liste des onglets
* @param {HTMLElement} popoverTarget cible de rangement des onglets
*/
function popAllTabs(tabsList, popoverTarget) {
let nextTab = popoverTarget.querySelector('.js-tabs__item');
while (nextTab) {
tabsList.append(nextTab);
nextTab = popoverTarget.querySelector('.js-tabs__item');
}
}
/**
* Vérifie si un élément est plus large que l'espace qui lui ai réversé
* @param {HTMLElement} element élément sur lequel la largeur est testée
* @param {HTMLElement} parent conteneur dans lequel la largeur est testée
* @return {boolean} true si le flex du conteneur a déclenché le wrap et qu'un élément est plus «bas» que le conteneur
*/
function isOverflown(element, parent) {
const availableWidth = parent.getBoundingClientRect().width - Array.from(parent.children)
.filter(i => i !== element)
.map(i => i.getBoundingClientRect().width)
.reduce((a,c) => a+c, 0);
return element.getBoundingClientRect().width > availableWidth - 50;
}
/**
* Fabrique de gestionnaire d'événements clavier pour les items du dropdown.
* @param {HTMLButtonElement} dropdown bouton du menu
* @return {EventListener} le gestionnaire
*/
function handleKeyupDropdown(dropdown) {
const keysAction = {
ArrowDown: focusNextItemDropdown,
ArrowUp: focusPreviousItemDropdown,
Home: openFirstItemDropdown,
End: openLastItemDropdown
};
/**
* Gestionnaire d'événement clavier pour les dropdowns.
* @param {KeyboardEvent} event event
*/
return function handleKeyupEventHandler(event) {
event.stopPropagation();
const buttons = Array.from(dropdown.popoverTargetElement.querySelectorAll('.js-tabs__button'));
const currentButtonIndex = buttons.findIndex((b) => b.id === event.target.id);
if (Object.hasOwn(keysAction, event.key)) {
keysAction[event.key]({currentButtonIndex, event, buttons, dropdown});
}
}
}
/**
* Fabrique de gestionnaire d'événements clavier pour les items du dropdown.
* @param {HTMLButtonElement} dropdown bouton du menu
* @return {EventListener} le gestionnaire
*/
function handleKeyupDropdownButton(dropdown) {
/**
* Gestionnaire d'événement clavier pour les dropdowns.
* @param {KeyboardEvent} event event
*/
return function handleKeyupEventHandler(event) {
event.stopPropagation();
event.preventDefault();
const buttons = Array.from(dropdown.popoverTargetElement.querySelectorAll('.js-tabs__button'));
if (event.key === 'ArrowDown') {
openFirstItemDropdown({buttons});
}
}
}
/**
* Empêche le scroll lorsque l'utilisateur utilise les flèches haut et bas sur le dropdown.
* @param {KeyboardEvent} event event
*/
function noGlobalScroll(event) {
if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
event.stopImmediatePropagation();
event.preventDefault();
}
}
/**
* Sélectionne l'onglet du popover.
* @param {HTMLButtonElement} dropdown le bouton de popover
* @param {Event} event l'événement déclencheur
*/
function selectTab({dropdown, event}) {
const currentTab = event.target.closest('.js-tabs__button');
select(currentTab);
dropdown.setAttribute('aria-selected', 'true');
dropdown.setAttribute('tabindex', '0');
dropdown.popoverTargetElement.hidePopover();
}
/**
* Fabrique de gestionnaire d'événements de click pour le dropdown.
* @param {HTMLButtonElement} dropdown bouton du menu
* @return {EventListener} le gestionnaire
*/
function handleClickDropdown(dropdown) {
/**
* Gestionnaire d'événement click pour le dropdown.
* @param {MouseEvent} event event
*/
return function handleClickDropdownHandler(event) {
selectTab({dropdown, event});
}
}
/**
* Active l'élément suivant dans le dropdown.
* @param {Number} currentButtonIndex index du menu courrant
* @param {HTMLButtonElement[]} buttons tableau des buttons du menu
*/
function focusNextItemDropdown({currentButtonIndex, buttons}) {
if (currentButtonIndex < buttons.length) {
buttons[currentButtonIndex + 1]?.focus();
}
}
/**
* Active l'élément précédent dans le dropdown.
* @param {Number} currentButtonIndex index du menu courrant
* @param {HTMLButtonElement[]} buttons tableau des buttons du menu
*/
function focusPreviousItemDropdown({currentButtonIndex, buttons}) {
if (currentButtonIndex > 0) {
buttons[currentButtonIndex - 1]?.focus();
}
}
/**
* Active le premier élément dans le dropdown.
* @param {HTMLButtonElement[]} buttons tableau des buttons du menu
*/
function openFirstItemDropdown({buttons}) {
buttons[0]?.focus();
}
/**
* Active le dernier élément dans le dropdown.
* @param {HTMLButtonElement[]} buttons tableau des buttons du menu
*/
function openLastItemDropdown({buttons}) {
buttons[buttons.length - 1]?.focus();
}
/**
* Récupère le dropdown si présent.
* @param {HTMLElement} tabs
* @return {HTMLButtonElement} dropdown
*/
export function getDropdown(tabs) {
return tabs.querySelector(`[data-dropdown="${tabs.dataset.key}"]`);
}
import {select} from './tabs-core.js'
import {getDropdown} from './tabs-limit.js';
/**
* Application des recommandations présentent ici : https://www.w3.org/WAI/ARIA/apg/patterns/tabs/
*/
/**
* Initialise les événements des boutons des onglets.
* @param {HTMLElement} tabs les onglets
* @param {HTMLButtonElement[]} tabButtons les boutons des onglets
* @return {undefined}
*/
export function initEventsButton(tabs, tabButtons) {
tabs.addEventListener('keyup', handleTabsKeyup(tabs), true);
for (const currentTab of tabButtons) {
currentTab.addEventListener('focus', handleClickHandler, true);
}
}
/**
* Fonction permettant de gérer les événements de type 'focus' sur les onglets.
* @param {Event} event l'événement créé par le click.
* @return {undefined}
*/
function handleClickHandler(event) {
event.preventDefault();
select(event.currentTarget);
}
/**
* Active l'onglet ou le dropdown.
* @param {HTMLButtonElement} tab l'onglet à activer
*/
function focusTab(tab) {
tab?.focus();
}
/**
* Active l'onglet précédent.
* @param {Number} currentTabIndex index de l'onglet courant
* @param {HTMLButtonElement[]} tabs tableau des onglets
*/
function focusPreviousTab({currentTabIndex, tabs}) {
if (currentTabIndex > 0) {
focusTab(tabs[currentTabIndex - 1]);
}
}
/**
* Active l'onglet suivant.
* @param {Number} currentTabIndex index de l'onglet courant
* @param {HTMLButtonElement[]} tabs tableau des onglets
*/
function focusNextTab({currentTabIndex, tabs}) {
if (currentTabIndex !== -1 && currentTabIndex < tabs.length - 1) {
focusTab(tabs[currentTabIndex + 1]);
}
}
/**
* Active le premier onglet.
* @param {HTMLElement[]} tabs tableau des onglets
*/
function focusFirstTab({tabs}) {
focusTab(tabs[0]);
}
/**
* Active le dernier onglet.
* @param {HTMLElement[]} tabs tableau des onglets
*/
function focusLastTab({tabs}) {
focusTab(tabs[tabs.length - 1]);
}
/**
* Fabrique pour la gestion des événements des onglets.
* @param {HTMLElement} tabs les onglets
* @return {EventListener} le gestionnaire
*/
function handleTabsKeyup(tabs) {
/**
* Fonction permettant de gérer les événements de type 'click' sur les onglets.
* @param {Event} event l'événement créé par le click.
* @returns {void}
*/
return function tabsKeyupHandler(event) {
if (event.target.closest(`[data-key]`).dataset.key !== tabs.dataset.key) {
return;
}
const tabButtonsList = Array.from(tabs.querySelectorAll(`[data-scope="${tabs.dataset.key}"] > .js-tabs__item > .js-tabs__button--focusable`));
const dropdown = getDropdown(tabs);
if (dropdown) {
tabButtonsList.push(dropdown);
}
const currentTabIndex = tabButtonsList.findIndex((t) => t.getAttribute('aria-selected') === 'true');
const keysAction = {
ArrowLeft: focusPreviousTab,
ArrowRight: focusNextTab,
Home: focusFirstTab,
End: focusLastTab
};
if (Object.hasOwn(keysAction, event.key)) {
keysAction[event.key]({currentTabIndex, tabs: tabButtonsList});
}
};
}
/**
* Évite qu'une fonction soit appelée trop fréquemment.
* @param {Function} func la function
* @param {number} limit
* @return {Function} la même fonction mais avec la limite
*/
export function throttle(func, limit) {
let lastFunc;
let lastRan;
return (...args) => {
const delta = Date.now() - (lastRan ?? 0);
if (!lastRan) {
func(...args);
lastRan = Date.now();
} else {
if (delta > limit) {
func(...args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(() => {
func(...args);
lastRan = Date.now();
}, Math.max(0, limit - delta));
}
}
};
}
import {initEventsDropdown, prepareLimit} from './tabs-limit.js';
import {initEventsButton} from './tabs-navigation.js';
import {select} from './tabs-core.js';
import {onPageReady} from '/static/js/script-loader-helper.js';
export function initTabs(tabs) {
const tabButtonsList = Array.from(tabs.querySelectorAll(`[data-scope="${tabs.dataset.key}"] > .js-tabs__item > .js-tabs__button`));
initEventsButton(tabs, tabButtonsList);
let selectedTab = tabs.querySelector(`[data-scope="${tabs.dataset.key}"] > .js-tabs__item > .js-tabs__button[data-selected="true"]`);
if (selectedTab) {
selectedTab.removeAttribute('data-selected');
} else {
selectedTab = tabButtonsList[0];
}
select(selectedTab);
}
/**
* Attente que la page soit entièrement chargée.
*/
onPageReady(() => {
/**
* Initialisation des onglets.
*/
document.querySelectorAll('.js-tabs').forEach((tabs) => {
initTabs(tabs);
});
/**
* Préparation des limites
*/
document.querySelectorAll('.js-tabs.tabs--limited:not(.js-limit--initialized)').forEach((tabs) => {
prepareLimit(tabs);
initEventsDropdown(tabs);
tabs.classList.add('js-limit--initialized');
});
});
<%@ tag pageEncoding="UTF-8" trimDirectiveWhitespaces="true" dynamic-attributes="dynattrs" %>
<%@ tag import="java.util.UUID" %>
<%@ taglib prefix="kuik" uri="kuik" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="resources" uri="resources" %>
<%@ attribute name="size" required="false" type="java.lang.String" description="Taille (défaut : sm)" %>
<%@ attribute name="moreitems" required="false" type="java.lang.String" description="Titre du groupe si la limite s'applique" %>
<%@ attribute name="skin" required="false" type="java.lang.String" description="Apparence du composant (tabs ou pills) Défaut : tabs" %>
<c:set var="key" value="${UUID.randomUUID().toString()}"/>
<c:set var="__old_tabsStep" value="${requestScope.__tabsStep}"/>
<c:set var="__old_size" value="${requestScope.__size}"/>
<c:if test="${empty pageScope.size}">
<c:set var="size" value="sm"/>
</c:if>
<c:set var="classesSize" value="tabs--${pageScope.size}"/>
<c:if test="${empty pageScope.skin}">
<c:set var="skin" value="tabs" />
</c:if>
<c:set var="classesSkin" value="tabs--skin-${pageScope.skin}"/>
<c:set value="tabs js-tabs ${pageScope.classesSize} ${pageScope.classesSkin}" var="classes"/>
<c:if test="${not empty pageScope.moreitems}">
<c:set var="classes" value="${pageScope.classes} tabs--limited"/>
</c:if>
<c:set var="attributes">
<c:forEach items="${dynattrs}" var="a">
<c:choose>
<c:when test="${a.key == 'class'}">
<c:set value="${classes} ${a.value}" var="classes"/>
</c:when>
<c:otherwise>${a.key}="${a.value}" </c:otherwise>
</c:choose>
</c:forEach>
</c:set>
<div class="${pageScope.classes}" data-key="${pageScope.key}" ${pageScope.attributes}>
<c:set var="__tabsStep" value="header" scope="request"/>
<div class="tabs__header js-tabs__header">
<ul role="tablist" class="tabs__list js-tabs__list" data-scope="${pageScope.key}">
<c:set var="__tabsStep" value="header" scope="request"/>
<c:set var="__size" value="${pageScope.size}" scope="request"/>
<jsp:doBody/>
<c:if test="${not empty pageScope.moreitems}">
<li class="tabs__item">
<kuik:dropdown priority="tertiary" data-dropdown="${pageScope.key}" title="${pageScope.moreitems}" class="tabs__button tabs__limit-dropdown js-tabs__button--focusable hidden" aria-hidden="true" popoverClasses="tabs__limit-popover" size="${pageScope.size}" tabindex="-1"/>
</li>
</c:if>
</ul>
<c:set var="__tabsStep" value="head-addon" scope="request"/>
<jsp:doBody/>
</div>
<c:set var="__tabsStep" value="body" scope="request"/>
<jsp:doBody/>
</div>
<c:set var="__tabsStep" scope="request" value="${__old_tabsStep}"/>
<c:set var="__size" scope="request" value="${__old_size}"/>
<resources:addScript path="/static/kuik/tabs/tabs.js" type="module"/>