Skip to content

feat(pagination): enable responsive layout #7722

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

Merged
merged 32 commits into from
Sep 18, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { modesDarkDefault } from "../../../.storybook/utils";
import readme from "./readme.md";
import { html } from "../../../support/formatting";
import { storyFilters } from "../../../.storybook/helpers";
import { Scale } from "../interfaces";

export default {
title: "Components/Pagination",
Expand All @@ -27,6 +28,40 @@ export const simple = (): string => html`
</calcite-pagination>
`;

const breakpoints = [475, 476, 768, 1152];

const getResponsiveTemplate = (width: number, scale: Scale) => {
const totalItems = 150000;
const pageSize = 100;
return html`<strong>Width: ${width}px</strong>
<div style="width: ${width}px; margin: 1em 0;">
<calcite-pagination
total-items="${totalItems}"
page-size="${pageSize}"
start-item="1"
scale="${scale}"
></calcite-pagination>
<calcite-pagination
total-items="${totalItems}"
page-size="${pageSize}"
start-item="5400"
scale="${scale}"
></calcite-pagination>
<calcite-pagination
total-items="${totalItems}"
page-size="${pageSize}"
start-item="149901"
scale="${scale}"
></calcite-pagination>
</div>`;
};

export const responsiveSmall = (): string => breakpoints.map((width) => getResponsiveTemplate(width, "s")).join("");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh, I like this very much. We could add a util to make it easier to create all the necessary breakpoint stories across components.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah a util would be nice here 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Taking a stab at this to help with the rest of the responsive component work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool. I think ill wait for it to be merged to update it here.


export const responsiveMedium = (): string => breakpoints.map((width) => getResponsiveTemplate(width, "m")).join("");

export const responsiveLarge = (): string => breakpoints.map((width) => getResponsiveTemplate(width, "l")).join("");

export const darkModeFrenchLocaleAndLargeScaleGetsMediumChevron_TestOnly = (): string => html`
<calcite-pagination
class="calcite-mode-dark"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ import {
import { Scale } from "../interfaces";
import { PaginationMessages } from "./assets/pagination/t9n";
import { CSS } from "./resources";
import { createObserver } from "../../utils/observers";
import { Breakpoints, getBreakpoints } from "../../utils/responsive";

const maxPagesDisplayed = 5;
export interface PaginationDetail {
start: number;
totalItems: number;
Expand Down Expand Up @@ -97,8 +98,17 @@ export class Pagination
// Private Properties
//
// --------------------------------------------------------------------------

@Element() el: HTMLCalcitePaginationElement;

@State() maxPagesDisplayed = 5;

private resizeObserver = createObserver("resize", (entries) =>
entries.forEach(this.resizeHandler)
);

private breakpoints: Breakpoints;

//--------------------------------------------------------------------------
//
// State
Expand Down Expand Up @@ -151,20 +161,24 @@ export class Pagination
connectedCallback(): void {
connectLocalized(this);
connectMessages(this);
this.resizeObserver?.observe(this.el);
}

async componentWillLoad(): Promise<void> {
await setUpMessages(this);
setUpLoadableComponent(this);
this.breakpoints = await getBreakpoints();
}

componentDidLoad(): void {
setComponentLoaded(this);
this.resize(this.el.clientWidth);
}

disconnectedCallback(): void {
disconnectLocalized(this);
disconnectMessages(this);
this.resizeObserver?.disconnect();
}

// --------------------------------------------------------------------------
Expand Down Expand Up @@ -198,6 +212,20 @@ export class Pagination
//
// --------------------------------------------------------------------------

private resize(width: number): void {
const { breakpoints } = this;

if (!breakpoints || !width) {
return;
}

this.maxPagesDisplayed = width < breakpoints.width.xsmall ? 3 : 5;
}

private resizeHandler = ({ contentRect: { width } }: ResizeObserverEntry): void => {
this.resize(width);
};

private getLastStart(): number {
const { totalItems, pageSize } = this;
const lastStart =
Expand All @@ -218,11 +246,15 @@ export class Pagination
};

private showLeftEllipsis() {
return Math.floor(this.startItem / this.pageSize) > 3;
return Math.floor(this.startItem / this.pageSize) > this.maxPagesDisplayed - 2;
}

private showRightEllipsis() {
return (this.totalItems - this.startItem) / this.pageSize > 3;
const singleCenterPage = this.maxPagesDisplayed < 5;
return (
(this.totalItems - this.startItem) / this.pageSize >
this.maxPagesDisplayed - (singleCenterPage ? 0 : 2)
);
}

private emitUpdate() {
Expand All @@ -236,9 +268,11 @@ export class Pagination
//--------------------------------------------------------------------------

renderPages(): VNode[] {
const { maxPagesDisplayed } = this;
const lastStart = this.getLastStart();
let end: number;
let nextStart: number;
const singleCenterPage = maxPagesDisplayed < 5;

// if we don't need ellipses render the whole set
if (this.totalItems / this.pageSize <= maxPagesDisplayed) {
Expand All @@ -248,20 +282,21 @@ export class Pagination
// if we're within max pages of page 1
if (this.startItem / this.pageSize < maxPagesDisplayed - 1) {
nextStart = 1 + this.pageSize;
end = 1 + 4 * this.pageSize;
end = 1 + (maxPagesDisplayed - 1) * this.pageSize;
} else {
// if we're within max pages of last page
if (this.startItem + 3 * this.pageSize >= this.totalItems) {
nextStart = lastStart - 4 * this.pageSize;
nextStart = lastStart - (maxPagesDisplayed - 1) * this.pageSize;
end = lastStart - this.pageSize;
} else {
nextStart = this.startItem - this.pageSize;
end = this.startItem + this.pageSize;
nextStart = this.startItem - (singleCenterPage ? 0 : this.pageSize);
end = this.startItem + (singleCenterPage ? 0 : this.pageSize);
}
}
}

const pages = [];
const pages: number[] = [];

while (nextStart <= end) {
pages.push(nextStart);
nextStart = nextStart + this.pageSize;
Expand Down Expand Up @@ -299,13 +334,13 @@ export class Pagination
}

renderLeftEllipsis(): VNode {
if (this.totalItems / this.pageSize > maxPagesDisplayed && this.showLeftEllipsis()) {
if (this.totalItems / this.pageSize > this.maxPagesDisplayed && this.showLeftEllipsis()) {
return <span class={`${CSS.ellipsis} ${CSS.ellipsisStart}`}>&hellip;</span>;
}
}

renderRightEllipsis(): VNode {
if (this.totalItems / this.pageSize > maxPagesDisplayed && this.showRightEllipsis()) {
if (this.totalItems / this.pageSize > this.maxPagesDisplayed && this.showRightEllipsis()) {
return <span class={`${CSS.ellipsis} ${CSS.ellipsisEnd}`}>&hellip;</span>;
}
}
Expand Down
4 changes: 3 additions & 1 deletion packages/calcite-components/src/utils/responsive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
interface Breakpoints {
export interface Breakpoints {
width: {
large: number;
medium: number;
Expand All @@ -17,6 +17,8 @@ function breakpointTokenToNumericalValue(style: CSSStyleDeclaration, tokenName:
* This util will return a breakpoints lookup object.
*
* Note that the breakpoints will be evaluated at the root and cached for reuse.
*
* @returns {Promise<Breakpoints>} The Breakpoints object.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we were no longer adding types via JSDoc and relying solely on TypeScript. cc @benelan

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My linter always tells me to do this and I do not argue.

Copy link
Member

@jcfranco jcfranco Sep 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always wise not to argue with linters. 🤜💥

Serious talk, we disabled jsdoc/require-param-type some time ago, so I'm wondering if this linting error is coming from somewhere else (maybe a VSCode plugin)? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Thats what I see. I've done npm install on the root.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jcfranco do we have require-returns disabled?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I was about to disable jsdoc/require-returns-type but it looks like Matt beat me to it. Should we also disable jsdoc/require-property-type while we are at it?

*/
export async function getBreakpoints(): Promise<Breakpoints> {
if (getBreakpointsPromise) {
Expand Down