Skip to content

Commit a5e39f4

Browse files
Fix dark mode styling (#19392)
* improving dark mode (esp for highlighted and selected states) * linting * PR feedback * PR feedback * Update ControlSelect.tsx
1 parent 7a78b57 commit a5e39f4

File tree

7 files changed

+170
-114
lines changed

7 files changed

+170
-114
lines changed

packages/connect-react/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
# Changelog
44

5+
## [2.3.0] - 2025-12-07
6+
7+
### Added
8+
9+
- Dark mode support for select components with new theme color tokens (`optionHover`, `optionSelected`, `optionSelectedHover`, `appIconBackground`)
10+
- App icon backgrounds in `SelectApp` for better visibility in dark mode
11+
512
## [2.2.0] - 2025-11-29
613

714
### Added

packages/connect-react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/connect-react",
3-
"version": "2.2.0",
3+
"version": "2.3.0",
44
"description": "Pipedream Connect library for React",
55
"files": [
66
"dist"

packages/connect-react/src/components/ControlApp.tsx

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,27 @@ export function ControlApp({ app }: ControlAppProps) {
5555
getProps, select, theme,
5656
} = useCustomize();
5757

58-
const {
59-
surface,
60-
border,
61-
text,
62-
textStrong,
63-
hoverBg,
64-
selectedBg,
65-
selectedHoverBg,
66-
} = resolveSelectColors(theme.colors);
58+
// Memoize color resolution to avoid recalculating on every render
59+
const resolvedColors = useMemo(() => resolveSelectColors(theme.colors), [
60+
theme.colors,
61+
]);
62+
63+
// Memoize base select styles - only recalculate when colors or boxShadow change
64+
const baseSelectStyles = useMemo(() => createBaseSelectStyles<SelectValue>({
65+
colors: {
66+
surface: resolvedColors.surface,
67+
border: resolvedColors.border,
68+
text: resolvedColors.text,
69+
textStrong: resolvedColors.textStrong,
70+
hoverBg: resolvedColors.hoverBg,
71+
selectedBg: resolvedColors.selectedBg,
72+
selectedHoverBg: resolvedColors.selectedHoverBg,
73+
},
74+
boxShadow: theme.boxShadow,
75+
}), [
76+
resolvedColors,
77+
theme.boxShadow,
78+
]);
6779

6880
const baseStyles: CSSProperties = {
6981
color: theme.colors.neutral60,
@@ -79,27 +91,14 @@ export function ControlApp({ app }: ControlAppProps) {
7991
gridArea: "control",
8092
};
8193

82-
const selectStyles = createBaseSelectStyles<SelectValue>({
83-
colors: {
84-
surface,
85-
border,
86-
text,
87-
textStrong,
88-
hoverBg,
89-
selectedBg,
90-
selectedHoverBg,
91-
},
92-
boxShadow: theme.boxShadow,
93-
});
94-
9594
const baseSelectProps: BaseReactSelectProps<SelectValue> = {
9695
components: {
9796
Option: BaseOption,
9897
},
9998
styles: {
100-
...selectStyles,
99+
...baseSelectStyles,
101100
control: (base, state) => ({
102-
...(selectStyles.control?.(base, state) ?? base),
101+
...(baseSelectStyles.control?.(base, state) ?? base),
103102
gridArea: "control",
104103
}),
105104
},

packages/connect-react/src/components/ControlSelect.tsx

Lines changed: 51 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@ import {
55
useState,
66
useRef,
77
} from "react";
8-
import type {
9-
CSSObjectWithLabel, MenuListProps,
10-
} from "react-select";
8+
import type { MenuListProps } from "react-select";
119
import Select, {
1210
components,
1311
Props as ReactSelectProps,
1412
} from "react-select";
1513
import CreatableSelect from "react-select/creatable";
16-
import type { BaseReactSelectProps } from "../hooks/customization-context";
1714
import { useCustomize } from "../hooks/customization-context";
1815
import { useFormFieldContext } from "../hooks/form-field-context";
1916
import type {
@@ -28,6 +25,10 @@ import {
2825
isArrayOfLabelValueWrapped,
2926
isLabelValueWrapped,
3027
} from "../utils/label-value";
28+
import {
29+
createBaseSelectStyles,
30+
resolveSelectColors,
31+
} from "../utils/select-styles";
3132
import { LoadMoreButton } from "./LoadMoreButton";
3233

3334
// XXX T and ConfigurableProp should be related
@@ -55,6 +56,29 @@ export function ControlSelect<T extends PropOptionValue>({
5556
const {
5657
select, theme,
5758
} = useCustomize();
59+
60+
// Memoize color resolution to avoid recalculating on every render
61+
const resolvedColors = useMemo(() => resolveSelectColors(theme.colors), [
62+
theme.colors,
63+
]);
64+
65+
// Memoize base select styles - only recalculate when colors or boxShadow change
66+
const baseSelectStyles = useMemo(() => createBaseSelectStyles<LabelValueOption<T>, boolean>({
67+
colors: {
68+
surface: resolvedColors.surface,
69+
border: resolvedColors.border,
70+
text: resolvedColors.text,
71+
textStrong: resolvedColors.textStrong,
72+
hoverBg: resolvedColors.hoverBg,
73+
selectedBg: resolvedColors.selectedBg,
74+
selectedHoverBg: resolvedColors.selectedHoverBg,
75+
},
76+
boxShadow: theme.boxShadow,
77+
}), [
78+
resolvedColors,
79+
theme.boxShadow,
80+
]);
81+
5882
const [
5983
selectOptions,
6084
setSelectOptions,
@@ -77,16 +101,6 @@ export function ControlSelect<T extends PropOptionValue>({
77101
value,
78102
])
79103

80-
const baseSelectProps: BaseReactSelectProps<LabelValueOption<T>, boolean> = {
81-
styles: {
82-
container: (base): CSSObjectWithLabel => ({
83-
...base,
84-
gridArea: "control",
85-
boxShadow: theme.boxShadow.input,
86-
}),
87-
},
88-
};
89-
90104
const selectValue: LabelValueOption<T> | LabelValueOption<T>[] | null = useMemo(() => {
91105
if (rawValue == null) {
92106
return null;
@@ -130,7 +144,15 @@ export function ControlSelect<T extends PropOptionValue>({
130144
selectOptions,
131145
]);
132146

133-
const props = select.getProps("controlSelect", baseSelectProps)
147+
// Get customization props from context
148+
// We pass our dark mode base styles as the base, so user customizations merge on top
149+
const customizationProps = select.getProps<
150+
"controlSelect",
151+
LabelValueOption<T>,
152+
boolean
153+
>("controlSelect", {
154+
styles: baseSelectStyles,
155+
})
134156

135157
// Use ref to store latest onLoadMore callback
136158
// This allows stable component reference while calling current callback
@@ -145,10 +167,10 @@ export function ControlSelect<T extends PropOptionValue>({
145167
showLoadMoreButtonRef.current = showLoadMoreButton;
146168

147169
// Memoize custom components to prevent remounting
148-
// Recompute when caller/customizer supplies new component overrides
170+
// Merge: customization context components -> caller overrides -> our MenuList wrapper
149171
const finalComponents = useMemo(() => {
150172
const mergedComponents = {
151-
...(props.components ?? {}),
173+
...(customizationProps.components ?? {}),
152174
...(componentsOverride ?? {}),
153175
};
154176
const ParentMenuList = mergedComponents.MenuList ?? components.MenuList;
@@ -174,8 +196,8 @@ export function ControlSelect<T extends PropOptionValue>({
174196
MenuList: CustomMenuList,
175197
};
176198
}, [
177-
props.components,
178199
componentsOverride,
200+
customizationProps.components,
179201
]);
180202

181203
const handleCreate = (inputValue: string) => {
@@ -254,21 +276,26 @@ export function ControlSelect<T extends PropOptionValue>({
254276
getOptionLabel={(option) => sanitizeOption(option).label}
255277
getOptionValue={(option) => String(sanitizeOption(option).value)}
256278
onChange={handleChange}
257-
{...props}
279+
// Apply customization context values as defaults
280+
classNamePrefix={customizationProps.classNamePrefix || "react-select"}
281+
classNames={customizationProps.classNames}
282+
theme={customizationProps.theme}
283+
// Spread selectProps after defaults so callers can override theme/classNames/classNamePrefix
258284
{...selectProps}
259285
{...additionalProps}
260-
// These must come AFTER spreads to avoid being overridden
261-
classNamePrefix="react-select"
262286
menuPortalTarget={
263287
typeof document !== "undefined"
264288
? document.body
265289
: null
266290
}
267291
menuPosition="fixed"
268292
styles={{
269-
// eslint-disable-next-line react/prop-types
270-
...props.styles,
271-
...selectProps?.styles,
293+
...customizationProps.styles,
294+
...(selectProps?.styles ?? {}),
295+
container: (base) => ({
296+
...base,
297+
gridArea: "control",
298+
}),
272299
menuPortal: (base) => ({
273300
...base,
274301
zIndex: 99999,

packages/connect-react/src/components/SelectApp.tsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -98,30 +98,30 @@ export function SelectApp({
9898
loadMore,
9999
]);
100100

101-
const {
102-
surface,
103-
border,
104-
text,
105-
textStrong,
106-
hoverBg,
107-
selectedBg,
108-
selectedHoverBg,
109-
appIconBg,
110-
} = resolveSelectColors(theme.colors);
101+
// Memoize color resolution to avoid recalculating on every render
102+
const resolvedColors = useMemo(() => resolveSelectColors(theme.colors), [
103+
theme.colors,
104+
]);
105+
106+
// Memoize base select styles - only recalculate when colors or boxShadow change
107+
const baseSelectStyles = useMemo(() => createBaseSelectStyles<App>({
108+
colors: {
109+
surface: resolvedColors.surface,
110+
border: resolvedColors.border,
111+
text: resolvedColors.text,
112+
textStrong: resolvedColors.textStrong,
113+
hoverBg: resolvedColors.hoverBg,
114+
selectedBg: resolvedColors.selectedBg,
115+
selectedHoverBg: resolvedColors.selectedHoverBg,
116+
},
117+
boxShadow: theme.boxShadow,
118+
}), [
119+
resolvedColors,
120+
theme.boxShadow,
121+
]);
111122

112123
const baseSelectProps: BaseReactSelectProps<App> = {
113-
styles: createBaseSelectStyles<App>({
114-
colors: {
115-
surface,
116-
border,
117-
text,
118-
textStrong,
119-
hoverBg,
120-
selectedBg,
121-
selectedHoverBg,
122-
},
123-
boxShadow: theme.boxShadow,
124-
}),
124+
styles: baseSelectStyles,
125125
};
126126

127127
const selectProps = select.getProps("selectApp", baseSelectProps);
@@ -139,7 +139,7 @@ export function SelectApp({
139139
style={{
140140
height: 24,
141141
width: 24,
142-
backgroundColor: appIconBg,
142+
backgroundColor: resolvedColors.appIconBg,
143143
borderRadius: 6,
144144
padding: 2,
145145
}}
@@ -163,7 +163,7 @@ export function SelectApp({
163163
style={{
164164
height: 24,
165165
width: 24,
166-
backgroundColor: appIconBg,
166+
backgroundColor: resolvedColors.appIconBg,
167167
borderRadius: 6,
168168
padding: 2,
169169
}}
@@ -197,7 +197,7 @@ export function SelectApp({
197197
Option,
198198
SingleValue,
199199
MenuList,
200-
appIconBg,
200+
resolvedColors.appIconBg,
201201
]);
202202
return (
203203
<Select

packages/connect-react/src/components/SelectComponent.tsx

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,27 @@ export function SelectComponent({
5050
select, theme,
5151
} = useCustomize();
5252

53-
const {
54-
surface,
55-
border,
56-
text,
57-
textStrong,
58-
hoverBg,
59-
selectedBg,
60-
selectedHoverBg,
61-
} = resolveSelectColors(theme.colors);
53+
// Memoize color resolution to avoid recalculating on every render
54+
const resolvedColors = useMemo(() => resolveSelectColors(theme.colors), [
55+
theme.colors,
56+
]);
57+
58+
// Memoize base select styles - only recalculate when colors or boxShadow change
59+
const baseSelectStyles = useMemo(() => createBaseSelectStyles<Component>({
60+
colors: {
61+
surface: resolvedColors.surface,
62+
border: resolvedColors.border,
63+
text: resolvedColors.text,
64+
textStrong: resolvedColors.textStrong,
65+
hoverBg: resolvedColors.hoverBg,
66+
selectedBg: resolvedColors.selectedBg,
67+
selectedHoverBg: resolvedColors.selectedHoverBg,
68+
},
69+
boxShadow: theme.boxShadow,
70+
}), [
71+
resolvedColors,
72+
theme.boxShadow,
73+
]);
6274

6375
const isLoadingMoreRef = useRef(isLoadingMore);
6476
isLoadingMoreRef.current = isLoadingMore;
@@ -83,18 +95,7 @@ export function SelectComponent({
8395
]);
8496

8597
const baseSelectProps: BaseReactSelectProps<Component> = {
86-
styles: createBaseSelectStyles<Component>({
87-
colors: {
88-
surface,
89-
border,
90-
text,
91-
textStrong,
92-
hoverBg,
93-
selectedBg,
94-
selectedHoverBg,
95-
},
96-
boxShadow: theme.boxShadow,
97-
}),
98+
styles: baseSelectStyles,
9899
};
99100

100101
const selectProps = select.getProps("selectComponent", baseSelectProps);

0 commit comments

Comments
 (0)