feat(activities): UI tweaks across feed, detail, hosting, calendar, scan, shell #91
2 changed files with 57 additions and 32 deletions
feat(activities): refine activity card for pending/rejected + compact
- Wash out pending/rejected events with opacity-50 + grayscale on a wrapper div so the operator sees at a glance the event isn't live, not just the small badge. - Pull the status badge OUT of the wash-out wrapper and absolute- position it on Card root (bottom-2 left-2, z-10) so it stays in full color above the dim card. Both pending and rejected use the destructive token — the label text differentiates the two states. Bottom-left so it doesn't collide with the category chip on full cards or the thumbnail on compact ones. - Compact rows in the Hosting view now show a small left-aligned thumbnail (w-20 h-20, self-center, ml-3, rounded-md) when the event carries an image — host can still recognize each event at a glance without paying the visual weight of a full hero. - Card root becomes `relative overflow-hidden`; the wrapper div owns the conditional flex-row (compact) / flex-col (default) layout and the opacity/grayscale toggling.
commit
1dfb025df3
|
|
@ -62,19 +62,46 @@ const isPast = computed(() => {
|
||||||
if (!end || isNaN(end.getTime())) return false
|
if (!end || isNaN(end.getTime())) return false
|
||||||
return end.getTime() < Date.now()
|
return end.getTime() < Date.now()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Pending / rejected events get a washed-out look so the user
|
||||||
|
// sees at a glance the event isn't live, not just the small badge.
|
||||||
|
const isNonApproved = computed(
|
||||||
|
() => !!props.event.lnbitsStatus && props.event.lnbitsStatus !== 'approved',
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Card
|
<Card
|
||||||
class="overflow-hidden cursor-pointer hover:shadow-lg transition-shadow duration-200 flex flex-col"
|
class="relative cursor-pointer hover:shadow-lg transition-shadow duration-200"
|
||||||
@click="emit('click', event)"
|
@click="emit('click', event)"
|
||||||
>
|
>
|
||||||
|
<!-- Wash-out wrapper. The pending/rejected status badge below sits
|
||||||
|
OUTSIDE this wrapper so it stays in full color and reads
|
||||||
|
clearly even when the card is dimmed + desaturated. -->
|
||||||
|
<div
|
||||||
|
class="transition-opacity duration-200"
|
||||||
|
:class="[
|
||||||
|
compact ? 'flex flex-row' : 'flex flex-col',
|
||||||
|
isNonApproved ? 'opacity-50 grayscale hover:opacity-90' : '',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<!-- Compact thumbnail — small square preview on the left of the
|
||||||
|
row when the event carries an image. `self-center` keeps it
|
||||||
|
vertically centered against a taller content column so we
|
||||||
|
don't get a top-anchored thumb with dead space below. -->
|
||||||
|
<img
|
||||||
|
v-if="compact && event.image"
|
||||||
|
:src="event.image"
|
||||||
|
:alt="event.title"
|
||||||
|
class="w-20 h-20 object-cover shrink-0 self-center ml-3 rounded-md"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
<!-- Image with overlaid badges. Cards without an image (or in
|
<!-- Image with overlaid badges. Cards without an image (or in
|
||||||
compact mode) skip the hero area entirely and surface their
|
compact mode) skip the hero area entirely and surface their
|
||||||
badges inline at the top of the content block — the solid-
|
badges inline at the top of the content block — the solid-
|
||||||
color placeholder + calendar glyph wasn't communicating
|
color placeholder + calendar glyph wasn't communicating
|
||||||
anything the title + details don't already. -->
|
anything the title + details don't already. -->
|
||||||
<div v-if="event.image && !compact" class="relative aspect-[16/9] overflow-hidden">
|
<div v-if="event.image && !compact" class="relative aspect-[16/9] overflow-hidden rounded-t-lg">
|
||||||
<img
|
<img
|
||||||
:src="event.image"
|
:src="event.image"
|
||||||
:alt="event.title"
|
:alt="event.title"
|
||||||
|
|
@ -110,27 +137,13 @@ const isPast = computed(() => {
|
||||||
{{ priceDisplay }}
|
{{ priceDisplay }}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
||||||
<!-- Pending/rejected overlay for the creator's own non-approved
|
<!-- Past badge — shown when the event has already ended. The
|
||||||
drafts. Only present when the event originated from a
|
pending/rejected status badge that used to share this slot
|
||||||
local LNbits event (Nostr-sourced events have no
|
is now an absolute overlay on Card root, above the wash-out,
|
||||||
lnbitsStatus). -->
|
so we still suppress Past when isNonApproved (the status
|
||||||
|
badge is more actionable in that case). -->
|
||||||
<Badge
|
<Badge
|
||||||
v-if="event.lnbitsStatus && event.lnbitsStatus !== 'approved'"
|
v-if="isPast && !isNonApproved"
|
||||||
:variant="event.lnbitsStatus === 'rejected' ? 'destructive' : 'secondary'"
|
|
||||||
class="absolute bottom-2 left-2 text-xs capitalize"
|
|
||||||
>
|
|
||||||
{{ event.lnbitsStatus === 'rejected' ? 'Rejected' : 'Pending review' }}
|
|
||||||
</Badge>
|
|
||||||
|
|
||||||
<!-- Past badge — shown when the event has already ended.
|
|
||||||
Only relevant on the feed when the "Past events" filter
|
|
||||||
chip is toggled on (otherwise these cards aren't rendered);
|
|
||||||
on the detail page the card view isn't used. Suppressed
|
|
||||||
when a pending/rejected status badge is taking the same
|
|
||||||
slot — that case is the creator's own past draft, which is
|
|
||||||
vanishingly rare and the status hint is more actionable. -->
|
|
||||||
<Badge
|
|
||||||
v-if="isPast && !(event.lnbitsStatus && event.lnbitsStatus !== 'approved')"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="absolute bottom-2 left-2 text-xs gap-1 bg-background/80 backdrop-blur"
|
class="absolute bottom-2 left-2 text-xs gap-1 bg-background/80 backdrop-blur"
|
||||||
>
|
>
|
||||||
|
|
@ -158,14 +171,7 @@ const isPast = computed(() => {
|
||||||
Yours
|
Yours
|
||||||
</Badge>
|
</Badge>
|
||||||
<Badge
|
<Badge
|
||||||
v-if="event.lnbitsStatus && event.lnbitsStatus !== 'approved'"
|
v-if="isPast && !isNonApproved"
|
||||||
:variant="event.lnbitsStatus === 'rejected' ? 'destructive' : 'secondary'"
|
|
||||||
class="text-xs capitalize"
|
|
||||||
>
|
|
||||||
{{ event.lnbitsStatus === 'rejected' ? 'Rejected' : 'Pending review' }}
|
|
||||||
</Badge>
|
|
||||||
<Badge
|
|
||||||
v-if="isPast && !(event.lnbitsStatus && event.lnbitsStatus !== 'approved')"
|
|
||||||
variant="outline"
|
variant="outline"
|
||||||
class="text-xs gap-1"
|
class="text-xs gap-1"
|
||||||
>
|
>
|
||||||
|
|
@ -251,5 +257,22 @@ const isPast = computed(() => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status badge — absolutely positioned on Card root so it sits
|
||||||
|
ABOVE the wash-out wrapper and keeps its full color.
|
||||||
|
Pending + rejected both lean on the destructive token so the
|
||||||
|
non-approved state reads as "needs attention" in every theme;
|
||||||
|
the label text differentiates the two specific states.
|
||||||
|
Bottom-right with a slight downward spill so it anchors
|
||||||
|
visually without competing with the category chip in the
|
||||||
|
badge row (full cards) or the thumbnail (compact cards). -->
|
||||||
|
<Badge
|
||||||
|
v-if="isNonApproved"
|
||||||
|
variant="destructive"
|
||||||
|
class="absolute -bottom-1 right-2 z-10 text-xs capitalize shadow"
|
||||||
|
>
|
||||||
|
{{ event.lnbitsStatus === 'rejected' ? 'Rejected' : 'Pending review' }}
|
||||||
|
</Badge>
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,12 @@ const { t } = useI18n()
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Event grid — compact mode collapses to a single column of
|
<!-- Event grid — compact mode collapses to a single column of
|
||||||
tight rows; default mode is the responsive card grid. -->
|
tight rows; default mode is the responsive card grid. The
|
||||||
|
compact gap is bumped a notch so the status badge spilling
|
||||||
|
past the card's bottom edge has room to sit between cards. -->
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
:class="compact ? 'flex flex-col gap-2' : 'grid gap-4 sm:grid-cols-2 lg:grid-cols-3'"
|
:class="compact ? 'flex flex-col gap-4' : 'grid gap-4 sm:grid-cols-2 lg:grid-cols-3'"
|
||||||
>
|
>
|
||||||
<EventCard
|
<EventCard
|
||||||
v-for="event in events"
|
v-for="event in events"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue