mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-15 11:20:36 +08:00
desktop: Share the avatar stack
Board cards show assignees the way the repository header shows delegates, overlapping and fanning out on hover, so the stack moves into its own component.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import type { Author } from "@bindings/cob/Author";
|
||||
|
||||
import { authorForNodeId } from "@app/lib/utils";
|
||||
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
|
||||
interface Props {
|
||||
people: Author[];
|
||||
avatarSize?: string;
|
||||
}
|
||||
|
||||
const { people, avatarSize = undefined }: Props = $props();
|
||||
|
||||
// The cards are portaled out of the stack, so moving onto one ends the
|
||||
// stack's hover. Keep it fanned out while any card is still open.
|
||||
let hovered = $state(false);
|
||||
const cardsOpen: Record<string, boolean> = $state({});
|
||||
const fanned = $derived(hovered || Object.values(cardsOpen).some(Boolean));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.avatars {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
/* Positioned so each avatar paints together with its ring; otherwise every
|
||||
ring is painted before any image and the one below covers it. */
|
||||
.avatar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 2px var(--color-surface-canvas);
|
||||
transition: margin-left 150ms ease;
|
||||
}
|
||||
.avatar + .avatar {
|
||||
margin-left: -0.625rem;
|
||||
}
|
||||
.avatars.fanned .avatar + .avatar {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="avatars"
|
||||
class:fanned
|
||||
onmouseenter={() => (hovered = true)}
|
||||
onmouseleave={() => (hovered = false)}>
|
||||
{#each people as person, index (person.did)}
|
||||
<div class="avatar" style:z-index={people.length - index}>
|
||||
<NodeId
|
||||
{...authorForNodeId(person)}
|
||||
avatarOnly
|
||||
{avatarSize}
|
||||
oncardtoggle={expanded => (cardsOpen[person.did] = expanded)} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
@@ -3,10 +3,9 @@
|
||||
import type { RepoInfo } from "@bindings/repo/RepoInfo";
|
||||
|
||||
import * as router from "@app/lib/router";
|
||||
import { authorForNodeId } from "@app/lib/utils";
|
||||
|
||||
import AvatarStack from "@app/components/AvatarStack.svelte";
|
||||
import CheckoutRepoButton from "@app/components/CheckoutRepoButton.svelte";
|
||||
import NodeId from "@app/components/NodeId.svelte";
|
||||
import ShareButton from "@app/components/ShareButton.svelte";
|
||||
import VisibilityBadge from "@app/components/VisibilityBadge.svelte";
|
||||
|
||||
@@ -18,12 +17,6 @@
|
||||
const { repo, config }: Props = $props();
|
||||
|
||||
const project = $derived(repo.payloads["xyz.radicle.project"]!);
|
||||
|
||||
// The cards are portaled out of the stack, so moving onto one ends the
|
||||
// stack's hover. Keep it fanned out while any card is still open.
|
||||
let hovered = $state(false);
|
||||
const cardsOpen: Record<string, boolean> = $state({});
|
||||
const fanned = $derived(hovered || Object.values(cardsOpen).some(Boolean));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -82,25 +75,6 @@
|
||||
.meta-value {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
.avatars {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
/* Positioned so each avatar paints together with its ring; otherwise every
|
||||
ring is painted before any image and the one below covers it. */
|
||||
.avatar {
|
||||
position: relative;
|
||||
display: flex;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 0 2px var(--color-surface-canvas);
|
||||
transition: margin-left 150ms ease;
|
||||
}
|
||||
.avatar + .avatar {
|
||||
margin-left: -0.625rem;
|
||||
}
|
||||
.avatars.fanned .avatar + .avatar {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -133,22 +107,7 @@
|
||||
<span class="meta-label">Delegates</span>
|
||||
<span class="meta-value">{repo.threshold}/{repo.delegates.length}</span>
|
||||
</button>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="avatars"
|
||||
class:fanned
|
||||
onmouseenter={() => (hovered = true)}
|
||||
onmouseleave={() => (hovered = false)}>
|
||||
{#each repo.delegates as delegate, index (delegate.did)}
|
||||
<div class="avatar" style:z-index={repo.delegates.length - index}>
|
||||
<NodeId
|
||||
{...authorForNodeId(delegate)}
|
||||
avatarOnly
|
||||
avatarSize="1.25rem"
|
||||
oncardtoggle={expanded => (cardsOpen[delegate.did] = expanded)} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<AvatarStack people={repo.delegates} avatarSize="1.25rem" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user