Skip to content

Commit

Permalink
Pagination icon only and query params (#508)
Browse files Browse the repository at this point in the history
* feat(pagination): add iconOnly for previous/next

* feat(pagination): add queryParams inputs to next/previous, add query params example

* docs(pagination): update query param example code

* feat(pagination): transform page query with numberAttribute and 1 as default

* defaults to 1 when page query is a string (e.g. ?page=spartan)

* docs(pagination): update query params example
  • Loading branch information
marcjulian authored Dec 10, 2024
1 parent be4a87d commit b2c4b37
Show file tree
Hide file tree
Showing 5 changed files with 334 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Component } from '@angular/core';
import {
HlmPaginationContentDirective,
HlmPaginationDirective,
HlmPaginationEllipsisComponent,
HlmPaginationItemDirective,
HlmPaginationLinkDirective,
HlmPaginationNextComponent,
HlmPaginationPreviousComponent,
} from '@spartan-ng/ui-pagination-helm';

@Component({
selector: 'spartan-pagination-icon-only',
standalone: true,
imports: [
HlmPaginationDirective,
HlmPaginationContentDirective,
HlmPaginationItemDirective,
HlmPaginationPreviousComponent,
HlmPaginationNextComponent,
HlmPaginationLinkDirective,
HlmPaginationEllipsisComponent,
],
template: `
<nav hlmPagination>
<ul hlmPaginationContent>
<li hlmPaginationItem>
<hlm-pagination-previous iconOnly="true" link="/components/menubar" />
</li>
<li hlmPaginationItem>
<a hlmPaginationLink link="#">1</a>
</li>
<li hlmPaginationItem>
<a hlmPaginationLink link="#" isActive>2</a>
</li>
<li hlmPaginationItem>
<a hlmPaginationLink link="#">3</a>
</li>
<li hlmPaginationItem>
<hlm-pagination-ellipsis />
</li>
<li hlmPaginationItem>
<hlm-pagination-next iconOnly="true" link="/components/popover" />
</li>
</ul>
</nav>
`,
})
export class PaginationIconOnlyComponent {}

export const iconOnlyCode = `
import { Component } from '@angular/core';
import {
HlmPaginationContentDirective,
HlmPaginationDirective,
HlmPaginationEllipsisComponent,
HlmPaginationItemDirective,
HlmPaginationLinkDirective,
HlmPaginationNextComponent,
HlmPaginationPreviousComponent,
} from '@spartan-ng/ui-pagination-helm';
@Component({
selector: 'spartan-pagination-icon-only',
standalone: true,
imports: [
HlmPaginationDirective,
HlmPaginationContentDirective,
HlmPaginationItemDirective,
HlmPaginationPreviousComponent,
HlmPaginationNextComponent,
HlmPaginationLinkDirective,
HlmPaginationEllipsisComponent,
],
template: \`
<nav hlmPagination>
<ul hlmPaginationContent>
<li hlmPaginationItem>
<hlm-pagination-previous iconOnly="true" link="/components/menubar" />
</li>
<li hlmPaginationItem>
<a hlmPaginationLink link="#">1</a>
</li>
<li hlmPaginationItem>
<a hlmPaginationLink link="#" isActive>2</a>
</li>
<li hlmPaginationItem>
<a hlmPaginationLink link="#">3</a>
</li>
<li hlmPaginationItem>
<hlm-pagination-ellipsis />
</li>
<li hlmPaginationItem>
<hlm-pagination-next iconOnly="true" link="/components/popover" />
</li>
</ul>
</nav>
\`,
})
export class PaginationIconOnlyComponent {}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { Component, computed, inject, numberAttribute } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import {
HlmPaginationContentDirective,
HlmPaginationDirective,
HlmPaginationItemDirective,
HlmPaginationLinkDirective,
HlmPaginationNextComponent,
HlmPaginationPreviousComponent,
} from '@spartan-ng/ui-pagination-helm';
import { map } from 'rxjs';

@Component({
selector: 'spartan-pagination-query-params',
standalone: true,
imports: [
HlmPaginationDirective,
HlmPaginationContentDirective,
HlmPaginationItemDirective,
HlmPaginationPreviousComponent,
HlmPaginationNextComponent,
HlmPaginationLinkDirective,
],
template: `
<nav hlmPagination>
<ul hlmPaginationContent>
@if (currentPage() > 1) {
<li hlmPaginationItem>
<hlm-pagination-previous link="." [queryParams]="{ page: currentPage() - 1 }" queryParamsHandling="merge" />
</li>
}
@for (page of pages; track 'page_' + page) {
<li hlmPaginationItem>
<a
hlmPaginationLink
[link]="currentPage() !== page ? '.' : undefined"
[queryParams]="{ page }"
queryParamsHandling="merge"
[isActive]="currentPage() === page"
>
{{ page }}
</a>
</li>
}
@if (currentPage() < pages[pages.length - 1]) {
<li hlmPaginationItem>
<hlm-pagination-next link="." [queryParams]="{ page: currentPage() + 1 }" queryParamsHandling="merge" />
</li>
}
</ul>
</nav>
`,
})
export class PaginationQueryParamsComponent {
private readonly _route = inject(ActivatedRoute);

/**
* Alternative would be to enable `withComponentInputBinding` in `provideRouter`.
* Than you can bind `input` signal to the query param.
*
* ```ts
* pageQuery = input<number, NumberInput>(1, {
* alias: 'page',
* transform: (value) => numberAttribute(value, 1),
* });
* ```
*
* This can replace `_pageQuery` and `currentPage` computed property.
*/
private readonly _pageQuery = toSignal(
this._route.queryParamMap.pipe(
map((params) => {
const pageQuery = params.get('page');
return pageQuery ? numberAttribute(pageQuery, 1) : undefined;
}),
),
);

public currentPage = computed(() => this._pageQuery() ?? 1);

public pages = [1, 2, 3, 4];
}

export const queryParamsCode = `
import { Component, computed, inject, numberAttribute } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import {
HlmPaginationContentDirective,
HlmPaginationDirective,
HlmPaginationItemDirective,
HlmPaginationLinkDirective,
HlmPaginationNextComponent,
HlmPaginationPreviousComponent,
} from '@spartan-ng/ui-pagination-helm';
import { map } from 'rxjs';
@Component({
selector: 'spartan-pagination-query-params',
standalone: true,
imports: [
HlmPaginationDirective,
HlmPaginationContentDirective,
HlmPaginationItemDirective,
HlmPaginationPreviousComponent,
HlmPaginationNextComponent,
HlmPaginationLinkDirective,
],
template: \`
<nav hlmPagination>
<ul hlmPaginationContent>
@if (currentPage() > 1) {
<li hlmPaginationItem>
<hlm-pagination-previous link="." [queryParams]="{ page: currentPage() - 1 }" queryParamsHandling="merge" />
</li>
}
@for (page of pages; track 'page_' + page) {
<li hlmPaginationItem>
<a
hlmPaginationLink
[link]="currentPage() !== page ? '.' : undefined"
[queryParams]="{ page }"
queryParamsHandling="merge"
[isActive]="currentPage() === page"
>
{{ page }}
</a>
</li>
}
@if (currentPage() < pages[pages.length - 1]) {
<li hlmPaginationItem>
<hlm-pagination-next link="." [queryParams]="{ page: currentPage() + 1 }" queryParamsHandling="merge" />
</li>
}
</ul>
</nav>
\`,
})
export class PaginationQueryParamsComponent {
private readonly _route = inject(ActivatedRoute);
/**
* Alternative would be to enable \`withComponentInputBinding\` in \`provideRouter\`.
* Than you can bind \`input\` signal to the query param.
*
* \`\`\`ts
* pageQuery = input<number, NumberInput>(1, {
* alias: 'page',
* transform: (value) => numberAttribute(value, 1),
* });
* \`\`\`
*
* This can replace \`_pageQuery\` and \`currentPage\` computed property.
*/
private readonly _pageQuery = toSignal(
this._route.queryParamMap.pipe(
map((params) => {
const pageQuery = params.get('page');
return pageQuery ? numberAttribute(pageQuery, 1) : undefined;
}),
),
);
public currentPage = computed(() => this._pageQuery() ?? 1);
public pages = [1, 2, 3, 4];
}
`;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { hlmH4 } from '@spartan-ng/ui-typography-helm';
import { CodePreviewDirective } from '../../../../shared/code/code-preview.directive';
import { CodeComponent } from '../../../../shared/code/code.component';
import { MainSectionDirective } from '../../../../shared/layout/main-section.directive';
import { PageBottomNavPlaceholderComponent } from '../../../../shared/layout/page-bottom-nav-placeholder.component';
import { PageBottomNavLinkComponent } from '../../../../shared/layout/page-bottom-nav/page-bottom-nav-link.component';
import { PageBottomNavComponent } from '../../../../shared/layout/page-bottom-nav/page-bottom-nav.component';
import { PageNavComponent } from '../../../../shared/layout/page-nav/page-nav.component';
Expand All @@ -14,6 +13,8 @@ import { TabsCliComponent } from '../../../../shared/layout/tabs-cli.component';
import { TabsComponent } from '../../../../shared/layout/tabs.component';
import { metaWith } from '../../../../shared/meta/meta.util';
import { PaginationAdvancedComponent, advancedCode } from './pagination--advanced.example';
import { PaginationIconOnlyComponent, iconOnlyCode } from './pagination--icon-only.example';
import { PaginationQueryParamsComponent, queryParamsCode } from './pagination--query-params.example';
import { PaginationPreviewComponent, defaultCode, defaultImports, defaultSkeleton } from './pagination.preview';

export const routeMeta: RouteMeta = {
Expand All @@ -36,8 +37,9 @@ export const routeMeta: RouteMeta = {
PageNavComponent,
PageBottomNavComponent,
PageBottomNavLinkComponent,
PageBottomNavPlaceholderComponent,
PaginationPreviewComponent,
PaginationQueryParamsComponent,
PaginationIconOnlyComponent,
PaginationAdvancedComponent,
],
template: `
Expand Down Expand Up @@ -65,6 +67,20 @@ export const routeMeta: RouteMeta = {
</div>
<spartan-section-sub-heading id="examples">Examples</spartan-section-sub-heading>
<h3 id="examples__default" class="${hlmH4} mb-2 mt-6">Query Params</h3>
<spartan-tabs firstTab="Preview" secondTab="Code">
<div spartanCodePreview firstTab>
<spartan-pagination-query-params />
</div>
<spartan-code secondTab [code]="queryParamsCode" />
</spartan-tabs>
<h3 id="examples__default" class="${hlmH4} mb-2 mt-6">Icon Only (Previous/Next)</h3>
<spartan-tabs firstTab="Preview" secondTab="Code">
<div spartanCodePreview firstTab>
<spartan-pagination-icon-only />
</div>
<spartan-code secondTab [code]="iconOnlyCode" />
</spartan-tabs>
<h3 id="examples__default" class="${hlmH4} mb-2 mt-6">Advanced Pagination</h3>
<spartan-tabs firstTab="Preview" secondTab="Code">
<div spartanCodePreview firstTab>
Expand All @@ -86,5 +102,7 @@ export default class PaginationPageComponent {
protected readonly defaultSkeleton = defaultSkeleton;
protected readonly defaultImports = defaultImports;

protected readonly queryParamsCode = queryParamsCode;
protected readonly iconOnlyCode = iconOnlyCode;
protected readonly advancedCode = advancedCode;
}
25 changes: 21 additions & 4 deletions libs/ui/pagination/helm/src/lib/hlm-pagination-next.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Component, computed, input } from '@angular/core';
import { BooleanInput } from '@angular/cdk/coercion';
import { booleanAttribute, Component, computed, input } from '@angular/core';
import { RouterLink } from '@angular/router';
import { lucideChevronRight } from '@ng-icons/lucide';
import { ButtonVariants } from '@spartan-ng/ui-button-helm';
import { hlm } from '@spartan-ng/ui-core';
import { HlmIconComponent, provideIcons } from '@spartan-ng/ui-icon-helm';
import { ClassValue } from 'clsx';
Expand All @@ -12,18 +14,33 @@ import { HlmPaginationLinkDirective } from './hlm-pagination-link.directive';
imports: [HlmPaginationLinkDirective, HlmIconComponent],
providers: [provideIcons({ lucideChevronRight })],
template: `
<a [class]="_computedClass()" hlmPaginationLink [link]="link()" size="default" [attr.aria-label]="ariaLabel()">
<span>{{ text() }}</span>
<a
[class]="_computedClass()"
hlmPaginationLink
[link]="link()"
[queryParams]="queryParams()"
[queryParamsHandling]="queryParamsHandling()"
[size]="size()"
[attr.aria-label]="ariaLabel()"
>
<span [class.sr-only]="iconOnly()">{{ text() }}</span>
<hlm-icon size="sm" name="lucideChevronRight" />
</a>
`,
})
export class HlmPaginationNextComponent {
public readonly userClass = input<ClassValue>('', { alias: 'class' });
public readonly link = input<RouterLink['routerLink']>();
public readonly queryParams = input<RouterLink['queryParams']>();
public readonly queryParamsHandling = input<RouterLink['queryParamsHandling']>();

public readonly ariaLabel = input<string>('Go to next page', { alias: 'aria-label' });
public readonly text = input<string>('Next');
public readonly iconOnly = input<boolean, BooleanInput>(false, {
transform: booleanAttribute,
});

protected readonly _computedClass = computed(() => hlm('gap-1 pr-2.5', this.userClass()));
protected readonly size = computed<ButtonVariants['size']>(() => (this.iconOnly() ? 'icon' : 'default'));

protected readonly _computedClass = computed(() => hlm('gap-1', !this.iconOnly() ? 'pr-2.5' : '', this.userClass()));
}
Loading

0 comments on commit b2c4b37

Please sign in to comment.