diff --git a/packages/connect-react/package.json b/packages/connect-react/package.json index bcd1a8a0d89f4..d7039ff9eeaaa 100644 --- a/packages/connect-react/package.json +++ b/packages/connect-react/package.json @@ -6,6 +6,7 @@ "dist" ], "type": "module", + "main": "./dist/connect-react.umd.js", "browser": "./dist/connect-react.umd.js", "module": "./dist/connect-react.es.js", "types": "./dist/connect-react.umd.d.ts", diff --git a/packages/connect-react/src/components/ControlApp.tsx b/packages/connect-react/src/components/ControlApp.tsx index 2c97211dfd320..484a098bbab8d 100644 --- a/packages/connect-react/src/components/ControlApp.tsx +++ b/packages/connect-react/src/components/ControlApp.tsx @@ -3,8 +3,14 @@ import { useFrontendClient } from "../hooks/frontend-client-context"; import { useAccounts } from "../hooks/use-accounts"; import { useFormFieldContext } from "../hooks/form-field-context"; import { useFormContext } from "../hooks/form-context"; -import { useCustomize } from "../hooks/customization-context"; -import type { BaseReactSelectProps } from "../hooks/customization-context"; +import { + useCustomize, + type BaseReactSelectProps, +} from "../hooks/customization-context"; +import { + createBaseSelectStyles, + resolveSelectColors, +} from "../utils/select-styles"; import { useMemo } from "react"; import type { CSSProperties } from "react"; import type { OptionProps } from "react-select"; @@ -49,6 +55,16 @@ export function ControlApp({ app }: ControlAppProps) { getProps, select, theme, } = useCustomize(); + const { + surface, + border, + text, + textStrong, + hoverBg, + selectedBg, + selectedHoverBg, + } = resolveSelectColors(theme.colors); + const baseStyles: CSSProperties = { color: theme.colors.neutral60, gridArea: "control", @@ -63,15 +79,28 @@ export function ControlApp({ app }: ControlAppProps) { gridArea: "control", }; + const selectStyles = createBaseSelectStyles({ + colors: { + surface, + border, + text, + textStrong, + hoverBg, + selectedBg, + selectedHoverBg, + }, + boxShadow: theme.boxShadow, + }); + const baseSelectProps: BaseReactSelectProps = { components: { Option: BaseOption, }, styles: { - control: (base) => ({ - ...base, + ...selectStyles, + control: (base, state) => ({ + ...(selectStyles.control?.(base, state) ?? base), gridArea: "control", - boxShadow: theme.boxShadow.input, }), }, }; diff --git a/packages/connect-react/src/components/SelectApp.tsx b/packages/connect-react/src/components/SelectApp.tsx index d44989e8d2852..1f546a495e981 100644 --- a/packages/connect-react/src/components/SelectApp.tsx +++ b/packages/connect-react/src/components/SelectApp.tsx @@ -6,6 +6,14 @@ import type { MenuListProps, OptionProps, SingleValueProps, } from "react-select"; import { useApps } from "../hooks/use-apps"; +import { + useCustomize, + type BaseReactSelectProps, +} from "../hooks/customization-context"; +import { + createBaseSelectStyles, + resolveSelectColors, +} from "../utils/select-styles"; import type { App, AppsListRequest, @@ -62,6 +70,9 @@ export function SelectApp({ SingleValue, MenuList, } = components; + const { + select, theme, + } = useCustomize(); const isLoadingMoreRef = useRef(isLoadingMore); isLoadingMoreRef.current = isLoadingMore; @@ -87,6 +98,34 @@ export function SelectApp({ loadMore, ]); + const { + surface, + border, + text, + textStrong, + hoverBg, + selectedBg, + selectedHoverBg, + appIconBg, + } = resolveSelectColors(theme.colors); + + const baseSelectProps: BaseReactSelectProps = { + styles: createBaseSelectStyles({ + colors: { + surface, + border, + text, + textStrong, + hoverBg, + selectedBg, + selectedHoverBg, + }, + boxShadow: theme.boxShadow, + }), + }; + + const selectProps = select.getProps("selectApp", baseSelectProps); + // Memoize custom components to prevent remounting const customComponents = useMemo(() => ({ Option: (optionProps: OptionProps) => ( @@ -100,6 +139,9 @@ export function SelectApp({ style={{ height: 24, width: 24, + backgroundColor: appIconBg, + borderRadius: 6, + padding: 2, }} alt={optionProps.data.name} /> @@ -121,6 +163,9 @@ export function SelectApp({ style={{ height: 24, width: 24, + backgroundColor: appIconBg, + borderRadius: 6, + padding: 2, }} alt={singleValueProps.data.name} /> @@ -152,13 +197,18 @@ export function SelectApp({ Option, SingleValue, MenuList, + appIconBg, ]); return ( o.name || o.key} @@ -97,7 +137,10 @@ export function SelectComponent({ onChange={(o) => onChange?.((o as Component) || undefined)} onMenuScrollToBottom={handleMenuScrollToBottom} isLoading={isLoading} - components={customComponents} + components={{ + ...selectProps.components, + ...customComponents, + }} menuPortalTarget={ typeof document !== "undefined" ? document.body @@ -105,6 +148,7 @@ export function SelectComponent({ } menuPosition="fixed" styles={{ + ...(selectProps.styles ?? {}), menuPortal: (base) => ({ ...base, zIndex: 99999, diff --git a/packages/connect-react/src/hooks/customization-context.tsx b/packages/connect-react/src/hooks/customization-context.tsx index bd9b377ffa87c..a6f3511f738e7 100644 --- a/packages/connect-react/src/hooks/customization-context.tsx +++ b/packages/connect-react/src/hooks/customization-context.tsx @@ -54,6 +54,8 @@ export const defaultComponents = { export type ReactSelectComponents = { controlAppSelect: typeof ControlApp; controlSelect: typeof ControlSelect; + selectApp: typeof ControlApp; + selectComponent: typeof ControlSelect; }; export type CustomComponents> = { diff --git a/packages/connect-react/src/theme.ts b/packages/connect-react/src/theme.ts index 0c670d8d5516f..70de50177555f 100644 --- a/packages/connect-react/src/theme.ts +++ b/packages/connect-react/src/theme.ts @@ -59,6 +59,17 @@ export type Colors = { // select.singleValue:color neutral80: string; neutral90: string; + + // Option state backgrounds (dark mode friendly) + // select.option:hover:backgroundColor + optionHover?: string; + // select.option:selected:backgroundColor + optionSelected?: string; + // select.option:selected:hover:backgroundColor + optionSelectedHover?: string; + + // App icon background color (for visibility in dark mode) + appIconBackground?: string; }; export type Shadows = { diff --git a/packages/connect-react/src/utils/select-styles.ts b/packages/connect-react/src/utils/select-styles.ts new file mode 100644 index 0000000000000..fe32a228a7ef8 --- /dev/null +++ b/packages/connect-react/src/utils/select-styles.ts @@ -0,0 +1,109 @@ +import type { + CSSObjectWithLabel, GroupBase, StylesConfig, +} from "react-select"; +import type { + Colors, Shadows, +} from "../theme"; + +export type SelectColorConfig = { + surface: string; + border: string; + text: string; + textStrong: string; + hoverBg: string; + selectedBg: string; + selectedHoverBg: string; +}; + +export type SelectStyleConfig = { + colors: SelectColorConfig; + boxShadow: Shadows; +}; + +/** + * Resolves theme colors with fallbacks for dark mode styling. + * Returns the theme value if defined, otherwise the fallback. + */ +export function resolveSelectColors(colors: Partial): SelectColorConfig & { appIconBg: string } { + const resolve = (key: K, fallback: string): string => { + const current = colors[key]; + return current !== undefined + ? current + : fallback; + }; + + return { + surface: resolve("neutral0", "#18181b"), + border: resolve("neutral20", "rgba(255,255,255,0.16)"), + text: resolve("neutral80", "#a1a1aa"), + textStrong: resolve("neutral90", "#e4e4e7"), + hoverBg: resolve("optionHover", "#27272a"), + selectedBg: resolve("optionSelected", "rgba(59,130,246,0.2)"), + selectedHoverBg: resolve("optionSelectedHover", "rgba(59,130,246,0.35)"), + appIconBg: resolve("appIconBackground", "#fff"), + }; +} + +/** + * Creates base styles for react-select components with dark mode support. + * Shared across SelectApp, SelectComponent, and ControlApp. + */ +export function createBaseSelectStyles< + Option, + IsMulti extends boolean = false, + Group extends GroupBase