+
+
+
+ React List Playground (Tailwind)
+
+
+
+
+
+
+
{searchChildren}
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {[...Array(5)].map((_, index) => (
-
+ {COLOR_OPTIONS.map((opt) => (
+
))}
-
-
-
-
-
-
-
-
-
No Data Found
-
-
- We couldn't find any matching records. Try adjusting your
- search or filters.
-
+
-
-
-
-
- {({ error }) => (
-
-
-
-
-
Something went wrong
-
- {error.message ||
- "An unexpected error occurred while fetching data."}
-
-
-
- {error.name}: {error.message}
-
-
-
- )}
-
-
- {({ items, sort, setSort }) => {
- return (
-
-
-
-
-
+
+
+
+
+ {[0, 1, 2, 3].map((i) => (
+
+ ))}
+
+
+
+
+
+
+
-
-
-
-
- | ID |
-
- Name{" "}
- {
- const sorting =
- sort?.sortOrder === ""
- ? "asc"
- : sort?.sortOrder === "asc"
- ? "desc"
- : "";
+
- setSort({
- by: "name", // Update the sortBy state t
- order: sorting,
- });
- }}
- >
-
-
- |
- Status |
-
- Update At{" "}
- {
- const sorting =
- sort?.sortOrder === ""
- ? "asc"
- : sort?.sortOrder === "asc"
- ? "desc"
- : "";
+
+ {({ error }) => (
+
+
+
+
+ {error.name}
+ {error.message}
+
+
+
+ )}
+
- setSort({
- ...sort,
- by: "date_updated", // Update the sortBy state t
- order: sorting,
- });
- }}
- >
-
-
- |
-
-
-
- {items.map((item) => (
-
- | {item.id} |
- {item.name} |
- {item.status} |
-
- {new Date(item.date_updated).toLocaleString()}
- |
-
- ))}
-
-
-
- );
- }}
-
- {/* Footer */}
-
-
-
-
- {({ visibleCount }) => (
-
- {visibleCount} Results
-
- )}
-
-
+
+ {({ items }) => (
+
+ {items.map((item, idx) => {
+ const row = item ?? {};
+ return (
+
+
+
+ ID {row.id}
+
-
-
Page Size:
-
- {({ perPage, setPerPage, options }) => (
-
-
-
-
+
+ {row.name}
+
+
+
+ Updated:{' '}
+ {row.date_updated
+ ? new Date(
+ row.date_updated,
+ ).toLocaleString()
+ : '-'}
+
+
+
+
+ {row.status ?? '-'}
+
+
+ );
+ })}
-
- )}
-
-
+ )}
+
-
-
- {({ setPage, page, pagesCount }) => (
-
- Go to:
- {
- const page = Number(e.target.value);
- setPage(page);
- }}
- className="page-input"
- />
- of {pagesCount}
-
- )}
-
-
-
+
+ {({ isLoading }) =>
+ isLoading ? (
+
+ Loading...
+
+ ) : null
+ }
+
+
-
- {({
- page,
- pagesToDisplay,
- hasNext,
- hasPrev,
- prev,
- next,
- first,
- last,
- setPage,
- }) => (
-
-
-
-
+
+
+
+ {({ visibleCount, count }) => (
+
+ {visibleCount} results
+ {typeof count === 'number'
+ ? ` (total ${count})`
+ : ''}
+
+ )}
+
-
-
-
+
+
+ {({ perPage, setPerPage, options }) => (
+
+ )}
+
-
- {pagesToDisplay.map((item, index) => {
- const isActive = item === page;
+
+ {({ setPage, page, pagesCount }) => (
+
+
+ setPage(Number(e.target.value))
+ }
+ className="w-20 rounded-md border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-100 shadow-sm outline-none focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20"
+ />
+
+ of {pagesCount}
+
+
+ )}
+
- return (
-
setPage(item)}
- className={`page-button ${
- isActive ? "active" : ""
- }`}
- >
- {item}
-
- );
- })}
-
+
+ {({
+ page,
+ pagesToDisplay,
+ hasPrev,
+ hasNext,
+ first,
+ prev,
+ next,
+ last,
+ setPage,
+ }) => (
+
+
+ First
+
+
+ Prev
+
-
-
-
+
+ {pagesToDisplay.map((p) => (
+ setPage(p)}
+ disabled={p === page}
+ className={`rounded-md border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-100 shadow-sm disabled:opacity-60 ${p === page ? 'bg-indigo-600 border-indigo-500' : ''
+ }`}
+ >
+ {p}
+
+ ))}
+
-
-
-
+
+ Next
+
+
+ Last
+
+
+ )}
+
+
+
- )}
-
-
+
+
+
-
-
+
+
);
-};
+}
-export default ListWrapper;
diff --git a/apps/playground/src/index.css b/apps/playground/src/index.css
index 08a3ac9..a50c033 100644
--- a/apps/playground/src/index.css
+++ b/apps/playground/src/index.css
@@ -1,3 +1,6 @@
+@import "tailwindcss";
+@source "../**/*.{js,jsx,ts,tsx}";
+
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
@@ -18,6 +21,7 @@ a {
color: #646cff;
text-decoration: inherit;
}
+
a:hover {
color: #535bf2;
}
@@ -46,9 +50,11 @@ button {
cursor: pointer;
transition: border-color 0.25s;
}
+
button:hover {
border-color: #646cff;
}
+
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
@@ -59,10 +65,12 @@ button:focus-visible {
color: #213547;
background-color: #ffffff;
}
+
a:hover {
color: #747bff;
}
+
button {
background-color: #f9f9f9;
}
-}
+}
\ No newline at end of file
diff --git a/apps/playground/tailwind.config.js b/apps/playground/tailwind.config.js
new file mode 100644
index 0000000..524f96e
--- /dev/null
+++ b/apps/playground/tailwind.config.js
@@ -0,0 +1,9 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./index.html', './src/**/*.{js,jsx,ts,tsx}'],
+ theme: {
+ extend: {},
+ },
+ plugins: [],
+};
+
diff --git a/packages/react-list/package.json b/packages/react-list/package.json
index ebe29c5..9ed6aa8 100644
--- a/packages/react-list/package.json
+++ b/packages/react-list/package.json
@@ -1,32 +1,41 @@
{
"name": "@7span/react-list",
- "version": "1.0.1",
+ "version": "1.1.0",
"description": "A simple and reusable list component for React",
"type": "module",
"scripts": {
"dev": "vite",
- "build": "vite build",
+ "build": "vite build && pnpm run build:types",
+ "build:types": "tsc -p tsconfig.build.json",
"lint": "eslint .",
"preview": "vite preview",
- "prepublishOnly": "npm run build"
+ "prepublishOnly": "pnpm run build"
},
- "main": "src/index.js",
+ "main": "dist/react-list.cjs",
+ "module": "dist/react-list.js",
+ "types": "dist/types/index.d.ts",
"exports": {
- ".": "./src/index.js"
+ ".": {
+ "types": "./dist/types/index.d.ts",
+ "import": "./dist/react-list.js",
+ "require": "./dist/react-list.cjs"
+ }
},
"files": [
- "dist",
- "src"
+ "dist"
],
+ "publishConfig": {
+ "access": "public"
+ },
"peerDependencies": {
"react": "^18.2.0 || ^19.0.0",
"react-dom": "^18.2.0 || ^19.0.0"
},
- "publishConfig": {
- "access": "public"
- },
"devDependencies": {
+ "@types/react": "^19.1.13",
+ "@types/react-dom": "^19.1.9",
"@vitejs/plugin-react": "^4.3.4",
+ "typescript": "^5.9.3",
"vite": "^6.3.1"
}
-}
+}
\ No newline at end of file
diff --git a/packages/react-list/src/components/attributes.jsx b/packages/react-list/src/components/attributes.jsx
deleted file mode 100644
index b3fc189..0000000
--- a/packages/react-list/src/components/attributes.jsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { memo, useCallback, useMemo } from "react";
-import { useListContext } from "../context/list-provider";
-
-export const ReactListAttributes = memo(({ children, renderAttribute }) => {
- const { listState } = useListContext();
- const { attrs, attrSettings, updateAttr } = listState;
-
- const handleAttrChange = useCallback(
- (attrName) => (e) => {
- updateAttr(attrName, "visible", e.target.checked);
- },
- [updateAttr]
- );
-
- const scope = useMemo(
- () => ({
- attrs,
- attrSettings,
- updateAttr,
- }),
- [attrs, attrSettings, updateAttr]
- );
-
- if (children) {
- return children(scope);
- }
-
- return (
-
- {attrs.map((attr, index) => {
- if (renderAttribute) {
- return renderAttribute({
- key: `attr-${index}`,
- attr,
- updateAttr,
- attrSettings,
- });
- }
-
- return (
-
- );
- })}
-
- );
-});
diff --git a/packages/react-list/src/components/attributes.tsx b/packages/react-list/src/components/attributes.tsx
new file mode 100644
index 0000000..fa4e2d7
--- /dev/null
+++ b/packages/react-list/src/components/attributes.tsx
@@ -0,0 +1,87 @@
+import { memo, useCallback, useMemo } from "react";
+import { useListContext } from "../context/list-provider";
+import type { ReactListAttrSettings, ReactListAttribute } from "../types";
+import type { ChangeEvent, ReactNode } from "react";
+
+type ReactListAttributesUpdateAttr = (
+ attrName: string,
+ settingKey: string,
+ value: unknown,
+) => void;
+
+type ReactListAttributesScope = {
+ attrs: ReactListAttribute[];
+ attrSettings: ReactListAttrSettings;
+ updateAttr: ReactListAttributesUpdateAttr;
+};
+
+type ReactListAttributesRenderAttributeArgs = {
+ key: string;
+ attr: ReactListAttribute;
+ updateAttr: ReactListAttributesUpdateAttr;
+ attrSettings: ReactListAttrSettings;
+};
+
+type ReactListAttributesProps = {
+ children?: ReactNode | ((scope: ReactListAttributesScope) => ReactNode);
+ renderAttribute?: (args: ReactListAttributesRenderAttributeArgs) => ReactNode;
+};
+
+export const ReactListAttributes = memo(
+ ({ children, renderAttribute }: ReactListAttributesProps) => {
+ const { listState } = useListContext();
+ const { attrs, attrSettings, updateAttr } = listState;
+
+ const handleAttrChange = useCallback(
+ (attrName: string) => (e: ChangeEvent
) => {
+ updateAttr(attrName, "visible", e.target.checked);
+ },
+ [updateAttr],
+ );
+
+ const scope = useMemo(
+ () => ({
+ attrs,
+ attrSettings,
+ updateAttr,
+ }),
+ [attrs, attrSettings, updateAttr],
+ );
+
+ if (typeof children === "function") {
+ return children(scope);
+ }
+
+ if (children) return children;
+
+ return (
+
+ {attrs.map((attr, index) => {
+ if (renderAttribute) {
+ return (
+
+ {renderAttribute({
+ key: `attr-${index}`,
+ attr,
+ updateAttr,
+ attrSettings,
+ })}
+
+ );
+ }
+
+ return (
+
+ );
+ })}
+
+ );
+ },
+);
diff --git a/packages/react-list/src/components/empty.jsx b/packages/react-list/src/components/empty.tsx
similarity index 77%
rename from packages/react-list/src/components/empty.jsx
rename to packages/react-list/src/components/empty.tsx
index 40dbeed..c97be07 100644
--- a/packages/react-list/src/components/empty.jsx
+++ b/packages/react-list/src/components/empty.tsx
@@ -1,7 +1,11 @@
import { memo } from "react";
import { useListContext } from "../context/list-provider";
-export const ReactListEmpty = memo(({ children }) => {
+type ReactListEmptyProps = {
+ children?: React.ReactNode;
+};
+
+export const ReactListEmpty = memo(({ children }: ReactListEmptyProps) => {
const { listState } = useListContext();
const { data: items, loader, error } = listState;
const { isLoading, initialLoading } = loader;
diff --git a/packages/react-list/src/components/error.jsx b/packages/react-list/src/components/error.tsx
similarity index 72%
rename from packages/react-list/src/components/error.jsx
rename to packages/react-list/src/components/error.tsx
index 957ad19..476b5d0 100644
--- a/packages/react-list/src/components/error.jsx
+++ b/packages/react-list/src/components/error.tsx
@@ -1,7 +1,14 @@
import { memo } from "react";
+import type { ReactNode } from "react";
import { useListContext } from "../context/list-provider";
-export const ReactListError = memo(({ children }) => {
+type ReactListErrorProps = {
+ children?:
+ | ReactNode
+ | ((scope: { error: Error }) => ReactNode);
+};
+
+export const ReactListError = memo(({ children }: ReactListErrorProps) => {
const { listState } = useListContext();
const { error, loader } = listState;
const { isLoading } = loader;
diff --git a/packages/react-list/src/components/go-to.jsx b/packages/react-list/src/components/go-to.tsx
similarity index 73%
rename from packages/react-list/src/components/go-to.jsx
rename to packages/react-list/src/components/go-to.tsx
index 361162b..a076d86 100644
--- a/packages/react-list/src/components/go-to.jsx
+++ b/packages/react-list/src/components/go-to.tsx
@@ -1,7 +1,21 @@
import { memo, useCallback, useMemo } from "react";
+import type { ReactNode } from "react";
import { useListContext } from "../context/list-provider";
-export const ReactListGoTo = memo(({ children }) => {
+type ReactListGoToScope = {
+ setPage: (page: number, addContext?: Record) => void;
+ page: number;
+ pages: number[];
+ pagesCount: number;
+};
+
+type ReactListGoToProps = {
+ children?:
+ | ReactNode
+ | ((scope: ReactListGoToScope) => ReactNode);
+};
+
+export const ReactListGoTo = memo(({ children }: ReactListGoToProps) => {
const { listState } = useListContext();
const { data, count, pagination, setPage, loader, error } = listState;
const { page, perPage } = pagination;
@@ -42,8 +56,10 @@ export const ReactListGoTo = memo(({ children }) => {
return (
- {children ? (
+ {typeof children === "function" ? (
children(scope)
+ ) : children ? (
+ children
) : (