From 8f28fcc902b062025b065585d7eeedb5fcf71323 Mon Sep 17 00:00:00 2001 From: Tom Udding Date: Fri, 10 Jul 2026 02:33:12 +0200 Subject: [PATCH 1/2] feat(activity): redesign activity overview with date on the left Rework the activity overview to improve readability and density, adapting a design mockup to our own Bootstrap tokens and dark mode. Each activity is now a calm panel with the date as a stacked badge on the left, the time and relative status alongside, and the sign-up deadline as its own badge; the list is grouped under month dividers on both the upcoming and archive pages, and the "Filters" toggle moved up inline with the page title. Along the way this also touches some app-wide styling: badges are pill-shaped and lighter by default (with a transparent tag variant and a brand-red one), and headings are a touch heavier and higher-contrast. --- assets/styles/_root.scss | 2 +- assets/styles/_variables.scss | 8 + assets/styles/components/_activity.scss | 88 ++++++++++ assets/styles/components/_badge.scss | 46 +++-- .../Extensions/ActivityDateRangeExtension.php | 90 ++++++++++ .../activity/admin/approvals/review.html.twig | 4 +- templates/activity/archive-my.html.twig | 2 + templates/activity/archive.html.twig | 14 +- templates/activity/index.html.twig | 6 +- templates/activity/my.html.twig | 10 +- templates/activity/view.html.twig | 2 +- .../Activity/ActivityOverview.html.twig | 41 +++-- .../partials/activity/filter-toggle.html.twig | 8 + .../partials/activity/list-item.html.twig | 166 ++++++++++-------- 14 files changed, 376 insertions(+), 111 deletions(-) create mode 100644 templates/partials/activity/filter-toggle.html.twig diff --git a/assets/styles/_root.scss b/assets/styles/_root.scss index dbeafd53b6..c51239d9b6 100644 --- a/assets/styles/_root.scss +++ b/assets/styles/_root.scss @@ -154,7 +154,7 @@ --#{$prefix}#{$color}-border-subtle: #{$value}; } - //--#{$prefix}heading-color: #{$headings-color-dark}; + --#{$prefix}heading-color: #{$headings-color-dark}; --#{$prefix}code-color: #{$code-color-dark}; //--#{$prefix}highlight-color: #{$mark-color-dark}; diff --git a/assets/styles/_variables.scss b/assets/styles/_variables.scss index 0a3bc2c897..a7d79985f5 100644 --- a/assets/styles/_variables.scss +++ b/assets/styles/_variables.scss @@ -24,6 +24,10 @@ $background-gray-dark: #131315; $title-size-small: 1.125rem; // Overrides +// Badges: fully pill-shaped and a slightly lighter weight, site-wide. +$badge-border-radius: $border-radius-pill; +$badge-font-weight: 600; + $body-bg-dark: #0E0E11; $body-color: $gewis-gray-dark; $body-color-dark: $gewis-gray-light; @@ -39,6 +43,10 @@ $border-color-translucent: rgba($gray-500, .175); $display-font-family: "Raleway", sans-serif; $headings-margin-bottom: 0; +// Headings are heavier and higher-contrast than body text: full black in light mode, white in gewis-night. +$headings-font-weight: 600; +$headings-color: $black; +$headings-color-dark: $white; $link-color: $gewis-red; $link-decoration: none; diff --git a/assets/styles/components/_activity.scss b/assets/styles/components/_activity.scss index 880b8d72c1..d28ba46253 100644 --- a/assets/styles/components/_activity.scss +++ b/assets/styles/components/_activity.scss @@ -53,3 +53,91 @@ margin-bottom: 0; } } + +// Activity overview list items reuse the shared .panel, but with a calmer, flatter surface than the default panel: the +// body background plus a border, so a long dense list reads as separated rows rather than a stack of filled cards. The +// selector is `.panel.panel-surface` (specificity 0,2,0) on purpose: _activity.scss is imported before _panel.scss, so +// a single-class modifier would lose the source-order tie to `.panel { --bs-panel-bg }`. +.panel.panel-surface { + --#{$prefix}panel-bg: var(--#{$prefix}body-bg); + + transition: background-color $transition-duration ease; + + // The whole panel is clickable (see activity_item_controller.ts); a subtle background lift signals that on hover. + &:hover { + --#{$prefix}panel-bg: var(--#{$prefix}tertiary-bg); + } +} + +@include color-mode(gewis-night) { + .panel.panel-surface { + --#{$prefix}panel-bg: var(--#{$prefix}body-bg); + + &:hover { + --#{$prefix}panel-bg: var(--#{$prefix}secondary-bg); + } + } +} + +// Stacked date on the left of each activity list item: red uppercase month on top, bold body-coloured day, small +// weekday underneath. No background box — it sits directly on the panel surface and is top-aligned with the title. +.activity-date-badge { + display: flex; + flex: 0 0 auto; + flex-direction: column; + align-items: center; + width: 3.5rem; + line-height: 1.2; + text-align: center; + + .activity-date-badge-month { + font-size: 0.75rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + white-space: nowrap; + color: $gewis-red; + } + + .activity-date-badge-day { + font-size: 1.25rem; + font-weight: 700; + white-space: nowrap; + color: var(--#{$prefix}body-color); + } + + .activity-date-badge-weekday { + font-size: 0.75rem; + white-space: nowrap; + color: var(--#{$prefix}secondary-color); + } + + // Multi-day / cross-month ranges carry more text ("31–02", "Jul–Aug"), so shrink the day figure to fit. + &.activity-date-badge-range .activity-date-badge-day { + font-size: 0.95rem; + letter-spacing: -0.02em; + } +} + +// Uppercase month divider ("JULY 2026") with a trailing rule, used in both the upcoming and archive overviews. +.activity-month-divider { + display: flex; + align-items: center; + gap: 0.75rem; + margin: 1.5rem 0 0.75rem; + color: var(--#{$prefix}secondary-color); + font-size: 0.875rem; + font-weight: 700; + letter-spacing: 0.05em; + text-transform: uppercase; + + &:first-child { + margin-top: 0; + } + + &::after { + content: ""; + flex: 1 1 auto; + border-top: var(--#{$prefix}border-width) solid var(--#{$prefix}border-color); + } +} diff --git a/assets/styles/components/_badge.scss b/assets/styles/components/_badge.scss index 28ae030d16..c5f5aa8048 100644 --- a/assets/styles/components/_badge.scss +++ b/assets/styles/components/_badge.scss @@ -1,27 +1,49 @@ -// Badges are like alerts, they use the same CSS custom properties (`--bs-{state}-bg-subtle`, -// `--bs-{state}-text-emphasis`, `--bs-{state}-border-subtle`). +// Filled, subtle badge variants (one per theme colour). They use the same CSS custom properties as alerts +// (`--bs-{state}-bg-subtle`, `--bs-{state}-text-emphasis`). No border: the (default) pill shape carries the visual +// weight, and a border on a subtle fill just muddies it. @each $state, $value in $theme-colors { .badge-#{$state} { --#{$prefix}badge-color: var(--#{$prefix}#{$state}-text-emphasis); background-color: var(--#{$prefix}#{$state}-bg-subtle); - border: var(--#{$prefix}border-width) var(--#{$prefix}border-style) - var(--#{$prefix}#{$state}-border-subtle); } } -// Neutral badge variant. Built from the surface colours instead of the state colours, so it is subtle in both -// `gewis-day` and `gewis-night`. +// GEWIS-red brand badge, keyed to the brand colour (which is not one of Bootstrap's theme colours). Same subtle, +// borderless treatment as `.badge-{state}`; named `-primary` to match the brand's primary/secondary naming. +.badge-gewis-primary { + --#{$prefix}badge-color: #{shade-color($gewis-red, 20%)}; + + background-color: #{tint-color($gewis-red, 80%)}; +} + +@include color-mode(gewis-night) { + .badge-gewis-primary { + --#{$prefix}badge-color: #{tint-color($gewis-red, 40%)}; + + background-color: rgba($gewis-red, 0.2); + } +} + +// Neutral "tag" variant: transparent fill with a visible border and muted text. Reads as a quiet, secondary chip — use +// it for labels and similar tags. Subtle in both `gewis-day` and `gewis-night` because it is built from surface tokens. .badge-tag { - --#{$prefix}badge-color: var(--#{$prefix}body-color); - background-color: var(--#{$prefix}secondary-bg); - border: var(--#{$prefix}border-width) var(--#{$prefix}border-style) - var(--#{$prefix}border-color); + --#{$prefix}badge-color: var(--#{$prefix}secondary-color); + + background-color: transparent; + border: var(--#{$prefix}border-width) var(--#{$prefix}border-style) var(--#{$prefix}border-color); } -// Outlined: transparent fill, coloured border + text from the paired `.badge-{state}` variant. Use as -// `class="badge badge-secondary badge-outlined"`. +// Outlined: transparent fill with a border and text taken from `currentColor`. Pair with a `.badge-{state}` to colour +// it, e.g. `class="badge badge-secondary badge-outlined"`. .badge-outlined { background-color: transparent; + border: var(--#{$prefix}border-width) var(--#{$prefix}border-style) currentcolor; +} + +// Less-rounded corners: overrides the app-wide pill radius with the standard border-radius for a calmer, boxier badge +// (e.g. the sign-up deadline). +.badge-squared { + --#{$prefix}badge-border-radius: var(--#{$prefix}border-radius); } // Selectable: hidden input + label.badge pattern, mirroring Bootstrap's `btn-check`. diff --git a/src/Twig/Extensions/ActivityDateRangeExtension.php b/src/Twig/Extensions/ActivityDateRangeExtension.php index a531f80b9d..71567529ea 100644 --- a/src/Twig/Extensions/ActivityDateRangeExtension.php +++ b/src/Twig/Extensions/ActivityDateRangeExtension.php @@ -34,6 +34,96 @@ public function getFunctions(): array 'activity_date_range', $this->activityDateRange(...), ), + new TwigFunction( + 'activity_date_badge', + $this->activityDateBadge(...), + ), + ]; + } + + /** + * Locale-aware split date parts for the left-hand stacked badge on the activity overview. Uppercasing is left to + * CSS (text-transform) to stay locale-correct for weekday/month abbreviations. The year is deliberately omitted to + * keep the badge compact; activity_date_range() beside the title already shows the year when it differs from today. + * + * @return array{weekday: string, day: string, month: string, range: bool} + */ + public function activityDateBadge( + DateTimeInterface $begin, + DateTimeInterface $end, + ): array { + $locale = Locale::getDefault(); + $sameDay = $begin->format('Y-m-d') === $end->format('Y-m-d'); + $sameMonth = $begin->format('Y-m') === $end->format('Y-m'); + + if ($sameDay) { + return [ + 'weekday' => $this->format( + $begin, + 'EEE', + $locale, + ), + 'day' => $this->format( + $begin, + 'd', + $locale, + ), + 'month' => $this->format( + $begin, + 'MMM', + $locale, + ), + 'range' => false, + ]; + } + + return [ + 'weekday' => sprintf( + '%s - %s', + $this->format( + $begin, + 'EEE', + $locale, + ), + $this->format( + $end, + 'EEE', + $locale, + ), + ), + 'day' => sprintf( + '%s - %s', + $this->format( + $begin, + 'dd', + $locale, + ), + $this->format( + $end, + 'dd', + $locale, + ), + ), + 'month' => $sameMonth + ? $this->format( + $begin, + 'MMM', + $locale, + ) + : sprintf( + '%s - %s', + $this->format( + $begin, + 'MMM', + $locale, + ), + $this->format( + $end, + 'MMM', + $locale, + ), + ), + 'range' => true, ]; } diff --git a/templates/activity/admin/approvals/review.html.twig b/templates/activity/admin/approvals/review.html.twig index 77c233a5cb..96e1948b73 100644 --- a/templates/activity/admin/approvals/review.html.twig +++ b/templates/activity/admin/approvals/review.html.twig @@ -184,9 +184,9 @@
{{ 'Category'|trans }}
- {{ revision.category|trans }} + {{ revision.category|trans }} {% if previous is not null and previous.category != revision.category %} - ({{ 'was'|trans }} {{ previous.category|trans }}) + ({{ 'was'|trans }} {{ previous.category|trans }}) {% endif %}
diff --git a/templates/activity/archive-my.html.twig b/templates/activity/archive-my.html.twig index d67a4a55ff..5fcf551030 100644 --- a/templates/activity/archive-my.html.twig +++ b/templates/activity/archive-my.html.twig @@ -7,6 +7,8 @@

{{ 'My past activities'|trans }}