|  | 
|  | 1 | +import { type ReactNode, type Ref, forwardRef, useEffect } from 'react' | 
|  | 2 | +import PropTypes from 'prop-types' | 
|  | 3 | + | 
|  | 4 | +import styled, { type StyledComponentPropsWithRef } from 'styled-components' | 
|  | 5 | + | 
|  | 6 | +import { type ModalProps } from 'honorable' | 
|  | 7 | + | 
|  | 8 | +import useLockedBody from '../hooks/useLockedBody' | 
|  | 9 | + | 
|  | 10 | +import { CloseIcon } from '../icons' | 
|  | 11 | + | 
|  | 12 | +import { HonorableModal } from './HonorableModal' | 
|  | 13 | +import IconFrame from './IconFrame' | 
|  | 14 | + | 
|  | 15 | +type FlyoverPropsType = Omit<ModalProps, 'size'> & { | 
|  | 16 | +  header?: ReactNode | 
|  | 17 | +  lockBody?: boolean | 
|  | 18 | +  scrollable?: boolean | 
|  | 19 | +  asForm?: boolean | 
|  | 20 | +  formProps?: StyledComponentPropsWithRef<'form'> | 
|  | 21 | +  width?: string | 
|  | 22 | +  minWidth?: number | 
|  | 23 | +  [x: string]: unknown | 
|  | 24 | +} | 
|  | 25 | + | 
|  | 26 | +const propTypes = { | 
|  | 27 | +  header: PropTypes.node, | 
|  | 28 | +  lockBody: PropTypes.bool, | 
|  | 29 | +  scrollable: PropTypes.bool, | 
|  | 30 | +  asForm: PropTypes.bool, | 
|  | 31 | +  width: PropTypes.string, | 
|  | 32 | +  minWidth: PropTypes.number, | 
|  | 33 | +} as const | 
|  | 34 | + | 
|  | 35 | +const FlyoverSC = styled.div(({ theme }) => ({ | 
|  | 36 | +  position: 'relative', | 
|  | 37 | +  backgroundColor: theme.colors['fill-zero'], | 
|  | 38 | +  height: '100%', | 
|  | 39 | +  display: 'flex', | 
|  | 40 | +  flexDirection: 'column', | 
|  | 41 | +  overflow: 'hidden', | 
|  | 42 | +})) | 
|  | 43 | + | 
|  | 44 | +const FlyoverContentSC = styled.div<{ | 
|  | 45 | +  $scrollable: boolean | 
|  | 46 | +}>(({ theme, $scrollable }) => ({ | 
|  | 47 | +  position: 'relative', | 
|  | 48 | +  zIndex: 0, | 
|  | 49 | +  margin: 0, | 
|  | 50 | +  padding: theme.spacing.large, | 
|  | 51 | +  backgroundColor: theme.colors['fill-zero'], | 
|  | 52 | +  ...theme.partials.text.body1, | 
|  | 53 | +  flexGrow: 1, | 
|  | 54 | +  ...($scrollable | 
|  | 55 | +    ? { overflow: 'auto' } | 
|  | 56 | +    : { | 
|  | 57 | +        display: 'flex', | 
|  | 58 | +        flexDirection: 'column', | 
|  | 59 | +        overflow: 'hidden', | 
|  | 60 | +      }), | 
|  | 61 | +})) | 
|  | 62 | + | 
|  | 63 | +const FlyoverHeaderWrapSC = styled.div(({ theme }) => ({ | 
|  | 64 | +  alignItems: 'center', | 
|  | 65 | +  justifyContent: 'start', | 
|  | 66 | +  gap: theme.spacing.small, | 
|  | 67 | +  height: 56, | 
|  | 68 | +  borderBottom: `1px solid ${theme.colors.border}`, | 
|  | 69 | +  backgroundColor: theme.colors.grey[950], | 
|  | 70 | +  display: 'flex', | 
|  | 71 | +  padding: `${theme.spacing.small}px ${theme.spacing.medium}px`, | 
|  | 72 | +})) | 
|  | 73 | + | 
|  | 74 | +const FlyoverHeaderSC = styled.h1(({ theme }) => ({ | 
|  | 75 | +  margin: 0, | 
|  | 76 | +  ...theme.partials.text.subtitle1, | 
|  | 77 | +  color: theme.colors.semanticDefault, | 
|  | 78 | +})) | 
|  | 79 | + | 
|  | 80 | +function FlyoverRef( | 
|  | 81 | +  { | 
|  | 82 | +    children, | 
|  | 83 | +    header, | 
|  | 84 | +    open = false, | 
|  | 85 | +    onClose, | 
|  | 86 | +    lockBody = true, | 
|  | 87 | +    asForm = false, | 
|  | 88 | +    formProps = {}, | 
|  | 89 | +    scrollable = true, | 
|  | 90 | +    width = '40%', | 
|  | 91 | +    minWidth = 570, | 
|  | 92 | +    ...props | 
|  | 93 | +  }: FlyoverPropsType, | 
|  | 94 | +  ref: Ref<any> | 
|  | 95 | +) { | 
|  | 96 | +  const [, setBodyLocked] = useLockedBody(open && lockBody) | 
|  | 97 | + | 
|  | 98 | +  useEffect(() => { | 
|  | 99 | +    setBodyLocked(lockBody && open) | 
|  | 100 | +  }, [lockBody, open, setBodyLocked]) | 
|  | 101 | + | 
|  | 102 | +  return ( | 
|  | 103 | +    <HonorableModal | 
|  | 104 | +      open={open} | 
|  | 105 | +      onClose={onClose} | 
|  | 106 | +      ref={ref} | 
|  | 107 | +      scrollable={scrollable} | 
|  | 108 | +      margin={0} | 
|  | 109 | +      padding={0} | 
|  | 110 | +      right="100%" | 
|  | 111 | +      height="100%" | 
|  | 112 | +      width={width} | 
|  | 113 | +      minWidth={minWidth} | 
|  | 114 | +      alignSelf="flex-end" | 
|  | 115 | +      BackdropProps={{ backgroundColor: 'transparent' }} | 
|  | 116 | +      InnerDefaultStyle={{ | 
|  | 117 | +        opacity: 0, | 
|  | 118 | +        transform: 'translateX(0)', | 
|  | 119 | +        transition: 'transform 300ms ease, opacity 300ms ease', | 
|  | 120 | +      }} | 
|  | 121 | +      InnerTransitionStyle={{ | 
|  | 122 | +        entering: { opacity: 1, transform: 'translateX(0)' }, | 
|  | 123 | +        entered: { opacity: 1, transform: 'translateX(0)' }, | 
|  | 124 | +        exiting: { opacity: 0, transform: 'translateX(1000px)' }, | 
|  | 125 | +        exited: { opacity: 0, transform: 'translateX(1000px)' }, | 
|  | 126 | +      }} | 
|  | 127 | +      {...props} | 
|  | 128 | +    > | 
|  | 129 | +      <FlyoverSC | 
|  | 130 | +        as={asForm ? 'form' : undefined} | 
|  | 131 | +        {...(asForm ? formProps : {})} | 
|  | 132 | +      > | 
|  | 133 | +        {!!header && ( | 
|  | 134 | +          <FlyoverHeaderWrapSC ref={ref}> | 
|  | 135 | +            <IconFrame | 
|  | 136 | +              textValue="" | 
|  | 137 | +              display="flex" | 
|  | 138 | +              size="small" | 
|  | 139 | +              clickable | 
|  | 140 | +              onClick={onClose} | 
|  | 141 | +              icon={<CloseIcon />} | 
|  | 142 | +            /> | 
|  | 143 | +            <FlyoverHeaderSC>{header}</FlyoverHeaderSC> | 
|  | 144 | +          </FlyoverHeaderWrapSC> | 
|  | 145 | +        )} | 
|  | 146 | +        <FlyoverContentSC $scrollable={scrollable}>{children}</FlyoverContentSC> | 
|  | 147 | +      </FlyoverSC> | 
|  | 148 | +    </HonorableModal> | 
|  | 149 | +  ) | 
|  | 150 | +} | 
|  | 151 | + | 
|  | 152 | +const Flyover = forwardRef(FlyoverRef) | 
|  | 153 | + | 
|  | 154 | +Flyover.propTypes = propTypes | 
|  | 155 | + | 
|  | 156 | +export default Flyover | 
0 commit comments