react-data-state 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/dist/index.d.mts +81 -0
- package/dist/index.d.ts +81 -0
- package/dist/index.js +310 -0
- package/dist/index.mjs +270 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# react-data-state
|
|
2
|
+
|
|
3
|
+
A lightweight React component to handle `loading`, `error`, and `empty` states with clean defaults and easy customization.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install react-data-state
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { DataState } from "react-data-state";
|
|
15
|
+
|
|
16
|
+
<DataState
|
|
17
|
+
loading={loading}
|
|
18
|
+
error={error}
|
|
19
|
+
data={users}
|
|
20
|
+
onRetry={refetchUsers}
|
|
21
|
+
emptyProps={{ message: "No users found" }}
|
|
22
|
+
>
|
|
23
|
+
{(data) => (
|
|
24
|
+
<ul>
|
|
25
|
+
{data.map((user) => (
|
|
26
|
+
<li key={user.id}>{user.name}</li>
|
|
27
|
+
))}
|
|
28
|
+
</ul>
|
|
29
|
+
)}
|
|
30
|
+
</DataState>;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Optional: Customize
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<DataState
|
|
37
|
+
loading={loading} // define your loading state
|
|
38
|
+
error={error} // define your error state
|
|
39
|
+
data={users} // define your data state
|
|
40
|
+
onRetry={refetch} // define your retry function
|
|
41
|
+
loadingProps={{
|
|
42
|
+
color: "#22c55e", // color of the spinner
|
|
43
|
+
trackColor: "#1f2937", // color of the spinner track
|
|
44
|
+
size: 30, // size of the spinner in pixels
|
|
45
|
+
thickness: 3, // thickness of the spinner
|
|
46
|
+
speedMs: 650, // speed in milliseconds for one full rotation
|
|
47
|
+
}}
|
|
48
|
+
errorProps={{
|
|
49
|
+
bgColor: "#2a1010", // background color of the error message
|
|
50
|
+
borderColor: "#ef4444", // border color of the error message
|
|
51
|
+
color: "#fecaca", // text color of the error message
|
|
52
|
+
retryLabel: "Try again", // label for the retry button
|
|
53
|
+
buttonBgColor: "#ef4444", // background color of the retry button
|
|
54
|
+
buttonTextColor: "#111827", // text color of the retry button
|
|
55
|
+
}}
|
|
56
|
+
emptyProps={{
|
|
57
|
+
message: "No users found", // message to display when data is empty
|
|
58
|
+
icon: "🔎", // Support: emoji, image, inline svg, Font Awesome element and React-icons component type
|
|
59
|
+
color: "#94a3b8", // text color of the empty message
|
|
60
|
+
bgColor: "#0f172a", // background color of the empty message
|
|
61
|
+
}}
|
|
62
|
+
>
|
|
63
|
+
{(data) => (
|
|
64
|
+
// Render your data here, like example above ↑
|
|
65
|
+
)}
|
|
66
|
+
</DataState>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Repo
|
|
70
|
+
|
|
71
|
+
GitHub: https://github.com/Bunheng-Dev/react-data-state
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React, { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type EmptyStateProps = {
|
|
5
|
+
message?: string;
|
|
6
|
+
icon?: React.ReactNode | React.ComponentType<{
|
|
7
|
+
style?: CSSProperties;
|
|
8
|
+
className?: string;
|
|
9
|
+
"aria-hidden"?: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
color?: string;
|
|
12
|
+
iconColor?: string;
|
|
13
|
+
bgColor?: string;
|
|
14
|
+
minHeight?: number | string;
|
|
15
|
+
fontSize?: number;
|
|
16
|
+
iconSize?: number;
|
|
17
|
+
gap?: number;
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
contentStyle?: CSSProperties;
|
|
20
|
+
iconContainerStyle?: CSSProperties;
|
|
21
|
+
iconStyle?: CSSProperties;
|
|
22
|
+
};
|
|
23
|
+
declare function EmptyState({ message, icon, color, iconColor, bgColor, minHeight, fontSize, iconSize, gap, style, contentStyle, iconContainerStyle, iconStyle, }: EmptyStateProps): react_jsx_runtime.JSX.Element;
|
|
24
|
+
|
|
25
|
+
type ErrorBoxProps = {
|
|
26
|
+
message?: string;
|
|
27
|
+
onRetry?: () => void;
|
|
28
|
+
bgColor?: string;
|
|
29
|
+
borderColor?: string;
|
|
30
|
+
color?: string;
|
|
31
|
+
borderRadius?: number;
|
|
32
|
+
padding?: number | string;
|
|
33
|
+
gap?: number;
|
|
34
|
+
retryLabel?: string;
|
|
35
|
+
buttonBgColor?: string;
|
|
36
|
+
buttonTextColor?: string;
|
|
37
|
+
buttonBorderColor?: string;
|
|
38
|
+
buttonBorderRadius?: number;
|
|
39
|
+
style?: CSSProperties;
|
|
40
|
+
buttonStyle?: CSSProperties;
|
|
41
|
+
};
|
|
42
|
+
declare function ErrorBox({ message, onRetry, bgColor, borderColor, color, borderRadius, padding, gap, retryLabel, buttonBgColor, buttonTextColor, buttonBorderColor, buttonBorderRadius, style, buttonStyle, }: ErrorBoxProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
|
|
44
|
+
type LoadingSpinnerProps = {
|
|
45
|
+
label?: string;
|
|
46
|
+
color?: string;
|
|
47
|
+
trackColor?: string;
|
|
48
|
+
size?: number;
|
|
49
|
+
thickness?: number;
|
|
50
|
+
minHeight?: number | string;
|
|
51
|
+
speedMs?: number;
|
|
52
|
+
style?: CSSProperties;
|
|
53
|
+
spinnerStyle?: CSSProperties;
|
|
54
|
+
};
|
|
55
|
+
declare function LoadingSpinner({ label, color, trackColor, size, thickness, minHeight, speedMs, style, spinnerStyle, }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
type StateContext<T> = {
|
|
58
|
+
loading: boolean;
|
|
59
|
+
error: unknown;
|
|
60
|
+
data?: T;
|
|
61
|
+
errorMessage: string;
|
|
62
|
+
onRetry?: () => void;
|
|
63
|
+
};
|
|
64
|
+
type StateRenderer<T> = ReactNode | ((context: StateContext<T>) => ReactNode);
|
|
65
|
+
type DataStateProps<T> = {
|
|
66
|
+
loading?: boolean;
|
|
67
|
+
error?: unknown;
|
|
68
|
+
data?: T;
|
|
69
|
+
children: ((data: T) => ReactNode) | ReactNode;
|
|
70
|
+
loadingComponent?: StateRenderer<T>;
|
|
71
|
+
errorComponent?: StateRenderer<T>;
|
|
72
|
+
emptyComponent?: StateRenderer<T>;
|
|
73
|
+
loadingProps?: LoadingSpinnerProps;
|
|
74
|
+
errorProps?: ErrorBoxProps;
|
|
75
|
+
emptyProps?: EmptyStateProps;
|
|
76
|
+
onRetry?: () => void;
|
|
77
|
+
isEmpty?: (data: T) => boolean;
|
|
78
|
+
};
|
|
79
|
+
declare function DataState<T>({ loading, error, data, children, errorComponent, loadingComponent, emptyComponent, loadingProps, errorProps, emptyProps, onRetry, isEmpty, }: DataStateProps<T>): react_jsx_runtime.JSX.Element;
|
|
80
|
+
|
|
81
|
+
export { DataState, type DataStateProps, EmptyState, type EmptyStateProps, ErrorBox, type ErrorBoxProps, LoadingSpinner, type LoadingSpinnerProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React, { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type EmptyStateProps = {
|
|
5
|
+
message?: string;
|
|
6
|
+
icon?: React.ReactNode | React.ComponentType<{
|
|
7
|
+
style?: CSSProperties;
|
|
8
|
+
className?: string;
|
|
9
|
+
"aria-hidden"?: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
color?: string;
|
|
12
|
+
iconColor?: string;
|
|
13
|
+
bgColor?: string;
|
|
14
|
+
minHeight?: number | string;
|
|
15
|
+
fontSize?: number;
|
|
16
|
+
iconSize?: number;
|
|
17
|
+
gap?: number;
|
|
18
|
+
style?: CSSProperties;
|
|
19
|
+
contentStyle?: CSSProperties;
|
|
20
|
+
iconContainerStyle?: CSSProperties;
|
|
21
|
+
iconStyle?: CSSProperties;
|
|
22
|
+
};
|
|
23
|
+
declare function EmptyState({ message, icon, color, iconColor, bgColor, minHeight, fontSize, iconSize, gap, style, contentStyle, iconContainerStyle, iconStyle, }: EmptyStateProps): react_jsx_runtime.JSX.Element;
|
|
24
|
+
|
|
25
|
+
type ErrorBoxProps = {
|
|
26
|
+
message?: string;
|
|
27
|
+
onRetry?: () => void;
|
|
28
|
+
bgColor?: string;
|
|
29
|
+
borderColor?: string;
|
|
30
|
+
color?: string;
|
|
31
|
+
borderRadius?: number;
|
|
32
|
+
padding?: number | string;
|
|
33
|
+
gap?: number;
|
|
34
|
+
retryLabel?: string;
|
|
35
|
+
buttonBgColor?: string;
|
|
36
|
+
buttonTextColor?: string;
|
|
37
|
+
buttonBorderColor?: string;
|
|
38
|
+
buttonBorderRadius?: number;
|
|
39
|
+
style?: CSSProperties;
|
|
40
|
+
buttonStyle?: CSSProperties;
|
|
41
|
+
};
|
|
42
|
+
declare function ErrorBox({ message, onRetry, bgColor, borderColor, color, borderRadius, padding, gap, retryLabel, buttonBgColor, buttonTextColor, buttonBorderColor, buttonBorderRadius, style, buttonStyle, }: ErrorBoxProps): react_jsx_runtime.JSX.Element;
|
|
43
|
+
|
|
44
|
+
type LoadingSpinnerProps = {
|
|
45
|
+
label?: string;
|
|
46
|
+
color?: string;
|
|
47
|
+
trackColor?: string;
|
|
48
|
+
size?: number;
|
|
49
|
+
thickness?: number;
|
|
50
|
+
minHeight?: number | string;
|
|
51
|
+
speedMs?: number;
|
|
52
|
+
style?: CSSProperties;
|
|
53
|
+
spinnerStyle?: CSSProperties;
|
|
54
|
+
};
|
|
55
|
+
declare function LoadingSpinner({ label, color, trackColor, size, thickness, minHeight, speedMs, style, spinnerStyle, }: LoadingSpinnerProps): react_jsx_runtime.JSX.Element;
|
|
56
|
+
|
|
57
|
+
type StateContext<T> = {
|
|
58
|
+
loading: boolean;
|
|
59
|
+
error: unknown;
|
|
60
|
+
data?: T;
|
|
61
|
+
errorMessage: string;
|
|
62
|
+
onRetry?: () => void;
|
|
63
|
+
};
|
|
64
|
+
type StateRenderer<T> = ReactNode | ((context: StateContext<T>) => ReactNode);
|
|
65
|
+
type DataStateProps<T> = {
|
|
66
|
+
loading?: boolean;
|
|
67
|
+
error?: unknown;
|
|
68
|
+
data?: T;
|
|
69
|
+
children: ((data: T) => ReactNode) | ReactNode;
|
|
70
|
+
loadingComponent?: StateRenderer<T>;
|
|
71
|
+
errorComponent?: StateRenderer<T>;
|
|
72
|
+
emptyComponent?: StateRenderer<T>;
|
|
73
|
+
loadingProps?: LoadingSpinnerProps;
|
|
74
|
+
errorProps?: ErrorBoxProps;
|
|
75
|
+
emptyProps?: EmptyStateProps;
|
|
76
|
+
onRetry?: () => void;
|
|
77
|
+
isEmpty?: (data: T) => boolean;
|
|
78
|
+
};
|
|
79
|
+
declare function DataState<T>({ loading, error, data, children, errorComponent, loadingComponent, emptyComponent, loadingProps, errorProps, emptyProps, onRetry, isEmpty, }: DataStateProps<T>): react_jsx_runtime.JSX.Element;
|
|
80
|
+
|
|
81
|
+
export { DataState, type DataStateProps, EmptyState, type EmptyStateProps, ErrorBox, type ErrorBoxProps, LoadingSpinner, type LoadingSpinnerProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
DataState: () => DataState,
|
|
34
|
+
EmptyState: () => EmptyState,
|
|
35
|
+
ErrorBox: () => ErrorBox,
|
|
36
|
+
LoadingSpinner: () => LoadingSpinner
|
|
37
|
+
});
|
|
38
|
+
module.exports = __toCommonJS(index_exports);
|
|
39
|
+
|
|
40
|
+
// src/components/EmptyState.tsx
|
|
41
|
+
var import_react = __toESM(require("react"));
|
|
42
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
43
|
+
var baseContainerStyle = {
|
|
44
|
+
minHeight: 120,
|
|
45
|
+
padding: 12,
|
|
46
|
+
display: "flex",
|
|
47
|
+
alignItems: "center",
|
|
48
|
+
justifyContent: "center",
|
|
49
|
+
textAlign: "center",
|
|
50
|
+
color: "#6b7280"
|
|
51
|
+
};
|
|
52
|
+
var baseContentStyle = {
|
|
53
|
+
display: "inline-flex",
|
|
54
|
+
alignItems: "center",
|
|
55
|
+
gap: 8,
|
|
56
|
+
fontSize: 15
|
|
57
|
+
};
|
|
58
|
+
function EmptyState({
|
|
59
|
+
message = "No data found",
|
|
60
|
+
icon = "\u{1F5ED}",
|
|
61
|
+
color = "#6b7280",
|
|
62
|
+
iconColor,
|
|
63
|
+
bgColor,
|
|
64
|
+
minHeight = 120,
|
|
65
|
+
fontSize = 15,
|
|
66
|
+
iconSize,
|
|
67
|
+
gap = 8,
|
|
68
|
+
style,
|
|
69
|
+
contentStyle,
|
|
70
|
+
iconContainerStyle,
|
|
71
|
+
iconStyle
|
|
72
|
+
}) {
|
|
73
|
+
const containerStyle = {
|
|
74
|
+
...baseContainerStyle,
|
|
75
|
+
minHeight,
|
|
76
|
+
color,
|
|
77
|
+
backgroundColor: bgColor,
|
|
78
|
+
...style
|
|
79
|
+
};
|
|
80
|
+
const resolvedContentStyle = {
|
|
81
|
+
...baseContentStyle,
|
|
82
|
+
gap,
|
|
83
|
+
fontSize,
|
|
84
|
+
...contentStyle
|
|
85
|
+
};
|
|
86
|
+
const resolvedIconContainerStyle = {
|
|
87
|
+
display: "inline-flex",
|
|
88
|
+
alignItems: "center",
|
|
89
|
+
justifyContent: "center",
|
|
90
|
+
color: iconColor ?? color,
|
|
91
|
+
...iconSize ? { width: iconSize, height: iconSize, fontSize: iconSize } : null,
|
|
92
|
+
...iconContainerStyle
|
|
93
|
+
};
|
|
94
|
+
const resolvedIconStyle = {
|
|
95
|
+
...iconSize ? { width: iconSize, height: iconSize } : null,
|
|
96
|
+
...iconColor ? { color: iconColor } : null,
|
|
97
|
+
...iconStyle
|
|
98
|
+
};
|
|
99
|
+
const renderIcon = () => {
|
|
100
|
+
if (icon === null || icon === void 0 || icon === false) return null;
|
|
101
|
+
if (typeof icon === "function") {
|
|
102
|
+
const IconComponent = icon;
|
|
103
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: resolvedIconContainerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IconComponent, { "aria-hidden": true, style: resolvedIconStyle }) });
|
|
104
|
+
}
|
|
105
|
+
if (import_react.default.isValidElement(icon)) {
|
|
106
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: resolvedIconContainerStyle, children: icon });
|
|
107
|
+
}
|
|
108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: resolvedIconContainerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: resolvedIconStyle, children: icon }) });
|
|
109
|
+
};
|
|
110
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: containerStyle, role: "status", "aria-live": "polite", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: resolvedContentStyle, children: [
|
|
111
|
+
renderIcon(),
|
|
112
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: message })
|
|
113
|
+
] }) });
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/components/ErrorBox.tsx
|
|
117
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
118
|
+
var baseBoxStyle = {
|
|
119
|
+
backgroundColor: "#fef2f2",
|
|
120
|
+
border: "1px solid #fecaca",
|
|
121
|
+
borderRadius: 10,
|
|
122
|
+
color: "#991b1b",
|
|
123
|
+
padding: "14px 16px",
|
|
124
|
+
display: "flex",
|
|
125
|
+
flexDirection: "column",
|
|
126
|
+
gap: 12
|
|
127
|
+
};
|
|
128
|
+
var baseRetryStyle = {
|
|
129
|
+
alignSelf: "center",
|
|
130
|
+
backgroundColor: "#b91c1c",
|
|
131
|
+
color: "#ffffff",
|
|
132
|
+
border: 0,
|
|
133
|
+
borderRadius: 8,
|
|
134
|
+
padding: "8px 12px",
|
|
135
|
+
fontSize: 14,
|
|
136
|
+
lineHeight: 1.1,
|
|
137
|
+
cursor: "pointer"
|
|
138
|
+
};
|
|
139
|
+
function ErrorBox({
|
|
140
|
+
message = "Something went wrong",
|
|
141
|
+
onRetry,
|
|
142
|
+
bgColor = "#fef2f2",
|
|
143
|
+
borderColor = "#fecaca",
|
|
144
|
+
color = "#991b1b",
|
|
145
|
+
borderRadius = 10,
|
|
146
|
+
padding = "14px 16px",
|
|
147
|
+
gap = 12,
|
|
148
|
+
retryLabel = "Retry",
|
|
149
|
+
buttonBgColor = "#b91c1c",
|
|
150
|
+
buttonTextColor = "#ffffff",
|
|
151
|
+
buttonBorderColor = "transparent",
|
|
152
|
+
buttonBorderRadius = 8,
|
|
153
|
+
style,
|
|
154
|
+
buttonStyle
|
|
155
|
+
}) {
|
|
156
|
+
const boxStyle = {
|
|
157
|
+
...baseBoxStyle,
|
|
158
|
+
backgroundColor: bgColor,
|
|
159
|
+
border: `1px solid ${borderColor}`,
|
|
160
|
+
color,
|
|
161
|
+
borderRadius,
|
|
162
|
+
padding,
|
|
163
|
+
gap,
|
|
164
|
+
...style
|
|
165
|
+
};
|
|
166
|
+
const retryStyle = {
|
|
167
|
+
...baseRetryStyle,
|
|
168
|
+
backgroundColor: buttonBgColor,
|
|
169
|
+
color: buttonTextColor,
|
|
170
|
+
border: `1px solid ${buttonBorderColor}`,
|
|
171
|
+
borderRadius: buttonBorderRadius,
|
|
172
|
+
...buttonStyle
|
|
173
|
+
};
|
|
174
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: boxStyle, role: "alert", "aria-live": "assertive", children: [
|
|
175
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { children: message }),
|
|
176
|
+
onRetry ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: onRetry, style: retryStyle, children: retryLabel }) : null
|
|
177
|
+
] });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/components/LoadingSpinner.tsx
|
|
181
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
182
|
+
var baseContainerStyle2 = {
|
|
183
|
+
minHeight: 120,
|
|
184
|
+
position: "relative",
|
|
185
|
+
display: "flex",
|
|
186
|
+
alignItems: "center",
|
|
187
|
+
justifyContent: "center"
|
|
188
|
+
};
|
|
189
|
+
function LoadingSpinner({
|
|
190
|
+
label = "Loading",
|
|
191
|
+
color = "#2563eb",
|
|
192
|
+
trackColor = "#d1d5db",
|
|
193
|
+
size = 28,
|
|
194
|
+
thickness = 3,
|
|
195
|
+
minHeight = 120,
|
|
196
|
+
speedMs = 750,
|
|
197
|
+
style,
|
|
198
|
+
spinnerStyle
|
|
199
|
+
}) {
|
|
200
|
+
const containerStyle = {
|
|
201
|
+
...baseContainerStyle2,
|
|
202
|
+
minHeight,
|
|
203
|
+
...style
|
|
204
|
+
};
|
|
205
|
+
const resolvedSpinnerStyle = {
|
|
206
|
+
width: size,
|
|
207
|
+
height: size,
|
|
208
|
+
borderRadius: "50%",
|
|
209
|
+
border: `${thickness}px solid ${trackColor}`,
|
|
210
|
+
borderTopColor: color,
|
|
211
|
+
animation: `rds-spin ${speedMs}ms linear infinite`,
|
|
212
|
+
...spinnerStyle
|
|
213
|
+
};
|
|
214
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: containerStyle, "aria-busy": "true", "aria-live": "polite", role: "status", children: [
|
|
215
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("style", { children: "@keyframes rds-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }" }),
|
|
216
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: resolvedSpinnerStyle, "aria-hidden": "true" }),
|
|
217
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
218
|
+
"span",
|
|
219
|
+
{
|
|
220
|
+
style: {
|
|
221
|
+
position: "absolute",
|
|
222
|
+
width: 1,
|
|
223
|
+
height: 1,
|
|
224
|
+
padding: 0,
|
|
225
|
+
margin: -1,
|
|
226
|
+
overflow: "hidden",
|
|
227
|
+
clip: "rect(0, 0, 0, 0)",
|
|
228
|
+
whiteSpace: "nowrap",
|
|
229
|
+
border: 0
|
|
230
|
+
},
|
|
231
|
+
children: label
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
] });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/DataState.tsx
|
|
238
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
239
|
+
var transitionStyle = {
|
|
240
|
+
animation: "rds-fade-in 180ms ease-out"
|
|
241
|
+
};
|
|
242
|
+
function StateTransition({ children }) {
|
|
243
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: transitionStyle, children: [
|
|
244
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: "@keyframes rds-fade-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } }" }),
|
|
245
|
+
children
|
|
246
|
+
] });
|
|
247
|
+
}
|
|
248
|
+
function resolveStateRenderer(renderer, context) {
|
|
249
|
+
if (renderer === void 0) return void 0;
|
|
250
|
+
return typeof renderer === "function" ? renderer(context) : renderer;
|
|
251
|
+
}
|
|
252
|
+
function getErrorMessage(error) {
|
|
253
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
254
|
+
const maybeMessage = error.message;
|
|
255
|
+
if (typeof maybeMessage === "string" && maybeMessage.trim().length > 0) return maybeMessage;
|
|
256
|
+
}
|
|
257
|
+
if (typeof error === "string" && error.trim().length > 0) return error;
|
|
258
|
+
return "Something went wrong";
|
|
259
|
+
}
|
|
260
|
+
function DataState({
|
|
261
|
+
loading,
|
|
262
|
+
error,
|
|
263
|
+
data,
|
|
264
|
+
children,
|
|
265
|
+
errorComponent,
|
|
266
|
+
loadingComponent,
|
|
267
|
+
emptyComponent,
|
|
268
|
+
loadingProps,
|
|
269
|
+
errorProps,
|
|
270
|
+
emptyProps,
|
|
271
|
+
onRetry,
|
|
272
|
+
isEmpty
|
|
273
|
+
}) {
|
|
274
|
+
const errorMessage = getErrorMessage(error);
|
|
275
|
+
const context = {
|
|
276
|
+
loading: Boolean(loading),
|
|
277
|
+
error,
|
|
278
|
+
data,
|
|
279
|
+
errorMessage,
|
|
280
|
+
onRetry
|
|
281
|
+
};
|
|
282
|
+
if (loading) {
|
|
283
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StateTransition, { children: resolveStateRenderer(loadingComponent, context) ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(LoadingSpinner, { ...loadingProps }) }, "loading");
|
|
284
|
+
}
|
|
285
|
+
if (error) {
|
|
286
|
+
const resolvedErrorProps = {
|
|
287
|
+
...errorProps,
|
|
288
|
+
message: errorProps?.message ?? errorMessage,
|
|
289
|
+
onRetry: errorProps?.onRetry ?? onRetry
|
|
290
|
+
};
|
|
291
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StateTransition, { children: resolveStateRenderer(errorComponent, context) ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ErrorBox, { ...resolvedErrorProps }) }, "error");
|
|
292
|
+
}
|
|
293
|
+
const hasNoData = data === void 0 || data === null;
|
|
294
|
+
const emptyByDefault = !hasNoData && Array.isArray(data) && data.length === 0;
|
|
295
|
+
const empty = hasNoData || !hasNoData && (isEmpty ? isEmpty(data) : emptyByDefault);
|
|
296
|
+
if (empty) {
|
|
297
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(StateTransition, { children: resolveStateRenderer(emptyComponent, context) ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(EmptyState, { ...emptyProps }) }, "empty");
|
|
298
|
+
}
|
|
299
|
+
if (typeof children === "function") {
|
|
300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: children(data) });
|
|
301
|
+
}
|
|
302
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
|
|
303
|
+
}
|
|
304
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
305
|
+
0 && (module.exports = {
|
|
306
|
+
DataState,
|
|
307
|
+
EmptyState,
|
|
308
|
+
ErrorBox,
|
|
309
|
+
LoadingSpinner
|
|
310
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
// src/components/EmptyState.tsx
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var baseContainerStyle = {
|
|
5
|
+
minHeight: 120,
|
|
6
|
+
padding: 12,
|
|
7
|
+
display: "flex",
|
|
8
|
+
alignItems: "center",
|
|
9
|
+
justifyContent: "center",
|
|
10
|
+
textAlign: "center",
|
|
11
|
+
color: "#6b7280"
|
|
12
|
+
};
|
|
13
|
+
var baseContentStyle = {
|
|
14
|
+
display: "inline-flex",
|
|
15
|
+
alignItems: "center",
|
|
16
|
+
gap: 8,
|
|
17
|
+
fontSize: 15
|
|
18
|
+
};
|
|
19
|
+
function EmptyState({
|
|
20
|
+
message = "No data found",
|
|
21
|
+
icon = "\u{1F5ED}",
|
|
22
|
+
color = "#6b7280",
|
|
23
|
+
iconColor,
|
|
24
|
+
bgColor,
|
|
25
|
+
minHeight = 120,
|
|
26
|
+
fontSize = 15,
|
|
27
|
+
iconSize,
|
|
28
|
+
gap = 8,
|
|
29
|
+
style,
|
|
30
|
+
contentStyle,
|
|
31
|
+
iconContainerStyle,
|
|
32
|
+
iconStyle
|
|
33
|
+
}) {
|
|
34
|
+
const containerStyle = {
|
|
35
|
+
...baseContainerStyle,
|
|
36
|
+
minHeight,
|
|
37
|
+
color,
|
|
38
|
+
backgroundColor: bgColor,
|
|
39
|
+
...style
|
|
40
|
+
};
|
|
41
|
+
const resolvedContentStyle = {
|
|
42
|
+
...baseContentStyle,
|
|
43
|
+
gap,
|
|
44
|
+
fontSize,
|
|
45
|
+
...contentStyle
|
|
46
|
+
};
|
|
47
|
+
const resolvedIconContainerStyle = {
|
|
48
|
+
display: "inline-flex",
|
|
49
|
+
alignItems: "center",
|
|
50
|
+
justifyContent: "center",
|
|
51
|
+
color: iconColor ?? color,
|
|
52
|
+
...iconSize ? { width: iconSize, height: iconSize, fontSize: iconSize } : null,
|
|
53
|
+
...iconContainerStyle
|
|
54
|
+
};
|
|
55
|
+
const resolvedIconStyle = {
|
|
56
|
+
...iconSize ? { width: iconSize, height: iconSize } : null,
|
|
57
|
+
...iconColor ? { color: iconColor } : null,
|
|
58
|
+
...iconStyle
|
|
59
|
+
};
|
|
60
|
+
const renderIcon = () => {
|
|
61
|
+
if (icon === null || icon === void 0 || icon === false) return null;
|
|
62
|
+
if (typeof icon === "function") {
|
|
63
|
+
const IconComponent = icon;
|
|
64
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: resolvedIconContainerStyle, children: /* @__PURE__ */ jsx(IconComponent, { "aria-hidden": true, style: resolvedIconStyle }) });
|
|
65
|
+
}
|
|
66
|
+
if (React.isValidElement(icon)) {
|
|
67
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: resolvedIconContainerStyle, children: icon });
|
|
68
|
+
}
|
|
69
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", style: resolvedIconContainerStyle, children: /* @__PURE__ */ jsx("span", { style: resolvedIconStyle, children: icon }) });
|
|
70
|
+
};
|
|
71
|
+
return /* @__PURE__ */ jsx("div", { style: containerStyle, role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsxs("div", { style: resolvedContentStyle, children: [
|
|
72
|
+
renderIcon(),
|
|
73
|
+
/* @__PURE__ */ jsx("span", { children: message })
|
|
74
|
+
] }) });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/components/ErrorBox.tsx
|
|
78
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
79
|
+
var baseBoxStyle = {
|
|
80
|
+
backgroundColor: "#fef2f2",
|
|
81
|
+
border: "1px solid #fecaca",
|
|
82
|
+
borderRadius: 10,
|
|
83
|
+
color: "#991b1b",
|
|
84
|
+
padding: "14px 16px",
|
|
85
|
+
display: "flex",
|
|
86
|
+
flexDirection: "column",
|
|
87
|
+
gap: 12
|
|
88
|
+
};
|
|
89
|
+
var baseRetryStyle = {
|
|
90
|
+
alignSelf: "center",
|
|
91
|
+
backgroundColor: "#b91c1c",
|
|
92
|
+
color: "#ffffff",
|
|
93
|
+
border: 0,
|
|
94
|
+
borderRadius: 8,
|
|
95
|
+
padding: "8px 12px",
|
|
96
|
+
fontSize: 14,
|
|
97
|
+
lineHeight: 1.1,
|
|
98
|
+
cursor: "pointer"
|
|
99
|
+
};
|
|
100
|
+
function ErrorBox({
|
|
101
|
+
message = "Something went wrong",
|
|
102
|
+
onRetry,
|
|
103
|
+
bgColor = "#fef2f2",
|
|
104
|
+
borderColor = "#fecaca",
|
|
105
|
+
color = "#991b1b",
|
|
106
|
+
borderRadius = 10,
|
|
107
|
+
padding = "14px 16px",
|
|
108
|
+
gap = 12,
|
|
109
|
+
retryLabel = "Retry",
|
|
110
|
+
buttonBgColor = "#b91c1c",
|
|
111
|
+
buttonTextColor = "#ffffff",
|
|
112
|
+
buttonBorderColor = "transparent",
|
|
113
|
+
buttonBorderRadius = 8,
|
|
114
|
+
style,
|
|
115
|
+
buttonStyle
|
|
116
|
+
}) {
|
|
117
|
+
const boxStyle = {
|
|
118
|
+
...baseBoxStyle,
|
|
119
|
+
backgroundColor: bgColor,
|
|
120
|
+
border: `1px solid ${borderColor}`,
|
|
121
|
+
color,
|
|
122
|
+
borderRadius,
|
|
123
|
+
padding,
|
|
124
|
+
gap,
|
|
125
|
+
...style
|
|
126
|
+
};
|
|
127
|
+
const retryStyle = {
|
|
128
|
+
...baseRetryStyle,
|
|
129
|
+
backgroundColor: buttonBgColor,
|
|
130
|
+
color: buttonTextColor,
|
|
131
|
+
border: `1px solid ${buttonBorderColor}`,
|
|
132
|
+
borderRadius: buttonBorderRadius,
|
|
133
|
+
...buttonStyle
|
|
134
|
+
};
|
|
135
|
+
return /* @__PURE__ */ jsxs2("div", { style: boxStyle, role: "alert", "aria-live": "assertive", children: [
|
|
136
|
+
/* @__PURE__ */ jsx2("div", { children: message }),
|
|
137
|
+
onRetry ? /* @__PURE__ */ jsx2("button", { type: "button", onClick: onRetry, style: retryStyle, children: retryLabel }) : null
|
|
138
|
+
] });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/components/LoadingSpinner.tsx
|
|
142
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
143
|
+
var baseContainerStyle2 = {
|
|
144
|
+
minHeight: 120,
|
|
145
|
+
position: "relative",
|
|
146
|
+
display: "flex",
|
|
147
|
+
alignItems: "center",
|
|
148
|
+
justifyContent: "center"
|
|
149
|
+
};
|
|
150
|
+
function LoadingSpinner({
|
|
151
|
+
label = "Loading",
|
|
152
|
+
color = "#2563eb",
|
|
153
|
+
trackColor = "#d1d5db",
|
|
154
|
+
size = 28,
|
|
155
|
+
thickness = 3,
|
|
156
|
+
minHeight = 120,
|
|
157
|
+
speedMs = 750,
|
|
158
|
+
style,
|
|
159
|
+
spinnerStyle
|
|
160
|
+
}) {
|
|
161
|
+
const containerStyle = {
|
|
162
|
+
...baseContainerStyle2,
|
|
163
|
+
minHeight,
|
|
164
|
+
...style
|
|
165
|
+
};
|
|
166
|
+
const resolvedSpinnerStyle = {
|
|
167
|
+
width: size,
|
|
168
|
+
height: size,
|
|
169
|
+
borderRadius: "50%",
|
|
170
|
+
border: `${thickness}px solid ${trackColor}`,
|
|
171
|
+
borderTopColor: color,
|
|
172
|
+
animation: `rds-spin ${speedMs}ms linear infinite`,
|
|
173
|
+
...spinnerStyle
|
|
174
|
+
};
|
|
175
|
+
return /* @__PURE__ */ jsxs3("div", { style: containerStyle, "aria-busy": "true", "aria-live": "polite", role: "status", children: [
|
|
176
|
+
/* @__PURE__ */ jsx3("style", { children: "@keyframes rds-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }" }),
|
|
177
|
+
/* @__PURE__ */ jsx3("div", { style: resolvedSpinnerStyle, "aria-hidden": "true" }),
|
|
178
|
+
/* @__PURE__ */ jsx3(
|
|
179
|
+
"span",
|
|
180
|
+
{
|
|
181
|
+
style: {
|
|
182
|
+
position: "absolute",
|
|
183
|
+
width: 1,
|
|
184
|
+
height: 1,
|
|
185
|
+
padding: 0,
|
|
186
|
+
margin: -1,
|
|
187
|
+
overflow: "hidden",
|
|
188
|
+
clip: "rect(0, 0, 0, 0)",
|
|
189
|
+
whiteSpace: "nowrap",
|
|
190
|
+
border: 0
|
|
191
|
+
},
|
|
192
|
+
children: label
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
] });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/DataState.tsx
|
|
199
|
+
import { Fragment, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
200
|
+
var transitionStyle = {
|
|
201
|
+
animation: "rds-fade-in 180ms ease-out"
|
|
202
|
+
};
|
|
203
|
+
function StateTransition({ children }) {
|
|
204
|
+
return /* @__PURE__ */ jsxs4("div", { style: transitionStyle, children: [
|
|
205
|
+
/* @__PURE__ */ jsx4("style", { children: "@keyframes rds-fade-in { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: translateY(0); } }" }),
|
|
206
|
+
children
|
|
207
|
+
] });
|
|
208
|
+
}
|
|
209
|
+
function resolveStateRenderer(renderer, context) {
|
|
210
|
+
if (renderer === void 0) return void 0;
|
|
211
|
+
return typeof renderer === "function" ? renderer(context) : renderer;
|
|
212
|
+
}
|
|
213
|
+
function getErrorMessage(error) {
|
|
214
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
215
|
+
const maybeMessage = error.message;
|
|
216
|
+
if (typeof maybeMessage === "string" && maybeMessage.trim().length > 0) return maybeMessage;
|
|
217
|
+
}
|
|
218
|
+
if (typeof error === "string" && error.trim().length > 0) return error;
|
|
219
|
+
return "Something went wrong";
|
|
220
|
+
}
|
|
221
|
+
function DataState({
|
|
222
|
+
loading,
|
|
223
|
+
error,
|
|
224
|
+
data,
|
|
225
|
+
children,
|
|
226
|
+
errorComponent,
|
|
227
|
+
loadingComponent,
|
|
228
|
+
emptyComponent,
|
|
229
|
+
loadingProps,
|
|
230
|
+
errorProps,
|
|
231
|
+
emptyProps,
|
|
232
|
+
onRetry,
|
|
233
|
+
isEmpty
|
|
234
|
+
}) {
|
|
235
|
+
const errorMessage = getErrorMessage(error);
|
|
236
|
+
const context = {
|
|
237
|
+
loading: Boolean(loading),
|
|
238
|
+
error,
|
|
239
|
+
data,
|
|
240
|
+
errorMessage,
|
|
241
|
+
onRetry
|
|
242
|
+
};
|
|
243
|
+
if (loading) {
|
|
244
|
+
return /* @__PURE__ */ jsx4(StateTransition, { children: resolveStateRenderer(loadingComponent, context) ?? /* @__PURE__ */ jsx4(LoadingSpinner, { ...loadingProps }) }, "loading");
|
|
245
|
+
}
|
|
246
|
+
if (error) {
|
|
247
|
+
const resolvedErrorProps = {
|
|
248
|
+
...errorProps,
|
|
249
|
+
message: errorProps?.message ?? errorMessage,
|
|
250
|
+
onRetry: errorProps?.onRetry ?? onRetry
|
|
251
|
+
};
|
|
252
|
+
return /* @__PURE__ */ jsx4(StateTransition, { children: resolveStateRenderer(errorComponent, context) ?? /* @__PURE__ */ jsx4(ErrorBox, { ...resolvedErrorProps }) }, "error");
|
|
253
|
+
}
|
|
254
|
+
const hasNoData = data === void 0 || data === null;
|
|
255
|
+
const emptyByDefault = !hasNoData && Array.isArray(data) && data.length === 0;
|
|
256
|
+
const empty = hasNoData || !hasNoData && (isEmpty ? isEmpty(data) : emptyByDefault);
|
|
257
|
+
if (empty) {
|
|
258
|
+
return /* @__PURE__ */ jsx4(StateTransition, { children: resolveStateRenderer(emptyComponent, context) ?? /* @__PURE__ */ jsx4(EmptyState, { ...emptyProps }) }, "empty");
|
|
259
|
+
}
|
|
260
|
+
if (typeof children === "function") {
|
|
261
|
+
return /* @__PURE__ */ jsx4(Fragment, { children: children(data) });
|
|
262
|
+
}
|
|
263
|
+
return /* @__PURE__ */ jsx4(Fragment, { children });
|
|
264
|
+
}
|
|
265
|
+
export {
|
|
266
|
+
DataState,
|
|
267
|
+
EmptyState,
|
|
268
|
+
ErrorBox,
|
|
269
|
+
LoadingSpinner
|
|
270
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-data-state",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "git+https://github.com/Bunheng-Dev/react-data-state.git"
|
|
7
|
+
},
|
|
8
|
+
"bugs": {
|
|
9
|
+
"url": "https://github.com/Bunheng-Dev/react-data-state/issues"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/Bunheng-Dev/react-data-state#readme",
|
|
12
|
+
"main": "dist/index.js",
|
|
13
|
+
"module": "dist/index.mjs",
|
|
14
|
+
"types": "dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
20
|
+
"dev": "tsup src/index.ts --watch"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": ">=18"
|
|
24
|
+
}
|
|
25
|
+
}
|