// // Copyright 2022 DXOS.org // import { createContext } from '@radix-ui/react-context'; import { Primitive } from '@radix-ui/react-primitive'; import { Slot } from '@radix-ui/react-slot'; import React, { type ComponentPropsWithRef, forwardRef, memo } from 'react'; import { type Density, type Elevation } from '@dxos/ui-types'; import { useDensityContext, useElevationContext, useThemeContext } from '../../hooks'; import { type ThemedClassName } from '../../util'; import { Icon } from '../Icon'; type ButtonProps = ThemedClassName> & { variant?: 'default' | 'primary' | 'outline' | 'ghost' | 'destructive' | 'valence'; density?: Density; elevation?: Elevation; asChild?: boolean; /** Render a trailing caret indicating the button opens a menu. */ caretDown?: boolean; }; type ButtonGroupContextValue = { inGroup?: boolean }; const BUTTON_GROUP_NAME = 'ButtonGroup'; const BUTTON_NAME = 'Button'; const [ButtonGroupProvider, useButtonGroupContext] = createContext(BUTTON_GROUP_NAME, { inGroup: false, }); const Button = memo( forwardRef( ( { classNames, children, density: densityProp, elevation: elevationProp, variant = 'default', asChild, caretDown, ...props }, ref, ) => { const { inGroup } = useButtonGroupContext(BUTTON_NAME); const { tx } = useThemeContext(); const elevation = useElevationContext(elevationProp); const density = useDensityContext(densityProp); const Comp = asChild ? Slot : Primitive.button; return ( {/* `asChild` forwards a single child via Slot (React.Children.only); only add the caret in the non-`asChild` case so Slot still receives exactly one child. */} {caretDown && !asChild ? ( <> {children} ) : ( children )} ); }, ), ); Button.displayName = BUTTON_NAME; type ButtonGroupProps = ThemedClassName> & { elevation?: Elevation; asChild?: boolean; }; const ButtonGroup = forwardRef( ({ children, elevation: propsElevation, classNames, asChild, ...props }, forwardedRef) => { const { tx } = useThemeContext(); const elevation = useElevationContext(propsElevation); const Comp = asChild ? Slot : Primitive.div; return ( {children} ); }, ); ButtonGroup.displayName = BUTTON_GROUP_NAME; export { Button, BUTTON_GROUP_NAME, ButtonGroup, useButtonGroupContext }; export type { ButtonGroupProps, ButtonProps };