Skip to content

Commit 10ac4f7

Browse files
committed
feat: sortable config grouping
1 parent 4e4c632 commit 10ac4f7

File tree

4 files changed

+169
-2
lines changed

4 files changed

+169
-2
lines changed

package-lock.json

Lines changed: 18 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"react-responsive": "^9.0.2",
8686
"react-router-dom": "^6.2.1",
8787
"react-select": "^5.7.4",
88+
"react-sortable-hoc": "^2.0.0",
8889
"react-table": "^7.7.0",
8990
"react-tooltip": "^5.26.3",
9091
"react-top-loading-bar": "^2.3.1",
@@ -107,6 +108,12 @@
107108
"@headlessui/react": "We are using insiders version to use `by` property on `Combobox`. Without `by`, selected item logic was difficult to achieve.",
108109
"@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."
109110
},
111+
"overrides": {
112+
"react-sortable-hoc": {
113+
"react": "^18.0",
114+
"react-dom": "^18.0"
115+
}
116+
},
110117
"proxy": "https://incident-commander.canary.lab.flanksource.com",
111118
"lint-staged": {
112119
"src/**/*.{js,jsx,ts,tsx}": [

src/components/Configs/ConfigsListFilters/ConfigGroupByDropdown.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
GroupByOptions,
44
MultiSelectDropdown
55
} from "@flanksource-ui/ui/Dropdowns/MultiSelectDropdown";
6+
import { NonWindowedMultiSelectDropdown } from "@flanksource-ui/ui/Dropdowns/MultiSelectNonWindowDropdown";
67
import { useQuery } from "@tanstack/react-query";
78
import { useCallback, useMemo } from "react";
89
import { BiLabel, BiStats } from "react-icons/bi";
@@ -151,7 +152,7 @@ export default function ConfigGroupByDropdown({
151152

152153
return (
153154
<div className="flex flex-col items-center gap-2">
154-
<MultiSelectDropdown
155+
<NonWindowedMultiSelectDropdown
155156
options={options}
156157
isLoading={isLoading}
157158
value={value}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import React, { ComponentProps, MouseEventHandler } from "react";
2+
import Select, {
3+
components,
4+
MultiValueGenericProps,
5+
MultiValueProps,
6+
Props,
7+
OnChangeValue,
8+
ActionMeta
9+
} from "react-select";
10+
import {
11+
SortableContainer,
12+
SortableContainerProps,
13+
SortableElement,
14+
SortEndHandler,
15+
SortableHandle
16+
} from "react-sortable-hoc";
17+
import { arrayMove } from "react-sortable-hoc";
18+
19+
export type GroupByOptions = {
20+
isTag?: boolean;
21+
value: string;
22+
label: string;
23+
icon?: React.ReactNode;
24+
};
25+
26+
type ConfigGroupByDropdownProps = Omit<
27+
ComponentProps<typeof Select>,
28+
"components" | "defaultValue" | "windowThreshold"
29+
> & {
30+
label?: string;
31+
containerClassName?: string;
32+
dropDownClassNames?: string;
33+
defaultValue?: string;
34+
closeMenuOnSelect?: boolean;
35+
value?: readonly GroupByOptions[];
36+
};
37+
38+
export function NonWindowedMultiSelectDropdown({
39+
isMulti = true,
40+
isClearable = true,
41+
options,
42+
className = "w-auto max-w-[400px]",
43+
label,
44+
containerClassName = "w-full",
45+
dropDownClassNames = "w-auto max-w-[300px]",
46+
value,
47+
defaultValue,
48+
closeMenuOnSelect = false,
49+
onChange = () => {},
50+
...props
51+
}: ConfigGroupByDropdownProps) {
52+
const SortableMultiValue = SortableElement(
53+
(props: MultiValueProps<GroupByOptions>) => {
54+
// this prevents the menu from being opened/closed when the user clicks
55+
// on a value to begin dragging it. ideally, detecting a click (instead of
56+
// a drag) would still focus the control and toggle the menu, but that
57+
// requires some magic with refs that are out of scope for this example
58+
const onMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
59+
e.preventDefault();
60+
e.stopPropagation();
61+
};
62+
const innerProps = { ...props.innerProps, onMouseDown };
63+
return <components.MultiValue {...props} innerProps={innerProps} />;
64+
}
65+
);
66+
67+
const SortableMultiValueLabel = SortableHandle(
68+
(props: MultiValueGenericProps) => <components.MultiValueLabel {...props} />
69+
);
70+
71+
const SortableSelect = SortableContainer(Select) as React.ComponentClass<
72+
Props<GroupByOptions, true> & SortableContainerProps
73+
>;
74+
75+
const [selected, setSelected] = React.useState<readonly GroupByOptions[]>(
76+
value || []
77+
);
78+
79+
const onSortEnd: SortEndHandler = ({ oldIndex, newIndex }) => {
80+
const newValue = arrayMove([...selected], oldIndex, newIndex);
81+
setSelected(newValue);
82+
onChange(newValue as OnChangeValue<GroupByOptions, true>, {
83+
action: "select-option",
84+
option: newValue[newIndex],
85+
name: props.name
86+
});
87+
};
88+
89+
return (
90+
<SortableSelect
91+
useDragHandle
92+
axis="xy"
93+
distance={4}
94+
onSortEnd={onSortEnd}
95+
helperClass="dragging"
96+
getHelperDimensions={({ node }: { node: HTMLElement }) =>
97+
node.getBoundingClientRect()
98+
}
99+
isClearable={isClearable}
100+
isMulti
101+
options={options}
102+
value={selected}
103+
onChange={(
104+
value: OnChangeValue<GroupByOptions, true>,
105+
actionMeta: ActionMeta<GroupByOptions>
106+
) => {
107+
setSelected(value);
108+
onChange(value, actionMeta);
109+
}}
110+
{...(props as any)}
111+
components={{
112+
MultiValue: SortableMultiValue,
113+
MultiValueLabel: SortableMultiValueLabel
114+
}}
115+
styles={{
116+
...props.styles,
117+
multiValue: (base) => ({
118+
...base,
119+
backgroundColor: "var(--select-multi-value-color, #e2e8f0)",
120+
borderRadius: "0.375rem"
121+
}),
122+
multiValueLabel: (base) => ({
123+
...base,
124+
color: "var(--select-multi-value-label-color, #1f2937)",
125+
fontSize: "0.875rem",
126+
padding: "0.25rem 0.5rem",
127+
cursor: "move"
128+
}),
129+
multiValueRemove: (base) => ({
130+
...base,
131+
color: "var(--select-multi-value-remove-color, #4b5563)",
132+
cursor: "pointer",
133+
":hover": {
134+
backgroundColor:
135+
"var(--select-multi-value-remove-hover-color, #dc2626)",
136+
color: "white"
137+
}
138+
})
139+
}}
140+
/>
141+
);
142+
}

0 commit comments

Comments
 (0)