import React, {useMemo} from "react"; import {Meta} from "@storybook/react"; import {table} from "@nextui-org/theme"; import {User} from "@nextui-org/user"; import {Chip, ChipProps} from "@nextui-org/chip"; import {Button} from "@nextui-org/button"; import {Spinner} from "@nextui-org/spinner"; import {Pagination} from "@nextui-org/pagination"; import {Tooltip} from "@nextui-org/tooltip"; import {EditIcon, DeleteIcon, EyeIcon} from "@nextui-org/shared-icons"; import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll"; import {useAsyncList} from "@react-stately/data"; import useSWR from "swr"; import { Table, TableHeader, TableColumn, TableBody, TableCell, TableRow, TableProps, getKeyValue, } from "../src"; export default { title: "Components/Table", component: Table, argTypes: { color: { control: { type: "select", }, options: ["default", "primary", "secondary", "success", "warning", "danger"], }, layout: { control: { type: "select", }, options: ["auto", "fixed"], }, radius: { control: { type: "select", }, options: ["none", "sm", "md", "lg", "full"], }, shadow: { control: { type: "select", }, options: ["none", "sm", "md", "lg"], }, selectionMode: { control: { type: "select", }, options: ["none", "single", "multiple"], }, isStriped: { control: { type: "boolean", }, }, }, } as Meta; const defaultProps = { ...table.defaultVariants, className: "max-w-lg", }; const rows = [ { key: "1", name: "Tony Reichert", role: "CEO", status: "Active", }, { key: "2", name: "Zoey Lang", role: "Technical Lead", status: "Paused", }, { key: "3", name: "Jane Fisher", role: "Senior Developer", status: "Active", }, { key: "4", name: "William Howard", role: "Community Manager", status: "Vacation", }, ]; const columns = [ { key: "name", label: "NAME", }, { key: "role", label: "ROLE", }, { key: "status", label: "STATUS", }, ]; type SWCharacter = { name: string; height: string; mass: string; birth_year: string; }; const StaticTemplate = (args: TableProps) => ( NAME ROLE STATUS Tony Reichert CEO Active Zoey Lang Technical Lead Paused Jane Fisher Senior Developer Active William Howard Community Manager Vacation
); const EmptyTemplate = (args: TableProps) => ( NAME ROLE STATUS {[]}
); const DynamicTemplate = (args: TableProps) => ( {(column) => {column.label}} {(item) => ( {(columnKey) => {getKeyValue(item, columnKey)}} )}
); const CustomCellTemplate = (args: TableProps) => { const columns = [ {name: "NAME", uid: "name"}, {name: "ROLE", uid: "role"}, {name: "STATUS", uid: "status"}, {name: "ACTIONS", uid: "actions"}, ]; const users = [ { id: 1, name: "Tony Reichert", role: "CEO", team: "Management", status: "active", age: "29", avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", email: "tony.reichert@example.com", }, { id: 2, name: "Zoey Lang", role: "Technical Lead", team: "Development", status: "paused", age: "25", avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d", email: "zoey.lang@example.com", }, { id: 3, name: "Jane Fisher", role: "Senior Developer", team: "Development", status: "active", age: "22", avatar: "https://i.pravatar.cc/150?u=a04258114e29026702d", email: "jane.fisher@example.com", }, { id: 4, name: "William Howard", role: "Community Manager", team: "Marketing", status: "vacation", age: "28", avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d", email: "william.howard@example.com", }, { id: 5, name: "Kristen Copper", role: "Sales Manager", team: "Sales", status: "active", age: "24", avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", email: "kristen.cooper@example.com", }, ]; type User = (typeof users)[number]; const statusColorMap: Record = { active: "success", paused: "danger", vacation: "warning", }; const renderCell = React.useCallback((user: User, columnKey: React.Key) => { const cellValue = user[columnKey as keyof User]; switch (columnKey) { case "name": return ( {user.email} ); case "role": return (

{cellValue}

{user.team}

); case "status": return ( {cellValue} ); case "actions": return (
); default: return cellValue; } }, []); return ( {(column) => ( {column.name} )} {(item) => ( {(columnKey) => {renderCell(item, columnKey)}} )}
); }; const CustomCellWithClassnamesTemplate = (args: TableProps) => { const columns = [ {name: "NAME", uid: "name"}, {name: "ROLE", uid: "role"}, {name: "STATUS", uid: "status"}, {name: "ACTIONS", uid: "actions"}, ]; const users = [ { id: 1, name: "Tony Reichert", role: "CEO", team: "Management", status: "active", age: "29", avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", email: "tony.reichert@example.com", }, { id: 2, name: "Zoey Lang", role: "Technical Lead", team: "Development", status: "paused", age: "25", avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d", email: "zoey.lang@example.com", }, { id: 3, name: "Jane Fisher", role: "Senior Developer", team: "Development", status: "active", age: "22", avatar: "https://i.pravatar.cc/150?u=a04258114e29026702d", email: "jane.fisher@example.com", }, { id: 4, name: "William Howard", role: "Community Manager", team: "Marketing", status: "vacation", age: "28", avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d", email: "william.howard@example.com", }, { id: 5, name: "Kristen Copper", role: "Sales Manager", team: "Sales", status: "active", age: "24", avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", email: "kristen.cooper@example.com", }, ]; type User = (typeof users)[number]; const statusColorMap: Record = { active: "success", paused: "danger", vacation: "warning", }; const renderCell = React.useCallback((user: User, columnKey: React.Key) => { const cellValue = user[columnKey as keyof User]; switch (columnKey) { case "name": return ( {user.email} ); case "role": return (

{cellValue}

{user.team}

); case "status": return ( {cellValue} ); case "actions": return (
); default: return cellValue; } }, []); return ( {(column) => ( {column.name} )} {(item) => ( {(columnKey) => {renderCell(item, columnKey)}} )}
); }; const SortableTemplate = (args: TableProps) => { let list = useAsyncList({ async load({signal}) { let res = await fetch(`https://swapi.py4e.com/api/people/?search`, { signal, }); let json = await res.json(); return { items: json.results, }; }, async sort({items, sortDescriptor}) { return { items: items.sort((a, b) => { let first = a[sortDescriptor.column!]; let second = b[sortDescriptor.column!]; let cmp = (parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1; if (sortDescriptor.direction === "descending") { cmp *= -1; } return cmp; }), }; }, }); return ( Name Height Mass Birth year {(item) => ( {(columnKey) => {getKeyValue(item, columnKey)}} )}
); }; const LoadMoreTemplate = (args: TableProps) => { const [page, setPage] = React.useState(1); let list = useAsyncList({ async load({signal, cursor}) { if (cursor) { setPage((prev) => prev + 1); } // If no cursor is available, then we're loading the first page. // Otherwise, the cursor is the next URL to load, as returned from the previous page. const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal}); let json = await res.json(); return { items: json.results, cursor: json.next, }; }, }); const hasMore = page < 9; return ( ) : null } {...args} > Name Height Mass Birth year {(item) => ( {(columnKey) => {getKeyValue(item, columnKey)}} )}
); }; const PaginatedTemplate = (args: TableProps) => { const [page, setPage] = React.useState(1); const rowsPerPage = 4; const paginatedRows = [ ...rows, { key: "5", name: "Emily Collins", role: "Marketing Manager", status: "Active", }, { key: "6", name: "Brian Kim", role: "Product Manager", status: "Active", }, { key: "7", name: "Laura Thompson", role: "UX Designer", status: "Active", }, { key: "8", name: "Michael Stevens", role: "Data Analyst", status: "Paused", }, { key: "9", name: "Sophia Nguyen", role: "Quality Assurance", status: "Active", }, { key: "10", name: "James Wilson", role: "Front-end Developer", status: "Vacation", }, { key: "11", name: "Ava Johnson", role: "Back-end Developer", status: "Active", }, { key: "12", name: "Isabella Smith", role: "Graphic Designer", status: "Active", }, { key: "13", name: "Oliver Brown", role: "Content Writer", status: "Paused", }, { key: "14", name: "Lucas Jones", role: "Project Manager", status: "Active", }, { key: "15", name: "Grace Davis", role: "HR Manager", status: "Active", }, { key: "16", name: "Elijah Garcia", role: "Network Administrator", status: "Active", }, { key: "17", name: "Emma Martinez", role: "Accountant", status: "Vacation", }, { key: "18", name: "Benjamin Lee", role: "Operations Manager", status: "Active", }, { key: "19", name: "Mia Hernandez", role: "Sales Manager", status: "Paused", }, { key: "20", name: "Daniel Lewis", role: "DevOps Engineer", status: "Active", }, { key: "21", name: "Amelia Clark", role: "Social Media Specialist", status: "Active", }, { key: "22", name: "Jackson Walker", role: "Customer Support", status: "Active", }, { key: "23", name: "Henry Hall", role: "Security Analyst", status: "Active", }, { key: "24", name: "Charlotte Young", role: "PR Specialist", status: "Paused", }, { key: "25", name: "Liam King", role: "Mobile App Developer", status: "Active", }, ]; const pages = Math.ceil(paginatedRows.length / rowsPerPage); const items = React.useMemo(() => { const start = (page - 1) * rowsPerPage; const end = start + rowsPerPage; return paginatedRows.slice(start, end); }, [page, paginatedRows]); return ( setPage(page)} /> } {...args} > NAME ROLE STATUS {(item) => ( {(columnKey) => {getKeyValue(item, columnKey)}} )}
); }; const fetcher = (...args: Parameters) => fetch(...args).then((res) => res.json()); const AsyncPaginatedTemplate = (args: TableProps) => { const [page, setPage] = React.useState(1); const {data, isLoading} = useSWR<{ count: number; results: SWCharacter[]; }>(`https://swapi.py4e.com/api/people?page=${page}`, fetcher, { keepPreviousData: true, }); const rowsPerPage = 10; const pages = useMemo(() => { return data?.count ? Math.ceil(data?.count / rowsPerPage) : 0; }, [data?.count, rowsPerPage]); const loadingState = isLoading || data?.results.length === 0 ? "loading" : "idle"; return ( 0 ? (
setPage(page)} />
) : null } {...args} > Name Height Mass Birth year } loadingState={loadingState} > {(item) => ( {(columnKey) => {getKeyValue(item, columnKey)}} )}
); }; const InfinitePaginationTemplate = (args: TableProps) => { const [hasMore, setHasMore] = React.useState(false); let list = useAsyncList({ async load({signal, cursor}) { // If no cursor is available, then we're loading the first page. // Otherwise, the cursor is the next URL to load, as returned from the previous page. const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal}); let json = await res.json(); setHasMore(json.next !== null); return { items: json.results, cursor: json.next, }; }, }); const [loaderRef, scrollerRef] = useInfiniteScroll({hasMore, onLoadMore: list.loadMore}); return ( ) : null } {...args} > Name Height Mass Birth year {(item) => ( {(columnKey) => {getKeyValue(item, columnKey)}} )}
); }; export const Default = { render: StaticTemplate, args: { ...defaultProps, }, }; export const Dynamic = { render: DynamicTemplate, args: { ...defaultProps, }, }; export const EmptyState = { render: EmptyTemplate, args: { ...defaultProps, }, }; export const NoHeader = { render: StaticTemplate, args: { ...defaultProps, hideHeader: true, }, }; export const CustomCells = { render: CustomCellTemplate, args: { ...defaultProps, className: "max-w-3xl", }, }; export const Striped = { render: StaticTemplate, args: { ...defaultProps, isStriped: true, }, }; export const RemoveWrapper = { render: StaticTemplate, args: { ...defaultProps, classNames: { table: "max-w-lg", }, removeWrapper: true, }, }; export const SingleSelection = { render: StaticTemplate, args: { ...defaultProps, selectionMode: "single", }, }; export const MultipleSelection = { render: StaticTemplate, args: { ...defaultProps, selectionMode: "multiple", color: "secondary", }, }; export const DisabledKeys = { render: StaticTemplate, args: { ...defaultProps, selectionMode: "multiple", disabledKeys: ["2"], color: "warning", }, }; export const DisallowEmptySelection = { render: StaticTemplate, args: { ...defaultProps, disallowEmptySelection: true, color: "primary", defaultSelectedKeys: ["2"], selectionMode: "multiple", }, }; export const Sortable = { render: SortableTemplate, args: { ...defaultProps, }, }; export const LoadMore = { render: LoadMoreTemplate, args: { ...defaultProps, className: "max-w-3xl max-h-auto", }, }; export const Paginated = { render: PaginatedTemplate, args: { ...defaultProps, className: "max-w-lg min-h-[292px]", }, }; export const AsyncPaginated = { render: AsyncPaginatedTemplate, args: { ...defaultProps, className: "max-w-3xl max-h-auto min-h-[400px]", }, }; export const InfinityPagination = { render: InfinitePaginationTemplate, args: { ...defaultProps, className: "max-w-3xl max-h-[440px] min-h-[400px] overflow-auto", }, }; export const HeaderSticky = { render: InfinitePaginationTemplate, args: { ...defaultProps, layout: "fixed", isHeaderSticky: true, className: "max-w-3xl max-h-[440px] min-h-[400px] overflow-auto", }, }; export const CustomWithClassNames = { render: CustomCellWithClassnamesTemplate, args: { ...defaultProps, classNames: { base: ["max-w-3xl", "bg-gradient-to-br", "from-purple-500", "to-indigo-900/90", "shadow-xl"], th: ["bg-transparent", "text-default-700", "border-b", "border-default"], td: [ "py-4", "text-sm", "text-default-700", "border-b", "border-default", "group-data-[last=true]:border-b-0", ], }, }, }; export const DisableAnimation = { render: StaticTemplate, args: { ...defaultProps, selectionMode: "multiple", color: "secondary", disableAnimation: true, }, };