-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sbb-paginator): add sbb-compact-paginator component variant (#3142)
- Loading branch information
1 parent
07b8eba
commit 2f3dc21
Showing
26 changed files
with
1,197 additions
and
414 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './interfaces/overlay-close-details.js'; | ||
export * from './interfaces/paginator-page.js'; | ||
export * from './interfaces/types.js'; | ||
export * from './interfaces/validation-change.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type SbbPaginatorPageEventDetails = { | ||
length: number; | ||
pageSize: number; | ||
pageIndex: number; | ||
previousPageIndex: number; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export * from './paginator/common.js'; | ||
export * from './paginator/compact-paginator.js'; | ||
export * from './paginator/paginator.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './common/paginator-common.js'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import { html, type LitElement, type PropertyValues, type TemplateResult } from 'lit'; | ||
import { property } from 'lit/decorators.js'; | ||
|
||
import { SbbLanguageController } from '../../core/controllers.js'; | ||
import { hostAttributes } from '../../core/decorators.js'; | ||
import { EventEmitter } from '../../core/eventing.js'; | ||
import { i18nNextPage, i18nPreviousPage, i18nSelectedPage } from '../../core/i18n.js'; | ||
import type { SbbPaginatorPageEventDetails } from '../../core/interfaces.js'; | ||
import { type AbstractConstructor, SbbDisabledMixin, SbbNegativeMixin } from '../../core/mixins.js'; | ||
|
||
import '../../button/mini-button.js'; | ||
import '../../button/mini-button-group.js'; | ||
import '../../divider.js'; | ||
|
||
export declare abstract class SbbPaginatorCommonElementMixinType { | ||
public accessor negative: boolean; | ||
public accessor disabled: boolean; | ||
public accessor length: number; | ||
public accessor pageSize: number; | ||
public accessor pageIndex: number; | ||
public accessor pagerPosition: 'start' | 'end'; | ||
public accessor size: 'm' | 's'; | ||
protected language: SbbLanguageController; | ||
protected numberOfPages(): number; | ||
protected pageIndexChanged(value: number): void; | ||
protected emitPageEvent(previousPageIndex: number): void; | ||
protected renderPrevNextButtons(): TemplateResult; | ||
protected abstract renderPaginator(): TemplateResult; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/naming-convention | ||
export const SbbPaginatorCommonElementMixin = <T extends AbstractConstructor<LitElement>>( | ||
superClass: T, | ||
): AbstractConstructor<SbbPaginatorCommonElementMixinType> & T => { | ||
@hostAttributes({ | ||
role: 'group', | ||
}) | ||
abstract class SbbPaginatorCommonElement | ||
extends SbbNegativeMixin(SbbDisabledMixin(superClass)) | ||
implements Partial<SbbPaginatorCommonElementMixinType> | ||
{ | ||
public static readonly events: Record<string, string> = { | ||
page: 'page', | ||
} as const; | ||
|
||
/** Total number of items. */ | ||
@property({ type: Number }) | ||
public set length(value: number) { | ||
this._length = isNaN(value) || value < 0 ? 0 : value; | ||
// Call setter of pageIndex to ensure bounds | ||
// eslint-disable-next-line no-self-assign | ||
this.pageIndex = this.pageIndex; | ||
} | ||
public get length(): number { | ||
return this._length; | ||
} | ||
private _length: number = 0; | ||
|
||
/** Number of items per page. */ | ||
@property({ attribute: 'page-size', type: Number }) | ||
public set pageSize(value: number) { | ||
// Current page needs to be updated to reflect the new page size. Navigate to the page | ||
// containing the previous page's first item. | ||
const previousPageSize = this.pageSize; | ||
this._pageSize = Math.max(value, 0); | ||
this.pageIndex = Math.floor((this.pageIndex * previousPageSize) / this.pageSize) || 0; | ||
} | ||
public get pageSize(): number { | ||
return this._pageSize; | ||
} | ||
private _pageSize: number = 10; | ||
|
||
/** Current page index. */ | ||
@property({ attribute: 'page-index', type: Number }) | ||
public set pageIndex(value: number) { | ||
this._pageIndex = this._coercePageIndexInRange(value); | ||
} | ||
public get pageIndex(): number { | ||
return this._pageIndex; | ||
} | ||
private _pageIndex: number = 0; | ||
|
||
/** Position of the prev/next buttons. */ | ||
@property({ attribute: 'pager-position', reflect: true }) public accessor pagerPosition: | ||
| 'start' | ||
| 'end' = 'start'; | ||
|
||
/** Size variant, either m or s. */ | ||
@property({ reflect: true }) public accessor size: 'm' | 's' = 'm'; | ||
|
||
private _page: EventEmitter<SbbPaginatorPageEventDetails> = new EventEmitter( | ||
this, | ||
SbbPaginatorCommonElement.events.page, | ||
{ composed: true, bubbles: true }, | ||
); | ||
protected language = new SbbLanguageController(this); | ||
protected abstract renderPaginator(): string; | ||
|
||
protected override updated(changedProperties: PropertyValues<this>): void { | ||
super.updated(changedProperties); | ||
|
||
// To reliably announce page change, we have to set the label in updated() (a tick later than the other changes). | ||
this.shadowRoot!.querySelector('sbb-screen-reader-only')!.textContent = | ||
this._currentPageLabel(); | ||
} | ||
|
||
/** Evaluate `pageIndex` by excluding edge cases. */ | ||
private _coercePageIndexInRange(pageIndex: number): number { | ||
return Math.max( | ||
Math.min(Math.max(isNaN(pageIndex) ? 0 : pageIndex, 0), this.numberOfPages() - 1), | ||
0, | ||
); | ||
} | ||
|
||
private _currentPageLabel(): string { | ||
return i18nSelectedPage(this.pageIndex + 1)[this.language.current]; | ||
} | ||
|
||
/** | ||
* Calculates the current number of pages based on the `length` and the `pageSize`; | ||
* value must be rounded up (e.g. `length = 21` and `pageSize = 10` means 3 pages). | ||
*/ | ||
protected numberOfPages(): number { | ||
return this.pageSize ? Math.ceil(this.length / this.pageSize) : 0; | ||
} | ||
|
||
/** | ||
* If the `pageIndex` changes due to user interaction, | ||
* emit the `page` event and then update the `pageIndex` value. | ||
*/ | ||
protected pageIndexChanged(value: number): void { | ||
const previousPageIndex = this.pageIndex; | ||
this.pageIndex = value; | ||
|
||
if (previousPageIndex !== this.pageIndex) { | ||
this.emitPageEvent(previousPageIndex); | ||
} | ||
} | ||
|
||
protected emitPageEvent(previousPageIndex: number): void { | ||
this._page.emit({ | ||
previousPageIndex, | ||
pageIndex: this.pageIndex, | ||
length: this.length, | ||
pageSize: this.pageSize, | ||
}); | ||
} | ||
|
||
protected renderPrevNextButtons(): TemplateResult { | ||
return html` | ||
<sbb-mini-button-group ?negative=${this.negative} size=${this.size === 's' ? 's' : 'l'}> | ||
<sbb-mini-button | ||
id="sbb-paginator-prev-page" | ||
aria-label=${i18nPreviousPage[this.language.current]} | ||
icon-name="chevron-small-left-small" | ||
?disabled=${this.disabled || this.pageIndex === 0} | ||
@click=${() => this.pageIndexChanged(this._pageIndex - 1)} | ||
></sbb-mini-button> | ||
<sbb-divider orientation="vertical"></sbb-divider> | ||
<sbb-mini-button | ||
id="sbb-paginator-next-page" | ||
aria-label=${i18nNextPage[this.language.current]} | ||
icon-name="chevron-small-right-small" | ||
?disabled=${this.disabled || this.pageIndex === this.numberOfPages() - 1} | ||
@click=${() => this.pageIndexChanged(this._pageIndex + 1)} | ||
></sbb-mini-button> | ||
</sbb-mini-button-group> | ||
`; | ||
} | ||
|
||
protected override render(): TemplateResult { | ||
return html` | ||
${this.renderPaginator()} | ||
<sbb-screen-reader-only role="status"></sbb-screen-reader-only> | ||
`; | ||
} | ||
} | ||
return SbbPaginatorCommonElement as unknown as AbstractConstructor<SbbPaginatorCommonElementMixinType> & | ||
T; | ||
}; | ||
|
||
declare global { | ||
interface HTMLElementEventMap { | ||
page: CustomEvent<SbbPaginatorPageEventDetails>; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './compact-paginator/compact-paginator.js'; |
134 changes: 134 additions & 0 deletions
134
...lements/paginator/compact-paginator/__snapshots__/compact-paginator.snapshot.spec.snap.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* @web/test-runner snapshot v1 */ | ||
export const snapshots = {}; | ||
|
||
snapshots["sbb-compact-paginator renders DOM"] = | ||
`<sbb-compact-paginator | ||
length="50" | ||
page-size="5" | ||
pager-position="start" | ||
role="group" | ||
size="m" | ||
> | ||
</sbb-compact-paginator> | ||
`; | ||
/* end snapshot sbb-compact-paginator renders DOM */ | ||
|
||
snapshots["sbb-compact-paginator renders Shadow DOM"] = | ||
`<div class="sbb-compact-paginator"> | ||
<sbb-mini-button-group size="l"> | ||
<sbb-mini-button | ||
aria-disabled="true" | ||
aria-label="Previous page" | ||
data-action="" | ||
data-button="" | ||
disabled="" | ||
icon-name="chevron-small-left-small" | ||
id="sbb-paginator-prev-page" | ||
role="button" | ||
slot="li-0" | ||
> | ||
</sbb-mini-button> | ||
<sbb-divider | ||
aria-orientation="vertical" | ||
orientation="vertical" | ||
role="separator" | ||
slot="li-1" | ||
> | ||
</sbb-divider> | ||
<sbb-mini-button | ||
aria-label="Next page" | ||
data-action="" | ||
data-button="" | ||
icon-name="chevron-small-right-small" | ||
id="sbb-paginator-next-page" | ||
role="button" | ||
slot="li-2" | ||
tabindex="0" | ||
> | ||
</sbb-mini-button> | ||
</sbb-mini-button-group> | ||
<span class="sbb-paginator__pages"> | ||
1 | ||
<sbb-divider | ||
aria-hidden="true" | ||
aria-orientation="vertical" | ||
class="sbb-compact-paginator__divider" | ||
orientation="vertical" | ||
role="separator" | ||
> | ||
</sbb-divider> | ||
10 | ||
</span> | ||
</div> | ||
<sbb-screen-reader-only role="status"> | ||
Page 1 selected. | ||
</sbb-screen-reader-only> | ||
`; | ||
/* end snapshot sbb-compact-paginator renders Shadow DOM */ | ||
|
||
snapshots["sbb-compact-paginator renders A11y tree Firefox"] = | ||
`<p> | ||
{ | ||
"role": "document", | ||
"name": "", | ||
"children": [ | ||
{ | ||
"role": "button", | ||
"name": "Previous page", | ||
"disabled": true | ||
}, | ||
{ | ||
"role": "button", | ||
"name": "Next page" | ||
}, | ||
{ | ||
"role": "text leaf", | ||
"name": "1" | ||
}, | ||
{ | ||
"role": "text leaf", | ||
"name": "10" | ||
}, | ||
{ | ||
"role": "text leaf", | ||
"name": "Page 1 selected." | ||
} | ||
] | ||
} | ||
</p> | ||
`; | ||
/* end snapshot sbb-compact-paginator renders A11y tree Firefox */ | ||
|
||
snapshots["sbb-compact-paginator renders A11y tree Chrome"] = | ||
`<p> | ||
{ | ||
"role": "WebArea", | ||
"name": "", | ||
"children": [ | ||
{ | ||
"role": "button", | ||
"name": "Previous page", | ||
"disabled": true | ||
}, | ||
{ | ||
"role": "button", | ||
"name": "Next page" | ||
}, | ||
{ | ||
"role": "text", | ||
"name": "1" | ||
}, | ||
{ | ||
"role": "text", | ||
"name": "10" | ||
}, | ||
{ | ||
"role": "text", | ||
"name": "Page 1 selected." | ||
} | ||
] | ||
} | ||
</p> | ||
`; | ||
/* end snapshot sbb-compact-paginator renders A11y tree Chrome */ | ||
|
45 changes: 45 additions & 0 deletions
45
src/elements/paginator/compact-paginator/compact-paginator.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
@use '../../core/styles' as sbb; | ||
|
||
// Box-sizing rules contained in typography are not traversing Shadow DOM boundaries. We need to include box-sizing mixin in every component. | ||
@include sbb.box-sizing; | ||
|
||
:host { | ||
display: block; | ||
|
||
--sbb-compact-paginator-height: var(--sbb-size-element-m); | ||
--sbb-compact-paginator-color: var(--sbb-color-metal); | ||
--sbb-paginator-compact-justify-content: start; | ||
} | ||
|
||
:host([size='s']) { | ||
--sbb-compact-paginator-height: var(--sbb-size-element-xs); | ||
} | ||
|
||
:host([negative]) { | ||
--sbb-compact-paginator-color: var(--sbb-color-storm); | ||
} | ||
|
||
:host([pager-position='end']) { | ||
--sbb-paginator-compact-justify-content: end; | ||
} | ||
|
||
.sbb-compact-paginator { | ||
display: flex; | ||
gap: var(--sbb-spacing-fixed-5x); | ||
justify-content: var(--sbb-paginator-compact-justify-content); | ||
min-height: var(--sbb-compact-paginator-height); | ||
} | ||
|
||
.sbb-paginator__pages { | ||
@include sbb.text-m--regular; | ||
|
||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
gap: var(--sbb-spacing-fixed-2x); | ||
color: var(--sbb-compact-paginator-color); | ||
} | ||
|
||
.sbb-compact-paginator__divider { | ||
height: #{sbb.px-to-rem-build(16)}; | ||
} |
Oops, something went wrong.