diff --git a/components/header-bar/src/notification-icon.js b/components/header-bar/src/notification-icon.js index 6a2010030..285a84749 100755 --- a/components/header-bar/src/notification-icon.js +++ b/components/header-bar/src/notification-icon.js @@ -1,9 +1,11 @@ -import { colors, theme, spacers } from '@dhis2/ui-constants' +import { colors, elevations, spacers } from '@dhis2/ui-constants' import { IconMessages24, IconMail24 } from '@dhis2/ui-icons' import PropTypes from 'prop-types' -import React from 'react' +import React, { useEffect, useState } from 'react' import i18n from './locales/index.js' +const BADGE_REVEAL_DURATION_MS = 3000 + function icon(kind) { if (kind === 'message') { return @@ -19,67 +21,123 @@ export const NotificationIcon = ({ dataTestId, title, 'aria-label': ariaLabel, -}) => ( - - {icon(kind)} +}) => { + const [badgeExpanded, setBadgeExpanded] = useState(count > 0) + + // Briefly reveal the full counter on mount, then collapse it to a dot + useEffect(() => { + if (count === 0) { + return + } + const timer = setTimeout( + () => setBadgeExpanded(false), + BADGE_REVEAL_DURATION_MS + ) + return () => clearTimeout(timer) + // count is intentionally omitted: this should only run once on mount + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + return ( + + {icon(kind)} + + {count > 0 && ( + + {count > 99 ? '99+' : count} + + )} - {count > 0 && {count}} + - -) + /* Dot state (default) */ + min-width: ${spacers.dp8}; + max-width: ${spacers.dp8}; + height: ${spacers.dp8}; + border-radius: ${spacers.dp4}; + padding: 0; + color: transparent; + box-shadow: 0 0 0 2px #2c6693; + + transition: max-width 150ms ease-in-out, + min-width 150ms ease-in-out, height 150ms ease-in-out, + border-radius 150ms ease-in-out, + padding 150ms ease-in-out, color 150ms ease-in-out, + box-shadow 150ms ease-in-out; + } + + /* Full counter badge */ + span.is-expanded, + a:hover span, + a:focus-visible span { + min-width: 18px; + max-width: 80px; + height: 18px; + border-radius: ${spacers.dp12}; + padding: 0 ${spacers.dp4}; + color: ${colors.teal800}; + box-shadow: ${elevations.e100}; + } + + @media (prefers-reduced-motion: reduce) { + span { + transition: none; + } + } + `} + + ) +} NotificationIcon.propTypes = { 'aria-label': PropTypes.string.isRequired,