diff --git a/apps/docs/content/components/table/async-pagination.raw.jsx b/apps/docs/content/components/table/async-pagination.raw.jsx new file mode 100644 index 0000000000..266a97e842 --- /dev/null +++ b/apps/docs/content/components/table/async-pagination.raw.jsx @@ -0,0 +1,69 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Pagination, + Spinner, + getKeyValue, +} from "@nextui-org/react"; +import useSWR from "swr"; + +const fetcher = (...args) => fetch(...args).then((res) => res.json()); + +export default function App() { + const [page, setPage] = React.useState(1); + + const {data, isLoading} = useSWR(`https://swapi.py4e.com/api/people?page=${page}`, fetcher, { + keepPreviousData: true, + }); + + const rowsPerPage = 10; + + const pages = React.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 + } + > + + Name + Height + Mass + Birth year + + } + loadingState={loadingState} + > + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/async-pagination.ts b/apps/docs/content/components/table/async-pagination.ts index f5c51a3c4f..f59ac27a4c 100644 --- a/apps/docs/content/components/table/async-pagination.ts +++ b/apps/docs/content/components/table/async-pagination.ts @@ -1,62 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Pagination, Spinner, getKeyValue} from "@nextui-org/react"; -import useSWR from "swr"; - -const fetcher = (...args) => fetch(...args).then((res) => res.json()); - -export default function App() { - const [page, setPage] = React.useState(1); - - const {data, isLoading} = useSWR(\`https://swapi.py4e.com/api/people?page=\$\{page\}\`, fetcher, { - keepPreviousData: true, - }); - - const rowsPerPage = 10; - - const pages = React.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 - } - > - - Name - Height - Mass - Birth year - - } - loadingState={loadingState} - > - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./async-pagination.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/controlled-selection.raw.jsx b/apps/docs/content/components/table/controlled-selection.raw.jsx new file mode 100644 index 0000000000..3ee296f7b1 --- /dev/null +++ b/apps/docs/content/components/table/controlled-selection.raw.jsx @@ -0,0 +1,75 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, +} from "@nextui-org/react"; + +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", + }, +]; + +export default function App() { + const [selectedKeys, setSelectedKeys] = React.useState(new Set(["2"])); + + return ( + + + {(column) => {column.label}} + + + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/controlled-selection.ts b/apps/docs/content/components/table/controlled-selection.ts index 0a54c2bd26..b141b81895 100644 --- a/apps/docs/content/components/table/controlled-selection.ts +++ b/apps/docs/content/components/table/controlled-selection.ts @@ -1,70 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue} from "@nextui-org/react"; - -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", - }, -]; - -export default function App() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set(["2"])); - - return ( - - - {(column) => {column.label}} - - - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./controlled-selection.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/custom-cells.raw.jsx b/apps/docs/content/components/table/custom-cells.raw.jsx new file mode 100644 index 0000000000..e29158daeb --- /dev/null +++ b/apps/docs/content/components/table/custom-cells.raw.jsx @@ -0,0 +1,271 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + User, + Chip, + Tooltip, +} from "@nextui-org/react"; + +export const columns = [ + {name: "NAME", uid: "name"}, + {name: "ROLE", uid: "role"}, + {name: "STATUS", uid: "status"}, + {name: "ACTIONS", uid: "actions"}, +]; + +export 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", + }, +]; + +export const EyeIcon = (props) => { + return ( + + ); +}; + +export const DeleteIcon = (props) => { + return ( + + ); +}; + +export const EditIcon = (props) => { + return ( + + ); +}; + +const statusColorMap = { + active: "success", + paused: "danger", + vacation: "warning", +}; + +export default function App() { + const renderCell = React.useCallback((user, columnKey) => { + const cellValue = user[columnKey]; + + 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)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/custom-cells.raw.tsx b/apps/docs/content/components/table/custom-cells.raw.tsx new file mode 100644 index 0000000000..46ff88e48c --- /dev/null +++ b/apps/docs/content/components/table/custom-cells.raw.tsx @@ -0,0 +1,278 @@ +import React, {SVGProps} from "react"; +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + User, + Chip, + Tooltip, + ChipProps, +} from "@nextui-org/react"; + +export type IconSvgProps = SVGProps & { + size?: number; +}; + +export const columns = [ + {name: "NAME", uid: "name"}, + {name: "ROLE", uid: "role"}, + {name: "STATUS", uid: "status"}, + {name: "ACTIONS", uid: "actions"}, +]; + +export 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", + }, +]; + +export const EyeIcon = (props: IconSvgProps) => { + return ( + + ); +}; + +export const DeleteIcon = (props: IconSvgProps) => { + return ( + + ); +}; + +export const EditIcon = (props: IconSvgProps) => { + return ( + + ); +}; +const statusColorMap: Record = { + active: "success", + paused: "danger", + vacation: "warning", +}; + +type User = (typeof users)[0]; + +export default function App() { + 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)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/custom-cells.ts b/apps/docs/content/components/table/custom-cells.ts index 92680efd5f..9b7f207298 100644 --- a/apps/docs/content/components/table/custom-cells.ts +++ b/apps/docs/content/components/table/custom-cells.ts @@ -1,366 +1,12 @@ -const data = `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", - }, -]; - -export {columns, users};`; - -const EyeIcon = `export const EyeIcon = (props) => ( - -);`; - -const DeleteIcon = `export const DeleteIcon = (props) => ( - -);`; - -const EditIcon = `export const EditIcon = (props) => ( - -);`; - -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, User, Chip, Tooltip, getKeyValue} from "@nextui-org/react"; -import {EditIcon} from "./EditIcon"; -import {DeleteIcon} from "./DeleteIcon"; -import {EyeIcon} from "./EyeIcon"; -import {columns, users} from "./data"; - -const statusColorMap = { - active: "success", - paused: "danger", - vacation: "warning", -}; - -export default function App() { - const renderCell = React.useCallback((user, columnKey) => { - const cellValue = user[columnKey]; - - 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 AppTs = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, User, Chip, Tooltip, ChipProps, getKeyValue} from "@nextui-org/react"; -import {EditIcon} from "./EditIcon"; -import {DeleteIcon} from "./DeleteIcon"; -import {EyeIcon} from "./EyeIcon"; -import {columns, users} from "./data"; - -const statusColorMap: Record = { - active: "success", - paused: "danger", - vacation: "warning", -}; - -type User = typeof users[0]; - -export default function App() { - 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)}} - - )} - -
- ); -}`; +import App from "./custom-cells.raw.jsx?raw"; +import AppTs from "./custom-cells.raw.tsx?raw"; const react = { "/App.jsx": App, - "/data.js": data, - "/EditIcon.jsx": EditIcon, - "/DeleteIcon.jsx": DeleteIcon, - "/EyeIcon.jsx": EyeIcon, }; const reactTs = { "/App.tsx": AppTs, - "/data.js": data, - "/EditIcon.jsx": EditIcon, - "/DeleteIcon.jsx": DeleteIcon, - "/EyeIcon.jsx": EyeIcon, }; export default { diff --git a/apps/docs/content/components/table/custom-styles.raw.jsx b/apps/docs/content/components/table/custom-styles.raw.jsx new file mode 100644 index 0000000000..f00049bb6f --- /dev/null +++ b/apps/docs/content/components/table/custom-styles.raw.jsx @@ -0,0 +1,662 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Input, + Button, + DropdownTrigger, + Dropdown, + DropdownMenu, + DropdownItem, + Chip, + User, + Pagination, +} from "@nextui-org/react"; + +export const columns = [ + {name: "ID", uid: "id", sortable: true}, + {name: "NAME", uid: "name", sortable: true}, + {name: "AGE", uid: "age", sortable: true}, + {name: "ROLE", uid: "role", sortable: true}, + {name: "TEAM", uid: "team"}, + {name: "EMAIL", uid: "email"}, + {name: "STATUS", uid: "status", sortable: true}, + {name: "ACTIONS", uid: "actions"}, +]; + +export const statusOptions = [ + {name: "Active", uid: "active"}, + {name: "Paused", uid: "paused"}, + {name: "Vacation", uid: "vacation"}, +]; + +export 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: "Tech 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: "Sr. Dev", + 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: "C.M.", + 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: "S. Manager", + team: "Sales", + status: "active", + age: "24", + avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", + email: "kristen.cooper@example.com", + }, + { + id: 6, + name: "Brian Kim", + role: "P. Manager", + team: "Management", + age: "29", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", + email: "brian.kim@example.com", + status: "active", + }, + { + id: 7, + name: "Michael Hunt", + role: "Designer", + team: "Design", + status: "paused", + age: "27", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d", + email: "michael.hunt@example.com", + }, + { + id: 8, + name: "Samantha Brooks", + role: "HR Manager", + team: "HR", + status: "active", + age: "31", + avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d", + email: "samantha.brooks@example.com", + }, + { + id: 9, + name: "Frank Harrison", + role: "F. Manager", + team: "Finance", + status: "vacation", + age: "33", + avatar: "https://i.pravatar.cc/150?img=4", + email: "frank.harrison@example.com", + }, + { + id: 10, + name: "Emma Adams", + role: "Ops Manager", + team: "Operations", + status: "active", + age: "35", + avatar: "https://i.pravatar.cc/150?img=5", + email: "emma.adams@example.com", + }, + { + id: 11, + name: "Brandon Stevens", + role: "Jr. Dev", + team: "Development", + status: "active", + age: "22", + avatar: "https://i.pravatar.cc/150?img=8", + email: "brandon.stevens@example.com", + }, + { + id: 12, + name: "Megan Richards", + role: "P. Manager", + team: "Product", + status: "paused", + age: "28", + avatar: "https://i.pravatar.cc/150?img=10", + email: "megan.richards@example.com", + }, + { + id: 13, + name: "Oliver Scott", + role: "S. Manager", + team: "Security", + status: "active", + age: "37", + avatar: "https://i.pravatar.cc/150?img=12", + email: "oliver.scott@example.com", + }, + { + id: 14, + name: "Grace Allen", + role: "M. Specialist", + team: "Marketing", + status: "active", + age: "30", + avatar: "https://i.pravatar.cc/150?img=16", + email: "grace.allen@example.com", + }, + { + id: 15, + name: "Noah Carter", + role: "IT Specialist", + team: "I. Technology", + status: "paused", + age: "31", + avatar: "https://i.pravatar.cc/150?img=15", + email: "noah.carter@example.com", + }, + { + id: 16, + name: "Ava Perez", + role: "Manager", + team: "Sales", + status: "active", + age: "29", + avatar: "https://i.pravatar.cc/150?img=20", + email: "ava.perez@example.com", + }, + { + id: 17, + name: "Liam Johnson", + role: "Data Analyst", + team: "Analysis", + status: "active", + age: "28", + avatar: "https://i.pravatar.cc/150?img=33", + email: "liam.johnson@example.com", + }, + { + id: 18, + name: "Sophia Taylor", + role: "QA Analyst", + team: "Testing", + status: "active", + age: "27", + avatar: "https://i.pravatar.cc/150?img=29", + email: "sophia.taylor@example.com", + }, + { + id: 19, + name: "Lucas Harris", + role: "Administrator", + team: "Information Technology", + status: "paused", + age: "32", + avatar: "https://i.pravatar.cc/150?img=50", + email: "lucas.harris@example.com", + }, + { + id: 20, + name: "Mia Robinson", + role: "Coordinator", + team: "Operations", + status: "active", + age: "26", + avatar: "https://i.pravatar.cc/150?img=45", + email: "mia.robinson@example.com", + }, +]; + +export function capitalize(s) { + return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : ""; +} + +export const PlusIcon = ({size = 24, width, height, ...props}) => { + return ( + + ); +}; + +export const VerticalDotsIcon = ({size = 24, width, height, ...props}) => { + return ( + + ); +}; + +export const SearchIcon = (props) => { + return ( + + ); +}; + +export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}) => { + return ( + + ); +}; + +const statusColorMap = { + active: "success", + paused: "danger", + vacation: "warning", +}; + +const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; + +export default function App() { + const [filterValue, setFilterValue] = React.useState(""); + const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); + const [visibleColumns, setVisibleColumns] = React.useState(new Set(INITIAL_VISIBLE_COLUMNS)); + const [statusFilter, setStatusFilter] = React.useState("all"); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [sortDescriptor, setSortDescriptor] = React.useState({ + column: "age", + direction: "ascending", + }); + const [page, setPage] = React.useState(1); + + const pages = Math.ceil(users.length / rowsPerPage); + + const hasSearchFilter = Boolean(filterValue); + + const headerColumns = React.useMemo(() => { + if (visibleColumns === "all") return columns; + + return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); + }, [visibleColumns]); + + const filteredItems = React.useMemo(() => { + let filteredUsers = [...users]; + + if (hasSearchFilter) { + filteredUsers = filteredUsers.filter((user) => + user.name.toLowerCase().includes(filterValue.toLowerCase()), + ); + } + if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { + filteredUsers = filteredUsers.filter((user) => + Array.from(statusFilter).includes(user.status), + ); + } + + return filteredUsers; + }, [users, filterValue, statusFilter]); + + const items = React.useMemo(() => { + const start = (page - 1) * rowsPerPage; + const end = start + rowsPerPage; + + return filteredItems.slice(start, end); + }, [page, filteredItems, rowsPerPage]); + + const sortedItems = React.useMemo(() => { + return [...items].sort((a, b) => { + const first = a[sortDescriptor.column]; + const second = b[sortDescriptor.column]; + const cmp = first < second ? -1 : first > second ? 1 : 0; + + return sortDescriptor.direction === "descending" ? -cmp : cmp; + }); + }, [sortDescriptor, items]); + + const renderCell = React.useCallback((user, columnKey) => { + const cellValue = user[columnKey]; + + switch (columnKey) { + case "name": + return ( + + {user.email} + + ); + case "role": + return ( +
+

{cellValue}

+

{user.team}

+
+ ); + case "status": + return ( + + {cellValue} + + ); + case "actions": + return ( +
+ + + + + + View + Edit + Delete + + +
+ ); + default: + return cellValue; + } + }, []); + + const onRowsPerPageChange = React.useCallback((e) => { + setRowsPerPage(Number(e.target.value)); + setPage(1); + }, []); + + const onSearchChange = React.useCallback((value) => { + if (value) { + setFilterValue(value); + setPage(1); + } else { + setFilterValue(""); + } + }, []); + + const topContent = React.useMemo(() => { + return ( +
+
+ } + value={filterValue} + variant="bordered" + onClear={() => setFilterValue("")} + onValueChange={onSearchChange} + /> +
+ + + + + + {statusOptions.map((status) => ( + + {capitalize(status.name)} + + ))} + + + + + + + + {columns.map((column) => ( + + {capitalize(column.name)} + + ))} + + + +
+
+
+ Total {users.length} users + +
+
+ ); + }, [ + filterValue, + statusFilter, + visibleColumns, + onSearchChange, + onRowsPerPageChange, + users.length, + hasSearchFilter, + ]); + + const bottomContent = React.useMemo(() => { + return ( +
+ + + {selectedKeys === "all" + ? "All items selected" + : `${selectedKeys.size} of ${items.length} selected`} + +
+ ); + }, [selectedKeys, items.length, page, pages, hasSearchFilter]); + + const classNames = React.useMemo( + () => ({ + wrapper: ["max-h-[382px]", "max-w-3xl"], + th: ["bg-transparent", "text-default-500", "border-b", "border-divider"], + td: [ + // changing the rows border radius + // first + "group-data-[first=true]/tr:first:before:rounded-none", + "group-data-[first=true]/tr:last:before:rounded-none", + // middle + "group-data-[middle=true]/tr:before:rounded-none", + // last + "group-data-[last=true]/tr:first:before:rounded-none", + "group-data-[last=true]/tr:last:before:rounded-none", + ], + }), + [], + ); + + return ( + + + {(column) => ( + + {column.name} + + )} + + + {(item) => ( + + {(columnKey) => {renderCell(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/custom-styles.raw.tsx b/apps/docs/content/components/table/custom-styles.raw.tsx new file mode 100644 index 0000000000..4529846f56 --- /dev/null +++ b/apps/docs/content/components/table/custom-styles.raw.tsx @@ -0,0 +1,674 @@ +import React, {SVGProps} from "react"; +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Input, + Button, + DropdownTrigger, + Dropdown, + DropdownMenu, + DropdownItem, + Chip, + User, + Pagination, + Selection, + ChipProps, + SortDescriptor, +} from "@nextui-org/react"; + +export type IconSvgProps = SVGProps & { + size?: number; +}; + +export const columns = [ + {name: "ID", uid: "id", sortable: true}, + {name: "NAME", uid: "name", sortable: true}, + {name: "AGE", uid: "age", sortable: true}, + {name: "ROLE", uid: "role", sortable: true}, + {name: "TEAM", uid: "team"}, + {name: "EMAIL", uid: "email"}, + {name: "STATUS", uid: "status", sortable: true}, + {name: "ACTIONS", uid: "actions"}, +]; + +export const statusOptions = [ + {name: "Active", uid: "active"}, + {name: "Paused", uid: "paused"}, + {name: "Vacation", uid: "vacation"}, +]; + +export 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: "Tech 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: "Sr. Dev", + 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: "C.M.", + 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: "S. Manager", + team: "Sales", + status: "active", + age: "24", + avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", + email: "kristen.cooper@example.com", + }, + { + id: 6, + name: "Brian Kim", + role: "P. Manager", + team: "Management", + age: "29", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", + email: "brian.kim@example.com", + status: "active", + }, + { + id: 7, + name: "Michael Hunt", + role: "Designer", + team: "Design", + status: "paused", + age: "27", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d", + email: "michael.hunt@example.com", + }, + { + id: 8, + name: "Samantha Brooks", + role: "HR Manager", + team: "HR", + status: "active", + age: "31", + avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d", + email: "samantha.brooks@example.com", + }, + { + id: 9, + name: "Frank Harrison", + role: "F. Manager", + team: "Finance", + status: "vacation", + age: "33", + avatar: "https://i.pravatar.cc/150?img=4", + email: "frank.harrison@example.com", + }, + { + id: 10, + name: "Emma Adams", + role: "Ops Manager", + team: "Operations", + status: "active", + age: "35", + avatar: "https://i.pravatar.cc/150?img=5", + email: "emma.adams@example.com", + }, + { + id: 11, + name: "Brandon Stevens", + role: "Jr. Dev", + team: "Development", + status: "active", + age: "22", + avatar: "https://i.pravatar.cc/150?img=8", + email: "brandon.stevens@example.com", + }, + { + id: 12, + name: "Megan Richards", + role: "P. Manager", + team: "Product", + status: "paused", + age: "28", + avatar: "https://i.pravatar.cc/150?img=10", + email: "megan.richards@example.com", + }, + { + id: 13, + name: "Oliver Scott", + role: "S. Manager", + team: "Security", + status: "active", + age: "37", + avatar: "https://i.pravatar.cc/150?img=12", + email: "oliver.scott@example.com", + }, + { + id: 14, + name: "Grace Allen", + role: "M. Specialist", + team: "Marketing", + status: "active", + age: "30", + avatar: "https://i.pravatar.cc/150?img=16", + email: "grace.allen@example.com", + }, + { + id: 15, + name: "Noah Carter", + role: "IT Specialist", + team: "I. Technology", + status: "paused", + age: "31", + avatar: "https://i.pravatar.cc/150?img=15", + email: "noah.carter@example.com", + }, + { + id: 16, + name: "Ava Perez", + role: "Manager", + team: "Sales", + status: "active", + age: "29", + avatar: "https://i.pravatar.cc/150?img=20", + email: "ava.perez@example.com", + }, + { + id: 17, + name: "Liam Johnson", + role: "Data Analyst", + team: "Analysis", + status: "active", + age: "28", + avatar: "https://i.pravatar.cc/150?img=33", + email: "liam.johnson@example.com", + }, + { + id: 18, + name: "Sophia Taylor", + role: "QA Analyst", + team: "Testing", + status: "active", + age: "27", + avatar: "https://i.pravatar.cc/150?img=29", + email: "sophia.taylor@example.com", + }, + { + id: 19, + name: "Lucas Harris", + role: "Administrator", + team: "Information Technology", + status: "paused", + age: "32", + avatar: "https://i.pravatar.cc/150?img=50", + email: "lucas.harris@example.com", + }, + { + id: 20, + name: "Mia Robinson", + role: "Coordinator", + team: "Operations", + status: "active", + age: "26", + avatar: "https://i.pravatar.cc/150?img=45", + email: "mia.robinson@example.com", + }, +]; + +export function capitalize(s) { + return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : ""; +} + +export const PlusIcon = ({size = 24, width, height, ...props}: IconSvgProps) => { + return ( + + ); +}; + +export const VerticalDotsIcon = ({size = 24, width, height, ...props}: IconSvgProps) => { + return ( + + ); +}; + +export const SearchIcon = (props: IconSvgProps) => { + return ( + + ); +}; + +export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}: IconSvgProps) => { + return ( + + ); +}; + +const statusColorMap: Record = { + active: "success", + paused: "danger", + vacation: "warning", +}; + +const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; + +type User = (typeof users)[0]; + +export default function App() { + const [filterValue, setFilterValue] = React.useState(""); + const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); + const [visibleColumns, setVisibleColumns] = React.useState( + new Set(INITIAL_VISIBLE_COLUMNS), + ); + const [statusFilter, setStatusFilter] = React.useState("all"); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [sortDescriptor, setSortDescriptor] = React.useState({ + column: "age", + direction: "ascending", + }); + const [page, setPage] = React.useState(1); + + const pages = Math.ceil(users.length / rowsPerPage); + + const hasSearchFilter = Boolean(filterValue); + + const headerColumns = React.useMemo(() => { + if (visibleColumns === "all") return columns; + + return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); + }, [visibleColumns]); + + const filteredItems = React.useMemo(() => { + let filteredUsers = [...users]; + + if (hasSearchFilter) { + filteredUsers = filteredUsers.filter((user) => + user.name.toLowerCase().includes(filterValue.toLowerCase()), + ); + } + if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { + filteredUsers = filteredUsers.filter((user) => + Array.from(statusFilter).includes(user.status), + ); + } + + return filteredUsers; + }, [users, filterValue, statusFilter]); + + const items = React.useMemo(() => { + const start = (page - 1) * rowsPerPage; + const end = start + rowsPerPage; + + return filteredItems.slice(start, end); + }, [page, filteredItems, rowsPerPage]); + + const sortedItems = React.useMemo(() => { + return [...items].sort((a: User, b: User) => { + const first = a[sortDescriptor.column as keyof User] as number; + const second = b[sortDescriptor.column as keyof User] as number; + const cmp = first < second ? -1 : first > second ? 1 : 0; + + return sortDescriptor.direction === "descending" ? -cmp : cmp; + }); + }, [sortDescriptor, items]); + + 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 ( +
+ + + + + + View + Edit + Delete + + +
+ ); + default: + return cellValue; + } + }, []); + + const onRowsPerPageChange = React.useCallback((e: React.ChangeEvent) => { + setRowsPerPage(Number(e.target.value)); + setPage(1); + }, []); + + const onSearchChange = React.useCallback((value?: string) => { + if (value) { + setFilterValue(value); + setPage(1); + } else { + setFilterValue(""); + } + }, []); + + const topContent = React.useMemo(() => { + return ( +
+
+ } + value={filterValue} + variant="bordered" + onClear={() => setFilterValue("")} + onValueChange={onSearchChange} + /> +
+ + + + + + {statusOptions.map((status) => ( + + {capitalize(status.name)} + + ))} + + + + + + + + {columns.map((column) => ( + + {capitalize(column.name)} + + ))} + + + +
+
+
+ Total {users.length} users + +
+
+ ); + }, [ + filterValue, + statusFilter, + visibleColumns, + onSearchChange, + onRowsPerPageChange, + users.length, + hasSearchFilter, + ]); + + const bottomContent = React.useMemo(() => { + return ( +
+ + + {selectedKeys === "all" + ? "All items selected" + : `${selectedKeys.size} of ${items.length} selected`} + +
+ ); + }, [selectedKeys, items.length, page, pages, hasSearchFilter]); + + const classNames = React.useMemo( + () => ({ + wrapper: ["max-h-[382px]", "max-w-3xl"], + th: ["bg-transparent", "text-default-500", "border-b", "border-divider"], + td: [ + // changing the rows border radius + // first + "group-data-[first=true]/tr:first:before:rounded-none", + "group-data-[first=true]/tr:last:before:rounded-none", + // middle + "group-data-[middle=true]/tr:before:rounded-none", + // last + "group-data-[last=true]/tr:first:before:rounded-none", + "group-data-[last=true]/tr:last:before:rounded-none", + ], + }), + [], + ); + + return ( + + + {(column) => ( + + {column.name} + + )} + + + {(item) => ( + + {(columnKey) => {renderCell(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/custom-styles.ts b/apps/docs/content/components/table/custom-styles.ts index e57b2904e9..29cf5fd0ee 100644 --- a/apps/docs/content/components/table/custom-styles.ts +++ b/apps/docs/content/components/table/custom-styles.ts @@ -1,1034 +1,8 @@ -const data = `const columns = [ - {name: "ID", uid: "id", sortable: true}, - {name: "NAME", uid: "name", sortable: true}, - {name: "AGE", uid: "age", sortable: true}, - {name: "ROLE", uid: "role", sortable: true}, - {name: "TEAM", uid: "team"}, - {name: "EMAIL", uid: "email"}, - {name: "STATUS", uid: "status", sortable: true}, - {name: "ACTIONS", uid: "actions"}, -]; - -const statusOptions = [ - {name: "Active", uid: "active"}, - {name: "Paused", uid: "paused"}, - {name: "Vacation", uid: "vacation"}, -]; - -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: "Tech 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: "Sr. Dev", - 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: "C.M.", - 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: "S. Manager", - team: "Sales", - status: "active", - age: "24", - avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", - email: "kristen.cooper@example.com", - }, - { - id: 6, - name: "Brian Kim", - role: "P. Manager", - team: "Management", - age: "29", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", - email: "brian.kim@example.com", - status: "active", - }, - { - id: 7, - name: "Michael Hunt", - role: "Designer", - team: "Design", - status: "paused", - age: "27", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d", - email: "michael.hunt@example.com", - }, - { - id: 8, - name: "Samantha Brooks", - role: "HR Manager", - team: "HR", - status: "active", - age: "31", - avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d", - email: "samantha.brooks@example.com", - }, - { - id: 9, - name: "Frank Harrison", - role: "F. Manager", - team: "Finance", - status: "vacation", - age: "33", - avatar: "https://i.pravatar.cc/150?img=4", - email: "frank.harrison@example.com", - }, - { - id: 10, - name: "Emma Adams", - role: "Ops Manager", - team: "Operations", - status: "active", - age: "35", - avatar: "https://i.pravatar.cc/150?img=5", - email: "emma.adams@example.com", - }, - { - id: 11, - name: "Brandon Stevens", - role: "Jr. Dev", - team: "Development", - status: "active", - age: "22", - avatar: "https://i.pravatar.cc/150?img=8", - email: "brandon.stevens@example.com", - }, - { - id: 12, - name: "Megan Richards", - role: "P. Manager", - team: "Product", - status: "paused", - age: "28", - avatar: "https://i.pravatar.cc/150?img=10", - email: "megan.richards@example.com", - }, - { - id: 13, - name: "Oliver Scott", - role: "S. Manager", - team: "Security", - status: "active", - age: "37", - avatar: "https://i.pravatar.cc/150?img=12", - email: "oliver.scott@example.com", - }, - { - id: 14, - name: "Grace Allen", - role: "M. Specialist", - team: "Marketing", - status: "active", - age: "30", - avatar: "https://i.pravatar.cc/150?img=16", - email: "grace.allen@example.com", - }, - { - id: 15, - name: "Noah Carter", - role: "IT Specialist", - team: "I. Technology", - status: "paused", - age: "31", - avatar: "https://i.pravatar.cc/150?img=15", - email: "noah.carter@example.com", - }, - { - id: 16, - name: "Ava Perez", - role: "Manager", - team: "Sales", - status: "active", - age: "29", - avatar: "https://i.pravatar.cc/150?img=20", - email: "ava.perez@example.com", - }, - { - id: 17, - name: "Liam Johnson", - role: "Data Analyst", - team: "Analysis", - status: "active", - age: "28", - avatar: "https://i.pravatar.cc/150?img=33", - email: "liam.johnson@example.com", - }, - { - id: 18, - name: "Sophia Taylor", - role: "QA Analyst", - team: "Testing", - status: "active", - age: "27", - avatar: "https://i.pravatar.cc/150?img=29", - email: "sophia.taylor@example.com", - }, - { - id: 19, - name: "Lucas Harris", - role: "Administrator", - team: "Information Technology", - status: "paused", - age: "32", - avatar: "https://i.pravatar.cc/150?img=50", - email: "lucas.harris@example.com", - }, - { - id: 20, - name: "Mia Robinson", - role: "Coordinator", - team: "Operations", - status: "active", - age: "26", - avatar: "https://i.pravatar.cc/150?img=45", - email: "mia.robinson@example.com", - }, -]; - -export {columns, users, statusOptions};`; - -const utils = `export function capitalize(s) { - return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : ""; -}`; - -const PlusIcon = `export const PlusIcon = ({size = 24, width, height, ...props}) => ( - -);`; - -const VerticalDotsIcon = `export const VerticalDotsIcon = ({size = 24, width, height, ...props}) => ( - -);`; - -const SearchIcon = `export const SearchIcon = (props) => ( - -);`; - -const ChevronDownIcon = `export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}) => ( - -);`; - -const App = `import { - Table, - TableHeader, - TableColumn, - TableBody, - TableRow, - TableCell, - Input, - Button, - DropdownTrigger, - Dropdown, - DropdownMenu, - DropdownItem, - Chip, - User, - Pagination, -} from "@nextui-org/react"; -import {PlusIcon} from "./PlusIcon"; -import {VerticalDotsIcon} from "./VerticalDotsIcon"; -import {SearchIcon} from "./SearchIcon"; -import {ChevronDownIcon} from "./ChevronDownIcon"; -import {columns, users, statusOptions} from "./data"; -import {capitalize} from "./utils"; - -const statusColorMap = { - active: "success", - paused: "danger", - vacation: "warning", -}; - -const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; - -export default function App() { - const [filterValue, setFilterValue] = React.useState(""); - const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); - const [visibleColumns, setVisibleColumns] = React.useState(new Set(INITIAL_VISIBLE_COLUMNS)); - const [statusFilter, setStatusFilter] = React.useState("all"); - const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [sortDescriptor, setSortDescriptor] = React.useState({ - column: "age", - direction: "ascending", - }); - const [page, setPage] = React.useState(1); - - const pages = Math.ceil(users.length / rowsPerPage); - - const hasSearchFilter = Boolean(filterValue); - - const headerColumns = React.useMemo(() => { - if (visibleColumns === "all") return columns; - - return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); - }, [visibleColumns]); - - const filteredItems = React.useMemo(() => { - let filteredUsers = [...users]; - - if (hasSearchFilter) { - filteredUsers = filteredUsers.filter((user) => - user.name.toLowerCase().includes(filterValue.toLowerCase()), - ); - } - if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { - filteredUsers = filteredUsers.filter((user) => - Array.from(statusFilter).includes(user.status), - ); - } - - return filteredUsers; - }, [users, filterValue, statusFilter]); - - const items = React.useMemo(() => { - const start = (page - 1) * rowsPerPage; - const end = start + rowsPerPage; - - return filteredItems.slice(start, end); - }, [page, filteredItems, rowsPerPage]); - - const sortedItems = React.useMemo(() => { - return [...items].sort((a, b) => { - const first = a[sortDescriptor.column]; - const second = b[sortDescriptor.column]; - const cmp = first < second ? -1 : first > second ? 1 : 0; - - return sortDescriptor.direction === "descending" ? -cmp : cmp; - }); - }, [sortDescriptor, items]); - - const renderCell = React.useCallback((user, columnKey) => { - const cellValue = user[columnKey]; - - switch (columnKey) { - case "name": - return ( - - {user.email} - - ); - case "role": - return ( -
-

{cellValue}

-

{user.team}

-
- ); - case "status": - return ( - - {cellValue} - - ); - case "actions": - return ( -
- - - - - - View - Edit - Delete - - -
- ); - default: - return cellValue; - } - }, []); - - const onRowsPerPageChange = React.useCallback((e) => { - setRowsPerPage(Number(e.target.value)); - setPage(1); - }, []); - - - const onSearchChange = React.useCallback((value) => { - if (value) { - setFilterValue(value); - setPage(1); - } else { - setFilterValue(""); - } - }, []); - - const topContent = React.useMemo(() => { - return ( -
-
- } - value={filterValue} - variant="bordered" - onClear={() => setFilterValue("")} - onValueChange={onSearchChange} - /> -
- - - - - - {statusOptions.map((status) => ( - - {capitalize(status.name)} - - ))} - - - - - - - - {columns.map((column) => ( - - {capitalize(column.name)} - - ))} - - - -
-
-
- Total {users.length} users - -
-
- ); - }, [ - filterValue, - statusFilter, - visibleColumns, - onSearchChange, - onRowsPerPageChange, - users.length, - hasSearchFilter, - ]); - - const bottomContent = React.useMemo(() => { - return ( -
- - - {selectedKeys === "all" - ? "All items selected" - : \`\${selectedKeys.size} of \${items.length} selected\`} - -
- ); - }, [selectedKeys, items.length, page, pages, hasSearchFilter]); - - const classNames = React.useMemo( - () => ({ - wrapper: ["max-h-[382px]", "max-w-3xl"], - th: ["bg-transparent", "text-default-500", "border-b", "border-divider"], - td: [ - // changing the rows border radius - // first - "group-data-[first=true]/tr:first:before:rounded-none", - "group-data-[first=true]/tr:last:before:rounded-none", - // middle - "group-data-[middle=true]/tr:before:rounded-none", - // last - "group-data-[last=true]/tr:first:before:rounded-none", - "group-data-[last=true]/tr:last:before:rounded-none", - ], - }), - [], - ); - - return ( - - - {(column) => ( - - {column.name} - - )} - - - {(item) => ( - - {(columnKey) => {renderCell(item, columnKey)}} - - )} - -
- ); -}`; - -const AppTs = `import { - Table, - TableHeader, - TableColumn, - TableBody, - TableRow, - TableCell, - Input, - Button, - DropdownTrigger, - Dropdown, - DropdownMenu, - DropdownItem, - Chip, - User, - Pagination, - Selection, - ChipProps, - SortDescriptor -} from "@nextui-org/react"; -import {PlusIcon} from "./PlusIcon"; -import {VerticalDotsIcon} from "./VerticalDotsIcon"; -import {ChevronDownIcon} from "./ChevronDownIcon"; -import {SearchIcon} from "./SearchIcon"; -import {columns, users, statusOptions} from "./data"; -import {capitalize} from "./utils"; - -const statusColorMap: Record = { - active: "success", - paused: "danger", - vacation: "warning", -}; - -const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; - -type User = typeof users[0]; - -export default function App() { - const [filterValue, setFilterValue] = React.useState(""); - const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); - const [visibleColumns, setVisibleColumns] = React.useState(new Set(INITIAL_VISIBLE_COLUMNS)); - const [statusFilter, setStatusFilter] = React.useState("all"); - const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [sortDescriptor, setSortDescriptor] = React.useState({ - column: "age", - direction: "ascending", - }); - const [page, setPage] = React.useState(1); - - const pages = Math.ceil(users.length / rowsPerPage); - - const hasSearchFilter = Boolean(filterValue); - - const headerColumns = React.useMemo(() => { - if (visibleColumns === "all") return columns; - - return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); - }, [visibleColumns]); - - const filteredItems = React.useMemo(() => { - let filteredUsers = [...users]; - - if (hasSearchFilter) { - filteredUsers = filteredUsers.filter((user) => - user.name.toLowerCase().includes(filterValue.toLowerCase()), - ); - } - if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { - filteredUsers = filteredUsers.filter((user) => - Array.from(statusFilter).includes(user.status), - ); - } - - return filteredUsers; - }, [users, filterValue, statusFilter]); - - const items = React.useMemo(() => { - const start = (page - 1) * rowsPerPage; - const end = start + rowsPerPage; - - return filteredItems.slice(start, end); - }, [page, filteredItems, rowsPerPage]); - - const sortedItems = React.useMemo(() => { - return [...items].sort((a: User, b: User) => { - const first = a[sortDescriptor.column as keyof User] as number; - const second = b[sortDescriptor.column as keyof User] as number; - const cmp = first < second ? -1 : first > second ? 1 : 0; - - return sortDescriptor.direction === "descending" ? -cmp : cmp; - }); - }, [sortDescriptor, items]); - - 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 ( -
- - - - - - View - Edit - Delete - - -
- ); - default: - return cellValue; - } - }, []); - - - const onRowsPerPageChange = React.useCallback((e: React.ChangeEvent) => { - setRowsPerPage(Number(e.target.value)); - setPage(1); - }, []); - - const onSearchChange = React.useCallback((value?: string) => { - if (value) { - setFilterValue(value); - setPage(1); - } else { - setFilterValue(""); - } - }, []); - - const topContent = React.useMemo(() => { - return ( -
-
- } - value={filterValue} - variant="bordered" - onClear={() => setFilterValue("")} - onValueChange={onSearchChange} - /> -
- - - - - - {statusOptions.map((status) => ( - - {capitalize(status.name)} - - ))} - - - - - - - - {columns.map((column) => ( - - {capitalize(column.name)} - - ))} - - - -
-
-
- Total {users.length} users - -
-
- ); - }, [ - filterValue, - statusFilter, - visibleColumns, - onSearchChange, - onRowsPerPageChange, - users.length, - hasSearchFilter, - ]); - - const bottomContent = React.useMemo(() => { - return ( -
- - - {selectedKeys === "all" - ? "All items selected" - : \`\${selectedKeys.size} of \${items.length} selected\`} - -
- ); - }, [selectedKeys, items.length, page, pages, hasSearchFilter]); - - const classNames = React.useMemo( - () => ({ - wrapper: ["max-h-[382px]", "max-w-3xl"], - th: ["bg-transparent", "text-default-500", "border-b", "border-divider"], - td: [ - // changing the rows border radius - // first - "group-data-[first=true]/tr:first:before:rounded-none", - "group-data-[first=true]/tr:last:before:rounded-none", - // middle - "group-data-[middle=true]/tr:before:rounded-none", - // last - "group-data-[last=true]/tr:first:before:rounded-none", - "group-data-[last=true]/tr:last:before:rounded-none", - ], - }), - [], - ); - - return ( - - - {(column) => ( - - {column.name} - - )} - - - {(item) => ( - - {(columnKey) => {renderCell(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./custom-styles.raw.jsx?raw"; +import AppTs from "./custom-styles.raw.tsx?raw"; const react = { "/App.jsx": App, - "/data.js": data, - "/utils.js": utils, - "/PlusIcon.jsx": PlusIcon, - "/VerticalDotsIcon.jsx": VerticalDotsIcon, - "/SearchIcon.jsx": SearchIcon, - "/ChevronDownIcon.jsx": ChevronDownIcon, }; const reactTs = { diff --git a/apps/docs/content/components/table/disabled-rows.raw.jsx b/apps/docs/content/components/table/disabled-rows.raw.jsx new file mode 100644 index 0000000000..2866f2b0a6 --- /dev/null +++ b/apps/docs/content/components/table/disabled-rows.raw.jsx @@ -0,0 +1,76 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, +} from "@nextui-org/react"; + +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", + }, +]; + +export default function App() { + const [selectedKeys, setSelectedKeys] = React.useState(new Set(["2"])); + + return ( + + + {(column) => {column.label}} + + + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/disabled-rows.ts b/apps/docs/content/components/table/disabled-rows.ts index bdb9ba21f6..c619fe06f6 100644 --- a/apps/docs/content/components/table/disabled-rows.ts +++ b/apps/docs/content/components/table/disabled-rows.ts @@ -1,71 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue} from "@nextui-org/react"; - -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", - }, -]; - -export default function App() { - const [selectedKeys, setSelectedKeys] = React.useState(new Set(["2"])); - - return ( - - - {(column) => {column.label}} - - - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./disabled-rows.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/disallow-empty-selection.raw.jsx b/apps/docs/content/components/table/disallow-empty-selection.raw.jsx new file mode 100644 index 0000000000..14a5db7045 --- /dev/null +++ b/apps/docs/content/components/table/disallow-empty-selection.raw.jsx @@ -0,0 +1,68 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + RadioGroup, + Radio, +} from "@nextui-org/react"; + +const colors = ["default", "primary", "secondary", "success", "warning", "danger"]; + +export default function App() { + const [selectedColor, setSelectedColor] = React.useState("default"); + + return ( +
+ + + NAME + ROLE + STATUS + + + + Tony Reichert + CEO + Active + + + Zoey Lang + Technical Lead + Paused + + + Jane Fisher + Senior Developer + Active + + + William Howard + Community Manager + Vacation + + +
+ + {colors.map((color) => ( + + {color} + + ))} + +
+ ); +} diff --git a/apps/docs/content/components/table/disallow-empty-selection.ts b/apps/docs/content/components/table/disallow-empty-selection.ts index 474b257da0..a1abceee1f 100644 --- a/apps/docs/content/components/table/disallow-empty-selection.ts +++ b/apps/docs/content/components/table/disallow-empty-selection.ts @@ -1,67 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, RadioGroup, Radio} from "@nextui-org/react"; - -const colors = ["default", "primary", "secondary", "success", "warning", "danger"]; - -export default function App() { - const [selectedColor, setSelectedColor] = React.useState("default"); - - return ( -
- - - NAME - ROLE - STATUS - - - - Tony Reichert - CEO - Active - - - Zoey Lang - Technical Lead - Paused - - - Jane Fisher - Senior Developer - Active - - - William Howard - Community Manager - Vacation - - -
- - {colors.map((color) => ( - - {color} - - ))} - -
- ); -}`; +import App from "./disallow-empty-selection.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/dynamic.raw.jsx b/apps/docs/content/components/table/dynamic.raw.jsx new file mode 100644 index 0000000000..a6e733dd18 --- /dev/null +++ b/apps/docs/content/components/table/dynamic.raw.jsx @@ -0,0 +1,68 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, +} from "@nextui-org/react"; + +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", + }, +]; + +export default function App() { + return ( + + + {(column) => {column.label}} + + + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/dynamic.ts b/apps/docs/content/components/table/dynamic.ts index bf20c16d51..5d3c97bb9a 100644 --- a/apps/docs/content/components/table/dynamic.ts +++ b/apps/docs/content/components/table/dynamic.ts @@ -1,63 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue} from "@nextui-org/react"; - -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", - }, -]; - -export default function App() { - return ( - - - {(column) => {column.label}} - - - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./dynamic.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/empty.raw.jsx b/apps/docs/content/components/table/empty.raw.jsx new file mode 100644 index 0000000000..2d27a10705 --- /dev/null +++ b/apps/docs/content/components/table/empty.raw.jsx @@ -0,0 +1,14 @@ +import {Table, TableHeader, TableColumn, TableBody} from "@nextui-org/react"; + +export default function App() { + return ( + + + NAME + ROLE + STATUS + + {[]} +
+ ); +} diff --git a/apps/docs/content/components/table/empty.ts b/apps/docs/content/components/table/empty.ts index 1a6e0ced1e..962836358a 100644 --- a/apps/docs/content/components/table/empty.ts +++ b/apps/docs/content/components/table/empty.ts @@ -1,17 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; - -export default function App() { - return ( - - - NAME - ROLE - STATUS - - {[]} -
- ); -}`; +import App from "./empty.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/infinite-pagination.raw.jsx b/apps/docs/content/components/table/infinite-pagination.raw.jsx new file mode 100644 index 0000000000..2af4de3203 --- /dev/null +++ b/apps/docs/content/components/table/infinite-pagination.raw.jsx @@ -0,0 +1,76 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Spinner, + getKeyValue, +} from "@nextui-org/react"; +import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll"; +import {useAsyncList} from "@react-stately/data"; + +export default function App() { + const [isLoading, setIsLoading] = React.useState(true); + const [hasMore, setHasMore] = React.useState(false); + + let list = useAsyncList({ + async load({signal, cursor}) { + if (cursor) { + setIsLoading(false); + } + + // 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 + } + classNames={{ + base: "max-h-[520px] overflow-scroll", + table: "min-h-[400px]", + }} + > + + Name + Height + Mass + Birth year + + } + > + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/infinite-pagination.raw.tsx b/apps/docs/content/components/table/infinite-pagination.raw.tsx new file mode 100644 index 0000000000..4d69e33341 --- /dev/null +++ b/apps/docs/content/components/table/infinite-pagination.raw.tsx @@ -0,0 +1,87 @@ +import React from "react"; +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Spinner, + getKeyValue, +} from "@nextui-org/react"; +import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll"; +import {useAsyncList} from "@react-stately/data"; + +interface SWCharacter { + name: string; + height: string; + mass: string; + birth_year: string; +} + +export default function App() { + const [isLoading, setIsLoading] = React.useState(true); + const [hasMore, setHasMore] = React.useState(false); + + let list = useAsyncList({ + async load({signal, cursor}) { + if (cursor) { + setIsLoading(false); + } + + // 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 + } + classNames={{ + base: "max-h-[520px] overflow-scroll", + table: "min-h-[400px]", + }} + > + + Name + Height + Mass + Birth year + + } + > + {(item: SWCharacter) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/infinite-pagination.ts b/apps/docs/content/components/table/infinite-pagination.ts index 145813869a..cdee063a64 100644 --- a/apps/docs/content/components/table/infinite-pagination.ts +++ b/apps/docs/content/components/table/infinite-pagination.ts @@ -1,154 +1,5 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Pagination, Spinner, getKeyValue} from "@nextui-org/react"; -import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll"; -import {useAsyncList} from "@react-stately/data"; - -export default function App() { - const [isLoading, setIsLoading] = React.useState(true); - const [hasMore, setHasMore] = React.useState(false); - - let list = useAsyncList({ - async load({signal, cursor}) { - - if (cursor) { - setIsLoading(false); - } - - // 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 - } - classNames={{ - base: "max-h-[520px] overflow-scroll", - table: "min-h-[400px]", - }} - > - - Name - Height - Mass - Birth year - - } - > - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- ); -}`; - -const AppTs = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Pagination, Spinner, getKeyValue} from "@nextui-org/react"; -import { useInfiniteScroll } from "@nextui-org/use-infinite-scroll"; -import { useAsyncList } from "@react-stately/data"; - -interface SWCharacter { - name: string; - height: string; - mass: string; - birth_year: string; -} - -export default function App() { - const [isLoading, setIsLoading] = React.useState(true); - const [hasMore, setHasMore] = React.useState(false); - - let list = useAsyncList({ - async load({ signal, cursor }) { - if (cursor) { - setIsLoading(false); - } - - // 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 - } - classNames={{ - base: "max-h-[520px] overflow-scroll", - table: "min-h-[400px]", - }} - > - - Name - Height - Mass - Birth year - - } - > - {(item: SWCharacter) => ( - - {(columnKey) => ( - {getKeyValue(item, columnKey)} - )} - - )} - -
- ); -}`; +import App from "./infinite-pagination.raw.jsx?raw"; +import AppTs from "./infinite-pagination.raw.tsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/load-more.raw.jsx b/apps/docs/content/components/table/load-more.raw.jsx new file mode 100644 index 0000000000..a011985059 --- /dev/null +++ b/apps/docs/content/components/table/load-more.raw.jsx @@ -0,0 +1,80 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, + Spinner, + Button, +} from "@nextui-org/react"; +import {useAsyncList} from "@react-stately/data"; + +export default function App() { + const [page, setPage] = React.useState(1); + const [isLoading, setIsLoading] = React.useState(true); + + 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(); + + if (!cursor) { + setIsLoading(false); + } + + return { + items: json.results, + cursor: json.next, + }; + }, + }); + + const hasMore = page < 9; + + return ( + + + + ) : null + } + classNames={{ + base: "max-h-[520px] overflow-scroll", + table: "min-h-[420px]", + }} + > + + Name + Height + Mass + Birth year + + } + > + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/load-more.ts b/apps/docs/content/components/table/load-more.ts index 70e8cd3825..872717cc04 100644 --- a/apps/docs/content/components/table/load-more.ts +++ b/apps/docs/content/components/table/load-more.ts @@ -1,73 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue, Spinner, Button} from "@nextui-org/react"; -import {useAsyncList} from "@react-stately/data"; - -export default function App() { - const [page, setPage] = React.useState(1); - const [isLoading, setIsLoading] = React.useState(true); - - 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(); - - if (!cursor) { - setIsLoading(false); - } - - return { - items: json.results, - cursor: json.next, - }; - }, - }); - - const hasMore = page < 9; - - return ( - - - - ) : null - } - classNames={{ - base: "max-h-[520px] overflow-scroll", - table: "min-h-[420px]", - }} - > - - Name - Height - Mass - Birth year - - } - > - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./load-more.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/multiple-selection.raw.jsx b/apps/docs/content/components/table/multiple-selection.raw.jsx new file mode 100644 index 0000000000..e6abd5b42e --- /dev/null +++ b/apps/docs/content/components/table/multiple-selection.raw.jsx @@ -0,0 +1,67 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + RadioGroup, + Radio, +} from "@nextui-org/react"; + +const colors = ["default", "primary", "secondary", "success", "warning", "danger"]; + +export default function App() { + const [selectedColor, setSelectedColor] = React.useState("default"); + + return ( +
+ + + NAME + ROLE + STATUS + + + + Tony Reichert + CEO + Active + + + Zoey Lang + Technical Lead + Paused + + + Jane Fisher + Senior Developer + Active + + + William Howard + Community Manager + Vacation + + +
+ + {colors.map((color) => ( + + {color} + + ))} + +
+ ); +} diff --git a/apps/docs/content/components/table/multiple-selection.ts b/apps/docs/content/components/table/multiple-selection.ts index e5be5d114a..f03bb84fb5 100644 --- a/apps/docs/content/components/table/multiple-selection.ts +++ b/apps/docs/content/components/table/multiple-selection.ts @@ -1,66 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, RadioGroup, Radio} from "@nextui-org/react"; - -const colors = ["default", "primary", "secondary", "success", "warning", "danger"]; - -export default function App() { - const [selectedColor, setSelectedColor] = React.useState("default"); - - return ( -
- - - NAME - ROLE - STATUS - - - - Tony Reichert - CEO - Active - - - Zoey Lang - Technical Lead - Paused - - - Jane Fisher - Senior Developer - Active - - - William Howard - Community Manager - Vacation - - -
- - {colors.map((color) => ( - - {color} - - ))} - -
- ); -}`; +import App from "./multiple-selection.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/paginated.raw.jsx b/apps/docs/content/components/table/paginated.raw.jsx new file mode 100644 index 0000000000..962c2dfdd1 --- /dev/null +++ b/apps/docs/content/components/table/paginated.raw.jsx @@ -0,0 +1,212 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Pagination, + getKeyValue, +} from "@nextui-org/react"; + +export const users = [ + { + 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", + }, + { + 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", + }, +]; + +export default function App() { + const [page, setPage] = React.useState(1); + const rowsPerPage = 4; + + const pages = Math.ceil(users.length / rowsPerPage); + + const items = React.useMemo(() => { + const start = (page - 1) * rowsPerPage; + const end = start + rowsPerPage; + + return users.slice(start, end); + }, [page, users]); + + return ( + + setPage(page)} + /> + + } + classNames={{ + wrapper: "min-h-[222px]", + }} + > + + NAME + ROLE + STATUS + + + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/paginated.ts b/apps/docs/content/components/table/paginated.ts index 75be89dc52..557add2fce 100644 --- a/apps/docs/content/components/table/paginated.ts +++ b/apps/docs/content/components/table/paginated.ts @@ -1,211 +1,7 @@ -const data = `export const users = [ - { - 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", - }, - { - 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 App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Pagination, getKeyValue} from "@nextui-org/react"; -import {users} from "./data"; - -export default function App() { - const [page, setPage] = React.useState(1); - const rowsPerPage = 4; - - const pages = Math.ceil(users.length / rowsPerPage); - - const items = React.useMemo(() => { - const start = (page - 1) * rowsPerPage; - const end = start + rowsPerPage; - - return users.slice(start, end); - }, [page, users]); - - return ( - - setPage(page)} - /> - - } - classNames={{ - wrapper: "min-h-[222px]", - }} - > - - NAME - ROLE - STATUS - - - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./paginated.raw.jsx?raw"; const react = { "/App.jsx": App, - "/data.js": data, }; export default { diff --git a/apps/docs/content/components/table/row-actions.raw.jsx b/apps/docs/content/components/table/row-actions.raw.jsx new file mode 100644 index 0000000000..1e6cf2dc5a --- /dev/null +++ b/apps/docs/content/components/table/row-actions.raw.jsx @@ -0,0 +1,88 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, + Radio, + RadioGroup, +} from "@nextui-org/react"; + +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", + }, +]; + +export default function App() { + const [selectionBehavior, setSelectionBehavior] = React.useState("toggle"); + + return ( +
+ alert(`Opening item ${key}...`)} + > + + {(column) => {column.label}} + + + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ + Toggle + Replace + +
+ ); +} diff --git a/apps/docs/content/components/table/row-actions.ts b/apps/docs/content/components/table/row-actions.ts index d9d0782566..06178c6b7f 100644 --- a/apps/docs/content/components/table/row-actions.ts +++ b/apps/docs/content/components/table/row-actions.ts @@ -1,81 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue, Radio, RadioGroup} from "@nextui-org/react"; - -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", - }, -]; - -export default function App() { - const [selectionBehavior, setSelectionBehavior] = React.useState("toggle"); - - return ( -
- alert(\`Opening item \${key}...\`)} - > - - {(column) => {column.label}} - - - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- - Toggle - Replace - -
- ); -}`; +import App from "./row-actions.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/selection-behavior.raw.jsx b/apps/docs/content/components/table/selection-behavior.raw.jsx new file mode 100644 index 0000000000..d0fec4a868 --- /dev/null +++ b/apps/docs/content/components/table/selection-behavior.raw.jsx @@ -0,0 +1,87 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, + Radio, + RadioGroup, +} from "@nextui-org/react"; + +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", + }, +]; + +export default function App() { + const [selectionBehavior, setSelectionBehavior] = React.useState("toggle"); + + return ( +
+ + + {(column) => {column.label}} + + + {(item) => ( + + {(columnKey) => {getKeyValue(item, columnKey)}} + + )} + +
+ + Toggle + Replace + +
+ ); +} diff --git a/apps/docs/content/components/table/selection-behavior.ts b/apps/docs/content/components/table/selection-behavior.ts index 566480015f..b36dec6d8b 100644 --- a/apps/docs/content/components/table/selection-behavior.ts +++ b/apps/docs/content/components/table/selection-behavior.ts @@ -1,80 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue, Radio, RadioGroup} from "@nextui-org/react"; - -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", - }, -]; - -export default function App() { - const [selectionBehavior, setSelectionBehavior] = React.useState("toggle"); - - return ( -
- - - {(column) => {column.label}} - - - {(item) => ( - - {(columnKey) => {getKeyValue(item, columnKey)}} - - )} - -
- - Toggle - Replace - -
- ); -}`; +import App from "./selection-behavior.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/single-selection.raw.jsx b/apps/docs/content/components/table/single-selection.raw.jsx new file mode 100644 index 0000000000..35122ba3f8 --- /dev/null +++ b/apps/docs/content/components/table/single-selection.raw.jsx @@ -0,0 +1,67 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + RadioGroup, + Radio, +} from "@nextui-org/react"; + +const colors = ["default", "primary", "secondary", "success", "warning", "danger"]; + +export default function App() { + const [selectedColor, setSelectedColor] = React.useState("default"); + + return ( +
+ + + NAME + ROLE + STATUS + + + + Tony Reichert + CEO + Active + + + Zoey Lang + Technical Lead + Paused + + + Jane Fisher + Senior Developer + Active + + + William Howard + Community Manager + Vacation + + +
+ + {colors.map((color) => ( + + {color} + + ))} + +
+ ); +} diff --git a/apps/docs/content/components/table/single-selection.ts b/apps/docs/content/components/table/single-selection.ts index e187ab933c..65d8454ff8 100644 --- a/apps/docs/content/components/table/single-selection.ts +++ b/apps/docs/content/components/table/single-selection.ts @@ -1,66 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, RadioGroup, Radio} from "@nextui-org/react"; - -const colors = ["default", "primary", "secondary", "success", "warning", "danger"]; - -export default function App() { - const [selectedColor, setSelectedColor] = React.useState("default"); - - return ( -
- - - NAME - ROLE - STATUS - - - - Tony Reichert - CEO - Active - - - Zoey Lang - Technical Lead - Paused - - - Jane Fisher - Senior Developer - Active - - - William Howard - Community Manager - Vacation - - -
- - {colors.map((color) => ( - - {color} - - ))} - -
- ); -}`; +import App from "./single-selection.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/sorting.raw.jsx b/apps/docs/content/components/table/sorting.raw.jsx new file mode 100644 index 0000000000..ddc13bfbbb --- /dev/null +++ b/apps/docs/content/components/table/sorting.raw.jsx @@ -0,0 +1,82 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + getKeyValue, + Spinner, +} from "@nextui-org/react"; +import {useAsyncList} from "@react-stately/data"; + +export default function App() { + const [isLoading, setIsLoading] = React.useState(true); + + let list = useAsyncList({ + async load({signal}) { + let res = await fetch("https://swapi.py4e.com/api/people/?search", { + signal, + }); + let json = await res.json(); + + setIsLoading(false); + + 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)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/sorting.ts b/apps/docs/content/components/table/sorting.ts index be3f41afb6..231fa295c0 100644 --- a/apps/docs/content/components/table/sorting.ts +++ b/apps/docs/content/components/table/sorting.ts @@ -1,75 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, getKeyValue, Spinner} from "@nextui-org/react"; -import {useAsyncList} from "@react-stately/data"; - -export default function App() { - const [isLoading, setIsLoading] = React.useState(true); - - let list = useAsyncList({ - async load({signal}) { - let res = await fetch('https://swapi.py4e.com/api/people/?search', { - signal, - }); - let json = await res.json(); - setIsLoading(false); - - 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)}} - - )} - -
- ); -}`; +import App from "./sorting.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/striped.raw.jsx b/apps/docs/content/components/table/striped.raw.jsx new file mode 100644 index 0000000000..a032ad7f63 --- /dev/null +++ b/apps/docs/content/components/table/striped.raw.jsx @@ -0,0 +1,35 @@ +import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; + +export default function App() { + return ( + + + NAME + ROLE + STATUS + + + + Tony Reichert + CEO + Active + + + Zoey Lang + Technical Lead + Paused + + + Jane Fisher + Senior Developer + Active + + + William Howard + Community Manager + Vacation + + +
+ ); +} diff --git a/apps/docs/content/components/table/striped.ts b/apps/docs/content/components/table/striped.ts index 2d9f32ff59..efcbdde82e 100644 --- a/apps/docs/content/components/table/striped.ts +++ b/apps/docs/content/components/table/striped.ts @@ -1,38 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; - -export default function App() { - return ( - - - NAME - ROLE - STATUS - - - - Tony Reichert - CEO - Active - - - Zoey Lang - Technical Lead - Paused - - - Jane Fisher - Senior Developer - Active - - - William Howard - Community Manager - Vacation - - -
- ); -}`; +import App from "./striped.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/usage.raw.jsx b/apps/docs/content/components/table/usage.raw.jsx new file mode 100644 index 0000000000..b6a8bf0c51 --- /dev/null +++ b/apps/docs/content/components/table/usage.raw.jsx @@ -0,0 +1,35 @@ +import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; + +export default function App() { + return ( + + + NAME + ROLE + STATUS + + + + Tony Reichert + CEO + Active + + + Zoey Lang + Technical Lead + Paused + + + Jane Fisher + Senior Developer + Active + + + William Howard + Community Manager + Vacation + + +
+ ); +} diff --git a/apps/docs/content/components/table/usage.ts b/apps/docs/content/components/table/usage.ts index b6e06ec81f..1118304c37 100644 --- a/apps/docs/content/components/table/usage.ts +++ b/apps/docs/content/components/table/usage.ts @@ -1,38 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; - -export default function App() { - return ( - - - NAME - ROLE - STATUS - - - - Tony Reichert - CEO - Active - - - Zoey Lang - Technical Lead - Paused - - - Jane Fisher - Senior Developer - Active - - - William Howard - Community Manager - Vacation - - -
- ); -}`; +import App from "./usage.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/use-case.raw.jsx b/apps/docs/content/components/table/use-case.raw.jsx new file mode 100644 index 0000000000..b1bbeaf73c --- /dev/null +++ b/apps/docs/content/components/table/use-case.raw.jsx @@ -0,0 +1,640 @@ +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Input, + Button, + DropdownTrigger, + Dropdown, + DropdownMenu, + DropdownItem, + Chip, + User, + Pagination, +} from "@nextui-org/react"; + +export const columns = [ + {name: "ID", uid: "id", sortable: true}, + {name: "NAME", uid: "name", sortable: true}, + {name: "AGE", uid: "age", sortable: true}, + {name: "ROLE", uid: "role", sortable: true}, + {name: "TEAM", uid: "team"}, + {name: "EMAIL", uid: "email"}, + {name: "STATUS", uid: "status", sortable: true}, + {name: "ACTIONS", uid: "actions"}, +]; + +export const statusOptions = [ + {name: "Active", uid: "active"}, + {name: "Paused", uid: "paused"}, + {name: "Vacation", uid: "vacation"}, +]; + +export 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: "Tech 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: "Sr. Dev", + 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: "C.M.", + 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: "S. Manager", + team: "Sales", + status: "active", + age: "24", + avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", + email: "kristen.cooper@example.com", + }, + { + id: 6, + name: "Brian Kim", + role: "P. Manager", + team: "Management", + age: "29", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", + email: "brian.kim@example.com", + status: "Active", + }, + { + id: 7, + name: "Michael Hunt", + role: "Designer", + team: "Design", + status: "paused", + age: "27", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d", + email: "michael.hunt@example.com", + }, + { + id: 8, + name: "Samantha Brooks", + role: "HR Manager", + team: "HR", + status: "active", + age: "31", + avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d", + email: "samantha.brooks@example.com", + }, + { + id: 9, + name: "Frank Harrison", + role: "F. Manager", + team: "Finance", + status: "vacation", + age: "33", + avatar: "https://i.pravatar.cc/150?img=4", + email: "frank.harrison@example.com", + }, + { + id: 10, + name: "Emma Adams", + role: "Ops Manager", + team: "Operations", + status: "active", + age: "35", + avatar: "https://i.pravatar.cc/150?img=5", + email: "emma.adams@example.com", + }, + { + id: 11, + name: "Brandon Stevens", + role: "Jr. Dev", + team: "Development", + status: "active", + age: "22", + avatar: "https://i.pravatar.cc/150?img=8", + email: "brandon.stevens@example.com", + }, + { + id: 12, + name: "Megan Richards", + role: "P. Manager", + team: "Product", + status: "paused", + age: "28", + avatar: "https://i.pravatar.cc/150?img=10", + email: "megan.richards@example.com", + }, + { + id: 13, + name: "Oliver Scott", + role: "S. Manager", + team: "Security", + status: "active", + age: "37", + avatar: "https://i.pravatar.cc/150?img=12", + email: "oliver.scott@example.com", + }, + { + id: 14, + name: "Grace Allen", + role: "M. Specialist", + team: "Marketing", + status: "active", + age: "30", + avatar: "https://i.pravatar.cc/150?img=16", + email: "grace.allen@example.com", + }, + { + id: 15, + name: "Noah Carter", + role: "IT Specialist", + team: "I. Technology", + status: "paused", + age: "31", + avatar: "https://i.pravatar.cc/150?img=15", + email: "noah.carter@example.com", + }, + { + id: 16, + name: "Ava Perez", + role: "Manager", + team: "Sales", + status: "active", + age: "29", + avatar: "https://i.pravatar.cc/150?img=20", + email: "ava.perez@example.com", + }, + { + id: 17, + name: "Liam Johnson", + role: "Data Analyst", + team: "Analysis", + status: "active", + age: "28", + avatar: "https://i.pravatar.cc/150?img=33", + email: "liam.johnson@example.com", + }, + { + id: 18, + name: "Sophia Taylor", + role: "QA Analyst", + team: "Testing", + status: "active", + age: "27", + avatar: "https://i.pravatar.cc/150?img=29", + email: "sophia.taylor@example.com", + }, + { + id: 19, + name: "Lucas Harris", + role: "Administrator", + team: "Information Technology", + status: "paused", + age: "32", + avatar: "https://i.pravatar.cc/150?img=50", + email: "lucas.harris@example.com", + }, + { + id: 20, + name: "Mia Robinson", + role: "Coordinator", + team: "Operations", + status: "active", + age: "26", + avatar: "https://i.pravatar.cc/150?img=45", + email: "mia.robinson@example.com", + }, +]; + +export function capitalize(s) { + return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : ""; +} + +export const PlusIcon = ({size = 24, width, height, ...props}) => { + return ( + + ); +}; + +export const VerticalDotsIcon = ({size = 24, width, height, ...props}) => { + return ( + + ); +}; + +export const SearchIcon = (props) => { + return ( + + ); +}; + +export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}) => { + return ( + + ); +}; + +const statusColorMap = { + active: "success", + paused: "danger", + vacation: "warning", +}; + +const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; + +export default function App() { + const [filterValue, setFilterValue] = React.useState(""); + const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); + const [visibleColumns, setVisibleColumns] = React.useState(new Set(INITIAL_VISIBLE_COLUMNS)); + const [statusFilter, setStatusFilter] = React.useState("all"); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [sortDescriptor, setSortDescriptor] = React.useState({ + column: "age", + direction: "ascending", + }); + const [page, setPage] = React.useState(1); + + const hasSearchFilter = Boolean(filterValue); + + const headerColumns = React.useMemo(() => { + if (visibleColumns === "all") return columns; + + return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); + }, [visibleColumns]); + + const filteredItems = React.useMemo(() => { + let filteredUsers = [...users]; + + if (hasSearchFilter) { + filteredUsers = filteredUsers.filter((user) => + user.name.toLowerCase().includes(filterValue.toLowerCase()), + ); + } + if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { + filteredUsers = filteredUsers.filter((user) => + Array.from(statusFilter).includes(user.status), + ); + } + + return filteredUsers; + }, [users, filterValue, statusFilter]); + + const pages = Math.ceil(filteredItems.length / rowsPerPage); + + const items = React.useMemo(() => { + const start = (page - 1) * rowsPerPage; + const end = start + rowsPerPage; + + return filteredItems.slice(start, end); + }, [page, filteredItems, rowsPerPage]); + + const sortedItems = React.useMemo(() => { + return [...items].sort((a, b) => { + const first = a[sortDescriptor.column]; + const second = b[sortDescriptor.column]; + const cmp = first < second ? -1 : first > second ? 1 : 0; + + return sortDescriptor.direction === "descending" ? -cmp : cmp; + }); + }, [sortDescriptor, items]); + + const renderCell = React.useCallback((user, columnKey) => { + const cellValue = user[columnKey]; + + switch (columnKey) { + case "name": + return ( + + {user.email} + + ); + case "role": + return ( +
+

{cellValue}

+

{user.team}

+
+ ); + case "status": + return ( + + {cellValue} + + ); + case "actions": + return ( +
+ + + + + + View + Edit + Delete + + +
+ ); + default: + return cellValue; + } + }, []); + + const onNextPage = React.useCallback(() => { + if (page < pages) { + setPage(page + 1); + } + }, [page, pages]); + + const onPreviousPage = React.useCallback(() => { + if (page > 1) { + setPage(page - 1); + } + }, [page]); + + const onRowsPerPageChange = React.useCallback((e) => { + setRowsPerPage(Number(e.target.value)); + setPage(1); + }, []); + + const onSearchChange = React.useCallback((value) => { + if (value) { + setFilterValue(value); + setPage(1); + } else { + setFilterValue(""); + } + }, []); + + const onClear = React.useCallback(() => { + setFilterValue(""); + setPage(1); + }, []); + + const topContent = React.useMemo(() => { + return ( +
+
+ } + value={filterValue} + onClear={() => onClear()} + onValueChange={onSearchChange} + /> +
+ + + + + + {statusOptions.map((status) => ( + + {capitalize(status.name)} + + ))} + + + + + + + + {columns.map((column) => ( + + {capitalize(column.name)} + + ))} + + + +
+
+
+ Total {users.length} users + +
+
+ ); + }, [ + filterValue, + statusFilter, + visibleColumns, + onRowsPerPageChange, + users.length, + onSearchChange, + hasSearchFilter, + ]); + + const bottomContent = React.useMemo(() => { + return ( +
+ + {selectedKeys === "all" + ? "All items selected" + : `${selectedKeys.size} of ${filteredItems.length} selected`} + + +
+ + +
+
+ ); + }, [selectedKeys, items.length, page, pages, hasSearchFilter]); + + return ( + + + {(column) => ( + + {column.name} + + )} + + + {(item) => ( + + {(columnKey) => {renderCell(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/use-case.raw.tsx b/apps/docs/content/components/table/use-case.raw.tsx new file mode 100644 index 0000000000..e5c0113e35 --- /dev/null +++ b/apps/docs/content/components/table/use-case.raw.tsx @@ -0,0 +1,653 @@ +import React, {SVGProps} from "react"; +import { + Table, + TableHeader, + TableColumn, + TableBody, + TableRow, + TableCell, + Input, + Button, + DropdownTrigger, + Dropdown, + DropdownMenu, + DropdownItem, + Chip, + User, + Pagination, + Selection, + ChipProps, + SortDescriptor, +} from "@nextui-org/react"; + +export type IconSvgProps = SVGProps & { + size?: number; +}; + +export function capitalize(s: string) { + return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : ""; +} + +export const PlusIcon = ({size = 24, width, height, ...props}: IconSvgProps) => { + return ( + + ); +}; + +export const VerticalDotsIcon = ({size = 24, width, height, ...props}: IconSvgProps) => { + return ( + + ); +}; + +export const SearchIcon = (props: IconSvgProps) => { + return ( + + ); +}; + +export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}: IconSvgProps) => { + return ( + + ); +}; + +export const columns = [ + {name: "ID", uid: "id", sortable: true}, + {name: "NAME", uid: "name", sortable: true}, + {name: "AGE", uid: "age", sortable: true}, + {name: "ROLE", uid: "role", sortable: true}, + {name: "TEAM", uid: "team"}, + {name: "EMAIL", uid: "email"}, + {name: "STATUS", uid: "status", sortable: true}, + {name: "ACTIONS", uid: "actions"}, +]; + +export const statusOptions = [ + {name: "Active", uid: "active"}, + {name: "Paused", uid: "paused"}, + {name: "Vacation", uid: "vacation"}, +]; + +export 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: "Tech 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: "Sr. Dev", + 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: "C.M.", + 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: "S. Manager", + team: "Sales", + status: "active", + age: "24", + avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", + email: "kristen.cooper@example.com", + }, + { + id: 6, + name: "Brian Kim", + role: "P. Manager", + team: "Management", + age: "29", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", + email: "brian.kim@example.com", + status: "Active", + }, + { + id: 7, + name: "Michael Hunt", + role: "Designer", + team: "Design", + status: "paused", + age: "27", + avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d", + email: "michael.hunt@example.com", + }, + { + id: 8, + name: "Samantha Brooks", + role: "HR Manager", + team: "HR", + status: "active", + age: "31", + avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d", + email: "samantha.brooks@example.com", + }, + { + id: 9, + name: "Frank Harrison", + role: "F. Manager", + team: "Finance", + status: "vacation", + age: "33", + avatar: "https://i.pravatar.cc/150?img=4", + email: "frank.harrison@example.com", + }, + { + id: 10, + name: "Emma Adams", + role: "Ops Manager", + team: "Operations", + status: "active", + age: "35", + avatar: "https://i.pravatar.cc/150?img=5", + email: "emma.adams@example.com", + }, + { + id: 11, + name: "Brandon Stevens", + role: "Jr. Dev", + team: "Development", + status: "active", + age: "22", + avatar: "https://i.pravatar.cc/150?img=8", + email: "brandon.stevens@example.com", + }, + { + id: 12, + name: "Megan Richards", + role: "P. Manager", + team: "Product", + status: "paused", + age: "28", + avatar: "https://i.pravatar.cc/150?img=10", + email: "megan.richards@example.com", + }, + { + id: 13, + name: "Oliver Scott", + role: "S. Manager", + team: "Security", + status: "active", + age: "37", + avatar: "https://i.pravatar.cc/150?img=12", + email: "oliver.scott@example.com", + }, + { + id: 14, + name: "Grace Allen", + role: "M. Specialist", + team: "Marketing", + status: "active", + age: "30", + avatar: "https://i.pravatar.cc/150?img=16", + email: "grace.allen@example.com", + }, + { + id: 15, + name: "Noah Carter", + role: "IT Specialist", + team: "I. Technology", + status: "paused", + age: "31", + avatar: "https://i.pravatar.cc/150?img=15", + email: "noah.carter@example.com", + }, + { + id: 16, + name: "Ava Perez", + role: "Manager", + team: "Sales", + status: "active", + age: "29", + avatar: "https://i.pravatar.cc/150?img=20", + email: "ava.perez@example.com", + }, + { + id: 17, + name: "Liam Johnson", + role: "Data Analyst", + team: "Analysis", + status: "active", + age: "28", + avatar: "https://i.pravatar.cc/150?img=33", + email: "liam.johnson@example.com", + }, + { + id: 18, + name: "Sophia Taylor", + role: "QA Analyst", + team: "Testing", + status: "active", + age: "27", + avatar: "https://i.pravatar.cc/150?img=29", + email: "sophia.taylor@example.com", + }, + { + id: 19, + name: "Lucas Harris", + role: "Administrator", + team: "Information Technology", + status: "paused", + age: "32", + avatar: "https://i.pravatar.cc/150?img=50", + email: "lucas.harris@example.com", + }, + { + id: 20, + name: "Mia Robinson", + role: "Coordinator", + team: "Operations", + status: "active", + age: "26", + avatar: "https://i.pravatar.cc/150?img=45", + email: "mia.robinson@example.com", + }, +]; + +const statusColorMap: Record = { + active: "success", + paused: "danger", + vacation: "warning", +}; + +const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; + +type User = (typeof users)[0]; + +export default function App() { + const [filterValue, setFilterValue] = React.useState(""); + const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); + const [visibleColumns, setVisibleColumns] = React.useState( + new Set(INITIAL_VISIBLE_COLUMNS), + ); + const [statusFilter, setStatusFilter] = React.useState("all"); + const [rowsPerPage, setRowsPerPage] = React.useState(5); + const [sortDescriptor, setSortDescriptor] = React.useState({ + column: "age", + direction: "ascending", + }); + + const [page, setPage] = React.useState(1); + + const hasSearchFilter = Boolean(filterValue); + + const headerColumns = React.useMemo(() => { + if (visibleColumns === "all") return columns; + + return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); + }, [visibleColumns]); + + const filteredItems = React.useMemo(() => { + let filteredUsers = [...users]; + + if (hasSearchFilter) { + filteredUsers = filteredUsers.filter((user) => + user.name.toLowerCase().includes(filterValue.toLowerCase()), + ); + } + if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { + filteredUsers = filteredUsers.filter((user) => + Array.from(statusFilter).includes(user.status), + ); + } + + return filteredUsers; + }, [users, filterValue, statusFilter]); + + const pages = Math.ceil(filteredItems.length / rowsPerPage); + + const items = React.useMemo(() => { + const start = (page - 1) * rowsPerPage; + const end = start + rowsPerPage; + + return filteredItems.slice(start, end); + }, [page, filteredItems, rowsPerPage]); + + const sortedItems = React.useMemo(() => { + return [...items].sort((a: User, b: User) => { + const first = a[sortDescriptor.column as keyof User] as number; + const second = b[sortDescriptor.column as keyof User] as number; + const cmp = first < second ? -1 : first > second ? 1 : 0; + + return sortDescriptor.direction === "descending" ? -cmp : cmp; + }); + }, [sortDescriptor, items]); + + 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 ( +
+ + + + + + View + Edit + Delete + + +
+ ); + default: + return cellValue; + } + }, []); + + const onNextPage = React.useCallback(() => { + if (page < pages) { + setPage(page + 1); + } + }, [page, pages]); + + const onPreviousPage = React.useCallback(() => { + if (page > 1) { + setPage(page - 1); + } + }, [page]); + + const onRowsPerPageChange = React.useCallback((e: React.ChangeEvent) => { + setRowsPerPage(Number(e.target.value)); + setPage(1); + }, []); + + const onSearchChange = React.useCallback((value?: string) => { + if (value) { + setFilterValue(value); + setPage(1); + } else { + setFilterValue(""); + } + }, []); + + const onClear = React.useCallback(() => { + setFilterValue(""); + setPage(1); + }, []); + + const topContent = React.useMemo(() => { + return ( +
+
+ } + value={filterValue} + onClear={() => onClear()} + onValueChange={onSearchChange} + /> +
+ + + + + + {statusOptions.map((status) => ( + + {capitalize(status.name)} + + ))} + + + + + + + + {columns.map((column) => ( + + {capitalize(column.name)} + + ))} + + + +
+
+
+ Total {users.length} users + +
+
+ ); + }, [ + filterValue, + statusFilter, + visibleColumns, + onSearchChange, + onRowsPerPageChange, + users.length, + hasSearchFilter, + ]); + + const bottomContent = React.useMemo(() => { + return ( +
+ + {selectedKeys === "all" + ? "All items selected" + : `${selectedKeys.size} of ${filteredItems.length} selected`} + + +
+ + +
+
+ ); + }, [selectedKeys, items.length, page, pages, hasSearchFilter]); + + return ( + + + {(column) => ( + + {column.name} + + )} + + + {(item) => ( + + {(columnKey) => {renderCell(item, columnKey)}} + + )} + +
+ ); +} diff --git a/apps/docs/content/components/table/use-case.ts b/apps/docs/content/components/table/use-case.ts index b46e960e7c..a03774d7c8 100644 --- a/apps/docs/content/components/table/use-case.ts +++ b/apps/docs/content/components/table/use-case.ts @@ -1,1324 +1,12 @@ -const data = `const columns = [ - {name: "ID", uid: "id", sortable: true}, - {name: "NAME", uid: "name", sortable: true}, - {name: "AGE", uid: "age", sortable: true}, - {name: "ROLE", uid: "role", sortable: true}, - {name: "TEAM", uid: "team"}, - {name: "EMAIL", uid: "email"}, - {name: "STATUS", uid: "status", sortable: true}, - {name: "ACTIONS", uid: "actions"}, -]; - -const statusOptions = [ - {name: "Active", uid: "active"}, - {name: "Paused", uid: "paused"}, - {name: "Vacation", uid: "vacation"}, -]; - -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: "Tech 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: "Sr. Dev", - 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: "C.M.", - 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: "S. Manager", - team: "Sales", - status: "active", - age: "24", - avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", - email: "kristen.cooper@example.com", - }, - { - id: 6, - name: "Brian Kim", - role: "P. Manager", - team: "Management", - age: "29", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", - email: "brian.kim@example.com", - status: "active", - }, - { - id: 7, - name: "Michael Hunt", - role: "Designer", - team: "Design", - status: "paused", - age: "27", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d", - email: "michael.hunt@example.com", - }, - { - id: 8, - name: "Samantha Brooks", - role: "HR Manager", - team: "HR", - status: "active", - age: "31", - avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d", - email: "samantha.brooks@example.com", - }, - { - id: 9, - name: "Frank Harrison", - role: "F. Manager", - team: "Finance", - status: "vacation", - age: "33", - avatar: "https://i.pravatar.cc/150?img=4", - email: "frank.harrison@example.com", - }, - { - id: 10, - name: "Emma Adams", - role: "Ops Manager", - team: "Operations", - status: "active", - age: "35", - avatar: "https://i.pravatar.cc/150?img=5", - email: "emma.adams@example.com", - }, - { - id: 11, - name: "Brandon Stevens", - role: "Jr. Dev", - team: "Development", - status: "active", - age: "22", - avatar: "https://i.pravatar.cc/150?img=8", - email: "brandon.stevens@example.com", - }, - { - id: 12, - name: "Megan Richards", - role: "P. Manager", - team: "Product", - status: "paused", - age: "28", - avatar: "https://i.pravatar.cc/150?img=10", - email: "megan.richards@example.com", - }, - { - id: 13, - name: "Oliver Scott", - role: "S. Manager", - team: "Security", - status: "active", - age: "37", - avatar: "https://i.pravatar.cc/150?img=12", - email: "oliver.scott@example.com", - }, - { - id: 14, - name: "Grace Allen", - role: "M. Specialist", - team: "Marketing", - status: "active", - age: "30", - avatar: "https://i.pravatar.cc/150?img=16", - email: "grace.allen@example.com", - }, - { - id: 15, - name: "Noah Carter", - role: "IT Specialist", - team: "I. Technology", - status: "paused", - age: "31", - avatar: "https://i.pravatar.cc/150?img=15", - email: "noah.carter@example.com", - }, - { - id: 16, - name: "Ava Perez", - role: "Manager", - team: "Sales", - status: "active", - age: "29", - avatar: "https://i.pravatar.cc/150?img=20", - email: "ava.perez@example.com", - }, - { - id: 17, - name: "Liam Johnson", - role: "Data Analyst", - team: "Analysis", - status: "active", - age: "28", - avatar: "https://i.pravatar.cc/150?img=33", - email: "liam.johnson@example.com", - }, - { - id: 18, - name: "Sophia Taylor", - role: "QA Analyst", - team: "Testing", - status: "active", - age: "27", - avatar: "https://i.pravatar.cc/150?img=29", - email: "sophia.taylor@example.com", - }, - { - id: 19, - name: "Lucas Harris", - role: "Administrator", - team: "Information Technology", - status: "paused", - age: "32", - avatar: "https://i.pravatar.cc/150?img=50", - email: "lucas.harris@example.com", - }, - { - id: 20, - name: "Mia Robinson", - role: "Coordinator", - team: "Operations", - status: "active", - age: "26", - avatar: "https://i.pravatar.cc/150?img=45", - email: "mia.robinson@example.com", - }, -]; - -export {columns, users, statusOptions};`; - -const dataTs = `const columns = [ - {name: "ID", uid: "id", sortable: true}, - {name: "NAME", uid: "name", sortable: true}, - {name: "AGE", uid: "age", sortable: true}, - {name: "ROLE", uid: "role", sortable: true}, - {name: "TEAM", uid: "team"}, - {name: "EMAIL", uid: "email"}, - {name: "STATUS", uid: "status", sortable: true}, - {name: "ACTIONS", uid: "actions"}, -]; - -const statusOptions = [ - {name: "Active", uid: "active"}, - {name: "Paused", uid: "paused"}, - {name: "Vacation", uid: "vacation"}, -]; - -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: "Tech 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: "Sr. Dev", - 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: "C.M.", - 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: "S. Manager", - team: "Sales", - status: "active", - age: "24", - avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d", - email: "kristen.cooper@example.com", - }, - { - id: 6, - name: "Brian Kim", - role: "P. Manager", - team: "Management", - age: "29", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d", - email: "brian.kim@example.com", - status: "Active", - }, - { - id: 7, - name: "Michael Hunt", - role: "Designer", - team: "Design", - status: "paused", - age: "27", - avatar: "https://i.pravatar.cc/150?u=a042581f4e29027007d", - email: "michael.hunt@example.com", - }, - { - id: 8, - name: "Samantha Brooks", - role: "HR Manager", - team: "HR", - status: "active", - age: "31", - avatar: "https://i.pravatar.cc/150?u=a042581f4e27027008d", - email: "samantha.brooks@example.com", - }, - { - id: 9, - name: "Frank Harrison", - role: "F. Manager", - team: "Finance", - status: "vacation", - age: "33", - avatar: "https://i.pravatar.cc/150?img=4", - email: "frank.harrison@example.com", - }, - { - id: 10, - name: "Emma Adams", - role: "Ops Manager", - team: "Operations", - status: "active", - age: "35", - avatar: "https://i.pravatar.cc/150?img=5", - email: "emma.adams@example.com", - }, - { - id: 11, - name: "Brandon Stevens", - role: "Jr. Dev", - team: "Development", - status: "active", - age: "22", - avatar: "https://i.pravatar.cc/150?img=8", - email: "brandon.stevens@example.com", - }, - { - id: 12, - name: "Megan Richards", - role: "P. Manager", - team: "Product", - status: "paused", - age: "28", - avatar: "https://i.pravatar.cc/150?img=10", - email: "megan.richards@example.com", - }, - { - id: 13, - name: "Oliver Scott", - role: "S. Manager", - team: "Security", - status: "active", - age: "37", - avatar: "https://i.pravatar.cc/150?img=12", - email: "oliver.scott@example.com", - }, - { - id: 14, - name: "Grace Allen", - role: "M. Specialist", - team: "Marketing", - status: "active", - age: "30", - avatar: "https://i.pravatar.cc/150?img=16", - email: "grace.allen@example.com", - }, - { - id: 15, - name: "Noah Carter", - role: "IT Specialist", - team: "I. Technology", - status: "paused", - age: "31", - avatar: "https://i.pravatar.cc/150?img=15", - email: "noah.carter@example.com", - }, - { - id: 16, - name: "Ava Perez", - role: "Manager", - team: "Sales", - status: "active", - age: "29", - avatar: "https://i.pravatar.cc/150?img=20", - email: "ava.perez@example.com", - }, - { - id: 17, - name: "Liam Johnson", - role: "Data Analyst", - team: "Analysis", - status: "active", - age: "28", - avatar: "https://i.pravatar.cc/150?img=33", - email: "liam.johnson@example.com", - }, - { - id: 18, - name: "Sophia Taylor", - role: "QA Analyst", - team: "Testing", - status: "active", - age: "27", - avatar: "https://i.pravatar.cc/150?img=29", - email: "sophia.taylor@example.com", - }, - { - id: 19, - name: "Lucas Harris", - role: "Administrator", - team: "Information Technology", - status: "paused", - age: "32", - avatar: "https://i.pravatar.cc/150?img=50", - email: "lucas.harris@example.com", - }, - { - id: 20, - name: "Mia Robinson", - role: "Coordinator", - team: "Operations", - status: "active", - age: "26", - avatar: "https://i.pravatar.cc/150?img=45", - email: "mia.robinson@example.com", - }, -]; - -export {columns, users, statusOptions};`; - -const types = `import {SVGProps} from "react"; - -export type IconSvgProps = SVGProps & { - size?: number; -};`; - -const utils = `export function capitalize(s) { - return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : ""; -}`; - -const PlusIcon = `export const PlusIcon = ({size = 24, width, height, ...props}) => ( - -);`; - -const VerticalDotsIcon = `export const VerticalDotsIcon = ({size = 24, width, height, ...props}) => ( - -);`; - -const SearchIcon = `export const SearchIcon = (props) => ( - -);`; - -const ChevronDownIcon = `export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}) => ( - -);`; - -const utilsTs = `export function capitalize(s: string) { - return s ? s.charAt(0).toUpperCase() + s.slice(1).toLowerCase() : ""; -}`; - -const PlusIconTs = `import {IconSvgProps} from "./types"; - -export const PlusIcon = ({size = 24, width, height, ...props}: IconSvgProps) => ( - -);`; - -const VerticalDotsIconTs = `import {IconSvgProps} from "./types"; - -export const VerticalDotsIcon = ({size = 24, width, height, ...props}: IconSvgProps) => ( - -);`; - -const SearchIconTs = `import {IconSvgProps} from "./types"; - -export const SearchIcon = (props: IconSvgProps) => ( - -);`; - -const ChevronDownIconTs = `import {IconSvgProps} from "./types"; - -export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}: IconSvgProps) => ( - -);`; - -const App = `import { - Table, - TableHeader, - TableColumn, - TableBody, - TableRow, - TableCell, - Input, - Button, - DropdownTrigger, - Dropdown, - DropdownMenu, - DropdownItem, - Chip, - User, - Pagination, -} from "@nextui-org/react"; -import {PlusIcon} from "./PlusIcon"; -import {VerticalDotsIcon} from "./VerticalDotsIcon"; -import {SearchIcon} from "./SearchIcon"; -import {ChevronDownIcon} from "./ChevronDownIcon"; -import {columns, users, statusOptions} from "./data"; -import {capitalize} from "./utils"; - -const statusColorMap = { - active: "success", - paused: "danger", - vacation: "warning", -}; - -const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; - -export default function App() { - const [filterValue, setFilterValue] = React.useState(""); - const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); - const [visibleColumns, setVisibleColumns] = React.useState(new Set(INITIAL_VISIBLE_COLUMNS)); - const [statusFilter, setStatusFilter] = React.useState("all"); - const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [sortDescriptor, setSortDescriptor] = React.useState({ - column: "age", - direction: "ascending", - }); - const [page, setPage] = React.useState(1); - - const hasSearchFilter = Boolean(filterValue); - - const headerColumns = React.useMemo(() => { - if (visibleColumns === "all") return columns; - - return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); - }, [visibleColumns]); - - const filteredItems = React.useMemo(() => { - let filteredUsers = [...users]; - - if (hasSearchFilter) { - filteredUsers = filteredUsers.filter((user) => - user.name.toLowerCase().includes(filterValue.toLowerCase()), - ); - } - if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { - filteredUsers = filteredUsers.filter((user) => - Array.from(statusFilter).includes(user.status), - ); - } - - return filteredUsers; - }, [users, filterValue, statusFilter]); - - const pages = Math.ceil(filteredItems.length / rowsPerPage); - - const items = React.useMemo(() => { - const start = (page - 1) * rowsPerPage; - const end = start + rowsPerPage; - - return filteredItems.slice(start, end); - }, [page, filteredItems, rowsPerPage]); - - const sortedItems = React.useMemo(() => { - return [...items].sort((a, b) => { - const first = a[sortDescriptor.column]; - const second = b[sortDescriptor.column]; - const cmp = first < second ? -1 : first > second ? 1 : 0; - - return sortDescriptor.direction === "descending" ? -cmp : cmp; - }); - }, [sortDescriptor, items]); - - const renderCell = React.useCallback((user, columnKey) => { - const cellValue = user[columnKey]; - - switch (columnKey) { - case "name": - return ( - - {user.email} - - ); - case "role": - return ( -
-

{cellValue}

-

{user.team}

-
- ); - case "status": - return ( - - {cellValue} - - ); - case "actions": - return ( -
- - - - - - View - Edit - Delete - - -
- ); - default: - return cellValue; - } - }, []); - - const onNextPage = React.useCallback(() => { - if (page < pages) { - setPage(page + 1); - } - }, [page, pages]); - - const onPreviousPage = React.useCallback(() => { - if (page > 1) { - setPage(page - 1); - } - }, [page]); - - const onRowsPerPageChange = React.useCallback((e) => { - setRowsPerPage(Number(e.target.value)); - setPage(1); - }, []); - - const onSearchChange = React.useCallback((value) => { - if (value) { - setFilterValue(value); - setPage(1); - } else { - setFilterValue(""); - } - }, []); - - const onClear = React.useCallback(()=>{ - setFilterValue("") - setPage(1) - },[]) - - const topContent = React.useMemo(() => { - return ( -
-
- } - value={filterValue} - onClear={() => onClear()} - onValueChange={onSearchChange} - /> -
- - - - - - {statusOptions.map((status) => ( - - {capitalize(status.name)} - - ))} - - - - - - - - {columns.map((column) => ( - - {capitalize(column.name)} - - ))} - - - -
-
-
- Total {users.length} users - -
-
- ); - }, [ - filterValue, - statusFilter, - visibleColumns, - onRowsPerPageChange, - users.length, - onSearchChange, - hasSearchFilter, - ]); - - const bottomContent = React.useMemo(() => { - return ( -
- - {selectedKeys === "all" - ? "All items selected" - : \`\${selectedKeys.size} of \${filteredItems.length} selected\`} - - -
- - -
-
- ); - }, [selectedKeys, items.length, page, pages, hasSearchFilter]); - - return ( - - - {(column) => ( - - {column.name} - - )} - - - {(item) => ( - - {(columnKey) => {renderCell(item, columnKey)}} - - )} - -
- ); -}`; - -const AppTs = `import { - Table, - TableHeader, - TableColumn, - TableBody, - TableRow, - TableCell, - Input, - Button, - DropdownTrigger, - Dropdown, - DropdownMenu, - DropdownItem, - Chip, - User, - Pagination, - Selection, - ChipProps, - SortDescriptor -} from "@nextui-org/react"; -import {PlusIcon} from "./PlusIcon"; -import {VerticalDotsIcon} from "./VerticalDotsIcon"; -import {ChevronDownIcon} from "./ChevronDownIcon"; -import {SearchIcon} from "./SearchIcon"; -import {columns, users, statusOptions} from "./data"; -import {capitalize} from "./utils"; - -const statusColorMap: Record = { - active: "success", - paused: "danger", - vacation: "warning", -}; - -const INITIAL_VISIBLE_COLUMNS = ["name", "role", "status", "actions"]; - -type User = typeof users[0]; - -export default function App() { - const [filterValue, setFilterValue] = React.useState(""); - const [selectedKeys, setSelectedKeys] = React.useState(new Set([])); - const [visibleColumns, setVisibleColumns] = React.useState(new Set(INITIAL_VISIBLE_COLUMNS)); - const [statusFilter, setStatusFilter] = React.useState("all"); - const [rowsPerPage, setRowsPerPage] = React.useState(5); - const [sortDescriptor, setSortDescriptor] = React.useState({ - column: "age", - direction: "ascending", - }); - - const [page, setPage] = React.useState(1); - - const hasSearchFilter = Boolean(filterValue); - - const headerColumns = React.useMemo(() => { - if (visibleColumns === "all") return columns; - - return columns.filter((column) => Array.from(visibleColumns).includes(column.uid)); - }, [visibleColumns]); - - const filteredItems = React.useMemo(() => { - let filteredUsers = [...users]; - - if (hasSearchFilter) { - filteredUsers = filteredUsers.filter((user) => - user.name.toLowerCase().includes(filterValue.toLowerCase()), - ); - } - if (statusFilter !== "all" && Array.from(statusFilter).length !== statusOptions.length) { - filteredUsers = filteredUsers.filter((user) => - Array.from(statusFilter).includes(user.status), - ); - } - - return filteredUsers; - }, [users, filterValue, statusFilter]); - - const pages = Math.ceil(filteredItems.length / rowsPerPage); - - const items = React.useMemo(() => { - const start = (page - 1) * rowsPerPage; - const end = start + rowsPerPage; - - return filteredItems.slice(start, end); - }, [page, filteredItems, rowsPerPage]); - - const sortedItems = React.useMemo(() => { - return [...items].sort((a: User, b: User) => { - const first = a[sortDescriptor.column as keyof User] as number; - const second = b[sortDescriptor.column as keyof User] as number; - const cmp = first < second ? -1 : first > second ? 1 : 0; - - return sortDescriptor.direction === "descending" ? -cmp : cmp; - }); - }, [sortDescriptor, items]); - - 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 ( -
- - - - - - View - Edit - Delete - - -
- ); - default: - return cellValue; - } - }, []); - - const onNextPage = React.useCallback(() => { - if (page < pages) { - setPage(page + 1); - } - }, [page, pages]); - - const onPreviousPage = React.useCallback(() => { - if (page > 1) { - setPage(page - 1); - } - }, [page]); - - const onRowsPerPageChange = React.useCallback((e: React.ChangeEvent) => { - setRowsPerPage(Number(e.target.value)); - setPage(1); - }, []); - - const onSearchChange = React.useCallback((value?: string) => { - if (value) { - setFilterValue(value); - setPage(1); - } else { - setFilterValue(""); - } - }, []); - - const onClear = React.useCallback(()=>{ - setFilterValue("") - setPage(1) - },[]) - - const topContent = React.useMemo(() => { - return ( -
-
- } - value={filterValue} - onClear={() => onClear()} - onValueChange={onSearchChange} - /> -
- - - - - - {statusOptions.map((status) => ( - - {capitalize(status.name)} - - ))} - - - - - - - - {columns.map((column) => ( - - {capitalize(column.name)} - - ))} - - - -
-
-
- Total {users.length} users - -
-
- ); - }, [ - filterValue, - statusFilter, - visibleColumns, - onSearchChange, - onRowsPerPageChange, - users.length, - hasSearchFilter, - ]); - - const bottomContent = React.useMemo(() => { - return ( -
- - {selectedKeys === "all" - ? "All items selected" - : \`\${selectedKeys.size} of \${filteredItems.length} selected\`} - - -
- - -
-
- ); - }, [selectedKeys, items.length, page, pages, hasSearchFilter]); - - return ( - - - {(column) => ( - - {column.name} - - )} - - - {(item) => ( - - {(columnKey) => {renderCell(item, columnKey)}} - - )} - -
- ); -}`; +import App from "./use-case.raw.jsx?raw"; +import AppTs from "./use-case.raw.tsx?raw"; const react = { "/App.jsx": App, - "/data.js": data, - "/utils.js": utils, - "/PlusIcon.jsx": PlusIcon, - "/SearchIcon.jsx": SearchIcon, - "/ChevronDownIcon.jsx": ChevronDownIcon, - "/VerticalDotsIcon.jsx": VerticalDotsIcon, }; const reactTs = { "/App.tsx": AppTs, - "/types.ts": types, - "/data.ts": dataTs, - "/utils.ts": utilsTs, - "/PlusIcon.tsx": PlusIconTs, - "/VerticalDotsIcon.tsx": VerticalDotsIconTs, - "/SearchIcon.tsx": SearchIconTs, - "/ChevronDownIcon.tsx": ChevronDownIconTs, }; export default { diff --git a/apps/docs/content/components/table/without-header.raw.jsx b/apps/docs/content/components/table/without-header.raw.jsx new file mode 100644 index 0000000000..e5506cc19f --- /dev/null +++ b/apps/docs/content/components/table/without-header.raw.jsx @@ -0,0 +1,35 @@ +import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; + +export default function App() { + return ( + + + NAME + ROLE + STATUS + + + + Tony Reichert + CEO + Active + + + Zoey Lang + Technical Lead + Paused + + + Jane Fisher + Senior Developer + Active + + + William Howard + Community Manager + Vacation + + +
+ ); +} diff --git a/apps/docs/content/components/table/without-header.ts b/apps/docs/content/components/table/without-header.ts index 18c4b55d98..90f0321bcc 100644 --- a/apps/docs/content/components/table/without-header.ts +++ b/apps/docs/content/components/table/without-header.ts @@ -1,38 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; - -export default function App() { - return ( - - - NAME - ROLE - STATUS - - - - Tony Reichert - CEO - Active - - - Zoey Lang - Technical Lead - Paused - - - Jane Fisher - Senior Developer - Active - - - William Howard - Community Manager - Vacation - - -
- ); -}`; +import App from "./without-header.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/components/table/without-wrapper.raw.jsx b/apps/docs/content/components/table/without-wrapper.raw.jsx new file mode 100644 index 0000000000..26d6e38715 --- /dev/null +++ b/apps/docs/content/components/table/without-wrapper.raw.jsx @@ -0,0 +1,35 @@ +import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; + +export default function App() { + return ( + + + NAME + ROLE + STATUS + + + + Tony Reichert + CEO + Active + + + Zoey Lang + Technical Lead + Paused + + + Jane Fisher + Senior Developer + Active + + + William Howard + Community Manager + Vacation + + +
+ ); +} diff --git a/apps/docs/content/components/table/without-wrapper.ts b/apps/docs/content/components/table/without-wrapper.ts index 74e22f5a2a..326e580709 100644 --- a/apps/docs/content/components/table/without-wrapper.ts +++ b/apps/docs/content/components/table/without-wrapper.ts @@ -1,38 +1,4 @@ -const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell} from "@nextui-org/react"; - -export default function App() { - return ( - - - NAME - ROLE - STATUS - - - - Tony Reichert - CEO - Active - - - Zoey Lang - Technical Lead - Paused - - - Jane Fisher - Senior Developer - Active - - - William Howard - Community Manager - Vacation - - -
- ); -}`; +import App from "./without-wrapper.raw.jsx?raw"; const react = { "/App.jsx": App, diff --git a/apps/docs/content/docs/components/table.mdx b/apps/docs/content/docs/components/table.mdx index 5415aa1e65..afa8e40228 100644 --- a/apps/docs/content/docs/components/table.mdx +++ b/apps/docs/content/docs/components/table.mdx @@ -43,20 +43,20 @@ NextUI exports 6 table-related components: @@ -300,7 +300,6 @@ Table also supports infinite pagination. To do so, you can use the `useAsyncList ```jsx import { useInfiniteScroll } from "@nextui-org/use-infinite-scroll"; - import { useAsyncList } from "@react-stately/data"; ```