Skip to content

Commit c3ec86b

Browse files
feat(ui): enhance IAICustomSelect (#3523)
Now accepts an array of strings or array of `IAICustomSelectOption`s. This supports custom labels and tooltips within the select component.
2 parents 6ad7cc4 + 05a1975 commit c3ec86b

File tree

7 files changed

+200
-115
lines changed

7 files changed

+200
-115
lines changed

invokeai/frontend/web/src/common/components/IAICustomSelect.tsx

Lines changed: 44 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { CheckIcon, ChevronUpIcon } from '@chakra-ui/icons';
22
import {
33
Box,
44
Flex,
5-
FlexProps,
65
FormControl,
76
FormControlProps,
87
FormLabel,
@@ -16,22 +15,27 @@ import {
1615
} from '@chakra-ui/react';
1716
import { autoUpdate, offset, shift, useFloating } from '@floating-ui/react-dom';
1817
import { useSelect } from 'downshift';
18+
import { isString } from 'lodash-es';
1919
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
2020

2121
import { memo, useMemo } from 'react';
2222
import { getInputOutlineStyles } from 'theme/util/getInputOutlineStyles';
2323

2424
export type ItemTooltips = { [key: string]: string };
2525

26+
export type IAICustomSelectOption = {
27+
value: string;
28+
label: string;
29+
tooltip?: string;
30+
};
31+
2632
type IAICustomSelectProps = {
2733
label?: string;
28-
items: string[];
29-
itemTooltips?: ItemTooltips;
30-
selectedItem: string;
31-
setSelectedItem: (v: string | null | undefined) => void;
34+
value: string;
35+
data: IAICustomSelectOption[] | string[];
36+
onChange: (v: string) => void;
3237
withCheckIcon?: boolean;
3338
formControlProps?: FormControlProps;
34-
buttonProps?: FlexProps;
3539
tooltip?: string;
3640
tooltipProps?: Omit<TooltipProps, 'children'>;
3741
ellipsisPosition?: 'start' | 'end';
@@ -40,18 +44,33 @@ type IAICustomSelectProps = {
4044
const IAICustomSelect = (props: IAICustomSelectProps) => {
4145
const {
4246
label,
43-
items,
44-
itemTooltips,
45-
setSelectedItem,
46-
selectedItem,
4747
withCheckIcon,
4848
formControlProps,
4949
tooltip,
50-
buttonProps,
5150
tooltipProps,
5251
ellipsisPosition = 'end',
52+
data,
53+
value,
54+
onChange,
5355
} = props;
5456

57+
const values = useMemo(() => {
58+
return data.map<IAICustomSelectOption>((v) => {
59+
if (isString(v)) {
60+
return { value: v, label: v };
61+
}
62+
return v;
63+
});
64+
}, [data]);
65+
66+
const stringValues = useMemo(() => {
67+
return values.map((v) => v.value);
68+
}, [values]);
69+
70+
const valueData = useMemo(() => {
71+
return values.find((v) => v.value === value);
72+
}, [values, value]);
73+
5574
const {
5675
isOpen,
5776
getToggleButtonProps,
@@ -60,10 +79,11 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
6079
highlightedIndex,
6180
getItemProps,
6281
} = useSelect({
63-
items,
64-
selectedItem,
65-
onSelectedItemChange: ({ selectedItem: newSelectedItem }) =>
66-
setSelectedItem(newSelectedItem),
82+
items: stringValues,
83+
selectedItem: value,
84+
onSelectedItemChange: ({ selectedItem: newSelectedItem }) => {
85+
newSelectedItem && onChange(newSelectedItem);
86+
},
6787
});
6888

6989
const { refs, floatingStyles } = useFloating<HTMLButtonElement>({
@@ -94,7 +114,6 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
94114
<Tooltip label={tooltip} {...tooltipProps}>
95115
<Flex
96116
{...getToggleButtonProps({ ref: refs.setReference })}
97-
{...buttonProps}
98117
sx={{
99118
alignItems: 'center',
100119
userSelect: 'none',
@@ -119,7 +138,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
119138
direction: labelTextDirection,
120139
}}
121140
>
122-
{selectedItem}
141+
{valueData?.label}
123142
</Text>
124143
<ChevronUpIcon
125144
sx={{
@@ -155,8 +174,8 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
155174
}}
156175
>
157176
<OverlayScrollbarsComponent>
158-
{items.map((item, index) => {
159-
const isSelected = selectedItem === item;
177+
{values.map((v, index) => {
178+
const isSelected = value === v.value;
160179
const isHighlighted = highlightedIndex === index;
161180
const fontWeight = isSelected ? 700 : 500;
162181
const bg = isHighlighted
@@ -166,9 +185,9 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
166185
: undefined;
167186
return (
168187
<Tooltip
169-
isDisabled={!itemTooltips}
170-
key={`${item}${index}`}
171-
label={itemTooltips?.[item]}
188+
isDisabled={!v.tooltip}
189+
key={`${v.value}${index}`}
190+
label={v.tooltip}
172191
hasArrow
173192
placement="right"
174193
>
@@ -182,8 +201,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
182201
transitionProperty: 'common',
183202
transitionDuration: '0.15s',
184203
}}
185-
key={`${item}${index}`}
186-
{...getItemProps({ item, index })}
204+
{...getItemProps({ item: v.value, index })}
187205
>
188206
{withCheckIcon ? (
189207
<Grid gridTemplateColumns="1.25rem auto">
@@ -198,7 +216,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
198216
fontWeight,
199217
}}
200218
>
201-
{item}
219+
{v.label}
202220
</Text>
203221
</GridItem>
204222
</Grid>
@@ -210,7 +228,7 @@ const IAICustomSelect = (props: IAICustomSelectProps) => {
210228
fontWeight,
211229
}}
212230
>
213-
{item}
231+
{v.label}
214232
</Text>
215233
)}
216234
</ListItem>

invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetModel.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
11
import { useAppDispatch } from 'app/store/storeHooks';
2-
import IAICustomSelect from 'common/components/IAICustomSelect';
2+
import IAICustomSelect, {
3+
IAICustomSelectOption,
4+
} from 'common/components/IAICustomSelect';
35
import {
46
CONTROLNET_MODELS,
5-
ControlNetModel,
7+
ControlNetModelName,
68
} from 'features/controlNet/store/constants';
79
import { controlNetModelChanged } from 'features/controlNet/store/controlNetSlice';
10+
import { map } from 'lodash-es';
811
import { memo, useCallback } from 'react';
912

1013
type ParamControlNetModelProps = {
1114
controlNetId: string;
12-
model: ControlNetModel;
15+
model: ControlNetModelName;
1316
};
1417

18+
const DATA: IAICustomSelectOption[] = map(CONTROLNET_MODELS, (m) => ({
19+
value: m.type,
20+
label: m.label,
21+
tooltip: m.type,
22+
}));
23+
1524
const ParamControlNetModel = (props: ParamControlNetModelProps) => {
1625
const { controlNetId, model } = props;
1726
const dispatch = useAppDispatch();
1827

1928
const handleModelChanged = useCallback(
2029
(val: string | null | undefined) => {
2130
// TODO: do not cast
22-
const model = val as ControlNetModel;
31+
const model = val as ControlNetModelName;
2332
dispatch(controlNetModelChanged({ controlNetId, model }));
2433
},
2534
[controlNetId, dispatch]
@@ -29,9 +38,9 @@ const ParamControlNetModel = (props: ParamControlNetModelProps) => {
2938
<IAICustomSelect
3039
tooltip={model}
3140
tooltipProps={{ placement: 'top', hasArrow: true }}
32-
items={CONTROLNET_MODELS}
33-
selectedItem={model}
34-
setSelectedItem={handleModelChanged}
41+
data={DATA}
42+
value={model}
43+
onChange={handleModelChanged}
3544
ellipsisPosition="start"
3645
withCheckIcon
3746
/>

invokeai/frontend/web/src/features/controlNet/components/parameters/ParamControlNetProcessorSelect.tsx

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import IAICustomSelect from 'common/components/IAICustomSelect';
1+
import IAICustomSelect, {
2+
IAICustomSelectOption,
3+
} from 'common/components/IAICustomSelect';
24
import { memo, useCallback } from 'react';
35
import {
46
ControlNetProcessorNode,
@@ -7,15 +9,28 @@ import {
79
import { controlNetProcessorTypeChanged } from '../../store/controlNetSlice';
810
import { useAppDispatch } from 'app/store/storeHooks';
911
import { CONTROLNET_PROCESSORS } from '../../store/constants';
12+
import { map } from 'lodash-es';
1013

1114
type ParamControlNetProcessorSelectProps = {
1215
controlNetId: string;
1316
processorNode: ControlNetProcessorNode;
1417
};
1518

16-
const CONTROLNET_PROCESSOR_TYPES = Object.keys(
17-
CONTROLNET_PROCESSORS
18-
) as ControlNetProcessorType[];
19+
const CONTROLNET_PROCESSOR_TYPES: IAICustomSelectOption[] = map(
20+
CONTROLNET_PROCESSORS,
21+
(p) => ({
22+
value: p.type,
23+
label: p.label,
24+
tooltip: p.description,
25+
})
26+
).sort((a, b) =>
27+
// sort 'none' to the top
28+
a.value === 'none'
29+
? -1
30+
: b.value === 'none'
31+
? 1
32+
: a.label.localeCompare(b.label)
33+
);
1934

2035
const ParamControlNetProcessorSelect = (
2136
props: ParamControlNetProcessorSelectProps
@@ -36,9 +51,9 @@ const ParamControlNetProcessorSelect = (
3651
return (
3752
<IAICustomSelect
3853
label="Processor"
39-
items={CONTROLNET_PROCESSOR_TYPES}
40-
selectedItem={processorNode.type ?? 'canny_image_processor'}
41-
setSelectedItem={handleProcessorTypeChanged}
54+
value={processorNode.type ?? 'canny_image_processor'}
55+
data={CONTROLNET_PROCESSOR_TYPES}
56+
onChange={handleProcessorTypeChanged}
4257
withCheckIcon
4358
/>
4459
);

0 commit comments

Comments
 (0)