From 131611e114f83a2b7aeac1aa6c129524fe7858f7 Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Mon, 20 Mar 2023 19:18:23 +0800 Subject: [PATCH] Upgrade react-select (#7824) * Upgrade react-select * figma 1-1 * Style X * Fixes timezone select --- apps/web/package.json | 4 +- packages/ui/components/form/select/Select.tsx | 129 ++++++++--------- .../ui/components/form/select/components.tsx | 137 +++--------------- packages/ui/components/form/select/index.ts | 4 +- .../components/form/select/select.stories.mdx | 10 +- .../ui/components/form/select/selectTheme.ts | 25 ++++ .../form/timezone-select/TimezoneSelect.tsx | 52 ++++++- packages/ui/package.json | 2 +- 8 files changed, 163 insertions(+), 200 deletions(-) create mode 100644 packages/ui/components/form/select/selectTheme.ts diff --git a/apps/web/package.json b/apps/web/package.json index 848b8d4cd9..574ccf9177 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -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", diff --git a/packages/ui/components/form/select/Select.tsx b/packages/ui/components/form/select/Select.tsx index 9e60e13875..3d02e18932 100644 --- a/packages/ui/components/form/select/Select.tsx +++ b/packages/ui/components/form/select/Select.tsx @@ -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 = GroupBase > = Props; -export const getReactSelectProps = < - Option, - IsMulti extends boolean = false, - Group extends GroupBase = GroupBase ->({ - className, - components, - menuPlacement = "auto", -}: { - className?: string; - components: SelectComponentsConfig; - 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 = GroupBase >({ - className, components, - styles, + menuPlacement, ...props }: SelectProps) => { const reactSelectProps = React.useMemo(() => { return getReactSelectProps({ - 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 ( 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; - -export const IconLeading = ({ icon, children, ...props }: IconLeadingProps) => { - return ( - - {icon} - {children} - - ); -}; - export const SelectField = function SelectField< Option, IsMulti extends boolean = false, diff --git a/packages/ui/components/form/select/components.tsx b/packages/ui/components/form/select/components.tsx index 54313a6910..bab1badbbd 100644 --- a/packages/ui/components/form/select/components.tsx +++ b/packages/ui/components/form/select/components.tsx @@ -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 = GroupBase >({ - className, ...props }: OptionProps) => { return ( - - <> + // This gets styled in the select classNames prop now - handles overrides with styles vs className here doesnt + + {props.label} {(props.data as unknown as ExtendedOption).needsUpgrade && } {props.isSelected && } - > + ); }; -export const ControlComponent = < - Option, - IsMulti extends boolean = false, - Group extends GroupBase = GroupBase ->({ - className, - ...props -}: ControlProps) => ( - -); +// 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; -export const SingleValueComponent = < - Option, - IsMulti extends boolean = false, - Group extends GroupBase = GroupBase ->({ - className, - ...props -}: SingleValueProps) => ( - -); - -export const ValueContainerComponent = < - Option, - IsMulti extends boolean = false, - Group extends GroupBase = GroupBase ->({ - className, - ...props -}: ValueContainerProps) => ( - -); - -export const MultiValueComponent = < - Option, - IsMulti extends boolean = false, - Group extends GroupBase = GroupBase ->({ - className, - ...props -}: MultiValueProps) => ( - -); - -export const MenuComponent = < - Option, - IsMulti extends boolean = false, - Group extends GroupBase = GroupBase ->({ - className, - ...props -}: MenuProps) => ( - -); - -export const MenuListComponent = < - Option, - IsMulti extends boolean = false, - Group extends GroupBase = GroupBase ->({ - className, - ...props -}: MenuListProps) => ( - -); +export const IconLeading = ({ icon, children, ...props }: IconLeadingProps) => { + return ( + + {icon} + {children} + + ); +}; diff --git a/packages/ui/components/form/select/index.ts b/packages/ui/components/form/select/index.ts index 5ac3f2514a..ab4144baef 100644 --- a/packages/ui/components/form/select/index.ts +++ b/packages/ui/components/form/select/index.ts @@ -1 +1,3 @@ -export { SelectWithValidation, SelectField, getReactSelectProps, Select } from "./Select"; +export { SelectWithValidation, SelectField, Select } from "./Select"; + +export { getReactSelectProps } from "./selectTheme"; diff --git a/packages/ui/components/form/select/select.stories.mdx b/packages/ui/components/form/select/select.stories.mdx index 28471a0718..ed33a3257a 100644 --- a/packages/ui/components/form/select/select.stories.mdx +++ b/packages/ui/components/form/select/select.stories.mdx @@ -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 = [ The difference between the types are when they are filled. }> - + - + - + - + = GroupBase +>({ + components, + menuPlacement = "auto", +}: { + components: SelectComponentsConfig; + menuPlacement?: MenuPlacement; +}) => { + return { + menuPlacement, + components: { + Input: InputComponent, + Option: OptionComponent, + ...components, + }, + unstyled: true, + }; +}; diff --git a/packages/ui/components/form/timezone-select/TimezoneSelect.tsx b/packages/ui/components/form/timezone-select/TimezoneSelect.tsx index 9d9a0542d2..aa622f6902 100644 --- a/packages/ui/components/form/timezone-select/TimezoneSelect.tsx +++ b/packages/ui/components/form/timezone-select/TimezoneSelect.tsx @@ -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 ( {(option as ITimezoneOption).value}} 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", + }} /> ); } diff --git a/packages/ui/package.json b/packages/ui/package.json index f0a84f471d..0c0db24515 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -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": "*",
{(option as ITimezoneOption).value}