Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion assets/styles/_root.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
8 changes: 8 additions & 0 deletions assets/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
88 changes: 88 additions & 0 deletions assets/styles/components/_activity.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
46 changes: 34 additions & 12 deletions assets/styles/components/_badge.scss
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down
90 changes: 90 additions & 0 deletions src/Twig/Extensions/ActivityDateRangeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
];
}

Expand Down
22 changes: 3 additions & 19 deletions templates/activity/admin/approvals/review.html.twig
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{% extends 'layout-admin.html.twig' %}

{# A localised text (a sign-up list's name) shown the same way as the Details panel: two flag-headed columns
(Dutch | English), each with the inline diff of that language's value. A null `old` shows just the new value (a new
or removed list, which has nothing to compare against); `struck` strikes both values through (a removed list). #}
{% macro localised_flag_diff(old, new, renderer, struck) %}
<div class="row mb-2">
<div class="col-md-6">
Expand All @@ -16,8 +13,6 @@
</div>
{% endmacro %}

{# One labelled field of a localised text block: a small label with the inline diff of a single language's value
below it. Renders nothing when the field is empty on both sides, so an unused language collapses to one note. #}
{% macro field_diff(label, old, new, renderer) %}
{% set rendered = diff(old, new, renderer|default('Combined')) %}
{% if rendered|trim is not empty %}
Expand All @@ -28,9 +23,6 @@
{% endif %}
{% endmacro %}

{# A single boolean list setting. With a `previous` counterpart a newly-enabled flag is green, a newly-disabled one
is shown struck-through in red, and an unchanged-but-on flag stays neutral; without one (a new/removed list) an
enabled flag is just shown neutral. An unchanged-off flag renders nothing. #}
{% macro flag(label, old, new) %}
{% if old is not null and new and not old %}
<span class="badge badge-success">{{ label }}</span>
Expand All @@ -41,9 +33,6 @@
{% endif %}
{% endmacro %}

{# The window, settings and custom fields of one sign-up list. With a lineage-matched `previous` counterpart every
part is shown as a diff (current value, with the old one struck/annotated when it changed, mirroring the main
body above); without one (a new or removed list) it just shows the list's own values. #}
{% macro signup_list_body(list, previous) %}
{% import _self as diffmacro %}
<div class="small mb-1">
Expand All @@ -67,8 +56,6 @@
<div class="mb-1">{{ flags }}</div>
{% endif %}

{# Custom fields, matched to the previous list's fields by position (mirroring the change detection in the
controller); trailing previous-only fields were removed. #}
{% set previousFields = previous is not null ? previous.fields.values : [] %}
{% if list.fields|length > 0 or previousFields|length > 0 %}
<ul class="list-unstyled small mb-0">
Expand Down Expand Up @@ -184,9 +171,9 @@
<div class="mb-2">
<h6 class="text-muted small mb-1">{{ 'Category'|trans }}</h6>
<div>
<span class="badge badge-info">{{ revision.category|trans }}</span>
<span class="badge badge-gewis-primary">{{ revision.category|trans }}</span>
{% if previous is not null and previous.category != revision.category %}
<span class="text-muted small">({{ 'was'|trans }} <span class="badge badge-info">{{ previous.category|trans }}</span>)</span>
<span class="text-muted small">({{ 'was'|trans }} <span class="badge badge-gewis-primary">{{ previous.category|trans }}</span>)</span>
{% endif %}
</div>
</div>
Expand Down Expand Up @@ -329,13 +316,10 @@
</div>
{% endif %}
{{ form_start(decisionForm, {'action': path('admin/activities/approvals/decide', {'revision': revision.getId})}) }}
{# The message field is only present for a decision (reviewer feedback) or a resubmission
(the organiser's required response); a fresh submit / start review / close gets no field. #}
{# The message field is only present for a decision (feedback) or a resubmission (required response); a fresh submit / start review / close has none. #}
{% if decisionForm.message is defined %}
{{ form_row(decisionForm.message) }}
{% endif %}
{# The progressing action is prominent (full-width, solid); the negative actions are smaller
outline buttons inline below it. Each tier self-sizes to whatever the workflow enabled. #}
<div class="d-grid gap-2">
{% if decisionForm.submit is defined %}
{{ form_widget(decisionForm.submit, {'attr': {'class': 'btn btn-success'}}) }}
Expand Down
1 change: 0 additions & 1 deletion templates/activity/admin/edit.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
</div>
</div>
{% if hasFeedback %}
{# A sidebar so the reviewer's feedback stays visible no matter which step the editor is on. #}
<div class="col-md-3">
<div class="panel position-sticky mt-0">
<div class="panel-heading">
Expand Down
1 change: 0 additions & 1 deletion templates/activity/admin/signups.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,5 @@
</div>
</div>

{# Shared confirmation modal for the draw, kept outside the live component so its re-render can't orphan it. #}
{% include 'partials/application/confirm-modal.html.twig' %}
{% endblock %}
2 changes: 2 additions & 0 deletions templates/activity/archive-my.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
<h1 class="mb-0">{{ 'My past activities'|trans }}</h1>

<div class="btn-toolbar gap-2" role="toolbar">
{% include 'partials/activity/filter-toggle.html.twig' %}

<a class="btn btn-outline-secondary" href="{{ path('activity/my') }}">
<i class="fas fa-chevron-left"></i> {{ 'My upcoming activities'|trans }}
</a>
Expand Down
Loading