From 4bb8d572ee8d40914d00eaeabcabfd65631c09f2 Mon Sep 17 00:00:00 2001 From: Sidney Alcantara Date: Tue, 12 Jan 2021 12:14:48 +1100 Subject: [PATCH] add withPopoverCell, Color field --- www/src/components/Table/withPopoverCell.tsx | 122 ++++++++++++++++++ .../fields/Color/PopoverBasicCell.tsx | 62 +++++++++ .../components/fields/Color/PopoverCell.tsx | 12 ++ .../fields/Color/SideDrawerField.tsx | 99 ++++++++++++++ www/src/components/fields/Color/index.tsx | 30 +++++ www/src/components/fields/index.tsx | 3 +- www/src/components/fields/types.ts | 9 +- 7 files changed, 335 insertions(+), 2 deletions(-) create mode 100644 www/src/components/Table/withPopoverCell.tsx create mode 100644 www/src/components/fields/Color/PopoverBasicCell.tsx create mode 100644 www/src/components/fields/Color/PopoverCell.tsx create mode 100644 www/src/components/fields/Color/SideDrawerField.tsx create mode 100644 www/src/components/fields/Color/index.tsx diff --git a/www/src/components/Table/withPopoverCell.tsx b/www/src/components/Table/withPopoverCell.tsx new file mode 100644 index 00000000..0a3417cf --- /dev/null +++ b/www/src/components/Table/withPopoverCell.tsx @@ -0,0 +1,122 @@ +import React, { Suspense, useState, useEffect, useRef } from "react"; +import { FormatterProps } from "react-data-grid"; +import { + IPopoverCellProps, + IPopoverBasicCellProps, +} from "components/fields/types"; + +import { + makeStyles, + createStyles, + Popover, + PopoverProps, +} from "@material-ui/core"; + +import ErrorBoundary from "components/ErrorBoundary"; +import { useFiretableContext } from "contexts/FiretableContext"; + +import { FieldType } from "constants/fields"; +import { getCellValue } from "utils/fns"; + +const useStyles = makeStyles((theme) => + createStyles({ + transparentPaper: { + backgroundColor: "transparent", + boxShadow: "none", + }, + }) +); + +export interface IPopoverCellOptions extends Partial { + transparent?: boolean; + readOnly?: boolean; +} + +/** + * 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 withPopoverCell( + Cell: React.ComponentType, + BasicCell: React.ForwardRefExoticComponent< + IPopoverBasicCellProps & React.RefAttributes + >, + options?: IPopoverCellOptions +) { + return function PopoverCell(props: FormatterProps) { + const classes = useStyles(); + + const { transparent, readOnly, ...popoverProps } = options ?? {}; + + const [showComplexCell, setShowComplexCell] = useState(false); + const basicCellRef = useRef(null); + + // COPIED FROM withCustomCell + 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]); + + const basicCell = ( + + ); + + if (!showComplexCell) return basicCell; + + const parentRef = basicCellRef.current?.parentElement; + + const handleSubmit = (value: any) => { + if (updateCell && !options?.readOnly) { + updateCell(props.row.ref, props.column.key as string, value); + setLocalValue(value); + } + }; + + // Switch to heavy cell Component once basic cell is clicked + return ( + <> + {basicCell} + + + + setShowComplexCell(false)} + {...popoverProps} + PaperProps={{ + classes: { + root: transparent ? classes.transparentPaper : "", + }, + ...popoverProps?.PaperProps, + }} + > + + + + + + ); + }; +} diff --git a/www/src/components/fields/Color/PopoverBasicCell.tsx b/www/src/components/fields/Color/PopoverBasicCell.tsx new file mode 100644 index 00000000..131aaf3e --- /dev/null +++ b/www/src/components/fields/Color/PopoverBasicCell.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import { IPopoverBasicCellProps } from "../types"; + +import { makeStyles, createStyles, Grid, ButtonBase } from "@material-ui/core"; + +const useStyles = makeStyles((theme) => + createStyles({ + root: { + position: "absolute", + top: 0, + right: 0, + bottom: 0, + left: 0, + paddingLeft: theme.spacing(1.5), + + font: "inherit", + color: "inherit !important", + letterSpacing: "inherit", + textAlign: "inherit", + }, + + colorIndicator: { + width: 20, + height: 20, + + boxShadow: `0 0 0 1px ${theme.palette.text.disabled} inset`, + borderRadius: theme.shape.borderRadius / 2, + }, + }) +); + +export const Color = React.forwardRef(function Color( + { value, setShowComplexCell, disabled }: IPopoverBasicCellProps, + ref: React.Ref +) { + const classes = useStyles(); + + return ( + setShowComplexCell(true)} + ref={ref} + disabled={disabled} + > + +
+ + + + {value?.hex} + + + ); +}); +export default Color; diff --git a/www/src/components/fields/Color/PopoverCell.tsx b/www/src/components/fields/Color/PopoverCell.tsx new file mode 100644 index 00000000..cc5990c9 --- /dev/null +++ b/www/src/components/fields/Color/PopoverCell.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import { IPopoverCellProps } from "../types"; +import { ChromePicker } from "react-color"; +import _get from "lodash/get"; + +export default function Color({ value, onSubmit }: IPopoverCellProps) { + const handleChangeComplete = (color) => onSubmit(color); + + return ( + + ); +} diff --git a/www/src/components/fields/Color/SideDrawerField.tsx b/www/src/components/fields/Color/SideDrawerField.tsx new file mode 100644 index 00000000..ca1cb3fa --- /dev/null +++ b/www/src/components/fields/Color/SideDrawerField.tsx @@ -0,0 +1,99 @@ +import React, { useState } from "react"; +import { Controller } from "react-hook-form"; +import { ISideDrawerFieldProps } from "../types"; +import { ChromePicker } from "react-color"; + +import { + makeStyles, + createStyles, + Grid, + ButtonBase, + Typography, + Collapse, +} from "@material-ui/core"; + +import { useFieldStyles } from "components/SideDrawer/Form/utils"; + +const useStyles = makeStyles((theme) => + createStyles({ + root: { + height: 56, + cursor: "pointer", + textAlign: "left", + borderRadius: theme.shape.borderRadius, + + backgroundColor: + theme.palette.type === "light" + ? "rgba(0, 0, 0, 0.09)" + : "rgba(255, 255, 255, 0.09)", + margin: 0, + width: "100%", + padding: theme.spacing(0, 0.75), + }, + + colorIndicator: { + width: 20, + height: 20, + marginLeft: 2, + + boxShadow: `0 0 0 1px ${theme.palette.text.disabled} inset`, + borderRadius: theme.shape.borderRadius, + }, + }) +); + +export default function Color({ + column, + control, + disabled, +}: ISideDrawerFieldProps) { + const classes = useStyles(); + const fieldClasses = useFieldStyles(); + + const [showPicker, setShowPicker] = useState(false); + const toggleOpen = () => setShowPicker((s) => !s); + + return ( + ( + <> + { + toggleOpen(); + onBlur(); + }} + component={ButtonBase} + focusRipple + disabled={disabled} + > + +
+ + + + + {value?.hex ?? "Choose a color…"} + + + + + + + + + )} + /> + ); +} diff --git a/www/src/components/fields/Color/index.tsx b/www/src/components/fields/Color/index.tsx new file mode 100644 index 00000000..b785980b --- /dev/null +++ b/www/src/components/fields/Color/index.tsx @@ -0,0 +1,30 @@ +import React, { lazy } from "react"; +import { IFieldConfig, FieldType } from "components/fields/types"; +import withPopoverCell from "components/Table/withPopoverCell"; + +import ColorIcon from "@material-ui/icons/Colorize"; +import PopoverBasicCell from "./PopoverBasicCell"; +import NullEditor from "components/Table/editors/NullEditor"; + +const PopoverCell = lazy( + () => import("./PopoverCell" /* webpackChunkName: "PopoverCell-Color" */) +); +const SideDrawerField = lazy( + () => + import("./SideDrawerField" /* webpackChunkName: "SideDrawerField-Color" */) +); + +export const config: IFieldConfig = { + type: FieldType.color, + name: "Color", + dataType: "Record", + initialValue: undefined, + icon: , + description: "Visual color picker. Supports Hex, RGBA, HSLA.", + TableCell: withPopoverCell(PopoverCell, PopoverBasicCell, { + anchorOrigin: { horizontal: "left", vertical: "bottom" }, + }), + TableEditor: NullEditor, + SideDrawerField, +}; +export default config; diff --git a/www/src/components/fields/index.tsx b/www/src/components/fields/index.tsx index 8619e2b5..59610a11 100644 --- a/www/src/components/fields/index.tsx +++ b/www/src/components/fields/index.tsx @@ -15,6 +15,7 @@ import Number_ from "./Number"; import Percentage from "./Percentage"; import Rating from "./Rating"; import Slider from "./Slider"; +import Color from "./Color"; import Date_ from "./Date"; import DateTime from "./DateTime"; import Duration from "./Duration"; @@ -44,7 +45,7 @@ export const FIELDS: IFieldConfig[] = [ Percentage, Rating, Slider, - // TODO: color, + Color, // DATE & TIME Date_, DateTime, diff --git a/www/src/components/fields/types.ts b/www/src/components/fields/types.ts index 52b69657..3a341194 100644 --- a/www/src/components/fields/types.ts +++ b/www/src/components/fields/types.ts @@ -28,13 +28,20 @@ export interface ICustomCellProps extends FormatterProps { docRef: firebase.firestore.DocumentReference; disabled: boolean; } - export interface IBasicCellProps { value: any; type: FieldType; name: string; } +export interface IPopoverCellProps extends ICustomCellProps { + setShowComplexCell: React.Dispatch>; +} +export interface IPopoverBasicCellProps extends IBasicCellProps { + setShowComplexCell: React.Dispatch>; + disabled: boolean; +} + export interface ISideDrawerFieldProps { column: FormatterProps["column"] & { config?: Record }; control: Control;