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
22 changes: 22 additions & 0 deletions tests/behat/bootstrap/FeatureContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,28 @@ public function elementAssertStacksAbove(string $selector, string $other_selecto
}
}

/**
* Assert that an element sits flush against the top of the viewport.
*
* The behat-steps "at the top of the viewport" step accepts any offset
* between zero and the viewport height, so it passes for an element that
* merely happens to be on screen. A sticky element has to be flush.
*
* @code
* Then the element ".ct-header" should be pinned to the top of the viewport
* @endcode
*
* @javascript
*/
#[Then('the element :selector should be pinned to the top of the viewport')]
public function elementAssertPinnedToViewportTop(string $selector): void {
$offset = (int) round((float) $this->elementExecuteJs($selector, 'return {{ELEMENT}}.getBoundingClientRect().top;'));

if ($offset !== 0) {
throw new \RuntimeException(sprintf('Expected element "%s" to be pinned to the top of the viewport, but it is %dpx from it.', $selector, $offset));
}
}

/**
* Assert that an element resolves to a computed style value.
*
Expand Down
4 changes: 2 additions & 2 deletions tests/behat/features/admin_navigation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Feature: Administration navigation for site administrator
Scenario: Site administrator sees the administration top bar above the sticky site header
Given I am logged in as a user with the "civictheme_site_administrator" role
When I visit "/"
Then the element ".top-bar" should stack above the element ".ct-header--sticky"
Then the element ".top-bar" should stack above the element ".ct-page__header--sticky"

@api @javascript
Scenario: Site administrator sees the open mobile navigation above the administration top bar
Expand All @@ -24,4 +24,4 @@ Feature: Administration navigation for site administrator
And I set the viewport to 390 by 844
And I trigger the JS event "click" on the element ".ct-mobile-navigation-trigger"
And I wait for 1 second
Then the element ".ct-header--sticky" should stack above the element ".top-bar"
Then the element ".ct-page__header--sticky" should stack above the element ".top-bar"
22 changes: 22 additions & 0 deletions tests/behat/features/sticky_header.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@header @p1 @drevops
Feature: Sticky site header

As a site visitor
I want the site header to stay at the top of the window while I scroll
So that I can navigate away from anywhere on a long page

@api @javascript
Scenario: Site visitor sees the header pinned to the top of the window while an alert is shown above it
Given the following "civictheme_alert" content:
| title | moderation_state | field_c_n_alert_type | field_c_n_body:value | :format | field_c_n_date_range:value | :end_value |
| [TEST] Site Alert | published | information | [TEST] Alert body copy. | civictheme_rich_text | 2020-01-01T00:00:00 | 2099-01-01T00:00:00 |

And I am an anonymous user

When I visit "/"
And I wait for 2 seconds
Then I should see the text "[TEST] Site Alert"
And the element ".ct-header" should appear after the element ".ct-alert"

When I scroll to the element ".ct-footer"
Then the element ".ct-header" should be pinned to the top of the viewport
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,6 @@

border-bottom: solid ct-particle(0.125);

// Custom: Add sticky header support with fixed positioning.
&--sticky {
position: fixed;
width: 100%;
z-index: $ct-header-sticky-zindex;

// The mobile navigation flyout sits inside the header, so the header's
// stacking context caps how high the flyout panel can rise. Lift the
// header to the flyout tier while the flyout is open, so the panel keeps
// covering everything else the way a full-screen overlay should.
&:has([data-flyout-expanded]) {
z-index: $ct-flyout-zindex;
}
}

&__top {
padding-top: ct-spacing();
padding-bottom: ct-spacing();
Expand Down
27 changes: 27 additions & 0 deletions web/themes/custom/drevops/components/04-templates/page/page.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,33 @@
.ct-page {
$root: &;

// Custom: Carry the sticky positioning on a rail of zero height rather than
// on the header itself. The rail reserves no space, so page content still
// starts at the top of the page and scrolls under the header, and anything
// rendered ahead of the header - alerts are injected there once they load -
// scrolls away before the header pins. A fixed header would instead park at
// its static position, which those alerts push down the page.
#{$root}__header {
&--sticky {
position: sticky;

// Administration chrome that reserves space at the top of the viewport
// publishes it through this variable, so the header pins below the
// administration top bar instead of underneath it.
top: var(--drupal-displace-offset-top, 0);
height: 0;
z-index: $ct-header-sticky-zindex;

// The mobile navigation flyout sits inside the header, so the rail's
// stacking context caps how high the flyout panel can rise. Lift the
// rail to the flyout tier while the flyout is open, so the panel keeps
// covering everything else the way a full-screen overlay should.
&:has([data-flyout-expanded]) {
z-index: $ct-flyout-zindex;
}
}
}

@include ct-component-theme($root) using($root, $theme) {
@include ct-component-property($root, $theme, background-color);
}
Expand Down
25 changes: 14 additions & 11 deletions web/themes/custom/drevops/components/04-templates/page/page.twig
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,20 @@

{% block header_block %}
{# Custom: Use include() instead of {% include only %} and add is_sticky support. #}
{{ include('civictheme:header', {
theme: header_theme,
is_sticky: header_is_sticky,
content_top1: header_top_1,
content_top2: header_top_2,
content_top3: header_top_3,
content_middle1: header_middle_1,
content_middle2: header_middle_2,
content_middle3: header_middle_3,
content_bottom1: header_bottom_1,
}, with_context: false) }}
{% set header_sticky_class = header_is_sticky ? 'ct-page__header--sticky' : '' %}
<div class="ct-page__header {{ header_sticky_class }}">
{{ include('civictheme:header', {
theme: header_theme,
is_sticky: header_is_sticky,
content_top1: header_top_1,
content_top2: header_top_2,
content_top3: header_top_3,
content_middle1: header_middle_1,
content_middle2: header_middle_2,
content_middle3: header_middle_3,
content_bottom1: header_bottom_1,
}, with_context: false) }}
</div>
{% endblock %}

{% block banner_block %}
Expand Down
Loading