Upgrade react-select (#7824)
* Upgrade react-select * figma 1-1 * Style X * Fixes timezone select
This commit is contained in:
1 parent
b3407e19b5
commit
131611e114
8 files changed
+163
-200
No files matched your search
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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": "*",
|
||||
|
||||
Reference in new issue
Block a user