Files
decision/web/src/lib/components/DocPanel.svelte
T
YvvandClaude Opus 5.5 7a022dd35e v3 : interface Svelte — accueil, groupes, lecteur du texte en vigueur, panneau contextuel, éditeur à assistant, trustinWallet
- Accueil sobre, menu complet à gauche (Décisions, Docs de référence, Mandats,
  Protocoles), intro demandée, quatre sections, connexion Ğ1.
- Lecteur : texte en vigueur en Literata, actions contextuelles par article
  (le vote, propositions, proposer), propositions en cours sous la « surface »
  avec leur niveau d'eau, aperçus « si les bien cotées passent » / « si tout
  passe », langue du texte fr/en/es (traductions éditoriales).
- Panneau : vote pour/contre, seuil et formule (MathML), inertie et son
  changement (voté en inertie +), coquille sans vote, traduction, signalement
  de contradiction, historique des corrections.
- trustinWallet (/acces) : groupes et membres, invitations à usage unique,
  administrateurs, retours d'usage.
- Contrôles : vitest (markdown, catalogues, anti-lexique), Playwright C1–C10 + M1.
- Sections du seed portées jusqu'à l'écran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 02:58:34 +02:00

210 lines
5.9 KiB
Svelte

<script lang="ts">
import type { DocState, PanelState } from '$lib/doc.svelte';
import { t } from '$lib/i18n/index.svelte';
import { relationOf, titleOf } from '$lib/labels';
import type { Lang } from '$lib/types';
import Icon from './Icon.svelte';
import ProposeView from './ProposeView.svelte';
import VoteView from './VoteView.svelte';
import WaterLevel from './WaterLevel.svelte';
let {
doc,
panel,
lang,
onchange,
onclose,
}: { doc: DocState; panel: PanelState; lang: Lang; onchange: (p: PanelState) => void; onclose: () => void } = $props();
const unit = $derived(doc.unit(panel.unitId));
const versions = $derived(
unit ? [unit.inForce, ...unit.proposals.filter((p) => p.status === 'open')].filter((p) => p !== null) : [],
);
const voteId = $derived(panel.proposalId ?? unit?.inForce?.id ?? unit?.proposals.find((p) => p.status === 'open')?.id ?? null);
const tabs = $derived(
(
[
['vote', t('panel.vote'), !!voteId],
['proposals', `${t('panel.proposals')}${versions.length > 1 ? ` · ${versions.length}` : ''}`, !!unit],
['propose', t('panel.propose'), true],
] as const
).filter(([, , show]) => show),
);
let body: HTMLElement | undefined = $state();
$effect(() => {
void panel.tab;
void panel.proposalId;
body?.scrollTo({ top: 0 });
});
</script>
<aside class="panel card" aria-label={t('panel.vote')}>
<div class="bar">
<div class="tabs" role="tablist">
{#each tabs as [key, label] (key)}
<button role="tab" type="button" aria-selected={panel.tab === key} class:on={panel.tab === key} onclick={() => onchange({ ...panel, tab: key, prefill: null })}>{label}</button>
{/each}
</div>
<button class="close" type="button" onclick={onclose} aria-label={t('panel.close')}><Icon name="close" /></button>
</div>
<div class="body" bind:this={body}>
{#if panel.tab === 'vote' && voteId}
<VoteView
{doc}
proposalId={voteId}
{lang}
onopen={(id) => onchange({ ...panel, proposalId: id, tab: 'vote' })}
onpropose={(prefill) => onchange({ ...panel, tab: 'propose', prefill, draftId: null })}
/>
{:else if panel.tab === 'proposals' && unit}
<ul class="versions">
{#each versions as v (v.id)}
<li>
<button type="button" class="version" class:current={v.id === voteId} onclick={() => onchange({ ...panel, proposalId: v.id, tab: 'vote' })}>
<WaterLevel tally={v.tally} evaluation={v.evaluation} blocked={v.blocked} />
<span class="text">
<span class="eyebrow">{v.status === 'in_force' ? t('panel.inforce') : t('panel.proposal')}</span>
<span class="title">{titleOf(v, unit)}</span>
<span class="faint small">{v.status === 'in_force' ? (v.seedMinimal ? t('doc.seed') : '') : relationOf(v, unit)} · {t('tally.short', { for: v.tally.for, against: v.tally.against })}</span>
</span>
</button>
</li>
{/each}
</ul>
<button class="btn small" type="button" onclick={() => onchange({ ...panel, tab: 'propose', prefill: null, draftId: null })}><Icon name="plus" size={16} />{t('doc.action.propose')}</button>
{:else if panel.tab === 'propose'}
<ProposeView
{doc}
unitId={panel.unitId}
draftId={panel.draftId ?? null}
prefill={panel.prefill ?? null}
ondone={(r) => onchange(r.proposalId ? { unitId: doc.proposal(r.proposalId)?.unitId ?? panel.unitId, proposalId: r.proposalId, tab: 'vote' } : { ...panel, tab: 'proposals' })}
/>
{/if}
</div>
</aside>
<style>
.panel {
position: fixed;
top: 0.8rem;
right: 0.8rem;
bottom: 0.8rem;
width: min(var(--panel), calc(100vw - 1.6rem));
display: flex;
flex-direction: column;
z-index: 35;
box-shadow: var(--shadow-strong);
animation: enter 0.24s var(--ease);
}
.bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.5rem;
padding: 0.7rem 0.7rem 0.5rem 0.9rem;
}
.tabs {
display: flex;
gap: 0.2rem;
background: var(--surface-2);
padding: 3px;
border-radius: var(--r-pill);
overflow-x: auto;
}
.tabs button {
border: 0;
background: transparent;
border-radius: var(--r-pill);
padding: 0.35rem 0.8rem;
min-height: 2.1rem;
font-weight: 750;
font-size: 0.86rem;
color: var(--ink-soft);
white-space: nowrap;
}
.tabs button.on {
background: var(--surface);
color: var(--ink);
box-shadow: var(--shadow);
}
.close {
display: grid;
place-items: center;
flex: none;
width: 2.25rem;
height: 2.25rem;
border: 0;
border-radius: var(--r-icon);
background: transparent;
color: var(--ink-soft);
}
.close:hover {
background: var(--water-wash);
}
.body {
flex: 1;
overflow-y: auto;
padding: 0.6rem 1.2rem 1.6rem;
overscroll-behavior: contain;
}
.versions {
list-style: none;
margin: 0 0 1rem;
padding: 0;
display: grid;
gap: 0.4rem;
}
.version {
display: flex;
gap: 0.8rem;
align-items: center;
width: 100%;
padding: 0.7rem 0.8rem;
border: 0;
border-radius: var(--r-input);
background: transparent;
text-align: left;
}
.version:hover,
.version.current {
background: var(--water-wash);
}
.text {
display: grid;
gap: 0.1rem;
}
.title {
font-weight: 750;
}
.small {
font-size: 0.82rem;
}
@keyframes enter {
from {
transform: translateX(16px);
opacity: 0;
}
}
@media (max-width: 899px) {
.panel {
top: auto;
left: 0;
right: 0;
bottom: 0;
width: 100%;
max-height: 86dvh;
border-radius: var(--r-card) var(--r-card) 0 0;
animation-name: sheet;
}
@keyframes sheet {
from {
transform: translateY(24px);
opacity: 0;
}
}
}
</style>