@rebasepro/ui 0.17.3 → 0.18.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 +4 -0
- package/dist/components/MultiSelect.d.ts +10 -0
- package/dist/index.es.js +2 -1
- package/dist/index.es.js.map +1 -1
- package/package.json +32 -23
- package/src/components/Alert.tsx +0 -118
- package/src/components/Autocomplete.tsx +0 -93
- package/src/components/Avatar.tsx +0 -73
- package/src/components/Badge.tsx +0 -44
- package/src/components/BooleanSwitch.tsx +0 -116
- package/src/components/BooleanSwitchWithLabel.tsx +0 -120
- package/src/components/Button.tsx +0 -110
- package/src/components/Card.tsx +0 -43
- package/src/components/CenteredView.tsx +0 -35
- package/src/components/Checkbox.tsx +0 -129
- package/src/components/Chip.tsx +0 -131
- package/src/components/CircularProgress.tsx +0 -53
- package/src/components/CircularProgressCenter.tsx +0 -27
- package/src/components/Collapse.tsx +0 -66
- package/src/components/ColorPicker.tsx +0 -140
- package/src/components/Container.tsx +0 -51
- package/src/components/DateTimeField.tsx +0 -363
- package/src/components/DebouncedTextField.tsx +0 -76
- package/src/components/Dialog.tsx +0 -159
- package/src/components/DialogActions.tsx +0 -29
- package/src/components/DialogContent.tsx +0 -27
- package/src/components/DialogTitle.tsx +0 -41
- package/src/components/ErrorBoundary.tsx +0 -236
- package/src/components/ExpandablePanel.tsx +0 -130
- package/src/components/FileUpload.tsx +0 -103
- package/src/components/FilterChip.tsx +0 -89
- package/src/components/IconButton.tsx +0 -101
- package/src/components/InfoLabel.tsx +0 -23
- package/src/components/InputLabel.tsx +0 -40
- package/src/components/Label.tsx +0 -32
- package/src/components/LoadingButton.tsx +0 -32
- package/src/components/Markdown.tsx +0 -55
- package/src/components/Menu.tsx +0 -114
- package/src/components/Menubar.tsx +0 -355
- package/src/components/MultiSelect.tsx +0 -433
- package/src/components/Paper.tsx +0 -23
- package/src/components/Popover.tsx +0 -162
- package/src/components/RadioGroup.tsx +0 -74
- package/src/components/ResizablePanels.tsx +0 -189
- package/src/components/SearchBar.tsx +0 -136
- package/src/components/Select.tsx +0 -326
- package/src/components/Separator.tsx +0 -28
- package/src/components/Sheet.tsx +0 -135
- package/src/components/Skeleton.tsx +0 -54
- package/src/components/Slider.tsx +0 -110
- package/src/components/Table.tsx +0 -178
- package/src/components/Tabs.tsx +0 -185
- package/src/components/TextField.tsx +0 -270
- package/src/components/TextareaAutosize.tsx +0 -368
- package/src/components/ToggleButtonGroup.tsx +0 -69
- package/src/components/Tooltip.tsx +0 -153
- package/src/components/Typography.tsx +0 -132
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
- package/src/components/VirtualTable/VirtualTable.tsx +0 -700
- package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
- package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
- package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
- package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
- package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
- package/src/components/VirtualTable/index.tsx +0 -10
- package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
- package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
- package/src/components/VirtualTable/types.tsx +0 -47
- package/src/components/common/SelectInputLabel.tsx +0 -23
- package/src/components/index.tsx +0 -62
- package/src/hooks/PortalContainerContext.tsx +0 -50
- package/src/hooks/index.ts +0 -7
- package/src/hooks/useDebounceCallback.tsx +0 -21
- package/src/hooks/useDebounceValue.tsx +0 -20
- package/src/hooks/useDebouncedCallback.ts +0 -39
- package/src/hooks/useInjectStyles.tsx +0 -31
- package/src/hooks/useOutsideAlerter.tsx +0 -46
- package/src/icons/GitHubIcon.tsx +0 -21
- package/src/icons/HandleIcon.tsx +0 -23
- package/src/icons/Icon.tsx +0 -33
- package/src/icons/LucideIconByName.tsx +0 -117
- package/src/icons/cool_icon_keys.ts +0 -12
- package/src/icons/icon_keys.ts +0 -1936
- package/src/icons/index.ts +0 -179
- package/src/index.css +0 -169
- package/src/index.ts +0 -47
- package/src/scripts/fixIconSizes.cjs +0 -92
- package/src/scripts/fixIconSizesToConst.cjs +0 -106
- package/src/scripts/generateIconKeys.ts +0 -41
- package/src/scripts/generateIcons.ts +0 -4
- package/src/scripts/saveIconFiles.ts +0 -89
- package/src/styles.ts +0 -141
- package/src/theme.css +0 -168
- package/src/util/chip_colors.ts +0 -242
- package/src/util/cls.ts +0 -8
- package/src/util/debounce.ts +0 -28
- package/src/util/hash.ts +0 -11
- package/src/util/index.ts +0 -4
- package/src/util/key_to_icon_component.ts +0 -11
- package/src/util/lazy_chunk.ts +0 -101
- package/src/views/CardView.tsx +0 -281
- package/src/views/CollectionView/CollectionCardView.tsx +0 -265
- package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
- package/src/views/CollectionView/CollectionListView.tsx +0 -240
- package/src/views/CollectionView/CollectionTableView.tsx +0 -229
- package/src/views/CollectionView/CollectionView.tsx +0 -450
- package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
- package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
- package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
- package/src/views/CollectionView/index.ts +0 -39
- package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
- package/src/views/CollectionView/utils.ts +0 -14
- package/src/views/Kanban/Board.tsx +0 -384
- package/src/views/Kanban/BoardColumn.tsx +0 -138
- package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
- package/src/views/Kanban/BoardSortableList.tsx +0 -182
- package/src/views/Kanban/board_types.ts +0 -80
- package/src/views/Kanban/index.tsx +0 -5
- package/src/views/Kanban/placement.ts +0 -62
- package/src/views/KanbanView.tsx +0 -32
- package/src/views/ListView.tsx +0 -296
- package/src/views/index.ts +0 -5
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import React, { useEffect, useState } from "react";
|
|
3
|
-
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
4
|
-
import { paperMixin } from "../styles";
|
|
5
|
-
import { cls } from "../util";
|
|
6
|
-
import { PortalContainerProvider, usePortalContainer } from "../hooks/PortalContainerContext";
|
|
7
|
-
|
|
8
|
-
export type DialogProps = {
|
|
9
|
-
open?: boolean;
|
|
10
|
-
onOpenChange?: (open: boolean) => void;
|
|
11
|
-
children: React.ReactNode;
|
|
12
|
-
className?: string;
|
|
13
|
-
containerClassName?: string;
|
|
14
|
-
fullWidth?: boolean;
|
|
15
|
-
fullHeight?: boolean;
|
|
16
|
-
fullScreen?: boolean;
|
|
17
|
-
scrollable?: boolean;
|
|
18
|
-
maxWidth?: keyof typeof widthClasses;
|
|
19
|
-
modal?: boolean;
|
|
20
|
-
onOpenAutoFocus?: (e: Event) => void;
|
|
21
|
-
onEscapeKeyDown?: (e: KeyboardEvent) => void;
|
|
22
|
-
onPointerDownOutside?: (e: Event) => void;
|
|
23
|
-
onInteractOutside?: (e: Event) => void;
|
|
24
|
-
/**
|
|
25
|
-
* If `true`, the dialog will not focus the first focusable element when opened.
|
|
26
|
-
*/
|
|
27
|
-
disableInitialFocus?: boolean;
|
|
28
|
-
portalContainer?: HTMLElement | null;
|
|
29
|
-
"aria-describedby"?: string;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const widthClasses = {
|
|
33
|
-
xs: "max-w-xs w-xs",
|
|
34
|
-
sm: "max-w-sm w-sm",
|
|
35
|
-
md: "max-w-md w-md",
|
|
36
|
-
lg: "max-w-lg w-lg",
|
|
37
|
-
xl: "max-w-xl w-xl",
|
|
38
|
-
"2xl": "max-w-2xl w-2xl",
|
|
39
|
-
"3xl": "max-w-3xl w-3xl",
|
|
40
|
-
"4xl": "max-w-4xl w-4xl",
|
|
41
|
-
"5xl": "max-w-5xl w-5xl",
|
|
42
|
-
"6xl": "max-w-6xl w-6xl",
|
|
43
|
-
"7xl": "max-w-7xl w-7xl",
|
|
44
|
-
full: "max-w-full w-full"
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
export const Dialog = ({
|
|
48
|
-
open,
|
|
49
|
-
onOpenChange,
|
|
50
|
-
children,
|
|
51
|
-
className,
|
|
52
|
-
containerClassName,
|
|
53
|
-
fullWidth = true,
|
|
54
|
-
fullHeight,
|
|
55
|
-
fullScreen,
|
|
56
|
-
scrollable = true,
|
|
57
|
-
maxWidth = "lg",
|
|
58
|
-
modal = true,
|
|
59
|
-
onOpenAutoFocus,
|
|
60
|
-
onEscapeKeyDown,
|
|
61
|
-
onPointerDownOutside,
|
|
62
|
-
onInteractOutside,
|
|
63
|
-
disableInitialFocus = true,
|
|
64
|
-
portalContainer,
|
|
65
|
-
"aria-describedby": ariaDescribedby
|
|
66
|
-
}: DialogProps) => {
|
|
67
|
-
const [displayed, setDisplayed] = useState(false);
|
|
68
|
-
// Where popups rendered *inside* this dialog get portaled to. A modal
|
|
69
|
-
// dialog locks scrolling everywhere but its own content, so a popup
|
|
70
|
-
// portaled to `document.body` renders fine and then refuses to scroll:
|
|
71
|
-
// `react-remove-scroll` cancels every wheel event outside the lock.
|
|
72
|
-
// Handing descendants this host keeps them inside it.
|
|
73
|
-
const [popupHost, setPopupHost] = useState<HTMLDivElement | null>(null);
|
|
74
|
-
|
|
75
|
-
// Get the portal container from context
|
|
76
|
-
const contextContainer = usePortalContainer();
|
|
77
|
-
|
|
78
|
-
// Prioritize manual prop, fallback to context container
|
|
79
|
-
const finalContainer = (portalContainer ?? contextContainer ?? undefined) as HTMLElement | undefined;
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
if (!open) {
|
|
83
|
-
const timeout = setTimeout(() => {
|
|
84
|
-
setDisplayed(false);
|
|
85
|
-
}, 100);
|
|
86
|
-
return () => clearTimeout(timeout);
|
|
87
|
-
} else {
|
|
88
|
-
setDisplayed(true);
|
|
89
|
-
return () => {
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
}, [open]);
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<DialogPrimitive.Root open={displayed || open}
|
|
96
|
-
modal={modal}
|
|
97
|
-
onOpenChange={onOpenChange}>
|
|
98
|
-
<DialogPrimitive.Portal container={finalContainer}>
|
|
99
|
-
|
|
100
|
-
<div className={cls("fixed inset-0 z-50", containerClassName)}>
|
|
101
|
-
|
|
102
|
-
<DialogPrimitive.Overlay
|
|
103
|
-
className={cls("fixed inset-0 transition-opacity ease-in-out duration-200 bg-black/50 dark:bg-black/60 backdrop-blur-sm",
|
|
104
|
-
displayed && open ? "opacity-100" : "opacity-0",
|
|
105
|
-
"z-50 fixed top-0 left-0 w-full h-full flex justify-center items-center"
|
|
106
|
-
)}
|
|
107
|
-
style={{
|
|
108
|
-
pointerEvents: displayed ? "auto" : "none"
|
|
109
|
-
}}
|
|
110
|
-
/>
|
|
111
|
-
|
|
112
|
-
<DialogPrimitive.Content
|
|
113
|
-
onEscapeKeyDown={onEscapeKeyDown}
|
|
114
|
-
onOpenAutoFocus={(e) => {
|
|
115
|
-
if (disableInitialFocus) {
|
|
116
|
-
e.preventDefault();
|
|
117
|
-
}
|
|
118
|
-
onOpenAutoFocus?.(e);
|
|
119
|
-
}}
|
|
120
|
-
onPointerDownOutside={onPointerDownOutside}
|
|
121
|
-
onInteractOutside={onInteractOutside}
|
|
122
|
-
aria-describedby={ariaDescribedby}
|
|
123
|
-
className={cls("relative h-full outline-none flex justify-center items-center z-60 opacity-100 transition-all duration-200 ease-in-out")}
|
|
124
|
-
>
|
|
125
|
-
<div
|
|
126
|
-
className={cls(paperMixin,
|
|
127
|
-
"rounded-2xl",
|
|
128
|
-
"z-60",
|
|
129
|
-
"relative",
|
|
130
|
-
"overflow-hidden",
|
|
131
|
-
"outline-none focus:outline-none",
|
|
132
|
-
fullWidth && !fullScreen ? "w-11/12" : undefined,
|
|
133
|
-
fullHeight && !fullScreen ? "h-full" : undefined,
|
|
134
|
-
"text-text-primary dark:text-text-primary-dark",
|
|
135
|
-
"justify-center items-center",
|
|
136
|
-
fullScreen ? "h-screen w-screen" : "max-h-[90vh] shadow-lg",
|
|
137
|
-
"ease-in-out duration-200",
|
|
138
|
-
scrollable && "overflow-y-auto",
|
|
139
|
-
displayed && open ? "opacity-100 scale-100" : "opacity-0 scale-[0.97]",
|
|
140
|
-
maxWidth && !fullScreen ? widthClasses[maxWidth] : undefined,
|
|
141
|
-
className
|
|
142
|
-
)}>
|
|
143
|
-
<PortalContainerProvider container={popupHost}>
|
|
144
|
-
{children}
|
|
145
|
-
</PortalContainerProvider>
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
{/* Sized to nothing so it changes no layout, but positioned and
|
|
149
|
-
stacked above the paper so popups portaled here are not
|
|
150
|
-
painted behind it. */}
|
|
151
|
-
<div ref={setPopupHost} className={"relative z-70 w-0 h-0"}/>
|
|
152
|
-
|
|
153
|
-
</DialogPrimitive.Content>
|
|
154
|
-
</div>
|
|
155
|
-
|
|
156
|
-
</DialogPrimitive.Portal>
|
|
157
|
-
</DialogPrimitive.Root>
|
|
158
|
-
);
|
|
159
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { defaultBorderMixin } from "../styles";
|
|
3
|
-
import { cls } from "../util";
|
|
4
|
-
|
|
5
|
-
export function DialogActions({
|
|
6
|
-
children,
|
|
7
|
-
position = "sticky",
|
|
8
|
-
translucent = true,
|
|
9
|
-
className
|
|
10
|
-
}: {
|
|
11
|
-
children: React.ReactNode,
|
|
12
|
-
position?: "sticky" | "absolute",
|
|
13
|
-
translucent?: boolean,
|
|
14
|
-
className?: string
|
|
15
|
-
}) {
|
|
16
|
-
|
|
17
|
-
return <div
|
|
18
|
-
className={cls(
|
|
19
|
-
defaultBorderMixin,
|
|
20
|
-
// `shrink-0`: as a flex child at the foot of a column this bar must
|
|
21
|
-
// keep its height instead of being squeezed by the content above it.
|
|
22
|
-
"pt-2 pb-4 px-4 border-t flex flex-row items-center justify-end shrink-0 bottom-0 right-0 left-0 text-right z-2 gap-2",
|
|
23
|
-
position,
|
|
24
|
-
"bg-white bg-opacity-60 bg-white/60 dark:bg-surface-800 dark:bg-opacity-60 dark:bg-surface-800/60",
|
|
25
|
-
translucent ? "backdrop-blur-sm" : "",
|
|
26
|
-
className)}>
|
|
27
|
-
{children}
|
|
28
|
-
</div>;
|
|
29
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import React from "react";
|
|
2
|
-
import { cls } from "../util";
|
|
3
|
-
|
|
4
|
-
export function DialogContent({
|
|
5
|
-
children,
|
|
6
|
-
className,
|
|
7
|
-
fullHeight,
|
|
8
|
-
includeMargin = true
|
|
9
|
-
}: {
|
|
10
|
-
children: React.ReactNode,
|
|
11
|
-
includeMargin?: boolean,
|
|
12
|
-
fullHeight?: boolean,
|
|
13
|
-
className?: string
|
|
14
|
-
}) {
|
|
15
|
-
|
|
16
|
-
if (fullHeight)
|
|
17
|
-
return <div className={"flex-grow flex flex-col h-full relative"}>
|
|
18
|
-
{children}
|
|
19
|
-
</div>;
|
|
20
|
-
|
|
21
|
-
return <div
|
|
22
|
-
className={cls("flex-grow",
|
|
23
|
-
{ "my-8 mx-8": includeMargin },
|
|
24
|
-
className)}>
|
|
25
|
-
{children}
|
|
26
|
-
</div>;
|
|
27
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import React from "react";
|
|
4
|
-
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
5
|
-
import * as VisuallyHidden from "@radix-ui/react-visually-hidden";
|
|
6
|
-
import { Typography, TypographyProps, TypographyVariant } from "./Typography";
|
|
7
|
-
import { cls } from "../util";
|
|
8
|
-
|
|
9
|
-
export type DialogContentProps = TypographyProps & {
|
|
10
|
-
children: React.ReactNode,
|
|
11
|
-
hidden?: boolean,
|
|
12
|
-
className?: string,
|
|
13
|
-
includeMargin?: boolean,
|
|
14
|
-
variant?: TypographyVariant
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export function DialogTitle({
|
|
18
|
-
children,
|
|
19
|
-
hidden,
|
|
20
|
-
className,
|
|
21
|
-
variant = "subtitle2",
|
|
22
|
-
gutterBottom = true,
|
|
23
|
-
includeMargin = true,
|
|
24
|
-
...props
|
|
25
|
-
}: DialogContentProps) {
|
|
26
|
-
|
|
27
|
-
const title = <DialogPrimitive.Title asChild>
|
|
28
|
-
<Typography variant={variant}
|
|
29
|
-
className={cls({ "mt-8 mx-8": includeMargin }, className)}
|
|
30
|
-
gutterBottom={gutterBottom}
|
|
31
|
-
{...props}>
|
|
32
|
-
{children}
|
|
33
|
-
</Typography>
|
|
34
|
-
</DialogPrimitive.Title>;
|
|
35
|
-
|
|
36
|
-
if (hidden) {
|
|
37
|
-
return <VisuallyHidden.Root>{title}</VisuallyHidden.Root>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return title;
|
|
41
|
-
}
|
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import React, { ErrorInfo, PropsWithChildren } from "react";
|
|
3
|
-
|
|
4
|
-
import { AlertCircleIcon, ShieldAlertIcon, RefreshCwIcon, ArrowLeftIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
|
5
|
-
import { iconSize } from "../icons/Icon";
|
|
6
|
-
import { Typography } from "./Typography";
|
|
7
|
-
import { Button } from "./Button";
|
|
8
|
-
import { cls } from "../util";
|
|
9
|
-
import { isChunkLoadError } from "../util/lazy_chunk";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* The one error a user can fix themselves. A deploy replaces the hashed chunk
|
|
13
|
-
* files, so a tab opened before it cannot load any lazy view it has not already
|
|
14
|
-
* fetched — with the browser's raw message ("Failed to fetch dynamically
|
|
15
|
-
* imported module: …") this reads as a broken build rather than a stale tab.
|
|
16
|
-
*/
|
|
17
|
-
const CHUNK_ERROR_DESCRIPTION =
|
|
18
|
-
"This app was updated while the tab was open, so part of it could not be loaded. Reload to continue.";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Checks whether the error message relates to missing permissions or
|
|
22
|
-
* authorization failures. Used to show a friendlier message in fullPage mode.
|
|
23
|
-
*/
|
|
24
|
-
function isPermissionError(error: Error | null): boolean {
|
|
25
|
-
if (!error) return false;
|
|
26
|
-
const msg = error.message?.toLowerCase() ?? "";
|
|
27
|
-
const code = (error as { code?: string }).code?.toLowerCase() ?? "";
|
|
28
|
-
return msg.includes("permission")
|
|
29
|
-
|| msg.includes("insufficient")
|
|
30
|
-
|| msg.includes("unauthorized")
|
|
31
|
-
|| msg.includes("forbidden")
|
|
32
|
-
|| msg.includes("access denied")
|
|
33
|
-
|| code === "permission_denied"
|
|
34
|
-
|| code === "insufficient_permissions"
|
|
35
|
-
|| (error as { status?: number }).status === 403;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface ErrorBoundaryProps {
|
|
39
|
-
/**
|
|
40
|
-
* When true, renders a full-page centered error screen instead of the
|
|
41
|
-
* compact inline error. Intended for wrapping top-level app roots or
|
|
42
|
-
* major route sections.
|
|
43
|
-
*/
|
|
44
|
-
fullPage?: boolean;
|
|
45
|
-
/**
|
|
46
|
-
* Optional callback invoked when the user clicks "Try again". If provided,
|
|
47
|
-
* the boundary resets its error state and calls this handler. If omitted,
|
|
48
|
-
* the "Try again" button resets the boundary state, re-mounting children.
|
|
49
|
-
*/
|
|
50
|
-
onReset?: () => void;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
interface ErrorBoundaryState {
|
|
54
|
-
error: Error | null;
|
|
55
|
-
showDetails: boolean;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export class ErrorBoundary extends React.Component<PropsWithChildren<ErrorBoundaryProps>, ErrorBoundaryState> {
|
|
59
|
-
constructor(props: PropsWithChildren<ErrorBoundaryProps>) {
|
|
60
|
-
super(props);
|
|
61
|
-
this.state = { error: null,
|
|
62
|
-
showDetails: false };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
static getDerivedStateFromError(error: Error) {
|
|
66
|
-
return { error };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
componentDidCatch(error: Error, _errorInfo: ErrorInfo) {
|
|
70
|
-
console.error(error);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private handleReset = () => {
|
|
74
|
-
this.setState({ error: null,
|
|
75
|
-
showDetails: false });
|
|
76
|
-
this.props.onReset?.();
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
private handleReload = () => {
|
|
80
|
-
window.location.reload();
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
private toggleDetails = () => {
|
|
84
|
-
this.setState(prev => ({ showDetails: !prev.showDetails }));
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
render() {
|
|
88
|
-
if (this.state.error) {
|
|
89
|
-
if (this.props.fullPage) {
|
|
90
|
-
return this.renderFullPage();
|
|
91
|
-
}
|
|
92
|
-
return this.renderInline();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return this.props.children;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private renderInline() {
|
|
99
|
-
const isStaleChunk = isChunkLoadError(this.state.error);
|
|
100
|
-
return (
|
|
101
|
-
<div className="flex flex-col m-2 items-start">
|
|
102
|
-
<div className="flex items-center m-2">
|
|
103
|
-
{isStaleChunk
|
|
104
|
-
? <RefreshCwIcon className={"text-text-secondary dark:text-text-secondary-dark"}
|
|
105
|
-
size={iconSize.small}/>
|
|
106
|
-
: <AlertCircleIcon className={"text-red-500 dark:text-red-400"} size={iconSize.small}/>}
|
|
107
|
-
<div className="ml-4">{isStaleChunk ? "New version available" : "Error"}</div>
|
|
108
|
-
</div>
|
|
109
|
-
<Typography variant={"caption"}>
|
|
110
|
-
{isStaleChunk
|
|
111
|
-
? CHUNK_ERROR_DESCRIPTION
|
|
112
|
-
: this.state.error?.message ?? "See the error in the console"}
|
|
113
|
-
</Typography>
|
|
114
|
-
{isStaleChunk && <Button variant="outlined"
|
|
115
|
-
color="neutral"
|
|
116
|
-
size="small"
|
|
117
|
-
className="mt-3 ml-2"
|
|
118
|
-
onClick={this.handleReload}>
|
|
119
|
-
<RefreshCwIcon size={16}/>
|
|
120
|
-
Reload
|
|
121
|
-
</Button>}
|
|
122
|
-
</div>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
private renderFullPage() {
|
|
127
|
-
const { error, showDetails } = this.state;
|
|
128
|
-
const isPermission = isPermissionError(error);
|
|
129
|
-
// Neither a fault nor a denial: the app is fine, this tab is behind it.
|
|
130
|
-
const isStaleChunk = !isPermission && isChunkLoadError(error);
|
|
131
|
-
|
|
132
|
-
const Icon = isPermission ? ShieldAlertIcon : isStaleChunk ? RefreshCwIcon : AlertCircleIcon;
|
|
133
|
-
const title = isPermission
|
|
134
|
-
? "Access denied"
|
|
135
|
-
: isStaleChunk
|
|
136
|
-
? "New version available"
|
|
137
|
-
: "Something went wrong";
|
|
138
|
-
const description = isPermission
|
|
139
|
-
? "You don't have permission to access this resource. Please check your account permissions or contact your administrator."
|
|
140
|
-
: isStaleChunk
|
|
141
|
-
? CHUNK_ERROR_DESCRIPTION
|
|
142
|
-
: "An unexpected error occurred. You can try reloading the page or going back.";
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<div className={cls(
|
|
146
|
-
"flex items-center justify-center min-h-[400px] h-full w-full",
|
|
147
|
-
"bg-surface-50 dark:bg-surface-950"
|
|
148
|
-
)}>
|
|
149
|
-
<div className="flex flex-col items-center max-w-md px-6 py-10 text-center">
|
|
150
|
-
<div className={cls(
|
|
151
|
-
"flex items-center justify-center w-14 h-14 rounded-xl mb-6",
|
|
152
|
-
isPermission
|
|
153
|
-
? "bg-amber-100 dark:bg-amber-900/30"
|
|
154
|
-
: isStaleChunk
|
|
155
|
-
? "bg-surface-100 dark:bg-surface-800"
|
|
156
|
-
: "bg-red-100 dark:bg-red-900/30"
|
|
157
|
-
)}>
|
|
158
|
-
<Icon
|
|
159
|
-
size={28}
|
|
160
|
-
className={isPermission
|
|
161
|
-
? "text-amber-600 dark:text-amber-400"
|
|
162
|
-
: isStaleChunk
|
|
163
|
-
? "text-text-secondary dark:text-text-secondary-dark"
|
|
164
|
-
: "text-red-500 dark:text-red-400"}
|
|
165
|
-
/>
|
|
166
|
-
</div>
|
|
167
|
-
|
|
168
|
-
<Typography variant={"h6"}
|
|
169
|
-
className="text-text-primary dark:text-text-primary-dark mb-2">
|
|
170
|
-
{title}
|
|
171
|
-
</Typography>
|
|
172
|
-
|
|
173
|
-
<Typography variant={"body2"}
|
|
174
|
-
className="text-text-secondary dark:text-text-secondary-dark mb-8">
|
|
175
|
-
{description}
|
|
176
|
-
</Typography>
|
|
177
|
-
|
|
178
|
-
<div className="flex gap-3">
|
|
179
|
-
{/* React caches a `lazy()` rejection: re-mounting the
|
|
180
|
-
children re-throws without re-running the import, so
|
|
181
|
-
"Try again" cannot recover a missing chunk. */}
|
|
182
|
-
{!isStaleChunk && <Button
|
|
183
|
-
variant="outlined"
|
|
184
|
-
color="neutral"
|
|
185
|
-
size="medium"
|
|
186
|
-
onClick={this.handleReset}
|
|
187
|
-
>
|
|
188
|
-
<ArrowLeftIcon size={16}/>
|
|
189
|
-
Try again
|
|
190
|
-
</Button>}
|
|
191
|
-
<Button
|
|
192
|
-
variant="filled"
|
|
193
|
-
color="primary"
|
|
194
|
-
size="medium"
|
|
195
|
-
onClick={this.handleReload}
|
|
196
|
-
>
|
|
197
|
-
<RefreshCwIcon size={16}/>
|
|
198
|
-
Reload page
|
|
199
|
-
</Button>
|
|
200
|
-
</div>
|
|
201
|
-
|
|
202
|
-
{/* For a stale chunk the message *is* the description
|
|
203
|
-
above; repeating it under a disclosure adds nothing. */}
|
|
204
|
-
{!isStaleChunk && error?.message && (
|
|
205
|
-
<div className="mt-8 w-full">
|
|
206
|
-
<button
|
|
207
|
-
onClick={this.toggleDetails}
|
|
208
|
-
className={cls(
|
|
209
|
-
"flex items-center gap-1 mx-auto text-xs",
|
|
210
|
-
"text-text-disabled dark:text-text-disabled-dark",
|
|
211
|
-
"hover:text-text-secondary dark:hover:text-text-secondary-dark",
|
|
212
|
-
"transition-colors cursor-pointer bg-transparent border-none p-0"
|
|
213
|
-
)}
|
|
214
|
-
>
|
|
215
|
-
Technical details
|
|
216
|
-
{showDetails
|
|
217
|
-
? <ChevronUpIcon size={14}/>
|
|
218
|
-
: <ChevronDownIcon size={14}/>}
|
|
219
|
-
</button>
|
|
220
|
-
{showDetails && (
|
|
221
|
-
<div className={cls(
|
|
222
|
-
"mt-3 p-3 rounded-lg text-left text-xs",
|
|
223
|
-
"bg-surface-100 dark:bg-surface-800/50",
|
|
224
|
-
"text-text-secondary dark:text-text-secondary-dark",
|
|
225
|
-
"font-mono break-all"
|
|
226
|
-
)}>
|
|
227
|
-
{error.message}
|
|
228
|
-
</div>
|
|
229
|
-
)}
|
|
230
|
-
</div>
|
|
231
|
-
)}
|
|
232
|
-
</div>
|
|
233
|
-
</div>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
"use client";
|
|
3
|
-
import React, { PropsWithChildren, useEffect, useState } from "react";
|
|
4
|
-
|
|
5
|
-
import * as Collapsible from "@radix-ui/react-collapsible";
|
|
6
|
-
import { defaultBorderMixin, fieldBackgroundMixin } from "../styles";
|
|
7
|
-
import { ChevronDownIcon } from "lucide-react";
|
|
8
|
-
import { cls } from "../util";
|
|
9
|
-
import { useInjectStyles } from "../hooks";
|
|
10
|
-
|
|
11
|
-
export function ExpandablePanel({
|
|
12
|
-
title,
|
|
13
|
-
children,
|
|
14
|
-
invisible = false,
|
|
15
|
-
expanded,
|
|
16
|
-
onExpandedChange,
|
|
17
|
-
initiallyExpanded = true,
|
|
18
|
-
titleClassName,
|
|
19
|
-
asField,
|
|
20
|
-
className,
|
|
21
|
-
innerClassName
|
|
22
|
-
}: PropsWithChildren<{
|
|
23
|
-
title: React.ReactNode,
|
|
24
|
-
invisible?: boolean,
|
|
25
|
-
initiallyExpanded?: boolean;
|
|
26
|
-
expanded?: boolean;
|
|
27
|
-
onExpandedChange?: (expanded: boolean) => void,
|
|
28
|
-
titleClassName?: string,
|
|
29
|
-
asField?: boolean,
|
|
30
|
-
className?: string,
|
|
31
|
-
innerClassName?: string
|
|
32
|
-
}>) {
|
|
33
|
-
|
|
34
|
-
useInjectStyles("ExpandablePanel", `
|
|
35
|
-
.CollapsibleContent {
|
|
36
|
-
overflow: hidden;
|
|
37
|
-
}
|
|
38
|
-
.CollapsibleContent[data-state='open'] {
|
|
39
|
-
animation: slideDown 220ms ease-out
|
|
40
|
-
}
|
|
41
|
-
.CollapsibleContent[data-state='closed'] {
|
|
42
|
-
animation: slideUp 220ms ease-out;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
@keyframes slideDown {
|
|
46
|
-
from {
|
|
47
|
-
height: 0;
|
|
48
|
-
}
|
|
49
|
-
to {
|
|
50
|
-
height: var(--radix-collapsible-content-height);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
@keyframes slideUp {
|
|
55
|
-
from {
|
|
56
|
-
height: var(--radix-collapsible-content-height);
|
|
57
|
-
}
|
|
58
|
-
to {
|
|
59
|
-
height: 0;
|
|
60
|
-
}
|
|
61
|
-
}`);
|
|
62
|
-
|
|
63
|
-
const [open, setOpen] = useState(expanded !== undefined ? expanded : initiallyExpanded);
|
|
64
|
-
const [allowOverflow, setAllowOverflow] = useState(open);
|
|
65
|
-
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
if (open) {
|
|
68
|
-
setTimeout(() => {
|
|
69
|
-
setAllowOverflow(true);
|
|
70
|
-
}, 220);
|
|
71
|
-
} else {
|
|
72
|
-
setAllowOverflow(false);
|
|
73
|
-
}
|
|
74
|
-
}, [open]);
|
|
75
|
-
|
|
76
|
-
useEffect(() => {
|
|
77
|
-
if (expanded !== undefined)
|
|
78
|
-
setOpen(expanded);
|
|
79
|
-
}, [expanded]);
|
|
80
|
-
|
|
81
|
-
return (<>
|
|
82
|
-
<Collapsible.Root
|
|
83
|
-
className={cls(
|
|
84
|
-
!invisible && defaultBorderMixin + " border",
|
|
85
|
-
"rounded-lg",
|
|
86
|
-
"w-full",
|
|
87
|
-
className
|
|
88
|
-
)}
|
|
89
|
-
open={open}
|
|
90
|
-
onOpenChange={(updatedOpen: boolean) => {
|
|
91
|
-
onExpandedChange?.(updatedOpen);
|
|
92
|
-
setOpen(updatedOpen);
|
|
93
|
-
}}>
|
|
94
|
-
|
|
95
|
-
<Collapsible.Trigger asChild>
|
|
96
|
-
<div
|
|
97
|
-
className={cls(
|
|
98
|
-
"rounded-t-lg flex items-center justify-between w-full min-h-[52px]",
|
|
99
|
-
"hover:bg-surface-accent-200 hover:bg-opacity-40 hover:bg-surface-accent-200/40 dark:hover:bg-surface-800 dark:hover:bg-opacity-40 dark:hover:bg-surface-800/40 active:bg-surface-accent-300/50 dark:active:bg-surface-700/50",
|
|
100
|
-
invisible ? "border-b px-2" : "p-4",
|
|
101
|
-
open ? "py-6" : "py-4",
|
|
102
|
-
"transition-all duration-200",
|
|
103
|
-
invisible && defaultBorderMixin,
|
|
104
|
-
asField && fieldBackgroundMixin,
|
|
105
|
-
titleClassName,
|
|
106
|
-
"cursor-pointer"
|
|
107
|
-
)}
|
|
108
|
-
role="button"
|
|
109
|
-
tabIndex={0}
|
|
110
|
-
aria-expanded={open}
|
|
111
|
-
>
|
|
112
|
-
{title}
|
|
113
|
-
<ChevronDownIcon className={cls("transition", open ? "rotate-180" : "")}/>
|
|
114
|
-
</div>
|
|
115
|
-
</Collapsible.Trigger>
|
|
116
|
-
|
|
117
|
-
<Collapsible.Content
|
|
118
|
-
className={cls("CollapsibleContent")}
|
|
119
|
-
style={{
|
|
120
|
-
overflow: allowOverflow ? "visible" : "hidden"
|
|
121
|
-
}}
|
|
122
|
-
>
|
|
123
|
-
<div className={innerClassName}>
|
|
124
|
-
{children}
|
|
125
|
-
</div>
|
|
126
|
-
</Collapsible.Content>
|
|
127
|
-
</Collapsible.Root>
|
|
128
|
-
</>
|
|
129
|
-
)
|
|
130
|
-
}
|