@ktortu/aaa 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/button/button-tokens.css +152 -0
- package/button/button.css +319 -0
- package/card/card-tokens.css +49 -0
- package/card/card.css +200 -0
- package/cdk/styles/foundation.css +83 -0
- package/cdk/styles/tabs.css +276 -0
- package/dialog/dialog.css +350 -0
- package/fesm2022/ktortu-aaa-button.mjs +128 -0
- package/fesm2022/ktortu-aaa-button.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-card.mjs +209 -0
- package/fesm2022/ktortu-aaa-card.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-cdk.mjs +183 -0
- package/fesm2022/ktortu-aaa-cdk.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-dialog.mjs +512 -0
- package/fesm2022/ktortu-aaa-dialog.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-forms.mjs +3215 -0
- package/fesm2022/ktortu-aaa-forms.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-menu.mjs +315 -0
- package/fesm2022/ktortu-aaa-menu.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-tabs.mjs +79 -0
- package/fesm2022/ktortu-aaa-tabs.mjs.map +1 -0
- package/fesm2022/ktortu-aaa-tooltip.mjs +356 -0
- package/fesm2022/ktortu-aaa-tooltip.mjs.map +1 -0
- package/fesm2022/ktortu-aaa.mjs +17 -0
- package/fesm2022/ktortu-aaa.mjs.map +1 -0
- package/forms/checkbox/checkbox-group.css +55 -0
- package/forms/checkbox/checkbox.css +216 -0
- package/forms/chips/chip-list.css +70 -0
- package/forms/chips/chip.css +92 -0
- package/forms/chips/tokens.css +102 -0
- package/forms/field/field.css +87 -0
- package/forms/multi-select/multi-select.css +136 -0
- package/forms/radio/radio-group.css +55 -0
- package/forms/radio/radio.css +165 -0
- package/forms/styles/field-box.css +171 -0
- package/forms/styles/select-panel.css +464 -0
- package/forms/styles/tokens.css +67 -0
- package/forms/switch/switch.css +188 -0
- package/menu/menu-tokens.css +58 -0
- package/menu/menu.css +224 -0
- package/package.json +96 -0
- package/styles/button.css +6 -0
- package/styles/card.css +6 -0
- package/styles/dialog.css +6 -0
- package/styles/forms.css +13 -0
- package/styles/foundation.css +7 -0
- package/styles/menu.css +6 -0
- package/styles/styles.css +24 -0
- package/styles/tabs.css +5 -0
- package/styles/tooltip.css +5 -0
- package/themes/theme-ant.css +44 -0
- package/themes/theme-architecte.css +83 -0
- package/themes/theme-aurora.css +97 -0
- package/themes/theme-bootstrap.css +46 -0
- package/themes/theme-carbon.css +49 -0
- package/themes/theme-catppuccin.css +66 -0
- package/themes/theme-cyberpunk.css +211 -0
- package/themes/theme-fluent.css +45 -0
- package/themes/theme-material-you.css +74 -0
- package/themes/theme-material.css +48 -0
- package/themes/theme-primer.css +46 -0
- package/themes/theme-vegetal.css +78 -0
- package/tooltip/tooltip.css +129 -0
- package/types/ktortu-aaa-button.d.ts +70 -0
- package/types/ktortu-aaa-card.d.ts +143 -0
- package/types/ktortu-aaa-cdk.d.ts +110 -0
- package/types/ktortu-aaa-dialog.d.ts +286 -0
- package/types/ktortu-aaa-forms.d.ts +1574 -0
- package/types/ktortu-aaa-menu.d.ts +171 -0
- package/types/ktortu-aaa-tabs.d.ts +27 -0
- package/types/ktortu-aaa-tooltip.d.ts +90 -0
- package/types/ktortu-aaa.d.ts +8 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* Design tokens de la carte (directive ktCard) — contrat public de theming.
|
|
2
|
+
|
|
3
|
+
Même architecture que --btn-* / --field-* / --dialog-* : tout DÉRIVE du socle --kt-*.
|
|
4
|
+
Surcharger un --kt-* (ex. --kt-surface) rebrande la carte avec le reste de la lib ;
|
|
5
|
+
surcharger un --card-* ici (ou sur un conteneur de spécificité ≥) ne touche que la carte.
|
|
6
|
+
Bascules NON déclarées (cf. plus bas) => effets inertes par défaut.
|
|
7
|
+
|
|
8
|
+
Chargé APRÈS foundation.css (qui pose les --kt-*). */
|
|
9
|
+
@layer kt-aaa.tokens {
|
|
10
|
+
:root {
|
|
11
|
+
/* ============ Surface ============ */
|
|
12
|
+
--card-bg: var(--kt-surface, #ffffff);
|
|
13
|
+
--card-fg: var(--kt-on-surface, #1f1f1f);
|
|
14
|
+
|
|
15
|
+
/* Fond de la variante `filled` : surface légèrement teintée (DÉRIVÉ, suit chaque thème). */
|
|
16
|
+
--card-filled-bg: color-mix(in oklab, var(--kt-on-surface, #1f1f1f) 5%, var(--kt-surface, #fff));
|
|
17
|
+
|
|
18
|
+
/* ============ Bordure ============ */
|
|
19
|
+
--card-border-color: var(--kt-outline, #c4c7c5);
|
|
20
|
+
--card-border-width: 1px;
|
|
21
|
+
|
|
22
|
+
/* ============ Forme & rythme ============ */
|
|
23
|
+
--card-radius: var(--kt-control-radius, 8px);
|
|
24
|
+
--card-padding: 1rem;
|
|
25
|
+
--card-gap: 0.75rem;
|
|
26
|
+
|
|
27
|
+
/* ============ Élévation (variante `elevated`) ============ */
|
|
28
|
+
--card-shadow: 0 1px 3px rgb(0 0 0 / 12%), 0 1px 2px rgb(0 0 0 / 8%);
|
|
29
|
+
|
|
30
|
+
/* ============ Voile d'état (carte interactive) — opacités discrètes ============ */
|
|
31
|
+
--card-state-hover-opacity: 0.04;
|
|
32
|
+
--card-state-pressed-opacity: 0.08;
|
|
33
|
+
|
|
34
|
+
/* ============ Anneau de focus (relayé sur toute la carte) ============ */
|
|
35
|
+
--card-focus-ring-color: var(--kt-focus-ring-color, #0842a0);
|
|
36
|
+
--card-focus-ring-width: var(--kt-focus-ring-width, 2px);
|
|
37
|
+
--card-focus-ring-offset: 2px;
|
|
38
|
+
|
|
39
|
+
/* ============ Bascules NON déclarées exprès (défauts entre parenthèses) ============
|
|
40
|
+
Même principe que --btn-* : non définies => effet neutre ; un thème (ou un conteneur) les
|
|
41
|
+
pose pour activer l'effet, sans qu'aucune carte n'ait à les redéclarer.
|
|
42
|
+
--card-shadow-hover (= --card-shadow) : élévation au survol d'une carte interactive.
|
|
43
|
+
--card-transition (none) : anime ombre/translation (carte inerte sinon).
|
|
44
|
+
--card-transform (none) : transform au repos.
|
|
45
|
+
--card-transform-hover (= --card-transform) : transform au survol (ex. translateY(-2px)).
|
|
46
|
+
--card-fill-image (none) : dégradé/motif empilé SOUS le voile d'état.
|
|
47
|
+
--card-backdrop-filter (none) : flou de la surface (thèmes verre). */
|
|
48
|
+
}
|
|
49
|
+
}
|
package/card/card.css
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
@layer kt-aaa.components {
|
|
2
|
+
/* Styles de la directive Card ([ktCard] + marqueurs structurels).
|
|
3
|
+
Global : enregistré comme button.css / dialog.css (la directive se pose sur n'importe quel
|
|
4
|
+
élément, hors de toute vue de composant). Tokens (--card-*) dans card-tokens.css (chargé avant).
|
|
5
|
+
|
|
6
|
+
La règle de base ne consomme que 3 variables résolues (--card-surface / --card-line /
|
|
7
|
+
--card-elevation) ; chaque bloc [data-variant] les pose — même technique que ktButton. */
|
|
8
|
+
|
|
9
|
+
[ktCard] {
|
|
10
|
+
/* Colonne flex : titre / média / contenu / actions s'empilent ; une carte « texte nu » sans
|
|
11
|
+
marqueur fonctionne aussi (chaque enfant devient un item). gap = rythme vertical. */
|
|
12
|
+
display: flex;
|
|
13
|
+
flex-direction: column;
|
|
14
|
+
gap: var(--card-gap, 0.75rem);
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
/* Ancre le lien étiré ([ktCardLink]::after) et tout enfant positionné. */
|
|
17
|
+
position: relative;
|
|
18
|
+
|
|
19
|
+
padding: var(--card-padding, 1rem);
|
|
20
|
+
color: var(--card-fg);
|
|
21
|
+
|
|
22
|
+
/* Défauts résolus = elevated. Surchargés par les blocs [data-variant]. */
|
|
23
|
+
--card-surface: var(--card-bg);
|
|
24
|
+
--card-line: transparent;
|
|
25
|
+
--card-elevation: var(--card-shadow);
|
|
26
|
+
|
|
27
|
+
/* Voile d'état via pseudo-élément ::before pour préserver background-image. */
|
|
28
|
+
background-color: var(--card-surface);
|
|
29
|
+
background-image: var(--card-fill-image, none);
|
|
30
|
+
|
|
31
|
+
border: var(--card-border-width, 1px) solid var(--card-line);
|
|
32
|
+
border-radius: var(--card-radius, 8px);
|
|
33
|
+
box-shadow: var(--card-elevation);
|
|
34
|
+
backdrop-filter: var(--card-backdrop-filter, none);
|
|
35
|
+
|
|
36
|
+
/* Mouvement : bascules => inerte par défaut (cf. card-tokens.css). */
|
|
37
|
+
transition: var(--card-transition, none);
|
|
38
|
+
transform: var(--card-transform, none);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* ============ VARIANTES (apparence) ============ */
|
|
42
|
+
[ktCard][data-variant='elevated'] {
|
|
43
|
+
--card-surface: var(--card-bg);
|
|
44
|
+
--card-line: transparent;
|
|
45
|
+
--card-elevation: var(--card-shadow);
|
|
46
|
+
}
|
|
47
|
+
[ktCard][data-variant='outlined'] {
|
|
48
|
+
--card-surface: var(--card-bg);
|
|
49
|
+
--card-line: var(--card-border-color);
|
|
50
|
+
--card-elevation: none;
|
|
51
|
+
}
|
|
52
|
+
[ktCard][data-variant='filled'] {
|
|
53
|
+
--card-surface: var(--card-filled-bg);
|
|
54
|
+
--card-line: transparent;
|
|
55
|
+
--card-elevation: none;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* ============ MÉDIA PLEINE LARGEUR ([ktCardMedia]) ============
|
|
59
|
+
Full-bleed : on annule le padding de la carte. Quand un média est présent, la carte clippe au
|
|
60
|
+
rayon (`overflow: hidden`) pour arrondir les coins du média — uniquement dans ce cas, afin de
|
|
61
|
+
ne pas rogner inutilement ombres/contenus des cartes sans média. */
|
|
62
|
+
[ktCard]:has([ktCardMedia]) {
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
}
|
|
65
|
+
[ktCardMedia] {
|
|
66
|
+
display: block;
|
|
67
|
+
margin: calc(-1 * var(--card-padding)) calc(-1 * var(--card-padding)) 0;
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
}
|
|
70
|
+
/* Un média qui n'est pas le premier enfant (ex. après le contenu) ne déborde pas vers le haut. */
|
|
71
|
+
[ktCardMedia]:not(:first-child) {
|
|
72
|
+
margin-block-start: 0;
|
|
73
|
+
}
|
|
74
|
+
[ktCardMedia] :where(img, video, picture, svg, canvas) {
|
|
75
|
+
display: block;
|
|
76
|
+
inline-size: 100%;
|
|
77
|
+
block-size: auto;
|
|
78
|
+
object-fit: cover;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ============ EN-TÊTE ([ktCardHeader]) ============ */
|
|
82
|
+
[ktCardHeader] {
|
|
83
|
+
display: flex;
|
|
84
|
+
align-items: center;
|
|
85
|
+
gap: 0.75rem;
|
|
86
|
+
}
|
|
87
|
+
/* Le titre dans un header prend l'espace libre (repousse une éventuelle action à droite). */
|
|
88
|
+
[ktCardHeader] :where(h1, h2, h3, h4, h5, h6) {
|
|
89
|
+
flex: 1 1 auto;
|
|
90
|
+
min-inline-size: 0;
|
|
91
|
+
margin: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* ============ CONTENU ([ktCardContent]) ============ */
|
|
95
|
+
[ktCardContent] {
|
|
96
|
+
display: block;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/* ============ ACTIONS ([ktCardActions]) ============
|
|
100
|
+
Épinglées en pied : margin-block-start auto les pousse en bas quand la carte a une hauteur
|
|
101
|
+
imposée (grille de cartes à hauteur égale). */
|
|
102
|
+
[ktCardActions] {
|
|
103
|
+
display: flex;
|
|
104
|
+
flex-wrap: wrap;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 0.5rem;
|
|
107
|
+
margin-block-start: auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ============ INTERACTIF (affordance — PAS un rôle) ============ */
|
|
111
|
+
[ktCard][data-interactive] {
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Voile d'état indépendant via pseudo-élément pour préserver background-image */
|
|
116
|
+
[ktCard][data-interactive]::before {
|
|
117
|
+
content: '';
|
|
118
|
+
position: absolute;
|
|
119
|
+
inset: 0;
|
|
120
|
+
border-radius: inherit;
|
|
121
|
+
background-color: var(--card-overlay, transparent);
|
|
122
|
+
pointer-events: none;
|
|
123
|
+
transition: background-color var(--card-transition, 0.15s ease);
|
|
124
|
+
z-index: 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Voile + élévation au survol/maintien (hors disabled). */
|
|
128
|
+
[ktCard][data-interactive]:hover:not([data-disabled]) {
|
|
129
|
+
--card-overlay: color-mix(in oklab, var(--card-fg) calc(var(--card-state-hover-opacity, 0.04) * 100%), transparent);
|
|
130
|
+
box-shadow: var(--card-shadow-hover, var(--card-elevation));
|
|
131
|
+
transform: var(--card-transform-hover, var(--card-transform, none));
|
|
132
|
+
}
|
|
133
|
+
[ktCard][data-interactive]:active:not([data-disabled]) {
|
|
134
|
+
--card-overlay: color-mix(
|
|
135
|
+
in oklab,
|
|
136
|
+
var(--card-fg) calc(var(--card-state-pressed-opacity, 0.08) * 100%),
|
|
137
|
+
transparent
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* ============ LIEN ÉTIRÉ ([ktCardLink]) ============
|
|
142
|
+
Le pseudo-élément couvre toute la carte : la surface entière devient cliquable sans imbriquer
|
|
143
|
+
de contrôles. radius hérité pour une zone de clic épousant les coins. */
|
|
144
|
+
[ktCardLink]::after {
|
|
145
|
+
content: '';
|
|
146
|
+
position: absolute;
|
|
147
|
+
inset: 0;
|
|
148
|
+
border-radius: inherit;
|
|
149
|
+
}
|
|
150
|
+
/* Le lien étiré ne porte pas son propre anneau : il est relayé sur la carte (cf. plus bas). */
|
|
151
|
+
[ktCardLink]:focus-visible {
|
|
152
|
+
outline: none;
|
|
153
|
+
}
|
|
154
|
+
/* Actions/contrôles SECONDAIRES : repassent au-dessus du lien étiré pour rester cliquables.
|
|
155
|
+
Scopé aux cartes qui ONT un lien étiré (`:has`) — une carte sans lien ne touche pas au
|
|
156
|
+
positionnement de ses enfants. :where(...) = spécificité nulle (surcharge sans lutte). */
|
|
157
|
+
[ktCard]:has([ktCardLink]) :where(a, button, input, select, textarea, label, [tabindex]):not([ktCardLink]) {
|
|
158
|
+
position: relative;
|
|
159
|
+
z-index: 1;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/* Anneau de focus relayé sur TOUTE la carte quand le lien primaire est focalisé au clavier
|
|
163
|
+
(cas [ktCardLink] descendant OU [ktCard] lui-même posé sur un <a>/<button>). */
|
|
164
|
+
[ktCard]:has([ktCardLink]:focus-visible),
|
|
165
|
+
[ktCard][ktCardLink]:focus-visible {
|
|
166
|
+
outline: var(--card-focus-ring-width, 2px) solid var(--card-focus-ring-color, #0842a0);
|
|
167
|
+
outline-offset: var(--card-focus-ring-offset, 2px);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/* ============ ÉTAT INERTE ([data-disabled]) ============ */
|
|
171
|
+
[ktCard][data-disabled] {
|
|
172
|
+
cursor: not-allowed;
|
|
173
|
+
/* Désature la surface entière (contenu arbitraire : opacity plutôt qu'un color-mix par token). */
|
|
174
|
+
opacity: 0.55;
|
|
175
|
+
box-shadow: none;
|
|
176
|
+
transform: none;
|
|
177
|
+
}
|
|
178
|
+
/* Neutralise le lien étiré d'une carte désactivée (le consommateur désactive aussi le contrôle). */
|
|
179
|
+
[ktCard][data-disabled] [ktCardLink]::after {
|
|
180
|
+
pointer-events: none;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/* ============ Mouvement réduit (WCAG 2.3.3) ============ */
|
|
184
|
+
@media (prefers-reduced-motion: reduce) {
|
|
185
|
+
[ktCard][data-interactive] {
|
|
186
|
+
transition: none;
|
|
187
|
+
}
|
|
188
|
+
[ktCard][data-interactive]:hover:not([data-disabled]) {
|
|
189
|
+
transform: none;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* ============ Contraste élevé Windows / forced-colors ============ */
|
|
194
|
+
@media (forced-colors: active) {
|
|
195
|
+
[ktCard] {
|
|
196
|
+
/* L'ombre et le voile disparaissent : on garantit une frontière visible. */
|
|
197
|
+
border: 1px solid CanvasText;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
@layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
|
|
2
|
+
|
|
3
|
+
/* Socle de design partagé — contrat racine du design system.
|
|
4
|
+
Source unique dont DÉRIVENT les tokens des composants (--field-*, --btn-*).
|
|
5
|
+
Surchargez un seul --kt-* (ex. --kt-primary) pour rebrander champs ET boutons d'un coup.
|
|
6
|
+
|
|
7
|
+
Chargé en PREMIER dans angular.json > styles (avant tokens.css / button-tokens.css). */
|
|
8
|
+
@layer kt-aaa.base {
|
|
9
|
+
:root {
|
|
10
|
+
/* Palette des contrôles natifs (scrollbars, pickers date/time…). Seule déclaration non-token
|
|
11
|
+
du socle : un thème sombre DOIT redéclarer `color-scheme: dark` pour que le popup scrollable
|
|
12
|
+
du select et les pickers natifs suivent. */
|
|
13
|
+
color-scheme: light;
|
|
14
|
+
|
|
15
|
+
/* ============ Couleurs sémantiques (seeds) ============ */
|
|
16
|
+
--kt-primary: #0842a0; /* bleu profond (7.5:1 contre blanc) */
|
|
17
|
+
--kt-danger: #8c1d18; /* rouge carmin (7.1:1 contre blanc) */
|
|
18
|
+
--kt-neutral: #2e3133;
|
|
19
|
+
|
|
20
|
+
/* ============ Surfaces & texte ============ */
|
|
21
|
+
--kt-surface: #ffffff;
|
|
22
|
+
--kt-on-surface: #1f1f1f; /* texte principal */
|
|
23
|
+
--kt-muted: #474747; /* texte secondaire / hint / icônes (7.6:1 contre blanc) */
|
|
24
|
+
--kt-outline: #c4c7c5; /* bordure au repos */
|
|
25
|
+
--kt-outline-strong: #5f6368; /* bordure hover */
|
|
26
|
+
|
|
27
|
+
/* ============ Géométrie de contrôle partagée (md) ============ */
|
|
28
|
+
--kt-control-height: 44px; /* field-box ET bouton md — cible tactile AAA */
|
|
29
|
+
--kt-control-radius: 8px;
|
|
30
|
+
--kt-control-font: 1rem;
|
|
31
|
+
|
|
32
|
+
/* Police : token de BASCULE --kt-font (NON déclaré ici exprès, même principe que --btn-radius).
|
|
33
|
+
Non défini => la page garde la police héritée (défaut navigateur). Un thème le déclare pour
|
|
34
|
+
imposer sa stack (ex. Segoe UI pour Fluent). Consommé par l'app hôte (body), pas par la lib
|
|
35
|
+
(les contrôles sont en `font: inherit`). */
|
|
36
|
+
|
|
37
|
+
/* Sélection de texte : bascules --kt-selection-bg / --kt-selection-fg (NON déclarées).
|
|
38
|
+
Défauts = mots-clés système Highlight/HighlightText (sélection navigateur). Consommées
|
|
39
|
+
par l'app hôte (::selection dans styles.css). */
|
|
40
|
+
|
|
41
|
+
/* ============ Anneau de focus ============ */
|
|
42
|
+
/* Couleur + épaisseur partagées ; l'offset et l'opacité restent propres à chaque composant. */
|
|
43
|
+
--kt-focus-ring-color: var(--kt-primary);
|
|
44
|
+
--kt-focus-ring-width: 2px;
|
|
45
|
+
|
|
46
|
+
/* ============ Gouttières de défilement (scrollbars) ============ */
|
|
47
|
+
/* Propriétés standard `scrollbar-width` / `scrollbar-color` — toutes deux HÉRITÉES : posées
|
|
48
|
+
ici sur :root (= <html>), elles cascadent vers la page ET toute zone scrollable de la lib
|
|
49
|
+
(listbox du select, contenu du dialog) sans règle d'application dédiée par composant.
|
|
50
|
+
Thumb et track DÉRIVÉS du socle (mélange on-surface ↔ surface) : la gouttière suit donc
|
|
51
|
+
automatiquement chaque thème — clair comme sombre — tant qu'il ne redéclare rien.
|
|
52
|
+
Bascules --kt-scrollbar-color (« thumb track ») / --kt-scrollbar-width : un thème impose sa
|
|
53
|
+
gouttière signature en les surchargeant (ex. néon Synthwave, gris chunky Windows 98).
|
|
54
|
+
NB : propriétés standard (pas les pseudo ::-webkit-scrollbar) — elles respectent
|
|
55
|
+
color-scheme et s'alignent sur le socle CSS moderne déjà utilisé (anchor, popover, oklch). */
|
|
56
|
+
--kt-scrollbar-thumb: color-mix(in srgb, var(--kt-on-surface) 30%, var(--kt-surface));
|
|
57
|
+
--kt-scrollbar-track: color-mix(in srgb, var(--kt-on-surface) 7%, var(--kt-surface));
|
|
58
|
+
scrollbar-width: var(--kt-scrollbar-width, thin);
|
|
59
|
+
scrollbar-color: var(--kt-scrollbar-color, var(--kt-scrollbar-thumb) var(--kt-scrollbar-track));
|
|
60
|
+
|
|
61
|
+
/* ============ Bottom-sheet PARTAGÉ (Select + Dialog) ============ */
|
|
62
|
+
/* La feuille modale glissant du bas est un concept de premier rang, mutualisé entre le popup
|
|
63
|
+
mobile du Select et le mode `sheet` du Dialog. Un thème thématise « la feuille » une fois ;
|
|
64
|
+
les deux composants suivent. (Le Dialog dérive aussi --dialog-radius/--dialog-scrim d'ici.) */
|
|
65
|
+
--kt-sheet-radius: 16px; /* rayon des coins HAUTS de la feuille */
|
|
66
|
+
--kt-sheet-shadow: 0 -4px 16px rgb(0 0 0 / 12%);
|
|
67
|
+
--kt-sheet-scrim: rgb(0 0 0 / 40%); /* voile du fond */
|
|
68
|
+
--kt-sheet-anim-duration: 120ms; /* glissement + fondu du scrim (entrée) */
|
|
69
|
+
--kt-sheet-exit-duration: 90ms; /* glissement + fondu du scrim (sortie) */
|
|
70
|
+
--kt-sheet-grab-color: var(--kt-outline, #c4c7c5); /* poignée de préhension */
|
|
71
|
+
--kt-sheet-max-block-size: 85svh; /* svh : tient compte du clavier virtuel / barres mobiles */
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* Entrée PARTAGÉE de la feuille : glissement du bas. Pendant « animation » des tokens --kt-sheet-*
|
|
75
|
+
ci-dessus — une seule keyframe, jouée par le mode `sheet` du Dialog (conteneur CDK) ET le popup
|
|
76
|
+
compact du Select (Popover `:popover-open`). Une keyframe est globale et indépendante du moteur :
|
|
77
|
+
fiable dans les deux contextes, contrairement à @starting-style + translate. */
|
|
78
|
+
@keyframes kt-sheet-in {
|
|
79
|
+
from {
|
|
80
|
+
translate: 0 100%;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
Design tokens & Styles génériques pour les Tabs (@angular/aria)
|
|
3
|
+
========================================================================== */
|
|
4
|
+
@layer kt-aaa.tokens {
|
|
5
|
+
:root {
|
|
6
|
+
/* ============ TOKENS DES TABS ============ */
|
|
7
|
+
/* Dérivés du socle global foundation.css (--kt-*) */
|
|
8
|
+
|
|
9
|
+
--tab-height: var(--kt-control-height, 44px);
|
|
10
|
+
--tab-text-color: var(--kt-muted);
|
|
11
|
+
--tab-text-hover: var(--kt-on-surface);
|
|
12
|
+
|
|
13
|
+
/* L'état actif prend la couleur primaire du thème par défaut */
|
|
14
|
+
--tab-active-color: var(--kt-primary);
|
|
15
|
+
|
|
16
|
+
/* Indicateur visuel (ligne du bas) */
|
|
17
|
+
--tab-indicator-width: 3px;
|
|
18
|
+
--tab-indicator-color: var(--tab-active-color);
|
|
19
|
+
|
|
20
|
+
/* Focus Ring AAA */
|
|
21
|
+
--tab-focus-ring-color: var(--kt-focus-ring-color);
|
|
22
|
+
--tab-focus-ring-width: var(--kt-focus-ring-width, 2px);
|
|
23
|
+
|
|
24
|
+
/* Bordures */
|
|
25
|
+
--tab-list-border-width: 2px;
|
|
26
|
+
--tab-list-border-color: var(--kt-outline);
|
|
27
|
+
|
|
28
|
+
/* ============ DÉBORDEMENT / DÉFILEMENT ============ */
|
|
29
|
+
/* La liste scrolle sur son propre axe quand elle déborde (jamais la page → WCAG 1.4.10).
|
|
30
|
+
`visible` pour désactiver. */
|
|
31
|
+
--tab-list-overflow: auto;
|
|
32
|
+
/* Affordance : ombres de défilement (Lea Verou) visibles seulement s'il reste du contenu
|
|
33
|
+
hors-champ, estompées aux extrémités. `--tab-scroll-shadow-spread: 0` les coupe. */
|
|
34
|
+
--tab-list-bg: var(--kt-surface);
|
|
35
|
+
--tab-scroll-shadow-size: 2rem; /* largeur du "cache" couleur de surface */
|
|
36
|
+
--tab-scroll-shadow-spread: 0.85rem; /* taille de l'ombre */
|
|
37
|
+
/* Dérivée du socle : ombre sombre en thème clair, claire en thème sombre. */
|
|
38
|
+
--tab-scroll-shadow-color: color-mix(in srgb, var(--kt-on-surface, #1f1f1f) 18%, transparent);
|
|
39
|
+
/* Marge laissée autour de l'onglet amené dans la vue (focus clavier / directive ktTabScroll). */
|
|
40
|
+
--tab-scroll-margin: 1rem;
|
|
41
|
+
--tab-scroll-snap: none; /* `x proximity` pour activer l'accroche */
|
|
42
|
+
--tab-scrollbar-width: thin;
|
|
43
|
+
/* Géométrie et fond (pour les thèmes extrêmes comme Win98) */
|
|
44
|
+
--tab-bg: transparent;
|
|
45
|
+
--tab-bg-hover: color-mix(in srgb, var(--tab-text-hover) 5%, transparent);
|
|
46
|
+
--tab-bg-active: transparent;
|
|
47
|
+
--tab-shadow: none;
|
|
48
|
+
--tab-shadow-hover: none;
|
|
49
|
+
--tab-shadow-active: none;
|
|
50
|
+
--tab-border: none;
|
|
51
|
+
--tab-border-active: none;
|
|
52
|
+
--tab-margin: 0;
|
|
53
|
+
--tab-padding-y: 0;
|
|
54
|
+
--tab-z-index-active: 1;
|
|
55
|
+
--tab-transform: none;
|
|
56
|
+
--tab-transform-hover: none;
|
|
57
|
+
--tab-transform-active: none;
|
|
58
|
+
--tab-radius: 0;
|
|
59
|
+
--tab-backdrop-filter: none;
|
|
60
|
+
--tab-transition: background-color 0.2s, color 0.2s, transform 0.1s, box-shadow 0.1s, backdrop-filter 0.2s;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@layer kt-aaa.components {
|
|
65
|
+
/* ==========================================================================
|
|
66
|
+
STYLES GLOBAUX (Ciblage des attributs ARIA natifs)
|
|
67
|
+
========================================================================== */
|
|
68
|
+
|
|
69
|
+
/* 1. Le conteneur principal */
|
|
70
|
+
[ngTabs] {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* 2. La liste des onglets */
|
|
76
|
+
[ngTabList] {
|
|
77
|
+
display: flex;
|
|
78
|
+
list-style: none;
|
|
79
|
+
border-bottom: var(--tab-list-border-width) solid var(--tab-list-border-color);
|
|
80
|
+
padding: 0;
|
|
81
|
+
margin: 0;
|
|
82
|
+
align-items: flex-end; /* Pour permettre aux onglets de tailles différentes (ex: actif plus grand) de s'aligner en bas */
|
|
83
|
+
position: relative; /* Établit le contexte de positionnement pour offsetTop/offsetLeft des enfants */
|
|
84
|
+
|
|
85
|
+
/* Débordement : la LISTE scrolle sur l'axe en ligne — jamais la page (corrige WCAG 1.4.10).
|
|
86
|
+
Le focus clavier d'@angular/aria amène l'onglet actif dans la vue ; la directive ktTabScroll
|
|
87
|
+
couvre l'onglet actif hors-champ au montage / en sélection programmatique. */
|
|
88
|
+
overflow-x: var(--tab-list-overflow, auto);
|
|
89
|
+
overflow-y: hidden;
|
|
90
|
+
scroll-behavior: smooth;
|
|
91
|
+
scrollbar-width: var(--tab-scrollbar-width, thin);
|
|
92
|
+
scroll-snap-type: var(--tab-scroll-snap, none);
|
|
93
|
+
|
|
94
|
+
/* Ombres de défilement (Lea Verou) : couche "cache" (couleur surface, attachement local) +
|
|
95
|
+
couche "ombre" (attachement scroll). L'ombre n'apparaît que s'il reste du contenu de ce côté
|
|
96
|
+
et s'estompe à l'extrémité → pas de "ghosting" permanent du 1er/dernier onglet. */
|
|
97
|
+
background:
|
|
98
|
+
linear-gradient(to right, var(--tab-list-bg, var(--kt-surface, #fff)) 30%, transparent) 0 0 /
|
|
99
|
+
var(--tab-scroll-shadow-size, 2rem) 100% no-repeat local,
|
|
100
|
+
linear-gradient(to left, var(--tab-list-bg, var(--kt-surface, #fff)) 30%, transparent) 100% 0 /
|
|
101
|
+
var(--tab-scroll-shadow-size, 2rem) 100% no-repeat local,
|
|
102
|
+
radial-gradient(farthest-side at 0 50%, var(--tab-scroll-shadow-color, rgb(0 0 0 / 16%)), transparent) 0 0 /
|
|
103
|
+
var(--tab-scroll-shadow-spread, 0.85rem) 100% no-repeat scroll,
|
|
104
|
+
radial-gradient(farthest-side at 100% 50%, var(--tab-scroll-shadow-color, rgb(0 0 0 / 16%)), transparent) 100% 0 /
|
|
105
|
+
var(--tab-scroll-shadow-spread, 0.85rem) 100% no-repeat scroll;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* 3. Un onglet individuel */
|
|
109
|
+
[ngTab] {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 0.5rem;
|
|
113
|
+
flex: none; /* ne pas rétrécir : les onglets gardent leur taille et débordent → scroll */
|
|
114
|
+
scroll-snap-align: start;
|
|
115
|
+
scroll-margin-inline: var(--tab-scroll-margin, 1rem); /* gap quand on l'amène dans la vue */
|
|
116
|
+
padding: var(--tab-padding-y) 1.5rem;
|
|
117
|
+
margin: var(--tab-margin);
|
|
118
|
+
height: var(--tab-height);
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
color: var(--tab-text-color);
|
|
121
|
+
background-color: var(--tab-bg);
|
|
122
|
+
box-shadow: var(--tab-shadow);
|
|
123
|
+
border: var(--tab-border);
|
|
124
|
+
border-radius: var(--tab-radius);
|
|
125
|
+
transform: var(--tab-transform);
|
|
126
|
+
backdrop-filter: var(--tab-backdrop-filter);
|
|
127
|
+
-webkit-backdrop-filter: var(--tab-backdrop-filter);
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
position: relative;
|
|
130
|
+
transition: var(--tab-transition);
|
|
131
|
+
user-select: none;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
[ngTab]:hover:not([disabled]):not([aria-disabled='true']) {
|
|
135
|
+
color: var(--tab-text-hover);
|
|
136
|
+
background-color: var(--tab-bg-hover);
|
|
137
|
+
box-shadow: var(--tab-shadow-hover);
|
|
138
|
+
transform: var(--tab-transform-hover);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* 4. Onglet Actif / Sélectionné */
|
|
142
|
+
[ngTab][aria-selected='true'] {
|
|
143
|
+
color: var(--tab-active-color);
|
|
144
|
+
background-color: var(--tab-bg-active);
|
|
145
|
+
box-shadow: var(--tab-shadow-active);
|
|
146
|
+
border: var(--tab-border-active);
|
|
147
|
+
transform: var(--tab-transform-active);
|
|
148
|
+
z-index: var(--tab-z-index-active);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Indicateur du bas pour l'onglet actif */
|
|
152
|
+
[ngTab][aria-selected='true']::after {
|
|
153
|
+
content: '';
|
|
154
|
+
position: absolute;
|
|
155
|
+
bottom: calc(var(--tab-list-border-width) * -1); /* Superpose la bordure de la liste */
|
|
156
|
+
left: 0;
|
|
157
|
+
right: 0;
|
|
158
|
+
height: var(--tab-indicator-width);
|
|
159
|
+
background-color: var(--tab-indicator-color);
|
|
160
|
+
border-radius: var(--tab-indicator-width) var(--tab-indicator-width) 0 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* 5. Focus AAA (Immanquable au clavier) */
|
|
164
|
+
[ngTab]:focus-visible {
|
|
165
|
+
outline: var(--tab-focus-ring-width) solid var(--tab-focus-ring-color);
|
|
166
|
+
outline-offset: calc(var(--tab-focus-ring-width) * -1);
|
|
167
|
+
background-color: color-mix(in srgb, var(--tab-focus-ring-color) 10%, transparent);
|
|
168
|
+
border-radius: 4px 4px 0 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* 6. État désactivé */
|
|
172
|
+
[ngTab][disabled],
|
|
173
|
+
[ngTab][aria-disabled='true'] {
|
|
174
|
+
opacity: 0.4;
|
|
175
|
+
cursor: not-allowed;
|
|
176
|
+
color: var(--tab-text-color);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/* 7. Panneaux de contenu (Tab Panels) */
|
|
180
|
+
[ngTabPanel] {
|
|
181
|
+
padding: var(--tab-panel-padding, 1.5rem 0);
|
|
182
|
+
background-color: var(--tab-panel-bg, transparent);
|
|
183
|
+
backdrop-filter: var(--tab-panel-backdrop-filter, none);
|
|
184
|
+
-webkit-backdrop-filter: var(--tab-panel-backdrop-filter, none);
|
|
185
|
+
border: var(--tab-panel-border, none);
|
|
186
|
+
border-radius: var(--tab-panel-radius, 0);
|
|
187
|
+
box-shadow: var(--tab-panel-shadow, none);
|
|
188
|
+
animation: tabFadeIn 0.3s ease-in-out;
|
|
189
|
+
margin-top: var(--tab-panel-margin-top, 0);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
[ngTabPanel][inert] {
|
|
193
|
+
display: none;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
[ngTabPanel]:focus-visible {
|
|
197
|
+
outline: var(--tab-focus-ring-width) solid var(--tab-focus-ring-color);
|
|
198
|
+
outline-offset: -2px; /* Intérieur pour ne pas déborder de la carte en verre */
|
|
199
|
+
border-radius: var(--tab-panel-radius, 4px);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Animation fluide respectant les préférences système */
|
|
203
|
+
@keyframes tabFadeIn {
|
|
204
|
+
from {
|
|
205
|
+
opacity: 0;
|
|
206
|
+
transform: translateY(5px);
|
|
207
|
+
}
|
|
208
|
+
to {
|
|
209
|
+
opacity: 1;
|
|
210
|
+
transform: translateY(0);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@media (prefers-reduced-motion: reduce) {
|
|
215
|
+
[ngTabPanel] {
|
|
216
|
+
animation: none;
|
|
217
|
+
}
|
|
218
|
+
[ngTabList] {
|
|
219
|
+
scroll-behavior: auto; /* pas de défilement animé lors de l'amenée dans la vue */
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/* ==========================================================================
|
|
224
|
+
ORIENTATION VERTICALE
|
|
225
|
+
========================================================================== */
|
|
226
|
+
|
|
227
|
+
[ngTabs]:has([ngTabList][aria-orientation='vertical']),
|
|
228
|
+
[ngTabs]:has([ngTabList][orientation='vertical']) {
|
|
229
|
+
flex-direction: row;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
[ngTabList][aria-orientation='vertical'],
|
|
233
|
+
[ngTabList][orientation='vertical'] {
|
|
234
|
+
flex-direction: column;
|
|
235
|
+
border-bottom: none;
|
|
236
|
+
border-right: var(--tab-list-border-width) solid var(--tab-list-border-color);
|
|
237
|
+
align-items: stretch;
|
|
238
|
+
|
|
239
|
+
/* Débordement vertical (la hauteur est contrainte par le conteneur hôte) + ombres haut/bas. */
|
|
240
|
+
overflow-x: hidden;
|
|
241
|
+
overflow-y: var(--tab-list-overflow, auto);
|
|
242
|
+
background:
|
|
243
|
+
linear-gradient(to bottom, var(--tab-list-bg, var(--kt-surface, #fff)) 30%, transparent) 0 0 / 100%
|
|
244
|
+
var(--tab-scroll-shadow-size, 2rem) no-repeat local,
|
|
245
|
+
linear-gradient(to top, var(--tab-list-bg, var(--kt-surface, #fff)) 30%, transparent) 0 100% / 100%
|
|
246
|
+
var(--tab-scroll-shadow-size, 2rem) no-repeat local,
|
|
247
|
+
radial-gradient(farthest-side at 50% 0, var(--tab-scroll-shadow-color, rgb(0 0 0 / 16%)), transparent) 0 0 / 100%
|
|
248
|
+
var(--tab-scroll-shadow-spread, 0.85rem) no-repeat scroll,
|
|
249
|
+
radial-gradient(farthest-side at 50% 100%, var(--tab-scroll-shadow-color, rgb(0 0 0 / 16%)), transparent) 0 100% /
|
|
250
|
+
100% var(--tab-scroll-shadow-spread, 0.85rem) no-repeat scroll;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
[ngTabList][aria-orientation='vertical'] [ngTab],
|
|
254
|
+
[ngTabList][orientation='vertical'] [ngTab] {
|
|
255
|
+
justify-content: flex-start;
|
|
256
|
+
scroll-margin-block: var(--tab-scroll-margin, 1rem);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/* On déplace l'indicateur sur le bord droit pour l'orientation verticale */
|
|
260
|
+
[ngTabList][aria-orientation='vertical'] [ngTab][aria-selected='true']::after,
|
|
261
|
+
[ngTabList][orientation='vertical'] [ngTab][aria-selected='true']::after {
|
|
262
|
+
bottom: 0;
|
|
263
|
+
left: auto;
|
|
264
|
+
right: calc(var(--tab-list-border-width) * -1); /* Superpose la bordure droite */
|
|
265
|
+
width: var(--tab-indicator-width);
|
|
266
|
+
height: auto;
|
|
267
|
+
top: 0;
|
|
268
|
+
border-radius: var(--tab-indicator-width) 0 0 var(--tab-indicator-width);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
[ngTabs]:has([ngTabList][aria-orientation='vertical']) [ngTabPanel],
|
|
272
|
+
[ngTabs]:has([ngTabList][orientation='vertical']) [ngTabPanel] {
|
|
273
|
+
flex-grow: 1;
|
|
274
|
+
padding: 0 1.5rem;
|
|
275
|
+
}
|
|
276
|
+
}
|