2
0

first commit

This commit is contained in:
2024-08-09 00:39:27 +02:00
commit 79688abe2e
5698 changed files with 497838 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
/* eslint-disable playwright/missing-playwright-await */
import { render, fireEvent } from "@testing-library/react";
import React from "react";
import { vi } from "vitest";
import { CheckboxField } from "./Checkbox";
const basicProps = { label: "Test Label", description: "Test Description" };
describe("Tests for CheckboxField component", () => {
test("Should render the label and the description correctly", () => {
const { getByText } = render(<CheckboxField {...basicProps} />);
const labelElement = getByText("Test Label");
expect(labelElement).toBeInTheDocument();
const descriptionElement = getByText("Test Description");
expect(descriptionElement).toBeInTheDocument();
});
test("Should render the description correctly when the prop descriptionAsLabel is true", () => {
const { getByText } = render(<CheckboxField {...basicProps} descriptionAsLabel />);
const descriptionElement = getByText("Test Label");
expect(descriptionElement).toBeInTheDocument();
});
test("Should trigger onChange event correctly", () => {
const handleChange = vi.fn();
const { getByRole } = render(<CheckboxField {...basicProps} onChange={handleChange} />);
const checkboxInput = getByRole("checkbox");
fireEvent.click(checkboxInput);
expect(handleChange).toHaveBeenCalled();
});
test("Should disable the checkbox when disabled prop is true", () => {
const { getByRole } = render(<CheckboxField {...basicProps} disabled />);
const checkboxInput = getByRole("checkbox");
expect(checkboxInput).toBeDisabled();
});
test("Should change the checked state when clicked", () => {
const { getByRole } = render(<CheckboxField {...basicProps} disabled />);
const checkboxInput = getByRole("checkbox");
expect(checkboxInput).not.toBeChecked();
expect(checkboxInput).toBeTruthy();
fireEvent.click(checkboxInput);
expect(checkboxInput).toBeChecked();
expect(checkboxInput).toBeTruthy();
fireEvent.click(checkboxInput);
expect(checkboxInput).not.toBeChecked();
});
});

View File

@@ -0,0 +1,115 @@
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
import { useId } from "@radix-ui/react-id";
import type { InputHTMLAttributes } from "react";
import React, { forwardRef } from "react";
import classNames from "@calcom/lib/classNames";
import { Icon } from "@calcom/ui";
type Props = InputHTMLAttributes<HTMLInputElement> & {
label?: React.ReactNode;
description: string;
descriptionAsLabel?: boolean;
informationIconText?: string;
error?: boolean;
className?: string;
descriptionClassName?: string;
/**
* Accepts this special property instead of allowing description itself to be accidentally used in dangerous way.
*/
descriptionAsSafeHtml?: string;
};
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
className={classNames(
"border-default data-[state=checked]:bg-brand-default data-[state=checked]:text-brand peer h-4 w-4 shrink-0 rounded-[4px] border ring-offset-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed",
className
)}
{...props}>
<CheckboxPrimitive.Indicator className={classNames("flex items-center justify-center text-current")}>
<Icon name="check" className="h-4 w-4" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
const CheckboxField = forwardRef<HTMLInputElement, Props>(
({ label, description, error, disabled, descriptionAsSafeHtml, ...rest }, ref) => {
const descriptionAsLabel = !label || rest.descriptionAsLabel;
const id = useId();
return (
<div className="block items-center sm:flex">
{label && (
<div className="min-w-48 mb-4 sm:mb-0">
{React.createElement(
descriptionAsLabel ? "div" : "label",
{
className: classNames("flex text-sm font-medium text-emphasis"),
...(!descriptionAsLabel
? {
htmlFor: rest.id ? rest.id : id,
}
: {}),
},
label
)}
</div>
)}
<div className="w-full">
<div className="relative flex items-center">
{React.createElement(
descriptionAsLabel ? "label" : "div",
{
className: classNames(
"relative flex items-start",
!error && descriptionAsLabel ? "text-emphasis" : "text-emphasis",
error && "text-error"
),
},
<>
<div className="flex h-5 items-center">
<input
{...rest}
ref={ref}
type="checkbox"
disabled={disabled}
id={rest.id ? rest.id : id}
className={classNames(
"text-emphasis focus:ring-emphasis dark:text-muted border-default bg-default focus:bg-default active:bg-default h-4 w-4 rounded checked:hover:bg-gray-600 focus:outline-none focus:ring-0 ltr:mr-2 rtl:ml-2",
!error && disabled
? "cursor-not-allowed bg-gray-300 checked:bg-gray-300 hover:bg-gray-300 hover:checked:bg-gray-300"
: "hover:bg-subtle hover:border-emphasis checked:bg-gray-800",
error &&
"border-error hover:bg-error hover:border-error checked:bg-darkerror checked:hover:border-error checked:hover:bg-darkerror",
rest.className
)}
/>
</div>
{descriptionAsSafeHtml ? (
<span
className={classNames("text-sm", rest.descriptionClassName)}
dangerouslySetInnerHTML={{
__html: descriptionAsSafeHtml,
}}
/>
) : (
<span className={classNames("text-sm", rest.descriptionClassName)}>{description}</span>
)}
</>
)}
{/* {informationIconText && <InfoBadge content={informationIconText}></InfoBadge>} */}
</div>
</div>
</div>
);
}
);
CheckboxField.displayName = "CheckboxField";
export { Checkbox, CheckboxField };

View File

@@ -0,0 +1,143 @@
import type { Dispatch, SetStateAction } from "react";
import React from "react";
import type {
GroupBase,
OptionProps,
MultiValueProps,
MultiValue as MultiValueType,
SingleValue,
} from "react-select";
import { components } from "react-select";
import type { Props } from "react-select";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Select } from "../select";
export type Option = {
value: string;
label: string;
};
const InputOption: React.FC<OptionProps<Option, boolean, GroupBase<Option>>> = ({
isDisabled,
isFocused,
isSelected,
children,
innerProps,
...rest
}) => {
const props = {
...innerProps,
};
return (
<components.Option
{...rest}
isDisabled={isDisabled}
isFocused={isFocused}
isSelected={isSelected}
innerProps={props}>
<input
type="checkbox"
className="text-emphasis focus:ring-emphasis dark:text-muted border-default h-4 w-4 rounded ltr:mr-2 rtl:ml-2"
checked={isSelected}
readOnly
/>
{children}
</components.Option>
);
};
type MultiSelectionCheckboxesProps = {
options: { label: string; value: string }[];
setSelected: Dispatch<SetStateAction<Option[]>>;
selected: Option[];
setValue: (s: Option[]) => unknown;
countText?: string;
};
const MultiValue = ({
index,
getValue,
countText,
}: {
index: number;
getValue: () => readonly Option[];
countText: string;
}) => {
const { t } = useLocale();
const count = getValue().filter((option) => option.value !== "all").length;
return <>{!index && count !== 0 && <div>{t(countText, { count })}</div>}</>;
};
export default function MultiSelectCheckboxes({
options,
isLoading,
selected,
setSelected,
setValue,
className,
isDisabled,
countText,
}: Omit<Props, "options"> & MultiSelectionCheckboxesProps) {
const additonalComponents = {
MultiValue: (props: MultiValueProps<Option, boolean, GroupBase<Option>>) => (
<MultiValue {...props} countText={countText || "selected"} />
),
};
const allOptions = [{ label: "Select all", value: "all" }, ...options];
const allSelected = selected.length === options.length ? allOptions : selected;
return (
<Select
value={allSelected}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
onChange={(s: MultiValueType<Option> | SingleValue<Option>, event: any) => {
const allSelected = [];
if (s !== null && Array.isArray(s) && s.length > 0) {
if (s.find((option) => option.value === "all")) {
if (event.action === "select-option") {
allSelected.push(...[{ label: "Select all", value: "all" }, ...options]);
} else {
allSelected.push(...s.filter((option) => option.value !== "all"));
}
} else {
if (s.length === options.length) {
if (s.find((option) => option.value === "all")) {
allSelected.push(...s.filter((option) => option.value !== "all"));
} else {
if (event.action === "select-option") {
allSelected.push(...[...s, { label: "Select all", value: "all" }]);
}
}
} else {
allSelected.push(...s);
}
}
}
setSelected(allSelected);
setValue(allSelected);
}}
variant="checkbox"
options={allOptions.length > 1 ? allOptions : []}
isMulti
isDisabled={isDisabled}
className={classNames(className ? className : "w-64 text-sm")}
isSearchable={true}
closeMenuOnSelect={false}
hideSelectedOptions={false}
isLoading={isLoading}
data-testid="multi-select-check-boxes"
components={{
...additonalComponents,
Option: InputOption,
}}
/>
);
}

View File

@@ -0,0 +1,99 @@
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
import {
Examples,
Example,
Note,
Title,
CustomArgsTable,
VariantsTable,
VariantRow,
} from "@calcom/storybook/components";
import { CheckboxField as Checkbox } from "./Checkbox";
<Meta title="UI/Form/Checkbox" component={Checkbox} />
<Title title="Checkbox " suffix="Brief" subtitle="Version 2.0 — Last Update: 22 Aug 2022" />
## Definition
Checkboxes are used in forms and databases to indicate an answer to a question, apply a batch of settings or allow the user to make a multi-selection from a list. Alternatively, a single checkbox may be used for making single selections
## Structure
<CustomArgsTable of={Checkbox} />
<Examples title="Checkbox style">
<Example title="Default">
<Checkbox label="Default" />
</Example>
<Example title="Error">
<Checkbox label="Error" error />
</Example>
<Example title="Disabled">
<Checkbox label="Disabled" disabled />
</Example>
<Example title="Disabled">
<Checkbox label="Disabled Checked" checked disabled />
</Example>
</Examples>
<Examples title="Description As Label">
<Example title="Default">
<Checkbox descriptionAsLabel description="Default Description" />
</Example>
<Example title="Error">
<Checkbox descriptionAsLabel description="Default Description" error />
</Example>
<Example title="Disabled">
<Checkbox descriptionAsLabel description="Default Description" disabled />
</Example>
<Example title="Disabled">
<Checkbox descriptionAsLabel description="Default Description" disabled checked />
</Example>
</Examples>
<Canvas>
<Story
name="Checkbox"
args={{
label: "Default",
description: "Default Description",
error: false,
disabled: false,
}}
argTypes={{
label: {
control: {
type: "text",
},
},
description: {
control: {
type: "text",
},
},
error: {
control: {
type: "boolean",
},
},
disabled: {
control: {
type: "boolean",
},
},
}}>
{({ label, description, error, disabled }) => (
<VariantsTable titles={[""]} columnMinWidth={150}>
<VariantRow variant="Default">
<Checkbox label={label} error={error} disabled={disabled} />
</VariantRow>
<VariantRow variant="Description As Label">
<Checkbox description={description} error={error} disabled={disabled} descriptionAsLabel />
</VariantRow>
</VariantsTable>
)}
</Story>
</Canvas>

View File

@@ -0,0 +1,3 @@
export { Checkbox, CheckboxField } from "./Checkbox";
export { default as MultiSelectCheckbox } from "./MultiSelectCheckboxes";
export type { Option } from "./MultiSelectCheckboxes";