import { type FC, type PropsWithChildren, type ReactNode, type RefAttributes, createContext, isValidElement, useContext } from "react"; import { ToggleButton as AriaToggleButton, ToggleButtonGroup as AriaToggleButtonGroup, type ToggleButtonGroupProps, type ToggleButtonProps, } from "react-aria-components"; import { cx, sortCx } from "@/utils/cx"; import { isReactComponent } from "@/utils/is-react-component"; export const styles = sortCx({ common: { root: [ "group/button-group inline-flex h-max cursor-pointer items-center bg-primary font-semibold whitespace-nowrap text-secondary shadow-skeumorphic ring-1 ring-primary outline-brand transition duration-100 ease-linear ring-inset", // Hover and focus styles "hover:bg-primary_hover hover:text-secondary_hover focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2", // Disabled styles "disabled:cursor-not-allowed disabled:bg-primary disabled:text-disabled", // Selected styles "selected:bg-active selected:text-secondary_hover selected:disabled:bg-disabled_subtle", ].join(" "), icon: "pointer-events-none text-fg-quaternary transition-[inherit] group-hover/button-group:text-fg-quaternary_hover group-disabled/button-group:text-fg-disabled_subtle", }, sizes: { sm: { root: "gap-1.5 px-3.5 py-2 text-sm not-last:pr-[calc(calc(var(--spacing)*3.5)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-3 data-icon-only:p-2", icon: "size-5", }, md: { root: "gap-1.5 px-4 py-2.5 text-sm not-last:pr-[calc(calc(var(--spacing)*4)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-3.5 data-icon-only:px-3", icon: "size-5", }, lg: { root: "gap-2 px-4.5 py-2.5 text-md not-last:pr-[calc(calc(var(--spacing)*4.5)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-4 data-icon-only:p-3", icon: "size-5", }, }, }); type ButtonSize = keyof typeof styles.sizes; const ButtonGroupContext = createContext<{ size: ButtonSize }>({ size: "md" }); interface ButtonGroupItemProps extends ToggleButtonProps, RefAttributes { iconLeading?: FC<{ className?: string }> | ReactNode; iconTrailing?: FC<{ className?: string }> | ReactNode; onClick?: () => void; className?: string; } export const ButtonGroupItem = ({ iconLeading: IconLeading, iconTrailing: IconTrailing, children, className, ...otherProps }: PropsWithChildren) => { const context = useContext(ButtonGroupContext); if (!context) { throw new Error("ButtonGroupItem must be used within a ButtonGroup component"); } const { size } = context; const isIcon = (IconLeading || IconTrailing) && !children; return ( {isReactComponent(IconLeading) && } {isValidElement(IconLeading) && IconLeading} {children} {isReactComponent(IconTrailing) && } {isValidElement(IconTrailing) && IconTrailing} ); }; interface ButtonGroupProps extends Omit, RefAttributes { size?: ButtonSize; className?: string; } export const ButtonGroup = ({ children, size = "md", className, ...otherProps }: ButtonGroupProps) => { return ( {children} ); };