diff --git a/CLA.md b/CLA.md index d460455fe..d0d91223d 100644 --- a/CLA.md +++ b/CLA.md @@ -20,4 +20,4 @@ You accept and agree to the following terms and conditions for your present and 7. Should You wish to submit work that is not Your original creation, You may submit it to Qwikifiers separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". -8. You agree to notify Qwikifiers of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. \ No newline at end of file +8. You agree to notify Qwikifiers of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. diff --git a/apps/website/src/components/menu/menu.tsx b/apps/website/src/components/menu/menu.tsx index 65bf8c72b..df24ff734 100644 --- a/apps/website/src/components/menu/menu.tsx +++ b/apps/website/src/components/menu/menu.tsx @@ -35,6 +35,10 @@ export const Menu = component$(({ onClose$ }) => { { label: 'Toggle', path: `/docs/${appState.theme.toLowerCase()}/toggle` }, { label: 'Tooltip', path: `/docs/${appState.theme.toLowerCase()}/tooltip` }, { label: 'Slider', path: `/docs/${appState.theme.toLowerCase()}/slider` }, + { + label: 'Pagination', + path: `/docs/${appState.theme.toLowerCase()}/pagination`, + }, { label: 'Progress', path: `/docs/${appState.theme.toLowerCase()}/progress`, diff --git a/apps/website/src/routes/docs/daisy/pagination/index.tsx b/apps/website/src/routes/docs/daisy/pagination/index.tsx new file mode 100644 index 000000000..b97b36e56 --- /dev/null +++ b/apps/website/src/routes/docs/daisy/pagination/index.tsx @@ -0,0 +1,22 @@ +import { component$, useStore } from '@builder.io/qwik'; +import { Pagination as DaisyPagination } from '@qwik-ui/theme-daisy'; + +export default component$(() => { + const store = useStore({ + pages: 10, + page: 1, + }); + + return ( +
+

This is the documentation for the Pagination

+ { + store.page = index; + }} + /> +
+ ); +}); diff --git a/apps/website/src/routes/docs/headless/pagination/index.tsx b/apps/website/src/routes/docs/headless/pagination/index.tsx new file mode 100644 index 000000000..34894c309 --- /dev/null +++ b/apps/website/src/routes/docs/headless/pagination/index.tsx @@ -0,0 +1,22 @@ +import { component$, useStore } from '@builder.io/qwik'; +import { Pagination as HeadlessPagination } from '@qwik-ui/headless'; + +export default component$(() => { + const store = useStore({ + pages: 10, + page: 1, + }); + + return ( + <> +

This is the documentation for the Pagination

+ { + store.page = index; + }} + /> + + ); +}); diff --git a/cla-signs/v1/cla.json b/cla-signs/v1/cla.json index cac6d3922..c23de0485 100644 --- a/cla-signs/v1/cla.json +++ b/cla-signs/v1/cla.json @@ -177,4 +177,4 @@ "pullRequestNo": 196 } ] -} \ No newline at end of file +} diff --git a/packages/daisy/src/components/pagination/pagination.tsx b/packages/daisy/src/components/pagination/pagination.tsx new file mode 100644 index 000000000..7393d93d4 --- /dev/null +++ b/packages/daisy/src/components/pagination/pagination.tsx @@ -0,0 +1,63 @@ +import { component$, $, PropFunction, Slot } from '@builder.io/qwik'; +import { + IRenderPaginationItemProps, + Pagination as HeadlessPagination, +} from '@qwik-ui/headless'; +import { Button } from '@qwik-ui/theme-daisy'; + +export interface PaginationProps { + pages: number; + page: number; + onPaging$: PropFunction<(index: number) => void>; +} + +/** + * Pagination + * ---------- + * A pagination component + * first page is 1 + * + * @example + * + */ + +export const RenderPaginationItem = component$( + ({ + 'aria-label': ariaLabel, + disabled, + 'aria-current': ariaCurrent, + onClick$, + key, + value, + }: IRenderPaginationItemProps) => { + return ( + + ); + } +); + +export const Pagination = component$((props: PaginationProps) => { + return ( +
+ { + return ...; + })} + /> +
+ ); +}); diff --git a/packages/daisy/src/index.ts b/packages/daisy/src/index.ts index 7a7852c35..af89ede7b 100644 --- a/packages/daisy/src/index.ts +++ b/packages/daisy/src/index.ts @@ -11,5 +11,6 @@ export * from './components/tabs'; export * from './components/toast/toast'; export * from './components/toggle/toggle'; export * from './components/tooltip/tooltip'; +export * from './components/pagination/pagination'; export * from './components/ratio/radio'; export * from './components/slider/slider'; diff --git a/packages/headless/src/components/pagination/pagination.tsx b/packages/headless/src/components/pagination/pagination.tsx new file mode 100644 index 000000000..4eb9bcc5f --- /dev/null +++ b/packages/headless/src/components/pagination/pagination.tsx @@ -0,0 +1,169 @@ +import { + $, + component$, + PropFunction, + JSXNode, + QRL, + FunctionComponent, + JSXChildren, + Component, + QwikIntrinsicElements, + Slot, + Fragment, +} from '@builder.io/qwik'; +import { JSX } from '@builder.io/qwik/jsx-runtime'; +import { Button as HeadlessButton } from '@qwik-ui/headless'; + +export interface IPaginationProps { + pages: number; + page: number; + onPaging$: PropFunction<(index: number) => void>; + RenderItem?: Component; + RenderDivider?: Component; +} + +export interface IRenderPaginationItemProps { + onClick$: PropFunction<() => void>; + disabled?: boolean; + 'aria-label': string; + 'aria-current'?: boolean; + value: PaginationItemValue; + key?: string | number; +} + +export type PaginationItemValue = 'prev' | 'next' | number; + +export function getPaginationItems(pages: number, page: number) { + // *show which arrows to light up + const canGo = { + prev: page > 1, + next: page < pages, + }; + + // one of first 5 pages -> should show 1, 2, 3, 4, 5 ... [last] + // or if length is less than 5, all pages + if (pages < 4 || page < 5) { + return { + items: Array.from({ length: Math.min(pages, 5) }, (_, i) => i + 1), + after: pages > 5 ? pages : -1, + before: -1, + ...canGo, + }; + } + + // one of last 4 pages -> should show [first] ... [6, 7, 8, 9, 10] + if (Math.abs(page - pages) < 4) { + return { + items: Array.from({ length: 5 }, (_, i) => pages - 4 + i), + before: 1, + after: -1, + ...canGo, + }; + } + + // it's somewhere in the middle + // -> [first] ... [4, 5, 6] ... [last] + return { + items: Array.from({ length: 3 }, (_, i) => page - 1 + i), + before: 1, + after: pages, + ...canGo, + }; +} + +export const RenderPaginationItem = component$( + ({ + 'aria-label': ariaLabel, + disabled, + onClick$, + key, + value, + }: IRenderPaginationItemProps) => { + return ( + + {value} + + ); + } +); + +export const PaginationDivider = component$(() => { + return ...; +}); + +/** + * Pagination + * ---------- + * A pagination component + * first page is 1 + * + * @example + * + */ +export const Pagination = component$( + ({ + RenderItem = RenderPaginationItem, + RenderDivider = PaginationDivider, + onPaging$, + page, + pages, + }: IPaginationProps) => { + const pagi = getPaginationItems(pages, page); + + const _onPaging$ = $((page: number) => { + if (page < 1 || page > pages) return; + onPaging$(page); + }); + + return ( + <> + _onPaging$(page - 1)} + disabled={!pagi.prev} + aria-label="Previous page" + value={'prev'} + /> + {pagi.before !== -1 && ( + <> + _onPaging$(pagi.before)} + aria-label="Page 1" + value={pagi.before} + /> + + + )} + {pagi.items.map((item) => ( + _onPaging$(item)} + aria-label={`Page ${item}`} + aria-current={item === page} + value={item} + /> + ))} + {pagi.after !== -1 && ( + <> + + _onPaging$(pagi.after)} + value={pagi.after} + /> + + )} + _onPaging$(page + 1)} + disabled={!pagi.next} + value={'next'} + /> + + ); + } +); diff --git a/packages/headless/src/index.ts b/packages/headless/src/index.ts index 3f2955bc8..da12653e0 100644 --- a/packages/headless/src/index.ts +++ b/packages/headless/src/index.ts @@ -3,6 +3,7 @@ export * from './components/button/button'; export * from './components/progress/progress'; export * from './components/button-group/button-group'; export * from './components/card'; +export * from './components/pagination/pagination'; export * from './components/collapse/collapse'; export * from './components/drawer'; export * from './components/menu/menu'; diff --git a/packages/website/server/@qwik-city-not-found-paths.js b/packages/website/server/@qwik-city-not-found-paths.js new file mode 100644 index 000000000..2b86c7631 --- /dev/null +++ b/packages/website/server/@qwik-city-not-found-paths.js @@ -0,0 +1,15 @@ +const notFounds = [ + [ + '/', + '\n\n\n \n \n 404 Resource Not Found\n \n \n\n

404 Resource Not Found

\n', + ], +]; +function getNotFound(p) { + for (const r of notFounds) { + if (p.startsWith(r[0])) { + return r[1]; + } + } + return 'Resource Not Found'; +} +export { getNotFound }; diff --git a/packages/website/server/@qwik-city-static-paths.js b/packages/website/server/@qwik-city-static-paths.js new file mode 100644 index 000000000..bf1be73f7 --- /dev/null +++ b/packages/website/server/@qwik-city-static-paths.js @@ -0,0 +1,27 @@ +const staticPaths = new Set([]); +function isStaticPath(method, url) { + if (method.toUpperCase() !== 'GET') { + return false; + } + const p = url.pathname; + if (p.startsWith('/build/')) { + return true; + } + if (p.startsWith('/assets/')) { + return true; + } + if (staticPaths.has(p)) { + return true; + } + if (p.endsWith('/q-data.json')) { + const pWithoutQdata = p.replace(/\/q-data.json$/, ''); + if (staticPaths.has(pWithoutQdata + '/')) { + return true; + } + if (staticPaths.has(pWithoutQdata)) { + return true; + } + } + return false; +} +export { isStaticPath }; diff --git a/packages/website/server/package.json b/packages/website/server/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/packages/website/server/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/packages/website/src/routes/docs/daisy/pagination/index.tsx b/packages/website/src/routes/docs/daisy/pagination/index.tsx new file mode 100644 index 000000000..f665fd2d7 --- /dev/null +++ b/packages/website/src/routes/docs/daisy/pagination/index.tsx @@ -0,0 +1,20 @@ +import { $, component$, useStore } from '@builder.io/qwik'; +import { Pagination } from '@qwik-ui/theme-daisy'; + +export default component$(() => { + const store = useStore({ page: 665 }); + + const incrementCount = $((newValue: number) => { + store.page = newValue; + }); + + return ( +
+

This is the documentation for the Pagination

+
+

Basic Example:

+ +
+
+ ); +}); diff --git a/packages/website/src/routes/docs/headless/pagination/index.tsx b/packages/website/src/routes/docs/headless/pagination/index.tsx new file mode 100644 index 000000000..a24afd6ea --- /dev/null +++ b/packages/website/src/routes/docs/headless/pagination/index.tsx @@ -0,0 +1,20 @@ +import { $, component$, useStore } from '@builder.io/qwik'; +import { Pagination } from '@qwik-ui/headless'; + +export default component$(() => { + const store = useStore({ page: 665 }); + + const incrementCount = $((newValue: number) => { + store.page = newValue; + }); + + return ( +
+

This is the documentation for the Pagination

+
+

Basic Example:

+ +
+
+ ); +});