Skip to content

Commit

Permalink
feat(overflowElements): ✨ add sticky header for overflow elements (#989)
Browse files Browse the repository at this point in the history
To make a sticky header element that is within Citizen overflow content (e.g. table header), you can attach the `.citizen-overflow-sticky-header` class to the element (for table it would be the `<tr>` element).

This is done through JS because `position: sticky` does not work with overflow.
  • Loading branch information
alistair3149 authored Dec 20, 2024
1 parent f2cc4fd commit dfdb2e6
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 27 deletions.
33 changes: 17 additions & 16 deletions resources/mixins.less
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,27 @@
top: var( --height-sticky-header ) !important;
}

.citizen-sticky-header-background() {
&::before {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: @z-index-bottom;
height: 100%;
content: '';
background-color: var( --color-surface-0 );
filter: opacity( 0.9 );
-webkit-backdrop-filter: saturate( 50% ) blur( 16px );
backdrop-filter: saturate( 50% ) blur( 16px );
}
}

.citizen-sticky-header(@bottomBorder: true, @zIndex: true) {
position: -webkit-sticky;
position: sticky;
.sticky-header-element;
.citizen-sticky-header-background;

& when (@bottomBorder ) {
box-shadow: 0 1px 0 0 var( --border-color-base );
Expand All @@ -64,22 +81,6 @@
& when (@zIndex ) {
z-index: @z-index-sticky;
}

// HACK: Hide overflow
// This has an issue if parent has overflow set
&::before {
position: absolute;
top: 0;
right: ~'calc( var(--padding-page ) * -1 )';
left: ~'calc( var(--padding-page ) * -1 )';
z-index: @z-index-bottom;
height: 100%;
content: '';
background-color: var( --color-surface-0 );
filter: opacity( 0.9 );
-webkit-backdrop-filter: saturate( 50% ) blur( 16px );
backdrop-filter: saturate( 50% ) blur( 16px );
}
}

// To hide objects, but keep them accessible for screen-readers
Expand Down
51 changes: 51 additions & 0 deletions resources/skins.citizen.scripts/overflowElements.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class OverflowElement {
this.contentWidth = 0;
this.onScroll = mw.util.throttle( this.onScroll.bind( this ), 250 );
this.updateState = this.updateState.bind( this );
this.headerToSticky = this.element.querySelector( '.citizen-overflow-sticky-header' );
}

/**
Expand Down Expand Up @@ -97,6 +98,9 @@ class OverflowElement {
[ isRightOverflowing, 'citizen-overflow--right' ]
];
this.toggleClasses( updateClasses );
if ( this.stickyHeader ) {
this.stickyHeader.style.setProperty( '--citizen-overflow-scroll-x', this.contentScrollLeft + 'px' );
}
} );
}

Expand Down Expand Up @@ -182,6 +186,50 @@ class OverflowElement {
}
}

syncStickyHeaderColumns() {
const stickyCols = this.colgroup.querySelectorAll( 'col' );

this.originalTh.forEach( ( col, index ) => {
stickyCols[ index ].style.minWidth = col.getBoundingClientRect().width + 'px';
} );
}

getStickyHeaderForTable() {
// If overflow content is a table, we need to create a proper table element
// for the sticky header, so that the styles are applied correctly
const stickyHeader = document.createElement( 'table' );
const thead = document.createElement( 'thead' );

// Copy attributes from original table to sticky header
for ( const { name, value } of this.element.attributes ) {
stickyHeader.setAttribute( name, value );
}
for ( const className of this.element.classList ) {
stickyHeader.classList.add( className );
}

// Create colgroup and columns so that we can align the column widths
this.colgroup = document.createElement( 'colgroup' );
this.originalTh = this.headerToSticky.querySelectorAll( 'th' );
for ( let i = 0; i < this.originalTh.length; i++ ) {
const colEl = document.createElement( 'col' );
this.colgroup.append( colEl );
}
this.syncStickyHeaderColumns();

thead.append( this.headerToSticky.cloneNode( true ) );
stickyHeader.append( thead, this.colgroup );
return stickyHeader;
}

setupStickyHeader() {
const isTable = this.element instanceof HTMLTableElement;
this.stickyHeader = isTable ? this.getStickyHeaderForTable() : document.createElement( 'div' );
this.stickyHeader.classList.add( 'citizen-overflow-content-sticky-header' );
this.stickyHeader.setAttribute( 'aria-hidden', 'true' ); // this is not useful for screen reader
this.content.insertBefore( this.stickyHeader, this.element );
}

/**
* Scrolls the content element by the specified offset.
*
Expand Down Expand Up @@ -302,6 +350,9 @@ class OverflowElement {
*/
init() {
this.wrap();
if ( this.headerToSticky ) {
this.setupStickyHeader();
}
this.setupResizeObserver();
this.setupIntersectionObserver();
this.resume();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@
.citizen-overflow--left.citizen-overflow--right > & {
.mask-gradient(90deg, transparent, #000 var( --overflow-gradient-size ), #000 ~'calc( 100% - var( --overflow-gradient-size ) )', transparent);
}

&-sticky-header {
--citizen-overflow-scroll-x: 0; // default value to be overriden
position: fixed;
top: 0;
border-bottom: var( --border-width-base ) solid var( --border-color-base );
transform: ~'translate( calc( var( --citizen-overflow-scroll-x ) * -1 ), var( --height-sticky-header ) )';
.citizen-sticky-header-background;
}
}

&-nav {
Expand Down
12 changes: 1 addition & 11 deletions resources/skins.citizen.styles/components/StickyHeader.less
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,9 @@
.citizen-page-header {
position: -webkit-sticky;
position: sticky;
.citizen-sticky-header-background;

&::before {
position: absolute;
top: 0;
right: 0;
left: 0;
z-index: @z-index-bottom;
height: 100%;
content: '';
background-color: var( --color-surface-0 );
filter: opacity( 0.9 );
-webkit-backdrop-filter: saturate( 50% ) blur( 16px );
backdrop-filter: saturate( 50% ) blur( 16px );
opacity: 0;
transition: var( --transition-hover );
transition-property: opacity;
Expand Down

0 comments on commit dfdb2e6

Please sign in to comment.