@rebasepro/ui 0.2.3 → 0.2.4
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/dist/components/Button.d.ts +2 -2
- package/dist/components/ErrorBoundary.d.ts +25 -3
- package/dist/components/VirtualTable/VirtualTable.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableCell.d.ts +6 -6
- package/dist/components/VirtualTable/VirtualTableHeader.d.ts +8 -8
- package/dist/components/VirtualTable/VirtualTableHeaderRow.d.ts +1 -1
- package/dist/components/VirtualTable/VirtualTableProps.d.ts +11 -11
- package/dist/components/VirtualTable/VirtualTableRow.d.ts +1 -1
- package/dist/components/VirtualTable/types.d.ts +9 -9
- package/dist/hooks/useDebounceCallback.d.ts +1 -1
- package/dist/index.css +1 -1
- package/dist/index.es.js +135 -76
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +130 -71
- package/dist/index.umd.js.map +1 -1
- package/dist/util/debounce.d.ts +1 -1
- package/package.json +2 -2
- package/src/components/BooleanSwitch.tsx +1 -1
- package/src/components/Button.tsx +11 -11
- package/src/components/ErrorBoundary.tsx +171 -18
- package/src/components/IconButton.tsx +4 -4
- package/src/components/MultiSelect.tsx +6 -6
- package/src/components/SearchBar.tsx +1 -1
- package/src/components/TextareaAutosize.tsx +2 -2
- package/src/components/VirtualTable/VirtualTable.tsx +8 -8
- package/src/components/VirtualTable/VirtualTableCell.tsx +6 -6
- package/src/components/VirtualTable/VirtualTableHeader.tsx +15 -15
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +6 -6
- package/src/components/VirtualTable/VirtualTableProps.tsx +11 -11
- package/src/components/VirtualTable/VirtualTableRow.tsx +2 -2
- package/src/components/VirtualTable/types.tsx +9 -9
- package/src/hooks/useDebounceCallback.tsx +2 -1
- package/src/index.css +1 -1
- package/src/util/debounce.ts +2 -1
- package/src/scripts/migrateIconImports.ts +0 -108
- package/src/scripts/test.ts +0 -6
package/dist/util/debounce.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @ignore
|
|
3
3
|
*/
|
|
4
|
-
export declare function debounce<T extends (...args: any[]) =>
|
|
4
|
+
export declare function debounce<T extends (...args: any[]) => unknown>(func: T, wait?: number): T & Cancelable;
|
|
5
5
|
/**
|
|
6
6
|
* @ignore
|
|
7
7
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.4",
|
|
5
5
|
"description": "Awesome Firebase/Firestore-based headless open-source CMS",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"react-use-measure": "^2.1.7",
|
|
73
73
|
"react-window": "^1.8.11",
|
|
74
74
|
"tailwind-merge": "^2.6.1",
|
|
75
|
-
"@rebasepro/types": "0.2.
|
|
75
|
+
"@rebasepro/types": "0.2.4"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
78
|
"react": ">=19.0.0",
|
|
@@ -29,7 +29,7 @@ export const BooleanSwitch = React.forwardRef(function BooleanSwitch({
|
|
|
29
29
|
role="switch"
|
|
30
30
|
aria-checked={allowIndeterminate && (value === null || value === undefined) ? "mixed" : !!value}
|
|
31
31
|
aria-disabled={disabled || undefined}
|
|
32
|
-
ref={ref
|
|
32
|
+
ref={ref}
|
|
33
33
|
tabIndex={disabled ? -1 : undefined}
|
|
34
34
|
onClick={disabled
|
|
35
35
|
? (e) => e.preventDefault()
|
|
@@ -12,7 +12,7 @@ export type ButtonProps<C extends React.ElementType = "button"> = {
|
|
|
12
12
|
fullWidth?: boolean;
|
|
13
13
|
className?: string;
|
|
14
14
|
component?: C;
|
|
15
|
-
onClick?: React.MouseEventHandler<
|
|
15
|
+
onClick?: React.MouseEventHandler<HTMLElement>;
|
|
16
16
|
} & React.ComponentPropsWithoutRef<C>;
|
|
17
17
|
|
|
18
18
|
const ButtonInner = React.memo(React.forwardRef<
|
|
@@ -29,10 +29,10 @@ const ButtonInner = React.memo(React.forwardRef<
|
|
|
29
29
|
component: Component,
|
|
30
30
|
color = "neutral",
|
|
31
31
|
...props
|
|
32
|
-
}: ButtonProps<
|
|
32
|
+
}: ButtonProps<React.ElementType>, ref) => {
|
|
33
33
|
|
|
34
34
|
const baseClasses =
|
|
35
|
-
"typography-button h-fit rounded-md whitespace-nowrap inline-flex items-center justify-center p-
|
|
35
|
+
"typography-button h-fit rounded-md whitespace-nowrap inline-flex items-center justify-center p-1.5 px-3 focus:outline-none transition-all ease-in-out duration-150 gap-1.5 active:scale-[0.98]";
|
|
36
36
|
|
|
37
37
|
const buttonClasses = cls({
|
|
38
38
|
"w-full": fullWidth,
|
|
@@ -68,11 +68,11 @@ const ButtonInner = React.memo(React.forwardRef<
|
|
|
68
68
|
|
|
69
69
|
const sizeClasses = cls(
|
|
70
70
|
{
|
|
71
|
-
"py-
|
|
72
|
-
"py-
|
|
73
|
-
"py-2
|
|
74
|
-
"py-
|
|
75
|
-
"py-
|
|
71
|
+
"py-0.5 px-1.5": size === "small",
|
|
72
|
+
"py-1.5 px-3": size === "medium",
|
|
73
|
+
"py-2 px-4": size === "large",
|
|
74
|
+
"py-2.5 px-5": size === "xl",
|
|
75
|
+
"py-3 px-6": size === "2xl"
|
|
76
76
|
}
|
|
77
77
|
);
|
|
78
78
|
|
|
@@ -83,7 +83,7 @@ const ButtonInner = React.memo(React.forwardRef<
|
|
|
83
83
|
if (Component) {
|
|
84
84
|
return (
|
|
85
85
|
<Component
|
|
86
|
-
ref={ref
|
|
86
|
+
ref={ref}
|
|
87
87
|
onClick={props.onClick}
|
|
88
88
|
className={cls(startIcon ? "pl-3" : "", baseClasses, buttonClasses, sizeClasses, iconColorClass, className)}
|
|
89
89
|
{...props}>
|
|
@@ -94,7 +94,7 @@ const ButtonInner = React.memo(React.forwardRef<
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
return (
|
|
97
|
-
<button ref={ref as
|
|
97
|
+
<button ref={ref as React.Ref<HTMLButtonElement>}
|
|
98
98
|
type={props.type ?? "button"}
|
|
99
99
|
onClick={props.onClick}
|
|
100
100
|
className={cls(startIcon ? "pl-3" : "", baseClasses, buttonClasses, sizeClasses, iconColorClass, className)}
|
|
@@ -111,4 +111,4 @@ const ButtonInner = React.memo(React.forwardRef<
|
|
|
111
111
|
|
|
112
112
|
ButtonInner.displayName = "Button"
|
|
113
113
|
|
|
114
|
-
export const Button = ButtonInner as React.FC<ButtonProps<
|
|
114
|
+
export const Button = ButtonInner as React.FC<ButtonProps<React.ElementType>>;
|
|
@@ -1,16 +1,54 @@
|
|
|
1
1
|
|
|
2
2
|
import React, { ErrorInfo, PropsWithChildren } from "react";
|
|
3
3
|
|
|
4
|
-
import { AlertCircleIcon } from "lucide-react";
|
|
4
|
+
import { AlertCircleIcon, ShieldAlertIcon, RefreshCwIcon, ArrowLeftIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
5
5
|
import { iconSize } from "../icons/Icon";
|
|
6
6
|
import { Typography } from "./Typography";
|
|
7
|
+
import { Button } from "./Button";
|
|
8
|
+
import { cls } from "../util";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Checks whether the error message relates to missing permissions or
|
|
12
|
+
* authorization failures. Used to show a friendlier message in fullPage mode.
|
|
13
|
+
*/
|
|
14
|
+
function isPermissionError(error: Error | null): boolean {
|
|
15
|
+
if (!error) return false;
|
|
16
|
+
const msg = error.message?.toLowerCase() ?? "";
|
|
17
|
+
const code = (error as { code?: string }).code?.toLowerCase() ?? "";
|
|
18
|
+
return msg.includes("permission")
|
|
19
|
+
|| msg.includes("insufficient")
|
|
20
|
+
|| msg.includes("unauthorized")
|
|
21
|
+
|| msg.includes("forbidden")
|
|
22
|
+
|| msg.includes("access denied")
|
|
23
|
+
|| code === "permission_denied"
|
|
24
|
+
|| code === "insufficient_permissions"
|
|
25
|
+
|| (error as { status?: number }).status === 403;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ErrorBoundaryProps {
|
|
29
|
+
/**
|
|
30
|
+
* When true, renders a full-page centered error screen instead of the
|
|
31
|
+
* compact inline error. Intended for wrapping top-level app roots or
|
|
32
|
+
* major route sections.
|
|
33
|
+
*/
|
|
34
|
+
fullPage?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Optional callback invoked when the user clicks "Try again". If provided,
|
|
37
|
+
* the boundary resets its error state and calls this handler. If omitted,
|
|
38
|
+
* the "Try again" button resets the boundary state, re-mounting children.
|
|
39
|
+
*/
|
|
40
|
+
onReset?: () => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface ErrorBoundaryState {
|
|
44
|
+
error: Error | null;
|
|
45
|
+
showDetails: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class ErrorBoundary extends React.Component<PropsWithChildren<ErrorBoundaryProps>, ErrorBoundaryState> {
|
|
49
|
+
constructor(props: PropsWithChildren<ErrorBoundaryProps>) {
|
|
12
50
|
super(props);
|
|
13
|
-
this.state = { error: null };
|
|
51
|
+
this.state = { error: null, showDetails: false };
|
|
14
52
|
}
|
|
15
53
|
|
|
16
54
|
static getDerivedStateFromError(error: Error) {
|
|
@@ -19,24 +57,139 @@ export class ErrorBoundary extends React.Component<PropsWithChildren<Record<stri
|
|
|
19
57
|
|
|
20
58
|
componentDidCatch(error: Error, _errorInfo: ErrorInfo) {
|
|
21
59
|
console.error(error);
|
|
22
|
-
// logErrorToMyService(error, errorInfo);
|
|
23
60
|
}
|
|
24
61
|
|
|
62
|
+
private handleReset = () => {
|
|
63
|
+
this.setState({ error: null, showDetails: false });
|
|
64
|
+
this.props.onReset?.();
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
private handleReload = () => {
|
|
68
|
+
window.location.reload();
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
private toggleDetails = () => {
|
|
72
|
+
this.setState(prev => ({ showDetails: !prev.showDetails }));
|
|
73
|
+
};
|
|
74
|
+
|
|
25
75
|
render() {
|
|
26
76
|
if (this.state.error) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
<div className="ml-4">Error</div>
|
|
32
|
-
</div>
|
|
33
|
-
<Typography variant={"caption"}>
|
|
34
|
-
{this.state.error?.message ?? "See the error in the console"}
|
|
35
|
-
</Typography>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
77
|
+
if (this.props.fullPage) {
|
|
78
|
+
return this.renderFullPage();
|
|
79
|
+
}
|
|
80
|
+
return this.renderInline();
|
|
38
81
|
}
|
|
39
82
|
|
|
40
83
|
return this.props.children;
|
|
41
84
|
}
|
|
85
|
+
|
|
86
|
+
private renderInline() {
|
|
87
|
+
return (
|
|
88
|
+
<div className="flex flex-col m-2">
|
|
89
|
+
<div className="flex items-center m-2">
|
|
90
|
+
<AlertCircleIcon className={"text-red-500"} size={iconSize.small}/>
|
|
91
|
+
<div className="ml-4">Error</div>
|
|
92
|
+
</div>
|
|
93
|
+
<Typography variant={"caption"}>
|
|
94
|
+
{this.state.error?.message ?? "See the error in the console"}
|
|
95
|
+
</Typography>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private renderFullPage() {
|
|
101
|
+
const { error, showDetails } = this.state;
|
|
102
|
+
const isPermission = isPermissionError(error);
|
|
103
|
+
|
|
104
|
+
const Icon = isPermission ? ShieldAlertIcon : AlertCircleIcon;
|
|
105
|
+
const title = isPermission
|
|
106
|
+
? "Access denied"
|
|
107
|
+
: "Something went wrong";
|
|
108
|
+
const description = isPermission
|
|
109
|
+
? "You don't have permission to access this resource. Please check your account permissions or contact your administrator."
|
|
110
|
+
: "An unexpected error occurred. You can try reloading the page or going back.";
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div className={cls(
|
|
114
|
+
"flex items-center justify-center min-h-[400px] h-full w-full",
|
|
115
|
+
"bg-surface-50 dark:bg-surface-950"
|
|
116
|
+
)}>
|
|
117
|
+
<div className="flex flex-col items-center max-w-md px-6 py-10 text-center">
|
|
118
|
+
<div className={cls(
|
|
119
|
+
"flex items-center justify-center w-14 h-14 rounded-2xl mb-6",
|
|
120
|
+
isPermission
|
|
121
|
+
? "bg-amber-100 dark:bg-amber-900/30"
|
|
122
|
+
: "bg-red-100 dark:bg-red-900/30"
|
|
123
|
+
)}>
|
|
124
|
+
<Icon
|
|
125
|
+
size={28}
|
|
126
|
+
className={isPermission
|
|
127
|
+
? "text-amber-600 dark:text-amber-400"
|
|
128
|
+
: "text-red-500 dark:text-red-400"}
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<Typography variant={"h6"}
|
|
133
|
+
className="text-text-primary dark:text-text-primary-dark mb-2">
|
|
134
|
+
{title}
|
|
135
|
+
</Typography>
|
|
136
|
+
|
|
137
|
+
<Typography variant={"body2"}
|
|
138
|
+
className="text-text-secondary dark:text-text-secondary-dark mb-8">
|
|
139
|
+
{description}
|
|
140
|
+
</Typography>
|
|
141
|
+
|
|
142
|
+
<div className="flex gap-3">
|
|
143
|
+
<Button
|
|
144
|
+
variant="outlined"
|
|
145
|
+
color="neutral"
|
|
146
|
+
size="medium"
|
|
147
|
+
onClick={this.handleReset}
|
|
148
|
+
>
|
|
149
|
+
<ArrowLeftIcon size={16}/>
|
|
150
|
+
Try again
|
|
151
|
+
</Button>
|
|
152
|
+
<Button
|
|
153
|
+
variant="filled"
|
|
154
|
+
color="primary"
|
|
155
|
+
size="medium"
|
|
156
|
+
onClick={this.handleReload}
|
|
157
|
+
>
|
|
158
|
+
<RefreshCwIcon size={16}/>
|
|
159
|
+
Reload page
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{error?.message && (
|
|
164
|
+
<div className="mt-8 w-full">
|
|
165
|
+
<button
|
|
166
|
+
onClick={this.toggleDetails}
|
|
167
|
+
className={cls(
|
|
168
|
+
"flex items-center gap-1 mx-auto text-xs",
|
|
169
|
+
"text-text-disabled dark:text-text-disabled-dark",
|
|
170
|
+
"hover:text-text-secondary dark:hover:text-text-secondary-dark",
|
|
171
|
+
"transition-colors cursor-pointer bg-transparent border-none p-0"
|
|
172
|
+
)}
|
|
173
|
+
>
|
|
174
|
+
Technical details
|
|
175
|
+
{showDetails
|
|
176
|
+
? <ChevronUpIcon size={14}/>
|
|
177
|
+
: <ChevronDownIcon size={14}/>}
|
|
178
|
+
</button>
|
|
179
|
+
{showDetails && (
|
|
180
|
+
<div className={cls(
|
|
181
|
+
"mt-3 p-3 rounded-lg text-left text-xs",
|
|
182
|
+
"bg-surface-100 dark:bg-surface-800",
|
|
183
|
+
"text-text-secondary dark:text-text-secondary-dark",
|
|
184
|
+
"font-mono break-all"
|
|
185
|
+
)}>
|
|
186
|
+
{error.message}
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
42
195
|
}
|
|
@@ -19,10 +19,10 @@ const buttonClasses = "hover:bg-surface-accent-200 hover:bg-opacity-75 hover:bg-
|
|
|
19
19
|
const baseClasses = "inline-flex items-center justify-center p-2 text-sm font-medium focus:outline-none transition-colors ease-in-out duration-150";
|
|
20
20
|
const colorClasses = "text-surface-accent-500 visited:text-surface-accent-500 dark:text-surface-accent-300 dark:visited:text-surface-300";
|
|
21
21
|
const sizeClasses = {
|
|
22
|
-
medium: "w-
|
|
23
|
-
small: "w-
|
|
24
|
-
smallest: "w-
|
|
25
|
-
large: "w-
|
|
22
|
+
medium: "w-9 !h-9 min-w-9 min-h-9",
|
|
23
|
+
small: "w-7 !h-7 min-w-7 min-h-7",
|
|
24
|
+
smallest: "w-5 !h-5 min-w-5 min-h-5",
|
|
25
|
+
large: "w-10 !h-10 min-w-10 min-h-10"
|
|
26
26
|
}
|
|
27
27
|
const shapeClasses = {
|
|
28
28
|
circular: "rounded-full",
|
|
@@ -98,7 +98,7 @@ export const MultiSelect = React.forwardRef<
|
|
|
98
98
|
) => {
|
|
99
99
|
const [isMounted, setIsMounted] = useState(false);
|
|
100
100
|
const [isPopoverOpen, setIsPopoverOpen] = useState(open ?? false);
|
|
101
|
-
const [selectedValues, setSelectedValues] = useState<
|
|
101
|
+
const [selectedValues, setSelectedValues] = useState<string[]>(value ?? []);
|
|
102
102
|
|
|
103
103
|
// Get the portal container from context
|
|
104
104
|
const contextContainer = usePortalContainer();
|
|
@@ -143,17 +143,17 @@ export const MultiSelect = React.forwardRef<
|
|
|
143
143
|
setSelectedValues(value ?? []);
|
|
144
144
|
}, [value]);
|
|
145
145
|
|
|
146
|
-
const updateValues = React.useCallback((values:
|
|
146
|
+
const updateValues = React.useCallback((values: string[]) => {
|
|
147
147
|
setSelectedValues(values);
|
|
148
148
|
onValueChange?.(values);
|
|
149
149
|
}, [onValueChange]);
|
|
150
150
|
|
|
151
|
-
const onItemClick = React.useCallback((newValue:
|
|
152
|
-
let newSelectedValues:
|
|
151
|
+
const onItemClick = React.useCallback((newValue: MultiSelectValue) => {
|
|
152
|
+
let newSelectedValues: string[];
|
|
153
153
|
if (selectedValues.some(v => String(v) === String(newValue))) {
|
|
154
154
|
newSelectedValues = selectedValues.filter(v => String(v) !== String(newValue));
|
|
155
155
|
} else {
|
|
156
|
-
newSelectedValues = [...selectedValues, newValue];
|
|
156
|
+
newSelectedValues = [...selectedValues, String(newValue)];
|
|
157
157
|
}
|
|
158
158
|
updateValues(newSelectedValues);
|
|
159
159
|
}, [selectedValues, updateValues]);
|
|
@@ -170,7 +170,7 @@ export const MultiSelect = React.forwardRef<
|
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
-
const toggleOption = (value:
|
|
173
|
+
const toggleOption = (value: string) => {
|
|
174
174
|
const newSelectedValues = selectedValues.some(v => String(v) === String(value))
|
|
175
175
|
? selectedValues.filter(v => String(v) !== String(value))
|
|
176
176
|
: [...selectedValues, value];
|
|
@@ -260,7 +260,7 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
|
|
|
260
260
|
value={value}
|
|
261
261
|
onChange={handleChange}
|
|
262
262
|
className={props.className}
|
|
263
|
-
ref={handleRef as
|
|
263
|
+
ref={handleRef as React.Ref<HTMLTextAreaElement>}
|
|
264
264
|
onFocus={onFocus}
|
|
265
265
|
onBlur={onBlur}
|
|
266
266
|
// Apply the rows prop to get a "correct" first SSR paint
|
|
@@ -279,7 +279,7 @@ export const TextareaAutosize = React.forwardRef(function TextareaAutosize(
|
|
|
279
279
|
aria-hidden
|
|
280
280
|
className={cls(props.className, props.shadowClassName)}
|
|
281
281
|
readOnly
|
|
282
|
-
ref={shadowRef
|
|
282
|
+
ref={shadowRef}
|
|
283
283
|
tabIndex={-1}
|
|
284
284
|
style={{
|
|
285
285
|
padding: 0,
|
|
@@ -40,7 +40,7 @@ VirtualListContext.displayName = "VirtualListContext";
|
|
|
40
40
|
|
|
41
41
|
type InnerElementProps = {
|
|
42
42
|
children: React.ReactNode,
|
|
43
|
-
style:
|
|
43
|
+
style: React.CSSProperties
|
|
44
44
|
};
|
|
45
45
|
|
|
46
46
|
// eslint-disable-next-line react/display-name
|
|
@@ -107,8 +107,8 @@ zIndex: 20 }}>
|
|
|
107
107
|
*
|
|
108
108
|
* @group Components
|
|
109
109
|
*/
|
|
110
|
-
export const VirtualTable = React.memo<VirtualTableProps<
|
|
111
|
-
function VirtualTable<T extends Record<string,
|
|
110
|
+
export const VirtualTable = React.memo<VirtualTableProps<Record<string, unknown>>>(
|
|
111
|
+
function VirtualTable<T extends Record<string, unknown>>({
|
|
112
112
|
data,
|
|
113
113
|
onResetPagination,
|
|
114
114
|
onEndReached,
|
|
@@ -229,7 +229,7 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
229
229
|
}, [columns, onColumnResize, onColumnResizeEnd]);
|
|
230
230
|
|
|
231
231
|
// saving the current filter as a ref as a workaround for header closure
|
|
232
|
-
const filterRef = useRef<VirtualTableFilterValues<
|
|
232
|
+
const filterRef = useRef<VirtualTableFilterValues<string> | undefined>(undefined);
|
|
233
233
|
|
|
234
234
|
useEffect(() => {
|
|
235
235
|
filterRef.current = filterInput;
|
|
@@ -299,11 +299,11 @@ export const VirtualTable = React.memo<VirtualTableProps<any>>(
|
|
|
299
299
|
onEndReachedInternal(scrollOffset);
|
|
300
300
|
}, [maxScroll, onEndReachedInternal]);
|
|
301
301
|
|
|
302
|
-
const onFilterUpdateInternal = useCallback((column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp,
|
|
302
|
+
const onFilterUpdateInternal = useCallback((column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, unknown]) => {
|
|
303
303
|
|
|
304
304
|
endReachCallbackThreshold.current = 0;
|
|
305
305
|
const filter = filterRef.current;
|
|
306
|
-
let newFilterValue: VirtualTableFilterValues<
|
|
306
|
+
let newFilterValue: VirtualTableFilterValues<string> = filter ? { ...filter } : {};
|
|
307
307
|
|
|
308
308
|
if (!filterForProperty) {
|
|
309
309
|
delete newFilterValue[column.key];
|
|
@@ -506,7 +506,7 @@ function MemoizedList({
|
|
|
506
506
|
const Row = useCallback(({
|
|
507
507
|
index,
|
|
508
508
|
style
|
|
509
|
-
}:
|
|
509
|
+
}: { index: number; style: React.CSSProperties }) => {
|
|
510
510
|
return <VirtualListContext.Consumer>
|
|
511
511
|
{({
|
|
512
512
|
onRowClick,
|
|
@@ -535,7 +535,7 @@ function MemoizedList({
|
|
|
535
535
|
</div>;
|
|
536
536
|
}
|
|
537
537
|
|
|
538
|
-
const rowData = data
|
|
538
|
+
const rowData = (data ? data[index] : undefined) as Record<string, unknown>;
|
|
539
539
|
return (
|
|
540
540
|
<VirtualTableRow
|
|
541
541
|
key={`row_${index}`}
|
|
@@ -8,22 +8,22 @@ type VirtualTableCellProps<T> = {
|
|
|
8
8
|
dataKey: string;
|
|
9
9
|
column: VirtualTableColumn;
|
|
10
10
|
columns: VirtualTableColumn[];
|
|
11
|
-
rowData:
|
|
12
|
-
cellData:
|
|
13
|
-
rowIndex:
|
|
11
|
+
rowData: T;
|
|
12
|
+
cellData: unknown;
|
|
13
|
+
rowIndex: number;
|
|
14
14
|
columnIndex: number;
|
|
15
15
|
cellRenderer: (props: CellRendererParams<T>) => React.ReactNode;
|
|
16
16
|
// Sortable props
|
|
17
17
|
sortableNodeRef?: (node: HTMLElement | null) => void;
|
|
18
18
|
sortableStyle?: React.CSSProperties;
|
|
19
|
-
sortableAttributes?: Record<string,
|
|
19
|
+
sortableAttributes?: Record<string, unknown>;
|
|
20
20
|
isDragging?: boolean;
|
|
21
21
|
isDraggable?: boolean;
|
|
22
22
|
frozen?: boolean;
|
|
23
|
-
extraData?:
|
|
23
|
+
extraData?: unknown;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
export const VirtualTableCell = React.memo<VirtualTableCellProps<
|
|
26
|
+
export const VirtualTableCell = React.memo<VirtualTableCellProps<unknown>>(
|
|
27
27
|
function VirtualTableCell<T>(props: VirtualTableCellProps<T>) {
|
|
28
28
|
return props.rowData ? props.cellRenderer(
|
|
29
29
|
{
|
|
@@ -14,8 +14,8 @@ import { defaultBorderMixin } from "../../styles";
|
|
|
14
14
|
import { cls } from "../../util/cls";
|
|
15
15
|
interface FilterFormProps<T> {
|
|
16
16
|
column: VirtualTableColumn<T>;
|
|
17
|
-
onFilterUpdate: (filter?: [VirtualTableWhereFilterOp,
|
|
18
|
-
filter?: [VirtualTableWhereFilterOp,
|
|
17
|
+
onFilterUpdate: (filter?: [VirtualTableWhereFilterOp, unknown], newOpenFilterState?: boolean) => void;
|
|
18
|
+
filter?: [VirtualTableWhereFilterOp, unknown];
|
|
19
19
|
onHover: boolean,
|
|
20
20
|
createFilterField: (props: FilterFormFieldProps<T>) => React.ReactNode;
|
|
21
21
|
hidden: boolean;
|
|
@@ -24,31 +24,31 @@ interface FilterFormProps<T> {
|
|
|
24
24
|
|
|
25
25
|
export type FilterFormFieldProps<CustomProps> = {
|
|
26
26
|
id: React.Key,
|
|
27
|
-
filterValue: [VirtualTableWhereFilterOp,
|
|
28
|
-
setFilterValue: (filterValue?: [VirtualTableWhereFilterOp,
|
|
27
|
+
filterValue: [VirtualTableWhereFilterOp, unknown] | undefined,
|
|
28
|
+
setFilterValue: (filterValue?: [VirtualTableWhereFilterOp, unknown]) => void;
|
|
29
29
|
column: VirtualTableColumn<CustomProps>;
|
|
30
30
|
hidden: boolean;
|
|
31
31
|
setHidden: (hidden: boolean) => void;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
type VirtualTableHeaderProps<M extends Record<string,
|
|
34
|
+
type VirtualTableHeaderProps<M extends Record<string, unknown>> = {
|
|
35
35
|
resizeHandleRef: React.Ref<HTMLDivElement>;
|
|
36
36
|
columnIndex: number;
|
|
37
37
|
isResizingIndex: number;
|
|
38
|
-
column: VirtualTableColumn<
|
|
38
|
+
column: VirtualTableColumn<unknown>;
|
|
39
39
|
onColumnSort: (key: Extract<keyof M, string>) => void;
|
|
40
|
-
filter?: [VirtualTableWhereFilterOp,
|
|
40
|
+
filter?: [VirtualTableWhereFilterOp, unknown];
|
|
41
41
|
sort: VirtualTableSort;
|
|
42
|
-
onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp,
|
|
42
|
+
onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, unknown]) => void;
|
|
43
43
|
onClickResizeColumn?: (columnIndex: number, column: VirtualTableColumn) => void;
|
|
44
|
-
createFilterField?: (props: FilterFormFieldProps<
|
|
44
|
+
createFilterField?: (props: FilterFormFieldProps<unknown>) => React.ReactNode;
|
|
45
45
|
AdditionalHeaderWidget?: (props: { onHover: boolean }) => React.ReactNode;
|
|
46
46
|
isDragging?: boolean;
|
|
47
47
|
isDraggable?: boolean;
|
|
48
48
|
};
|
|
49
49
|
|
|
50
|
-
export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<
|
|
51
|
-
function VirtualTableHeader<M extends Record<string,
|
|
50
|
+
export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<Record<string, unknown>>>(
|
|
51
|
+
function VirtualTableHeader<M extends Record<string, unknown>>({
|
|
52
52
|
resizeHandleRef,
|
|
53
53
|
columnIndex,
|
|
54
54
|
isResizingIndex,
|
|
@@ -73,7 +73,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
|
|
|
73
73
|
setOpenFilter(true);
|
|
74
74
|
}, []);
|
|
75
75
|
|
|
76
|
-
const update = useCallback((filterForProperty?: [VirtualTableWhereFilterOp,
|
|
76
|
+
const update = useCallback((filterForProperty?: [VirtualTableWhereFilterOp, unknown], newOpenFilterState?: boolean) => {
|
|
77
77
|
onFilterUpdate(column, filterForProperty);
|
|
78
78
|
if (newOpenFilterState !== undefined)
|
|
79
79
|
setOpenFilter(newOpenFilterState);
|
|
@@ -182,7 +182,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
|
|
|
182
182
|
</div>}
|
|
183
183
|
|
|
184
184
|
{column.resizable && <div
|
|
185
|
-
ref={resizeHandleRef as
|
|
185
|
+
ref={resizeHandleRef as React.Ref<HTMLDivElement>}
|
|
186
186
|
data-no-dnd="true"
|
|
187
187
|
className={cls(
|
|
188
188
|
"absolute h-full w-[6px] top-0 right-0 cursor-col-resize",
|
|
@@ -199,7 +199,7 @@ export const VirtualTableHeader = React.memo<VirtualTableHeaderProps<any>>(
|
|
|
199
199
|
|
|
200
200
|
</ErrorBoundary>
|
|
201
201
|
);
|
|
202
|
-
}, equal) as React.FunctionComponent<VirtualTableHeaderProps<
|
|
202
|
+
}, equal) as React.FunctionComponent<VirtualTableHeaderProps<Record<string, unknown>>>;
|
|
203
203
|
|
|
204
204
|
function FilterForm<M>({
|
|
205
205
|
column,
|
|
@@ -213,7 +213,7 @@ function FilterForm<M>({
|
|
|
213
213
|
|
|
214
214
|
const id = column.key;
|
|
215
215
|
|
|
216
|
-
const [filterInternal, setFilterInternal] = useState<[VirtualTableWhereFilterOp,
|
|
216
|
+
const [filterInternal, setFilterInternal] = useState<[VirtualTableWhereFilterOp, unknown] | undefined>(filter);
|
|
217
217
|
|
|
218
218
|
useEffect(() => {
|
|
219
219
|
setFilterInternal(filter);
|
|
@@ -28,13 +28,13 @@ const SortableColumnHeader = ({
|
|
|
28
28
|
columnIndex: number;
|
|
29
29
|
columnRefs: React.Ref<HTMLDivElement>[];
|
|
30
30
|
isResizing: number;
|
|
31
|
-
onFilterUpdate:
|
|
32
|
-
filter: [VirtualTableWhereFilterOp,
|
|
31
|
+
onFilterUpdate: (column: VirtualTableColumn, filterForProperty?: [VirtualTableWhereFilterOp, unknown]) => void;
|
|
32
|
+
filter: [VirtualTableWhereFilterOp, unknown] | undefined;
|
|
33
33
|
sortByProperty: string | undefined;
|
|
34
34
|
currentSort: "asc" | "desc" | undefined;
|
|
35
|
-
onColumnSort:
|
|
35
|
+
onColumnSort: (key: string) => void;
|
|
36
36
|
onClickResizeColumn: (index: number) => void;
|
|
37
|
-
createFilterField:
|
|
37
|
+
createFilterField: ((props: import("./VirtualTableHeader").FilterFormFieldProps<unknown>) => React.ReactNode) | undefined;
|
|
38
38
|
isDragging: boolean;
|
|
39
39
|
isDraggable: boolean;
|
|
40
40
|
}) => {
|
|
@@ -125,7 +125,7 @@ export const VirtualTableHeaderRow = ({
|
|
|
125
125
|
rowHeight = 54,
|
|
126
126
|
draggingColumnId,
|
|
127
127
|
headerHeight = 48
|
|
128
|
-
}: VirtualTableContextProps<
|
|
128
|
+
}: VirtualTableContextProps<Record<string, unknown>>) => {
|
|
129
129
|
|
|
130
130
|
const columnRefs = useMemo(() => columns.map(() => createRef<HTMLDivElement>()), [columns.length]);
|
|
131
131
|
const [isResizing, setIsResizing] = useState(-1);
|
|
@@ -212,7 +212,7 @@ export const VirtualTableHeaderRow = ({
|
|
|
212
212
|
className={cls(defaultBorderMixin, "z-20 sticky min-w-full flex w-fit flex-row top-0 left-0 border-b bg-surface-50 dark:bg-surface-900")}
|
|
213
213
|
style={{ height: headerHeight }}>
|
|
214
214
|
{columns.map((column, columnIndex) => {
|
|
215
|
-
const filterForThisProperty: [VirtualTableWhereFilterOp,
|
|
215
|
+
const filterForThisProperty: [VirtualTableWhereFilterOp, unknown] | undefined =
|
|
216
216
|
column && filter && filter[column.key]
|
|
217
217
|
? filter[column.key]
|
|
218
218
|
: undefined;
|