mirror of
https://github.com/rowyio/rowy.git
synced 2025-12-29 00:16:39 +01:00
add basic Checkbox field config
This commit is contained in:
@@ -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…
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
6
www/src/components/fields/Checkbox/BasicCell.tsx
Normal file
6
www/src/components/fields/Checkbox/BasicCell.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import React from "react";
|
||||
import { IBasicCellProps } from "../types";
|
||||
|
||||
export default function BasicCell({ name }: IBasicCellProps) {
|
||||
return <>{name}</>;
|
||||
}
|
||||
76
www/src/components/fields/Checkbox/SideDrawerField.tsx
Normal file
76
www/src/components/fields/Checkbox/SideDrawerField.tsx
Normal 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>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
88
www/src/components/fields/Checkbox/TableCell.tsx
Normal file
88
www/src/components/fields/Checkbox/TableCell.tsx
Normal 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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
22
www/src/components/fields/Checkbox/index.tsx
Normal file
22
www/src/components/fields/Checkbox/index.tsx
Normal 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;
|
||||
70
www/src/components/fields/index.tsx
Normal file
70
www/src/components/fields/index.tsx
Normal 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
40
www/src/components/fields/types.d.ts
vendored
Normal 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;
|
||||
}
|
||||
71
www/src/components/fields/withCustomCell.tsx
Normal file
71
www/src/components/fields/withCustomCell.tsx
Normal 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;
|
||||
};
|
||||
}
|
||||
@@ -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]: "Display’s the row’s document ID",
|
||||
[FieldType.user]: "Displays the _ft_updatedBy field for editing history.",
|
||||
[FieldType.id]: "Displays the row’s document ID. Cannot be sorted.",
|
||||
[FieldType.last]: "Internally used to display last column with row actions.",
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user