"use client"; import {mergeProps} from "@base-ui/react/merge-props"; import {useRender} from "@base-ui/react/use-render"; import * as React from "react"; import {cn} from "@/lib/utilities"; import styles from "./button.module.css"; const variantStyles: Record = { default: styles.default!, destructive: styles.destructive!, outline: styles.outline!, secondary: styles.secondary!, ghost: styles.ghost!, link: styles.link!, }; const sizeStyles: Record = { default: styles.sizeDefault!, sm: styles.sizeSm!, lg: styles.sizeLg!, icon: styles.sizeIcon!, }; export type ButtonVariant = "default" | "destructive" | "outline" | "secondary" | "ghost" | "link"; export type ButtonSize = "default" | "sm" | "lg" | "icon"; /** * Serializable button state exposed to Base UI render callbacks. */ export interface ButtonState extends Record { variant: ButtonVariant; size: ButtonSize; disabled: boolean; } interface ButtonVariantOptions { variant?: ButtonVariant; size?: ButtonSize; className?: string; } /** * Props for the shared button component. */ export interface ButtonProps extends Omit, "children" | "className" | "disabled"> { /** * Visual style variant. * @default "default" */ variant?: ButtonVariant; /** * Size preset. * @default "default" */ size?: ButtonSize; /** * Whether the button should ignore user interaction. * @default false */ disabled?: boolean; /** * Additional CSS classes merged with the button styles. * @default undefined */ className?: string; /** * Custom element or render callback used to replace the default ` * * ``` * * @see {@link https://base-ui.com/react/components/button | Base UI Button} */ const Button = React.forwardRef((props: Readonly, ref): React.ReactElement => { const {render, asChild = false, variant = "default", size = "default", disabled = false, className, children, ...otherProps} = props; const state: Button.State = {variant, size, disabled}; const composedClassName = buttonVariants({variant, size, className}); const renderProp = asChild && React.isValidElement(children) ? children : render; const shouldRenderNativeButton = !renderProp || isIntrinsicButtonElement(renderProp); const typeProps: Pick, "type"> = shouldRenderNativeButton ? {type: "button"} : {}; const interactionProps = shouldRenderNativeButton ? {disabled} : createNonNativeInteractionProps(disabled); return useRender({ defaultTagName: "button", ref, render: renderProp, state, props: mergeProps<"button">({className: composedClassName}, typeProps, otherProps, interactionProps, { children: renderProp ? undefined : children, }), }); }); Button.displayName = "Button"; // eslint-disable-next-line no-redeclare -- required for the canonical component namespace typing API namespace Button { export type State = ButtonState; export type Props = ButtonProps; } export {Button, buttonVariants};