feat(17): restyle discussion view and planning page in backend/

- discussion.templ: #discussion-messages uses .ui-card; DiscussionMessageRow
  uses .message-row/.message-other/.message-bubble/.message-meta classes;
  day separator gets data-day-separator attribute
- planning.templ: wraps content in .overview-section; heading uses
  .overview-section-heading with h1; empty state uses .ui-card
- app.css: add Section 26 .message-* bubble classes; extend
  .overview-section-heading selector to include h1

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arthur Belleville 2026-05-17 10:01:24 +02:00
parent e3596ff753
commit 56194cfdb5
No known key found for this signature in database
3 changed files with 77 additions and 20 deletions

View file

@ -352,6 +352,7 @@
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.overview-section-heading h1,
.overview-section-heading h3, .overview-section-heading h3,
.tasks-section-header h3 { .tasks-section-header h3 {
color: var(--color-surface-muted-inverse); color: var(--color-surface-muted-inverse);
@ -700,3 +701,61 @@
font-style: italic; font-style: italic;
padding: 0.75rem 1rem; padding: 0.75rem 1rem;
} }
/* ============================================================
Section 26 Discussion message bubbles (Phase 17)
============================================================ */
.message-row {
display: flex;
flex-direction: column;
padding: 0.75rem 1rem;
}
.message-row.message-own {
align-items: flex-end;
}
.message-row.message-other {
align-items: flex-start;
}
.message-meta {
display: flex;
align-items: baseline;
gap: 0.5rem;
margin-bottom: 0.25rem;
}
.message-author {
color: var(--color-text-primary);
font-size: 0.875rem;
font-weight: 600;
}
.message-timestamp {
color: var(--color-text-muted);
font-size: 0.75rem;
}
.message-bubble {
border-radius: 0.25rem 0.75rem 0.75rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
max-width: 70%;
padding: 0.75rem 1rem;
white-space: pre-wrap;
word-break: break-words;
}
.message-row.message-own .message-bubble {
background-color: rgba(128, 78, 236, 0.10);
border-radius: 0.75rem 0.75rem 0.25rem 0.75rem;
color: var(--color-text-primary);
}
.message-row.message-other .message-bubble {
background-color: var(--color-surface-default, #ffffff);
border: 1px solid var(--color-border-default, #e2e8f0);
color: var(--color-text-primary);
}

View file

@ -15,18 +15,16 @@ templ DiscussionTabFragment(tablo sqlc.Tablo, data DiscussionTabData, form Discu
<p class="mt-1 text-sm text-slate-600">1 participant</p> <p class="mt-1 text-sm text-slate-600">1 participant</p>
</div> </div>
</div> </div>
<div id="discussion-messages" class="rounded border border-slate-200 bg-white"> <div id="discussion-messages" class="ui-card">
if len(data.Messages) == 0 { if len(data.Messages) == 0 {
@DiscussionEmptyState() @DiscussionEmptyState()
} else { } else {
<div class="divide-y divide-slate-100"> for i, message := range data.Messages {
for i, message := range data.Messages { if DiscussionShowDaySeparator(data.Messages, i) {
if DiscussionShowDaySeparator(data.Messages, i) { @DiscussionDaySeparator(message.CreatedAt)
@DiscussionDaySeparator(message.CreatedAt)
}
@DiscussionMessageRow(message)
} }
</div> @DiscussionMessageRow(message)
}
} }
</div> </div>
@DiscussionComposer(tablo, form, errs, csrfToken) @DiscussionComposer(tablo, form, errs, csrfToken)
@ -34,25 +32,25 @@ templ DiscussionTabFragment(tablo sqlc.Tablo, data DiscussionTabData, form Discu
} }
templ DiscussionEmptyState() { templ DiscussionEmptyState() {
<div class="bg-slate-50 px-4 py-8 text-center"> <div class="ui-card-body py-8 text-center">
<h3 class="text-xl font-semibold leading-snug text-slate-800">No messages yet</h3> <h3 class="text-xl font-semibold leading-snug text-slate-800">No messages yet</h3>
<p class="mt-2 text-base text-slate-600">Start the discussion for this tablo.</p> <p class="mt-2 text-base text-slate-600">Start the discussion for this tablo.</p>
</div> </div>
} }
templ DiscussionDaySeparator(createdAt time.Time) { templ DiscussionDaySeparator(createdAt time.Time) {
<div class="bg-slate-50 px-4 py-3 text-center text-sm text-slate-500"> <div class="bg-slate-50 px-4 py-2 text-center text-sm text-slate-500" data-day-separator="true">
{ DiscussionDateLabel(createdAt) } { DiscussionDateLabel(createdAt) }
</div> </div>
} }
templ DiscussionMessageRow(message DiscussionMessageView) { templ DiscussionMessageRow(message DiscussionMessageView) {
<article id={ "discussion-message-" + message.ID.String() } data-message-id={ message.ID.String() } class="px-4 py-3"> <article id={ "discussion-message-" + message.ID.String() } data-message-id={ message.ID.String() } class="message-row message-other">
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-1"> <div class="message-meta">
<span class="text-sm font-semibold text-slate-900">{ message.AuthorEmail }</span> <span class="message-author">{ message.AuthorEmail }</span>
<time class="text-xs text-slate-500" datetime={ message.CreatedAt.Format(time.RFC3339) }>{ DiscussionTimestampLabel(message.CreatedAt) }</time> <time class="message-timestamp" datetime={ message.CreatedAt.Format(time.RFC3339) }>{ DiscussionTimestampLabel(message.CreatedAt) }</time>
</div> </div>
<p class="mt-2 whitespace-pre-wrap break-words text-base leading-6 text-slate-900">{ message.Body }</p> <div class="message-bubble">{ message.Body }</div>
</article> </article>
} }

View file

@ -7,10 +7,10 @@ import (
templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, agenda PlanningAgenda) { templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, agenda PlanningAgenda) {
@AppLayout("Planning - Xtablo", user, csrfToken, activePath, tablos) { @AppLayout("Planning - Xtablo", user, csrfToken, activePath, tablos) {
<div class="space-y-6"> <section class="overview-section">
<div class="flex flex-wrap items-start justify-between gap-4"> <div class="overview-section-heading">
<div> <div>
<h1 class="text-[28px] font-semibold leading-tight">Planning</h1> <h1>Planning</h1>
<p class="mt-1 text-sm text-slate-600">{ agenda.RangeLabel }</p> <p class="mt-1 text-sm text-slate-600">{ agenda.RangeLabel }</p>
</div> </div>
<nav class="flex flex-wrap items-center gap-2" aria-label="Planning navigation"> <nav class="flex flex-wrap items-center gap-2" aria-label="Planning navigation">
@ -27,7 +27,7 @@ templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos
</nav> </nav>
</div> </div>
if len(agenda.Events) == 0 { if len(agenda.Events) == 0 {
<div class="border border-slate-200 bg-slate-50 px-4 py-12"> <div class="ui-card ui-card-body py-12 text-center">
<h2 class="text-xl font-semibold leading-snug text-slate-800">No events in this range</h2> <h2 class="text-xl font-semibold leading-snug text-slate-800">No events in this range</h2>
<p class="mt-2 text-base text-slate-600">Use the navigation controls to browse another 14-day window.</p> <p class="mt-2 text-base text-slate-600">Use the navigation controls to browse another 14-day window.</p>
</div> </div>
@ -38,7 +38,7 @@ templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos
} }
</ul> </ul>
} }
</div> </section>
} }
} }