diff --git a/apps/devfractal-demo/package.json b/apps/devfractal-demo/package.json index 672bfba7..a70bc6e0 100644 --- a/apps/devfractal-demo/package.json +++ b/apps/devfractal-demo/package.json @@ -71,6 +71,7 @@ "react-dom": "18.2.0", "react-error-boundary": "4.0.11", "react-router-dom": "6.16.0", + "react-virtuoso": "4.6.1", "sort-by": "1.2.0", "tailwind-merge": "1.14.0", "tailwindcss-animate": "1.0.7", diff --git a/apps/devfractal-demo/src/main.tsx b/apps/devfractal-demo/src/main.tsx index 71cb63d0..a42fc70d 100644 --- a/apps/devfractal-demo/src/main.tsx +++ b/apps/devfractal-demo/src/main.tsx @@ -3,7 +3,7 @@ import { RouterProvider, createBrowserRouter } from 'react-router-dom' import { queryClient } from '@/queryClient' -import { DataTable } from './server-side/DataTable' +import { DataGridApp } from './server-side/DataGridApp' import './global.css' if (process.env.NODE_ENV === 'development') { @@ -14,7 +14,7 @@ if (process.env.NODE_ENV === 'development') { const router = createBrowserRouter([ { path: '*', - element: , + element: , }, ]) diff --git a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts index 190e7f31..08b6b9ee 100644 --- a/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts +++ b/apps/devfractal-demo/src/mocks/data-table/dataHandlers.ts @@ -1,62 +1,26 @@ -import { fromSearchParams, isArray, isNotNullish, toInt } from 'devfractal' +import { fromSearchParams } from 'devfractal' import { rest } from 'msw' -import { - getSearchedProducts, - getSlicedProducts, - getSortedProducts, -} from './operations' +import { getDataFromTable } from './database' export const dataHandlers = [ rest.get('/api/data/products', (req, res, ctx) => { const queryParams = fromSearchParams(req.url.searchParams) - - if ( - isNotNullish(queryParams.searchBy) && - isNotNullish(queryParams.search) - ) { - return res( - ctx.json( - getSearchedProducts( - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], - queryParams.searchBy, - queryParams.search, - queryParams.sortBy, - queryParams.order, - ), - ), - ) - } - - if (isNotNullish(queryParams.sortBy) && isNotNullish(queryParams.order)) { - return res( - ctx.json( - getSortedProducts( - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], - queryParams.sortBy, - queryParams.order, - ), - ), - ) - } + const { show, column, page, limit, sortBy, order, searchBy, search } = + queryParams return res( ctx.json( - getSlicedProducts( - toInt(queryParams.page), - toInt(queryParams.limit), - isArray(queryParams.column) - ? queryParams.column - : [queryParams.column], - ), + getDataFromTable({ + show, + column, + page, + limit, + sortBy, + order, + searchBy, + search, + }), ), ) }), diff --git a/apps/devfractal-demo/src/mocks/data-table/database.ts b/apps/devfractal-demo/src/mocks/data-table/database.ts new file mode 100644 index 00000000..e9f27db7 --- /dev/null +++ b/apps/devfractal-demo/src/mocks/data-table/database.ts @@ -0,0 +1,232 @@ +import { + any, + chain, + filter, + isArray, + isDefined, + omit$, + paged, + pipe, +} from 'devfractal' + +import { data, type Product } from '@/server-side' + +import { orderBy, picked } from './utils' + +export type ProductsResponse = { + products: Array + currentPage: number + totalPages: number + totalItems: number + columns: string[] +} + +export function getDataFromTable(params: { + show: 'all' | 'paged' + column: string | string[] + page?: number + limit?: number + sortBy?: string + order?: 'asc' | 'desc' + searchBy?: keyof Product | 'all' + search?: string +}): ProductsResponse { + const selected = isArray(params.column) ? params.column : [params.column] + + if ( + params.show === 'all' && + isDefined(params.sortBy) && + isDefined(params.order) && + isDefined(params.searchBy) && + isDefined(params.search) + ) { + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + orderBy(params.sortBy as keyof Product, params.order), + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + + if ( + params.show === 'all' && + isDefined(params.sortBy) && + isDefined(params.order) + ) { + return { + products: chain( + data, + orderBy(params.sortBy as keyof Product, params.order), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + + if ( + params.show === 'all' && + isDefined(params.searchBy) && + isDefined(params.search) + ) { + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + + if (params.show === 'all') { + return { + products: chain(data, picked([...selected] as (keyof Product)[])), + columns: selected, + currentPage: 0, + totalItems: data.length, + totalPages: 0, + } + } + + return getPaginatedProducts({ ...omit$(params, ['show']) }) +} + +function getPaginatedProducts(params: { + column: string | string[] + page?: number + limit?: number + sortBy?: string + order?: 'asc' | 'desc' + searchBy?: keyof Product | 'all' + search?: string +}): ProductsResponse { + const selected = isArray(params.column) ? params.column : [params.column] + + if ( + isDefined(params.page) && + isDefined(params.limit) && + isDefined(params.sortBy) && + isDefined(params.order) && + isDefined(params.searchBy) && + isDefined(params.search) + ) { + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + paged(params.page, params.limit), + orderBy(params.sortBy as keyof Product, params.order), + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + + if ( + isDefined(params.page) && + isDefined(params.limit) && + isDefined(params.sortBy) && + isDefined(params.order) + ) { + return { + products: chain( + data, + paged(params.page, params.limit), + orderBy(params.sortBy as keyof Product, params.order), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + + if ( + isDefined(params.page) && + isDefined(params.limit) && + isDefined(params.searchBy) && + isDefined(params.search) + ) { + const searchKey = params.searchBy + const searchStr = params.search + + return { + products: chain( + data, + paged(params.page, params.limit), + filter(product => hasSearchString(product, searchKey, searchStr)), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + + if (isDefined(params.page) && isDefined(params.limit)) { + return { + products: chain( + data, + paged(params.page, params.limit), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: params.page, + totalItems: data.length, + totalPages: Math.ceil(data.length / params.limit), + } + } + + return { + products: chain( + data, + paged(1, 10), + picked([...selected] as (keyof Product)[]), + ), + columns: selected, + currentPage: 1, + totalItems: data.length, + totalPages: Math.ceil(data.length / 10), + } +} + +function hasSearchString, K extends keyof T>( + obj: T, + key: K | 'all', + text: string, +): boolean { + if (key !== 'all') { + return obj[key].toString().toLowerCase().includes(text.toLowerCase()) + } + + return pipe( + Object.values(obj), + any(value => value.toString().toLowerCase().includes(text.toLowerCase())), + ) +} diff --git a/apps/devfractal-demo/src/mocks/data-table/operations.ts b/apps/devfractal-demo/src/mocks/data-table/operations.ts deleted file mode 100644 index c7a69f7b..00000000 --- a/apps/devfractal-demo/src/mocks/data-table/operations.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { pick$ } from 'devfractal' - -import { data, type Product, type Products } from '@/server-side/products' -import { iorderBy } from '@/server-side/utils' - -export type ProductsResponse = { - products: Products - currentPage: number - totalPages: number - totalItems: number - columns: string[] -} - -const products: Products = [] - -// Load data -const intializeProducts = (): void => { - products.push(...data) -} - -intializeProducts() - -// Operations - -export const getSlicedProducts = ( - page: number, - limit: number, - columns: string[], -): ProductsResponse => { - // @TODO: use paged from array methods - const endIndex = page * limit - const startIndex = endIndex - limit - - const totalPages = Math.ceil(products.length / limit) - - return { - products: products - .slice(startIndex, endIndex) - .map(product => - pick$(product, ['id', ...(columns as (keyof Product)[])]), - ), - currentPage: page, - totalPages, - totalItems: products.length, - columns, - } -} - -export const getSortedProducts = ( - page: number, - limit: number, - columns: string[], - sortKey: keyof Product, - order: 'asc' | 'desc', -): ProductsResponse => { - const slicedResult = getSlicedProducts(page, limit, columns) - - return { - ...slicedResult, - products: iorderBy(slicedResult.products, sortKey, order), - } -} - -// @TODO: Function overloading? - -export const getSearchedProducts = ( - page: number, - limit: number, - columns: string[], - searchBy: keyof Product | 'all', - search: string, - sortKey?: keyof Product, - order?: 'asc' | 'desc', -): ProductsResponse => { - const result = - sortKey && order - ? getSortedProducts(page, limit, columns, sortKey, order) - : getSlicedProducts(page, limit, columns) - - const products = - searchBy !== 'all' - ? result.products.filter(product => - product[searchBy] - .toString() - .toLowerCase() - .includes(search.toLowerCase()), - ) - : result.products.filter(product => - Object.values(product).some(value => - value.toString().toLowerCase().includes(search.toLowerCase()), - ), - ) - - const totalPages = Math.ceil(products.length / limit) - - return { - ...result, - products, - totalPages, - } -} diff --git a/apps/devfractal-demo/src/mocks/data-table/utils.ts b/apps/devfractal-demo/src/mocks/data-table/utils.ts new file mode 100644 index 00000000..0d5af526 --- /dev/null +++ b/apps/devfractal-demo/src/mocks/data-table/utils.ts @@ -0,0 +1,56 @@ +import { all, chain, isDefined, map, pick$, pipe } from 'devfractal' + +type Order = 'asc' | 'desc' + +function cmp(key: K, order: Order) { + return (a: T, b: T): number => { + const x = a[key] + const y = b[key] + + if (x < y) { + return order === 'asc' ? -1 : 1 + } + if (x > y) { + return order === 'asc' ? 1 : -1 + } + return 0 + } +} + +export function iorderBy( + arr: Iterable, + key: K, + order: Order = 'asc', +): T[] { + return [...arr].sort(cmp(key, order)) +} + +export function orderBy( + key: K, + order: Order = 'asc', +) { + return (arr: Iterable) => { + return [...arr].sort(cmp(key, order)) + } +} + +// @TODO: Possible to convert to type guard? +export function areDefined(arr: unknown[]): boolean { + return pipe( + arr, + all(el => isDefined(el)), + ) +} + +export function picked(keys: K[]) { + return (arr: Iterable): Iterable> => { + return chain( + arr, + map(e => pick$(e, [...keys])), + ) + } +} + +export function insertAt(src: readonly T[], index: number, value: T) { + return [...src.slice(0, index), value, ...src.slice(index)] +} diff --git a/apps/devfractal-demo/src/server-side/DataTable.tsx b/apps/devfractal-demo/src/server-side/DataGridApp.tsx similarity index 66% rename from apps/devfractal-demo/src/server-side/DataTable.tsx rename to apps/devfractal-demo/src/server-side/DataGridApp.tsx index 90ec1495..2a56453c 100644 --- a/apps/devfractal-demo/src/server-side/DataTable.tsx +++ b/apps/devfractal-demo/src/server-side/DataGridApp.tsx @@ -1,30 +1,34 @@ import { useQuery } from '@tanstack/react-query' import { - Table, VStack, Text, HStack, toInt, toSearch, remove$, - insert$, fromSearchParams, + isEmptyString, + omit$, + isArray, } from 'devfractal' import { useSearchParams } from 'react-router-dom' -import { DataBody } from './components/DataBody' -import { DataHeader } from './components/DataHeaders' +import { insertAt } from '@/mocks/data-table/utils' + +import { DataTable } from './components/DataTable' import { Header } from './components/Header' import { Pagination } from './components/Pagination' +import { VirtualDataTable } from './components/VirtualDataTable' import { headers } from './products' import { fetchProducts } from './query' // @TODO: Row operations: https://ui.shadcn.com/docs/components/combobox#dropdown-menu // https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-parent-state -export function DataTable(): JSX.Element { +export function DataGridApp(): JSX.Element { const [state, setState] = useSearchParams( toSearch({ + show: 'paged', page: 1, limit: 10, column: ['title', 'price', 'brand', 'category'], @@ -37,6 +41,8 @@ export function DataTable(): JSX.Element { const { isLoading, isSuccess, data } = useQuery({ queryKey: [ 'products', + queryParams.show, + queryParams.show, queryParams.page, queryParams.limit, queryParams.column, @@ -47,8 +53,9 @@ export function DataTable(): JSX.Element { ], queryFn: () => fetchProducts({ - page: toInt(queryParams.page), - limit: toInt(queryParams.limit), + show: queryParams.show, + page: queryParams.page ? toInt(queryParams.page) : undefined, + limit: queryParams.limit ? toInt(queryParams.limit) : undefined, column: queryParams.column, sortBy: queryParams.sortBy, order: queryParams.order, @@ -93,18 +100,33 @@ export function DataTable(): JSX.Element { } function handleSearch(value: { searchBy: string; search: string }) { - setState(toSearch({ ...queryParams, ...value })) + const nextSearchState = isEmptyString(value.search) + ? omit$(queryParams, ['searchBy', 'search']) + : { ...queryParams, ...value } + + setState(toSearch(nextSearchState)) + } + + function handleInfinite() { + const show = queryParams.show === 'all' ? 'paged' : 'all' + const params = + show === 'all' ? omit$(queryParams, ['page', 'limit']) : queryParams + + setState(toSearch({ ...params, show })) } - // @TODO: Improve - add generics may be function handleColumns(header: string) { + const selected = isArray(queryParams.column) + ? queryParams.column + : [queryParams.column] + const index = headers.indexOf(header) - const column = queryParams.column.includes(header) - ? (remove$( - queryParams.column, - queryParams.column.indexOf(header), - ) as string[]) - : insert$(queryParams.column, index, header) + + const column = selected.includes(header) + ? selected.length > 1 + ? remove$(selected, selected.indexOf(header)) + : selected + : insertAt(selected, index, header) setState(toSearch({ ...queryParams, column })) } @@ -121,25 +143,35 @@ export function DataTable(): JSX.Element { onSelect={handleColumns} columns={data.columns} /> - - + ) : ( + - -
+ )} - {/* @TODO: CLean up prop passing */} ) : ( diff --git a/apps/devfractal-demo/src/server-side/components/DataBody.tsx b/apps/devfractal-demo/src/server-side/components/DataBody.tsx deleted file mode 100644 index df450bde..00000000 --- a/apps/devfractal-demo/src/server-side/components/DataBody.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { TableBody, TableCell, TableRow } from 'devfractal' - -// @TODO: Improve generics -export function DataBody< - T extends { id: number; [k: string]: number | string }, ->({ data }: { data: readonly T[] }): JSX.Element { - return ( - - {data.length > 0 ? ( - data.map(product => { - return ( - - {Object.keys(product) - .filter(key => key !== 'id') - .map(item => { - return {product[item]} - })} - - ) - }) - ) : ( - - No data! - - )} - - ) -} diff --git a/apps/devfractal-demo/src/server-side/components/DataTable.tsx b/apps/devfractal-demo/src/server-side/components/DataTable.tsx new file mode 100644 index 00000000..519836dd --- /dev/null +++ b/apps/devfractal-demo/src/server-side/components/DataTable.tsx @@ -0,0 +1,90 @@ +import { faker } from '@faker-js/faker' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, + isArray, + keys, +} from 'devfractal' + +import { HeaderWrapper } from './HeaderWrapper' + +export function DataTable(props: { + data: Array + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + +
+ ) +} + +export function DataHeader({ + headers, + onOrder, + onSearch, +}: { + headers: string[] + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + + {isArray(headers) ? ( + headers.map(header => { + return ( + + + + ) + }) + ) : ( + + + + )} + + + ) +} + +// @TODO: Improve generics +export function DataBody({ data }: { data: Array }): JSX.Element { + return ( + + {data.length > 0 ? ( + data.map(product => { + return ( + + {keys(product) + .filter(key => key !== 'id') + .map(item => { + return {product[item]} + })} + + ) + }) + ) : ( + + No data! + + )} + + ) +} diff --git a/apps/devfractal-demo/src/server-side/components/Header.tsx b/apps/devfractal-demo/src/server-side/components/Header.tsx index af3d8f86..6c52f9f8 100644 --- a/apps/devfractal-demo/src/server-side/components/Header.tsx +++ b/apps/devfractal-demo/src/server-side/components/Header.tsx @@ -28,8 +28,6 @@ export function Header({ return ( - {/* @TODO: shad-cn does not seem to support multiple selections */} - diff --git a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx b/apps/devfractal-demo/src/server-side/components/HeaderWrapper.tsx similarity index 59% rename from apps/devfractal-demo/src/server-side/components/DataHeaders.tsx rename to apps/devfractal-demo/src/server-side/components/HeaderWrapper.tsx index d83aece3..a821277d 100644 --- a/apps/devfractal-demo/src/server-side/components/DataHeaders.tsx +++ b/apps/devfractal-demo/src/server-side/components/HeaderWrapper.tsx @@ -1,56 +1,14 @@ -import { PopoverContent } from '@radix-ui/react-popover' import { Button, Input, Popover, + PopoverContent, PopoverTrigger, - TableHead, - TableHeader, - TableRow, VStack, - isArray, } from 'devfractal' import React from 'react' -export function DataHeader({ - headers, - onOrder, - onSearch, -}: { - headers: string[] - onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void - onSearch: (value: { searchBy: string; search: string }) => void -}): JSX.Element { - return ( - - - {isArray(headers) ? ( - headers.map(header => { - return ( - - - - ) - }) - ) : ( - - - - )} - - - ) -} - -function HeaderWrapper({ +export function HeaderWrapper({ header, onOrder, onSearch, diff --git a/apps/devfractal-demo/src/server-side/components/Pagination.tsx b/apps/devfractal-demo/src/server-side/components/Pagination.tsx index 5bb066b9..79766c86 100644 --- a/apps/devfractal-demo/src/server-side/components/Pagination.tsx +++ b/apps/devfractal-demo/src/server-side/components/Pagination.tsx @@ -1,6 +1,7 @@ import { Button, HStack, + Input, Select, SelectContent, SelectGroup, @@ -8,68 +9,96 @@ import { SelectTrigger, SelectValue, Text, + toInt, } from 'devfractal' // @TODO: Add prop types export function Pagination({ + show, currentPage, totalPages, + totalItems, limit, onSetLimit, onNext, onPrev, onFirst, onLast, + onCheck, }: { + show: 'all' | 'paged' currentPage: number totalPages: number limit: number + totalItems: number onSetLimit: (value: string) => void onNext: () => void onPrev: () => void onFirst: () => void onLast: (last: number) => void + onCheck: () => void }): JSX.Element { return ( - - per page - + {show === 'paged' && ( + + )} - - {currentPage} of {totalPages} - + {show === 'paged' && ( + per page + )} - - - - - + + + Show all {totalItems} + + + {show === 'paged' && ( + + {currentPage} of {totalPages} + + )} + + {show === 'paged' && ( + + + + + + + )} ) } diff --git a/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx new file mode 100644 index 00000000..12112df1 --- /dev/null +++ b/apps/devfractal-demo/src/server-side/components/VirtualDataTable.tsx @@ -0,0 +1,65 @@ +import { Box, isArray, keys } from 'devfractal' +import { TableVirtuoso } from 'react-virtuoso' + +import { HeaderWrapper } from './HeaderWrapper' + +// @TODO: Type mismatch with shad-cn and div-table as components -> Cannot be merged due to inconsistent html semantics +// @TODO: Need to check if only the table body can use virtual table +// @TODO: Fix styling for virtual table +export function VirtualDataTable({ + data, + headers, + onOrder, + onSearch, +}: { + headers: string[] + data: Array + onOrder: (value: { sortBy: string; order: 'asc' | 'desc' }) => void + onSearch: (value: { searchBy: string; search: string }) => void +}): JSX.Element { + return ( + + ( + + {isArray(headers) ? ( + headers.map(header => { + return ( + + + + ) + }) + ) : ( + + + + )} + + )} + itemContent={(_index, product) => ( + <> + {keys(product) + .filter(key => key !== 'id') + .map(item => { + return ( + + {product[item]} + + ) + })} + + )} + /> + + ) +} diff --git a/apps/devfractal-demo/src/server-side/index.ts b/apps/devfractal-demo/src/server-side/index.ts new file mode 100644 index 00000000..ad2af726 --- /dev/null +++ b/apps/devfractal-demo/src/server-side/index.ts @@ -0,0 +1,2 @@ +export * from './DataGridApp' +export * from './products' diff --git a/apps/devfractal-demo/src/server-side/query.ts b/apps/devfractal-demo/src/server-side/query.ts index b36e06b4..55517ce6 100644 --- a/apps/devfractal-demo/src/server-side/query.ts +++ b/apps/devfractal-demo/src/server-side/query.ts @@ -1,10 +1,11 @@ import { isDefined, keys, pick$, urlcat } from 'devfractal' -import type { ProductsResponse } from '@/mocks/data-table/operations' +import type { ProductsResponse } from '@/mocks/data-table/database' export async function fetchProducts(params: { - page: number - limit: number + show: 'all' | 'paged' + page?: number + limit?: number column: string[] sortBy?: string order?: 'asc' | 'desc' diff --git a/apps/devfractal-demo/src/server-side/utils.ts b/apps/devfractal-demo/src/server-side/utils.ts deleted file mode 100644 index e182529f..00000000 --- a/apps/devfractal-demo/src/server-side/utils.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { pick$ } from 'devfractal' - -import { type Product, type Products } from './products' - -type Order = 'asc' | 'desc' - -export function cmp(key: K, order: Order) { - return (a: T, b: T): number => { - const x = a[key] - const y = b[key] - - if (x < y) { - return order === 'asc' ? -1 : 1 - } - if (x > y) { - return order === 'asc' ? 1 : -1 - } - return 0 - } -} - -export function iorderBy( - arr: Iterable, - key: K, - order: Order = 'asc', -): T[] { - return [...arr].sort(cmp(key, order)) -} - -// @TODO: Type safety and server side version -export const getProductsByColumns = (products: Products, columns: string[]) => { - return products.map(product => - pick$(product, ['id', ...(columns as (keyof Product)[])]), - ) -} diff --git a/examples/ui-examples/src/main.tsx b/examples/ui-examples/src/main.tsx index d060717b..6e19ef3e 100644 --- a/examples/ui-examples/src/main.tsx +++ b/examples/ui-examples/src/main.tsx @@ -1,5 +1,4 @@ import { createRoot } from '@srtp/react' - import './global.css' import { H1 } from '@srtp/ui' diff --git a/libs/ui-core/src/cui/ErrorFallback.tsx b/libs/ui-core/src/cui/ErrorFallback.tsx index 6050400b..6e68d8fe 100644 --- a/libs/ui-core/src/cui/ErrorFallback.tsx +++ b/libs/ui-core/src/cui/ErrorFallback.tsx @@ -4,6 +4,7 @@ import { ZodError } from 'zod' import { H2, Paragraph } from '../ui' import { cn } from '../utils' + import { Container } from './Container' import { ZodErrorView } from './ZodErrorView' diff --git a/libs/ui-core/src/cui/Loading.tsx b/libs/ui-core/src/cui/Loading.tsx index 4be6142e..c56c0122 100644 --- a/libs/ui-core/src/cui/Loading.tsx +++ b/libs/ui-core/src/cui/Loading.tsx @@ -1,4 +1,5 @@ import { Loader2 } from 'lucide-react' + import { cn } from '../utils' export type LoadingProps = Readonly<{ diff --git a/libs/ui-core/src/cui/ZodErrorView.tsx b/libs/ui-core/src/cui/ZodErrorView.tsx index cff5492b..c7da1f5a 100644 --- a/libs/ui-core/src/cui/ZodErrorView.tsx +++ b/libs/ui-core/src/cui/ZodErrorView.tsx @@ -2,6 +2,7 @@ import { CastError } from '@srtp/core' import type { ZodError } from 'zod' import { H1, Ul } from '../ui' + import { Container } from './Container' export type ZodErrorProps = { diff --git a/libs/ui/src/pagination/usePagination.test.ts b/libs/ui/src/pagination/usePagination.test.ts index 97ff9bc6..12c6d159 100644 --- a/libs/ui/src/pagination/usePagination.test.ts +++ b/libs/ui/src/pagination/usePagination.test.ts @@ -1,5 +1,6 @@ import { renderHook, act } from '@testing-library/react' import { expect, expectTypeOf, test, describe } from 'vitest' + import { usePagination, type PaginationResult, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14e8a7dc..9c43dd4c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -459,6 +459,9 @@ importers: react-router-dom: specifier: 6.16.0 version: 6.16.0(react-dom@18.2.0)(react@18.2.0) + react-virtuoso: + specifier: 4.6.1 + version: 4.6.1(react-dom@18.2.0)(react@18.2.0) sort-by: specifier: 1.2.0 version: 1.2.0 @@ -24636,6 +24639,17 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-virtuoso@4.6.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-dQq0yOdRjdWIYaiTvUbudqmTodGSdWGt5lVYz3mM07TTEV91yv7eL5Fn3FPEJOA36whScxPWg0GiYBIOZYEIEA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=16 || >=17 || >= 18' + react-dom: '>=16 || >=17 || >= 18' + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-window-infinite-loader@1.0.9(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-5Hg89IdU4Vrp0RT8kZYKeTIxWZYhNkVXeI1HbKo01Vm/Z7qztDvXljwx16sMzsa9yapRJQW3ODZfMUw38SOWHw==} engines: {node: '>8.0.0'}