Files
decision/web/src/lib/components/TitleBar.svelte
T

144 lines
4.1 KiB
Svelte

<script lang="ts">
// The title of the document is a module (ADR 0009, inertia 4): one line under the title,
// of the same voice as the order of a section — its weight in notches, its state, the
// titles proposed. « Le vote » unfolds here the vote of any of its versions; another title
// is written in the panel.
import type { Snippet } from 'svelte';
import { i18n, t } from '$lib/i18n/index.svelte';
import { moduleName } from '$lib/labels';
import type { Proposal, Unit } from '$lib/types';
import Crans from './Crans.svelte';
let {
unit,
stale = false,
selected = false,
vote,
fold,
onvote,
onpropose,
}: {
/** The title module of the document. */
unit: Unit;
/** The title read is a translation of an older title. */
stale?: boolean;
selected?: boolean;
/** The version whose vote is unfolded, wherever it lives. */
vote: string | null;
fold: Snippet<[Proposal]>;
onvote: (proposalId: string) => void;
onpropose: () => void;
} = $props();
const p = $derived(unit.inForce);
const votes = $derived((p?.tally.for ?? 0) + (p?.tally.against ?? 0));
const proposed = $derived(unit.proposals.filter((x) => x.status === 'open' && x.kind === 'title'));
const shown = $derived([p, ...unit.proposals.filter((x) => x.status === 'open')].find((x) => x?.id === vote) ?? null);
</script>
<div class="title-unit" class:selected class:unfolded={!!shown} role="group" aria-label={moduleName(unit)}>
<p class="bar">
<span class="status faint">
{moduleName(unit)}
{#if p?.seedMinimal} · <span title={t('doc.seed.long')}>{i18n.plural('doc.seed.votes', votes)}</span>
{:else if p?.enteredAt} · {t('doc.adopted', { date: i18n.date(p.enteredAt) })}{/if}
</span>
<Crans level={unit.inertia} />
{#if stale}<span class="tag ochre">{t('doc.lang.stale')}</span>{/if}
{#if proposed.length}
{@const first = proposed[0]}
<button type="button" class="proposed" onclick={() => first && onvote(first.id)}>{i18n.plural('title.proposed', proposed.length)}</button>
{/if}
<span class="links">
{#if p}<button type="button" class:on={shown?.id === p.id} aria-expanded={shown?.id === p.id} aria-controls="vote-{p.id}" onclick={() => onvote(p.id)}>{t('doc.action.vote')}</button>{/if}
<button type="button" onclick={onpropose}>{t('title.propose')}</button>
</span>
</p>
{#if shown}<div class="fold">{@render fold(shown)}</div>{/if}
</div>
<style>
.title-unit {
margin: 0.5rem -1.3rem 0;
padding: 0.2rem 1.3rem;
border-radius: var(--r-card);
transition: background-color 0.18s ease;
}
.title-unit.selected,
.title-unit.unfolded {
background: var(--surface);
box-shadow: var(--shadow);
padding-block: 0.5rem 0.9rem;
}
.bar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.3rem 0.8rem;
font-size: 0.82rem;
font-weight: 700;
color: var(--ink-soft);
min-height: 2.25rem;
}
.status {
font-weight: 700;
}
.proposed {
border: 0;
background: var(--water-wash);
color: var(--water);
font-weight: 750;
font-size: 0.8rem;
padding: 0.25rem 0.7rem;
min-height: 2rem;
border-radius: var(--r-pill);
}
.links {
display: flex;
flex-wrap: wrap;
gap: 0.2rem;
opacity: 0;
transition: opacity 0.15s ease;
}
/* Discreet as under an article: the gestures show when the head is pointed at. */
:global(.head:hover) .links,
.title-unit:focus-within .links,
.title-unit.selected .links,
.title-unit.unfolded .links {
opacity: 1;
}
.links button {
border: 0;
background: transparent;
color: var(--water);
font-weight: 750;
padding: 0.3rem 0.6rem;
min-height: 2.25rem;
border-radius: var(--r-pill);
}
.links button:hover,
.links button.on {
background: var(--water-wash);
}
.fold {
margin-top: 0.4rem;
}
@media (hover: none) {
.links {
opacity: 1;
}
}
@media (max-width: 899px) {
.links button,
.proposed {
min-height: 2.75rem;
}
}
@media (prefers-reduced-motion: reduce) {
.title-unit,
.links {
transition: none;
}
}
</style>