@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,188 @@
|
|
|
1
|
+
@layer kt-aaa.components {
|
|
2
|
+
:host {
|
|
3
|
+
display: block;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.kt-switch-field {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: var(--field-gap, 0.375rem);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.kt-switch-row {
|
|
13
|
+
display: flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
gap: var(--field-control-gap, 0.5rem);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* Le bouton switch (la glissière / track) */
|
|
19
|
+
.kt-switch {
|
|
20
|
+
position: relative;
|
|
21
|
+
display: inline-flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
box-sizing: border-box;
|
|
24
|
+
inline-size: var(--switch-width, 2.75rem);
|
|
25
|
+
block-size: var(--switch-height, 1.5rem);
|
|
26
|
+
padding: var(--switch-padding, 2px);
|
|
27
|
+
border-radius: var(--switch-radius, 999px);
|
|
28
|
+
border: var(--switch-border-width, 2px) solid
|
|
29
|
+
var(--switch-border-color, var(--field-border-color, var(--kt-outline, #c4c7c5)));
|
|
30
|
+
background-color: var(--switch-bg, var(--field-bg, var(--kt-surface, #ffffff)));
|
|
31
|
+
cursor: pointer;
|
|
32
|
+
outline: none;
|
|
33
|
+
/* Élévation/glow de repos : retombe sur le token de thème des champs (--field-shadow). */
|
|
34
|
+
box-shadow: var(--switch-shadow, var(--field-shadow, none));
|
|
35
|
+
/* Token de BASCULE (non déclaré => valeur ci-dessous). Comme --btn-transition : ne pas y
|
|
36
|
+
mettre box-shadow si un thème anime déjà l'ombre en continu. */
|
|
37
|
+
transition: var(
|
|
38
|
+
--switch-transition,
|
|
39
|
+
background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
40
|
+
border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
|
|
41
|
+
box-shadow 0.2s ease
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Zone de clic étendue à 44px de haut (AAA 2.5.5) via un pseudo-élément transparent */
|
|
46
|
+
.kt-switch::after {
|
|
47
|
+
content: '';
|
|
48
|
+
position: absolute;
|
|
49
|
+
top: 50%;
|
|
50
|
+
left: 50%;
|
|
51
|
+
transform: translate(-50%, -50%);
|
|
52
|
+
width: 100%;
|
|
53
|
+
height: 44px;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* La pastille mobile (le curseur / thumb) */
|
|
57
|
+
.kt-switch__thumb {
|
|
58
|
+
display: block;
|
|
59
|
+
inline-size: var(--switch-thumb-size, 1rem);
|
|
60
|
+
block-size: var(--switch-thumb-size, 1rem);
|
|
61
|
+
border-radius: var(--switch-thumb-radius, 50%);
|
|
62
|
+
background-color: var(--switch-thumb-bg, var(--kt-outline-strong, #5f6368));
|
|
63
|
+
box-shadow: var(--switch-thumb-shadow, 0 1px 3px rgba(0, 0, 0, 0.2));
|
|
64
|
+
/* Token de BASCULE (non déclaré => glissement actuel, rebond léger). */
|
|
65
|
+
transition: var(
|
|
66
|
+
--switch-thumb-transition,
|
|
67
|
+
transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1),
|
|
68
|
+
background-color 0.2s ease
|
|
69
|
+
);
|
|
70
|
+
transform: translateX(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* État ACTIVÉ (aria-checked="true") + HALO coloré dérivé du primaire du thème (glow visible,
|
|
74
|
+
en fondu via la transition). Surchargeable/désactivable par --switch-shadow-active. */
|
|
75
|
+
.kt-switch[aria-checked='true'] {
|
|
76
|
+
background-color: var(--switch-bg-active, var(--kt-primary, #0b57d0));
|
|
77
|
+
border-color: var(--switch-border-color-active, var(--kt-primary, #0b57d0));
|
|
78
|
+
box-shadow: var(
|
|
79
|
+
--switch-shadow-active,
|
|
80
|
+
0 0 8px color-mix(in srgb, var(--switch-bg-active, var(--kt-primary, #0b57d0)) 50%, transparent)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.kt-switch[aria-checked='true'] .kt-switch__thumb {
|
|
85
|
+
background-color: var(--switch-thumb-bg-active, #ffffff);
|
|
86
|
+
/* Décale le curseur vers la droite : width de la track - size du thumb - padding*2 - border*2 */
|
|
87
|
+
transform: translateX(
|
|
88
|
+
calc(
|
|
89
|
+
var(--switch-width, 2.75rem) - var(--switch-thumb-size, 1rem) - (var(--switch-padding, 2px) * 2) -
|
|
90
|
+
(var(--switch-border-width, 2px) * 2)
|
|
91
|
+
)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* État HOVER */
|
|
96
|
+
.kt-switch:hover:not(:disabled) {
|
|
97
|
+
border-color: var(--switch-border-color-hover, var(--field-border-color-hover, var(--kt-outline-strong, #5f6368)));
|
|
98
|
+
box-shadow: var(--switch-shadow-hover, var(--field-shadow-hover, var(--switch-shadow, var(--field-shadow, none))));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.kt-switch[aria-checked='true']:hover:not(:disabled) {
|
|
102
|
+
background-color: var(--switch-bg-active-hover, color-mix(in srgb, var(--kt-primary, #0b57d0) 90%, black));
|
|
103
|
+
border-color: var(--switch-border-color-active-hover, color-mix(in srgb, var(--kt-primary, #0b57d0) 90%, black));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* État FOCUS : anneau via `outline` (comme les champs/checkbox/radio → pas de conflit avec le
|
|
107
|
+
glow box-shadow du thème), + glow de focus du thème (--field-shadow-focus). */
|
|
108
|
+
.kt-switch:focus-visible {
|
|
109
|
+
outline: var(--kt-focus-ring-width, 2px) solid var(--kt-focus-ring-color, #0b57d0);
|
|
110
|
+
outline-offset: var(--switch-focus-ring-offset, 2px);
|
|
111
|
+
box-shadow: var(--switch-shadow-focus, var(--field-shadow-focus, var(--switch-shadow, var(--field-shadow, none))));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* État DÉSACTIVÉ (disabled) */
|
|
115
|
+
.kt-switch:disabled {
|
|
116
|
+
cursor: not-allowed;
|
|
117
|
+
opacity: 0.5;
|
|
118
|
+
background-color: var(--field-disabled-bg, #f1f3f4);
|
|
119
|
+
border-color: var(--field-border-color, #c4c7c5);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.kt-switch:disabled .kt-switch__thumb {
|
|
123
|
+
background-color: var(--kt-outline, #c4c7c5);
|
|
124
|
+
box-shadow: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* État INVALID */
|
|
128
|
+
.kt-switch-field--invalid .kt-switch {
|
|
129
|
+
border-color: var(--field-error-color, var(--kt-danger, #b3261e));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.kt-switch-field--invalid .kt-switch[aria-checked='true'] {
|
|
133
|
+
background-color: var(--field-error-color, var(--kt-danger, #b3261e));
|
|
134
|
+
border-color: var(--field-error-color, var(--kt-danger, #b3261e));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* Typo Label */
|
|
138
|
+
.kt-switch-label {
|
|
139
|
+
font-size: var(--field-label-font-size, 0.875rem);
|
|
140
|
+
font-weight: var(--field-label-weight, 500);
|
|
141
|
+
color: var(--field-label-color, inherit);
|
|
142
|
+
cursor: pointer;
|
|
143
|
+
user-select: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.kt-switch-field--disabled .kt-switch-label {
|
|
147
|
+
cursor: not-allowed;
|
|
148
|
+
color: var(--field-hint-color, #5f6368);
|
|
149
|
+
opacity: 0.6;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.kt-switch-label__required {
|
|
153
|
+
margin-inline-start: 0.125rem;
|
|
154
|
+
color: var(--field-required-color, #8c1d18);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Hint & Error */
|
|
158
|
+
.kt-switch-hint {
|
|
159
|
+
margin: 0;
|
|
160
|
+
font-size: var(--field-hint-font-size, 0.8125rem);
|
|
161
|
+
color: var(--field-hint-color, #474747);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.kt-switch-error {
|
|
165
|
+
margin: 0;
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
font-size: var(--field-hint-font-size, 0.8125rem);
|
|
169
|
+
color: var(--field-error-color, #8c1d18);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Apparition d'un message d'erreur : même token que les champs (cf. field.css).
|
|
173
|
+
Sur le MESSAGE (inséré à l'invalidation), pas sur le conteneur. */
|
|
174
|
+
.kt-switch-error-message {
|
|
175
|
+
animation: var(--field-error-animation, none);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@media (prefers-reduced-motion: reduce) {
|
|
179
|
+
.kt-switch,
|
|
180
|
+
.kt-switch__thumb {
|
|
181
|
+
transition: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.kt-switch-error-message {
|
|
185
|
+
animation: none;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/* Design tokens du menu (directives ktMenu / ktMenuItem) — contrat public de theming.
|
|
2
|
+
|
|
3
|
+
Même architecture que --card-* / --field-* / --dialog-* : tout DÉRIVE du socle --kt-*.
|
|
4
|
+
Surcharger un --kt-* (ex. --kt-surface) rebrande le menu avec le reste de la lib ; surcharger
|
|
5
|
+
un --kt-menu-* (ici ou sur un conteneur de spécificité ≥) ne touche que le menu. Conséquence
|
|
6
|
+
recherchée : le menu est correct sur les 14 thèmes SANS une seule ligne dédiée par thème (comme
|
|
7
|
+
la scrollbar et la bottom-sheet), un thème ne pose un --kt-menu-* que pour une signature.
|
|
8
|
+
|
|
9
|
+
Chargé APRÈS foundation.css (qui pose les --kt-*). */
|
|
10
|
+
@layer kt-aaa.tokens {
|
|
11
|
+
:root {
|
|
12
|
+
/* ============ Surface ============ */
|
|
13
|
+
--kt-menu-bg: var(--kt-surface, #ffffff);
|
|
14
|
+
--kt-menu-fg: var(--kt-on-surface, #1f1f1f);
|
|
15
|
+
--kt-menu-border-color: var(--kt-outline, #c4c7c5);
|
|
16
|
+
--kt-menu-border-width: 1px;
|
|
17
|
+
--kt-menu-radius: var(--kt-control-radius, 8px);
|
|
18
|
+
--kt-menu-shadow: 0 4px 12px rgb(0 0 0 / 12%);
|
|
19
|
+
--kt-menu-padding: 0.25rem;
|
|
20
|
+
--kt-menu-min-width: 12rem;
|
|
21
|
+
|
|
22
|
+
/* Décalage du dropdown sous le trigger. */
|
|
23
|
+
--kt-menu-offset: 0.25rem;
|
|
24
|
+
/* Écart visuel entre un menu et son sous-menu (comblé par un pont de survol invisible : le gap
|
|
25
|
+
reste élégant SANS fermer le sous-menu quand le pointeur le traverse en diagonale). */
|
|
26
|
+
--kt-menu-submenu-gap: 0.375rem;
|
|
27
|
+
/* Plan d'empilement : au-dessus du contenu de page (le menu est position:fixed, hors flux). */
|
|
28
|
+
--kt-menu-z: 1000;
|
|
29
|
+
|
|
30
|
+
/* ============ Items ============ */
|
|
31
|
+
--kt-menu-item-min-height: 44px; /* cible tactile AAA (WCAG 2.5.5), y compris desktop */
|
|
32
|
+
--kt-menu-item-padding: 0.5rem 0.75rem;
|
|
33
|
+
--kt-menu-item-gap: 0.625rem;
|
|
34
|
+
--kt-menu-item-radius: calc(var(--kt-menu-radius, 8px) - var(--kt-menu-padding, 0.25rem));
|
|
35
|
+
|
|
36
|
+
/* Survol / item actif (focus roving) : voile dérivé, suit chaque thème. */
|
|
37
|
+
--kt-menu-item-hover-bg: color-mix(in srgb, currentColor 8%, transparent);
|
|
38
|
+
/* Item coché (checkbox/radio) : teinte primaire discrète. */
|
|
39
|
+
--kt-menu-item-checked-color: var(--kt-primary, #0842a0);
|
|
40
|
+
/* Item destructeur (opt-in via [data-tone="danger"]). */
|
|
41
|
+
--kt-menu-item-danger-color: var(--kt-danger, #8c1d18);
|
|
42
|
+
|
|
43
|
+
/* ============ Anneau de focus ============ */
|
|
44
|
+
--kt-menu-focus-ring-color: var(--kt-focus-ring-color, #0842a0);
|
|
45
|
+
--kt-menu-focus-ring-width: var(--kt-focus-ring-width, 2px);
|
|
46
|
+
|
|
47
|
+
/* ============ Séparateur ============ */
|
|
48
|
+
--kt-menu-separator-color: var(--kt-outline, #c4c7c5);
|
|
49
|
+
|
|
50
|
+
/* ============ Bascules NON déclarées exprès (défauts entre parenthèses) ============
|
|
51
|
+
Même principe que --btn-* / --card-* : non définies => effet neutre ; un thème (ou un
|
|
52
|
+
conteneur) les pose pour activer l'effet, sans qu'aucun menu n'ait à les redéclarer.
|
|
53
|
+
--kt-menu-enter-animation (none) : animation d'entrée du dropdown (transform/opacity
|
|
54
|
+
uniquement — le menu est ancré, ne pas animer la position).
|
|
55
|
+
--kt-menu-backdrop-filter (none) : flou de la surface (thèmes verre).
|
|
56
|
+
--kt-menu-item-transition (none) : transition du voile de survol. */
|
|
57
|
+
}
|
|
58
|
+
}
|
package/menu/menu.css
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
@layer kt-aaa.components {
|
|
2
|
+
/* Styles des directives Menu ([ktMenu] / [ktMenuItem] / [ktMenuSeparator]).
|
|
3
|
+
Global : enregistré comme button.css / card.css / dialog.css (les directives se posent sur les
|
|
4
|
+
hôtes d'@angular/aria, hors de toute vue de composant). Tokens (--kt-menu-*) dans
|
|
5
|
+
menu-tokens.css (chargé avant).
|
|
6
|
+
|
|
7
|
+
Le COMPORTEMENT (ouverture, clavier, focus roving, retour focus, sous-menus, typeahead) vient
|
|
8
|
+
d'aria. Ce fichier ne fait que l'APPARENCE + le POSITIONNEMENT (CSS Anchor Positioning), piloté
|
|
9
|
+
par `data-visible` (posé par aria) et les anchor-name/position-anchor (posés par [ktMenuTrigger]
|
|
10
|
+
et [ktMenuItem]). Aucune dépendance à une police d'icônes : chevron et coche sont dessinés en CSS. */
|
|
11
|
+
|
|
12
|
+
/* ===== Surface du menu ===== */
|
|
13
|
+
[ktMenu] {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
display: flex;
|
|
16
|
+
flex-direction: column;
|
|
17
|
+
margin: 0;
|
|
18
|
+
padding: var(--kt-menu-padding, 0.25rem);
|
|
19
|
+
min-inline-size: var(--kt-menu-min-width, 12rem);
|
|
20
|
+
max-inline-size: min(90vw, 22rem);
|
|
21
|
+
list-style: none;
|
|
22
|
+
background: var(--kt-menu-bg, #fff);
|
|
23
|
+
color: var(--kt-menu-fg, #1f1f1f);
|
|
24
|
+
border: var(--kt-menu-border-width, 1px) solid var(--kt-menu-border-color, #c4c7c5);
|
|
25
|
+
border-radius: var(--kt-menu-radius, 8px);
|
|
26
|
+
box-shadow: var(--kt-menu-shadow, 0 4px 12px rgb(0 0 0 / 12%));
|
|
27
|
+
/* Translucidité (verre) : bascule => aucun effet par défaut. */
|
|
28
|
+
backdrop-filter: var(--kt-menu-backdrop-filter, none);
|
|
29
|
+
z-index: var(--kt-menu-z, 1000);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Masqué tant que NON explicitement ouvert : aria pose data-visible="true" à l'ouverture, "false"
|
|
33
|
+
sinon. On masque sur `:not([data-visible='true'])` (et non sur ='false') pour rester caché AUSSI
|
|
34
|
+
avant l'initialisation d'aria (SSR / 1er paint, où l'attribut est encore absent) — pas de flash.
|
|
35
|
+
`display:none` retire l'élément du flux ET du parcours de tabulation (items non focusables). */
|
|
36
|
+
[ktMenu]:not([data-visible='true']) {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* ===== Positionnement : dropdown ancré (CSS Anchor Positioning), comme le popup du Select ===== */
|
|
41
|
+
@supports (anchor-name: --x) {
|
|
42
|
+
[ktMenu] {
|
|
43
|
+
position: fixed;
|
|
44
|
+
inset: auto;
|
|
45
|
+
inset-block-start: anchor(block-end);
|
|
46
|
+
inset-inline-start: anchor(inline-start);
|
|
47
|
+
margin-block-start: var(--kt-menu-offset, 0.25rem);
|
|
48
|
+
/* Bascule de côté / au-dessus si le bas/droite manque de place. */
|
|
49
|
+
position-try-fallbacks:
|
|
50
|
+
flip-block,
|
|
51
|
+
flip-inline,
|
|
52
|
+
flip-block flip-inline;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Sous-menu : s'ouvre en latéral (inline-end), ancré au bord droit de son item parent, avec un
|
|
56
|
+
petit espace visuel (--kt-menu-submenu-gap). */
|
|
57
|
+
[ktMenu][data-kt-submenu] {
|
|
58
|
+
/* Décalage TOTAL depuis le bord de l'item (= l'ancre) jusqu'au bord de la surface du sous-menu :
|
|
59
|
+
padding + bordure du menu PARENT (que l'item ne traverse pas) + l'espace visuel voulu. Ainsi
|
|
60
|
+
l'écart SURFACE-à-SURFACE vaut exactement --kt-menu-submenu-gap, quel que soit le thème. */
|
|
61
|
+
--_submenu-offset: calc(
|
|
62
|
+
var(--kt-menu-padding, 0.25rem) + var(--kt-menu-border-width, 1px) + var(--kt-menu-submenu-gap, 0.375rem)
|
|
63
|
+
);
|
|
64
|
+
inset-block-start: anchor(block-start);
|
|
65
|
+
inset-inline-start: anchor(inline-end);
|
|
66
|
+
margin-block-start: 0;
|
|
67
|
+
margin-inline-start: var(--_submenu-offset);
|
|
68
|
+
position-try-fallbacks:
|
|
69
|
+
flip-inline,
|
|
70
|
+
flip-block,
|
|
71
|
+
flip-block flip-inline;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* Ponts de survol invisibles comblant l'espace menu↔sous-menu : le gap reste élégant, mais le
|
|
76
|
+
pointeur ne quitte jamais une zone survolable en le traversant (sinon aria annule l'ouverture
|
|
77
|
+
sur trajectoire diagonale). Chaque pont couvre TOUTE la distance bord-d'item → bord-de-sous-menu
|
|
78
|
+
(--_submenu-offset), sur toute la hauteur (inset-block:0). Hors de la boîte des items → aucun
|
|
79
|
+
clic bloqué. Deux côtés : ::before pour l'ouverture normale, ::after pour le cas rabattu (flip). */
|
|
80
|
+
[ktMenu][data-kt-submenu]::before,
|
|
81
|
+
[ktMenu][data-kt-submenu]::after {
|
|
82
|
+
content: '';
|
|
83
|
+
position: absolute;
|
|
84
|
+
inset-block: 0;
|
|
85
|
+
inline-size: var(--_submenu-offset);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
[ktMenu][data-kt-submenu]::before {
|
|
89
|
+
inset-inline-start: calc(-1 * var(--_submenu-offset));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
[ktMenu][data-kt-submenu]::after {
|
|
93
|
+
inset-inline-end: calc(-1 * var(--_submenu-offset));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* Repli sans anchor positioning (ex. iOS < 26) : le menu reste rendu près de son trigger dans le
|
|
97
|
+
flux (position statique). Moins joli mais sûr — jamais coincé en 0,0. */
|
|
98
|
+
@supports not (anchor-name: --x) {
|
|
99
|
+
[ktMenu] {
|
|
100
|
+
position: absolute;
|
|
101
|
+
margin-block-start: var(--kt-menu-offset, 0.25rem);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Déclencheur OUVERT = ancre du dropdown : s'il se transforme (animation de thème au hover/active
|
|
106
|
+
du bouton — cf. --btn-transform-hover/-active), il déplace le menu ancré. On fige donc sa
|
|
107
|
+
transformation TANT QUE le menu est ouvert (aria pose aria-expanded sur le trigger) ; l'animation
|
|
108
|
+
reste normale menu fermé. `!important` car les règles :hover/:active de ktButton sont plus
|
|
109
|
+
spécifiques. Le retour visuel de couleur/fond au survol n'est pas concerné (seul le déplacement
|
|
110
|
+
l'est) ; la transition est neutralisée pour éviter une ré-animation au moment du figeage. */
|
|
111
|
+
[ktMenuTrigger][aria-expanded='true'] {
|
|
112
|
+
transform: none !important;
|
|
113
|
+
translate: none !important;
|
|
114
|
+
scale: none !important;
|
|
115
|
+
/* On neutralise SEULEMENT les transitions de déplacement (pour éviter une ré-animation du
|
|
116
|
+
figeage) ; le fondu de couleur/fond/bord au survol du déclencheur reste préservé. */
|
|
117
|
+
transition-property: background-color, color, border-color, box-shadow, outline-color !important;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Entrée du dropdown : token de BASCULE (non déclaré => aucune animation). Joué uniquement à
|
|
121
|
+
l'ouverture réelle (data-visible="true"). */
|
|
122
|
+
[ktMenu][data-visible='true'] {
|
|
123
|
+
animation: var(--kt-menu-enter-animation, none);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@media (prefers-reduced-motion: reduce) {
|
|
127
|
+
[ktMenu] {
|
|
128
|
+
animation: none;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/* ===== Items ===== */
|
|
133
|
+
[ktMenuItem] {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: var(--kt-menu-item-gap, 0.625rem);
|
|
137
|
+
box-sizing: border-box;
|
|
138
|
+
min-block-size: var(--kt-menu-item-min-height, 44px);
|
|
139
|
+
padding: var(--kt-menu-item-padding, 0.5rem 0.75rem);
|
|
140
|
+
border: 0;
|
|
141
|
+
border-radius: var(--kt-menu-item-radius, 6px);
|
|
142
|
+
background: transparent;
|
|
143
|
+
color: inherit;
|
|
144
|
+
font: inherit;
|
|
145
|
+
text-align: start;
|
|
146
|
+
text-decoration: none;
|
|
147
|
+
inline-size: 100%;
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
user-select: none;
|
|
150
|
+
transition: var(--kt-menu-item-transition, none);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Item actif : aria utilise le focus roving (focus DOM réel) → on style `:focus-visible`. Le survol
|
|
154
|
+
souris partage le même voile. (`:focus` simple couvre aussi le focus programmatique d'aria.) */
|
|
155
|
+
[ktMenuItem]:focus:not([aria-disabled='true']),
|
|
156
|
+
[ktMenuItem]:hover:not([aria-disabled='true']) {
|
|
157
|
+
background: var(--kt-menu-item-hover-bg, color-mix(in srgb, currentColor 8%, transparent));
|
|
158
|
+
outline: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
[ktMenuItem]:focus-visible {
|
|
162
|
+
outline: var(--kt-menu-focus-ring-width, 2px) solid var(--kt-menu-focus-ring-color, #0842a0);
|
|
163
|
+
outline-offset: -2px;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
[ktMenuItem][aria-disabled='true'] {
|
|
167
|
+
opacity: 0.5;
|
|
168
|
+
cursor: not-allowed;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* Tonalité destructrice (opt-in : [data-tone="danger"] posé par le consommateur). */
|
|
172
|
+
[ktMenuItem][data-tone='danger'] {
|
|
173
|
+
color: var(--kt-menu-item-danger-color, #8c1d18);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* ===== Indicateur de sous-menu : chevron dessiné en CSS (aria pose aria-haspopup) ===== */
|
|
177
|
+
[ktMenuItem][aria-haspopup='true']::after {
|
|
178
|
+
content: '';
|
|
179
|
+
flex: none;
|
|
180
|
+
margin-inline-start: auto;
|
|
181
|
+
inline-size: 0.4em;
|
|
182
|
+
block-size: 0.4em;
|
|
183
|
+
border-top: 2px solid currentColor;
|
|
184
|
+
border-right: 2px solid currentColor;
|
|
185
|
+
transform: rotate(45deg); /* pointe vers l'inline-end (chevron ›) */
|
|
186
|
+
opacity: 0.7;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* Support RTL pour le chevron du sous-menu (doit pointer vers la gauche) */
|
|
190
|
+
[dir='rtl'] [ktMenuItem][aria-haspopup='true']::after,
|
|
191
|
+
:host-context([dir='rtl']) [ktMenuItem][aria-haspopup='true']::after {
|
|
192
|
+
transform: rotate(-135deg); /* pointe vers l'inline-start (chevron ‹) */
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* ===== Indicateur coché : checkbox / radio (aria-checked, posé par [ktMenuItemCheckbox/Radio]) =====
|
|
196
|
+
La coche occupe un emplacement de tête réservé pour aligner les libellés ; dessinée via un mask
|
|
197
|
+
SVG (suit currentColor, pas de police d'icônes). */
|
|
198
|
+
[ktMenuItem][aria-checked]::before {
|
|
199
|
+
content: '';
|
|
200
|
+
flex: none;
|
|
201
|
+
inline-size: 1.15em;
|
|
202
|
+
block-size: 1.15em;
|
|
203
|
+
background-color: currentColor;
|
|
204
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 16.17 4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z'/%3E%3C/svg%3E")
|
|
205
|
+
center / contain no-repeat;
|
|
206
|
+
visibility: hidden; /* réserve la place quand non coché → libellés alignés */
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
[ktMenuItem][aria-checked='true'] {
|
|
210
|
+
color: var(--kt-menu-item-checked-color, #0842a0);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
[ktMenuItem][aria-checked='true']::before {
|
|
214
|
+
visibility: visible;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ===== Séparateur ===== */
|
|
218
|
+
[ktMenuSeparator] {
|
|
219
|
+
block-size: 0;
|
|
220
|
+
margin-block: var(--kt-menu-padding, 0.25rem);
|
|
221
|
+
border: 0;
|
|
222
|
+
border-block-start: 1px solid var(--kt-menu-separator-color, #c4c7c5);
|
|
223
|
+
}
|
|
224
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktortu/aaa",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Bibliothèque de composants Angular headless + thémés par tokens CSS.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"angular",
|
|
7
|
+
"headless",
|
|
8
|
+
"components",
|
|
9
|
+
"css-tokens",
|
|
10
|
+
"design-system",
|
|
11
|
+
"accessibility"
|
|
12
|
+
],
|
|
13
|
+
"author": "ktortu",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/ktortu/aaa.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/ktortu/aaa/issues"
|
|
21
|
+
},
|
|
22
|
+
"homepage": "https://github.com/ktortu/aaa#readme",
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@angular/aria": "^22.0.0",
|
|
28
|
+
"@angular/cdk": "^22.0.0",
|
|
29
|
+
"@angular/common": "^22.0.0",
|
|
30
|
+
"@angular/core": "^22.0.0",
|
|
31
|
+
"@angular/forms": "^22.0.0",
|
|
32
|
+
"temporal-polyfill": "^0.3.2"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"tslib": "^2.3.0"
|
|
36
|
+
},
|
|
37
|
+
"sideEffects": [
|
|
38
|
+
"**/*.css"
|
|
39
|
+
],
|
|
40
|
+
"exports": {
|
|
41
|
+
"./styles.css": {
|
|
42
|
+
"default": "./styles/styles.css"
|
|
43
|
+
},
|
|
44
|
+
"./themes/*.css": {
|
|
45
|
+
"default": "./themes/*.css"
|
|
46
|
+
},
|
|
47
|
+
"./cdk/styles/*.css": {
|
|
48
|
+
"default": "./cdk/styles/*.css"
|
|
49
|
+
},
|
|
50
|
+
"./*.css": {
|
|
51
|
+
"default": "./styles/*.css"
|
|
52
|
+
},
|
|
53
|
+
"./package.json": {
|
|
54
|
+
"default": "./package.json"
|
|
55
|
+
},
|
|
56
|
+
".": {
|
|
57
|
+
"types": "./types/ktortu-aaa.d.ts",
|
|
58
|
+
"default": "./fesm2022/ktortu-aaa.mjs"
|
|
59
|
+
},
|
|
60
|
+
"./button": {
|
|
61
|
+
"types": "./types/ktortu-aaa-button.d.ts",
|
|
62
|
+
"default": "./fesm2022/ktortu-aaa-button.mjs"
|
|
63
|
+
},
|
|
64
|
+
"./card": {
|
|
65
|
+
"types": "./types/ktortu-aaa-card.d.ts",
|
|
66
|
+
"default": "./fesm2022/ktortu-aaa-card.mjs"
|
|
67
|
+
},
|
|
68
|
+
"./cdk": {
|
|
69
|
+
"types": "./types/ktortu-aaa-cdk.d.ts",
|
|
70
|
+
"default": "./fesm2022/ktortu-aaa-cdk.mjs"
|
|
71
|
+
},
|
|
72
|
+
"./dialog": {
|
|
73
|
+
"types": "./types/ktortu-aaa-dialog.d.ts",
|
|
74
|
+
"default": "./fesm2022/ktortu-aaa-dialog.mjs"
|
|
75
|
+
},
|
|
76
|
+
"./forms": {
|
|
77
|
+
"types": "./types/ktortu-aaa-forms.d.ts",
|
|
78
|
+
"default": "./fesm2022/ktortu-aaa-forms.mjs"
|
|
79
|
+
},
|
|
80
|
+
"./menu": {
|
|
81
|
+
"types": "./types/ktortu-aaa-menu.d.ts",
|
|
82
|
+
"default": "./fesm2022/ktortu-aaa-menu.mjs"
|
|
83
|
+
},
|
|
84
|
+
"./tabs": {
|
|
85
|
+
"types": "./types/ktortu-aaa-tabs.d.ts",
|
|
86
|
+
"default": "./fesm2022/ktortu-aaa-tabs.mjs"
|
|
87
|
+
},
|
|
88
|
+
"./tooltip": {
|
|
89
|
+
"types": "./types/ktortu-aaa-tooltip.d.ts",
|
|
90
|
+
"default": "./fesm2022/ktortu-aaa-tooltip.mjs"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"module": "fesm2022/ktortu-aaa.mjs",
|
|
94
|
+
"typings": "types/ktortu-aaa.d.ts",
|
|
95
|
+
"type": "module"
|
|
96
|
+
}
|
package/styles/card.css
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
@layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
|
|
2
|
+
|
|
3
|
+
/* Dialog (moteur @angular/cdk/dialog + directives kt). Nécessite le socle, ET l'overlay CDK :
|
|
4
|
+
pensez à importer aussi '@angular/cdk/overlay-prebuilt.css'.
|
|
5
|
+
@import '@ktortu/aaa/dialog.css'; */
|
|
6
|
+
@import '../dialog/dialog.css';
|
package/styles/forms.css
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
@layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
|
|
2
|
+
|
|
3
|
+
/* Formulaires : styles de base partagés des champs, chips et switch (tokens + apparence).
|
|
4
|
+
Nécessite le socle (@ktortu/aaa/foundation.css).
|
|
5
|
+
@import '@ktortu/aaa/forms.css'; */
|
|
6
|
+
@import '../forms/styles/tokens.css';
|
|
7
|
+
@import '../forms/chips/tokens.css';
|
|
8
|
+
@import '../forms/styles/field-box.css';
|
|
9
|
+
@import '../forms/switch/switch.css';
|
|
10
|
+
@import '../forms/checkbox/checkbox.css';
|
|
11
|
+
@import '../forms/checkbox/checkbox-group.css';
|
|
12
|
+
@import '../forms/radio/radio.css';
|
|
13
|
+
@import '../forms/radio/radio-group.css';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
|
|
2
|
+
|
|
3
|
+
/* Socle de design @ktortu/aaa — tokens `--kt-*` dont DÉRIVENT tous les composants.
|
|
4
|
+
REQUIS, à importer EN PREMIER, avant tout CSS de composant :
|
|
5
|
+
@import '@ktortu/aaa/foundation.css';
|
|
6
|
+
Surcharger un seul `--kt-*` (ex. --kt-primary) rebrande toute la lib. */
|
|
7
|
+
@import '../cdk/styles/foundation.css';
|
package/styles/menu.css
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
@layer kt-aaa.tokens, kt-aaa.base, kt-aaa.components;
|
|
2
|
+
|
|
3
|
+
/* Feuille de styles AGRÉGÉE de @ktortu/aaa — socle + styles de base de tous les composants.
|
|
4
|
+
Import unique pour le consommateur : @import '@ktortu/aaa/styles.css';
|
|
5
|
+
(ou, côté Angular, ajout dans la section "styles" de angular.json).
|
|
6
|
+
|
|
7
|
+
Préférez cet import si vous utilisez plusieurs composants. Pour n'embarquer QUE certains
|
|
8
|
+
composants, importez plutôt le socle + les bundles voulus (cf. README) :
|
|
9
|
+
@import '@ktortu/aaa/foundation.css'; // requis
|
|
10
|
+
@import '@ktortu/aaa/menu.css'; // + uniquement ce qui vous intéresse
|
|
11
|
+
|
|
12
|
+
ORDRE DE CASCADE garanti ici : socle d'abord, puis chaque bundle composant (qui inclut ses
|
|
13
|
+
propres tokens). Les THÈMES (theme-*.css) ne sont PAS inclus : optionnels, à importer APRÈS
|
|
14
|
+
(ex. @import '@ktortu/aaa/themes/theme-material.css';). Le Dialog s'appuie sur l'overlay CDK :
|
|
15
|
+
importez aussi '@angular/cdk/overlay-prebuilt.css'. */
|
|
16
|
+
|
|
17
|
+
@import './foundation.css';
|
|
18
|
+
@import './tabs.css';
|
|
19
|
+
@import './forms.css';
|
|
20
|
+
@import './button.css';
|
|
21
|
+
@import './card.css';
|
|
22
|
+
@import './tooltip.css';
|
|
23
|
+
@import './dialog.css';
|
|
24
|
+
@import './menu.css';
|
package/styles/tabs.css
ADDED