UNPKG

grommet

Version:

focus on the essential experience

126 lines (122 loc) 6.23 kB
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } import React, { useRef } from 'react'; import styled from 'styled-components'; import { parseMetricToNum } from '../../utils'; import { useLayoutEffect } from '../../utils/use-isomorphic-layout-effect'; import { Box } from '../Box'; import { Stack } from '../Stack'; import { Text } from '../Text'; import { useThemeValue } from '../../utils/useThemeValue'; var StyledBadgeContainer = styled(Box).withConfig({ displayName: "Badge__StyledBadgeContainer", componentId: "sc-1es4ws1-0" })(["", ""], function (props) { return props.theme.button.badge.container.extend; }); export var Badge = function Badge(_ref) { var children = _ref.children, content = _ref.content; var _useThemeValue = useThemeValue(), theme = _useThemeValue.theme, passThemeFlag = _useThemeValue.passThemeFlag; var containerRef = useRef(); var contentRef = useRef(); var stackRef = useRef(); var defaultBadgeDimension = typeof content === 'boolean' || content && content.value && typeof content.value === 'boolean' ? // empty badge should be smaller. this value was chosen as a default // after experimenting with various values parseMetricToNum(theme.button.badge.size.medium) / 2 + "px" : theme.button.badge.size.medium; // scale badge to fit its contents, leaving space horizontally // that is proportional to vertical space useLayoutEffect(function () { // when window resizes and hits a responsive breakpoint, width of the badge // can change (because pad is responsive, etc.). we want to recalculate // width since badge offset is reliant on its dimensions. var onResize = function onResize() { if (containerRef != null && containerRef.current) { containerRef.current.style.minHeight = ''; containerRef.current.style.minWidth = ''; if (contentRef != null && contentRef.current) { if (typeof content === 'number' || typeof content === 'object' && content.value) { containerRef.current.style.minHeight = defaultBadgeDimension; containerRef.current.style.minWidth = defaultBadgeDimension; var _contentRef$current$g = contentRef.current.getBoundingClientRect(), contentHeight = _contentRef$current$g.height, contentWidth = _contentRef$current$g.width; // only adjust the width if contentHeight > 0 // jest returns 0 for all getBoundingClientRect values, // so this ensures snapshots are closer to correct values if (contentHeight) { // height of content includes extra space around font from // line-height. account for this extra space with 2.5 multiplier // to add proportional horizontal space var height = defaultBadgeDimension; var width = defaultBadgeDimension; var verticalSpace = (parseMetricToNum(height) - contentHeight) * 2.5; containerRef.current.style.minHeight = height; containerRef.current.style.minWidth = Math.max(parseMetricToNum(width), Math.ceil(contentWidth + verticalSpace)) + "px"; } } else { // caller has provided custom JSX containerRef.current.style.minHeight = contentRef.current.getBoundingClientRect().width; containerRef.current.style.minWidth = contentRef.current.getBoundingClientRect().height; } } else { containerRef.current.style.minHeight = defaultBadgeDimension; containerRef.current.style.minWidth = defaultBadgeDimension; } } }; window.addEventListener('resize', onResize); onResize(); return function () { window.removeEventListener('resize', onResize); }; }, [content, defaultBadgeDimension]); // offset the badge so it overlaps content useLayoutEffect(function () { if (stackRef != null && stackRef.current) { // when badge has content, offset should be 50%. // when badge is empty, offset by a smaller amount to keep the badge // closer to the content. this value was chosen as a reasonable default // after testing with various grommet icons. var offset = typeof content === 'boolean' || content && content.value === true ? '25%' : '50%'; // second child of Stack is the div that receives absolute positioning // and contains our badge content stackRef.current.children[1].style.top = 0; stackRef.current.children[1].style.right = 0; // eslint-disable-next-line max-len stackRef.current.children[1].style.transform = "translate(" + offset + ", -" + offset + ")"; stackRef.current.children[1].style.transformOrigin = '100% 0%'; } }, [content]); var value; if (typeof content === 'number') value = content;else if (typeof content === 'object') value = content.value; var badge; if (typeof value === 'number' || typeof value === 'boolean' || typeof content === 'boolean') { if (typeof value === 'number') { var max = content.max || 9; badge = /*#__PURE__*/React.createElement(Text, { color: "text-strong", size: theme.button.badge.text.size.medium, weight: "normal", ref: contentRef }, value > max ? max + "+" : value); } badge = /*#__PURE__*/React.createElement(StyledBadgeContainer, _extends({ ref: containerRef, align: "center", background: content.background || theme.button.badge.container.background, flex: false, justify: "center", round: true, pad: !(typeof value === 'boolean' || typeof content === 'boolean') ? theme.button.badge.container.pad : undefined }, passThemeFlag), badge); // caller has provided their own JSX and we will just render that } else badge = /*#__PURE__*/React.createElement(Box, { ref: contentRef }, content); return /*#__PURE__*/React.createElement(Stack, { ref: stackRef, anchor: "top-right" }, children, badge); };