migrate field types based on TextEditor

This commit is contained in:
Sidney Alcantara
2022-11-09 17:07:22 +11:00
parent 387c2db2bc
commit 5678f96c2b
29 changed files with 223 additions and 386 deletions

View File

@@ -0,0 +1,77 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import { useSaveOnUnmount } from "@src/hooks/useSaveOnUnmount";
import { InputBase, InputBaseProps } from "@mui/material";
import { spreadSx } from "@src/utils/ui";
export interface IEditorCellTextFieldProps extends IEditorCellProps<string> {
InputProps?: Partial<InputBaseProps>;
}
export default function EditorCellTextField({
column,
value,
onSubmit,
setFocusInsideCell,
InputProps = {},
}: IEditorCellTextFieldProps) {
const [localValue, setLocalValue] = useSaveOnUnmount(value, onSubmit);
const maxLength = column.config?.maxLength;
return (
<InputBase
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
fullWidth
autoFocus
onKeyDown={(e) => {
if (
e.key === "ArrowLeft" ||
e.key === "ArrowRight" ||
e.key === "ArrowUp" ||
e.key === "ArrowDown"
) {
e.stopPropagation();
}
if (e.key === "Escape") {
// Escape removes focus inside cell, this runs before save on unmount
setLocalValue(value);
}
if (e.key === "Enter" && !e.shiftKey) {
// Removes focus from inside cell, triggering save on unmount
setFocusInsideCell(false);
}
}}
onClick={(e) => e.stopPropagation()}
onDoubleClick={(e) => e.stopPropagation()}
{...InputProps}
inputProps={{ maxLength, ...InputProps.inputProps }}
sx={[
{
width: "100%",
height: "calc(100% - 1px)",
marginTop: "1px",
paddingBottom: "1px",
backgroundColor: "var(--cell-background-color)",
outline: "inherit",
outlineOffset: "inherit",
font: "inherit", // Prevent text jumping
letterSpacing: "inherit", // Prevent text jumping
"& .MuiInputBase-input": { p: "var(--cell-padding)" },
"& textarea.MuiInputBase-input": {
lineHeight: (theme) => theme.typography.body2.lineHeight,
maxHeight: "100%",
boxSizing: "border-box",
py: 3 / 8,
},
},
...spreadSx(InputProps.sx),
]}
/>
);
}

View File

@@ -14,6 +14,9 @@ export const StyledCell = styled("div")(({ theme }) => ({
height: "100%",
contain: "strict",
overflow: "hidden",
display: "flex",
alignItems: "center",
},
backgroundColor: "var(--cell-background-color)",

View File

@@ -446,7 +446,11 @@ export default function Table({
cell.column.columnDef.meta?.config?.required
)}
aria-selected={isSelectedCell}
aria-describedby="rowy-table-cell-description"
aria-describedby={
canEditCell
? "rowy-table-editable-cell-description"
: undefined
}
style={{
width: cell.column.getSize(),
height: tableSchema.rowHeight,
@@ -537,7 +541,10 @@ export default function Table({
</StyledTable>
<Portal>
<div id="rowy-table-cell-description" style={{ display: "none" }}>
<div
id="rowy-table-editable-cell-description"
style={{ display: "none" }}
>
Press Enter to edit.
</div>
</Portal>

View File

@@ -1,122 +0,0 @@
import { useRef, useLayoutEffect } from "react";
import { EditorProps } from "react-data-grid";
import { useSetAtom } from "jotai";
import { get } from "lodash-es";
import { TextField } from "@mui/material";
import { tableScope, updateFieldAtom } from "@src/atoms/tableScope";
import { FieldType } from "@src/constants/fields";
import { getFieldType } from "@src/components/fields";
/** WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE */
export default function TextEditor({ row, column }: EditorProps<any>) {
const updateField = useSetAtom(updateFieldAtom, tableScope);
const type = getFieldType(column as any);
const cellValue = get(row, column.key);
const defaultValue =
type === FieldType.percentage && typeof cellValue === "number"
? cellValue * 100
: cellValue;
const inputRef = useRef<HTMLInputElement>(null);
// WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE
useLayoutEffect(() => {
const inputElement = inputRef.current;
return () => {
const newValue = inputElement?.value;
let formattedValue: any = newValue;
if (newValue !== undefined) {
if (type === FieldType.number) {
formattedValue = Number(newValue);
} else if (type === FieldType.percentage) {
formattedValue = Number(newValue) / 100;
}
updateField({
path: row._rowy_ref.path,
fieldName: column.key,
value: formattedValue,
});
}
};
}, [column.key, row._rowy_ref.path, type, updateField]);
let inputType = "text";
switch (type) {
case FieldType.email:
inputType = "email";
break;
case FieldType.phone:
inputType = "tel";
break;
case FieldType.url:
inputType = "url";
break;
case FieldType.number:
case FieldType.percentage:
inputType = "number";
break;
default:
break;
}
const { maxLength } = (column as any).config;
return (
<TextField
defaultValue={defaultValue}
type={inputType}
fullWidth
multiline={type === FieldType.longText}
variant="standard"
inputProps={{
ref: inputRef,
maxLength: maxLength,
}}
sx={{
width: "100%",
height: "100%",
backgroundColor: "var(--cell-background-color)",
"& .MuiInputBase-root": {
height: "100%",
font: "inherit", // Prevent text jumping
letterSpacing: "inherit", // Prevent text jumping
p: 0,
},
"& .MuiInputBase-input": {
height: "100%",
font: "inherit", // Prevent text jumping
letterSpacing: "inherit", // Prevent text jumping
p: "var(--cell-padding)",
pb: 1 / 8,
},
"& textarea.MuiInputBase-input": {
lineHeight: (theme) => theme.typography.body2.lineHeight,
maxHeight: "100%",
boxSizing: "border-box",
py: 3 / 8,
},
}}
InputProps={{
endAdornment:
(column as any).type === FieldType.percentage ? "%" : undefined,
}}
autoFocus
onKeyDown={(e) => {
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
e.stopPropagation();
}
if (e.key === "Escape") {
(e.target as any).value = defaultValue;
}
}}
/>
);
}

View File

@@ -36,7 +36,7 @@ export interface ICellOptions {
*/
export default function withTableCell(
DisplayCellComponent: React.ComponentType<IDisplayCellProps>,
EditorCellComponent: React.ComponentType<IEditorCellProps>,
EditorCellComponent: React.ComponentType<IEditorCellProps> | null,
editorMode: "focus" | "inline" | "popover" = "focus",
options: ICellOptions = {}
) {
@@ -115,13 +115,16 @@ export default function withTableCell(
});
};
const editorCell = (
// Show displayCell as a fallback if intentionally null
const editorCell = EditorCellComponent ? (
<EditorCellComponent
{...basicCellProps}
tabIndex={focusInsideCell ? 0 : -1}
onSubmit={handleSubmit}
parentRef={parentRef}
/>
) : (
displayCell
);
if (editorMode === "focus" && focusInsideCell) {

View File

@@ -0,0 +1,6 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
export default function Email(props: IEditorCellProps<string>) {
return <EditorCellTextField {...props} InputProps={{ type: "email" }} />;
}

View File

@@ -1,10 +1,10 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
import withTableCell from "@src/components/Table/withTableCell";
import EmailIcon from "@mui/icons-material/MailOutlined";
import BasicCell from "@src/components/fields/_BasicCell/BasicCellValue";
import TextEditor from "@src/components/Table/editors/TextEditor";
import DisplayCell from "@src/components/fields/_BasicCell/BasicCellValue";
import EditorCell from "./EditorCell";
import { filterOperators } from "@src/components/fields/ShortText/Filter";
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
@@ -23,8 +23,7 @@ export const config: IFieldConfig = {
icon: <EmailIcon />,
description: "Email address. Not validated.",
contextMenuActions: BasicContextMenuActions,
TableCell: withBasicCell(BasicCell),
TableEditor: TextEditor,
TableCell: withTableCell(DisplayCell, EditorCell),
SideDrawerField,
filter: {
operators: filterOperators,

View File

@@ -1,8 +1,8 @@
import { IHeavyCellProps } from "@src/components/fields/types";
import { IDisplayCellProps } from "@src/components/fields/types";
import { useTheme } from "@mui/material";
export default function Id({ docRef }: IHeavyCellProps) {
export default function Id({ docRef }: IDisplayCellProps) {
const theme = useTheme();
return (

View File

@@ -1,17 +1,9 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withHeavyCell from "@src/components/fields/_withTableCell/withHeavyCell";
import withTableCell from "@src/components/Table/withTableCell";
import DisplayCell from "./DisplayCell";
import SideDrawerField from "./SideDrawerField";
import { Id as IdIcon } from "@src/assets/icons";
import BasicCell from "@src/components/fields/_BasicCell/BasicCellValue";
import withSideDrawerEditor from "@src/components/Table/editors/withSideDrawerEditor";
const TableCell = lazy(
() => import("./TableCell" /* webpackChunkName: "TableCell-Id" */)
);
const SideDrawerField = lazy(
() => import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Id" */)
);
export const config: IFieldConfig = {
type: FieldType.id,
@@ -21,8 +13,7 @@ export const config: IFieldConfig = {
initialValue: "",
icon: <IdIcon />,
description: "Displays the rows ID. Read-only. Cannot be sorted.",
TableCell: withHeavyCell(BasicCell, TableCell),
TableEditor: withSideDrawerEditor(TableCell),
TableCell: withTableCell(DisplayCell, null),
SideDrawerField,
};
export default config;

View File

@@ -1,16 +1,15 @@
import { IBasicCellProps } from "@src/components/fields/types";
import { IDisplayCellProps } from "@src/components/fields/types";
import { useTheme } from "@mui/material";
export default function LongText({ value }: IBasicCellProps) {
export default function LongText({ value }: IDisplayCellProps) {
const theme = useTheme();
return (
<div
style={{
width: "100%",
maxHeight: "100%",
padding: theme.spacing(3 / 8, 0),
padding: theme.spacing(1, 0),
whiteSpace: "pre-line",
lineHeight: theme.typography.body2.lineHeight,

View File

@@ -0,0 +1,6 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
export default function LongText(props: IEditorCellProps<string>) {
return <EditorCellTextField {...props} InputProps={{ multiline: true }} />;
}

View File

@@ -1,21 +1,15 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
import withTableCell from "@src/components/Table/withTableCell";
import LongTextIcon from "@mui/icons-material/Notes";
import BasicCell from "./BasicCell";
import TextEditor from "@src/components/Table/editors/TextEditor";
import DisplayCell from "./DisplayCell";
import EditorCell from "./EditorCell";
import SideDrawerField from "./SideDrawerField";
import { filterOperators } from "./Filter";
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
const SideDrawerField = lazy(
() =>
import(
"./SideDrawerField" /* webpackChunkName: "SideDrawerField-LongText" */
)
);
const Settings = lazy(
() => import("./Settings" /* webpackChunkName: "Settings-LongText" */)
);
@@ -30,8 +24,7 @@ export const config: IFieldConfig = {
icon: <LongTextIcon />,
description: "Text displayed on multiple lines.",
contextMenuActions: BasicContextMenuActions,
TableCell: withBasicCell(BasicCell),
TableEditor: TextEditor,
TableCell: withTableCell(DisplayCell, EditorCell),
SideDrawerField,
settings: Settings,
filter: {

View File

@@ -1,5 +0,0 @@
import { IBasicCellProps } from "@src/components/fields/types";
export default function Number_({ value }: IBasicCellProps) {
return <>{`${value ?? ""}`}</>;
}

View File

@@ -0,0 +1,5 @@
import { IDisplayCellProps } from "@src/components/fields/types";
export default function Number_({ value }: IDisplayCellProps) {
return <>{`${value ?? ""}`}</>;
}

View File

@@ -0,0 +1,12 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
export default function Number_(props: IEditorCellProps<number>) {
return (
<EditorCellTextField
{...(props as any)}
InputProps={{ type: "number" }}
onSubmit={(v) => props.onSubmit(Number(v))}
/>
);
}

View File

@@ -1,10 +1,10 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
import withTableCell from "@src/components/Table/withTableCell";
import { Number as NumberIcon } from "@src/assets/icons";
import BasicCell from "./BasicCell";
import TextEditor from "@src/components/Table/editors/TextEditor";
import DisplayCell from "./DisplayCell";
import EditorCell from "./EditorCell";
import { filterOperators } from "./Filter";
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
const SideDrawerField = lazy(
@@ -22,8 +22,7 @@ export const config: IFieldConfig = {
icon: <NumberIcon />,
description: "Numeric value.",
contextMenuActions: BasicContextMenuActions,
TableCell: withBasicCell(BasicCell),
TableEditor: TextEditor,
TableCell: withTableCell(DisplayCell, EditorCell),
SideDrawerField,
filter: {
operators: filterOperators,

View File

@@ -1,23 +0,0 @@
import { IBasicCellProps } from "@src/components/fields/types";
import { useTheme } from "@mui/material";
export default function Percentage({ value }: IBasicCellProps) {
const theme = useTheme();
if (value === null || value === undefined) return null;
const percentage = typeof value === "number" ? value : 0;
return (
<div
style={{
textAlign: "right",
color: theme.palette.text.primary,
position: "relative",
zIndex: 1,
}}
>
{Math.round(percentage * 100)}%
</div>
);
}

View File

@@ -1,9 +1,9 @@
import { IHeavyCellProps } from "@src/components/fields/types";
import { IDisplayCellProps } from "@src/components/fields/types";
import { useTheme } from "@mui/material";
import { resultColorsScale } from "@src/utils/color";
export default function Percentage({ column, value }: IHeavyCellProps) {
export default function Percentage({ column, value }: IDisplayCellProps) {
const theme = useTheme();
const { colors } = (column as any).config;

View File

@@ -0,0 +1,13 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
export default function Percentage(props: IEditorCellProps<number>) {
return (
<EditorCellTextField
{...(props as any)}
InputProps={{ type: "number", endAdornment: "%" }}
value={typeof props.value === "number" ? props.value * 100 : props.value}
onSubmit={(v) => props.onSubmit(Number(v) / 100)}
/>
);
}

View File

@@ -1,20 +1,13 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withHeavyCell from "@src/components/fields/_withTableCell/withHeavyCell";
import withTableCell from "@src/components/Table/withTableCell";
import { Percentage as PercentageIcon } from "@src/assets/icons";
import TextEditor from "@src/components/Table/editors/TextEditor";
import DisplayCell from "./DisplayCell";
import EditorCell from "./EditorCell";
import { filterOperators } from "@src/components/fields/Number/Filter";
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
const BasicCell = lazy(
() => import("./BasicCell" /* webpackChunkName: "BasicCell-Percentage" */)
);
const TableCell = lazy(
() => import("./TableCell" /* webpackChunkName: "TableCell-Percentage" */)
);
const SideDrawerField = lazy(
() =>
import(
@@ -37,8 +30,7 @@ export const config: IFieldConfig = {
requireConfiguration: true,
description: "Percentage stored as a number between 0 and 1.",
contextMenuActions: BasicContextMenuActions,
TableCell: withHeavyCell(BasicCell, TableCell),
TableEditor: TextEditor,
TableCell: withTableCell(DisplayCell, EditorCell),
SideDrawerField,
settings: Settings,
filter: {

View File

@@ -0,0 +1,6 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
export default function Phone(props: IEditorCellProps<string>) {
return <EditorCellTextField {...props} InputProps={{ type: "tel" }} />;
}

View File

@@ -1,10 +1,10 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
import withTableCell from "@src/components/Table/withTableCell";
import PhoneIcon from "@mui/icons-material/PhoneOutlined";
import BasicCell from "@src/components/fields/_BasicCell/BasicCellValue";
import TextEditor from "@src/components/Table/editors/TextEditor";
import DisplayCell from "@src/components/fields/_BasicCell/BasicCellValue";
import EditorCell from "./EditorCell";
import { filterOperators } from "@src/components/fields/ShortText/Filter";
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
@@ -23,8 +23,7 @@ export const config: IFieldConfig = {
icon: <PhoneIcon />,
description: "Phone number stored as text. Not validated.",
contextMenuActions: BasicContextMenuActions,
TableCell: withBasicCell(BasicCell),
TableEditor: TextEditor,
TableCell: withTableCell(DisplayCell, EditorCell),
SideDrawerField,
filter: {
operators: filterOperators,

View File

@@ -1,12 +1,12 @@
import { useAtom } from "jotai";
import { IBasicCellProps } from "@src/components/fields/types";
import { IDisplayCellProps } from "@src/components/fields/types";
import { Stack, IconButton } from "@mui/material";
import LaunchIcon from "@mui/icons-material/Launch";
import { projectScope, projectIdAtom } from "@src/atoms/projectScope";
export default function Reference({ value }: IBasicCellProps) {
export default function Reference({ value }: IDisplayCellProps) {
const [projectId] = useAtom(projectIdAtom, projectScope);
const path = value?.path ?? "";
@@ -17,8 +17,7 @@ export default function Reference({ value }: IBasicCellProps) {
direction="row"
alignItems="center"
justifyContent="space-between"
className="cell-collapse-padding"
sx={{ p: "var(--cell-padding)", pr: 0.5 }}
sx={{ p: "var(--cell-padding)", pr: 0.5, width: "100%" }}
>
<div style={{ flexGrow: 1, overflow: "hidden" }}>{path}</div>

View File

@@ -1,100 +1,33 @@
import { useRef, useLayoutEffect } from "react";
import { useAtom, useSetAtom } from "jotai";
import { EditorProps } from "react-data-grid";
import { get } from "lodash-es";
import { useSnackbar } from "notistack";
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
import { TextField } from "@mui/material";
import { useAtom } from "jotai";
import { doc, deleteField } from "firebase/firestore";
import { useSnackbar } from "notistack";
import { projectScope } from "@src/atoms/projectScope";
import { firebaseDbAtom } from "@src/sources/ProjectSourceFirebase";
import { tableScope, updateFieldAtom } from "@src/atoms/tableScope";
import { doc, deleteField } from "firebase/firestore";
/** WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE */
export default function TextEditor({ row, column }: EditorProps<any>) {
const [firebaseDb] = useAtom(firebaseDbAtom, projectScope);
const updateField = useSetAtom(updateFieldAtom, tableScope);
export default function Reference(
props: IEditorCellProps<ReturnType<typeof doc>>
) {
const { enqueueSnackbar } = useSnackbar();
const inputRef = useRef<HTMLInputElement>(null);
// WARNING: THIS DOES NOT WORK IN REACT 18 STRICT MODE
useLayoutEffect(() => {
const inputElement = inputRef.current;
return () => {
const newValue = inputElement?.value;
if (newValue !== undefined && newValue !== "") {
try {
const refValue = doc(firebaseDb, newValue);
updateField({
path: row._rowy_ref.path,
fieldName: column.key,
value: refValue,
});
} catch (e: any) {
enqueueSnackbar(`Invalid path: ${e.message}`, { variant: "error" });
}
} else {
updateField({
path: row._rowy_ref.path,
fieldName: column.key,
value: deleteField(),
});
}
};
}, [column.key, row._rowy_ref.path, updateField]);
const defaultValue = get(row, column.key)?.path ?? "";
const { maxLength } = (column as any).config;
const [firebaseDb] = useAtom(firebaseDbAtom, projectScope);
return (
<TextField
defaultValue={defaultValue}
fullWidth
variant="standard"
inputProps={{
ref: inputRef,
maxLength: maxLength,
}}
sx={{
width: "100%",
height: "100%",
backgroundColor: "var(--cell-background-color)",
"& .MuiInputBase-root": {
height: "100%",
font: "inherit", // Prevent text jumping
letterSpacing: "inherit", // Prevent text jumping
p: 0,
},
"& .MuiInputBase-input": {
height: "100%",
font: "inherit", // Prevent text jumping
letterSpacing: "inherit", // Prevent text jumping
p: "var(--cell-padding)",
pb: 1 / 8,
},
"& textarea.MuiInputBase-input": {
lineHeight: (theme) => theme.typography.body2.lineHeight,
maxHeight: "100%",
boxSizing: "border-box",
py: 3 / 8,
},
}}
// InputProps={{
// endAdornment:
// (column as any).type === FieldType.percentage ? "%" : undefined,
// }}
autoFocus
onKeyDown={(e) => {
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
e.stopPropagation();
}
if (e.key === "Escape") {
(e.target as any).value = defaultValue;
<EditorCellTextField
{...(props as any)}
value={props.value?.path ?? ""}
onSubmit={(newValue) => {
if (newValue !== undefined && newValue !== "") {
try {
const refValue = doc(firebaseDb, newValue);
props.onSubmit(refValue);
} catch (e: any) {
enqueueSnackbar(`Invalid path: ${e.message}`, { variant: "error" });
}
} else {
props.onSubmit(deleteField() as any);
}
}}
/>

View File

@@ -1,24 +1,18 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withTableCell from "@src/components/Table/withTableCell";
import { Reference } from "@src/assets/icons";
//import InlineCell from "./InlineCell";
import BasicCell from "./BasicCell";
import DisplayCell from "./DisplayCell";
import EditorCell from "./EditorCell";
import { filterOperators } from "@src/components/fields/ShortText/Filter";
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
const EditorCell = lazy(
() => import("./EditorCell" /* webpackChunkName: "EditorCell-Reference" */)
);
const SideDrawerField = lazy(
() =>
import(
"./SideDrawerField" /* webpackChunkName: "SideDrawerField-Reference" */
)
);
// const Settings = lazy(
// () => import("./Settings" /* webpackChunkName: "Settings-Reference" */)
// );
export const config: IFieldConfig = {
type: FieldType.reference,
@@ -29,10 +23,10 @@ export const config: IFieldConfig = {
initializable: true,
icon: <Reference />,
description: "Firestore document reference",
TableCell: withBasicCell(BasicCell),
TableEditor: EditorCell,
TableCell: withTableCell(DisplayCell, EditorCell, "focus", {
disablePadding: true,
}),
SideDrawerField,
//settings: Settings,
filter: { operators: filterOperators },
};
export default config;

View File

@@ -1,61 +1,6 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import { useSaveOnUnmount } from "@src/hooks/useSaveOnUnmount";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
import { InputBase } from "@mui/material";
export default function ShortText({
column,
value,
onSubmit,
setFocusInsideCell,
}: IEditorCellProps<string>) {
const [localValue, setLocalValue] = useSaveOnUnmount(value, onSubmit);
const maxLength = column.config?.maxLength;
return (
<InputBase
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
fullWidth
inputProps={{ maxLength }}
sx={{
width: "100%",
height: "calc(100% - 1px)",
marginTop: "1px",
paddingBottom: "1px",
backgroundColor: "var(--cell-background-color)",
outline: "inherit",
outlineOffset: "inherit",
font: "inherit", // Prevent text jumping
letterSpacing: "inherit", // Prevent text jumping
"& .MuiInputBase-input": { p: "var(--cell-padding)" },
"& textarea.MuiInputBase-input": {
lineHeight: (theme) => theme.typography.body2.lineHeight,
maxHeight: "100%",
boxSizing: "border-box",
py: 3 / 8,
},
}}
autoFocus
onKeyDown={(e) => {
if (e.key === "ArrowLeft" || e.key === "ArrowRight") {
e.stopPropagation();
}
if (e.key === "Escape") {
// Escape removes focus inside cell, this runs before save on unmount
setLocalValue(value);
}
if (e.key === "Enter") {
// Removes focus from inside cell, triggering save on unmount
setFocusInsideCell(false);
}
}}
onClick={(e) => e.stopPropagation()}
onDoubleClick={(e) => e.stopPropagation()}
/>
);
export default function ShortText(props: IEditorCellProps<string>) {
return <EditorCellTextField {...props} />;
}

View File

@@ -1,9 +1,9 @@
import { IBasicCellProps } from "@src/components/fields/types";
import { IDisplayCellProps } from "@src/components/fields/types";
import { Stack, IconButton } from "@mui/material";
import LaunchIcon from "@mui/icons-material/Launch";
export default function Url({ value }: IBasicCellProps) {
export default function Url({ value }: IDisplayCellProps) {
if (!value || typeof value !== "string") return null;
const href = value.includes("http") ? value : `https://${value}`;
@@ -13,8 +13,7 @@ export default function Url({ value }: IBasicCellProps) {
direction="row"
alignItems="center"
justifyContent="space-between"
className="cell-collapse-padding"
sx={{ p: "var(--cell-padding)", pr: 0.5 }}
sx={{ p: "var(--cell-padding)", pr: 0.5, width: "100%" }}
>
<div style={{ flexGrow: 1, overflow: "hidden" }}>{value}</div>

View File

@@ -0,0 +1,6 @@
import type { IEditorCellProps } from "@src/components/fields/types";
import EditorCellTextField from "@src/components/Table/EditorCellTextField";
export default function Url(props: IEditorCellProps<string>) {
return <EditorCellTextField {...props} InputProps={{ type: "url" }} />;
}

View File

@@ -1,10 +1,10 @@
import { lazy } from "react";
import { IFieldConfig, FieldType } from "@src/components/fields/types";
import withBasicCell from "@src/components/fields/_withTableCell/withBasicCell";
import withTableCell from "@src/components/Table/withTableCell";
import UrlIcon from "@mui/icons-material/Link";
import TableCell from "./TableCell";
import TextEditor from "@src/components/Table/editors/TextEditor";
import DisplayCell from "./DisplayCell";
import EditorCell from "./EditorCell";
import { filterOperators } from "@src/components/fields/ShortText/Filter";
import BasicContextMenuActions from "@src/components/fields/_BasicCell/BasicCellContextMenuActions";
@@ -23,8 +23,9 @@ export const config: IFieldConfig = {
icon: <UrlIcon />,
description: "Web address. Not validated.",
contextMenuActions: BasicContextMenuActions,
TableCell: withBasicCell(TableCell),
TableEditor: TextEditor,
TableCell: withTableCell(DisplayCell, EditorCell, "focus", {
disablePadding: true,
}),
SideDrawerField,
filter: {
operators: filterOperators,