import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { colors } from '../../config.json';
import { messages } from '../../shared/gettext';
import { InAppNotificationIndicatorType } from '../../shared/notifications/notification';
import * as AppButton from './AppButton';
import ImageView from './ImageView';
const NOTIFICATION_AREA_ID = 'notification-area';
export const NotificationTitle = styled.span({
fontFamily: 'Open Sans',
fontSize: '13px',
fontWeight: 800,
lineHeight: '18px',
color: colors.white,
});
export const NotificationSubtitleText = styled.span({
fontFamily: 'Open Sans',
fontSize: '13px',
fontWeight: 600,
lineHeight: '18px',
color: colors.white60,
});
interface INotificationSubtitleProps {
children?: React.ReactNode;
}
export function NotificationSubtitle(props: INotificationSubtitleProps) {
return React.Children.count(props.children) > 0 ? : null;
}
export const NotificationOpenLinkActionButton = styled(AppButton.SimpleButton)({
flex: 1,
justifyContent: 'center',
cursor: 'default',
padding: '4px',
background: 'transparent',
border: 'none',
});
export const NotificationOpenLinkActionIcon = styled(ImageView)({
[NotificationOpenLinkActionButton + ':hover &']: {
backgroundColor: colors.white80,
},
});
interface INotifcationOpenLinkActionProps {
onClick: () => Promise;
children?: React.ReactNode;
}
export function NotificationOpenLinkAction(props: INotifcationOpenLinkActionProps) {
return (
);
}
export const NotificationContent = styled.div.attrs({ id: NOTIFICATION_AREA_ID })({
display: 'flex',
flexDirection: 'column',
flex: 1,
paddingRight: '4px',
});
export const NotificationActions = styled.div({
display: 'flex',
flex: 0,
flexDirection: 'column',
justifyContent: 'center',
});
interface INotificationIndicatorProps {
type?: InAppNotificationIndicatorType;
}
const notificationIndicatorTypeColorMap = {
success: colors.green,
warning: colors.yellow,
error: colors.red,
};
export const NotificationIndicator = styled.div((props: INotificationIndicatorProps) => ({
width: '10px',
height: '10px',
borderRadius: '5px',
marginTop: '4px',
marginRight: '8px',
backgroundColor: props.type ? notificationIndicatorTypeColorMap[props.type] : 'transparent',
}));
interface ICollapsibleProps {
alignBottom: boolean;
contentHeight?: number;
collapsibleHeight?: number;
}
const TRANSITION_DURATION = 350;
// 52px is the height of the banner when the notification contains a title and subtitle which are
// one line each.
const TRANSITION_BASE_DISTANCE = 52;
const Collapsible = styled.div({}, (props: ICollapsibleProps) => {
// Calculate the transition duration based on travel distance.
const distance = Math.abs((props.collapsibleHeight ?? 0) - (props.contentHeight ?? 0));
const duration = Math.ceil(TRANSITION_DURATION * (distance / TRANSITION_BASE_DISTANCE));
return {
display: 'flex',
flexDirection: 'column',
justifyContent: props.alignBottom ? 'flex-end' : 'flex-start',
backgroundColor: 'rgba(25, 38, 56, 0.95)',
overflow: 'hidden',
// Using auto as the initial value prevents transition if a notification is visible on mount.
height: props.contentHeight === undefined ? 'auto' : `${props.contentHeight}px`,
transition: `height ${duration}ms ease-in-out`,
};
});
const Content = styled.section({
display: 'flex',
flexDirection: 'row',
padding: '8px 12px 8px 16px',
height: 'fit-content',
});
interface INotificationBannerProps {
children?: React.ReactNode; // Array,
className?: string;
visible: boolean;
}
export function NotificationBanner(props: INotificationBannerProps) {
const [contentHeight, setContentHeight] = useState();
const [alignBottom, setAlignBottom] = useState(false);
const contentRef = useRef() as React.RefObject;
const collapsibleRef = useRef() as React.RefObject;
// Save last non-undefined children to be able to show them during the hide-transition.
const prevChildren = useRef();
useEffect(() => {
prevChildren.current = props.children ?? prevChildren.current;
}, [props.children]);
const onTransitionEnd = useCallback(() => setAlignBottom(false), []);
useLayoutEffect(() => {
const newHeight = props.visible ? contentRef.current?.getBoundingClientRect().height ?? 0 : 0;
if (newHeight !== contentHeight) {
setContentHeight(newHeight);
setAlignBottom((alignBottom) => alignBottom || contentHeight === 0 || newHeight === 0);
}
});
return (
{props.visible ? props.children : prevChildren.current}
);
}