@spaced-out/ui-design-system
Version:
Sense UI components library
182 lines (164 loc) • 4.35 kB
Flow
// @flow strict
import * as React from 'react';
import useMountTransition from '../../hooks/useMountTransition';
import {
motionDurationNormal,
motionDurationSlow,
} from '../../styles/variables/_motion';
import {
spaceFluid,
spaceNegFluid,
spaceNone,
} from '../../styles/variables/_space';
import classify from '../../utils/classify';
import {Button} from '../Button';
import type {ModalProps} from '../Modal';
import {Modal} from '../Modal';
import {Truncate} from '../Truncate';
import css from './Panel.module.css';
export type PanelSize = 'small' | 'medium' | 'large';
export type PanelAnchor = 'left' | 'right';
export type PanelHeaderProps = {
children?: React.Node,
hideCloseBtn?: boolean,
onCloseButtonClick?: ?(SyntheticEvent<HTMLElement>) => mixed,
className?: string,
size?: 'medium' | 'small',
};
type FooterClassNames = $ReadOnly<{
wrapper?: string,
actions?: string,
}>;
export type PanelFooterProps = {
children?: React.Node,
classNames?: FooterClassNames,
};
export type PanelBodyProps = {
children?: React.Node,
className?: string,
};
export type PanelProps = {
...ModalProps,
allowBackgroundInteraction?: boolean,
size?: PanelSize,
anchor?: PanelAnchor,
};
const getDefaultPanelAnimation = (anchor: PanelAnchor) => ({
// Configure both open and close durations:
duration: {
open: parseInt(motionDurationSlow),
close: parseInt(motionDurationNormal),
},
initial: {
transform: `translateX(${anchor === 'right' ? spaceFluid : spaceNegFluid})`,
},
open: {
transform: `translateX(${spaceNone})`,
},
close: {
transform: `translateX(${anchor === 'right' ? spaceFluid : spaceNegFluid})`,
},
});
export const PanelHeader = ({
children,
hideCloseBtn,
onCloseButtonClick,
className,
size,
}: PanelHeaderProps): React.Node => (
<>
{React.Children.count(children) > 0 && (
<div className={classify(css.panelHeader, className)}>
<div className={css.headerContent}>
<Truncate>{children}</Truncate>
</div>
{!hideCloseBtn && (
<Button
iconLeftName="xmark"
type="ghost"
onClick={onCloseButtonClick}
ariaLabel="Close Button"
size={size}
></Button>
)}
</div>
)}
</>
);
export const PanelBody: React$AbstractComponent<
PanelBodyProps,
HTMLDivElement,
> = React.forwardRef<PanelBodyProps, HTMLDivElement>(
({children, className}: PanelBodyProps, ref): React.Node => (
<div ref={ref} className={classify(css.panelBody, className)}>
{children}
</div>
),
);
export const PanelFooter = ({
children,
classNames,
}: PanelFooterProps): React.Node => (
<>
{React.Children.count(children) > 0 && (
<div className={classify(css.panelFooter, classNames?.wrapper)}>
<div className={classify(css.panelFooterActions, classNames?.actions)}>
{children}
</div>
</div>
)}
</>
);
export const Panel = ({
children,
isOpen = false,
size = 'medium',
anchor = 'left',
onClose,
hideBackdrop = true,
classNames,
customAnimation,
tapOutsideToClose = true,
allowBackgroundInteraction,
...restPanelProps
}: PanelProps): React.Node => {
const isTransitioning = useMountTransition(
isOpen,
parseInt(motionDurationNormal),
);
const defaultPanelAnimation = getDefaultPanelAnimation(anchor);
return (
<Modal
isOpen={isOpen}
onClose={onClose}
hideBackdrop={hideBackdrop}
tapOutsideToClose={allowBackgroundInteraction ? false : tapOutsideToClose}
allowBackgroundInteraction={allowBackgroundInteraction}
{...restPanelProps}
classNames={{
container: classify(
css.panelContainer,
{
[css.in]: isTransitioning,
[css.open]: isOpen,
},
classNames?.container,
),
content: classify(
css.panel,
css[anchor],
{
[css.medium]: size === 'medium',
[css.small]: size === 'small',
[css.large]: size === 'large',
},
classNames?.content,
),
backdrop: classify(classNames?.backdrop),
}}
customAnimation={customAnimation || defaultPanelAnimation}
>
{children}
</Modal>
);
};