diff --git a/package-lock.json b/package-lock.json index 7acf02127..cc6010aeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "@flanksource/flanksource-ui", - "version": "1.0.842", + "version": "1.0.860", "dependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@clerk/nextjs": "^5.3.0", @@ -85,6 +85,7 @@ "react-responsive": "^9.0.2", "react-router-dom": "^6.2.1", "react-select": "^5.7.4", + "react-sortable-hoc": "^2.0.0", "react-table": "^7.7.0", "react-tooltip": "^5.26.3", "react-top-loading-bar": "^2.3.1", @@ -37912,6 +37913,22 @@ "react-dom": ">=15.0.0" } }, + "node_modules/react-sortable-hoc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz", + "integrity": "sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + }, + "peerDependencies": { + "prop-types": "^15.5.7", + "react": "^16.3.0 || ^17.0.0", + "react-dom": "^16.3.0 || ^17.0.0" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", diff --git a/package.json b/package.json index f4974174b..fa7ba53ab 100644 --- a/package.json +++ b/package.json @@ -85,6 +85,7 @@ "react-responsive": "^9.0.2", "react-router-dom": "^6.2.1", "react-select": "^5.7.4", + "react-sortable-hoc": "^2.0.0", "react-table": "^7.7.0", "react-tooltip": "^5.26.3", "react-top-loading-bar": "^2.3.1", @@ -107,6 +108,12 @@ "@headlessui/react": "We are using insiders version to use `by` property on `Combobox`. Without `by`, selected item logic was difficult to achieve.", "@tanstack/react-query-devtools": "React Query Devtools are only included in bundles when process.env.NODE_ENV === 'development', so we don't have to exclude it from production build. Check it's docs." }, + "overrides": { + "react-sortable-hoc": { + "react": "^18.0", + "react-dom": "^18.0" + } + }, "proxy": "https://incident-commander.canary.lab.flanksource.com", "lint-staged": { "src/**/*.{js,jsx,ts,tsx}": [ diff --git a/src/components/Configs/ConfigsListFilters/ConfigGroupByDropdown.tsx b/src/components/Configs/ConfigsListFilters/ConfigGroupByDropdown.tsx index 54bfe795e..33caf84b4 100644 --- a/src/components/Configs/ConfigsListFilters/ConfigGroupByDropdown.tsx +++ b/src/components/Configs/ConfigsListFilters/ConfigGroupByDropdown.tsx @@ -3,6 +3,7 @@ import { GroupByOptions, MultiSelectDropdown } from "@flanksource-ui/ui/Dropdowns/MultiSelectDropdown"; +import { NonWindowedMultiSelectDropdown } from "@flanksource-ui/ui/Dropdowns/MultiSelectNonWindowDropdown"; import { useQuery } from "@tanstack/react-query"; import { useCallback, useMemo } from "react"; import { BiLabel, BiStats } from "react-icons/bi"; @@ -151,7 +152,7 @@ export default function ConfigGroupByDropdown({ return (
- , + "components" | "defaultValue" | "windowThreshold" +> & { + label?: string; + containerClassName?: string; + dropDownClassNames?: string; + defaultValue?: string; + closeMenuOnSelect?: boolean; + value?: readonly GroupByOptions[]; +}; + +export function NonWindowedMultiSelectDropdown({ + isMulti = true, + isClearable = true, + options, + className = "w-auto max-w-[400px]", + label, + containerClassName = "w-full", + dropDownClassNames = "w-auto max-w-[300px]", + value, + defaultValue, + closeMenuOnSelect = false, + onChange = () => {}, + ...props +}: ConfigGroupByDropdownProps) { + const SortableMultiValue = SortableElement( + (props: MultiValueProps) => { + // this prevents the menu from being opened/closed when the user clicks + // on a value to begin dragging it. ideally, detecting a click (instead of + // a drag) would still focus the control and toggle the menu, but that + // requires some magic with refs that are out of scope for this example + const onMouseDown: MouseEventHandler = (e) => { + e.preventDefault(); + e.stopPropagation(); + }; + const innerProps = { ...props.innerProps, onMouseDown }; + return ; + } + ); + + const SortableMultiValueLabel = SortableHandle( + (props: MultiValueGenericProps) => + ); + + const SortableSelect = SortableContainer(Select) as React.ComponentClass< + Props & SortableContainerProps + >; + + const [selected, setSelected] = React.useState( + value || [] + ); + + const onSortEnd: SortEndHandler = ({ oldIndex, newIndex }) => { + const newValue = arrayMove([...selected], oldIndex, newIndex); + setSelected(newValue); + onChange(newValue as OnChangeValue, { + action: "select-option", + option: newValue[newIndex], + name: props.name + }); + }; + + return ( + + node.getBoundingClientRect() + } + isClearable={isClearable} + isMulti + options={options} + value={selected} + onChange={( + value: OnChangeValue, + actionMeta: ActionMeta + ) => { + setSelected(value); + onChange(value, actionMeta); + }} + {...(props as any)} + components={{ + MultiValue: SortableMultiValue, + MultiValueLabel: SortableMultiValueLabel + }} + styles={{ + ...props.styles, + multiValue: (base) => ({ + ...base, + backgroundColor: "var(--select-multi-value-color, #e2e8f0)", + borderRadius: "0.375rem" + }), + multiValueLabel: (base) => ({ + ...base, + color: "var(--select-multi-value-label-color, #1f2937)", + fontSize: "0.875rem", + padding: "0.25rem 0.5rem", + cursor: "move" + }), + multiValueRemove: (base) => ({ + ...base, + color: "var(--select-multi-value-remove-color, #4b5563)", + cursor: "pointer", + ":hover": { + backgroundColor: + "var(--select-multi-value-remove-hover-color, #dc2626)", + color: "white" + } + }) + }} + /> + ); +}