// @flow import React, { Children, cloneElement, forwardRef, useImperativeHandle, useLayoutEffect, useRef, useState, } from 'react' import { tabbable } from 'tabbable' import ResizeObserver from 'resize-observer-polyfill' import { Box, BoxProps } from '../Box' import { useBoxSize } from '../system' import { getRect } from '../utils' import { Button } from './Button' type InputProps = { inputProps: any isOpen: boolean open: any close: any } type InputSignature = (inputProps: InputProps) => React.ReactNode type ButtonInputProps = { /** * Clones immediate child with input specific props. Use a function to access props and pass them yourself. */ children: InputSignature | React.ReactElement /** * Size of internal [Button](/button) and child [Input](/input). */ size?: 'xs' | 'sm' | 'md' | 'lg' /** * Callback when the input reverts back to a button. */ onClose: () => void /** * Callback when button is pressed to reveal the input. */ onOpen: () => void } & BoxProps type Props = React.RefAttributes & React.HTMLAttributes & ButtonInputProps function getFixedValue(value) { // Safari doesn't like sub pixels return parseInt(value.toFixed(0), 10) + 1 } const ButtonInput = forwardRef( ({ children, style, size, onOpen, onClose, ...restProps }: Props, ref) => { const containerRef = useRef() const inputRef = useRef() const buttonRef = useRef() const [isOpen, setOpen] = useState(false) const { boxSize } = useBoxSize(size) const containerProps = { innerRef: containerRef, height: boxSize, position: 'relative', overflow: 'hidden', transition: 'width 200ms cubic-bezier(.02,.8,.38,1)', } as BoxProps const inputContainerProps = { innerRef: inputRef, position: 'absolute', top: 0, right: 0, left: 0, opacity: isOpen ? 1 : 0, transition: 'opacity 180ms ease', } as BoxProps const inputProps = { size, secondaryStroke: false, disabled: !isOpen, onBlur: () => { const input = inputRef.current.querySelector('input') if (input.value.length === 0) { close() } }, onKeyDown: (event) => { if (event.key === 'Escape') { close() } }, } const buttonContainerProps = { innerRef: buttonRef, position: 'absolute', top: 0, right: 0, left: 0, opacity: isOpen ? 0 : 1, transition: 'opacity 180ms ease', } as BoxProps const buttonProps = { ...restProps, size, disabled: isOpen, onClick: open, } function open() { setOpen(true) const input = inputRef.current.querySelector('input') if (input && input.focus) { setTimeout(() => input.focus()) } if (onOpen) { onOpen() } } function close() { setOpen(false) const [button] = tabbable(buttonRef.current) if (button && button.focus) { setTimeout(() => button.focus()) } if (onClose) { onClose() } } function transition() { const container = containerRef.current const inputChild = inputRef.current.firstChild as HTMLElement const buttonChild = buttonRef.current.firstChild as HTMLElement inputChild.style.width = '' buttonChild.style.width = '' const inputRect = getRect(inputChild, { clone: true }) const buttonRect = getRect(buttonChild, { clone: true }) const containerWidth = getFixedValue( isOpen ? inputRect.width : buttonRect.width ) container.style.width = containerWidth + 'px' inputChild.style.width = '100%' buttonChild.style.width = '100%' } useLayoutEffect(() => { transition() const ro = new ResizeObserver(transition) ro.observe(isOpen ? inputRef.current : buttonRef.current) return () => { ro.disconnect() } }, [isOpen]) useImperativeHandle( ref, () => ({ open, close, }), [] ) // @ts-ignore const button =