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,119 @@
import { useId } from "@radix-ui/react-id";
import { Root as ToggleGroupPrimitive, Item as ToggleGroupItemPrimitive } from "@radix-ui/react-toggle-group";
import { useState } from "react";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Label } from "../../../components/form/inputs/Label";
const boolean = (yesNo: "yes" | "no") => (yesNo === "yes" ? true : yesNo === "no" ? false : undefined);
const yesNo = (boolean?: boolean) => (boolean === true ? "yes" : boolean === false ? "no" : undefined);
type VariantStyles = {
commonClass?: string;
toggleGroupPrimitiveClass?: string;
};
const getVariantStyles = (variant: string) => {
const variants: Record<string, VariantStyles> = {
default: {
commonClass: "px-4 w-full py-[10px]",
},
small: {
commonClass: "w-[49px] px-3 py-1.5",
toggleGroupPrimitiveClass: "space-x-1",
},
};
return variants[variant];
};
export const BooleanToggleGroup = function BooleanToggleGroup({
defaultValue = true,
value,
disabled = false,
// eslint-disable-next-line @typescript-eslint/no-empty-function
onValueChange = () => {},
variant = "default",
...passThrough
}: {
defaultValue?: boolean;
value?: boolean;
onValueChange?: (value?: boolean) => void;
disabled?: boolean;
variant?: "default" | "small";
}) {
// Maintain a state because it is not necessary that onValueChange the parent component would re-render. Think react-hook-form
// Also maintain a string as boolean isn't accepted as ToggleGroupPrimitive value
const [yesNoValue, setYesNoValue] = useState<"yes" | "no" | undefined>(yesNo(value));
if (!yesNoValue) {
setYesNoValue(yesNo(defaultValue));
onValueChange(defaultValue);
return null;
}
const commonClass = classNames(
getVariantStyles(variant).commonClass,
"inline-flex items-center justify-center rounded text-sm font-medium leading-4",
disabled && "cursor-not-allowed"
);
const selectedClass = classNames(commonClass, "bg-emphasis text-emphasis");
const unselectedClass = classNames(commonClass, "text-default hover:bg-subtle hover:text-emphasis");
return (
<ToggleGroupPrimitive
value={yesNoValue}
type="single"
disabled={disabled}
className={classNames(
"border-subtle flex h-9 space-x-2 rounded-md border p-1 rtl:space-x-reverse",
getVariantStyles(variant).toggleGroupPrimitiveClass
)}
onValueChange={(yesNoValue: "yes" | "no") => {
setYesNoValue(yesNoValue);
onValueChange(boolean(yesNoValue));
}}
{...passThrough}>
<ToggleGroupItemPrimitive
className={classNames(boolean(yesNoValue) ? selectedClass : unselectedClass)}
disabled={disabled}
value="yes">
Yes
</ToggleGroupItemPrimitive>
<ToggleGroupItemPrimitive
disabled={disabled}
className={classNames(!boolean(yesNoValue) ? selectedClass : unselectedClass)}
value="no">
No
</ToggleGroupItemPrimitive>
</ToggleGroupPrimitive>
);
};
export const BooleanToggleGroupField = function BooleanToggleGroupField(
props: Parameters<typeof BooleanToggleGroup>[0] & {
label?: string;
containerClassName?: string;
name?: string;
labelProps?: React.ComponentProps<typeof Label>;
className?: string;
error?: string;
}
) {
const { t } = useLocale();
const { label = t(props.name || ""), containerClassName, labelProps, className, ...passThrough } = props;
const id = useId();
return (
<div className={classNames(containerClassName)}>
<div className={className}>
{!!label && (
<Label htmlFor={id} {...labelProps} className={classNames(props.error && "text-error", "mt-4")}>
{label}
</Label>
)}
</div>
<BooleanToggleGroup {...passThrough} />
</div>
);
};

View File

@@ -0,0 +1,77 @@
import * as RadixToggleGroup from "@radix-ui/react-toggle-group";
import type { ReactNode } from "react";
import { classNames } from "@calcom/lib";
import { Tooltip } from "@calcom/ui";
interface ToggleGroupProps extends Omit<RadixToggleGroup.ToggleGroupSingleProps, "type"> {
options: {
value: string;
label: string | ReactNode;
disabled?: boolean;
tooltip?: string;
iconLeft?: ReactNode;
}[];
isFullWidth?: boolean;
}
const OptionalTooltipWrapper = ({
children,
tooltipText,
}: {
children: ReactNode;
tooltipText?: ReactNode;
}) => {
if (tooltipText) {
return (
<Tooltip delayDuration={150} sideOffset={12} side="bottom" content={tooltipText}>
{children}
</Tooltip>
);
}
return <>{children}</>;
};
export const ToggleGroup = ({
options,
onValueChange,
isFullWidth,
customClassNames,
...props
}: ToggleGroupProps & { customClassNames?: string }) => {
return (
<>
<RadixToggleGroup.Root
type="single"
{...props}
onValueChange={onValueChange}
className={classNames(
`min-h-9 border-default bg-default relative inline-flex gap-0.5 rounded-md border p-1 rtl:flex-row-reverse`,
props.className,
isFullWidth && "w-full",
customClassNames
)}>
{options.map((option) => (
<OptionalTooltipWrapper key={option.value} tooltipText={option.tooltip}>
<RadixToggleGroup.Item
disabled={option.disabled}
value={option.value}
data-testid={`toggle-group-item-${option.value}`}
className={classNames(
"aria-checked:bg-emphasis relative rounded-[4px] px-3 py-1 text-sm leading-tight transition",
option.disabled
? "text-gray-400 hover:cursor-not-allowed"
: "text-default [&[aria-checked='false']]:hover:text-emphasis",
isFullWidth && "w-full"
)}>
<div className="item-center flex justify-center ">
{option.iconLeft && <span className="mr-2 flex h-4 w-4 items-center">{option.iconLeft}</span>}
{option.label}
</div>
</RadixToggleGroup.Item>
</OptionalTooltipWrapper>
))}
</RadixToggleGroup.Root>
</>
);
};

View File

@@ -0,0 +1,2 @@
export { ToggleGroup } from "./ToggleGroup";
export { BooleanToggleGroup, BooleanToggleGroupField } from "./BooleanToggleGroup";

View File

@@ -0,0 +1,108 @@
import { TooltipProvider } from "@radix-ui/react-tooltip";
import { Canvas, Meta, Story } from "@storybook/addon-docs";
import {
CustomArgsTable,
Examples,
Example,
Title,
VariantsTable,
VariantRow,
} from "@calcom/storybook/components";
import { Icon } from "@calcom/ui";
import { ToggleGroup } from "./ToggleGroup";
<Meta title="UI/Form/ToggleGroup" component={ToggleGroup} />
<Title title="ToggleGroup" suffix="Brief" subtitle="Version 1.0 — Last Update: 17 Aug 2023" />
## Definition
The `ToggleGroup` component is used to create a group of toggle items with optional tooltips.
## Structure
<CustomArgsTable of={ToggleGroup} />
## Examples
<Examples title="Toggle Group With Icon Left">
<Example>
<TooltipProvider>
<ToggleGroup
options={[
{ value: "option1", label: "Option 1", tooltip: "Tooltip for Option 1", iconLeft: <Icon name="arrow-right" /> },
{ value: "option2", label: "Option 2", iconLeft: <Icon name="arrow-right" /> },
{ value: "option3", label: "Option 3", iconLeft: <Icon name="arrow-right" /> },
{
value: "option4",
label: "Option 4",
tooltip: "Tooltip for Option 4",
iconLeft: <Icon name="arrow-right" />,
},
{ value: "option5", label: "Option 5", iconLeft: <Icon name="arrow-right" />, disabled: true },
]}
/>
</TooltipProvider>
</Example>
</Examples>
## ToggleGroup Story
<Canvas>
<Story
name="Default"
args={{
options: [
{ value: "option1", label: "Option 1", tooltip: "Tooltip for Option 1" },
{ value: "option2", label: "Option 2" },
{ value: "option3", label: "Option 3" },
{
value: "option4",
label: "Option 4",
tooltip: "Tooltip for Option 4",
},
{ value: "option5", label: "Option 5", disabled: true },
],
}}
argTypes={{
options: {
value: {
control: {
type: "text",
},
},
lable: {
control: {
type: "text",
},
},
tooltip: {
control: {
type: "text",
},
},
disabled: {
control: {
type: "boolean",
},
},
isFullWidth: {
control: {
type: "boolean",
},
},
},
}}>
{({ options, isFullWidth }) => (
<VariantsTable titles={["Default"]} columnMinWidth={150}>
<VariantRow>
<TooltipProvider>
<ToggleGroup options={options} isFullWidth={isFullWidth} />
</TooltipProvider>
</VariantRow>
</VariantsTable>
)}
</Story>
</Canvas>