Upgrade react-select (#7824)

* Upgrade react-select

* figma 1-1

* Style X

* Fixes timezone select
This commit is contained in:
sean-brydon authored and GitHub committed 2023-03-20 11:18:23 +00:00
1 parent b3407e19b5
commit 131611e114
8 files changed
+163 -200

No files matched your search

+2 -2
View File
@@ -111,8 +111,8 @@
"react-multi-email": "^0.5.3",
"react-phone-number-input": "^3.2.7",
"react-schemaorg": "^2.0.0",
"react-select": "^5.4.0",
"react-timezone-select": "^1.3.2",
"react-select": "^5.7.0",
"react-timezone-select": "^1.4.0",
"react-use-intercom": "1.5.1",
"react-virtualized-auto-sizer": "^1.0.6",
"react-window": "^1.8.7",
+58 -71
View File
@@ -1,29 +1,13 @@
import { useId } from "@radix-ui/react-id";
import * as React from "react";
import type {
GroupBase,
Props,
SingleValue,
MultiValue,
SelectComponentsConfig,
MenuPlacement,
} from "react-select";
import ReactSelect, { components as reactSelectComponents } from "react-select";
import type { GroupBase, Props, SingleValue, MultiValue } from "react-select";
import ReactSelect from "react-select";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Label } from "../inputs/Label";
import {
ControlComponent,
InputComponent,
MenuComponent,
MenuListComponent,
OptionComponent,
SingleValueComponent,
ValueContainerComponent,
MultiValueComponent,
} from "./components";
import { getReactSelectProps } from "./selectTheme";
export type SelectProps<
Option,
@@ -31,79 +15,82 @@ export type SelectProps<
Group extends GroupBase<Option> = GroupBase<Option>
> = Props<Option, IsMulti, Group>;
export const getReactSelectProps = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
components,
menuPlacement = "auto",
}: {
className?: string;
components: SelectComponentsConfig<Option, IsMulti, Group>;
menuPlacement?: MenuPlacement;
}) => ({
menuPlacement,
className: classNames("block min-h-6 w-full min-w-0 flex-1 rounded-md", className),
classNamePrefix: "cal-react-select",
components: {
...reactSelectComponents,
IndicatorSeparator: () => null,
Input: InputComponent,
Option: OptionComponent,
Control: ControlComponent,
SingleValue: SingleValueComponent,
Menu: MenuComponent,
MenuList: MenuListComponent,
ValueContainer: ValueContainerComponent,
MultiValue: MultiValueComponent,
...components,
},
});
export const Select = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
components,
styles,
menuPlacement,
...props
}: SelectProps<Option, IsMulti, Group>) => {
const reactSelectProps = React.useMemo(() => {
return getReactSelectProps<Option, IsMulti, Group>({
className,
components: components || {},
menuPlacement,
});
}, [className, components]);
}, [components, menuPlacement]);
// Annoyingly if we update styles here we have to update timezone select too
// We cant create a generate function for this as we can't force state changes - onSelect styles dont change for example
return (
<ReactSelect
{...reactSelectProps}
{...props}
styles={{
...styles,
classNames={{
input: () => classNames("dark:text-darkgray-900 text-gray-900", props.classNames?.input),
option: (state) =>
classNames(
"dark:bg-darkgray-100 flex cursor-pointer justify-between py-2.5 px-3 rounded-none text-gray-700 dark:text-darkgray-700",
state.isFocused && "dark:bg-darkgray-200 bg-gray-100",
state.isSelected && "dark:bg-darkgray-300 bg-gray-200 text-gray-900 dark:text-darkgray-900",
props.classNames?.option
),
placeholder: (state) =>
classNames("text-gray-400 text-sm dark:text-darkgray-400", state.isFocused && "hidden"),
dropdownIndicator: () => "text-gray-600 dark:text-darkgray-400",
control: (state) =>
classNames(
"dark:bg-darkgray-100 dark:border-darkgray-300 !min-h-9 border-gray-300 bg-white text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:ring-2 focus-within:ring-gray-800 hover:border-gray-400 dark:focus-within:ring-darkgray-900 rounded-md border ",
state.isMulti ? (state.hasValue ? "p-1" : "px-3 py-2") : "py-2 px-3",
props.classNames?.control
),
singleValue: () =>
classNames(
"dark:text-darkgray-900 dark:placeholder:text-darkgray-500 text-black placeholder:text-gray-400",
props.classNames?.singleValue
),
valueContainer: () =>
classNames(
"dark:text-darkgray-900 dark:placeholder:text-darkgray-500 text-black placeholder:text-gray-400 flex gap-1",
props.classNames?.valueContainer
),
multiValue: () =>
classNames(
"dark:bg-darkgray-200 dark:text-darkgray-700 rounded-md bg-gray-100 text-gray-700 py-1.5 px-2 flex items-center text-sm leading-none",
props.classNames?.multiValue
),
menu: () =>
classNames(
"dark:bg-darkgray-100 rounded-md bg-white text-sm leading-4 dark:text-white mt-1 border border-gray-200 dark:border-darkgray-200 ",
props.classNames?.menu
),
menuList: () => classNames("scroll-bar scrollbar-track-w-20 rounded-md", props.classNames?.menuList),
indicatorsContainer: (state) =>
classNames(
state.selectProps.menuIsOpen
? state.isMulti
? "[&>*:last-child]:rotate-180 [&>*:last-child]:transition-transform"
: "rotate-180 transition-transform"
: "text-gray-600 dark:text-darkgray-600" // Woo it adds another SVG here on multi for some reason
),
multiValueRemove: () => "text-gray-600 dark:text-darkgray-600 py-auto ml-2",
...props.classNames,
}}
{...props}
/>
);
};
type IconLeadingProps = {
icon: React.ReactNode;
children?: React.ReactNode;
} & React.ComponentProps<typeof reactSelectComponents.Control>;
export const IconLeading = ({ icon, children, ...props }: IconLeadingProps) => {
return (
<reactSelectComponents.Control {...props}>
{icon}
{children}
</reactSelectComponents.Control>
);
};
export const SelectField = function SelectField<
Option,
IsMulti extends boolean = false,
+18 -119
View File
@@ -1,14 +1,4 @@
import type {
ControlProps,
GroupBase,
InputProps,
MenuListProps,
MenuProps,
MultiValueProps,
OptionProps,
SingleValueProps,
ValueContainerProps,
} from "react-select";
import type { GroupBase, InputProps, OptionProps } from "react-select";
import { components as reactSelectComponents } from "react-select";
import { classNames } from "@calcom/lib";
@@ -47,122 +37,31 @@ export const OptionComponent = <
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
...props
}: OptionProps<Option, IsMulti, Group>) => {
return (
<reactSelectComponents.Option
{...props}
className={classNames(
className,
"dark:bg-darkgray-100 !flex !cursor-pointer justify-between !py-3",
props.isFocused && "dark:!bg-darkgray-200 !bg-gray-100",
props.isSelected && "dark:!bg-darkgray-300 !bg-neutral-900"
)}>
<>
// This gets styled in the select classNames prop now - handles overrides with styles vs className here doesnt
<reactSelectComponents.Option {...props}>
<div className="flex">
<span className="mr-auto">{props.label}</span>
{(props.data as unknown as ExtendedOption).needsUpgrade && <UpgradeTeamsBadge />}
{props.isSelected && <FiCheck className="ml-2 h-4 w-4" />}
</>
</div>
</reactSelectComponents.Option>
);
};
export const ControlComponent = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
...props
}: ControlProps<Option, IsMulti, Group>) => (
<reactSelectComponents.Control
{...props}
className={classNames(
className,
"dark:bg-darkgray-100 dark:border-darkgray-300 !min-h-9 border-gray-300 bg-white text-sm leading-4 placeholder:text-sm placeholder:font-normal focus-within:border-0 focus-within:ring-2 focus-within:ring-neutral-800 hover:border-neutral-400 dark:focus-within:ring-white"
)}
/>
);
// We need to override this component if we need a icon - we can't simpily override styles
type IconLeadingProps = {
icon: React.ReactNode;
children?: React.ReactNode;
} & React.ComponentProps<typeof reactSelectComponents.Control>;
export const SingleValueComponent = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
...props
}: SingleValueProps<Option, IsMulti, Group>) => (
<reactSelectComponents.SingleValue
{...props}
className={classNames(
className,
"dark:text-darkgray-900 dark:placeholder:text-darkgray-500 text-black placeholder:text-gray-400"
)}
/>
);
export const ValueContainerComponent = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
...props
}: ValueContainerProps<Option, IsMulti, Group>) => (
<reactSelectComponents.ValueContainer
{...props}
className={classNames(
"dark:text-darkgray-900 dark:placeholder:text-darkgray-500 text-black placeholder:text-gray-400",
className
)}
/>
);
export const MultiValueComponent = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
...props
}: MultiValueProps<Option, IsMulti, Group>) => (
<reactSelectComponents.MultiValue
{...props}
className={classNames(
"dark:bg-darkgray-200 dark:text-darkgray-900 !rounded-md bg-gray-100 text-gray-700",
className
)}
/>
);
export const MenuComponent = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
...props
}: MenuProps<Option, IsMulti, Group>) => (
<reactSelectComponents.Menu
{...props}
className={classNames(
"dark:bg-darkgray-100 !rounded-md bg-white text-sm leading-4 dark:text-white",
className
)}
/>
);
export const MenuListComponent = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
className,
...props
}: MenuListProps<Option, IsMulti, Group>) => (
<reactSelectComponents.MenuList
{...props}
className={classNames("scroll-bar scrollbar-track-w-20 rounded-md", className)}
/>
);
export const IconLeading = ({ icon, children, ...props }: IconLeadingProps) => {
return (
<reactSelectComponents.Control {...props}>
{icon}
{children}
</reactSelectComponents.Control>
);
};
+3 -1
View File
@@ -1 +1,3 @@
export { SelectWithValidation, SelectField, getReactSelectProps, Select } from "./Select";
export { SelectWithValidation, SelectField, Select } from "./Select";
export { getReactSelectProps } from "./selectTheme";
@@ -19,6 +19,8 @@ Dropdown fields allow users to input existing options that is preset by the deis
export const options = [
{ value: 0, label: "Option One" },
{ value: 1, label: "Option Two" },
{ value: 3, label: "Option Three" },
{ value: 4, label: "Option Four" },
];
@@ -29,27 +31,27 @@ export const options = [
<li>The difference between the types are when they are filled. </li>
</ul>
}>
<Example title="Single Select [Unselected]">
<Example title="Single Select [Unselected]" isFullWidth>
<SelectField
label={"Single Select"}
options={options}
/>
</Example>
<Example title="Single Select [Selected]">
<Example title="Single Select [Selected]" isFullWidth>
<SelectField
label={"Single Select"}
options={options}
defaultValue={options[0]}
/>
</Example>
<Example title="Multi Select [Unselected]">
<Example title="Multi Select [Unselected]" isFullWidth >
<SelectField
label={"Multi Select"}
options={options}
isMulti={true}
/>
</Example>
<Example title="Multi Select [Selected]">
<Example title="Multi Select [Selected]" isFullWidth>
<SelectField
label={"Multi Select"}
options={options}
@@ -0,0 +1,25 @@
import type { GroupBase, SelectComponentsConfig, MenuPlacement } from "react-select";
import { InputComponent, OptionComponent } from "./components";
export const getReactSelectProps = <
Option,
IsMulti extends boolean = false,
Group extends GroupBase<Option> = GroupBase<Option>
>({
components,
menuPlacement = "auto",
}: {
components: SelectComponentsConfig<Option, IsMulti, Group>;
menuPlacement?: MenuPlacement;
}) => {
return {
menuPlacement,
components: {
Input: InputComponent,
Option: OptionComponent,
...components,
},
unstyled: true,
};
};
@@ -2,6 +2,7 @@ import { useMemo, useState } from "react";
import type { ITimezoneOption, ITimezone, Props as SelectProps } from "react-timezone-select";
import BaseSelect, { allTimezones } from "react-timezone-select";
import { classNames } from "@calcom/lib";
import { filterByCities, addCitiesToDropdown, handleOptionLabel } from "@calcom/lib/timezone";
import { trpc } from "@calcom/trpc/react";
@@ -22,11 +23,14 @@ export function TimezoneSelect({ className, components, ...props }: SelectProps)
};
const reactSelectProps = useMemo(() => {
return getReactSelectProps({ className, components: components || {} });
}, [className, components]);
return getReactSelectProps({
components: components || {},
});
}, [components]);
return (
<BaseSelect
className={className}
isLoading={isLoading}
isDisabled={isLoading}
{...reactSelectProps}
@@ -39,6 +43,50 @@ export function TimezoneSelect({ className, components, ...props }: SelectProps)
{...props}
formatOptionLabel={(option) => <p className="truncate">{(option as ITimezoneOption).value}</p>}
getOptionLabel={(option) => handleOptionLabel(option as ITimezoneOption, cities)}
classNames={{
input: () => classNames("dark:text-darkgray-900 text-gray-900", props.classNames?.input),
option: (state) =>
classNames(
"dark:bg-darkgray-100 flex cursor-pointer justify-between py-2.5 px-3 rounded-none text-gray-700 dark:text-darkgray-700",
state.isFocused && "dark:!bg-darkgray-200 !bg-gray-100",
state.isSelected && "dark:!bg-darkgray-300 !bg-gray-200 text-gray-900 !dark:text-darkgray-900",
props.classNames?.option
),
placeholder: (state) =>
classNames("text-gray-400 text-sm dark:text-darkgray-400", state.isFocused && "hidden"),
dropdownIndicator: () => "text-gray-600 dark:text-darkgray-400",
control: () => classNames("", props.classNames?.control), // We remove all styling here to fit theme of booking page - no min-h also
singleValue: () =>
classNames(
"dark:text-darkgray-900 dark:placeholder:text-darkgray-500 text-black placeholder:text-gray-400",
props.classNames?.singleValue
),
valueContainer: () =>
classNames(
"dark:text-darkgray-900 dark:placeholder:text-darkgray-500 text-black placeholder:text-gray-400 flex gap-1",
props.classNames?.valueContainer
),
multiValue: () =>
classNames(
"dark:bg-darkgray-200 dark:text-darkgray-700 rounded-md bg-gray-100 text-gray-700 py-1.5 px-2 flex items-center text-sm leading-none",
props.classNames?.multiValue
),
menu: () =>
classNames(
"dark:bg-darkgray-100 rounded-md bg-white text-sm leading-4 dark:text-white mt-1 border border-gray-200 dark:border-darkgray-200 ",
props.classNames?.menu
),
menuList: () => classNames("scroll-bar scrollbar-track-w-20 rounded-md", props.classNames?.menuList),
indicatorsContainer: (state) =>
classNames(
state.selectProps.menuIsOpen
? state.isMulti
? "[&>*:last-child]:rotate-180 [&>*:last-child]:transition-transform"
: "rotate-180 transition-transform"
: "text-gray-600 dark:text-darkgray-600" // Woo it adds another SVG here on multi for some reason
),
multiValueRemove: () => "text-gray-600 dark:text-darkgray-600 py-auto ml-2",
}}
/>
);
}
+1 -1
View File
@@ -34,7 +34,7 @@
"react-feather": "^2.0.10",
"react-hook-form": "^7.43.3",
"react-icons": "^4.4.0",
"react-select": "^5.4.0"
"react-select": "^5.7.0"
},
"devDependencies": {
"@calcom/config": "*",