add basic Checkbox field config

This commit is contained in:
Sidney Alcantara
2020-11-23 12:23:04 +11:00
parent e9795a46c0
commit 1d10e082ca
12 changed files with 391 additions and 18 deletions

View File

@@ -25,8 +25,8 @@
- Sort and filter by row values
- Resize and rename columns
- 27 different column types.
[Read more](https://github.com/AntlerVC/firetable/wiki/Column-Types)
- 27 different field types.
[Read more](https://github.com/AntlerVC/firetable/wiki/Field-Types)
- Basic types: Short Text, Long Text, Email, Phone, URL…
- Custom UI pickers: Date, Checkbox, Single Select, Multi Select…

View File

@@ -96,6 +96,7 @@
"@types/use-persisted-state": "^0.3.0",
"firebase-tools": "^8.12.1",
"husky": "^4.2.5",
"monaco-editor": "^0.21.2",
"playwright": "^1.5.2",
"prettier": "^2.1.1",
"pretty-quick": "^3.0.0"

View File

@@ -151,22 +151,16 @@ export default function SideDrawer() {
>
<ErrorBoundary>
<div className={classes.drawerContents}>
{open && fields && urlDocState.doc ? (
{open && fields && (urlDocState.doc || cell) && (
<Form
key={urlDocState.path}
fields={fields}
values={urlDocState.doc ?? {}}
values={
urlDocState.doc ?? cell?.row
? tableState?.rows[cell!.row] ?? {}
: {}
}
/>
) : (
open &&
fields &&
cell && (
<Form
key={cell.row}
fields={fields}
values={tableState?.rows[cell.row] ?? {}}
/>
)
)}
</div>
</ErrorBoundary>

View File

@@ -0,0 +1,6 @@
import React from "react";
import { IBasicCellProps } from "../types";
export default function BasicCell({ name }: IBasicCellProps) {
return <>{name}</>;
}

View File

@@ -0,0 +1,76 @@
import React from "react";
import { Controller } from "react-hook-form";
import { ISideDrawerFieldProps } from "../types";
import {
makeStyles,
createStyles,
ButtonBase,
FormControlLabel,
Switch,
} from "@material-ui/core";
const useStyles = makeStyles((theme) =>
createStyles({
buttonBase: {
borderRadius: theme.shape.borderRadius,
backgroundColor:
theme.palette.type === "light"
? "rgba(0, 0, 0, 0.09)"
: "rgba(255, 255, 255, 0.09)",
padding: theme.spacing(9 / 8, 1, 9 / 8, 1.5),
width: "100%",
display: "flex",
textAlign: "left",
},
formControlLabel: {
margin: 0,
width: "100%",
display: "flex",
},
label: {
flexGrow: 1,
whiteSpace: "normal",
},
})
);
export default function Checkbox({ column, control }: ISideDrawerFieldProps) {
const classes = useStyles();
return (
<Controller
control={control}
name={name}
render={({ onChange, onBlur, value }) => {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
onChange(event.target.checked);
};
const handleClick = () => onChange(!value);
return (
<ButtonBase className={classes.buttonBase} onClick={handleClick}>
<FormControlLabel
control={
<Switch
color="secondary"
checked={value}
onChange={handleChange}
onBlur={onBlur}
disabled={column.editable === false}
/>
}
label={column.name}
labelPlacement="start"
classes={{ root: classes.formControlLabel, label: classes.label }}
/>
</ButtonBase>
);
}}
/>
);
}

View File

@@ -0,0 +1,88 @@
import React from "react";
import { ICustomCellProps } from "../types";
import _get from "lodash/get";
import {
makeStyles,
createStyles,
FormControlLabel,
Switch,
} from "@material-ui/core";
import { green } from "@material-ui/core/colors";
import Confirmation from "components/Confirmation";
const useStyles = makeStyles((theme) =>
createStyles({
root: { paddingLeft: theme.spacing(1.5) },
label: {
font: "inherit",
letterSpacing: "inherit",
flexGrow: 1,
width: "calc(100% - 58px)",
overflowX: "hidden",
},
switchBase: {
"&$switchChecked": { color: green["A700"] },
"&$switchChecked + $switchTrack": { backgroundColor: green["A700"] },
},
switchChecked: {},
switchTrack: {},
})
);
const replacer = (data: any) => (m: string, key: string) => {
const objKey = key.split(":")[0];
const defaultValue = key.split(":")[1] || "";
return _get(data, objKey, defaultValue);
};
export default function Checkbox({
row,
column,
value,
onSubmit,
}: ICustomCellProps) {
const classes = useStyles();
let component = (
<Switch
checked={!!value}
onChange={() => onSubmit(!value)}
disabled={!column.editable}
classes={{
switchBase: classes.switchBase,
checked: classes.switchChecked,
track: classes.switchTrack,
}}
/>
);
if ((column as any)?.config?.confirmation)
component = (
<Confirmation
message={{
title: (column as any).config.confirmation.title,
body: (column as any).config.confirmation.body.replace(
/\{\{(.*?)\}\}/g,
replacer(row)
),
}}
functionName="onChange"
>
{component}
</Confirmation>
);
return (
<FormControlLabel
control={component}
label={column.name}
labelPlacement="start"
className="cell-collapse-padding"
classes={classes}
/>
);
}

View File

@@ -0,0 +1,22 @@
import React from "react";
import { IFieldConfig, FieldType } from "../types";
import withCustomCell from "../withCustomCell";
import CheckboxIcon from "@material-ui/icons/CheckBox";
import TableCell from "./TableCell";
import BasicCell from "./BasicCell";
import NullEditor from "components/Table/editors/NullEditor";
import SideDrawerField from "./SideDrawerField";
export const config: IFieldConfig = {
type: FieldType.checkbox,
name: "Checkbox",
dataType: "boolean",
initialValue: false,
icon: <CheckboxIcon />,
description: "Either checked or unchecked. Unchecked by default.",
TableCell: withCustomCell(TableCell, BasicCell),
TableEditor: NullEditor,
SideDrawerField,
};
export default config;

View File

@@ -0,0 +1,70 @@
import _find from "lodash/find";
import { IFieldConfig } from "./types";
// Import field configs
import Checkbox from "./Checkbox";
// Export field configs in order for FieldsDropdown
export const FIELDS: IFieldConfig[] = [Checkbox];
// Define field type strings used in Firetable column config
export enum FieldType {
// TEXT
shortText = "SIMPLE_TEXT",
longText = "LONG_TEXT",
email = "EMAIL",
phone = "PHONE_NUMBER",
url = "URL",
// NUMERIC
checkbox = "CHECK_BOX",
number = "NUMBER",
percentage = "PERCENTAGE",
rating = "RATING",
slider = "SLIDER",
color = "COLOR",
// DATE & TIME
date = "DATE",
dateTime = "DATE_TIME",
duration = "DURATION",
// FILE
image = "IMAGE",
file = "FILE",
// SELECT
singleSelect = "SINGLE_SELECT",
multiSelect = "MULTI_SELECT",
// CONNECTION
subTable = "SUB_TABLE",
connectTable = "DOCUMENT_SELECT",
connectService = "SERVICE_SELECT",
// CODE
json = "JSON",
code = "CODE",
richText = "RICH_TEXT",
// CLOUD FUNCTION
action = "ACTION",
derivative = "DERIVATIVE",
aggregate = "AGGREGATE",
// FIRETABLE
user = "USER",
id = "ID",
last = "LAST",
}
/**
* Returns icon associated with field type
* @param fieldType
*/
export const getFieldIcon = (fieldType: FieldType) => {
const field = _find(FIELDS, { type: fieldType });
return field?.icon;
};
/**
* Returns `true` if it receives an existing fieldType
* @param fieldType
*/
export const isFieldType = (fieldType: any) => {
const fieldTypes = FIELDS.map((field) => field.type);
return fieldTypes.includes(fieldType);
};

40
www/src/components/fields/types.d.ts vendored Normal file
View File

@@ -0,0 +1,40 @@
import { FieldType } from ".";
export { FieldType };
import { ICustomCellProps, IBasicCellProps } from "./withCustomCell";
import { FormatterProps, EditorProps } from "react-data-grid";
import { Control } from "react-hook-form";
export interface IFieldConfig {
type: FieldType;
name: string;
dataType: string;
initialValue: any;
icon?: React.ReactNode;
description?: string;
setupGuideLink?: string;
TableCell: React.ComponentType<FormatterProps>;
TableEditor: React.ComponentType<EditorProps>;
SideDrawerField: React.ComponentType<ISideDrawerFieldProps>;
// settings
csvExport?: (value: any) => string;
csvImportParser?: (value: string) => any;
}
export interface ICustomCellProps extends FormatterProps<any> {
value: any;
onSubmit: (value: any) => void;
docRef: firebase.firestore.DocumentReference;
}
export interface IBasicCellProps {
value: any;
type: FieldType;
name: string;
}
export interface ISideDrawerFieldProps {
column: FormatterProps<any>["column"];
control: Control;
docRef: firebase.firestore.DocumentReference;
}

View File

@@ -0,0 +1,71 @@
import React, { Suspense, useState, useEffect } from "react";
import { FormatterProps } from "react-data-grid";
import { ICustomCellProps, IBasicCellProps } from "./types";
import ErrorBoundary from "components/ErrorBoundary";
import { useFiretableContext } from "contexts/FiretableContext";
import { FieldType } from ".";
import { getCellValue } from "utils/fns";
/**
* HOC to wrap around custom cell formatters.
* Renders BasicCell while scrolling for better scroll performance.
* @param Cell The cell component to display
* @param BasicCell The lighter cell component to display while scrolling
* @param readOnly Prevent the formatter from updating the cell value
*/
export default function withCustomCell(
Cell: React.ComponentType<ICustomCellProps>,
BasicCell: React.ComponentType<IBasicCellProps>,
readOnly: boolean = false
) {
return function CustomCell(props: FormatterProps<any>) {
const { updateCell } = useFiretableContext();
// TODO: Investigate if this still needs to be a state
const value = getCellValue(props.row, props.column.key as string);
const [localValue, setLocalValue] = useState(value);
useEffect(() => {
setLocalValue(value);
}, [value]);
// Initially display BasicCell to improve scroll performance
const basicCell = (
<BasicCell
value={localValue}
name={(props.column as any).name}
type={(props.column as any).type as FieldType}
/>
);
const [component, setComponent] = useState(basicCell);
// Switch to heavy cell Component once scrolling has finished
useEffect(() => {
setTimeout(() => {
setComponent(
<ErrorBoundary fullScreen={false} basic wrap="nowrap">
<Suspense fallback={basicCell}>
<Cell
{...props}
docRef={props.row.ref}
value={localValue}
onSubmit={handleSubmit}
/>
</Suspense>
</ErrorBoundary>
);
});
}, [localValue]);
const handleSubmit = (value: any) => {
if (updateCell && !readOnly) {
updateCell(props.row.ref, props.column.key as string, value);
setLocalValue(value);
}
};
return component;
};
}

View File

@@ -207,9 +207,9 @@ export const FIELD_TYPE_DESCRIPTIONS = {
"File uploaded to Firebase Storage. Supports any file type.",
[FieldType.singleSelect]:
"Dropdown selector with searchable options and radio button behaviour. Optionally allows users to input custom values. Max selection: 1 option.",
"Dropdown selector with searchable options and radio button behavior. Optionally allows users to input custom values. Max selection: 1 option.",
[FieldType.multiSelect]:
"Dropdown selector with searchable options and check box behaviour. Optionally allows users to input custom values. Max selection: all options.",
"Dropdown selector with searchable options and check box behavior. Optionally allows users to input custom values. Max selection: all options.",
[FieldType.subTable]:
"Creates a sub-table. Also displays number of rows inside the sub-table. Max sub-table levels: 100.",
@@ -232,8 +232,8 @@ export const FIELD_TYPE_DESCRIPTIONS = {
[FieldType.color]: "Visual color picker. Supports Hex, RGBA, HSLA.",
[FieldType.slider]: "Slider with adjustable range. Returns a numeric value.",
[FieldType.user]: "Used to display _ft_updatedBy field for editing history",
[FieldType.user]: "Displays the rows document ID",
[FieldType.user]: "Displays the _ft_updatedBy field for editing history.",
[FieldType.id]: "Displays the rows document ID. Cannot be sorted.",
[FieldType.last]: "Internally used to display last column with row actions.",
};

View File

@@ -10097,6 +10097,11 @@ mkdirp@^1.0.3:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
monaco-editor@^0.21.2:
version "0.21.2"
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.21.2.tgz#37054e63e480d51a2dd17d609dcfb192304d5605"
integrity sha512-jS51RLuzMaoJpYbu7F6TPuWpnWTLD4kjRW0+AZzcryvbxrTwhNy1KC9yboyKpgMTahpUbDUsuQULoo0GV1EPqg==
morgan@^1.10.0, morgan@^1.8.2:
version "1.10.0"
resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"