Skip to content

Commit

Permalink
fix: generic type the popup context, feat add default action to menu (#…
Browse files Browse the repository at this point in the history
…109)

* fix: type the popup context

* feat: add default handler to popup menu

* fix: make no-unused-vars an error, remove dev.eslint

* fix: renamed menu action to defaultAction

* lint

* Tidy up null checks and field types
  • Loading branch information
matttdawson authored Nov 13, 2022
1 parent 0125c5c commit d6073e4
Show file tree
Hide file tree
Showing 13 changed files with 92 additions and 153 deletions.
95 changes: 0 additions & 95 deletions .dev.eslintrc.js

This file was deleted.

2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ module.exports = {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-empty-function": ["warn", { allow: ["arrowFunctions"] }],
"@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], // prepend var with _ (e.g.. _myVar) to ignore this pattern
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], // prepend var with _ (e.g.. _myVar) to ignore this pattern
"@typescript-eslint/no-use-before-define": "off", // We will want to use before define to keep exports at the top
"@typescript-eslint/ban-ts-comment": "off", // We use explicit overrides
"@typescript-eslint/naming-convention": "off", // React's convention is to use CamelCase for component file names
Expand Down
4 changes: 2 additions & 2 deletions src/components/GridPopoverHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { GridContext } from "../contexts/GridContext";
import { GridBaseRow } from "./Grid";
import { ControlledMenu } from "../react-menu3";
import { GridPopoverContext } from "../contexts/GridPopoverContext";
import { useGridPopoverContext } from "../contexts/GridPopoverContext";
import { MenuCloseEvent } from "../react-menu3/types";

export interface GridPopoverHookProps<RowType> {
Expand All @@ -12,7 +12,7 @@ export interface GridPopoverHookProps<RowType> {

export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopoverHookProps<RowType>) => {
const { stopEditing } = useContext(GridContext);
const { anchorRef, saving, updateValue } = useContext(GridPopoverContext);
const { anchorRef, saving, updateValue } = useGridPopoverContext<RowType>();
const saveButtonRef = useRef<HTMLButtonElement>(null);
const [isOpen, setOpen] = useState(false);

Expand Down
35 changes: 20 additions & 15 deletions src/components/gridForm/GridFormDropDown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { delay } from "lodash-es";
import debounce from "debounce-promise";
import { CellEditorCommon } from "../GridCell";
import { useGridPopoverHook } from "../GridPopoverHook";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";
import { useGridPopoverContext } from "../../contexts/GridPopoverContext";

export interface GridPopoutEditDropDownSelectedItem<RowType, ValueType> {
// Note the row that was clicked on will be first
Expand Down Expand Up @@ -44,6 +44,7 @@ export interface GridFormPopoutDropDownProps<RowType extends GridBaseRow, ValueT
| "GridPopoverEditDropDown-containerUnlimited"
| string
| undefined;
// local means the filter won't change if it's reloaded, reload means it does change
filtered?: "local" | "reload";
filterPlaceholder?: string;
onSelectedItem?: (props: GridPopoutEditDropDownSelectedItem<RowType, ValueType>) => Promise<void>;
Expand All @@ -54,10 +55,14 @@ export interface GridFormPopoutDropDownProps<RowType extends GridBaseRow, ValueT
optionsRequestCancel?: () => void;
}

const fieldToString = (field: any) => {
return typeof field == "symbol" ? field.toString() : `${field}`;
};

export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
props: GridFormPopoutDropDownProps<RowType, ValueType>,
) => {
const { selectedRows, field, updateValue } = useContext(GridPopoverContext);
const { selectedRows, field, updateValue } = useGridPopoverContext<RowType>();
const { stopEditing } = useContext(GridContext);

const [filter, setFilter] = useState("");
Expand Down Expand Up @@ -208,7 +213,9 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
)}
<ComponentLoadingWrapper loading={!options} className={"GridFormDropDown-options"}>
<>
{options && options.length == filteredValues?.length && <MenuItem key={`${field}-empty`}>[Empty]</MenuItem>}
{options && options.length == filteredValues?.length && (
<MenuItem key={`${fieldToString(field)}-empty`}>[Empty]</MenuItem>
)}
{options?.map((item: FinalSelectOption<ValueType | string>, index) =>
item.value === MenuSeparatorString ? (
<MenuDivider key={`$$divider_${index}`} />
Expand All @@ -218,7 +225,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
<div key={`menu-wrapper-${index}`}>
{!item.subComponent ? (
<MenuItem
key={`${field}-${index}`}
key={`${fieldToString(field)}-${index}`}
disabled={!!item.disabled}
title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
value={item.value}
Expand All @@ -229,12 +236,11 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
{item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
</MenuItem>
) : (
<FocusableItem className={"LuiDeprecatedForms"} key={`${field}-${index}_subcomponent`}>
<FocusableItem className={"LuiDeprecatedForms"} key={`${fieldToString(field)}-${index}_subcomponent`}>
{(ref: any) =>
item.subComponent &&
item.subComponent(
{
setValue: (value: any) => {
item.subComponent && (
<item.subComponent
setValue={(value: any) => {
const localSubComponentValues = [...subComponentValues];
const subComponentValueIndex = localSubComponentValues.findIndex(
({ optionValue }) => optionValue === item.value,
Expand All @@ -248,8 +254,8 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
});
}
setSubComponentValues(localSubComponentValues);
},
keyDown: (key: string, event: KeyboardEvent<HTMLInputElement>) => {
}}
keyDown={(key: string, event: KeyboardEvent<HTMLInputElement>) => {
const subComponentItem = subComponentValues.find(
({ optionValue }) => optionValue === item.value,
);
Expand All @@ -265,10 +271,9 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
});
}
return false;
},
key: `${field}-${index}_subcomponent_inner`,
},
ref,
}}
key={`${fieldToString(field)}-${index}_subcomponent_inner`}
/>
)
}
</FocusableItem>
Expand Down
10 changes: 6 additions & 4 deletions src/components/gridForm/GridFormEditBearing.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import "../../styles/GridFormEditBearing.scss";

import { useCallback, useContext, useState } from "react";
import { useCallback, useState } from "react";
import { GridBaseRow } from "../Grid";
import { TextInputFormatted } from "../../lui/TextInputFormatted";
import { bearingNumberParser, bearingStringValidator, convertDDToDMS } from "../../utils/bearing";
import { useGridPopoverHook } from "../GridPopoverHook";
import { CellEditorCommon } from "../GridCell";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";
import { useGridPopoverContext } from "../../contexts/GridPopoverContext";

export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends CellEditorCommon {
placeHolder?: string;
Expand All @@ -15,7 +15,7 @@ export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends C
}

export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridFormEditBearingProps<RowType>) => {
const { field, value: initialValue } = useContext(GridPopoverContext);
const { field, value: initialValue } = useGridPopoverContext<RowType>();

const [value, setValue] = useState<string>(`${initialValue ?? ""}`);

Expand All @@ -34,7 +34,9 @@ export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridForm
if (field == null) {
console.error("field is not defined in ColDef");
} else {
selectedRows.forEach((row) => ((row as Record<string, any>)[field] = parsedValue));
selectedRows.forEach((row) => {
row[field] = parsedValue as any;
});
}
}
return true;
Expand Down
6 changes: 3 additions & 3 deletions src/components/gridForm/GridFormMessage.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import clsx from "clsx";
import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
import { GridBaseRow } from "../Grid";
import { useGridPopoverHook } from "../GridPopoverHook";
import { CellEditorCommon } from "../GridCell";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";
import { useGridPopoverContext } from "../../contexts/GridPopoverContext";

export interface GridFormMessageProps<RowType extends GridBaseRow> extends CellEditorCommon {
message: (selectedRows: RowType[]) => Promise<string | JSX.Element> | string | JSX.Element;
}

export const GridFormMessage = <RowType extends GridBaseRow>(props: GridFormMessageProps<RowType>) => {
const { selectedRows } = useContext(GridPopoverContext);
const { selectedRows } = useGridPopoverContext<RowType>();

const [message, setMessage] = useState<string | JSX.Element | null>(null);
const { popoverWrapper } = useGridPopoverHook({ className: props.className });
Expand Down
6 changes: 3 additions & 3 deletions src/components/gridForm/GridFormMultiSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import "../../styles/GridFormMultiSelect.scss";

import { FocusableItem, MenuDivider, MenuItem } from "../../react-menu3";
import { KeyboardEvent, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { GridBaseRow } from "../Grid";
import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
import { delay, fromPairs, isEqual, omit, pick, toPairs } from "lodash-es";
Expand All @@ -11,7 +11,7 @@ import { MenuSeparatorString } from "./GridFormDropDown";
import { CellEditorCommon } from "../GridCell";
import { ClickEvent } from "../../react-menu3/types";
import { GridSubComponentContext } from "contexts/GridSubComponentContext";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";
import { useGridPopoverContext } from "../../contexts/GridPopoverContext";

interface MultiFinalSelectOption<ValueType> {
value: ValueType;
Expand Down Expand Up @@ -45,7 +45,7 @@ export interface GridFormMultiSelectProps<RowType extends GridBaseRow, ValueType
export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
props: GridFormMultiSelectProps<RowType, ValueType>,
) => {
const { selectedRows } = useContext(GridPopoverContext);
const { selectedRows } = useGridPopoverContext<RowType>();

const initialiseValues = useMemo(() => {
const r = props.initialSelectedValues && props.initialSelectedValues(selectedRows);
Expand Down
32 changes: 25 additions & 7 deletions src/components/gridForm/GridFormPopoverMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { GridBaseRow } from "../Grid";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
import { FocusableItem, MenuDivider, MenuItem } from "../../react-menu3";
import { useGridPopoverHook } from "../GridPopoverHook";
import { CellEditorCommon } from "../GridCell";
import { GridSubComponentContext } from "../../contexts/GridSubComponentContext";
import { ClickEvent } from "../../react-menu3/types";
import { GridPopoverContext } from "../../contexts/GridPopoverContext";
import { useGridPopoverContext } from "../../contexts/GridPopoverContext";

export interface GridFormPopoutMenuProps<RowType extends GridBaseRow> extends CellEditorCommon {
options: (selectedRows: RowType[]) => Promise<MenuOption<RowType>[]>;
defaultAction?: (selectedRows: RowType[], menuOption: SelectedMenuOptionResult<RowType>) => Promise<void>;
}

/** Menu configuration types **/
Expand Down Expand Up @@ -37,7 +38,7 @@ export interface MenuOption<RowType extends GridBaseRow> {
* you need a useMemo around your columnDefs
*/
export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridFormPopoutMenuProps<RowType>) => {
const { selectedRows, updateValue } = useContext(GridPopoverContext);
const { selectedRows, updateValue } = useGridPopoverContext<RowType>();

const optionsInitialising = useRef(false);
const [options, setOptions] = useState<MenuOption<RowType>[]>();
Expand All @@ -48,29 +49,46 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
const subComponentIsValid = useRef(false);
const [subSelectedValue, setSubSelectedValue] = useState<any>();

const defaultAction = useCallback(
async (selectedRows: RowType[], menuOption: SelectedMenuOptionResult<RowType>) => {
if (props.defaultAction) await props.defaultAction(selectedRows, menuOption);
else console.error(`No action specified for ${menuOption.label} menu options`);
},
[props],
);

// Load up options list if it's async function
useEffect(() => {
if (options || optionsInitialising.current) return;
optionsInitialising.current = true;
const optionsConf = props.options ?? [];

(async () => {
setOptions(typeof optionsConf == "function" ? await optionsConf(selectedRows) : optionsConf);
const newOptions = typeof optionsConf == "function" ? await optionsConf(selectedRows) : optionsConf;
setOptions(newOptions);
if (!props.defaultAction) {
const anyOptionsAreMissingAction = newOptions.some((option) => !option.action);
if (anyOptionsAreMissingAction) {
console.error("There's no default action handler and some Menu options are missing an action handler", {
invalidMenuOptions: newOptions.filter((option) => !option.action),
});
}
}
optionsInitialising.current = false;
})();
}, [options, props.options, selectedRows]);
}, [options, props.defaultAction, props.options, selectedRows]);

const actionClick = useCallback(
async (menuOption: MenuOption<RowType>) => {
actionProcessing.current = true;
return updateValue(async () => {
const result = { ...menuOption, subValue: subSelectedValue };
menuOption.action && (await menuOption.action(selectedRows, result));
await (menuOption.action ?? defaultAction)(selectedRows, result);
actionProcessing.current = false;
return true;
});
},
[selectedRows, subSelectedValue, updateValue],
[defaultAction, selectedRows, subSelectedValue, updateValue],
);

const onMenuItemClick = useCallback(
Expand Down
Loading

0 comments on commit d6073e4

Please sign in to comment.