import { forwardRef, useMemo } from 'react'; import { View } from 'react-native'; import { HeroText } from '../../helpers/components'; import { colorKit, useThemeColor } from '../../helpers/theme'; import type { PressableRef } from '../../helpers/types'; import { childrenToString, createContext } from '../../helpers/utils'; import { PressableFeedback } from '../pressable-feedback'; import { DISPLAY_NAME } from './button.constants'; import buttonStyles, { styleSheet } from './button.styles'; import type { ButtonContextValue, ButtonLabelProps, ButtonRootProps, } from './button.types'; const [ButtonProvider, useButton] = createContext({ name: 'ButtonContext', }); // -------------------------------------------------- const ButtonRoot = forwardRef((props, ref) => { const { children, variant = 'primary', pressableFeedbackVariant = 'highlight', pressableFeedbackHighlightProps, pressableFeedbackRippleProps, size = 'md', isIconOnly = false, isDisabled = false, className, style, accessibilityRole = 'button', ...restProps } = props; const [ themeColorAccentHover, themeColorDefaultHover, themeColorDangerHover, themeColorDangerSoftHover, ] = useThemeColor([ 'accent-hover', 'default-hover', 'danger-hover', 'danger-soft-hover', ]); const stringifiedChildren = childrenToString(children); const tvStyles = buttonStyles.root({ variant, size, isIconOnly, isDisabled, className, }); const highlightColorMap = useMemo(() => { switch (variant) { case 'primary': return themeColorAccentHover; case 'secondary': return themeColorDefaultHover; case 'tertiary': return themeColorDefaultHover; case 'ghost': return colorKit.setAlpha(themeColorDefaultHover, 0.3).hex(); case 'danger': return themeColorDangerHover; case 'danger-soft': return themeColorDangerSoftHover; } }, [ variant, themeColorAccentHover, themeColorDefaultHover, themeColorDangerHover, themeColorDangerSoftHover, ]); const highlightAnimationConfig = useMemo(() => { if (pressableFeedbackVariant !== 'highlight') { return undefined; } const defaultConfig = { backgroundColor: { value: highlightColorMap, }, opacity: { value: [0, 1] as [number, number], }, }; // Merge with provided animation if available if ( pressableFeedbackHighlightProps?.animation && typeof pressableFeedbackHighlightProps.animation === 'object' ) { const providedAnimation = pressableFeedbackHighlightProps.animation; return { backgroundColor: { ...defaultConfig.backgroundColor, ...providedAnimation.backgroundColor, }, opacity: { ...defaultConfig.opacity, ...providedAnimation.opacity, }, }; } return defaultConfig; }, [ pressableFeedbackVariant, highlightColorMap, pressableFeedbackHighlightProps?.animation, ]); const rippleAnimationConfig = useMemo(() => { if (pressableFeedbackVariant !== 'ripple') { return undefined; } const defaultConfig = { backgroundColor: { value: highlightColorMap }, opacity: { value: [0, 1, 0] as [number, number, number] }, }; // Merge with provided animation if available if ( pressableFeedbackRippleProps?.animation && typeof pressableFeedbackRippleProps.animation === 'object' ) { const providedAnimation = pressableFeedbackRippleProps.animation; return { backgroundColor: { ...defaultConfig.backgroundColor, ...providedAnimation.backgroundColor, }, opacity: { ...defaultConfig.opacity, ...providedAnimation.opacity, }, ...(providedAnimation.scale && { scale: providedAnimation.scale }), ...(providedAnimation.progress && { progress: providedAnimation.progress, }), }; } return defaultConfig; }, [ pressableFeedbackVariant, highlightColorMap, pressableFeedbackRippleProps?.animation, ]); const contextValue = useMemo( () => ({ size, variant, isDisabled, }), [size, variant, isDisabled] ); return ( {pressableFeedbackVariant === 'highlight' && ( )} {pressableFeedbackVariant === 'ripple' && ( )} {stringifiedChildren ? ( {stringifiedChildren} ) : ( children )} ); }); // -------------------------------------------------- const ButtonLabel = forwardRef((props, ref) => { const { children, className, ...restProps } = props; const { size, variant } = useButton(); const tvStyles = buttonStyles.label({ size, variant, className, }); return ( {children} ); }); // -------------------------------------------------- ButtonRoot.displayName = DISPLAY_NAME.BUTTON_ROOT; ButtonLabel.displayName = DISPLAY_NAME.BUTTON_LABEL; /** * Compound Button component with sub-components * * @component Button - Main button container that handles press interactions, animations, and variants. * Renders with string children as label or accepts compound components for custom layouts. * * @component Button.Label - Text content of the button. When string is provided, * it renders as Text. Otherwise renders children as-is. * * Props flow from Button to sub-components via context (size, variant, isDisabled). * All components use animated views with layout transitions for smooth animations. * * @see Full documentation: https://heroui.com/components/button */ const CompoundButton = Object.assign(ButtonRoot, { /** Button label - renders text or custom content */ Label: ButtonLabel, }); export { useButton }; export default CompoundButton;