Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sbb-paginator): add sbb-compact-paginator component variant #3142

Merged
merged 26 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
13b0934
feat(sbb-paginator): move to new folder and create common files
DavideMininni-Fincons Oct 2, 2024
81c8dd3
feat: add sbb-compact-paginator
DavideMininni-Fincons Oct 2, 2024
e35cd5f
fix: build
DavideMininni-Fincons Oct 3, 2024
1c6df7c
feat: a11y, add snapshot, minor fixes
DavideMininni-Fincons Oct 4, 2024
3177424
style: fix color
DavideMininni-Fincons Oct 4, 2024
9dce3e7
Merge remote-tracking branch 'origin/main' into feat/sbb-compact-pagi…
DavideMininni-Fincons Oct 7, 2024
bf78482
fix: common logic
DavideMininni-Fincons Oct 7, 2024
9c6a597
Merge remote-tracking branch 'origin/main' into feat/sbb-compact-pagi…
DavideMininni-Fincons Oct 14, 2024
8b6a3e1
fix: review Jeri pt.1
DavideMininni-Fincons Oct 14, 2024
5c85d75
fix: review Jeri pt.2
DavideMininni-Fincons Oct 14, 2024
e458302
fix: accessibility
DavideMininni-Fincons Oct 15, 2024
275abf7
fix: review Manuel
DavideMininni-Fincons Oct 17, 2024
46a4a7f
Merge remote-tracking branch 'origin/main' into feat/sbb-compact-pagi…
DavideMininni-Fincons Oct 17, 2024
9f63644
fix: divider height
DavideMininni-Fincons Oct 18, 2024
2d7f575
fix: divider height
DavideMininni-Fincons Oct 18, 2024
f353dcb
fix: divider height
DavideMininni-Fincons Oct 18, 2024
9cec8fc
fix: divider height
DavideMininni-Fincons Oct 18, 2024
de809ec
fix: snapshot
DavideMininni-Fincons Oct 19, 2024
bcf631c
fix: a11y part 1
DavideMininni-Fincons Oct 21, 2024
41cc91a
fix: tests
DavideMininni-Fincons Oct 21, 2024
249fb57
fix: regen snap
DavideMininni-Fincons Oct 21, 2024
dcb8347
fix: review Mursel pt.2
DavideMininni-Fincons Oct 23, 2024
95a3a4e
Merge remote-tracking branch 'origin/main' into feat/sbb-compact-pagi…
DavideMininni-Fincons Oct 23, 2024
84aa8ca
fix: merge
DavideMininni-Fincons Oct 23, 2024
2ca48ac
fix: review Jeri
DavideMininni-Fincons Oct 23, 2024
380d15b
fix: review Jeri pt.2
DavideMininni-Fincons Oct 24, 2024
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
1 change: 1 addition & 0 deletions src/elements/core/interfaces.ts
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';
6 changes: 6 additions & 0 deletions src/elements/core/interfaces/paginator-page.ts
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;
};
2 changes: 2 additions & 0 deletions src/elements/paginator.ts
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';
1 change: 1 addition & 0 deletions src/elements/paginator/common.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './common/paginator-common.js';
186 changes: 186 additions & 0 deletions src/elements/paginator/common/paginator-common.ts
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>;
}
}
1 change: 1 addition & 0 deletions src/elements/paginator/compact-paginator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './compact-paginator/compact-paginator.js';
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 src/elements/paginator/compact-paginator/compact-paginator.scss
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);
DavideMininni-Fincons marked this conversation as resolved.
Show resolved Hide resolved
--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)};
}
Loading
Loading