UNPKG

@react-querybuilder/material

Version:

Custom MUI (Material Design) components for react-querybuilder

559 lines (558 loc) 18.4 kB
import * as React from "react"; import { createContext, forwardRef, useContext, useMemo } from "react"; import { ActionElement, DragHandle, NotToggle, ShiftActions, ValueEditor, ValueSelector, defaultTranslations, getCompatContextProvider, getFirstOption, isOptionGroupArray, parseNumber, useValueEditor, useValueSelector } from "react-querybuilder"; import CloseIcon from "@mui/icons-material/Close"; import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import DragIndicator from "@mui/icons-material/DragIndicator"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; import LockIcon from "@mui/icons-material/Lock"; import LockOpenIcon from "@mui/icons-material/LockOpen"; import Button from "@mui/material/Button"; import Checkbox from "@mui/material/Checkbox"; import FormControl from "@mui/material/FormControl"; import FormControlLabel from "@mui/material/FormControlLabel"; import InputLabel from "@mui/material/InputLabel"; import ListSubheader from "@mui/material/ListSubheader"; import MenuItem from "@mui/material/MenuItem"; import Radio from "@mui/material/Radio"; import RadioGroup from "@mui/material/RadioGroup"; import Select from "@mui/material/Select"; import Switch from "@mui/material/Switch"; import TextareaAutosize from "@mui/material/TextareaAutosize"; import TextField from "@mui/material/TextField"; //#region src/RQBMaterialContext.ts /** * @group Components */ const RQBMaterialContext = createContext(null); //#endregion //#region src/MaterialActionElement.tsx /** * @group Components */ const MaterialActionElement = ({ className, handleOnClick, label, title, disabled, disabledTranslation, testID, path, level, rules, context, validation, ruleOrGroup, schema, muiComponents: muiComponentsProp, ...otherProps }) => { const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp; const key = muiComponents ? "mui" : "no-mui"; if (!muiComponents) { const AE = ActionElement; return /* @__PURE__ */ React.createElement(AE, { key, className, handleOnClick, label, title, disabled, disabledTranslation, testID, path, level, rules, context, validation, ruleOrGroup, schema }); } const { Button } = muiComponents; return /* @__PURE__ */ React.createElement(Button, { key, variant: "contained", color: "secondary", className, title: disabledTranslation && disabled ? disabledTranslation.title : title, size: "small", disabled: disabled && !disabledTranslation, onClick: (e) => handleOnClick(e), ...otherProps }, disabledTranslation && disabled ? disabledTranslation.label : label); }; //#endregion //#region src/MaterialDragHandle.tsx /** * @group Components */ const MaterialDragHandle = forwardRef(({ className, title, path, level, testID, label, disabled, context, validation, schema, ruleOrGroup, muiComponents: muiComponentsProp, ...otherProps }, dragRef) => { const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp; const key = muiComponents ? "mui" : "no-mui"; if (!muiComponents) return /* @__PURE__ */ React.createElement(DragHandle, { key, path, level, className, title, testID, label, disabled, context, validation, schema, ruleOrGroup }); const { DragIndicator } = muiComponents; return /* @__PURE__ */ React.createElement("span", { key, ref: dragRef, className, title }, /* @__PURE__ */ React.createElement(DragIndicator, otherProps)); }); //#endregion //#region src/MaterialNotToggle.tsx /** * @group Components */ const MaterialNotToggle = ({ className, handleOnChange, label, checked, title, disabled, level, path, context, validation, testID, schema, ruleGroup, muiComponents: muiComponentsProp, ...otherProps }) => { const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp; const { FormControlLabel, Switch } = muiComponents ?? {}; const switchControl = useMemo(() => Switch && /* @__PURE__ */ React.createElement(Switch, { checked: !!checked, onChange: (e) => handleOnChange(e.target.checked), ...otherProps }), [ checked, handleOnChange, otherProps, Switch ]); const key = muiComponents ? "mui" : "no-mui"; if (!muiComponents) return /* @__PURE__ */ React.createElement(NotToggle, { key, className, handleOnChange, label, checked, title, disabled, path, level, context, validation, testID, schema, ruleGroup }); return /* @__PURE__ */ React.createElement(FormControlLabel, { key, className, title, disabled, control: switchControl, label: label ?? "" }); }; //#endregion //#region src/MaterialShiftActions.tsx /** * @group Components */ const MaterialShiftActions = ({ path, shiftUp, shiftDown, shiftUpDisabled, shiftDownDisabled, disabled, className, labels, titles, testID, muiComponents: muiComponentsProp, ...otherProps }) => { const muiComponents = React.useContext(RQBMaterialContext) ?? muiComponentsProp; const key = muiComponents ? "mui" : "no-mui"; if (!muiComponents) return /* @__PURE__ */ React.createElement(ShiftActions, { key, path, disabled, className, labels, titles, testID, shiftUp, shiftDown, shiftUpDisabled, shiftDownDisabled, ...otherProps }); const { Button } = muiComponents; return /* @__PURE__ */ React.createElement("div", { key, "data-testid": testID, className }, /* @__PURE__ */ React.createElement(Button, { sx: { boxShadow: "none" }, variant: "contained", color: "secondary", className, title: titles?.shiftUp, size: "small", disabled: disabled || shiftUpDisabled, onClick: shiftUp }, labels?.shiftUp), /* @__PURE__ */ React.createElement(Button, { sx: { boxShadow: "none" }, variant: "contained", color: "secondary", className, title: titles?.shiftDown, size: "small", disabled: disabled || shiftDownDisabled, onClick: shiftDown }, labels?.shiftDown)); }; //#endregion //#region src/MaterialValueEditor.tsx /** * @group Components */ const MaterialValueEditor = (props) => { const { muiComponents: muiComponentsProp, ...propsForValueEditor } = props; const { field: _f, fieldData, operator, value, handleOnChange, title, className, type, inputType, path, level, values = [], listsAsArrays, separator, valueSource: _vs, disabled, testID, selectorComponent: SelectorComponent = props.schema.controls.valueSelector, showInputLabels: silProp, extraProps, parseNumbers: _parseNumbers, ...propsForValueSelector } = propsForValueEditor; const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp; const { valueAsArray, multiValueHandler, bigIntValueHandler, parseNumberMethod, valueListItemClassName, inputTypeCoerced } = useValueEditor(propsForValueEditor); const masterKey = muiComponents ? "mui" : "no-mui"; const { Checkbox, FormControl, FormControlLabel, Radio, RadioGroup, Switch, TextareaAutosize, TextField, showInputLabels: silCtx } = useMemo(() => muiComponents ?? {}, [muiComponents]); if (!muiComponents) return /* @__PURE__ */ React.createElement(ValueEditor, { skipHook: true, key: masterKey, ...propsForValueEditor }); if (operator === "null" || operator === "notNull") return null; const placeHolderText = fieldData?.placeholder ?? ""; const showInputLabels = silProp || silCtx; if ((operator === "between" || operator === "notBetween") && (type === "select" || type === "text")) { const editors = ["From", "To"].map((key, i) => { if (type === "text") return /* @__PURE__ */ React.createElement(TextField, { key, variant: "standard", type: inputTypeCoerced, className: valueListItemClassName, placeholder: placeHolderText, value: valueAsArray[i] ?? "", disabled, label: showInputLabels ? key : void 0, onChange: (e) => multiValueHandler(e.target.value, i), ...extraProps }); return /* @__PURE__ */ React.createElement(SelectorComponent, { key, ...propsForValueSelector, title: showInputLabels ? key : void 0, path, level, className: valueListItemClassName, handleOnChange: (v) => multiValueHandler(v, i), muiComponents, disabled, value: valueAsArray[i] ?? getFirstOption(values), options: values, listsAsArrays }); }); return /* @__PURE__ */ React.createElement(FormControl, { key: masterKey, "data-testid": testID, className, title, disabled }, editors[0], separator, editors[1]); } switch (type) { case "select": case "multiselect": return /* @__PURE__ */ React.createElement(SelectorComponent, { key: masterKey, ...propsForValueSelector, muiComponents, path, level, className, handleOnChange, options: values, value, disabled, title, multiple: type === "multiselect", listsAsArrays }); case "textarea": return /* @__PURE__ */ React.createElement(TextareaAutosize, { key: masterKey, value, title, disabled, className, placeholder: placeHolderText, onChange: (e) => handleOnChange(e.target.value), ...extraProps }); case "switch": return /* @__PURE__ */ React.createElement(Switch, { key: masterKey, checked: !!value, title, disabled, className, onChange: (e) => handleOnChange(e.target.checked), ...extraProps }); case "checkbox": return /* @__PURE__ */ React.createElement(Checkbox, { key: masterKey, className, title, onChange: (e) => handleOnChange(e.target.checked), checked: !!value, disabled, ...extraProps }); case "radio": return /* @__PURE__ */ React.createElement(FormControl, { key: masterKey, className, title, component: "fieldset", disabled, ...extraProps }, /* @__PURE__ */ React.createElement(RadioGroup, { value, onChange: (e) => handleOnChange(e.target.value) }, values.map((v) => /* @__PURE__ */ React.createElement(FormControlLabel, { key: v.name, disabled, value: v.name, control: /* @__PURE__ */ React.createElement(Radio, null), name: v.name, label: v.label })))); } /** * TODO: Provide either (1) examples or (2) alternate exports that support `inputType` * "date", "datetime-local", and "time", with components from `@mui/x-date-pickers` * (`<DatePicker />`, `<DateTimePicker />`, and `<TimePicker />`, respecitively). */ if (inputType === "bigint") return /* @__PURE__ */ React.createElement(TextField, { key: masterKey, variant: "standard", "data-testid": testID, type: inputTypeCoerced, placeholder: placeHolderText, value: `${value}`, title, className, disabled, label: showInputLabels ? title : void 0, onChange: (e) => bigIntValueHandler(e.target.value), ...extraProps }); return /* @__PURE__ */ React.createElement(TextField, { key: masterKey, variant: "standard", type: inputTypeCoerced, value, title, disabled, className, placeholder: placeHolderText, label: showInputLabels ? title : void 0, onChange: (e) => handleOnChange(parseNumber(e.target.value, { parseNumbers: parseNumberMethod })), ...extraProps }); }; //#endregion //#region src/utils.tsx // v8 ignore next const defaultToOptionsOptions = { ListSubheader: () => null, MenuItem: () => /* @__PURE__ */ React.createElement(React.Fragment, null) }; const toOptions = (arr = [], { ListSubheader, MenuItem } = defaultToOptionsOptions) => { if (isOptionGroupArray(arr)) { const optArray = []; for (const og of arr) optArray.push(/* @__PURE__ */ React.createElement(ListSubheader, { key: og.label }, og.label), ...og.options.map((opt) => /* @__PURE__ */ React.createElement(MenuItem, { key: opt.name, value: opt.name }, opt.label))); return optArray; } /* v8 ignore else -- @preserve */ if (Array.isArray(arr)) return arr.map((opt) => /* @__PURE__ */ React.createElement(MenuItem, { key: opt.name, value: opt.name }, opt.label)); /* v8 ignore next -- @preserve */ return null; }; //#endregion //#region src/MaterialValueSelector.tsx /** * @group Components */ const MaterialValueSelector = ({ className, handleOnChange, options, value, disabled, title, multiple, listsAsArrays, testID, rule, ruleGroup, rules, level, path, context, validation, operator, field, fieldData, schema, muiComponents: muiComponentsProp, showInputLabels: silProp, defaultValue: _defaultValue, ...otherProps }) => { const muiComponents = useContext(RQBMaterialContext) ?? muiComponentsProp; const { onChange, val } = useValueSelector({ handleOnChange, listsAsArrays, multiple, value }); const muiSelectChangeHandler = React.useCallback(({ target: { value: v } }) => { onChange(v); }, [onChange]); const key = muiComponents ? "mui" : "no-mui"; if (!muiComponents) { const VS = ValueSelector; return /* @__PURE__ */ React.createElement(VS, { key, className, handleOnChange, options, value, disabled, title, multiple, listsAsArrays, testID, rule, ruleGroup, rules, level, path, context, validation, operator, field, fieldData, schema }); } const { FormControl, InputLabel, ListSubheader, MenuItem, Select, showInputLabels: silCtx } = muiComponents; const showInputLabels = silProp || silCtx; return /* @__PURE__ */ React.createElement(FormControl, { key, variant: "standard", className, title, disabled }, showInputLabels && /* @__PURE__ */ React.createElement(InputLabel, null, title), /* @__PURE__ */ React.createElement(Select, { value: val, onChange: muiSelectChangeHandler, multiple, disabled, label: showInputLabels ? title : void 0, ...otherProps }, toOptions(options, { ListSubheader, MenuItem }))); }; //#endregion //#region src/translations.tsx const CloseIconWrapper = () => { const muiComponents = React.useContext(RQBMaterialContext); if (!muiComponents) return defaultTranslations.removeRule.label; const { CloseIcon } = muiComponents; return /* @__PURE__ */ React.createElement(CloseIcon, null); }; const ContentCopyIconWrapper = () => { const muiComponents = React.useContext(RQBMaterialContext); if (!muiComponents) return defaultTranslations.cloneRule.label; const { ContentCopyIcon } = muiComponents; return /* @__PURE__ */ React.createElement(ContentCopyIcon, null); }; const LockIconWrapper = () => { const muiComponents = React.useContext(RQBMaterialContext); if (!muiComponents) return defaultTranslations.lockRuleDisabled.label; const { LockIcon } = muiComponents; return /* @__PURE__ */ React.createElement(LockIcon, null); }; const LockOpenIconWrapper = () => { const muiComponents = React.useContext(RQBMaterialContext); if (!muiComponents) return defaultTranslations.lockRule.label; const { LockOpenIcon } = muiComponents; return /* @__PURE__ */ React.createElement(LockOpenIcon, null); }; const ShiftDownIconWrapper = () => { const muiComponents = React.useContext(RQBMaterialContext); if (!muiComponents) return defaultTranslations.shiftActionDown.label; const { KeyboardArrowDownIcon } = muiComponents; return /* @__PURE__ */ React.createElement(KeyboardArrowDownIcon, null); }; const ShiftUpIconWrapper = () => { const muiComponents = React.useContext(RQBMaterialContext); if (!muiComponents) return defaultTranslations.shiftActionUp.label; const { KeyboardArrowUpIcon } = muiComponents; return /* @__PURE__ */ React.createElement(KeyboardArrowUpIcon, null); }; const materialTranslations = { removeGroup: { label: /* @__PURE__ */ React.createElement(CloseIconWrapper, null) }, removeRule: { label: /* @__PURE__ */ React.createElement(CloseIconWrapper, null) }, cloneRule: { label: /* @__PURE__ */ React.createElement(ContentCopyIconWrapper, null) }, cloneRuleGroup: { label: /* @__PURE__ */ React.createElement(ContentCopyIconWrapper, null) }, lockGroup: { label: /* @__PURE__ */ React.createElement(LockOpenIconWrapper, null) }, lockRule: { label: /* @__PURE__ */ React.createElement(LockOpenIconWrapper, null) }, lockGroupDisabled: { label: /* @__PURE__ */ React.createElement(LockIconWrapper, null) }, lockRuleDisabled: { label: /* @__PURE__ */ React.createElement(LockIconWrapper, null) }, shiftActionDown: { label: /* @__PURE__ */ React.createElement(ShiftDownIconWrapper, null) }, shiftActionUp: { label: /* @__PURE__ */ React.createElement(ShiftUpIconWrapper, null) } }; //#endregion //#region src/useMuiComponents.ts const defaultMuiComponents = { DragIndicator, Button, Checkbox, CloseIcon, ContentCopyIcon, FormControl, FormControlLabel, InputLabel, KeyboardArrowDownIcon, KeyboardArrowUpIcon, ListSubheader, LockIcon, LockOpenIcon, MenuItem, Radio, RadioGroup, Select, Switch, TextareaAutosize, TextField }; /** * @group Hooks */ const useMuiComponents = (preloadedComponents) => { const muiComponentsFromContext = useContext(RQBMaterialContext); return useMemo(() => preloadedComponents && muiComponentsFromContext ? { ...defaultMuiComponents, ...muiComponentsFromContext, ...preloadedComponents } : preloadedComponents ? { ...defaultMuiComponents, ...preloadedComponents } : muiComponentsFromContext ? { ...defaultMuiComponents, ...muiComponentsFromContext } : defaultMuiComponents, [muiComponentsFromContext, preloadedComponents]); }; //#endregion //#region src/index.tsx /** * @group Props */ const materialControlElements = { actionElement: MaterialActionElement, dragHandle: MaterialDragHandle, notToggle: MaterialNotToggle, shiftActions: MaterialShiftActions, valueEditor: MaterialValueEditor, valueSelector: MaterialValueSelector }; const MaterialContextProvider = getCompatContextProvider({ controlElements: materialControlElements, translations: materialTranslations }); /** * @group Components */ const QueryBuilderMaterial = ({ muiComponents: muiComponentsProp, showInputLabels, ...props }) => { const muiComponents = useMuiComponents(muiComponentsProp); const ctxValue = useMemo(() => ({ ...muiComponents, ...muiComponentsProp, showInputLabels }), [ muiComponents, muiComponentsProp, showInputLabels ]); return /* @__PURE__ */ React.createElement(RQBMaterialContext.Provider, { value: ctxValue }, /* @__PURE__ */ React.createElement(MaterialContextProvider, props)); }; //#endregion export { MaterialActionElement, MaterialDragHandle, MaterialNotToggle, MaterialShiftActions, MaterialValueEditor, MaterialValueSelector, QueryBuilderMaterial, RQBMaterialContext, defaultMuiComponents, materialControlElements, materialTranslations, useMuiComponents }; //# sourceMappingURL=react-querybuilder_material.mjs.map