Revoir l'ergonomie des panneaux et de la colonne des projets
Les panneaux d'édition étaient modaux et la colonne de gauche tronquait tout ce qu'elle portait. Sept corrections, mesurées sur data/exemple.json. Panneaux - Le voile qui recouvrait la page disparaît : la frise reste manipulable pendant l'édition, et cliquer une autre barre y bascule la sélection. Il rendait aussi le raccourci flèches inatteignable, le focus partant dans le champ « Nom » sans qu'on puisse cliquer ailleurs pour en sortir. - Un clic dans une zone inerte referme le panneau : ce que rendait le voile, sans le calque qui figeait la frise. - Les paramètres d'un projet quittent le <dialog> modal pour le même panneau qu'une phase, avec la même édition au fil de la saisie. « Nouveau projet » crée donc le projet aussitôt, comme le fait déjà l'ajout d'une phase : plus aucune création ne passe par un formulaire. Colonne des projets - Le nom ne disposait que de 56 px sur 250, et « client » s'affichait « c. ». Les commandes passent en surimpression au survol, les tags cèdent en bloc derrière un compteur, et le nom cède en dernier. - La colonne se redimensionne, largeur conservée dans localStorage. - L'œil disparaît : il faisait doublon avec l'entrée « Masquer » du menu. Un « + » le remplace, l'ajout d'une phase étant l'action la plus fréquente, et le nom du projet devient cliquable — il portait déjà un curseur « main » sans aucune action derrière. - Plus de commande « Ajouter un jalon » : un jalon est une phase dont on coche la case, pas un autre objet. Frise - Le nom d'une phase reste accroché au bord visible quand sa barre s'enfonce sous la colonne. - Cliquer un nom de phase hors écran ramène la frise sur elle. - La barre cumulative d'un projet s'affiche aussi quand il est déplié, amincie pour ne pas se faire passer pour une barre de phase. Arbitrages consignés dans docs/decisions.md, sections 8 et 17 à 21. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
218
css/style.css
218
css/style.css
@@ -25,7 +25,9 @@
|
||||
--hauteur-barre: 26px;
|
||||
--hauteur-ligne: 38px;
|
||||
--hauteur-entete: 66px; /* trois bandes de 22 px : année, mois, semaine */
|
||||
--largeur-libelles: 250px;
|
||||
/* Doit rester en accord avec `LARGEUR_LIBELLES.defaut` (app.js), sur lequel
|
||||
retombe le double-clic de la poignée. */
|
||||
--largeur-libelles: 300px;
|
||||
|
||||
--rayon: 6px;
|
||||
--ombre: 0 1px 2px rgb(15 23 42 / 0.08);
|
||||
@@ -268,6 +270,37 @@ button[aria-pressed="true"] {
|
||||
border-bottom: 1px solid var(--bordure);
|
||||
}
|
||||
|
||||
/* Zone de saisie large de 9 px pour un trait qui n'en fait qu'un : viser une
|
||||
bordure d'un pixel à la souris est un exercice de patience. */
|
||||
.poignee-colonne {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: -4px;
|
||||
width: 9px;
|
||||
cursor: col-resize;
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
.poignee-colonne::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0 4px;
|
||||
background: var(--accent);
|
||||
opacity: 0;
|
||||
transition: opacity 0.12s;
|
||||
}
|
||||
|
||||
.poignee-colonne:hover::after,
|
||||
.poignee-colonne:focus-visible::after,
|
||||
.poignee-colonne[data-glisse="true"]::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.poignee-colonne:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.grille__entete {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
@@ -369,6 +402,9 @@ button[aria-pressed="true"] {
|
||||
/* --- libellés ------------------------------------------------------------ */
|
||||
|
||||
.libelle-projet {
|
||||
--fond-libelle: color-mix(in srgb, var(--couleur-projet) 8%, var(--surface));
|
||||
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@@ -376,25 +412,72 @@ button[aria-pressed="true"] {
|
||||
padding: 0 8px 0 6px;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px solid var(--bordure);
|
||||
background: color-mix(in srgb, var(--couleur-projet) 8%, var(--surface));
|
||||
background: var(--fond-libelle);
|
||||
}
|
||||
|
||||
/* Commandes secondaires, en surimpression sur le bord droit de la ligne. Le
|
||||
dégradé leur donne un fond opaque sans trancher : le nom qu'elles recouvrent
|
||||
s'estompe au lieu d'être coupé net. */
|
||||
.libelle-projet__commandes {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
padding: 0 6px 0 18px;
|
||||
opacity: 0;
|
||||
background: linear-gradient(to right, transparent, var(--fond-libelle) 18px);
|
||||
transition: opacity 0.12s;
|
||||
}
|
||||
|
||||
.libelle-projet:hover .libelle-projet__commandes,
|
||||
.libelle-projet:focus-within .libelle-projet__commandes {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Un projet masqué garde ses commandes visibles : le menu est le seul chemin
|
||||
pour le rétablir, il ne doit pas falloir le deviner sous le curseur. */
|
||||
.libelle-projet--masque .libelle-projet__commandes {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Le nom est ce qui identifie le projet : il cède en dernier, et jamais
|
||||
au-delà d'une largeur où il reste reconnaissable. Sous l'ancienne règle
|
||||
(`min-width: 4em`), les tags et l'étiquette de dérive le réduisaient à
|
||||
« Refon… » dans une colonne pourtant deux fois trop large pour lui. */
|
||||
.libelle-projet__nom {
|
||||
flex: 1;
|
||||
/* Le nom cède la place aux pastilles, mais jamais entièrement : sous quatre
|
||||
caractères il ne resterait qu'un « … » qui n'identifie plus rien. */
|
||||
min-width: 4em;
|
||||
flex: 1 1 auto;
|
||||
min-width: 7em;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Les pastilles rognent le nom, pas l'inverse — mais elles s'effacent à leur
|
||||
tour plutôt que de repousser l'œil et le menu hors de la colonne. */
|
||||
.libelle-projet .tags {
|
||||
flex: 0 1 auto;
|
||||
max-width: 45%;
|
||||
max-width: 40%;
|
||||
}
|
||||
|
||||
/* Une pastille écrasée à « c. » occupe la place d'un tag sans en donner
|
||||
l'information. Elle garde donc une largeur minimale et cède en bloc :
|
||||
`pastillesTags` n'en affiche qu'un nombre fixe, suivies d'un compteur. */
|
||||
.libelle-projet .tag {
|
||||
flex: 0 0 auto;
|
||||
min-width: 3.4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tag--reste {
|
||||
flex: 0 0 auto;
|
||||
align-self: center;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
color: var(--texte-doux);
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.libelle-projet__derive {
|
||||
@@ -429,19 +512,6 @@ button[aria-pressed="true"] {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.icone-oeil {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 2px 4px;
|
||||
cursor: pointer;
|
||||
opacity: 0.45;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.icone-oeil:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* `display: block` et non `flex` : `text-overflow: ellipsis` ne s'applique pas
|
||||
au texte d'un conteneur flex, et le nom serait coupé net. Le centrage
|
||||
vertical passe donc par `line-height`. */
|
||||
@@ -487,16 +557,36 @@ button[aria-pressed="true"] {
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: var(--hauteur-barre);
|
||||
padding: 0 7px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
cursor: grab;
|
||||
box-shadow: var(--ombre);
|
||||
user-select: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/* Le nom se décale pour rester au bord de la vue quand la barre s'enfonce sous
|
||||
la colonne des libellés — le cas d'une phase longue dont on ne voit que la
|
||||
fin. Trois conditions le rendent possible, et aucune n'est décorative :
|
||||
- `sticky` s'ancre au conteneur défilant le plus proche. Un `overflow:
|
||||
hidden` sur `.barre` en ferait ce conteneur, et le décalage n'aurait plus
|
||||
lieu : le rognage du texte trop long est donc porté ici, pas sur la barre.
|
||||
- le décalage est plafonné par la boîte du parent, donc le nom ne sort jamais
|
||||
de sa barre — d'où `inline-block`, qui laisse au texte la marge de manœuvre
|
||||
qu'un bloc pleine largeur n'aurait pas.
|
||||
- `left` vaut la largeur de la colonne des libellés : c'est là que commence
|
||||
la portion réellement visible de la frise. */
|
||||
.barre__nom {
|
||||
position: sticky;
|
||||
left: var(--largeur-libelles);
|
||||
display: inline-block;
|
||||
max-width: 100%;
|
||||
padding: 0 7px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
vertical-align: top;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.barre:hover {
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
@@ -604,11 +694,11 @@ button[aria-pressed="true"] {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
/* --- projet plié --------------------------------------------------------- */
|
||||
/* --- barre cumulative d'un projet ---------------------------------------- */
|
||||
|
||||
/* Barre unique segmentée par phase. Lecture seule : voir docs/decisions.md,
|
||||
section 8. */
|
||||
.barre-pliee {
|
||||
/* Barre unique segmentée par phase, affichée que le projet soit plié ou
|
||||
déplié. Lecture seule : voir docs/decisions.md, section 8. */
|
||||
.barre-cumulative {
|
||||
position: absolute;
|
||||
top: calc((var(--hauteur-ligne) - var(--hauteur-barre)) / 2);
|
||||
height: var(--hauteur-barre);
|
||||
@@ -645,6 +735,17 @@ button[aria-pressed="true"] {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* Dépliée, la barre cumulative coiffe les phases du projet : elle s'amincit
|
||||
pour ne pas se faire passer pour l'une d'elles — elle ne se glisse pas. */
|
||||
.barre-cumulative--fine {
|
||||
--hauteur-cumulative: 9px;
|
||||
|
||||
top: calc((var(--hauteur-ligne) - var(--hauteur-cumulative)) / 2);
|
||||
height: var(--hauteur-cumulative);
|
||||
border-radius: 3px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* --- barre fantôme de référence ------------------------------------------ */
|
||||
|
||||
.fantome {
|
||||
@@ -665,8 +766,18 @@ button[aria-pressed="true"] {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Le fantôme suit la barre qu'il double : sous une barre cumulative amincie,
|
||||
il flotterait sinon dans le vide. Déclaré après `.fantome`, dont il partage
|
||||
la spécificité — l'ordre est ce qui le fait gagner. */
|
||||
.fantome--fin {
|
||||
top: calc((var(--hauteur-ligne) + 9px) / 2);
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
/* --- panneau de détail --------------------------------------------------- */
|
||||
|
||||
/* Non modal : aucun voile derrière lui, la frise reste manipulable pendant
|
||||
l'édition et cliquer une autre barre y bascule la sélection. */
|
||||
.panneau {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@@ -682,6 +793,13 @@ button[aria-pressed="true"] {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Le panneau reçoit le focus à l'ouverture pour que le clavier le suive, mais
|
||||
il n'est pas un contrôle : un anneau de focus autour de tout le panneau
|
||||
n'apprendrait rien et attirerait l'œil loin du champ à remplir. */
|
||||
.panneau:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.panneau__entete {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -771,13 +889,6 @@ button[aria-pressed="true"] {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.voile {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgb(15 23 42 / 0.15);
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
/* --- menu contextuel ------------------------------------------------------ */
|
||||
|
||||
.menu {
|
||||
@@ -830,7 +941,8 @@ button[aria-pressed="true"] {
|
||||
border-top: 1px solid var(--bordure);
|
||||
}
|
||||
|
||||
.menu-projet {
|
||||
/* Commandes de la ligne d'un projet : ajouter une phase, ouvrir le menu. */
|
||||
.commande-projet {
|
||||
border: 0;
|
||||
background: none;
|
||||
padding: 2px 5px;
|
||||
@@ -841,13 +953,16 @@ button[aria-pressed="true"] {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.menu-projet:hover {
|
||||
.commande-projet:hover {
|
||||
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
||||
color: var(--texte);
|
||||
}
|
||||
|
||||
/* --- dialogues ------------------------------------------------------------ */
|
||||
/* --- dialogue de confirmation --------------------------------------------- */
|
||||
|
||||
/* Seul `<dialog>` restant : les paramètres d'un projet ont rejoint un panneau
|
||||
(voir `.panneau`). Modal ici à dessein — interrompre est le but quand une
|
||||
action est irréversible. */
|
||||
.dialogue {
|
||||
padding: 0;
|
||||
border: 1px solid var(--bordure-forte);
|
||||
@@ -880,24 +995,6 @@ button[aria-pressed="true"] {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dialogue label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--texte-doux);
|
||||
}
|
||||
|
||||
.dialogue input[type="text"] {
|
||||
font: inherit;
|
||||
padding: 7px 9px;
|
||||
border: 1px solid var(--bordure-forte);
|
||||
border-radius: var(--rayon);
|
||||
background: var(--fond);
|
||||
color: var(--texte);
|
||||
}
|
||||
|
||||
.couleurs {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -931,11 +1028,6 @@ button[aria-pressed="true"] {
|
||||
box-shadow: inset 0 0 0 2px var(--surface);
|
||||
}
|
||||
|
||||
.dialogue__erreur {
|
||||
color: var(--danger) !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.dialogue__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
Reference in New Issue
Block a user