mirror of
https://github.com/dstotijn/hetty.git
synced 2025-07-01 18:47:29 -04:00
Reuse components across Proxy and Sender modules
This commit is contained in:
@ -1,21 +0,0 @@
|
||||
import { Paper } from "@mui/material";
|
||||
|
||||
function CenteredPaper({ children }: { children: React.ReactNode }): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<Paper
|
||||
elevation={0}
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 36,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Paper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CenteredPaper;
|
51
admin/src/lib/components/ConfirmationDialog.tsx
Normal file
51
admin/src/lib/components/ConfirmationDialog.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import Button from "@mui/material/Button";
|
||||
import Dialog from "@mui/material/Dialog";
|
||||
import DialogActions from "@mui/material/DialogActions";
|
||||
import DialogContent from "@mui/material/DialogContent";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import DialogTitle from "@mui/material/DialogTitle";
|
||||
import React, { useState } from "react";
|
||||
|
||||
export function useConfirmationDialog() {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const close = () => setIsOpen(false);
|
||||
const open = () => setIsOpen(true);
|
||||
|
||||
return { open, close, isOpen };
|
||||
}
|
||||
|
||||
interface ConfirmationDialog {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onConfirm: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function ConfirmationDialog(props: ConfirmationDialog) {
|
||||
const { onClose, onConfirm, isOpen, children } = props;
|
||||
|
||||
function confirm() {
|
||||
onConfirm();
|
||||
onClose();
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={onClose}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Are you sure?</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">{children}</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button onClick={confirm} autoFocus>
|
||||
Confirm
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
201
admin/src/lib/components/KeyValuePair.tsx
Normal file
201
admin/src/lib/components/KeyValuePair.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
import ClearIcon from "@mui/icons-material/Clear";
|
||||
import {
|
||||
Alert,
|
||||
IconButton,
|
||||
InputBase,
|
||||
InputBaseProps,
|
||||
Snackbar,
|
||||
styled,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellProps,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableRowProps,
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
|
||||
const StyledInputBase = styled(InputBase)<InputBaseProps>(() => ({
|
||||
fontSize: "0.875rem",
|
||||
"&.MuiInputBase-root input": {
|
||||
p: 0,
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledTableRow = styled(TableRow)<TableRowProps>(() => ({
|
||||
"& .delete-button": {
|
||||
visibility: "hidden",
|
||||
},
|
||||
"&:hover .delete-button": {
|
||||
visibility: "inherit",
|
||||
},
|
||||
}));
|
||||
|
||||
export interface KeyValuePair {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface KeyValuePairTableProps {
|
||||
items: KeyValuePair[];
|
||||
onChange?: (key: string, value: string, index: number) => void;
|
||||
onDelete?: (index: number) => void;
|
||||
}
|
||||
|
||||
export function KeyValuePairTable({ items, onChange, onDelete }: KeyValuePairTableProps): JSX.Element {
|
||||
const [copyConfOpen, setCopyConfOpen] = useState(false);
|
||||
|
||||
const handleCellClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
const windowSel = window.getSelection();
|
||||
|
||||
if (!windowSel || !document) {
|
||||
return;
|
||||
}
|
||||
|
||||
const r = document.createRange();
|
||||
r.selectNode(e.currentTarget);
|
||||
windowSel.removeAllRanges();
|
||||
windowSel.addRange(r);
|
||||
document.execCommand("copy");
|
||||
windowSel.removeAllRanges();
|
||||
|
||||
setCopyConfOpen(true);
|
||||
};
|
||||
|
||||
const handleCopyConfClose = (_: Event | React.SyntheticEvent, reason?: string) => {
|
||||
if (reason === "clickaway") {
|
||||
return;
|
||||
}
|
||||
|
||||
setCopyConfOpen(false);
|
||||
};
|
||||
|
||||
const baseCellStyle = {
|
||||
"&:hover": {
|
||||
cursor: "copy",
|
||||
},
|
||||
};
|
||||
|
||||
const KeyTableCell = styled(TableCell)<TableCellProps>(() => (!onChange ? baseCellStyle : {}));
|
||||
const ValueTableCell = styled(TableCell)<TableCellProps>(() => ({
|
||||
...(!onChange && baseCellStyle),
|
||||
width: "60%",
|
||||
wordBreak: "break-all",
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Snackbar open={copyConfOpen} autoHideDuration={3000} onClose={handleCopyConfClose}>
|
||||
<Alert onClose={handleCopyConfClose} severity="info">
|
||||
Copied to clipboard.
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
<TableContainer sx={{ overflowX: "initial" }}>
|
||||
<Table size="small" stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Key</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
{onDelete && <TableCell padding="checkbox"></TableCell>}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody
|
||||
sx={{
|
||||
"td, th, input": {
|
||||
fontFamily: "'JetBrains Mono', monospace",
|
||||
fontSize: "0.75rem",
|
||||
py: 0.2,
|
||||
},
|
||||
"td span, th span": {
|
||||
display: "block",
|
||||
py: 0.7,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{items.map(({ key, value }, idx) => (
|
||||
<StyledTableRow key={idx} hover>
|
||||
<KeyTableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
onClick={(e) => {
|
||||
!onChange && handleCellClick(e);
|
||||
}}
|
||||
>
|
||||
{!onChange && <span>{key}</span>}
|
||||
{onChange && (
|
||||
<StyledInputBase
|
||||
size="small"
|
||||
fullWidth
|
||||
placeholder="Key"
|
||||
value={key}
|
||||
onChange={(e) => {
|
||||
onChange && onChange(e.target.value, value, idx);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</KeyTableCell>
|
||||
<ValueTableCell
|
||||
onClick={(e) => {
|
||||
!onChange && handleCellClick(e);
|
||||
}}
|
||||
>
|
||||
{!onChange && value}
|
||||
{onChange && (
|
||||
<StyledInputBase
|
||||
size="small"
|
||||
fullWidth
|
||||
placeholder="Value"
|
||||
value={value}
|
||||
onChange={(e) => {
|
||||
onChange && onChange(key, e.target.value, idx);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</ValueTableCell>
|
||||
{onDelete && (
|
||||
<TableCell>
|
||||
<div className="delete-button">
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onDelete && onDelete(idx);
|
||||
}}
|
||||
sx={{
|
||||
visibility: onDelete === undefined || items.length === idx + 1 ? "hidden" : "inherit",
|
||||
}}
|
||||
>
|
||||
<ClearIcon fontSize="inherit" />
|
||||
</IconButton>
|
||||
</div>
|
||||
</TableCell>
|
||||
)}
|
||||
</StyledTableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function sortKeyValuePairs(items: KeyValuePair[]): KeyValuePair[] {
|
||||
const sorted = [...items];
|
||||
|
||||
sorted.sort((a, b) => {
|
||||
if (a.key < b.key) {
|
||||
return -1;
|
||||
}
|
||||
if (a.key > b.key) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
export default KeyValuePairTable;
|
91
admin/src/lib/components/RequestTabs.tsx
Normal file
91
admin/src/lib/components/RequestTabs.tsx
Normal file
@ -0,0 +1,91 @@
|
||||
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
||||
import { Box, Tab } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import { KeyValuePairTable, KeyValuePair, KeyValuePairTableProps } from "./KeyValuePair";
|
||||
|
||||
import Editor from "lib/components/Editor";
|
||||
|
||||
enum TabValue {
|
||||
QueryParams = "queryParams",
|
||||
Headers = "headers",
|
||||
Body = "body",
|
||||
}
|
||||
|
||||
interface RequestTabsProps {
|
||||
queryParams: KeyValuePair[];
|
||||
headers: KeyValuePair[];
|
||||
onQueryParamChange?: KeyValuePairTableProps["onChange"];
|
||||
onQueryParamDelete?: KeyValuePairTableProps["onDelete"];
|
||||
onHeaderChange?: KeyValuePairTableProps["onChange"];
|
||||
onHeaderDelete?: KeyValuePairTableProps["onDelete"];
|
||||
body?: string | null;
|
||||
onBodyChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
function RequestTabs(props: RequestTabsProps): JSX.Element {
|
||||
const {
|
||||
queryParams,
|
||||
onQueryParamChange,
|
||||
onQueryParamDelete,
|
||||
headers,
|
||||
onHeaderChange,
|
||||
onHeaderDelete,
|
||||
body,
|
||||
onBodyChange,
|
||||
} = props;
|
||||
const [tabValue, setTabValue] = useState(TabValue.QueryParams);
|
||||
|
||||
const tabSx = {
|
||||
textTransform: "none",
|
||||
};
|
||||
|
||||
const queryParamsLength = onQueryParamChange ? queryParams.length - 1 : queryParams.length;
|
||||
const headersLength = onHeaderChange ? headers.length - 1 : headers.length;
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "column", height: "100%" }}>
|
||||
<TabContext value={tabValue}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider", mb: 1 }}>
|
||||
<TabList onChange={(_, value) => setTabValue(value)}>
|
||||
<Tab
|
||||
value={TabValue.QueryParams}
|
||||
label={"Query Params" + (queryParamsLength ? ` (${queryParamsLength})` : "")}
|
||||
sx={tabSx}
|
||||
/>
|
||||
<Tab value={TabValue.Headers} label={"Headers" + (headersLength ? ` (${headersLength})` : "")} sx={tabSx} />
|
||||
<Tab
|
||||
value={TabValue.Body}
|
||||
label={"Body" + (body?.length ? ` (${body.length} byte` + (body.length > 1 ? "s" : "") + ")" : "")}
|
||||
sx={tabSx}
|
||||
/>
|
||||
</TabList>
|
||||
</Box>
|
||||
<Box flex="1 auto" overflow="scroll" height="100%">
|
||||
<TabPanel value={TabValue.QueryParams} sx={{ p: 0, height: "100%" }}>
|
||||
<Box>
|
||||
<KeyValuePairTable items={queryParams} onChange={onQueryParamChange} onDelete={onQueryParamDelete} />
|
||||
</Box>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabValue.Headers} sx={{ p: 0, height: "100%" }}>
|
||||
<Box>
|
||||
<KeyValuePairTable items={headers} onChange={onHeaderChange} onDelete={onHeaderDelete} />
|
||||
</Box>
|
||||
</TabPanel>
|
||||
<TabPanel value={TabValue.Body} sx={{ p: 0, height: "100%" }}>
|
||||
<Editor
|
||||
content={body || ""}
|
||||
onChange={(value) => {
|
||||
onBodyChange && onBodyChange(value || "");
|
||||
}}
|
||||
monacoOptions={{ readOnly: onBodyChange === undefined }}
|
||||
contentType={headers.find(({ key }) => key.toLowerCase() === "content-type")?.value}
|
||||
/>
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</TabContext>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default RequestTabs;
|
125
admin/src/lib/components/RequestsTable.tsx
Normal file
125
admin/src/lib/components/RequestsTable.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
import {
|
||||
TableContainer,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableBody,
|
||||
styled,
|
||||
TableCellProps,
|
||||
TableRowProps,
|
||||
} from "@mui/material";
|
||||
|
||||
import HttpStatusIcon from "./HttpStatusIcon";
|
||||
|
||||
import { HttpMethod } from "lib/graphql/generated";
|
||||
|
||||
const baseCellStyle = {
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
} as const;
|
||||
|
||||
const MethodTableCell = styled(TableCell)<TableCellProps>(() => ({
|
||||
...baseCellStyle,
|
||||
width: "100px",
|
||||
}));
|
||||
|
||||
const OriginTableCell = styled(TableCell)<TableCellProps>(() => ({
|
||||
...baseCellStyle,
|
||||
maxWidth: "100px",
|
||||
}));
|
||||
|
||||
const PathTableCell = styled(TableCell)<TableCellProps>(() => ({
|
||||
...baseCellStyle,
|
||||
maxWidth: "200px",
|
||||
}));
|
||||
|
||||
const StatusTableCell = styled(TableCell)<TableCellProps>(() => ({
|
||||
...baseCellStyle,
|
||||
width: "100px",
|
||||
}));
|
||||
|
||||
const RequestTableRow = styled(TableRow)<TableRowProps>(() => ({
|
||||
"&:hover": {
|
||||
cursor: "pointer",
|
||||
},
|
||||
}));
|
||||
|
||||
interface HttpRequest {
|
||||
id: string;
|
||||
url: string;
|
||||
method: HttpMethod;
|
||||
response?: HttpResponse | null;
|
||||
}
|
||||
|
||||
interface HttpResponse {
|
||||
statusCode: number;
|
||||
statusReason: string;
|
||||
body?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
requests: HttpRequest[];
|
||||
activeRowId?: string;
|
||||
onRowClick?: (id: string) => void;
|
||||
onContextMenu?: (e: React.MouseEvent, id: string) => void;
|
||||
}
|
||||
|
||||
export default function RequestsTable(props: Props): JSX.Element {
|
||||
const { requests, activeRowId, onRowClick, onContextMenu } = props;
|
||||
|
||||
return (
|
||||
<TableContainer sx={{ overflowX: "initial" }}>
|
||||
<Table size="small" stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Method</TableCell>
|
||||
<TableCell>Origin</TableCell>
|
||||
<TableCell>Path</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{requests.map(({ id, method, url, response }) => {
|
||||
const { origin, pathname, search, hash } = new URL(url);
|
||||
|
||||
return (
|
||||
<RequestTableRow
|
||||
key={id}
|
||||
hover
|
||||
selected={id === activeRowId}
|
||||
onClick={() => {
|
||||
onRowClick && onRowClick(id);
|
||||
}}
|
||||
onContextMenu={(e) => {
|
||||
onContextMenu && onContextMenu(e, id);
|
||||
}}
|
||||
>
|
||||
<MethodTableCell>
|
||||
<code>{method}</code>
|
||||
</MethodTableCell>
|
||||
<OriginTableCell>{origin}</OriginTableCell>
|
||||
<PathTableCell>{decodeURIComponent(pathname + search + hash)}</PathTableCell>
|
||||
<StatusTableCell>
|
||||
{response && <Status code={response.statusCode} reason={response.statusReason} />}
|
||||
</StatusTableCell>
|
||||
</RequestTableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function Status({ code, reason }: { code: number; reason: string }): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<HttpStatusIcon status={code} />{" "}
|
||||
<code>
|
||||
{code} {reason}
|
||||
</code>
|
||||
</div>
|
||||
);
|
||||
}
|
39
admin/src/lib/components/Response.tsx
Normal file
39
admin/src/lib/components/Response.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
import { sortKeyValuePairs } from "./KeyValuePair";
|
||||
import ResponseTabs from "./ResponseTabs";
|
||||
|
||||
import ResponseStatus from "lib/components/ResponseStatus";
|
||||
import { HttpResponseLog } from "lib/graphql/generated";
|
||||
|
||||
interface ResponseProps {
|
||||
response?: HttpResponseLog | null;
|
||||
}
|
||||
|
||||
function Response({ response }: ResponseProps): JSX.Element {
|
||||
return (
|
||||
<Box height="100%">
|
||||
<Box sx={{ position: "absolute", right: 0, mt: 1.4 }}>
|
||||
<Typography variant="overline" color="textSecondary" sx={{ float: "right", ml: 3 }}>
|
||||
Response
|
||||
</Typography>
|
||||
{response && (
|
||||
<Box sx={{ float: "right", mt: 0.2 }}>
|
||||
<ResponseStatus
|
||||
proto={response.proto}
|
||||
statusCode={response.statusCode}
|
||||
statusReason={response.statusReason}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<ResponseTabs
|
||||
body={response?.body}
|
||||
headers={sortKeyValuePairs(response?.headers || [])}
|
||||
hasResponse={response !== undefined && response !== null}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default Response;
|
68
admin/src/lib/components/ResponseTabs.tsx
Normal file
68
admin/src/lib/components/ResponseTabs.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
||||
import { Box, Paper, Tab, Typography } from "@mui/material";
|
||||
import React, { useState } from "react";
|
||||
|
||||
import Editor from "lib/components/Editor";
|
||||
import { KeyValuePairTable } from "lib/components/KeyValuePair";
|
||||
import { HttpResponseLog } from "lib/graphql/generated";
|
||||
|
||||
interface ResponseTabsProps {
|
||||
headers: HttpResponseLog["headers"];
|
||||
body: HttpResponseLog["body"];
|
||||
hasResponse: boolean;
|
||||
}
|
||||
|
||||
enum TabValue {
|
||||
Body = "body",
|
||||
Headers = "headers",
|
||||
}
|
||||
|
||||
const reqNotSent = (
|
||||
<Paper variant="centered">
|
||||
<Typography>Response not received yet.</Typography>
|
||||
</Paper>
|
||||
);
|
||||
|
||||
function ResponseTabs(props: ResponseTabsProps): JSX.Element {
|
||||
const { headers, body, hasResponse } = props;
|
||||
const [tabValue, setTabValue] = useState(TabValue.Body);
|
||||
|
||||
const contentType = headers.find((header) => header.key.toLowerCase() === "content-type")?.value;
|
||||
|
||||
const tabSx = {
|
||||
textTransform: "none",
|
||||
};
|
||||
|
||||
return (
|
||||
<Box height="100%" sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<TabContext value={tabValue}>
|
||||
<Box sx={{ borderBottom: 1, borderColor: "divider", mb: 1 }}>
|
||||
<TabList onChange={(_, value) => setTabValue(value)}>
|
||||
<Tab
|
||||
value={TabValue.Body}
|
||||
label={"Body" + (body?.length ? ` (${body.length} byte` + (body.length > 1 ? "s" : "") + ")" : "")}
|
||||
sx={tabSx}
|
||||
/>
|
||||
<Tab
|
||||
value={TabValue.Headers}
|
||||
label={"Headers" + (headers.length ? ` (${headers.length})` : "")}
|
||||
sx={tabSx}
|
||||
/>
|
||||
</TabList>
|
||||
</Box>
|
||||
<Box flex="1 auto" overflow="hidden">
|
||||
<TabPanel value={TabValue.Body} sx={{ p: 0, height: "100%" }}>
|
||||
{body && <Editor content={body} contentType={contentType} />}
|
||||
{!hasResponse && reqNotSent}
|
||||
</TabPanel>
|
||||
<TabPanel value={TabValue.Headers} sx={{ p: 0, height: "100%", overflow: "scroll" }}>
|
||||
{headers.length > 0 && <KeyValuePairTable items={headers} />}
|
||||
{!hasResponse && reqNotSent}
|
||||
</TabPanel>
|
||||
</Box>
|
||||
</TabContext>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResponseTabs;
|
53
admin/src/lib/components/SplitPane.tsx
Normal file
53
admin/src/lib/components/SplitPane.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { alpha, styled } from "@mui/material/styles";
|
||||
import ReactSplitPane, { SplitPaneProps } from "react-split-pane";
|
||||
|
||||
const BORDER_WIDTH_FACTOR = 1.75;
|
||||
const SIZE_FACTOR = 4;
|
||||
const MARGIN_FACTOR = -1.75;
|
||||
|
||||
const SplitPane = styled(ReactSplitPane)<SplitPaneProps>(({ theme }) => ({
|
||||
".Resizer": {
|
||||
zIndex: theme.zIndex.mobileStepper,
|
||||
boxSizing: "border-box",
|
||||
backgroundClip: "padding-box",
|
||||
backgroundColor: alpha(theme.palette.grey[400], 0.05),
|
||||
},
|
||||
".Resizer:hover": {
|
||||
transition: "all 0.5s ease",
|
||||
backgroundColor: alpha(theme.palette.primary.main, 1),
|
||||
},
|
||||
|
||||
".Resizer.horizontal": {
|
||||
height: theme.spacing(SIZE_FACTOR),
|
||||
marginTop: theme.spacing(MARGIN_FACTOR),
|
||||
marginBottom: theme.spacing(MARGIN_FACTOR),
|
||||
borderTop: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
|
||||
borderBottom: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
|
||||
borderBottomColor: "rgba(255, 255, 255, 0)",
|
||||
cursor: "row-resize",
|
||||
width: "100%",
|
||||
},
|
||||
|
||||
".Resizer.vertical": {
|
||||
width: theme.spacing(SIZE_FACTOR),
|
||||
marginLeft: theme.spacing(MARGIN_FACTOR),
|
||||
marginRight: theme.spacing(MARGIN_FACTOR),
|
||||
borderLeft: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
|
||||
borderRight: `${theme.spacing(BORDER_WIDTH_FACTOR)} solid rgba(255, 255, 255, 0)`,
|
||||
cursor: "col-resize",
|
||||
},
|
||||
|
||||
".Resizer.disabled": {
|
||||
cursor: "not-allowed",
|
||||
},
|
||||
|
||||
".Resizer.disabled:hover": {
|
||||
borderColor: "transparent",
|
||||
},
|
||||
|
||||
".Pane": {
|
||||
overflow: "hidden",
|
||||
},
|
||||
}));
|
||||
|
||||
export default SplitPane;
|
Reference in New Issue
Block a user