Skip to content

Commit

Permalink
Pagination (#11)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Nov 28, 2023
1 parent e2422e5 commit 05fc47c
Show file tree
Hide file tree
Showing 18 changed files with 26,418 additions and 84 deletions.
5 changes: 4 additions & 1 deletion packages/fastui-bootstrap/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { components, ClassNameGenerator, CustomRender, ClassName } from 'fa

import { Modal } from './modal'
import { Navbar } from './navbar'
import { Pagination } from './pagination'

export const customRender: CustomRender = (props) => {
const { type } = props
Expand All @@ -14,6 +15,8 @@ export const customRender: CustomRender = (props) => {
return () => <Navbar {...props} />
case 'Modal':
return () => <Modal {...props} />
case 'Pagination':
return () => <Pagination {...props} />
}
}

Expand All @@ -32,7 +35,7 @@ export const classNameGenerator: ClassNameGenerator = ({ props, fullPath, subEle
case 'Button':
return 'btn btn-primary'
case 'Table':
return 'table table-striped'
return 'table table-striped table-bordered'
case 'Form':
case 'ModelForm':
return formClassName(subElement)
Expand Down
98 changes: 98 additions & 0 deletions packages/fastui-bootstrap/src/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { FC } from 'react'
import { components, ClassName, renderClassName, events } from 'fastui'

interface Link {
Display: FC
ariaLabel?: string
locked?: boolean
active?: boolean
page?: number
}

export const Pagination: FC<components.PaginationProps> = (props) => {
const { page, pageCount } = props

if (pageCount === 1) return null

const links: Link[] = [
{
Display: () => <span aria-hidden="true">&laquo;</span>,
ariaLabel: 'Previous',
locked: page === 1,
page: page - 1,
},
{
Display: () => <>1</>,
locked: page === 1,
active: page === 1,
page: 1,
},
]

if (page > 4) {
links.push({ Display: () => <>...</> })
}

for (let p = page - 2; p <= page + 2; p++) {
if (p <= 1 || p >= pageCount) continue
links.push({
Display: () => <>{p}</>,
locked: page === p,
active: page === p,
page: p,
})
}

if (page < pageCount - 3) {
links.push({ Display: () => <>...</> })
}

links.push({
Display: () => <>{pageCount}</>,
locked: page === pageCount,
page: pageCount,
})

links.push({
Display: () => <span aria-hidden="true">&raquo;</span>,
ariaLabel: 'Next',
locked: page === pageCount,
page: page + 1,
})

return (
<nav aria-label="Pagination">
<ul className="pagination justify-content-center">
{links.map((link, i) => (
<PaginationLink key={i} {...link} />
))}
</ul>
</nav>
)
}

const PaginationLink: FC<Link> = ({ Display, ariaLabel, locked, active, page }) => {
if (!page) {
return (
<li className="page-item">
<span className="page-link px-2 text-muted">
<Display />
</span>
</li>
)
}
const className = renderClassName({ 'page-link': true, disabled: locked && !active, active } as ClassName)
let onClick: events.GoToEvent
if (page === 1) {
onClick = { type: 'go-to', query: { page: null } }
} else {
onClick = { type: 'go-to', query: { page } }
}
return (
<li className="page-item">
<components.LinkRender onClick={onClick} className={className} locked={locked || active} ariaLabel={ariaLabel}>
<Display />
</components.LinkRender>
</li>
)
}
2 changes: 1 addition & 1 deletion packages/fastui/src/components/LinkList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { LinkProps, LinkComp } from './link'
export interface LinkListProps {
type: 'LinkList'
links: LinkProps[]
mode?: 'tabs' | 'vertical'
mode?: 'tabs' | 'vertical' | 'pagination'
className?: ClassName
}

Expand Down
2 changes: 1 addition & 1 deletion packages/fastui/src/components/display.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ const DisplayAuto: FC<{ value: JSONPrimitive }> = ({ value }) => {
return <>&times;</>
}
} else if (typeof value === 'number') {
return <>{value.toString()}</>
return <>{value.toLocaleString()}</>
} else {
return <>{value}</>
}
Expand Down
5 changes: 5 additions & 0 deletions packages/fastui/src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { LinkListProps, LinkListComp } from './LinkList'
import { NavbarProps, NavbarComp } from './navbar'
import { ModalComp, ModalProps } from './modal'
import { TableComp, TableProps } from './table'
import { PaginationProps, PaginationComp } from './pagination'
import {
AllDisplayProps,
DisplayArray,
Expand All @@ -52,6 +53,7 @@ export type {
ButtonProps,
ModalProps,
TableProps,
PaginationProps,
LinkProps,
LinkListProps,
NavbarProps,
Expand Down Expand Up @@ -79,6 +81,7 @@ export type FastProps =
| ButtonProps
| ModalProps
| TableProps
| PaginationProps
| LinkProps
| LinkListProps
| NavbarProps
Expand Down Expand Up @@ -147,6 +150,8 @@ export const AnyComp: FC<FastProps> = (props) => {
return <ModalComp {...props} />
case 'Table':
return <TableComp {...props} />
case 'Pagination':
return <PaginationComp {...props} />
case 'Display':
return <DisplayComp {...props} />
case 'DisplayArray':
Expand Down
33 changes: 21 additions & 12 deletions packages/fastui/src/components/link.tsx
Original file line number Diff line number Diff line change
@@ -1,50 +1,59 @@
import { FC, MouseEventHandler, ReactNode } from 'react'
import { FC, MouseEventHandler, ReactNode, useContext } from 'react'

import { ClassName, useClassName } from '../hooks/className'
import { LocationContext } from '../hooks/locationContext'
import { useFireEvent, AnyEvent } from '../events'

import { FastProps, AnyCompList } from './index'

export interface LinkProps {
type: 'Link'
components: FastProps[]
mode?: 'navbar' | 'tabs' | 'vertical'
mode?: 'navbar' | 'tabs' | 'vertical' | 'pagination'
active?: boolean | string
locked?: boolean
onClick?: AnyEvent
className?: ClassName
}

export const LinkComp: FC<LinkProps> = (props) => (
<LinkRender className={useClassName(props)} onClick={props.onClick}>
<LinkRender className={useClassName(props)} onClick={props.onClick} locked={props.locked}>
<AnyCompList propsList={props.components} />
</LinkRender>
)

interface LinkRenderProps {
children: ReactNode
mode?: 'navbar' | 'tabs' | 'vertical'
active?: boolean | string
locked?: boolean
onClick?: AnyEvent
className?: string
ariaLabel?: string
}

export const LinkRender: FC<LinkRenderProps> = (props) => {
const { className, children, onClick } = props
const { className, ariaLabel, children, onClick, locked } = props

const { fireEvent } = useFireEvent()

let href = '#'
if (onClick && onClick.type === 'go-to') {
href = onClick.url
const { computeQuery } = useContext(LocationContext)

let href = locked ? undefined : '#'
if (!locked && onClick && onClick.type === 'go-to') {
if (onClick.url) {
href = onClick.url
} else if (onClick.query) {
href = computeQuery(onClick.query)
}
}

const clickHandler: MouseEventHandler<HTMLAnchorElement> = (e) => {
e.preventDefault()
fireEvent(onClick)
if (!locked) {
fireEvent(onClick)
}
}

return (
<a href={href} className={className} onClick={clickHandler}>
<a href={href} className={className} onClick={clickHandler} aria-label={ariaLabel} aria-disabled={locked}>
{children}
</a>
)
Expand Down
43 changes: 43 additions & 0 deletions packages/fastui/src/components/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { FC } from 'react'

import { ClassName, useClassName } from '../hooks/className'

import { LinkProps } from './link'
import { LinkListComp } from './LinkList'

export interface PaginationProps {
type: 'Pagination'
page: number
pageSize: number
total: number
pageCount: number
className?: ClassName
}

export const PaginationComp: FC<PaginationProps> = (props) => {
const { page, pageCount } = props
const className = useClassName(props)

if (pageCount === 1) return null

const links: LinkProps[] = [
{
type: 'Link',
components: [{ type: 'Text', text: 'Previous' }],
locked: page !== 1,
onClick: { type: 'go-to', query: { page: page - 1 } },
},
{
type: 'Link',
components: [{ type: 'Text', text: 'Next' }],
locked: page !== pageCount,
onClick: { type: 'go-to', query: { page: page + 1 } },
},
]

return (
<div className={className}>
<LinkListComp type="LinkList" links={links} mode="pagination" />
</div>
)
}
21 changes: 14 additions & 7 deletions packages/fastui/src/components/table.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FC } from 'react'
import { FC, CSSProperties } from 'react'

import type { JsonData } from './Json'

Expand All @@ -14,6 +14,7 @@ interface ColumnProps {
display?: DisplayChoices
title?: string
onClick?: AnyEvent
widthPercent?: number
className?: ClassName
}

Expand All @@ -34,7 +35,9 @@ export const TableComp: FC<TableProps> = (props) => {
<thead>
<tr>
{columns.map((col, id) => (
<th key={id}>{col.title ?? asTitle(col.field)}</th>
<th key={id} style={colWidth(col.widthPercent)}>
{col.title ?? asTitle(col.field)}
</th>
))}
</tr>
</thead>
Expand All @@ -51,6 +54,8 @@ export const TableComp: FC<TableProps> = (props) => {
)
}

const colWidth = (w: number | undefined): CSSProperties | undefined => (w ? { width: `${w}%` } : undefined)

interface CellProps {
row: Row
column: ColumnProps
Expand All @@ -64,11 +69,13 @@ const Cell: FC<CellProps> = ({ row, column }) => {
if (event) {
if (event.type === 'go-to') {
// for go-to events, substitute the row values into the url
const url = subKeys(event.url, row)
if (url === null) {
event = null
} else {
event.url = url
if (event.url) {
const url = subKeys(event.url, row)
if (url === null) {
event = null
} else {
event.url = url
}
}
}
}
Expand Down
10 changes: 8 additions & 2 deletions packages/fastui/src/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export interface PageEvent {

export interface GoToEvent {
type: 'go-to'
url: string
url?: string
query?: Record<string, string | number | null>
}

export interface BackEvent {
Expand Down Expand Up @@ -50,7 +51,12 @@ export function useFireEvent(): { fireEvent: (event?: AnyEvent) => void } {
break
}
case 'go-to':
location.goto(event.url)
if (event.url) {
location.goto(event.url)
}
if (event.query) {
location.setQuery(event.query)
}
break
case 'back':
location.back()
Expand Down
Loading

0 comments on commit 05fc47c

Please sign in to comment.