* WIP * Create Booking Questions builder * Renaming things * wip * wip * Implement Add Guests and other fixes * Fixes after testing * Fix wrong status code 404 * Fixes * Lint fixes * Self review comments addressed * More self review comments addressed * Feedback from zomars * BugFixes after testing * More fixes discovered during review * Update packages/lib/hooks/useHasPaidPlan.ts Co-authored-by: Omar López <zomars@me.com> * More fixes discovered during review * Update packages/ui/components/form/inputs/Input.tsx Co-authored-by: Omar López <zomars@me.com> * More fixes discovered during review * Update packages/features/bookings/lib/getBookingFields.ts Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> * More PR review fixes * Hide label using labelSrOnly * Fix Carinas feedback and implement 2 workflows thingy * Misc fixes * Fixes from Loom comments and PR * Fix a lint errr * Fix cancellation reason * Fix regression in edit due to name conflict check * Update packages/features/form-builder/FormBuilder.tsx Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> * Fix options not set when default value is used * Restoring reqBody to avoid uneeded conflicts with main * Type fix * Update apps/web/components/booking/pages/BookingPage.tsx Co-authored-by: Omar López <zomars@me.com> * Update packages/features/form-builder/FormBuilder.tsx Co-authored-by: Omar López <zomars@me.com> * Update apps/web/components/booking/pages/BookingPage.tsx Co-authored-by: Omar López <zomars@me.com> * Apply suggestions from code review Co-authored-by: Omar López <zomars@me.com> * Show fields but mark them disabled * Apply suggestions from code review Co-authored-by: Omar López <zomars@me.com> * More comments * Fix booking success page crash when a booking doesnt have newly added required fields response * Dark theme asterisk not visible * Make location required in zodSchema as was there in production * Linting * Remove _metadata.ts files for apps that have config.json * Revert "Remove _metadata.ts files for apps that have config.json" This reverts commit d79bdd336cf312a30a8943af94c059947bd91ccd. * Fix lint error * Fix missing condition for samlSPConfig * Delete unexpectedly added file * yarn.lock change not required * fix types * Make checkboxes rounded * Fix defaultLabel being stored as label due to SSR rendering * Shaved 16kb from booking page * Explicit types for profile * Show payment value only if price is greater than 0 * Fix type error * Add back inferred types as they are failing * Fix duplicate label on number --------- Co-authored-by: zomars <zomars@me.com> Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com> Co-authored-by: Efraín Rochín <roae.85@gmail.com>
219 lines
5.6 KiB
TypeScript
219 lines
5.6 KiB
TypeScript
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 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";
|
|
|
|
export type SelectProps<
|
|
Option,
|
|
IsMulti extends boolean = false,
|
|
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 h-[36px] 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,
|
|
...props
|
|
}: SelectProps<Option, IsMulti, Group>) => {
|
|
const reactSelectProps = React.useMemo(() => {
|
|
return getReactSelectProps<Option, IsMulti, Group>({
|
|
className,
|
|
components: components || {},
|
|
});
|
|
}, [className, components]);
|
|
|
|
return (
|
|
<ReactSelect
|
|
{...reactSelectProps}
|
|
{...props}
|
|
styles={{
|
|
option: (defaultStyles, state) => ({
|
|
...defaultStyles,
|
|
backgroundColor: state.isSelected
|
|
? state.isFocused
|
|
? "var(--brand-color)"
|
|
: "var(--brand-color)"
|
|
: state.isFocused
|
|
? "var(--brand-color-dark-mode)"
|
|
: "var(--brand-text-color)",
|
|
}),
|
|
...styles,
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
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,
|
|
Group extends GroupBase<Option> = GroupBase<Option>
|
|
>(
|
|
props: {
|
|
required?: boolean;
|
|
name?: string;
|
|
containerClassName?: string;
|
|
label?: string;
|
|
labelProps?: React.ComponentProps<typeof Label>;
|
|
className?: string;
|
|
error?: string;
|
|
} & SelectProps<Option, IsMulti, Group>
|
|
) {
|
|
const { t } = useLocale();
|
|
const { label = t(props.name || ""), containerClassName, labelProps, className, ...passThrough } = props;
|
|
const id = useId();
|
|
return (
|
|
<div className={classNames(containerClassName)}>
|
|
<div className={classNames(className)}>
|
|
{!!label && (
|
|
<Label htmlFor={id} {...labelProps} className={classNames(props.error && "text-red-900")}>
|
|
{label}
|
|
</Label>
|
|
)}
|
|
</div>
|
|
<Select {...passThrough} />
|
|
</div>
|
|
);
|
|
};
|
|
|
|
/**
|
|
* TODO: It should replace Select after through testing
|
|
*/
|
|
export function SelectWithValidation<
|
|
Option extends { label: string; value: string },
|
|
IsMulti extends boolean = false,
|
|
Group extends GroupBase<Option> = GroupBase<Option>
|
|
>({
|
|
required = false,
|
|
onChange,
|
|
value,
|
|
...remainingProps
|
|
}: SelectProps<Option, IsMulti, Group> & { required?: boolean }) {
|
|
const [hiddenInputValue, _setHiddenInputValue] = React.useState(() => {
|
|
if (value instanceof Array || !value) {
|
|
return;
|
|
}
|
|
return value.value || "";
|
|
});
|
|
|
|
const setHiddenInputValue = React.useCallback((value: MultiValue<Option> | SingleValue<Option>) => {
|
|
let hiddenInputValue = "";
|
|
if (value instanceof Array) {
|
|
hiddenInputValue = value.map((val) => val.value).join(",");
|
|
} else {
|
|
hiddenInputValue = value?.value || "";
|
|
}
|
|
_setHiddenInputValue(hiddenInputValue);
|
|
}, []);
|
|
|
|
React.useEffect(() => {
|
|
if (!value) {
|
|
return;
|
|
}
|
|
setHiddenInputValue(value);
|
|
}, [value, setHiddenInputValue]);
|
|
|
|
return (
|
|
<div className={classNames("relative", remainingProps.className)}>
|
|
<Select
|
|
value={value}
|
|
{...remainingProps}
|
|
onChange={(value, ...remainingArgs) => {
|
|
setHiddenInputValue(value);
|
|
if (onChange) {
|
|
onChange(value, ...remainingArgs);
|
|
}
|
|
}}
|
|
/>
|
|
{required && (
|
|
<input
|
|
tabIndex={-1}
|
|
autoComplete="off"
|
|
style={{
|
|
opacity: 0,
|
|
width: "100%",
|
|
height: 1,
|
|
position: "absolute",
|
|
}}
|
|
value={hiddenInputValue}
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
onChange={() => {}}
|
|
// TODO:Not able to get focus to work
|
|
// onFocus={() => selectRef.current?.focus()}
|
|
required={required}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|