forked from yvv/decision
196 lines
5.9 KiB
Svelte
196 lines
5.9 KiB
Svelte
<script lang="ts">
|
|
// An article as the text reads it: its title, its body, its state; two gestures —
|
|
// « Le vote » unfolds under it (its other versions inside), « Proposer une modification »
|
|
// opens the panel, where one writes (lot U, UX-lecteur.md). A seed proposal (ADR 0011),
|
|
// read at the head while nothing is in force, is marked so: never the bare body of a text
|
|
// in force.
|
|
import type { Snippet } from 'svelte';
|
|
import type { Shown } from '$lib/doc.svelte';
|
|
import { i18n, t } from '$lib/i18n/index.svelte';
|
|
import { needOf } from '$lib/labels';
|
|
import type { Proposal } from '$lib/types';
|
|
import Crans from './Crans.svelte';
|
|
import Prose from './Prose.svelte';
|
|
import WaterLevel from './WaterLevel.svelte';
|
|
|
|
let {
|
|
text,
|
|
mark = 'unchanged',
|
|
seedMinimal = false,
|
|
seed = null,
|
|
enteredAt = null,
|
|
rivals = 0,
|
|
selected = false,
|
|
actions = true,
|
|
voteFor = null,
|
|
voteOpen = false,
|
|
votes = 0,
|
|
proposeLabel,
|
|
fold,
|
|
onaction,
|
|
}: {
|
|
text: Shown;
|
|
mark?: 'unchanged' | 'added' | 'replaced' | 'removed';
|
|
seedMinimal?: boolean;
|
|
/** A seed proposal at the head (ADR 0011): how far it is from passing, not in force. */
|
|
seed?: Proposal | null;
|
|
enteredAt?: string | null;
|
|
/** Its other versions open to the vote: said in its state, read in « Le vote ». */
|
|
rivals?: number;
|
|
selected?: boolean;
|
|
actions?: boolean;
|
|
/** The version « Le vote » unfolds, and whether it is unfolded. */
|
|
voteFor?: string | null;
|
|
voteOpen?: boolean;
|
|
/** The votes on the version in force, shown next to the seed mark. */
|
|
votes?: number;
|
|
proposeLabel?: string;
|
|
/** The vote, unfolded under the text. */
|
|
fold?: Snippet;
|
|
onaction: (action: 'vote' | 'propose') => void;
|
|
} = $props();
|
|
</script>
|
|
|
|
<article
|
|
class="unit {mark}"
|
|
class:selected
|
|
class:seed={!!seed}
|
|
class:unfolded={voteOpen}
|
|
>
|
|
<header>
|
|
{#if seed}<WaterLevel tally={seed.tally} evaluation={seed.evaluation} blocked={seed.blocked} />{/if}
|
|
<h2 class="eyebrow">{text.title}</h2>
|
|
{#if seed}<span class="tag water" title={t('doc.seedProposal.long')}>{t('doc.seedProposal')}</span>{/if}
|
|
{#if mark !== 'unchanged'}<span class="tag {mark === 'removed' ? 'ochre' : 'moss'}">{t(`doc.mark.${mark}`)}</span>{/if}
|
|
{#if text.stale}<span class="tag ochre">{t('doc.lang.stale')}</span>{:else if text.missing}<span class="tag">{t('doc.lang.missing')}</span>{/if}
|
|
</header>
|
|
<Prose text={text.body} />
|
|
{#if actions}
|
|
<footer class="actions">
|
|
<!-- Its vote unfolded says the totals, the « oui » to pass, the inertia and the other
|
|
versions: the line does not say them again (lot U2). -->
|
|
{#if !(seed && voteOpen)}
|
|
<span class="status faint">
|
|
{#if seed}
|
|
{@const need = needOf(seed)}
|
|
{t('tally.short', { for: seed.tally.for, against: seed.tally.against })}{#if need}{' · '}<span title={need.title}>{need.short}</span>{/if}
|
|
<span class="crans"><Crans level={seed.votedAtInertia} short /></span>
|
|
{:else if seedMinimal}<span title={t('doc.seed.long')}>{i18n.plural('doc.seed.votes', votes)}</span>
|
|
{:else if enteredAt}{t('doc.adopted', { date: i18n.date(enteredAt) })}{/if}
|
|
{#if rivals > 0 && !voteOpen}{' · '}{i18n.plural('doc.others', rivals)}{/if}
|
|
</span>
|
|
{/if}
|
|
<span class="links">
|
|
<button type="button" class:on={voteOpen} aria-expanded={voteOpen} aria-controls={voteFor ? `vote-${voteFor}` : undefined} onclick={() => onaction('vote')}>{t('doc.action.vote')}</button>
|
|
<button type="button" onclick={() => onaction('propose')}>{proposeLabel ?? t('doc.action.propose')}</button>
|
|
</span>
|
|
</footer>
|
|
{/if}
|
|
{#if voteOpen && fold}{@render fold()}{/if}
|
|
</article>
|
|
|
|
<style>
|
|
.unit {
|
|
position: relative;
|
|
padding: 1.1rem 1.3rem 0.9rem;
|
|
margin: 0 -1.3rem;
|
|
border-radius: var(--r-card);
|
|
transition: background-color 0.18s ease;
|
|
}
|
|
.unit + :global(.unit) {
|
|
margin-top: 0.5rem;
|
|
}
|
|
.unit:hover,
|
|
.unit:focus-within,
|
|
.unit.selected,
|
|
.unit.unfolded {
|
|
background: var(--surface);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
/* A seed proposal: the water of a vote under way, never the bare page of a text in force. */
|
|
.unit.seed {
|
|
background: color-mix(in srgb, var(--water-wash) 55%, transparent);
|
|
}
|
|
.unit.seed :global(.prose) {
|
|
color: var(--ink-soft);
|
|
}
|
|
header {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.55rem;
|
|
}
|
|
.removed :global(.prose) {
|
|
text-decoration: line-through;
|
|
text-decoration-thickness: 1px;
|
|
color: var(--ink-faint);
|
|
}
|
|
.added,
|
|
.replaced {
|
|
background: var(--moss-wash);
|
|
}
|
|
.crans {
|
|
margin-left: 0.6rem;
|
|
}
|
|
/* The gestures keep their place whether the line of state is there or not (it steps aside
|
|
while the vote is unfolded, lot U2): on the right on a desktop, on the left on a phone. */
|
|
.actions {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
justify-content: flex-start;
|
|
gap: 0.4rem 1rem;
|
|
margin-top: 0.7rem;
|
|
min-height: 2.25rem;
|
|
font-size: 0.84rem;
|
|
}
|
|
.links {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 0.2rem;
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
.unit:hover .links,
|
|
.unit:focus-within .links,
|
|
.unit.selected .links,
|
|
.unit.unfolded .links {
|
|
opacity: 1;
|
|
}
|
|
.links button {
|
|
border: 0;
|
|
background: transparent;
|
|
color: var(--water);
|
|
font-weight: 750;
|
|
padding: 0.35rem 0.6rem;
|
|
min-height: 2.25rem;
|
|
border-radius: var(--r-pill);
|
|
}
|
|
.links button:hover,
|
|
.links button.on {
|
|
background: var(--water-wash);
|
|
}
|
|
@media (hover: none) {
|
|
.links {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
@media (min-width: 900px) {
|
|
.links {
|
|
margin-left: auto;
|
|
}
|
|
}
|
|
@media (max-width: 899px) {
|
|
.links button {
|
|
min-height: 2.75rem;
|
|
}
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.unit,
|
|
.links {
|
|
transition: none;
|
|
}
|
|
}
|
|
</style>
|