cal.pub0.org/packages/ui/components/form/toggleGroup/ToggleGroup.tsx

100 lines
3.5 KiB
TypeScript

import * as RadixToggleGroup from "@radix-ui/react-toggle-group";
import type { ReactNode } from "react";
import { useEffect, useRef, useState } 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, ...props }: ToggleGroupProps) => {
const [value, setValue] = useState<string | undefined>(props.defaultValue);
const activeRef = useRef<HTMLSpanElement>(null);
useEffect(() => {
if (value && onValueChange) onValueChange(value);
}, [value, onValueChange]);
return (
<>
<RadixToggleGroup.Root
type="single"
{...props}
onValueChange={setValue}
className={classNames(
"min-h-9 bg-muted border-default relative inline-flex gap-0.5 rounded-md border p-1",
props.className,
isFullWidth && "w-full"
)}>
{/* Active toggle. It's a separate element so we can animate it nicely. */}
<span
ref={activeRef}
aria-hidden
className={classNames(
"bg-emphasis absolute top-[4px] bottom-[4px] left-0 z-[0] rounded-[4px]",
// Disable the animation until after initial render, that way when the component would
// rerender the styles are immediately set and we don't see a flash moving the element
// into position because of the animation.
activeRef?.current && "transition-all"
)}
/>
{options.map((option) => (
<OptionalTooltipWrapper key={option.value} tooltipText={option.tooltip}>
<RadixToggleGroup.Item
disabled={option.disabled}
value={option.value}
className={classNames(
"relative rounded-[4px] px-3 py-1 text-sm leading-tight",
option.disabled
? "text-gray-400 hover:cursor-not-allowed"
: "text-default [&[aria-checked='false']]:hover:bg-subtle",
isFullWidth && "w-full"
)}
ref={(node) => {
if (node && value === option.value) {
// Sets position of active toggle element with inline styles.
// This way we trigger as little rerenders as possible.
if (!activeRef.current || activeRef?.current.style.left === `${node.offsetLeft}px`) return;
activeRef.current.style.left = `${node.offsetLeft}px`;
activeRef.current.style.width = `${node.offsetWidth}px`;
}
return node;
}}>
<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>
</>
);
};