mirror of
https://github.com/rowyio/rowy.git
synced 2026-07-12 13:28:48 +02:00
add SingleSelect, MultiSelect
This commit is contained in:
@@ -64,6 +64,13 @@ export default function withPopoverCell(
|
||||
setLocalValue(value);
|
||||
}, [value]);
|
||||
|
||||
const handleSubmit = (value: any) => {
|
||||
if (updateCell && !options?.readOnly) {
|
||||
updateCell(props.row.ref, props.column.key as string, value);
|
||||
setLocalValue(value);
|
||||
}
|
||||
};
|
||||
|
||||
const basicCell = (
|
||||
<BasicCell
|
||||
value={localValue}
|
||||
@@ -72,6 +79,7 @@ export default function withPopoverCell(
|
||||
setShowComplexCell={setShowComplexCell}
|
||||
ref={basicCellRef}
|
||||
disabled={props.column.editable === false}
|
||||
onSubmit={handleSubmit}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -79,13 +87,6 @@ export default function withPopoverCell(
|
||||
|
||||
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 (
|
||||
<>
|
||||
@@ -104,6 +105,8 @@ export default function withPopoverCell(
|
||||
},
|
||||
...popoverProps?.PaperProps,
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onKeyDown={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Cell
|
||||
{...props}
|
||||
@@ -112,6 +115,7 @@ export default function withPopoverCell(
|
||||
onSubmit={handleSubmit}
|
||||
disabled={props.column.editable === false}
|
||||
setShowComplexCell={setShowComplexCell}
|
||||
parentRef={parentRef}
|
||||
/>
|
||||
</Popover>
|
||||
</Suspense>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import React from "react";
|
||||
import { Grid, Tooltip, Button } from "@material-ui/core";
|
||||
|
||||
export const ConvertStringToArray = ({ value, onSubmit }) => (
|
||||
<Grid container wrap="nowrap" alignItems="center">
|
||||
<Grid item xs style={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
{value}
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Tooltip title="It looks like this is a string. Click to convert to an array">
|
||||
<Button
|
||||
onClick={() => onSubmit([value])}
|
||||
style={{
|
||||
display: "flex",
|
||||
minWidth: 0,
|
||||
marginRight: -12,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
}}
|
||||
>
|
||||
Fix
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
89
www/src/components/fields/MultiSelect/PopoverBasicCell.tsx
Normal file
89
www/src/components/fields/MultiSelect/PopoverBasicCell.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import { IPopoverBasicCellProps } from "../types";
|
||||
|
||||
import { makeStyles, createStyles, ButtonBase, Grid } from "@material-ui/core";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
|
||||
import { sanitiseValue } from "./utils";
|
||||
import FormattedChip from "components/FormattedChip";
|
||||
import { ConvertStringToArray } from "./ConvertStringToArray";
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
height: "100%",
|
||||
padding: theme.spacing(0, 1, 0, 1.5),
|
||||
|
||||
font: "inherit",
|
||||
color: "inherit !important",
|
||||
letterSpacing: "inherit",
|
||||
textAlign: "inherit",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
|
||||
value: {
|
||||
flex: 1,
|
||||
maxWidth: `calc(100% - 24px + 4px)`,
|
||||
overflow: "hidden",
|
||||
marginRight: 0,
|
||||
},
|
||||
chip: {
|
||||
display: "flex",
|
||||
cursor: "inherit",
|
||||
},
|
||||
chipLabel: { whiteSpace: "nowrap" },
|
||||
|
||||
icon: {
|
||||
display: "block",
|
||||
color: theme.palette.action.active,
|
||||
},
|
||||
disabled: {
|
||||
color: theme.palette.action.disabled,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export const SingleSelect = React.forwardRef(function SingleSelect(
|
||||
{ value, setShowComplexCell, disabled, onSubmit }: IPopoverBasicCellProps,
|
||||
ref: React.Ref<any>
|
||||
) {
|
||||
const classes = useStyles();
|
||||
|
||||
if (typeof value === "string" && value !== "")
|
||||
return <ConvertStringToArray value={value} onSubmit={onSubmit} />;
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
className={clsx("cell-collapse-padding", classes.root)}
|
||||
onClick={() => setShowComplexCell(true)}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
>
|
||||
<Grid
|
||||
container
|
||||
wrap="nowrap"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
className={classes.value}
|
||||
>
|
||||
{sanitiseValue(value).map(
|
||||
(item) =>
|
||||
typeof item === "string" && (
|
||||
<Grid item key={item}>
|
||||
<FormattedChip
|
||||
label={item}
|
||||
classes={{ root: classes.chip, label: classes.chipLabel }}
|
||||
/>
|
||||
</Grid>
|
||||
)
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<ArrowDropDownIcon
|
||||
className={clsx(classes.icon, disabled && classes.disabled)}
|
||||
/>
|
||||
</ButtonBase>
|
||||
);
|
||||
});
|
||||
export default SingleSelect;
|
||||
43
www/src/components/fields/MultiSelect/PopoverCell.tsx
Normal file
43
www/src/components/fields/MultiSelect/PopoverCell.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import { IPopoverCellProps } from "../types";
|
||||
import _get from "lodash/get";
|
||||
|
||||
import MultiSelect_ from "@antlerengineering/multiselect";
|
||||
|
||||
import { sanitiseValue } from "./utils";
|
||||
|
||||
export default function SingleSelect({
|
||||
value,
|
||||
onSubmit,
|
||||
column,
|
||||
parentRef,
|
||||
setShowComplexCell,
|
||||
disabled,
|
||||
}: IPopoverCellProps) {
|
||||
const config = column.config ?? {};
|
||||
|
||||
return (
|
||||
<MultiSelect_
|
||||
value={sanitiseValue(value)}
|
||||
onChange={onSubmit}
|
||||
options={config.options ?? []}
|
||||
multiple
|
||||
freeText={config.freeText}
|
||||
disabled={disabled}
|
||||
label={column.name}
|
||||
labelPlural={column.name}
|
||||
TextFieldProps={{
|
||||
style: { display: "none" },
|
||||
SelectProps: {
|
||||
open: true,
|
||||
MenuProps: {
|
||||
anchorEl: parentRef,
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
transformOrigin: { vertical: "top", horizontal: "left" },
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClose={() => setShowComplexCell(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
76
www/src/components/fields/MultiSelect/SideDrawerField.tsx
Normal file
76
www/src/components/fields/MultiSelect/SideDrawerField.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import React from "react";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { ISideDrawerFieldProps } from "../types";
|
||||
|
||||
import { useTheme, Grid } from "@material-ui/core";
|
||||
import MultiSelect_ from "@antlerengineering/multiselect";
|
||||
import FormattedChip from "components/FormattedChip";
|
||||
|
||||
import { sanitiseValue } from "./utils";
|
||||
import { ConvertStringToArray } from "./ConvertStringToArray";
|
||||
|
||||
export default function SingleSelect({
|
||||
column,
|
||||
control,
|
||||
disabled,
|
||||
}: ISideDrawerFieldProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const config = column.config ?? {};
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={column.key}
|
||||
render={({ onChange, onBlur, value }) => {
|
||||
const handleDelete = (index: number) => () => {
|
||||
const newValues = [...value];
|
||||
newValues.splice(index, 1);
|
||||
onChange(newValues);
|
||||
};
|
||||
|
||||
if (typeof value === "string" && value !== "")
|
||||
return <ConvertStringToArray value={value} onSubmit={onChange} />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<MultiSelect_
|
||||
value={sanitiseValue(value)}
|
||||
onChange={onChange}
|
||||
options={config.options ?? []}
|
||||
multiple={true}
|
||||
freeText={config.freeText}
|
||||
disabled={disabled}
|
||||
TextFieldProps={{
|
||||
label: "",
|
||||
hiddenLabel: true,
|
||||
onBlur,
|
||||
}}
|
||||
/>
|
||||
|
||||
{value && Array.isArray(value) && (
|
||||
<Grid
|
||||
container
|
||||
spacing={1}
|
||||
style={{ marginTop: theme.spacing(1) }}
|
||||
>
|
||||
{value.map(
|
||||
(item, i) =>
|
||||
item?.length > 0 && (
|
||||
<Grid item key={item}>
|
||||
<FormattedChip
|
||||
size="medium"
|
||||
label={item}
|
||||
onDelete={disabled ? undefined : handleDelete(i)}
|
||||
/>
|
||||
</Grid>
|
||||
)
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
35
www/src/components/fields/MultiSelect/index.tsx
Normal file
35
www/src/components/fields/MultiSelect/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "components/fields/types";
|
||||
import withPopoverCell from "components/Table/withPopoverCell";
|
||||
|
||||
import MultiSelectIcon from "assets/icons/MultiSelect";
|
||||
import PopoverBasicCell from "./PopoverBasicCell";
|
||||
import NullEditor from "components/Table/editors/NullEditor";
|
||||
|
||||
const PopoverCell = lazy(
|
||||
() =>
|
||||
import("./PopoverCell" /* webpackChunkName: "PopoverCell-MultiSelect" */)
|
||||
);
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import(
|
||||
"./SideDrawerField" /* webpackChunkName: "SideDrawerField-MultiSelect" */
|
||||
)
|
||||
);
|
||||
|
||||
export const config: IFieldConfig = {
|
||||
type: FieldType.multiSelect,
|
||||
name: "MultiSelect",
|
||||
dataType: "string[]",
|
||||
initialValue: [],
|
||||
icon: <MultiSelectIcon />,
|
||||
description:
|
||||
"Dropdown selector with searchable options and check box behavior. Optionally allows users to input custom values. Max selection: all options.",
|
||||
TableCell: withPopoverCell(PopoverCell, PopoverBasicCell, {
|
||||
anchorOrigin: { horizontal: "left", vertical: "bottom" },
|
||||
transparent: true,
|
||||
}),
|
||||
TableEditor: NullEditor,
|
||||
SideDrawerField,
|
||||
};
|
||||
export default config;
|
||||
4
www/src/components/fields/MultiSelect/utils.ts
Normal file
4
www/src/components/fields/MultiSelect/utils.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export const sanitiseValue = (value: any) => {
|
||||
if (value === undefined || value === null || value === "") return [];
|
||||
else return value as string[];
|
||||
};
|
||||
61
www/src/components/fields/SingleSelect/PopoverBasicCell.tsx
Normal file
61
www/src/components/fields/SingleSelect/PopoverBasicCell.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from "react";
|
||||
import clsx from "clsx";
|
||||
import { IPopoverBasicCellProps } from "../types";
|
||||
|
||||
import { makeStyles, createStyles, ButtonBase } from "@material-ui/core";
|
||||
import ArrowDropDownIcon from "@material-ui/icons/ArrowDropDown";
|
||||
|
||||
import { sanitiseValue } from "./utils";
|
||||
|
||||
const useStyles = makeStyles((theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
height: "100%",
|
||||
padding: theme.spacing(0, 1, 0, 1.5),
|
||||
|
||||
font: "inherit",
|
||||
color: "inherit !important",
|
||||
letterSpacing: "inherit",
|
||||
textAlign: "inherit",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
|
||||
value: {
|
||||
flex: 1,
|
||||
maxWidth: `calc(100% - 24px)`,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
|
||||
icon: {
|
||||
display: "block",
|
||||
color: theme.palette.action.active,
|
||||
},
|
||||
disabled: {
|
||||
color: theme.palette.action.disabled,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
export const SingleSelect = React.forwardRef(function SingleSelect(
|
||||
{ value, setShowComplexCell, disabled }: IPopoverBasicCellProps,
|
||||
ref: React.Ref<any>
|
||||
) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<ButtonBase
|
||||
className={clsx("cell-collapse-padding", classes.root)}
|
||||
onClick={() => setShowComplexCell(true)}
|
||||
ref={ref}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className={classes.value}>{sanitiseValue(value)}</div>
|
||||
|
||||
<ArrowDropDownIcon
|
||||
className={clsx(classes.icon, disabled && classes.disabled)}
|
||||
/>
|
||||
</ButtonBase>
|
||||
);
|
||||
});
|
||||
export default SingleSelect;
|
||||
43
www/src/components/fields/SingleSelect/PopoverCell.tsx
Normal file
43
www/src/components/fields/SingleSelect/PopoverCell.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import { IPopoverCellProps } from "../types";
|
||||
import _get from "lodash/get";
|
||||
|
||||
import MultiSelect_ from "@antlerengineering/multiselect";
|
||||
|
||||
import { sanitiseValue } from "./utils";
|
||||
|
||||
export default function SingleSelect({
|
||||
value,
|
||||
onSubmit,
|
||||
column,
|
||||
parentRef,
|
||||
setShowComplexCell,
|
||||
disabled,
|
||||
}: IPopoverCellProps) {
|
||||
const config = column.config ?? {};
|
||||
|
||||
return (
|
||||
<MultiSelect_
|
||||
value={sanitiseValue(value)}
|
||||
onChange={onSubmit}
|
||||
options={config.options ?? []}
|
||||
multiple={false}
|
||||
freeText={config.freeText}
|
||||
disabled={disabled}
|
||||
label={column.name}
|
||||
labelPlural={column.name}
|
||||
TextFieldProps={{
|
||||
style: { display: "none" },
|
||||
SelectProps: {
|
||||
open: true,
|
||||
MenuProps: {
|
||||
anchorEl: parentRef,
|
||||
anchorOrigin: { vertical: "bottom", horizontal: "left" },
|
||||
transformOrigin: { vertical: "top", horizontal: "left" },
|
||||
},
|
||||
},
|
||||
}}
|
||||
onClose={() => setShowComplexCell(false)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
49
www/src/components/fields/SingleSelect/SideDrawerField.tsx
Normal file
49
www/src/components/fields/SingleSelect/SideDrawerField.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import { Controller } from "react-hook-form";
|
||||
import { ISideDrawerFieldProps } from "../types";
|
||||
|
||||
import { useTheme } from "@material-ui/core";
|
||||
import MultiSelect_ from "@antlerengineering/multiselect";
|
||||
import FormattedChip from "components/FormattedChip";
|
||||
|
||||
import { sanitiseValue } from "./utils";
|
||||
|
||||
export default function SingleSelect({
|
||||
column,
|
||||
control,
|
||||
disabled,
|
||||
}: ISideDrawerFieldProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
const config = column.config ?? {};
|
||||
|
||||
return (
|
||||
<Controller
|
||||
control={control}
|
||||
name={column.key}
|
||||
render={({ onChange, onBlur, value }) => (
|
||||
<>
|
||||
<MultiSelect_
|
||||
value={sanitiseValue(value)}
|
||||
onChange={onChange}
|
||||
options={config.options ?? []}
|
||||
multiple={false}
|
||||
freeText={config.freeText}
|
||||
disabled={disabled}
|
||||
TextFieldProps={{
|
||||
label: "",
|
||||
hiddenLabel: true,
|
||||
onBlur,
|
||||
}}
|
||||
/>
|
||||
|
||||
{value?.length > 0 && (
|
||||
<div style={{ marginTop: theme.spacing(1) }}>
|
||||
<FormattedChip size="medium" label={value} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
35
www/src/components/fields/SingleSelect/index.tsx
Normal file
35
www/src/components/fields/SingleSelect/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { lazy } from "react";
|
||||
import { IFieldConfig, FieldType } from "components/fields/types";
|
||||
import withPopoverCell from "components/Table/withPopoverCell";
|
||||
|
||||
import SingleSelectIcon from "@material-ui/icons/FormatListBulleted";
|
||||
import PopoverBasicCell from "./PopoverBasicCell";
|
||||
import NullEditor from "components/Table/editors/NullEditor";
|
||||
|
||||
const PopoverCell = lazy(
|
||||
() =>
|
||||
import("./PopoverCell" /* webpackChunkName: "PopoverCell-SingleSelect" */)
|
||||
);
|
||||
const SideDrawerField = lazy(
|
||||
() =>
|
||||
import(
|
||||
"./SideDrawerField" /* webpackChunkName: "SideDrawerField-SingleSelect" */
|
||||
)
|
||||
);
|
||||
|
||||
export const config: IFieldConfig = {
|
||||
type: FieldType.singleSelect,
|
||||
name: "SingleSelect",
|
||||
dataType: "string | null",
|
||||
initialValue: null,
|
||||
icon: <SingleSelectIcon />,
|
||||
description:
|
||||
"Dropdown selector with searchable options and radio button behavior. Optionally allows users to input custom values. Max selection: 1 option.",
|
||||
TableCell: withPopoverCell(PopoverCell, PopoverBasicCell, {
|
||||
anchorOrigin: { horizontal: "left", vertical: "bottom" },
|
||||
transparent: true,
|
||||
}),
|
||||
TableEditor: NullEditor,
|
||||
SideDrawerField,
|
||||
};
|
||||
export default config;
|
||||
5
www/src/components/fields/SingleSelect/utils.ts
Normal file
5
www/src/components/fields/SingleSelect/utils.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export const sanitiseValue = (value: any) => {
|
||||
if (value === undefined || value === null || value === "") return null;
|
||||
else if (Array.isArray(value)) return value[0] as string;
|
||||
else return value as string;
|
||||
};
|
||||
@@ -21,6 +21,8 @@ import DateTime from "./DateTime";
|
||||
import Duration from "./Duration";
|
||||
import Image_ from "./Image";
|
||||
import File_ from "./File";
|
||||
import SingleSelect from "./SingleSelect";
|
||||
import MultiSelect from "./MultiSelect";
|
||||
import SubTable from "./SubTable";
|
||||
import Json from "./Json";
|
||||
import Code from "./Code";
|
||||
@@ -54,8 +56,8 @@ export const FIELDS: IFieldConfig[] = [
|
||||
Image_,
|
||||
File_,
|
||||
// SELECT
|
||||
// TODO: singleSelect,
|
||||
// TODO: multiSelect,
|
||||
SingleSelect,
|
||||
MultiSelect,
|
||||
// CONNECTION
|
||||
SubTable,
|
||||
// TODO: connectTable,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FieldType } from "constants/fields";
|
||||
|
||||
import { FormatterProps, EditorProps } from "react-data-grid";
|
||||
import { Control } from "react-hook-form";
|
||||
import { PopoverProps } from "@material-ui/core";
|
||||
|
||||
export { FieldType };
|
||||
|
||||
@@ -36,10 +37,12 @@ export interface IBasicCellProps {
|
||||
|
||||
export interface IPopoverCellProps extends ICustomCellProps {
|
||||
setShowComplexCell: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
parentRef: PopoverProps["anchorEl"];
|
||||
}
|
||||
export interface IPopoverBasicCellProps extends IBasicCellProps {
|
||||
setShowComplexCell: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
disabled: boolean;
|
||||
onSubmit: (value: any) => void;
|
||||
}
|
||||
|
||||
export interface ISideDrawerFieldProps {
|
||||
|
||||
Reference in New Issue
Block a user