refactor(ui): remove unused components and exports (#26222)
Co-authored-by: Volnei Munhoz <volnei@cal.com> Co-authored-by: Keith Williams <keithwillcode@gmail.com>
This commit is contained in:
co-authored by
Volnei Munhoz
Keith Williams
parent
c7a80abcba
commit
18b041514f
@@ -1,215 +0,0 @@
|
||||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { render, fireEvent, screen } from "@testing-library/react";
|
||||
import * as React from "react";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { expect, vi } from "vitest";
|
||||
|
||||
import PhoneInput from "@calcom/features/components/phone-input/PhoneInput";
|
||||
|
||||
import { getBookingFieldsWithSystemFields } from "../../../lib/getBookingFields";
|
||||
import { BookingFields } from "./BookingFields";
|
||||
|
||||
// Mock PhoneInput to avoid calling the lazy import
|
||||
vi.mock("@calcom/features/components/phone-input", () => {
|
||||
return {
|
||||
default: PhoneInput,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/ui/components/address", async (originalImport) => {
|
||||
const { AddressInputNonLazy } = (await originalImport()) as Record<string, unknown>;
|
||||
// Dynamic imports of Components are not supported in Vitest. So, we use the non-lazy version of the components
|
||||
return {
|
||||
AddressInput: AddressInputNonLazy,
|
||||
};
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type FormMethods = UseFormReturn<any>;
|
||||
|
||||
// Add tRPC mock before tests
|
||||
vi.mock("@calcom/trpc/react", () => ({
|
||||
trpc: {
|
||||
viewer: {
|
||||
public: {
|
||||
countryCode: {
|
||||
useQuery: () => ({
|
||||
data: { countryCode: "US" },
|
||||
isLoading: false,
|
||||
error: null,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const renderComponent = ({
|
||||
props: props,
|
||||
formDefaultValues,
|
||||
}: {
|
||||
props: Parameters<typeof BookingFields>[0];
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
formDefaultValues?: any;
|
||||
}) => {
|
||||
let formMethods: UseFormReturn | undefined;
|
||||
const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
||||
const form = useForm({
|
||||
defaultValues: formDefaultValues,
|
||||
});
|
||||
formMethods = form;
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<FormProvider {...form}>{children}</FormProvider>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
const result = render(<BookingFields {...props} />, { wrapper: Wrapper });
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return { result, formMethods: formMethods! };
|
||||
};
|
||||
|
||||
describe("BookingFields", () => {
|
||||
it("should correctly render with location fields", () => {
|
||||
const AttendeePhoneNumberOption = {
|
||||
label: "attendee_phone_number",
|
||||
value: "phone",
|
||||
};
|
||||
|
||||
const OrganizerLinkOption = {
|
||||
label: "https://google.com",
|
||||
value: "link",
|
||||
};
|
||||
|
||||
const locations = [
|
||||
{
|
||||
type: AttendeePhoneNumberOption.value,
|
||||
},
|
||||
{
|
||||
link: "https://google.com",
|
||||
type: OrganizerLinkOption.value,
|
||||
displayLocationPublicly: true,
|
||||
},
|
||||
];
|
||||
const { formMethods } = renderComponent({
|
||||
props: {
|
||||
fields: getBookingFieldsWithSystemFields({
|
||||
disableGuests: false,
|
||||
bookingFields: [],
|
||||
metadata: null,
|
||||
workflows: [],
|
||||
customInputs: [],
|
||||
}),
|
||||
locations,
|
||||
isDynamicGroupBooking: false,
|
||||
bookingData: null,
|
||||
},
|
||||
formDefaultValues: {},
|
||||
});
|
||||
|
||||
component.fillName({ value: "John Doe" });
|
||||
component.fillEmail({ value: "john.doe@example.com" });
|
||||
component.fillNotes({ value: "This is a note" });
|
||||
expectScenarios.expectNameToBe({ value: "John Doe", formMethods });
|
||||
expectScenarios.expectEmailToBe({ value: "john.doe@example.com", formMethods });
|
||||
expectScenarios.expectNotesToBe({ value: "This is a note", formMethods });
|
||||
|
||||
component.fillRadioInputLocation({ label: AttendeePhoneNumberOption.label, inputValue: "+1234567890" });
|
||||
expectScenarios.expectLocationToBe({
|
||||
formMethods,
|
||||
label: AttendeePhoneNumberOption.label,
|
||||
toMatch: {
|
||||
formattedValue: "+1 (234) 567-890",
|
||||
value: { optionValue: "+1234567890", value: AttendeePhoneNumberOption.value },
|
||||
},
|
||||
});
|
||||
|
||||
component.fillRadioInputLocation({ label: OrganizerLinkOption.label });
|
||||
expectScenarios.expectLocationToBe({
|
||||
formMethods,
|
||||
label: OrganizerLinkOption.label,
|
||||
toMatch: {
|
||||
formattedValue: "+1 (234) 567-890",
|
||||
value: { optionValue: "", value: OrganizerLinkOption.value },
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const component = {
|
||||
getName: ({ label = "your_name" }: { label?: string } = {}) =>
|
||||
screen.getByRole("textbox", {
|
||||
name: new RegExp(label),
|
||||
}) as HTMLInputElement,
|
||||
getEmail: () => screen.getByRole("textbox", { name: /email/i }) as HTMLInputElement,
|
||||
getLocationRadioOption: ({ label }: { label: string }) =>
|
||||
screen.getByRole("radio", { name: new RegExp(label) }) as HTMLInputElement,
|
||||
getLocationRadioInput: ({ placeholder }: { placeholder: string }) =>
|
||||
screen.getByPlaceholderText(placeholder) as HTMLInputElement,
|
||||
getNotes: () => screen.getByRole("textbox", { name: /additional_notes/i }) as HTMLInputElement,
|
||||
getGuests: () => screen.getByLabelText("guests"),
|
||||
fillName: ({ value }: { value: string }) => {
|
||||
fireEvent.change(component.getName(), { target: { value } });
|
||||
},
|
||||
fillEmail: ({ value }: { value: string }) => {
|
||||
fireEvent.change(component.getEmail(), { target: { value } });
|
||||
},
|
||||
fillRadioInputLocation: ({ label, inputValue }: { label: string; inputValue?: string }) => {
|
||||
fireEvent.click(component.getLocationRadioOption({ label }));
|
||||
|
||||
if (inputValue) {
|
||||
let placeholder = label;
|
||||
if (label === "attendee_phone_number") {
|
||||
placeholder = "enter_phone_number";
|
||||
} else {
|
||||
// radioInput doesn't have a label, so we need to identify by placeholder
|
||||
throw new Error("Tell me how to identify the placeholder for this location input");
|
||||
}
|
||||
fireEvent.change(component.getLocationRadioInput({ placeholder }), {
|
||||
target: { value: inputValue },
|
||||
});
|
||||
}
|
||||
},
|
||||
fillNotes: ({ value }: { value: string }) => {
|
||||
fireEvent.change(component.getNotes(), { target: { value } });
|
||||
},
|
||||
};
|
||||
|
||||
const expectScenarios = {
|
||||
expectNameToBe: ({ value, formMethods }: { value: string; formMethods: FormMethods }) => {
|
||||
expect(component.getName().value).toEqual(value);
|
||||
expect(formMethods.getValues("responses.name")).toEqual(value);
|
||||
},
|
||||
expectEmailToBe: ({ value, formMethods }: { value: string; formMethods: FormMethods }) => {
|
||||
expect(component.getEmail().value).toEqual(value);
|
||||
expect(formMethods.getValues("responses.email")).toEqual(value);
|
||||
},
|
||||
expectLocationToBe: ({
|
||||
formMethods,
|
||||
label,
|
||||
toMatch: { formattedValue, value },
|
||||
}: {
|
||||
label: string;
|
||||
toMatch: {
|
||||
formattedValue?: string;
|
||||
value: {
|
||||
optionValue: string;
|
||||
value: string;
|
||||
};
|
||||
};
|
||||
formMethods: FormMethods;
|
||||
}) => {
|
||||
expect(component.getLocationRadioOption({ label }).checked).toBe(true);
|
||||
if (value.optionValue) {
|
||||
expect(component.getLocationRadioInput({ placeholder: "enter_phone_number" }).value).toEqual(
|
||||
formattedValue
|
||||
);
|
||||
}
|
||||
expect(formMethods.getValues("responses.location")).toEqual(value);
|
||||
},
|
||||
expectNotesToBe: ({ value, formMethods }: { value: string; formMethods: FormMethods }) => {
|
||||
expect(component.getNotes().value).toEqual(value);
|
||||
expect(formMethods.getValues("responses.notes")).toEqual(value);
|
||||
},
|
||||
};
|
||||
@@ -1,3 +1,2 @@
|
||||
export { default as AddressInput } from "./AddressInputLazy";
|
||||
export { default as MultiEmail } from "./MultiEmailLazy";
|
||||
export { default as AddressInputNonLazy } from "./AddressInput";
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { Badge } from "./Badge";
|
||||
|
||||
export const UpgradeOrgsBadge = function UpgradeOrgsBadge() {
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<Tooltip content={t("orgs_upgrade_to_enable_feature")}>
|
||||
<a href="https://cal.com/enterprise" target="_blank">
|
||||
<Badge variant="gray">{t("upgrade")}</Badge>
|
||||
</a>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,5 @@
|
||||
export { Badge } from "./Badge";
|
||||
export { UpgradeTeamsBadge } from "./UpgradeTeamsBadge";
|
||||
export { CreditsBadge } from "./CreditsBadge";
|
||||
export { UpgradeOrgsBadge } from "./UpgradeOrgsBadge";
|
||||
export { InfoBadge } from "./InfoBadge";
|
||||
export type { BadgeProps } from "./Badge";
|
||||
|
||||
@@ -4,13 +4,9 @@ export {
|
||||
DropdownItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuTriggerItem,
|
||||
} from "./Dropdown";
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
export { Checkbox, MultiSelectCheckbox, CheckboxField } from "./checkbox";
|
||||
export type { Option as MultiSelectCheckboxesOptionType } from "./checkbox";
|
||||
export { HintsOrErrors } from "./inputs/HintOrErrors";
|
||||
export {
|
||||
EmailField,
|
||||
EmailInput,
|
||||
FieldsetLegend,
|
||||
InputGroupBox,
|
||||
InputLeading,
|
||||
PasswordField,
|
||||
TextArea,
|
||||
TextAreaField,
|
||||
@@ -15,8 +11,6 @@ export {
|
||||
} from "./inputs/Input";
|
||||
|
||||
export { MultiOptionInput } from "./inputs/MultiOptionInput";
|
||||
|
||||
export { InputFieldWithSelect } from "./inputs/InputFieldWithSelect";
|
||||
export type { InputFieldProps, InputProps } from "./inputs/types";
|
||||
export { InputField, Input, TextField, inputStyles } from "./inputs/TextField";
|
||||
export { InputError } from "./inputs/InputError";
|
||||
@@ -25,12 +19,9 @@ export { Label } from "./inputs/Label";
|
||||
export { Select, SelectField, SelectWithValidation, getReactSelectProps } from "./select";
|
||||
|
||||
export { DateRangePickerLazy as DateRangePicker } from "./date-range-picker";
|
||||
export { Slider } from "./slider";
|
||||
export { RangeSlider } from "./slider/RangeSlider";
|
||||
export { RangeSliderPopover } from "./slider/RangeSliderPopover";
|
||||
export { BooleanToggleGroup, BooleanToggleGroupField, ToggleGroup } from "./toggleGroup";
|
||||
export { DatePicker } from "./datepicker";
|
||||
export { FormStep, Steps, Stepper } from "./step";
|
||||
export { Steps } from "./step";
|
||||
export { WizardForm } from "./wizard";
|
||||
export { default as ColorPicker } from "./color-picker/colorpicker";
|
||||
export { SettingsToggle, Switch } from "./switch";
|
||||
|
||||
@@ -14,15 +14,6 @@ import { Input, InputField, inputStyles } from "../inputs/TextField";
|
||||
import { Label } from "./Label";
|
||||
import type { InputFieldProps } from "./types";
|
||||
|
||||
export function InputLeading(props: JSX.IntrinsicElements["div"]) {
|
||||
return (
|
||||
<span className="bg-cal-muted border-default text-subtle inline-flex shrink-0 items-center rounded-l-sm border px-3 ltr:border-r-0 rtl:border-l-0 sm:text-sm sm:leading-4">
|
||||
{props.children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export const PasswordField = forwardRef<HTMLInputElement, InputFieldProps>(function PasswordField(
|
||||
props,
|
||||
ref
|
||||
@@ -149,24 +140,6 @@ export const TextAreaField = forwardRef<HTMLTextAreaElement, TextAreaFieldProps>
|
||||
);
|
||||
});
|
||||
|
||||
export function FieldsetLegend(props: JSX.IntrinsicElements["legend"]) {
|
||||
return (
|
||||
<legend {...props} className={classNames("text-default text-sm font-medium leading-4", props.className)}>
|
||||
{props.children}
|
||||
</legend>
|
||||
);
|
||||
}
|
||||
|
||||
export function InputGroupBox(props: JSX.IntrinsicElements["div"]) {
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
className={classNames("bg-default border-default stack-y-2 rounded-sm border p-2", props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const NumberInput = forwardRef<HTMLInputElement, InputFieldProps>(function NumberInput(props, ref) {
|
||||
return (
|
||||
<Input
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import React, { forwardRef } from "react";
|
||||
|
||||
import { UnstyledSelect } from "../../address/Select";
|
||||
import { InputField } from "./TextField";
|
||||
import type { InputFieldProps } from "./types";
|
||||
|
||||
export const InputFieldWithSelect = forwardRef<
|
||||
HTMLInputElement,
|
||||
InputFieldProps & { selectProps: typeof UnstyledSelect }
|
||||
>(function EmailField(props, ref) {
|
||||
return (
|
||||
<InputField
|
||||
ref={ref}
|
||||
{...props}
|
||||
inputIsFullWidth={false}
|
||||
addOnClassname="px-0!"
|
||||
addOnSuffix={<UnstyledSelect {...props.selectProps} />}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -1,11 +1,8 @@
|
||||
/* eslint-disable playwright/missing-playwright-await */
|
||||
import { TooltipProvider } from "@radix-ui/react-tooltip";
|
||||
import { render, fireEvent } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { UnstyledSelect } from "../../address/Select";
|
||||
import { EmailField, TextAreaField, PasswordField, NumberInput, FilterSearchField } from "./Input";
|
||||
import { InputFieldWithSelect } from "./InputFieldWithSelect";
|
||||
import { InputField } from "./TextField";
|
||||
|
||||
const onChangeMock = vi.fn();
|
||||
@@ -117,31 +114,6 @@ describe("Tests for TextAreaField Component", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for InputFieldWithSelect Component", () => {
|
||||
test("Should render correctly with InputField and UnstyledSelect", () => {
|
||||
const onChangeMock = vi.fn();
|
||||
|
||||
const selectProps = {
|
||||
value: null,
|
||||
onChange: onChangeMock,
|
||||
name: "testSelect",
|
||||
options: [
|
||||
{ value: "Option 1", label: "Option 1" },
|
||||
{ value: "Option 2", label: "Option 2" },
|
||||
{ value: "Option 3", label: "Option 3" },
|
||||
],
|
||||
} as unknown as typeof UnstyledSelect;
|
||||
|
||||
const { getByText } = render(<InputFieldWithSelect selectProps={selectProps} label="testSelect" />);
|
||||
|
||||
const inputElement = getByText("Select...");
|
||||
fireEvent.mouseDown(inputElement);
|
||||
|
||||
const optionElement = getByText("Option 1");
|
||||
expect(optionElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Tests for NumberInput Component", () => {
|
||||
test("Should render correctly with input type number", () => {
|
||||
const { getByRole } = render(<NumberInput name="numberInput" />);
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
import * as React from "react";
|
||||
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
|
||||
const RangeSlider = React.forwardRef<
|
||||
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SliderPrimitive.Root
|
||||
ref={ref}
|
||||
className={classNames("relative flex w-full touch-none select-none items-center", className)}
|
||||
{...props}>
|
||||
<SliderPrimitive.Track className="bg-subtle border-subtle relative h-2 w-full grow overflow-hidden rounded-full border">
|
||||
<SliderPrimitive.Range className="bg-brand-default absolute h-full" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="border-subtle bg-brand-default ring-offset-background focus-visible:ring-ring block h-4 w-4 rounded-full border-2 border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
<SliderPrimitive.Thumb className="border-subtle bg-brand-default ring-offset-background focus-visible:ring-ring block h-4 w-4 rounded-full border-2 border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
));
|
||||
RangeSlider.displayName = SliderPrimitive.Root.displayName;
|
||||
|
||||
export { RangeSlider };
|
||||
@@ -1,129 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { useState } from "react";
|
||||
|
||||
import { Badge } from "../../badge/Badge";
|
||||
import { Button } from "../../button/Button";
|
||||
import { inputStyles, TextField } from "../inputs/TextField";
|
||||
import { RangeSlider } from "./RangeSlider";
|
||||
|
||||
interface RangeSliderPopoverProps {
|
||||
triggerText: string;
|
||||
resetBtnText?: string;
|
||||
applyBtnText?: string;
|
||||
value: number[];
|
||||
onChange: (value: number[]) => void;
|
||||
min: number;
|
||||
max: number;
|
||||
step?: number;
|
||||
badgeVariant?: "default" | "success" | "gray" | "warning" | "orange" | "red";
|
||||
badgeSuffix?: string;
|
||||
inputSuffix?: string;
|
||||
inputLeading?: string;
|
||||
}
|
||||
|
||||
export const RangeSliderPopover = ({
|
||||
resetBtnText = "Reset",
|
||||
applyBtnText = "Apply",
|
||||
triggerText,
|
||||
value,
|
||||
onChange,
|
||||
min,
|
||||
max,
|
||||
step = 1,
|
||||
badgeVariant = "default",
|
||||
badgeSuffix,
|
||||
inputSuffix,
|
||||
inputLeading,
|
||||
}: RangeSliderPopoverProps) => {
|
||||
const [internalValue, setInternalValue] = useState<number[]>(value);
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const handleReset = () => {
|
||||
setInternalValue([min, max]);
|
||||
};
|
||||
|
||||
const handleApply = () => {
|
||||
onChange(internalValue);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover.Root open={open} onOpenChange={setOpen}>
|
||||
<Popover.Trigger asChild>
|
||||
<button className={inputStyles()}>
|
||||
<span className="text-subtle text-sm font-medium leading-none">{triggerText}:</span>
|
||||
{value && (
|
||||
<Badge variant={badgeVariant} className="ml-2">
|
||||
{value[0]} - {value[1]} {badgeSuffix}
|
||||
</Badge>
|
||||
)}
|
||||
</button>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content
|
||||
align="start"
|
||||
className="border-subtle shadow-dropdown bg-default z-50 mt-2 w-72 rounded-lg border px-4 pb-3 pt-4 font-(family-name:--font-sans)">
|
||||
<div className="mb-4">
|
||||
<RangeSlider
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
value={internalValue}
|
||||
onValueChange={setInternalValue}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4 flex items-center justify-between gap-2">
|
||||
<TextField
|
||||
labelSrOnly
|
||||
name="start"
|
||||
type="number"
|
||||
min={min}
|
||||
max={internalValue[1]}
|
||||
value={internalValue[0]}
|
||||
onChange={(e) => {
|
||||
const newValue = parseInt(e.target.value);
|
||||
if (!isNaN(newValue) && newValue >= min && newValue <= internalValue[1]) {
|
||||
setInternalValue([newValue, internalValue[1]]);
|
||||
}
|
||||
}}
|
||||
addOnLeading={inputLeading ? inputLeading : undefined}
|
||||
addOnSuffix={inputSuffix ? inputSuffix : undefined}
|
||||
containerClassName="w-full"
|
||||
/>
|
||||
<TextField
|
||||
labelSrOnly
|
||||
name="end"
|
||||
type="number"
|
||||
min={internalValue[0]}
|
||||
max={max}
|
||||
value={internalValue[1]}
|
||||
onChange={(e) => {
|
||||
const newValue = parseInt(e.target.value);
|
||||
if (!isNaN(newValue) && newValue >= internalValue[0] && newValue <= max) {
|
||||
setInternalValue([internalValue[0], newValue]);
|
||||
}
|
||||
}}
|
||||
addOnLeading={inputLeading ? inputLeading : undefined}
|
||||
addOnSuffix={inputSuffix ? inputSuffix : undefined}
|
||||
containerClassName="w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="-mx-4">
|
||||
<hr className="border-subtle" />
|
||||
<div className="mt-3 flex items-center justify-end gap-2 px-4">
|
||||
<Button color="minimal" size="sm" onClick={handleReset}>
|
||||
{resetBtnText}
|
||||
</Button>
|
||||
<Button color="secondary" size="sm" onClick={handleApply}>
|
||||
{applyBtnText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
);
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as SliderPrimitive from "@radix-ui/react-slider";
|
||||
import * as React from "react";
|
||||
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
|
||||
const Slider = React.forwardRef<
|
||||
React.ElementRef<typeof SliderPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SliderPrimitive.Root
|
||||
ref={ref}
|
||||
className={classNames("relative flex w-full touch-none select-none items-center", className)}
|
||||
{...props}>
|
||||
<SliderPrimitive.Track className="bg-subtle border-subtle relative h-2 w-full grow overflow-hidden rounded-full border">
|
||||
<SliderPrimitive.Range className="bg-brand-default absolute h-full" />
|
||||
</SliderPrimitive.Track>
|
||||
<SliderPrimitive.Thumb className="border-subtle bg-brand-default ring-offset-background focus-visible:ring-ring block h-4 w-4 rounded-full border-2 border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
<SliderPrimitive.Thumb className="border-subtle bg-brand-default ring-offset-background focus-visible:ring-ring block h-4 w-4 rounded-full border-2 border transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||
</SliderPrimitive.Root>
|
||||
));
|
||||
Slider.displayName = SliderPrimitive.Root.displayName;
|
||||
|
||||
export { Slider };
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
|
||||
type Props = {
|
||||
steps: number;
|
||||
currentStep: number;
|
||||
};
|
||||
|
||||
// It might be worth passing this label string from outside the component so we can translate it?
|
||||
function FormStep({ currentStep, steps }: Props) {
|
||||
return (
|
||||
<div className="w-full">
|
||||
<p className="text-muted text-xs font-medium">
|
||||
Step {currentStep} of {steps}
|
||||
</p>
|
||||
<div className="flex flex-nowrap space-x-1">
|
||||
{[...Array(steps)].map((_, j) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"h-1 w-full rounded-sm",
|
||||
currentStep - 1 >= j ? "bg-black" : "bg-gray-400"
|
||||
)}
|
||||
key={j}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FormStep;
|
||||
@@ -1,71 +0,0 @@
|
||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import Link from "next/link";
|
||||
|
||||
type DefaultStep = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
function Stepper<T extends DefaultStep>(props: {
|
||||
href: string;
|
||||
step: number;
|
||||
steps: T[];
|
||||
disableSteps?: boolean;
|
||||
stepLabel?: (currentStep: number, totalSteps: number) => string;
|
||||
}) {
|
||||
const {
|
||||
href,
|
||||
steps,
|
||||
stepLabel = (currentStep, totalSteps) => `Step ${currentStep} of ${totalSteps}`,
|
||||
} = props;
|
||||
const [stepperRef] = useAutoAnimate<HTMLOListElement>();
|
||||
return (
|
||||
<>
|
||||
{steps.length > 1 && (
|
||||
<nav className="flex items-center justify-center" aria-label="Progress">
|
||||
<p className="text-sm font-medium">{stepLabel(props.step, steps.length)}</p>
|
||||
<ol role="list" className="ml-8 flex items-center space-x-5" ref={stepperRef}>
|
||||
{steps.map((mapStep, index) => (
|
||||
<li key={mapStep.title}>
|
||||
{index + 1 < props.step ? (
|
||||
<Link
|
||||
href={props.disableSteps ? "#" : `${href}?step=${index + 1}`}
|
||||
shallow
|
||||
replace
|
||||
className="hover:bg-inverted block h-2.5 w-2.5 rounded-full bg-gray-600">
|
||||
<span className="sr-only">{mapStep.title}</span>
|
||||
</Link>
|
||||
) : index + 1 === props.step ? (
|
||||
<Link
|
||||
href={props.disableSteps ? "#" : `${href}?step=${index + 1}`}
|
||||
shallow
|
||||
replace
|
||||
className="relative flex items-center justify-center"
|
||||
aria-current="step">
|
||||
<span className="absolute flex h-5 w-5 p-px" aria-hidden="true">
|
||||
<span className="bg-emphasis h-full w-full rounded-full" />
|
||||
</span>
|
||||
<span
|
||||
className="relative block h-2.5 w-2.5 rounded-full bg-gray-600"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="sr-only">{mapStep.title}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
href={props.disableSteps ? "#" : `${href}?step=${index + 1}`}
|
||||
shallow
|
||||
replace
|
||||
className="bg-emphasis block h-2.5 w-2.5 rounded-full hover:bg-gray-400">
|
||||
<span className="sr-only">{mapStep.title}</span>
|
||||
</Link>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Stepper;
|
||||
@@ -1,3 +1 @@
|
||||
export { default as FormStep } from "./FormStep";
|
||||
export { Steps } from "./Steps";
|
||||
export { default as Stepper } from "./Stepper";
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import { cva } from "class-variance-authority";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import classNames from "@calcom/ui/classNames";
|
||||
|
||||
import { ButtonOrLink } from "../dropdown";
|
||||
import { Icon } from "../icon";
|
||||
import type { IconName } from "../icon";
|
||||
|
||||
export type NavigationItemType = {
|
||||
isLastChild?: boolean;
|
||||
isExpanded?: boolean;
|
||||
onToggle?: () => void;
|
||||
name: string;
|
||||
href?: string;
|
||||
isLoading?: boolean;
|
||||
badge?: React.ReactNode;
|
||||
icon?: IconName;
|
||||
child?: NavigationItemType[];
|
||||
onlyMobile?: boolean;
|
||||
onlyDesktop?: boolean;
|
||||
moreOnMobile?: boolean;
|
||||
isCurrent?: boolean;
|
||||
};
|
||||
|
||||
const navigationItemStyles = cva(
|
||||
"text-default group flex items-center rounded-[10px] p-2 text-sm font-medium transition hover:bg-subtle hover:text-emphasis",
|
||||
{
|
||||
variants: {
|
||||
isChild: {
|
||||
true: "[&[aria-current='page']]:text-emphasis [&[aria-current='page']]:bg-emphasis hidden h-8 ml-16 lg:flex lg:ml-10 relative before:absolute before:left-[-24px] before:-top-2 before:h-[calc(100%+0.5rem)] before:w-0.5 before:bg-subtle before:content-[''] first:before:rounded-t-full last:before:rounded-b-full",
|
||||
false: "[&[aria-current='page']]:text-emphasis mt-0.5 text-sm",
|
||||
},
|
||||
hasChild: {
|
||||
true: "aria-[aria-current='page']:bg-transparent! relative after:absolute after:left-[-24px] after:top-6 after:h-[calc(100%-1.5rem)] after:w-0.5 after:bg-subtle after:content-[''] first:after:rounded-t-full last:after:rounded-b-full",
|
||||
false: "[&[aria-current='page']]:bg-subtle",
|
||||
},
|
||||
isFirstChild: {
|
||||
true: "mt-0",
|
||||
false: "mt-px",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
isChild: false,
|
||||
hasChild: false,
|
||||
isFirstChild: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const Label = ({ children }: { children: React.ReactNode }) => {
|
||||
return <span className="text-default ml-3">{children}</span>;
|
||||
};
|
||||
|
||||
const NavigationItemComponent = ({
|
||||
item,
|
||||
isChild,
|
||||
index,
|
||||
}: {
|
||||
item: NavigationItemType;
|
||||
isChild?: boolean;
|
||||
index?: number;
|
||||
}) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<ButtonOrLink
|
||||
data-test-id={item.name}
|
||||
href={item.href}
|
||||
className={navigationItemStyles({
|
||||
isChild,
|
||||
hasChild: !!item.child,
|
||||
isFirstChild: isChild && index === 0,
|
||||
})}
|
||||
aria-current={item.isCurrent ? "page" : undefined}
|
||||
onClick={item.onToggle}>
|
||||
{item.icon && (
|
||||
<Icon
|
||||
name={item.isLoading ? "rotate-cw" : item.icon}
|
||||
className={classNames(
|
||||
"text-subtle mr-2 h-4 w-4 shrink-0 rtl:ml-2 md:ltr:mx-auto lg:ltr:mr-2",
|
||||
item.isLoading && "animate-spin"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
aria-current={item.isCurrent ? "page" : undefined}
|
||||
/>
|
||||
)}
|
||||
<span className="text-emphasis hidden w-full justify-between truncate text-ellipsis lg:flex">
|
||||
{item.name}
|
||||
{item.badge && item.badge}
|
||||
</span>
|
||||
</ButtonOrLink>
|
||||
{item.child &&
|
||||
item.isExpanded &&
|
||||
item.child.map((childItem, childIndex) => (
|
||||
<NavigationItem key={childItem.name} item={childItem} isChild index={childIndex} />
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const NavigationItem = Object.assign(NavigationItemComponent, { Label });
|
||||
@@ -5,4 +5,3 @@ export type { NavTabProps } from "./tabs/HorizontalTabs";
|
||||
export { default as VerticalTabItem } from "./tabs/VerticalTabItem";
|
||||
export type { VerticalTabItemProps } from "./tabs/VerticalTabItem";
|
||||
export { default as VerticalTabs } from "./tabs/VerticalTabs";
|
||||
export { NavigationItem } from "./NavigationItem";
|
||||
|
||||
Reference in New Issue
Block a user